3 Commits

Author SHA1 Message Date
twotalesanimation ec125261e4 Slim the Docker image and clean up after every deploy
Deploy Vault CMS / deploy (push) Successful in 1m15s
- Build Next.js as a standalone server and copy only that into the
  runtime image, instead of the whole node_modules tree
- Mark three dynamic file reads with turbopackIgnore so the standalone
  trace does not pull in the entire project
- Deploy workflow prunes unused images and build cache older than a day
  at the start and end of every run, including failed ones, and logs
  disk usage before building

Failed builds were leaving their layers behind until the server's disk
filled, which broke the deploy, the database and Gitea sign-in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 20:43:34 +02:00
twotalesanimation a834caa511 Fix watch tracking, video unlocking and access-control gaps
Deploy Vault CMS / deploy (push) Failing after 38s
Watch progress and unlocking:
- Player samples the playhead and reports the ranges actually played,
  so skipped sections earn no credit and early plays are not lost
- Server merges ranges into compact segment rows; progress never regresses
- Single 80% unlock rule enforced server-side (lib/watch-progress.ts);
  the Unlock button and automatic unlock now agree
- Lock state and progress bar update live from each progress save
- Playlist is derived from the video, so unlocks work for shared
  playlists and for links from watch history and liked videos
- First/previous video decided by playlist position, not index numbers

Access control:
- Video API and HLS/MP4 routes require a signed-in viewer who is
  enrolled and has the video unlocked; thumbnails require sign-in
- Enrolment sync reads course codes from the allow-list, not the request
- /api/admin/meta is admin-only
- Account status, role and levels are re-checked from the database
  during a session, so deactivated students lose access within a minute

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 16:58:07 +02:00
twotalesanimation a863a5cbf3 Serve uploaded MP4s at /uploads/videos with Range support
Video records store url = /uploads/videos/<id>.mp4, but nothing served
that path: Next.js only serves static files from public/, so the
player's MP4 fallback 404'd and videos were unplayable until HLS
transcoding finished. (Thumbnails hit the same problem historically —
see scripts/update-thumbnail-urls.ts.)

Add a route handler at the exact stored URL so no DB migration is
needed. Unlike the HLS route it streams from disk via createReadStream
instead of buffering the file, and implements HTTP Range requests
(206/416, open-ended and suffix ranges) — browsers require ranges for
<video> seeking, and files can be multi-GB.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 17:41:37 +02:00