Files
picpeak/backend/migrations
Paul Nothaft 2f05fcc39d feat(gallery): reveal mode — hide gallery from guests until reveal (#838) (#856)
* feat(gallery): reveal mode — hide gallery from guests until reveal (#838)

Guests can upload during the event but see no photos until the host
reveals the gallery, manually ("Reveal now") or at a scheduled time.

- migration 165: events.reveal_mode / reveal_at / revealed_at. Effective
  visibility is computed at REQUEST time (reveal_at <= now opens the
  gate exactly on schedule); the minutely scheduler only stamps
  revealed_at durably and emits a gallery.revealed workflow trigger
- server-side enforcement in gallery.js: /photos returns the event
  shell with photos: [] + hidden_until_reveal for plain guests;
  image/download/stats endpoints 403 with GALLERY_HIDDEN (photo IDs are
  sequential — listing-only gating would be probeable); feedback-summary
  gated too. Slideshow tokens (surprise beamer), client access and the
  admin preview bypass; the guest upload route stays open
- admin: reveal toggle + optional scheduled datetime next to the guest
  upload settings, status line and "Reveal now" button on the overview;
  re-enabling the toggle clears revealed_at so a gallery can re-hide
- guest UI: upload-only view (hero, friendly message, scheduled time,
  upload button) for every layout; i18n for all 8 locales
- timestamps written as ISO strings — the SQLite driver stringifies raw
  Date objects into garbage; ISO round-trips on both engines
- 14 integration tests over minted gallery/slideshow/client/admin tokens

* fix(gallery): reveal/re-arm semantics + upload button i18n key (#838)

- "Reveal now" also clears a pending reveal_at: the schedule is
  consumed, so the full-form admin save can't accidentally re-hide a
  revealed gallery with a stale future date
- setting a FUTURE reveal_at on a revealed gallery re-arms hiding —
  the one intentional way to re-hide without double-toggling the mode
- guest upload button uses the existing upload.uploadPhotos key
  (gallery.uploadPhotos never existed; the button showed EN everywhere)

* fix(gallery): close reveal bypasses from review round 1 (#838)

- the hero-derivative route and the secure-images token-mint +
  secure-download routes are now reveal-gated: hero serves a 1920px
  derivative of ANY sequential photo id and secure tokens fetch
  originals — both were open bypasses while hidden. blockHiddenGallery
  moved to utils/revealMode.js and shared
- customer-portal tokens (via:'customer', no accessLevel) now bypass
  reveal mode — they are the host/customer, not a guest, and were
  getting the upload-only view
- an open hidden guest view refetches exactly at reveal_at plus a 60s
  fallback poll, so the gallery appears without a manual reload
- gallery.revealed added to the workflow editor's trigger picker so
  the advertised notification hook is reachable in the UI
- migration 165 guards each column independently (partial-state safe)

* fix(gallery): reveal round 2 — remaining bypass surfaces + lifecycle edges (#838)

- legacy /api/images router reveal-gated (view, secure-token + signed-url
  minting), and the signed-URL SERVE path re-checks hidden state via a
  backward-compatible bypass flag in the token payload
- secure-image tokens record revealBypass at mint and are re-validated
  at serve time — a re-hide kills in-flight guest tokens within the
  request, while slideshow/client tokens keep working
- OG metadata and the unauthenticated /og cover fall back to the brand
  logo / 404 while hidden — no hero-photo spoiler for social crawlers
- photo-feedback GET/POST reveal-gated (sequential ids were enumerable);
  /my-feedback returns the empty back-compat shape (rows leak filename +
  storage path)
- the reveal scheduler skips drafts — no premature stamp/notification
  for unpublished galleries
- emitWorkflowEvent gains an additive dedupSuffix; both reveal emitters
  pass the reveal timestamp so a re-hidden gallery's second reveal
  fires workflows again instead of deduping into silence

* fix(gallery): reveal round 3 — schedule consumption + two-way client sync (#838)

- the scheduler now consumes reveal_at when stamping (matching "Reveal
  now"), and re-arming via a partial API update clears a stale PAST
  schedule — previously {reveal_mode:true} without reveal_at could
  instantly re-open the gate through the leftover date
- /photos exposes reveal_armed so an open VISIBLE gallery keeps a 60s
  poll while the mode is on — a re-hide now propagates to open clients
  in both directions, not just hidden→visible

Codex round-3 claim about timestamp-without-timezone drift on non-UTC
Postgres was verified FALSE: knex's table.timestamp() creates
timestamptz on PG (confirmed via information_schema on a live install),
which stores absolute instants regardless of server TZ.

---------

Co-authored-by: Paul Nothaft <paul@MacStudio-von-Paul.local>
2026-07-22 20:59:59 +02:00
..

Database Migrations

This directory contains database migrations for the PicPeak photo sharing platform.

Directory Structure

/core

Essential migrations that are always run for new deployments. These include:

  • init.js - Initial database schema creation
  • Backup service tables (029-035)
  • Gallery feedback tables (033)
  • Pre-generated watermarks (061)

/legacy

Migrations needed only when upgrading from older versions. New deployments can skip these as the core schema already includes all necessary tables and columns.

For New Deployments

If you're deploying this application for the first time:

  1. The initializeDatabase() function in src/database/db.js will create all necessary tables
  2. Only migrations in the /core directory will be run
  3. This ensures a clean, optimized database schema

For Existing Deployments

If you're upgrading from an older version:

  1. All migrations (both core and legacy) will be run in sequence
  2. The migration system tracks which migrations have been applied
  3. Only new migrations will be executed

Running Migrations

# Development
npm run migrate

# Production
npm run migrate:prod

Note on Duplicate Migration Numbers

The legacy directory contains renamed duplicates:

  • 014_add_host_name_to_events_duplicate.js (was duplicate of 014)
  • 027_add_rate_limit_settings_duplicate.js (was duplicate of 027)

These have been renamed to avoid conflicts while preserving the migration history.