feat(chat): личный чат тьютор ↔ админ с вложениями
Диалоги 1:1 (один тред на тьютора), отдельный SSE-канал whatido:chat вместо общей ленты notifications, вложения с белым списком расширений и вставкой из буфера (Ctrl+V), очистка по chat_retention_days. Таблицы в бэкап, chat_attachments добавлен в sweepOrphanedUploads.
This commit is contained in:
@@ -158,6 +158,17 @@ This document defines how AI agents should work with the WhatIDo codebase. Follo
|
||||
- **Воркер отчётов о занятии не имеет `wake`-эндпоинта ни во внутреннем API, ни в `apiV1`** — он будится неявно через `wakeLessonAiWorker()` из `POST`/`PUT /lesson-reports`, когда `ai_check === true` (строго boolean; строка `"true"` не срабатывает) и `lesson_ai_enabled` ≠ `'false'`
|
||||
- **`api_keys` НЕ входит в бэкап** (как `sessions`), а `POST /api/restore` делает `DELETE FROM api_keys` — после восстановления все внешние ключи мертвы, их надо выпустить заново
|
||||
|
||||
### 3g. Чат тьютор ↔ админ (`server.js`, `public/chat.html`, `public/js/chat.js`)
|
||||
- **Модель — личные диалоги 1:1**, не общая комната: один тред на тьютора (`chat_threads.tutor_id UNIQUE`). Тьютор видит и пишет только свой тред, админ видит все треды и может писать каждому. Тред создаётся лениво: тьютор — первым сообщением или `POST /api/chat/threads` без тела, админ — с `{ user_id }`
|
||||
- **Таблицы**: `chat_threads` (`tutor_unread`/`admin_unread` — счётчики прочитанного по ролям, `last_message_text` — превью для списка), `chat_messages`, `chat_attachments`. Схема — в `db/init.sql`, `db/migration.sql` и `ensureChatTables()`
|
||||
- **Отдельный SSE-канал `whatido:chat`**, эндпоинт `GET /api/chat/stream` — **не** общая лента `notifications`. Причина: `notifications` широковещательная (видимость только через `admin_only` + `branch_id`), личная переписка попала бы туда и стала видна посторонним тьюторам. Живой чат держит свой счётчик `setChatUnread()` и бейдж `#sidebarChatBadge`
|
||||
- **Файлы вложений** — отдельный multer `chatFilesUpload` с allowlist `CHAT_ALLOWED_EXT` (изображения, документы, архивы). Нельзя переиспользовать `adminUpload`: в нём `.html`/`.htm` явно разрешены поверх `BLOCKED_EXT` — для чата это лишняя поверхность. Правило: расширение проверяется по белому списку, а не «не заблокировано»
|
||||
- **Отдача вложений** — `GET /api/chat/files/:token` под `fileLimiter` + `loadUserByToken`, доступ проверяется `chatVisible(user, tutor_id)`: админ или владелец треда, иначе 403. Изображения отдаются inline (`?thumb=1` — WebP-миниатюра), видео — `?play=1` с `Accept-Ranges`, остальное — attachment. Файл **не** должен попадать в `express.static` или в `api/files/:token` (там доступ только по токену, без проверки участника треда)
|
||||
- **Вставка из буфера обмена (Ctrl+V)** — обработчик `paste` в `public/js/chat.js`, образец в `js/index.js`. Файл нормализуется через `chatPasteFileName()` (безымянные скриншоты получают имя по `todayIso()`/`nowHm()`), проверяется тем же allowlist и лимитами, что и сервер
|
||||
- **Очистка**: `purgeOldChat()` по настройке `chat_retention_days` (по умолчанию 365), при старте и раз в сутки. Файлы вложений удаляются через `safeUnlink`, пустые треды — `DELETE ... WHERE NOT EXISTS`
|
||||
- **`sweepOrphanedUploads()` обязан включать `chat_attachments.path`** — без этого вложения чата снесутся как «сироты»
|
||||
- **Бэкап**: `chat_threads`/`chat_messages`/`chat_attachments` входят в `BACKUP_TABLES` и `BACKUP_SEQUENCE_TABLES`, нормализуются в `normalizeRestoreData` и вставляются в restore с проверкой FK
|
||||
|
||||
### 4. API Patterns
|
||||
- **Middleware**: `requireAuth` — читает `X-Auth-Token`, 401 без валидной активной сессии. `requireAdmin` — самодостаточный (внутри вызывает `requireAuth`, если `req.user` ещё нет), 403 при `role !== 'admin'`. `optionalAuth` — для публичных страниц с персонализацией
|
||||
- **Филиалы**: `branchScope(user)` / `branchWhere(user, alias)` — для не-admin `user.branch_ids` (из `user_branches`) ограничивают выборку; у `admin` `ids = null` и фильтр не добавляется
|
||||
@@ -289,6 +300,11 @@ node diff.selftest.js # unit, no stack needed
|
||||
# External API checks
|
||||
node api-keys.selftest.js # e2e, needs running stack
|
||||
curl -H "X-Api-Key: wsk_..." http://localhost:3003/api/v1/me
|
||||
|
||||
# Chat checks (live tutor↔admin, attachments, isolation)
|
||||
curl -H "X-Auth-Token: $TOKEN" http://localhost:3003/api/chat/threads
|
||||
curl -N "http://localhost:3003/api/chat/stream?token=$TOKEN" # SSE, first frame is `ready`
|
||||
curl -H "X-Auth-Token: $TOKEN" http://localhost:3003/api/chat/unread
|
||||
```
|
||||
|
||||
`diff.js` builds the audit payload for text changes: word-level segments
|
||||
@@ -354,6 +370,8 @@ guards against.
|
||||
| `public/admin.js` | Shared frontend logic, модалка отчёта о занятии (`openLessonModal`) |
|
||||
| `public/js/datetime.js` | Единая точка форматирования дат/времени: часовой пояс + 24h/12h. Подключается на всех страницах |
|
||||
| `public/lessons.html` | Отчёты о занятиях: список, фильтры, редактирование |
|
||||
| `public/chat.html` | Чат тьютор↔админ: список диалогов, лента сообщений, вложения (Ctrl+V) |
|
||||
| `public/js/chat.js` | Логика чата: загрузка треда/сообщений, отправка, вставка файлов из буфера |
|
||||
| `scripts/backup.sh` | Host-level backup script (DB dump + storage export) |
|
||||
| `scripts/restore.sh` | Host-level restore script (DB dump + storage import) |
|
||||
| `scripts/storage-sync.js` | Export/import all storage objects (used by backup/restore) |
|
||||
|
||||
Reference in New Issue
Block a user