changing routes and hierarchy of files for memories
This commit is contained in:
79
server/src/services/memories/helpersService.ts
Normal file
79
server/src/services/memories/helpersService.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { canAccessTrip, db } from "../../db/database";
|
||||
|
||||
// helpers for handling return types
|
||||
|
||||
type ServiceError = { success: false; error: { message: string; status: number } };
|
||||
export type ServiceResult<T> = { success: true; data: T } | ServiceError;
|
||||
|
||||
|
||||
export function fail(error: string, status: number): ServiceError {
|
||||
return { success: false, error: { message: error, status } };
|
||||
}
|
||||
|
||||
|
||||
export function success<T>(data: T): ServiceResult<T> {
|
||||
return { success: true, data: data };
|
||||
}
|
||||
|
||||
|
||||
export function mapDbError(error: unknown, fallbackMessage: string): ServiceError {
|
||||
if (error instanceof Error && /unique|constraint/i.test(error.message)) {
|
||||
return fail('Resource already exists', 409);
|
||||
}
|
||||
return fail(fallbackMessage, 500);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------
|
||||
// types used across memories services
|
||||
export type Selection = {
|
||||
provider: string;
|
||||
asset_ids: string[];
|
||||
};
|
||||
|
||||
|
||||
//-----------------------------------------------
|
||||
//access check helper
|
||||
|
||||
export function canAccessUserPhoto(requestingUserId: number, ownerUserId: number, tripId: string, assetId: string, provider: string): boolean {
|
||||
if (requestingUserId === ownerUserId) {
|
||||
return true;
|
||||
}
|
||||
const sharedAsset = db.prepare(`
|
||||
SELECT 1
|
||||
FROM trip_photos
|
||||
WHERE user_id = ?
|
||||
AND asset_id = ?
|
||||
AND provider = ?
|
||||
AND trip_id = ?
|
||||
AND shared = 1
|
||||
LIMIT 1
|
||||
`).get(ownerUserId, assetId, provider, tripId);
|
||||
|
||||
if (!sharedAsset) {
|
||||
return false;
|
||||
}
|
||||
return !!canAccessTrip(tripId, requestingUserId);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------
|
||||
//helpers for album link syncing
|
||||
|
||||
export function getAlbumIdFromLink(tripId: string, linkId: string, userId: number): ServiceResult<string> {
|
||||
const access = canAccessTrip(tripId, userId);
|
||||
if (!access) return fail('Trip not found or access denied', 404);
|
||||
|
||||
try {
|
||||
const row = db.prepare('SELECT album_id FROM trip_album_links WHERE id = ? AND trip_id = ? AND user_id = ?')
|
||||
.get(linkId, tripId, userId) as { album_id: string } | null;
|
||||
|
||||
return row ? success(row.album_id) : fail('Album link not found', 404);
|
||||
} catch {
|
||||
return fail('Failed to retrieve album link', 500);
|
||||
}
|
||||
}
|
||||
|
||||
export function updateSyncTimeForAlbumLink(linkId: string): void {
|
||||
db.prepare('UPDATE trip_album_links SET last_synced_at = CURRENT_TIMESTAMP WHERE id = ?').run(linkId);
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { db } from '../db/database';
|
||||
import { maybe_encrypt_api_key, decrypt_api_key } from './apiKeyCrypto';
|
||||
import { checkSsrf } from '../utils/ssrfGuard';
|
||||
import { writeAudit } from './auditLog';
|
||||
import { addTripPhotos, getAlbumIdFromLink, Selection, updateSyncTimeForAlbumLink } from './memoriesService';
|
||||
import { error } from 'node:console';
|
||||
import { db } from '../../db/database';
|
||||
import { maybe_encrypt_api_key, decrypt_api_key } from '../apiKeyCrypto';
|
||||
import { checkSsrf } from '../../utils/ssrfGuard';
|
||||
import { writeAudit } from '../auditLog';
|
||||
import { addTripPhotos} from './unifiedService';
|
||||
import { getAlbumIdFromLink, updateSyncTimeForAlbumLink, Selection } from './helpersService';
|
||||
|
||||
// ── Credentials ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Readable } from 'node:stream';
|
||||
import { pipeline } from 'node:stream/promises';
|
||||
import { Response as ExpressResponse } from 'express';
|
||||
import { db } from '../db/database';
|
||||
import { decrypt_api_key, maybe_encrypt_api_key } from './apiKeyCrypto';
|
||||
import { checkSsrf } from '../utils/ssrfGuard';
|
||||
import { addTripPhotos, getAlbumIdFromLink, Selection, updateSyncTimeForAlbumLink } from './memoriesService';
|
||||
import { error } from 'node:console';
|
||||
import { th } from 'zod/locales';
|
||||
import { db } from '../../db/database';
|
||||
import { decrypt_api_key, maybe_encrypt_api_key } from '../apiKeyCrypto';
|
||||
import { checkSsrf } from '../../utils/ssrfGuard';
|
||||
import { addTripPhotos} from './unifiedService';
|
||||
import { getAlbumIdFromLink, updateSyncTimeForAlbumLink, Selection } from './helpersService';
|
||||
|
||||
const SYNOLOGY_API_TIMEOUT_MS = 30000;
|
||||
const SYNOLOGY_PROVIDER = 'synologyphotos';
|
||||
@@ -1,49 +1,15 @@
|
||||
import { db, canAccessTrip } from '../db/database';
|
||||
import { notifyTripMembers } from './notifications';
|
||||
import { broadcast } from '../websocket';
|
||||
import { db, canAccessTrip } from '../../db/database';
|
||||
import { notifyTripMembers } from '../notifications';
|
||||
import { broadcast } from '../../websocket';
|
||||
|
||||
import {
|
||||
ServiceResult,
|
||||
fail,
|
||||
success,
|
||||
mapDbError,
|
||||
Selection,
|
||||
} from './helpersService';
|
||||
|
||||
type ServiceError = { success: false; error: { message: string; status: number } };
|
||||
type ServiceResult<T> = { success: true; data: T } | ServiceError;
|
||||
|
||||
function fail(error: string, status: number): ServiceError {
|
||||
return { success: false, error: { message: error, status }};
|
||||
}
|
||||
|
||||
function success<T>(data: T): ServiceResult<T> {
|
||||
return { success: true, data: data };
|
||||
}
|
||||
|
||||
function mapDbError(error: unknown, fallbackMessage: string): ServiceError {
|
||||
if (error instanceof Error && /unique|constraint/i.test(error.message)) {
|
||||
return fail('Resource already exists', 409);
|
||||
}
|
||||
return fail(fallbackMessage, 500);
|
||||
}
|
||||
|
||||
//-----------------------------------------------
|
||||
//access check helper
|
||||
|
||||
export function canAccessUserPhoto(requestingUserId: number, ownerUserId: number, tripId: string, assetId: string, provider: string): boolean {
|
||||
if (requestingUserId === ownerUserId) {
|
||||
return true;
|
||||
}
|
||||
const sharedAsset = db.prepare(`
|
||||
SELECT 1
|
||||
FROM trip_photos
|
||||
WHERE user_id = ?
|
||||
AND asset_id = ?
|
||||
AND provider = ?
|
||||
AND trip_id = ?
|
||||
AND shared = 1
|
||||
LIMIT 1
|
||||
`).get(ownerUserId, assetId, provider, tripId);
|
||||
|
||||
if (!sharedAsset) {
|
||||
return false;
|
||||
}
|
||||
return !!canAccessTrip(tripId, requestingUserId);
|
||||
}
|
||||
|
||||
export function listTripPhotos(tripId: string, userId: number): ServiceResult<any[]> {
|
||||
const access = canAccessTrip(tripId, userId);
|
||||
@@ -108,11 +74,6 @@ function _addTripPhoto(tripId: string, userId: number, provider: string, assetId
|
||||
return result.changes > 0;
|
||||
}
|
||||
|
||||
export type Selection = {
|
||||
provider: string;
|
||||
asset_ids: string[];
|
||||
};
|
||||
|
||||
export async function addTripPhotos(
|
||||
tripId: string,
|
||||
userId: number,
|
||||
@@ -181,7 +142,6 @@ export async function setTripPhotoSharing(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function removeTripPhoto(
|
||||
tripId: string,
|
||||
userId: number,
|
||||
@@ -262,25 +222,6 @@ export function removeAlbumLink(tripId: string, linkId: string, userId: number):
|
||||
}
|
||||
}
|
||||
|
||||
//helpers for album link syncing
|
||||
|
||||
export function getAlbumIdFromLink(tripId: string, linkId: string, userId: number): ServiceResult<string> {
|
||||
const access = canAccessTrip(tripId, userId);
|
||||
if (!access) return fail('Trip not found or access denied', 404);
|
||||
|
||||
try {
|
||||
const row = db.prepare('SELECT album_id FROM trip_album_links WHERE id = ? AND trip_id = ? AND user_id = ?')
|
||||
.get(linkId, tripId, userId) as { album_id: string } | null;
|
||||
|
||||
return row ? success(row.album_id) : fail('Album link not found', 404);
|
||||
} catch {
|
||||
return fail('Failed to retrieve album link', 500);
|
||||
}
|
||||
}
|
||||
|
||||
export function updateSyncTimeForAlbumLink(linkId: string): void {
|
||||
db.prepare('UPDATE trip_album_links SET last_synced_at = CURRENT_TIMESTAMP WHERE id = ?').run(linkId);
|
||||
}
|
||||
|
||||
//-----------------------------------------------
|
||||
// notifications helper
|
||||
@@ -306,5 +247,3 @@ async function _notifySharedTripPhotos(
|
||||
return fail('Failed to send notifications', 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user