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