recipe-inator/frontend/src/api/client.ts

65 lines
2 KiB
TypeScript

import type { Recipe, RecipeInput, RecipeSummary } from "../types/recipe";
const BASE = "/api";
async function request<T>(path: string, options?: RequestInit): Promise<T> {
const isFormData = options?.body instanceof FormData;
const res = await fetch(`${BASE}${path}`, {
...options,
headers: {
...(isFormData ? {} : { "Content-Type": "application/json" }),
...options?.headers,
},
});
if (!res.ok) {
let detail = "";
try {
const data = await res.json();
detail = data.detail ?? JSON.stringify(data);
} catch {
detail = await res.text();
}
throw new Error(detail || `Request failed: ${res.status}`);
}
if (res.status === 204) return undefined as T;
return res.json() as Promise<T>;
}
export function listRecipes(search?: string): Promise<RecipeSummary[]> {
const query = search ? `?search=${encodeURIComponent(search)}` : "";
return request(`/recipes${query}`);
}
export function getRecipe(id: number): Promise<Recipe> {
return request(`/recipes/${id}`);
}
export function createRecipe(payload: RecipeInput): Promise<Recipe> {
return request(`/recipes`, { method: "POST", body: JSON.stringify(payload) });
}
export function updateRecipe(id: number, payload: RecipeInput): Promise<Recipe> {
return request(`/recipes/${id}`, { method: "PUT", body: JSON.stringify(payload) });
}
export function deleteRecipe(id: number): Promise<void> {
return request(`/recipes/${id}`, { method: "DELETE" });
}
export function uploadRecipeImage(id: number, file: File): Promise<Recipe> {
const form = new FormData();
form.append("file", file);
return request(`/recipes/${id}/image`, { method: "POST", body: form });
}
export function deleteRecipeImage(id: number): Promise<Recipe> {
return request(`/recipes/${id}/image`, { method: "DELETE" });
}
export function pdfDownloadUrl(id: number): string {
return `${BASE}/recipes/${id}/pdf`;
}
export function uploadedImageUrl(filename: string): string {
return `/uploads/${filename}`;
}