admin-projects/src/services/learningAdmin.ts
wangdl 5de0b0ecaf
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 11s
refactor: merge admin/learning into admin-api/learning, restore API isolation
- 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>
2026-06-18 19:26:04 +08:00

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 }),
};