2026-09-29 22:50:30 +02:00
|
|
|
/**
|
|
|
|
|
* What the admin panel sees of a proofing session: the stored record plus the
|
|
|
|
|
* album's name and the state, without the Immich lookups repeated per caller.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { immich, type ImmichAlbum } from '@/lib/immich';
|
|
|
|
|
import { imageUrl } from '@/lib/urls';
|
|
|
|
|
import {
|
|
|
|
|
downloadsRemaining,
|
|
|
|
|
sessionState,
|
|
|
|
|
type ProofSession,
|
|
|
|
|
type ProofState,
|
|
|
|
|
} from '@/lib/proofSessions';
|
|
|
|
|
|
|
|
|
|
export interface AdminProofSession {
|
|
|
|
|
id: string;
|
|
|
|
|
clientName: string;
|
|
|
|
|
albumId: string;
|
|
|
|
|
/** Null when Immich no longer has the album (or cannot be reached). */
|
|
|
|
|
albumName: string | null;
|
|
|
|
|
albumAssetCount: number | null;
|
2026-10-04 15:49:06 +03:00
|
|
|
alias: string | null;
|
2026-09-29 22:50:30 +02:00
|
|
|
/** Relative path of the client link. */
|
|
|
|
|
link: string;
|
|
|
|
|
createdAt: string;
|
|
|
|
|
expiresOn: string | null;
|
|
|
|
|
download: ProofSession['download'];
|
|
|
|
|
downloadLimit: number | null;
|
|
|
|
|
downloadsUsed: number;
|
|
|
|
|
downloadsRemaining: number | null;
|
|
|
|
|
selected: number;
|
|
|
|
|
updatedAt: string | null;
|
|
|
|
|
submittedAt: string | null;
|
|
|
|
|
state: ProofState;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface AdminProofPick {
|
|
|
|
|
assetId: string;
|
|
|
|
|
position: number;
|
|
|
|
|
fileName: string;
|
|
|
|
|
thumbUrl: string;
|
|
|
|
|
takenAt?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Load each album once, tolerating ones that are gone or unreachable. */
|
|
|
|
|
export async function loadAlbums(ids: string[]): Promise<Map<string, ImmichAlbum | null>> {
|
|
|
|
|
const unique = Array.from(new Set(ids));
|
|
|
|
|
const albums = await Promise.all(
|
|
|
|
|
unique.map((id) => immich.getProofingAlbum(id).catch(() => null)),
|
|
|
|
|
);
|
|
|
|
|
return new Map(unique.map((id, i) => [id, albums[i]]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function toAdminSession(
|
|
|
|
|
session: ProofSession,
|
|
|
|
|
album: ImmichAlbum | null,
|
|
|
|
|
): AdminProofSession {
|
|
|
|
|
return {
|
|
|
|
|
id: session.id,
|
|
|
|
|
clientName: session.clientName,
|
|
|
|
|
albumId: session.albumId,
|
|
|
|
|
albumName: album?.albumName ?? null,
|
|
|
|
|
albumAssetCount: album ? album.assets.length : null,
|
2026-10-04 15:49:06 +03:00
|
|
|
alias: session.alias ?? null,
|
|
|
|
|
link: session.alias ? `/c/${session.alias}` : `/proof/${session.token}`,
|
2026-09-29 22:50:30 +02:00
|
|
|
createdAt: session.createdAt,
|
|
|
|
|
expiresOn: session.expiresOn ?? null,
|
|
|
|
|
download: session.download,
|
|
|
|
|
downloadLimit: session.downloadLimit ?? null,
|
|
|
|
|
downloadsUsed: session.downloadsUsed,
|
|
|
|
|
downloadsRemaining: downloadsRemaining(session),
|
|
|
|
|
selected: session.selection.length,
|
|
|
|
|
updatedAt: session.updatedAt ?? null,
|
|
|
|
|
submittedAt: session.submittedAt ?? null,
|
|
|
|
|
state: sessionState(session),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** The selected photos in album order, with what the export and preview need. */
|
|
|
|
|
export function picksFor(session: ProofSession, album: ImmichAlbum | null): AdminProofPick[] {
|
|
|
|
|
if (!album) return [];
|
|
|
|
|
const chosen = new Set(session.selection);
|
|
|
|
|
const media = album.assets.filter((a) => a.type === 'IMAGE' || a.type === 'VIDEO');
|
|
|
|
|
const picks: AdminProofPick[] = [];
|
|
|
|
|
media.forEach((asset, index) => {
|
|
|
|
|
if (!chosen.has(asset.id)) return;
|
|
|
|
|
picks.push({
|
|
|
|
|
assetId: asset.id,
|
|
|
|
|
position: index + 1,
|
|
|
|
|
fileName: asset.originalFileName,
|
2026-10-02 18:14:23 +02:00
|
|
|
thumbUrl: imageUrl(asset, 'thumbnail'),
|
2026-09-29 22:50:30 +02:00
|
|
|
...(asset.localDateTime || asset.fileCreatedAt
|
|
|
|
|
? { takenAt: asset.localDateTime ?? asset.fileCreatedAt }
|
|
|
|
|
: {}),
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
return picks;
|
|
|
|
|
}
|