Aller au contenu principal

Galleries Sharing, Download Basket, and Owner Notifications

This document is the active source of truth for the next gallery delivery work after the owner library/session decoupling refactor.

It covers four closely related concerns that must be implemented as one coherent product surface:

  1. share a gallery with specific people,
  2. keep owner preview/live delivery behavior coherent,
  3. let recipients select multiple images for bulk download,
  4. notify the photographer when someone downloads one or more photos.
  • SLIDESHOW_EDITOR_REDESIGN.md — slideshow editor, cover modes, timeline UX, export quality, render queue (gallery-slideshow), and published playback on owner / public / client portal surfaces. Use it when delivery work touches published slideshow or shared gallery viewing—not only ZIP downloads.
  • GALLERIES_LIBRARY_PLAN.md — owner gallery library refactor, session decoupling, and /galleries navigation; sharing builds on that workspace model.

Progress​

  • Slice 1 - download event tracking: in progress
    • plan documented
    • migration for gallery_download_events started
    • backend tracking service started
    • existing owner / portal / public download endpoints being instrumented
    • owner gallery download stats endpoint added as the first reporting surface
  • Slice 2 - basket download API: in progress
    • owner / portal / public POST download-url endpoints now accept assetIds[] for ZIP basket generation
    • public / portal / owner preview viewers now expose a dedicated local download basket, separate from favorites
  • Slice 3 - share recipients: in progress
    • gallery_shares migration added
    • owner share CRUD routes added
    • share token can now unlock a gallery before publication through the same slug URL
    • first owner UI for direct share links is now wired in the gallery editor
    • share permissions can now be adjusted inline from the editor links tab
    • direct shares now read as authorized recipients when created from name + email
    • send-email flow from the gallery editor is now being wired with a review modal before send
    • direct shares now distinguish client vs guest
    • each listed recipient now carries download activity counters
    • public gallery access now supports explicit modes: open, password, or name + email
    • the public lock screen now uses one clear entry mode at a time instead of stacking password and request flows
    • owner sharing surfaces now focus on direct shares only; manual access-request approval is removed from the main UI and API flow
  • Slice 4 - owner activity panel: in progress
    • owner gallery download stats are visible in the editor delivery tab
    • recent download activity feed is now exposed and rendered in the editor

Immediate Next Slice​

The next delivery slice after recipient typing is guest-specific visibility:

  • allow assets to stay visible to clients while hidden from guests,
  • expose that control in the gallery editor,
  • enforce it on public/direct-share viewers based on recipientType.

Why This Needs A Dedicated Plan​

The current gallery domain already contains several adjacent concepts:

  • public gallery access by slug,
  • owner preview access,
  • client portal gallery access,
  • favorites / gallery selection,
  • section/gallery ZIP downloads.

Those flows work, but they are not yet organized around a clear delivery model for real recipients.

The main risks if we implement the next features ad hoc are:

  • overloading selection to mean both favorites and download intent,
  • mixing owner preview and public sharing as separate products,
  • creating notifications that cannot identify who downloaded what,
  • adding share links without a permissions model.

Product Principle​

There should be one gallery experience, with different access modes:

  • owner preview: same external gallery URL, available to the owner even before publish,
  • public live: same URL becomes public when the gallery is published and public access is enabled,
  • direct recipient share: scoped access for named recipients who are not necessarily portal clients,
  • client portal access: authenticated access for linked portal clients.

The UI should not present four unrelated systems. The domain should express one delivery surface with explicit access channels.

Current State​

Already implemented:

  • owner library /galleries,
  • session-linked and standalone galleries,
  • public slug URL,
  • owner preview via the same external gallery URL,
  • portal visibility toggle,
  • public password,
  • selection on public/client/owner preview,
  • gallery and section ZIP download endpoints.

Current gaps:

  • no gallery-specific sharing model for inviting named external recipients,
  • no owner notification when a recipient downloads assets,
  • no basket selection dedicated to download,
  • no audit trail of downloads by actor, scope, and assets.

Domain Model To Add​

Create a dedicated gallery share model instead of reusing generic organization resource shares.

Recommended table: gallery_shares

Fields:

  • id
  • gallery_id
  • owner_id
  • label
  • email
  • first_name
  • last_name
  • access_mode
    • LINK
    • EMAIL_LINK
    • PORTAL_LINKED
  • token_hash
  • expires_at
  • revoked_at
  • allow_web_download
  • allow_full_download
  • allow_favorites
  • allow_submit_selection
  • allow_download_basket
  • created_at
  • updated_at

Why a dedicated table:

  • gallery-specific permissions differ from org resource sharing,
  • public gallery access is anonymous/tokenized, not organization membership,
  • we need per-recipient audit and notification metadata.

For public recipients, do not rely only on the public gallery password token. Introduce scoped access tokens for gallery shares so the app can identify:

  • who accessed the gallery,
  • which share was used,
  • whether the actor is portal, owner, or external recipient.

Recommended table: gallery_share_access_tokens

Fields:

  • id
  • gallery_share_id
  • token_hash
  • issued_at
  • expires_at
  • last_used_at
  • revoked_at
  • source_label
  • source_type
    • OWNER_PREVIEW
    • PUBLIC
    • PORTAL
    • SHARE

3. Download Events​

Add an explicit audit table for download activity.

Recommended table: gallery_download_events

