PUBLIC_BASE_URL was pointing to the planned pages.dev domain that
was never wired up, so QR codes encoded a non-functional URL. Use
the request's own origin as the source of truth (which also handles
custom domains automatically when added later) and only fall back
to PUBLIC_BASE_URL when an explicit override is passed via ?url=.
Also update PUBLIC_BASE_URL to the actual workers.dev hostname so
nothing else points at the wrong place.
https://claude.ai/code/session_01TPBqgcSJMppgrpiq7fLywL
GET /api/qrcode returns the QR for the upload URL of the event.
?format=png (default, 800px) for screen, ?format=svg for vector, and
?format=pdf for an A6 printable card with the couple's names, the
event date (if set), and a "Aponte a câmera" instruction. The
encoded URL defaults to the request's origin + /enviar so it works
across environments without extra config; ?url= overrides for tests.
https://claude.ai/code/session_01TPBqgcSJMppgrpiq7fLywL
Single-PUT init was inserting rows with status='approved' (skipping
the confirmation step), so a failed browser->R2 PUT left ghost
entries that the gallery happily listed. Always start as 'pending';
the existing /confirm flow is the only place that should approve.
https://claude.ai/code/session_01TPBqgcSJMppgrpiq7fLywL
Backend:
- POST /api/uploads/init validates against event_config (size, video
policy, duration), creates an uploads row, and returns either a
single presigned PUT or a multipart batch of presigned part URLs
depending on file size (50 MB threshold, 10 MB parts).
- POST /api/uploads/:id/confirm completes multipart on R2 if needed,
HEADs the object to verify upload, and flips status to approved
(post-moderation) or pending (pre-moderation).
- POST /api/uploads/:id/abort cancels in-flight multipart and removes
the row.
- GET /api/gallery returns approved uploads in cursor-paginated
reverse chronological order, with public R2 URLs.
- GET /api/stats returns lightweight counts for future home/admin use.
Frontend:
- lib/upload.ts handles single and multipart uploads via XHR with
progress callbacks, video duration extraction, and abort signals.
- /enviar: real form with file picker (image/video), author name,
message, per-file progress, multi-file support, and a success state.
- /galeria: responsive grid of approved items with lazy-loaded images,
video preview tiles, infinite "load more", and a fullscreen lightbox
showing the author and message.
Schema: turnstileToken is now optional so the MVP works without
Turnstile wired up; we layer it back in later.
https://claude.ai/code/session_01TPBqgcSJMppgrpiq7fLywL
- Drop tsconfig.node.json: composite + noEmit conflict, and the
vite.config.ts doesn't need its own project / node types.
- Inline vite.config.ts in the main tsconfig include list.
- Switch web build from `tsc -b` to `tsc --noEmit` since there are
no project references anymore.
- Make b64urlDecode return Uint8Array<ArrayBuffer> so its output
is accepted by crypto.subtle.verify under stricter lib types.
- Cast the Pages EventContext to ExecutionContext when forwarding
to Hono; props is unused at runtime.
https://claude.ai/code/session_01TPBqgcSJMppgrpiq7fLywL
Set up pnpm workspaces with three packages: a shared Zod-schema
package, a Hono-based API exported as a library, and a Vite/React/
Tailwind frontend that mounts the API via Cloudflare Pages Functions.
Storage is abstracted behind an S3-compatible provider so the project
can migrate from R2 to a self-hosted MinIO without code changes.
https://claude.ai/code/session_01TPBqgcSJMppgrpiq7fLywL