| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- const API_BASE = process.env.NEXT_PUBLIC_API_URL || '';
- interface FetchOptions extends RequestInit {
- token?: string;
- }
- async function apiFetch<T = unknown>(
- endpoint: string,
- options: FetchOptions = {}
- ): Promise<T> {
- const { token, ...fetchOptions } = options;
- const headers: Record<string, string> = {
- 'Content-Type': 'application/json',
- ...(options.headers as Record<string, string> || {}),
- };
- if (token) {
- headers['Authorization'] = `Bearer ${token}`;
- }
- const res = await fetch(`${API_BASE}${endpoint}`, {
- ...fetchOptions,
- headers,
- credentials: 'include',
- });
- if (!res.ok) {
- const error = await res.json().catch(() => ({ error: res.statusText }));
- throw new Error(error.error || `HTTP ${res.status}`);
- }
- return res.json();
- }
- // ── Auth ─────────────────────────────────────────────────────────────────────
- export const authApi = {
- register: (data: { email: string; name: string; password: string }) =>
- apiFetch<{ user: User; token: string }>('/api/auth/register', {
- method: 'POST',
- body: JSON.stringify(data),
- }),
- login: (data: { email: string; password: string }) =>
- apiFetch<{ user: User; token: string }>('/api/auth/login', {
- method: 'POST',
- body: JSON.stringify(data),
- }),
- logout: () =>
- apiFetch('/api/auth/logout', { method: 'POST' }),
- me: (token: string) =>
- apiFetch<{ user: User }>('/api/auth/me', { token }),
- };
- // ── Projects ─────────────────────────────────────────────────────────────────
- export const projectsApi = {
- list: (token: string) =>
- apiFetch<{ projects: Project[] }>('/api/projects', { token }),
- create: (token: string, data: { name: string; description?: string }) =>
- apiFetch<{ project: Project }>('/api/projects', {
- method: 'POST',
- body: JSON.stringify(data),
- token,
- }),
- get: (token: string, id: string) =>
- apiFetch<{ project: Project }>(`/api/projects/${id}`, { token }),
- update: (token: string, id: string, data: { name?: string; description?: string }) =>
- apiFetch<{ project: Project }>(`/api/projects/${id}`, {
- method: 'PUT',
- body: JSON.stringify(data),
- token,
- }),
- delete: (token: string, id: string) =>
- apiFetch(`/api/projects/${id}`, { method: 'DELETE', token }),
- inviteMember: (token: string, projectId: string, email: string, role: string) =>
- apiFetch(`/api/projects/${projectId}/members`, {
- method: 'POST',
- body: JSON.stringify({ email, role }),
- token,
- }),
- };
- // ── Assets ───────────────────────────────────────────────────────────────────
- export const assetsApi = {
- list: (token: string, projectId: string) =>
- apiFetch<{ assets: Asset[] }>(`/api/assets?projectId=${projectId}`, { token }),
- get: (token: string, id: string) =>
- apiFetch<{ asset: AssetWithComments }>(`/api/assets/${id}`, { token }),
- upload: (token: string, formData: FormData) =>
- fetch(`${API_BASE}/api/assets/upload`, {
- method: 'POST',
- headers: { Authorization: `Bearer ${token}` },
- body: formData,
- }).then(r => r.json()),
- updateStatus: (token: string, id: string, status: string) =>
- apiFetch<{ asset: Asset }>(`/api/assets/${id}/status`, {
- method: 'PUT',
- body: JSON.stringify({ status }),
- token,
- }),
- delete: (token: string, id: string) =>
- apiFetch(`/api/assets/${id}`, { method: 'DELETE', token }),
- };
- // ── Comments ─────────────────────────────────────────────────────────────────
- export const commentsApi = {
- list: (token: string, assetId: string, resolved?: boolean) => {
- const q = resolved !== undefined ? `?resolved=${resolved}` : '';
- return apiFetch<{ comments: Comment[] }>(`/api/assets/${assetId}/comments${q}`, { token });
- },
- create: (token: string, assetId: string, data: {
- content: string;
- timestamp?: number;
- annotations?: AnnotationData[];
- parentId?: string;
- }) =>
- apiFetch<{ comment: Comment }>(`/api/assets/${assetId}/comments`, {
- method: 'POST',
- body: JSON.stringify(data),
- token,
- }),
- resolve: (token: string, id: string) =>
- apiFetch<{ comment: Comment }>(`/api/comments/${id}/resolve`, { method: 'PUT', token }),
- updateAnnotations: (token: string, id: string, annotations: AnnotationData[]) =>
- apiFetch<{ comment: Comment }>(`/api/comments/${id}/annotations`, {
- method: 'PUT',
- body: JSON.stringify({ annotations }),
- token,
- }),
- delete: (token: string, id: string) =>
- apiFetch(`/api/comments/${id}`, { method: 'DELETE', token }),
- };
- // ── Users ────────────────────────────────────────────────────────────────────
- export const usersApi = {
- list: (token: string) =>
- apiFetch<{ users: AdminUser[] }>('/api/users', { token }),
- getMe: (token: string) =>
- apiFetch<{ user: AdminUser }>('/api/users/me', { token }),
- updateMe: (token: string, data: {
- name?: string;
- avatarUrl?: string;
- currentPassword?: string;
- newPassword?: string;
- }) =>
- apiFetch<{ user: AdminUser }>('/api/users/me', {
- method: 'PUT',
- body: JSON.stringify(data),
- token,
- }),
- updateRole: (token: string, id: string, role: string) =>
- apiFetch<{ user: AdminUser }>(`/api/users/${id}/role`, {
- method: 'PUT',
- body: JSON.stringify({ role }),
- token,
- }),
- updateActive: (token: string, id: string, active: boolean) =>
- apiFetch<{ user: AdminUser }>(`/api/users/${id}/active`, {
- method: 'PUT',
- body: JSON.stringify({ active }),
- token,
- }),
- deleteUser: (token: string, id: string) =>
- apiFetch(`/api/users/${id}`, { method: 'DELETE', token }),
- };
- // ── Types ─────────────────────────────────────────────────────────────────────
- export interface User {
- id: string;
- email: string;
- name: string;
- role: string;
- avatarUrl?: string | null;
- }
- export interface AdminUser extends User {
- active: boolean;
- createdAt: string;
- _count?: {
- memberships: number;
- comments: number;
- };
- }
- export interface Project {
- id: string;
- name: string;
- description?: string | null;
- createdAt: string;
- members: Array<{ id: string; role: string; user: User }>;
- _count?: { assets: number };
- }
- export interface Asset {
- id: string;
- projectId: string;
- title: string;
- filename: string;
- filePath: string;
- thumbnail?: string | null;
- hlsPath?: string | null;
- duration?: number | null;
- fps?: number;
- mimeType: string;
- status: string;
- createdAt: string;
- _count?: { comments: number };
- }
- export interface AssetWithComments extends Asset {
- project: Project;
- comments: Comment[];
- }
- export interface Comment {
- id: string;
- assetId: string;
- userId: string;
- content: string;
- timestamp?: number | null;
- annotations?: AnnotationData[] | null;
- resolved: boolean;
- parentId?: string | null;
- createdAt: string;
- user: User;
- replies?: Comment[];
- }
- export interface AnnotationData {
- type: 'pen' | 'arrow' | 'rect' | 'ellipse' | 'text';
- color: string;
- points?: [number, number][];
- text?: string;
- boundingBox?: { x: number; y: number; width: number; height: number };
- }
|