WebOS completion
This commit is contained in:
269
app/src/lib/velocityPlatformClient.ts
Normal file
269
app/src/lib/velocityPlatformClient.ts
Normal file
@@ -0,0 +1,269 @@
|
||||
import { API_URL } from '@/lib/api';
|
||||
|
||||
export const VELOCITY_TOKEN_KEY = 'velocity-api-token';
|
||||
|
||||
export interface VelocityUserProfile {
|
||||
user_id: string;
|
||||
role: string;
|
||||
}
|
||||
|
||||
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 {
|
||||
const headers = new Headers(init);
|
||||
if (includeJson && !headers.has('Content-Type')) {
|
||||
headers.set('Content-Type', 'application/json');
|
||||
}
|
||||
if (!headers.has('Accept')) {
|
||||
headers.set('Accept', 'application/json');
|
||||
}
|
||||
const token = getVelocityToken();
|
||||
if (token && !headers.has('Authorization')) {
|
||||
headers.set('Authorization', `Bearer ${token}`);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
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 setVelocityToken(token: string) {
|
||||
localStorage.setItem(VELOCITY_TOKEN_KEY, token);
|
||||
}
|
||||
|
||||
export function getVelocityToken(): string | null {
|
||||
return localStorage.getItem(VELOCITY_TOKEN_KEY);
|
||||
}
|
||||
|
||||
export function clearVelocityToken() {
|
||||
localStorage.removeItem(VELOCITY_TOKEN_KEY);
|
||||
}
|
||||
|
||||
export function normalizeVelocityRole(role: string | null | undefined): string {
|
||||
return (role ?? '').trim().toUpperCase();
|
||||
}
|
||||
|
||||
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 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))}`,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user