See CHANGELOG.md for the full v0.16.0 entry. --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: ralksta <ralksta@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Ralf <rohlenbo79@gmail.com> Co-authored-by: Paul Thies <110185070+ImScheinox@users.noreply.github.com> Co-authored-by: lancetm714 <108419425+lancetm714@users.noreply.github.com>
84 lines
2.4 KiB
TypeScript
84 lines
2.4 KiB
TypeScript
/**
|
|
* Admin API: Browse Immich assets for the hero picker.
|
|
* Supports pagination and fetching favorites first.
|
|
*/
|
|
|
|
import { NextRequest, NextResponse } from 'next/server';
|
|
import { withAdmin } from '@/lib/admin/withAdmin';
|
|
import { getConfig } from '@/lib/config';
|
|
|
|
interface ImmichSearchResult {
|
|
assets: {
|
|
items: Array<{
|
|
id: string;
|
|
type: string;
|
|
originalFileName: string;
|
|
thumbhash: string | null;
|
|
fileCreatedAt: string;
|
|
isFavorite: boolean;
|
|
}>;
|
|
nextPage: string | null;
|
|
};
|
|
}
|
|
|
|
/** GET: Search/browse assets from Immich for hero image selection. */
|
|
export const GET = withAdmin(async (request: NextRequest) => {
|
|
const config = getConfig();
|
|
// Credentials, not `needsSetup`: this route is how the operator picks the
|
|
// albums that gallery.yaml is built from, so refusing to run until that file
|
|
// exists is a deadlock (#507).
|
|
if (config.needsCredentials) {
|
|
return NextResponse.json({ error: 'Immich not configured' }, { status: 503 });
|
|
}
|
|
|
|
const { searchParams } = new URL(request.url);
|
|
const page = parseInt(searchParams.get('page') || '1', 10);
|
|
const favoritesOnly = searchParams.get('favorites') === 'true';
|
|
const pageSize = 50;
|
|
|
|
try {
|
|
// Use Immich search/metadata endpoint to query assets
|
|
const body: Record<string, unknown> = {
|
|
type: 'IMAGE',
|
|
size: pageSize,
|
|
page,
|
|
order: 'desc',
|
|
isNotInAlbum: false,
|
|
};
|
|
|
|
if (favoritesOnly) {
|
|
body.isFavorite = true;
|
|
}
|
|
|
|
const res = await fetch(`${config.immich.apiUrl}/search/metadata`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'x-api-key': config.immich.apiKey,
|
|
'Content-Type': 'application/json',
|
|
Accept: 'application/json',
|
|
},
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
return NextResponse.json({ error: `Immich API returned ${res.status}` }, { status: 502 });
|
|
}
|
|
|
|
const data = (await res.json()) as ImmichSearchResult;
|
|
const assets = data.assets.items.map((asset) => ({
|
|
id: asset.id,
|
|
originalFileName: asset.originalFileName,
|
|
fileCreatedAt: asset.fileCreatedAt,
|
|
isFavorite: asset.isFavorite,
|
|
}));
|
|
|
|
return NextResponse.json({
|
|
assets,
|
|
nextPage: data.assets.nextPage ? page + 1 : null,
|
|
});
|
|
} catch (err) {
|
|
console.error('[Admin] Failed to fetch assets from Immich:', err);
|
|
return NextResponse.json({ error: 'Failed to connect to Immich' }, { status: 502 });
|
|
}
|
|
});
|