export const FRUIT_TYPES = [ 'Apfelsorten', 'Birnensorten', 'Quittensorten', 'Aprikosen', 'Pfirsiche', 'Mirabellen', 'Renekloden', 'Pflaumen', 'Zwetschen', 'Sauerkirschen', 'Süßkirschen', 'Brombeeren', 'Erdbeeren', 'Himbeeren', 'Johannisbeeren', 'Stachelbeeren', 'Wein', ] as const export type FruitType = (typeof FRUIT_TYPES)[number] export interface FruitImage { id: number fruit_id: number filename: string | null image_type: string title: string | null url: string created_at: string } export interface Fruit { id: number name: string osdb_number: string comment: string | null fruit_type: string synonyms: string[] images: FruitImage[] created_at: string updated_at: string } export interface FruitListResponse { items: Fruit[] total: number limit: number offset: number } export interface FruitWriteDTO { name: string osdb_number: string comment?: string | null fruit_type: string synonyms: string[] } export function imageUrl(fruitId: number, imageId: number): string { return `/api/v1/fruits/${fruitId}/images/${imageId}` } async function checkOk(res: Response): Promise { if (!res.ok) { let msg = `${res.status}` try { const body = await res.json() msg = body.error ?? body.errors?.join(', ') ?? msg } catch { // ignore parse error } const err = new Error(msg) as Error & { status: number } err.status = res.status throw err } return res } export async function listFruits(limit = 50, offset = 0): Promise { const res = await fetch(`/api/v1/fruits?limit=${limit}&offset=${offset}`) return (await checkOk(res)).json() } export async function getFruit(id: number): Promise { const res = await fetch(`/api/v1/fruits/${id}`) return (await checkOk(res)).json() } export async function createFruit(dto: FruitWriteDTO): Promise { const res = await fetch('/api/v1/fruits', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(dto), }) return (await checkOk(res)).json() } export async function updateFruit(id: number, dto: FruitWriteDTO): Promise { const res = await fetch(`/api/v1/fruits/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(dto), }) return (await checkOk(res)).json() } export async function deleteFruit(id: number): Promise { const res = await fetch(`/api/v1/fruits/${id}`, { method: 'DELETE' }) await checkOk(res) } export async function listImages(fruitId: number): Promise { const res = await fetch(`/api/v1/fruits/${fruitId}/images`) return (await checkOk(res)).json() } export async function addImage( fruitId: number, file: File, imageType: string, title?: string, ): Promise { const form = new FormData() form.append('image', file) form.append('image_type', imageType) if (title) form.append('title', title) // Do NOT set Content-Type — browser sets it with the correct multipart boundary const res = await fetch(`/api/v1/fruits/${fruitId}/images`, { method: 'POST', body: form }) return (await checkOk(res)).json() } export async function deleteImage(fruitId: number, imageId: number): Promise { const res = await fetch(`/api/v1/fruits/${fruitId}/images/${imageId}`, { method: 'DELETE' }) await checkOk(res) }