MM-57326: [Shared Channels] Message priority, acknowledgement and persistent notifications need to be synced (#30736)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
fa1c77d9b0
Коммит
85391de22a
@@ -82,10 +82,11 @@ func (scs *Service) processSyncMessage(c request.CTX, syncMsg *model.SyncMsg, rc
|
||||
|
||||
var err error
|
||||
syncResp := model.SyncResponse{
|
||||
UserErrors: make([]string, 0),
|
||||
UsersSyncd: make([]string, 0),
|
||||
PostErrors: make([]string, 0),
|
||||
ReactionErrors: make([]string, 0),
|
||||
UserErrors: make([]string, 0),
|
||||
UsersSyncd: make([]string, 0),
|
||||
PostErrors: make([]string, 0),
|
||||
ReactionErrors: make([]string, 0),
|
||||
AcknowledgementErrors: make([]string, 0),
|
||||
}
|
||||
|
||||
// Check if feature flag is enabled for membership changes
|
||||
@@ -103,6 +104,7 @@ func (scs *Service) processSyncMessage(c request.CTX, syncMsg *model.SyncMsg, rc
|
||||
mlog.Int("user_count", len(syncMsg.Users)),
|
||||
mlog.Int("post_count", len(syncMsg.Posts)),
|
||||
mlog.Int("reaction_count", len(syncMsg.Reactions)),
|
||||
mlog.Int("acknowledgement_count", len(syncMsg.Acknowledgements)),
|
||||
mlog.Int("status_count", len(syncMsg.Statuses)),
|
||||
mlog.Int("membership_change_count", len(syncMsg.MembershipChanges)),
|
||||
)
|
||||
@@ -234,6 +236,31 @@ func (scs *Service) processSyncMessage(c request.CTX, syncMsg *model.SyncMsg, rc
|
||||
}
|
||||
}
|
||||
|
||||
// add/remove acknowledgements
|
||||
for _, acknowledgement := range syncMsg.Acknowledgements {
|
||||
if _, err := scs.upsertSyncAcknowledgement(acknowledgement, targetChannel, rc); err != nil {
|
||||
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "Error upserting sync acknowledgement",
|
||||
mlog.String("remote", rc.Name),
|
||||
mlog.String("user_id", acknowledgement.UserId),
|
||||
mlog.String("post_id", acknowledgement.PostId),
|
||||
mlog.Int("acknowledged_at", acknowledgement.AcknowledgedAt),
|
||||
mlog.Err(err),
|
||||
)
|
||||
syncResp.AcknowledgementErrors = append(syncResp.AcknowledgementErrors, acknowledgement.PostId)
|
||||
} else {
|
||||
scs.server.Log().Log(mlog.LvlSharedChannelServiceDebug, "Acknowledgement upserted via sync",
|
||||
mlog.String("remote", rc.Name),
|
||||
mlog.String("user_id", acknowledgement.UserId),
|
||||
mlog.String("post_id", acknowledgement.PostId),
|
||||
mlog.Int("acknowledged_at", acknowledgement.AcknowledgedAt),
|
||||
)
|
||||
|
||||
if syncResp.AcknowledgementsLastUpdateAt < acknowledgement.AcknowledgedAt {
|
||||
syncResp.AcknowledgementsLastUpdateAt = acknowledgement.AcknowledgedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, status := range syncMsg.Statuses {
|
||||
scs.app.SaveAndBroadcastStatus(status)
|
||||
}
|
||||
@@ -431,7 +458,6 @@ func (scs *Service) upsertSyncPost(post *model.Post, targetChannel *model.Channe
|
||||
|
||||
post.RemoteId = model.NewPointer(rc.RemoteId)
|
||||
rctx := request.EmptyContext(scs.server.Log())
|
||||
|
||||
rpost, err := scs.server.GetStore().Post().GetSingle(rctx, post.Id, true)
|
||||
if err != nil {
|
||||
if _, ok := err.(errNotFound); !ok {
|
||||
@@ -460,8 +486,7 @@ func (scs *Service) upsertSyncPost(post *model.Post, targetChannel *model.Channe
|
||||
if appErr == nil {
|
||||
scs.server.Log().Log(mlog.LvlSharedChannelServiceDebug, "Created sync post",
|
||||
mlog.String("post_id", post.Id),
|
||||
mlog.String("channel_id", post.ChannelId),
|
||||
)
|
||||
mlog.String("channel_id", post.ChannelId))
|
||||
}
|
||||
} else if post.DeleteAt > 0 {
|
||||
// delete post
|
||||
@@ -472,14 +497,37 @@ func (scs *Service) upsertSyncPost(post *model.Post, targetChannel *model.Channe
|
||||
mlog.String("channel_id", post.ChannelId),
|
||||
)
|
||||
}
|
||||
} else if post.EditAt > rpost.EditAt || post.Message != rpost.Message {
|
||||
// update post
|
||||
rpost, appErr = scs.app.UpdatePost(request.EmptyContext(scs.server.Log()), post, nil)
|
||||
if appErr == nil {
|
||||
scs.server.Log().Log(mlog.LvlSharedChannelServiceDebug, "Updated sync post",
|
||||
mlog.String("post_id", post.Id),
|
||||
mlog.String("channel_id", post.ChannelId),
|
||||
)
|
||||
} else if post.EditAt > rpost.EditAt || post.Message != rpost.Message || post.UpdateAt > rpost.UpdateAt || post.Metadata != nil {
|
||||
var priority *model.PostPriority
|
||||
var acknowledgements []*model.PostAcknowledgement
|
||||
|
||||
if post.Metadata != nil {
|
||||
// Save the received priority
|
||||
if post.Metadata.Priority != nil {
|
||||
priority = post.Metadata.Priority
|
||||
}
|
||||
|
||||
// Save the received acknowledgements
|
||||
if post.Metadata.Acknowledgements != nil {
|
||||
acknowledgements = post.Metadata.Acknowledgements
|
||||
}
|
||||
}
|
||||
|
||||
// First update the basic post
|
||||
rpost, appErr = scs.app.UpdatePost(rctx, post, nil)
|
||||
if appErr != nil {
|
||||
rerr := errors.New(appErr.Error())
|
||||
return nil, rerr
|
||||
}
|
||||
|
||||
// Handle priority metadata separately if needed
|
||||
if priority != nil {
|
||||
rpost = scs.syncRemotePriorityMetadata(rctx, post, priority, rpost)
|
||||
}
|
||||
|
||||
// Handle acknowledgements metadata separately if needed
|
||||
if acknowledgements != nil {
|
||||
rpost = scs.syncRemoteAcknowledgementsMetadata(rctx, post, acknowledgements, rpost)
|
||||
}
|
||||
} else {
|
||||
// nothing to update
|
||||
@@ -496,6 +544,105 @@ func (scs *Service) upsertSyncPost(post *model.Post, targetChannel *model.Channe
|
||||
return rpost, rerr
|
||||
}
|
||||
|
||||
// syncRemotePriorityMetadata handles syncing priority metadata from a remote post.
|
||||
// It completely replaces existing priority settings with the ones from the remote post,
|
||||
// regardless of update type.
|
||||
func (scs *Service) syncRemotePriorityMetadata(rctx request.CTX, post *model.Post, priority *model.PostPriority, rpost *model.Post) *model.Post {
|
||||
// First, create a new priority object with proper post and channel IDs
|
||||
newPriority := &model.PostPriority{
|
||||
PostId: post.Id,
|
||||
ChannelId: post.ChannelId,
|
||||
}
|
||||
|
||||
// Copy the priority values from the remote post
|
||||
if priority.Priority != nil {
|
||||
newPriority.Priority = priority.Priority
|
||||
}
|
||||
|
||||
if priority.RequestedAck != nil {
|
||||
newPriority.RequestedAck = priority.RequestedAck
|
||||
}
|
||||
|
||||
if priority.PersistentNotifications != nil {
|
||||
newPriority.PersistentNotifications = priority.PersistentNotifications
|
||||
}
|
||||
|
||||
// Save the new priority - this will replace any existing priority for the post
|
||||
savedPriority, priorityErr := scs.server.GetStore().PostPriority().Save(newPriority)
|
||||
if priorityErr != nil {
|
||||
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "Error saving post priority from remote",
|
||||
mlog.String("post_id", post.Id),
|
||||
mlog.String("channel_id", post.ChannelId),
|
||||
mlog.Err(priorityErr),
|
||||
)
|
||||
} else {
|
||||
// If the priority is successfully saved, ensure it's in the returned post
|
||||
if rpost.Metadata == nil {
|
||||
rpost.Metadata = &model.PostMetadata{}
|
||||
}
|
||||
// Use the saved priority from the database operation
|
||||
rpost.Metadata.Priority = savedPriority
|
||||
}
|
||||
|
||||
return rpost
|
||||
}
|
||||
|
||||
// syncRemoteAcknowledgementsMetadata handles syncing acknowledgements metadata from a remote post.
|
||||
// It replaces all existing acknowledgements with the ones from the remote post.
|
||||
func (scs *Service) syncRemoteAcknowledgementsMetadata(rctx request.CTX, post *model.Post, acknowledgements []*model.PostAcknowledgement, rpost *model.Post) *model.Post {
|
||||
// When syncing from remote, we completely replace the existing acknowledgements
|
||||
// with the ones received from the remote, regardless of update type
|
||||
|
||||
// Get existing acknowledgements and delete them using batch operation
|
||||
existingAcks, appErrGet := scs.app.GetAcknowledgementsForPost(post.Id)
|
||||
if appErrGet != nil {
|
||||
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "Error getting existing acknowledgements for remote sync",
|
||||
mlog.String("post_id", post.Id),
|
||||
mlog.Err(appErrGet),
|
||||
)
|
||||
} else if len(existingAcks) > 0 {
|
||||
// Use batch delete for better performance
|
||||
if nErr := scs.server.GetStore().PostAcknowledgement().BatchDelete(existingAcks); nErr != nil {
|
||||
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "Error batch deleting acknowledgements for remote sync",
|
||||
mlog.String("post_id", post.Id),
|
||||
mlog.Int("count", len(existingAcks)),
|
||||
mlog.Err(nErr),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Extract all user IDs from acknowledgements for batch processing
|
||||
userIDs := make([]string, 0, len(acknowledgements))
|
||||
for _, ack := range acknowledgements {
|
||||
userIDs = append(userIDs, ack.UserId)
|
||||
}
|
||||
|
||||
// Use batch operation to save all acknowledgements at once
|
||||
var savedAcks []*model.PostAcknowledgement
|
||||
|
||||
if len(userIDs) > 0 {
|
||||
var appErrAck *model.AppError
|
||||
savedAcks, appErrAck = scs.app.SaveAcknowledgementsForPost(rctx, post.Id, userIDs)
|
||||
if appErrAck != nil {
|
||||
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "Error syncing remote post acknowledgements",
|
||||
mlog.String("post_id", post.Id),
|
||||
mlog.Int("count", len(userIDs)),
|
||||
mlog.Err(appErrAck),
|
||||
)
|
||||
// Fall back to original acknowledgements if batch save fails
|
||||
savedAcks = acknowledgements
|
||||
}
|
||||
}
|
||||
|
||||
// Update acknowledgements in the returned post
|
||||
if rpost.Metadata == nil {
|
||||
rpost.Metadata = &model.PostMetadata{}
|
||||
}
|
||||
rpost.Metadata.Acknowledgements = savedAcks
|
||||
|
||||
return rpost
|
||||
}
|
||||
|
||||
func (scs *Service) upsertSyncReaction(reaction *model.Reaction, targetChannel *model.Channel, rc *model.RemoteCluster) (*model.Reaction, error) {
|
||||
savedReaction := reaction
|
||||
var appErr *model.AppError
|
||||
@@ -542,3 +689,54 @@ func (scs *Service) upsertSyncReaction(reaction *model.Reaction, targetChannel *
|
||||
}
|
||||
return savedReaction, retErr
|
||||
}
|
||||
|
||||
func (scs *Service) upsertSyncAcknowledgement(acknowledgement *model.PostAcknowledgement, targetChannel *model.Channel, rc *model.RemoteCluster) (*model.PostAcknowledgement, error) {
|
||||
savedAcknowledgement := acknowledgement
|
||||
var appErr *model.AppError
|
||||
|
||||
// check that the acknowledgement's post is in the target channel. This ensures the acknowledgement can only be associated with a post
|
||||
// that is in a channel shared with the remote.
|
||||
rctx := request.EmptyContext(scs.server.Log())
|
||||
post, err := scs.server.GetStore().Post().GetSingle(rctx, acknowledgement.PostId, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching post for acknowledgement sync: %w", err)
|
||||
}
|
||||
if post.ChannelId != targetChannel.Id {
|
||||
return nil, fmt.Errorf("acknowledgement sync failed: %w", ErrChannelIDMismatch)
|
||||
}
|
||||
|
||||
existingAcknowledgement, err := scs.server.GetStore().PostAcknowledgement().GetSingle(acknowledgement.UserId, acknowledgement.PostId, rc.RemoteId)
|
||||
if err != nil && !isNotFoundError(err) {
|
||||
return nil, fmt.Errorf("error fetching acknowledgement for sync: %w", err)
|
||||
}
|
||||
|
||||
if existingAcknowledgement == nil {
|
||||
// acknowledgement does not exist; check that user belongs to remote and create acknowledgement
|
||||
// this is not done for delete since deletion can be done by admins on the remote
|
||||
user, err := scs.server.GetStore().User().Get(context.TODO(), acknowledgement.UserId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching user for acknowledgement sync: %w", err)
|
||||
}
|
||||
if user.GetRemoteID() != rc.RemoteId {
|
||||
return nil, fmt.Errorf("acknowledgement sync failed: %w", ErrRemoteIDMismatch)
|
||||
}
|
||||
acknowledgement.RemoteId = model.NewPointer(rc.RemoteId)
|
||||
acknowledgement.ChannelId = targetChannel.Id
|
||||
savedAcknowledgement, appErr = scs.app.SaveAcknowledgementForPostWithModel(request.EmptyContext(scs.server.Log()), acknowledgement)
|
||||
} else {
|
||||
// make sure the acknowledgement being deleted is owned by the remote
|
||||
if existingAcknowledgement.GetRemoteID() != rc.RemoteId {
|
||||
return nil, fmt.Errorf("acknowledgement sync failed: %w", ErrRemoteIDMismatch)
|
||||
}
|
||||
if acknowledgement.AcknowledgedAt == 0 {
|
||||
// Delete the acknowledgement
|
||||
appErr = scs.app.DeleteAcknowledgementForPostWithModel(request.EmptyContext(scs.server.Log()), acknowledgement)
|
||||
}
|
||||
}
|
||||
|
||||
var retErr error
|
||||
if appErr != nil {
|
||||
retErr = errors.New(appErr.Error())
|
||||
}
|
||||
return savedAcknowledgement, retErr
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user