ceo-web/src/lib/api.ts

188 lines
4.3 KiB
TypeScript

import { supabase } from './supabase';
const API_URL = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3001';
export const ACTIVE_TENANT_STORAGE_KEY = 'ceo-os.active-tenant';
export class ApiError extends Error {
constructor(
public readonly status: number,
message: string,
) {
super(message);
}
}
interface ApiFetchOptions {
method?: 'GET' | 'POST' | 'PATCH' | 'DELETE';
body?: unknown;
/** Rutele de bootstrap (/v1/me, /v1/tenants) nu trimit tenant header. */
tenantId?: string | null;
}
/**
* Toate apelurile catre ceo-api trec pe aici: Bearer din sesiunea Supabase,
* tenantul activ in x-tenant-id (niciodata in body/query — TenantGuard pe
* server respinge exact acel pattern).
*/
export async function apiFetch<T>(path: string, options: ApiFetchOptions = {}): Promise<T> {
const { data } = await supabase.auth.getSession();
const token = data.session?.access_token;
if (!token) {
throw new ApiError(401, 'Not signed in');
}
const headers: Record<string, string> = {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
};
if (options.tenantId) {
headers['x-tenant-id'] = options.tenantId;
}
const response = await fetch(`${API_URL}${path}`, {
method: options.method ?? 'GET',
headers,
body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
});
if (!response.ok) {
let message = `Request failed (${response.status})`;
try {
const payload = (await response.json()) as { message?: string | string[] };
if (payload.message) {
message = Array.isArray(payload.message) ? payload.message.join('; ') : payload.message;
}
} catch {
// corpul nu e JSON — pastram mesajul generic
}
throw new ApiError(response.status, message);
}
return (await response.json()) as T;
}
// --- Tipuri aliniate la contractele ceo-api ---
export interface TenantMembership {
tenantId: string;
tenantName: string;
role: 'owner' | 'admin' | 'member';
}
export interface MeResponse {
userId: string;
email: string | null;
tenants: TenantMembership[];
}
export interface Organization {
id: string;
name: string;
legalName: string | null;
country: string | null;
registryId: string | null;
domain: string | null;
createdAt: string;
}
export type TaskStatus = 'open' | 'in_progress' | 'blocked' | 'done' | 'cancelled';
export interface Task {
id: string;
title: string;
priority: number;
dueAt: string | null;
status: TaskStatus;
createdAt: string;
}
export interface TenantMember {
userId: string;
email: string | null;
role: 'owner' | 'admin' | 'member';
createdAt: string;
}
export interface CompanySearchResult {
organization_id: string;
organization_name: string;
normalized_domain: string;
hq_city: string;
hq_country: string;
industries: string[];
num_current_employees: number | null;
revenue_in_thousands: number | null;
}
export interface CompanySearchResponse {
results: CompanySearchResult[];
count: number;
}
export interface SavedSegment {
id: string;
name: string;
query: string;
resultLimit: number;
createdAt: string;
}
export interface Source {
label: string;
url: string;
}
export interface ResearchBrief {
id: string;
organizationId: string;
organizationName: string | null;
title: string;
summary: string;
sources: Source[];
createdAt: string;
}
export interface Briefing {
generatedAt: string;
overdueTasks: Task[];
upcomingTasks: Task[];
weekInReview: {
newOrganizations: number;
newSegments: number;
newResearchBriefs: number;
};
aiExplanation: string | null;
}
export type GoalStatus = 'not_started' | 'on_track' | 'at_risk' | 'achieved' | 'abandoned';
export interface Goal {
id: string;
tenantId: string;
ownerUserId: string;
horizon: string;
metric: string;
target: string;
milestones: unknown[];
status: GoalStatus;
createdAt: string;
updatedAt: string;
}
export type EvidenceStatus = 'missing' | 'partial' | 'complete' | 'not_required';
export interface Transaction {
id: string;
tenantId: string;
organizationId: string;
type: string;
/** Stored as string from DB (numeric type). */
amountMinorUnits: string;
currency: string;
transactionDate: string;
evidenceStatus: EvidenceStatus;
source: string | null;
createdAt: string;
updatedAt: string;
}