initial commit

This commit is contained in:
2026-05-09 12:34:45 -04:00
commit 97fabc2c17
49 changed files with 4856 additions and 0 deletions

101
frontend/src/api/client.ts Normal file
View File

@@ -0,0 +1,101 @@
const BASE = "/api";
export interface Library {
id: number;
name: string;
path: string;
}
export interface Tag {
id: number;
name: string;
category: string;
}
export interface TagsByCategory {
category: string;
tags: Tag[];
}
export interface MediaItem {
id: number;
library_id: number;
rel_path: string;
filename: string;
media_type: "image" | "video";
size_bytes: number | null;
missing: boolean;
tags: Tag[];
created_at: string;
updated_at: string;
}
export interface BrowseEntry {
name: string;
type: "dir" | "image" | "video";
rel_path: string;
media_item_id: number | null;
}
export interface BrowseResult {
path: string;
entries: BrowseEntry[];
}
async function request<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(`${BASE}${path}`, {
headers: { "Content-Type": "application/json" },
...init,
});
if (!res.ok) {
const text = await res.text().catch(() => "");
throw new Error(`${res.status}: ${text}`);
}
if (res.status === 204) return undefined as T;
return res.json();
}
export const api = {
libraries: {
list: () => request<Library[]>("/libraries"),
create: (name: string, path: string) =>
request<Library>("/libraries", {
method: "POST",
body: JSON.stringify({ name, path }),
}),
delete: (id: number) =>
request<void>(`/libraries/${id}`, { method: "DELETE" }),
browse: (id: number, path = "") =>
request<BrowseResult>(`/libraries/${id}/browse?path=${encodeURIComponent(path)}`),
},
media: {
get: (id: number) => request<MediaItem>(`/media/${id}`),
fileUrl: (id: number) => `${BASE}/media/${id}/file`,
thumbnailUrl: (id: number) => `${BASE}/media/${id}/thumbnail`,
setTags: (id: number, tagIds: number[]) =>
request<MediaItem>(`/media/${id}/tags`, {
method: "PUT",
body: JSON.stringify({ tag_ids: tagIds }),
}),
},
tags: {
list: () => request<TagsByCategory[]>("/tags"),
create: (name: string, category: string) =>
request<Tag>("/tags", {
method: "POST",
body: JSON.stringify({ name, category }),
}),
delete: (id: number) =>
request<void>(`/tags/${id}`, { method: "DELETE" }),
},
search: (params: { q?: string; tags?: number[]; library_id?: number }) => {
const p = new URLSearchParams();
if (params.q) p.set("q", params.q);
if (params.tags?.length) p.set("tags", params.tags.join(","));
if (params.library_id != null) p.set("library_id", String(params.library_id));
return request<MediaItem[]>(`/search?${p}`);
},
};