fix: sync latest channel and contact fixes

This commit is contained in:
A 2026-07-03 13:26:33 +08:00
parent e83650b9fd
commit 35e5d38f4d
11 changed files with 255 additions and 81 deletions

View file

@ -2748,8 +2748,8 @@ func TestPublicChannelPreviewAllowsNonMemberHistory(t *testing.T) {
if err != nil {
t.Fatalf("non-member GetDifference public preview: %v", err)
}
if !diff.Final || diff.Pts != sent.Event.Pts || len(diff.NewMessages) != 1 || diff.NewMessages[0].Body != "public preview post" {
t.Fatalf("preview diff = %+v, want public preview post at current pts", diff)
if !diff.Final || diff.Pts != sent.Event.Pts || len(diff.Events) != 0 || len(diff.NewMessages) != 0 || len(diff.OtherUpdates) != 0 {
t.Fatalf("preview diff = %+v, want empty public preview difference at current pts", diff)
}
if diff.Dialog.UnreadCount != 0 || diff.Dialog.ReadInboxMaxID < sent.Message.ID {
t.Fatalf("preview diff dialog = %+v, want read-only public preview dialog", diff.Dialog)

View file

@ -421,20 +421,38 @@ func (s *Service) GetPeerSettings(ctx context.Context, userID int64, peer domain
return domain.PeerSettings{}, err
}
shareContact := found && !contact.Mutual
needContactsException := false
if s.privacy != nil {
peerCanSeePhone, err := s.privacy.CanSee(ctx, userID, peer.ID, domain.PrivacyKeyPhoneNumber)
peerCanSeePhone, err := s.peerCanSeeCurrentUserPhone(ctx, userID, peer.ID)
if err != nil {
return domain.PeerSettings{}, err
}
needContactsException = !peerCanSeePhone
shareContact = found && !peerCanSeePhone
}
return domain.PeerSettings{
AddContact: !found,
BlockContact: !blocked,
ShareContact: shareContact,
AddContact: !found,
BlockContact: !blocked,
ShareContact: shareContact,
NeedContactsException: needContactsException,
}, nil
}
func (s *Service) peerCanSeeCurrentUserPhone(ctx context.Context, ownerUserID, viewerUserID int64) (bool, error) {
allowed, err := s.privacy.CanSee(ctx, ownerUserID, viewerUserID, domain.PrivacyKeyPhoneNumber)
if err != nil || allowed {
return allowed, err
}
if s.contacts == nil {
return false, nil
}
_, found, err := s.contacts.Get(ctx, viewerUserID, ownerUserID)
if err != nil {
return false, err
}
return found, nil
}
// BlockContact adds peer to the current user's blocklist.
func (s *Service) BlockContact(ctx context.Context, userID, peerUserID int64, date int) (bool, error) {
if s == nil || s.contacts == nil || userID == 0 || peerUserID == 0 || peerUserID == userID {

View file

@ -6,6 +6,7 @@ import (
"reflect"
"testing"
privacyapp "telesrv/internal/app/privacy"
"telesrv/internal/domain"
"telesrv/internal/store"
"telesrv/internal/store/memory"
@ -391,6 +392,94 @@ func TestAddContactNormalizesPhoneToDigits(t *testing.T) {
}
}
func TestAddContactPhonePrivacyExceptionPeerSettings(t *testing.T) {
ctx := context.Background()
users := memory.NewUserStore()
contactsStore := memory.NewContactStore()
privacySvc := privacyapp.NewService(memory.NewPrivacyStore(), contactsStore)
alice, err := users.Create(ctx, domain.User{Phone: "15550000101", FirstName: "Alice", LastName: "A"})
if err != nil {
t.Fatalf("create alice: %v", err)
}
bob, err := users.Create(ctx, domain.User{Phone: "15550000102", FirstName: "Bob", LastName: "B"})
if err != nil {
t.Fatalf("create bob: %v", err)
}
carol, err := users.Create(ctx, domain.User{Phone: "15550000103", FirstName: "Carol", LastName: "C"})
if err != nil {
t.Fatalf("create carol: %v", err)
}
dave, err := users.Create(ctx, domain.User{Phone: "15550000104", FirstName: "Dave", LastName: "D"})
if err != nil {
t.Fatalf("create dave: %v", err)
}
svc := NewService(contactsStore, users).Configure(WithPrivacyEvaluator(privacySvc))
bobSettings, err := svc.GetPeerSettings(ctx, alice.ID, domain.Peer{Type: domain.PeerTypeUser, ID: bob.ID})
if err != nil {
t.Fatalf("bob peer settings before add: %v", err)
}
if !bobSettings.AddContact || bobSettings.ShareContact || !bobSettings.NeedContactsException {
t.Fatalf("bob settings before add = %+v, want add + need exception only", bobSettings)
}
if _, err := svc.AddContact(ctx, alice.ID, domain.ContactInput{
ContactUserID: bob.ID,
Phone: bob.Phone,
FirstName: "Bobby",
}); err != nil {
t.Fatalf("alice add bob: %v", err)
}
bobSettings, err = svc.GetPeerSettings(ctx, alice.ID, domain.Peer{Type: domain.PeerTypeUser, ID: bob.ID})
if err != nil {
t.Fatalf("bob peer settings after add without exception: %v", err)
}
if bobSettings.AddContact || !bobSettings.ShareContact || !bobSettings.NeedContactsException {
t.Fatalf("bob settings after add without exception = %+v, want share + need exception", bobSettings)
}
if allowed, err := privacySvc.CanSee(ctx, alice.ID, bob.ID, domain.PrivacyKeyPhoneNumber); err != nil {
t.Fatalf("bob can see alice phone: %v", err)
} else if allowed {
t.Fatalf("bob can see alice phone = true, want false before exception")
}
if _, err := svc.AddContact(ctx, alice.ID, domain.ContactInput{
ContactUserID: carol.ID,
Phone: carol.Phone,
FirstName: "Carol",
AddPhonePrivacyException: true,
}); err != nil {
t.Fatalf("alice add carol with exception: %v", err)
}
carolSettings, err := svc.GetPeerSettings(ctx, alice.ID, domain.Peer{Type: domain.PeerTypeUser, ID: carol.ID})
if err != nil {
t.Fatalf("carol peer settings after exception: %v", err)
}
if carolSettings.AddContact || carolSettings.ShareContact || carolSettings.NeedContactsException {
t.Fatalf("carol settings after exception = %+v, want no add/share/need exception", carolSettings)
}
if allowed, err := privacySvc.CanSee(ctx, alice.ID, carol.ID, domain.PrivacyKeyPhoneNumber); err != nil {
t.Fatalf("carol can see alice phone: %v", err)
} else if !allowed {
t.Fatalf("carol can see alice phone = false, want true after exception")
}
if _, err := contactsStore.Upsert(ctx, dave.ID, domain.ContactInput{
ContactUserID: alice.ID,
Phone: alice.Phone,
FirstName: alice.FirstName,
LastName: alice.LastName,
}); err != nil {
t.Fatalf("dave add alice: %v", err)
}
daveSettings, err := svc.GetPeerSettings(ctx, alice.ID, domain.Peer{Type: domain.PeerTypeUser, ID: dave.ID})
if err != nil {
t.Fatalf("dave peer settings before alice add: %v", err)
}
if !daveSettings.AddContact || daveSettings.ShareContact || daveSettings.NeedContactsException {
t.Fatalf("dave settings with reverse contact = %+v, want add only", daveSettings)
}
}
func TestAcceptContactRequiresExistingContactRequest(t *testing.T) {
ctx := context.Background()
users := memory.NewUserStore()

View file

@ -336,6 +336,9 @@ func (s *Service) appendMissingChannelPeerPreviews(ctx context.Context, userID i
if !ok || view.Forbidden {
continue
}
if view.Self.Status != domain.ChannelMemberActive {
continue
}
history, err := s.channels.ListChannelHistory(ctx, userID, domain.ChannelHistoryFilter{
ChannelID: channelID,
Limit: 1,

View file

@ -695,7 +695,7 @@ func TestGetPeerDialogsRejectsHugeVector(t *testing.T) {
}
}
func TestGetPeerDialogsIncludesPublicChannelPreviewForNonMember(t *testing.T) {
func TestGetPeerDialogsSkipsPublicChannelPreviewForNonMember(t *testing.T) {
ctx := context.Background()
channelStore := memory.NewChannelStore()
channels := appchannels.NewService(channelStore)
@ -716,13 +716,12 @@ func TestGetPeerDialogsIncludesPublicChannelPreviewForNonMember(t *testing.T) {
}); err != nil {
t.Fatalf("UpdateUsername public: %v", err)
}
sent, err := channels.SendMessage(ctx, 1001, domain.SendChannelMessageRequest{
if _, err := channels.SendMessage(ctx, 1001, domain.SendChannelMessageRequest{
ChannelID: public.Channel.ID,
RandomID: 99,
Message: "public peer dialog top",
Date: 1700002010,
})
if err != nil {
}); err != nil {
t.Fatalf("SendMessage public: %v", err)
}
private, err := channels.CreateChannel(ctx, 1001, domain.CreateChannelRequest{
@ -741,28 +740,12 @@ func TestGetPeerDialogsIncludesPublicChannelPreviewForNonMember(t *testing.T) {
if err != nil {
t.Fatalf("GetPeerDialogs public preview: %v", err)
}
if len(list.Dialogs) != 1 {
t.Fatalf("dialogs = %+v, want only public preview dialog", list.Dialogs)
}
dialog := findChannelDialog(t, list, public.Channel.ID)
if dialog.TopMessage != sent.Message.ID || dialog.TopMessageDate != sent.Message.Date {
t.Fatalf("preview dialog top = id %d date %d, want %d/%d", dialog.TopMessage, dialog.TopMessageDate, sent.Message.ID, sent.Message.Date)
}
if !dialog.ChannelLeft {
t.Fatalf("preview dialog ChannelLeft = false, want read-only left preview")
}
if dialog.UnreadCount != 0 || dialog.ReadInboxMaxID < sent.Message.ID || dialog.ReadOutboxMaxID < sent.Message.ID {
t.Fatalf("preview dialog read/unread = %+v, want read through top and no unread", dialog)
}
if len(list.ChannelMessages) != 1 || list.ChannelMessages[0].Body != "public peer dialog top" {
t.Fatalf("channel messages = %+v, want public top message", list.ChannelMessages)
}
if len(list.Channels) != 1 || list.Channels[0].ID != public.Channel.ID {
t.Fatalf("channels = %+v, want public channel shell", list.Channels)
if len(list.Dialogs) != 0 || len(list.ChannelMessages) != 0 || len(list.Channels) != 0 || list.Count != 0 {
t.Fatalf("peer dialogs = %+v, want no materialized public preview dialog", list)
}
}
func TestGetPeerDialogsBatchesMissingChannelPreviews(t *testing.T) {
func TestGetPeerDialogsBatchesMissingChannelVisibilityChecks(t *testing.T) {
ctx := context.Background()
channelStore := &countingDialogChannelStore{ChannelStore: memory.NewChannelStore()}
channels := appchannels.NewService(channelStore)
@ -783,13 +766,12 @@ func TestGetPeerDialogsBatchesMissingChannelPreviews(t *testing.T) {
}); err != nil {
t.Fatalf("UpdateUsername first: %v", err)
}
firstMsg, err := channels.SendMessage(ctx, 1001, domain.SendChannelMessageRequest{
if _, err := channels.SendMessage(ctx, 1001, domain.SendChannelMessageRequest{
ChannelID: first.Channel.ID,
RandomID: 101,
Message: "first public preview",
Date: 1700002110,
})
if err != nil {
}); err != nil {
t.Fatalf("SendMessage first: %v", err)
}
@ -808,13 +790,12 @@ func TestGetPeerDialogsBatchesMissingChannelPreviews(t *testing.T) {
}); err != nil {
t.Fatalf("UpdateUsername second: %v", err)
}
secondMsg, err := channels.SendMessage(ctx, 1001, domain.SendChannelMessageRequest{
if _, err := channels.SendMessage(ctx, 1001, domain.SendChannelMessageRequest{
ChannelID: second.Channel.ID,
RandomID: 102,
Message: "second public preview",
Date: 1700002130,
})
if err != nil {
}); err != nil {
t.Fatalf("SendMessage second: %v", err)
}
@ -839,19 +820,10 @@ func TestGetPeerDialogsBatchesMissingChannelPreviews(t *testing.T) {
t.Fatalf("GetPeerDialogs batch previews: %v", err)
}
if channelStore.getChannelsCalls != 1 || channelStore.getChannelCalls != 0 {
t.Fatalf("preview channel calls: GetChannels=%d GetChannel=%d, want one batch call only", channelStore.getChannelsCalls, channelStore.getChannelCalls)
t.Fatalf("visibility channel calls: GetChannels=%d GetChannel=%d, want one batch call only", channelStore.getChannelsCalls, channelStore.getChannelCalls)
}
if len(list.Dialogs) != 2 {
t.Fatalf("dialogs = %+v, want two public previews", list.Dialogs)
}
if got := findChannelDialog(t, list, first.Channel.ID); got.TopMessage != firstMsg.Message.ID {
t.Fatalf("first preview top = %d, want %d", got.TopMessage, firstMsg.Message.ID)
}
if got := findChannelDialog(t, list, second.Channel.ID); got.TopMessage != secondMsg.Message.ID {
t.Fatalf("second preview top = %d, want %d", got.TopMessage, secondMsg.Message.ID)
}
if len(list.ChannelMessages) != 2 {
t.Fatalf("channel messages = %+v, want two top messages", list.ChannelMessages)
if len(list.Dialogs) != 0 || len(list.ChannelMessages) != 0 || len(list.Channels) != 0 || list.Count != 0 {
t.Fatalf("peer dialogs = %+v, want no public preview dialogs", list)
}
}

View file

@ -135,20 +135,9 @@ func TestPublicChannelPreviewRPCsAllowNonMember(t *testing.T) {
if err != nil {
t.Fatalf("non-member getChannelDifference public preview: %v", err)
}
fullDiff, ok := diff.(*tg.UpdatesChannelDifference)
if !ok || !fullDiff.Final || fullDiff.Pts != sent.Event.Pts || len(fullDiff.NewMessages) != 1 {
t.Fatalf("channel difference = %T %+v, want one public preview message at current pts", diff, diff)
}
diffMsg, ok := fullDiff.NewMessages[0].(*tg.Message)
if !ok || diffMsg.Message != "public preview rpc post" {
t.Fatalf("channel difference message = %T %+v, want public preview rpc post", fullDiff.NewMessages[0], fullDiff.NewMessages[0])
}
if len(fullDiff.Chats) != 1 {
t.Fatalf("channel difference chats = %d, want public channel chat", len(fullDiff.Chats))
}
diffChat, ok := fullDiff.Chats[0].(*tg.Channel)
if !ok || !diffChat.Left || diffChat.ID != public.Channel.ID {
t.Fatalf("channel difference chat = %T %+v, want left public channel", fullDiff.Chats[0], fullDiff.Chats[0])
emptyDiff, ok := diff.(*tg.UpdatesChannelDifferenceEmpty)
if !ok || !emptyDiff.Final || emptyDiff.Pts != sent.Event.Pts {
t.Fatalf("channel difference = %T %+v, want empty public preview difference at current pts", diff, diff)
}
domainPeers, err := r.dialogPeersFromInput(WithUserID(ctx, viewer.ID), viewer.ID, []tg.InputDialogPeerClass{&tg.InputDialogPeer{Peer: peer}})
@ -162,8 +151,8 @@ func TestPublicChannelPreviewRPCsAllowNonMember(t *testing.T) {
if err != nil {
t.Fatalf("dialog service public preview: %v", err)
}
if len(directPeerDialogs.Dialogs) != 1 || len(directPeerDialogs.ChannelMessages) != 1 || len(directPeerDialogs.Channels) != 1 {
t.Fatalf("direct peer dialogs = %+v, want one dialog/message/channel", directPeerDialogs)
if len(directPeerDialogs.Dialogs) != 0 || len(directPeerDialogs.ChannelMessages) != 0 || len(directPeerDialogs.Channels) != 0 {
t.Fatalf("direct peer dialogs = %+v, want no public preview dialog/message/channel", directPeerDialogs)
}
peerDialogsReq := &tg.MessagesGetPeerDialogsRequest{
@ -181,20 +170,8 @@ func TestPublicChannelPreviewRPCsAllowNonMember(t *testing.T) {
if !ok {
t.Fatalf("getPeerDialogs response = %T, want peer dialogs", peerDialogsEnc)
}
if len(peerDialogs.Dialogs) != 1 || len(peerDialogs.Messages) != 1 || len(peerDialogs.Chats) != 1 {
t.Fatalf("peer dialogs = %+v, want one dialog/message/channel", peerDialogs)
}
tgDialog, ok := peerDialogs.Dialogs[0].(*tg.Dialog)
if !ok || tgDialog.TopMessage <= 0 || tgDialog.UnreadCount != 0 {
t.Fatalf("peer dialog = %T %+v, want read-only public preview dialog", peerDialogs.Dialogs[0], peerDialogs.Dialogs[0])
}
tgMessage, ok := peerDialogs.Messages[0].(*tg.Message)
if !ok || tgMessage.Message != "public preview rpc post" {
t.Fatalf("peer dialog message = %T %+v, want public preview rpc post", peerDialogs.Messages[0], peerDialogs.Messages[0])
}
peerDialogChat, ok := peerDialogs.Chats[0].(*tg.Channel)
if !ok || !peerDialogChat.Left || peerDialogChat.ID != public.Channel.ID {
t.Fatalf("peer dialog chat = %T %+v, want left public channel", peerDialogs.Chats[0], peerDialogs.Chats[0])
if len(peerDialogs.Dialogs) != 0 || len(peerDialogs.Messages) != 0 || len(peerDialogs.Chats) != 0 {
t.Fatalf("peer dialogs = %+v, want no public preview dialog/message/channel", peerDialogs)
}
if _, err := channelService.JoinChannel(ctx, viewer.ID, public.Channel.ID, 1700010120); err != nil {

View file

@ -10,6 +10,7 @@ import (
"strings"
appchannels "telesrv/internal/app/channels"
appcontacts "telesrv/internal/app/contacts"
appprivacy "telesrv/internal/app/privacy"
appstories "telesrv/internal/app/stories"
appupdates "telesrv/internal/app/updates"
appusers "telesrv/internal/app/users"
@ -1057,6 +1058,84 @@ func TestContactsAcceptContactReturnsSettingsAndReset(t *testing.T) {
}
}
func TestContactsAddContactPhonePrivacyExceptionUpdatesPeerSettings(t *testing.T) {
ctx := context.Background()
userStore := memory.NewUserStore()
contactsStore := memory.NewContactStore()
privacySvc := appprivacy.NewService(memory.NewPrivacyStore(), contactsStore)
alice, err := userStore.Create(ctx, domain.User{AccessHash: 31, Phone: "2001", FirstName: "Alice", LastName: "A"})
if err != nil {
t.Fatalf("create alice: %v", err)
}
bob, err := userStore.Create(ctx, domain.User{AccessHash: 32, Phone: "2002", FirstName: "Bob", LastName: "B"})
if err != nil {
t.Fatalf("create bob: %v", err)
}
carol, err := userStore.Create(ctx, domain.User{AccessHash: 33, Phone: "2003", FirstName: "Carol", LastName: "C"})
if err != nil {
t.Fatalf("create carol: %v", err)
}
contactsSvc := appcontacts.NewService(contactsStore, userStore).Configure(appcontacts.WithPrivacyEvaluator(privacySvc))
usersSvc := appusers.NewService(userStore, appusers.WithContactStore(contactsStore), appusers.WithPrivacyEvaluator(privacySvc))
r := New(Config{}, Deps{
Contacts: contactsSvc,
Users: usersSvc,
}, zaptest.NewLogger(t), fixedClock{now: time.Unix(1700000410, 0)})
withoutException, err := r.onContactsAddContact(WithUserID(ctx, alice.ID), &tg.ContactsAddContactRequest{
ID: &tg.InputUser{UserID: bob.ID, AccessHash: bob.AccessHash},
Phone: bob.Phone,
FirstName: "Bobby",
})
if err != nil {
t.Fatalf("contacts.addContact without exception: %v", err)
}
bobSettings := firstPeerSettingsUpdate(t, withoutException)
if bobSettings.Settings.AddContact || !bobSettings.Settings.ShareContact || !bobSettings.Settings.NeedContactsException {
t.Fatalf("bob peer settings = %+v, want share + need exception", bobSettings.Settings)
}
if allowed, err := privacySvc.CanSee(ctx, alice.ID, bob.ID, domain.PrivacyKeyPhoneNumber); err != nil {
t.Fatalf("bob can see alice phone: %v", err)
} else if allowed {
t.Fatalf("bob can see alice phone = true, want false before exception")
}
withException, err := r.onContactsAddContact(WithUserID(ctx, alice.ID), &tg.ContactsAddContactRequest{
AddPhonePrivacyException: true,
ID: &tg.InputUser{UserID: carol.ID, AccessHash: carol.AccessHash},
Phone: carol.Phone,
FirstName: "Carol",
})
if err != nil {
t.Fatalf("contacts.addContact with exception: %v", err)
}
carolSettings := firstPeerSettingsUpdate(t, withException)
if carolSettings.Settings.AddContact || carolSettings.Settings.ShareContact || carolSettings.Settings.NeedContactsException {
t.Fatalf("carol peer settings = %+v, want no add/share/need exception", carolSettings.Settings)
}
if allowed, err := privacySvc.CanSee(ctx, alice.ID, carol.ID, domain.PrivacyKeyPhoneNumber); err != nil {
t.Fatalf("carol can see alice phone: %v", err)
} else if !allowed {
t.Fatalf("carol can see alice phone = false, want true after exception")
}
}
func firstPeerSettingsUpdate(t *testing.T, updates tg.UpdatesClass) *tg.UpdatePeerSettings {
t.Helper()
got, ok := updates.(*tg.Updates)
if !ok {
t.Fatalf("updates = %T, want *tg.Updates", updates)
}
if len(got.Updates) == 0 {
t.Fatalf("updates = %+v, want UpdatePeerSettings", got.Updates)
}
settings, ok := got.Updates[0].(*tg.UpdatePeerSettings)
if !ok {
t.Fatalf("update[0] = %T, want UpdatePeerSettings", got.Updates[0])
}
return settings
}
func TestContactsStatusesUsesOnlineSessionFallback(t *testing.T) {
ctx := context.Background()
alice := domain.User{ID: 1000000001, AccessHash: 11, FirstName: "Alice"}

View file

@ -2,6 +2,7 @@ package memory
import (
"context"
"errors"
"sort"
"telesrv/internal/domain"
)
@ -119,8 +120,23 @@ func (s *ChannelStore) GetChannelDialogs(_ context.Context, viewerUserID int64,
continue
}
seen[channelID] = struct{}{}
channel, member, _, err := s.channelForViewerLocked(viewerUserID, channelID)
channel, member, err := s.channelAndMemberLocked(viewerUserID, channelID)
if err != nil {
if !errors.Is(err, domain.ErrChannelPrivate) && !errors.Is(err, domain.ErrChannelInvalid) {
continue
}
var ok bool
channel, ok = s.channels[channelID]
if !ok || channel.Deleted || !channel.Monoforum || channel.LinkedMonoforumID == 0 {
continue
}
parentMember, ok := s.members[channel.LinkedMonoforumID][viewerUserID]
if !ok || parentMember.Status != domain.ChannelMemberActive || !isChannelAdmin(parentMember) {
continue
}
member = syntheticMonoforumAdminMember(channel, parentMember)
}
if member.Status != domain.ChannelMemberActive {
continue
}
dialog := channelDialogToDialog(s.dialogForMemberLocked(viewerUserID, channel, member), channel.Pts, member.Status)

View file

@ -27,6 +27,16 @@ func (s *ChannelStore) ListChannelDifference(_ context.Context, req domain.Chann
if preview {
dialog = previewChannelDialog(req.UserID, channel, member)
}
if preview && member.Status != domain.ChannelMemberActive {
return domain.ChannelDifference{
Channel: channel,
Self: member,
Pts: channel.Pts,
Final: true,
Timeout: 30,
Dialog: dialog,
}, nil
}
if channel.Pts-req.Pts > limit {
messages := make([]domain.ChannelMessage, 0, domain.MaxChannelDifferenceTooLongMessages)
for i := len(s.messages[req.ChannelID]) - 1; i >= 0 && len(messages) < domain.MaxChannelDifferenceTooLongMessages; i-- {

View file

@ -115,7 +115,7 @@ func TestChannelStoreDifferenceStartsAtMemberAvailableMinPts(t *testing.T) {
}
}
func TestChannelStorePublicPreviewDifferenceAllowsNonMember(t *testing.T) {
func TestChannelStorePublicPreviewDifferenceSkipsNonMemberMessages(t *testing.T) {
pool := testPool(t)
ctx := context.Background()
suffix := randomSuffix(t)
@ -183,8 +183,8 @@ func TestChannelStorePublicPreviewDifferenceAllowsNonMember(t *testing.T) {
if err != nil {
t.Fatalf("list public preview difference: %v", err)
}
if !diff.Final || diff.Pts != sent.Event.Pts || len(diff.NewMessages) != 1 || diff.NewMessages[0].Body != "public preview difference" {
t.Fatalf("preview diff = %+v, want one public preview message at current pts", diff)
if !diff.Final || diff.Pts != sent.Event.Pts || len(diff.Events) != 0 || len(diff.NewMessages) != 0 || len(diff.OtherUpdates) != 0 {
t.Fatalf("preview diff = %+v, want empty public preview difference at current pts", diff)
}
if diff.Dialog.UnreadCount != 0 || diff.Dialog.ReadInboxMaxID < sent.Message.ID {
t.Fatalf("preview diff dialog = %+v, want read-only public preview dialog", diff.Dialog)

View file

@ -27,6 +27,16 @@ func (s *ChannelStore) ListChannelDifference(ctx context.Context, req domain.Cha
if limit <= 0 || limit > domain.MaxChannelDifferenceLimit {
limit = domain.MaxChannelDifferenceLimit
}
if preview && member.Status != domain.ChannelMemberActive {
return domain.ChannelDifference{
Channel: channel,
Self: member,
Pts: channel.Pts,
Final: true,
Timeout: 30,
Dialog: previewChannelDialog(req.UserID, channel, member),
}, nil
}
if channel.Pts-req.Pts > limit {
args := []any{req.ChannelID}
where := "channel_id = $1 AND NOT deleted"