docs: Add comprehensive project documentation covering architecture, development, and APIs, and update AGENTS.md.
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -7,8 +7,6 @@ backend/build/
|
|||||||
frontend/node_modules/
|
frontend/node_modules/
|
||||||
frontend/dist/
|
frontend/dist/
|
||||||
|
|
||||||
.agents
|
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
*.DS_Store
|
*.DS_Store
|
||||||
GEMINI.md
|
|
||||||
169
AGENTS.md
169
AGENTS.md
@@ -28,174 +28,59 @@ magistr/
|
|||||||
│ ├── admin/ # Интерфейс администратора
|
│ ├── admin/ # Интерфейс администратора
|
||||||
│ ├── teacher/ # Интерфейс преподавателя
|
│ ├── teacher/ # Интерфейс преподавателя
|
||||||
│ └── student/ # Интерфейс студента
|
│ └── student/ # Интерфейс студента
|
||||||
|
├── docs/ # 📖 Документация проекта
|
||||||
├── compose.yaml # Docker Compose конфигурация
|
├── compose.yaml # Docker Compose конфигурация
|
||||||
└── .env # Переменные окружения
|
└── .env # Переменные окружения
|
||||||
```
|
```
|
||||||
|
|
||||||
**Внешние зависимости (родительская директория)**
|
**Внешние зависимости (родительская директория)**
|
||||||
|
|
||||||
На уровень выше расположен `../caddy-proxy/`. Это реверс-прокси, обрабатывающий трафик для `magistr.zuev.company`. Если возникают проблемы с доменом или внешним доступом, проверяйте `Caddyfile` там.
|
На уровень выше расположен конфиг kubernetes `../k8s/`, все файлы сборки на проде расположены там.
|
||||||
|
|
||||||
так же на уровень выше расположен конфиг kubernetes `../k8s/`, все файлы сборки на проде расположены там.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Команды сборки и запуска
|
## Быстрый справочник команд
|
||||||
|
|
||||||
### Docker Compose (основной способ)
|
|
||||||
|
|
||||||
Сборка и запуск всех сервисов (backend, frontend, PostgreSQL) выполняется через Docker Compose.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Сборка и запуск всех сервисов
|
# Сборка и запуск
|
||||||
docker compose up -d --build
|
docker compose up -d --build
|
||||||
|
|
||||||
# Остановка всех сервисов
|
# Полный сброс БД
|
||||||
docker compose down
|
docker compose down -v && docker compose up -d
|
||||||
|
|
||||||
# Просмотр логов всех сервисов
|
# Логи конкретного сервиса
|
||||||
docker compose logs -f
|
|
||||||
|
|
||||||
# Просмотр логов конкретного сервиса
|
|
||||||
docker compose logs -f backend
|
docker compose logs -f backend
|
||||||
|
|
||||||
# Пересоздать контейнер базы данных (полный сброс данных и повтор миграций Flyway)
|
|
||||||
docker compose down -v
|
|
||||||
docker compose up -d db
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Frontend
|
Подробнее — см. [`docs/README.md`](docs/README.md) и [`docs/INFRASTRUCTURE.md`](docs/INFRASTRUCTURE.md).
|
||||||
|
|
||||||
Статические файлы обслуживаются через Apache (httpd:alpine). Изменения в файлах frontend требуют пересборки контейнера.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Соглашения о коде (Code Style)
|
## Критические правила для агентов
|
||||||
|
|
||||||
### Java (Backend)
|
### Flyway миграции
|
||||||
|
- **ЗАПРЕЩЕНО** изменять существующие файлы миграций (например, `V1__init.sql`). Это сломает контрольные суммы Flyway.
|
||||||
**Именование:**
|
- Новые миграции: `V{N}__{описание}.sql` в `backend/src/main/resources/db/migration/`
|
||||||
- Классы: PascalCase (например, `LessonsController`, `LessonResponse`)
|
- Подробнее — см. [`docs/DATABASE.md`](docs/DATABASE.md)
|
||||||
- Методы и переменные: camelCase
|
|
||||||
- Константы: UPPER_SNAKE_CASE
|
|
||||||
- Пакеты: lowercase (например, `com.magistr.app.controller`)
|
|
||||||
|
|
||||||
**Импорты:**
|
|
||||||
- Группировка: static imports, затем external packages, затем internal
|
|
||||||
- Используйте wildcard imports для пакетов того же модуля: `import com.magistr.app.model.*;`
|
|
||||||
- Порядок: java.*, javax.*, external.*, internal.*
|
|
||||||
|
|
||||||
**Форматирование:**
|
|
||||||
- Отступы: 4 пробела (стандарт Java)
|
|
||||||
- Фигурные скобки: K&R style (открывающая на той же строке)
|
|
||||||
- Длина строки: до 120 символов
|
|
||||||
- Всегда используйте фигурные скобки для if/for/while
|
|
||||||
|
|
||||||
**Типы и аннотации:**
|
|
||||||
- Используйте явные типы вместо `var` для возвращаемых значений публичных методов
|
|
||||||
- Аннотации JPA: `@Entity`, `@Table`, `@Id`, `@GeneratedValue`, `@Column`
|
|
||||||
- Используйте `@JsonInclude(JsonInclude.Include.NON_NULL)` для DTO
|
|
||||||
- Для логгирования используйте SLF4J: `LoggerFactory.getLogger(ClassName.class)`
|
|
||||||
|
|
||||||
**Обработка ошибок:**
|
|
||||||
- Возвращайте `ResponseEntity<?>` с соответствующим HTTP статусом
|
|
||||||
- Логируйте ошибки с полным стектрейсом: `logger.error("msg: {}", e.getMessage(), e)`
|
|
||||||
- Для валидации используйте отдельные классы-валидаторы (см. `DayAndWeekValidator`)
|
|
||||||
|
|
||||||
**Архитектура контроллеров:**
|
|
||||||
- Используйте constructor injection для зависимостей
|
|
||||||
- Все endpoints имеют префикс `/api/`
|
|
||||||
- Возвращайте понятные сообщения об ошибках на русском языке
|
|
||||||
|
|
||||||
### Frontend (JavaScript)
|
|
||||||
|
|
||||||
**Именование:**
|
|
||||||
- Файлы: kebab-case (например, `main.js`, `schedule-view.js`)
|
|
||||||
- Функции и переменные: camelCase
|
|
||||||
- Константы: UPPER_SNAKE_CASE
|
|
||||||
|
|
||||||
**Модули:**
|
|
||||||
- Используйте ES6 modules с `import`/`export`
|
|
||||||
- Всегда указывайте расширение при импорте: `import { x } from './api.js';`
|
|
||||||
|
|
||||||
**Форматирование:**
|
|
||||||
- Отступы: 4 пробела
|
|
||||||
- Используйте template literals вместо конкатенации строк
|
|
||||||
- Предпочитайте `const` переменные, используйте `let` только при необходимости переприсваивания
|
|
||||||
|
|
||||||
**Лучшие практики:**
|
|
||||||
- Используйте `async/await` для асинхронных операций
|
|
||||||
- Всегда обрабатывайте ошибки в блоках `catch`
|
|
||||||
- Используйте деструктуризацию объектов
|
|
||||||
- Кешируйте DOM-элементы в переменные
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Работа с базой данных и мультитенантностью
|
|
||||||
|
|
||||||
**Мультитенантность:**
|
|
||||||
- Приложение поддерживает множество клиентов (университетов). Каждый клиент имеет свою изолированную базу данных PostgreSQL.
|
|
||||||
- Маршрутизация к нужной БД происходит динамически на основе поддомена (`TenantInterceptor` -> `TenantContext` -> `TenantRoutingDataSource`).
|
|
||||||
- Список клиентов хранится в Kubernetes `ConfigMap` (`tenants-config`), который монтируется в под бэкенда как `/config/tenants.json`.
|
|
||||||
- Локально список берётся из файла `backend/tenants.json`.
|
|
||||||
- При добавлении нового клиента в интерфейсе `DatabaseController` через K8s API обновляет `ConfigMap`. Все реплики бэкенда заметят изменения и в фоне инициализируют новый пул соединений (`TenantConfigWatcher`).
|
|
||||||
|
|
||||||
**Миграции схемы (Flyway):**
|
|
||||||
- Автогенерация Hibernate ОТКЛЮЧЕНА (`ddl-auto=none`). Структура баз данных управляется строго через **Flyway**.
|
|
||||||
- Все изменения схемы БД вносятся путем создания новых файлов в `backend/src/main/resources/db/migration/` (название строго `V2__add_new_table.sql` и т.д.).
|
|
||||||
- **ЗАПРЕЩЕНО** изменять существующие файлы миграций (например, `V1__init.sql`), которые уже закоммичены. Это сломает контрольные суммы Flyway.
|
|
||||||
- Flyway запускается программно при первом обращении к базе тенанта. Чтобы запустить Flyway для уже существующих тенантов (накатить V2), необходимо перезапустить бэкенд: `kubectl rollout restart deployment backend -n magistr`.
|
|
||||||
- Для локального сброса базы до изначального состояния: `docker compose down -v && docker compose up -d`.
|
|
||||||
|
|
||||||
**Сущности и связи:**
|
|
||||||
- Foreign keys с `ON DELETE CASCADE` для поддержания целостности
|
|
||||||
- Используйте расширение `pgcrypto` для хеширования паролей (bcrypt)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Функциональные требования к системе (Бизнес-логика)
|
|
||||||
|
|
||||||
### 1. Ролевая модель
|
|
||||||
- **Администратор (Деканат)**: Полный доступ, настройка топологии университета, управление аудиторным фондом, подтверждение переносов, регистрация инцидентов.
|
|
||||||
- **Преподаватель**: Просмотр своего расписания, подача заявок на перенос, отметка о своём отсутствии.
|
|
||||||
- **Студент**: Только просмотр расписания (Read-only).
|
|
||||||
|
|
||||||
### 2. Управление ресурсами и топология
|
|
||||||
- **Управление аудиториями**:
|
|
||||||
- Указание вместимости.
|
|
||||||
- Привязка доступного оборудования (через сущность Equipments: Проектор, ПК, Лаборатория).
|
|
||||||
- Установка статуса "Не доступно" (блокирует назначение пар в этот период).
|
|
||||||
- **Управление группами**:
|
|
||||||
- Управление списком студентов (и возможность деления на подгруппы).
|
|
||||||
- **Управление дисциплинами**:
|
|
||||||
- Создание предметов и привязка их к преподавателям (какие дисциплины имеет право вести конкретный преподаватель).
|
|
||||||
|
|
||||||
### 3. Логика расписания
|
|
||||||
- **Сетка**: 7 фиксированных слотов по 1.5 часа (08:00 - 09:30, и т.д.) + поддержка кастомного времени.
|
|
||||||
- **Проверка конфликтов**:
|
|
||||||
- *Критический конфликт*: Преподаватель не может находиться в двух разных аудиториях одновременно.
|
|
||||||
- *Уточнение по преподавателям*: Преподаватель может иметь несколько пар одновременно (для разных групп), только если они проходят в одной и той же аудитории (потоковая лекция).
|
|
||||||
- **Потоковые занятия**:
|
|
||||||
- Возможность назначить одну лекцию сразу нескольким группам (технически — несколько записей в БД или одна запись со списком групп).
|
|
||||||
- Проверка вместимости: вместимость аудитории должна покрывать суммарную численность всех групп, находящихся в этой аудитории в данный слот.
|
|
||||||
|
|
||||||
### 4. Управление инцидентами (Инклюзия отсутствия)
|
|
||||||
- **Отсутствие (Sickness/Business Trip)**: Регистрация отсутствия преподавателя (с указанием причины и периода дат).
|
|
||||||
- **Обнаружение коллизий**: Автоматическая подсветка конфликтующих пар в расписании (Red Zone).
|
|
||||||
- **Система разрешения конфликтов (Resolution Wizard)**:
|
|
||||||
- Предложение подходящей замены преподавателя на этот слот.
|
|
||||||
- Предложение переноса занятия на другое время или в другую аудиторию.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Языковые требования
|
|
||||||
|
|
||||||
|
### Языковые требования
|
||||||
- **Все ответы и комментарии на русском языке**
|
- **Все ответы и комментарии на русском языке**
|
||||||
- Сообщения об ошибках и логи на русском
|
- Сообщения об ошибках и логи на русском
|
||||||
- Пользовательский интерфейс на русском
|
- Пользовательский интерфейс на русском
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Существующие правила проекта
|
## Подробная документация
|
||||||
|
|
||||||
См. `.agent/rules/main.md` и `.agent/rules/database_schema.md` для полного контекста о функциональных требованиях и схеме БД.
|
Полная документация проекта находится в папке `docs/`:
|
||||||
|
|
||||||
|
| Документ | Содержание |
|
||||||
|
|----------|-----------|
|
||||||
|
| [`docs/README.md`](docs/README.md) | Обзор проекта, стек технологий, быстрый старт |
|
||||||
|
| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | Архитектура системы, мультитенантность, аутентификация |
|
||||||
|
| [`docs/BUSINESS_LOGIC.md`](docs/BUSINESS_LOGIC.md) | Бизнес-логика, ролевая модель, правила расписания |
|
||||||
|
| [`docs/DATABASE.md`](docs/DATABASE.md) | Схема БД (ER-диаграмма), описание всех таблиц, Flyway |
|
||||||
|
| [`docs/API.md`](docs/API.md) | REST API эндпоинты с примерами запросов и ответов |
|
||||||
|
| [`docs/INFRASTRUCTURE.md`](docs/INFRASTRUCTURE.md) | Docker, Kubernetes, CI/CD, мониторинг (SigNoz) |
|
||||||
|
| [`docs/DEVELOPMENT.md`](docs/DEVELOPMENT.md) | Code Style, соглашения, пошаговое создание нового эндпоинта |
|
||||||
|
| [`docs/FRONTEND.md`](docs/FRONTEND.md) | Frontend архитектура, SPA-маршрутизация, CSS, адаптивность |
|
||||||
|
|||||||
420
docs/API.md
Normal file
420
docs/API.md
Normal file
@@ -0,0 +1,420 @@
|
|||||||
|
# 🔌 REST API
|
||||||
|
|
||||||
|
Все эндпоинты имеют префикс `/api/`. Ответы возвращаются в формате JSON.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Аутентификация
|
||||||
|
|
||||||
|
### `POST /api/auth/login`
|
||||||
|
|
||||||
|
Вход в систему.
|
||||||
|
|
||||||
|
**Тело запроса:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "admin",
|
||||||
|
"password": "admin"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Успешный ответ (200):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "OK",
|
||||||
|
"token": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"role": "ADMIN",
|
||||||
|
"redirect": "/admin/"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ошибка (401):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "Неверное имя пользователя или пароль",
|
||||||
|
"token": null,
|
||||||
|
"role": null,
|
||||||
|
"redirect": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> После получения токена клиент должен передавать его в заголовке: `Authorization: Bearer <token>`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Пользователи
|
||||||
|
|
||||||
|
### `GET /api/users`
|
||||||
|
|
||||||
|
Список всех пользователей.
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{ "id": 1, "username": "admin", "role": "ADMIN" },
|
||||||
|
{ "id": 2, "username": "Тестовый преподаватель", "role": "TEACHER" }
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `GET /api/users/teachers`
|
||||||
|
|
||||||
|
Список только преподавателей (роль `TEACHER`).
|
||||||
|
|
||||||
|
### `POST /api/users`
|
||||||
|
|
||||||
|
Создание пользователя.
|
||||||
|
|
||||||
|
**Тело запроса:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "Новый преподаватель",
|
||||||
|
"password": "password123",
|
||||||
|
"role": "TEACHER"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Валидация:**
|
||||||
|
- `username` — обязателен
|
||||||
|
- `password` — минимум 4 символа
|
||||||
|
- `role` — `ADMIN`, `TEACHER` или `STUDENT`
|
||||||
|
|
||||||
|
### `DELETE /api/users/{id}`
|
||||||
|
|
||||||
|
Удаление пользователя.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Расписание (Lessons)
|
||||||
|
|
||||||
|
### `GET /api/users/lessons`
|
||||||
|
|
||||||
|
Список всех занятий с разрешёнными именами (преподаватель, группа, дисциплина, аудитория).
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"teacherName": "Тестовый преподаватель",
|
||||||
|
"groupName": "ИВТ-21-1",
|
||||||
|
"classroomName": "101 Ленинская",
|
||||||
|
"educationFormName": "Бакалавриат",
|
||||||
|
"subjectName": "Высшая математика",
|
||||||
|
"typeLesson": "Лекция",
|
||||||
|
"lessonFormat": "Очно",
|
||||||
|
"day": "Понедельник",
|
||||||
|
"week": "Верхняя",
|
||||||
|
"time": "11:40 - 13:10"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `GET /api/users/lessons/{teacherId}`
|
||||||
|
|
||||||
|
Занятия конкретного преподавателя.
|
||||||
|
|
||||||
|
### `POST /api/users/lessons/create`
|
||||||
|
|
||||||
|
Создание занятия.
|
||||||
|
|
||||||
|
**Тело запроса:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"teacherId": 2,
|
||||||
|
"groupId": 1,
|
||||||
|
"subjectId": 1,
|
||||||
|
"lessonFormat": "Очно",
|
||||||
|
"typeLesson": "Лекция",
|
||||||
|
"classroomId": 1,
|
||||||
|
"day": "Понедельник",
|
||||||
|
"week": "Верхняя",
|
||||||
|
"time": "11:40 - 13:10"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Валидация:**
|
||||||
|
| Поле | Правило |
|
||||||
|
|------|---------|
|
||||||
|
| `teacherId` | Обязателен, ≠ 0 |
|
||||||
|
| `groupId` | Обязателен, ≠ 0 |
|
||||||
|
| `subjectId` | Обязателен, ≠ 0 |
|
||||||
|
| `lessonFormat` | `Очно` или `Онлайн` |
|
||||||
|
| `typeLesson` | `Лекция`, `Практическая работа`, `Лабораторная работа` |
|
||||||
|
| `classroomId` | Обязателен, ≠ 0 |
|
||||||
|
| `day` | Пн–Сб (на русском) |
|
||||||
|
| `week` | `Верхняя`, `Нижняя`, `Обе` |
|
||||||
|
| `time` | Обязателен |
|
||||||
|
|
||||||
|
### `PUT /api/users/lessons/update/{lessonId}`
|
||||||
|
|
||||||
|
Обновление занятия. Поддерживает partial update — передаются только изменённые поля.
|
||||||
|
|
||||||
|
### `DELETE /api/users/lessons/delete/{lessonId}`
|
||||||
|
|
||||||
|
Удаление занятия.
|
||||||
|
|
||||||
|
### `GET /api/users/lessons/ping`
|
||||||
|
|
||||||
|
Проверка доступности контроллера. Возвращает строку `pong`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Группы
|
||||||
|
|
||||||
|
### `GET /api/groups`
|
||||||
|
|
||||||
|
Список всех групп.
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "ИВТ-21-1",
|
||||||
|
"groupSize": 25,
|
||||||
|
"educationFormId": 1,
|
||||||
|
"educationFormName": "Бакалавриат"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `POST /api/groups`
|
||||||
|
|
||||||
|
Создание группы.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "ИБ-31м",
|
||||||
|
"groupSize": 20,
|
||||||
|
"educationFormId": 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `DELETE /api/groups/{id}`
|
||||||
|
|
||||||
|
Удаление группы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Аудитории
|
||||||
|
|
||||||
|
### `GET /api/classrooms`
|
||||||
|
|
||||||
|
Список аудиторий с привязанным оборудованием.
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "101 Ленинская",
|
||||||
|
"capacity": 120,
|
||||||
|
"isAvailable": true,
|
||||||
|
"equipments": [
|
||||||
|
{ "id": 1, "name": "Проектор" },
|
||||||
|
{ "id": 4, "name": "Интерактивная доска" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `POST /api/classrooms`
|
||||||
|
|
||||||
|
Создание аудитории.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "404 Лаборатория",
|
||||||
|
"capacity": 30,
|
||||||
|
"isAvailable": true,
|
||||||
|
"equipmentIds": [1, 2, 3]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `PUT /api/classrooms/{id}`
|
||||||
|
|
||||||
|
Обновление аудитории (partial update).
|
||||||
|
|
||||||
|
### `DELETE /api/classrooms/{id}`
|
||||||
|
|
||||||
|
Удаление аудитории.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Дисциплины
|
||||||
|
|
||||||
|
### `GET /api/subjects`
|
||||||
|
|
||||||
|
Список всех дисциплин.
|
||||||
|
|
||||||
|
### `POST /api/subjects`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "name": "Физика" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `DELETE /api/subjects/{id}`
|
||||||
|
|
||||||
|
Удаление дисциплины.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Оборудование
|
||||||
|
|
||||||
|
### `GET /api/equipments`
|
||||||
|
|
||||||
|
Список всего оборудования.
|
||||||
|
|
||||||
|
### `POST /api/equipments`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "name": "3D-принтер" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `DELETE /api/equipments/{id}`
|
||||||
|
|
||||||
|
Удаление оборудования.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Формы обучения
|
||||||
|
|
||||||
|
### `GET /api/education-forms`
|
||||||
|
|
||||||
|
Список форм обучения.
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{ "id": 1, "name": "Бакалавриат" },
|
||||||
|
{ "id": 2, "name": "Магистратура" }
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `POST /api/education-forms`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "name": "Аспирантура" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `DELETE /api/education-forms/{id}`
|
||||||
|
|
||||||
|
Удаление формы обучения. **Невозможно**, если к ней привязаны группы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Привязка «Преподаватель ↔ Дисциплина»
|
||||||
|
|
||||||
|
### `GET /api/teacher-subjects`
|
||||||
|
|
||||||
|
Список всех привязок.
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"userId": 2,
|
||||||
|
"userName": "Тестовый преподаватель",
|
||||||
|
"subjectId": 1,
|
||||||
|
"subjectName": "Высшая математика"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `POST /api/teacher-subjects`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"userId": 2,
|
||||||
|
"subjectId": 3
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `DELETE /api/teacher-subjects`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"userId": 2,
|
||||||
|
"subjectId": 3
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Управление тенантами (Базы данных)
|
||||||
|
|
||||||
|
### `GET /api/database/status`
|
||||||
|
|
||||||
|
Статус текущего подключения (определяется по домену запроса).
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tenant": "default",
|
||||||
|
"connected": true,
|
||||||
|
"configured": true,
|
||||||
|
"name": "Default",
|
||||||
|
"url": "jdbc:postgresql://db:5432/app_db"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `GET /api/database/tenants`
|
||||||
|
|
||||||
|
Список всех тенантов.
|
||||||
|
|
||||||
|
### `POST /api/database/tenants`
|
||||||
|
|
||||||
|
Добавление нового тенанта.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "СВФУ",
|
||||||
|
"domain": "swsu",
|
||||||
|
"url": "jdbc:postgresql://db-host:5432/swsu_db",
|
||||||
|
"username": "dbuser",
|
||||||
|
"password": "dbpass"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Логика:**
|
||||||
|
1. Создаёт HikariCP пул для нового тенанта
|
||||||
|
2. Запускает Flyway миграции на его БД
|
||||||
|
3. Обновляет Kubernetes ConfigMap
|
||||||
|
|
||||||
|
### `DELETE /api/database/tenants/{domain}`
|
||||||
|
|
||||||
|
Удаление тенанта.
|
||||||
|
|
||||||
|
### `POST /api/database/test`
|
||||||
|
|
||||||
|
Тест подключения к произвольной БД (без регистрации тенанта).
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"url": "jdbc:postgresql://host:5432/testdb",
|
||||||
|
"username": "user",
|
||||||
|
"password": "pass"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "Подключение успешно!"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Коды ответов
|
||||||
|
|
||||||
|
| Код | Описание |
|
||||||
|
|-----|----------|
|
||||||
|
| `200` | Успех |
|
||||||
|
| `400` | Ошибка валидации (с `message` в теле) |
|
||||||
|
| `401` | Неверные учётные данные |
|
||||||
|
| `404` | Ресурс / тенант не найден |
|
||||||
|
| `500` | Внутренняя ошибка сервера |
|
||||||
142
docs/ARCHITECTURE.md
Normal file
142
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
# 🏗 Архитектура системы
|
||||||
|
|
||||||
|
## Общая схема
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
Client["🌐 Браузер"] -->|HTTPS| Caddy["Caddy Proxy"]
|
||||||
|
Caddy -->|:80| Frontend["Frontend<br/>(Apache httpd:alpine)"]
|
||||||
|
Caddy -->|/api/*| Backend["Backend<br/>(Spring Boot 3.2.5)"]
|
||||||
|
|
||||||
|
Backend --> TenantRouter{"TenantRoutingDataSource"}
|
||||||
|
TenantRouter -->|swsu.zuev.company| DB1["PostgreSQL<br/>swsu_db"]
|
||||||
|
TenantRouter -->|mgu.zuev.company| DB2["PostgreSQL<br/>mgu_db"]
|
||||||
|
TenantRouter -->|...| DBn["PostgreSQL<br/>tenant_n_db"]
|
||||||
|
|
||||||
|
Backend -->|Метрики, Логи, Трейсы| OTel["OpenTelemetry Collector"]
|
||||||
|
OTel --> SigNoz["SigNoz"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Компоненты
|
||||||
|
|
||||||
|
### Frontend (Apache httpd:alpine)
|
||||||
|
- **Тип:** Статические файлы (HTML/CSS/JS)
|
||||||
|
- **Контейнер:** `httpd:alpine` — лёгкий Apache HTTP Server
|
||||||
|
- **Порт:** 80
|
||||||
|
- **Содержание:** Три изолированных интерфейса — `admin/`, `teacher/`, `student/`
|
||||||
|
- **JS-модули:** Vanilla JavaScript с ES6 Modules (`import`/`export`)
|
||||||
|
|
||||||
|
### Backend (Spring Boot 3.2.5)
|
||||||
|
- **Тип:** REST API сервер
|
||||||
|
- **Язык:** Java 17
|
||||||
|
- **Порт:** 8080 (внутренний)
|
||||||
|
- **ORM:** Hibernate (JPA), `ddl-auto=none`
|
||||||
|
- **Миграции:** Flyway (программный запуск при подключении тенанта)
|
||||||
|
- **Аутентификация:** bcrypt (через `BCryptPasswordEncoder`), UUID-токены
|
||||||
|
|
||||||
|
### PostgreSQL
|
||||||
|
- **Версия:** `postgres:alpine3.23`
|
||||||
|
- **Локально:** Одна БД `app_db` (тенант `default`)
|
||||||
|
- **Продакшн:** Множество БД, по одной на каждый университет (тенант)
|
||||||
|
|
||||||
|
### Caddy (реверс-прокси)
|
||||||
|
- **Расположение:** `../caddy-proxy/`
|
||||||
|
- **Назначение:** TLS-терминация, маршрутизация запросов к backend/frontend
|
||||||
|
- **Домен:** `*.zuev.company`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Мультитенантная архитектура
|
||||||
|
|
||||||
|
Ключевая особенность системы — изоляция данных каждого университета в отдельной БД PostgreSQL.
|
||||||
|
|
||||||
|
### Принцип работы
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant Browser as Браузер
|
||||||
|
participant Interceptor as TenantInterceptor
|
||||||
|
participant Context as TenantContext
|
||||||
|
participant Router as TenantRoutingDataSource
|
||||||
|
participant DB as PostgreSQL
|
||||||
|
|
||||||
|
Browser->>Interceptor: GET /api/users<br/>Host: swsu.zuev.company
|
||||||
|
Interceptor->>Interceptor: resolveTenant("swsu.zuev.company") → "swsu"
|
||||||
|
Interceptor->>Context: setCurrentTenant("swsu")
|
||||||
|
Note over Interceptor: Проверка: hasTenant("swsu")?
|
||||||
|
Interceptor-->>Browser: 404 если тенант не найден
|
||||||
|
|
||||||
|
Note over Context,Router: Обработка запроса контроллером
|
||||||
|
Router->>Router: determineCurrentLookupKey() → "swsu"
|
||||||
|
Router->>DB: SQL запрос к swsu_db
|
||||||
|
DB-->>Browser: Ответ с данными
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ключевые классы
|
||||||
|
|
||||||
|
| Класс | Назначение |
|
||||||
|
|-------|-----------|
|
||||||
|
| `TenantInterceptor` | Извлекает поддомен из заголовка `Host` и определяет тенант |
|
||||||
|
| `TenantContext` | `ThreadLocal`-хранилище имени текущего тенанта |
|
||||||
|
| `TenantRoutingDataSource` | Наследует `AbstractRoutingDataSource`, маршрутизирует запросы к нужной БД |
|
||||||
|
| `TenantDataSourceConfig` | Загружает конфигурацию тенантов из JSON-файла, создаёт HikariCP пулы |
|
||||||
|
| `TenantConfigWatcher` | Периодически (каждые 30 сек) перечитывает `tenants.json`, синхронизирует тенантов |
|
||||||
|
| `ConfigMapUpdater` | Обновляет Kubernetes ConfigMap при добавлении/удалении тенанта через API |
|
||||||
|
| `TenantConfig` | POJO с параметрами тенанта: `name`, `domain`, `url`, `username`, `password` |
|
||||||
|
|
||||||
|
### Определение тенанта
|
||||||
|
|
||||||
|
Логика определения тенанта по заголовку `Host`:
|
||||||
|
|
||||||
|
| Host | Результат |
|
||||||
|
|------|----------|
|
||||||
|
| `swsu.zuev.company` | `swsu` |
|
||||||
|
| `mgu.zuev.company` | `mgu` |
|
||||||
|
| `localhost` | `default` |
|
||||||
|
| `localhost:8080` | `default` |
|
||||||
|
| `192.168.1.1` | `default` |
|
||||||
|
|
||||||
|
### Конфигурация тенантов
|
||||||
|
|
||||||
|
Список тенантов хранится в JSON-файле:
|
||||||
|
- **Локально:** `backend/tenants.json`
|
||||||
|
- **Продакшн:** Kubernetes ConfigMap `tenants-config`, монтируется в `/config/tenants.json`
|
||||||
|
|
||||||
|
Формат:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "ЮЗГУ",
|
||||||
|
"domain": "swsu",
|
||||||
|
"url": "jdbc:postgresql://db-host:5432/swsu_db",
|
||||||
|
"username": "dbuser",
|
||||||
|
"password": "dbpass"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Жизненный цикл тенанта
|
||||||
|
|
||||||
|
1. **Добавление через API:** `POST /api/database/tenants` → создаёт HikariCP пул → запускает Flyway миграции → обновляет ConfigMap
|
||||||
|
2. **Синхронизация подов:** `TenantConfigWatcher` каждые 30 сек проверяет `tenants.json` → добавляет новые / удаляет отсутствующие тенанты
|
||||||
|
3. **Удаление:** `DELETE /api/database/tenants/{domain}` → закрывает пул → обновляет ConfigMap
|
||||||
|
|
||||||
|
### Fallback при отсутствии тенантов
|
||||||
|
|
||||||
|
Если при запуске нет ни одного настроенного тенанта:
|
||||||
|
1. Проверяется наличие `spring.datasource.url` → создаётся тенант `default`
|
||||||
|
2. Если datasource тоже нет → создаётся H2 in-memory заглушка для инициализации Spring JPA
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Аутентификация
|
||||||
|
|
||||||
|
Система использует **простую модель аутентификации** без JWT или Spring Security фильтров:
|
||||||
|
|
||||||
|
1. Клиент отправляет `POST /api/auth/login` с `username` и `password`
|
||||||
|
2. Backend проверяет пароль через `BCryptPasswordEncoder`
|
||||||
|
3. При успехе возвращается:
|
||||||
|
- UUID-токен (для заголовка `Authorization: Bearer`)
|
||||||
|
- Роль пользователя (`ADMIN`, `TEACHER`, `STUDENT`)
|
||||||
|
- Redirect URL (`/admin/`, `/teacher/`, `/student/`)
|
||||||
|
4. Токен хранится в `localStorage` на клиенте
|
||||||
149
docs/BUSINESS_LOGIC.md
Normal file
149
docs/BUSINESS_LOGIC.md
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
# 📋 Бизнес-логика
|
||||||
|
|
||||||
|
## Ролевая модель
|
||||||
|
|
||||||
|
Система поддерживает три роли пользователей:
|
||||||
|
|
||||||
|
| Роль | Enum | Возможности |
|
||||||
|
|------|------|------------|
|
||||||
|
| **Администратор** (Деканат) | `ADMIN` | Полный доступ: CRUD пользователей, групп, аудиторий, дисциплин, расписания. Управление тенантами (БД). |
|
||||||
|
| **Преподаватель** | `TEACHER` | Просмотр своего расписания. В перспективе — подача заявок на перенос. |
|
||||||
|
| **Студент** | `STUDENT` | Только просмотр расписания (Read-only). |
|
||||||
|
|
||||||
|
После авторизации пользователь перенаправляется на свой интерфейс:
|
||||||
|
- `ADMIN` → `/admin/`
|
||||||
|
- `TEACHER` → `/teacher/`
|
||||||
|
- `STUDENT` → `/student/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Управление ресурсами
|
||||||
|
|
||||||
|
### Кафедры (Departments)
|
||||||
|
|
||||||
|
Организационные единицы университета. К кафедре привязываются пользователи, группы и дисциплины.
|
||||||
|
|
||||||
|
- Имеют уникальный числовой `code`
|
||||||
|
- Предзаполнены: «Кафедра ИБ», «Кафедра ВТ», «Кафедра КТ»
|
||||||
|
|
||||||
|
### Специальности (Specialties)
|
||||||
|
|
||||||
|
Учебные направления с кодом по ФГОС.
|
||||||
|
|
||||||
|
- Примеры: «Информационная безопасность» (10.03.01), «Программная инженерия» (09.03.04)
|
||||||
|
|
||||||
|
### Формы обучения (Education Forms)
|
||||||
|
|
||||||
|
Уровни/формы обучения для привязки к группам.
|
||||||
|
|
||||||
|
- Предзаполнены: Бакалавриат, Магистратура, Специалитет
|
||||||
|
- Нельзя удалить форму обучения, если к ней привязаны группы
|
||||||
|
|
||||||
|
### Учебные группы (Student Groups)
|
||||||
|
|
||||||
|
- **Поля:** Название (уникальное), численность, форма обучения, кафедра, курс (1–6)
|
||||||
|
- **Подгруппы:** Возможно деление группы на подгруппы (таблица `subgroups`)
|
||||||
|
|
||||||
|
### Аудитории (Classrooms)
|
||||||
|
|
||||||
|
- **Поля:** Название (уникальное), вместимость (> 0), корпус, этаж, доступность
|
||||||
|
- **Оборудование:** К каждой аудитории привязывается список оборудования (Many-to-Many) с указанием количества
|
||||||
|
- **Статус:** Флаг `is_available` для блокирования назначения пар
|
||||||
|
|
||||||
|
### Оборудование (Equipments)
|
||||||
|
|
||||||
|
Каталог оборудования для привязки к аудиториям.
|
||||||
|
|
||||||
|
- Предзаполнены: Проектор, ПК, Лаборатория, Интерактивная доска, Документ-камера, Аудиосистема
|
||||||
|
- Уникальность по названию
|
||||||
|
|
||||||
|
### Дисциплины (Subjects)
|
||||||
|
|
||||||
|
- **Поля:** Название (уникальное), код, кафедра, описание
|
||||||
|
- Привязка преподавателей через `teacher_subjects` (Many-to-Many)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Логика расписания
|
||||||
|
|
||||||
|
### Сущность «Занятие» (Lesson)
|
||||||
|
|
||||||
|
Каждая запись в расписании содержит:
|
||||||
|
|
||||||
|
| Поле | Описание | Пример |
|
||||||
|
|------|----------|--------|
|
||||||
|
| `teacher_id` | Преподаватель | 2 |
|
||||||
|
| `group_id` | Учебная группа | 1 |
|
||||||
|
| `subject_id` | Дисциплина | 3 |
|
||||||
|
| `lesson_format` | Формат проведения | `Очно`, `Онлайн` |
|
||||||
|
| `type_lesson` | Тип занятия | `Лекция`, `Практическая работа`, `Лабораторная работа` |
|
||||||
|
| `classroom_id` | Аудитория | 1 |
|
||||||
|
| `day` | День недели | `Понедельник` ... `Суббота` |
|
||||||
|
| `week` | Чётность недели | `Верхняя`, `Нижняя`, `Обе` |
|
||||||
|
| `time` | Временной слот | `8:00 - 9:30` |
|
||||||
|
|
||||||
|
### Временны́е слоты
|
||||||
|
|
||||||
|
Система использует 7 фиксированных слотов по 90 минут:
|
||||||
|
|
||||||
|
| № | Время |
|
||||||
|
|---|-------|
|
||||||
|
| 1 | 08:00 – 09:30 |
|
||||||
|
| 2 | 09:40 – 11:10 |
|
||||||
|
| 3 | 11:40 – 13:10 |
|
||||||
|
| 4 | 13:30 – 15:00 |
|
||||||
|
| 5 | 15:00 – 16:30 |
|
||||||
|
| 6 | 16:40 – 18:10 |
|
||||||
|
| 7 | 18:30 – 20:00 |
|
||||||
|
|
||||||
|
### Валидация при создании/обновлении
|
||||||
|
|
||||||
|
- **Дни:** только `Понедельник` – `Суббота` (`DayAndWeekValidator`)
|
||||||
|
- **Недели:** только `Верхняя`, `Нижняя`, `Обе`
|
||||||
|
- **Формат:** только `Очно`, `Онлайн` (`TypeAndFormatLessonValidator`)
|
||||||
|
- **Тип:** только `Лекция`, `Практическая работа`, `Лабораторная работа`
|
||||||
|
- Все ID (преподаватель, группа, дисциплина, аудитория) обязательны и не могут быть 0
|
||||||
|
|
||||||
|
### Данные к составлению расписания (Schedule Data)
|
||||||
|
|
||||||
|
Таблица `schedule_data` хранит **плановую нагрузку** для составления расписания:
|
||||||
|
|
||||||
|
| Поле | Описание |
|
||||||
|
|------|----------|
|
||||||
|
| `department_id` | Кафедра |
|
||||||
|
| `semester` | Номер семестра |
|
||||||
|
| `group_id` | Учебная группа |
|
||||||
|
| `subjects_id` | Дисциплина |
|
||||||
|
| `lesson_type_id` | Тип занятия |
|
||||||
|
| `number_of_hours` | Количество часов |
|
||||||
|
| `is_division` | Деление на подгруппы |
|
||||||
|
| `teacher_id` | Преподаватель |
|
||||||
|
| `semester_type` | Тип семестра (Весенний / Осенний) |
|
||||||
|
| `period` | Учебный год (напр. `2024/2025`) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Привязка преподаватель ↔ дисциплина
|
||||||
|
|
||||||
|
Связь Many-to-Many через таблицу `teacher_subjects`:
|
||||||
|
- Указывается, какие дисциплины может вести конкретный преподаватель
|
||||||
|
- Дополнительные поля: `qualification_level`, `experience_years`
|
||||||
|
|
||||||
|
Дополнительная связь через `teacher_lesson_types`:
|
||||||
|
- Определяет, какие **типы занятий** (лекция, практика, лаба) может вести преподаватель по конкретной дисциплине
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Бизнес-правила (планируемые)
|
||||||
|
|
||||||
|
> **Примечание:** Следующие правила описаны в требованиях, но пока не полностью реализованы в коде.
|
||||||
|
|
||||||
|
### Проверка конфликтов
|
||||||
|
- **Критический конфликт:** Преподаватель не может одновременно находиться в двух разных аудиториях
|
||||||
|
- **Исключение:** Преподаватель может вести несколько пар одновременно (потоковая лекция), если все группы в одной аудитории
|
||||||
|
- **Вместимость:** Суммарная численность всех групп в слоте не должна превышать вместимость аудитории
|
||||||
|
|
||||||
|
### Управление инцидентами
|
||||||
|
- Регистрация отсутствия преподавателя (болезнь, командировка) с указанием периода
|
||||||
|
- Автоматическая подсветка конфликтующих пар (Red Zone)
|
||||||
|
- Resolution Wizard: предложение замены преподавателя или переноса занятия
|
||||||
362
docs/DATABASE.md
Normal file
362
docs/DATABASE.md
Normal file
@@ -0,0 +1,362 @@
|
|||||||
|
# 🗄 База данных
|
||||||
|
|
||||||
|
## Общая информация
|
||||||
|
|
||||||
|
- **СУБД:** PostgreSQL (локально `postgres:alpine3.23`, продакшн — managed PostgreSQL)
|
||||||
|
- **Управление схемой:** Flyway (программный запуск)
|
||||||
|
- **Hibernate DDL:** Отключён (`ddl-auto=none`)
|
||||||
|
- **Расширения:** `pgcrypto` (bcrypt-хеширование паролей)
|
||||||
|
- **Мультитенантность:** Каждый тенант = отдельная БД
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ER-диаграмма
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
departments {
|
||||||
|
BIGSERIAL id PK
|
||||||
|
VARCHAR name
|
||||||
|
BIGINT code UK
|
||||||
|
}
|
||||||
|
|
||||||
|
specialties {
|
||||||
|
BIGSERIAL id PK
|
||||||
|
VARCHAR name
|
||||||
|
VARCHAR specialty_code
|
||||||
|
}
|
||||||
|
|
||||||
|
users {
|
||||||
|
BIGSERIAL id PK
|
||||||
|
VARCHAR username UK
|
||||||
|
VARCHAR password
|
||||||
|
VARCHAR role
|
||||||
|
VARCHAR full_name
|
||||||
|
VARCHAR job_title
|
||||||
|
BIGINT department_id FK
|
||||||
|
TIMESTAMP created_at
|
||||||
|
TIMESTAMP updated_at
|
||||||
|
}
|
||||||
|
|
||||||
|
education_forms {
|
||||||
|
BIGSERIAL id PK
|
||||||
|
VARCHAR name UK
|
||||||
|
TEXT description
|
||||||
|
TIMESTAMP created_at
|
||||||
|
}
|
||||||
|
|
||||||
|
student_groups {
|
||||||
|
BIGSERIAL id PK
|
||||||
|
VARCHAR name UK
|
||||||
|
BIGINT group_size
|
||||||
|
BIGINT education_form_id FK
|
||||||
|
BIGINT department_id FK
|
||||||
|
INT course
|
||||||
|
TIMESTAMP created_at
|
||||||
|
}
|
||||||
|
|
||||||
|
subgroups {
|
||||||
|
BIGSERIAL id PK
|
||||||
|
BIGINT group_id FK
|
||||||
|
VARCHAR name
|
||||||
|
INT student_capacity
|
||||||
|
}
|
||||||
|
|
||||||
|
subjects {
|
||||||
|
BIGSERIAL id PK
|
||||||
|
VARCHAR name UK
|
||||||
|
VARCHAR code
|
||||||
|
BIGINT department_id FK
|
||||||
|
TEXT description
|
||||||
|
TIMESTAMP created_at
|
||||||
|
}
|
||||||
|
|
||||||
|
lesson_types {
|
||||||
|
BIGSERIAL id PK
|
||||||
|
VARCHAR name UK
|
||||||
|
VARCHAR color_code
|
||||||
|
INT duration_minutes
|
||||||
|
}
|
||||||
|
|
||||||
|
equipments {
|
||||||
|
BIGSERIAL id PK
|
||||||
|
VARCHAR name UK
|
||||||
|
TEXT description
|
||||||
|
VARCHAR inventory_number
|
||||||
|
}
|
||||||
|
|
||||||
|
classrooms {
|
||||||
|
BIGSERIAL id PK
|
||||||
|
VARCHAR name UK
|
||||||
|
INT capacity
|
||||||
|
VARCHAR building
|
||||||
|
INT floor
|
||||||
|
BOOLEAN is_available
|
||||||
|
TEXT description
|
||||||
|
TIMESTAMP created_at
|
||||||
|
}
|
||||||
|
|
||||||
|
classroom_equipments {
|
||||||
|
BIGINT classroom_id FK,PK
|
||||||
|
BIGINT equipment_id FK,PK
|
||||||
|
INT quantity
|
||||||
|
TEXT notes
|
||||||
|
}
|
||||||
|
|
||||||
|
teacher_subjects {
|
||||||
|
BIGINT user_id FK,PK
|
||||||
|
BIGINT subject_id FK,PK
|
||||||
|
VARCHAR qualification_level
|
||||||
|
INT experience_years
|
||||||
|
}
|
||||||
|
|
||||||
|
teacher_lesson_types {
|
||||||
|
BIGINT user_id FK,PK
|
||||||
|
BIGINT subject_id FK,PK
|
||||||
|
BIGINT lesson_type_id FK,PK
|
||||||
|
}
|
||||||
|
|
||||||
|
lessons {
|
||||||
|
BIGSERIAL id PK
|
||||||
|
BIGINT teacher_id FK
|
||||||
|
BIGINT group_id FK
|
||||||
|
BIGINT subject_id FK
|
||||||
|
VARCHAR lesson_format
|
||||||
|
VARCHAR type_lesson
|
||||||
|
BIGINT classroom_id FK
|
||||||
|
VARCHAR day
|
||||||
|
VARCHAR week
|
||||||
|
VARCHAR time
|
||||||
|
}
|
||||||
|
|
||||||
|
schedule_data {
|
||||||
|
BIGSERIAL id PK
|
||||||
|
BIGINT department_id FK
|
||||||
|
INT semester
|
||||||
|
BIGINT group_id FK
|
||||||
|
BIGINT subjects_id FK
|
||||||
|
BIGINT lesson_type_id FK
|
||||||
|
INT number_of_hours
|
||||||
|
BOOLEAN is_division
|
||||||
|
BIGINT teacher_id FK
|
||||||
|
VARCHAR semester_type
|
||||||
|
VARCHAR period
|
||||||
|
}
|
||||||
|
|
||||||
|
departments ||--o{ users : "department_id"
|
||||||
|
departments ||--o{ student_groups : "department_id"
|
||||||
|
departments ||--o{ subjects : "department_id"
|
||||||
|
departments ||--o{ schedule_data : "department_id"
|
||||||
|
education_forms ||--o{ student_groups : "education_form_id"
|
||||||
|
student_groups ||--o{ subgroups : "group_id"
|
||||||
|
student_groups ||--o{ lessons : "group_id"
|
||||||
|
student_groups ||--o{ schedule_data : "group_id"
|
||||||
|
users ||--o{ lessons : "teacher_id"
|
||||||
|
users ||--o{ teacher_subjects : "user_id"
|
||||||
|
users ||--o{ teacher_lesson_types : "user_id"
|
||||||
|
users ||--o{ schedule_data : "teacher_id"
|
||||||
|
subjects ||--o{ lessons : "subject_id"
|
||||||
|
subjects ||--o{ teacher_subjects : "subject_id"
|
||||||
|
subjects ||--o{ teacher_lesson_types : "subject_id"
|
||||||
|
subjects ||--o{ schedule_data : "subjects_id"
|
||||||
|
lesson_types ||--o{ teacher_lesson_types : "lesson_type_id"
|
||||||
|
lesson_types ||--o{ schedule_data : "lesson_type_id"
|
||||||
|
classrooms ||--o{ lessons : "classroom_id"
|
||||||
|
classrooms ||--o{ classroom_equipments : "classroom_id"
|
||||||
|
equipments ||--o{ classroom_equipments : "equipment_id"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Описание таблиц
|
||||||
|
|
||||||
|
### Справочники высшего уровня
|
||||||
|
|
||||||
|
#### `departments` — Кафедры
|
||||||
|
| Колонка | Тип | Описание |
|
||||||
|
|---------|-----|----------|
|
||||||
|
| `id` | BIGSERIAL PK | ID кафедры |
|
||||||
|
| `name` | VARCHAR(255) | Название кафедры |
|
||||||
|
| `code` | BIGINT UNIQUE | Код кафедры |
|
||||||
|
|
||||||
|
#### `specialties` — Специальности
|
||||||
|
| Колонка | Тип | Описание |
|
||||||
|
|---------|-----|----------|
|
||||||
|
| `id` | BIGSERIAL PK | ID специальности |
|
||||||
|
| `name` | VARCHAR(255) | Название специальности |
|
||||||
|
| `specialty_code` | VARCHAR(255) | Код ФГОС (напр. `10.03.01`) |
|
||||||
|
|
||||||
|
### Пользователи
|
||||||
|
|
||||||
|
#### `users` — Пользователи системы
|
||||||
|
| Колонка | Тип | Описание |
|
||||||
|
|---------|-----|----------|
|
||||||
|
| `id` | BIGSERIAL PK | ID пользователя |
|
||||||
|
| `username` | VARCHAR(50) UNIQUE | Логин |
|
||||||
|
| `password` | VARCHAR(255) | bcrypt-хеш пароля |
|
||||||
|
| `role` | VARCHAR(20) | `ADMIN`, `TEACHER`, `STUDENT` |
|
||||||
|
| `full_name` | VARCHAR(255) | ФИО |
|
||||||
|
| `job_title` | VARCHAR(255) | Должность |
|
||||||
|
| `department_id` | BIGINT FK → departments | Кафедра |
|
||||||
|
| `created_at` | TIMESTAMP | Дата создания |
|
||||||
|
| `updated_at` | TIMESTAMP | Дата обновления (авто-триггер) |
|
||||||
|
|
||||||
|
> **Триггер:** `update_users_updated_at` автоматически обновляет `updated_at` при любом `UPDATE`.
|
||||||
|
|
||||||
|
### Учебный процесс
|
||||||
|
|
||||||
|
#### `education_forms` — Формы обучения
|
||||||
|
| Колонка | Тип | Описание |
|
||||||
|
|---------|-----|----------|
|
||||||
|
| `id` | BIGSERIAL PK | ID |
|
||||||
|
| `name` | VARCHAR(100) UNIQUE | Название (Бакалавриат, Магистратура, Специалитет) |
|
||||||
|
| `description` | TEXT | Описание |
|
||||||
|
|
||||||
|
#### `student_groups` — Учебные группы
|
||||||
|
| Колонка | Тип | Описание |
|
||||||
|
|---------|-----|----------|
|
||||||
|
| `id` | BIGSERIAL PK | ID |
|
||||||
|
| `name` | VARCHAR(100) UNIQUE | Название группы (напр. `ИВТ-21-1`) |
|
||||||
|
| `group_size` | BIGINT | Количество студентов |
|
||||||
|
| `education_form_id` | BIGINT FK → education_forms | Форма обучения |
|
||||||
|
| `department_id` | BIGINT FK → departments | Кафедра |
|
||||||
|
| `course` | INT CHECK(1–6) | Курс |
|
||||||
|
|
||||||
|
#### `subgroups` — Подгруппы
|
||||||
|
| Колонка | Тип | Описание |
|
||||||
|
|---------|-----|----------|
|
||||||
|
| `id` | BIGSERIAL PK | ID |
|
||||||
|
| `group_id` | BIGINT FK → student_groups (CASCADE) | Родительская группа |
|
||||||
|
| `name` | VARCHAR(100) | Название подгруппы |
|
||||||
|
| `student_capacity` | INT | Количество студентов |
|
||||||
|
|
||||||
|
#### `subjects` — Дисциплины
|
||||||
|
| Колонка | Тип | Описание |
|
||||||
|
|---------|-----|----------|
|
||||||
|
| `id` | BIGSERIAL PK | ID |
|
||||||
|
| `name` | VARCHAR(200) UNIQUE | Название |
|
||||||
|
| `code` | VARCHAR(20) | Код предмета |
|
||||||
|
| `department_id` | BIGINT FK → departments | Кафедра |
|
||||||
|
| `description` | TEXT | Описание |
|
||||||
|
|
||||||
|
### Аудиторный фонд
|
||||||
|
|
||||||
|
#### `classrooms` — Аудитории
|
||||||
|
| Колонка | Тип | Описание |
|
||||||
|
|---------|-----|----------|
|
||||||
|
| `id` | BIGSERIAL PK | ID |
|
||||||
|
| `name` | VARCHAR(50) UNIQUE | Название (напр. `101 Ленинская`) |
|
||||||
|
| `capacity` | INT CHECK(> 0) | Вместимость |
|
||||||
|
| `building` | VARCHAR(50) | Корпус |
|
||||||
|
| `floor` | INT | Этаж |
|
||||||
|
| `is_available` | BOOLEAN | Доступна для назначения пар |
|
||||||
|
| `description` | TEXT | Описание |
|
||||||
|
|
||||||
|
#### `equipments` — Оборудование
|
||||||
|
| Колонка | Тип | Описание |
|
||||||
|
|---------|-----|----------|
|
||||||
|
| `id` | BIGSERIAL PK | ID |
|
||||||
|
| `name` | VARCHAR(50) UNIQUE | Название |
|
||||||
|
| `description` | TEXT | Описание |
|
||||||
|
| `inventory_number` | VARCHAR(50) | Инвентарный номер |
|
||||||
|
|
||||||
|
#### `classroom_equipments` — Привязка оборудования к аудиториям
|
||||||
|
| Колонка | Тип | Описание |
|
||||||
|
|---------|-----|----------|
|
||||||
|
| `classroom_id` | BIGINT PK, FK → classrooms (CASCADE) | Аудитория |
|
||||||
|
| `equipment_id` | BIGINT PK, FK → equipments (CASCADE) | Оборудование |
|
||||||
|
| `quantity` | INT CHECK(> 0) | Количество единиц |
|
||||||
|
| `notes` | TEXT | Примечания |
|
||||||
|
|
||||||
|
### Расписание
|
||||||
|
|
||||||
|
#### `lessons` — Основное расписание занятий
|
||||||
|
| Колонка | Тип | Описание |
|
||||||
|
|---------|-----|----------|
|
||||||
|
| `id` | BIGSERIAL PK | ID |
|
||||||
|
| `teacher_id` | BIGINT FK → users | Преподаватель |
|
||||||
|
| `group_id` | BIGINT FK → student_groups | Группа |
|
||||||
|
| `subject_id` | BIGINT FK → subjects | Дисциплина |
|
||||||
|
| `lesson_format` | VARCHAR(255) | `Очно` / `Онлайн` |
|
||||||
|
| `type_lesson` | VARCHAR(255) | `Лекция` / `Практическая работа` / `Лабораторная работа` |
|
||||||
|
| `classroom_id` | BIGINT FK → classrooms | Аудитория |
|
||||||
|
| `day` | VARCHAR(255) | День недели |
|
||||||
|
| `week` | VARCHAR(255) | `Верхняя` / `Нижняя` / `Обе` |
|
||||||
|
| `time` | VARCHAR(255) | Временной слот |
|
||||||
|
|
||||||
|
#### `lesson_types` — Типы занятий (справочник)
|
||||||
|
| Колонка | Тип | Описание |
|
||||||
|
|---------|-----|----------|
|
||||||
|
| `id` | BIGSERIAL PK | ID |
|
||||||
|
| `name` | VARCHAR(50) UNIQUE | Название типа |
|
||||||
|
| `color_code` | VARCHAR(7) | HEX-цвет для UI (напр. `#FF6B6B`) |
|
||||||
|
| `duration_minutes` | INT | Длительность (по умолчанию 90) |
|
||||||
|
|
||||||
|
### Связи «Преподаватель ↔ Дисциплина»
|
||||||
|
|
||||||
|
#### `teacher_subjects` — Квалификация преподавателей
|
||||||
|
| Колонка | Тип | Описание |
|
||||||
|
|---------|-----|----------|
|
||||||
|
| `user_id` | BIGINT PK, FK → users (CASCADE) | Преподаватель |
|
||||||
|
| `subject_id` | BIGINT PK, FK → subjects (CASCADE) | Дисциплина |
|
||||||
|
| `qualification_level` | VARCHAR(50) | Уровень квалификации |
|
||||||
|
| `experience_years` | INT | Стаж |
|
||||||
|
|
||||||
|
#### `teacher_lesson_types` — Типы занятий преподавателя
|
||||||
|
| Колонка | Тип | Описание |
|
||||||
|
|---------|-----|----------|
|
||||||
|
| `user_id` | BIGINT PK, FK → users (CASCADE) | Преподаватель |
|
||||||
|
| `subject_id` | BIGINT PK, FK → subjects (CASCADE) | Дисциплина |
|
||||||
|
| `lesson_type_id` | BIGINT PK, FK → lesson_types (CASCADE) | Тип занятия |
|
||||||
|
|
||||||
|
#### `schedule_data` — Данные к составлению расписания
|
||||||
|
| Колонка | Тип | Описание |
|
||||||
|
|---------|-----|----------|
|
||||||
|
| `id` | BIGSERIAL PK | ID |
|
||||||
|
| `department_id` | BIGINT FK → departments | Кафедра |
|
||||||
|
| `semester` | INT | Номер семестра |
|
||||||
|
| `group_id` | BIGINT FK → student_groups | Группа |
|
||||||
|
| `subjects_id` | BIGINT FK → subjects | Дисциплина |
|
||||||
|
| `lesson_type_id` | BIGINT FK → lesson_types | Тип занятия |
|
||||||
|
| `number_of_hours` | INT | Количество часов |
|
||||||
|
| `is_division` | BOOLEAN | Деление на подгруппы |
|
||||||
|
| `teacher_id` | BIGINT FK → users | Преподаватель |
|
||||||
|
| `semester_type` | VARCHAR(255) | Весенний / Осенний |
|
||||||
|
| `period` | VARCHAR(255) | Учебный год |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Flyway миграции
|
||||||
|
|
||||||
|
### Правила работы
|
||||||
|
|
||||||
|
1. Все миграции находятся в `backend/src/main/resources/db/migration/`
|
||||||
|
2. Формат имени: `V{номер}__{описание}.sql` (напр. `V1__init.sql`, `V2__add_departments.sql`)
|
||||||
|
3. **ЗАПРЕЩЕНО** изменять уже закоммиченные файлы миграций — это сломает контрольные суммы Flyway
|
||||||
|
4. Flyway запускается **программно** при первом обращении к БД тенанта (`TenantConfigWatcher.initDatabaseForTenant()`)
|
||||||
|
5. Настройка `baselineOnMigrate=true` — если в БД уже есть данные, Flyway начнёт с baseline
|
||||||
|
|
||||||
|
### Текущие миграции
|
||||||
|
|
||||||
|
| Файл | Описание |
|
||||||
|
|------|----------|
|
||||||
|
| `V1__init.sql` | Инициализация: все таблицы, тестовые данные, триггеры, комментарии |
|
||||||
|
|
||||||
|
### Накатывание на существующих тенантов
|
||||||
|
|
||||||
|
Для применения новой миграции к уже существующим тенантам необходимо перезапустить backend:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Kubernetes
|
||||||
|
kubectl rollout restart deployment backend -n magistr
|
||||||
|
|
||||||
|
# Docker Compose (локально)
|
||||||
|
docker compose restart backend
|
||||||
|
```
|
||||||
|
|
||||||
|
### Полный сброс БД (локально)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose down -v # Удаляет volumes (данные)
|
||||||
|
docker compose up -d # Пересоздаёт БД с нуля
|
||||||
|
```
|
||||||
275
docs/DEVELOPMENT.md
Normal file
275
docs/DEVELOPMENT.md
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
# 🛠 Руководство для разработчиков
|
||||||
|
|
||||||
|
## Локальный запуск
|
||||||
|
|
||||||
|
### Предварительные требования
|
||||||
|
|
||||||
|
- Docker и Docker Compose
|
||||||
|
- Git
|
||||||
|
- (Опционально) Java 17 + Maven 3.9+ для запуска backend вне Docker
|
||||||
|
|
||||||
|
### Первый запуск
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Создать Docker-сеть
|
||||||
|
docker network create proxy
|
||||||
|
|
||||||
|
# Собрать и запустить
|
||||||
|
docker compose up -d --build
|
||||||
|
|
||||||
|
# Убедиться, что всё работает
|
||||||
|
docker compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
Приложение доступно: **http://localhost:80**
|
||||||
|
|
||||||
|
### Пересборка после изменений
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Пересобрать только backend
|
||||||
|
docker compose up -d --build backend
|
||||||
|
|
||||||
|
# Пересобрать только frontend
|
||||||
|
docker compose up -d --build frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
### Полный сброс данных
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose down -v # Удаляет БД
|
||||||
|
docker compose up -d # Пересоздаёт с нуля
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Соглашения о коде
|
||||||
|
|
||||||
|
### Java (Backend)
|
||||||
|
|
||||||
|
#### Именование
|
||||||
|
|
||||||
|
| Категория | Стиль | Пример |
|
||||||
|
|-----------|-------|--------|
|
||||||
|
| Классы | PascalCase | `LessonsController`, `LessonResponse` |
|
||||||
|
| Методы и переменные | camelCase | `getAllLessons()`, `teacherId` |
|
||||||
|
| Константы | UPPER_SNAKE_CASE | `ROLE_REDIRECTS` |
|
||||||
|
| Пакеты | lowercase | `com.magistr.app.controller` |
|
||||||
|
|
||||||
|
#### Архитектурные правила
|
||||||
|
|
||||||
|
- **Constructor Injection** — все зависимости через конструктор (не `@Autowired` на поля)
|
||||||
|
- **Controller → Repository** — контроллеры работают напрямую с репозиториями (без слоя service)
|
||||||
|
- **Префикс `/api/`** — все REST-эндпоинты
|
||||||
|
- **`ResponseEntity<?>`** — все мутирующие методы возвращают `ResponseEntity` с HTTP-статусом
|
||||||
|
- **Сообщения на русском** — все ошибки и уведомления на русском языке
|
||||||
|
|
||||||
|
#### Логирование
|
||||||
|
|
||||||
|
Используйте SLF4J:
|
||||||
|
|
||||||
|
```java
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(MyController.class);
|
||||||
|
|
||||||
|
// Информационные сообщения
|
||||||
|
logger.info("Запрос на получение всех занятий");
|
||||||
|
|
||||||
|
// Ошибки с полным стектрейсом
|
||||||
|
logger.error("Ошибка при сохранении: {}", e.getMessage(), e);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Валидация
|
||||||
|
|
||||||
|
- Для сложных правил — отдельные классы-валидаторы (`DayAndWeekValidator`, `TypeAndFormatLessonValidator`)
|
||||||
|
- Для простых — inline-проверки в контроллере с `ResponseEntity.badRequest()`
|
||||||
|
|
||||||
|
#### Импорты
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 1. Static imports
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
// 2. Java/Jakarta
|
||||||
|
import java.util.*;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
|
||||||
|
// 3. External libraries
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
// 4. Internal packages (wildcard для того же модуля)
|
||||||
|
import com.magistr.app.model.*;
|
||||||
|
import com.magistr.app.repository.*;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Форматирование
|
||||||
|
|
||||||
|
- **Отступы:** 4 пробела
|
||||||
|
- **Скобки:** K&R style (открывающая на той же строке)
|
||||||
|
- **Длина строки:** до 120 символов
|
||||||
|
- **Фигурные скобки** обязательны для `if`/`for`/`while`
|
||||||
|
|
||||||
|
### JavaScript (Frontend)
|
||||||
|
|
||||||
|
#### Именование
|
||||||
|
|
||||||
|
| Категория | Стиль | Пример |
|
||||||
|
|-----------|-------|--------|
|
||||||
|
| Файлы | kebab-case | `main.js`, `schedule-view.js` |
|
||||||
|
| Функции и переменные | camelCase | `loadUsers()`, `pageTitle` |
|
||||||
|
| Константы | UPPER_SNAKE_CASE | `API_BASE_URL` |
|
||||||
|
|
||||||
|
#### Модули
|
||||||
|
|
||||||
|
- ES6 Modules с `import`/`export`
|
||||||
|
- **Всегда указывать расширение:** `import { api } from './api.js';`
|
||||||
|
|
||||||
|
#### Лучшие практики
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ✅ Предпочитайте const
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
|
||||||
|
// ✅ Async/await вместо .then()
|
||||||
|
async function loadData() {
|
||||||
|
try {
|
||||||
|
const data = await api.get('/api/users');
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Ошибка:', e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Template literals
|
||||||
|
const msg = `Найдено ${items.length} записей`;
|
||||||
|
|
||||||
|
// ✅ Деструктуризация
|
||||||
|
const { id, name, role } = user;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Форматирование
|
||||||
|
|
||||||
|
- **Отступы:** 4 пробела
|
||||||
|
- **Кавычки:** одинарные `'`
|
||||||
|
- **Точки с запятой:** обязательны
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Создание нового эндпоинта (пошагово)
|
||||||
|
|
||||||
|
### 1. Модель (если нужна новая таблица)
|
||||||
|
|
||||||
|
Создайте Flyway миграцию `V{N}__{description}.sql`:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- backend/src/main/resources/db/migration/V3__add_absences.sql
|
||||||
|
CREATE TABLE IF NOT EXISTS absences (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
teacher_id BIGINT NOT NULL REFERENCES users(id),
|
||||||
|
reason VARCHAR(255) NOT NULL,
|
||||||
|
start_date DATE NOT NULL,
|
||||||
|
end_date DATE NOT NULL
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Создайте JPA-сущность:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Entity
|
||||||
|
@Table(name = "absences")
|
||||||
|
public class Absence {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Репозиторий
|
||||||
|
|
||||||
|
```java
|
||||||
|
public interface AbsenceRepository extends JpaRepository<Absence, Long> {
|
||||||
|
List<Absence> findByTeacherId(Long teacherId);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. DTO (опционально)
|
||||||
|
|
||||||
|
```java
|
||||||
|
public record AbsenceResponse(Long id, String teacherName, String reason) {}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Контроллер
|
||||||
|
|
||||||
|
```java
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/absences")
|
||||||
|
public class AbsenceController {
|
||||||
|
private final AbsenceRepository absenceRepository;
|
||||||
|
|
||||||
|
public AbsenceController(AbsenceRepository absenceRepository) {
|
||||||
|
this.absenceRepository = absenceRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public List<Absence> getAll() {
|
||||||
|
return absenceRepository.findAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Работа с миграциями Flyway
|
||||||
|
|
||||||
|
### Правила
|
||||||
|
|
||||||
|
1. **Никогда** не изменяйте уже закоммиченные файлы миграций
|
||||||
|
2. Имя файла: `V{номер}__{описание}.sql` (два подчёркивания!)
|
||||||
|
3. Нумерация строго инкрементальная: `V1`, `V2`, `V3`, ...
|
||||||
|
4. После добавления — перезапустите backend для применения
|
||||||
|
|
||||||
|
### Применение
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Локально — сброс и повтор всех миграций
|
||||||
|
docker compose down -v && docker compose up -d
|
||||||
|
|
||||||
|
# Продакшн — применить к существующим тенантам
|
||||||
|
kubectl rollout restart deployment backend -n magistr
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Структура пакетов (Backend)
|
||||||
|
|
||||||
|
```
|
||||||
|
com.magistr.app/
|
||||||
|
├── Application.java # Точка входа
|
||||||
|
├── config/
|
||||||
|
│ ├── AppConfig.java # Бины (BCryptPasswordEncoder)
|
||||||
|
│ ├── DataInitializer.java # Инициализация данных
|
||||||
|
│ └── tenant/ # Мультитенантность
|
||||||
|
│ ├── TenantConfig.java # POJO конфигурации тенанта
|
||||||
|
│ ├── TenantContext.java # ThreadLocal текущего тенанта
|
||||||
|
│ ├── TenantInterceptor.java # Определение тенанта из Host
|
||||||
|
│ ├── TenantRoutingDataSource.java # Маршрутизация к БД
|
||||||
|
│ ├── TenantDataSourceConfig.java # Spring-конфигурация
|
||||||
|
│ ├── TenantConfigWatcher.java # Периодическая синхронизация
|
||||||
|
│ └── ConfigMapUpdater.java # Обновление K8s ConfigMap
|
||||||
|
├── controller/ # REST-контроллеры
|
||||||
|
│ ├── AuthController.java
|
||||||
|
│ ├── LessonsController.java
|
||||||
|
│ ├── ClassroomController.java
|
||||||
|
│ ├── DatabaseController.java
|
||||||
|
│ ├── UserController.java
|
||||||
|
│ ├── GroupController.java
|
||||||
|
│ ├── SubjectController.java
|
||||||
|
│ ├── EquipmentController.java
|
||||||
|
│ ├── EducationFormController.java
|
||||||
|
│ └── TeacherSubjectController.java
|
||||||
|
├── dto/ # Data Transfer Objects
|
||||||
|
├── model/ # JPA-сущности
|
||||||
|
├── repository/ # Spring Data JPA
|
||||||
|
└── utils/ # Валидаторы
|
||||||
|
├── DayAndWeekValidator.java
|
||||||
|
└── TypeAndFormatLessonValidator.java
|
||||||
|
```
|
||||||
203
docs/FRONTEND.md
Normal file
203
docs/FRONTEND.md
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
# 🎨 Frontend
|
||||||
|
|
||||||
|
## Общая информация
|
||||||
|
|
||||||
|
| Параметр | Значение |
|
||||||
|
|----------|----------|
|
||||||
|
| **Фреймворк** | Нет (Vanilla JavaScript) |
|
||||||
|
| **Модульная система** | ES6 Modules (`import`/`export`) |
|
||||||
|
| **Стили** | CSS (модульный подход) |
|
||||||
|
| **Шрифт** | [Inter](https://fonts.google.com/specimen/Inter) (Google Fonts) |
|
||||||
|
| **Веб-сервер** | Apache httpd:alpine |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Структура файлов
|
||||||
|
|
||||||
|
```
|
||||||
|
frontend/
|
||||||
|
├── index.html # 🔐 Страница авторизации (общая)
|
||||||
|
├── script.js # Логика авторизации
|
||||||
|
├── style.css # Стили страницы авторизации
|
||||||
|
├── theme-toggle.js # Переключение светлой/тёмной темы
|
||||||
|
├── Dockerfile # httpd:alpine
|
||||||
|
│
|
||||||
|
├── admin/ # 👨💼 Интерфейс администратора
|
||||||
|
│ ├── index.html # SPA-оболочка с sidebar
|
||||||
|
│ ├── css/
|
||||||
|
│ │ ├── main.css # CSS-переменные, цвета, типографика
|
||||||
|
│ │ ├── layout.css # Раскладка (sidebar, topbar, content)
|
||||||
|
│ │ ├── components.css # Кнопки, таблицы, карточки, формы
|
||||||
|
│ │ └── modals.css # Модальные окна
|
||||||
|
│ ├── js/
|
||||||
|
│ │ ├── main.js # Инициализация, маршрутизация, навигация
|
||||||
|
│ │ ├── api.js # HTTP-обёртка (fetch + Authorization)
|
||||||
|
│ │ ├── utils.js # Утилиты
|
||||||
|
│ │ ├── otel.js # OpenTelemetry (клиентская телеметрия)
|
||||||
|
│ │ └── views/ # Модули представлений
|
||||||
|
│ │ ├── users.js # Управление пользователями
|
||||||
|
│ │ ├── groups.js # Управление группами
|
||||||
|
│ │ ├── classrooms.js # Управление аудиториями
|
||||||
|
│ │ ├── subjects.js # Управление дисциплинами
|
||||||
|
│ │ ├── equipments.js # Управление оборудованием
|
||||||
|
│ │ ├── edu-forms.js # Формы обучения
|
||||||
|
│ │ ├── schedule.js # Расписание занятий
|
||||||
|
│ │ └── database.js # Управление тенантами
|
||||||
|
│ └── views/ # HTML-шаблоны представлений
|
||||||
|
│ ├── users.html
|
||||||
|
│ ├── groups.html
|
||||||
|
│ ├── classrooms.html
|
||||||
|
│ ├── subjects.html
|
||||||
|
│ ├── equipments.html
|
||||||
|
│ ├── edu-forms.html
|
||||||
|
│ ├── schedule.html
|
||||||
|
│ └── database.html
|
||||||
|
│
|
||||||
|
├── teacher/ # 👩🏫 Интерфейс преподавателя
|
||||||
|
│ └── index.html # Просмотр расписания
|
||||||
|
│
|
||||||
|
└── student/ # 🎓 Интерфейс студента
|
||||||
|
└── index.html # Просмотр расписания (read-only)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Система маршрутизации (Admin SPA)
|
||||||
|
|
||||||
|
Админ-панель работает как **Single Page Application** без фреймворка.
|
||||||
|
|
||||||
|
Навигация реализована через `data-tab` атрибуты на элементах sidebar:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<a href="#" class="nav-item" data-tab="users">Пользователи</a>
|
||||||
|
<a href="#" class="nav-item" data-tab="groups">Группы</a>
|
||||||
|
<a href="#" class="nav-item" data-tab="schedule">Расписание занятий</a>
|
||||||
|
```
|
||||||
|
|
||||||
|
При клике на пункт меню `main.js`:
|
||||||
|
1. Загружает HTML-шаблон из `views/{tab}.html` через `fetch()`
|
||||||
|
2. Вставляет его в `#app-content`
|
||||||
|
3. Подключает соответствующий JS-модуль из `js/views/{tab}.js`
|
||||||
|
4. Обновляет заголовок страницы (`#page-title`)
|
||||||
|
|
||||||
|
### Разделы админ-панели
|
||||||
|
|
||||||
|
| Tab | Описание | API |
|
||||||
|
|-----|----------|-----|
|
||||||
|
| `users` | CRUD пользователей | `/api/users` |
|
||||||
|
| `groups` | CRUD групп | `/api/groups` |
|
||||||
|
| `edu-forms` | Формы обучения | `/api/education-forms` |
|
||||||
|
| `equipments` | Оборудование | `/api/equipments` |
|
||||||
|
| `classrooms` | Аудитории | `/api/classrooms` |
|
||||||
|
| `subjects` | Дисциплины | `/api/subjects` |
|
||||||
|
| `schedule` | Расписание | `/api/users/lessons` |
|
||||||
|
| `database` | Тенанты | `/api/database` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API-клиент (`api.js`)
|
||||||
|
|
||||||
|
Все HTTP-запросы проходят через обёртку `apiFetch()`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
export async function apiFetch(endpoint, method = 'GET', body = null) {
|
||||||
|
const response = await fetch(endpoint, {
|
||||||
|
method,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: body ? JSON.stringify(body) : null
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(data?.message || `Ошибка HTTP: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shortcut-методы
|
||||||
|
export const api = {
|
||||||
|
get: (url) => apiFetch(url, 'GET'),
|
||||||
|
post: (url, body) => apiFetch(url, 'POST', body),
|
||||||
|
put: (url, body) => apiFetch(url, 'PUT', body),
|
||||||
|
delete: (url, body) => apiFetch(url, 'DELETE', body)
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Токен берётся из `localStorage.getItem('token')`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Аутентификация (Frontend)
|
||||||
|
|
||||||
|
### Страница входа (`/index.html`)
|
||||||
|
|
||||||
|
1. Пользователь вводит логин/пароль
|
||||||
|
2. `script.js` отправляет `POST /api/auth/login`
|
||||||
|
3. При успехе сохраняет в `localStorage`:
|
||||||
|
- `token` — UUID-токен
|
||||||
|
- `role` — роль пользователя
|
||||||
|
4. Перенаправляет на соответствующий интерфейс:
|
||||||
|
- `ADMIN` → `/admin/`
|
||||||
|
- `TEACHER` → `/teacher/`
|
||||||
|
- `STUDENT` → `/student/`
|
||||||
|
|
||||||
|
### Проверка авторизации
|
||||||
|
|
||||||
|
На каждой странице проверяется наличие токена и роли:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
export function isAuthenticatedAsAdmin() {
|
||||||
|
const role = localStorage.getItem('role');
|
||||||
|
return token && role === 'ADMIN';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Выход
|
||||||
|
|
||||||
|
Кнопка «Выйти» очищает `localStorage` и перенаправляет на `/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CSS-архитектура
|
||||||
|
|
||||||
|
### Модульный подход
|
||||||
|
|
||||||
|
Стили разделены на 4 файла (порядок подключения важен):
|
||||||
|
|
||||||
|
1. **`main.css`** — CSS-переменные (цвета, шрифты, отступы), глобальные стили, тёмная тема
|
||||||
|
2. **`layout.css`** — Sidebar, topbar, content area, responsive
|
||||||
|
3. **`components.css`** — Кнопки, таблицы, карточки, badge, формы
|
||||||
|
4. **`modals.css`** — Модальные окна
|
||||||
|
|
||||||
|
### Темизация
|
||||||
|
|
||||||
|
CSS-переменные позволяют поддерживать светлую/тёмную тему:
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--bg-primary: #ffffff;
|
||||||
|
--text-primary: #1a1a2e;
|
||||||
|
--accent: #6366f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] {
|
||||||
|
--bg-primary: #0f0f23;
|
||||||
|
--text-primary: #e2e8f0;
|
||||||
|
--accent: #818cf8;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Переключение — через `theme-toggle.js`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Адаптивность
|
||||||
|
|
||||||
|
Интерфейс адаптирован под мобильные устройства:
|
||||||
|
- Sidebar скрывается на экранах < 768px
|
||||||
|
- Появляется кнопка-гамбургер (`#menu-toggle`)
|
||||||
|
- Sidebar выезжает как overlay
|
||||||
|
- Таблицы получают горизонтальный скролл
|
||||||
137
docs/INFRASTRUCTURE.md
Normal file
137
docs/INFRASTRUCTURE.md
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
# 🏭 Инфраструктура
|
||||||
|
|
||||||
|
## Docker Compose (локальная разработка)
|
||||||
|
|
||||||
|
### Сервисы
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
backend: # Spring Boot (Java 17), порт 8080
|
||||||
|
frontend: # Apache httpd:alpine, порт 80
|
||||||
|
db: # PostgreSQL alpine3.23, порт 5432
|
||||||
|
```
|
||||||
|
|
||||||
|
### Сеть
|
||||||
|
|
||||||
|
Все сервисы работают в Docker-сети `proxy` (external). Перед первым запуском:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker network create proxy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Переменные окружения
|
||||||
|
|
||||||
|
Файл `.env` в корне проекта:
|
||||||
|
|
||||||
|
```env
|
||||||
|
POSTGRES_USER=myuser
|
||||||
|
POSTGRES_PASSWORD=supersecretpassword
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dockerfile (Backend)
|
||||||
|
|
||||||
|
Backend собирается через multi-stage сборку Maven:
|
||||||
|
1. Этап сборки: `maven:3-eclipse-temurin-17-alpine` → `mvn package`
|
||||||
|
2. Этап запуска: `eclipse-temurin:17-jre-alpine` → `java -jar app.jar`
|
||||||
|
|
||||||
|
### Dockerfile (Frontend)
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM httpd:alpine
|
||||||
|
COPY . /usr/local/apache2/htdocs/
|
||||||
|
RUN chown -R www-data:www-data /usr/local/apache2/htdocs/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Kubernetes (продакшн)
|
||||||
|
|
||||||
|
### Расположение конфигурации
|
||||||
|
|
||||||
|
Файлы Kubernetes манифестов: `../k8s/`
|
||||||
|
|
||||||
|
### Ключевые ресурсы
|
||||||
|
|
||||||
|
| Ресурс | Тип | Описание |
|
||||||
|
|--------|-----|----------|
|
||||||
|
| `backend` | Deployment | Spring Boot приложение |
|
||||||
|
| `frontend` | Deployment | Apache httpd |
|
||||||
|
| `tenants-config` | ConfigMap | JSON-список тенантов |
|
||||||
|
|
||||||
|
### ConfigMap для тенантов
|
||||||
|
|
||||||
|
ConfigMap `tenants-config` монтируется в под backend по пути `/config/tenants.json`.
|
||||||
|
|
||||||
|
При добавлении тенанта через API:
|
||||||
|
1. `DatabaseController` обновляет in-memory DataSource
|
||||||
|
2. `ConfigMapUpdater` обновляет ConfigMap через Kubernetes API
|
||||||
|
3. `TenantConfigWatcher` на остальных подах подхватывает изменения (каждые 30 сек)
|
||||||
|
|
||||||
|
### Обновление backend
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl rollout restart deployment backend -n magistr
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Caddy (реверс-прокси)
|
||||||
|
|
||||||
|
**Расположение:** `../caddy-proxy/` для локальной разработки, в продакшене - отдельный сервис
|
||||||
|
|
||||||
|
В продакшене Caddy обрабатывает входящий трафик для `*.zuev.company`:
|
||||||
|
- Автоматическое получение TLS-сертификатов (Let's Encrypt)
|
||||||
|
- Маршрутизация `/api/*` → backend:8080
|
||||||
|
- Маршрутизация статики → frontend:80
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CI/CD (Gitea Actions)
|
||||||
|
|
||||||
|
### Пайплайн сборки Docker-образов
|
||||||
|
|
||||||
|
Расположение: `.gitea/workflows/docker-build.yaml`
|
||||||
|
|
||||||
|
Основные шаги:
|
||||||
|
1. Checkout кода
|
||||||
|
2. Login в Docker Registry
|
||||||
|
3. Build + Push образов (`backend`, `frontend`)
|
||||||
|
4. Генерация меток через `docker/metadata-action`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Мониторинг (SigNoz + OpenTelemetry)
|
||||||
|
|
||||||
|
### Архитектура мониторинга
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
Backend["Spring Boot"] -->|OTLP gRPC| Collector["OTel Collector"]
|
||||||
|
Frontend["JS (otel.js)"] -->|OTLP HTTP| Collector
|
||||||
|
Collector --> SigNoz["SigNoz"]
|
||||||
|
|
||||||
|
Collector -->|"Метрики PostgreSQL"| PgExporter["pg_exporter"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Интеграция Backend
|
||||||
|
|
||||||
|
Backend отправляет через OpenTelemetry:
|
||||||
|
- **Логи** — через Logback + OTLP exporter
|
||||||
|
- **Трейсы** — автоинструментация Spring Boot
|
||||||
|
- **Метрики** — JVM метрики, HTTP метрики
|
||||||
|
|
||||||
|
Tenant ID добавляется в:
|
||||||
|
- MDC (логи): `MDC.put("tenant.id", tenant)`
|
||||||
|
- Span атрибуты: `Span.current().setAttribute("tenant.id", tenant)`
|
||||||
|
|
||||||
|
### Интеграция Frontend
|
||||||
|
|
||||||
|
Файл `admin/js/otel.js` — клиентская телеметрия:
|
||||||
|
- Метрики производительности страниц
|
||||||
|
- Трейсы пользовательских действий
|
||||||
|
|
||||||
|
### Дашборды SigNoz
|
||||||
|
|
||||||
|
- JVM Dashboard (Heap, GC, Threads)
|
||||||
|
- PostgreSQL Dashboard (Connections, Queries)
|
||||||
|
- HTTP Dashboard (Requests, Latency, Errors)
|
||||||
113
docs/README.md
Normal file
113
docs/README.md
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# 📚 Magistr — Система управления университетским расписанием
|
||||||
|
|
||||||
|
## Обзор
|
||||||
|
|
||||||
|
**Magistr** — веб-приложение для управления расписанием занятий университета. Система поддерживает мультитенантную архитектуру (каждый университет = отдельная база данных), ролевую модель доступа (Администратор, Преподаватель, Студент) и полное управление аудиторным фондом, группами, дисциплинами и преподавательским составом.
|
||||||
|
|
||||||
|
- **Продакшн:** [https://magistr.zuev.company](https://magistr.zuev.company)
|
||||||
|
- **Локальная разработка:** [http://localhost:80](http://localhost:80)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Стек технологий
|
||||||
|
|
||||||
|
| Компонент | Технология |
|
||||||
|
|-----------|-----------|
|
||||||
|
| **Backend** | Java 17, Spring Boot 3.2.5 |
|
||||||
|
| **Frontend** | Vanilla JavaScript (ES6 Modules) + HTML/CSS |
|
||||||
|
| **База данных** | PostgreSQL (через Flyway миграции) |
|
||||||
|
| **Контейнеризация** | Docker, Docker Compose |
|
||||||
|
| **Продакшн** | Kubernetes, Caddy (реверс-прокси) |
|
||||||
|
| **Мониторинг** | SigNoz, OpenTelemetry |
|
||||||
|
| **CI/CD** | Gitea Actions |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Быстрый старт
|
||||||
|
|
||||||
|
### Предварительные требования
|
||||||
|
|
||||||
|
- Docker и Docker Compose
|
||||||
|
- Git
|
||||||
|
|
||||||
|
### Локальный запуск
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Клонировать репозиторий
|
||||||
|
git clone <repo-url> magistr && cd magistr
|
||||||
|
|
||||||
|
# 2. Создать Docker-сеть (если ещё не создана)
|
||||||
|
docker network create proxy
|
||||||
|
|
||||||
|
# 3. Запустить все сервисы
|
||||||
|
docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
После запуска приложение доступно по адресу: **http://localhost:80**
|
||||||
|
|
||||||
|
**Учётные данные по умолчанию:**
|
||||||
|
|
||||||
|
| Логин | Пароль | Роль |
|
||||||
|
|-------|--------|------|
|
||||||
|
| `admin` | `admin` | Администратор |
|
||||||
|
| `Тестовый преподаватель` | `1234567890` | Преподаватель |
|
||||||
|
|
||||||
|
### Полезные команды
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Просмотр логов
|
||||||
|
docker compose logs -f backend
|
||||||
|
|
||||||
|
# Полный сброс базы данных (удаление данных + повтор миграций)
|
||||||
|
docker compose down -v
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# Остановка всех сервисов
|
||||||
|
docker compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
magistr/
|
||||||
|
├── backend/ # Java Spring Boot backend
|
||||||
|
│ └── src/main/
|
||||||
|
│ ├── java/com/magistr/app/
|
||||||
|
│ │ ├── controller/ # REST-контроллеры (10 шт.)
|
||||||
|
│ │ ├── model/ # JPA-сущности
|
||||||
|
│ │ ├── dto/ # Data Transfer Objects
|
||||||
|
│ │ ├── repository/ # Spring Data JPA репозитории
|
||||||
|
│ │ ├── config/ # Конфигурация приложения
|
||||||
|
│ │ │ └── tenant/ # Мультитенантность
|
||||||
|
│ │ └── utils/ # Валидаторы
|
||||||
|
│ └── resources/
|
||||||
|
│ ├── application.properties
|
||||||
|
│ └── db/migration/ # Flyway SQL миграции
|
||||||
|
├── frontend/ # Статический фронтенд
|
||||||
|
│ ├── index.html # Страница авторизации
|
||||||
|
│ ├── admin/ # Админ-панель (деканат)
|
||||||
|
│ │ ├── js/views/ # Модули представлений
|
||||||
|
│ │ └── css/ # Стили
|
||||||
|
│ ├── teacher/ # Интерфейс преподавателя
|
||||||
|
│ └── student/ # Интерфейс студента
|
||||||
|
├── docs/ # 📖 Документация (вы здесь)
|
||||||
|
├── compose.yaml # Docker Compose конфигурация
|
||||||
|
├── .env # Переменные окружения
|
||||||
|
└── AGENTS.md # Руководство для AI-агентов
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📖 Навигация по документации
|
||||||
|
|
||||||
|
| Документ | Содержание |
|
||||||
|
|----------|-----------|
|
||||||
|
| [Архитектура](ARCHITECTURE.md) | Общая архитектура, мультитенантность, взаимодействие компонентов |
|
||||||
|
| [Бизнес-логика](BUSINESS_LOGIC.md) | Ролевая модель, правила расписания, управление ресурсами |
|
||||||
|
| [База данных](DATABASE.md) | Схема БД, описание таблиц, Flyway миграции |
|
||||||
|
| [REST API](API.md) | Все эндпоинты с примерами запросов и ответов |
|
||||||
|
| [Инфраструктура](INFRASTRUCTURE.md) | Docker, Kubernetes, CI/CD, мониторинг |
|
||||||
|
| [Разработка](DEVELOPMENT.md) | Code Style, соглашения, инструкции для разработчиков |
|
||||||
|
| [Frontend](FRONTEND.md) | Архитектура фронтенда, модули, стили |
|
||||||
Reference in New Issue
Block a user