Commit Graph
11 Commits
Author SHA1 Message Date
dev 2afe676969 feat(notifications): центр уведомлений о системных событиях
Добавлена система уведомлений о системных и фоновых событиях (новые записи
журнала, обработка фото, авто-проверка текста, блокировки IP, бэкапы).

- backend (server.js, worker.js):
  - каталог NOTIFY_TYPES с метаданными и уровнями
  - таблицы notifications и notification_reads в db/init.sql и db/migration.sql
  - SSE-стрим GET /api/notifications/stream через Redis pub/sub с in-memory fallback
  - REST API: список, счётчик непрочитанных, отметка о прочтении, удаление, очистка
  - настройки уведомлений в settings (notify_enabled, notify_retention_days, notify_<тип>)
  - автоматическая очистка старых уведомлений по расписанию
- frontend:
  - колокольчик со счётчиком непрочитанных в шапке (admin.js)
  - страница списка уведомлений public/notifications.html и public/js/notifications.js
  - секция настроек уведомлений в public/settings.html и public/js/settings.js
  - стили для уведомлений в public/admin.css
- тесты и документация:
  - добавлены проверки в api.smoketest.js
  - обновлены README.md и AGENTS.md
2026-09-27 23:34:47 +03:00
dev f31b8deea2 feat(settings): блок «Статус стека» — на чём всё крутится
На странице Настроек появился блок с состоянием стека системы: версии
компонентов, состояние сервисов и нагрузка. Рендерится из нового блока
stack в ответе GET /api/system-info, в том же стиле, что и соседний
блок системной информации (sys-card / sys-item / sys-bar, точки
worker-dot, полосы загрузки и памяти с порогами 70 % и 90 %).

Карточки: Приложение (версия и коммит, Node.js, PID и RSS, аптайм,
куча, сворачиваемый список библиотек), Сервер (ОС, ядро, архитектура и
число ядер, модель CPU, loadavg, память, аптайм, контейнер), База
данных (PostgreSQL и версия, хост, состояние пула), Кэш (драйвер,
версия Redis, ключи, память, попадания и промахи, операций в памяти) и
Хранилище (драйвер, endpoint, бакет или каталог).

- server.js: getStackInfo() — версии из package.json, node_modules и
  public/version.json, ОС из /etc/os-release, определение Docker,
  SHOW server_version, счётчики пула pg, состояние Redis. Считается вне
  cacheWrap, чтобы версии и нагрузка не отдавались из 30-секундного
  кэша; хосты только через URL.hostname, без учётных данных URL
- settings.html: карточка sec-stack и пункт «Стек» в навигации
- settings.js: renderStackInfo(), форматирование аптаймов с
  русскими склонениями, degrade-состояние «нет связи — в памяти»;
  кнопка обновления перезагружает оба блока одним запросом
- admin.css: .sys-inline для точки статуса рядом с текстом
- api.smoketest.js: контракт блока stack и проверка, что в ответе нет
  учётных данных из DATABASE_URL/REDIS_URL
2026-09-27 12:47:04 +03:00
dev 3ecf87146a build(docker): перейти на Node 22 (AWS SDK требует node >=22) 2026-09-27 12:05:55 +03:00
dev 54cf5bbfa4 feat(audit): показывать изменения текста записи по шагам
При сохранении записи журнала (PUT /api/entries/:id) сравнивается
состояние до и после, и в audit_log пишется не только факт правки,
но и сами изменения: пословный дифф текста, статистика добавленных
и удалённых слов, а также смена ФИО, группы и темы модуля.

- diff.js: пословный LCS-дифф без зависимостей, обрезка больших
  текстов, сборка изменений по полям записи, облегчённый target
  для списка аудита
- source правки: manual / ai / ai_manual / ai_revert; журнал шлёт
  edit_source, сервер доверяет явному значению и определяет источник
  по description_ai как запасной вариант
- те же диффы пишутся для автопроверки ИИ (entry.ai.auto-check)
  и отката к оригиналу (entry.ai.revert)
- GET /api/audit отдаёт список без diff, GET /api/audit/:id — полный
  target, чтобы не грузить килобайты текста на каждую строку
- Аудит: колонка «Кто», сводка в таблице, модалка с подсветкой
  удалённого и добавленного текста, «было/стало» для полей
- auth.login теперь пишет user_id, иначе колонка «Кто» показывала
  «система»
- diff.selftest.js: 16 тестов диффа; README и AGENTS обновлены
2026-09-27 11:29:17 +03:00
dev 19676c9b64 test(auth): зафиксировать контракт авторизации в smoke-тесте
Документация долго описывала X-Admin-Token как способ авторизации, хотя его
нет в коде. Расхождение не ловилось ничем: curl-пример в доках ходил на
GET /api/groups, а он публичный (optionalAuth) и отвечает 200 без токена,
то есть авторизацию не проверял вообще.

Добавлены проверки, которые падают при возврате статического токена:
- защищённый маршрут без токена -> 401;
- мусорный X-Auth-Token -> 401;
- X-Admin-Token (значение ADMIN_PASSWORD) -> 401;
- Authorization: Bearer -> 401;
- ADMIN_PASSWORD как токен -> 401;
- позитивный контроль: валидный токен на admin-маршруте -> 200, иначе
  проверки выше проходили бы из-за сломанного роута;
- /api/groups остаётся публичным -> 200 без токена.

Хелпер api() научен принимать произвольные заголовки — иначе X-Admin-Token
и Bearer не отправить. AGENTS.md дополнен описанием контракта и пунктом в
чеклисте безопасности.
2026-09-26 16:08:32 +03:00
dev 87541a5ce8 docs(auth): исправить устаревшее описание авторизации
Документация утверждала, что доступ админский и задаётся заголовком
X-Admin-Token со значением ADMIN_PASSWORD, и что без ADMIN_PASSWORD сервер
не стартует. Ни то, ни другое не верно:

- X-Admin-Token в server.js отсутствует полностью, авторизация держится на
  сессиях: POST /api/auth/login (bcrypt) выдаёт токен, который клиент шлёт
  в X-Auth-Token. Проверено на живом стенде: X-Auth-Token -> 200,
  X-Admin-Token -> 401 на /api/auth/me и /api/users;
- ADMIN_PASSWORD участвует только в ensureFirstAdmin() — создании первого
  админа в пустой БД. Без него сервер пишет предупреждение и стартует;
- роли и филиалы: requireAuth (любой активный), requireAdmin (role=admin,
  самодостаточный), optionalAuth; не-admin ограничены user_branches через
  branchScope/branchWhere — это в доках не описывалось.

Заодно curl-пример проверки авторизации в AGENTS.md вёл на GET /api/groups,
который публичный (optionalAuth) и отвечает 200 без токена, то есть авторизацию
не проверял. Переведён на /api/auth/me.

Секрет Gitea убран из URL remote в ~/.git-credentials (600) — deploy.sh
работает без промпта.
2026-09-26 16:05:47 +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 d943b77f58 chore(deploy): bake commit version into image and add scripts/deploy.sh 2026-09-23 23:01:36 +03:00
dev b7e798b867 Add AI auto-check worker, share link message/link fields, and update journal/settings UI 2026-09-11 10:29:49 +03:00
dev d6e589d2f5 feat: add branches feature, security audit, and multi-branch support 2026-09-09 09:41:07 +03:00