feat(backup): формат бэкапа v2 — audit/уведомления/баны, counts и одноразовый тикет

Валидация и нормализация restore вынесены из server.js в backup-restore.js,
покрыты юнит-тестами backup.selftest.js (30+ проверок, без стенда).

В бэкап добавлены audit_log, notifications, notification_reads, banned_ips,
счётчики counts, метаданные приложения и версия формата (принимаются 1..2).
Тикет бэкапа стал одноразовым: файл удаляется сразу после отдачи,
uploadBackup фильтрует расширения. sessions в бэкап не входит намеренно.

sweepOrphanedUploads учитывает аватары, обложки, оригиналы фото и photo_jobs —
иначе restore сносил файлы сразу после восстановления.
storage.getStream при STORAGE_LOCAL_FALLBACK читает локальный файл, а не S3.
This commit is contained in:
dev
2026-10-04 01:36:58 +03:00
parent 32aabe9a80
commit 76d36e5c35
5 changed files with 698 additions and 396 deletions
+13 -3
View File
@@ -155,10 +155,15 @@ This document defines how AI agents should work with the WhatIDo codebase. Follo
- Cert: app generates self-signed cert at build (`certs/cert.pem`), mounted into tailscale container
### 8. Backup / Restore
- **Admin UI**: `/api/backup` (download tar.gz), `/api/restore` (upload tar.gz)
- **Admin UI**: `POST /api/backup` → тикет + `GET /api/backup/:token` (разовая ссылка, 30 мин), `POST /api/restore` (upload `.tar.gz`)
- **Scripts**: `scripts/backup.sh`, `scripts/restore.sh` (host-level)
- Backup format: `data.json` (all tables) + `uploads/` directory
- Restore validates all data, resets sequences, sweeps orphans
- Формат архива: `tar.gz` с `data.json` + `uploads/`. Версия формата — `BACKUP_FORMAT_VERSION` в `backup-restore.js` (сейчас `2`), принимаются версии `1..2`; версия пишется в `data.json.version` и возвращается в ответе `POST /api/backup` и `POST /api/restore`
- `data.json` содержит `version`, `created_at`, `app` (версия/коммит), `counts` (строки по таблицам + `files`) и сами данные. Таблицы перечислены в `BACKUP_TABLES` — **при добавлении таблицы править её и в `buildBackupArchive`, и здесь**
- `sessions` в бэкап **не входит** намеренно: после restore все токены должны умереть. `audit_log`, `notifications`, `notification_reads`, `banned_ips` — входят
- Файлы: `storage.downloadAll` кладёт в архив всё, кроме регенерируемых `.thumbs/` и `.cache/`; `.originals/` (оригиналы фото до ИИ-обработки) **входят** и восстанавливаются через `uploadTree`
- Restore: валидация всего через `normalizeRestoreData` (`backup-restore.js`), транзакция с `DELETE` в FK-безопасном порядке → `INSERT` → `setval` по `BACKUP_SEQUENCE_TABLES` → файлы → `sweepOrphanedUploads()` → `loadBans()` → `invalidateAll()`
- **Колонки, которые normalizeRestoreData обязана сохранять**: `groups.deleted_at`/`purge_at`, `entries.purge_at`. Потеря `deleted_at` воскрешает мягко удалённые группы как активные — это не «мелочь», а порча данных
- `sweepOrphanedUploads()` считает ссылками фото из `entries.photo_path`/`photo_original_path`, `project_files.path`, `group_photos`, `entry_photos`, `student_photos`, `modules.photo_path`, `students.photo_path`, `groups.cover_path`, `photo_jobs.before_path`/`after_path`, `settings.system_logo`. Новая колонка с путём к файлу → добавить сюда, иначе sweep снесёт файл сразу после restore
---
@@ -235,6 +240,10 @@ node redis.selftest.js # unit + degradation, ne
node api.smoketest.js # e2e, needs running stack
docker compose exec redis redis-cli -a "$REDIS_PASSWORD" --no-auth-warning INFO
docker compose stop redis && node api.smoketest.js # app must keep working in-memory
# Backup checks
node backup.selftest.js # unit, no stack needed
curl -sk -X POST https://127.0.0.1:3443/api/backup -H "X-Auth-Token: $TOKEN" # -> counts по всем таблицам
docker compose start redis # app reconnects on its own
# Audit diff checks
@@ -290,6 +299,7 @@ guards against.
| `redis.js` | Redis abstraction: cache, counters, rate-limit store, pub/sub, in-memory fallback |
| `redis.selftest.js` | Self-tests for `redis.js`, including behaviour with Redis unavailable |
| `diff.js` / `diff.selftest.js` | Word-level text diff and audit change payload; self-tests |
| `backup-restore.js` / `backup.selftest.js` | Backup format version, `normalizeRestoreData` validation of restore payloads, backup table lists; self-tests |
| `api.smoketest.js` | End-to-end API smoke test against a running stack |
| `worker.js` | Background AI auto-check workers: entry messages, lesson-report template check, photo enhance |
| `db/init.sql` | Initial schema (runs on fresh DB) |