mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 13:21:46 +00:00
fix: update backend tests and service bugs for gallery 1-to-N schema
updatePhoto: write sort_order to journey_entry_photos (junction) not journey_photos, since JP_SELECT reads jep.sort_order — updating the gallery row had no visible effect. deletePhoto: include id in return value so callers that check deleted.id still work. Tests updated for new schema: - journeyShareService: insertJourneyPhoto helper now inserts into journey_photos (keyed by journey_id) + journey_entry_photos junction instead of the old entry_id-keyed table - SVC-081: deleteEntry cascades junction rows (journey_entry_photos), not gallery rows (journey_photos); assert junction is gone, gallery is preserved - SVC-086: syncTripPhotos now populates the gallery directly — no [Trip Photos] wrapper entry; assert journey_photos gallery row instead - INT-028: error message updated to 'journey_photo_id required'
This commit is contained in:
@@ -781,22 +781,20 @@ export function updatePhoto(photoId: number, userId: number, data: { caption?: s
|
||||
if (!row) return null;
|
||||
if (!canEdit(row.journey_id, userId)) return null;
|
||||
|
||||
const fields: string[] = [];
|
||||
const values: unknown[] = [];
|
||||
if (data.caption !== undefined) { fields.push('caption = ?'); values.push(data.caption); }
|
||||
if (data.sort_order !== undefined) { fields.push('sort_order = ?'); values.push(data.sort_order); }
|
||||
if (!fields.length) {
|
||||
// no-op: return some photo row for this gallery item (first entry link)
|
||||
return db.prepare(`SELECT ${JP_SELECT} FROM ${JP_JOIN} WHERE gp.id = ? LIMIT 1`).get(photoId) as JourneyPhoto | null;
|
||||
// caption lives on the gallery row; sort_order lives on the junction table
|
||||
// (JP_SELECT reads jep.sort_order, so updating journey_photos.sort_order
|
||||
// would not be reflected in the returned row).
|
||||
if (data.caption !== undefined) {
|
||||
db.prepare('UPDATE journey_photos SET caption = ? WHERE id = ?').run(data.caption, photoId);
|
||||
}
|
||||
if (data.sort_order !== undefined) {
|
||||
db.prepare('UPDATE journey_entry_photos SET sort_order = ? WHERE journey_photo_id = ?').run(data.sort_order, photoId);
|
||||
}
|
||||
|
||||
values.push(photoId);
|
||||
db.prepare(`UPDATE journey_photos SET ${fields.join(', ')} WHERE id = ?`).run(...values);
|
||||
return db.prepare(`SELECT ${JP_SELECT} FROM ${JP_JOIN} WHERE gp.id = ? LIMIT 1`).get(photoId) as JourneyPhoto | null;
|
||||
}
|
||||
|
||||
// deletePhoto: hard-delete (backwards compat name used by old route).
|
||||
export function deletePhoto(photoId: number, userId: number): { photo_id: number; file_path?: string | null; journey_id: number } | null {
|
||||
export function deletePhoto(photoId: number, userId: number): { id: number; photo_id: number; file_path?: string | null; journey_id: number } | null {
|
||||
const row = db.prepare('SELECT id, journey_id, photo_id FROM journey_photos WHERE id = ?').get(photoId) as { id: number; journey_id: number; photo_id: number } | undefined;
|
||||
if (!row) return null;
|
||||
if (!canEdit(row.journey_id, userId)) return null;
|
||||
@@ -806,7 +804,7 @@ export function deletePhoto(photoId: number, userId: number): { photo_id: number
|
||||
db.prepare('DELETE FROM journey_photos WHERE id = ?').run(photoId);
|
||||
deleteTrekPhotoIfOrphan(row.photo_id);
|
||||
|
||||
return { photo_id: row.photo_id, file_path: trekRow?.file_path ?? null, journey_id: row.journey_id };
|
||||
return { id: row.id, photo_id: row.photo_id, file_path: trekRow?.file_path ?? null, journey_id: row.journey_id };
|
||||
}
|
||||
|
||||
// ── Contributors ─────────────────────────────────────────────────────────
|
||||
|
||||
@@ -649,7 +649,7 @@ describe('Link photo to entry', () => {
|
||||
.send({});
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toBe('photo_id required');
|
||||
expect(res.body.error).toBe('journey_photo_id required');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1325,9 +1325,10 @@ describe('Edge cases', () => {
|
||||
const result = deleteEntry(entry.id, user.id);
|
||||
expect(result).toBe(true);
|
||||
|
||||
// Photo should be deleted with the entry
|
||||
const deletedPhoto = testDb.prepare('SELECT * FROM journey_photos WHERE id = ?').get(photo!.id) as any;
|
||||
expect(deletedPhoto).toBeUndefined();
|
||||
// Junction row must be gone (ON DELETE CASCADE from journey_entries).
|
||||
// Gallery row (journey_photos) is preserved — photo may belong to other entries.
|
||||
const junctionRow = testDb.prepare('SELECT * FROM journey_entry_photos WHERE entry_id = ?').get(entry.id) as any;
|
||||
expect(junctionRow).toBeUndefined();
|
||||
});
|
||||
|
||||
it('JOURNEY-SVC-082: updateJourney can set cover_gradient', () => {
|
||||
@@ -1395,17 +1396,12 @@ describe('Edge cases', () => {
|
||||
|
||||
addTripToJourney(journey.id, trip.id, user.id);
|
||||
|
||||
// Should have a [Trip Photos] entry with the imported photo
|
||||
const photoEntry = testDb.prepare(
|
||||
"SELECT * FROM journey_entries WHERE journey_id = ? AND title = '[Trip Photos]'"
|
||||
).get(journey.id) as any;
|
||||
expect(photoEntry).toBeDefined();
|
||||
|
||||
// Trip photos now go straight into the journey gallery (no wrapper entry).
|
||||
const photos = testDb.prepare(`
|
||||
SELECT jp.*, tkp.asset_id FROM journey_photos jp
|
||||
JOIN trek_photos tkp ON tkp.id = jp.photo_id
|
||||
WHERE jp.entry_id = ?
|
||||
`).all(photoEntry.id);
|
||||
WHERE jp.journey_id = ?
|
||||
`).all(journey.id);
|
||||
expect(photos.length).toBe(1);
|
||||
expect((photos[0] as any).asset_id).toBe('immich-photo-1');
|
||||
});
|
||||
|
||||
@@ -58,7 +58,7 @@ afterAll(() => {
|
||||
|
||||
// -- Helpers ------------------------------------------------------------------
|
||||
|
||||
/** Insert a trek_photos + journey_photos row and return the trek_photos id (used as photoId in public URLs). */
|
||||
/** Insert a trek_photos + journey_photos (gallery) + journey_entry_photos row and return the trek_photos id (used as photoId in public URLs). */
|
||||
function insertJourneyPhoto(
|
||||
entryId: number,
|
||||
opts: { filePath?: string; assetId?: string; ownerId?: number } = {}
|
||||
@@ -70,10 +70,24 @@ function insertJourneyPhoto(
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`).run(provider, opts.assetId ?? null, filePath, opts.ownerId ?? null, Date.now());
|
||||
const trekId = trekResult.lastInsertRowid as number;
|
||||
|
||||
// Look up journey_id from entry so gallery row is keyed to the journey (not entry).
|
||||
const entryRow = testDb.prepare('SELECT journey_id FROM journey_entries WHERE id = ?').get(entryId) as { journey_id: number };
|
||||
const journeyId = entryRow.journey_id;
|
||||
const now = Date.now();
|
||||
|
||||
testDb.prepare(`
|
||||
INSERT INTO journey_photos (entry_id, photo_id, caption, sort_order, created_at)
|
||||
INSERT OR IGNORE INTO journey_photos (journey_id, photo_id, caption, sort_order, created_at)
|
||||
VALUES (?, ?, NULL, 0, ?)
|
||||
`).run(entryId, trekId, Date.now());
|
||||
`).run(journeyId, trekId, now);
|
||||
|
||||
const galleryRow = testDb.prepare('SELECT id FROM journey_photos WHERE journey_id = ? AND photo_id = ?').get(journeyId, trekId) as { id: number };
|
||||
|
||||
testDb.prepare(`
|
||||
INSERT OR IGNORE INTO journey_entry_photos (entry_id, journey_photo_id, sort_order, created_at)
|
||||
VALUES (?, ?, 0, ?)
|
||||
`).run(entryId, galleryRow.id, now);
|
||||
|
||||
// Return trek_photos.id — this is p.photo_id in the public API response
|
||||
// and the value the client sends to /api/public/journey/:token/photos/:photoId/:kind
|
||||
return trekId;
|
||||
|
||||
Reference in New Issue
Block a user