From 77ab302a9e354c618d2258bde0d7c2ca9d4d78ba Mon Sep 17 00:00:00 2001 From: akashroy8210 Date: Fri, 19 Jun 2026 11:16:04 +0530 Subject: [PATCH] Add PDF streaming viewer and token caching --- .../browse/components/file-display/index.jsx | 15 +++-- server/index.js | 1 + .../contribution/contribution.controller.js | 61 ++++++++++++++++++- .../contribution/contribution.routes.js | 2 +- .../modules/onedrive/onedrive.controller.js | 33 +++++++++- server/services/UploadFile.js | 2 + 6 files changed, 104 insertions(+), 10 deletions(-) diff --git a/client/src/screens/browse/components/file-display/index.jsx b/client/src/screens/browse/components/file-display/index.jsx index b0e0065..07492fc 100644 --- a/client/src/screens/browse/components/file-display/index.jsx +++ b/client/src/screens/browse/components/file-display/index.jsx @@ -8,6 +8,7 @@ import { getThumbnail } from "../../../../api/File"; import clientRoot from "../../../../api/server"; import capitalise from "../../../../utils/capitalise.js"; import Share from "../../../share"; +import API_BASE_URL from "../../../../api/server"; import { verifyFile, unverifyFile } from "../../../../api/File"; import { RemoveFileFromFolder, @@ -18,12 +19,14 @@ import { getFileDownloadLink } from "../../../../api/File"; import { fetchFolder } from "../../../../api/Folder.js"; const FileDisplay = ({ file, path, code, isMobileView = false }) => { + console.log(file._id); + const user = useSelector((state) => state.user?.user); const fileSize = formatFileSize(file.size); const fileType = formatFileType(file.name); const [showDialog, setShowDialog] = useState(false); const [dialogType, setDialogType] = useState("verify"); - const [onConfirmAction, setOnConfirmAction] = useState(() => () => {}); + const [onConfirmAction, setOnConfirmAction] = useState(() => () => { }); const [isProcessing, setIsProcessing] = useState(false); let name = file.name; @@ -96,7 +99,10 @@ const FileDisplay = ({ file, path, code, isMobileView = false }) => { toast.error("Please login to preview file."); return; } - window.open(preview_url, "_blank"); + window.open( + `${API_BASE_URL}/api/contribution/view/${file._id}`, + "_blank" + ); }; @@ -144,9 +150,8 @@ const FileDisplay = ({ file, path, code, isMobileView = false }) => { return (
{ logger.error( { message: err.message, + microsoftResponse: err.response?.data, route: req.originalUrl, method: req.method, }, diff --git a/server/modules/contribution/contribution.controller.js b/server/modules/contribution/contribution.controller.js index c607f19..b73af46 100644 --- a/server/modules/contribution/contribution.controller.js +++ b/server/modules/contribution/contribution.controller.js @@ -4,9 +4,11 @@ import AppError from "../../utils/appError.js"; import validatePayload from "../../utils/validate.js"; import UploadFile from "../../services/UploadFile.js"; import fs from "fs"; -import { FolderModel } from "../course/course.model.js"; +import { FolderModel, FileModel } from "../course/course.model.js"; import logger from "../../utils/logger.js"; import { normalizeCourseCode, getCourseCodeCaseInsensitiveRegex } from "../../utils/course.js"; +import { getAccessToken } from "../onedrive/onedrive.controller.js"; +import axios from "axios"; async function ContributionCreation(contributionId, data) { const existingContribution = await Contribution.findOne({ contributionId }); @@ -43,6 +45,7 @@ async function HandleFileToDB(contributionId, fileId) { async function GetAllContributions(req, res, next) { const allContributions = await Contribution.find({}); + console.log(allContributions); res.json(allContributions); } @@ -50,7 +53,7 @@ async function HandleFileUpload(req, res, next) { logger.info("Handling File Upload"); const contributionId = req.headers["contribution-id"]; const files = req.files; - + console.log(files.length) if (!files || files.length === 0) { return res.status(400).json({ error: "No files were uploaded" }); } @@ -157,6 +160,57 @@ async function GetBrContribution(req, res, next) { } } +async function viewFile(req, res, next) { + try { + const { id } = req.params; + + console.time("file is fetched") + + const file = await FileModel.findById(id); + + console.timeEnd("file is fetched") + + if (!file) { + console.log("file not found") + return res.status(404).json({ message: "File not found" }); + } + + console.time("access token fetched") + + const accessToken = await getAccessToken(); + if (!accessToken) { + return res.status(500).json({ message: "Access token not found" }); + } + + console.timeEnd("access token fetched") + + console.time("graph request") + const response = await axios.get(`https://graph.microsoft.com/v1.0/me/drive/items/${file.fileId}/content`, { + headers: { + Authorization: ` Bearer ${accessToken}` + }, + responseType: "stream" + }) + if (!response) { + return res.status(500).json({ message: "File not found" }); + } + console.timeEnd("graph request") + + console.time("res sent to client") + res.setHeader("Content-Type", "application/pdf") + console.timeEnd("res sent to client") + + console.time("stream finish") + response.data.on("end", () => { + console.timeEnd("stream finish"); + }); + response.data.pipe(res) + + } catch (error) { + next(error); + } +} + export default { GetAllContributions, CreateNewContribution, @@ -164,5 +218,6 @@ export default { GetMyContributions, DeleteContribution, GetContributionsUpdatedSince, - GetBrContribution + GetBrContribution, + viewFile }; diff --git a/server/modules/contribution/contribution.routes.js b/server/modules/contribution/contribution.routes.js index 45c3f96..5caa4ab 100644 --- a/server/modules/contribution/contribution.routes.js +++ b/server/modules/contribution/contribution.routes.js @@ -14,5 +14,5 @@ router.post("/upload", upload.array("file"), catchAsync(ContributionController.H // router.get("/:id", catchAsync(ContributionController.CreateNewContribution)); router.post("/updated", catchAsync(ContributionController.GetContributionsUpdatedSince)); router.post("/br",isAuthenticated, ContributionController.GetBrContribution) - +router.get('/view/:id',catchAsync(ContributionController.viewFile)) export default router; diff --git a/server/modules/onedrive/onedrive.controller.js b/server/modules/onedrive/onedrive.controller.js index 4479f15..5e7b99c 100644 --- a/server/modules/onedrive/onedrive.controller.js +++ b/server/modules/onedrive/onedrive.controller.js @@ -319,14 +319,45 @@ async function visitFile(file, currCourse) { return NewFile._id; } +// export async function getAccessToken() { +// let data; +// if (fs.existsSync("./onedrive-refresh-token.token")) { +// data = await refreshAccessToken(); +// } else { +// data = await generateAccessToken(); +// } +// return data.access_token; +// } +let cachedAccessToken = null; +let tokenExpiry = 0; + export async function getAccessToken() { + + if ( + cachedAccessToken && + Date.now() < tokenExpiry + ) { + console.log("Using cached token"); + return cachedAccessToken; + } + + console.log("Fetching fresh token"); + let data; + if (fs.existsSync("./onedrive-refresh-token.token")) { data = await refreshAccessToken(); } else { data = await generateAccessToken(); } - return data.access_token; + + cachedAccessToken = data.access_token; + + tokenExpiry = + Date.now() + + (data.expires_in - 60) * 1000; + + return cachedAccessToken; } async function refreshAccessToken() { diff --git a/server/services/UploadFile.js b/server/services/UploadFile.js index 107fa9d..8c40966 100644 --- a/server/services/UploadFile.js +++ b/server/services/UploadFile.js @@ -76,6 +76,8 @@ async function createUploadSession(folderId, fileName) { async function UploadFile(contributionId, filePath, fileName) { const folderId = parent_item_id; + console.log("Folder ID:", folderId); + console.log("ONEDRIVE_FOLDER_ID", process.env.ONEDRIVE_FOLDER_ID); const session = await createUploadSession(folderId, fileName); if (!session?.url) { logger.error("Error uploading!");