Files
plume/services/api.ts
2026-02-16 22:02:45 +01:00

157 lines
5.2 KiB
TypeScript

import {
BookProject,
Chapter,
Entity,
Idea,
UserProfile
} from '../types';
const API_BASE_URL = '/api'; // Proxied by Vite
// --- API CLIENT GENERIC ---
const api = {
// Generic fetch with cookies
// Generic fetch with cookies
// Generic fetch with cookies
async request<T = any>(endpoint: string, options: RequestInit = {}) {
// Append Instance to query params
const instanceId = "54770_plumeia"; // Fallback if env missing in some contexts
const separator = endpoint.includes('?') ? '&' : '?';
const url = `${API_BASE_URL}${endpoint}${separator}Instance=${instanceId}`;
const headers = {
'Content-Type': 'application/json',
'X-Database-Instance': instanceId, // Keep header just in case
...options.headers,
};
const response = await fetch(url, {
...options,
headers,
credentials: 'include', // IMPORTANT: Send cookies
});
if (!response.ok) {
let errorMsg = `Error ${response.status}: ${response.statusText}`;
try {
const errorJson = await response.json();
console.error("API Error JSON:", errorJson);
if (errorJson.error) errorMsg = errorJson.error;
if (errorJson.message) errorMsg = errorJson.message;
} catch (e) {
// Ignore json parse error
}
throw new Error(errorMsg);
}
// Return null if 204 No Content
if (response.status === 204) return null;
return response.json();
},
// --- AUTH ENDPOINTS ---
auth: {
async getSession() {
try {
return await api.request('/user-auth/get-session');
} catch (e) {
return null; // No session
}
},
async signIn(email: string, password: string) {
return api.request('/user-auth/sign-in/email', {
method: 'POST',
body: JSON.stringify({ email, password }),
});
},
async signUp(email: string, password: string, name: string) {
return api.request('/user-auth/sign-up/email', {
method: 'POST',
body: JSON.stringify({ email, password, name }),
});
},
async signOut() {
return api.request('/user-auth/sign-out', {
method: 'POST',
});
}
},
// --- DATA ENDPOINTS ---
data: {
// Generic list (read all)
async list<T>(table: string) {
// Swagger: GET /read/{table} -> { status: "success", data: [...] }
const res = await api.request<{ data: T[] }>(`/data/read/${table}`);
return res.data || [];
},
// Generic get (read one)
async get<T>(table: string, id: string) {
// Swagger: GET /read/{table}/{id} -> { status: "success", data: { ... } }
const res = await api.request<{ data: T }>(`/data/read/${table}/${id}`);
return res.data;
},
// Generic create
async create<T>(table: string, data: any) {
// Swagger: POST /create/{table} -> { status: "success", id: 123 }
// Return the whole response so caller can get ID
return api.request(`/data/create/${table}`, {
method: 'POST',
body: JSON.stringify(data),
}) as Promise<T>;
},
// Generic update
async update<T>(table: string, id: string, data: any) {
// Swagger: PUT /update/{table}/{id}
// Note: Swagger for update usually expects "update" path prefix
return api.request(`/data/update/${table}/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
}) as Promise<T>;
},
// Generic delete
async delete(table: string, id: string) {
// Swagger: DELETE /delete/{table}/{id}
return api.request(`/data/delete/${table}/${id}`, {
method: 'DELETE',
});
},
// --- Specialized Project Methods ---
// Fetch full project with related data (chapters, entities, etc)
// NOTE: In a real app, you might want to fetch these separately or use a join if supported.
// For now, we'll fetch the project and then fetch its children.
async getFullProject(projectId: string): Promise<BookProject> {
const project = await this.get<any>('projects', projectId);
// Fetch related data in parallel
const [chapters, entities, ideas] = await Promise.all([
api.request(`/data/chapters/list?project_id=${projectId}`),
api.request(`/data/entities/list?project_id=${projectId}`),
api.request(`/data/ideas/list?project_id=${projectId}`),
]);
return {
...project,
chapters: chapters || [],
entities: entities || [],
ideas: ideas || [],
// templates: [], // Not yet in DB
// workflow: null // Not yet in DB
};
}
}
};
export default api;