add client

This commit is contained in:
2026-07-14 23:50:40 +07:00
parent fec1f271d0
commit a2b8094ff7
+270
View File
@@ -0,0 +1,270 @@
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> = 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<ApiResponse<PingRsp>> {
const { data } = await this.client.get('/api/test/ping');
return data;
}
async echo(params: EchoReq): Promise<ApiResponse<EchoRsp>> {
const { data } = await this.client.post('/api/test/echo', params);
return data;
}
async signup(params: SignupReq): Promise<ApiResponse<SignupRsp>> {
const { data } = await this.client.post('/api/auth/signup', params);
return data;
}
async refreshPassword(params: RefreshPasswordReq): Promise<ApiResponse<RefreshPasswordRsp>> {
const { data } = await this.client.post('/api/auth/refresh-password', params);
return data;
}
async login(params: LoginReq): Promise<ApiResponse<LoginRsp>> {
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<ApiResponse<RefreshRsp>> {
const { data } = await this.client.post('/api/auth/refresh', params);
return data;
}
async logout(): Promise<void> {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
}
// ---------- USERS ----------
async getUsers(params: GetUsersReq = {}): Promise<ApiResponse<GetUsersRsp>> {
const { data } = await this.client.get('/api/users');
return data;
}
async getUserById(params: GetUserByIdReq): Promise<ApiResponse<GetUserByIdRsp>> {
const { data } = await this.client.get(`/api/users/${params.id}`);
return data;
}
async getMe(params: GetMeReq = {}): Promise<ApiResponse<GetMeRsp>> {
const { data } = await this.client.get('/api/users/me');
return data;
}
async addUserRole(params: AddUserRoleReq): Promise<ApiResponse<AddUserRoleRsp>> {
const { id, role } = params;
const { data } = await this.client.post(`/api/users/${id}/role/add`, { role });
return data;
}
async deleteUserRole(params: DeleteUserRoleReq): Promise<ApiResponse<DeleteUserRoleRsp>> {
const { id, role } = params;
const { data } = await this.client.post(`/api/users/${id}/role/delete`, { role });
return data;
}
// ---------- PERMISSIONS ----------
async getPermissions(params: GetPermissionsReq = {}): Promise<ApiResponse<GetPermissionsRsp>> {
const { data } = await this.client.get('/api/ui/permissions');
return data;
}
// ---------- FILES ----------
async uploadFile(params: UploadFileReq): Promise<ApiResponse<UploadFileRsp>> {
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<Blob> {
const response = await this.client.get(`/api/files/${params.filename}`, {
responseType: 'blob',
});
return response.data;
}
// ---------- SCENARIOS ----------
async addScenario(params: AddScenarioReq): Promise<ApiResponse<AddScenarioRsp>> {
const { data } = await this.client.post('/api/scenario', params);
return data;
}
async getMyScenarios(params: GetMyScenariosReq = {}): Promise<ApiResponse<GetMyScenariosRsp>> {
const { data } = await this.client.get('/api/my-scenarios');
return data;
}
async getScenario(params: GetScenarioReq): Promise<ApiResponse<GetScenarioRsp>> {
const { data } = await this.client.get(`/api/scenarios/${params.id}`);
return data;
}
async updateScenario(params: UpdateScenarioReq): Promise<ApiResponse<UpdateScenarioRsp>> {
const { id, ...body } = params;
const { data } = await this.client.put(`/api/scenarios/${id}`, body);
return data;
}
async deleteScenario(params: DeleteScenarioReq): Promise<ApiResponse<DeleteScenarioRsp>> {
const { data } = await this.client.delete(`/api/scenarios/${params.id}`);
return data;
}
// ---------- SCENARIO PLACES ----------
async addScenarioPlace(params: AddScenarioPlaceReq): Promise<ApiResponse<AddScenarioPlaceRsp>> {
const { id, place } = params;
const { data } = await this.client.post(`/api/scenarios/${id}/places`, place);
return data;
}
async updateScenarioPlace(params: UpdateScenarioPlaceReq): Promise<ApiResponse<UpdateScenarioPlaceRsp>> {
const { id, code, place } = params;
const { data } = await this.client.put(`/api/scenarios/${id}/places/${code}`, place);
return data;
}
async deleteScenarioPlace(params: DeleteScenarioPlaceReq): Promise<ApiResponse<DeleteScenarioPlaceRsp>> {
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';