Files
immich-folio/app/api/admin/assets/route.ts
ralksta e8e65e7172 feat(admin): add asset picker with favorites, drag-and-drop reordering, preview links, and keyboard shortcuts
- Asset Picker: Visual browser for hero images with Favorites tab (shown first)
  and All Photos tab. Includes infinite scroll and UUID fallback input for
  assets not easily found in the grid.
- Drag & Drop: All lists (hero images, standalone albums, subpage albums,
  subpage tiles) are now reorderable via drag handles using @dnd-kit.
- Preview Links: 'Preview Site' button in save bar, '↗ Site' in header, and
  per-subpage preview links with URL slug preview shown inline.
- Keyboard Shortcuts: ⌘+S / Ctrl+S to save in both Pages and Settings tabs.
- Unsaved Changes Guard: beforeunload warning prevents accidental data loss.
- New API route /api/admin/assets for browsing Immich assets with pagination
  and favorites filter via search/metadata endpoint.
2026-05-29 17:59:25 +02:00

88 lines
2.5 KiB
TypeScript

/**
* Admin API: Browse Immich assets for the hero picker.
* Supports pagination and fetching favorites first.
*/
import { NextRequest, NextResponse } from 'next/server';
import { isAdminAuthenticated, isAdminEnabled } from '@/lib/admin/auth';
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 async function GET(request: NextRequest) {
if (!isAdminEnabled()) {
return NextResponse.json({ error: 'Admin not enabled' }, { status: 403 });
}
if (!(await isAdminAuthenticated())) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const config = getConfig();
if (config.needsSetup) {
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 });
}
}