import axios, { type AxiosInstance, type InternalAxiosRequestConfig, AxiosError } from 'axios'; import type { LoginReq, LoginRsp, SignupReq, SignupRsp, RefreshPasswordReq, RefreshPasswordRsp, RefreshReq, RefreshRsp, GetUsersReq, GetUsersRsp, GetUserByIdReq, GetUserByIdRsp, GetMeReq, GetMeRsp, AddUserRoleReq, AddUserRoleRsp, DeleteUserRoleReq, DeleteUserRoleRsp, GetPermissionsReq, GetPermissionsRsp, UploadFileReq, UploadFileRsp, DownloadFileReq, AddScenarioReq, AddScenarioRsp, GetMyScenariosReq, GetMyScenariosRsp, GetScenarioReq, GetScenarioRsp, UpdateScenarioReq, UpdateScenarioRsp, DeleteScenarioReq, DeleteScenarioRsp, AddScenarioPlaceReq, AddScenarioPlaceRsp, UpdateScenarioPlaceReq, UpdateScenarioPlaceRsp, DeleteScenarioPlaceReq, DeleteScenarioPlaceRsp, PingReq, PingRsp, EchoReq, EchoRsp, } from './generated/main'; // Типизированный ответ с error полем type ApiResponse = T & { error?: string }; class ApiClient { private client: AxiosInstance; private baseUrl: string; constructor() { this.baseUrl = import.meta.env.VITE_API_URL || 'http://localhost:8080'; this.client = axios.create({ baseURL: this.baseUrl, timeout: 30000, headers: { 'Content-Type': 'application/json', }, }); // Интерсептор для токена this.client.interceptors.request.use((config) => { const token = localStorage.getItem('accessToken'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); // Интерсептор для рефреша токена this.client.interceptors.response.use( (response) => response, async (error: AxiosError) => { const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean }; if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; try { const refreshToken = localStorage.getItem('refreshToken'); if (!refreshToken) { throw new Error('No refresh token'); } const response = await this.refresh({ refreshToken }); localStorage.setItem('accessToken', response.accessToken!); localStorage.setItem('refreshToken', response.refreshToken!); originalRequest.headers.Authorization = `Bearer ${response.accessToken}`; return this.client(originalRequest); } catch (refreshError) { // Рефреш не удался — разлогиниваем localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); window.location.href = '/login'; return Promise.reject(refreshError); } } return Promise.reject(error); } ); } // ---------- AUTH ---------- async ping(params: PingReq = {}): Promise> { const { data } = await this.client.get('/api/test/ping'); return data; } async echo(params: EchoReq): Promise> { const { data } = await this.client.post('/api/test/echo', params); return data; } async signup(params: SignupReq): Promise> { const { data } = await this.client.post('/api/auth/signup', params); return data; } async refreshPassword(params: RefreshPasswordReq): Promise> { const { data } = await this.client.post('/api/auth/refresh-password', params); return data; } async login(params: LoginReq): Promise> { const { data } = await this.client.post('/api/auth/login', params); if (data.accessToken) { localStorage.setItem('accessToken', data.accessToken); if (data.refreshToken) { localStorage.setItem('refreshToken', data.refreshToken); } } return data; } async refresh(params: RefreshReq): Promise> { const { data } = await this.client.post('/api/auth/refresh', params); return data; } async logout(): Promise { localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); } // ---------- USERS ---------- async getUsers(params: GetUsersReq = {}): Promise> { const { data } = await this.client.get('/api/users'); return data; } async getUserById(params: GetUserByIdReq): Promise> { const { data } = await this.client.get(`/api/users/${params.id}`); return data; } async getMe(params: GetMeReq = {}): Promise> { const { data } = await this.client.get('/api/users/me'); return data; } async addUserRole(params: AddUserRoleReq): Promise> { const { id, role } = params; const { data } = await this.client.post(`/api/users/${id}/role/add`, { role }); return data; } async deleteUserRole(params: DeleteUserRoleReq): Promise> { const { id, role } = params; const { data } = await this.client.post(`/api/users/${id}/role/delete`, { role }); return data; } // ---------- PERMISSIONS ---------- async getPermissions(params: GetPermissionsReq = {}): Promise> { const { data } = await this.client.get('/api/ui/permissions'); return data; } // ---------- FILES ---------- async uploadFile(params: UploadFileReq): Promise> { if (!params.data) { throw new Error('File data is required'); } const formData = new FormData(); formData.append('filename', params.filename!); // Преобразуем bytes в Blob const dataArray = new Uint8Array(params.data); const blob = new Blob([dataArray]); formData.append('data', blob); const { data } = await this.client.post('/api/files/upload', formData, { headers: { 'Content-Type': 'multipart/form-data', }, }); return data; } async downloadFile(params: DownloadFileReq): Promise { const response = await this.client.get(`/api/files/${params.filename}`, { responseType: 'blob', }); return response.data; } // ---------- SCENARIOS ---------- async addScenario(params: AddScenarioReq): Promise> { const { data } = await this.client.post('/api/scenario', params); return data; } async getMyScenarios(params: GetMyScenariosReq = {}): Promise> { const { data } = await this.client.get('/api/my-scenarios'); return data; } async getScenario(params: GetScenarioReq): Promise> { const { data } = await this.client.get(`/api/scenarios/${params.id}`); return data; } async updateScenario(params: UpdateScenarioReq): Promise> { const { id, ...body } = params; const { data } = await this.client.put(`/api/scenarios/${id}`, body); return data; } async deleteScenario(params: DeleteScenarioReq): Promise> { const { data } = await this.client.delete(`/api/scenarios/${params.id}`); return data; } // ---------- SCENARIO PLACES ---------- async addScenarioPlace(params: AddScenarioPlaceReq): Promise> { const { id, place } = params; const { data } = await this.client.post(`/api/scenarios/${id}/places`, place); return data; } async updateScenarioPlace(params: UpdateScenarioPlaceReq): Promise> { const { id, code, place } = params; const { data } = await this.client.put(`/api/scenarios/${id}/places/${code}`, place); return data; } async deleteScenarioPlace(params: DeleteScenarioPlaceReq): Promise> { const { id, code } = params; const { data } = await this.client.delete(`/api/scenarios/${id}/places/${code}`); return data; } } // Единый экспорт export const api = new ApiClient(); // Экспорт типов для удобства export * from './generated/main';