feat: journey gallery 1-to-N model with M:N entry-photo junction table

Replaces the old model where journey_photos was keyed per-entry with a
per-journey gallery table (one row per unique photo per journey) and a new
junction table journey_entry_photos that links gallery photos to entries.

Key changes:
- Migration 121: renames old journey_photos to journey_photos_old, creates the
  new gallery table + junction table, backfills both from existing data, drops
  the backup, removes synthetic 'Gallery' / '[Trip Photos]' wrapper entries
- journeyService: rewrites photo helpers (JP_SELECT/JOIN now joins via
  journey_entry_photos → journey_photos → trek_photos); adds uploadGalleryPhotos,
  addProviderPhotoToGallery, unlinkPhotoFromEntry, deleteGalleryPhoto; simplifies
  deletePhoto and linkPhotoToEntry against the new schema; syncTripPhotos inserts
  directly into the gallery instead of a wrapper entry
- journeyShareService: updates public photo and asset validation queries to join
  through the gallery table instead of entry_id; getPublicJourney now returns a
  dedicated gallery array alongside per-entry photos
- journey routes: adds gallery upload, provider-photo, and delete endpoints
  (POST/DELETE /:id/gallery/*); adds unlink-from-entry route
  (DELETE /entries/:entryId/photos/:journeyPhotoId); updates link-photo to
  accept journey_photo_id with a backwards-compat photo_id alias
- types: adds GalleryPhoto interface
- client api: adds uploadGalleryPhotos, addProviderPhotosToGallery, unlinkPhoto,
  deleteGalleryPhoto; updates linkPhoto param name to journeyPhotoId
- journeyStore: adds GalleryPhoto type, gallery field on JourneyDetail,
  uploadGalleryPhotos / unlinkPhoto / deleteGalleryPhoto store actions
- JourneyDetailPage + tests: updated to work with the new gallery model
This commit is contained in:
jubnl
2026-04-22 15:58:31 +02:00
parent 7c9e945b8c
commit 71aa8f8051
10 changed files with 510 additions and 297 deletions
+8 -10
View File
@@ -129,15 +129,14 @@ export function canAccessUserPhoto(requestingUserId: number, ownerUserId: number
// Journey photos use tripId=0 — check journey_photos + journey_contributors
if (tripId === '0') {
const journeyPhoto = db.prepare(`
SELECT jp.entry_id, je.journey_id
FROM journey_photos jp
JOIN trek_photos tkp ON tkp.id = jp.photo_id
JOIN journey_entries je ON je.id = jp.entry_id
SELECT gp.journey_id
FROM journey_photos gp
JOIN trek_photos tkp ON tkp.id = gp.photo_id
WHERE tkp.asset_id = ?
AND tkp.provider = ?
AND tkp.owner_id = ?
LIMIT 1
`).get(assetId, provider, ownerUserId) as { entry_id: number; journey_id: number } | undefined;
`).get(assetId, provider, ownerUserId) as { journey_id: number } | undefined;
if (!journeyPhoto) return false;
const access = db.prepare(`
@@ -194,13 +193,12 @@ export function canAccessTrekPhoto(requestingUserId: number, trekPhotoId: number
// Check journey_photos — is this photo in a journey the user can access?
const journeyAccess = db.prepare(`
SELECT 1 FROM journey_photos jp
JOIN journey_entries je ON je.id = jp.entry_id
WHERE jp.photo_id = ?
SELECT 1 FROM journey_photos gp
WHERE gp.photo_id = ?
AND EXISTS (
SELECT 1 FROM journeys j WHERE j.id = je.journey_id AND j.user_id = ?
SELECT 1 FROM journeys j WHERE j.id = gp.journey_id AND j.user_id = ?
UNION ALL
SELECT 1 FROM journey_contributors jc WHERE jc.journey_id = je.journey_id AND jc.user_id = ?
SELECT 1 FROM journey_contributors jc WHERE jc.journey_id = gp.journey_id AND jc.user_id = ?
)
LIMIT 1
`).get(trekPhotoId, requestingUserId, requestingUserId);