feat(cosmetics): оформление «для друзей» в друзьях и личных беседах
- API: список друзей отдаёт вычисленное оформление «для друзей» одним запросом на весь список (AGENT.md 7.2) - клиент: рамка, иконка, цвет и эффект ника в списке друзей, в ленте личных бесед — из профиля собеседника - тесты: Go на список друзей, Vitest на отрисовку в друзьях
This commit is contained in:
@@ -29,6 +29,9 @@ type relationshipUser struct {
|
||||
Timezone string `json:"timezone"`
|
||||
// Relationship: friend | incoming | outgoing | blocked | none
|
||||
Relationship string `json:"relationship"`
|
||||
// Cosmetics — оформление «для друзей»: в DM и списке друзей применяется
|
||||
// именно оно (AGENT.md 7.2).
|
||||
Cosmetics *cosmeticsPayload `json:"cosmetics,omitempty"`
|
||||
}
|
||||
|
||||
type relationshipListOutput struct {
|
||||
@@ -398,9 +401,20 @@ func (s *Server) relationshipProfiles(ctx context.Context, userID uint64, kind s
|
||||
if err != nil {
|
||||
return nil, humaError(err)
|
||||
}
|
||||
// Оформление «для друзей» (AGENT.md 7.2) — одним запросом на весь список.
|
||||
ids := make([]uint64, 0, len(profiles))
|
||||
for i := range profiles {
|
||||
ids = append(ids, profiles[i].UserID)
|
||||
}
|
||||
cosmetics, err := s.store.EffectiveCosmeticsForUsers(ctx, ids)
|
||||
if err != nil {
|
||||
return nil, humaError(err)
|
||||
}
|
||||
result := make([]relationshipUser, 0, len(profiles))
|
||||
for i := range profiles {
|
||||
result = append(result, s.relationshipPayload(userID, &profiles[i], string(kind)))
|
||||
payload := s.relationshipPayload(userID, &profiles[i], string(kind))
|
||||
payload.Cosmetics = cosmeticsFromEffective(cosmetics[profiles[i].UserID])
|
||||
result = append(result, payload)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -348,3 +348,48 @@ func TestSelfRoleAssignmentForCosmetics(t *testing.T) {
|
||||
t.Fatalf("кик себя = %d, ожидался 403", kick.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFriendsListCarriesCosmetics(t *testing.T) {
|
||||
srv, owner, member, guildID, memberID := cosmeticsFixture(t)
|
||||
|
||||
// Стиль «для друзей» с эффектом и цветом из палитры.
|
||||
style := doJSON(t, srv, http.MethodPut, styleScopePath("global"),
|
||||
`{"nick_color":3066993,"nick_effect":"gradient"}`, member)
|
||||
if style.Code != http.StatusOK {
|
||||
t.Fatalf("стиль «для друзей» = %d (%s)", style.Code, style.Body.String())
|
||||
}
|
||||
|
||||
// Дружба в обе стороны.
|
||||
request := doJSON(t, srv, http.MethodPost, "/api/v1/users/@me/relationships",
|
||||
`{"user_id":"`+memberID+`"}`, owner)
|
||||
if request.Code != http.StatusOK && request.Code != http.StatusCreated {
|
||||
t.Fatalf("заявка в друзья = %d (%s)", request.Code, request.Body.String())
|
||||
}
|
||||
me := doJSON(t, srv, http.MethodGet, "/api/v1/users/@me", "", owner)
|
||||
ownerID := decodeResponse[struct {
|
||||
User struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"user"`
|
||||
}](t, me).User.ID
|
||||
accept := doJSON(t, srv, http.MethodPost,
|
||||
"/api/v1/users/@me/relationships/"+ownerID+"/accept", "", member)
|
||||
if accept.Code != http.StatusOK {
|
||||
t.Fatalf("принятие заявки = %d (%s)", accept.Code, accept.Body.String())
|
||||
}
|
||||
|
||||
friends := doJSON(t, srv, http.MethodGet, "/api/v1/users/@me/relationships", "", owner)
|
||||
list := decodeResponse[struct {
|
||||
Friends []struct {
|
||||
UserID string `json:"user_id"`
|
||||
Cosmetics *effectiveCosmetics `json:"cosmetics"`
|
||||
} `json:"friends"`
|
||||
}](t, friends)
|
||||
if len(list.Friends) != 1 {
|
||||
t.Fatalf("друзей %d, ожидался 1 (%s)", len(list.Friends), friends.Body.String())
|
||||
}
|
||||
got := list.Friends[0].Cosmetics
|
||||
if got == nil || got.NickEffect != "gradient" || got.NickColor == nil || *got.NickColor != 3066993 {
|
||||
t.Fatalf("оформление друга = %+v", got)
|
||||
}
|
||||
_ = guildID
|
||||
}
|
||||
|
||||
@@ -404,6 +404,27 @@ func (s *Store) EffectiveCosmeticsForGuild(ctx context.Context, guildID uint64)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// EffectiveCosmeticsForUsers вычисляет оформление «для друзей» для пачки
|
||||
// пользователей: так список друзей и DM получают его одним запросом.
|
||||
func (s *Store) EffectiveCosmeticsForUsers(ctx context.Context, userIDs []uint64) (map[uint64]EffectiveCosmetics, error) {
|
||||
result := make(map[uint64]EffectiveCosmetics, len(userIDs))
|
||||
if len(userIDs) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
styles, err := s.listUserStylesFor(ctx, userIDs, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
grants, err := s.CosmeticGrantsForUsers(ctx, userIDs, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, userID := range userIDs {
|
||||
result[userID] = resolveCosmetics(nil, nil, nil, styles.global[userID], grants[userID])
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// EffectiveCosmeticsForUser вычисляет оформление пользователя в контексте
|
||||
// «для друзей» (DM, список друзей, глобальные места).
|
||||
func (s *Store) EffectiveCosmeticsForUser(ctx context.Context, userID uint64) (EffectiveCosmetics, error) {
|
||||
|
||||
Reference in New Issue
Block a user