finishing refactor

This commit is contained in:
Marek Maslowski
2026-04-04 18:16:46 +02:00
parent 8c125738e8
commit 3d0249e076
3 changed files with 77 additions and 85 deletions

View File

@@ -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) => { router.get('/assets/:tripId/:photoId/:ownerId/:kind', authenticate, async (req: Request, res: Response) => {
const authReq = req as AuthRequest; const authReq = req as AuthRequest;
const { tripId, photoId, ownerId, kind } = req.params; const { tripId, photoId, ownerId, kind } = req.params;
const { size = 'sm' } = req.query; const { size = "sm" } = req.query;
if (kind !== 'thumbnail' && kind !== 'original') { if (kind !== 'thumbnail' && kind !== 'original') {
handleServiceResult(res, fail('Invalid asset kind', 400)); handleServiceResult(res, fail('Invalid asset kind', 400));

View File

@@ -32,7 +32,6 @@ export function handleServiceResult<T>(res: Response, result: ServiceResult<T>):
res.status(result.error.status).json({ error: result.error.message }); res.status(result.error.status).json({ error: result.error.message });
} }
else { else {
console.log('Service result data:', result.data);
res.json(result.data); res.json(result.data);
} }
} }
@@ -52,22 +51,51 @@ export type StatusResult = {
error: string error: string
}; };
export type SyncAlbumResult = {
added: number;
total: number
};
export type AlbumsList = { export type AlbumsList = {
albums: Array<{ id: string; albumName: string; assetCount: number }> albums: Array<{ id: string; albumName: string; assetCount: number }>
}; };
export type AssetInfo = { export type Asset = {
id: string; id: string;
takenAt: string; takenAt: string;
}; };
export type AssetsList = { export type AssetsList = {
assets: AssetInfo[], assets: Asset[],
total: number, total: number,
hasMore: boolean 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 //for loading routes to settings page, and validating which services user has connected
type PhotoProviderConfig = { type PhotoProviderConfig = {
settings_get: string; settings_get: string;
@@ -134,19 +162,25 @@ export function updateSyncTimeForAlbumLink(linkId: string): void {
} }
export async function pipeAsset(url: string, response: Response): Promise<void> { export async function pipeAsset(url: string, response: Response): Promise<void> {
const resp = await fetch(url); 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); response.status(resp.status);
if (resp.headers.get('cache-control')) response.set('Cache-Control', resp.headers.get('cache-control') as string); if (resp.headers.get('content-type')) response.set('Content-Type', resp.headers.get('content-type') as string);
if (resp.headers.get('content-length')) response.set('Content-Length', resp.headers.get('content-length') as string); if (resp.headers.get('cache-control')) response.set('Cache-Control', resp.headers.get('cache-control') as string);
if (resp.headers.get('content-disposition')) response.set('Content-Disposition', resp.headers.get('content-disposition') 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(); if (!resp.body) {
response.end();
}
else {
pipeline(Readable.fromWeb(resp.body), response);
}
} }
else { catch (error) {
pipeline(Readable.fromWeb(resp.body), response); response.status(500).json({ error: 'Failed to fetch asset' });
response.end();
} }
} }

View File

@@ -15,13 +15,20 @@ import {
pipeAsset, pipeAsset,
AlbumsList, AlbumsList,
AssetsList, AssetsList,
StatusResult StatusResult,
SyncAlbumResult,
AssetInfo
} from './helpersService'; } from './helpersService';
const SYNOLOGY_API_TIMEOUT_MS = 30000;
const SYNOLOGY_PROVIDER = 'synologyphotos'; const SYNOLOGY_PROVIDER = 'synologyphotos';
const SYNOLOGY_ENDPOINT_PATH = '/photo/webapi/entry.cgi'; 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 { interface SynologyCredentials {
synology_url: string; synology_url: string;
@@ -29,6 +36,12 @@ interface SynologyCredentials {
synology_password: string; synology_password: string;
} }
interface SynologySettings {
synology_url: string;
synology_username: string;
connected: boolean;
}
interface ApiCallParams { interface ApiCallParams {
api: string; api: string;
method: string; method: string;
@@ -42,53 +55,6 @@ interface SynologyApiResponse<T> {
error?: { code: number }; 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<string, string | null>;
body: ReadableStream<Uint8Array> | 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 { interface SynologyPhotoItem {
id?: string | number; 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<SynologyUserRecord> { function _readSynologyUser(userId: number, columns: string[]): ServiceResult<SynologyUserRecord> {
try { try {
@@ -182,7 +142,7 @@ async function _fetchSynologyJson<T>(url: string, body: URLSearchParams): Promis
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
}, },
body, body,
signal: AbortSignal.timeout(SYNOLOGY_API_TIMEOUT_MS), signal: AbortSignal.timeout(30000),
}); });
if (!resp.ok) { if (!resp.ok) {
@@ -238,7 +198,7 @@ async function _requestSynologyApi<T>(userId: number, params: ApiCallParams): Pr
return result; return result;
} }
function _normalizeSynologyPhotoInfo(item: SynologyPhotoItem): SynologyPhotoInfo { function _normalizeSynologyPhotoInfo(item: SynologyPhotoItem): AssetInfo {
const address = item.additional?.address || {}; const address = item.additional?.address || {};
const exif = item.additional?.exif || {}; const exif = item.additional?.exif || {};
const gps = item.additional?.gps || {}; const gps = item.additional?.gps || {};
@@ -259,8 +219,6 @@ function _normalizeSynologyPhotoInfo(item: SynologyPhotoItem): SynologyPhotoInfo
lng: gps.longitude || null, lng: gps.longitude || null,
orientation: item.additional?.orientation || null, orientation: item.additional?.orientation || null,
description: item.additional?.description || null, description: item.additional?.description || null,
filename: item.filename || null,
filesize: item.filesize || null,
width: item.additional?.resolution?.width || null, width: item.additional?.resolution?.width || null,
height: item.additional?.resolution?.height || null, height: item.additional?.resolution?.height || null,
fileSize: item.filesize || null, fileSize: item.filesize || null,
@@ -390,9 +348,9 @@ export async function listSynologyAlbums(userId: number): Promise<ServiceResult<
} }
export async function syncSynologyAlbumLink(userId: number, tripId: string, linkId: string): Promise<ServiceResult<{ added: number; total: number }>> { export async function syncSynologyAlbumLink(userId: number, tripId: string, linkId: string): Promise<ServiceResult<SyncAlbumResult>> {
const response = getAlbumIdFromLink(tripId, linkId, userId); const response = getAlbumIdFromLink(tripId, linkId, userId);
if (!response.success) return response as ServiceResult<{ added: number; total: number }>; if (!response.success) return response as ServiceResult<SyncAlbumResult>;
const allItems: SynologyPhotoItem[] = []; const allItems: SynologyPhotoItem[] = [];
const pageSize = 1000; const pageSize = 1000;
@@ -409,7 +367,7 @@ export async function syncSynologyAlbumLink(userId: number, tripId: string, link
additional: ['thumbnail'], additional: ['thumbnail'],
}); });
if (!result.success) return result as ServiceResult<{ added: number; total: number }>; if (!result.success) return result as ServiceResult<SyncAlbumResult>;
const items = result.data.list || []; const items = result.data.list || [];
allItems.push(...items); allItems.push(...items);
@@ -425,7 +383,7 @@ export async function syncSynologyAlbumLink(userId: number, tripId: string, link
updateSyncTimeForAlbumLink(linkId); updateSyncTimeForAlbumLink(linkId);
const result = await addTripPhotos(tripId, userId, true, [selection]); 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<SyncAlbumResult>;
return success({ added: result.data.added, total: allItems.length }); 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); 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 allItems = result.data.list || [];
const total = allItems.length; 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<ServiceResult<SynologyPhotoInfo>> { export async function getSynologyAssetInfo(userId: number, photoId: string, targetUserId?: number): Promise<ServiceResult<AssetInfo>> {
const parsedId = _splitPackedSynologyId(photoId); const parsedId = _splitPackedSynologyId(photoId);
const result = await _requestSynologyApi<{ list: SynologyPhotoItem[] }>(targetUserId, { const result = await _requestSynologyApi<{ list: SynologyPhotoItem[] }>(targetUserId, {
api: 'SYNO.Foto.Browse.Item', api: 'SYNO.Foto.Browse.Item',
method: 'get', method: 'get',
version: 5, 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'], additional: ['resolution', 'exif', 'gps', 'address', 'orientation', 'description'],
}); });
if (!result.success) return result as ServiceResult<SynologyPhotoInfo>; if (!result.success) return result as ServiceResult<AssetInfo>;
const metadata = result.data.list?.[0]; const metadata = result.data.list?.[0];
if (!metadata) return fail('Photo not found', 404); if (!metadata) return fail('Photo not found', 404);
@@ -518,7 +476,7 @@ export async function streamSynologyAsset(
mode: 'download', mode: 'download',
id: parsedId.id, id: parsedId.id,
type: 'unit', type: 'unit',
size: String(size || SYNOLOGY_DEFAULT_THUMBNAIL_SIZE), size: size,
cache_key: parsedId.cacheKey, cache_key: parsedId.cacheKey,
_sid: sid.data, _sid: sid.data,
}) })