# 🔌 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/", "departmentId": 1, "userId": 1 } ``` **Ошибка (401):** ```json { "success": false, "message": "Неверное имя пользователя или пароль", "token": null, "role": null, "redirect": null, "departmentId": null, "userId": null } ``` > После получения токена клиент должен передавать его в заголовке: `Authorization: Bearer ` --- ## Пользователи ### `GET /api/users` Список всех пользователей. **Ответ:** ```json [ { "id": 1, "username": "admin", "role": "ADMIN", "fullName": "Иванов Админ Иванович", "jobTitle": "Доцент", "departmentName": "Кафедра ИБ" }, { "id": 2, "username": "Тестовый преподаватель", "role": "TEACHER", "fullName": "Петров Препод Петрович", "jobTitle": "Профессор", "departmentName": "Кафедра ВТ" } ] ``` ### `GET /api/users/teachers` Список только преподавателей (роль `TEACHER`). ### `GET /api/users/teachers/{departmentId}` Список преподавателей привязанных к конкретной кафедре (роль `TEACHER`, код кафедры `departmentId`). ### `POST /api/users` Создание пользователя. **Тело запроса:** ```json { "username": "teacher1", "password": "password", "role": "TEACHER", "fullName": "Test Teacher", "jobTitle": "Proffessor", "departmentId": 1 } ``` **Валидация:** - `username` — обязателен и уникален - `password` — минимум 4 символа - `role` — `ADMIN`, `TEACHER` или `STUDENT` - `fullName` — обязателен - `departmentId` — обязателен ### `DELETE /api/users/{id}` Удаление пользователя. --- ## Динамическое расписание Новая модель расписания строится из правил (`schedule_rules`) и слотов (`schedule_rule_slots`). Фактические занятия рендерятся на диапазон дат только для дней, где календарный учебный график группы имеет код, разрешающий обычные пары. ### `GET /api/schedule` Получение расписания группы или преподавателя за период. **Параметры:** | Параметр | Обязателен | Описание | |----------|------------|----------| | `groupId` | Да, если нет `teacherId` | ID учебной группы | | `teacherId` | Да, если нет `groupId` | ID преподавателя | | `startDate` | Да | Начало периода в формате `YYYY-MM-DD` | | `endDate` | Да | Конец периода в формате `YYYY-MM-DD` | Передаётся ровно один параметр: `groupId` или `teacherId`. Максимальный диапазон — 120 дней. Если у группы нет назначения календарного графика на учебный год даты, расписание для неё возвращается пустым списком. **Пример:** ```http GET /api/schedule?groupId=1&startDate=2026-04-27&endDate=2026-05-03 ``` **Ответ:** ```json [ { "scheduleRuleId": 10, "scheduleRuleSlotId": 31, "date": "2026-04-27", "dayOfWeek": 1, "dayName": "Понедельник", "weekNumber": 13, "parity": "ODD", "timeSlotId": 3, "timeSlotOrder": 3, "startTime": "11:40:00", "endTime": "13:10:00", "subjectId": 1, "subjectName": "Высшая математика", "teacherId": 2, "teacherName": "Петров Препод Петрович", "classroomId": 1, "classroomName": "101 Ленинская", "lessonTypeId": 1, "lessonTypeName": "Лекция", "lessonFormat": "Очно", "subgroupId": null, "groupIds": [1], "groupNames": ["ИВТ-21-1"], "activityType": "Т", "totalAcademicHours": 72, "consumedAcademicHoursBeforeLesson": 24, "remainingAcademicHoursAfterLesson": 46 } ] ``` ### `GET /api/admin/time-slots` Список временных слотов занятий. CRUD доступен по: | Метод | URL | Назначение | |-------|-----|------------| | `GET` | `/api/admin/time-slots` | Список слотов | | `POST` | `/api/admin/time-slots` | Создать слот | | `PUT` | `/api/admin/time-slots/{id}` | Обновить слот | | `DELETE` | `/api/admin/time-slots/{id}` | Удалить слот | **Тело создания/обновления:** ```json { "orderNumber": 1, "startTime": "08:00:00", "endTime": "09:30:00", "durationMinutes": 90 } ``` ### Учебные годы, семестры и коды календарного графика | Метод | URL | Назначение | |-------|-----|------------| | `GET` | `/api/admin/calendar/years` | Учебные годы с семестрами | | `POST` | `/api/admin/calendar/years` | Создать учебный год | | `PUT` | `/api/admin/calendar/years/{id}` | Обновить учебный год | | `DELETE` | `/api/admin/calendar/years/{id}` | Удалить учебный год | | `GET` | `/api/admin/calendar/years/{academicYearId}/semesters` | Семестры учебного года | | `POST` | `/api/admin/calendar/years/{academicYearId}/semesters` | Создать семестр | | `PUT` | `/api/admin/calendar/semesters/{id}` | Обновить семестр | | `GET` | `/api/admin/calendar/activity-types` | Справочник кодов активностей графика | | `POST` | `/api/admin/calendar/activity-types` | Создать код активности | | `PUT` | `/api/admin/calendar/activity-types/{id}` | Обновить код активности | | `DELETE` | `/api/admin/calendar/activity-types/{id}` | Удалить код активности | **Тело создания/обновления кода активности:** ```json { "code": "Т", "name": "Теоретическое обучение", "allowSchedule": true, "colorCode": "#22c55e", "displayOrder": 10, "description": "Обычные пары разрешены" } ``` ### Календарные учебные графики | Метод | URL | Назначение | |-------|-----|------------| | `GET` | `/api/admin/academic-calendars` | Список графиков, фильтры `academicYearId`, `specialtyId`, `profileId` | | `GET` | `/api/admin/academic-calendars/study-forms` | Формы обучения графика | | `GET` | `/api/admin/academic-calendars/{id}` | Один график | | `POST` | `/api/admin/academic-calendars` | Создать график | | `PUT` | `/api/admin/academic-calendars/{id}` | Обновить график | | `DELETE` | `/api/admin/academic-calendars/{id}` | Удалить график | | `GET` | `/api/admin/academic-calendars/{id}/grid` | Дневная сетка графика | | `PUT` | `/api/admin/academic-calendars/{id}/grid` | Полное сохранение дневной сетки | **Тело создания/обновления графика:** ```json { "title": "09.03.04 очная форма 2025-2026", "academicYearId": 1, "specialtyId": 2, "specialtyProfileId": 3, "studyFormId": 1, "courseCount": 4 } ``` **Ячейка сетки графика:** ```json { "calendarId": 1, "courseNumber": 1, "date": "2025-09-01", "weekNumber": 1, "dayOfWeek": 1, "activityTypeId": 1 } ``` ### `POST /api/admin/schedule-rules` Создание правила динамического расписания. ```json { "subjectId": 1, "semesterId": 1, "activeFromDate": "2026-02-01", "totalAcademicHours": 72, "groupIds": [1, 2], "slots": [ { "dayOfWeek": 1, "parity": "BOTH", "timeSlotId": 3, "subgroupId": null, "teacherId": 2, "classroomId": 1, "lessonTypeId": 1, "lessonFormat": "Очно" } ] } ``` CRUD доступен по: | Метод | URL | Назначение | |-------|-----|------------| | `GET` | `/api/admin/schedule-rules` | Список правил, фильтры `semesterId`, `groupId` | | `GET` | `/api/admin/schedule-rules/{id}` | Одно правило | | `POST` | `/api/admin/schedule-rules` | Создать правило | | `PUT` | `/api/admin/schedule-rules/{id}` | Обновить правило | | `DELETE` | `/api/admin/schedule-rules/{id}` | Удалить правило | ### `GET /api/lesson-types` Справочник типов занятий для конструктора правил расписания. **Ответ:** ```json [ { "id": 1, "name": "Лекция" }, { "id": 2, "name": "Практика" } ] ``` --- ## Кафедры и специальности ### Кафедры | Метод | URL | Назначение | |-------|-----|------------| | `GET` | `/api/departments` | Список кафедр | | `POST` | `/api/departments` | Создать кафедру | | `PUT` | `/api/departments/{id}` | Обновить кафедру | | `DELETE` | `/api/departments/{id}` | Удалить кафедру | **Тело создания/обновления:** ```json { "departmentName": "Кафедра ИБ", "departmentCode": 1 } ``` ### Специальности | Метод | URL | Назначение | |-------|-----|------------| | `GET` | `/api/specialties` | Список специальностей | | `POST` | `/api/specialties` | Создать специальность | | `PUT` | `/api/specialties/{id}` | Обновить специальность | | `DELETE` | `/api/specialties/{id}` | Удалить специальность | | `GET` | `/api/specialties/{id}/profiles` | Профили выбранной специальности | | `POST` | `/api/specialties/{id}/profiles` | Создать профиль | | `PUT` | `/api/specialties/{id}/profiles/{profileId}` | Обновить профиль | | `DELETE` | `/api/specialties/{id}/profiles/{profileId}` | Удалить профиль | **Тело создания/обновления:** ```json { "specialityName": "Программная инженерия", "specialityCode": "09.03.04" } ``` При создании специальности автоматически создаётся профиль `Без профиля`. **Тело создания/обновления профиля:** ```json { "name": "Безопасность автоматизированных систем", "description": "Необязательное описание" } ``` --- ## Группы ### `GET /api/groups` Список всех групп. **Ответ:** ```json [ { "id": 1, "name": "ИВТ-21-1", "groupSize": 25, "educationFormId": 1, "educationFormName": "Бакалавриат", "departmentId": 1, "yearStartStudy": 2023, "course": 3, "semester": 6, "specialtyId": 1, "specialtyCode": "09.03.04", "specialtyName": "Программная инженерия", "specialtyProfileId": 2, "specialtyProfileName": "Без профиля", "specialityCode": 1 } ] ``` ### `GET /api/groups/{departmentId}` Список всех групп привязанных к конкретной кафедре. ### `POST /api/groups` Создание группы. ```json { "name": "ИВТ-11", "groupSize": 12, "educationFormId": 1, "departmentId": 1, "yearStartStudy": 2026, "specialtyId": 1, "specialtyProfileId": 2 } ``` `specialtyId` и `specialtyProfileId` обязательны. Поле `specialityCode` сохранено как legacy-alias для старых клиентов и исторически содержит ID записи из `/api/specialties`. Текущий курс вычисляется из `yearStartStudy`. Название группы не является уникальным полем: допускается несколько групп с одинаковым `name`. ### `PUT /api/groups/{id}` Редактирование группы. ```json { "name": "ИВТ-11", "groupSize": 24, "educationFormId": 1, "departmentId": 1, "yearStartStudy": 2026, "specialtyId": 1, "specialtyProfileId": 2 } ``` ### `DELETE /api/groups/{id}` Удаление группы. ### Календарные графики группы | Метод | URL | Назначение | |-------|-----|------------| | `GET` | `/api/groups/{id}/calendar-assignments` | Список назначений графиков группе | | `PUT` | `/api/groups/{id}/calendar-assignments` | Создать или заменить назначение на учебный год | | `DELETE` | `/api/groups/{id}/calendar-assignments/{assignmentId}` | Удалить назначение | **Тело назначения графика:** ```json { "academicYearId": 1, "calendarId": 5 } ``` Назначаемый график должен относиться к тому же учебному году, специальности и профилю, что и группа. --- ## Аудитории ### `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` Список всех дисциплин. ```json { "name": "Физика", "code": null, "departmentId": 1 } ``` ### `GET /api/subjects/{departmentId}` Список всех дисциплин привязанных к кафедре. ### `POST /api/subjects` ```json { "name": "Физика", "code": null, "departmentId": 1 } ``` ### `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` | Внутренняя ошибка сервера |