Initial commit: Velocity-OS migration

This commit is contained in:
2026-05-01 12:32:19 +05:30
commit 407af828d4
283 changed files with 207782 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
/**
* Velocity-OS API Client
* Thin wrapper around fetch. Injects JWT auth header automatically.
* All requests go to /api (proxied by Traefik to core-api:8443).
*/
import { useAuthStore } from '@/store/authStore';
const BASE_URL = '/api';
export class ApiError extends Error {
constructor(public status: number, message: string) {
super(message);
this.name = 'ApiError';
}
}
async function apiFetch<T>(
path: string,
options: RequestInit = {}
): Promise<T> {
const token = useAuthStore.getState().token;
const response = await fetch(`${BASE_URL}${path}`, {
...options,
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
...(options.headers ?? {}),
},
});
if (response.status === 401) {
// Token expired — clear session and let AuthGuard redirect
useAuthStore.getState().clearSession();
throw new ApiError(401, 'Session expired');
}
if (!response.ok) {
const body = await response.text().catch(() => '');
throw new ApiError(response.status, body || `HTTP ${response.status}`);
}
if (response.status === 204) return undefined as T;
return response.json();
}
export const api = {
get: <T>(path: string) => apiFetch<T>(path),
post: <T>(path: string, body: unknown) => apiFetch<T>(path, { method: 'POST', body: JSON.stringify(body) }),
patch: <T>(path: string, body: unknown) => apiFetch<T>(path, { method: 'PATCH', body: JSON.stringify(body) }),
delete: <T>(path: string) => apiFetch<T>(path, { method: 'DELETE' }),
};