docker composed

This commit is contained in:
2026-10-08 00:26:42 +03:00
parent 86bcd5b141
commit 728d342a89
14 changed files with 360 additions and 27 deletions
+7 -3
View File
@@ -1,8 +1,12 @@
// Базовый URL API. Если фронт и бэк на одном домене, можно использовать '' или '/api'
const API_BASE = 'http://localhost:8000';
// frontend/js/api.js
import { apiFetch } from "./auth.js";
import { API_BASE } from "./config.js"; // <-- Добавили импорт
// УДАЛИТЬ эти строки из начала api.js:
// export const API_BASE = ...
export async function fetchWorkoutTypes() {
const res = await fetch(`${API_BASE}/workout-types/`);
const res = await apiFetch("/workout-types/");
if (!res.ok) throw new Error('Failed to fetch workout types');
return await res.json();
}
+148
View File
@@ -0,0 +1,148 @@
// 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;
}
}
+5
View File
@@ -0,0 +1,5 @@
// frontend/js/config.js
export const API_BASE =
window.GYM_API_BASE ||
localStorage.getItem("gym_api_base") ||
"/api";