finishing refactor
This commit is contained in:
@@ -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));
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user