feat: initial microservices project structure

Services:
- api: FastAPI gateway with JWT auth, async endpoints, WebSocket
- worker-gpu: CUDA sentence-transformers, FAISS IVFFlat, Ollama LLM
- worker-indexer: Winnowing+MinHash plagiarism detection, PDF/DOCX extraction
- worker-notifier: SMTP email notifications
- worker-gost: GOST 7.1-2003 and GOST R 7.0.5-2008 formatting

Infrastructure:
- docker-compose.yml (production) + docker-compose.dev.yml (hot reload)
- Nginx reverse proxy + WebSocket support
- PostgreSQL 16 with Alembic migrations
- Elasticsearch 8 with Russian/English analyzers
- MinIO, RabbitMQ, Redis, Ollama

Frontend:
- React 18 + Vite + TypeScript + TailwindCSS + Zustand + React Query v5
- 9 pages: Home, Search, Cabinet, Task, Check, Bibliography, Pricing, Login, Register

Scripts:
- Parser stubs: OpenAlex, КиберЛенинка, arXiv (Phase 0 - to be filled)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jze9
2026-05-24 19:42:39 +05:00
commit 7758315632
120 changed files with 9500 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
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}`),
};
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}`),
};