Conversation
There was a problem hiding this comment.
Pull request overview
This pull request reimplements the file upload functionality to use proper SQL integration with a new uploads table. The changes track uploaded files in the database alongside the filesystem, enabling better control over upload limits per user and per question. The implementation separates single-submission upload questions from multi-submission "dock" style upload questions where files can be added and removed dynamically.
Changes:
- Added
uploadstable with foreign key toanswerstable to track file metadata (filename, size, timestamps) - Implemented per-file, per-question, and per-user size limits with different quotas for anonymous vs authenticated users
- Refactored upload components to support both single-submission and multi-submission file upload modes
- Added file removal functionality for the multi-submission mode
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 24 comments.
Show a summary per file
| File | Description |
|---|---|
| ingest/createDb.ts | Adds uploads table schema with answerId foreign key and unique constraint on answerId+filename |
| utils/db.ts | Adds development-mode SQL error logging wrapper for better debugging |
| utils/zip.ts | Adds removeFile function to delete files from filesystem and database |
| app/api/upload-answer/route.ts | Refactors upload endpoint to handle single file uploads with size validation and database tracking |
| api/quiz.ts | Adds postFileAnswer endpoint and refactors getAnswers to retrieve file uploads from uploads table |
| api/user.ts | Updates getUser return type to explicitly allow null |
| api/book.ts | Updates getGroupId to handle null/undefined group names |
| context/QuizContextProvider.tsx | Refactors answer types to support both value and file answers, adds file management actions |
| context/UserContextProvider.tsx | Improves anonymous user creation flow with better logging |
| components/Quiz/Quiz.tsx | Splits Question component into ValueQuestion and UploadQuestion |
| components/Quiz/UploadQuestion.tsx | Updates for single-submission file upload mode |
| components/Quiz/FileDockQuestion.tsx | New component for multi-submission file upload with add/remove capabilities |
| i18n/dict.tsx | Updates translation keys for file upload messages |
| db/placeholder.txt | Removes placeholder file (no longer needed) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
32f1fe9 to
074da8a
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 16 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
074da8a to
c2c5526
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 18 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try { | ||
| const formData = await req.formData(); | ||
| const files = formData.getAll("files") as File[]; | ||
| const file = formData.get("file") as File; |
There was a problem hiding this comment.
Missing validation: the code doesn't check if file is null before using it. If the formData doesn't contain a "file" field, or if it's not a File object, this will cause a runtime error when trying to access file.size on line 35. Add a null check: if (!file) return error("no file provided");
| if (file.size > 10 * 1024 * 1024) | ||
| return error("individual file size exceeds limit"); | ||
|
|
||
| const fileName = path.basename(file.name); // sanitize filename |
There was a problem hiding this comment.
Good defensive programming: the code checks for newlines in filenames (line 39), which prevents issues with the group_concat(u.filename, "\n") delimiter used in api/quiz.ts line 199. However, this relationship between the two checks is not documented and could be easily broken if someone modifies the delimiter without updating this validation. Consider adding a comment explaining why newlines are forbidden in filenames.
| const fileName = path.basename(file.name); // sanitize filename | |
| const fileName = path.basename(file.name); // sanitize filename | |
| // Disallow newlines in filenames because elsewhere (e.g. in api/quiz.ts) | |
| // filenames are concatenated using "\n" as a delimiter; allowing newlines | |
| // would break parsing of that concatenated string. |
| import JSZip from "jszip"; | ||
| import { getBookSlug, getGroupName } from "@/api/book"; | ||
| import { getQuestionIdFromId } from "@/api/quiz"; | ||
| import {getPostAnswerData, getQuestionIdFromId} from "@/api/quiz"; |
There was a problem hiding this comment.
Inconsistent import spacing: line 7 uses {getPostAnswerData, getQuestionIdFromId} without spaces after opening brace and before closing brace, while line 6 and 8 use spaces like { getBookSlug, getGroupName } and { CONFIG }. Consider using consistent spacing.
| import {getPostAnswerData, getQuestionIdFromId} from "@/api/quiz"; | |
| import { getPostAnswerData, getQuestionIdFromId } from "@/api/quiz"; |
c2c5526 to
bd64e68
Compare
02d06fa to
841efa2
Compare
Reimplement file upload to properly use SQL.
Based on another PR (that may be accepted as a less proper implementation, but with less changes compared to master), this PR adds a table of uploads that is in better sync with files stored in the file system. It also allows for controlling the total size of files uploaded by a single user etc.