fixes based on comment (missing api compatability and translation keys)
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
|||||||
} from '../../services/memories/unifiedService';
|
} from '../../services/memories/unifiedService';
|
||||||
import immichRouter from './immich';
|
import immichRouter from './immich';
|
||||||
import synologyRouter from './synology';
|
import synologyRouter from './synology';
|
||||||
|
import { Selection } from '../../services/memories/helpersService';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -33,13 +34,14 @@ router.post('/unified/trips/:tripId/photos', authenticate, async (req: Request,
|
|||||||
const authReq = req as AuthRequest;
|
const authReq = req as AuthRequest;
|
||||||
const { tripId } = req.params;
|
const { tripId } = req.params;
|
||||||
const sid = req.headers['x-socket-id'] as string;
|
const sid = req.headers['x-socket-id'] as string;
|
||||||
|
const selections: Selection[] = Array.isArray(req.body?.selections) ? req.body.selections : [];
|
||||||
|
|
||||||
const shared = req.body?.shared === undefined ? true : !!req.body?.shared;
|
const shared = req.body?.shared === undefined ? true : !!req.body?.shared;
|
||||||
const result = await addTripPhotos(
|
const result = await addTripPhotos(
|
||||||
tripId,
|
tripId,
|
||||||
authReq.user.id,
|
authReq.user.id,
|
||||||
shared,
|
shared,
|
||||||
req.body?.selections || [],
|
selections,
|
||||||
sid,
|
sid,
|
||||||
);
|
);
|
||||||
if ('error' in result) return res.status(result.error.status).json({ error: result.error.message });
|
if ('error' in result) return res.status(result.error.status).json({ error: result.error.message });
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Readable } from 'node:stream';
|
|||||||
import { pipeline } from 'node:stream/promises';
|
import { pipeline } from 'node:stream/promises';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { canAccessTrip, db } from "../../db/database";
|
import { canAccessTrip, db } from "../../db/database";
|
||||||
|
import { checkSsrf } from '../../utils/ssrfGuard';
|
||||||
|
|
||||||
// helpers for handling return types
|
// helpers for handling return types
|
||||||
|
|
||||||
@@ -163,6 +164,13 @@ 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> {
|
||||||
try{
|
try{
|
||||||
|
|
||||||
|
const SsrfResult = await checkSsrf(url);
|
||||||
|
if (!SsrfResult.allowed) {
|
||||||
|
response.status(400).json({ error: SsrfResult.error });
|
||||||
|
response.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const resp = await fetch(url);
|
const resp = await fetch(url);
|
||||||
|
|
||||||
response.status(resp.status);
|
response.status(resp.status);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { db } from '../../db/database';
|
import { db } from '../../db/database';
|
||||||
import { decrypt_api_key, maybe_encrypt_api_key } from '../apiKeyCrypto';
|
import { decrypt_api_key, encrypt_api_key, maybe_encrypt_api_key } from '../apiKeyCrypto';
|
||||||
import { checkSsrf } from '../../utils/ssrfGuard';
|
import { checkSsrf } from '../../utils/ssrfGuard';
|
||||||
import { addTripPhotos } from './unifiedService';
|
import { addTripPhotos } from './unifiedService';
|
||||||
import {
|
import {
|
||||||
@@ -136,6 +136,10 @@ function _buildSynologyFormBody(params: ApiCallParams): URLSearchParams {
|
|||||||
|
|
||||||
async function _fetchSynologyJson<T>(url: string, body: URLSearchParams): Promise<ServiceResult<T>> {
|
async function _fetchSynologyJson<T>(url: string, body: URLSearchParams): Promise<ServiceResult<T>> {
|
||||||
const endpoint = _buildSynologyEndpoint(url);
|
const endpoint = _buildSynologyEndpoint(url);
|
||||||
|
const SsrfResult = await checkSsrf(endpoint);
|
||||||
|
if (!SsrfResult.allowed) {
|
||||||
|
return fail(SsrfResult.error, 400);
|
||||||
|
}
|
||||||
const resp = await fetch(endpoint, {
|
const resp = await fetch(endpoint, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -226,9 +230,6 @@ function _normalizeSynologyPhotoInfo(item: SynologyPhotoItem): AssetInfo {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function _cacheSynologySID(userId: number, sid: string): void {
|
|
||||||
db.prepare('UPDATE users SET synology_sid = ? WHERE id = ?').run(sid, userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _clearSynologySID(userId: number): void {
|
function _clearSynologySID(userId: number): void {
|
||||||
db.prepare('UPDATE users SET synology_sid = NULL WHERE id = ?').run(userId);
|
db.prepare('UPDATE users SET synology_sid = NULL WHERE id = ?').run(userId);
|
||||||
@@ -239,11 +240,11 @@ function _splitPackedSynologyId(rawId: string): { id: string; cacheKey: string;
|
|||||||
return { id, cacheKey: rawId, assetId: rawId };
|
return { id, cacheKey: rawId, assetId: rawId };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function _getSynologySession(userId: number): Promise<ServiceResult<string>> {
|
async function _getSynologySession(userId: number): Promise<ServiceResult<string>> {
|
||||||
const cachedSid = _readSynologyUser(userId, ['synology_sid']);
|
const cachedSid = _readSynologyUser(userId, ['synology_sid']);
|
||||||
if (cachedSid.success && cachedSid.data?.synology_sid) {
|
if (cachedSid.success && cachedSid.data?.synology_sid) {
|
||||||
return success(cachedSid.data.synology_sid);
|
const decryptedSid = decrypt_api_key(cachedSid.data.synology_sid);
|
||||||
|
return success(decryptedSid);
|
||||||
}
|
}
|
||||||
|
|
||||||
const creds = _getSynologyCredentials(userId);
|
const creds = _getSynologyCredentials(userId);
|
||||||
@@ -257,7 +258,8 @@ async function _getSynologySession(userId: number): Promise<ServiceResult<string
|
|||||||
return resp as ServiceResult<string>;
|
return resp as ServiceResult<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
_cacheSynologySID(userId, resp.data);
|
const encrypted = encrypt_api_key(resp.data);
|
||||||
|
db.prepare('UPDATE users SET synology_sid = ? WHERE id = ?').run(encrypted, userId);
|
||||||
return success(resp.data);
|
return success(resp.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,16 @@ import {
|
|||||||
mapDbError,
|
mapDbError,
|
||||||
Selection,
|
Selection,
|
||||||
} from './helpersService';
|
} from './helpersService';
|
||||||
|
import { ca } from 'zod/locales';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function _validProvider(provider: string): boolean {
|
||||||
|
const validProviders = ['immich', 'synologyphotos'];
|
||||||
|
return validProviders.includes(provider.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
export function listTripPhotos(tripId: string, userId: number): ServiceResult<any[]> {
|
export function listTripPhotos(tripId: string, userId: number): ServiceResult<any[]> {
|
||||||
const access = canAccessTrip(tripId, userId);
|
const access = canAccessTrip(tripId, userId);
|
||||||
if (!access) {
|
if (!access) {
|
||||||
@@ -67,11 +75,19 @@ export function listTripAlbumLinks(tripId: string, userId: number): ServiceResul
|
|||||||
//-----------------------------------------------
|
//-----------------------------------------------
|
||||||
// managing photos in trip
|
// managing photos in trip
|
||||||
|
|
||||||
function _addTripPhoto(tripId: string, userId: number, provider: string, assetId: string, shared: boolean, albumLinkId?: string): boolean {
|
function _addTripPhoto(tripId: string, userId: number, provider: string, assetId: string, shared: boolean, albumLinkId?: string): ServiceResult<boolean> {
|
||||||
const result = db.prepare(
|
if (!_validProvider(provider)) {
|
||||||
'INSERT OR IGNORE INTO trip_photos (trip_id, user_id, asset_id, provider, shared, album_link_id) VALUES (?, ?, ?, ?, ?, ?)'
|
return fail(`Provider: "${provider}" is not supported`, 400);
|
||||||
).run(tripId, userId, assetId, provider, shared ? 1 : 0, albumLinkId || null);
|
}
|
||||||
return result.changes > 0;
|
try {
|
||||||
|
const result = db.prepare(
|
||||||
|
'INSERT OR IGNORE INTO trip_photos (trip_id, user_id, asset_id, provider, shared, album_link_id) VALUES (?, ?, ?, ?, ?, ?)'
|
||||||
|
).run(tripId, userId, assetId, provider, shared ? 1 : 0, albumLinkId || null);
|
||||||
|
return success(result.changes > 0);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
return mapDbError(error, 'Failed to add photo to trip');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addTripPhotos(
|
export async function addTripPhotos(
|
||||||
@@ -91,24 +107,27 @@ export async function addTripPhotos(
|
|||||||
return fail('No photos selected', 400);
|
return fail('No photos selected', 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
let added = 0;
|
||||||
let added = 0;
|
for (const selection of selections) {
|
||||||
for (const selection of selections) {
|
if (!_validProvider(selection.provider)) {
|
||||||
for (const raw of selection.asset_ids) {
|
return fail(`Provider: "${selection.provider}" is not supported`, 400);
|
||||||
const assetId = String(raw || '').trim();
|
}
|
||||||
if (!assetId) continue;
|
for (const raw of selection.asset_ids) {
|
||||||
if (_addTripPhoto(tripId, userId, selection.provider, assetId, shared, albumLinkId)) {
|
const assetId = String(raw || '').trim();
|
||||||
added++;
|
if (!assetId) continue;
|
||||||
}
|
const result = _addTripPhoto(tripId, userId, selection.provider, assetId, shared, albumLinkId);
|
||||||
|
if (!result.success) {
|
||||||
|
return result as ServiceResult<{ added: number; shared: boolean }>;
|
||||||
|
}
|
||||||
|
if (result.data) {
|
||||||
|
added++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await _notifySharedTripPhotos(tripId, userId, added);
|
|
||||||
broadcast(tripId, 'memories:updated', { userId }, sid);
|
|
||||||
return success({ added, shared });
|
|
||||||
} catch (error) {
|
|
||||||
return mapDbError(error, 'Failed to add trip photos');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _notifySharedTripPhotos(tripId, userId, added);
|
||||||
|
broadcast(tripId, 'memories:updated', { userId }, sid);
|
||||||
|
return success({ added, shared });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -163,7 +182,7 @@ export function removeTripPhoto(
|
|||||||
AND asset_id = ?
|
AND asset_id = ?
|
||||||
AND provider = ?
|
AND provider = ?
|
||||||
`).run(tripId, userId, assetId, provider);
|
`).run(tripId, userId, assetId, provider);
|
||||||
|
|
||||||
broadcast(tripId, 'memories:updated', { userId }, sid);
|
broadcast(tripId, 'memories:updated', { userId }, sid);
|
||||||
|
|
||||||
return success(true);
|
return success(true);
|
||||||
@@ -192,6 +211,11 @@ export function createTripAlbumLink(tripId: string, userId: number, providerRaw:
|
|||||||
return fail('album_id required', 400);
|
return fail('album_id required', 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!_validProvider(provider)) {
|
||||||
|
return fail(`Provider: "${provider}" is not supported`, 400);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = db.prepare(
|
const result = db.prepare(
|
||||||
'INSERT OR IGNORE INTO trip_album_links (trip_id, user_id, provider, album_id, album_name) VALUES (?, ?, ?, ?, ?)'
|
'INSERT OR IGNORE INTO trip_album_links (trip_id, user_id, provider, album_id, album_name) VALUES (?, ?, ?, ?, ?)'
|
||||||
@@ -214,10 +238,12 @@ export function removeAlbumLink(tripId: string, linkId: string, userId: number):
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.prepare('DELETE FROM trip_photos WHERE trip_id = ? AND album_link_id = ?')
|
db.transaction(() => {
|
||||||
.run(tripId, linkId);
|
db.prepare('DELETE FROM trip_photos WHERE trip_id = ? AND album_link_id = ?')
|
||||||
db.prepare('DELETE FROM trip_album_links WHERE id = ? AND trip_id = ? AND user_id = ?')
|
.run(tripId, linkId);
|
||||||
.run(linkId, tripId, userId);
|
db.prepare('DELETE FROM trip_album_links WHERE id = ? AND trip_id = ? AND user_id = ?')
|
||||||
|
.run(linkId, tripId, userId);
|
||||||
|
});
|
||||||
return success(true);
|
return success(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return mapDbError(error, 'Failed to remove album link');
|
return mapDbError(error, 'Failed to remove album link');
|
||||||
|
|||||||
Reference in New Issue
Block a user