Задача Егора

This commit is contained in:
dipatrik10
2026-07-16 22:56:43 +03:00
parent 85f61436b6
commit 3f685d3e22
33 changed files with 3777 additions and 279 deletions

View File

@@ -1,6 +1,7 @@
# 🔌 REST API
Все эндпоинты имеют префикс `/api/`. Ответы возвращаются в формате JSON.
Все прикладные эндпоинты имеют префикс `/api/`. Служебные проверки Kubernetes доступны
под `/actuator/health/`. Ответы возвращаются в формате JSON.
Необработанные ошибки проходят через единый `GlobalExceptionHandler`. Для `400`, `404` и `500` используется общий JSON-формат:
@@ -18,6 +19,53 @@
---
## Служебные проверки состояния
Эти endpoints предназначены для Kubernetes kubelet, не требуют bearer-токен и не зависят
от tenant-домена в заголовке `Host`. Из Actuator наружу опубликован только `health`, а
состав компонентов, домены, JDBC URL и credentials в ответах скрыты.
### `GET /actuator/health/liveness`
Проверяет только жизнеспособность процесса. Недоступность tenant-БД не меняет liveness и
не должна создавать цикл перезапусков pod.
**Ответ работающего процесса (200):**
```json
{
"status": "UP"
}
```
### `GET /actuator/health/readiness`
Разрешает направлять трафик в pod только после успешных миграций и свежей успешной
проверки соединения со всеми обязательными tenant-БД. Пустая конфигурация, H2-заглушка,
ошибка чтения tenant-конфигурации, незавершённая или неуспешная миграция, недоступное либо
просроченное соединение делают pod неготовым.
**Готов (200):**
```json
{
"status": "UP"
}
```
**Не готов (503):**
```json
{
"status": "DOWN"
}
```
`UP` и `DOWN` — стандартные машинные значения протокола Spring Boot Actuator, а не
пользовательские сообщения интерфейса.
---
## Аутентификация
### `POST /api/auth/login`
@@ -1090,6 +1138,8 @@ GET /api/workload/teachers?departmentId=1&startDate=2026-05-20&endDate=2026-06-0
## Управление тенантами (Базы данных)
Все endpoints раздела доступны только пользователю с ролью `ADMIN`.
### `GET /api/database/status`
Статус текущего подключения (определяется по домену запроса).
@@ -1107,11 +1157,23 @@ GET /api/workload/teachers?departmentId=1&startDate=2026-05-20&endDate=2026-06-0
### `GET /api/database/tenants`
Список всех тенантов.
Список всех тенантов. Пароль в ответ не включается.
```json
[
{
"name": "СВФУ",
"domain": "swsu",
"url": "jdbc:postgresql://db-host:5432/swsu_db",
"username": "dbuser",
"connected": true
}
]
```
### `POST /api/database/tenants`
Добавление нового тенанта.
Создание нового или обновление существующего тенанта по `domain`.
```json
{
@@ -1123,19 +1185,56 @@ GET /api/workload/teachers?departmentId=1&startDate=2026-05-20&endDate=2026-06-0
}
```
`domain` приводится к нижнему регистру и должен быть одной DNS-меткой длиной от 1 до
63 символов. `url` обязателен и должен начинаться с `jdbc:`. Если `name` пуст, вместо
него используется нормализованный `domain`.
**Логика:**
1. Создаёт HikariCP пул для нового тенанта
2. Запускает Flyway миграции на его БД
3. Обновляет внешний Kubernetes Secret `tenants-secret`
1. Создаёт временный HikariCP pool, ещё не доступный маршрутизатору.
2. Открывает соединение и явно проверяет его готовность.
3. Выполняет Flyway-валидацию и миграции tenant-БД.
4. Читает актуальный `tenants-secret`, применяет только upsert запрошенного `domain` и
выполняет условный `PUT` с прочитанным Kubernetes `resourceVersion`.
5. При конфликте повторно читает Secret и заново применяет свою мутацию с ограниченным
retry/backoff; неизменившаяся конфигурация не записывается повторно.
6. Одной атомарной публикацией заменяет связку `TenantConfig + DataSource`.
7. Передаёт прежний pool на отложенное закрытие после завершения активных запросов
либо по истечении защитного таймаута.
Backend соединяется с Kubernetes API только через проверенный service-account CA и
hostname verification. Если безопасно сохранить tenant-конфигурацию не удалось, операция
не возвращается как успешная. Значения credentials никогда не включаются в ответ или лог
Kubernetes updater.
При ошибке credentials, проверки соединения, Flyway или сохранения Secret временный pool
закрывается, а прежнее подключение продолжает обслуживать запросы. Если Kubernetes
подтвердил запись, но последующая локальная активация завершилась ошибкой, backend
восстанавливает прежний снимок только пока Secret сохраняет `resourceVersion` этой записи.
Более новое изменение другого pod не перезаписывается. Неопределённый сетевой результат
сначала сверяется повторным чтением и не создаёт основания для небезопасной компенсации.
Ошибка lifecycle возвращается как безопасный русский ответ без JDBC/Flyway details:
```json
{
"success": false,
"message": "Не удалось выполнить миграции базы данных тенанта"
}
```
HTTP-статусы: `400` для некорректного payload и `503` для ошибки подключения, миграции,
персистенции или активации.
Вне Kubernetes обновление Secret пропускается, поэтому добавленный через API tenant живёт
только до перезапуска процесса. Для постоянной локальной конфигурации используется
неотслеживаемый файл `backend/tenants.json`.
### `DELETE /api/database/tenants/{domain}`
Удаление тенанта.
Удаление тенанта. Backend читает актуальный Secret, удаляет только запрошенный `domain` и
выполняет условный `PUT` по `resourceVersion`, затем атомарно исключает tenant из локальной
маршрутизации и передаёт pool на отложенное закрытие. При отказе локального удаления
компенсация также допускается только для подтверждённой версии и не затирает более новое
изменение другого pod. Неизвестный `domain` возвращает `404`, lifecycle-ошибка — `503`.
### `POST /api/database/test`
@@ -1166,5 +1265,8 @@ Kubernetes updater.
| `200` | Успех |
| `400` | Ошибка валидации или некорректные параметры запроса |
| `401` | Неверные учётные данные |
| `403` | Недостаточно прав для операции |
| `404` | Ресурс / тенант не найден |
| `409` | Конфликт бизнес-инвариантов или конкурентного изменения |
| `500` | Внутренняя ошибка сервера |
| `503` | Внешняя БД или обязательная инфраструктура временно недоступна |