* 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 <[email protected]>
This commit is contained in:
co-authored by
Paul Nothaft
parent
3d6c9848dc
commit
2f05fcc39d
@@ -813,6 +813,10 @@
|
||||
}
|
||||
},
|
||||
"gallery": {
|
||||
"revealPendingTitle": "Die Fotos sind noch eine Überraschung",
|
||||
"revealPendingMessage": "Der Gastgeber gibt die Galerie später frei — schauen Sie bald wieder vorbei!",
|
||||
"revealScheduledFor": "Freigabe geplant für {{date}}",
|
||||
"revealUploadHint": "Sie können schon jetzt eigene Fotos zur Sammlung beitragen:",
|
||||
"expires": "Läuft ab",
|
||||
"expired": "Abgelaufen",
|
||||
"contactOrganizer": "Bitte kontaktieren Sie den Veranstalter, wenn Sie Zugriff auf diese Fotos benötigen.",
|
||||
@@ -964,6 +968,17 @@
|
||||
"failedToToggleDownloads": "Aktualisierung der Download-Berechtigung fehlgeschlagen"
|
||||
},
|
||||
"events": {
|
||||
"revealMode": "Reveal-Modus (Galerie bis zur Freigabe verbergen)",
|
||||
"revealModeHelp": "Gäste können hochladen, sehen aber keine Fotos, bis Sie die Galerie freigeben — manuell oder zum geplanten Zeitpunkt. Diashow und Kundenzugang funktionieren weiter.",
|
||||
"revealAt": "Geplante Freigabe (optional)",
|
||||
"revealAtHelp": "Leer lassen, um manuell mit „Jetzt freigeben\" freizugeben.",
|
||||
"revealModeStatus": "Reveal-Modus",
|
||||
"revealed": "Freigegeben",
|
||||
"hiddenUntilReveal": "Für Gäste verborgen",
|
||||
"revealScheduled": "Geplant: {{date}}",
|
||||
"revealNow": "Jetzt freigeben",
|
||||
"revealedToast": "Galerie freigegeben — Gäste sehen die Fotos jetzt",
|
||||
"revealError": "Galerie konnte nicht freigegeben werden",
|
||||
"totalPhotos": "Gesamtfotos",
|
||||
"totalViews": "Gesamtaufrufe",
|
||||
"totalDownloads": "Gesamte Downloads",
|
||||
|
||||
@@ -360,6 +360,10 @@
|
||||
}
|
||||
},
|
||||
"gallery": {
|
||||
"revealPendingTitle": "The photos are still a surprise",
|
||||
"revealPendingMessage": "The host will reveal the gallery later — check back soon!",
|
||||
"revealScheduledFor": "Reveal scheduled for {{date}}",
|
||||
"revealUploadHint": "You can already add your own photos to the collection:",
|
||||
"expires": "Expires",
|
||||
"expired": "Expired",
|
||||
"contactOrganizer": "Please contact the event organizer if you need access to these photos.",
|
||||
@@ -511,6 +515,17 @@
|
||||
"failedToToggleDownloads": "Failed to update download permission"
|
||||
},
|
||||
"events": {
|
||||
"revealMode": "Reveal mode (hide gallery until reveal)",
|
||||
"revealModeHelp": "Guests can upload but see no photos until you reveal the gallery — manually or at the scheduled time. Slideshow and client access keep working.",
|
||||
"revealAt": "Scheduled reveal (optional)",
|
||||
"revealAtHelp": "Leave empty to reveal manually with the \"Reveal now\" button.",
|
||||
"revealModeStatus": "Reveal mode",
|
||||
"revealed": "Revealed",
|
||||
"hiddenUntilReveal": "Hidden from guests",
|
||||
"revealScheduled": "Scheduled: {{date}}",
|
||||
"revealNow": "Reveal now",
|
||||
"revealedToast": "Gallery revealed — guests can see the photos now",
|
||||
"revealError": "Failed to reveal the gallery",
|
||||
"title": "Events",
|
||||
"create": "Create",
|
||||
"createEvent": "Create Event",
|
||||
|
||||
@@ -223,6 +223,10 @@
|
||||
"passwordHint": "La contraseña la proporcionó el organizador del evento. Contacta con ellos si no la tienes."
|
||||
},
|
||||
"gallery": {
|
||||
"revealPendingTitle": "Las fotos siguen siendo una sorpresa",
|
||||
"revealPendingMessage": "El anfitrión revelará la galería más tarde — ¡vuelve pronto!",
|
||||
"revealScheduledFor": "Revelación programada para {{date}}",
|
||||
"revealUploadHint": "Ya puedes añadir tus propias fotos a la colección:",
|
||||
"title": "Galería de fotos",
|
||||
"welcomeMessage": "Mensaje de bienvenida",
|
||||
"expiresOn": "Expira el",
|
||||
@@ -342,6 +346,17 @@
|
||||
"categoryHeroHint": "Si no se establece una foto de portada para una categoría, se usará la foto hero por defecto."
|
||||
},
|
||||
"events": {
|
||||
"revealMode": "Modo revelación (ocultar la galería hasta revelarla)",
|
||||
"revealModeHelp": "Los invitados pueden subir fotos pero no verlas hasta que reveles la galería — manualmente o a la hora programada. La presentación y el acceso de clientes siguen funcionando.",
|
||||
"revealAt": "Revelación programada (opcional)",
|
||||
"revealAtHelp": "Déjalo vacío para revelar manualmente con el botón \"Revelar ahora\".",
|
||||
"revealModeStatus": "Modo revelación",
|
||||
"revealed": "Revelada",
|
||||
"hiddenUntilReveal": "Oculta para los invitados",
|
||||
"revealScheduled": "Programada: {{date}}",
|
||||
"revealNow": "Revelar ahora",
|
||||
"revealedToast": "Galería revelada — los invitados ya pueden ver las fotos",
|
||||
"revealError": "No se pudo revelar la galería",
|
||||
"title": "Eventos",
|
||||
"create": "Crear",
|
||||
"createEvent": "Crear evento",
|
||||
|
||||
@@ -221,6 +221,10 @@
|
||||
"passwordHint": "Le mot de passe a été fourni par l'organisateur de l'événement. Contactez-le si vous ne l'avez pas."
|
||||
},
|
||||
"gallery": {
|
||||
"revealPendingTitle": "Les photos sont encore une surprise",
|
||||
"revealPendingMessage": "L'hôte dévoilera la galerie plus tard — revenez bientôt !",
|
||||
"revealScheduledFor": "Révélation prévue le {{date}}",
|
||||
"revealUploadHint": "Vous pouvez déjà ajouter vos propres photos à la collection :",
|
||||
"expires": "Expire",
|
||||
"expired": "Expiré",
|
||||
"contactOrganizer": "Veuillez contacter l'organisateur de l'événement si vous avez besoin d'accéder à ces photos.",
|
||||
@@ -358,6 +362,17 @@
|
||||
"categoryHeroHint": "Si aucune photo de couverture n'est définie pour une catégorie, la photo par défaut sera utilisée."
|
||||
},
|
||||
"events": {
|
||||
"revealMode": "Mode révélation (masquer la galerie jusqu'à la révélation)",
|
||||
"revealModeHelp": "Les invités peuvent téléverser mais ne voient aucune photo tant que vous ne révélez pas la galerie — manuellement ou à l'heure programmée. Le diaporama et l'accès client continuent de fonctionner.",
|
||||
"revealAt": "Révélation programmée (optionnel)",
|
||||
"revealAtHelp": "Laissez vide pour révéler manuellement avec le bouton « Révéler maintenant ».",
|
||||
"revealModeStatus": "Mode révélation",
|
||||
"revealed": "Révélée",
|
||||
"hiddenUntilReveal": "Masquée pour les invités",
|
||||
"revealScheduled": "Programmée : {{date}}",
|
||||
"revealNow": "Révéler maintenant",
|
||||
"revealedToast": "Galerie révélée — les invités peuvent maintenant voir les photos",
|
||||
"revealError": "Impossible de révéler la galerie",
|
||||
"title": "Événements",
|
||||
"create": "Créer",
|
||||
"createEvent": "Créer un événement",
|
||||
|
||||
@@ -221,6 +221,10 @@
|
||||
"passwordHint": "Het wachtwoord is verstrekt door de organisator van het evenement. Neem contact op als u het niet heeft."
|
||||
},
|
||||
"gallery": {
|
||||
"revealPendingTitle": "De foto's zijn nog een verrassing",
|
||||
"revealPendingMessage": "De gastheer onthult de galerij later — kom snel terug!",
|
||||
"revealScheduledFor": "Onthulling gepland voor {{date}}",
|
||||
"revealUploadHint": "Je kunt nu al je eigen foto's aan de collectie toevoegen:",
|
||||
"expires": "Verloopt",
|
||||
"expired": "Verlopen",
|
||||
"contactOrganizer": "Neem contact op met de organisator als u toegang tot deze foto's nodig heeft.",
|
||||
@@ -358,6 +362,17 @@
|
||||
"categoryHeroHint": "Als er geen omslagfoto is ingesteld voor een categorie, wordt de standaard hero-foto gebruikt."
|
||||
},
|
||||
"events": {
|
||||
"revealMode": "Onthullingsmodus (galerij verbergen tot de onthulling)",
|
||||
"revealModeHelp": "Gasten kunnen uploaden maar zien geen foto's totdat je de galerij onthult — handmatig of op het geplande tijdstip. Diavoorstelling en klanttoegang blijven werken.",
|
||||
"revealAt": "Geplande onthulling (optioneel)",
|
||||
"revealAtHelp": "Laat leeg om handmatig te onthullen met de knop \"Nu onthullen\".",
|
||||
"revealModeStatus": "Onthullingsmodus",
|
||||
"revealed": "Onthuld",
|
||||
"hiddenUntilReveal": "Verborgen voor gasten",
|
||||
"revealScheduled": "Gepland: {{date}}",
|
||||
"revealNow": "Nu onthullen",
|
||||
"revealedToast": "Galerij onthuld — gasten kunnen de foto's nu zien",
|
||||
"revealError": "Galerij onthullen mislukt",
|
||||
"title": "Evenementen",
|
||||
"create": "Aanmaken",
|
||||
"createEvent": "Evenement aanmaken",
|
||||
|
||||
@@ -224,6 +224,10 @@
|
||||
"passwordHint": "A senha foi fornecida pelo organizador do evento. Entre em contato se não a tiver."
|
||||
},
|
||||
"gallery": {
|
||||
"revealPendingTitle": "As fotos ainda são uma surpresa",
|
||||
"revealPendingMessage": "O anfitrião revelará a galeria mais tarde — volte em breve!",
|
||||
"revealScheduledFor": "Revelação agendada para {{date}}",
|
||||
"revealUploadHint": "Você já pode adicionar suas próprias fotos à coleção:",
|
||||
"expires": "Expira",
|
||||
"expired": "Expirada",
|
||||
"contactOrganizer": "Entre em contato com o organizador do evento se precisar de acesso a estas fotos",
|
||||
@@ -366,6 +370,17 @@
|
||||
"categoryHeroHint": "Se nenhuma foto de capa for definida, a foto de destaque padrão será usada."
|
||||
},
|
||||
"events": {
|
||||
"revealMode": "Modo revelação (ocultar a galeria até a revelação)",
|
||||
"revealModeHelp": "Os convidados podem enviar fotos, mas não as veem até você revelar a galeria — manualmente ou no horário agendado. A apresentação de slides e o acesso do cliente continuam funcionando.",
|
||||
"revealAt": "Revelação agendada (opcional)",
|
||||
"revealAtHelp": "Deixe vazio para revelar manualmente com o botão \"Revelar agora\".",
|
||||
"revealModeStatus": "Modo revelação",
|
||||
"revealed": "Revelada",
|
||||
"hiddenUntilReveal": "Oculta para os convidados",
|
||||
"revealScheduled": "Agendada: {{date}}",
|
||||
"revealNow": "Revelar agora",
|
||||
"revealedToast": "Galeria revelada — os convidados já podem ver as fotos",
|
||||
"revealError": "Falha ao revelar a galeria",
|
||||
"title": "Eventos",
|
||||
"create": "Criar",
|
||||
"createEvent": "Criar Evento",
|
||||
|
||||
@@ -227,6 +227,10 @@
|
||||
"passwordHint": "Пароль предоставлен организатором события. Свяжитесь с ним, если у вас его нет."
|
||||
},
|
||||
"gallery": {
|
||||
"revealPendingTitle": "Фотографии пока остаются сюрпризом",
|
||||
"revealPendingMessage": "Организатор откроет галерею позже — загляните ещё раз!",
|
||||
"revealScheduledFor": "Открытие запланировано на {{date}}",
|
||||
"revealUploadHint": "Вы уже можете добавить свои фотографии в коллекцию:",
|
||||
"expires": "Истекает",
|
||||
"expired": "Истекла",
|
||||
"contactOrganizer": "Свяжитесь с организатором события, если вам нужен доступ к этим фотографиям",
|
||||
@@ -374,6 +378,17 @@
|
||||
"categoryHeroHint": "Если обложка для категории не задана, будет использоваться стандартное главное фото."
|
||||
},
|
||||
"events": {
|
||||
"revealMode": "Режим сюрприза (скрыть галерею до открытия)",
|
||||
"revealModeHelp": "Гости могут загружать фото, но не видят их, пока вы не откроете галерею — вручную или в запланированное время. Слайд-шоу и клиентский доступ продолжают работать.",
|
||||
"revealAt": "Запланированное открытие (необязательно)",
|
||||
"revealAtHelp": "Оставьте пустым, чтобы открыть вручную кнопкой «Открыть сейчас».",
|
||||
"revealModeStatus": "Режим сюрприза",
|
||||
"revealed": "Открыта",
|
||||
"hiddenUntilReveal": "Скрыта от гостей",
|
||||
"revealScheduled": "Запланировано: {{date}}",
|
||||
"revealNow": "Открыть сейчас",
|
||||
"revealedToast": "Галерея открыта — гости теперь видят фотографии",
|
||||
"revealError": "Не удалось открыть галерею",
|
||||
"title": "События",
|
||||
"create": "Создать",
|
||||
"createEvent": "Создать событие",
|
||||
|
||||
@@ -221,6 +221,10 @@
|
||||
"passwordHint": "Geslo vam je posredoval organizator dogodka. Če ga nimate, se obrnite nanj."
|
||||
},
|
||||
"gallery": {
|
||||
"revealPendingTitle": "Fotografije so še presenečenje",
|
||||
"revealPendingMessage": "Gostitelj bo galerijo razkril pozneje — kmalu preverite znova!",
|
||||
"revealScheduledFor": "Razkritje načrtovano za {{date}}",
|
||||
"revealUploadHint": "Svoje fotografije lahko dodate v zbirko že zdaj:",
|
||||
"expires": "Poteče",
|
||||
"expired": "Poteklo",
|
||||
"contactOrganizer": "Če potrebujete dostop do teh fotografij, se obrnite na organizatorja dogodka.",
|
||||
@@ -358,6 +362,17 @@
|
||||
"categoryHeroHint": "Če za kategorijo ni nastavljena naslovna fotografija, bo uporabljena privzeta hero fotografija."
|
||||
},
|
||||
"events": {
|
||||
"revealMode": "Način razkritja (skrij galerijo do razkritja)",
|
||||
"revealModeHelp": "Gostje lahko nalagajo fotografije, vendar jih ne vidijo, dokler galerije ne razkrijete — ročno ali ob načrtovanem času. Diaprojekcija in dostop za stranke delujeta naprej.",
|
||||
"revealAt": "Načrtovano razkritje (neobvezno)",
|
||||
"revealAtHelp": "Pustite prazno za ročno razkritje z gumbom »Razkrij zdaj«.",
|
||||
"revealModeStatus": "Način razkritja",
|
||||
"revealed": "Razkrita",
|
||||
"hiddenUntilReveal": "Skrita za goste",
|
||||
"revealScheduled": "Načrtovano: {{date}}",
|
||||
"revealNow": "Razkrij zdaj",
|
||||
"revealedToast": "Galerija razkrita — gostje zdaj vidijo fotografije",
|
||||
"revealError": "Galerije ni bilo mogoče razkriti",
|
||||
"title": "Dogodki",
|
||||
"create": "Ustvari",
|
||||
"createEvent": "Ustvari dogodek",
|
||||
|
||||
Reference in New Issue
Block a user