Skip to content

Add downloadable bootcamp session materials - #390

Merged
aamoghS merged 2 commits into
devfrom
feat/bootcamp-materials
Sep 18, 2026
Merged

aamoghS merged 2 commits into
devfrom
feat/bootcamp-materials

Conversation

@github-actions

@github-actions github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

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

  • Staff, always.
  • Anyone whose member.bootcamp_term matches 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.
  • Everyone else gets 404, not 403.

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-bootcamp under sessions/<eventId>/<materialId>.<ext>, already created with uniform access and public access prevention enforced, and the App Hosting runtime service account already holds objectAdmin on it. A second bucket rather than a prefix in dsgt-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; .html and .svg are 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 push creates bootcamp_material on deploy; it is additive, so nothing manual. BOOTCAMP_BUCKET is set in apphosting.yaml. The resume-bucket comment there is corrected in passing — it said us-central1 and carried a placeholder service account; the bucket is us-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.ts has two lint errors that are pre-existing on dev and untouched here.

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.


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/bootcamp and 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_material metadata in Postgres, bytes in gs://dsgt-bootcamp via new BOOTCAMP_BUCKET (App Hosting + Turbo), and Next.js API routes (POST upload, proxied streaming GET, staff DELETE) instead of tRPC. The bootcamp router’s myProgress / attendance responses 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.ts adds the @ alias for route-handler tests. Docs and resume-bucket comments in apphosting.yaml are updated in passing.

Reviewed by Cursor Bugbot for commit 71c27f6. Bugbot is set up for automated code reviews on this repo. Configure here.

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.
@github-actions
github-actions Bot requested a review from aamoghS as a code owner September 18, 2026 03:02
@aamoghS aamoghS changed the title Feature: feat/bootcamp-materials to dev Add downloadable bootcamp session materials Sep 18, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread sites/mainweb/app/(portal)/api/bootcamp/materials/route.ts
@aamoghS aamoghS mentioned this pull request Sep 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor Author

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown
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
aamoghS merged commit 37acbcd into dev Sep 18, 2026
11 checks passed
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant