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 avatar_url: string;
19 file_size: number;
20 width: number;
21 height: number;
22 mime_type: string;
23 innate_tags?: string[];
24 created_at: string;
25 updated_at: string;
26 tags: string[];
27}
28
29export interface ImageList {
30 images: Image[];
31 total: number;
32}
33
34export interface Tag {
35 tag: string;
36 usage_count: number;
37}
38
39export interface TagDetail {
40 tag: string;
41 usage_count: number;
42}
43
44export interface ImageDetail extends Image {
45 tags_detail: TagDetail[];
46 is_owner: boolean;
47}
48
49export interface CurrentUser {
50 id: number;
51 username: string;
52 is_admin: boolean;
53 avatar_url: string;
54 created_at: string;
55 updated_at: string;
56}
57
58export interface ApiError {
59 error: string;
60}
61
62async function request<T>(path: string, opts: RequestInit = {}): Promise<T> {
63 const res = await fetch(BASE + path, {
64 credentials: 'include',
65 headers: opts.body instanceof FormData ? {} : { 'Content-Type': 'application/json' },
66 ...opts,
67 });
68 if (!res.ok) {
69 const body = await res.json().catch(() => ({ error: res.statusText }));
70 throw new Error((body as ApiError).error || `HTTP ${res.status}`);
71 }
72 return res.json();
73}
74
75export async function login(username: string, password: string): Promise<User> {
76 return request<User>('/auth/login', {
77 method: 'POST',
78 body: JSON.stringify({ username, password }),
79 });
80}
81
82export async function register(username: string, password: string): Promise<User> {
83 return request<User>('/auth/register', {
84 method: 'POST',
85 body: JSON.stringify({ username, password }),
86 });
87}
88
89export async function logout(): Promise<void> {
90 await request('/auth/logout', { method: 'POST' });
91}
92
93export async function listImages(tags: string[] = [], uploader = '', page = 1, perPage = 50): Promise<ImageList> {
94 const params = new URLSearchParams();
95 params.set('page', String(page));
96 params.set('per_page', String(perPage));
97 for (const t of tags) {
98 params.append('tags', t);
99 }
100 if (uploader) {
101 params.set('uploader', uploader);
102 }
103 return request<ImageList>(`/images?${params}`);
104}
105
106export async function getImage(id: number): Promise<ImageDetail> {
107 return request<ImageDetail>(`/images/${id}`);
108}
109
110export function imageDownloadUrl(id: number): string {
111 return `${BASE}/images/${id}/download`;
112}
113
114export function thumbnailUrl(id: number): string {
115 return `${BASE}/images/${id}/thumbnail`;
116}
117
118export async function uploadImage(file: File, tags: string[]): Promise<Image> {
119 const fd = new FormData();
120 fd.append('file', file);
121 for (const t of tags) {
122 fd.append('tags', t);
123 }
124 return request<Image>('/upload', { method: 'POST', body: fd });
125}
126
127export async function suggestTags(query: string): Promise<Tag[]> {
128 return request<Tag[]>(`/tags/suggest?q=${encodeURIComponent(query)}`);
129}
130
131export async function suggestUploaders(query: string): Promise<Tag[]> {
132 return request<Tag[]>(`/users/suggest?q=${encodeURIComponent(query)}`);
133}
134
135export async function uploadAvatar(file: File): Promise<{ avatar_url: string }> {
136 const fd = new FormData();
137 fd.append('avatar', file);
138 return request<{ avatar_url: string }>('/users/me/avatar', { method: 'POST', body: fd });
139}
140
141export async function getMe(): Promise<CurrentUser> {
142 return request<CurrentUser>('/users/me');
143}
144
145export interface SiteConfig {
146 site_title: string;
147 favicon_url?: string;
148 logo_url?: string;
149}
150
151export async function getConfig(): Promise<SiteConfig> {
152 return request<SiteConfig>('/config');
153}
154
155export async function addTags(imageId: number, tags: string[]): Promise<{ tags_detail: TagDetail[] }> {
156 return request<{ tags_detail: TagDetail[] }>(`/images/${imageId}/tags`, {
157 method: 'POST',
158 body: JSON.stringify({ tags }),
159 });
160}
161
162export async function removeTags(imageId: number, tags: string[]): Promise<{ tags_detail: TagDetail[] }> {
163 return request<{ tags_detail: TagDetail[] }>(`/images/${imageId}/tags`, {
164 method: 'DELETE',
165 body: JSON.stringify({ tags }),
166 });
167}