let refreshPromise = null; const AUTH_KEYS = ['token', 'role', 'departmentId', 'userId']; export function getToken() { return localStorage.getItem('token'); } export function isAuthenticatedAsAdmin() { const role = localStorage.getItem('role'); return getToken() && role === 'ADMIN'; } export function isAuthenticatedAsRole(expectedRole) { const role = localStorage.getItem('role'); return getToken() && role === expectedRole; } export function isAuthenticatedAsAny(roles) { const role = localStorage.getItem('role'); return getToken() && roles.includes(role); } function getHeaders(contentType = 'application/json') { const headers = {}; const token = getToken(); if (token) { headers.Authorization = `Bearer ${token}`; } if (contentType) { headers['Content-Type'] = contentType; } return headers; } export async function apiFetch(endpoint, method = 'GET', body = null, retryOnUnauthorized = true) { const options = { method, headers: getHeaders(body ? 'application/json' : null), credentials: 'same-origin' }; if (body) { options.body = JSON.stringify(body); } const response = await fetch(endpoint, options); let data; try { data = await response.json(); } catch (e) { data = null; } if (response.status === 401 && retryOnUnauthorized) { const refreshed = await refreshAccessToken(); if (refreshed) { return apiFetch(endpoint, method, body, false); } clearAuthState(); window.location.href = '/'; } if (!response.ok) { const error = new Error(data?.message || `Ошибка HTTP: ${response.status}`); error.status = response.status; error.data = data; throw error; } return data; } export async function refreshAccessToken() { if (refreshPromise) { return refreshPromise; } refreshPromise = (async () => { const response = await fetch('/api/auth/refresh', { method: 'POST', credentials: 'same-origin' }); const data = await response.json().catch(() => null); if (!response.ok || !data?.token) { return false; } storeAuthState(data); return true; })().finally(() => { refreshPromise = null; }); return refreshPromise; } const cache = new Map(); const CACHABLE_PREFIXES = [ '/api/departments', '/api/specialties', '/api/education-forms', '/api/users/teachers', '/api/equipments', '/api/classrooms' ]; function invalidateCache(url) { const cacheKeysToClear = [ '/api/departments', '/api/specialties', '/api/education-forms', '/api/users', '/api/teacher-requests', '/api/department/teacher-requests', '/api/department/teachers', '/api/equipments', '/api/classrooms' ]; const affectedPrefixes = new Set(cacheKeysToClear.filter(prefix => url.includes(prefix))); if (url.includes('/api/teacher-requests')) { affectedPrefixes.add('/api/users'); } if (url.includes('/api/department/teachers')) { affectedPrefixes.add('/api/users/teachers'); } for (const prefix of affectedPrefixes) { for (const cacheKey of cache.keys()) { if (cacheKey.includes(prefix)) { cache.delete(cacheKey); } } } } export function clearCache() { cache.clear(); } export async function logout() { try { await fetch('/api/auth/logout', { method: 'POST', credentials: 'same-origin' }); } catch (e) { console.warn('Не удалось завершить серверную сессию:', e.message); } finally { clearAuthState(); clearCache(); } } export function storeAuthState(data) { if (data.token) localStorage.setItem('token', data.token); if (data.role) localStorage.setItem('role', data.role); if (data.departmentId !== undefined && data.departmentId !== null) { localStorage.setItem('departmentId', data.departmentId); } if (data.userId !== undefined && data.userId !== null) { localStorage.setItem('userId', data.userId); } } export function clearAuthState() { AUTH_KEYS.forEach(key => localStorage.removeItem(key)); } export const api = { get: async (url) => { const isCachable = CACHABLE_PREFIXES.some(prefix => url.startsWith(prefix)); if (isCachable) { if (!cache.has(url)) { cache.set(url, await apiFetch(url, 'GET')); } return cache.get(url); } return apiFetch(url, 'GET'); }, post: async (url, body) => { invalidateCache(url); return apiFetch(url, 'POST', body); }, put: async (url, body) => { invalidateCache(url); return apiFetch(url, 'PUT', body); }, delete: async (url, body = null) => { invalidateCache(url); return apiFetch(url, 'DELETE', body); } };