Добавлена сущность «что прошли на занятии»:
- lesson_reports (init.sql + migration.sql + ensureLessonReportsTable)
- GET/POST /api/lesson-reports, PUT/DELETE /api/lesson-reports/:id
с branchScope, уникальностью (group_id, lesson_date) и лимитом текста
- уведомление lesson.report (NOTIFY_TYPES + настройка + иконка)
- кэш-префикс lessons: + инвалидация stats:/dashboard:
- восстановление lesson_reports в normalizeRestoreData
- блок recent_lessons в /api/dashboard
Фронтенд:
- public/lessons.html + public/js/lessons.js — список с фильтрами и правкой
- openLessonModal в admin.js — общая модалка из журнала и дашборда
- кнопки в журнале и быстрые действия дашборда
Также исправлен сдвиг индексов параметров в notificationsScope —
$1 уходил повторно в список филиалов из-за params.push без смещения.
Воркер и сервер принимают параметры ИИ-обработки фото: модель апскейла
(x2plus / general-x4v3 / animevideo-v3), режим лиц (off / face / all),
модель лиц (gfpgan / codeformer) и strength. Пустое тело запроса ведёт себя
как раньше: action='ai', params=NULL (инвариант I2).
worker.js:
- таймаут выбирается по params.face: PHOTO_AI_TIMEOUT_MS для апскейла,
PHOTO_AI_FACE_TIMEOUT_MS (600000) для face-режима
- runAiEnhance шлёт model/face/face_model/strength и понимает оба
контракта: JSON с image_base64 и сырой image/jpeg старого сервиса
- тело не-2xx ответа больше не выбрасывается: readErrorBody() добавляет
причину к сообщению, иначе оператор видит «ИИ-сервис ответил 400» без
объяснения
- applyResult пишет в аудит model/face/face_model/device/faces_found/
elapsed_ms/warnings и выбирает текст уведомления по факту режима;
warnings видны оператору, если лица не нашлись
- CONFIG: + face_timeout_ms, default_model, face_model
server.js:
- POST /api/entries/:id/photo/enhance-ai принимает и валидирует тело до
запроса записи — невалидный вход даёт 400, а не 404/500
- PHOTO_JOB_ACTIONS вынесен на уровень модуля, + ai_face и ai_upscale
- GET /api/photo-ai/health (requireAdmin) — прямой прокси /health
- photoAiHealth(timeoutMs), в «Статусе стека» вызывается с 2000 мс
- getStackInfo(): блок photo_ai (engine, host, device, vram, модели)
- настройки photo_ai_face_mode / photo_ai_face_model / photo_ai_device_pref
с валидацией в PUT /api/settings, дефолты в init.sql, migration.sql,
public-settings и ensurePhotoJobsTable()
Приёмка (живой стек, CPU + отдельно CUDA) — в TODO_PHOTO_FACE_AI.md,
журнал раздела 4: I1 байт-в-байт 5/5 и совпадение sha256 с raw-путём,
I2, I3 при пустом PHOTO_AI_URL, I5 на обрыве и на 503 с Retry-After,
7 невалидных тел → 400, api.smoketest.js 57 PASS.
Добавлена система уведомлений о системных и фоновых событиях (новые записи
журнала, обработка фото, авто-проверка текста, блокировки 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
- DB: add photo_jobs.applied column (init + migration + ensure)
- Worker: generate preview only (after_path), no longer mutates entry
- New POST /api/entries/:id/photo/jobs/:jobId/apply — apply done job
result to entry (backs up current photo, marks applied)
- New POST /api/entries/:id/photo/jobs/:jobId/reject — discard result,
delete temp file, mark rejected
- saveEnhance: apply AI result directly when sliders are at defaults
- Photo history: '✓ Применить' action for unapplied done AI jobs;
'rejected' status label
- sweepOrphanedUploads keeps done-not-applied preview files
- Replace in-memory photoAiJobs Map with DB-backed photo_jobs table
- Background photo worker (worker.js) with retry, backoff, stale reset
- Handles both AI (Real-ESRGAN) and server-side (sharp) enhancement
- Controlled via photo_worker_enabled setting
- Photo job history in enhance modal with before/after thumbnails + rollback
- Worker dashboard: photo jobs section with status counts, recent table,
compare slider for before/after, rollback from worker UI
- New endpoints: /api/photo-jobs/status|wake|enabled|requeue-failed,
/api/entries/:id/photo/jobs (history), .../rollback
- swapEntryPhotoFiles logs every mutation to photo_jobs table