forum: fix reply_to_top_id for replies inside a forum
resolveChannelReply applied discussion-thread logic (reply_to_top_id = the replied-to message's own id) to forum replies. Replying to a General message produced reply_to_top_id = <that id>, a topic no client can resolve: the reply vanished from every topic view and reply-jump on strict clients said "message doesn't exist". Forum replies now inherit the target's topic via domain.ForumReplyTopicID (target's topic, or its own id if it's a topic-create, else General), and General (topic 1) is accepted as a valid virtual topic everywhere, so sends carrying top_msg_id: 1 are no longer rejected. Non-forum discussion threads are unchanged.
This commit is contained in:
parent
f0bf315bf3
commit
c7a77c23c8
4 changed files with 213 additions and 37 deletions
|
|
@ -850,21 +850,14 @@ WHERE owner_user_id=$1 AND peer_type='user' AND peer_id=$2 AND box_id=$3 AND NOT
|
|||
if req.ReplyTo.TopMessageID <= 0 || !channel.Forum {
|
||||
return nil, domain.ErrReplyMessageIDInvalid
|
||||
}
|
||||
topic, err := s.getForumTopic(ctx, db, req.ChannelID, req.ReplyTo.TopMessageID)
|
||||
if err != nil {
|
||||
return nil, domain.ErrReplyMessageIDInvalid
|
||||
}
|
||||
if topic.Hidden {
|
||||
return nil, domain.ErrReplyMessageIDInvalid
|
||||
}
|
||||
if topic.Closed && !canManageForumTopic(channel, member, topic, req.UserID, selfBoostsApplied) {
|
||||
return nil, domain.ErrChannelWriteForbidden
|
||||
}
|
||||
reply := cloneMessageReply(req.ReplyTo)
|
||||
reply.MessageID = 0
|
||||
reply.Peer = channelPeer
|
||||
reply.TopMessageID = topic.TopicID
|
||||
reply.ForumTopic = true
|
||||
if err := s.validateForumReplyTopic(ctx, db, channel, member, req.ReplyTo.TopMessageID, req.UserID, selfBoostsApplied); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reply.TopMessageID = req.ReplyTo.TopMessageID
|
||||
return reply, nil
|
||||
}
|
||||
target, err := s.getChannelMessage(ctx, db, req.ChannelID, req.ReplyTo.MessageID)
|
||||
|
|
@ -880,6 +873,22 @@ WHERE owner_user_id=$1 AND peer_type='user' AND peer_id=$2 AND box_id=$3 AND NOT
|
|||
reply := cloneMessageReply(req.ReplyTo)
|
||||
reply.MessageID = target.ID
|
||||
reply.Peer = channelPeer
|
||||
|
||||
if channel.Forum {
|
||||
// A forum reply belongs to the TARGET's topic, never the target's own id.
|
||||
topicID := domain.ForumReplyTopicID(target)
|
||||
if req.ReplyTo.TopMessageID > 0 && req.ReplyTo.TopMessageID != topicID {
|
||||
return nil, domain.ErrReplyMessageIDInvalid
|
||||
}
|
||||
if err := s.validateForumReplyTopic(ctx, db, channel, member, topicID, req.UserID, selfBoostsApplied); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reply.TopMessageID = topicID
|
||||
reply.ForumTopic = true
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
// Non-forum discussion thread: reply_to_top_id is the comment-thread root.
|
||||
reply.TopMessageID = target.ID
|
||||
if target.ReplyTo != nil && target.ReplyTo.TopMessageID > 0 {
|
||||
reply.TopMessageID = target.ReplyTo.TopMessageID
|
||||
|
|
@ -896,19 +905,29 @@ WHERE owner_user_id=$1 AND peer_type='user' AND peer_id=$2 AND box_id=$3 AND NOT
|
|||
if channel.Forum && req.ReplyTo.TopMessageID > 0 && req.ReplyTo.TopMessageID != reply.TopMessageID {
|
||||
return nil, domain.ErrReplyMessageIDInvalid
|
||||
}
|
||||
if channel.Forum && reply.TopMessageID > 0 {
|
||||
if topic, err := s.getForumTopic(ctx, db, req.ChannelID, reply.TopMessageID); err == nil && !topic.Hidden {
|
||||
if topic.Closed && !canManageForumTopic(channel, member, topic, req.UserID, selfBoostsApplied) {
|
||||
return nil, domain.ErrChannelWriteForbidden
|
||||
}
|
||||
reply.ForumTopic = true
|
||||
} else if err != nil && !errors.Is(err, domain.ErrMessageIDInvalid) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
// validateForumReplyTopic checks that topicID is a topic the caller may post
|
||||
// into. General (ForumGeneralTopicID) is a virtual topic with no
|
||||
// channel_forum_topics row and is always valid.
|
||||
func (s *ChannelStore) validateForumReplyTopic(ctx context.Context, db sqlcgen.DBTX, channel domain.Channel, member domain.ChannelMember, topicID int, userID int64, selfBoostsApplied int) error {
|
||||
if topicID == domain.ForumGeneralTopicID {
|
||||
return nil
|
||||
}
|
||||
topic, err := s.getForumTopic(ctx, db, channel.ID, topicID)
|
||||
if err != nil {
|
||||
return domain.ErrReplyMessageIDInvalid
|
||||
}
|
||||
if topic.Hidden {
|
||||
return domain.ErrReplyMessageIDInvalid
|
||||
}
|
||||
if topic.Closed && !canManageForumTopic(channel, member, topic, userID, selfBoostsApplied) {
|
||||
return domain.ErrChannelWriteForbidden
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func visibleChannelTopAfter(ctx context.Context, db sqlcgen.DBTX, channelID int64, availableMinID int, fallbackDate int) (int, int, error) {
|
||||
var id, date int
|
||||
err := db.QueryRow(ctx, `
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue