All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 11s
- Move AdminLearningService + DTOs to learning-session module - Merge 21 new endpoints into existing admin-api/learning controller - Add analysis and ai-usage methods to unified service - Delete admin-learning module (no longer needed) - Revert JwtAuthGuard /api/admin bypass (was breaking isolation) - Fix: /api/* now exclusively serves user/iOS traffic again Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
70 lines
3.7 KiB
TypeScript
70 lines
3.7 KiB
TypeScript
const BASE = '/admin-api/learning';
|
|
|
|
export interface PaginatedResponse<T> {
|
|
items: T[];
|
|
total: number;
|
|
}
|
|
|
|
interface AnomalyData {
|
|
deltaOutliers: unknown[];
|
|
futureEvents: unknown[];
|
|
}
|
|
|
|
async function getApi<T>(path: string, params?: Record<string, any>): Promise<T> {
|
|
let url = `${BASE}${path}`;
|
|
if (params) {
|
|
const qs = Object.entries(params)
|
|
.filter(([, v]) => v !== undefined && v !== '')
|
|
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
|
|
.join('&');
|
|
if (qs) url += `?${qs}`;
|
|
}
|
|
const res = await fetch(url, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${localStorage.getItem('admin_access_token') || ''}` } });
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
const json = await res.json();
|
|
return json.data ?? json;
|
|
}
|
|
|
|
async function postApi<T>(path: string, body?: any): Promise<T> {
|
|
const res = await fetch(`${BASE}${path}`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${localStorage.getItem('admin_access_token') || ''}` },
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
});
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
const json = await res.json();
|
|
return json.data ?? json;
|
|
}
|
|
|
|
export interface DashboardData {
|
|
overview: { totalEvents: number; todayEvents: number; failedEvents: number; duplicateEvents: number; warningEvents: number; totalActiveSeconds: number };
|
|
sessions: { active: number; interrupted: number; completed: number; total: number };
|
|
users: { activeToday: number; totalWithEvents: number };
|
|
materials: { totalRead: number; totalMarkedRead: number };
|
|
recentAnomalies: Array<{ eventId: string; userId: string; materialId: string; eventType: string; errorCode?: string; occurredAt: string }>;
|
|
}
|
|
|
|
export const learningAdminAPI = {
|
|
getDashboard: (params?: { knowledgeBaseId?: string; startDate?: string; endDate?: string }) =>
|
|
getApi<DashboardData>('/dashboard', params),
|
|
getKnowledgeBases: () => getApi<Array<{ id: string; name: string }>>('/knowledge-bases'),
|
|
getReadingEvents: (params?: Record<string, any>) => getApi<PaginatedResponse<unknown>>('/reading-events', params),
|
|
getReadingEventDetail: (id: string) => getApi<any>(`/reading-events/${id}`),
|
|
getFailedEvents: (params?: Record<string, any>) => getApi<PaginatedResponse<unknown>>('/reading-events/failed', params),
|
|
getSessions: (params?: Record<string, any>) => getApi<PaginatedResponse<unknown>>('/sessions', params),
|
|
getSessionDetail: (id: string) => getApi<any>(`/sessions/${id}`),
|
|
getProgress: (params?: Record<string, any>) => getApi<PaginatedResponse<unknown>>('/progress', params),
|
|
getDailyActivities: (params?: Record<string, any>) => getApi<PaginatedResponse<unknown>>('/daily-activities', params),
|
|
getRecords: (params?: Record<string, any>) => getApi<PaginatedResponse<unknown>>('/records', params),
|
|
getUserTimeline: (userId: string) => getApi<unknown>('/user-timeline', { userId }),
|
|
getUserDiagnose: (userId: string) => getApi<unknown>('/user-diagnose', { userId }),
|
|
getMaterialDiagnose: (materialId: string) => getApi<unknown>('/material-diagnose', { materialId }),
|
|
getAnomalies: () => getApi<AnomalyData>('/anomalies'),
|
|
getTemporaryMaterials: (params?: Record<string, any>) => getApi<PaginatedResponse<unknown>>('/temporary-materials', params),
|
|
recalculate: () => postApi<unknown>('/recalculate'),
|
|
replayEvent: (eventId: string) => postApi<{ success: boolean; message: string }>(`/reading-events/${eventId}/replay`),
|
|
replayEventsBatch: (params: { userId?: string; materialId?: string; startDate?: string; endDate?: string; eventType?: string }) =>
|
|
postApi<{ replayed: number; failed: number }>('/reading-events/replay-batch', params),
|
|
exportData: (type: string) => getApi<unknown>('/export', { type }),
|
|
};
|