fix: sync legacy community projection

Sync telesrv 07644e0 (fix(communities): hide aggregates from legacy layers).
This commit is contained in:
A 2026-07-20 16:50:37 +08:00
parent ba82fbccd6
commit 08c763b683
3 changed files with 54 additions and 6 deletions

View file

@ -9,6 +9,8 @@ import (
"telesrv/internal/domain"
)
const communitiesLayer = 228
func communityErr(err error) error {
switch {
case err == nil:
@ -160,6 +162,17 @@ func (r *Router) communityMutationUpdates(ctx context.Context, userID int64, vie
}
func (r *Router) withCommunityDialogList(ctx context.Context, userID int64, filter domain.DialogFilter, list domain.DialogList) (domain.DialogList, error) {
if LayerFrom(ctx) < communitiesLayer {
return list, nil
}
return r.withCollapsedCommunityDialogs(ctx, userID, filter, list)
}
// withCollapsedCommunityDialogs applies the account-level Community dialog
// state without a wire-layer visibility decision. Business invariants such as
// the shared pinned limit use this path; RPC response construction must use
// withCommunityDialogList instead.
func (r *Router) withCollapsedCommunityDialogs(ctx context.Context, userID int64, filter domain.DialogFilter, list domain.DialogList) (domain.DialogList, error) {
if r.deps.Communities == nil || (filter.HasFolderID && filter.FolderID != domain.DialogMainFolderID) {
return list, nil
}

View file

@ -39,7 +39,7 @@ func communityRPCChannel(t *testing.T, service *appchannels.Service, creator dom
}
func TestCommunityDialogsSharePinnedLimit(t *testing.T) {
ctx := context.Background()
ctx := WithLayer(context.Background(), communitiesLayer)
users := memory.NewUserStore()
owner, err := users.Create(ctx, domain.User{AccessHash: 711, Phone: "15552000011", FirstName: "Pin Owner"})
if err != nil {
@ -102,6 +102,21 @@ func TestCommunityDialogsSharePinnedLimit(t *testing.T) {
if len(order) != domain.MaxPinnedDialogsMainFolder || order[0] != (domain.Peer{Type: domain.PeerTypeChannel, ID: ordinary.ID}) {
t.Fatalf("combined pinned order = %+v (dialogs=%+v communities=%+v count=%d), want ordinary dialog promoted above Communities", order, pinned.Dialogs, pinned.Communities, pinned.Count)
}
legacyPinned, err := r.pinnedDialogsList(WithLayer(ctx, 227), owner.ID, domain.DialogMainFolderID)
if err != nil {
t.Fatal(err)
}
if len(legacyPinned.Communities) != 0 || len(legacyPinned.Dialogs) != 1 || legacyPinned.Count != 1 {
t.Fatalf("Layer 227 pinned dialogs = %+v, want only the ordinary pinned dialog", legacyPinned)
}
legacyOrdinary := communityRPCChannel(t, channelService, owner, "Legacy Ordinary Pinned Channel")
legacyToggle := &tg.MessagesToggleDialogPinRequest{Peer: &tg.InputDialogPeer{Peer: &tg.InputPeerChannel{
ChannelID: legacyOrdinary.ID, AccessHash: legacyOrdinary.AccessHash,
}}}
legacyToggle.SetPinned(true)
if ok, err := r.onMessagesToggleDialogPin(WithLayer(WithUserID(ctx, owner.ID), 227), legacyToggle); err == nil || ok || !tgerr.Is(err, "PINNED_DIALOGS_TOO_MUCH") {
t.Fatalf("Layer 227 pin beyond shared account limit = %v, %v", ok, err)
}
overLimit := &tg.MessagesToggleDialogPinRequest{Peer: &tg.InputDialogPeerCommunity{Community: inputs[len(inputs)-1]}}
overLimit.SetPinned(true)
if ok, err := r.onMessagesToggleDialogPin(WithUserID(ctx, owner.ID), overLimit); err == nil || ok || !tgerr.Is(err, "PINNED_DIALOGS_TOO_MUCH") {
@ -116,7 +131,7 @@ func TestCommunityDialogsSharePinnedLimit(t *testing.T) {
}
func TestCommunitiesRPCLayer228Lifecycle(t *testing.T) {
ctx := context.Background()
ctx := WithLayer(context.Background(), communitiesLayer)
userStore := memory.NewUserStore()
owner, _ := userStore.Create(ctx, domain.User{AccessHash: 701, Phone: "15552000001", FirstName: "Owner"})
member, _ := userStore.Create(ctx, domain.User{AccessHash: 702, Phone: "15552000002", FirstName: "Member"})
@ -186,8 +201,12 @@ func TestCommunitiesRPCLayer228Lifecycle(t *testing.T) {
if len(collapsed.Chats) == 0 || !collapsed.Chats[0].(*tg.Community).CollapsedInDialogs {
t.Fatalf("collapsed updates = %+v", collapsed.Chats)
}
legacyList, err := r.withCommunityDialogList(WithLayer(ctx, 227), owner.ID, domain.DialogFilter{}, domain.DialogList{Count: 7})
if err != nil || len(legacyList.Communities) != 0 || legacyList.Count != 7 {
t.Fatalf("Layer 227 community dialog projection = %+v err=%v, want unchanged list", legacyList, err)
}
list, err := r.withCommunityDialogList(ctx, owner.ID, domain.DialogFilter{}, domain.DialogList{})
if err != nil || len(list.Communities) != 1 {
if err != nil || len(list.Communities) != 1 || list.Count != 1 {
t.Fatalf("community dialog list = %+v err=%v", list, err)
}
dialogs := tgMessagesDialogs(owner.ID, list).(*tg.MessagesDialogs)

View file

@ -9,6 +9,14 @@ import (
)
func (r *Router) pinnedDialogsList(ctx context.Context, userID int64, folderID int) (domain.DialogList, error) {
list, err := r.pinnedDialogsBaseList(ctx, userID, folderID)
if err != nil {
return domain.DialogList{}, err
}
return r.withCommunityDialogList(ctx, userID, domain.DialogFilter{PinnedOnly: true, HasFolderID: true, FolderID: folderID}, list)
}
func (r *Router) pinnedDialogsBaseList(ctx context.Context, userID int64, folderID int) (domain.DialogList, error) {
if r == nil {
return domain.DialogList{}, nil
}
@ -28,11 +36,19 @@ func (r *Router) pinnedDialogsList(ctx context.Context, userID int64, folderID i
return domain.DialogList{}, err
}
if list, ok := value.(domain.DialogList); ok {
return r.withCommunityDialogList(ctx, userID, domain.DialogFilter{PinnedOnly: true, HasFolderID: true, FolderID: folderID}, list)
return list, nil
}
return domain.DialogList{}, nil
}
func (r *Router) combinedPinnedDialogsList(ctx context.Context, userID int64, folderID int) (domain.DialogList, error) {
list, err := r.pinnedDialogsBaseList(ctx, userID, folderID)
if err != nil {
return domain.DialogList{}, err
}
return r.withCollapsedCommunityDialogs(ctx, userID, domain.DialogFilter{PinnedOnly: true, HasFolderID: true, FolderID: folderID}, list)
}
// combinedPinnedDialogPeers merges ordinary dialogs and collapsed Communities
// by their shared server order. The two persistence implementations deliberately
// store their own rows, but Layer 228 exposes one messages.getPinnedDialogs list.
@ -74,7 +90,7 @@ func combinedPinnedDialogPeers(list domain.DialogList) []domain.Peer {
}
func (r *Router) ensureCombinedPinCapacity(ctx context.Context, userID int64, folderID int, peer domain.Peer) error {
list, err := r.pinnedDialogsList(ctx, userID, folderID)
list, err := r.combinedPinnedDialogsList(ctx, userID, folderID)
if err != nil {
return err
}
@ -94,7 +110,7 @@ func (r *Router) ensureCombinedPinCapacity(ctx context.Context, userID int64, fo
// dialogs and Communities. It is called after the underlying row is pinned so
// both stores can project the same mixed order without owning each other's data.
func (r *Router) promoteCombinedPinnedDialog(ctx context.Context, userID int64, folderID int, peer domain.Peer) error {
list, err := r.pinnedDialogsList(ctx, userID, folderID)
list, err := r.combinedPinnedDialogsList(ctx, userID, folderID)
if err != nil {
return err
}