fix: sync authorize monoforum message views
This commit is contained in:
parent
fd47dd765a
commit
80c36a8ab4
5 changed files with 169 additions and 13 deletions
|
|
@ -17,7 +17,7 @@ func (s *ChannelStore) GetChannelMessageViews(_ context.Context, req domain.Chan
|
|||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
channel, member, err := s.channelAndMemberLocked(req.UserID, req.ChannelID)
|
||||
channel, member, _, err := s.channelForViewerLocked(req.UserID, req.ChannelID)
|
||||
if err != nil {
|
||||
return domain.ChannelMessageViewsResult{}, err
|
||||
}
|
||||
|
|
@ -33,7 +33,8 @@ func (s *ChannelStore) GetChannelMessageViews(_ context.Context, req domain.Chan
|
|||
if _, ok := wanted[msg.ID]; !ok {
|
||||
continue
|
||||
}
|
||||
if msg.Deleted || msg.ID <= member.AvailableMinID {
|
||||
if msg.Deleted || msg.ID <= member.AvailableMinID ||
|
||||
!channelMessageVisibleToViewerLocked(channel, member, req.UserID, msg) {
|
||||
continue
|
||||
}
|
||||
visible[msg.ID] = struct{}{}
|
||||
|
|
|
|||
|
|
@ -141,6 +141,43 @@ func TestSendMonoforumMessageAndHistory(t *testing.T) {
|
|||
if len(exactMessages.Messages) != 1 || exactMessages.Messages[0].ID != m1.Message.ID {
|
||||
t.Fatalf("subscriber exact monoforum messages = %+v, want only own message %d", exactMessages.Messages, m1.Message.ID)
|
||||
}
|
||||
ptsBeforeViews := store.channels[monoID].Pts
|
||||
subViews, err := store.GetChannelMessageViews(ctx, domain.ChannelMessageViewsRequest{
|
||||
UserID: 42, ChannelID: monoID, IDs: []int{m1.Message.ID, otherMessage.Message.ID},
|
||||
Increment: true, Date: 1_700_001_006,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("subscriber get monoforum message views: %v", err)
|
||||
}
|
||||
if len(subViews.Views) != 1 || subViews.Views[m1.Message.ID] != 1 {
|
||||
t.Fatalf("subscriber monoforum views = %+v, want own message %d at 1", subViews.Views, m1.Message.ID)
|
||||
}
|
||||
if _, ok := subViews.Views[otherMessage.Message.ID]; ok {
|
||||
t.Fatalf("subscriber monoforum views leaked other saved_peer message %d", otherMessage.Message.ID)
|
||||
}
|
||||
repeatedViews, err := store.GetChannelMessageViews(ctx, domain.ChannelMessageViewsRequest{
|
||||
UserID: 42, ChannelID: monoID, IDs: []int{m1.Message.ID},
|
||||
Increment: true, Date: 1_700_001_007,
|
||||
})
|
||||
if err != nil || repeatedViews.Views[m1.Message.ID] != 1 {
|
||||
t.Fatalf("repeated subscriber monoforum views = %+v, %v; want idempotent 1", repeatedViews.Views, err)
|
||||
}
|
||||
if got := store.msgViews[monoID][otherMessage.Message.ID]; got != 0 {
|
||||
t.Fatalf("hidden saved_peer views = %d, want 0 before admin view", got)
|
||||
}
|
||||
adminViews, err := store.GetChannelMessageViews(ctx, domain.ChannelMessageViewsRequest{
|
||||
UserID: 1, ChannelID: monoID, IDs: []int{m1.Message.ID, otherMessage.Message.ID},
|
||||
Increment: true, Date: 1_700_001_008,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("admin get monoforum message views: %v", err)
|
||||
}
|
||||
if len(adminViews.Views) != 2 || adminViews.Views[m1.Message.ID] != 2 || adminViews.Views[otherMessage.Message.ID] != 1 {
|
||||
t.Fatalf("admin monoforum views = %+v, want both saved peers at 2/1", adminViews.Views)
|
||||
}
|
||||
if got := store.channels[monoID].Pts; got != ptsBeforeViews {
|
||||
t.Fatalf("message views advanced monoforum pts = %d, want unchanged %d", got, ptsBeforeViews)
|
||||
}
|
||||
if _, err := store.SetChannelMessageReactions(ctx, domain.SetChannelMessageReactionsRequest{
|
||||
UserID: 42, ChannelID: monoID, MessageID: m1.Message.ID,
|
||||
Reactions: []domain.MessageReaction{{Type: domain.MessageReactionEmoji, Emoticon: "\U0001f44d"}},
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ func (s *ChannelStore) GetChannelMessageViews(ctx context.Context, req domain.Ch
|
|||
if req.UserID == 0 || req.ChannelID == 0 {
|
||||
return domain.ChannelMessageViewsResult{}, domain.ErrChannelInvalid
|
||||
}
|
||||
channel, member, err := s.getChannelForMember(ctx, s.db, req.UserID, req.ChannelID)
|
||||
channel, member, _, err := s.getChannelForViewer(ctx, s.db, req.UserID, req.ChannelID)
|
||||
if err != nil {
|
||||
return domain.ChannelMessageViewsResult{}, err
|
||||
}
|
||||
|
|
@ -43,6 +43,16 @@ func (s *ChannelStore) GetChannelMessageViews(ctx context.Context, req domain.Ch
|
|||
if date <= 0 {
|
||||
date = nowUnix()
|
||||
}
|
||||
args := []any{req.ChannelID, id32, req.UserID, date, member.AvailableMinID}
|
||||
visibility := ""
|
||||
if channel.Monoforum && !member.CanManageDirectMessages() {
|
||||
args = append(args, string(domain.PeerTypeUser), req.UserID)
|
||||
visibility = fmt.Sprintf(
|
||||
" AND m.saved_peer_type = $%d AND m.saved_peer_id = $%d",
|
||||
len(args)-1,
|
||||
len(args),
|
||||
)
|
||||
}
|
||||
rows, err := s.db.Query(ctx, `
|
||||
WITH inserted AS (
|
||||
INSERT INTO channel_message_viewers (channel_id, message_id, viewer_user_id, viewed_at)
|
||||
|
|
@ -52,6 +62,7 @@ WITH inserted AS (
|
|||
AND m.id = ANY($2::int[])
|
||||
AND NOT m.deleted
|
||||
AND m.id > $5
|
||||
`+visibility+`
|
||||
ON CONFLICT DO NOTHING
|
||||
RETURNING message_id
|
||||
), updated AS (
|
||||
|
|
@ -65,7 +76,7 @@ WITH inserted AS (
|
|||
)
|
||||
SELECT i.message_id
|
||||
FROM inserted i
|
||||
LEFT JOIN updated u ON u.id = i.message_id`, req.ChannelID, id32, req.UserID, date, member.AvailableMinID)
|
||||
LEFT JOIN updated u ON u.id = i.message_id`, args...)
|
||||
if err != nil {
|
||||
return domain.ChannelMessageViewsResult{}, fmt.Errorf("increment channel message views: %w", err)
|
||||
}
|
||||
|
|
@ -82,7 +93,7 @@ LEFT JOIN updated u ON u.id = i.message_id`, req.ChannelID, id32, req.UserID, da
|
|||
}
|
||||
rows.Close()
|
||||
}
|
||||
summaries, err := s.listChannelMessageViewSummaries(ctx, req.ChannelID, id32, member.AvailableMinID)
|
||||
summaries, err := s.listChannelMessageViewSummaries(ctx, req.UserID, channel, member, id32)
|
||||
if err != nil {
|
||||
return domain.ChannelMessageViewsResult{}, err
|
||||
}
|
||||
|
|
@ -110,13 +121,17 @@ LEFT JOIN updated u ON u.id = i.message_id`, req.ChannelID, id32, req.UserID, da
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (s *ChannelStore) listChannelMessageViewSummaries(ctx context.Context, channelID int64, ids []int32, availableMinID int) ([]channelMessageViewSummary, error) {
|
||||
args := []any{channelID, ids}
|
||||
func (s *ChannelStore) listChannelMessageViewSummaries(ctx context.Context, viewerUserID int64, channel domain.Channel, member domain.ChannelMember, ids []int32) ([]channelMessageViewSummary, error) {
|
||||
args := []any{channel.ID, ids}
|
||||
where := "channel_id = $1 AND id = ANY($2::int[]) AND NOT deleted"
|
||||
if availableMinID > 0 {
|
||||
args = append(args, availableMinID)
|
||||
if member.AvailableMinID > 0 {
|
||||
args = append(args, member.AvailableMinID)
|
||||
where += fmt.Sprintf(" AND id > $%d", len(args))
|
||||
}
|
||||
if channel.Monoforum && !member.CanManageDirectMessages() {
|
||||
args = append(args, string(domain.PeerTypeUser), viewerUserID)
|
||||
where += fmt.Sprintf(" AND saved_peer_type = $%d AND saved_peer_id = $%d", len(args)-1, len(args))
|
||||
}
|
||||
rows, err := s.db.Query(ctx, `
|
||||
SELECT id, views_count, post, discussion_channel_id, discussion_message_id, sender_user_id, from_peer_type, from_peer_id
|
||||
FROM channel_messages
|
||||
|
|
|
|||
|
|
@ -203,6 +203,65 @@ func TestSendMonoforumMessageAndHistoryPostgres(t *testing.T) {
|
|||
if len(exactMessages.Messages) != 1 || exactMessages.Messages[0].ID != m1.Message.ID {
|
||||
t.Fatalf("subscriber exact monoforum messages = %+v, want only own message %d", exactMessages.Messages, m1.Message.ID)
|
||||
}
|
||||
monoBeforeViews, err := channels.GetChannelByID(ctx, monoID)
|
||||
if err != nil {
|
||||
t.Fatalf("get monoforum before views: %v", err)
|
||||
}
|
||||
subViews, err := channels.GetChannelMessageViews(ctx, domain.ChannelMessageViewsRequest{
|
||||
UserID: sub.ID, ChannelID: monoID, IDs: []int{m1.Message.ID, otherMessage.Message.ID},
|
||||
Increment: true, Date: 1700001006,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("subscriber get monoforum message views: %v", err)
|
||||
}
|
||||
if len(subViews.Views) != 1 || subViews.Views[m1.Message.ID] != 1 {
|
||||
t.Fatalf("subscriber monoforum views = %+v, want own message %d at 1", subViews.Views, m1.Message.ID)
|
||||
}
|
||||
if _, ok := subViews.Views[otherMessage.Message.ID]; ok {
|
||||
t.Fatalf("subscriber monoforum views leaked other saved_peer message %d", otherMessage.Message.ID)
|
||||
}
|
||||
var hiddenViews int
|
||||
var hiddenViewer bool
|
||||
if err := pool.QueryRow(ctx, `
|
||||
SELECT m.views_count,
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM channel_message_viewers v
|
||||
WHERE v.channel_id = m.channel_id
|
||||
AND v.message_id = m.id
|
||||
AND v.viewer_user_id = $3
|
||||
)
|
||||
FROM channel_messages m
|
||||
WHERE m.channel_id = $1 AND m.id = $2`, monoID, otherMessage.Message.ID, sub.ID).Scan(&hiddenViews, &hiddenViewer); err != nil {
|
||||
t.Fatalf("load hidden monoforum view state: %v", err)
|
||||
}
|
||||
if hiddenViews != 0 || hiddenViewer {
|
||||
t.Fatalf("hidden monoforum view state = count %d viewer %v, want 0/false", hiddenViews, hiddenViewer)
|
||||
}
|
||||
repeatedViews, err := channels.GetChannelMessageViews(ctx, domain.ChannelMessageViewsRequest{
|
||||
UserID: sub.ID, ChannelID: monoID, IDs: []int{m1.Message.ID},
|
||||
Increment: true, Date: 1700001007,
|
||||
})
|
||||
if err != nil || repeatedViews.Views[m1.Message.ID] != 1 {
|
||||
t.Fatalf("repeated subscriber monoforum views = %+v, %v; want idempotent 1", repeatedViews.Views, err)
|
||||
}
|
||||
adminViews, err := channels.GetChannelMessageViews(ctx, domain.ChannelMessageViewsRequest{
|
||||
UserID: owner.ID, ChannelID: monoID, IDs: []int{m1.Message.ID, otherMessage.Message.ID},
|
||||
Increment: true, Date: 1700001008,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("admin get monoforum message views: %v", err)
|
||||
}
|
||||
if len(adminViews.Views) != 2 || adminViews.Views[m1.Message.ID] != 2 || adminViews.Views[otherMessage.Message.ID] != 1 {
|
||||
t.Fatalf("admin monoforum views = %+v, want both saved peers at 2/1", adminViews.Views)
|
||||
}
|
||||
monoAfterViews, err := channels.GetChannelByID(ctx, monoID)
|
||||
if err != nil {
|
||||
t.Fatalf("get monoforum after views: %v", err)
|
||||
}
|
||||
if monoAfterViews.Pts != monoBeforeViews.Pts {
|
||||
t.Fatalf("message views advanced monoforum pts = %d, want unchanged %d", monoAfterViews.Pts, monoBeforeViews.Pts)
|
||||
}
|
||||
if _, err := channels.SetChannelMessageReactions(ctx, domain.SetChannelMessageReactionsRequest{
|
||||
UserID: sub.ID, ChannelID: monoID, MessageID: m1.Message.ID,
|
||||
Reactions: []domain.MessageReaction{{Type: domain.MessageReactionEmoji, Emoticon: "\U0001f44d"}},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue