const BASE = '/api'; export interface User { id: number; username: string; is_admin: boolean; created_at: string; updated_at: string; } export interface Image { id: number; filename: string; storage_path: string; thumbnail_path: string | null; uploaded_by: number; uploader_username: string; avatar_url: string; file_size: number; width: number; height: number; mime_type: string; innate_tags?: string[]; duration?: number; gem_count?: number; gemmed?: boolean; created_at: string; updated_at: string; tags: string[]; } export interface ImageList { images: Image[]; total: number; } export interface Tag { tag: string; usage_count: number; } export interface TagDetail { tag: string; usage_count: number; } export interface ImageDetail extends Image { tags_detail: TagDetail[]; is_owner: boolean; } export interface CurrentUser { id: number; username: string; is_admin: boolean; avatar_url: string; created_at: string; updated_at: string; } export interface ApiError { error: string; } async function request(path: string, opts: RequestInit = {}): Promise { const res = await fetch(BASE + path, { credentials: 'include', headers: opts.body instanceof FormData ? {} : { 'Content-Type': 'application/json' }, ...opts, }); if (!res.ok) { const body = await res.json().catch(() => ({ error: res.statusText })); throw new Error((body as ApiError).error || `HTTP ${res.status}`); } return res.json(); } export async function login(username: string, password: string): Promise { return request('/auth/login', { method: 'POST', body: JSON.stringify({ username, password }), }); } export async function register(username: string, password: string): Promise { return request('/auth/register', { method: 'POST', body: JSON.stringify({ username, password }), }); } export async function logout(): Promise { await request('/auth/logout', { method: 'POST' }); } export async function listImages(tags: string[] = [], uploader = '', page = 1, perPage = 50, sortGems = false, filterGems = false): Promise { const params = new URLSearchParams(); params.set('page', String(page)); params.set('per_page', String(perPage)); for (const t of tags) { params.append('tags', t); } if (uploader) { params.set('uploader', uploader); } if (sortGems) { params.set('sort', 'gems'); } if (filterGems) { params.set('gems', 'true'); } return request(`/images?${params}`); } export async function getImage(id: number): Promise { return request(`/images/${id}`); } export function imageDownloadUrl(id: number): string { return `${BASE}/images/${id}/download`; } export function thumbnailUrl(id: number): string { return `${BASE}/images/${id}/thumbnail`; } export async function uploadImage(file: File, tags: string[]): Promise { const fd = new FormData(); fd.append('file', file); for (const t of tags) { fd.append('tags', t); } return request('/upload', { method: 'POST', body: fd }); } export async function suggestTags(query: string): Promise { return request(`/tags/suggest?q=${encodeURIComponent(query)}`); } export async function suggestUploaders(query: string): Promise { return request(`/users/suggest?q=${encodeURIComponent(query)}`); } export async function uploadAvatar(file: File): Promise<{ avatar_url: string }> { const fd = new FormData(); fd.append('avatar', file); return request<{ avatar_url: string }>('/users/me/avatar', { method: 'POST', body: fd }); } export async function getMe(): Promise { return request('/users/me'); } export interface SiteConfig { site_title: string; favicon_url?: string; logo_url?: string; } export async function getConfig(): Promise { return request('/config'); } export async function addTags(imageId: number, tags: string[]): Promise<{ tags_detail: TagDetail[] }> { return request<{ tags_detail: TagDetail[] }>(`/images/${imageId}/tags`, { method: 'POST', body: JSON.stringify({ tags }), }); } export async function removeTags(imageId: number, tags: string[]): Promise<{ tags_detail: TagDetail[] }> { return request<{ tags_detail: TagDetail[] }>(`/images/${imageId}/tags`, { method: 'DELETE', body: JSON.stringify({ tags }), }); } export async function toggleGem(imageId: number): Promise<{ gemmed: boolean; gem_count: number }> { return request<{ gemmed: boolean; gem_count: number }>(`/images/${imageId}/gem`, { method: 'POST', }); }