feat(chat): правка и удаление сообщений

- PUT/DELETE /api/chat/threads/:id/messages/:messageId под requireAuth+chatLimiter,
  доступ через chatMessageFor (тред + принадлежность сообщения)
- править может только автор (админу чужое сообщение 403), удалять — автор или админ
- мягкое удаление: body='', вложения каскадом из БД + safeUnlink по path,
  счётчик непрочитанных уменьшается на 1 у своей стороны (GREATEST(col-1,0))
- refreshChatThreadPreview() пересчитывает превью треда по последнему живому сообщению
- SSE: publishChat с type, список типов валидируется через CHAT_EVENTS; новые
  message_update/message_delete подписаны в connectChatStream (admin.js)
- аудит chat.message.update с textDiff и chat.message.delete без текста
- колонки edited_at/deleted_at в db/init.sql, db/migration.sql, ensureChatTables()
  и в normalizeRestoreData + restore-INSERT — удалённые сообщения не воскресают
- фронтенд: кнопки правки/удаления по наведению, модалка правки (Ctrl+Enter),
  метка «изменено», плейсхолдер «Сообщение удалено», обновление по SSE
- покрытие в api.smoketest.js (правка/удаление/403/400/404) и backup.selftest.js
This commit is contained in:
dev
2026-10-05 18:32:58 +03:00
parent 7e4b1a1324
commit 1740c341a0
11 changed files with 478 additions and 12 deletions
+15
View File
@@ -956,6 +956,17 @@ body.report-card-open .report-fields{display:flex}
.msg-who{font-weight:600;color:var(--text);font-size:.78rem}
.msg-role{border:1px solid var(--border);border-radius:5px;padding:0 5px;font-size:.65rem}
.msg-time{white-space:nowrap}
.msg-edited{font-style:italic}
.msg-actions{display:inline-flex;align-items:center;gap:2px;opacity:0;transition:opacity .15s}
.msg:hover .msg-actions,.msg-actions:focus-within{opacity:1}
.msg-act{background:none;border:none;padding:2px;border-radius:6px;color:var(--muted);cursor:pointer;display:inline-flex}
.msg-act:hover{color:var(--accent);background:var(--bg)}
.msg-act-del:hover{color:#ef4444}
.msg-act svg{width:13px;height:13px}
.msg-deleted-body{font-style:italic;color:var(--muted)}
.chat-edit-modal{max-width:560px}
.chat-edit-modal textarea{min-height:110px;max-height:40dvh}
.chat-edit-modal textarea:focus{border-color:var(--accent)}
.msg-body{font-size:.88rem;line-height:1.45;white-space:pre-wrap;word-break:break-word}
.msg-files{display:flex;flex-wrap:wrap;gap:8px}
.msg-file{display:flex;align-items:center;gap:8px;background:var(--bg);border:1px solid var(--border);border-radius:9px;padding:7px 10px;font-size:.78rem;color:var(--text);text-decoration:none;max-width:230px;min-width:0;transition:border-color .15s}
@@ -987,6 +998,10 @@ body.report-card-open .report-fields{display:flex}
.chat-hint{font-size:.75rem;margin:0}
.chat-new-modal{max-width:420px}
@media(hover:none){
.msg-actions{opacity:1}
}
@media(max-width:900px){
.chat-layout{grid-template-columns:1fr}
.chat-side{max-height:230px}