MAC / frontend /src /lib /api.js
Aaryan17's picture
chore: upload MAC codebase to HF Space
0e76632 verified
/**
* MAC API client. Keep browser fetch calls here so Svelte pages stay thin.
*/
const BASE = '/api/v1';
function getToken() {
if (typeof localStorage === 'undefined') return null;
return localStorage.getItem('mac_token');
}
function headers(extra = {}) {
/** @type {Record<string, string>} */
const h = { 'Content-Type': 'application/json', ...extra };
const token = getToken();
if (token) h.Authorization = `Bearer ${token}`;
return h;
}
async function handleResponse(res) {
if (res.ok) {
const ct = res.headers.get('content-type') || '';
if (ct.includes('application/json')) return res.json();
return res.text();
}
let detail = `HTTP ${res.status}`;
try {
const body = await res.json();
detail = body?.detail?.message || body?.detail || body?.message || JSON.stringify(body);
} catch {}
const err = /** @type {Error & { status?: number }} */ (new Error(detail));
err.status = res.status;
throw err;
}
async function get(path, params = {}) {
const url = new URL(BASE + path, location.origin);
Object.entries(params).forEach(([k, v]) => {
if (v !== undefined && v !== null && v !== '') url.searchParams.set(k, v);
});
return handleResponse(await fetch(url, { headers: headers() }));
}
async function post(path, body = {}) {
return handleResponse(await fetch(BASE + path, {
method: 'POST',
headers: headers(),
body: JSON.stringify(body),
}));
}
async function patch(path, body = {}) {
return handleResponse(await fetch(BASE + path, {
method: 'PATCH',
headers: headers(),
body: JSON.stringify(body),
}));
}
async function put(path, body = {}) {
return handleResponse(await fetch(BASE + path, {
method: 'PUT',
headers: headers(),
body: JSON.stringify(body),
}));
}
async function del(path) {
return handleResponse(await fetch(BASE + path, { method: 'DELETE', headers: headers() }));
}
async function formPost(path, formData) {
const token = getToken();
return handleResponse(await fetch(BASE + path, {
method: 'POST',
headers: token ? { Authorization: `Bearer ${token}` } : {},
body: formData,
}));
}
export const auth = {
login: (roll_number, password) => post('/auth/login', { roll_number, password }),
verify: (roll_number, dob) => post('/auth/verify', { roll_number, dob }),
refresh: (refresh_token) => post('/auth/refresh', { refresh_token }),
me: () => get('/auth/me'),
logout: () => post('/auth/logout'),
changePassword: (old_password, new_password) => post('/auth/change-password', { old_password, new_password }),
setPassword: (new_password, confirm_password) => post('/auth/set-password', { new_password, confirm_password }),
updateProfile: (data) => put('/auth/me/profile', data),
};
export const setup = {
status: () => get('/setup/status'),
createAdmin: (name, email, password) => post('/setup/create-admin', { name, email, password }),
recovery: () => get('/setup/recovery'),
};
export const query = {
chatStream(messages, model = 'auto', options = {}) {
const token = getToken();
return fetch(BASE + '/query/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },
body: JSON.stringify({ messages, model, stream: true, ...options }),
});
},
complete: (messages, model = 'auto') => post('/query/chat', { messages, model, stream: false }),
};
export const models = {
list: () => get('/models'),
legacyList: () => get('/models/list'),
health: () => get('/models/health'),
community: () => get('/models/community'),
submissions: () => get('/models/submissions'),
};
export const usage = {
mine: (_days = 30) => get('/usage/me'),
stats: () => get('/usage/me'),
history: (page = 1, per_page = 50, filters = {}) => get('/usage/me/history', { page, per_page, ...filters }),
quota: () => get('/usage/me/quota'),
all: (page = 1, per_page = 50, department = '') => get('/usage/admin/all', { page, per_page, department }),
models: () => get('/usage/admin/models'),
};
export const quota = {
limits: () => get('/quota/limits'),
mine: () => get('/quota/me'),
remaining: () => get('/quota/me'),
setUser: (roll_number, data) => put(`/quota/admin/user/${roll_number}`, data),
exceeded: () => get('/quota/admin/exceeded'),
};
export const keys = {
list: () => get('/keys/my-key').then((key) => (key ? [key] : [])),
generate: (label = '') => post('/keys/generate', { label }),
stats: () => get('/keys/my-key/stats'),
revoke: (_key_id = null) => del('/keys/my-key'),
adminAll: () => get('/keys/admin/all'),
adminRevoke: (roll_number) => post('/keys/admin/revoke', { roll_number }),
};
export const scopedKeys = {
create: (data) => post('/scoped-keys', data),
mine: () => get('/scoped-keys/my'),
revoke: (id) => del(`/scoped-keys/${id}`),
adminAll: (page = 1, per_page = 100) => get('/scoped-keys/admin/all', { page, per_page }),
adminRevoke: (id) => del(`/scoped-keys/admin/${id}`),
};
export const features = {
status: () => get('/features/status'),
toggle: (key, enabled) => patch(`/admin/features/${key}`, { enabled }),
};
export const hardware = {
local: () => get('/hardware/local'),
recommendations: () => get('/hardware/recommendations'),
};
export const network = {
localIp: () => get('/network/local-ip'),
discover: (timeout = 2) => get('/network/discover', { timeout }),
};
export const system = {
version: () => get('/system/version'),
updateStatus: () => get('/system/update-status'),
restart: () => post('/admin/system/restart'),
logs: (lines = 200) => get('/admin/system/logs', { lines }),
};
export const users = {
list: (page = 1, limit = 50, search = '') => get('/auth/admin/users', { page, per_page: limit, search }),
update: (user_id, data) => put(`/auth/admin/users/${user_id}`, data),
updateRole: (user_id, role) => put(`/auth/admin/users/${user_id}/role`, { role }),
updateStatus: (user_id, is_active) => put(`/auth/admin/users/${user_id}/status`, { is_active }),
create: (data) => post('/auth/admin/users', data),
delete: (user_id) => del(`/auth/admin/users/${user_id}`),
resetPassword: (user_id) => post(`/auth/admin/users/${user_id}/reset-password`),
regenerateKey: (user_id) => post(`/auth/admin/users/${user_id}/regenerate-key`),
stats: () => get('/auth/admin/stats'),
registry: () => get('/auth/admin/registry'),
addRegistry: (data) => post('/auth/admin/registry', data),
bulkRegistry: (students) => post('/auth/admin/registry/bulk', { students }),
uploadRegistry: (formData) => formPost('/auth/admin/registry/upload', formData),
};
export const guardrails = {
getRules: () => get('/guardrails/rules'),
updateRules: (data) => put('/guardrails/rules', data),
addRule: (data) => post('/guardrails/rules', data),
toggleRule: (id) => patch(`/guardrails/rules/${id}/toggle`),
deleteRule: (id) => del(`/guardrails/rules/${id}`),
};
export const rag = {
list: () => get('/rag/documents'),
upload: (formData) => formPost('/rag/ingest', formData),
delete: (doc_id) => del(`/rag/documents/${doc_id}`),
collections: () => get('/rag/collections'),
};
export const notifications = {
list: (page = 1, per_page = 30) => get('/notifications', { page, per_page }),
markRead: (id) => post(`/notifications/${id}/read`),
markAllRead: () => post('/notifications/read-all'),
auditLogs: (page = 1, per_page = 100) => get('/notifications/audit-logs', { page, per_page }),
activity: (limit = 100) => get('/notifications/activity-stream', { limit }),
};
export const cluster = {
nodes: () => get('/cluster/nodes'),
node: (id) => get(`/cluster/nodes/${id}`),
nodeAction: (id, action) => post(`/cluster/nodes/${id}/action`, { action }),
history: (id, limit = 60) => get(`/cluster/nodes/${id}/history`, { limit }),
enrollTokens: () => get('/cluster/enroll-tokens'),
createEnrollToken: (label = 'Worker Node', expires_hours = 24) => post('/cluster/enroll-token', { label, expires_hours }),
deployModel: (node_id, data) => post(`/cluster/nodes/${node_id}/deploy`, data),
removeDeployment: (node_id, dep_id) => del(`/cluster/nodes/${node_id}/deploy/${dep_id}`),
};
export const academic = {
branches: () => get('/academic/branches'),
createBranch: (data) => post('/academic/branches', data),
updateBranch: (id, data) => patch(`/academic/branches/${id}`, data),
deleteBranch: (id) => del(`/academic/branches/${id}`),
sections: () => get('/academic/sections'),
branchSections: (branch_id) => get(`/academic/branches/${branch_id}/sections`),
createSection: (data) => post('/academic/sections', data),
};
export const files = {
list: () => get('/files'),
upload: (formData) => formPost('/files/upload', formData),
download: (id) => `${BASE}/files/${id}/download`,
deleteFile: (id) => del(`/files/${id}`),
stats: (id) => get(`/files/${id}/stats`),
};
export const notebooks = {
list: (include_archived = false) => get('/notebooks', { include_archived }),
create: (data) => post('/notebooks', data),
get: (id) => get(`/notebooks/${id}`),
update: (id, data) => patch(`/notebooks/${id}`, data),
delete: (id) => del(`/notebooks/${id}`),
addCell: (id, data) => post(`/notebooks/${id}/cells`, data),
updateCell: (cell_id, data) => patch(`/notebooks/cells/${cell_id}`, data),
deleteCell: (cell_id) => del(`/notebooks/cells/${cell_id}`),
runCell: (cell_id) => post(`/notebooks/cells/${cell_id}/run`),
executions: (cell_id) => get(`/notebooks/cells/${cell_id}/executions`),
reorder: (id, cell_ids) => post(`/notebooks/${id}/reorder`, { cell_ids }),
};
export const doubts = {
create: (data) => post('/doubts', data),
mine: (page = 1, per_page = 20) => get('/doubts/my', { page, per_page }),
all: (filters = {}) => get('/doubts/all', filters),
get: (id) => get(`/doubts/${id}`),
reply: (id, body) => post(`/doubts/${id}/reply`, { body }),
close: (id) => post(`/doubts/${id}/close`),
};
export const attendance = {
settings: () => get('/attendance/settings'),
updateSettings: (data) => put('/attendance/settings', data),
subjects: () => get('/attendance/subjects'),
faceStatus: () => get('/attendance/face-status'),
registerFace: (face_image_base64) => post('/attendance/register-face', { face_image_base64 }),
sessions: (filters = {}) => get('/attendance/sessions', filters),
createSession: (data) => post('/attendance/sessions', data),
closeSession: (id) => post(`/attendance/sessions/${id}/close`),
mark: (session_id, face_image_base64) => post('/attendance/mark', { session_id, face_image_base64 }),
report: (id) => get(`/attendance/sessions/${id}/report`),
adminOverview: (filters = {}) => get('/attendance/admin/overview', filters),
summary: (department = '') => get('/attendance/summary', { department }),
reportCsvUrl: (id) => `${BASE}/attendance/sessions/${id}/report/csv`,
reportPdfUrl: (id) => `${BASE}/attendance/sessions/${id}/report/pdf`,
summaryCsvUrl: (department = '') => `${BASE}/attendance/summary/csv${department ? `?department=${encodeURIComponent(department)}` : ''}`,
};
export const copyCheck = {
sessions: (page = 1, per_page = 50) => get('/copy-check/sessions', { page, per_page }),
createSession: (formData) => formPost('/copy-check/sessions', formData),
getSession: (id) => get(`/copy-check/sessions/${id}`),
students: (id) => get(`/copy-check/sessions/${id}/students`),
uploadSheet: (id, formData) => formPost(`/copy-check/sessions/${id}/sheets`, formData),
evaluate: (id) => post(`/copy-check/sessions/${id}/evaluate`),
plagiarism: (id) => post(`/copy-check/sessions/${id}/plagiarism`),
archive: (id) => patch(`/copy-check/sessions/${id}/archive`),
reportUrl: (id) => `${BASE}/copy-check/sessions/${id}/report/pdf`,
};