web/src/lib/api.ts (view raw)
1const BASE = '/api';
2
3export interface User {
4 id: number;
5 username: string;
6 is_admin: boolean;
7 created_at: string;
8 updated_at: string;
9}
10
11export interface Image {
12 id: number;
13 filename: string;
14 storage_path: string;
15 thumbnail_path: string | null;
16 uploaded_by: number;
17 uploader_username: string;
18 file_size: number;
19 width: number;
20 height: number;
21 mime_type: string;
22 created_at: string;
23 updated_at: string;
24 tags: string[];
25}
26
27export interface ImageList {
28 images: Image[];
29 total: number;
30}
31
32export interface Tag {
33 tag: string;
34 usage_count: number;
35}
36
37export interface TagDetail {
38 tag: string;
39 usage_count: number;
40}
41
42export interface ImageDetail extends Image {
43 tags_detail: TagDetail[];
44}
45
46export interface ApiError {
47 error: string;
48}
49
50async function request<T>(path: string, opts: RequestInit = {}): Promise<T> {
51 const res = await fetch(BASE + path, {
52 credentials: 'include',
53 headers: opts.body instanceof FormData ? {} : { 'Content-Type': 'application/json' },
54 ...opts,
55 });
56 if (!res.ok) {
57 const body = await res.json().catch(() => ({ error: res.statusText }));
58 throw new Error((body as ApiError).error || `HTTP ${res.status}`);
59 }
60 return res.json();
61}
62
63export async function login(username: string, password: string): Promise<User> {
64 return request<User>('/auth/login', {
65 method: 'POST',
66 body: JSON.stringify({ username, password }),
67 });
68}
69
70export async function register(username: string, password: string): Promise<User> {
71 return request<User>('/auth/register', {
72 method: 'POST',
73 body: JSON.stringify({ username, password }),
74 });
75}
76
77export async function logout(): Promise<void> {
78 await request('/auth/logout', { method: 'POST' });
79}
80
81export async function listImages(tags: string[] = [], page = 1, perPage = 50): Promise<ImageList> {
82 const params = new URLSearchParams();
83 params.set('page', String(page));
84 params.set('per_page', String(perPage));
85 for (const t of tags) {
86 params.append('tags', t);
87 }
88 return request<ImageList>(`/images?${params}`);
89}
90
91export async function getImage(id: number): Promise<ImageDetail> {
92 return request<ImageDetail>(`/images/${id}`);
93}
94
95export function imageDownloadUrl(id: number): string {
96 return `${BASE}/images/${id}/download`;
97}
98
99export function thumbnailUrl(id: number): string {
100 return `${BASE}/images/${id}/thumbnail`;
101}
102
103export async function uploadImage(file: File, tags: string[]): Promise<Image> {
104 const fd = new FormData();
105 fd.append('file', file);
106 for (const t of tags) {
107 fd.append('tags', t);
108 }
109 return request<Image>('/upload', { method: 'POST', body: fd });
110}
111
112export async function suggestTags(query: string): Promise<Tag[]> {
113 return request<Tag[]>(`/tags/suggest?q=${encodeURIComponent(query)}`);
114}