|
@@ -0,0 +1,304 @@
|
|
|
|
|
+/**
|
|
|
|
|
+ * API service layer for backend communication.
|
|
|
|
|
+ * Provides functions for all API endpoints with error handling.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Requirements: 10.1
|
|
|
|
|
+ */
|
|
|
|
|
+import axios, { AxiosInstance, AxiosError } from 'axios';
|
|
|
|
|
+import type { Project } from '../atoms/project-atoms';
|
|
|
|
|
+import type { Task } from '../atoms/task-atoms';
|
|
|
|
|
+import type { Annotation } from '../atoms/annotation-atoms';
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * API base URL - defaults to localhost:8000 for development
|
|
|
|
|
+ */
|
|
|
|
|
+const API_BASE_URL = process.env.NX_API_BASE_URL || 'http://localhost:8000';
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Axios instance with default configuration
|
|
|
|
|
+ */
|
|
|
|
|
+const apiClient: AxiosInstance = axios.create({
|
|
|
|
|
+ baseURL: API_BASE_URL,
|
|
|
|
|
+ timeout: 30000,
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
|
|
+ },
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Response interceptor for error handling
|
|
|
|
|
+ */
|
|
|
|
|
+apiClient.interceptors.response.use(
|
|
|
|
|
+ (response) => response,
|
|
|
|
|
+ (error: AxiosError) => {
|
|
|
|
|
+ // Extract error message from response
|
|
|
|
|
+ const errorMessage =
|
|
|
|
|
+ (error.response?.data as any)?.detail ||
|
|
|
|
|
+ error.message ||
|
|
|
|
|
+ 'An unexpected error occurred';
|
|
|
|
|
+
|
|
|
|
|
+ // Log error for debugging
|
|
|
|
|
+ console.error('API Error:', {
|
|
|
|
|
+ url: error.config?.url,
|
|
|
|
|
+ method: error.config?.method,
|
|
|
|
|
+ status: error.response?.status,
|
|
|
|
|
+ message: errorMessage,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Return a rejected promise with formatted error
|
|
|
|
|
+ return Promise.reject({
|
|
|
|
|
+ status: error.response?.status,
|
|
|
|
|
+ message: errorMessage,
|
|
|
|
|
+ originalError: error,
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+);
|
|
|
|
|
+
|
|
|
|
|
+// ============================================================================
|
|
|
|
|
+// Project API Functions
|
|
|
|
|
+// ============================================================================
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Project creation data
|
|
|
|
|
+ */
|
|
|
|
|
+export interface ProjectCreateData {
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ description: string;
|
|
|
|
|
+ config: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Project update data
|
|
|
|
|
+ */
|
|
|
|
|
+export interface ProjectUpdateData {
|
|
|
|
|
+ name?: string;
|
|
|
|
|
+ description?: string;
|
|
|
|
|
+ config?: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * List all projects
|
|
|
|
|
+ */
|
|
|
|
|
+export async function listProjects(): Promise<Project[]> {
|
|
|
|
|
+ const response = await apiClient.get<Project[]>('/api/projects');
|
|
|
|
|
+ return response.data;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Create a new project
|
|
|
|
|
+ */
|
|
|
|
|
+export async function createProject(
|
|
|
|
|
+ data: ProjectCreateData
|
|
|
|
|
+): Promise<Project> {
|
|
|
|
|
+ const response = await apiClient.post<Project>('/api/projects', data);
|
|
|
|
|
+ return response.data;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Get project by ID
|
|
|
|
|
+ */
|
|
|
|
|
+export async function getProject(projectId: string): Promise<Project> {
|
|
|
|
|
+ const response = await apiClient.get<Project>(`/api/projects/${projectId}`);
|
|
|
|
|
+ return response.data;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Update project
|
|
|
|
|
+ */
|
|
|
|
|
+export async function updateProject(
|
|
|
|
|
+ projectId: string,
|
|
|
|
|
+ data: ProjectUpdateData
|
|
|
|
|
+): Promise<Project> {
|
|
|
|
|
+ const response = await apiClient.put<Project>(
|
|
|
|
|
+ `/api/projects/${projectId}`,
|
|
|
|
|
+ data
|
|
|
|
|
+ );
|
|
|
|
|
+ return response.data;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Delete project
|
|
|
|
|
+ */
|
|
|
|
|
+export async function deleteProject(projectId: string): Promise<void> {
|
|
|
|
|
+ await apiClient.delete(`/api/projects/${projectId}`);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Get tasks for a specific project
|
|
|
|
|
+ */
|
|
|
|
|
+export async function getProjectTasks(projectId: string): Promise<Task[]> {
|
|
|
|
|
+ return listTasks({ project_id: projectId });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ============================================================================
|
|
|
|
|
+// Task API Functions
|
|
|
|
|
+// ============================================================================
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Task creation data
|
|
|
|
|
+ */
|
|
|
|
|
+export interface TaskCreateData {
|
|
|
|
|
+ project_id: string;
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ data: Record<string, any>;
|
|
|
|
|
+ assigned_to?: string | null;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Task update data
|
|
|
|
|
+ */
|
|
|
|
|
+export interface TaskUpdateData {
|
|
|
|
|
+ name?: string;
|
|
|
|
|
+ data?: Record<string, any>;
|
|
|
|
|
+ status?: 'pending' | 'in_progress' | 'completed';
|
|
|
|
|
+ assigned_to?: string | null;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Task list filters
|
|
|
|
|
+ */
|
|
|
|
|
+export interface TaskListFilters {
|
|
|
|
|
+ project_id?: string;
|
|
|
|
|
+ status?: string;
|
|
|
|
|
+ assigned_to?: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * List all tasks with optional filters
|
|
|
|
|
+ */
|
|
|
|
|
+export async function listTasks(filters?: TaskListFilters): Promise<Task[]> {
|
|
|
|
|
+ const params = new URLSearchParams();
|
|
|
|
|
+ if (filters?.project_id) params.append('project_id', filters.project_id);
|
|
|
|
|
+ if (filters?.status) params.append('status', filters.status);
|
|
|
|
|
+ if (filters?.assigned_to) params.append('assigned_to', filters.assigned_to);
|
|
|
|
|
+
|
|
|
|
|
+ const response = await apiClient.get<Task[]>('/api/tasks', { params });
|
|
|
|
|
+ return response.data;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Create a new task
|
|
|
|
|
+ */
|
|
|
|
|
+export async function createTask(data: TaskCreateData): Promise<Task> {
|
|
|
|
|
+ const response = await apiClient.post<Task>('/api/tasks', data);
|
|
|
|
|
+ return response.data;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Get task by ID
|
|
|
|
|
+ */
|
|
|
|
|
+export async function getTask(taskId: string): Promise<Task> {
|
|
|
|
|
+ const response = await apiClient.get<Task>(`/api/tasks/${taskId}`);
|
|
|
|
|
+ return response.data;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Update task
|
|
|
|
|
+ */
|
|
|
|
|
+export async function updateTask(
|
|
|
|
|
+ taskId: string,
|
|
|
|
|
+ data: TaskUpdateData
|
|
|
|
|
+): Promise<Task> {
|
|
|
|
|
+ const response = await apiClient.put<Task>(`/api/tasks/${taskId}`, data);
|
|
|
|
|
+ return response.data;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Delete task
|
|
|
|
|
+ */
|
|
|
|
|
+export async function deleteTask(taskId: string): Promise<void> {
|
|
|
|
|
+ await apiClient.delete(`/api/tasks/${taskId}`);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ============================================================================
|
|
|
|
|
+// Annotation API Functions
|
|
|
|
|
+// ============================================================================
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Annotation creation data
|
|
|
|
|
+ */
|
|
|
|
|
+export interface AnnotationCreateData {
|
|
|
|
|
+ task_id: string;
|
|
|
|
|
+ user_id: string;
|
|
|
|
|
+ result: Record<string, any>;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Annotation update data
|
|
|
|
|
+ */
|
|
|
|
|
+export interface AnnotationUpdateData {
|
|
|
|
|
+ result: Record<string, any>;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Annotation list filters
|
|
|
|
|
+ */
|
|
|
|
|
+export interface AnnotationListFilters {
|
|
|
|
|
+ task_id?: string;
|
|
|
|
|
+ user_id?: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * List all annotations with optional filters
|
|
|
|
|
+ */
|
|
|
|
|
+export async function listAnnotations(
|
|
|
|
|
+ filters?: AnnotationListFilters
|
|
|
|
|
+): Promise<Annotation[]> {
|
|
|
|
|
+ const params = new URLSearchParams();
|
|
|
|
|
+ if (filters?.task_id) params.append('task_id', filters.task_id);
|
|
|
|
|
+ if (filters?.user_id) params.append('user_id', filters.user_id);
|
|
|
|
|
+
|
|
|
|
|
+ const response = await apiClient.get<Annotation[]>('/api/annotations', {
|
|
|
|
|
+ params,
|
|
|
|
|
+ });
|
|
|
|
|
+ return response.data;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Create a new annotation
|
|
|
|
|
+ */
|
|
|
|
|
+export async function createAnnotation(
|
|
|
|
|
+ data: AnnotationCreateData
|
|
|
|
|
+): Promise<Annotation> {
|
|
|
|
|
+ const response = await apiClient.post<Annotation>('/api/annotations', data);
|
|
|
|
|
+ return response.data;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Get annotation by ID
|
|
|
|
|
+ */
|
|
|
|
|
+export async function getAnnotation(annotationId: string): Promise<Annotation> {
|
|
|
|
|
+ const response = await apiClient.get<Annotation>(
|
|
|
|
|
+ `/api/annotations/${annotationId}`
|
|
|
|
|
+ );
|
|
|
|
|
+ return response.data;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Update annotation
|
|
|
|
|
+ */
|
|
|
|
|
+export async function updateAnnotation(
|
|
|
|
|
+ annotationId: string,
|
|
|
|
|
+ data: AnnotationUpdateData
|
|
|
|
|
+): Promise<Annotation> {
|
|
|
|
|
+ const response = await apiClient.put<Annotation>(
|
|
|
|
|
+ `/api/annotations/${annotationId}`,
|
|
|
|
|
+ data
|
|
|
|
|
+ );
|
|
|
|
|
+ return response.data;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Get annotations by task ID
|
|
|
|
|
+ */
|
|
|
|
|
+export async function getTaskAnnotations(taskId: string): Promise<Annotation[]> {
|
|
|
|
|
+ const response = await apiClient.get<Annotation[]>(
|
|
|
|
|
+ `/api/annotations/tasks/${taskId}/annotations`
|
|
|
|
|
+ );
|
|
|
|
|
+ return response.data;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Export the configured axios instance for advanced usage
|
|
|
|
|
+ */
|
|
|
|
|
+export { apiClient };
|