Add downloadable bootcamp session materials - #390
Merged
Merged
Conversation
Officers attach files to a bootcamp session from /admin/bootcamp; everyone enrolled in that bootcamp downloads them from /club/bootcamp. Slides and notebooks were the one thing the portal could not hand a cohort. Files live in gs://dsgt-bootcamp (BOOTCAMP_BUCKET), a second bucket rather than a prefix in the resume one: resumes are documents no member may read, handouts are files every enrolled member may, and that difference is worth a bucket boundary rather than a path check. A material hangs off the session event, not a week number, so the term comes with the row and the access check is one lookup. The gate asks for the material's own term, not the current one, so whoever bought the fall bootcamp keeps the fall notebooks in January and never sees the spring ones. A caller who may not read a file gets 404, not 403. Downloads are proxied rather than redirected to a signed URL. The bucket keeps uniform access and public access prevention enforced, a copied link is worth nothing to somebody not signed in, and there is no extra IAM role to grant. The body is streamed: 25MB times a class opening the slides at once is more than an instance has. Uploads skip tRPC, as resumes do, because superjson base64s the whole body. Kinds are an allowlist whose entry is the content type served back, so an unlisted kind has none and is refused; .html and .svg are absent because both would run script from our own origin. The object is named by row id, never by the uploaded filename. The enrolled member page is rebuilt alongside it. It previously showed a work-in-progress notice while the syllabus and workspace button rendered only for people who had not joined, so a paying member saw less than a stranger. One list now serves both, with the room and time, an attendance badge per week and the files for members. vitest.config.ts is new at the root and does one thing: alias @ to sites/mainweb, which a test needs to import a route handler.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e59d1ba. Configure here.
Merged
Contributor
Author
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
Contributor
Author
|
Visit the preview URL for this PR (updated for commit 71c27f6): https://hacklytics2027--pr-390-16mdbw62.web.app (expires Fri, 25 Sep 2026 03:17:42 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: c48ba34db61581e25fe2978355160b5eefe0e83f |
aamoghS
added a commit
that referenced
this pull request
Sep 18, 2026
Resolve the #390 overlap in favour of bootcamp_workshop. #390's routes, components and tests are removed; its table stays declared so drizzle-kit push does not stop at a DROP TABLE prompt, to be dropped separately. Club page keeps #390's syllabus with attendance badges and adds the welcome copy and weekly materials table, so enrolled members see both. Fixes from review: - Gate member reads and downloads on the workshop's own term, not currentTerm(), so a cohort keeps its material after the term rolls. - upsertSession takes a workshopId and uses that row's term and week, and the admin page calls it only when the date or room changed, so a TBA save no longer detaches the live session for that week. - Rescheduling no longer overwrites the event's title, and an omitted location leaves the event's room alone. - A workshop's week is fixed after creation; the session joins on it. - Drop the process-wide setMaxListeners(15).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Officers attach files to a bootcamp session from
/admin/bootcamp; everyone enrolled in that bootcamp downloads them from/club/bootcamp. Slides and notebooks were the one thing the portal could not hand a cohort.Who can read a file
member.bootcamp_termmatches the term of the session the file hangs off — not the current term. Somebody who bought the fall bootcamp keeps the fall notebooks in January, and never sees the spring ones.The material hangs off the session event rather than a week number, so the term comes with the row and the check is one lookup.
Storage
gs://dsgt-bootcampundersessions/<eventId>/<materialId>.<ext>, already created with uniform access and public access prevention enforced, and the App Hosting runtime service account already holdsobjectAdminon it. A second bucket rather than a prefix indsgt-resumes: resumes are documents no member may read, handouts are files every enrolled member may.Downloads are proxied, not signed. The bucket stays private, a copied link is worth nothing to somebody not signed in, and no extra IAM role is needed. The body is streamed — 25MB times a class opening the slides at once is more than an instance has.
Uploads skip tRPC, as resumes do, because superjson base64s the whole body. Kinds are an allowlist whose entry is the content type served back, so an unlisted kind has none and is refused;
.htmland.svgare absent because both would run script from our own origin. Objects are named by row id, never by the uploaded filename.Member page
Rebuilt. It previously showed a work-in-progress notice while the syllabus and workspace button rendered only for people who had not joined — a paying member saw less than a stranger. One list now serves both, with room and time, a per-week Attended / Missed / Upcoming badge, and the files for members.
Deploy
drizzle-kit pushcreatesbootcamp_materialon deploy; it is additive, so nothing manual.BOOTCAMP_BUCKETis set inapphosting.yaml. The resume-bucket comment there is corrected in passing — it saidus-central1and carried a placeholder service account; the bucket isus-east4, matching the backend's Cloud Run region, and both commands are already applied.Tests
44 new: file rules, the access gate (including the null-term trap, where a file on a non-bootcamp event against a member who bought nothing is
null === null), and the upload/download/delete handlers — 401 unsigned, 404 for a member on upload, 400 on a disallowed kind, 413 on an oversized claim before the body is read, object written before the row, 404 before the bucket is touched for an unauthorised download, attachment and nosniff headers, row-then-object on delete.Full suite green: 807 tests, 34 files, typecheck clean.
packages/db/scripts/seed-club-projects.tshas two lint errors that are pre-existing ondevand untouched here.vitest.config.tsis new at the root and does one thing: alias@tosites/mainweb, which a test needs to import a route handler.Note
Medium Risk
New authenticated file upload/download surface with term-based authorization and Cloud Storage; behavior is heavily tested but mistakes in access checks would expose handouts.
Overview
Adds bootcamp session handouts so staff attach files per session from
/admin/bootcampand enrolled members download them from/club/bootcamp, gated on the session’s bootcamp term (not the current term) with 404 for unauthorized readers.Introduces
bootcamp_materialmetadata in Postgres, bytes ings://dsgt-bootcampvia newBOOTCAMP_BUCKET(App Hosting + Turbo), and Next.js API routes (POSTupload, proxied streamingGET, staffDELETE) instead of tRPC. The bootcamp router’smyProgress/attendanceresponses now include per-session materials lists.Member bootcamp page is rebuilt: enrolled users see meeting info, syllabus with attendance badges, and downloads; the old WIP-only enrolled experience is removed. Staff get a Handouts section with upload/remove UI. Access helpers enforce the null-term trap; extensive unit tests cover file rules, gates, and route refusals. Root
vitest.config.tsadds the@alias for route-handler tests. Docs and resume-bucket comments inapphosting.yamlare updated in passing.Reviewed by Cursor Bugbot for commit 71c27f6. Bugbot is set up for automated code reviews on this repo. Configure here.