- New examRepository (R2.1 seam): the only module talking to /api/exam (Supabase JWT → Bearer, axios to API_BASE). list/get/create/archive templates. - ExamDashboardPage (/exam-marker): lists institute templates, create dialog, archive; cards link to setup (S4-9). Wrapped in ErrorBoundary (R6.4). - exam.types.ts mirrors the API contract. - Dashboard 'Exam Marker' quick action (top-level discovery, R1.3/R6.1). Note: the in-canvas TeacherNavigation is unsuitable for a nav section (it's the worker prev/next tab bar) — flagged; used the dashboard entry instead. - R1.1 removal: deleted src/pages/tldraw/CCExamMarker/ (old 3-PDF viewer) and the now-dead CCExamMarkerPanel + its wiring in CCPanel/BasePanel (examMarkerProps was only ever passed by the deleted page). - Fixed pre-existing broken AppRoutes test (missing TimetableListPage mock export). Build green (vite); AppRoutes route tests pass; typecheck clean for new files. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
57 lines
2.0 KiB
TypeScript
57 lines
2.0 KiB
TypeScript
/**
|
|
* examRepository — the SINGLE module that talks to the /api/exam backend (spec R2.1 seam).
|
|
*
|
|
* All exam-marker persistence flows through here so a later dual-write / offline cache can slot
|
|
* in without touching feature code. Mirrors the auth pattern of timetableService: take the
|
|
* Supabase session JWT and send it as a Bearer token; the API enforces RLS as the user.
|
|
*/
|
|
import axios from 'axios';
|
|
|
|
import { API_BASE } from '../../config/apiConfig';
|
|
import { supabase } from '../../supabaseClient';
|
|
import type {
|
|
CreateTemplatePayload,
|
|
ExamTemplate,
|
|
ExamTemplateDetail,
|
|
} from '../../types/exam.types';
|
|
|
|
const EXAM_BASE = `${API_BASE}/api/exam`;
|
|
|
|
async function authHeaders(): Promise<Record<string, string>> {
|
|
const { data: { session } } = await supabase.auth.getSession();
|
|
if (!session?.access_token) {
|
|
throw new Error('No authentication token available');
|
|
}
|
|
return { Authorization: `Bearer ${session.access_token}` };
|
|
}
|
|
|
|
export const examRepository = {
|
|
async listTemplates(includeArchived = false): Promise<ExamTemplate[]> {
|
|
const headers = await authHeaders();
|
|
const res = await axios.get<{ templates: ExamTemplate[] }>(
|
|
`${EXAM_BASE}/templates`,
|
|
{ headers, params: { include_archived: includeArchived } },
|
|
);
|
|
return res.data.templates ?? [];
|
|
},
|
|
|
|
async getTemplate(templateId: string): Promise<ExamTemplateDetail> {
|
|
const headers = await authHeaders();
|
|
const res = await axios.get<ExamTemplateDetail>(`${EXAM_BASE}/templates/${templateId}`, { headers });
|
|
return res.data;
|
|
},
|
|
|
|
async createTemplate(payload: CreateTemplatePayload): Promise<ExamTemplate> {
|
|
const headers = await authHeaders();
|
|
const res = await axios.post<ExamTemplate>(`${EXAM_BASE}/templates`, payload, { headers });
|
|
return res.data;
|
|
},
|
|
|
|
async archiveTemplate(templateId: string): Promise<void> {
|
|
const headers = await authHeaders();
|
|
await axios.delete(`${EXAM_BASE}/templates/${templateId}`, { headers });
|
|
},
|
|
};
|
|
|
|
export default examRepository;
|