mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-22 06:41:46 +00:00
c0c59b6d80
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
57 lines
2.4 KiB
TypeScript
57 lines
2.4 KiB
TypeScript
import express, { Request, Response } from 'express';
|
|
import { getPublicJourney, validateShareTokenForAsset, validateShareTokenForPhoto } from '../services/journeyShareService';
|
|
import { streamPhoto } from '../services/memories/photoResolverService';
|
|
import { streamImmichAsset } from '../services/memories/immichService';
|
|
import path from 'node:path';
|
|
import fs from 'node:fs';
|
|
|
|
const router = express.Router();
|
|
|
|
router.get('/:token', (req: Request, res: Response) => {
|
|
const data = getPublicJourney(req.params.token);
|
|
if (!data) return res.status(404).json({ error: 'Not found' });
|
|
res.json(data);
|
|
});
|
|
|
|
// Unified public photo proxy — uses trek_photo_id
|
|
router.get('/:token/photos/:photoId/:kind', async (req: Request, res: Response) => {
|
|
const { token, photoId, kind } = req.params;
|
|
const valid = validateShareTokenForPhoto(token, Number(photoId));
|
|
if (!valid) return res.status(404).json({ error: 'Not found' });
|
|
|
|
await streamPhoto(res, valid.ownerId, Number(photoId), kind === 'thumbnail' ? 'thumbnail' : 'original');
|
|
});
|
|
|
|
// Legacy public photo proxy — validates share token instead of auth
|
|
router.get('/:token/photo/:provider/:assetId/:ownerId/:kind', async (req: Request, res: Response) => {
|
|
const { token, provider, assetId, ownerId, kind } = req.params;
|
|
|
|
const valid = validateShareTokenForAsset(token, assetId);
|
|
if (!valid) return res.status(404).json({ error: 'Not found' });
|
|
|
|
if (provider === 'local') {
|
|
const filePath = path.join(__dirname, '../../uploads/journey', assetId);
|
|
const resolved = path.resolve(filePath);
|
|
const uploadsDir = path.resolve(__dirname, '../../uploads');
|
|
if (!resolved.startsWith(uploadsDir) || !fs.existsSync(resolved)) {
|
|
return res.status(404).json({ error: 'Not found' });
|
|
}
|
|
res.set('Cache-Control', 'public, max-age=86400');
|
|
return res.sendFile(resolved);
|
|
}
|
|
|
|
const effectiveOwnerId = valid.ownerId || Number(ownerId);
|
|
if (provider === 'immich') {
|
|
await streamImmichAsset(res, effectiveOwnerId, assetId, kind === 'thumbnail' ? 'thumbnail' : 'original', effectiveOwnerId);
|
|
} else {
|
|
try {
|
|
const { streamSynologyAsset } = await import('../services/memories/synologyService');
|
|
await streamSynologyAsset(res, effectiveOwnerId, effectiveOwnerId, assetId, kind === 'thumbnail' ? 'thumbnail' : 'original');
|
|
} catch {
|
|
res.status(404).json({ error: 'Provider not supported' });
|
|
}
|
|
}
|
|
});
|
|
|
|
export default router;
|