# Milestone 06 — MinIO Photo Uploads ## Reference Read `plans/wedding-bootstrap-plan.md` first. This milestone depends on Milestones 01, 03, and 04. ## Goal Add one-photo-per-invitation upload support using MinIO through S3-compatible presigned URLs. ## Scope Implement only: - S3/MinIO client. - Presigned upload endpoint. - Upload completion endpoint. - Guest-facing photo upload UI. - One-current-photo-per-invitation behavior. - Replacement until deadline. - Admin signed preview/download URL. - Upload validation. ## Non-goals Do not implement: - Multiple photos per invitation. - Public unauthenticated photo browsing. - Image processing/resizing unless explicitly requested later. - Complex gallery UI. - Another storage provider. ## Required constraints - Use MinIO via S3-compatible AWS SDK APIs. - One image per invitation/group. - Replacing the image is allowed until `RSVP_DEADLINE`. - After the deadline, guest upload is read-only/disabled. - Validate invitation token before presigning upload. - Admin UI must use shadcn/ui components only. - Guest-facing copy must use `invitations.language`. ## Implementation tasks - Add `lib/storage/s3.ts`. - Add upload helper functions in `lib/storage/uploads.ts`. - Add validation schema for presign/complete requests. - Add `app/api/uploads/presign/route.ts`. - Add `app/api/uploads/complete/route.ts`. - Generate object keys like: ```txt invitations/{invitationId}/{uuid}-{safeFilename} ``` - Upsert `photo_uploads` metadata. - Optionally delete old object when replacing. - Add guest upload component/section. - Add localized upload copy. - Add admin preview/download via short-lived signed GET URL. ## Validation rules - Allowed MIME types: - `image/jpeg` - `image/png` - `image/webp` - Enforce a maximum file size. - Require valid invitation token. - Require deadline not passed for guest upload/replace. ## Verification Run: ```bash docker compose up -d postgres minio minio-init npm run lint npm run build ``` Manual checks: - Guest can upload one valid image. - Admin can preview/download the image. - Guest can replace image before deadline. - Metadata in `photo_uploads` is updated on replace. - Invalid MIME type is rejected. - Oversized file is rejected. - Invalid token cannot presign upload. - After deadline, upload/replace is disabled. - Admin pages use shadcn/ui only. ## Acceptance criteria - MinIO upload flow works end-to-end. - Exactly one current photo is associated with each invitation. - Uploads are secured by invitation token and deadline. - Admin can access uploaded photo previews/downloads.