diff --git a/client/src/components/Memories/MemoriesPanel.tsx b/client/src/components/Memories/MemoriesPanel.tsx index 8787dd7..4614f6d 100644 --- a/client/src/components/Memories/MemoriesPanel.tsx +++ b/client/src/components/Memories/MemoriesPanel.tsx @@ -81,7 +81,6 @@ export default function MemoriesPanel({ tripId, startDate, endDate }: MemoriesPa const [albumsLoading, setAlbumsLoading] = useState(false) const [albumLinks, setAlbumLinks] = useState<{ id: number; provider: string; album_id: string; album_name: string; user_id: number; username: string; sync_enabled: number; last_synced_at: string | null }[]>([]) const [syncing, setSyncing] = useState(null) - const pickerIntegrationBase = selectedProvider ? `/integrations/${selectedProvider}` : '' const loadAlbumLinks = async () => { try { @@ -110,8 +109,17 @@ export default function MemoriesPanel({ tripId, startDate, endDate }: MemoriesPa } const linkAlbum = async (albumId: string, albumName: string) => { + if (!selectedProvider) { + toast.error(t('memories.error.linkAlbum')) + return + } + try { - await apiClient.post(`${pickerIntegrationBase}/trips/${tripId}/album-links`, { album_id: albumId, album_name: albumName }) + await apiClient.post(`/integrations/memories/trips/${tripId}/album-links`, { + album_id: albumId, + album_name: albumName, + provider: selectedProvider, + }) setShowAlbumPicker(false) await loadAlbumLinks() // Auto-sync after linking diff --git a/server/src/routes/immich.ts b/server/src/routes/immich.ts index 31391f4..4225367 100644 --- a/server/src/routes/immich.ts +++ b/server/src/routes/immich.ts @@ -16,7 +16,6 @@ import { proxyOriginal, isValidAssetId, listAlbums, - createAlbumLink, syncAlbumAssets, getAssetInfo, } from '../services/immichService'; @@ -125,17 +124,6 @@ router.get('/albums', authenticate, async (req: Request, res: Response) => { res.json({ albums: result.albums }); }); -router.post('/trips/:tripId/album-links', authenticate, async (req: Request, res: Response) => { - const authReq = req as AuthRequest; - const { tripId } = req.params; - if (!canAccessTrip(tripId, authReq.user.id)) return res.status(404).json({ error: 'Trip not found' }); - const { album_id, album_name } = req.body; - if (!album_id) return res.status(400).json({ error: 'album_id required' }); - const result = createAlbumLink(tripId, authReq.user.id, album_id, album_name); - if (!result.success) return res.status(400).json({ error: result.error }); - res.json({ success: true }); -}); - router.post('/trips/:tripId/album-links/:linkId/sync', authenticate, async (req: Request, res: Response) => { const authReq = req as AuthRequest; const { tripId, linkId } = req.params; diff --git a/server/src/routes/memories.ts b/server/src/routes/memories.ts index 6bd6e6f..5a6f1cf 100644 --- a/server/src/routes/memories.ts +++ b/server/src/routes/memories.ts @@ -5,6 +5,7 @@ import { AuthRequest } from '../types'; import { listTripPhotos, listTripAlbumLinks, + createTripAlbumLink, removeAlbumLink, addTripPhotos, removeTripPhoto, @@ -90,4 +91,12 @@ router.put('/trips/:tripId/photos/sharing', authenticate, (req: Request, res: Re broadcast(tripId, 'memories:updated', { userId: authReq.user.id }, req.headers['x-socket-id'] as string); }); +router.post('/trips/:tripId/album-links', authenticate, (req: Request, res: Response) => { + const authReq = req as AuthRequest; + const { tripId } = req.params; + const result = createTripAlbumLink(tripId, authReq.user.id, req.body?.provider, req.body?.album_id, req.body?.album_name); + if ('error' in result) return res.status(result.status).json({ error: result.error }); + res.json({ success: true }); +}); + export default router; diff --git a/server/src/routes/synology.ts b/server/src/routes/synology.ts index 9b26acc..f5f7203 100644 --- a/server/src/routes/synology.ts +++ b/server/src/routes/synology.ts @@ -86,25 +86,6 @@ router.get('/albums', authenticate, async (req: Request, res: Response) => { } }); -router.post('/trips/:tripId/album-links', authenticate, (req: Request, res: Response) => { - const authReq = req as AuthRequest; - const { tripId } = req.params; - const body = req.body as Record; - const albumId = parseStringBodyField(body.album_id); - const albumName = parseStringBodyField(body.album_name); - - if (!albumId) { - return handleSynologyError(res, new SynologyServiceError(400, 'Album ID is required'), 'Missing required fields'); - } - - try { - linkSynologyAlbum(authReq.user.id, tripId, albumId, albumName || undefined); - res.json({ success: true }); - } catch (err: unknown) { - handleSynologyError(res, err, 'Failed to link album'); - } -}); - router.post('/trips/:tripId/album-links/:linkId/sync', authenticate, async (req: Request, res: Response) => { const authReq = req as AuthRequest; const { tripId, linkId } = req.params; diff --git a/server/src/services/immichService.ts b/server/src/services/immichService.ts index eb563cd..9d8e9c3 100644 --- a/server/src/services/immichService.ts +++ b/server/src/services/immichService.ts @@ -285,22 +285,6 @@ export async function listAlbums( } -export function createAlbumLink( - tripId: string, - userId: number, - albumId: string, - albumName: string -): { success: boolean; error?: string } { - try { - db.prepare( - 'INSERT OR IGNORE INTO trip_album_links (trip_id, user_id, provider, album_id, album_name) VALUES (?, ?, ?, ?, ?)' - ).run(tripId, userId, 'immich', albumId, albumName || ''); - return { success: true }; - } catch { - return { success: false, error: 'Album already linked' }; - } -} - export async function syncAlbumAssets( tripId: string, linkId: string, diff --git a/server/src/services/memoriesService.ts b/server/src/services/memoriesService.ts index eb8acc3..5fdfbce 100644 --- a/server/src/services/memoriesService.ts +++ b/server/src/services/memoriesService.ts @@ -76,6 +76,37 @@ export function listTripAlbumLinks(tripId: string, userId: number): { links: any return { links }; } +export function createTripAlbumLink( + tripId: string, + userId: number, + providerRaw: unknown, + albumIdRaw: unknown, + albumNameRaw: unknown, +): { success: true } | ServiceError { + const denied = accessDeniedIfMissing(tripId, userId); + if (denied) return denied; + + const provider = String(providerRaw || '').toLowerCase(); + const albumId = String(albumIdRaw || '').trim(); + const albumName = String(albumNameRaw || '').trim(); + + if (!provider) { + return { error: 'provider is required', status: 400 }; + } + if (!albumId) { + return { error: 'album_id required', status: 400 }; + } + + try { + db.prepare( + 'INSERT OR IGNORE INTO trip_album_links (trip_id, user_id, provider, album_id, album_name) VALUES (?, ?, ?, ?, ?)' + ).run(tripId, userId, provider, albumId, albumName); + return { success: true }; + } catch { + return { error: 'Album already linked', status: 400 }; + } +} + export function removeAlbumLink(tripId: string, linkId: string, userId: number): { success: true } | ServiceError { const denied = accessDeniedIfMissing(tripId, userId); if (denied) return denied; diff --git a/server/src/services/synologyService.ts b/server/src/services/synologyService.ts index 1c25ef5..7b3182e 100644 --- a/server/src/services/synologyService.ts +++ b/server/src/services/synologyService.ts @@ -438,23 +438,6 @@ export async function listSynologyAlbums(userId: number): Promise<{ albums: Arra return { albums }; } -export function linkSynologyAlbum(userId: number, tripId: string, albumId: string | number | undefined, albumName?: string): void { - if (!canAccessTrip(tripId, userId)) { - throw new SynologyServiceError(404, 'Trip not found'); - } - - if (!albumId) { - throw new SynologyServiceError(400, 'album_id required'); - } - - const changes = db.prepare( - 'INSERT OR IGNORE INTO trip_album_links (trip_id, user_id, provider, album_id, album_name) VALUES (?, ?, ?, ?, ?)' - ).run(tripId, userId, SYNOLOGY_PROVIDER, String(albumId), albumName || '').changes; - - if (changes === 0) { - throw new SynologyServiceError(400, 'Album already linked'); - } -} export async function syncSynologyAlbumLink(userId: number, tripId: string, linkId: string): Promise<{ added: number; total: number }> { const link = db.prepare(`SELECT * FROM trip_album_links WHERE id = ? AND trip_id = ? AND user_id = ? AND provider = ?`)