mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 14:21:46 +00:00
feat: unified photo provider abstraction layer (#584)
Introduce trek_photos as central photo registry. Frontend uses /api/photos/:id/:kind instead of provider-specific URLs. Adding a new photo provider is now backend-only work. - New trek_photos table (migration 98) with photo_id FK in trip_photos and journey_photos - Unified /api/photos/:id/thumbnail|original|info endpoint - photoResolverService for central resolution and streaming - ProviderPicker: add "All Photos" tab, rename tabs, fix i18n - Localize all hardcoded strings in JourneyDetailPage (14 langs) - Fix date formatting to use browser locale instead of hardcoded 'en' - Journey stats as styled tile cards
This commit is contained in:
@@ -1435,6 +1435,115 @@ function runMigrations(db: Database.Database): void {
|
||||
() => {
|
||||
try { db.exec("ALTER TABLE vacay_plans ADD COLUMN week_start INTEGER NOT NULL DEFAULT 1"); } catch {}
|
||||
},
|
||||
// Migration: Unified Photo Provider Abstraction Layer (#584)
|
||||
// Central trek_photos registry; trip_photos + journey_photos reference via photo_id
|
||||
() => {
|
||||
// 1. Create the central photo registry
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS trek_photos (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
provider TEXT NOT NULL,
|
||||
asset_id TEXT,
|
||||
owner_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
file_path TEXT,
|
||||
thumbnail_path TEXT,
|
||||
width INTEGER,
|
||||
height INTEGER,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
db.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_trek_photos_provider_asset ON trek_photos(provider, asset_id, owner_id) WHERE asset_id IS NOT NULL');
|
||||
db.exec('CREATE INDEX IF NOT EXISTS idx_trek_photos_owner ON trek_photos(owner_id)');
|
||||
|
||||
// 2. Migrate trip_photos → trek_photos + photo_id FK
|
||||
const tripPhotosExists = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'trip_photos'").get();
|
||||
if (tripPhotosExists) {
|
||||
// Insert existing trip photo references into trek_photos (deduplicate by provider+asset_id+owner)
|
||||
db.exec(`
|
||||
INSERT OR IGNORE INTO trek_photos (provider, asset_id, owner_id, created_at)
|
||||
SELECT DISTINCT provider, asset_id, user_id, COALESCE(added_at, CURRENT_TIMESTAMP)
|
||||
FROM trip_photos
|
||||
WHERE asset_id IS NOT NULL AND TRIM(asset_id) != ''
|
||||
`);
|
||||
|
||||
// Recreate trip_photos with photo_id FK
|
||||
db.exec(`
|
||||
CREATE TABLE trip_photos_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
trip_id INTEGER NOT NULL REFERENCES trips(id) ON DELETE CASCADE,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
photo_id INTEGER NOT NULL REFERENCES trek_photos(id) ON DELETE CASCADE,
|
||||
shared INTEGER NOT NULL DEFAULT 1,
|
||||
album_link_id INTEGER REFERENCES trip_album_links(id) ON DELETE SET NULL,
|
||||
added_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(trip_id, user_id, photo_id)
|
||||
)
|
||||
`);
|
||||
db.exec(`
|
||||
INSERT OR IGNORE INTO trip_photos_new (trip_id, user_id, photo_id, shared, album_link_id, added_at)
|
||||
SELECT tp.trip_id, tp.user_id, tkp.id, tp.shared, tp.album_link_id, tp.added_at
|
||||
FROM trip_photos tp
|
||||
JOIN trek_photos tkp ON tkp.provider = tp.provider AND tkp.asset_id = tp.asset_id AND tkp.owner_id = tp.user_id
|
||||
`);
|
||||
db.exec('DROP TABLE trip_photos');
|
||||
db.exec('ALTER TABLE trip_photos_new RENAME TO trip_photos');
|
||||
db.exec('CREATE INDEX IF NOT EXISTS idx_trip_photos_trip ON trip_photos(trip_id)');
|
||||
db.exec('CREATE INDEX IF NOT EXISTS idx_trip_photos_photo ON trip_photos(photo_id)');
|
||||
}
|
||||
|
||||
// 3. Migrate journey_photos → trek_photos + photo_id FK
|
||||
const journeyPhotosExists = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'journey_photos'").get();
|
||||
if (journeyPhotosExists) {
|
||||
// Insert provider-based journey photos into trek_photos
|
||||
db.exec(`
|
||||
INSERT OR IGNORE INTO trek_photos (provider, asset_id, owner_id, width, height, created_at)
|
||||
SELECT DISTINCT provider, asset_id, owner_id, width, height, created_at
|
||||
FROM journey_photos
|
||||
WHERE provider != 'local' AND asset_id IS NOT NULL AND TRIM(asset_id) != ''
|
||||
`);
|
||||
// Insert local journey photos into trek_photos (each is unique)
|
||||
db.exec(`
|
||||
INSERT INTO trek_photos (provider, file_path, thumbnail_path, width, height, created_at)
|
||||
SELECT 'local', file_path, thumbnail_path, width, height, created_at
|
||||
FROM journey_photos
|
||||
WHERE provider = 'local' AND file_path IS NOT NULL
|
||||
`);
|
||||
|
||||
// Recreate journey_photos with photo_id FK
|
||||
db.exec(`
|
||||
CREATE TABLE journey_photos_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
entry_id INTEGER NOT NULL,
|
||||
photo_id INTEGER NOT NULL REFERENCES trek_photos(id) ON DELETE CASCADE,
|
||||
caption TEXT,
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
shared INTEGER NOT NULL DEFAULT 1,
|
||||
created_at INTEGER NOT NULL,
|
||||
FOREIGN KEY (entry_id) REFERENCES journey_entries(id) ON DELETE CASCADE
|
||||
)
|
||||
`);
|
||||
// Migrate provider photos
|
||||
db.exec(`
|
||||
INSERT INTO journey_photos_new (entry_id, photo_id, caption, sort_order, shared, created_at)
|
||||
SELECT jp.entry_id, tkp.id, jp.caption, jp.sort_order, jp.shared, jp.created_at
|
||||
FROM journey_photos jp
|
||||
JOIN trek_photos tkp ON tkp.provider = jp.provider AND tkp.asset_id = jp.asset_id AND tkp.owner_id = jp.owner_id
|
||||
WHERE jp.provider != 'local' AND jp.asset_id IS NOT NULL
|
||||
`);
|
||||
// Migrate local photos (match by file_path)
|
||||
db.exec(`
|
||||
INSERT INTO journey_photos_new (entry_id, photo_id, caption, sort_order, shared, created_at)
|
||||
SELECT jp.entry_id, tkp.id, jp.caption, jp.sort_order, jp.shared, jp.created_at
|
||||
FROM journey_photos jp
|
||||
JOIN trek_photos tkp ON tkp.provider = 'local' AND tkp.file_path = jp.file_path
|
||||
WHERE jp.provider = 'local' AND jp.file_path IS NOT NULL
|
||||
`);
|
||||
db.exec('DROP TABLE journey_photos');
|
||||
db.exec('ALTER TABLE journey_photos_new RENAME TO journey_photos');
|
||||
db.exec('CREATE INDEX IF NOT EXISTS idx_journey_photos_entry ON journey_photos(entry_id)');
|
||||
db.exec('CREATE INDEX IF NOT EXISTS idx_journey_photos_photo ON journey_photos(photo_id)');
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
if (currentVersion < migrations.length) {
|
||||
|
||||
Reference in New Issue
Block a user