// frontend/js/auth.js import { API_BASE } from "./config.js"; const TOKEN_KEY = "gym_auth_token"; const USER_KEY = "gym_auth_user"; /** * Получить сохранённый токен */ export function getToken() { return localStorage.getItem(TOKEN_KEY); } /** * Получить сохранённого пользователя */ export function getStoredUser() { try { const raw = localStorage.getItem(USER_KEY); return raw ? JSON.parse(raw) : null; } catch { return null; } } /** * Сохранить сессию (токен + данные пользователя) */ export function setSession(token, user) { localStorage.setItem(TOKEN_KEY, token); localStorage.setItem(USER_KEY, JSON.stringify(user)); } /** * Очистить сессию (Logout) */ export function clearSession() { localStorage.removeItem(TOKEN_KEY); localStorage.removeItem(USER_KEY); } /** * Защищённый Fetch wrapper. * Автоматически добавляет Authorization header. * При получении 401 очищает сессию и вызывает событие 'auth:unauthorized'. */ export async function apiFetch(path, options = {}) { const token = getToken(); const headers = new Headers(options.headers || {}); if (token) { headers.set("Authorization", `Bearer ${token}`); } // Если есть тело запроса и Content-Type не задан явно — ставим JSON if (options.body && !headers.has("Content-Type")) { headers.set("Content-Type", "application/json"); } let res; try { res = await fetch(`${API_BASE}${path}`, { ...options, headers, }); } catch (e) { // Сеть недоступна или другая ошибка транспорта // Не логаутим автоматически, просто пробрасываем ошибку throw e; } // Если сервер ответил 401 Unauthorized — токен протух или неверный if (res.status === 401) { clearSession(); window.dispatchEvent(new Event("auth:unauthorized")); } return res; } /** * Логин пользователя */ export async function login(username, password) { const res = await fetch(`${API_BASE}/auth/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password }), }); if (!res.ok) { const err = await parseError(res, "Login failed"); throw new Error(err); } const data = await res.json(); setSession(data.access_token, data.user); return data.user; } /** * Регистрация нового пользователя */ export async function register(username, password, role = "personal") { const res = await fetch(`${API_BASE}/auth/register`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password, role }), }); if (!res.ok) { const err = await parseError(res, "Register failed"); throw new Error(err); } const data = await res.json(); setSession(data.access_token, data.user); return data.user; } /** * Проверка токена / получение текущего пользователя */ export async function me() { const res = await apiFetch("/auth/me"); if (!res.ok) return null; return await res.json(); } /** * Хелпер для парсинга ошибок FastAPI/Pydantic */ async function parseError(res, fallbackMessage) { try { const err = await res.json(); if (typeof err.detail === "string") return err.detail; if (Array.isArray(err.detail)) { return err.detail .map((d) => `${d.loc?.join(".") || ""}: ${d.msg || ""}`.trim()) .join("; "); } return JSON.stringify(err.detail || err); } catch { return fallbackMessage || res.statusText; } }