app/src/services/exam/examRepository.ts

104 lines
3.7 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 {
BatchQueueResponse,
BatchResultsResponse,
CreateBatchPayload,
CreateTemplatePayload,
ExamTemplate,
ExamTemplateDetail,
MarkingBatch,
MarkUpsertPayload,
} 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 });
},
async createBatch(payload: CreateBatchPayload): Promise<MarkingBatch> {
const headers = await authHeaders();
const res = await axios.post<MarkingBatch>(`${EXAM_BASE}/batches`, payload, { headers });
return res.data;
},
async listBatches(params: { includeArchived?: boolean; templateId?: string } = {}): Promise<MarkingBatch[]> {
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<BatchQueueResponse> {
const headers = await authHeaders();
const res = await axios.get<BatchQueueResponse>(`${EXAM_BASE}/batches/${batchId}/queue`, { headers });
return res.data;
},
async getBatchResults(batchId: string): Promise<BatchResultsResponse> {
const headers = await authHeaders();
const res = await axios.get<BatchResultsResponse>(`${EXAM_BASE}/batches/${batchId}/results`, { headers });
return res.data;
},
async getBatchCsv(batchId: string): Promise<string> {
const headers = await authHeaders();
const res = await axios.get<string>(`${EXAM_BASE}/batches/${batchId}/csv`, { headers, responseType: 'text' });
return res.data;
},
async upsertMark(markId: string, payload: MarkUpsertPayload): Promise<unknown> {
const headers = await authHeaders();
const res = await axios.put(`${EXAM_BASE}/marks/${markId}`, payload, { headers });
return res.data;
},
};
export default examRepository;