146 lines
4.3 KiB
TypeScript
146 lines
4.3 KiB
TypeScript
/**
|
|
* PeakPulse backend client — typed HTTP wrappers for the peakpulse-backend (port 4010).
|
|
*
|
|
* Auth: Bearer token from the caller's JWT (same JWT_SECRET as platform-service).
|
|
*/
|
|
|
|
import { config } from './config.js';
|
|
|
|
export interface PeakPulseClientOptions {
|
|
token?: string;
|
|
requestId?: string;
|
|
productId?: string;
|
|
}
|
|
|
|
async function peakpulseFetch<T>(
|
|
path: string,
|
|
init: RequestInit,
|
|
opts: PeakPulseClientOptions
|
|
): Promise<T> {
|
|
const headers: Record<string, string> = {
|
|
'Content-Type': 'application/json',
|
|
...(opts.token ? { Authorization: `Bearer ${opts.token}` } : {}),
|
|
...(opts.requestId ? { 'x-request-id': opts.requestId } : {}),
|
|
...(opts.productId ? { 'x-product-id': opts.productId } : { 'x-product-id': 'peakpulse' }),
|
|
};
|
|
const res = await fetch(`${config.PEAKPULSE_BACKEND_URL}${path}`, {
|
|
...init,
|
|
headers: { ...((init.headers as Record<string, string>) ?? {}), ...headers },
|
|
signal: AbortSignal.timeout(15_000),
|
|
});
|
|
if (!res.ok) {
|
|
const body = await res.text().catch(() => '');
|
|
throw new Error(`peakpulse-backend ${init.method ?? 'GET'} ${path} → ${res.status}: ${body}`);
|
|
}
|
|
return res.json() as Promise<T>;
|
|
}
|
|
|
|
// ── Sessions ───────────────────────────────────────────────────────────────
|
|
|
|
export interface PeakSessionDoc {
|
|
id: string;
|
|
clientId?: string;
|
|
userId: string;
|
|
productId: string;
|
|
activityType: 'hiking' | 'skiing' | 'cycling' | 'running';
|
|
status: 'active' | 'completed' | 'paused';
|
|
startTime: string;
|
|
endTime?: string;
|
|
durationSeconds?: number;
|
|
distanceMeters?: number;
|
|
maxSpeedMps?: number;
|
|
averageSpeedMps?: number;
|
|
elevationGainMeters?: number;
|
|
elevationLossMeters?: number;
|
|
maxElevationMeters?: number;
|
|
locationName?: string;
|
|
startLatitude?: number;
|
|
startLongitude?: number;
|
|
unitPreference?: string;
|
|
weather?: {
|
|
tempCelsius?: number;
|
|
conditionCode?: string;
|
|
windSpeedKph?: number;
|
|
uvIndex?: number;
|
|
};
|
|
skiMetrics?: {
|
|
runCount?: number;
|
|
verticalDescentMeters?: number;
|
|
liftTimeSeconds?: number;
|
|
skiTimeSeconds?: number;
|
|
};
|
|
trackPointCount?: number;
|
|
hapticMilestoneCount?: number;
|
|
savedToHealthKit?: boolean;
|
|
notes?: string;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface PeakSessionExport {
|
|
exportVersion: string;
|
|
productId: string;
|
|
session: Omit<
|
|
PeakSessionDoc,
|
|
'userId' | 'clientId' | 'savedToHealthKit' | 'createdAt' | 'updatedAt'
|
|
>;
|
|
exportedAt: string;
|
|
}
|
|
|
|
export function peakpulseSessionsList(
|
|
params: {
|
|
activityType?: string;
|
|
status?: string;
|
|
limit?: number;
|
|
offset?: number;
|
|
},
|
|
opts: PeakPulseClientOptions
|
|
): Promise<{ items: PeakSessionDoc[]; total: number }> {
|
|
const qs = new URLSearchParams();
|
|
if (params.activityType) qs.set('activityType', params.activityType);
|
|
if (params.status) qs.set('status', params.status);
|
|
if (params.limit !== undefined) qs.set('limit', String(params.limit));
|
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
|
const q = qs.toString();
|
|
return peakpulseFetch(`/peak/sessions${q ? `?${q}` : ''}`, { method: 'GET' }, opts);
|
|
}
|
|
|
|
export function peakpulseSessionExport(
|
|
sessionId: string,
|
|
opts: PeakPulseClientOptions
|
|
): Promise<PeakSessionExport> {
|
|
return peakpulseFetch(`/peak/sessions/${sessionId}/export`, { method: 'GET' }, opts);
|
|
}
|
|
|
|
export function peakpulseGetStats(opts: PeakPulseClientOptions): Promise<Record<string, unknown>> {
|
|
return peakpulseFetch('/peak/stats', { method: 'GET' }, opts);
|
|
}
|
|
|
|
// ── Routes ─────────────────────────────────────────────────────────────────
|
|
|
|
export interface PeakRouteDoc {
|
|
id: string;
|
|
sessionId: string;
|
|
userId: string;
|
|
productId: string;
|
|
trackPoints: unknown[];
|
|
hapticEvents: unknown[];
|
|
trackPointCount: number;
|
|
hapticEventCount: number;
|
|
boundingBox?: {
|
|
minLat: number;
|
|
maxLat: number;
|
|
minLon: number;
|
|
maxLon: number;
|
|
};
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export function peakpulseRouteGet(
|
|
sessionId: string,
|
|
opts: PeakPulseClientOptions
|
|
): Promise<PeakRouteDoc> {
|
|
return peakpulseFetch(`/peak/routes/${sessionId}`, { method: 'GET' }, opts);
|
|
}
|