diff --git a/server/src/routes/memories/synology.ts b/server/src/routes/memories/synology.ts index 3bce3e4..94ddd88 100644 --- a/server/src/routes/memories/synology.ts +++ b/server/src/routes/memories/synology.ts @@ -108,7 +108,7 @@ router.get('/assets/:tripId/:photoId/:ownerId/info', authenticate, async (req: R router.get('/assets/:tripId/:photoId/:ownerId/:kind', authenticate, async (req: Request, res: Response) => { const authReq = req as AuthRequest; const { tripId, photoId, ownerId, kind } = req.params; - const { size = 'sm' } = req.query; + const { size = "sm" } = req.query; if (kind !== 'thumbnail' && kind !== 'original') { handleServiceResult(res, fail('Invalid asset kind', 400)); diff --git a/server/src/services/memories/helpersService.ts b/server/src/services/memories/helpersService.ts index 59a673d..a349269 100644 --- a/server/src/services/memories/helpersService.ts +++ b/server/src/services/memories/helpersService.ts @@ -32,7 +32,6 @@ export function handleServiceResult(res: Response, result: ServiceResult): res.status(result.error.status).json({ error: result.error.message }); } else { - console.log('Service result data:', result.data); res.json(result.data); } } @@ -52,22 +51,51 @@ export type StatusResult = { error: string }; +export type SyncAlbumResult = { + added: number; + total: number +}; + + export type AlbumsList = { albums: Array<{ id: string; albumName: string; assetCount: number }> }; -export type AssetInfo = { +export type Asset = { id: string; takenAt: string; }; export type AssetsList = { - assets: AssetInfo[], + assets: Asset[], total: number, hasMore: boolean }; +export type AssetInfo = { + id: string; + takenAt: string | null; + city: string | null; + country: string | null; + state?: string | null; + camera?: string | null; + lens?: string | null; + focalLength?: string | number | null; + aperture?: string | number | null; + shutter?: string | number | null; + iso?: string | number | null; + lat?: number | null; + lng?: number | null; + orientation?: number | null; + description?: string | null; + width?: number | null; + height?: number | null; + fileSize?: number | null; + fileName?: string | null; +} + + //for loading routes to settings page, and validating which services user has connected type PhotoProviderConfig = { settings_get: string; @@ -134,19 +162,25 @@ export function updateSyncTimeForAlbumLink(linkId: string): void { } export async function pipeAsset(url: string, response: Response): Promise { - const resp = await fetch(url); - - response.status(resp.status); - if (resp.headers.get('content-type')) response.set('Content-Type', resp.headers.get('content-type') as string); - if (resp.headers.get('cache-control')) response.set('Cache-Control', resp.headers.get('cache-control') as string); - if (resp.headers.get('content-length')) response.set('Content-Length', resp.headers.get('content-length') as string); - if (resp.headers.get('content-disposition')) response.set('Content-Disposition', resp.headers.get('content-disposition') as string); - - if (!resp.body) { - response.end(); + try{ + const resp = await fetch(url); + + response.status(resp.status); + if (resp.headers.get('content-type')) response.set('Content-Type', resp.headers.get('content-type') as string); + if (resp.headers.get('cache-control')) response.set('Cache-Control', resp.headers.get('cache-control') as string); + if (resp.headers.get('content-length')) response.set('Content-Length', resp.headers.get('content-length') as string); + if (resp.headers.get('content-disposition')) response.set('Content-Disposition', resp.headers.get('content-disposition') as string); + + if (!resp.body) { + response.end(); + } + else { + pipeline(Readable.fromWeb(resp.body), response); + } } - else { - pipeline(Readable.fromWeb(resp.body), response); + catch (error) { + response.status(500).json({ error: 'Failed to fetch asset' }); + response.end(); } } \ No newline at end of file diff --git a/server/src/services/memories/synologyService.ts b/server/src/services/memories/synologyService.ts index a444c20..76b8916 100644 --- a/server/src/services/memories/synologyService.ts +++ b/server/src/services/memories/synologyService.ts @@ -15,13 +15,20 @@ import { pipeAsset, AlbumsList, AssetsList, - StatusResult + StatusResult, + SyncAlbumResult, + AssetInfo } from './helpersService'; -const SYNOLOGY_API_TIMEOUT_MS = 30000; const SYNOLOGY_PROVIDER = 'synologyphotos'; const SYNOLOGY_ENDPOINT_PATH = '/photo/webapi/entry.cgi'; -const SYNOLOGY_DEFAULT_THUMBNAIL_SIZE = 'sm'; + +interface SynologyUserRecord { + synology_url?: string | null; + synology_username?: string | null; + synology_password?: string | null; + synology_sid?: string | null; +}; interface SynologyCredentials { synology_url: string; @@ -29,6 +36,12 @@ interface SynologyCredentials { synology_password: string; } +interface SynologySettings { + synology_url: string; + synology_username: string; + connected: boolean; +} + interface ApiCallParams { api: string; method: string; @@ -42,53 +55,6 @@ interface SynologyApiResponse { error?: { code: number }; } -export interface SynologySettings { - synology_url: string; - synology_username: string; - connected: boolean; -} - -export interface SynologyAlbumLinkInput { - album_id?: string | number; - album_name?: string; -} - -export interface SynologySearchInput { - from?: string; - to?: string; - offset?: number; - limit?: number; -} - -export interface SynologyProxyResult { - status: number; - headers: Record; - body: ReadableStream | null; -} - -interface SynologyPhotoInfo { - id: string; - takenAt: string | null; - city: string | null; - country: string | null; - state?: string | null; - camera?: string | null; - lens?: string | null; - focalLength?: string | number | null; - aperture?: string | number | null; - shutter?: string | number | null; - iso?: string | number | null; - lat?: number | null; - lng?: number | null; - orientation?: number | null; - description?: string | null; - filename?: string | null; - filesize?: number | null; - width?: number | null; - height?: number | null; - fileSize?: number | null; - fileName?: string | null; -} interface SynologyPhotoItem { id?: string | number; @@ -115,12 +81,6 @@ interface SynologyPhotoItem { }; } -type SynologyUserRecord = { - synology_url?: string | null; - synology_username?: string | null; - synology_password?: string | null; - synology_sid?: string | null; -}; function _readSynologyUser(userId: number, columns: string[]): ServiceResult { try { @@ -182,7 +142,7 @@ async function _fetchSynologyJson(url: string, body: URLSearchParams): Promis 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', }, body, - signal: AbortSignal.timeout(SYNOLOGY_API_TIMEOUT_MS), + signal: AbortSignal.timeout(30000), }); if (!resp.ok) { @@ -238,7 +198,7 @@ async function _requestSynologyApi(userId: number, params: ApiCallParams): Pr return result; } -function _normalizeSynologyPhotoInfo(item: SynologyPhotoItem): SynologyPhotoInfo { +function _normalizeSynologyPhotoInfo(item: SynologyPhotoItem): AssetInfo { const address = item.additional?.address || {}; const exif = item.additional?.exif || {}; const gps = item.additional?.gps || {}; @@ -259,8 +219,6 @@ function _normalizeSynologyPhotoInfo(item: SynologyPhotoItem): SynologyPhotoInfo lng: gps.longitude || null, orientation: item.additional?.orientation || null, description: item.additional?.description || null, - filename: item.filename || null, - filesize: item.filesize || null, width: item.additional?.resolution?.width || null, height: item.additional?.resolution?.height || null, fileSize: item.filesize || null, @@ -390,9 +348,9 @@ export async function listSynologyAlbums(userId: number): Promise> { +export async function syncSynologyAlbumLink(userId: number, tripId: string, linkId: string): Promise> { const response = getAlbumIdFromLink(tripId, linkId, userId); - if (!response.success) return response as ServiceResult<{ added: number; total: number }>; + if (!response.success) return response as ServiceResult; const allItems: SynologyPhotoItem[] = []; const pageSize = 1000; @@ -409,7 +367,7 @@ export async function syncSynologyAlbumLink(userId: number, tripId: string, link additional: ['thumbnail'], }); - if (!result.success) return result as ServiceResult<{ added: number; total: number }>; + if (!result.success) return result as ServiceResult; const items = result.data.list || []; allItems.push(...items); @@ -425,7 +383,7 @@ export async function syncSynologyAlbumLink(userId: number, tripId: string, link updateSyncTimeForAlbumLink(linkId); const result = await addTripPhotos(tripId, userId, true, [selection]); - if (!result.success) return result as ServiceResult<{ added: number; total: number }>; + if (!result.success) return result as ServiceResult; return success({ added: result.data.added, total: allItems.length }); } @@ -451,7 +409,7 @@ export async function searchSynologyPhotos(userId: number, from?: string, to?: s } const result = await _requestSynologyApi<{ list: SynologyPhotoItem[]; total: number }>(userId, params); - if (!result.success) return result as ServiceResult<{ assets: SynologyPhotoInfo[]; total: number; hasMore: boolean }>; + if (!result.success) return result as ServiceResult<{ assets: AssetInfo[]; total: number; hasMore: boolean }>; const allItems = result.data.list || []; const total = allItems.length; @@ -464,17 +422,17 @@ export async function searchSynologyPhotos(userId: number, from?: string, to?: s }); } -export async function getSynologyAssetInfo(userId: number, photoId: string, targetUserId?: number): Promise> { +export async function getSynologyAssetInfo(userId: number, photoId: string, targetUserId?: number): Promise> { const parsedId = _splitPackedSynologyId(photoId); const result = await _requestSynologyApi<{ list: SynologyPhotoItem[] }>(targetUserId, { api: 'SYNO.Foto.Browse.Item', method: 'get', version: 5, - id: `[${parsedId.id}]`, + id: `[${Number(parsedId.id) + 1}]`, //for some reason synology wants id moved by one to get image info additional: ['resolution', 'exif', 'gps', 'address', 'orientation', 'description'], }); - if (!result.success) return result as ServiceResult; + if (!result.success) return result as ServiceResult; const metadata = result.data.list?.[0]; if (!metadata) return fail('Photo not found', 404); @@ -518,7 +476,7 @@ export async function streamSynologyAsset( mode: 'download', id: parsedId.id, type: 'unit', - size: String(size || SYNOLOGY_DEFAULT_THUMBNAIL_SIZE), + size: size, cache_key: parsedId.cacheKey, _sid: sid.data, })