Move user-uploaded files from browser IndexedDB to R2/D1#165
Draft
pokle wants to merge 3 commits into
Draft
Conversation
…ser to R2/D1
Tracks now live in R2 under u/{userId}/track/{sha256}.igc.gz with metadata in
the new user_track D1 table. Tasks live in user_task as JSON. Annotations live
in user_annotation scoped to (user, track) so they're visible to anyone viewing
the track via /analysis.html?u={username}&track={track_id}.
Files are public-by-link by default; the dashboard surfaces this with a
permanent banner. There is no listing endpoint under /api/u/:username/ — files
are not discoverable. Per-user quotas: 500 tracks, 200 tasks, 200 MB total.
Account deletion via auth-api now lists + deletes every R2 object under
u/{userId}/ before removing the user row; the FK cascade then wipes the D1
metadata. A one-time client-side migration uploads any leftover IndexedDB
items on the next signed-in page load.
https://claude.ai/code/session_01ASJpfj483tfd8XjkQ5MZxX
The new user-file download responses set Content-Disposition,
X-Filename, and X-Display-Name from user-supplied filenames and
IGC pilot names. WHATWG Headers values must be ASCII, so an IGC
with a non-ASCII pilot ("François") would 500 the download.
Add asciiHeaderSafe() and an RFC 5987 filename*=UTF-8'' builder;
also cap x-filename at 255 chars.
Vite's dev proxy only forwarded /api/auth and /api/comp; the new
/api/user and /api/u/ routes 404'd under `bun run dev` because
the Pages Functions only run in production. Add proxy entries
so manual testing works end-to-end.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Preview Deployment |
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.
Summary
Replaces the browser-only IndexedDB storage for user IGC tracks, XCTSK tasks, and map annotations with server-side storage scoped per user. Account deletion now wipes the user's R2 prefix as well as their D1 rows.
u/{userId}/track/{sha256}.igc.gz, metadata in newuser_tracktable (D1)user_task.xctsk_json(no R2 — same shape as comptask.xctsk)user_annotationtable, scoped to(user, track); public-by-link readable so anyone viewing a track sees the owner's strokesu/{userId}/in R2, then deletes the user row; FK cascade handles the D1 rowsFiles are public-by-link by default — anyone with a share link can view, but there is no
/api/u/:username/tracks(or…/tasks) listing endpoint, so libraries aren't discoverable. The dashboard surfaces this with a permanent banner above the tabs.Per-user quotas: 500 tracks, 200 tasks, 200 MB total. Quota errors surface in the UI as toasts (analysis page) /
alert(dashboard).One-time client-side migration (
web/frontend/src/auth/user-files-migration.ts) uploads any leftover IndexedDB tracks/tasks the first time the user signs in after this rollout; orphaned legacy annotations are dropped with a console warning (they had no track association in the old schema).Public share-link routing
Analysis page now accepts
?u={username}&track={sha256}and?u={username}&task={code}. Storage layer flips into "public namespace" mode and resolves reads against/api/u/:username/…; writes are blocked while in public mode. Annotation layer is set readonly for non-owners.Anonymous fallback
Anonymous users keep getting in-memory analysis.
storage.isAvailable()short-circuits tofalsewhen signed out, soloadIGCFile/loadTaskskip persistence; the annotation layer goes in-memory only. Drawing still works; nothing persists.Test plan
bun run typecheck:all— all packages greenbun run --filter competition-api test— 248/248 pass (16 new user-files cases covering upload, get, list, delete, quotas, ownership, public reads, annotation cascade)bun run --filter auth-api test— 3 new delete-account tests pass; the pre-existingtwo users do not see each other's preferencestest occasionally times out at the harness default 5 s on slower runners (unrelated to this change, reproduces on baseline)bun test --cwd . ./web/engine— 406/406 pass/analysis.htmlsigned in → uploaded, dashboard lists it, URL becomes?storedTrack=<id>?u={username}&track={id}link → loads in another browser, no draw UIu/{userId}/empty, D1 rows gonehttps://claude.ai/code/session_01ASJpfj483tfd8XjkQ5MZxX
Generated by Claude Code