Files
Project_Velocity/app/src/lib/velocityPlatformClient.ts

331 lines
8.8 KiB
TypeScript

import { API_URL } from '@/lib/api';
import {
buildVelocityHeaders,
setVelocityToken,
} from '@/lib/velocitySession';
export {
VELOCITY_TOKEN_KEY,
clearVelocityToken,
getVelocityToken,
setVelocityToken,
} from '@/lib/velocitySession';
export interface VelocityUserProfile {
user_id: string;
role: string;
tenant_id?: string;
full_name?: string | null;
email?: string | null;
avatar_url?: string | null;
}
export interface VelocityActiveUser {
user_id: string;
role: string;
tenant_id?: string;
full_name?: string | null;
email?: string | null;
avatar_url?: string | null;
}
export interface VelocityLoginResponse {
access_token: string;
token_type: string;
expires_in: number;
}
export interface AdminHealthSnapshot {
status: string;
timestamp: string;
database: {
connected: boolean;
latency_ms: number;
};
queues: {
pending_transcriptions: number;
pending_synthetic_jobs: number;
pending_admin_actions: number;
pending_inventory_batches: number;
};
active_sessions: {
total: number;
by_surface: Record<string, number>;
};
}
export interface AdminQueueSnapshot {
transcription_jobs: Record<string, number>;
synthetic_jobs: Record<string, number>;
inventory_batches: Record<string, number>;
admin_actions: Record<string, number>;
timestamp: string;
}
export interface AdminInstallSnapshot {
installs: Array<{
surface_type: string;
app_version: string;
session_count: number;
last_seen: string | null;
}>;
timestamp: string;
}
export interface AdminActionRecord {
action_event_id: string;
action_id: string;
action_type: string;
target_type: string;
target_id: string;
requested_by: string;
status: string;
result_message?: string | null;
executed_at?: string | null;
created_at: string;
}
export interface AdminActionRequest {
action_type: string;
target_type: string;
target_id: string;
payload?: Record<string, unknown>;
idempotency_key?: string;
}
export interface MobileEdgeAlertSnapshot {
pending_insights: number;
upcoming_calendar_events_24h: number;
pending_transcriptions: number;
generated_at: string;
}
export interface MobileCalendarEvent {
calendar_event_id: string;
lead_id?: string | null;
title: string;
description?: string | null;
start_at: string;
end_at: string;
all_day: boolean;
status: string;
reminder_minutes: number[];
created_by: string;
location?: string | null;
metadata: Record<string, unknown>;
created_at: string;
}
export interface MobileCommunicationEvent {
event_id: string;
lead_id: string;
channel: string;
direction: string;
provider?: string | null;
capture_mode: string;
consent_state: string;
timestamp: string;
duration_seconds?: number | null;
summary?: string | null;
raw_reference?: string | null;
recording_ref?: string | null;
provider_metadata: Record<string, unknown>;
created_at: string;
}
export interface InventoryImportBatchSummary {
batch_id: string;
source_type: string;
submitted_by: string;
status: string;
total_rows: number;
accepted_rows: number;
rejected_rows: number;
created_at: string;
completed_at?: string | null;
}
export interface InventoryPropertySummary {
property_id: string;
project_name: string;
developer_name: string;
property_type: string;
location: Record<string, unknown>;
price_bands: Array<Record<string, unknown>>;
unit_mix: Array<Record<string, unknown>>;
status: string;
ingested_at?: string | null;
created_at?: string | null;
}
function buildHeaders(init?: HeadersInit, includeJson = true): Headers {
return buildVelocityHeaders(init, includeJson);
}
async function platformFetch<T>(path: string, init?: RequestInit): Promise<T> {
const response = await fetch(`${API_URL}${path}`, {
...init,
headers: buildHeaders(init?.headers, init?.body !== undefined),
});
if (!response.ok) {
const body = await response.json().catch(() => ({}));
throw new Error(
typeof body?.detail === 'string'
? body.detail
: typeof body?.message === 'string'
? body.message
: `Request failed: ${response.status}`,
);
}
return response.json() as Promise<T>;
}
export function normalizeVelocityRole(role: string | null | undefined): string {
return (role ?? '').trim().toUpperCase();
}
export function resolveVelocityFullName(profile: Pick<VelocityUserProfile, 'full_name' | 'email' | 'user_id'>): string {
const fullName = profile.full_name?.trim();
if (fullName) {
return fullName;
}
const email = profile.email?.trim();
if (email) {
return email;
}
return profile.user_id;
}
export function resolveVelocityFirstName(profile: Pick<VelocityUserProfile, 'full_name' | 'email' | 'user_id'>): string {
const fullName = profile.full_name?.trim();
if (fullName) {
return fullName.split(/\s+/)[0] ?? fullName;
}
const email = profile.email?.trim();
if (email) {
const local = email.split('@')[0]?.trim() ?? '';
if (local) {
const normalized = local.replace(/[._-]+/g, ' ').trim();
const firstToken = normalized.split(/\s+/)[0] ?? normalized;
return firstToken.charAt(0).toUpperCase() + firstToken.slice(1);
}
}
return profile.user_id;
}
export function isAdminRole(role: string | null | undefined): boolean {
const normalized = normalizeVelocityRole(role);
return normalized === 'ADMIN' || normalized === 'SUPERADMIN';
}
export async function loginVelocity(email: string, password: string): Promise<VelocityUserProfile> {
const auth = await platformFetch<VelocityLoginResponse>('/api/auth/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
});
setVelocityToken(auth.access_token);
return getVelocityMe();
}
export async function getVelocityMe(): Promise<VelocityUserProfile> {
return platformFetch<VelocityUserProfile>('/api/auth/me', {
method: 'GET',
});
}
export async function listVelocityUsers(): Promise<VelocityActiveUser[]> {
return platformFetch<VelocityActiveUser[]>('/api/auth/users', {
method: 'GET',
});
}
export async function uploadVelocityAvatar(file: File): Promise<{ avatar_url: string }> {
const form = new FormData();
form.append('file', file);
const response = await fetch(`${API_URL}/api/auth/profile/avatar`, {
method: 'POST',
headers: buildHeaders(undefined, false),
body: form,
});
if (!response.ok) {
const body = await response.json().catch(() => ({}));
throw new Error(
typeof body?.detail === 'string'
? body.detail
: typeof body?.message === 'string'
? body.message
: `Request failed: ${response.status}`,
);
}
return response.json() as Promise<{ avatar_url: string }>;
}
export async function getAdminHealth(): Promise<AdminHealthSnapshot> {
return platformFetch<AdminHealthSnapshot>('/api/admin-surface/health');
}
export async function getAdminQueues(): Promise<AdminQueueSnapshot> {
return platformFetch<AdminQueueSnapshot>('/api/admin-surface/queues');
}
export async function getAdminInstalls(): Promise<AdminInstallSnapshot> {
return platformFetch<AdminInstallSnapshot>('/api/admin-surface/installs');
}
export async function listAdminActions(limit = 20): Promise<{ actions: AdminActionRecord[] }> {
return platformFetch<{ actions: AdminActionRecord[] }>(
`/api/admin-surface/actions?limit=${encodeURIComponent(String(limit))}`,
);
}
export async function submitAdminAction(body: AdminActionRequest): Promise<{
action_event_id: string;
action_id: string;
status: string;
created_at: string;
}> {
return platformFetch('/api/admin-surface/actions', {
method: 'POST',
body: JSON.stringify(body),
});
}
export async function getMobileAlerts(): Promise<MobileEdgeAlertSnapshot> {
return platformFetch<MobileEdgeAlertSnapshot>('/api/mobile-edge/alerts');
}
export async function getMobileCalendarEvents(): Promise<{ events: MobileCalendarEvent[] }> {
return platformFetch<{ events: MobileCalendarEvent[] }>('/api/mobile-edge/calendar');
}
export async function getMobileEventsByLead(
leadId: string,
limit = 20,
): Promise<{ events: MobileCommunicationEvent[] }> {
const params = new URLSearchParams({
lead_id: leadId,
limit: String(limit),
});
return platformFetch<{ events: MobileCommunicationEvent[] }>(`/api/mobile-edge/events?${params.toString()}`);
}
export async function listInventoryImportBatches(limit = 10): Promise<{ batches: InventoryImportBatchSummary[] }> {
return platformFetch<{ batches: InventoryImportBatchSummary[] }>(
`/api/inventory/import-batches?limit=${encodeURIComponent(String(limit))}`,
);
}
export async function listInventoryProperties(limit = 100): Promise<{ properties: InventoryPropertySummary[] }> {
return platformFetch<{ properties: InventoryPropertySummary[] }>(
`/api/inventory/properties?limit=${encodeURIComponent(String(limit))}`,
);
}