149 lines
4.1 KiB
JavaScript
149 lines
4.1 KiB
JavaScript
// 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;
|
|
}
|
|
}
|