Commit Graph
12 Commits
Author SHA1 Message Date
dev 00fbf41efb build(photo-ai): loopback-порт 8081 и раздел про фото-ИИ в README
photo-ai не имел ports, а хостовый 8080 уже занят text-corrector: ручные
проверки из плана попадали не туда. Проброшено 127.0.0.1:8081:8080 — только
loopback, наружу ничего не публикуется.

Заодно исправлен рассинхрон .env.example: там стоял пустой PHOTO_AI_URL с
комментарием «пусто = контейнер photo-ai», из-за чего копирование примера
молча выключало фото-ИИ (в compose дефолт http://photo-ai:8080). Теперь
дефолт указан явно, пустое значение описано как «выключить сервис».
2026-09-28 23:38:39 +03:00
dev 403574fe79 chore(photo-ai): раздел 0 — жёсткие инварианты I1–I7 зафиксированы
План фото-ИИ с восстановлением лиц разбит на этапы; этот коммит закрывает
раздел 0 — семь инвариантов, которые нельзя ломать дальше. Два из них были
нарушены в текущем коде и исправлены здесь.

I5 (мягкие ошибки не сжигают попытки). Раньше любой сбой photo-ai —
503, обрыв сети, таймаут — попадал в общий catch, инкрементил attempts и
через три попытки переводил задание в error. Теперь ошибки разделены:
5xx/429/425/408 и сетевая недоступность возвращают задание в pending без
инкремента attempts, с экспоненциальной паузой 10 с → 300 с; лимит мягких
повторов (по умолчанию 60) даёт одну честную ошибку с понятным текстом.
Таймаут AbortSignal.timeout — жёсткая ошибка с попытками, как и раньше.
Счётчик мягких повторов живёт в памяти процесса и в счётчиках воркера,
метаданные повтора — в audit_log.target (soft_attempt/soft_limit), без
новых колонок. Новый аудит-код photo.job.soft_retry и подпись в audit.js.
Переменные PHOTO_AI_SOFT_MAX_RETRIES, PHOTO_AI_SOFT_BACKOFF_MS,
PHOTO_AI_SOFT_BACKOFF_MAX_MS описаны в .env.example и отдаются в
worker.config в GET /api/photo-jobs/status.

I7 (никаких прямых fs.* по uploads/). runAiEnhance писал результат
fs.writeFileSync в uploads/ и только потом persist в S3; enhanceWithSharp
делал то же через sharp toFile. Оба теперь считают буфер и пишут его
через storage.put — драйвер выбирает сам, локальной копии не остаётся.
Из worker.js убраны require('fs'), require('path') и параметр uploadsDir.

I3 (photo-ai не обязателен). photo_ai_enabled вычислялся внутри
cacheWrap('public-settings'), поэтому после перезапуска с пустым
PHOTO_AI_URL кнопка «🤖 ИИ» оставалась видимой до истечения кэша (60 с),
хотя enhance-ai уже отдавал 503. Флаг вынесен из кэша: он выводится из
PHOTO_AI_URL в памяти процесса и всегда актуален.

Проверено на стенде (журнал — в TODO_PHOTO_FACE_AI.md, раздел 0):
- I1: эталон /enhance снят на 5 фото (3 реальных, 2 синтетических),
  два независимых прогона и прогон после правок совпали байт-в-байт
  (sha256), /health отдаёт ok. Скрипты и эталон — в backups/ (вне git)
- I2: задание с params IS NULL и action='ai' дошло до done при
  attempts=0, результат отдан из S3 (200)
- I3: с пустым PHOTO_AI_URL photo_ai_enabled=false сразу после старта,
  enhance-ai → 503, остальные маршруты API живы
- I4: nvidia-ctk и nvidia-container-runtime на хосте отсутствуют, runtime
  только runc — фото-ИИ поднялся на CPU, /health не падает. Проверка
  PHOTO_AI_DEVICE переносится на приёмку Stage 1 (переменной ещё нет)
- I6: db/ не тронут, состав колонок photo_jobs прежний
- I7: node --check для всех изменённых JS, комментариев в диффе нет,
  весь SQL параметризован

api.smoketest.js: контракт фото-воркера — согласованность
photo_ai_enabled и ai_configured, ключи мягких повторов в worker.config,
503/404 для enhance-ai на несуществующей записи (тест не создаёт реальных
заданий). Вместе с планом и чек-листом этапов.
2026-09-28 23:19:16 +03:00
dev 0e38a280d7 feat(redis): кэш, rate limit, баны IP и pub/sub через Redis
Добавлен сервис redis:7-alpine (AOF, requirepass, maxmemory + allkeys-lru,
healthcheck, том redis-data, порт только на 127.0.0.1) и абстракция redis.js
по образцу storage.js.

Переведено на Redis:
- кэш ответов API и настроек (было Map в памяти), инвалидация по префиксу
  через SCAN + DEL;
- rate limit для api/entry/file — общие счётчики вместо MemoryStore;
- баны IP и счётчики неудачных входа — с TTL, вместо опроса БД каждую минуту;
- кэш сессий (30 с) с invalidateSessions() на каждой мутации users/sessions/
  user_branches, иначе деактивированный пользователь сохранил бы доступ;
- pub/sub для SSE-событий и мгновенного пробуждения фоновых воркеров вместо
  ожидания цикла опроса БД.

Отказоустойчивость: при недоступном Redis все операции уходят в in-memory
backend с той же семантикой, приложение стартует и работает без Redis и
возвращается в Redis автоматически. Первое подключение ограничено по времени
(REDIS_CONNECT_TIMEOUT_MS, 5 с) — node-redis не отклоняет connect() при
недоступном сервере, а повторяет попытки бесконечно.

Добавлены тесты: redis.selftest.js (в т.ч. поведение при недоступном
сервере) и api.smoketest.js (сквозная проверка API, включая инвалидацию
кэша и мгновенную смерть сессии после logout).
2026-09-26 15:26:00 +03:00
dev ddd49707ae feat(storage): S3-совместимое хранилище файлов (SeaweedFS/MinIO) и миграция uploads
- storage.js: абстракция хранилища с драйверами local и s3 (AWS SDK v3),
  ключи объектов совпадают с текущими путями /uploads/<файл>, поэтому схема БД
  и URL не меняются
- docker-compose.yml: сервис s3 (SeaweedFS, том s3-data, API только на loopback),
  переменные S3_*/STORAGE_*, restart unless-stopped для app и db
- docker-compose.minio.yml: оверрайд S3-сервиса на MinIO (образ из своего зеркала)
- server.js/worker.js: чтение и запись файлов только через storage (отдача
  /uploads/*, миниатюры, share-файлы, zip-отчёты, enhance/apply/rollback,
  photo-worker), автосоздание бакета, глобальная персистенция загрузок multer
- бэкап/восстановление и scripts/backup.sh, restore.sh — через scripts/storage-sync.js
- scripts/migrate-to-s3.js: идемпотентная миграция uploads/ в бакет
  (--dry-run, --verify-only, --delete-local)
- админка: блок «Хранилище» в системной информации
- .env.example, README.md, AGENTS.md: описание драйверов, переменных и перехода на S3
2026-09-26 11:08:53 +03:00
dev 3a345cbefd feat: AI photo enhancement (Real-ESRGAN container) + restore original
- new photo-ai service: FastAPI + Real-ESRGAN x2plus on CPU, internal only
- async job queue in server (POST enhance-ai / GET status), 5min timeout, sequential processing
- keep original photo backup (entries.photo_original_path, uploads/.originals), restore-original endpoint
- UI: AI button and restore-original button in enhance modal
- db: photo_original_path column (init.sql, migration.sql, runtime ensure)
2026-09-17 15:37:49 +03:00
dev 0b763e5738 feat(cloudflared): timeout+fallback for WG handshake (WG_HANDSHAKE_TIMEOUT) so tunnel still starts when VPN peer is down 2026-09-13 00:34:43 +03:00
dev 854f2d4650 feat: публикация наружу через Cloudflare Quick Tunnel (cloudflared) 2026-09-12 15:06:56 +03:00
dev f8036fae79 Fix: backup/restore now includes group_photos and entry_photos tables; increase AI request timeout to 120s (configurable via AI_REQUEST_TIMEOUT_MS) 2026-09-11 23:12:16 +03:00
dev 4db02d75bc Add AI-check details modal on worker page and raise backup upload limit 2026-09-11 16:26:30 +03:00
dev 275ed46dfb Add HF model auto-download with configurable AI env, and share link visibility toggles 2026-09-11 13:12:13 +03:00
dev 3850fe35e4 Update project files 2026-09-10 11:26:51 +03:00
dev dd5a2ea288 drop caddy and cloudflared; publish via tailscale funnel; rewrite README 2026-09-08 10:40:20 +03:00