feat(dm): иконка групповой беседы в хранилище и API (Фаза 7)
D-070: у групповой беседы не было своей иконки — показывались имя и первая
буква аватара, как у 1:1 без аватара. Колонки под файл у канала не было.
Миграция 00024 добавляет `channels.icon_file_id` (REFERENCES files ON DELETE
SET NULL): NULL у 1:1 и комнат сервера, файл — у группы. Store читает и пишет
поле через `Channel`/`UpdateChannelParams` (`IconFileID`/`ClearIcon`), отдаёт
его в `ListDMChannels` и в READY, а уборка сирот больше не считает иконку
беседы мусором. Когда в группе остаётся два участника, беседа снова обычная
личная — иконка сбрасывается вместе с именем и владельцем.
Ручки `POST/DELETE /channels/{id}/icon` принимают multipart (назначение файла
`dm_icon`, аватарный лимит, проверка `image/`) и доступны только владельцу
беседы: участнику-не-владельцу 403 `perm.denied`, посторонним 404 (существование
чужой беседы не подтверждаем), у 1:1 своей иконки нет — 422 `dm.not_group`.
Файл иконки отдаётся только участникам беседы. После изменения участникам
уходит `DM_CHANNEL_UPDATE`, поэтому иконка меняется без перезагрузки
(AGENT.md 7.7, 7.8, 8.3, 11.6; D-042, D-070).
This commit is contained in:
@@ -284,7 +284,7 @@ func (s *Store) listDirectChannels(ctx context.Context, userID uint64) ([]DMChan
|
||||
// пользователя немного, а список нужен интерфейсу для карточки беседы.
|
||||
func (s *Store) listGroupChannels(ctx context.Context, userID uint64) ([]DMChannelSummary, error) {
|
||||
rows, err := s.reader.QueryContext(ctx, `
|
||||
SELECT c.id, c.name, c.dm_owner_id, c.created_at,
|
||||
SELECT c.id, c.name, c.dm_owner_id, c.created_at, c.icon_file_id,
|
||||
(SELECT COUNT(*) FROM dm_participants everyone WHERE everyone.channel_id = c.id),
|
||||
(SELECT m.id FROM messages m WHERE m.channel_id = c.id ORDER BY m.id DESC LIMIT 1),
|
||||
(SELECT m.created_at FROM messages m WHERE m.channel_id = c.id ORDER BY m.id DESC LIMIT 1),
|
||||
@@ -305,11 +305,12 @@ func (s *Store) listGroupChannels(ctx context.Context, userID uint64) ([]DMChann
|
||||
summary DMChannelSummary
|
||||
ownerID sql.NullInt64
|
||||
createdAt string
|
||||
iconID sql.NullInt64
|
||||
lastMessageID sql.NullInt64
|
||||
lastMessageAt sql.NullString
|
||||
lastAuthorID sql.NullInt64
|
||||
)
|
||||
if err := rows.Scan(&summary.Channel.ID, &summary.GroupName, &ownerID, &createdAt,
|
||||
if err := rows.Scan(&summary.Channel.ID, &summary.GroupName, &ownerID, &createdAt, &iconID,
|
||||
&summary.MemberCount, &lastMessageID, &lastMessageAt, &lastAuthorID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -318,6 +319,9 @@ func (s *Store) listGroupChannels(ctx context.Context, userID uint64) ([]DMChann
|
||||
summary.Channel.Type = ChannelDM
|
||||
summary.Channel.Name = summary.GroupName
|
||||
summary.Channel.DMOwnerID = summary.OwnerID
|
||||
// Иконка группы — своя (D-070), в отличие от 1:1, где показывается
|
||||
// аватар собеседника (AvatarFileID).
|
||||
summary.Channel.IconFileID = optionalID(iconID)
|
||||
summary.Channel.CreatedAt = parseTimestamp(createdAt)
|
||||
if lastMessageID.Valid {
|
||||
summary.LastMessageID = uint64(lastMessageID.Int64)
|
||||
|
||||
Reference in New Issue
Block a user