git feature

This commit is contained in:
2026-06-30 14:46:19 +07:00
parent 251c1b7573
commit 11e5a76977
23 changed files with 1680 additions and 28 deletions

View File

@@ -81,6 +81,31 @@ export const apiAuth = {
},
};
export interface GitHubStatus {
connected: boolean;
githubLogin?: string;
connectedAt?: string;
scope?: string;
}
export const apiGitHub = {
status: async (): Promise<GitHubStatus> => {
const res = await staffFetch('/auth/github/status');
if (!res.ok) await parseError(res, 'Không tải trạng thái GitHub');
return res.json();
},
authorizeUrl: async (): Promise<{ authorizeUrl: string }> => {
const res = await staffFetch('/auth/github/authorize');
if (!res.ok) await parseError(res, 'Không bắt đầu OAuth GitHub');
return res.json();
},
disconnect: async () => {
const res = await staffFetch('/auth/github', { method: 'DELETE' });
if (!res.ok) await parseError(res, 'Ngắt kết nối GitHub thất bại');
return res.json();
},
};
export interface EmailDomainItem {
id: number;
domain: string;
@@ -586,6 +611,9 @@ export interface ExamRoomItem {
endTime: string;
allowedApps: string;
quizUrl: string;
gitRepoUrl?: string;
gitBranch?: string;
gitPublishUrl?: string;
status: 'draft' | 'ready' | 'ended' | 'cancelled';
studentCount: number;
paperCount: number;
@@ -752,4 +780,31 @@ export const apiExam = {
return res.json() as Promise<{ data: ExamSubmission[] }>;
},
downloadSubmission: (id: number, subId: number) => `${API_BASE}/exam-rooms/${id}/submissions/${subId}/download`,
downloadAllSubmissions: async (id: number, fallbackName: string) => {
let res: Response;
try {
res = await staffFetch(`/exam-rooms/${id}/submissions/download-all`);
} catch {
throw new Error('Không kết nối được server (Failed to fetch). Kiểm tra server đang chạy và thử lại.');
}
if (!res.ok) await parseError(res, 'Tải bài nộp gộp thất bại');
const blob = await res.blob();
if (!blob.size) throw new Error('File ZIP trống — có thể bài nộp trên server bị thiếu file');
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${fallbackName.replace(/[<>:"/\\|?*]+/g, '_')}_bai_nop.zip`;
a.click();
URL.revokeObjectURL(url);
},
saveGitSettings: async (id: number, payload: { gitRepoUrl?: string; gitBranch?: string }) => {
const res = await staffFetch(`/exam-rooms/${id}/git-settings`, { method: 'PATCH', body: JSON.stringify(payload) });
if (!res.ok) await parseError(res, 'Lưu cấu hình Git thất bại');
return res.json() as Promise<{ gitRepoUrl: string; gitBranch: string; gitPublishUrl?: string }>;
},
publishSubmissionsGit: async (id: number) => {
const res = await staffFetch(`/exam-rooms/${id}/submissions/publish-git`, { method: 'POST' });
if (!res.ok) await parseError(res, 'Đẩy lên Git thất bại');
return res.json() as Promise<{ ok: boolean; url: string; gitPublishUrl: string; openUrl?: string; message: string }>;
},
};