From f4322850d6a6e6d69426c0202778ba18b4961b3d Mon Sep 17 00:00:00 2001 From: Kevin Lecou Date: Fri, 18 Sep 2026 09:21:31 +0200 Subject: [PATCH] =?UTF-8?q?Branche=20la=20galerie=20invit=C3=A9=20au=20vra?= =?UTF-8?q?i=20backend=20(Edge=20Function=20public-gallery)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nouvelle Edge Function publique (aucun token requis — même modèle de confiance que l'upload, le slug n'est pas un secret) qui résout un slug via get_public_event puis renvoie les photos de get_event_photos avec des URLs signées. Déployée et testée en conditions réelles sur kevin-lecou-hub (slug inconnu -> 404, event sans photo -> 200 vide, event avec photo -> signed_url fonctionnelle), CORS vérifié. Front (src/galerie/) : remplace les données factices par un vrai fetch + sondage (polling 25s, pas de Supabase Realtime — le schéma photobooth reste volontairement jamais exposé, l'activer aurait demandé de rouvrir ce verrou RLS). Rotation des cartes figée par photo (pas de "tremblement" au rafraîchissement), seules les nouvelles photos jouent l'animation de chute. États chargement/erreur/vide ajoutés (absents de la maquette). Testé en réel : une photo uploadée apparaît bien sur le mur. Suppression des données de maquette (gallery-mock.js, mock-photos/). --- .gitignore | 3 + src/galerie/config.example.js | 10 + src/galerie/gallery-mock.js | 102 --------- src/galerie/gallery.js | 247 +++++++++++++++++++++ src/galerie/index.html | 57 +++-- src/galerie/mock-photos/mock-1.jpg | Bin 8563 -> 0 bytes src/galerie/mock-photos/mock-2.jpg | Bin 7092 -> 0 bytes src/galerie/mock-photos/mock-3.jpg | Bin 8283 -> 0 bytes src/galerie/mock-photos/mock-4.jpg | Bin 8099 -> 0 bytes src/galerie/mock-photos/mock-5.jpg | Bin 7869 -> 0 bytes src/galerie/mock-photos/mock-6.jpg | Bin 7655 -> 0 bytes src/galerie/mock-photos/mock-7.jpg | Bin 7493 -> 0 bytes src/galerie/mock-photos/mock-8.jpg | Bin 8610 -> 0 bytes src/galerie/style.css | 67 ++++-- supabase/functions/public-gallery/index.ts | 177 +++++++++++++++ 15 files changed, 527 insertions(+), 136 deletions(-) create mode 100644 src/galerie/config.example.js delete mode 100644 src/galerie/gallery-mock.js create mode 100644 src/galerie/gallery.js delete mode 100644 src/galerie/mock-photos/mock-1.jpg delete mode 100644 src/galerie/mock-photos/mock-2.jpg delete mode 100644 src/galerie/mock-photos/mock-3.jpg delete mode 100644 src/galerie/mock-photos/mock-4.jpg delete mode 100644 src/galerie/mock-photos/mock-5.jpg delete mode 100644 src/galerie/mock-photos/mock-6.jpg delete mode 100644 src/galerie/mock-photos/mock-7.jpg delete mode 100644 src/galerie/mock-photos/mock-8.jpg create mode 100644 supabase/functions/public-gallery/index.ts diff --git a/.gitignore b/.gitignore index 8cff6e7..451ddc5 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,9 @@ src/e/config.js # Config front runtime — galerie admin (cf. src/admin/gallery/config.example.js) src/admin/gallery/config.js +# Config front runtime — galerie invité (cf. src/galerie/config.example.js) +src/galerie/config.js + # Build dist/ build/ diff --git a/src/galerie/config.example.js b/src/galerie/config.example.js new file mode 100644 index 0000000..a7850e5 --- /dev/null +++ b/src/galerie/config.example.js @@ -0,0 +1,10 @@ +// Copier ce fichier en config.js (non commité, voir .gitignore) et renseigner +// les vraies valeurs Supabase avant déploiement. Ce fichier est chargé avant +// gallery.js dans index.html. +// +// SUPABASE_ANON_KEY est la clé PUBLIQUE (anon) Supabase — jamais la +// service_role key, qui ne doit jamais être exposée côté client. +window.__PHOTOBOOTH_CONFIG__ = { + SUPABASE_URL: "https://xxxxxxxxxxxx.supabase.co", + SUPABASE_ANON_KEY: "REMPLACER_PAR_LA_CLE_ANON_PUBLIQUE", +}; diff --git a/src/galerie/gallery-mock.js b/src/galerie/gallery-mock.js deleted file mode 100644 index 5c9382a..0000000 --- a/src/galerie/gallery-mock.js +++ /dev/null @@ -1,102 +0,0 @@ -// Écran galerie invité — MAQUETTE avec données factices. -// -// Champs alignés sur le vrai schéma (photobooth.photos : nom_invite, -// message, url_storage -> ici un chemin mock, uploaded_at) pour que le -// câblage réel n'ait qu'à remplacer MOCK_PHOTOS par un vrai fetch, sans -// toucher au rendu. Voir index.html pour le détail de ce qui manque -// encore côté backend (RPC/Edge Function publique de listing). - -const MOCK_PHOTOS = [ - { nom_invite: "Camille", message: "Cette soirée est parfaite, merci !", url_storage: "/galerie/mock-photos/mock-1.jpg", uploaded_at: "2026-09-18T19:12:00Z" }, - { nom_invite: "Thomas", message: "On s'est jamais autant amusés", url_storage: "/galerie/mock-photos/mock-2.jpg", uploaded_at: "2026-09-18T19:20:00Z" }, - { nom_invite: null, message: "Vive les mariés !", url_storage: "/galerie/mock-photos/mock-3.jpg", uploaded_at: "2026-09-18T19:25:00Z" }, - { nom_invite: "Léa", message: null, url_storage: "/galerie/mock-photos/mock-4.jpg", uploaded_at: "2026-09-18T19:31:00Z" }, - { nom_invite: "Hugo & Zoé", message: "Merci pour cette belle journée", url_storage: "/galerie/mock-photos/mock-5.jpg", uploaded_at: "2026-09-18T19:40:00Z" }, - { nom_invite: "Inès", message: "Le gâteau était incroyable 🍰", url_storage: "/galerie/mock-photos/mock-6.jpg", uploaded_at: "2026-09-18T19:44:00Z" }, - { nom_invite: "Nathan", message: "À refaire dans 10 ans !", url_storage: "/galerie/mock-photos/mock-7.jpg", uploaded_at: "2026-09-18T19:52:00Z" }, - { nom_invite: "Sarah", message: "Team dancefloor jusqu'au bout", url_storage: "/galerie/mock-photos/mock-8.jpg", uploaded_at: "2026-09-18T20:01:00Z" }, -]; - -const eventNameEl = document.getElementById("event-name"); -const gridEl = document.getElementById("polaroid-grid"); -const demoAddBtn = document.getElementById("demo-add-btn"); - -function getSlugFromPath() { - const match = window.location.pathname.match(/\/galerie\/([^/]+)\/?$/); - return match ? decodeURIComponent(match[1]) : null; -} - -// Rotation aléatoire par carte, -4°/+4° (cf. skill photobooth-da), fixée -// une fois par carte (pas recalculée à chaque re-render) pour que la -// grille ne "tremble" pas. -function randomRotation() { - return (Math.random() * 8 - 4).toFixed(2); -} - -function buildCaption(photo) { - const parts = []; - if (photo.nom_invite) parts.push(photo.nom_invite); - if (photo.message) parts.push(photo.message); - return parts; -} - -function renderPolaroid(photo, { animate = false } = {}) { - const li = document.createElement("li"); - li.className = "polaroid" + (animate ? " polaroid--dropping" : ""); - li.style.setProperty("--rot", `${randomRotation()}deg`); - - const img = document.createElement("img"); - img.className = "polaroid__photo"; - img.src = photo.url_storage; - img.alt = photo.nom_invite - ? `Photo envoyée par ${photo.nom_invite}` - : "Photo envoyée par un invité"; - img.loading = "lazy"; - - const captionParts = buildCaption(photo); - const caption = document.createElement("p"); - caption.className = "polaroid__caption"; - if (captionParts.length === 0) { - caption.classList.add("polaroid__caption--empty"); - } else { - caption.textContent = captionParts.join(" — "); - } - - li.append(img, caption); - return li; -} - -function renderAll(photos) { - gridEl.innerHTML = ""; - // Plus récent en premier, comme la galerie admin. - [...photos].reverse().forEach((photo) => { - gridEl.appendChild(renderPolaroid(photo)); - }); -} - -function init() { - const slug = getSlugFromPath(); - eventNameEl.textContent = slug - ? `Galerie — ${slug.replace(/-/g, " ")}` - : "Galerie de l'événement"; - renderAll(MOCK_PHOTOS); -} - -init(); - -// --- Démo uniquement : simule l'arrivée d'un nouveau polaroid -------------- -let demoCounter = 0; -const demoNames = ["Chloé", "Adam", "Manon", "Yanis", "Juliette"]; -const demoMessages = ["On pense fort à vous !", "Quelle ambiance ce soir 🎉", "Merci de nous avoir invités", null]; - -demoAddBtn.addEventListener("click", () => { - demoCounter += 1; - const photo = { - nom_invite: demoNames[demoCounter % demoNames.length], - message: demoMessages[demoCounter % demoMessages.length], - url_storage: `/galerie/mock-photos/mock-${(demoCounter % 8) + 1}.jpg`, - uploaded_at: new Date().toISOString(), - }; - const card = renderPolaroid(photo, { animate: true }); - gridEl.prepend(card); -}); diff --git a/src/galerie/gallery.js b/src/galerie/gallery.js new file mode 100644 index 0000000..497bb5e --- /dev/null +++ b/src/galerie/gallery.js @@ -0,0 +1,247 @@ +// Écran galerie invité — photobooth-qr +// +// Contrat backend : GET {SUPABASE_URL}/functions/v1/public-gallery?slug= +// (Edge Function publique, aucun token requis — cf. docs/decisions.md +// 2026-09-18 : les invités voient désormais toutes les photos de +// l'événement, même modèle de confiance que l'upload : le slug n'est pas +// un secret, il circule via QR code). +// +// Rafraîchissement par sondage (polling), pas de Supabase Realtime : le +// schéma photobooth est volontairement jamais exposé et Realtime respecte +// les policies RLS (aucune pour anon), l'activer aurait demandé de rouvrir +// ce verrou pour un gain marginal sur un usage V1 (événements de taille +// raisonnable). Compromis assumé : chaque poll régénère des URLs signées +// pour toutes les photos, pour tous les invités qui ont la page ouverte — +// acceptable pour un mariage, à revisiter si le produit vise un jour des +// événements à très fort trafic simultané. + +const POLL_INTERVAL_MS = 25_000; + +const eventNameEl = document.getElementById("event-name"); +const eventMetaEl = document.getElementById("event-meta"); +const loadingBlock = document.getElementById("status-loading"); +const errorBlock = document.getElementById("status-error"); +const errorMessageEl = document.getElementById("status-error-message"); +const retryBtn = document.getElementById("retry-btn"); +const emptyStateEl = document.getElementById("empty-state"); +const gridEl = document.getElementById("polaroid-grid"); + +const ERROR_MESSAGES = { + "config-missing": + "Cette page n'est pas configurée correctement. Préviens l'organisateur de l'événement.", + "event-not-found": + "Cet événement n'existe pas, ou le lien utilisé est incorrect. Vérifie le QR code ou le lien.", + network: + "La connexion a été coupée. Vérifie ta connexion internet et réessaie.", + "server-overloaded": + "Le service est momentanément surchargé. Réessaie dans quelques minutes.", + unknown: "Une erreur inattendue est survenue. Réessaie, ou reviens un peu plus tard.", +}; + +function appError(code, cause) { + const err = new Error(code); + err.code = code; + err.cause = cause; + return err; +} + +function getConfig() { + const cfg = window.__PHOTOBOOTH_CONFIG__; + if (!cfg || !cfg.SUPABASE_URL || !cfg.SUPABASE_ANON_KEY) { + throw appError("config-missing"); + } + return cfg; +} + +function getSlugFromPath() { + const match = window.location.pathname.match(/\/galerie\/([^/]+)\/?$/); + return match ? decodeURIComponent(match[1]) : null; +} + +// Rotation aléatoire -4°/+4° (skill photobooth-da), figée par photo (Map +// id -> degré) pour que la grille ne "tremble" pas à chaque poll — seules +// les photos réellement nouvelles reçoivent une rotation fraîche. +const rotationById = new Map(); +function rotationFor(id) { + if (!rotationById.has(id)) { + rotationById.set(id, (Math.random() * 8 - 4).toFixed(2)); + } + return rotationById.get(id); +} + +const renderedIds = new Set(); + +function buildCaption(photo) { + const parts = []; + if (photo.nom_invite) parts.push(photo.nom_invite); + if (photo.message) parts.push(photo.message); + return parts; +} + +function renderPolaroid(photo, { animate = false } = {}) { + const li = document.createElement("li"); + li.className = "polaroid" + (animate ? " polaroid--dropping" : ""); + li.style.setProperty("--rot", `${rotationFor(photo.id)}deg`); + li.dataset.photoId = photo.id; + + const img = document.createElement("img"); + img.className = "polaroid__photo"; + img.src = photo.signed_url || ""; + img.alt = photo.nom_invite + ? `Photo envoyée par ${photo.nom_invite}` + : "Photo envoyée par un invité"; + img.loading = "lazy"; + + const captionParts = buildCaption(photo); + const caption = document.createElement("p"); + caption.className = "polaroid__caption"; + if (captionParts.length === 0) { + caption.classList.add("polaroid__caption--empty"); + } else { + caption.textContent = captionParts.join(" — "); + } + + li.append(img, caption); + return li; +} + +function showLoading() { + loadingBlock.hidden = false; + errorBlock.hidden = true; + emptyStateEl.hidden = true; + gridEl.hidden = true; +} + +function showError(rawErr, retryAction) { + const err = rawErr && rawErr.code ? rawErr : appError("unknown", rawErr); + loadingBlock.hidden = true; + errorBlock.hidden = false; + errorMessageEl.textContent = ERROR_MESSAGES[err.code] || ERROR_MESSAGES.unknown; + retryBtn.hidden = !retryAction; + retryBtn.onclick = retryAction || null; + if (err.cause) console.error(`[galerie:${err.code}]`, err.cause); +} + +async function fetchGallery(slug) { + const { SUPABASE_URL, SUPABASE_ANON_KEY } = getConfig(); + let res; + try { + res = await fetch( + `${SUPABASE_URL}/functions/v1/public-gallery?slug=${encodeURIComponent(slug)}`, + { + headers: { + apikey: SUPABASE_ANON_KEY, + Authorization: `Bearer ${SUPABASE_ANON_KEY}`, + }, + } + ); + } catch (err) { + throw appError("network", err); + } + + if (res.status === 404) throw appError("event-not-found"); + if (res.status >= 500) throw appError("server-overloaded"); + if (!res.ok) throw appError("unknown", await res.text().catch(() => null)); + + return res.json(); +} + +function formatEventMeta(event) { + const date = new Date(event.date_evenement); + const dateLabel = Number.isNaN(date.getTime()) + ? null + : date.toLocaleDateString("fr-FR", { day: "numeric", month: "long", year: "numeric" }); + const parts = []; + if (dateLabel) parts.push(dateLabel); + parts.push(`${event.photo_count} photo${event.photo_count > 1 ? "s" : ""}`); + return parts.join(" · "); +} + +function renderGallery(data) { + eventNameEl.textContent = data.event.nom || "Galerie de l'événement"; + eventMetaEl.textContent = formatEventMeta(data.event); + + loadingBlock.hidden = true; + errorBlock.hidden = true; + + if (data.photos.length === 0) { + emptyStateEl.hidden = false; + gridEl.hidden = true; + return; + } + + emptyStateEl.hidden = true; + gridEl.hidden = false; + + // Plus récent en premier. On ne reconstruit pas tout le DOM à chaque + // poll (perdrait l'état d'animation/scroll) : seules les photos dont + // l'id n'a jamais été vu sont insérées, avec l'effet "chute". + const photosDesc = [...data.photos].reverse(); + const incomingIds = new Set(photosDesc.map((p) => p.id)); + + // Retire les photos qui auraient disparu côté serveur (rare : édition + // manuelle en base, ou event modifié) pour rester cohérent. + [...gridEl.children].forEach((li) => { + if (!incomingIds.has(li.dataset.photoId)) { + li.remove(); + renderedIds.delete(li.dataset.photoId); + } + }); + + photosDesc.forEach((photo, index) => { + if (renderedIds.has(photo.id)) { + // Déjà affichée : on rafraîchit juste l'URL signée (elle expire au + // bout de 10 min), sans toucher au reste du DOM de la carte. + const existing = gridEl.querySelector(`[data-photo-id="${photo.id}"] .polaroid__photo`); + if (existing && photo.signed_url) existing.src = photo.signed_url; + return; + } + const card = renderPolaroid(photo, { animate: true }); + if (index === 0) { + gridEl.prepend(card); + } else { + const prevSibling = gridEl.children[index - 1]; + prevSibling ? prevSibling.after(card) : gridEl.appendChild(card); + } + renderedIds.add(photo.id); + }); +} + +let pollTimer = null; + +async function loadOnce({ showSpinner } = { showSpinner: false }) { + const slug = getSlugFromPath(); + if (!slug) { + showError(appError("event-not-found")); + return; + } + if (showSpinner) showLoading(); + try { + const data = await fetchGallery(slug); + renderGallery(data); + } catch (err) { + showError(err, () => loadOnce({ showSpinner: true })); + } +} + +function startPolling() { + if (pollTimer) return; + pollTimer = setInterval(() => loadOnce({ showSpinner: false }), POLL_INTERVAL_MS); +} + +// Pas de sondage pendant que l'onglet est masqué (économie réseau/batterie, +// cohérent avec l'esprit sobriété du projet) ; on refait un chargement +// immédiat au retour au premier plan pour ne pas attendre un cycle entier. +document.addEventListener("visibilitychange", () => { + if (document.hidden) { + if (pollTimer) { + clearInterval(pollTimer); + pollTimer = null; + } + } else { + loadOnce({ showSpinner: false }); + startPolling(); + } +}); + +loadOnce({ showSpinner: true }).then(startPolling); diff --git a/src/galerie/index.html b/src/galerie/index.html index a04ac8d..447895a 100644 --- a/src/galerie/index.html +++ b/src/galerie/index.html @@ -5,20 +5,22 @@ Galerie de l'événement @@ -37,20 +39,33 @@ photobooth

Galerie de l'événement

-

Tous les souvenirs déposés par les invités, en direct

+

Tous les souvenirs déposés par les invités, en direct

- +
+ +

Chargement de la galerie…

+
- - + + + + + - + + + diff --git a/src/galerie/mock-photos/mock-1.jpg b/src/galerie/mock-photos/mock-1.jpg deleted file mode 100644 index c3308f40ccdec7b870cc330731444e5deb9c30c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8563 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 diff --git a/src/galerie/mock-photos/mock-2.jpg b/src/galerie/mock-photos/mock-2.jpg deleted file mode 100644 index b1cfb4948bf95b1bd519db7a8517687f6f63d6f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7092 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 ANB{r; diff --git a/src/galerie/mock-photos/mock-4.jpg b/src/galerie/mock-photos/mock-4.jpg deleted file mode 100644 index 4981a5040b03b164be52b20a539e68ab755b6c40..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8099 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 diff --git a/src/galerie/mock-photos/mock-5.jpg b/src/galerie/mock-photos/mock-5.jpg deleted file mode 100644 index 5ce002075e93f6d9b85b9c406d43b774ee3d63cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7869 zcmeHLXH-+$x=lb(Py|F!P~j?<-VBivT0BaT61foRO^DRcK}rzt+@mN(auMkzN1_1| zib#{Hhx$ke9HdFr0HH`%YAAWpb9@r;zBk^V_r|#6t{;1S^V@UI^{qA5*n90we@FiS z;4#qE*98E9fIk|22yhj^%JQXsxw10aA@)OG+7WhkHg=99oSYm-I5;?uaUbJ6di*E{ z2Ny5bac&+SULMY4C-_eA@G)YZFDAe*O4dV17($+-97h@1|7D}U0q`CHt^ik9fxG|~ zULY$kklqA10{{Y8fsFQNy^QV<8#^lt@G#@%WgZ{^$il*QsyXuCFv*tqW8gA8vnTWAivXL?5f|HWZMhYVEp> z^Vqe+8pddv5pP3Whil-!G{84}D-gto*Fgxh8G7T2|7du7HC*Rpgpd?k^12M~N1n8_ zwUyQJEOXiZjkzw1ERZBwMvEd@hwZ8Az5AwL(CnAeZrTN&%+pgeM^*A;DBf1)ca-w0 z%mNr#bEQ{4;8`Q|dIEtY;#L`|;uJYNhRog0k*28*{baT6+*duTM?GRu1w~hO`=kXy zVOGWq#a;r|v`-~=F6v=Rzl3o|azh+S?L?@8E`=V}m?yrLg|m?@AyvQrKqyph*O`)=8uW6AULr=P68RNdiRVDndZ`T}`pmBi z109nBF7qNOeQ1J;d`!=0V-Ous5hdsBHMT2e1_FWk1dW2Fin#ZB9*Aw+c36YfEd0`j zu+?bmh}d_R0wR-*d2%x>_?L|p>r0*bgomw5y(Mw#Pjd~-6B=HS%jZMK-2?S`n5_s!IidG!kRY3<(eO&^fNc}SOLQtPdk{vYZEH&Gr= zWZN!^U~><%)m{`<>auWQ!n#zu*Um11!#pvRT3nf2W(q0jnjYao9_D3~t`M>gr>-Jz zzU@Cn9Cc2@z&-0_#smyKe%r<8rMb4HWH zF8=oRUt#83F7dR87o;9{af~#|{X?(LuF6`ziS2%`Zx}elaj!0}PQbG}bG>~{JA{PO z2qJk~KTQ<#vq&fF9@cyJ#%6LTZWhyQyVkif(D?UPhO*#RQ_5&X4)=E?#leNXC;Tf`y(ME2NrVG-HBhsm5T)Uqo76`pjAsX^U30T zqzaZVxb|DE_qB=QD!2547eA1F zO`Tm9kQ?*tb((X9`8|1x(lsD^W+_}MvlcvNE;M;!CrEy+TeBWCBCC-)#gBVA*c#Gi zSq6dN4%*E|HTK86)`<--@31#{FE##bqRgDPEu__|tb9#&sBtidoJUL-mGm)9Flcss z5C)r9PSH*{X!B==%|7eR4wic@HgJcU+7Vv1z7rvhYS&nqLi^DHhA5-jWGcp#{8H%G z0}fn=C0<0tlP!tkg#kGjXXr2;zy%YKw_cT)&nP6d3@5C-axgid`kuH|R>VDLNzB73 zR$5r2r54sfN~+aX;-cM1;}pWRw6%vtbKl8-M8{tlX<8P*YBjZLsbI>v&r5-=`fVWv z=N~V}m%4kp02u-^fHnv1_5Q z9Ub7{k1G|?(gH0Al%+uweKDr(pusoa`F}(;6O(5kRk3(=wI_4{_>big;LpxqgC{dQ=lQRNGGZoG68IpXRnF5WCR$V1(}~7OdC&j z293O7=I%qlY^oUF=?n{Xd?k6qc#YKG+9BbR9TmZM27mI@WhOE^>9Da1hZE*#VC&rb z=ChzaNG?4n=#`&su+YKXGRH77>tNf+t|=vsCkOXY-#^2weL};!i~MN z!K~enIEzf^fHy;QK#fRKK9UX~p{W5nySM0oW8-3Z%w*+c>8XKAhe@2oS~!=)BPUCh zc!AUdXBlPy|flS-bTYFlJiMz5Er3IH}x4Fhl^t{Ng6LMD51hO*%Z45iK z-0DDyPFKBE`RN}YCzzT0Q4UqfBwIL+hR6bMfg_tFmeEw~Q#A2L9F_Tz1kC^ab~8%3 z$2GHC2@VzLdmKWE(k)VTezKZ$bH67OX7_nw)@=FFEqYsEOrjHl!AjU^`1=kJa^g>K zED%g>n8<)r+Zi=kv3-$peikiI9PaPKNF|q9LPM7`k@k?8xeu|5KKG{CosTHyta^PM zoycwC?OBoBmtFfRhu8MpT_!SHio_yHBNCfUDuZcl%xoG=#tc`5MD*THnrv-d=~f!} z3ln`GU~QgFa3Kfd9Maz5zexvpO~(r#zRN70 zc~GYswzjC3f5U7tORmkWa^_5bcB#XVE6l_UWCqSOay{`rdi<>8eNbalecgY%k2y!6 zkzF@khK;S~O^jRMfUlvljw8c~O~4 zF^l~JI$*7cCWSBOhaCw6*gSufGL={~jGqt4GHdSz&nI)?yugaX1v-d(8aXR_Eg13P zK@WUPPPK~@-c>bqA*BS&*TV5Il_D2)l>a)IONZv)kvFS%Z@n>0;2P;#DYrbNIh0U? zP2K0R2eKqSv24%BvT3h$+P{{c-rN=(oU1Qsys8sBs&4|e-oyMOWa+<*2|Lb~t$g1E&K#Q0=z8Q)u6=^0b==bl(!JSzA4hbNvw$R2Xk2HmpUBoI4|J z?&DSf+K62p^I_arC1h^b#hn@TxAua15ly8m3m`xERMu`cGP({wht@6C8ONTBikU>b zh4&U|)@!YeVpOa%BX-{n#@Wd%(Tsw9zYTq$XOA=<-x2$d->kC>t2(OUX|80Y zGp*gmdJmG-64!G7W_LgY07~c~Jl>2k&Xt#pb^OMUzf zR_EY7;>Hm5XOhaSRT~AIkQ!n$vu_T1ea##?C-Y2{^}$CYlr>Ef&FxX23)QXHB(`9< ztw+PJ%77US(q3Z2`Xjx-AfG;sbDSgMbqyYss8z5d7N(y!k!^^A_uia}M{WJ{12T}r z7hQg@)NHHQ83q-^9JwlPFkY;b_*_D)%A?I`*}^a7XIY1&9h>%6e+CQ zY;WloVDzJj03)9U7TCu4hNM?7cj>@q^&Vuv_46m`fY!)JqY-mV%9OET(kjo7qyS-W z+e()a3-8{O-A1lp1v$5Z0OMx_tFomi3;h|P<@M6InR2}kxl>ww@>q?@3i6%P2(I9( z7QsI>14O50eh@mxi@UI`dy3!OB1{S&?&F$aVThmFa2Nu^aTA-}Z%0D+y>kAr3a#=V zNtdn(%EP52AZejU9mJzLF>7+}Jw+Pazye*IrW-hbLzQi8IX}wlx$aI8P9F@{aEu4f zvsvBTQ;h5FO4r2@^DVpHu);@P16!kf<--dVN?(%K`va=q^^IWaDC%|{vk04@i1mB( z!Rr6Fw2Fvz+Lz&-C*$Y<(_CrajbZkH0a#nz_G)BMeD?e9$B{KqjYxCVUDBg~ftm&W z-KdY6;a@|(r&ay$`ZY7^DQai3{)JNm9dv+vp<^L==-sh=G;IVt7mq*HYU|hXHcJsU zb>A`bE%YPXTzKep^rSUoN9Zh$qUschoi;9*2%%IjThJ^~pki)kk@GdO=JoB8!Ww8; zLr_gtU(1&y$KXP+jnGi(daVSqYQz)i-8s6HRW$YgLo3y&V0yK(M6enRH#mFD!Gdbl zXFkznDLoxfzDfSi?&A`v%QCYn^G@7e!fL!RY9 zgO=msnAYIxhYO#Ve93u53L@ScFKiSr&)FsT!%q2o+Y=xQ59iS0E=I|18lGJ^wefg< z|DP`IclwSG+iHog3BA^LsltVAOOU1Q-%UMoKJ&IRUcm0=3ORPuBvQs~gI2n@;5OE4 zzAHC&Lvg0iSn2ao{^x)DZo-IwW2>{~k}D}iIwxE2V3B8iA}4p&H$(hDZ9{oqi6{NrXvMO@5gPlQ1=V&ua- zT5~xBeckFJT;etIj&f>)12H3U8oxv+9jil|8E!2-4_BI*CEhVI(r%hM@pA8PMn;`m z%2HFKRWRQ9>>e-FTh&3+Lb*_irP)~7oNl0_+!XEO=9O%iYwvj<;lzcH8U2e+G7P#> z@BD)4*WZh_g)-MDmis@=_Sei!V!CMv;$C79U(a~{Z0ntJJe;yXO zRpbZD1xFYY7{4*`sL*=X^ohCGN1kH$f^$W&0cHa`peJ{n?FVabuTn>=ZHC_858qLV zsmZjC@nVdCi78(Vt{kVhq+46Of5JIiM1|WCKjQNj(>ly>F5NBW7cP`E2-|BOoX){a zSS4yczoU0>nhp@c3HS9Ck4ut~KE8=QhU3#jO%*lKYP^#jA)n+>Db+oWc@}eN_ku*R z({O94>g`8fyOfT=BwC6kl})Qc070zX)n6~!?NQep5jCdNAyp70DFdalt$VKeKKbEv zfR7`TYQLe^yBKQRQ#-G)^&qBf2V=o)yV-sPTv*&ACZrxtYPS6~v-*`~^l~~K@XXbQ zhBv|PknW>`1#dXRlxNnOj7XWYipE996P`T_)2n(`(jO@x{)!F|E?%N~Zptm#i5^#@ e-3v{_2jE>?TWss9X?G4z`QMp;(G>vF2mTW~@m&xA diff --git a/src/galerie/mock-photos/mock-6.jpg b/src/galerie/mock-photos/mock-6.jpg deleted file mode 100644 index fa20aa487500acb89906771ec1382176978b4d47..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7655 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 L-`fFIe*b>}-|N;I diff --git a/src/galerie/mock-photos/mock-7.jpg b/src/galerie/mock-photos/mock-7.jpg deleted file mode 100644 index a4067660e8b49d0080e1f7c614bb3087c8c5ea50..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7493 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 ZA$qzZU~`bl#OkfP`#1Z)d;)8){sEx2gGT@W diff --git a/src/galerie/mock-photos/mock-8.jpg b/src/galerie/mock-photos/mock-8.jpg deleted file mode 100644 index c593d0743b7a49896a369f18bc95116f437f78f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8610 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 voir la règle + [hidden] ajoutée juste après sa définition plus haut. #status-loading, + #status-error et .empty-state n'ont pas de display personnalisé, [hidden] + fonctionne donc nativement pour eux, pas de règle supplémentaire requise + — mais si un display leur est ajouté un jour, il faudra faire pareil. */ + +.spinner { + display: inline-block; + width: 26px; + height: 26px; + margin-bottom: 0.6rem; + border: 3px solid rgba(255, 94, 91, 0.22); + border-top-color: var(--color-coral); + border-radius: 50%; + animation: spin 0.8s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +#status-error p { + margin: 0 0 0.9rem; +} + +#retry-btn { font: inherit; - font-family: var(--font-ui); font-weight: 700; - font-size: 0.85rem; color: var(--color-ink); background: var(--color-coral); border: none; border-radius: 999px; - padding: 0.8rem 1.3rem; - box-shadow: var(--shadow-soft); + padding: 0.7rem 1.3rem; cursor: pointer; } -.demo-add-btn:active { - transform: translateX(-50%) translateY(1px); +.empty-state { + text-align: center; + padding: 2.5rem 1.2rem; + color: var(--color-muted); + font-family: var(--font-hand); + font-size: 1.25rem; } @media (prefers-reduced-motion: reduce) { .polaroid, - .polaroid--dropping { + .polaroid--dropping, + .spinner { animation: none !important; transition: none !important; } diff --git a/supabase/functions/public-gallery/index.ts b/supabase/functions/public-gallery/index.ts new file mode 100644 index 0000000..457702a --- /dev/null +++ b/supabase/functions/public-gallery/index.ts @@ -0,0 +1,177 @@ +// Edge Function `public-gallery` +// +// Contrat : GET {SUPABASE_URL}/functions/v1/public-gallery?slug= +// +// Tourne avec la service_role key (cf. _shared/supabase-admin.ts), MAIS +// sans aucune vérification de token — c'est le point central de cette +// fonction, à la différence de `admin-gallery`. L'accès est intentionnellement +// ouvert à quiconque connaît le `slug` : même modèle de confiance que +// l'upload invité (le slug circule via QR code, ce n'est pas un secret). +// +// Origine : revirement produit du 2026-09-18 (cf. docs/decisions.md) — les +// invités peuvent désormais voir toutes les photos d'un événement, pas +// seulement envoyer la leur. Ça inverse la règle "lecture des photos jamais +// publique" du cahier des charges initial / docs/modele-donnees.md : ce +// revirement est acté comme décision explicite du 2026-09-18, pas tranché +// par cet agent. +// +// Sécurité : on résout le slug via `public.get_public_event` (PAS +// `get_event_for_admin`, qui expose admin_token) — cette fonction ne +// renvoie que {id, nom, statut, date_evenement, expire_at, +// max_photos_per_guest}, jamais admin_token / client_nom / facture_montant. +// Les photos elles-mêmes viennent de `public.get_event_photos` +// (service_role uniquement), qui ne renvoie que {id, url_storage, +// nom_invite, message, uploaded_at} — jamais l'email/téléphone d'un invité +// (ces colonnes n'existent d'ailleurs pas dans photobooth.photos, cf. +// docs/modele-donnees.md). +// +// `get_public_event` renvoie l'événement même si son statut n'est pas +// `actif` (expiré/archivé) — comportement hérité, inchangé ici. Cette +// fonction renvoie donc telles quelles les photos d'un événement expiré si +// elles existent encore en base (la purge Storage/DB à J+7 est un job +// séparé, cf. expire-events) : pas de filtre `statut = 'actif'` ajouté ici, +// le front peut décider d'afficher un message adapté à partir de +// `event.statut`, cohérent avec le comportement déjà choisi pour +// `get_public_event` côté page d'upload. +import { getSupabaseAdmin, getStorageBucket } from "../_shared/supabase-admin.ts"; +import { corsHeaders, handleCorsPreflight } from "../_shared/cors.ts"; + +// URLs signées affichées dans la galerie invité : courte durée, c'est pour +// un affichage immédiat dans le navigateur de l'invité, pas pour un lien à +// partager. Même valeur que `admin-gallery` (cf. GALLERY_SIGNED_URL_TTL_SECONDS +// dans supabase/functions/admin-gallery/index.ts). +const GALLERY_SIGNED_URL_TTL_SECONDS = 10 * 60; // 10 minutes + +type PhotoRow = { + id: string; + nom_invite: string | null; + message: string | null; + uploaded_at: string; + url_storage: string; +}; + +type PublicEventRow = { + id: string; + nom: string; + statut: string; + date_evenement: string; + expire_at: string; + max_photos_per_guest: number | null; +}; + +function jsonResponse(status: number, body: unknown): Response { + return new Response(JSON.stringify(body), { + status, + headers: { ...corsHeaders(), "Content-Type": "application/json" }, + }); +} + +// 404 générique : même statut/corps que le "slug inconnu" de admin-gallery, +// pour ne pas distinguer un slug qui n'existe pas d'un autre cas d'erreur +// silencieuse côté appelant. +function notFound(): Response { + return jsonResponse(404, { error: "not_found" }); +} + +Deno.serve(async (req) => { + const preflight = handleCorsPreflight(req); + if (preflight) return preflight; + + if (req.method !== "GET") { + return jsonResponse(405, { error: "method_not_allowed" }); + } + + const url = new URL(req.url); + const slug = url.searchParams.get("slug"); + + if (!slug) { + return jsonResponse(400, { error: "missing_parameters" }); + } + + const supabaseAdmin = getSupabaseAdmin(); + + // 1. Résout l'event par slug via public.get_public_event (anon-safe, + // n'expose jamais admin_token/client_nom/facture_montant) — pas + // get_event_for_admin, réservée à admin-gallery. + const { data: event, error: eventError } = await supabaseAdmin + .rpc("get_public_event", { p_slug: slug }) + .maybeSingle(); + + if (eventError) { + console.error("[public-gallery] get_public_event failed", eventError); + return jsonResponse(500, { error: "internal_error" }); + } + + if (!event) { + return notFound(); + } + + const typedEvent = event as PublicEventRow; + + // 2. Photos de l'event, triées chronologiquement (get_event_photos renvoie + // déjà uploaded_at ASC) — pas de tri inversé ici contrairement à + // admin-gallery : une galerie invité affiche naturellement l'ordre + // d'arrivée des photos, pas de raison produit de l'inverser pour l'instant. + let photos: PhotoRow[]; + try { + const { data, error } = await supabaseAdmin.rpc("get_event_photos", { + p_event_id: typedEvent.id, + }); + if (error) throw error; + photos = (data ?? []) as PhotoRow[]; + } catch (err) { + console.error("[public-gallery] get_event_photos failed", err); + return jsonResponse(500, { error: "internal_error" }); + } + + // Event sans photo : état normal (pas d'upload pour l'instant), pas une + // erreur — 200 avec photos: [], contrairement à admin-gallery?action=zip + // qui renvoie 404/no_photos (là, l'absence de photo empêche l'action + // demandée "générer un ZIP" ; ici il n'y a pas d'action, juste un état + // d'affichage à rendre). + const signedUrlByPath = new Map(); + if (photos.length > 0) { + const paths = photos.map((p) => p.url_storage); + const bucket = getStorageBucket(); + const { data: signedUrls, error: signError } = await supabaseAdmin.storage + .from(bucket) + .createSignedUrls(paths, GALLERY_SIGNED_URL_TTL_SECONDS); + + if (signError) { + console.error("[public-gallery] createSignedUrls failed", signError); + return jsonResponse(500, { error: "internal_error" }); + } + + for (let i = 0; i < paths.length; i++) { + const entry = signedUrls?.[i]; + if (entry?.signedUrl) { + signedUrlByPath.set(paths[i], entry.signedUrl); + } else if (entry?.error) { + // Une photo dont l'URL n'a pas pu être signée (ex. fichier absent du + // Storage alors que la ligne existe encore côté DB) ne doit pas + // faire échouer toute la galerie : on la renvoie avec + // signed_url: null, au front de gérer l'affichage. + console.warn( + `[public-gallery] signed url failed for ${paths[i]}`, + entry.error + ); + } + } + } + + return jsonResponse(200, { + event: { + nom: typedEvent.nom, + date_evenement: typedEvent.date_evenement, + statut: typedEvent.statut, + photo_count: photos.length, + }, + photos: photos.map((p) => ({ + id: p.id, + nom_invite: p.nom_invite, + message: p.message, + uploaded_at: p.uploaded_at, + signed_url: signedUrlByPath.get(p.url_storage) ?? null, + })), + }); +});