/** * Platform Service API client for the admin dashboard. * Uses @bytelyst/api-client shared package. * * Replaces direct Cosmos DB calls for audit logging. */ import { createApiClient } from '@bytelyst/api-client'; const platformApi = createApiClient({ baseUrl: `${process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'}/api`, defaultHeaders: { 'x-product-id': process.env.PRODUCT_ID || 'lysnrai', }, }); // ── Audit ─────────────────────────────────────────────────────── export async function queryAudit( options: { category?: string; userId?: string; action?: string; days?: number; limit?: number; offset?: number; } = {} ) { const params = new URLSearchParams(); if (options.category) params.set('category', options.category); if (options.userId) params.set('userId', options.userId); if (options.action) params.set('action', options.action); if (options.days) params.set('days', String(options.days)); if (options.limit) params.set('limit', String(options.limit)); if (options.offset) params.set('offset', String(options.offset)); const qs = params.toString(); return platformApi.fetch<{ records: unknown[]; count: number }>(`/audit${qs ? `?${qs}` : ''}`); } export async function getAuditStats(days = 30) { return platformApi.fetch<{ stats: Record; days: number }>( `/audit/stats?days=${days}` ); } export async function logAudit(input: { userId: string; action: string; category?: string; details?: Record; ipAddress?: string; userAgent?: string; }) { return platformApi.fetch<{ accepted: boolean }>('/audit', { method: 'POST', body: JSON.stringify(input), }); } // ── Auth (proxied to platform-service) ────────────────────────── export async function loginViaService(email: string, password: string, productId: string) { return platformApi.fetch<{ accessToken: string; refreshToken: string; user: { id: string; email: string; role: string; plan: string; displayName: string }; }>('/auth/login', { method: 'POST', body: JSON.stringify({ email, password, productId }), }); } export async function forgotPasswordViaService(email: string, productId: string) { return platformApi.fetch<{ message: string }>('/auth/forgot-password', { method: 'POST', body: JSON.stringify({ email, productId }), }); } export async function changePasswordViaService( token: string, currentPassword: string, newPassword: string ) { return platformApi.fetch<{ message: string }>('/auth/change-password', { method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: JSON.stringify({ currentPassword, newPassword }), }); } export async function deleteAccountViaService(token: string, password: string) { return platformApi.fetch<{ message: string }>('/auth/account', { method: 'DELETE', headers: { Authorization: `Bearer ${token}` }, body: JSON.stringify({ password }), }); } export async function getMeViaService(token: string) { return platformApi.fetch<{ id: string; email: string; role: string; plan: string; displayName: string; }>('/auth/me', { headers: { Authorization: `Bearer ${token}` }, }); } export async function verifyTokenViaService(token: string) { return platformApi.fetch<{ valid: boolean; payload: Record | null; }>('/auth/verify', { method: 'POST', body: JSON.stringify({ token }), }); } // ── Admin User Management ───────────────────────────────────── export interface UserDoc { id: string; productId: string; email: string; plan: 'free' | 'pro' | 'enterprise'; role: 'super_admin' | 'admin' | 'viewer' | 'user'; displayName: string; status: 'active' | 'disabled'; lastLoginAt: string | null; createdAt: string; updatedAt: string; } export async function listUsers( token: string, limit = 100, offset = 0 ): Promise<{ users: UserDoc[] }> { return platformApi.fetch<{ users: UserDoc[] }>(`/auth/users?limit=${limit}&offset=${offset}`, { headers: { Authorization: `Bearer ${token}` }, }); } export async function getUserCounts( token: string ): Promise<{ total: number; byPlan: Record }> { return platformApi.fetch<{ total: number; byPlan: Record }>('/auth/users/count', { headers: { Authorization: `Bearer ${token}` }, }); } export async function getUser(token: string, id: string): Promise { return platformApi.fetch(`/auth/users/${id}`, { headers: { Authorization: `Bearer ${token}` }, }); } export async function updateUser( token: string, id: string, updates: Record ): Promise { return platformApi.fetch(`/auth/users/${id}`, { method: 'PUT', headers: { Authorization: `Bearer ${token}` }, body: JSON.stringify(updates), }); } export async function deleteUser(token: string, id: string): Promise<{ success: boolean }> { return platformApi.fetch<{ success: boolean }>(`/auth/users/${id}`, { method: 'DELETE', headers: { Authorization: `Bearer ${token}` }, }); } export async function registerUser(input: { email: string; password: string; displayName: string; role?: string; productId: string; }): Promise<{ user: UserDoc; accessToken: string; refreshToken: string }> { return platformApi.fetch<{ user: UserDoc; accessToken: string; refreshToken: string }>( '/auth/register', { method: 'POST', body: JSON.stringify(input) } ); } // ── Products ────────────────────────────────────────────────── export interface ProductDoc { id: string; productId: string; displayName: string; licensePrefix: string; packageName: string; defaultPlan: 'free' | 'pro'; trialDays: number; deviceLimits: { free: number; pro: number; enterprise: number }; websiteUrl: string; status: 'active' | 'disabled'; createdAt: string; updatedAt: string; } export async function listProducts(): Promise<{ products: ProductDoc[] }> { return platformApi.fetch<{ products: ProductDoc[] }>('/products'); } export async function getProduct(id: string): Promise { return platformApi.fetch(`/products/${id}`); } export async function createProduct(input: Record): Promise { return platformApi.fetch('/products', { method: 'POST', body: JSON.stringify(input), }); } export async function updateProduct( id: string, updates: Record ): Promise { return platformApi.fetch(`/products/${id}`, { method: 'PUT', body: JSON.stringify(updates), }); } // ── Plans ───────────────────────────────────────────────────── export interface PlanConfig { id: string; productId: string; name: string; displayName: string; price: number; tokens: number; words: number; dictations: number; features: string[]; stripePriceId?: string; active: boolean; createdAt: string; updatedAt: string; } export async function listPlans(productId: string): Promise<{ plans: PlanConfig[] }> { return platformApi.fetch<{ plans: PlanConfig[] }>(`/plans`, { headers: { 'x-product-id': productId }, }); } export async function seedPlans(productId: string): Promise<{ plans: PlanConfig[] }> { return platformApi.fetch<{ plans: PlanConfig[] }>('/plans/seed', { method: 'POST', headers: { 'x-product-id': productId }, }); } // ── Product Onboarding ──────────────────────────────────────── /** * Onboard a new product: seed default plans + kill_switch flag. * Called automatically after product creation. */ export async function onboardProduct(productId: string): Promise<{ plans: PlanConfig[]; flags: { key: string; enabled: boolean }[]; }> { // 1. Seed default plans (free, pro, enterprise) const planResult = await seedPlans(productId); // 2. Create kill_switch flag (disabled by default) const flagResults: { key: string; enabled: boolean }[] = []; try { const flag = await platformApi.fetch<{ key: string; enabled: boolean }>('/flags', { method: 'POST', headers: { 'x-product-id': productId }, body: JSON.stringify({ key: 'kill_switch', enabled: false, description: 'Emergency kill switch — disables the product when enabled', platforms: [], segments: [], percentage: 100, }), }); flagResults.push(flag); } catch { // kill_switch may already exist — that's fine } return { plans: planResult.plans, flags: flagResults }; } // ── Feature Flags ───────────────────────────────────────────── export interface FlagDoc { id: string; productId: string; key: string; description: string; enabled: boolean; platforms: string[]; segments: string[]; percentage: number; createdAt: string; updatedAt: string; } export async function listFlags(): Promise<{ flags: FlagDoc[] }> { return platformApi.fetch<{ flags: FlagDoc[] }>('/flags'); } export async function createFlag(input: Record): Promise { return platformApi.fetch('/flags', { method: 'POST', body: JSON.stringify(input), }); } export async function updateFlag(key: string, updates: Record): Promise { return platformApi.fetch(`/flags/${encodeURIComponent(key)}`, { method: 'PUT', body: JSON.stringify(updates), }); } export async function deleteFlag(key: string): Promise { await platformApi.fetch(`/flags/${encodeURIComponent(key)}`, { method: 'DELETE' }); } export async function evaluateFlag( key: string, userId: string ): Promise<{ enabled: boolean; key: string; userId: string }> { return platformApi.fetch<{ enabled: boolean; key: string; userId: string }>( `/flags/evaluate?key=${encodeURIComponent(key)}&userId=${encodeURIComponent(userId)}` ); } // ── API Tokens ─────────────────────────────────────────────── export interface ApiTokenResponse { id: string; productId: string; userId: string; userName: string; name: string; prefix: string; status: 'active' | 'revoked' | 'expired'; scopes: string[]; createdAt: string; expiresAt: string; lastUsed: string | null; } export async function listTokens(token: string): Promise<{ tokens: ApiTokenResponse[] }> { return platformApi.fetch<{ tokens: ApiTokenResponse[] }>('/tokens', { headers: { Authorization: `Bearer ${token}` }, }); } export async function createToken( token: string, input: { name: string; scopes?: string[]; expiresInDays?: number } ): Promise { return platformApi.fetch('/tokens', { method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: JSON.stringify(input), }); } export async function revokeToken(token: string, tokenId: string): Promise<{ success: boolean }> { return platformApi.fetch<{ success: boolean }>(`/tokens/${tokenId}`, { method: 'PATCH', headers: { Authorization: `Bearer ${token}` }, body: JSON.stringify({ action: 'revoke' }), }); } export async function deleteToken(token: string, tokenId: string): Promise<{ success: boolean }> { return platformApi.fetch<{ success: boolean }>(`/tokens/${tokenId}`, { method: 'DELETE', headers: { Authorization: `Bearer ${token}` }, }); } export async function countActiveTokens(token: string): Promise<{ count: number }> { return platformApi.fetch<{ count: number }>('/tokens/count', { headers: { Authorization: `Bearer ${token}` }, }); } // ── Themes ─────────────────────────────────────────────────── export interface ThemeDoc { id: string; productId: string; name: string; description: string | null; ios: Record; android: Record; desktop: Record; is_active: boolean; is_default: boolean; version: string; created_at: string; updated_at: string; created_by: string | null; } export async function listThemes(token: string): Promise { return platformApi.fetch('/themes', { headers: { Authorization: `Bearer ${token}` }, }); } export async function getTheme(token: string, id: string): Promise { return platformApi.fetch(`/themes/${id}`, { headers: { Authorization: `Bearer ${token}` }, }); } export async function createTheme( token: string, input: Record ): Promise { return platformApi.fetch('/themes', { method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: JSON.stringify(input), }); } export async function updateTheme( token: string, id: string, updates: Record ): Promise { return platformApi.fetch(`/themes/${id}`, { method: 'PUT', headers: { Authorization: `Bearer ${token}` }, body: JSON.stringify(updates), }); } export async function deleteTheme(token: string, id: string): Promise<{ success: boolean }> { return platformApi.fetch<{ success: boolean }>(`/themes/${id}`, { method: 'DELETE', headers: { Authorization: `Bearer ${token}` }, }); } export async function activateTheme(token: string, id: string): Promise { return platformApi.fetch(`/themes/${id}/activate`, { method: 'POST', headers: { Authorization: `Bearer ${token}` }, }); } export async function getActiveTheme(productId: string): Promise { return platformApi.fetch(`/themes/active?productId=${encodeURIComponent(productId)}`); } // ── Notifications ───────────────────────────────────────────── export interface NotificationPrefs { pushEnabled: boolean; emailEnabled: boolean; categories: Record; } export async function getNotificationPrefs(userId: string): Promise { try { return await platformApi.fetch(`/notifications/prefs/${userId}`); } catch { return null; } } export async function listDevices(userId: string) { return platformApi.fetch<{ devices: unknown[] }>(`/notifications/devices/${userId}`); } export async function listNotifications(limit = 50, offset = 0) { return platformApi.fetch<{ notifications: unknown[]; count: number }>( `/notifications?limit=${limit}&offset=${offset}` ); } // ── Telemetry ──────────────────────────────────────────────────── export interface TelemetryEvent { id: string; productId: string; userId?: string; anonymousInstallId?: string; sessionId: string; platform: string; channel: string; osFamily: string; osVersion?: string; deviceModel?: string; appVersion: string; buildNumber: string; releaseChannel: string; eventType: string; module: string; feature?: string; eventName: string; errorDomain?: string; errorCode?: string; message?: string; tags?: Record; metrics?: Record; context?: Record; occurredAt: string; receivedAt: string; } export interface TelemetryCluster { id: string; fingerprint: string; platform: string; channel: string; module: string; eventName: string; affectedVersions: Array<{ appVersion: string; buildNumber: string; count: number; lastSeenAt: string; }>; firstSeenAt: string; lastSeenAt: string; totalCount: number; affectedUserIds: string[]; affectedInstallIds: string[]; affectedOsFamilies: string[]; sampleErrorDomain?: string; sampleErrorCode?: string; sampleMessage?: string; severity: string; status?: string; resolvedBy?: string; resolvedAt?: string; } export interface TelemetryMetrics { totalEventsIngested: number; totalEventsRejected: number; totalBatchRequests: number; totalRateLimited: number; totalPiiBlocked: number; totalDuplicatesDropped: number; uptimeSince: string; } export async function queryTelemetryEvents( token: string, filters: Record = {} ): Promise<{ events: TelemetryEvent[]; total: number; continuationToken?: string }> { const params = new URLSearchParams(filters); return platformApi.fetch<{ events: TelemetryEvent[]; total: number; continuationToken?: string }>( `/telemetry/query?${params.toString()}`, { headers: { Authorization: `Bearer ${token}` } } ); } export async function queryTelemetryClusters( token: string, filters: Record = {} ): Promise<{ clusters: TelemetryCluster[]; total: number }> { const params = new URLSearchParams(filters); return platformApi.fetch<{ clusters: TelemetryCluster[]; total: number }>( `/telemetry/clusters?${params.toString()}`, { headers: { Authorization: `Bearer ${token}` } } ); } // ── Telemetry Policies ────────────────────────────────────────── export interface TelemetryPolicy { id: string; productId: string; name: string; description?: string; enabled: boolean; priority: number; eventTypes: string[]; modules: string[]; samplingRate: number; targeting: { platforms?: string[]; channels?: string[]; osFamilies?: string[]; appVersions?: string[]; buildNumbers?: string[]; buildNumberRange?: { min?: number; max?: number }; userIds?: string[]; anonymousInstallIds?: string[]; countryCodes?: string[]; regionCodes?: string[]; releaseChannels?: string[]; percentage?: number; }; startsAt?: string; expiresAt?: string; createdAt: string; updatedAt: string; createdBy?: string; } export async function listTelemetryPolicies( token: string ): Promise<{ policies: TelemetryPolicy[] }> { return platformApi.fetch<{ policies: TelemetryPolicy[] }>('/telemetry/policies', { headers: { Authorization: `Bearer ${token}` }, }); } export async function createTelemetryPolicy( token: string, input: Record ): Promise { return platformApi.fetch('/telemetry/policies', { method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: JSON.stringify(input), }); } export async function updateTelemetryPolicy( token: string, id: string, updates: Record ): Promise { return platformApi.fetch(`/telemetry/policies/${id}`, { method: 'PUT', headers: { Authorization: `Bearer ${token}` }, body: JSON.stringify(updates), }); } export async function deleteTelemetryPolicy( token: string, id: string ): Promise<{ success: boolean }> { return platformApi.fetch<{ success: boolean }>(`/telemetry/policies/${id}`, { method: 'DELETE', headers: { Authorization: `Bearer ${token}` }, }); } // ── Telemetry Policy Preview ───────────────────────────────────── export async function previewTelemetryPolicy( token: string, targeting: Record ): Promise<{ matchedClients: number; totalClients: number; sampleSize: number }> { return platformApi.fetch<{ matchedClients: number; totalClients: number; sampleSize: number }>( '/telemetry/policies/preview', { method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: JSON.stringify({ targeting }), } ); } // ── Telemetry Cluster Actions ──────────────────────────────────── export async function updateClusterStatus( token: string, clusterId: string, pk: string, status: 'open' | 'resolved' | 'ignored' ): Promise { return platformApi.fetch( `/telemetry/clusters/${encodeURIComponent(clusterId)}?pk=${encodeURIComponent(pk)}`, { method: 'PATCH', headers: { Authorization: `Bearer ${token}` }, body: JSON.stringify({ status }), } ); } export async function getTelemetryMetrics(token: string): Promise { return platformApi.fetch('/telemetry/metrics', { headers: { Authorization: `Bearer ${token}` }, }); } export interface GeoDistributionEntry { countryCode: string; count: number; } export async function getTelemetryGeoDistribution( token: string, from?: string, to?: string ): Promise<{ distribution: GeoDistributionEntry[] }> { const params = new URLSearchParams(); if (from) params.set('from', from); if (to) params.set('to', to); const qs = params.toString() ? `?${params.toString()}` : ''; return platformApi.fetch<{ distribution: GeoDistributionEntry[] }>(`/telemetry/geo${qs}`, { headers: { Authorization: `Bearer ${token}` }, }); } export async function eraseTelemetryUser( token: string, userId: string ): Promise<{ userId: string; eventsDeleted: number; clustersUpdated: number }> { return platformApi.fetch<{ userId: string; eventsDeleted: number; clustersUpdated: number }>( `/telemetry/user/${encodeURIComponent(userId)}`, { method: 'DELETE', headers: { Authorization: `Bearer ${token}` }, } ); }