Conversation
🚧 header and footer base versions
🚧 info-page added, minor fixes
🚧 added fake masonry gallery, api route, mock pictures
🚧 modal picture component in early stage
* 🚧 passed picture props, push escape to close modal * 🚧 modal component layout update, proper placement * 🚧 modal responsible, layout fixes
- Remove agents.md from .gitignore so it stays tracked - Add typecheck and prisma convenience scripts to package.json - Add ESLint override to allow process.env in lib/env/** - Create lib/env/server.ts with zod validation for all server env vars Co-authored-by: Cursor <cursoragent@cursor.com>
- Install prisma 7, @prisma/adapter-pg, next-auth, zod, aws-sdk, exifr, bcryptjs - Create prisma schema with Photo model (EXIF fields, soft-delete, indexes) and NextAuth models (User, Account, Session, VerificationToken) - Create prisma.config.ts for Prisma 7 migration adapter - Create lib/db/prisma.ts singleton with PrismaPg adapter - Add .env.example with all required server environment variables - Extend ESLint env override to cover lib/db/** and prisma/** - Add prisma/generated/ to .gitignore, allow .env.example Co-authored-by: Cursor <cursoragent@cursor.com>
- Configure NextAuth v5 (beta) with Credentials provider for single-user admin - Auth validates against ADMIN_USERNAME / ADMIN_PASSWORD_HASH from env - Use JWT session strategy (no DB sessions needed for single-user) - Add middleware to protect /admin/** routes with login redirect - Create lib/auth/require-auth.ts helper for write API route protection - Add /admin/login page with credentials form - Add /admin dashboard placeholder (session-gated) - Add /api/auth/[...nextauth] catch-all route handler Co-authored-by: Cursor <cursoragent@cursor.com>
- Create S3-compatible client for DigitalOcean Spaces (lib/storage/client.ts) - Add signed PUT URL generation with immutable UUID-based keys - Keys follow pattern: original/<uuid>.<ext>, preview/<uuid>.webp, thumb/<uuid>.webp - Originals are private ACL, preview/thumb are public-read - Update next.config.ts with remotePatterns for Spaces CDN domains Co-authored-by: Cursor <cursoragent@cursor.com>
- POST /api/uploads/sign: generates signed PUT URLs for original/preview/thumb variants with immutable UUID-based keys (auth-gated) - POST /api/photos/commit: persists photo metadata and EXIF fields to DB after successful upload (auth-gated, GPS intentionally excluded) - Add zod schemas in lib/validations/ for upload and photo inputs - No storage secrets exposed in API responses Co-authored-by: Cursor <cursoragent@cursor.com>
- Create lib/client/extract-exif.ts: extracts safe EXIF fields via exifr (GPS explicitly disabled per policy) - Create lib/client/image-resize.ts: generates preview (~1600px) and thumb (~400px) WebP variants in the browser using OffscreenCanvas - Add /admin/upload page with full upload flow: select files -> extract EXIF -> resize -> request signed URLs -> PUT to storage -> commit metadata to DB - Supports batch upload with per-file status indicators Co-authored-by: Cursor <cursoragent@cursor.com>
- Rewrite /api/pictures to query Postgres via Prisma (published + not soft-deleted, cursor-based pagination by ID) - Build public image URLs from STORAGE_PUBLIC_BASE_URL + key - Update gallery page: string cursor, extended Picture type with metadata - Update modal-pic to display preview URL, title, description, and EXIF info (camera, lens, focal length, aperture, shutter, ISO) - Remove lib/mock-pics.ts (no longer needed) Co-authored-by: Cursor <cursoragent@cursor.com>
- GET /api/photos: admin-only list with filter (all/published/draft/deleted) - PATCH /api/photos/:id: update metadata and publish state (zod-validated) - POST /api/photos/:id/delete: soft-delete (sets deletedAt timestamp) - POST /api/photos/:id/restore: restore soft-deleted photo (clears deletedAt) - Add /admin/photos page with grid view, status badges, hover actions (publish/unpublish, delete, restore), and filter tabs - Public gallery already filters by isPublished=true and deletedAt=null Co-authored-by: Cursor <cursoragent@cursor.com>
- Rewrite README with setup instructions, env vars, scripts, CORS config, architecture overview, and deployment checklist - Add decision log to agents.md documenting all MVP architectural choices: Prisma 7 adapter, NextAuth Credentials, ESLint env policy, browser-side image processing, EXIF GPS exclusion, cursor pagination, and Spaces config Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aed6285807
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // 4. Upload files directly to storage | ||
| await Promise.all([ | ||
| fetch(originalItem.url, { method: "PUT", body: entry.file, headers: { "Content-Type": entry.file.type } }), | ||
| fetch(previewItem.url, { method: "PUT", body: preview, headers: { "Content-Type": "image/webp" } }), | ||
| fetch(thumbItem.url, { method: "PUT", body: thumb, headers: { "Content-Type": "image/webp" } }), | ||
| ]); |
There was a problem hiding this comment.
Verify PUT uploads before committing metadata
The three fetch(..., { method: "PUT" }) calls are awaited via Promise.all, but their HTTP statuses are never checked. fetch resolves on 403/4xx/5xx, so a failed upload (expired signed URL, CORS rejection, storage outage) will still proceed to the commit request and create DB rows that point to missing objects, breaking the gallery until manually cleaned up. Please verify each response is ok (or throw on failure) before calling /api/photos/commit.
Useful? React with 👍 / 👎.
| orderBy: { createdAt: "desc" }, | ||
| take: PAGE_SIZE + 1, // fetch one extra to check if there's a next page | ||
| ...(cursorParam | ||
| ? { | ||
| cursor: { id: cursorParam }, | ||
| skip: 1, // skip the cursor item itself |
There was a problem hiding this comment.
Use a stable cursor order for pagination
Pagination uses cursor: { id: cursorParam } while ordering by createdAt only. When multiple photos share the same createdAt (e.g., batch uploads), the ordering is non-deterministic and the cursor’s position can shift between pages, causing duplicates or missed items. A stable order (e.g., orderBy: [{ createdAt: "desc" }, { id: "desc" }] with a matching cursor/tie-breaker) avoids this.
Useful? React with 👍 / 👎.
No description provided.