Commit Graph
30 Commits
Author SHA1 Message Date
dev 3c127b895e feat(datetime): timezone/time_format в настройках + единый хелпер дат
Часовой пояс и формат времени (24h/12h) перенесены из браузера в настройки
приложения: валидация IANA-зоны, сид в db/init.sql + db/migration.sql, отдача
в GET /api/public-settings, раздел #sec-datetime в настройках с живым превью.

Границы суток в SQL переведены на tzDayStart/tzDayEnd + bindTz ($TZ$) — раньше
$n::date по TIMESTAMPTZ считал дни в UTC (у контейнера TimeZone=UTC) и молча
сдвигал выборку на день; хардкод Europe/Moscow вычищен, now() заменён на
tzWall().

Новый public/js/datetime.js: три семейства хелперов (instant / чистая
DATE-строка без Date() / чистая TIME-строка), подключён на всех страницах
включая публичные share/report/index, initDateTime() встроен в checkAuth().
Прямые toLocale*/getFullYear/toISOString().slice(0,10) в public/ выпилены.

Попутно: bindTz добавляет параметр только при наличии $TZ$ в SQL (иначе запрос
виснет вечно без global error handler) — регрессия закрыта в api.smoketest.js;
починены off-by-one месяца в report.js и группировка по дате в share.js.
2026-10-04 10:49:48 +03:00
dev 32aabe9a80 fix(lesson-ai): обновление промпта у существующих БД + правило в AGENTS.md
INSERT ... ON CONFLICT (key) DO NOTHING вставляет значение только если
ключа ещё нет, поэтому новый шаблон промпта доезжал лишь до свежих
установок — у всех, кто уже пользовался разделом sec-lesson-ai, в settings
оставался старый промпт.

- в db/migration.sql добавлен идемпотентный
  UPDATE settings SET value = '<новый промпт>'
  WHERE key = 'lesson_ai_prompt' AND value IN ('<старый дефолт>')
  по образцу уже существующей миграции для ai_prompt
- условие по value IN (...) обязательно: без него миграция затёрла бы
  промпт, отредактированный админом в UI
- в db/init.sql UPDATE не добавляется: файл выполняется только на пустой БД
- AGENTS.md, раздел 2: правило «Сид настроек» (DO NOTHING не обновляет
  существующие значения) и «Проверка сида» (прогон в транзакции с откатом)
- AGENTS.md, раздел 3d: смена шаблона промпта — четыре правки, а не три;
  все текстовые копии должны быть побайтово идентичны LESSON_AI_DEFAULT_PROMPT

Проверено на postgres:16 в транзакции с откатом, ON_ERROR_STOP=1
- старый дефолт (935 симв.) -> миграция -> новый (2348 симв.)
- повторный прогон ничего не меняет (идемпотентно)
- кастомный промпт админа миграцией не затрагивается
2026-10-04 00:58:12 +03:00
dev 2363d099d2 feat(lesson-ai): новый шаблон промпта + предупреждение о дубле отчёта
- заменён дефолтный промпт редактуры отчёта о занятии на формат
  «сообщение тьютора»: связный текст от 3-го лица, 1 абзац 2–4
  предложения, начало «На занятии ребята …», практика «В конце занятия …»,
  без группы/даты/времени и markdown, обращение на «вы» убрано, «они» → «каждый»
- синхронизированы три копии промпта: LESSON_AI_DEFAULT_PROMPT в server.js
  и сид lesson_ai_prompt в db/init.sql и db/migration.sql; добавлен пример
  «плохо → хорошо» с запретом переносить факты примера
- дубль отчёта в модалке создания больше не автозаполняет форму: показывается
  баннер #lessonDup с кнопкой перехода к существующему отчёту
