all repos — weimar @ master

Unnamed repository; edit this file 'description' to name the repository.

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