- Резенд письма верификации (/auth/resend-verification), модалка на фронте с поллингом статуса, страница /verify-email/:token - SMTP переведён на собственный Postfix (mail.jze9mail.ru, STARTTLS, SMTP_TLS_VERIFY) вместо Yandex-заглушки в дефолтах и .env.example - OLLAMA_URL и модель в worker-gpu синхронизированы с новым GPU-хостом (llama3:8b -> qwen2.5:7b, которой раньше не было на сервере) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
123 lines
4.3 KiB
TypeScript
123 lines
4.3 KiB
TypeScript
import axios from 'axios';
|
|
import { useAuthStore } from '../store/auth';
|
|
|
|
export const api = axios.create({
|
|
baseURL: import.meta.env.VITE_API_URL || '/api',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
timeout: 30000,
|
|
});
|
|
|
|
// Добавить JWT токен к каждому запросу
|
|
api.interceptors.request.use((config) => {
|
|
const token = useAuthStore.getState().token;
|
|
if (token) {
|
|
config.headers.Authorization = `Bearer ${token}`;
|
|
}
|
|
return config;
|
|
});
|
|
|
|
// Обработка ответов: при 401 — разлогинить
|
|
api.interceptors.response.use(
|
|
(response) => response,
|
|
(error) => {
|
|
if (error.response?.status === 401) {
|
|
useAuthStore.getState().logout();
|
|
}
|
|
return Promise.reject(error);
|
|
}
|
|
);
|
|
|
|
// ─── API методы ───────────────────────────────────────────────────────────────
|
|
|
|
export const authApi = {
|
|
register: (data: { email: string; password: string; name: string }) =>
|
|
api.post('/auth/register', data),
|
|
|
|
login: (data: { email: string; password: string }) =>
|
|
api.post('/auth/login', data),
|
|
|
|
me: () => api.get('/auth/me'),
|
|
|
|
verifyEmail: (token: string) =>
|
|
api.post(`/auth/verify-email/${token}`),
|
|
|
|
resendVerification: () =>
|
|
api.post('/auth/resend-verification'),
|
|
};
|
|
|
|
export const tasksApi = {
|
|
list: (limit = 20, offset = 0) =>
|
|
api.get('/tasks/', { params: { limit, offset } }),
|
|
|
|
get: (taskId: string) =>
|
|
api.get(`/tasks/${taskId}`),
|
|
|
|
delete: (taskId: string) =>
|
|
api.delete(`/tasks/${taskId}`),
|
|
};
|
|
|
|
export const searchApi = {
|
|
create: (data: {
|
|
query: string;
|
|
lang?: string;
|
|
year_from?: number;
|
|
year_to?: number;
|
|
category?: string;
|
|
}) => api.post('/search/', data),
|
|
};
|
|
|
|
export const documentsApi = {
|
|
uploadForCheck: (file: File) => {
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
return api.post('/documents/check', formData, {
|
|
headers: { 'Content-Type': 'multipart/form-data' },
|
|
timeout: 120000, // 2 минуты для загрузки
|
|
});
|
|
},
|
|
};
|
|
|
|
export const reportsApi = {
|
|
get: (taskId: string) =>
|
|
api.get(`/reports/${taskId}`),
|
|
};
|
|
|
|
// ─── Админка ────────────────────────────────────────────────────────────────
|
|
export const adminApi = {
|
|
openSession: () => api.post('/admin/session'),
|
|
verifySession: (code: string) => api.get(`/admin/session/verify/${code}`),
|
|
|
|
health: () => api.get('/admin/health'),
|
|
stats: () => api.get('/admin/stats'),
|
|
|
|
users: (params?: { q?: string; limit?: number; offset?: number }) =>
|
|
api.get('/admin/users', { params }),
|
|
updateUser: (id: number, data: { plan?: string; is_verified?: boolean; is_admin?: boolean }) =>
|
|
api.patch(`/admin/users/${id}`, data),
|
|
deleteUser: (id: number) => api.delete(`/admin/users/${id}`),
|
|
|
|
tasks: (params?: { status?: string; type?: string; user_id?: number; limit?: number; offset?: number }) =>
|
|
api.get('/admin/tasks', { params }),
|
|
task: (publicId: string) => api.get(`/admin/tasks/${publicId}`),
|
|
requeueTask: (publicId: string) => api.post(`/admin/tasks/${publicId}/requeue`),
|
|
deleteTask: (publicId: string) => api.delete(`/admin/tasks/${publicId}`),
|
|
|
|
documents: (params?: { q?: string; source?: string; year?: number; limit?: number; offset?: number }) =>
|
|
api.get('/admin/documents', { params }),
|
|
deleteDocument: (id: number) => api.delete(`/admin/documents/${id}`),
|
|
|
|
storage: () => api.get('/admin/storage'),
|
|
|
|
sources: () => api.get('/admin/sources'),
|
|
createSource: (data: Record<string, unknown>) => api.post('/admin/sources', data),
|
|
updateSource: (id: number, data: Record<string, unknown>) => api.patch(`/admin/sources/${id}`, data),
|
|
deleteSource: (id: number) => api.delete(`/admin/sources/${id}`),
|
|
runSource: (id: number) => api.post(`/admin/sources/${id}/run`),
|
|
|
|
staging: (params?: { status?: string; limit?: number; offset?: number }) =>
|
|
api.get('/admin/staging', { params }),
|
|
stagingDetail: (id: number) => api.get(`/admin/staging/${id}`),
|
|
approveStaging: (id: number) => api.post(`/admin/staging/${id}/approve`),
|
|
rejectStaging: (id: number) => api.post(`/admin/staging/${id}/reject`),
|
|
};
|