734 lines
22 KiB
TypeScript
734 lines
22 KiB
TypeScript
/**
|
|
* 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<string, number>; days: number }>(
|
|
`/audit/stats?days=${days}`
|
|
);
|
|
}
|
|
|
|
export async function logAudit(input: {
|
|
userId: string;
|
|
action: string;
|
|
category?: string;
|
|
details?: Record<string, unknown>;
|
|
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<string, unknown> | 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<string, number> }> {
|
|
return platformApi.fetch<{ total: number; byPlan: Record<string, number> }>('/auth/users/count', {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
}
|
|
|
|
export async function getUser(token: string, id: string): Promise<UserDoc> {
|
|
return platformApi.fetch<UserDoc>(`/auth/users/${id}`, {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
}
|
|
|
|
export async function updateUser(
|
|
token: string,
|
|
id: string,
|
|
updates: Record<string, unknown>
|
|
): Promise<UserDoc> {
|
|
return platformApi.fetch<UserDoc>(`/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<ProductDoc> {
|
|
return platformApi.fetch<ProductDoc>(`/products/${id}`);
|
|
}
|
|
|
|
export async function createProduct(input: Record<string, unknown>): Promise<ProductDoc> {
|
|
return platformApi.fetch<ProductDoc>('/products', {
|
|
method: 'POST',
|
|
body: JSON.stringify(input),
|
|
});
|
|
}
|
|
|
|
export async function updateProduct(
|
|
id: string,
|
|
updates: Record<string, unknown>
|
|
): Promise<ProductDoc> {
|
|
return platformApi.fetch<ProductDoc>(`/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<string, unknown>): Promise<FlagDoc> {
|
|
return platformApi.fetch<FlagDoc>('/flags', {
|
|
method: 'POST',
|
|
body: JSON.stringify(input),
|
|
});
|
|
}
|
|
|
|
export async function updateFlag(key: string, updates: Record<string, unknown>): Promise<FlagDoc> {
|
|
return platformApi.fetch<FlagDoc>(`/flags/${encodeURIComponent(key)}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(updates),
|
|
});
|
|
}
|
|
|
|
export async function deleteFlag(key: string): Promise<void> {
|
|
await platformApi.fetch<void>(`/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<ApiTokenResponse & { rawToken: string }> {
|
|
return platformApi.fetch<ApiTokenResponse & { rawToken: string }>('/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<string, string>;
|
|
android: Record<string, string>;
|
|
desktop: Record<string, string>;
|
|
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<ThemeDoc[]> {
|
|
return platformApi.fetch<ThemeDoc[]>('/themes', {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
}
|
|
|
|
export async function getTheme(token: string, id: string): Promise<ThemeDoc> {
|
|
return platformApi.fetch<ThemeDoc>(`/themes/${id}`, {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
}
|
|
|
|
export async function createTheme(
|
|
token: string,
|
|
input: Record<string, unknown>
|
|
): Promise<ThemeDoc> {
|
|
return platformApi.fetch<ThemeDoc>('/themes', {
|
|
method: 'POST',
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
body: JSON.stringify(input),
|
|
});
|
|
}
|
|
|
|
export async function updateTheme(
|
|
token: string,
|
|
id: string,
|
|
updates: Record<string, unknown>
|
|
): Promise<ThemeDoc> {
|
|
return platformApi.fetch<ThemeDoc>(`/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<ThemeDoc> {
|
|
return platformApi.fetch<ThemeDoc>(`/themes/${id}/activate`, {
|
|
method: 'POST',
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
}
|
|
|
|
export async function getActiveTheme(productId: string): Promise<ThemeDoc> {
|
|
return platformApi.fetch<ThemeDoc>(`/themes/active?productId=${encodeURIComponent(productId)}`);
|
|
}
|
|
|
|
// ── Notifications ─────────────────────────────────────────────
|
|
|
|
export interface NotificationPrefs {
|
|
pushEnabled: boolean;
|
|
emailEnabled: boolean;
|
|
categories: Record<string, boolean>;
|
|
}
|
|
|
|
export async function getNotificationPrefs(userId: string): Promise<NotificationPrefs | null> {
|
|
try {
|
|
return await platformApi.fetch<NotificationPrefs>(`/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<string, string>;
|
|
metrics?: Record<string, number>;
|
|
context?: Record<string, unknown>;
|
|
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<string, string> = {}
|
|
): 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<string, string> = {}
|
|
): 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<string, unknown>
|
|
): Promise<TelemetryPolicy> {
|
|
return platformApi.fetch<TelemetryPolicy>('/telemetry/policies', {
|
|
method: 'POST',
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
body: JSON.stringify(input),
|
|
});
|
|
}
|
|
|
|
export async function updateTelemetryPolicy(
|
|
token: string,
|
|
id: string,
|
|
updates: Record<string, unknown>
|
|
): Promise<TelemetryPolicy> {
|
|
return platformApi.fetch<TelemetryPolicy>(`/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<string, unknown>
|
|
): 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<TelemetryCluster> {
|
|
return platformApi.fetch<TelemetryCluster>(
|
|
`/telemetry/clusters/${encodeURIComponent(clusterId)}?pk=${encodeURIComponent(pk)}`,
|
|
{
|
|
method: 'PATCH',
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
body: JSON.stringify({ status }),
|
|
}
|
|
);
|
|
}
|
|
|
|
export async function getTelemetryMetrics(token: string): Promise<TelemetryMetrics> {
|
|
return platformApi.fetch<TelemetryMetrics>('/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}` },
|
|
}
|
|
);
|
|
}
|