feat(dm): групповые личные беседы (AGENT.md 7.8, Фаза 7)
Беседа на несколько участников живёт в тех же таблицах, что и 1:1
(channels.type = 'dm' + dm_participants), добавляется только владелец
(channels.dm_owner_id, миграция 00020).
Сервер: POST /users/@me/channels/group (2–9 приглашённых, имя необязательно —
собирается из имён), PUT/DELETE /channels/{id}/recipients/{user_id}
(добавляет любой участник, удаляет других и переименовывает только владелец,
выйти может каждый сам), PATCH /channels/{id}. Правила состава: группа — это
3+ участника; при двух беседа снова обычная личная (имя и владелец
сбрасываются), при одном — удаляется вместе с перепиской. Заблокированного
нельзя ни пригласить, ни добавить; посторонним беседа не видна (404).
READY и REST отдают is_group, name, member_count, owner_id и состав;
участники получают DM_CHANNEL_CREATE/DM_CHANNEL_UPDATE, удалённый —
DM_CHANNEL_DELETE.
Клиент: группа в сайдбаре с числом участников (без точки чужого статуса),
шапка беседы с числом участников и именами в подсказке, создание группы из
списка друзей (минимум двое) с переходом в новую беседу.
Тесты: Go — полный жизненный цикл (создание, состав у каждого участника,
добавление, запрет посторонним, удаление владельцем, выход, превращение в
1:1, удаление последней беседы), валидация (меньше трёх, дубликаты,
заблокированный) и группа в READY; web — 4 vitest (сайдбар, шапка, создание,
выключенная кнопка).
This commit is contained in:
@@ -144,6 +144,12 @@ type ReadyChannel struct {
|
||||
Status string `json:"status,omitempty"`
|
||||
LastMessageID string `json:"last_message_id,omitempty"`
|
||||
LastMessageAt string `json:"last_message_at,omitempty"`
|
||||
// Групповая личная беседа (AGENT.md 7.8, Фаза 7): имя, состав и владелец.
|
||||
// У 1:1 is_group = false, а собеседник лежит в recipient_id.
|
||||
IsGroup bool `json:"is_group,omitempty"`
|
||||
MemberCount int `json:"member_count,omitempty"`
|
||||
MemberIDs []string `json:"member_ids,omitempty"`
|
||||
OwnerID string `json:"owner_id,omitempty"`
|
||||
}
|
||||
|
||||
type ReadyRole struct {
|
||||
|
||||
@@ -92,6 +92,19 @@ func (s *Snapshot) Build(ctx context.Context, user *store.User) (*Ready, error)
|
||||
RecipientID: formatID(summary.RecipientID),
|
||||
Status: visibleStatus(summary.Status, summary.LastSeenAt),
|
||||
}
|
||||
if summary.IsGroup {
|
||||
// Групповая беседа: имя и состав вместо собеседника.
|
||||
channel.IsGroup = true
|
||||
channel.Name = summary.GroupName
|
||||
channel.MemberCount = summary.MemberCount
|
||||
channel.MemberIDs = make([]string, 0, len(summary.Participants))
|
||||
for _, participant := range summary.Participants {
|
||||
channel.MemberIDs = append(channel.MemberIDs, formatID(participant.UserID))
|
||||
}
|
||||
if summary.OwnerID != nil {
|
||||
channel.OwnerID = formatID(*summary.OwnerID)
|
||||
}
|
||||
}
|
||||
if summary.AvatarFileID != nil {
|
||||
channel.IconFileID = formatID(*summary.AvatarFileID)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user