946 lines
31 KiB
Markdown
946 lines
31 KiB
Markdown
# 🔌 REST API
|
||
|
||
Все эндпоинты имеют префикс `/api/`. Ответы возвращаются в формате JSON.
|
||
|
||
---
|
||
|
||
## Аутентификация
|
||
|
||
### `POST /api/auth/login`
|
||
|
||
Вход в систему.
|
||
|
||
**Тело запроса:**
|
||
```json
|
||
{
|
||
"username": "admin",
|
||
"password": "admin"
|
||
}
|
||
```
|
||
|
||
**Успешный ответ (200):**
|
||
```json
|
||
{
|
||
"success": true,
|
||
"message": "OK",
|
||
"token": "eyJhbGciOiJIUzI1NiJ9...",
|
||
"role": "ADMIN",
|
||
"redirect": "/admin/",
|
||
"departmentId": 1,
|
||
"userId": 1
|
||
}
|
||
```
|
||
|
||
Ответ также устанавливает `HttpOnly` cookie `magistr_refresh` для обновления access-токена.
|
||
|
||
**Ошибка (401):**
|
||
```json
|
||
{
|
||
"success": false,
|
||
"message": "Неверное имя пользователя или пароль",
|
||
"token": null,
|
||
"role": null,
|
||
"redirect": null,
|
||
"departmentId": null,
|
||
"userId": null
|
||
}
|
||
```
|
||
|
||
> После получения access JWT клиент должен передавать его в заголовке: `Authorization: Bearer <token>`.
|
||
|
||
Поддерживаемые роли: `ADMIN`, `EDUCATION_OFFICE`, `DEPARTMENT`, `SCHEDULE_VIEWER`, `TEACHER`, `STUDENT`.
|
||
|
||
Redirect по ролям:
|
||
|
||
| Роль | Redirect |
|
||
|------|----------|
|
||
| `ADMIN` | `/admin/` |
|
||
| `EDUCATION_OFFICE` | `/admin/#schedule-view` |
|
||
| `DEPARTMENT` | `/admin/#department-workspace` |
|
||
| `SCHEDULE_VIEWER` | `/admin/#schedule-view` |
|
||
| `TEACHER` | `/teacher/` |
|
||
| `STUDENT` | `/student/` |
|
||
|
||
### `POST /api/auth/refresh`
|
||
|
||
Обновляет access JWT по refresh-cookie. Тело запроса не требуется.
|
||
|
||
**Успешный ответ (200):**
|
||
```json
|
||
{
|
||
"success": true,
|
||
"message": "OK",
|
||
"token": "eyJhbGciOiJIUzI1NiJ9...",
|
||
"role": "ADMIN",
|
||
"redirect": "/admin/",
|
||
"departmentId": 1,
|
||
"userId": 1
|
||
}
|
||
```
|
||
|
||
Refresh-токен ротируется при каждом успешном обновлении, а старый refresh-токен отзывается.
|
||
|
||
### `POST /api/auth/logout`
|
||
|
||
Отзывает текущий refresh-токен и очищает refresh-cookie.
|
||
|
||
**Успешный ответ (200):**
|
||
```json
|
||
{
|
||
"success": true,
|
||
"message": "Выход выполнен"
|
||
}
|
||
```
|
||
|
||
### `GET /api/auth/me`
|
||
|
||
Возвращает текущего пользователя по bearer-токену.
|
||
|
||
```json
|
||
{
|
||
"userId": 1,
|
||
"username": "admin",
|
||
"role": "ADMIN",
|
||
"departmentId": 1
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Пользователи
|
||
|
||
### `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`, `EDUCATION_OFFICE`, `DEPARTMENT`, `SCHEDULE_VIEWER`, `TEACHER` или `STUDENT`
|
||
- `fullName` — обязателен
|
||
- `departmentId` — обязателен
|
||
|
||
### `DELETE /api/users/{id}`
|
||
|
||
Архивирование пользователя. Исторические связи и расписание остаются в БД, но пользователь больше не может войти.
|
||
|
||
### `POST /api/users/{id}/restore`
|
||
|
||
Восстановление архивного пользователя.
|
||
|
||
### `GET /api/users/{id}/department-history`
|
||
|
||
История переводов преподавателя между кафедрами.
|
||
|
||
### `POST /api/users/{id}/department-transfer`
|
||
|
||
Перевод преподавателя на другую кафедру без потери прошлых связей.
|
||
|
||
```json
|
||
{
|
||
"departmentId": 2,
|
||
"validFrom": "2026-06-01",
|
||
"comment": "Перевод на кафедру ВТ"
|
||
}
|
||
```
|
||
|
||
### `GET /api/users/teachers/by-department/{departmentId}?date=2026-06-01`
|
||
|
||
Список преподавателей кафедры на конкретную дату по таблице истории.
|
||
|
||
---
|
||
|
||
## Права ролей на API
|
||
|
||
Скрытие вкладок во frontend не является защитой. Все `/api/**` запросы, кроме `POST /api/auth/login`, `POST /api/auth/refresh` и `POST /api/auth/logout`, проходят через bearer access JWT и `@RequireRoles`.
|
||
|
||
Важные ограничения:
|
||
|
||
- `DEPARTMENT` не может создавать аудитории, кафедры, специальности, группы, пользователей или правила расписания через API;
|
||
- `DEPARTMENT` создаёт и комментирует дисциплины через `/api/department/*`, где кафедра берётся из текущего пользователя;
|
||
- `/api/teacher-subjects` для `DEPARTMENT` разрешает связывать только преподавателей и дисциплины своей кафедры;
|
||
- `SCHEDULE_VIEWER` имеет read-only доступ к просмотру расписаний, справочникам-фильтрам и загруженности.
|
||
|
||
---
|
||
|
||
## Динамическое расписание
|
||
|
||
Новая модель расписания строится из правил (`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 дней. Если у группы нет назначения календарного графика на учебный год даты, расписание для неё возвращается пустым списком. Время пары берётся из базового слота правила, но для конкретной даты может быть заменено субботней или ручной сеткой времени из `/api/admin/time-slots`.
|
||
|
||
**Пример:**
|
||
```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,
|
||
"subgroupName": null,
|
||
"subgroupIds": [],
|
||
"subgroupNames": [],
|
||
"groupIds": [1],
|
||
"groupNames": ["ИВТ-21-1"],
|
||
"activityType": "Т",
|
||
"lessonTypeAcademicHours": 32,
|
||
"consumedLessonTypeAcademicHoursBeforeLesson": 12,
|
||
"remainingLessonTypeAcademicHoursAfterLesson": 18
|
||
}
|
||
]
|
||
```
|
||
|
||
### `GET /api/admin/time-slots`
|
||
|
||
Список временных слотов занятий. Слот принадлежит конкретной сетке времени: базовой, автоматической субботней или ручной.
|
||
|
||
| Метод | URL | Назначение |
|
||
|-------|-----|------------|
|
||
| `GET` | `/api/admin/time-slots` | Список слотов |
|
||
| `GET` | `/api/admin/time-slots/effective?date=2026-05-02` | Эффективные слоты для даты |
|
||
| `POST` | `/api/admin/time-slots` | Создать слот |
|
||
| `PUT` | `/api/admin/time-slots/{id}` | Обновить слот |
|
||
| `DELETE` | `/api/admin/time-slots/{id}` | Удалить слот |
|
||
| `GET` | `/api/admin/time-slots/scopes` | Список сеток времени |
|
||
| `POST` | `/api/admin/time-slots/scopes` | Создать ручную сетку |
|
||
| `PUT` | `/api/admin/time-slots/scopes/{id}` | Переименовать ручную сетку |
|
||
| `DELETE` | `/api/admin/time-slots/scopes/{id}` | Удалить ручную сетку |
|
||
| `GET` | `/api/admin/time-slots/date-assignments` | Ручные назначения дат |
|
||
| `POST` | `/api/admin/time-slots/date-assignments` | Применить ручную сетку к дате |
|
||
| `DELETE` | `/api/admin/time-slots/date-assignments/{id}` | Убрать ручное назначение |
|
||
|
||
**Тело создания/обновления:**
|
||
```json
|
||
{
|
||
"orderNumber": 1,
|
||
"scopeId": 1,
|
||
"startTime": "08:00:00",
|
||
"endTime": "09:30:00",
|
||
"durationMinutes": 90
|
||
}
|
||
```
|
||
|
||
**Создание ручной сетки:**
|
||
```json
|
||
{
|
||
"name": "Праздничная сетка"
|
||
}
|
||
```
|
||
|
||
**Ручное применение сетки к дате:**
|
||
```json
|
||
{
|
||
"date": "2026-05-08",
|
||
"scopeId": 3
|
||
}
|
||
```
|
||
|
||
Базовая сетка применяется по умолчанию. Субботняя сетка применяется автоматически по субботам. Ручные сетки применяются только на датах из `date-assignments`, имеют приоритет над автоматической субботней сеткой и назначаются пользователем из модального окна ячейки редактора календарного графика. В правилах расписания выбираются только базовые слоты; эффективное время пары подставляется при генерации.
|
||
|
||
### Учебные годы, семестры и коды календарного графика
|
||
|
||
| Метод | 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/{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
|
||
}
|
||
```
|
||
|
||
`studyFormId` берётся из общего справочника форм обучения `GET /api/education-forms`; отдельного справочника форм для календарных графиков нет.
|
||
|
||
**Ячейка сетки графика:**
|
||
```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,
|
||
"lectureAcademicHours": 32,
|
||
"laboratoryAcademicHours": 16,
|
||
"practiceAcademicHours": 24,
|
||
"lectureStartWeek": 1,
|
||
"laboratoryStartWeek": 3,
|
||
"practiceStartWeek": 2,
|
||
"groupIds": [1, 2],
|
||
"slots": [
|
||
{
|
||
"dayOfWeek": 1,
|
||
"parity": "BOTH",
|
||
"timeSlotId": 3,
|
||
"subgroupId": null,
|
||
"subgroupIds": [],
|
||
"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}` | Удалить правило |
|
||
|
||
`timeSlotId` должен ссылаться на базовый слот (`scopeApplyMode = DEFAULT`). Субботняя и ручные сетки не выбираются в правиле напрямую.
|
||
|
||
`subgroupIds` можно передавать только для лабораторного слота. Каждая подгруппа должна относиться к одной из групп правила. Если лабораторная проводится у нескольких групп одновременно, в одном слоте можно передать разные подгруппы этих групп, например `[10, 22]`. Для совместимости одиночный `subgroupId` тоже принимается, но новый формат — `subgroupIds`. Для лекций и практик оба поля должны быть пустыми, иначе API вернёт ошибку валидации. В одном слоте нельзя выбрать больше одной подгруппы одной и той же группы.
|
||
|
||
Часы и недели начала задаются отдельно для лекций, лабораторных и практик. Если для типа занятий указан ненулевой лимит часов, в правиле должен быть хотя бы один слот этого типа; если слот типа есть, его лимит часов должен быть больше нуля.
|
||
|
||
### `GET /api/lesson-types`
|
||
|
||
Справочник типов занятий для конструктора правил расписания.
|
||
|
||
**Ответ:**
|
||
```json
|
||
[
|
||
{ "id": 1, "name": "Лекция" },
|
||
{ "id": 2, "name": "Практика" },
|
||
{ "id": 3, "name": "Лабораторная работа" }
|
||
]
|
||
```
|
||
|
||
### `GET /api/schedule/search`
|
||
|
||
Расширенный поиск расписания. В отличие от `GET /api/schedule`, принимает несколько фильтров одновременно.
|
||
|
||
| Параметр | Описание |
|
||
|----------|----------|
|
||
| `startDate` / `endDate` | Обязательный период |
|
||
| `groupId` | Учебная группа |
|
||
| `teacherId` | Преподаватель |
|
||
| `classroomId` | Аудитория |
|
||
| `departmentId` | Кафедра |
|
||
| `subjectId` | Дисциплина |
|
||
| `lessonTypeId` | Тип занятия |
|
||
| `timeSlotId` | Временной слот |
|
||
| `parity` | `BOTH`, `ODD`, `EVEN` |
|
||
|
||
Пример:
|
||
|
||
```http
|
||
GET /api/schedule/search?classroomId=1&startDate=2026-05-20&endDate=2026-05-27
|
||
```
|
||
|
||
Ответ совпадает со структурой `RenderedLessonDto` из `GET /api/schedule`.
|
||
|
||
### Точечные изменения расписания учебного отдела
|
||
|
||
| Метод | URL | Назначение |
|
||
|-------|-----|------------|
|
||
| `GET` | `/api/edu-office/schedule/overrides` | Список точечных изменений |
|
||
| `POST` | `/api/edu-office/schedule/overrides` | Создать перенос, отмену или замену |
|
||
| `PUT` | `/api/edu-office/schedule/overrides/{id}` | Обновить изменение |
|
||
| `DELETE` | `/api/edu-office/schedule/overrides/{id}` | Удалить изменение |
|
||
|
||
```json
|
||
{
|
||
"baseRuleSlotId": 31,
|
||
"lessonDate": "2026-05-21",
|
||
"action": "REPLACE",
|
||
"newClassroomId": 2,
|
||
"newTeacherId": 5,
|
||
"comment": "Замена аудитории и преподавателя"
|
||
}
|
||
```
|
||
|
||
`action=CANCEL` отменяет конкретную пару. `MOVE` и `REPLACE` могут менять аудиторию, преподавателя, формат и временной слот.
|
||
|
||
## Загруженность
|
||
|
||
| Метод | URL | Назначение |
|
||
|-------|-----|------------|
|
||
| `GET` | `/api/workload/teachers` | Загруженность преподавателей |
|
||
| `GET` | `/api/workload/classrooms` | Загруженность аудиторий |
|
||
| `GET` | `/api/workload/departments` | Загруженность кафедр |
|
||
| `GET` | `/api/workload/time-slots` | Загруженность по парам |
|
||
| `GET` | `/api/workload/free-classrooms` | Свободные аудитории на дату и пару |
|
||
|
||
Общие параметры для отчётов: `startDate`, `endDate`, опционально `departmentId`.
|
||
|
||
Пример:
|
||
|
||
```http
|
||
GET /api/workload/teachers?departmentId=1&startDate=2026-05-20&endDate=2026-06-01
|
||
```
|
||
|
||
Ответ:
|
||
|
||
```json
|
||
[
|
||
{
|
||
"id": 2,
|
||
"name": "Петров Препод Петрович",
|
||
"departmentId": 1,
|
||
"departmentName": "Кафедра ИБ",
|
||
"lessonCount": 8,
|
||
"academicHours": 16,
|
||
"occupiedSlotCount": 8
|
||
}
|
||
]
|
||
```
|
||
|
||
## Кабинет кафедры
|
||
|
||
| Метод | URL | Назначение |
|
||
|-------|-----|------------|
|
||
| `GET` | `/api/department/subjects` | Дисциплины текущей кафедры |
|
||
| `POST` | `/api/department/subjects/import` | Загрузка списка дисциплин |
|
||
| `GET` | `/api/department/subjects/{subjectId}/comments` | Комментарии дисциплины |
|
||
| `POST` | `/api/department/subjects/{subjectId}/comments` | Добавить комментарий |
|
||
| `GET` | `/api/department/teachers` | Преподаватели кафедры |
|
||
| `GET` | `/api/department/schedule` | Расписание кафедры |
|
||
|
||
---
|
||
|
||
## Кафедры и специальности
|
||
|
||
### Кафедры
|
||
|
||
| Метод | 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}`
|
||
|
||
Удаление группы.
|
||
|
||
### Подгруппы группы
|
||
|
||
Подгруппы используются только для деления лабораторных занятий. Лекции и практики не принимают `subgroupId` и `subgroupIds`.
|
||
|
||
| Метод | URL | Назначение |
|
||
|-------|-----|------------|
|
||
| `GET` | `/api/subgroups` | Список всех подгрупп |
|
||
| `GET` | `/api/groups/{groupId}/subgroups` | Подгруппы конкретной группы |
|
||
| `POST` | `/api/groups/{groupId}/subgroups` | Создать подгруппу |
|
||
| `PUT` | `/api/groups/{groupId}/subgroups/{id}` | Обновить подгруппу |
|
||
| `DELETE` | `/api/groups/{groupId}/subgroups/{id}` | Удалить подгруппу, если она не используется в расписании |
|
||
|
||
**Тело создания/обновления:**
|
||
```json
|
||
{
|
||
"name": "Подгруппа 1",
|
||
"studentCapacity": 12
|
||
}
|
||
```
|
||
|
||
**Ответ:**
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"groupId": 1,
|
||
"groupName": "ИВТ-21-1",
|
||
"name": "Подгруппа 1",
|
||
"studentCapacity": 12
|
||
}
|
||
```
|
||
|
||
### Календарные графики группы
|
||
|
||
| Метод | 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,
|
||
"building": "Главный корпус",
|
||
"floor": 2,
|
||
"isAvailable": true,
|
||
"equipments": [
|
||
{ "id": 1, "name": "Проектор" },
|
||
{ "id": 4, "name": "Интерактивная доска" }
|
||
]
|
||
}
|
||
]
|
||
```
|
||
|
||
### `POST /api/classrooms`
|
||
|
||
Создание аудитории.
|
||
|
||
```json
|
||
{
|
||
"name": "404 Лаборатория",
|
||
"capacity": 30,
|
||
"building": "Лабораторный корпус",
|
||
"floor": 4,
|
||
"isAvailable": true,
|
||
"equipmentIds": [1, 2, 3]
|
||
}
|
||
```
|
||
|
||
### `PUT /api/classrooms/{id}`
|
||
|
||
Обновление аудитории (partial update).
|
||
|
||
### `DELETE /api/classrooms/{id}`
|
||
|
||
Архивирование аудитории. Архивная аудитория остаётся в историческом расписании, но не выбирается в новых назначениях.
|
||
|
||
### `POST /api/classrooms/{id}/restore`
|
||
|
||
Восстановление архивной аудитории.
|
||
|
||
---
|
||
|
||
## Дисциплины
|
||
|
||
### `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` | Внутренняя ошибка сервера |
|