678 lines
18 KiB
Markdown
678 lines
18 KiB
Markdown
# 🔌 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 <token>`
|
||
|
||
---
|
||
|
||
## Пользователи
|
||
|
||
### `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` | Внутренняя ошибка сервера |
|