Files
ros_control/docs/changes/016-unique-ids-and-event-log/summary.md
T
ayurishchevandClaude Sonnet 5 26dd1f8be4 Уникальные 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>
2026-09-19 17:01:52 +03:00

6.1 KiB
Raw Blame History

Итоги: 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 проверены отдельным тестовым объектом; первый штатный бэкап проверит их на реальных файлах.
  • Журнал не ротируется (растёт только при изменениях, опрос пишет лишь смены состояния); срок хранения/очистка — при необходимости отдельной доработкой.
  • Пробные записи проверки (создание/удаление тестового устройства и группы) остались в журнале как обычные события.