Spaces:
Sleeping
Sleeping
| import type { | |
| CreateOrderRequest, | |
| ForgotPasswordRequest, | |
| LoginRequest, | |
| LoginResponse, | |
| Order, | |
| OrderStatusUpdateRequest, | |
| Product, | |
| RegisterRequest | |
| } from '../types/domain'; | |
| const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? ''; | |
| export class ApiError extends Error { | |
| constructor( | |
| message: string, | |
| public readonly status: number, | |
| public readonly payload?: unknown | |
| ) { | |
| super(message); | |
| this.name = 'ApiError'; | |
| } | |
| } | |
| interface RequestOptions extends Omit<RequestInit, 'body'> { | |
| token?: string | null; | |
| body?: unknown; | |
| } | |
| function url(path: string) { | |
| return `${API_BASE_URL}${path}`; | |
| } | |
| async function parseResponse(response: Response) { | |
| const contentType = response.headers.get('content-type') ?? ''; | |
| if (response.status === 204) { | |
| return undefined; | |
| } | |
| if (contentType.includes('application/json')) { | |
| return response.json(); | |
| } | |
| return response.text(); | |
| } | |
| async function request<T>(path: string, options: RequestOptions = {}): Promise<T> { | |
| const headers = new Headers(options.headers); | |
| if (options.body !== undefined && !headers.has('Content-Type')) { | |
| headers.set('Content-Type', 'application/json'); | |
| } | |
| if (options.token) { | |
| headers.set('Authorization', `Bearer ${options.token}`); | |
| } | |
| const response = await fetch(url(path), { | |
| ...options, | |
| headers, | |
| body: options.body !== undefined ? JSON.stringify(options.body) : undefined | |
| }); | |
| const payload = await parseResponse(response); | |
| if (!response.ok) { | |
| const message = | |
| typeof payload === 'string' | |
| ? payload | |
| : payload && typeof payload === 'object' && 'message' in payload | |
| ? String((payload as { message: unknown }).message) | |
| : `Request failed with status ${response.status}`; | |
| throw new ApiError(message, response.status, payload); | |
| } | |
| return payload as T; | |
| } | |
| export const api = { | |
| login: (body: LoginRequest) => request<LoginResponse>('/auth/login', { method: 'POST', body }), | |
| register: (body: RegisterRequest) => request('/auth/register', { method: 'POST', body }), | |
| forgotPassword: (body: ForgotPasswordRequest) => | |
| request<string>('/auth/forgot-password', { method: 'POST', body }), | |
| getProducts: (token: string, activeOnly = false) => | |
| request<Product[]>(activeOnly ? '/products/active' : '/products', { token }), | |
| createProduct: (token: string, body: Product) => request<Product>('/products', { method: 'POST', token, body }), | |
| updateProduct: (token: string, pid: number, body: Product) => | |
| request<Product>(`/products/${pid}`, { method: 'PUT', token, body }), | |
| deleteProduct: (token: string, pid: number) => request<void>(`/products/${pid}`, { method: 'DELETE', token }), | |
| addStock: (token: string, pid: number, quantity: number, async = false) => | |
| request<Product | string>(`/products/${pid}/add-stock${async ? '/async' : ''}`, { | |
| method: 'PATCH', | |
| token, | |
| body: { quantity } | |
| }), | |
| reserveStock: (token: string, pid: number, quantity: number, async = false) => | |
| request<Product | string>(`/products/${pid}/reserve-stock${async ? '/async' : ''}`, { | |
| method: 'PATCH', | |
| token, | |
| body: { quantity } | |
| }), | |
| createOrder: (token: string, body: CreateOrderRequest) => | |
| request<Order>('/orders', { method: 'POST', token, body }), | |
| getMyOrders: (token: string) => request<Order[]>('/orders/mine', { token }), | |
| getAllOrders: (token: string) => request<Order[]>('/orders', { token }), | |
| updateOrderStatus: (token: string, orderId: number, body: OrderStatusUpdateRequest) => | |
| request<Order>(`/orders/${orderId}/status`, { method: 'PATCH', token, body }) | |
| }; | |
| export function streamUrl(admin: boolean) { | |
| return url(admin ? '/orders/stream' : '/orders/stream/mine'); | |
| } | |