feat(files): загрузка и выдача вложений

- POST /api/v1/channels/{id}/files (multipart, ATTACH_FILES): файл пишется на
  диск под идентификатором, в БД хранятся метаданные и sha256; имя файла
  очищается от путей и управляющих символов, объём ограничен MAX_UPLOAD_SIZE;
- GET/HEAD /files/{id}: содержимое с проверкой прав на комнату сообщения
  (невидимая комната и чужие вложения → 404), ETag, immutable-кэш,
  Content-Disposition с RFC 5987 для не-ASCII имён;
- вложения привязываются к сообщению при отправке (attachment_ids), сироты
  ищутся через store.ListOrphanFiles для обслуживания;
- GET /api/v1/files/{id} — метаданные файла для клиента;
- chi-ручки теперь умеют отдавать huma-ошибки в общем конверте
  (writeHumaAPIError), иначе 404 превращался в 500;
- READY отдаёт реальные read states пользователя.

Тесты: загрузка, скачивание участником, скрытая комната → 404, вложение в
сообщении, проверка подписи ETag.
This commit is contained in:
2026-09-19 23:36:16 +03:00
parent 63bf1e63b5
commit 0e39b8e027
25 changed files with 1162 additions and 39 deletions
+1 -6
View File
@@ -137,12 +137,7 @@ export function UserPanel() {
</button>
</div>
<Menu
open={menuOpen}
label={t('user.menu.label')}
onClose={closeMenu}
anchorRef={triggerRef}
>
<Menu open={menuOpen} label={t('user.menu.label')} onClose={closeMenu} anchorRef={triggerRef}>
<MenuGroupLabel>{displayName}</MenuGroupLabel>
<MenuItem icon="👤" onClick={goTo('/settings/account/profile')}>
{t('user.menu.account')}