/** * 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 { BatchQueueResponse, BatchResultsResponse, CreateBatchPayload, CreateTemplatePayload, ExamTemplate, ExamTemplateDetail, MarkingBatch, MarkUpsertPayload, } from '../../types/exam.types'; const EXAM_BASE = `${API_BASE}/api/exam`; async function authHeaders(): Promise> { 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 { 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 { const headers = await authHeaders(); const res = await axios.get(`${EXAM_BASE}/templates/${templateId}`, { headers }); return res.data; }, async createTemplate(payload: CreateTemplatePayload): Promise { const headers = await authHeaders(); const res = await axios.post(`${EXAM_BASE}/templates`, payload, { headers }); return res.data; }, async archiveTemplate(templateId: string): Promise { const headers = await authHeaders(); await axios.delete(`${EXAM_BASE}/templates/${templateId}`, { headers }); }, async createBatch(payload: CreateBatchPayload): Promise { const headers = await authHeaders(); const res = await axios.post(`${EXAM_BASE}/batches`, payload, { headers }); return res.data; }, async listBatches(params: { includeArchived?: boolean; templateId?: string } = {}): Promise { const headers = await authHeaders(); const res = await axios.get<{ batches: MarkingBatch[] }>(`${EXAM_BASE}/batches`, { headers, params: { include_archived: params.includeArchived ?? false, template_id: params.templateId, }, }); return res.data.batches ?? []; }, async getBatchQueue(batchId: string): Promise { const headers = await authHeaders(); const res = await axios.get(`${EXAM_BASE}/batches/${batchId}/queue`, { headers }); return res.data; }, async getBatchResults(batchId: string): Promise { const headers = await authHeaders(); const res = await axios.get(`${EXAM_BASE}/batches/${batchId}/results`, { headers }); return res.data; }, async getBatchCsv(batchId: string): Promise { const headers = await authHeaders(); const res = await axios.get(`${EXAM_BASE}/batches/${batchId}/csv`, { headers, responseType: 'text' }); return res.data; }, async upsertMark(markId: string, payload: MarkUpsertPayload): Promise { const headers = await authHeaders(); const res = await axios.put(`${EXAM_BASE}/marks/${markId}`, payload, { headers }); return res.data; }, }; export default examRepository;