Spaces:
Sleeping
Sleeping
taboola
add student metrics dashboard, save quiz answer records, save original uploaded files
25cb9ab Download chatkit/frontend/src/lib/api.ts from taboola-cz/ClassLensPortal: direct link, hf CLI and curl.
- Browser
- Download file 4.47 kB
-
https://huggingface.co/spaces/taboola-cz/ClassLensPortal/resolve/main/chatkit/frontend/src/lib/api.ts
- Command line
-
hf download hf://spaces/taboola-cz/ClassLensPortal/chatkit/frontend/src/lib/api.ts
-
curl -L -o api.ts https://huggingface.co/spaces/taboola-cz/ClassLensPortal/resolve/main/chatkit/frontend/src/lib/api.ts
4.47 kB
| import { API_BASE_URL } from "./config"; | |
| const TOKEN_KEY = "classlens_token"; | |
| // Student and teacher logins share one token slot, so we track which kind of | |
| // account it belongs to — lets the app pick the right "am I logged in" check | |
| // (student vs teacher) before the first API call comes back. | |
| const ROLE_KEY = "classlens_role"; | |
| export type UserRole = "student" | "teacher"; | |
| export function getToken(): string | null { | |
| return localStorage.getItem(TOKEN_KEY); | |
| } | |
| export function setToken(token: string, role: UserRole) { | |
| localStorage.setItem(TOKEN_KEY, token); | |
| localStorage.setItem(ROLE_KEY, role); | |
| } | |
| export function getRole(): UserRole | null { | |
| const role = localStorage.getItem(ROLE_KEY); | |
| return role === "student" || role === "teacher" ? role : null; | |
| } | |
| export function clearToken() { | |
| localStorage.removeItem(TOKEN_KEY); | |
| localStorage.removeItem(ROLE_KEY); | |
| } | |
| function authHeaders(): Record<string, string> { | |
| const token = getToken(); | |
| return token ? { Authorization: `Bearer ${token}` } : {}; | |
| } | |
| async function handleResponse<T>(res: Response): Promise<T> { | |
| if (res.status === 401) { | |
| // Only treat this as an expired session (clear + reload) if the request | |
| // actually carried a token. A login/register call sends no token, so a | |
| // 401 there means "wrong credentials," not "session expired" — let it | |
| // fall through so the caller can show the real error message instead of | |
| // silently reloading the login page. | |
| if (getToken()) { | |
| clearToken(); | |
| window.location.reload(); | |
| throw new Error("Unauthorized"); | |
| } | |
| } | |
| if (!res.ok) { | |
| const text = await res.text(); | |
| let msg = text; | |
| try { | |
| const j: unknown = JSON.parse(text); | |
| if (j && typeof j === "object") { | |
| const obj = j as { detail?: unknown; message?: unknown }; | |
| const detail = obj.detail ?? obj.message; | |
| if (typeof detail === "string") msg = detail; | |
| } | |
| } catch { | |
| // Response body wasn't JSON — fall back to the raw text already in msg. | |
| } | |
| throw new Error(msg); | |
| } | |
| return (await res.json()) as T; | |
| } | |
| export async function apiGet<T>(path: string): Promise<T> { | |
| const res = await fetch(`${API_BASE_URL}${path}`, { | |
| headers: { ...authHeaders() }, | |
| }); | |
| return handleResponse<T>(res); | |
| } | |
| export async function apiPost<T>(path: string, body?: unknown): Promise<T> { | |
| const res = await fetch(`${API_BASE_URL}${path}`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json", ...authHeaders() }, | |
| body: body ? JSON.stringify(body) : undefined, | |
| }); | |
| return handleResponse<T>(res); | |
| } | |
| export async function apiPatch<T>(path: string, body: unknown): Promise<T> { | |
| const res = await fetch(`${API_BASE_URL}${path}`, { | |
| method: "PATCH", | |
| headers: { "Content-Type": "application/json", ...authHeaders() }, | |
| body: JSON.stringify(body), | |
| }); | |
| return handleResponse<T>(res); | |
| } | |
| export async function apiPut<T>(path: string, body: unknown): Promise<T> { | |
| const res = await fetch(`${API_BASE_URL}${path}`, { | |
| method: "PUT", | |
| headers: { "Content-Type": "application/json", ...authHeaders() }, | |
| body: JSON.stringify(body), | |
| }); | |
| return handleResponse<T>(res); | |
| } | |
| export async function apiDelete<T = void>(path: string): Promise<T> { | |
| const res = await fetch(`${API_BASE_URL}${path}`, { | |
| method: "DELETE", | |
| headers: { ...authHeaders() }, | |
| }); | |
| if (res.status === 401) { | |
| clearToken(); | |
| window.location.reload(); | |
| throw new Error("Unauthorized"); | |
| } | |
| if (!res.ok) { | |
| const text = await res.text(); | |
| throw new Error(text); | |
| } | |
| const ct = res.headers.get("content-type") || ""; | |
| if (ct.includes("application/json")) return res.json() as Promise<T>; | |
| return undefined as T; | |
| } | |
| /** Fetches a binary response (e.g. a stored PDF/image) as a blob, attaching | |
| * the same bearer auth header as the JSON helpers above — a plain <img>/ | |
| * <iframe> src can't carry that header, so callers create an object URL from | |
| * this instead. */ | |
| export async function apiGetBlob(path: string): Promise<Blob> { | |
| const res = await fetch(`${API_BASE_URL}${path}`, { | |
| headers: { ...authHeaders() }, | |
| }); | |
| if (!res.ok) throw new Error(await res.text()); | |
| return res.blob(); | |
| } | |
| export async function apiUpload<T>(path: string, formData: FormData): Promise<T> { | |
| const res = await fetch(`${API_BASE_URL}${path}`, { | |
| method: "POST", | |
| headers: { ...authHeaders() }, | |
| body: formData, | |
| }); | |
| return handleResponse<T>(res); | |
| } | |