По запросу пользователя (вне очереди AGENT.md §13):
- главного сервера как точки входа больше нет: новичок начинает с пустым
списком серверов, вход — только по приглашению или созданием своего;
- друзья: поиск по логину с экранированием LIKE, заявки (POST
/users/@me/relationships), принятие, удаление/отклонение, списки friends/
incoming/outgoing/blocked;
- личные беседы: POST /users/@me/channels (идемпотентно), GET
/users/@me/channels со собеседником, статусом и последним сообщением;
сообщения в DM работают через общие ручки комнат, доступ — только участникам
(посторонний получает 404, события в Gateway тоже фильтруются);
- присутствие: last_seen_at обновляется при активности, «невидимка» и простой
дольше двух минут выглядят как офлайн, смена статуса рассылает
PRESENCE_UPDATE друзьям;
- READY отдаёт dm_channels (собеседник, аватар, статус, последнее сообщение);
- профиль: timezone (по умолчанию Europe/Moscow) в PATCH /users/@me,
загрузка аватара POST /users/@me/avatar (проверка, что это изображение, в том
числе по содержимому) и удаление DELETE /users/@me/avatar; старый файл
удаляется с диска;
- тесты: заявки в друзья, личные беседы и их изоляция, «невидимка», часовой
пояс и аватар, PRESENCE_UPDATE другу, отсутствие событий DM у постороннего.
Тестовый сервер писал файлы вложения в `internal/server/files`, потому что
DATA_DIR не был задан: теперь тесты используют временный каталог, а
`saveUpload` отказывается работать без настроенного каталога данных вместо
записи в текущий рабочий каталог процесса. Лишние файлы удалены.
- 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.