Files
magistr/docs/API.md

38 KiB
Raw Blame History

🔌 REST API

Все эндпоинты имеют префикс /api/. Ответы возвращаются в формате JSON.

Необработанные ошибки проходят через единый GlobalExceptionHandler. Для 400, 404 и 500 используется общий JSON-формат:

{
  "timestamp": "2026-05-27T19:47:54",
  "status": 400,
  "error": "Некорректный запрос",
  "message": "Некорректные параметры запроса",
  "path": "/api/schedule"
}

Контроллеры, у которых исторически есть собственная обработка ошибок, могут возвращать более короткий объект с полем message.


Аутентификация

POST /api/auth/login

Вход в систему.

Тело запроса:

{
  "username": "admin",
  "password": "admin"
}

Успешный ответ (200):

{
  "success": true,
  "message": "OK",
  "token": "eyJhbGciOiJIUzI1NiJ9...",
  "role": "ADMIN",
  "redirect": "/admin/",
  "departmentId": 1,
  "userId": 1
}

Ответ также устанавливает HttpOnly cookie magistr_refresh для обновления access-токена.

Ошибка (401):

{
  "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):

{
  "success": true,
  "message": "OK",
  "token": "eyJhbGciOiJIUzI1NiJ9...",
  "role": "ADMIN",
  "redirect": "/admin/",
  "departmentId": 1,
  "userId": 1
}

Refresh-токен ротируется при каждом успешном обновлении, а старый refresh-токен отзывается.

POST /api/auth/logout

Отзывает текущий refresh-токен и очищает refresh-cookie.

Успешный ответ (200):

{
  "success": true,
  "message": "Выход выполнен"
}

GET /api/auth/me

Возвращает текущего пользователя по bearer-токену.

{
  "userId": 1,
  "username": "admin",
  "role": "ADMIN",
  "departmentId": 1
}

departmentId присутствует в ответе всегда, но может быть null для пользователей без привязки к кафедре.


Пользователи

GET /api/users

Список всех пользователей.

Ответ:

[
  { "id": 1, "username": "admin", "role": "ADMIN", "fullName": "Иванов Админ Иванович", "jobTitle": "Доцент", "departmentName": "Кафедра ИБ", "departmentId": 1, "status": "ACTIVE" },
  { "id": 2, "username": "teacher1", "role": "TEACHER", "fullName": "Петров Препод Петрович", "jobTitle": "Профессор", "departmentName": "Кафедра ВТ", "departmentId": 2, "status": "ACTIVE" }
]

UserResponse единый для списков пользователей, списков преподавателей и ответов создания/восстановления. Поля departmentName, departmentId и status не выводятся только если равны null.

GET /api/users/teachers

Список только преподавателей (роль TEACHER).

GET /api/users/teachers/{departmentId}

Список преподавателей привязанных к конкретной кафедре (роль TEACHER, код кафедры departmentId). Ответ использует ту же структуру UserResponse, что и GET /api/users.

Выборка учитывает активные записи teacher_department_assignments на текущую дату и legacy-привязку users.department_id. Роль DEPARTMENT может запрашивать только свою кафедру.

POST /api/users

Создание пользователя.

Тело запроса:

{
  "username": "teacher1",
  "password": "password",
  "role": "TEACHER",
  "fullName": "Test Teacher",
  "jobTitle": "Proffessor",
  "departmentId": 1
}

Валидация:

  • username — обязателен и уникален
  • password — минимум 8 символов
  • roleADMIN, 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

Перевод преподавателя на другую кафедру без потери прошлых связей.

{
  "departmentId": 2,
  "validFrom": "2026-06-01",
  "comment": "Перевод на кафедру ВТ"
}

GET /api/users/teachers/by-department/{departmentId}?date=2026-06-01

Список преподавателей кафедры на конкретную дату по таблице истории и legacy-привязке users.department_id. Роль DEPARTMENT может запрашивать только свою кафедру.


Заявки на создание преподавателей

Администратор просматривает и обрабатывает заявки кафедр на создание новых преподавателей.

Метод URL Назначение
GET /api/teacher-requests?status=PENDING Список заявок, опционально с фильтром статуса
POST /api/teacher-requests/{id}/approve Одобрить заявку, скорректировать данные и создать преподавателя
POST /api/teacher-requests/{id}/reject Отклонить заявку

Тело одобрения:

{
  "departmentId": 2,
  "username": "teacher.new",
  "password": "secure-pass",
  "fullName": "Новый Преподаватель",
  "jobTitle": "Доцент",
  "reviewComment": "Данные проверены"
}

Пароль задаёт только администратор при одобрении заявки. Сама заявка пароль не хранит.

Тело отклонения:

{
  "reviewComment": "Нужно уточнить ФИО"
}

Права ролей на 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.

Пример:

GET /api/schedule?groupId=1&startDate=2026-04-27&endDate=2026-05-03

Ответ:

[
  {
    "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} Убрать ручное назначение

Тело создания/обновления:

{
  "orderNumber": 1,
  "scopeId": 1,
  "startTime": "08:00:00",
  "endTime": "09:30:00",
  "durationMinutes": 90
}

Создание ручной сетки:

{
  "name": "Праздничная сетка"
}

Ручное применение сетки к дате:

{
  "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} Удалить код активности

Тело создания/обновления кода активности:

{
  "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 Полное сохранение дневной сетки
GET /api/admin/academic-calendars/{id}/subjects Дисциплины графика по номерам учебных семестров
PUT /api/admin/academic-calendars/{id}/subjects Полная замена привязок дисциплин графика

Тело создания/обновления графика:

{
  "title": "09.03.04 очная форма 2025-2026",
  "academicYearId": 1,
  "specialtyId": 2,
  "specialtyProfileId": 3,
  "studyFormId": 1,
  "courseCount": 4
}

studyFormId берётся из общего справочника форм обучения GET /api/education-forms; отдельного справочника форм для календарных графиков нет. courseCount должен быть в диапазоне 1..8.

Ячейка сетки графика:

{
  "calendarId": 1,
  "courseNumber": 1,
  "date": "2025-09-01",
  "weekNumber": 1,
  "dayOfWeek": 1,
  "activityTypeId": 1
}

Привязка дисциплин к графику:

[
  { "semesterNumber": 1, "subjectId": 1 },
  { "semesterNumber": 1, "subjectId": 2 },
  { "semesterNumber": 2, "subjectId": 3 }
]

semesterNumber — номер учебного семестра внутри графика: для 4 курсов доступны значения 1..8. API принимает только существующие неархивные дисциплины из /api/subjects, не допускает дубли одной дисциплины в одном семестре и возвращает сохранённые записи с subjectName, subjectCode и departmentId.

POST /api/admin/schedule-rules

Создание правила динамического расписания.

{
  "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

Справочник типов занятий для конструктора правил расписания.

Ответ:

[
  { "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

Если указан только teacherId без groupId и departmentId, поиск строит расписание преподавателя напрямую и не обходит все группы. Широкий поиск без groupId и departmentId разрешён только до 50 активных групп; при большем количестве групп API вернёт 400 с просьбой уточнить группу или кафедру.

Пример:

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} Удалить изменение
{
  "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.

Пример:

GET /api/workload/teachers?departmentId=1&startDate=2026-05-20&endDate=2026-06-01

Ответ:

[
  {
    "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 Преподаватели кафедры
POST /api/department/teachers/{teacherId}/assignments Добавить существующего преподавателя на кафедру
GET /api/department/teacher-requests Заявки кафедры на создание преподавателей
POST /api/department/teacher-requests Создать заявку на нового преподавателя
GET /api/department/schedule Расписание кафедры

GET /api/department/teachers возвращает актуальных преподавателей кафедры по teacher_department_assignments и дополнительно учитывает старую привязку users.department_id, чтобы не терять преподавателей без записи в истории назначений.

POST /api/department/teachers/{teacherId}/assignments создаёт дополнительную открытую связь преподавателя с кафедрой (is_primary=false). Для роли DEPARTMENT кафедра берётся из текущего пользователя, администратор может передать departmentId.

{
  "departmentId": 2,
  "comment": "Совместительство"
}

POST /api/department/teacher-requests принимает логин, ФИО, должность и комментарий. Повторная pending-заявка с тем же логином запрещена.

{
  "username": "teacher.new",
  "fullName": "Новый Преподаватель",
  "jobTitle": "Доцент",
  "comment": "Нужен для дисциплин кафедры"
}

Кафедры и специальности

Кафедры

Метод URL Назначение
GET /api/departments Список кафедр
POST /api/departments Создать кафедру
PUT /api/departments/{id} Обновить кафедру
DELETE /api/departments/{id} Удалить кафедру

Тело создания/обновления:

{
  "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} Удалить профиль

Тело создания/обновления:

{
  "specialityName": "Программная инженерия",
  "specialityCode": "09.03.04"
}

При создании специальности автоматически создаётся профиль Без профиля.

Тело создания/обновления профиля:

{
  "name": "Безопасность автоматизированных систем",
  "description": "Необязательное описание"
}

Группы

GET /api/groups

Список групп, доступных для выбора в текущем учебном контуре. Группы, завершившие обучение по назначенному календарному графику, и архивные группы не возвращаются по умолчанию.

Параметр includeArchived=true возвращает все группы, включая архивные и завершившие обучение.

Ответ:

[
  {
    "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,
    "status": "ACTIVE",
    "active": true,
    "studyState": "ACTIVE",
    "studyStateName": "Активна"
  }
]

GET /api/groups/{departmentId}

Список групп выбранной кафедры, доступных для текущих рабочих сценариев. Завершившие обучение и архивные группы исключаются.

POST /api/groups

Создание группы.

{
  "name": "ИВТ-11",
  "groupSize": 12,
  "educationFormId": 1,
  "departmentId": 1,
  "yearStartStudy": 2026,
  "specialtyId": 1,
  "specialtyProfileId": 2
}

specialtyId и specialtyProfileId обязательны. Поле specialityCode сохранено как legacy-alias для старых клиентов и исторически содержит ID записи из /api/specialties. Текущий курс вычисляется из yearStartStudy, но не опускается ниже 0, если обучение ещё не началось.

Поле active показывает, можно ли выбирать группу в текущих рабочих сценариях. studyState принимает значения ACTIVE, NOT_STARTED, GRADUATED, INACTIVE, ARCHIVED.

Название группы не является уникальным полем: допускается несколько групп с одинаковым name.

PUT /api/groups/{id}

Редактирование группы.

{
  "name": "ИВТ-11",
  "groupSize": 24,
  "educationFormId": 1,
  "departmentId": 1,
  "yearStartStudy": 2026,
  "specialtyId": 1,
  "specialtyProfileId": 2
}

DELETE /api/groups/{id}

Архивирование группы. Запись остаётся в истории, поэтому расписание за прошлые даты не теряет связь с группой.

POST /api/groups/{id}/restore

Восстановление архивной группы.

Подгруппы группы

Подгруппы используются только для деления лабораторных занятий. Лекции и практики не принимают 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} Удалить подгруппу, если она не используется в расписании

Тело создания/обновления:

{
  "name": "Подгруппа 1",
  "studentCapacity": 12
}

Ответ:

{
  "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} Удалить назначение

Тело назначения графика:

{
  "academicYearId": 1,
  "calendarId": 5
}

Ответ назначения:

{
  "id": 12,
  "groupId": 1,
  "groupName": "ИВТ-21-1",
  "academicYearId": 1,
  "academicYearTitle": "2025-2026",
  "calendarId": 5,
  "calendarTitle": "09.03.04 очная форма 2025-2026",
  "subjects": [
    {
      "id": 44,
      "calendarId": 5,
      "semesterNumber": 1,
      "subjectId": 1,
      "subjectName": "Высшая математика",
      "subjectCode": "Б1.О.01",
      "departmentId": 1
    }
  ]
}

Назначаемый график должен относиться к тому же учебному году, специальности, профилю и форме обучения, что и группа.


Аудитории

GET /api/classrooms

Список аудиторий с привязанным оборудованием.

Ответ:

[
  {
    "id": 1,
    "name": "101 Ленинская",
    "capacity": 120,
    "building": "Главный корпус",
    "floor": 2,
    "isAvailable": true,
    "equipments": [
      { "id": 1, "name": "Проектор" },
      { "id": 4, "name": "Интерактивная доска" }
    ]
  }
]

POST /api/classrooms

Создание аудитории.

{
  "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

Список всех дисциплин.

{ 
  "name": "Физика",
  "code": null,
  "departmentId": 1
}

GET /api/subjects/{departmentId}

Список всех дисциплин привязанных к кафедре.

POST /api/subjects

{ 
  "name": "Физика",
  "code": null,
  "departmentId": 1
}

DELETE /api/subjects/{id}

Удаление дисциплины.


Оборудование

GET /api/equipments

Список всего оборудования.

POST /api/equipments

{ "name": "3D-принтер" }

DELETE /api/equipments/{id}

Удаление оборудования.


Формы обучения

GET /api/education-forms

Список форм обучения.

Ответ:

[
  { "id": 1, "name": "Бакалавриат" },
  { "id": 2, "name": "Магистратура" }
]

POST /api/education-forms

{ "name": "Аспирантура" }

DELETE /api/education-forms/{id}

Удаление формы обучения. Невозможно, если к ней привязаны группы или календарные учебные графики.


Привязка «Преподаватель ↔ Дисциплина»

GET /api/teacher-subjects

Список всех привязок.

Ответ:

[
  {
    "userId": 2,
    "userName": "Тестовый преподаватель",
    "subjectId": 1,
    "subjectName": "Высшая математика"
  }
]

POST /api/teacher-subjects

{
  "userId": 2,
  "subjectId": 3
}

DELETE /api/teacher-subjects

{
  "userId": 2,
  "subjectId": 3
}

Управление тенантами (Базы данных)

GET /api/database/status

Статус текущего подключения (определяется по домену запроса).

Ответ:

{
  "tenant": "default",
  "connected": true,
  "configured": true,
  "name": "Default",
  "url": "jdbc:postgresql://db:5432/app_db"
}

GET /api/database/tenants

Список всех тенантов.

POST /api/database/tenants

Добавление нового тенанта.

{
  "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

Тест подключения к произвольной БД (без регистрации тенанта).

{
  "url": "jdbc:postgresql://host:5432/testdb",
  "username": "user",
  "password": "pass"
}

Ответ:

{
  "success": true,
  "message": "Подключение успешно!"
}

Коды ответов

Код Описание
200 Успех
400 Ошибка валидации или некорректные параметры запроса
401 Неверные учётные данные
404 Ресурс / тенант не найден
500 Внутренняя ошибка сервера