- проверка на дубль срабатывает и при смене даты (#lessonDate), защищена
  счётчиком поколений lessonDupSeq от гонки при быстрой смене группы/даты
- стили .lesson-dup в public/admin.css, уточнена подсказка в карточке
  шаблона отчёта на public/settings.html
- DDL не менялся: ни таблиц, ни колонок, ни индексов
2026-10-04 00:54:23 +03:00
dev b931c0a760 feat(lesson-ai): проверка отчёта о занятии по шаблону + история версий
Галочка «Проверить по шаблону» в окне отчёта отправляет текст модели:
совпал с шаблоном — остаётся как есть (skipped), не совпал — переписывается
в деловом виде (done). Обработка идёт в фоне, HTTP-запрос не ждёт модель,
оригинал тьютора сохраняется в text_original.

- схема: text_original/text_ai/ai_status/ai_checked_at/ai_error в
  lesson_reports, таблица lesson_report_versions, ensureLessonReportsTable()
- настройки lesson_ai_enabled и lesson_ai_prompt (раздел sec-lesson-ai),
  значения только 'true'/'false'
- worker.js: createLessonReportChecker (FOR UPDATE OF lr SKIP LOCKED,
  до 3 попыток), хук назовён notifyEvent — notify в createPhotoEnhanceWorker
  уже занят будильником
- server.js: wakeLessonAiWorker, onLessonAiDone (версия, аудит с diff,
  уведомление lesson.ai.formatted, SSE lesson_report_status), маршруты
  /versions, /versions/:id/restore и /ai/revert
- aiComplete вместо aiCorrectText: общий вызов модели с таймаутом
- бэкап/восстановление: lesson_reports и lesson_report_versions в payload
- фронтенд: openLessonVersions/restoreLessonVersion в admin.js, бейджи
  статусов в lessons.js, лейблы аудита, renderAuditPager
- docs: раздел 3d в AGENTS.md и Agent Workflow, пункт в README
- тесты: контракт lesson-report и настройки уведомления в api.smoketest.js
2026-10-04 00:12:24 +03:00
dev a84f33307e feat(lessons): отчёты о занятиях — таблица, CRUD, страница и блок на дашборде
Добавлена сущность «что прошли на занятии»:
- 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 без смещения.
2026-10-03 23:33:45 +03:00
dev 884188e9ec feat(photo-ai): Stage 4 — face-режим, апскейл-модели и метаданные в аудите
Воркер и сервер принимают параметры ИИ-обработки фото: модель апскейла
(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.
2026-09-29 15:08:46 +03:00
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 8bf54fb95d feat(trash): deferred purge after configurable days, groups in trash 2026-09-24 13:11:31 +03:00
dev e6c69c8a78 feat(settings): option to show/hide camera button, preview click opens camera 2026-09-24 12:00:20 +03:00
dev 4623358f21 feat(students): multi-photo gallery, group photos in export, KIBERone rebrand 2026-09-23 13:32:09 +03:00
dev 5e2533b876 feat(groups): assign tutor to group 2026-09-19 13:17:40 +03:00
dev 1ef81f9d1c wip: student profile/report 2026-09-19 13:06:21 +03:00
dev d7d4cc1133 feat(modules): soft delete with restore and active filter 2026-09-18 20:04:02 +03:00
dev e6291a0235 feat(modules): module topics for student form, admin CRUD with pagination
- db: modules table (name, lessons_count) + entries.module_id (ON DELETE SET NULL), migration + idempotent startup ensure
- api: GET /api/modules (public, search + limit/offset, entries_count), POST/PUT/DELETE (admin, audit-logged)
- entries: accept/validate module_id on create/update, return module_name, module_id filter
- backup/restore: include modules and entries.module_id
- student form: required module select, hidden while no modules exist
- admin: modules.html + js/modules.js list with pagination, search, create/edit/delete modal
- journal: module filter, module select in edit modal, module badge, CSV column
2026-09-18 18:56:42 +03:00
dev 5874e2b619 feat: photo jobs apply/reject flow with applied flag in history
- 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
2026-09-17 18:51:26 +03:00
dev 1145be02f3 feat: persistent photo job queue, worker, history with rollback
- 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
2026-09-17 17:36:56 +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 57e48a9d4f fix(ai): adaptive max_tokens per backend (reasoning profiles get 1024+, native llama.cpp stays compact; per-profile max_tokens override); include entry texts in ai_status SSE notify so journal updates live without reload; audit log action filter; links pagination; settings validation & UI improvements 2026-09-17 14:12:36 +03:00
dev bbfde902a5 feat(journal): live AI status updates in entry cards via Postgres NOTIFY trigger + SSE ai_status events 2026-09-17 11:14:27 +03:00
dev fd753c1318 Add cookie notice with settings, jfif preview support, and share page title from link name 2026-09-12 10:05:02 +03:00
dev 2c7ec17c8b Add IP autoban system: banned_ips table, global ipGuard middleware, 24h bans on honeypot fill / 10 failed logins / 10 wrong share passwords; trust proxy for real client IPs behind funnel; admin API and settings UI to manage active bans 2026-09-12 00:35:51 +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 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 3850fe35e4 Update project files 2026-09-10 11:26:51 +03:00
dev 7ccc9199e0 Add cover_path column to groups table for group cover photo feature 2026-09-10 01:16:32 +03:00
dev f13031e9b3 add detach files feature, utf8 filename fix, and error pages 2026-09-07 09:42:28 +03:00
dev 621c3c3b5c add soft delete with trash page and group photo chronology with styling 2026-09-06 13:51:47 +03:00
dev ac8621c5aa serve project files by token hash, preview images inline and download other files 2026-09-06 11:33:33 +03:00
dev 3b1341a0c2 add autocomplete input for student selection in journal filters, link creation, and links admin 2026-09-05 20:26:21 +03:00
dev 6dc54e49b8 batch add students: add optional group selection in batch modal 2026-09-05 18:31:28 +03:00