mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 22:31:46 +00:00
Fix ghost Gallery entries in journal timeline and public share
- deleteEntry now deletes photos with the entry instead of moving them to a hidden Gallery entry that becomes an undeletable ghost - deletePhoto cleans up empty Gallery entries after last photo removed - getJourneyFull and getPublicJourney filter out empty Gallery entries so existing ghosts are hidden in both internal and shared views
This commit is contained in:
@@ -114,15 +114,21 @@ export function getJourneyFull(journeyId: number, userId: number) {
|
|||||||
(photosByEntry[p.entry_id] ||= []).push(p);
|
(photosByEntry[p.entry_id] ||= []).push(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
const enrichedEntries = entries.map(e => ({
|
const enrichedEntries = entries
|
||||||
...e,
|
.filter(e => {
|
||||||
tags: e.tags ? JSON.parse(e.tags) : [],
|
// hide empty Gallery entries (no photos, no story)
|
||||||
pros_cons: e.pros_cons ? JSON.parse(e.pros_cons) : null,
|
if (e.title === 'Gallery' && !e.story && !(photosByEntry[e.id]?.length)) return false;
|
||||||
photos: photosByEntry[e.id] || [],
|
return true;
|
||||||
source_trip_name: e.source_trip_id
|
})
|
||||||
? (db.prepare('SELECT title FROM trips WHERE id = ?').get(e.source_trip_id) as { title: string } | undefined)?.title || null
|
.map(e => ({
|
||||||
: null,
|
...e,
|
||||||
}));
|
tags: e.tags ? JSON.parse(e.tags) : [],
|
||||||
|
pros_cons: e.pros_cons ? JSON.parse(e.pros_cons) : null,
|
||||||
|
photos: photosByEntry[e.id] || [],
|
||||||
|
source_trip_name: e.source_trip_id
|
||||||
|
? (db.prepare('SELECT title FROM trips WHERE id = ?').get(e.source_trip_id) as { title: string } | undefined)?.title || null
|
||||||
|
: null,
|
||||||
|
}));
|
||||||
|
|
||||||
// linked trips
|
// linked trips
|
||||||
const trips = db.prepare(`
|
const trips = db.prepare(`
|
||||||
@@ -552,24 +558,16 @@ export function deleteEntry(entryId: number, userId: number): boolean {
|
|||||||
if (!entry) return false;
|
if (!entry) return false;
|
||||||
if (!canEdit(entry.journey_id, userId)) return false;
|
if (!canEdit(entry.journey_id, userId)) return false;
|
||||||
|
|
||||||
// move photos to hidden Gallery entry so they stay in the gallery
|
// delete photos along with the entry — no more orphan Gallery entries
|
||||||
const hasPhotos = db.prepare('SELECT 1 FROM journey_photos WHERE entry_id = ?').get(entryId);
|
db.prepare('DELETE FROM journey_photos WHERE entry_id = ?').run(entryId);
|
||||||
if (hasPhotos) {
|
|
||||||
let gallery = db.prepare(
|
|
||||||
"SELECT id FROM journey_entries WHERE journey_id = ? AND title = 'Gallery' AND id != ?"
|
|
||||||
).get(entry.journey_id, entryId) as { id: number } | undefined;
|
|
||||||
if (!gallery) {
|
|
||||||
const now = ts();
|
|
||||||
const res = db.prepare(`
|
|
||||||
INSERT INTO journey_entries (journey_id, author_id, type, title, entry_date, sort_order, created_at, updated_at)
|
|
||||||
VALUES (?, ?, 'entry', 'Gallery', ?, 999, ?, ?)
|
|
||||||
`).run(entry.journey_id, entry.author_id, entry.entry_date, now, now);
|
|
||||||
gallery = { id: Number(res.lastInsertRowid) };
|
|
||||||
}
|
|
||||||
db.prepare('UPDATE journey_photos SET entry_id = ? WHERE entry_id = ?').run(gallery.id, entryId);
|
|
||||||
}
|
|
||||||
|
|
||||||
db.prepare('DELETE FROM journey_entries WHERE id = ?').run(entryId);
|
db.prepare('DELETE FROM journey_entries WHERE id = ?').run(entryId);
|
||||||
|
|
||||||
|
// clean up any empty Gallery entries in this journey
|
||||||
|
db.prepare(`
|
||||||
|
DELETE FROM journey_entries WHERE journey_id = ? AND title = 'Gallery'
|
||||||
|
AND id NOT IN (SELECT DISTINCT entry_id FROM journey_photos)
|
||||||
|
`).run(entry.journey_id);
|
||||||
|
|
||||||
broadcastJourneyEvent(entry.journey_id, 'journey:entry:deleted', { entryId }, userId);
|
broadcastJourneyEvent(entry.journey_id, 'journey:entry:deleted', { entryId }, userId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -673,6 +671,16 @@ export function deletePhoto(photoId: number, userId: number): (JourneyPhoto & {
|
|||||||
if (!canEdit(photo.journey_id, userId)) return null;
|
if (!canEdit(photo.journey_id, userId)) return null;
|
||||||
|
|
||||||
db.prepare('DELETE FROM journey_photos WHERE id = ?').run(photoId);
|
db.prepare('DELETE FROM journey_photos WHERE id = ?').run(photoId);
|
||||||
|
|
||||||
|
// clean up empty Gallery entries left behind
|
||||||
|
const remaining = db.prepare('SELECT 1 FROM journey_photos WHERE entry_id = ?').get(photo.entry_id);
|
||||||
|
if (!remaining) {
|
||||||
|
const entry = db.prepare('SELECT * FROM journey_entries WHERE id = ?').get(photo.entry_id) as JourneyEntry | undefined;
|
||||||
|
if (entry && entry.title === 'Gallery' && !entry.story) {
|
||||||
|
db.prepare('DELETE FROM journey_entries WHERE id = ?').run(photo.entry_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return photo;
|
return photo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,12 +111,18 @@ export function getPublicJourney(token: string) {
|
|||||||
(photosByEntry[p.entry_id] ||= []).push(p);
|
(photosByEntry[p.entry_id] ||= []).push(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
const enrichedEntries = entries.map(e => ({
|
const enrichedEntries = entries
|
||||||
...e,
|
.filter(e => {
|
||||||
tags: e.tags ? JSON.parse(e.tags) : [],
|
// hide empty Gallery entries (no photos, no story)
|
||||||
pros_cons: e.pros_cons ? JSON.parse(e.pros_cons) : null,
|
if (e.title === 'Gallery' && !e.story && !(photosByEntry[e.id]?.length)) return false;
|
||||||
photos: photosByEntry[e.id] || [],
|
return true;
|
||||||
}));
|
})
|
||||||
|
.map(e => ({
|
||||||
|
...e,
|
||||||
|
tags: e.tags ? JSON.parse(e.tags) : [],
|
||||||
|
pros_cons: e.pros_cons ? JSON.parse(e.pros_cons) : null,
|
||||||
|
photos: photosByEntry[e.id] || [],
|
||||||
|
}));
|
||||||
|
|
||||||
// Stats
|
// Stats
|
||||||
const stats = {
|
const stats = {
|
||||||
|
|||||||
Reference in New Issue
Block a user