Files
data-entry-app/frontend/src/lib/api.ts
T

174 lines
5.6 KiB
TypeScript
Raw Normal View History

2026-04-25 20:43:37 +12:00
import { env } from '$env/dynamic/public';
2026-04-25 22:51:36 +12:00
import { browser } from '$app/environment';
import {
mockClientAccess,
mockClientAccessExport,
mockCosts,
mockMixes,
mockProducts,
mockRawMaterials,
mockScenarios
} from '$lib/mock';
2026-04-25 20:43:37 +12:00
import type {
2026-04-25 22:51:36 +12:00
ClientAccessAccount,
ClientAccessPowerBiExport,
ClientUserCreateInput,
ClientUserUpdateInput,
2026-04-25 20:43:37 +12:00
LoginResponse,
2026-04-25 22:51:36 +12:00
Mix,
MixCreateInput,
MixIngredientUpdateInput,
MixUpdateInput,
2026-04-25 20:43:37 +12:00
Product,
ProductCostBreakdown,
RawMaterial,
RawMaterialCreateInput,
RawMaterialPriceCreateInput,
Scenario
} from '$lib/types';
2026-04-25 22:51:36 +12:00
import { getStoredAdminSession, getStoredClientSession } from '$lib/session';
2026-04-25 20:43:37 +12:00
const API_BASE_URL = env.PUBLIC_API_BASE_URL || 'http://localhost:8000';
2026-04-25 22:51:36 +12:00
type AuthMode = 'none' | 'client' | 'admin';
function getToken(auth: AuthMode) {
if (!browser) {
return null;
}
if (auth === 'client') {
return getStoredClientSession()?.token ?? null;
}
if (auth === 'admin') {
return getStoredAdminSession()?.token ?? null;
}
return null;
}
async function fetchJson<T>(path: string, fallback: T, auth: AuthMode = 'none'): Promise<T> {
2026-04-25 20:43:37 +12:00
try {
2026-04-25 22:51:36 +12:00
const token = getToken(auth);
const response = await fetch(`${API_BASE_URL}${path}`, {
headers: token ? { Authorization: `Bearer ${token}` } : undefined
});
2026-04-25 20:43:37 +12:00
if (!response.ok) {
2026-04-25 22:51:36 +12:00
if (auth !== 'none') {
throw new Error(response.statusText || 'Unauthorized');
}
2026-04-25 20:43:37 +12:00
return fallback;
}
return (await response.json()) as T;
2026-04-25 22:51:36 +12:00
} catch (error) {
if (auth !== 'none') {
throw error;
}
2026-04-25 20:43:37 +12:00
return fallback;
}
}
2026-04-25 22:51:36 +12:00
async function request<T>(path: string, options: RequestInit, auth: AuthMode = 'none'): Promise<T> {
const token = getToken(auth);
2026-04-25 20:43:37 +12:00
const response = await fetch(`${API_BASE_URL}${path}`, {
headers: {
'Content-Type': 'application/json',
2026-04-25 22:51:36 +12:00
...(token ? { Authorization: `Bearer ${token}` } : {}),
2026-04-25 20:43:37 +12:00
...(options.headers ?? {})
},
...options
});
if (!response.ok) {
let message = 'Request failed';
try {
const body = (await response.json()) as { detail?: string };
message = body.detail ?? message;
} catch {
message = response.statusText || message;
}
throw new Error(message);
}
return (await response.json()) as T;
}
export const api = {
2026-04-25 22:51:36 +12:00
rawMaterials: () => fetchJson<RawMaterial[]>('/api/raw-materials', mockRawMaterials, 'client'),
mixes: () => fetchJson('/api/mixes', mockMixes, 'client'),
mix: (mixId: number) => request<Mix>(`/api/mixes/${mixId}`, { method: 'GET' }, 'client'),
products: () => fetchJson<Product[]>('/api/products', mockProducts, 'client'),
productCosts: () => fetchJson<ProductCostBreakdown[]>('/api/powerbi/product-costs', mockCosts, 'client'),
scenarios: () => fetchJson<Scenario[]>('/api/scenarios', mockScenarios, 'client'),
clientAccess: () => fetchJson<ClientAccessAccount[]>('/api/client-access', mockClientAccess, 'admin'),
clientAccessExport: () => fetchJson<ClientAccessPowerBiExport>('/api/powerbi/client-access', mockClientAccessExport, 'admin'),
dataQuality: () => fetchJson('/api/powerbi/data-quality-issues', [], 'client'),
clientLogin: (email: string, password: string) =>
request<LoginResponse>('/api/auth/client/login', {
method: 'POST',
body: JSON.stringify({ email, password })
}),
adminLogin: (email: string, password: string) =>
request<LoginResponse>('/api/auth/admin/login', {
method: 'POST',
body: JSON.stringify({ email, password })
}),
2026-04-25 20:43:37 +12:00
login: (email: string, password: string) =>
2026-04-25 22:51:36 +12:00
request<LoginResponse>('/api/auth/client/login', {
2026-04-25 20:43:37 +12:00
method: 'POST',
body: JSON.stringify({ email, password })
}),
2026-04-25 22:51:36 +12:00
createMix: (payload: MixCreateInput) =>
request<Mix>('/api/mixes', {
method: 'POST',
body: JSON.stringify(payload)
}, 'client'),
updateMix: (mixId: number, payload: MixUpdateInput) =>
request<Mix>(`/api/mixes/${mixId}`, {
method: 'PATCH',
body: JSON.stringify(payload)
}, 'client'),
addMixIngredient: (mixId: number, payload: { raw_material_id: number; quantity_kg: number; notes?: string | null }) =>
request<Mix>(`/api/mixes/${mixId}/ingredients`, {
method: 'POST',
body: JSON.stringify(payload)
}, 'client'),
updateMixIngredient: (mixId: number, ingredientId: number, payload: MixIngredientUpdateInput) =>
request<Mix>(`/api/mixes/${mixId}/ingredients/${ingredientId}`, {
method: 'PATCH',
body: JSON.stringify(payload)
}, 'client'),
deleteMixIngredient: (mixId: number, ingredientId: number) =>
request<Mix>(`/api/mixes/${mixId}/ingredients/${ingredientId}`, {
method: 'DELETE'
}, 'client'),
2026-04-25 20:43:37 +12:00
createRawMaterial: (payload: RawMaterialCreateInput) =>
request<RawMaterial>('/api/raw-materials', {
method: 'POST',
body: JSON.stringify(payload)
2026-04-25 22:51:36 +12:00
}, 'client'),
2026-04-25 20:43:37 +12:00
addRawMaterialPrice: (rawMaterialId: number, payload: RawMaterialPriceCreateInput) =>
request(`/api/raw-materials/${rawMaterialId}/prices`, {
method: 'POST',
body: JSON.stringify(payload)
2026-04-25 22:51:36 +12:00
}, 'client'),
createClientUser: (payload: ClientUserCreateInput) =>
request<ClientAccessAccount>('/api/client-access/users', {
method: 'POST',
body: JSON.stringify(payload)
}, 'admin'),
updateClientUser: (userId: number, payload: ClientUserUpdateInput) =>
request<ClientAccessAccount>(`/api/client-access/users/${userId}`, {
method: 'PATCH',
body: JSON.stringify(payload)
}, 'admin'),
updateClientFeature: (featureId: number, payload: { enabled: boolean }) =>
request<ClientAccessAccount>(`/api/client-access/features/${featureId}`, {
method: 'PATCH',
body: JSON.stringify(payload)
}, 'admin')
2026-04-25 20:43:37 +12:00
};