Fields:

  • id
  • gallery_id
  • gallery_share_id nullable
  • selection_id nullable
  • actor_type
    • OWNER
    • PORTAL_CLIENT
    • PUBLIC_RECIPIENT
  • actor_label
  • download_scope
    • ASSET
    • SECTION
    • GALLERY
    • BASKET
  • download_kind
    • WEB
    • FULL
  • asset_count
  • section_id nullable
  • asset_ids jsonb
  • created_at

This table is the basis for:

  • owner notifications,
  • future reporting,
  • abuse/debug visibility.

4. Download Basket​

Do not reuse selection.assetIds for bulk download.

Reason:

  • current selection is clearly tied to favorites/client curation,
  • download basket is operational and temporary,
  • forcing them into one field creates confusing UX and brittle rules.

Recommended approach:

  • basket state is client-side first,
  • persisted optionally only if needed later,
  • server receives assetIds[] when generating a basket ZIP.

That means the first implementation does not require a new persistent basket table.

UX Model​

Add a dedicated Sharing workspace inside gallery settings/editor, focused on real delivery:

Sections:

  1. Access channels

    • Client portal
    • Public link
    • Direct shares
  2. Live gallery URL

    • one URL only
    • preview/live state
  3. Recipients

    • recipient list
    • add recipient modal
    • resend / revoke / copy share link
  4. Download permissions

    • web/full
    • basket allowed
    • favorites allowed
  5. Activity

    • recent downloads
    • recent access

Recipient Flow​

Recipients should be able to:

  • open the gallery from a secure share link,
  • browse normally,
  • select images into a download basket,
  • trigger Download selected,
  • optionally use section/gallery ZIP if permissions allow.

Owner Notifications​

Owner should receive a notification when:

  • a recipient downloads one asset,
  • a recipient downloads a basket,
  • a recipient downloads a section,
  • a recipient downloads the full gallery.

Notification examples:

  • Emily Carter downloaded 6 photos from Wedding gallery
  • Portal client downloaded the Reception section
  • Public recipient downloaded the full gallery in web resolution

Backend Slice Plan​

Slice 1: Download Event Tracking​

Goal:

  • no new recipient model yet,
  • instrument existing asset/section/gallery downloads,
  • notify owner.

Work:

  • add gallery_download_events,
  • record events from:
    • owner preview download endpoints,
    • client portal download endpoints,
    • public gallery download endpoints,
  • create owner notifications through NotificationsService.

This is the lowest-risk first slice because it builds observability immediately.

Slice 2: Basket Download API​

Goal:

  • allow selecting arbitrary visible assets for ZIP download.

API additions:

  • owner preview:
    • POST /sessions/:sessionId/galleries/:galleryId/download-url body: { kind, assetIds? }
  • portal:
    • POST /client-accounts/me/galleries/:galleryId/download-url body: { kind, assetIds? }
  • public/share:
    • POST /public-galleries/:slug/download-url body: { kind, assetIds?, accessToken? }

Important:

  • keep existing GET section/gallery ZIP routes for compatibility,
  • add POST for basket ZIP generation,
  • validate that every requested asset belongs to the gallery and is visible in the caller’s scope.

Slice 3: Share Recipients​

Goal:

  • create named share recipients with permissions.

API additions:

  • GET /galleries/:galleryId/shares
  • POST /galleries/:galleryId/shares
  • PATCH /galleries/:galleryId/shares/:shareId
  • DELETE /galleries/:galleryId/shares/:shareId
  • POST /galleries/:galleryId/shares/:shareId/send

Public/share access:

  • add share token resolver that can open the same gallery page in recipient mode,
  • same gallery UI, different actor context.

Slice 4: Activity Panel​

Goal:

  • expose download/access history in the owner gallery editor.

API:

  • GET /galleries/:galleryId/activity

Frontend Slice Plan​

Public and portal gallery pages get:

  • Select for download mode,
  • per-asset checkbox/selection affordance,
  • sticky action bar:
    • selected count,
    • clear selection,
    • download selected.

This must remain distinct from Favorites only.

Slice 2: Sharing Workspace In Editor​

Gallery editor gets:

  • recipient list,
  • add recipient modal,
  • delivery permissions per recipient,
  • activity summary.

Slice 3: Notifications Surface​

No dedicated screen required initially. Use existing in-app notifications and, later, optional email notifications.

Notification Design​

Add new notification types:

  • GALLERY_ASSETS_DOWNLOADED
  • GALLERY_SECTION_DOWNLOADED
  • GALLERY_BASKET_DOWNLOADED
  • GALLERY_FULL_DOWNLOADED

Metadata should include:

  • galleryId
  • galleryTitle
  • shareId
  • actorType
  • actorLabel
  • downloadKind
  • assetCount
  • sectionId when relevant

Implementation Rules​

  • Do not overload existing selection semantics.
  • Do not create a second gallery viewer for shares.
  • Do not make preview and live separate URLs again.
  • Prefer explicit audit events over inferred behavior.
  • Keep backward compatibility for existing public/client download endpoints.
  • Use one notification creation path through NotificationsService.
  1. download event tracking + owner notifications
  2. POST basket ZIP endpoints
  3. public/client/preview download basket UI
  4. gallery share recipients model + management UI
  5. activity panel in gallery editor

Open Questions​

  • Should external recipients also be able to submit favorites/selection, or only download?
  • Should recipient share links expire by default?
  • Do we want email notifications in addition to in-app notifications for owner download events?
  • Do we want recipient identity to be mandatory, or allow anonymous named links?

Current recommendation:

  • allow named recipients,
  • default to expiring links,
  • in-app notifications first,
  • email notifications later,
  • keep anonymous public link as a separate, coarser access channel.