Уникальные ID сущностей, журнал событий и его UI с ротацией и очисткой
Глобально уникальные ID (docs/changes/016): - у устройств, групп, задач, резервных копий и записей журнала ID вида <префикс>_<uuid7> (dev_, grp_, job_, bkp_, evt_): типы не пересекаются, внутри типа ID не повторяются и сортируются по времени; - миграция БД v1 заменяет числовые ID с пересчётом ссылок (копия файла БД перед миграцией, сверка числа строк, одна транзакция); - резервная копия = пара файлов с одним bkp_ ID, файлы в S3 получают метаданные backup-id/device-id; синхронизация метаданных с бакетом; - журнал событий в БД (events): создание/изменение/удаление, задачи, бэкапы, смена online/offline, вход в UI; API чтения GET /api/v1/events. Журнал в UI, ротация и очистка (docs/changes/017): - страница «Журнал»: фильтры, подгрузка «Показать ещё», окно записи; - настройки ротации (срок и максимум записей) хранятся в БД (схема v2), ротация при старте, раз в час и после сохранения настроек; - очистка журнала только через окно с паролем пользователя, блокировка после 5 неверных попыток, остаётся запись о факте очистки; через API очистки нет. Тесты: 20 из 20. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1e5fb17755
commit
26dd1f8be4
43 files changed
+1677
-157
No files matched your search
@@ -0,0 +1,27 @@
|
||||
# Итоги: 016 — глобально уникальные ID и журнал событий
|
||||
|
||||
## Сделано
|
||||
- **ID** (`app/ids.py`): у каждой сущности — `<префикс>_<uuid7>`: `dev_` устройство, `grp_` группа, `job_` задача, `bkp_` резервная копия, `evt_` запись журнала.
|
||||
Типы не пересекаются по построению (префикс); внутри типа UUIDv7 = время (мс) + 12-битный счётчик + 62 случайных бита, строго возрастает внутри процесса, сортировка по ID = по времени создания;
|
||||
ID не переиспользуются (в отличие от прежних `max(id)+1`); PRIMARY KEY в каждой таблице; ID чужого типа в пути/запросе → 404 с пояснением, числовые ID больше не существуют.
|
||||
- **Журнал событий** (`events`, `app/services/events.py`): каждая запись со своим `evt_` ID, ссылкой на ID сущности, актором (`ui:<пользователь>` / `api` / `poller` / `system`) и данными.
|
||||
Типы: `device.created|updated|deleted|online|offline`, `group.created|renamed|deleted`, `devices.moved`, `job.created|started|done|failed`, `backup.created|done|failed|imported|deleted`, `auth.login|failed`, `system.migrated`.
|
||||
В `online/offline` пишутся только смены состояния; пароли в журнал не попадают. API: `GET /api/v1/events` (фильтры `entity_id`, `type`, `device_id`, `job_id`, `before`, `limit`), `GET /api/v1/events/{id}`. UI-страницы журнала нет (нужен макет).
|
||||
- **Резервные копии**: одна копия = пара файлов с одним `bkp_` ID; строка хранит `job_id` задачи и снимок имени устройства; файлы загружаются в S3 с метаданными `backup-id` и `device-id`;
|
||||
файлы бакета без строки получают ID автоматически (при старте и при просмотре списка), копии без файлов помечаются удалёнными (`deleted_at`), строка и ID остаются в истории. В API списка файлов есть `backup_id`, в UI ID виден в подсказке.
|
||||
- **Миграция** (`app/migrations.py`, `PRAGMA user_version` 0→1): числовые ID заменены везде с пересчётом ссылок, порядок записей сохранён (время в ID берётся из `created_at`), выполняется одной транзакцией, сверяет число строк, перед этим создаёт копию БД (`ros_control.db.bak-<метка>` в томе).
|
||||
- **API/UI**: все ID — строки; в таблице «Задачи» колонка «ID» показывает последние 8 символов (полный — в подсказке) — единственное видимое отличие от утверждённого макета.
|
||||
|
||||
## Проверено
|
||||
- `pytest`: 17 из 17 (12 прежних обновлены, 5 новых: уникальность 50 тыс. ID и типы, миграция из старой схемы, журнал событий, API журнала и валидация ID, связь бэкапов с бакетом).
|
||||
- Репетиция на копии боевой БД, затем боевая миграция: 4 группы, 6 устройств, 32 задачи, 23 бэкапа — число строк, связи «устройство → группа → задачи → бэкапы» и порядок сохранены; 70 ID, все уникальны; повторный запуск ничего не меняет.
|
||||
- Сверка с бакетом: 4 копии (08:26–09:38), чьих файлов уже нет в бакете, помечены удалёнными; все 24 файла в бакете связаны с 12 активными копиями.
|
||||
- Живой журнал: создание тестового устройства (актор `api`), уход в offline (актор `poller`), удаление; числовой ID и ID чужого типа → 404. Полный сценарий в браузере на мигрированных данных без ошибок JS.
|
||||
- S3: объект загружается с метаданными, значения совпадают. Yandex Object Storage возвращает ключи в виде `Backup-Id`, `Device-Id` — при чтении сравнивать без учёта регистра. Тестовый объект удалён.
|
||||
|
||||
## Оговорки
|
||||
- **Ломающее изменение**: старые curl-скрипты с числовыми ID не работают. Откат — из `ros_control.db.bak-*` в томе и предыдущего коммита.
|
||||
- Уже лежащие в бакете объекты метаданных S3 не получили (переписывать объекты не стали): связь с ними — через БД.
|
||||
- Полный бэкап на реальном устройстве после изменения не запускался (создаёт файлы, `.rsc` с секретами) — метаданные S3 проверены отдельным тестовым объектом; первый штатный бэкап проверит их на реальных файлах.
|
||||
- Журнал не ротируется (растёт только при изменениях, опрос пишет лишь смены состояния); срок хранения/очистка — при необходимости отдельной доработкой.
|
||||
- Пробные записи проверки (создание/удаление тестового устройства и группы) остались в журнале как обычные события.
|
||||
Reference in new issue
Block a user