login ok
This commit is contained in:
@@ -1,264 +0,0 @@
|
||||
import { BookProject, Chapter, Entity, Idea, WorkflowData, UserProfile } from '../types';
|
||||
|
||||
// Configuration NoCodeBackend
|
||||
const AUTH_API_ROOT = 'https://app.nocodebackend.com/api/user-auth';
|
||||
const DATA_API_ROOT = 'https://openapi.nocodebackend.com';
|
||||
const INSTANCE_ID = '54770_plumeia_db';
|
||||
// WARNING: SECRET_KEY restored for debugging purposes (createItem failed without it).
|
||||
// Ideally, RLS should allow user creation. If not, this key is needed.
|
||||
const SECRET_KEY = 'c11b7be982d6f369b71a74a7735caf8c2d25d6b09bb5a6ac52d12d49bff3acf1';
|
||||
const mcp_server = "ncb_67f8e246c8e4e18598918212035ba6b309d6cac9a6aec919";
|
||||
/**
|
||||
* Récupère les headers avec gestion du token de session
|
||||
* @param forceUserToken Si true, on n'utilise pas la SECRET_KEY en fallback (pour l'auth)
|
||||
*/
|
||||
const getHeaders = (isAuthAction = false) => {
|
||||
const token = localStorage.getItem('ncb_session_token');
|
||||
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Database-Instance': INSTANCE_ID
|
||||
};
|
||||
|
||||
// RLS requires the User Token in the Authorization header
|
||||
if (token && !isAuthAction) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
} else {
|
||||
// FALLBACK: Use Secret Key if no token is available (e.g., initial profile creation)
|
||||
headers['Authorization'] = `Bearer ${SECRET_KEY}`;
|
||||
}
|
||||
|
||||
return headers;
|
||||
};
|
||||
|
||||
/**
|
||||
* Service d'authentification NoCodeBackend
|
||||
*/
|
||||
export const authService = {
|
||||
async getSession() {
|
||||
console.log("[Auth] getSession");
|
||||
const token = localStorage.getItem('ncb_session_token');
|
||||
if (!token) return null;
|
||||
console.log("[Auth] getSession token:", token);
|
||||
|
||||
try {
|
||||
const res = await fetch(`${AUTH_API_ROOT}/get-session`, {
|
||||
method: 'GET',
|
||||
headers: getHeaders(),
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
console.log("[Auth] getSession data:", data);
|
||||
|
||||
if (!res.ok || !data) { // Vérifie que data existe
|
||||
if (res.status === 401) localStorage.removeItem('ncb_session_token');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Utilise le chaînage optionnel ?.
|
||||
return data?.user || (data?.id ? data : null);
|
||||
} catch (err) {
|
||||
console.error("[Auth] Erreur critique getSession:", err);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async signIn(email: string, password: string) {
|
||||
console.log("[Auth] Tentative de connexion pour:", email);
|
||||
try {
|
||||
const res = await fetch(`${AUTH_API_ROOT}/sign-in/email`, {
|
||||
method: 'POST',
|
||||
headers: getHeaders(true),
|
||||
body: JSON.stringify({ email, password }),
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
console.log("[Auth] signIn: Payload reçu:", data);
|
||||
|
||||
if (!res.ok) {
|
||||
return { error: data.message || 'Identifiants incorrects' };
|
||||
}
|
||||
|
||||
// DEBUG: Trace exact token extraction
|
||||
console.log("[Auth] Token extraction attempt:", {
|
||||
'user.token': data.user?.token,
|
||||
'token': data.token,
|
||||
'session.token': data.session?.token,
|
||||
'auth_token': data.auth_token,
|
||||
'accessToken': data.accessToken,
|
||||
'_token': data._token
|
||||
});
|
||||
|
||||
const token = data.user?.token || data.token || data.session?.token || data.auth_token || data.accessToken || data._token;
|
||||
console.log("[Auth] Final extracted token:", token);
|
||||
|
||||
if (token) {
|
||||
localStorage.setItem('ncb_session_token', token);
|
||||
} else {
|
||||
console.error("[Auth] WARNING: No token found in response!");
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (err) {
|
||||
console.error("[Auth] Erreur réseau sign-in:", err);
|
||||
return { error: 'Erreur réseau lors de la connexion' };
|
||||
}
|
||||
},
|
||||
|
||||
async signUp(email: string, password: string, name: string) {
|
||||
try {
|
||||
const res = await fetch(`${AUTH_API_ROOT}/sign-up/email`, {
|
||||
method: 'POST',
|
||||
headers: getHeaders(true),
|
||||
body: JSON.stringify({ email, password, name }),
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
return { error: data.message || "Erreur lors de la création du compte" };
|
||||
}
|
||||
|
||||
const token = data.user?.token || data.token || data.session?.token || data.auth_token || data.accessToken;
|
||||
|
||||
if (token) {
|
||||
localStorage.setItem('ncb_session_token', token);
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (err) {
|
||||
return { error: "Erreur réseau lors de l'inscription" };
|
||||
}
|
||||
},
|
||||
|
||||
async signOut() {
|
||||
try {
|
||||
await fetch(`${AUTH_API_ROOT}/sign-out`, {
|
||||
method: 'POST',
|
||||
headers: getHeaders(true),
|
||||
body: JSON.stringify({}),
|
||||
credentials: 'include'
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("[Auth] Erreur sign-out:", err);
|
||||
} finally {
|
||||
localStorage.removeItem('ncb_session_token');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const dataService = {
|
||||
async getProfile(userId: string) {
|
||||
try {
|
||||
const encodedId = encodeURIComponent(userId);
|
||||
const url = `${DATA_API_ROOT}/read/profiles?Instance=${INSTANCE_ID}&user_id=${encodedId}`;
|
||||
const res = await fetch(url, { headers: getHeaders(), credentials: 'include' });
|
||||
const json = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
console.error("[Data] Erreur lecture profil API:", json);
|
||||
return null;
|
||||
}
|
||||
|
||||
return json.data?.[0] || null;
|
||||
} catch (err) {
|
||||
console.error("[Data] Erreur lecture profil:", err);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async getProjects(userId: string) {
|
||||
try {
|
||||
const encodedId = encodeURIComponent(userId);
|
||||
const url = `${DATA_API_ROOT}/read/projects?Instance=${INSTANCE_ID}&user_id=${encodedId}`;
|
||||
const res = await fetch(url, { headers: getHeaders(), credentials: 'include' });
|
||||
const json = await res.json();
|
||||
return json.data || [];
|
||||
} catch (err) {
|
||||
console.error("[Data] Erreur lecture projets:", err);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
async createProject(projectData: any) {
|
||||
try {
|
||||
const url = `${DATA_API_ROOT}/create/projects?Instance=${INSTANCE_ID}`;
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: getHeaders(),
|
||||
body: JSON.stringify(projectData),
|
||||
credentials: 'include'
|
||||
});
|
||||
const data = await res.json();
|
||||
return data;
|
||||
} catch (err) {
|
||||
console.error("[Data] Erreur création projet:", err);
|
||||
return { status: 'error' };
|
||||
}
|
||||
},
|
||||
|
||||
async getRelatedData(table: string, projectId: number) {
|
||||
try {
|
||||
const url = `${DATA_API_ROOT}/read/${table}?Instance=${INSTANCE_ID}&project_id=${projectId}`;
|
||||
const res = await fetch(url, { headers: getHeaders(), credentials: 'include' });
|
||||
const json = await res.json();
|
||||
return json.data || [];
|
||||
} catch (err) {
|
||||
console.error(`[Data] Erreur lecture ${table}:`, err);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
async createItem(table: string, data: any) {
|
||||
try {
|
||||
console.log(`[Data] Création item dans ${table}...`, data);
|
||||
const url = `${DATA_API_ROOT}/create/${table}?Instance=${INSTANCE_ID}`;
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: getHeaders(),
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
const result = await res.json();
|
||||
if (!res.ok) {
|
||||
console.error(`[Data] Erreur API création ${table}:`, result);
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.error(`[Data] Erreur réseau création item ${table}:`, err);
|
||||
return { status: 'error', message: err };
|
||||
}
|
||||
},
|
||||
|
||||
async updateItem(table: string, id: number, data: any) {
|
||||
try {
|
||||
const url = `${DATA_API_ROOT}/update/${table}/${id}?Instance=${INSTANCE_ID}`;
|
||||
await fetch(url, {
|
||||
method: 'PUT',
|
||||
headers: getHeaders(),
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error(`[Data] Erreur mise à jour item ${table}:`, err);
|
||||
}
|
||||
},
|
||||
|
||||
async deleteItem(table: string, id: number) {
|
||||
try {
|
||||
const url = `${DATA_API_ROOT}/delete/${table}/${id}?Instance=${INSTANCE_ID}`;
|
||||
await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: getHeaders(),
|
||||
credentials: 'include'
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`[Data] Erreur suppression item ${table}:`, err);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,233 +0,0 @@
|
||||
|
||||
import { BookProject, Chapter, Entity, Idea, WorkflowData, UserProfile } from '../types';
|
||||
|
||||
// --- CONFIGURATION ---
|
||||
const AUTH_API_ROOT = 'https://app.nocodebackend.com/api/user-auth';
|
||||
// Use the logic from the guide: Data API is at app.nocodebackend.com/api/data
|
||||
const DATA_API_ROOT = 'https://app.nocodebackend.com/api/data';
|
||||
const INSTANCE_ID = '54770_plumeia_db';
|
||||
|
||||
// --- HELPERS ---
|
||||
|
||||
/**
|
||||
* Retrieves headers with the Session Token from localStorage.
|
||||
* Used for both Auth (when authenticated) and Data requests.
|
||||
*/
|
||||
const getHeaders = () => {
|
||||
const token = localStorage.getItem('ncb_session_token');
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Database-Instance': INSTANCE_ID
|
||||
};
|
||||
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
return headers;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts and stores the session token from the Auth Response.
|
||||
* Handles various token field names found in NCB responses.
|
||||
*/
|
||||
const handleAuthResponse = async (res: Response) => {
|
||||
const data = await res.json();
|
||||
console.log("[API] Auth Response:", data);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(data.message || 'Authentication failed');
|
||||
}
|
||||
|
||||
// extract token
|
||||
const token =
|
||||
data.user?.token ||
|
||||
data.token ||
|
||||
data.session?.token ||
|
||||
data.auth_token ||
|
||||
data.accessToken ||
|
||||
data._token;
|
||||
|
||||
if (token) {
|
||||
console.log("[API] Token extracted and saved:", token);
|
||||
localStorage.setItem('ncb_session_token', token);
|
||||
} else {
|
||||
console.warn("[API] No token found in successful auth response!");
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
// --- AUTH SERVICE ---
|
||||
|
||||
export const authService = {
|
||||
async getSession() {
|
||||
const token = localStorage.getItem('ncb_session_token');
|
||||
if (!token) return null;
|
||||
|
||||
try {
|
||||
const res = await fetch(`${AUTH_API_ROOT}/get-session?Instance=${INSTANCE_ID}`, {
|
||||
method: 'GET',
|
||||
headers: getHeaders()
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
// If 401, token is invalid
|
||||
if (res.status === 401) {
|
||||
localStorage.removeItem('ncb_session_token');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
return data.user || data;
|
||||
} catch (err) {
|
||||
console.error("[Auth] getSession error:", err);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async signIn(email: string, password: string) {
|
||||
try {
|
||||
const res = await fetch(`${AUTH_API_ROOT}/sign-in/email?Instance=${INSTANCE_ID}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Database-Instance': INSTANCE_ID
|
||||
},
|
||||
body: JSON.stringify({ email, password })
|
||||
});
|
||||
|
||||
return await handleAuthResponse(res);
|
||||
} catch (err: any) {
|
||||
console.error("[Auth] signIn error:", err);
|
||||
return { error: err.message || 'Connection failed' };
|
||||
}
|
||||
},
|
||||
|
||||
async signUp(email: string, password: string, name: string) {
|
||||
try {
|
||||
const res = await fetch(`${AUTH_API_ROOT}/sign-up/email?Instance=${INSTANCE_ID}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Database-Instance': INSTANCE_ID
|
||||
},
|
||||
body: JSON.stringify({ email, password, name })
|
||||
});
|
||||
|
||||
return await handleAuthResponse(res);
|
||||
} catch (err: any) {
|
||||
return { error: err.message || 'Registration failed' };
|
||||
}
|
||||
},
|
||||
|
||||
async signOut() {
|
||||
try {
|
||||
await fetch(`${AUTH_API_ROOT}/sign-out?Instance=${INSTANCE_ID}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("[Auth] signOut error:", err);
|
||||
} finally {
|
||||
localStorage.removeItem('ncb_session_token');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// --- DATA SERVICE ---
|
||||
|
||||
export const dataService = {
|
||||
async getProfile(userId: string) {
|
||||
try {
|
||||
// Use standard read endpoint
|
||||
const encodedId = encodeURIComponent(userId);
|
||||
const url = `${DATA_API_ROOT}/read/profiles?Instance=${INSTANCE_ID}&user_id=${encodedId}`;
|
||||
const res = await fetch(url, { headers: getHeaders() });
|
||||
const json = await res.json();
|
||||
return json.data?.[0] || null;
|
||||
} catch (err) {
|
||||
console.error("[Data] getProfile error:", err);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async getProjects(userId: string) {
|
||||
try {
|
||||
const encodedId = encodeURIComponent(userId);
|
||||
const url = `${DATA_API_ROOT}/read/projects?Instance=${INSTANCE_ID}&user_id=${encodedId}`;
|
||||
const res = await fetch(url, { headers: getHeaders() });
|
||||
const json = await res.json();
|
||||
return json.data || [];
|
||||
} catch (err) {
|
||||
console.error("[Data] getProjects error:", err);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
async createProject(projectData: any) {
|
||||
return this.createItem('projects', projectData);
|
||||
},
|
||||
|
||||
async getRelatedData(table: string, projectId: number) {
|
||||
try {
|
||||
const url = `${DATA_API_ROOT}/read/${table}?Instance=${INSTANCE_ID}&project_id=${projectId}`;
|
||||
const res = await fetch(url, { headers: getHeaders() });
|
||||
const json = await res.json();
|
||||
return json.data || [];
|
||||
} catch (err) {
|
||||
console.error(`[Data] getRelatedData ${table} error:`, err);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
async createItem(table: string, data: any) {
|
||||
try {
|
||||
console.log(`[Data] Creating item in ${table}...`, data);
|
||||
const url = `${DATA_API_ROOT}/create/${table}?Instance=${INSTANCE_ID}`;
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: getHeaders(),
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
const result = await res.json();
|
||||
if (!res.ok) {
|
||||
console.error(`[Data] Create ${table} failed:`, result);
|
||||
return { status: 'error', message: result.message || 'Creation failed' };
|
||||
}
|
||||
return { status: 'success', ...result };
|
||||
} catch (err) {
|
||||
console.error(`[Data] Create ${table} network error:`, err);
|
||||
return { status: 'error', message: err };
|
||||
}
|
||||
},
|
||||
|
||||
async updateItem(table: string, id: number, data: any) {
|
||||
try {
|
||||
const url = `${DATA_API_ROOT}/update/${table}/${id}?Instance=${INSTANCE_ID}`;
|
||||
await fetch(url, {
|
||||
method: 'PUT',
|
||||
headers: getHeaders(),
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`[Data] Update ${table} error:`, err);
|
||||
}
|
||||
},
|
||||
|
||||
async deleteItem(table: string, id: number) {
|
||||
try {
|
||||
const url = `${DATA_API_ROOT}/delete/${table}/${id}?Instance=${INSTANCE_ID}`;
|
||||
await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: getHeaders()
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`[Data] Delete ${table} error:`, err);
|
||||
}
|
||||
}
|
||||
};
|
||||
479
services/api.ts
479
services/api.ts
@@ -1,401 +1,156 @@
|
||||
import {
|
||||
BookProject,
|
||||
Chapter,
|
||||
Entity,
|
||||
Idea,
|
||||
UserProfile
|
||||
} from '../types';
|
||||
|
||||
import { BookProject, Chapter, Entity, Idea, WorkflowData, UserProfile } from '../types';
|
||||
const API_BASE_URL = '/api'; // Proxied by Vite
|
||||
|
||||
// --- CONFIGURATION ---
|
||||
const AUTH_API_ROOT = '/api/user-auth';
|
||||
const DATA_API_ROOT = '/api/data';
|
||||
const INSTANCE_ID = '54770_plumeia_db';
|
||||
// --- API CLIENT GENERIC ---
|
||||
|
||||
// --- HELPERS ---
|
||||
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}`;
|
||||
|
||||
// --- HELPERS ---
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Database-Instance': instanceId, // Keep header just in case
|
||||
...options.headers,
|
||||
};
|
||||
|
||||
const getHeaders = () => {
|
||||
/*const token = localStorage.getItem('ncb_session_token');
|
||||
const sessionData = localStorage.getItem('ncb_session_data');
|
||||
console.log("[API] Token:", token);
|
||||
console.log("[API] Session Data:", sessionData);*/
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Database-Instance': INSTANCE_ID,
|
||||
// 'credentials': 'include'
|
||||
};
|
||||
/*
|
||||
if (token) {
|
||||
// Fallback standard auth
|
||||
//headers['Authorization'] = `Bearer ${token}`;
|
||||
|
||||
// User-requested specific Cookie format
|
||||
// Note: Browsers typically block manual "Cookie" header setting in fetch.
|
||||
// This is implemented per user request, but relies on the environment allowing it
|
||||
// or using credentials: 'include' for actual cookies.
|
||||
let cookieString = `better-auth.session_token=${token}`;
|
||||
if (sessionData) {
|
||||
cookieString += `; better-auth.session_data=${sessionData}`;
|
||||
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
|
||||
}
|
||||
headers['Cookie'] = cookieString;
|
||||
|
||||
console.log("[API] Cookie:", cookieString);
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
|
||||
// Debug headers
|
||||
console.log("[API] Generated Headers:", headers);
|
||||
*/
|
||||
return headers;
|
||||
};
|
||||
/*
|
||||
const handleAuthResponse = async (res: Response) => {
|
||||
const data = await res.json();
|
||||
console.log("[API] Auth Response:", data);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(data.message || 'Authentication failed');
|
||||
}
|
||||
// Return null if 204 No Content
|
||||
if (response.status === 204) return null;
|
||||
|
||||
// Token extraction strategy
|
||||
const token =
|
||||
data.user?.token ||
|
||||
data.token ||
|
||||
data.session?.token ||
|
||||
data.auth_token ||
|
||||
data.accessToken ||
|
||||
data._token;
|
||||
|
||||
// Extract session data if available (often needed for Better Auth)
|
||||
const sessionData = data.session_data || data.session?.data || data.user?.session_data;
|
||||
|
||||
if (token) {
|
||||
console.log("[API] Token extracted and saved:", token);
|
||||
localStorage.setItem('ncb_session_token', token);
|
||||
|
||||
if (sessionData) {
|
||||
console.log("[API] Session Data extracted and saved");
|
||||
localStorage.setItem('ncb_session_data', sessionData);
|
||||
}
|
||||
} else {
|
||||
console.warn("[API] No token found in successful auth response!");
|
||||
}
|
||||
|
||||
return data;
|
||||
};*/
|
||||
|
||||
const handleAuthResponse = async (res: Response) => {
|
||||
const data = await res.json();
|
||||
console.log("[API] Raw Response Data:", data);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(data.message || 'Authentication failed');
|
||||
}
|
||||
|
||||
// --- LOGIQUE DES SETTEURS ---
|
||||
|
||||
// 1. Extraction du Token (selon la structure Better-Auth)
|
||||
const token = data.session?.token || data.token;
|
||||
|
||||
// 2. Extraction du Session Data
|
||||
// On prend l'objet session complet et on le stringifie pour le stockage
|
||||
const sessionData = data.session ? JSON.stringify(data.session) : null;
|
||||
|
||||
if (token) {
|
||||
localStorage.setItem('ncb_session_token', token);
|
||||
console.log("[Auth] Token saved to LocalStorage");
|
||||
}
|
||||
|
||||
if (sessionData) {
|
||||
localStorage.setItem('ncb_session_data', sessionData);
|
||||
console.log("[Auth] Session Data saved to LocalStorage");
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
// --- AUTH SERVICE ---
|
||||
|
||||
export const authService = {
|
||||
/*async getSession() {
|
||||
const token = localStorage.getItem('ncb_session_token');
|
||||
// Note: Even if we use cookies, we keep token logic as fallback or for UI state
|
||||
// if (!token) return null;
|
||||
|
||||
try {
|
||||
const url = `${AUTH_API_ROOT}/get-session?Instance=${INSTANCE_ID}`;
|
||||
console.log(`[API] GET session ${url}`);
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: 'GET',
|
||||
//headers: getHeaders(),
|
||||
credentials: 'include' // IMPORTANT: Send cookies
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
console.log(`[API] getSession failed with status: ${res.status}`);
|
||||
if (res.status === 401) {
|
||||
localStorage.removeItem('ncb_session_token');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
console.log("[API] getSession success:", data);
|
||||
return data.user || data;
|
||||
} catch (err) {
|
||||
console.error("[Auth] getSession error:", err);
|
||||
return null;
|
||||
}
|
||||
},*/
|
||||
|
||||
async getSession() {
|
||||
console.log("[getSession] démarrage");
|
||||
const token = localStorage.getItem('ncb_session_token');
|
||||
try {
|
||||
const url = `${AUTH_API_ROOT}/get-session?Instance=${INSTANCE_ID}`;
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Database-Instance': INSTANCE_ID
|
||||
};
|
||||
|
||||
// Si on a un token mais pas encore la session complète, on l'envoie
|
||||
if (token) {
|
||||
headers['Cookie'] = `better-auth.session_token=${token}`;
|
||||
// On peut aussi essayer le header Authorization au cas où
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: headers,
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
console.log("[getSession] getSession success:", data);
|
||||
// --- LES SETTEURS ICI ---
|
||||
if (data.session) {
|
||||
localStorage.setItem('ncb_session_token', data.session.token);
|
||||
localStorage.setItem('ncb_session_data', JSON.stringify(data.session));
|
||||
console.log("[getSession] Données récupérées depuis getSession");
|
||||
}
|
||||
|
||||
return data.user || data;
|
||||
}
|
||||
return null;
|
||||
} catch (err) {
|
||||
console.error("[Auth] getSession error:", err);
|
||||
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) {
|
||||
try {
|
||||
// Force X-Database-Instance header as URL param is insufficient for some NCB versions
|
||||
const url = `${AUTH_API_ROOT}/sign-in/email?Instance=${INSTANCE_ID}`;
|
||||
console.log(`[API] POST ${url}`, { email, password: '***' });
|
||||
|
||||
const res = await fetch(url, {
|
||||
async signIn(email: string, password: string) {
|
||||
return api.request('/user-auth/sign-in/email', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Database-Instance': INSTANCE_ID
|
||||
},
|
||||
body: JSON.stringify({ email, password }),
|
||||
credentials: 'include' // IMPORTANT: Receive & Save cookies
|
||||
});
|
||||
const authData = await handleAuthResponse(res);
|
||||
await this.getSession();
|
||||
console.log("sign in", res);
|
||||
},
|
||||
|
||||
return await authData;
|
||||
} catch (err: any) {
|
||||
console.error("[Auth] signIn error:", err);
|
||||
return { error: err.message || 'Connection failed' };
|
||||
}
|
||||
},
|
||||
|
||||
async signUp(email: string, password: string, name: string) {
|
||||
try {
|
||||
const url = `${AUTH_API_ROOT}/sign-up/email?Instance=${INSTANCE_ID}`;
|
||||
console.log(`[API] POST ${url}`, { email, name });
|
||||
|
||||
const res = await fetch(url, {
|
||||
async signUp(email: string, password: string, name: string) {
|
||||
return api.request('/user-auth/sign-up/email', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Database-Instance': INSTANCE_ID
|
||||
},
|
||||
body: JSON.stringify({ email, password, name }),
|
||||
credentials: 'include' // IMPORTANT: Receive & Save cookies
|
||||
});
|
||||
},
|
||||
|
||||
return await handleAuthResponse(res);
|
||||
} catch (err: any) {
|
||||
console.error("[Auth] signUp error:", err);
|
||||
return { error: err.message || 'Registration failed' };
|
||||
}
|
||||
},
|
||||
|
||||
async signOut() {
|
||||
try {
|
||||
const url = `${AUTH_API_ROOT}/sign-out?Instance=${INSTANCE_ID}`;
|
||||
console.log(`[API] POST ${url}`);
|
||||
|
||||
await fetch(url, {
|
||||
async signOut() {
|
||||
return api.request('/user-auth/sign-out', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include' // IMPORTANT: Clear cookies
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("[Auth] signOut error:", err);
|
||||
} finally {
|
||||
localStorage.removeItem('ncb_session_token');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// --- DATA SERVICE ---
|
||||
|
||||
export const dataService = {
|
||||
|
||||
// -- PROFILES --
|
||||
async getProfile(userId: string) {
|
||||
try {
|
||||
const encodedId = encodeURIComponent(userId);
|
||||
const url = `${DATA_API_ROOT}/read/profiles?Instance=${INSTANCE_ID}&user_id=${encodedId}`;
|
||||
console.log(`[API] GET ${url}`);
|
||||
|
||||
const res = await fetch(url, {
|
||||
headers: getHeaders(),
|
||||
credentials: 'include'
|
||||
});
|
||||
const json = await res.json();
|
||||
console.log("[Data] getProfile result:", json);
|
||||
return json.data?.[0] || null;
|
||||
} catch (err) {
|
||||
console.error("[Data] getProfile error:", err);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async createProfile(profileData: any) {
|
||||
return this.createItem('profiles', profileData);
|
||||
},
|
||||
// --- 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 || [];
|
||||
},
|
||||
|
||||
// -- PROJECTS --
|
||||
async getProjects(userId: string) {
|
||||
try {
|
||||
const encodedId = encodeURIComponent(userId);
|
||||
const url = `${DATA_API_ROOT}/read/projects?Instance=${INSTANCE_ID}&user_id=${encodedId}`;
|
||||
console.log(`[API] GET ${url}`);
|
||||
// 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;
|
||||
},
|
||||
|
||||
const res = await fetch(url, {
|
||||
headers: getHeaders(),
|
||||
credentials: 'include'
|
||||
});
|
||||
const json = await res.json();
|
||||
console.log(`[Data] getProjects found ${json.data?.length || 0} items`);
|
||||
return json.data || [];
|
||||
} catch (err) {
|
||||
console.error("[Data] getProjects error:", err);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
async createProject(projectData: Partial<BookProject> & { user_id: string }) {
|
||||
// Map Frontend types to Database Columns explicitly if needed, but names match mostly
|
||||
// DB: title, author, genre, sub_genre, target_audience, tone, pov, tense, synopsis, themes, style_guide
|
||||
return this.createItem('projects', projectData);
|
||||
},
|
||||
|
||||
// -- GENERIC CRUD --
|
||||
|
||||
async getRelatedData(table: string, projectId: number) {
|
||||
try {
|
||||
const url = `${DATA_API_ROOT}/read/${table}?Instance=${INSTANCE_ID}&project_id=${projectId}`;
|
||||
console.log(`[API] GET ${url}`);
|
||||
|
||||
const res = await fetch(url, {
|
||||
headers: getHeaders(),
|
||||
credentials: 'include'
|
||||
});
|
||||
const json = await res.json();
|
||||
console.log(`[Data] getRelatedData ${table} found ${json.data?.length || 0} items`);
|
||||
return json.data || [];
|
||||
} catch (err) {
|
||||
console.error(`[Data] getRelatedData ${table} error:`, err);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
async createItem(table: string, data: any) {
|
||||
try {
|
||||
console.log(`[Data] Creating item in ${table}...`, data);
|
||||
const url = `${DATA_API_ROOT}/create/${table}?Instance=${INSTANCE_ID}`;
|
||||
console.log(`[API] POST ${url}`, data);
|
||||
|
||||
const res = await fetch(url, {
|
||||
// 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',
|
||||
headers: getHeaders(),
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'include'
|
||||
});
|
||||
}) as Promise<T>;
|
||||
},
|
||||
|
||||
const result = await res.json();
|
||||
console.log(`[Data] Create ${table} response:`, result);
|
||||
|
||||
if (!res.ok) {
|
||||
console.error(`[Data] Create ${table} failed:`, result);
|
||||
return { status: 'error', message: result.message || 'Creation failed' };
|
||||
}
|
||||
return { status: 'success', ...result };
|
||||
} catch (err) {
|
||||
console.error(`[Data] Create ${table} network error:`, err);
|
||||
return { status: 'error', message: err };
|
||||
}
|
||||
},
|
||||
|
||||
async updateItem(table: string, id: number, data: any) {
|
||||
try {
|
||||
const url = `${DATA_API_ROOT}/update/${table}/${id}?Instance=${INSTANCE_ID}`;
|
||||
console.log(`[API] PUT ${url}`, data);
|
||||
|
||||
const res = await fetch(url, {
|
||||
// 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',
|
||||
headers: getHeaders(),
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'include'
|
||||
});
|
||||
}) as Promise<T>;
|
||||
},
|
||||
|
||||
if (!res.ok) {
|
||||
const err = await res.json();
|
||||
console.error(`[Data] Update ${table} failed:`, err);
|
||||
} else {
|
||||
console.log(`[Data] Update ${table} success`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`[Data] Update ${table} error:`, err);
|
||||
}
|
||||
},
|
||||
|
||||
async deleteItem(table: string, id: number) {
|
||||
try {
|
||||
const url = `${DATA_API_ROOT}/delete/${table}/${id}?Instance=${INSTANCE_ID}`;
|
||||
console.log(`[API] DELETE ${url}`);
|
||||
|
||||
const res = await fetch(url, {
|
||||
// Generic delete
|
||||
async delete(table: string, id: string) {
|
||||
// Swagger: DELETE /delete/{table}/{id}
|
||||
return api.request(`/data/delete/${table}/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: getHeaders(),
|
||||
credentials: 'include'
|
||||
});
|
||||
},
|
||||
|
||||
if (!res.ok) {
|
||||
const err = await res.json();
|
||||
console.error(`[Data] Delete ${table} failed:`, err);
|
||||
} else {
|
||||
console.log(`[Data] Delete ${table} success`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`[Data] Delete ${table} error:`, err);
|
||||
// --- 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;
|
||||
|
||||
Reference in New Issue
Block a user