-
Notifications
You must be signed in to change notification settings - Fork 3
Add PDF streaming viewer and token caching #174
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical Bug: This redirects all file types through the new streaming endpoint, but the backend currently hardcodes the response to PDF. This will break the preview for images, |
||
| `${API_BASE_URL}/api/contribution/view/${file._id}`, | ||
| "_blank" | ||
| ); | ||
| }; | ||
|
|
||
|
|
||
|
|
@@ -144,9 +150,8 @@ const FileDisplay = ({ file, path, code, isMobileView = false }) => { | |
|
|
||
| return ( | ||
| <div | ||
| className={`file-display ${ | ||
| user?.isBR ? (file.isVerified ? "verified" : "unverified") : "" | ||
| }`} | ||
| className={`file-display ${user?.isBR ? (file.isVerified ? "verified" : "unverified") : "" | ||
| }`} | ||
| > | ||
| <img | ||
| src={thumbnailUrl} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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,14 +45,15 @@ async function HandleFileToDB(contributionId, fileId) { | |
|
|
||
| async function GetAllContributions(req, res, next) { | ||
| const allContributions = await Contribution.find({}); | ||
| console.log(allContributions); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please remove these leftover debug logs |
||
| res.json(allContributions); | ||
| } | ||
|
|
||
| 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) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please remove these leftover debug logs to keep the production logs clean |
||
| if (!files || files.length === 0) { | ||
| return res.status(400).json({ error: "No files were uploaded" }); | ||
| } | ||
|
|
@@ -157,12 +160,64 @@ async function GetBrContribution(req, res, next) { | |
| } | ||
| } | ||
|
|
||
| async function viewFile(req, res, next) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are about 8 leftover |
||
| 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") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical Bug: Because coursehub accepts all file types (images, word documents, etc.), hardcoding the Content-Type to |
||
| 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, | ||
| HandleFileUpload, | ||
| GetMyContributions, | ||
| DeleteContribution, | ||
| GetContributionsUpdatedSince, | ||
| GetBrContribution | ||
| GetBrContribution, | ||
| viewFile | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please remove these debug logs to keep the production console clean. |
||
| return cachedAccessToken; | ||
| } | ||
|
|
||
| console.log("Fetching fresh token"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please remove these debug logs to keep the production console clean. |
||
|
|
||
| let data; | ||
|
|
||
| if (fs.existsSync("./onedrive-refresh-token.token")) { | ||
| data = await refreshAccessToken(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optimization: There is a slight race condition here. If a large class of students all try to view a PDF at the exact moment the token expires, the server will concurrently execute |
||
| } 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() { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please remove this leftover console logs on line 79 and 80 |
||
| console.log("ONEDRIVE_FOLDER_ID", process.env.ONEDRIVE_FOLDER_ID); | ||
| const session = await createUploadSession(folderId, fileName); | ||
| if (!session?.url) { | ||
| logger.error("Error uploading!"); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove these leftover debug logs.