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