MM-57326: [Shared Channels] Message priority, acknowledgement and persistent notifications need to be synced (#30736)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
fa1c77d9b0
Коммит
85391de22a
@@ -205,6 +205,26 @@ func (_m *MockAppIface) CreateUploadSession(c request.CTX, us *model.UploadSessi
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// DeleteAcknowledgementForPostWithModel provides a mock function with given fields: c, acknowledgement
|
||||
func (_m *MockAppIface) DeleteAcknowledgementForPostWithModel(c request.CTX, acknowledgement *model.PostAcknowledgement) *model.AppError {
|
||||
ret := _m.Called(c, acknowledgement)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteAcknowledgementForPostWithModel")
|
||||
}
|
||||
|
||||
var r0 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, *model.PostAcknowledgement) *model.AppError); ok {
|
||||
r0 = rf(c, acknowledgement)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// DeletePost provides a mock function with given fields: c, postID, deleteByID
|
||||
func (_m *MockAppIface) DeletePost(c request.CTX, postID string, deleteByID string) (*model.Post, *model.AppError) {
|
||||
ret := _m.Called(c, postID, deleteByID)
|
||||
@@ -289,6 +309,38 @@ func (_m *MockAppIface) FileReader(path string) (filestore.ReadCloseSeeker, *mod
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetAcknowledgementsForPost provides a mock function with given fields: postID
|
||||
func (_m *MockAppIface) GetAcknowledgementsForPost(postID string) ([]*model.PostAcknowledgement, *model.AppError) {
|
||||
ret := _m.Called(postID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetAcknowledgementsForPost")
|
||||
}
|
||||
|
||||
var r0 []*model.PostAcknowledgement
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(string) ([]*model.PostAcknowledgement, *model.AppError)); ok {
|
||||
return rf(postID)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) []*model.PostAcknowledgement); ok {
|
||||
r0 = rf(postID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.PostAcknowledgement)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
|
||||
r1 = rf(postID)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetOrCreateDirectChannel provides a mock function with given fields: c, userId, otherUserId, channelOptions
|
||||
func (_m *MockAppIface) GetOrCreateDirectChannel(c request.CTX, userId string, otherUserId string, channelOptions ...model.ChannelOption) (*model.Channel, *model.AppError) {
|
||||
_va := make([]interface{}, len(channelOptions))
|
||||
@@ -508,6 +560,26 @@ func (_m *MockAppIface) PermanentDeleteChannel(c request.CTX, channel *model.Cha
|
||||
return r0
|
||||
}
|
||||
|
||||
// PreparePostForClient provides a mock function with given fields: c, post, isNewPost, includeDeleted, includePriority
|
||||
func (_m *MockAppIface) PreparePostForClient(c request.CTX, post *model.Post, isNewPost bool, includeDeleted bool, includePriority bool) *model.Post {
|
||||
ret := _m.Called(c, post, isNewPost, includeDeleted, includePriority)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PreparePostForClient")
|
||||
}
|
||||
|
||||
var r0 *model.Post
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, *model.Post, bool, bool, bool) *model.Post); ok {
|
||||
r0 = rf(c, post, isNewPost, includeDeleted, includePriority)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Post)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Publish provides a mock function with given fields: message
|
||||
func (_m *MockAppIface) Publish(message *model.WebSocketEvent) {
|
||||
_m.Called(message)
|
||||
@@ -533,6 +605,70 @@ func (_m *MockAppIface) RemoveUserFromChannel(c request.CTX, userID string, remo
|
||||
return r0
|
||||
}
|
||||
|
||||
// SaveAcknowledgementForPostWithModel provides a mock function with given fields: c, acknowledgement
|
||||
func (_m *MockAppIface) SaveAcknowledgementForPostWithModel(c request.CTX, acknowledgement *model.PostAcknowledgement) (*model.PostAcknowledgement, *model.AppError) {
|
||||
ret := _m.Called(c, acknowledgement)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SaveAcknowledgementForPostWithModel")
|
||||
}
|
||||
|
||||
var r0 *model.PostAcknowledgement
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, *model.PostAcknowledgement) (*model.PostAcknowledgement, *model.AppError)); ok {
|
||||
return rf(c, acknowledgement)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, *model.PostAcknowledgement) *model.PostAcknowledgement); ok {
|
||||
r0 = rf(c, acknowledgement)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.PostAcknowledgement)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, *model.PostAcknowledgement) *model.AppError); ok {
|
||||
r1 = rf(c, acknowledgement)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SaveAcknowledgementsForPost provides a mock function with given fields: c, postID, userIDs
|
||||
func (_m *MockAppIface) SaveAcknowledgementsForPost(c request.CTX, postID string, userIDs []string) ([]*model.PostAcknowledgement, *model.AppError) {
|
||||
ret := _m.Called(c, postID, userIDs)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SaveAcknowledgementsForPost")
|
||||
}
|
||||
|
||||
var r0 []*model.PostAcknowledgement
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, []string) ([]*model.PostAcknowledgement, *model.AppError)); ok {
|
||||
return rf(c, postID, userIDs)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, []string) []*model.PostAcknowledgement); ok {
|
||||
r0 = rf(c, postID, userIDs)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.PostAcknowledgement)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, string, []string) *model.AppError); ok {
|
||||
r1 = rf(c, postID, userIDs)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SaveAndBroadcastStatus provides a mock function with given fields: status
|
||||
func (_m *MockAppIface) SaveAndBroadcastStatus(status *model.Status) {
|
||||
_m.Called(status)
|
||||
|
||||
@@ -78,6 +78,11 @@ type AppIface interface {
|
||||
OnSharedChannelsAttachmentSyncMsg(fi *model.FileInfo, post *model.Post, rc *model.RemoteCluster) error
|
||||
OnSharedChannelsProfileImageSyncMsg(user *model.User, rc *model.RemoteCluster) error
|
||||
Publish(message *model.WebSocketEvent)
|
||||
SaveAcknowledgementForPostWithModel(c request.CTX, acknowledgement *model.PostAcknowledgement) (*model.PostAcknowledgement, *model.AppError)
|
||||
DeleteAcknowledgementForPostWithModel(c request.CTX, acknowledgement *model.PostAcknowledgement) *model.AppError
|
||||
SaveAcknowledgementsForPost(c request.CTX, postID string, userIDs []string) ([]*model.PostAcknowledgement, *model.AppError)
|
||||
GetAcknowledgementsForPost(postID string) ([]*model.PostAcknowledgement, *model.AppError)
|
||||
PreparePostForClient(c request.CTX, post *model.Post, isNewPost, includeDeleted, includePriority bool) *model.Post
|
||||
}
|
||||
|
||||
// errNotFound allows checking against Store.ErrNotFound errors without making Store a dependency.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -469,6 +469,9 @@ func (scs *Service) handlePostError(postId string, task syncTask, rc *model.Remo
|
||||
return
|
||||
}
|
||||
|
||||
// Populate metadata for the retry post
|
||||
post = scs.app.PreparePostForClient(request.EmptyContext(scs.server.Log()), post, false, false, true)
|
||||
|
||||
syncMsg := model.NewSyncMsg(task.channelID)
|
||||
syncMsg.Posts = []*model.Post{post}
|
||||
|
||||
|
||||
@@ -32,12 +32,13 @@ type syncData struct {
|
||||
rc *model.RemoteCluster
|
||||
scr *model.SharedChannelRemote
|
||||
|
||||
users map[string]*model.User
|
||||
profileImages map[string]*model.User
|
||||
posts []*model.Post
|
||||
reactions []*model.Reaction
|
||||
statuses []*model.Status
|
||||
attachments []attachment
|
||||
users map[string]*model.User
|
||||
profileImages map[string]*model.User
|
||||
posts []*model.Post
|
||||
reactions []*model.Reaction
|
||||
acknowledgements []*model.PostAcknowledgement
|
||||
statuses []*model.Status
|
||||
attachments []attachment
|
||||
|
||||
resultRepeat bool
|
||||
resultNextCursor model.GetPostsSinceForSyncCursor
|
||||
@@ -59,7 +60,7 @@ func newSyncData(task syncTask, rc *model.RemoteCluster, scr *model.SharedChanne
|
||||
}
|
||||
|
||||
func (sd *syncData) isEmpty() bool {
|
||||
return len(sd.users) == 0 && len(sd.profileImages) == 0 && len(sd.posts) == 0 && len(sd.reactions) == 0 && len(sd.attachments) == 0
|
||||
return len(sd.users) == 0 && len(sd.profileImages) == 0 && len(sd.posts) == 0 && len(sd.reactions) == 0 && len(sd.acknowledgements) == 0 && len(sd.attachments) == 0
|
||||
}
|
||||
|
||||
func (sd *syncData) isCursorChanged() bool {
|
||||
@@ -75,6 +76,7 @@ func (sd *syncData) setDataFromMsg(msg *model.SyncMsg) {
|
||||
sd.users = msg.Users
|
||||
sd.posts = msg.Posts
|
||||
sd.reactions = msg.Reactions
|
||||
sd.acknowledgements = msg.Acknowledgements
|
||||
sd.statuses = msg.Statuses
|
||||
}
|
||||
|
||||
@@ -185,6 +187,11 @@ func (scs *Service) syncForRemote(task syncTask, rc *model.RemoteCluster) error
|
||||
return fmt.Errorf("cannot fetch reactions for sync %v: %w", sd, err)
|
||||
}
|
||||
|
||||
// fetch acknowledgements for posts
|
||||
if err := scs.fetchAcknowledgementsForSync(sd); err != nil {
|
||||
return fmt.Errorf("cannot fetch acknowledgements for sync %v: %w", sd, err)
|
||||
}
|
||||
|
||||
// fetch users associated with posts & reactions
|
||||
if err := scs.fetchPostUsersForSync(sd); err != nil {
|
||||
return fmt.Errorf("cannot fetch post users for sync %v: %w", sd, err)
|
||||
@@ -218,6 +225,7 @@ func (scs *Service) syncForRemote(task syncTask, rc *model.RemoteCluster) error
|
||||
mlog.Int("images", len(sd.profileImages)),
|
||||
mlog.Int("posts", len(sd.posts)),
|
||||
mlog.Int("reactions", len(sd.reactions)),
|
||||
mlog.Int("acknowledgements", len(sd.acknowledgements)),
|
||||
mlog.Int("attachments", len(sd.attachments)),
|
||||
)
|
||||
|
||||
@@ -319,6 +327,13 @@ func (scs *Service) fetchPostsForSync(sd *syncData) error {
|
||||
sd.posts = appendPosts(sd.posts, posts, scs.server.GetStore().Post(), cursor.LastPostUpdateAt, scs.server.Log())
|
||||
}
|
||||
|
||||
// Populate metadata for all posts before syncing
|
||||
for i, post := range sd.posts {
|
||||
if post != nil {
|
||||
sd.posts[i] = scs.app.PreparePostForClient(request.EmptyContext(scs.server.Log()), post, false, false, true)
|
||||
}
|
||||
}
|
||||
|
||||
sd.resultNextCursor = nextCursor
|
||||
sd.resultRepeat = count >= maxPostsPerSync
|
||||
|
||||
@@ -377,6 +392,28 @@ func (scs *Service) fetchReactionsForSync(sd *syncData) error {
|
||||
return merr.ErrorOrNil()
|
||||
}
|
||||
|
||||
// fetchAcknowledgementsForSync populates the sync data with any new acknowledgements since the last sync.
|
||||
func (scs *Service) fetchAcknowledgementsForSync(sd *syncData) error {
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
if metrics := scs.server.GetMetrics(); metrics != nil {
|
||||
metrics.ObserveSharedChannelsSyncCollectionStepDuration(sd.rc.RemoteId, "Acknowledgements", time.Since(start).Seconds())
|
||||
}
|
||||
}()
|
||||
|
||||
merr := merror.New()
|
||||
for _, post := range sd.posts {
|
||||
// any acknowledgements originating from the remote cluster are filtered out
|
||||
acknowledgements, err := scs.server.GetStore().PostAcknowledgement().GetForPostSince(post.Id, sd.scr.LastPostUpdateAt, sd.rc.RemoteId, true)
|
||||
if err != nil {
|
||||
merr.Append(fmt.Errorf("could not get acknowledgements for post %s: %w", post.Id, err))
|
||||
continue
|
||||
}
|
||||
sd.acknowledgements = append(sd.acknowledgements, acknowledgements...)
|
||||
}
|
||||
return merr.ErrorOrNil()
|
||||
}
|
||||
|
||||
// fetchPostUsersForSync populates the sync data with all users associated with posts.
|
||||
func (scs *Service) fetchPostUsersForSync(sd *syncData) error {
|
||||
start := time.Now()
|
||||
@@ -402,6 +439,10 @@ func (scs *Service) fetchPostUsersForSync(sd *syncData) error {
|
||||
userIDs[reaction.UserId] = p2mm{}
|
||||
}
|
||||
|
||||
for _, acknowledgement := range sd.acknowledgements {
|
||||
userIDs[acknowledgement.UserId] = p2mm{}
|
||||
}
|
||||
|
||||
for _, post := range sd.posts {
|
||||
// add author
|
||||
userIDs[post.UserId] = p2mm{}
|
||||
@@ -495,7 +536,9 @@ func (scs *Service) filterPostsForSync(sd *syncData) {
|
||||
// - new posts (EditAt == 0)
|
||||
// - edited posts (EditAt >= LastPostUpdateAt)
|
||||
// - deleted posts (DeleteAt > 0)
|
||||
if p.EditAt > 0 && p.EditAt < sd.scr.LastPostUpdateAt && p.DeleteAt == 0 {
|
||||
// - posts with metadata changes (acknowledgements/priority)
|
||||
hasMetadataChanges := p.Metadata != nil && (p.Metadata.Acknowledgements != nil || p.Metadata.Priority != nil)
|
||||
if p.EditAt > 0 && p.EditAt < sd.scr.LastPostUpdateAt && p.DeleteAt == 0 && !hasMetadataChanges {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -514,6 +557,7 @@ func (scs *Service) filterPostsForSync(sd *syncData) {
|
||||
|
||||
filtered = append(filtered, p)
|
||||
}
|
||||
|
||||
sd.posts = filtered
|
||||
}
|
||||
|
||||
@@ -552,6 +596,13 @@ func (scs *Service) sendSyncData(sd *syncData) error {
|
||||
scs.updateCursorForRemote(sd.scr.Id, sd.rc, sd.resultNextCursor)
|
||||
}
|
||||
|
||||
// send acknowledgements
|
||||
if len(sd.acknowledgements) != 0 {
|
||||
if err := scs.sendAcknowledgementSyncData(sd); err != nil {
|
||||
merr.Append(fmt.Errorf("cannot send acknowledgement sync data: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
// send reactions
|
||||
if len(sd.reactions) != 0 {
|
||||
if err := scs.sendReactionSyncData(sd); err != nil {
|
||||
@@ -679,6 +730,29 @@ func (scs *Service) sendReactionSyncData(sd *syncData) error {
|
||||
})
|
||||
}
|
||||
|
||||
// sendAcknowledgementSyncData sends the collected acknowledgement updates to the remote cluster.
|
||||
func (scs *Service) sendAcknowledgementSyncData(sd *syncData) error {
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
if metrics := scs.server.GetMetrics(); metrics != nil {
|
||||
metrics.ObserveSharedChannelsSyncSendStepDuration(sd.rc.RemoteId, "Acknowledgements", time.Since(start).Seconds())
|
||||
}
|
||||
}()
|
||||
|
||||
msg := model.NewSyncMsg(sd.task.channelID)
|
||||
msg.Acknowledgements = sd.acknowledgements
|
||||
|
||||
return scs.sendSyncMsgToRemote(msg, sd.rc, func(syncResp model.SyncResponse, errResp error) {
|
||||
if len(syncResp.AcknowledgementErrors) != 0 {
|
||||
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "Response indicates error for acknowledgement(s) sync",
|
||||
mlog.String("channel_id", sd.task.channelID),
|
||||
mlog.String("remote_id", sd.rc.RemoteId),
|
||||
mlog.Array("acknowledgement_posts", syncResp.AcknowledgementErrors),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// sendStatusSyncData sends the collected status updates to the remote cluster.
|
||||
func (scs *Service) sendStatusSyncData(sd *syncData) error {
|
||||
msg := model.NewSyncMsg(sd.task.channelID)
|
||||
|
||||
Ссылка в новой задаче
Block a user