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