photo_jobs.before_path хранит путь к оригиналу фото (/uploads/.originals/<файл>), но normalizeRestoreData проверял это поле через optUploadPath/isSafeUploadPath, который запрещает "/" — при наличии завершённых улучшений фото весь импорт падал с 400 «Неверный формат бэкапа: Invalid upload path».
- добавлены reqPhotoRefPath/optPhotoRefPath: допустимы /uploads/<файл> и /uploads/.originals/<файл> (та же ORIGINALS_PATH_RE, что и для entries.photo_original_path); применяются к photo_jobs.before_path/after_path
- POST /api/restore: понятная ошибка, если загружен архив скрипта scripts/backup.sh (db.sql.gz + _uploads) вместо веб-архива
- README: форматы скриптового и веб-архива не взаимозаменяемы
Формирование и скачивание бэкапа разделены: POST /api/backup собирает архив
на диске и возвращает временную ссылку, GET /api/backup/:token отдаёт его
через res.download (Content-Length, Accept-Ranges, 206 при докачке).
- больше нет fs.readFileSync всего архива и res.send буфера (~550 МБ RAM -> ~60 МБ)
- GET /api/backup сохранён для совместимости, тоже потоковый
- gzip level 1 (архив из JPEG почти не сжимается), чистка /tmp/wido-backups по TTL 30 мин
- settings.html/js: нативное скачивание браузером с прогрессом и докачкой,
понятные ошибки вместо «Ошибка сети при формировании бэкапа»
- export/restore share_links (was silently dropped, FK blocked restore)
- keep groups.tutor_id and groups.cover_path, entries.photo_original_path,
project_files.detached_at on restore
- include uploads/.originals files in backup archive
- insert users before groups to satisfy tutor_id FK
- return 500 JSON instead of hanging when restore fails
- 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
- Fix runAiEnhance missing return/closing brace from previous commit
- Remove duplicate code fragments in worker.js
- Add renderPhotoPending() to show pending/processing jobs in worker dashboard
- New pending queue table in worker.html photo section
- 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
- Side-by-side before/after comparison with draggable divider
- Zoom (scroll wheel) and pan when zoomed in, double-click to reset
- Sliders moved to sidebar panel, responsive layout
- Original/Result badges on the comparison view
- Helper functions: setEnhanceClip, showEnhanceResult, applyEnhanceTransform
- Modal no longer auto-closes after Real-ESRGAN completes; result loads into
canvas so user can review, adjust sliders, and choose to apply or discard
- Backend skips swapEntryPhotoFiles until user confirms via Применить
- New DELETE /api/entries/:id/photo/enhance-ai/preview for temp file cleanup
- photo-ai Dockerfile: patch basicsr via find+sed instead of import (avoids
torchvision.functional_tensor import crash)
- webcam capture resolution/quality configurable in admin settings (defaults 640x480 / 0.92)
- enhance photo modal in journal: original vs preview with sliders (brightness, contrast, saturation, sharpen) and auto-levels button
- new endpoint PUT /api/entries/:id/photo/enhance replaces photo, cleans old file and thumb
- sharper HEIC conversion (0.92) and webp thumbnails (85)
- worker: fail explicitly on empty AI response
- renderStudentReport: детский учебный дизайн (крупные скругления, sticky-навигация, секции-карточки)
- журнал занятий в две колонки (одна на мобильных)
- единая галерея фото и видео: листание кнопками/стрелками, счётчик
- воспроизведение видео прямо в лайтбоксе
- кнопка закрытия ✕, закрытие по фону и Esc
- модалка экспорта отчёта ученика (период и выбор содержимого)
- Dockerfile.cloudflared: add iptables + ip6tables (wg-quick needs them for ::/0 full tunnel)
- docker-compose: privileged:true for cloudflared so wg-quick can set net.ipv4.conf.all.src_valid_mark
- start-cloudflared.sh: restore resolv.conf after wg-quick (resolvconf wiped docker DNS 127.0.0.11 => app unresolvable => Host Error)
- Make Детали cell clickable to open a modal with the full details text\n- Translate missing audit action keys (auth.login, user.*, branch.*, group.photo.*, entry.photo.*)
Restore .ai-badge as a text pill and scope the compact icon circle to .ai-badge-ic (used by journal); worker page text badges now render at correct size
- Replace emoji icons with Lucide across admin pages; add vendor/lucide.min.js and renderIcons() helper\n- Reorder sidebar logically (Dashboard, Journal, Students, Groups, Files, Links, Trash + admin sections)\n- Groups: open photo chronology only via the Фото button; covers no longer clickable\n- Journal: show group badge over card photo, compact AI-status icon beside description, and date range in empty-state message\n- Update README (AI worker, Lucide, worker.js)
- New /api/system-info endpoint returning DB size, table sizes, photo/file counts, uploads stats, disk usage
- System info cards in settings page (responsive grid)
- Replaced inline onclick handlers with data attributes + event delegation in groups.html
- Add honeypot field to public submission form + server-side check
- Serve shared files only in context of a valid share link (/api/share/:shareToken/files/:fileToken)
- Switch backup restore upload to diskStorage (50MB) with temp-dir cleanup
- Limit JSON body to 1mb
- Document fixed audit items
- require ADMIN_PASSWORD (no default), remove CORS
- close public DB port, move DB credentials to .env (DB_PASSWORD)
- fix HTML escaping, add helmet + sec headers (no CSP due to inline scripts)
- rate limit public routes by IP (express-rate-limit)
- validate restore data and confine file unlinking to uploads/
- block dangerous upload extensions, 30MB per-entry limit, SVG not served inline
- return 400 on unknown group_id in POST /api/entries
- add commented Caddy/Let's Encrypt reverse-proxy scaffold + Caddyfile.example
- update README