Gallery approval
The photographer uploads photographs to a private gallery, publishes the gallery, and then approves customer access. Those are different actions. A customer can view or download a photograph only when the gallery is published and access is approved.
Publication can be PUBLISHED while access is still PENDING_APPROVAL. That gallery is private.
Sequence
Authorization check
Every view and every download runs this check in Express. Next.js does not sign URLs.
| Check | Required value |
|---|---|
| Session | Authenticated customer, from the access token. |
| Ownership | galleries.customer_id equals the access token subject. |
| Publication | PUBLISHED. |
| Access | APPROVED. |
| Photo | READY and belongs to that gallery. |
Failure returns no bytes and no URL. An administrator preview uses an admin route, requires MFA, and writes an audit row. It does not reuse the customer route with a skipped check.
Approval history
galleries.access_status is the value the check reads. Each decision also inserts gallery_access_reviews:
| Decision | Access status | Review row |
|---|---|---|
| Approve | APPROVED | APPROVED, reviewer, optional reason, timestamp. |
| Reject before approval | REVOKED | REJECTED. The customer still cannot download. |
| Revoke after approval | REVOKED | REVOKED. |
Review rows are append-only. The latest decision is also copied onto galleries.approved_by and galleries.approved_at when the status becomes APPROVED. Clearing approval on revoke does not delete the review history. approved_by may remain as the last approver; access_status is what authorization uses. A revoke sets access_status to REVOKED even if approved_by is still populated. The check requires APPROVED, so a leftover approver id does not grant access.
Signed URLs
The API signs a URL only after the check passes. The lifetime is 5 minutes. Revocation stops new signatures. A URL that was issued earlier remains usable until it expires. That window is accepted so the storage service does not need a revocation list. The short lifetime is the control.
Originals and thumbnails stay in a private bucket. The database stores storage_key and thumbnail_key.
Uploads
The API accepts JPEG, PNG, and WebP after checking the file signature, not only the extension. The initial size limit is 50 MB per original. A passing file is stored as PROCESSING. The worker writes the thumbnail and sets READY. Customers cannot request a photo that is not READY.
Ratings
The gallery owner can rate a ready photo once, with an integer from 1 to 5 and optional feedback. The unique key is (photo_id, customer_id). The insert queues a notification to the photographer. Ratings are refused when access is not approved.