From 81e91e056f044e8230e6fb5ede37c6dd717c917f Mon Sep 17 00:00:00 2001 From: Zuev Date: Thu, 19 Mar 2026 23:47:01 +0300 Subject: [PATCH 1/4] feat: Add OpenTelemetry integration by creating `otel.js` and importing it into `main.js`. --- frontend/admin/js/main.js | 2 ++ frontend/admin/js/otel.js | 47 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 frontend/admin/js/otel.js diff --git a/frontend/admin/js/main.js b/frontend/admin/js/main.js index 235203e..ee2f9e4 100755 --- a/frontend/admin/js/main.js +++ b/frontend/admin/js/main.js @@ -1,3 +1,5 @@ +import './otel.js'; + import { isAuthenticatedAsAdmin } from './api.js'; import { applyRippleEffect, closeAllDropdownsOnOutsideClick } from './utils.js'; diff --git a/frontend/admin/js/otel.js b/frontend/admin/js/otel.js new file mode 100644 index 0000000..2de329d --- /dev/null +++ b/frontend/admin/js/otel.js @@ -0,0 +1,47 @@ +import { WebTracerProvider } from 'https://esm.sh/@opentelemetry/sdk-trace-web@1.22.0'; +import { getWebAutoInstrumentations } from 'https://esm.sh/@opentelemetry/auto-instrumentations-web@0.37.0'; +import { OTLPTraceExporter } from 'https://esm.sh/@opentelemetry/exporter-trace-otlp-http@0.49.1'; +import { BatchSpanProcessor } from 'https://esm.sh/@opentelemetry/sdk-trace-base@1.22.0'; +import { registerInstrumentations } from 'https://esm.sh/@opentelemetry/instrumentation@0.49.1'; +import { ZoneContextManager } from 'https://esm.sh/@opentelemetry/context-zone@1.22.0'; +import { Resource } from 'https://esm.sh/@opentelemetry/resources@1.22.0'; +import { SemanticResourceAttributes } from 'https://esm.sh/@opentelemetry/semantic-conventions@1.22.0'; + +// Инициализация провайдера метрик и трейсов с именем сервиса +const provider = new WebTracerProvider({ + resource: new Resource({ + [SemanticResourceAttributes.SERVICE_NAME]: 'magistr-frontend-admin', + }), +}); + +// Экспортер отправляет данные на относительный путь /otel/v1/traces. +// На проде Caddy перехватит этот запрос и проксирует в SigNoz OTLP Collector (порт 4318). +const traceExporter = new OTLPTraceExporter({ + url: window.location.origin + '/otel/v1/traces', +}); + +// Использование BatchSpanProcessor для буферизации трейсов перед отправкой +provider.addSpanProcessor(new BatchSpanProcessor(traceExporter)); + +// Использование ZoneContextManager для поддержки асинхронных операций (Promise, setTimeout, etc) +provider.register({ + contextManager: new ZoneContextManager(), +}); + +// Регистрация авто-инструментаций для бразуера (document-load, xml-http-request, fetch, history, etc) +registerInstrumentations({ + instrumentations: [ + getWebAutoInstrumentations({ + '@opentelemetry/instrumentation-fetch': { + propagateTraceHeaderCorsUrls: /.*/, + clearTimingResources: true, + }, + '@opentelemetry/instrumentation-xml-http-request': { + propagateTraceHeaderCorsUrls: /.*/, + clearTimingResources: true, + }, + }), + ], +}); + +console.log('OpenTelemetry Web SDK initialized successfully.'); From 491807cd9464afcc8695f1b41de9021036abd5e7 Mon Sep 17 00:00:00 2001 From: Zuev Date: Sun, 22 Mar 2026 02:49:13 +0300 Subject: [PATCH 2/4] docs: Add comprehensive project documentation covering architecture, development, and APIs, and update AGENTS.md. --- .gitignore | 4 +- AGENTS.md | 169 +++-------------- docs/API.md | 420 +++++++++++++++++++++++++++++++++++++++++ docs/ARCHITECTURE.md | 142 ++++++++++++++ docs/BUSINESS_LOGIC.md | 149 +++++++++++++++ docs/DATABASE.md | 362 +++++++++++++++++++++++++++++++++++ docs/DEVELOPMENT.md | 275 +++++++++++++++++++++++++++ docs/FRONTEND.md | 203 ++++++++++++++++++++ docs/INFRASTRUCTURE.md | 137 ++++++++++++++ docs/README.md | 113 +++++++++++ 10 files changed, 1829 insertions(+), 145 deletions(-) create mode 100644 docs/API.md create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/BUSINESS_LOGIC.md create mode 100644 docs/DATABASE.md create mode 100644 docs/DEVELOPMENT.md create mode 100644 docs/FRONTEND.md create mode 100644 docs/INFRASTRUCTURE.md create mode 100644 docs/README.md diff --git a/.gitignore b/.gitignore index b362f1c..98c3a42 100755 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,6 @@ backend/build/ frontend/node_modules/ frontend/dist/ -.agents .idea/ .vscode/ -*.DS_Store -GEMINI.md \ No newline at end of file +*.DS_Store \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 1e900b4..033f2d5 100755 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,174 +28,59 @@ magistr/ │ ├── admin/ # Интерфейс администратора │ ├── teacher/ # Интерфейс преподавателя │ └── student/ # Интерфейс студента +├── docs/ # 📖 Документация проекта ├── compose.yaml # Docker Compose конфигурация └── .env # Переменные окружения ``` **Внешние зависимости (родительская директория)** -На уровень выше расположен `../caddy-proxy/`. Это реверс-прокси, обрабатывающий трафик для `magistr.zuev.company`. Если возникают проблемы с доменом или внешним доступом, проверяйте `Caddyfile` там. - -так же на уровень выше расположен конфиг kubernetes `../k8s/`, все файлы сборки на проде расположены там. +На уровень выше расположен конфиг kubernetes `../k8s/`, все файлы сборки на проде расположены там. --- -## Команды сборки и запуска - -### Docker Compose (основной способ) - -Сборка и запуск всех сервисов (backend, frontend, PostgreSQL) выполняется через Docker Compose. +## Быстрый справочник команд ```bash -# Сборка и запуск всех сервисов +# Сборка и запуск 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 - -# Пересоздать контейнер базы данных (полный сброс данных и повтор миграций Flyway) -docker compose down -v -docker compose up -d db ``` -### Frontend - -Статические файлы обслуживаются через Apache (httpd:alpine). Изменения в файлах frontend требуют пересборки контейнера. +Подробнее — см. [`docs/README.md`](docs/README.md) и [`docs/INFRASTRUCTURE.md`](docs/INFRASTRUCTURE.md). --- -## Соглашения о коде (Code Style) +## Критические правила для агентов -### Java (Backend) - -**Именование:** -- Классы: PascalCase (например, `LessonsController`, `LessonResponse`) -- Методы и переменные: 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)**: - - Предложение подходящей замены преподавателя на этот слот. - - Предложение переноса занятия на другое время или в другую аудиторию. - ---- - -## Языковые требования +### Flyway миграции +- **ЗАПРЕЩЕНО** изменять существующие файлы миграций (например, `V1__init.sql`). Это сломает контрольные суммы Flyway. +- Новые миграции: `V{N}__{описание}.sql` в `backend/src/main/resources/db/migration/` +- Подробнее — см. [`docs/DATABASE.md`](docs/DATABASE.md) +### Языковые требования - **Все ответы и комментарии на русском языке** - Сообщения об ошибках и логи на русском - Пользовательский интерфейс на русском --- -## Существующие правила проекта +## Подробная документация -См. `.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, адаптивность | diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..f8e40c3 --- /dev/null +++ b/docs/API.md @@ -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 ` + +--- + +## Пользователи + +### `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` | Внутренняя ошибка сервера | diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..f4e3cf9 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,142 @@ +# 🏗 Архитектура системы + +## Общая схема + +```mermaid +graph TD + Client["🌐 Браузер"] -->|HTTPS| Caddy["Caddy Proxy"] + Caddy -->|:80| Frontend["Frontend
(Apache httpd:alpine)"] + Caddy -->|/api/*| Backend["Backend
(Spring Boot 3.2.5)"] + + Backend --> TenantRouter{"TenantRoutingDataSource"} + TenantRouter -->|swsu.zuev.company| DB1["PostgreSQL
swsu_db"] + TenantRouter -->|mgu.zuev.company| DB2["PostgreSQL
mgu_db"] + TenantRouter -->|...| DBn["PostgreSQL
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
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` на клиенте diff --git a/docs/BUSINESS_LOGIC.md b/docs/BUSINESS_LOGIC.md new file mode 100644 index 0000000..08cf64e --- /dev/null +++ b/docs/BUSINESS_LOGIC.md @@ -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: предложение замены преподавателя или переноса занятия diff --git a/docs/DATABASE.md b/docs/DATABASE.md new file mode 100644 index 0000000..c025547 --- /dev/null +++ b/docs/DATABASE.md @@ -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 # Пересоздаёт БД с нуля +``` diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md new file mode 100644 index 0000000..a0ba3a2 --- /dev/null +++ b/docs/DEVELOPMENT.md @@ -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 { + List 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 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 +``` diff --git a/docs/FRONTEND.md b/docs/FRONTEND.md new file mode 100644 index 0000000..c571a55 --- /dev/null +++ b/docs/FRONTEND.md @@ -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 +Пользователи +Группы +Расписание занятий +``` + +При клике на пункт меню `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 +- Таблицы получают горизонтальный скролл diff --git a/docs/INFRASTRUCTURE.md b/docs/INFRASTRUCTURE.md new file mode 100644 index 0000000..6c80225 --- /dev/null +++ b/docs/INFRASTRUCTURE.md @@ -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) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..3775cc5 --- /dev/null +++ b/docs/README.md @@ -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 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) | Архитектура фронтенда, модули, стили | From fcd7baac71dda34dec670e701587f78142147839 Mon Sep 17 00:00:00 2001 From: Zuev Date: Sun, 22 Mar 2026 15:22:10 +0300 Subject: [PATCH 3/4] feat: Add AutoUpdateDocs agent skill and new logging documentation, updating AGENTS.md. --- .agents/skills/AutoUpdateDocs.md | 85 ++++++++++++++++ AGENTS.md | 1 + docs/LOGGING.md | 167 +++++++++++++++++++++++++++++++ 3 files changed, 253 insertions(+) create mode 100644 .agents/skills/AutoUpdateDocs.md create mode 100644 docs/LOGGING.md diff --git a/.agents/skills/AutoUpdateDocs.md b/.agents/skills/AutoUpdateDocs.md new file mode 100644 index 0000000..8910bf4 --- /dev/null +++ b/.agents/skills/AutoUpdateDocs.md @@ -0,0 +1,85 @@ +--- +name: AutoUpdateDocs +description: Автоматическое обновление документации проекта после изменений в коде +--- + +# Скилл: Автоматическое обновление документации + +## Когда активировать + +Этот скилл **ДОЛЖЕН** выполняться автоматически после любых изменений, затрагивающих: + +- **Контроллеры** (`backend/src/main/java/com/magistr/app/controller/`) → обновить `docs/API.md` +- **Модели или миграции** (`model/`, `db/migration/`) → обновить `docs/DATABASE.md` +- **Конфигурация тенантов** (`config/tenant/`) → обновить `docs/ARCHITECTURE.md` +- **Бизнес-правила или валидаторы** (`utils/`) → обновить `docs/BUSINESS_LOGIC.md` +- **Frontend** (`frontend/`) → обновить `docs/FRONTEND.md` +- **Docker/Kubernetes** (`compose.yaml`, `Dockerfile`, `../k8s/`) → обновить `docs/INFRASTRUCTURE.md` +- **Code style или структура пакетов** → обновить `docs/DEVELOPMENT.md` +- **Общая структура проекта** → обновить `docs/README.md` + +## Карта соответствия «файл → документация» + +| Изменённый файл/директория | Файл документации | +|----------------------------|-------------------| +| `controller/*Controller.java` | `docs/API.md` | +| `db/migration/V*__.sql` | `docs/DATABASE.md` | +| `model/*.java` | `docs/DATABASE.md` | +| `dto/*.java` | `docs/API.md` | +| `config/tenant/*.java` | `docs/ARCHITECTURE.md` | +| `utils/*.java` | `docs/BUSINESS_LOGIC.md` | +| `frontend/admin/js/views/*.js` | `docs/FRONTEND.md` | +| `frontend/admin/css/*.css` | `docs/FRONTEND.md` | +| `compose.yaml`, `Dockerfile` | `docs/INFRASTRUCTURE.md` | +| `application.properties` | `docs/ARCHITECTURE.md` | + +## Пошаговая инструкция + +### 1. Определить затронутые файлы документации + +После выполнения задачи пользователя — проверить по таблице выше, какие файлы документации нужно обновить. + +### 2. Прочитать текущую документацию + +Открыть соответствующий файл из `docs/` и найти секцию, которую нужно обновить. + +### 3. Внести точечные изменения + +Обновить **только затронутые секции**, не переписывая весь файл. Примеры: + +#### Новый контроллер → `docs/API.md` +Добавить новую секцию с описанием эндпоинтов: +- Метод + URL +- Тело запроса (JSON пример) +- Ответ (JSON пример) +- Валидация + +#### Новая миграция → `docs/DATABASE.md` +- Добавить новую таблицу в ER-диаграмму (Mermaid) +- Добавить описание таблицы и колонок +- Добавить запись в таблицу «Текущие миграции» + +#### Новый view → `docs/FRONTEND.md` +- Добавить в дерево файлов +- Добавить в таблицу «Разделы админ-панели» + +### 4. Обновить AGENTS.md (при необходимости) + +Если изменения затрагивают: +- Структуру директорий → обновить дерево в `AGENTS.md` +- Критические правила (Flyway, новые ограничения) → обновить секцию «Критические правила» + +### 5. Сообщить пользователю + +В конце ответа кратко упомянуть, какие файлы документации были обновлены: + +> 📝 Обновлена документация: `docs/API.md` (добавлен эндпоинт `POST /api/absences`) + +## Правила + +1. **Язык:** Вся документация на русском языке +2. **Формат:** Сохранять существующий стиль оформления файла (заголовки, таблицы, примеры кода) +3. **Не удалять:** Не удалять существующие секции без явного запроса пользователя +4. **Mermaid:** При изменении схемы БД — обязательно обновлять ER-диаграмму в `docs/DATABASE.md` +5. **Минимальные правки:** Не переписывать весь файл ради добавления одной строки — использовать точечные изменения +6. **Консистентность:** Если одно и то же понятие упоминается в нескольких файлах `docs/`, обновить все вхождения diff --git a/AGENTS.md b/AGENTS.md index 033f2d5..a6da314 100755 --- a/AGENTS.md +++ b/AGENTS.md @@ -84,3 +84,4 @@ docker compose logs -f backend | [`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, адаптивность | +| [`docs/LOGGING.md`](docs/LOGGING.md) | Логирование: SLF4J + Logback, MDC, OpenTelemetry → SigNoz | diff --git a/docs/LOGGING.md b/docs/LOGGING.md new file mode 100644 index 0000000..41ad688 --- /dev/null +++ b/docs/LOGGING.md @@ -0,0 +1,167 @@ +# 📋 Логирование + +## Стек технологий + +| Компонент | Технология | +|-----------|------------| +| Фасад | SLF4J (`org.slf4j.Logger`) | +| Реализация | Logback (поставляется с `spring-boot-starter-web`) | +| Конфигурация | Стандартная Spring Boot (без кастомного `logback.xml`) | +| Экспорт (прод) | OpenTelemetry Java Agent → OTLP → SigNoz | +| Контекст тенанта | SLF4J MDC (`tenant.id`) | + +--- + +## Архитектура + +```mermaid +graph LR + Code["Java-код
log.info(...)"] --> SLF4J["SLF4J API"] + SLF4J --> Logback["Logback"] + Logback -->|"Локальная разработка"| Console["stdout / stderr"] + Logback -->|"Продакшн"| OTelAgent["OTel Java Agent
(Logback Appender)"] + OTelAgent -->|"OTLP HTTP"| SigNoz["SigNoz"] +``` + +### Локальная разработка + +Логи выводятся в `stdout` контейнера в стандартном формате Spring Boot: + +``` +2026-03-22 12:00:00.123 INFO 1 --- [main] c.m.app.config.DataInitializer : Initializing databases for 1 tenant(s)... +``` + +Просмотр логов: + +```bash +docker compose logs -f backend +``` + +### Продакшн (Kubernetes) + +OpenTelemetry Java Agent подключается как `-javaagent` в [Dockerfile](file:///mnt/HDD/magistr/magistr/backend/Dockerfile) и автоматически перехватывает логи Logback, экспортируя их в SigNoz по OTLP. + +```dockerfile +ENTRYPOINT ["java", "-javaagent:opentelemetry-javaagent.jar", "-jar", "app.jar"] +``` + +Конфигурация агента задаётся через переменные окружения в [backend.yaml](file:///mnt/HDD/magistr/k8s/backend.yaml): + +| Переменная | Значение | Назначение | +|------------|----------|------------| +| `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://192.168.1.100:4318` | Адрес SigNoz Collector | +| `OTEL_SERVICE_NAME` | `magistr-backend` | Имя сервиса в SigNoz | +| `OTEL_RESOURCE_ATTRIBUTES` | `deployment.environment=default` | Окружение | +| `OTEL_LOGS_EXPORTER` | `otlp` | Экспорт логов через OTLP | +| `OTEL_METRICS_EXPORTER` | `otlp` | Экспорт метрик через OTLP | +| `OTEL_TRACES_EXPORTER` | `otlp` | Экспорт трейсов через OTLP | +| `OTEL_INSTRUMENTATION_LOGBACK_APPENDER_EXPERIMENTAL_CAPTURE_MDC_ATTRIBUTES` | `tenant.id` | Захват MDC-атрибута в логи | + +> [!NOTE] +> В локальной разработке OpenTelemetry Agent также встроен в Docker-образ, но без переменных `OTEL_*` он работает в режиме noop — логи идут только в stdout. + +--- + +## Мультитенантный контекст (MDC) + +Каждый HTTP-запрос обогащается tenant ID через [TenantInterceptor](file:///mnt/HDD/magistr/magistr/backend/src/main/java/com/magistr/app/config/tenant/TenantInterceptor.java): + +```java +// preHandle — при входе запроса +MDC.put("tenant.id", tenant); +Span.current().setAttribute("tenant.id", tenant); + +// afterCompletion — после завершения +MDC.remove("tenant.id"); +``` + +Это позволяет: +- Фильтровать логи по тенанту в SigNoz +- Коррелировать логи с трейсами через Span-атрибуты +- Идентифицировать, к какому университету относится каждая запись + +--- + +## Использование в коде + +### Классы с логированием + +| Класс | Уровни | Что логируется | +|-------|--------|----------------| +| `TenantInterceptor` | DEBUG, WARN | Резолвинг тенанта, неизвестный тенант (404) | +| `TenantDataSourceConfig` | INFO, WARN, ERROR | Загрузка тенантов, fallback на H2 | +| `TenantRoutingDataSource` | INFO, WARN | Добавление/удаление тенантов, тест соединения | +| `TenantConfigWatcher` | INFO, ERROR, WARN | Изменения ConfigMap, Flyway миграции | +| `ConfigMapUpdater` | INFO, WARN, ERROR | Обновление ConfigMap в K8s | +| `DataInitializer` | INFO | Инициализация БД при старте | +| `LessonsController` | INFO, DEBUG, ERROR | CRUD-операции с занятиями, валидация | + +### Паттерн использования + +```java +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MyClass { + private static final Logger log = LoggerFactory.getLogger(MyClass.class); + + public void doSomething() { + log.info("Операция выполнена: param={}", value); + log.error("Ошибка: {}", e.getMessage(), e); // со стектрейсом + } +} +``` + +### Рекомендации по уровням + +| Уровень | Когда использовать | +|---------|-------------------| +| `ERROR` | Необработанные ошибки, сбои подключения к БД, провалы миграций | +| `WARN` | Неизвестный тенант, нет конфигурации, fallback-сценарии | +| `INFO` | Успешные операции, CRUD-действия, старт/стоп компонентов | +| `DEBUG` | Детали резолвинга тенанта, ping-запросы | + +--- + +## Настройка уровня логирования + +В [application.properties](file:///mnt/HDD/magistr/magistr/backend/src/main/resources/application.properties) (по умолчанию закомментировано): + +```properties +# Включить DEBUG для всего приложения +#logging.level.root=DEBUG + +# Только для пакета приложения +logging.level.com.magistr.app=DEBUG + +# Только для конкретного класса +logging.level.com.magistr.app.config.tenant.TenantInterceptor=DEBUG +``` + +Также можно задавать через переменные окружения: + +```bash +LOGGING_LEVEL_ROOT=DEBUG +LOGGING_LEVEL_COM_MAGISTR_APP=DEBUG +``` + +--- + +## Просмотр логов + +### Локально (Docker Compose) + +```bash +# Все логи backend +docker compose logs -f backend + +# Фильтрация по ключевому слову +docker compose logs -f backend | grep "tenant" +``` + +### Продакшн (SigNoz) + +Логи доступны в веб-интерфейсе SigNoz → раздел **Logs**: +- Фильтрация по `service.name = magistr-backend` +- Фильтрация по `tenant.id` (из MDC) +- Корреляция с трейсами через общий `trace_id` From e03a68b7a85ec905763de18ede99f18ea068cb6b Mon Sep 17 00:00:00 2001 From: Zuev Date: Mon, 23 Mar 2026 02:05:02 +0300 Subject: [PATCH 4/4] feat: add frontend-design skill with its documentation and license, and update gitignore. --- .agents/skills/frontend-design/LICENSE.txt | 177 +++++++++++++++++++++ .agents/skills/frontend-design/SKILL.md | 42 +++++ .gitignore | 3 +- 3 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 .agents/skills/frontend-design/LICENSE.txt create mode 100644 .agents/skills/frontend-design/SKILL.md diff --git a/.agents/skills/frontend-design/LICENSE.txt b/.agents/skills/frontend-design/LICENSE.txt new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/.agents/skills/frontend-design/LICENSE.txt @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/.agents/skills/frontend-design/SKILL.md b/.agents/skills/frontend-design/SKILL.md new file mode 100644 index 0000000..78d2f00 --- /dev/null +++ b/.agents/skills/frontend-design/SKILL.md @@ -0,0 +1,42 @@ +--- +name: frontend-design +description: Создание выразительных, готовых к продакшену frontend-интерфейсов с высоким качеством дизайна. Используйте этот навык, когда пользователь просит разработать веб-компоненты, страницы, артефакты, постеры или приложения (например, сайты, лендинги, дашборды, React-компоненты, HTML/CSS верстку или когда нужно стилизовать/улучшить любой веб-интерфейс). Генерирует креативный, отточенный код и UI-дизайн, избегая шаблонной эстетики ИИ. +license: Полные условия в LICENSE.txt +--- + +Этот навык направляет создание выразительных, готовых к продакшену frontend-интерфейсов, которые избегают шаблонной "ИИ-эстетики". Создавайте реально работающий код с исключительным вниманием к эстетическим деталям и творческим решениям. + +Пользователь предоставляет требования к фронтенду: компонент, страницу, приложение или интерфейс для разработки. Требования могут включать контекст о цели, аудитории или технических ограничениях. + +## Дизайн-мышление + +Перед написанием кода поймите контекст и примите СМЕЛОЕ эстетическое направление: +- **Цель**: Какую проблему решает этот интерфейс? Кто им пользуется? +- **Тон**: Выберите крайность: брутальный минимализм, максималистский хаос, ретро-футуризм, органический/природный, люксовый/утонченный, игривый/игрушечный, редакционный/журнальный, брутализм/грубый, арт-деко/геометрический, мягкий/пастельный, индустриальный/утилитарный и т.д. Вариантов очень много. Используйте их для вдохновения, но создайте дизайн, верный выбранному эстетическому направлению. +- **Ограничения**: Технические требования (фреймворк, производительность, доступность). +- **Отличительная черта**: Что делает это НЕЗАБЫВАЕМЫМ? Какую единственную вещь кто-то запомнит? + +**КРИТИЧЕСКИ ВАЖНО**: Выберите четкое концептуальное направление и выполните его с точностью. Смелый максимализм и утонченный минимализм — оба работают, ключ кроется в осознанности намерений, а не в интенсивности. + +Затем реализуйте рабочий код (HTML/CSS/JS, React, Vue и т.д.), который: +- Готов к продакшену и функционален +- Визуально поразителен и легко запоминается +- Согласован с четкой эстетической точкой зрения +- Тщательно проработан в каждой детали + +## Руководство по эстетике фронтенда + +Сфокусируйтесь на: +- **Типографика**: Выбирайте шрифты, которые красивы, уникальны и интересны. Избегайте общих шрифтов, таких как Arial и Inter; вместо этого делайте выбор в пользу выразительных, неожиданных и характерных вариантов, которые повышают уровень эстетики фронтенда. Сочетайте акцидентный шрифт (display) с утонченным текстовым (body). +- **Цвет и тема**: Придерживайтесь согласованной эстетики. Используйте CSS-переменные для консистентности. Доминирующие цвета с резкими акцентами работают намного лучше, чем робкие, равномерно распределенные палитры. +- **Анимация (Motion)**: Используйте анимации для эффектов и микро-взаимодействий. Отдавайте предпочтение CSS-решениям для HTML. Используйте библиотеки анимаций для React, если они доступны. Фокусируйтесь на моментах с высоким влиянием: одна хорошо срежиссированная загрузка страницы с каскадным появлением элементов (animation-delay) создает больше восторга, чем множество разрозненных микро-взаимодействий. Используйте триггеры при скролле (scroll-triggering) и состояния наведения (hover), которые удивляют. +- **Пространственная композиция**: Неожиданные макеты. Асимметрия. Перекрытие. Диагональное направление. Элементы, ломающие сетку. Обильное негативное пространство ИЛИ контролируемая плотность элементов. +- **Фоны и визуальные детали**: Создавайте атмосферу и глубину вместо использования скучных сплошных цветов по умолчанию. Добавляйте контекстуальные эффекты и текстуры, соответствующие общей эстетике. Применяйте творческие формы: градиентные сетки, шумовые текстуры, геометрические паттерны, слоистые прозрачности, драматичные тени, декоративные рамки, кастомные курсоры и эффекты зернистости (grain). + +НИКОГДА не используйте шаблонную сгенерированную ИИ эстетику: заезженные семейства шрифтов (Inter, Roboto, Arial, системные шрифты), клишированные цветовые схемы (особенно фиолетовые градиенты на белом фоне), предсказуемые макеты и паттерны компонентов, а также типовой скучный дизайн без характера, не учитывающий контекст. + +Интерпретируйте творчески и делайте неожиданные выборы, которые кажутся действительно разработанными под данный контекст. Ни один дизайн не должен быть шаблонным ("под копирку"). Варьируйте между светлыми и темными темами, разными шрифтами, различной эстетикой. НИКОГДА не сходитесь к общим выборам (например, Space Grotesk) в разных генерациях кода. + +**ВАЖНО**: Сопоставляйте сложность реализации с эстетическим видением. Максималистские дизайны требуют сложного кода с масштабными анимациями и эффектами. Минималистские или утонченные дизайны требуют сдержанности, точности и крайне внимательного отношения к отступам, типографике и тонким деталям. Элегантность исходит из хорошего воплощения видения. + +Помните: ИИ способен на выдающуюся творческую работу. Не сдерживайтесь, покажите, что можно создать на самом деле, когда вы мыслите нестандартно и полностью привержены особому видению. diff --git a/.gitignore b/.gitignore index 98c3a42..9647f9d 100755 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ frontend/dist/ .idea/ .vscode/ -*.DS_Store \ No newline at end of file +*.DS_Store +skills-lock.json \ No newline at end of file