Задача Егора
This commit is contained in:
116
docs/API.md
116
docs/API.md
@@ -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` | Внешняя БД или обязательная инфраструктура временно недоступна |
|
||||
|
||||
Reference in New Issue
Block a user