MM-35133 trigger sync user immediately after change (#17579)
- ensure changes to user profile sync immediately - refactor sync send
Этот коммит содержится в:
@@ -1071,7 +1071,7 @@ type AppIface interface {
|
|||||||
UpdateScheme(scheme *model.Scheme) (*model.Scheme, *model.AppError)
|
UpdateScheme(scheme *model.Scheme) (*model.Scheme, *model.AppError)
|
||||||
UpdateSessionsIsGuest(userID string, isGuest bool)
|
UpdateSessionsIsGuest(userID string, isGuest bool)
|
||||||
UpdateSharedChannel(sc *model.SharedChannel) (*model.SharedChannel, error)
|
UpdateSharedChannel(sc *model.SharedChannel) (*model.SharedChannel, error)
|
||||||
UpdateSharedChannelRemoteNextSyncAt(id string, syncTime int64) error
|
UpdateSharedChannelRemoteCursor(id string, cursor model.GetPostsSinceForSyncCursor) error
|
||||||
UpdateSidebarCategories(userID, teamID string, categories []*model.SidebarCategoryWithChannels) ([]*model.SidebarCategoryWithChannels, *model.AppError)
|
UpdateSidebarCategories(userID, teamID string, categories []*model.SidebarCategoryWithChannels) ([]*model.SidebarCategoryWithChannels, *model.AppError)
|
||||||
UpdateSidebarCategoryOrder(userID, teamID string, categoryOrder []string) *model.AppError
|
UpdateSidebarCategoryOrder(userID, teamID string, categoryOrder []string) *model.AppError
|
||||||
UpdateTeam(team *model.Team) (*model.Team, *model.AppError)
|
UpdateTeam(team *model.Team) (*model.Team, *model.AppError)
|
||||||
|
|||||||
@@ -16427,9 +16427,9 @@ func (a *OpenTracingAppLayer) UpdateSharedChannel(sc *model.SharedChannel) (*mod
|
|||||||
return resultVar0, resultVar1
|
return resultVar0, resultVar1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *OpenTracingAppLayer) UpdateSharedChannelRemoteNextSyncAt(id string, syncTime int64) error {
|
func (a *OpenTracingAppLayer) UpdateSharedChannelRemoteCursor(id string, cursor model.GetPostsSinceForSyncCursor) error {
|
||||||
origCtx := a.ctx
|
origCtx := a.ctx
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateSharedChannelRemoteNextSyncAt")
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateSharedChannelRemoteCursor")
|
||||||
|
|
||||||
a.ctx = newCtx
|
a.ctx = newCtx
|
||||||
a.app.Srv().Store.SetContext(newCtx)
|
a.app.Srv().Store.SetContext(newCtx)
|
||||||
@@ -16439,7 +16439,7 @@ func (a *OpenTracingAppLayer) UpdateSharedChannelRemoteNextSyncAt(id string, syn
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
resultVar0 := a.app.UpdateSharedChannelRemoteNextSyncAt(id, syncTime)
|
resultVar0 := a.app.UpdateSharedChannelRemoteCursor(id, cursor)
|
||||||
|
|
||||||
if resultVar0 != nil {
|
if resultVar0 != nil {
|
||||||
span.LogFields(spanlog.Error(resultVar0))
|
span.LogFields(spanlog.Error(resultVar0))
|
||||||
|
|||||||
@@ -2190,8 +2190,8 @@ func TestSharedChannelSyncForPostActions(t *testing.T) {
|
|||||||
}, channel, false, true)
|
}, channel, false, true)
|
||||||
require.Nil(t, err, "Creating a post should not error")
|
require.Nil(t, err, "Creating a post should not error")
|
||||||
|
|
||||||
assert.Len(t, remoteClusterService.notifications, 1)
|
assert.Len(t, remoteClusterService.channelNotifications, 1)
|
||||||
assert.Equal(t, channel.Id, remoteClusterService.notifications[0])
|
assert.Equal(t, channel.Id, remoteClusterService.channelNotifications[0])
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("updating a post in a shared channel performs a content sync when sync service is running on that node", func(t *testing.T) {
|
t.Run("updating a post in a shared channel performs a content sync when sync service is running on that node", func(t *testing.T) {
|
||||||
@@ -2217,9 +2217,9 @@ func TestSharedChannelSyncForPostActions(t *testing.T) {
|
|||||||
_, err = th.App.UpdatePost(th.Context, post, true)
|
_, err = th.App.UpdatePost(th.Context, post, true)
|
||||||
require.Nil(t, err, "Updating a post should not error")
|
require.Nil(t, err, "Updating a post should not error")
|
||||||
|
|
||||||
assert.Len(t, remoteClusterService.notifications, 2)
|
assert.Len(t, remoteClusterService.channelNotifications, 2)
|
||||||
assert.Equal(t, channel.Id, remoteClusterService.notifications[0])
|
assert.Equal(t, channel.Id, remoteClusterService.channelNotifications[0])
|
||||||
assert.Equal(t, channel.Id, remoteClusterService.notifications[1])
|
assert.Equal(t, channel.Id, remoteClusterService.channelNotifications[1])
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("deleting a post in a shared channel performs a content sync when sync service is running on that node", func(t *testing.T) {
|
t.Run("deleting a post in a shared channel performs a content sync when sync service is running on that node", func(t *testing.T) {
|
||||||
@@ -2246,9 +2246,9 @@ func TestSharedChannelSyncForPostActions(t *testing.T) {
|
|||||||
require.Nil(t, err, "Deleting a post should not error")
|
require.Nil(t, err, "Deleting a post should not error")
|
||||||
|
|
||||||
// one creation and two deletes
|
// one creation and two deletes
|
||||||
assert.Len(t, remoteClusterService.notifications, 3)
|
assert.Len(t, remoteClusterService.channelNotifications, 3)
|
||||||
assert.Equal(t, channel.Id, remoteClusterService.notifications[0])
|
assert.Equal(t, channel.Id, remoteClusterService.channelNotifications[0])
|
||||||
assert.Equal(t, channel.Id, remoteClusterService.notifications[1])
|
assert.Equal(t, channel.Id, remoteClusterService.channelNotifications[1])
|
||||||
assert.Equal(t, channel.Id, remoteClusterService.notifications[2])
|
assert.Equal(t, channel.Id, remoteClusterService.channelNotifications[2])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,9 +44,9 @@ func TestSharedChannelSyncForReactionActions(t *testing.T) {
|
|||||||
|
|
||||||
th.TearDown() // We need to enforce teardown because reaction instrumentation happens in a goroutine
|
th.TearDown() // We need to enforce teardown because reaction instrumentation happens in a goroutine
|
||||||
|
|
||||||
assert.Len(t, sharedChannelService.notifications, 2)
|
assert.Len(t, sharedChannelService.channelNotifications, 2)
|
||||||
assert.Equal(t, channel.Id, sharedChannelService.notifications[0])
|
assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[0])
|
||||||
assert.Equal(t, channel.Id, sharedChannelService.notifications[1])
|
assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[1])
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("removing a reaction in a shared channel performs a content sync when sync service is running on that node", func(t *testing.T) {
|
t.Run("removing a reaction in a shared channel performs a content sync when sync service is running on that node", func(t *testing.T) {
|
||||||
@@ -79,8 +79,8 @@ func TestSharedChannelSyncForReactionActions(t *testing.T) {
|
|||||||
|
|
||||||
th.TearDown() // We need to enforce teardown because reaction instrumentation happens in a goroutine
|
th.TearDown() // We need to enforce teardown because reaction instrumentation happens in a goroutine
|
||||||
|
|
||||||
assert.Len(t, sharedChannelService.notifications, 2)
|
assert.Len(t, sharedChannelService.channelNotifications, 2)
|
||||||
assert.Equal(t, channel.Id, sharedChannelService.notifications[0])
|
assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[0])
|
||||||
assert.Equal(t, channel.Id, sharedChannelService.notifications[1])
|
assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[1])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ type Server struct {
|
|||||||
|
|
||||||
telemetryService *telemetry.TelemetryService
|
telemetryService *telemetry.TelemetryService
|
||||||
|
|
||||||
|
serviceMux sync.RWMutex
|
||||||
remoteClusterService remotecluster.RemoteClusterServiceIFace
|
remoteClusterService remotecluster.RemoteClusterServiceIFace
|
||||||
sharedChannelService SharedChannelServiceIFace
|
sharedChannelService SharedChannelServiceIFace
|
||||||
|
|
||||||
@@ -873,16 +874,19 @@ func (s *Server) startInterClusterServices(license *model.License, app *App) err
|
|||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
s.remoteClusterService, err = remotecluster.NewRemoteClusterService(s)
|
rcs, err := remotecluster.NewRemoteClusterService(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = s.remoteClusterService.Start(); err != nil {
|
if err = rcs.Start(); err != nil {
|
||||||
s.remoteClusterService = nil
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.serviceMux.Lock()
|
||||||
|
s.remoteClusterService = rcs
|
||||||
|
s.serviceMux.Unlock()
|
||||||
|
|
||||||
// Shared Channels service
|
// Shared Channels service
|
||||||
|
|
||||||
// License check
|
// License check
|
||||||
@@ -897,15 +901,19 @@ func (s *Server) startInterClusterServices(license *model.License, app *App) err
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
s.sharedChannelService, err = sharedchannel.NewSharedChannelService(s, app)
|
scs, err := sharedchannel.NewSharedChannelService(s, app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = s.sharedChannelService.Start(); err != nil {
|
if err = scs.Start(); err != nil {
|
||||||
s.remoteClusterService = nil
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.serviceMux.Lock()
|
||||||
|
s.sharedChannelService = scs
|
||||||
|
s.serviceMux.Unlock()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -967,11 +975,18 @@ func (s *Server) Shutdown() {
|
|||||||
mlog.Warn("Unable to cleanly shutdown telemetry client", mlog.Err(err))
|
mlog.Warn("Unable to cleanly shutdown telemetry client", mlog.Err(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.serviceMux.RLock()
|
||||||
|
if s.sharedChannelService != nil {
|
||||||
|
if err = s.sharedChannelService.Shutdown(); err != nil {
|
||||||
|
mlog.Error("Error shutting down shared channel services", mlog.Err(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
if s.remoteClusterService != nil {
|
if s.remoteClusterService != nil {
|
||||||
if err = s.remoteClusterService.Shutdown(); err != nil {
|
if err = s.remoteClusterService.Shutdown(); err != nil {
|
||||||
mlog.Error("Error shutting down intercluster services", mlog.Err(err))
|
mlog.Error("Error shutting down intercluster services", mlog.Err(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
s.serviceMux.RUnlock()
|
||||||
|
|
||||||
s.StopHTTPServer()
|
s.StopHTTPServer()
|
||||||
s.stopLocalModeServer()
|
s.stopLocalModeServer()
|
||||||
@@ -1992,12 +2007,16 @@ func (s *Server) GetStore() store.Store {
|
|||||||
// GetRemoteClusterService returns the `RemoteClusterService` instantiated by the server.
|
// GetRemoteClusterService returns the `RemoteClusterService` instantiated by the server.
|
||||||
// May be nil if the service is not enabled via license.
|
// May be nil if the service is not enabled via license.
|
||||||
func (s *Server) GetRemoteClusterService() remotecluster.RemoteClusterServiceIFace {
|
func (s *Server) GetRemoteClusterService() remotecluster.RemoteClusterServiceIFace {
|
||||||
|
s.serviceMux.RLock()
|
||||||
|
defer s.serviceMux.RUnlock()
|
||||||
return s.remoteClusterService
|
return s.remoteClusterService
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSharedChannelSyncService returns the `SharedChannelSyncService` instantiated by the server.
|
// GetSharedChannelSyncService returns the `SharedChannelSyncService` instantiated by the server.
|
||||||
// May be nil if the service is not enabled via license.
|
// May be nil if the service is not enabled via license.
|
||||||
func (s *Server) GetSharedChannelSyncService() SharedChannelServiceIFace {
|
func (s *Server) GetSharedChannelSyncService() SharedChannelServiceIFace {
|
||||||
|
s.serviceMux.RLock()
|
||||||
|
defer s.serviceMux.RUnlock()
|
||||||
return s.sharedChannelService
|
return s.sharedChannelService
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2010,12 +2029,16 @@ func (s *Server) GetMetrics() einterfaces.MetricsInterface {
|
|||||||
// SetRemoteClusterService sets the `RemoteClusterService` to be used by the server.
|
// SetRemoteClusterService sets the `RemoteClusterService` to be used by the server.
|
||||||
// For testing only.
|
// For testing only.
|
||||||
func (s *Server) SetRemoteClusterService(remoteClusterService remotecluster.RemoteClusterServiceIFace) {
|
func (s *Server) SetRemoteClusterService(remoteClusterService remotecluster.RemoteClusterServiceIFace) {
|
||||||
|
s.serviceMux.Lock()
|
||||||
|
defer s.serviceMux.Unlock()
|
||||||
s.remoteClusterService = remoteClusterService
|
s.remoteClusterService = remoteClusterService
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSharedChannelSyncService sets the `SharedChannelSyncService` to be used by the server.
|
// SetSharedChannelSyncService sets the `SharedChannelSyncService` to be used by the server.
|
||||||
// For testing only.
|
// For testing only.
|
||||||
func (s *Server) SetSharedChannelSyncService(sharedChannelService SharedChannelServiceIFace) {
|
func (s *Server) SetSharedChannelSyncService(sharedChannelService SharedChannelServiceIFace) {
|
||||||
|
s.serviceMux.Lock()
|
||||||
|
defer s.serviceMux.Unlock()
|
||||||
s.sharedChannelService = sharedChannelService
|
s.sharedChannelService = sharedChannelService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -133,8 +133,8 @@ func (a *App) GetRemoteClusterForUser(remoteID string, userID string) (*model.Re
|
|||||||
return rc, nil
|
return rc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) UpdateSharedChannelRemoteNextSyncAt(id string, syncTime int64) error {
|
func (a *App) UpdateSharedChannelRemoteCursor(id string, cursor model.GetPostsSinceForSyncCursor) error {
|
||||||
return a.Srv().Store.SharedChannel().UpdateRemoteNextSyncAt(id, syncTime)
|
return a.Srv().Store.SharedChannel().UpdateRemoteCursor(id, cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) DeleteSharedChannelRemote(id string) (bool, error) {
|
func (a *App) DeleteSharedChannelRemote(id string) (bool, error) {
|
||||||
@@ -153,3 +153,13 @@ func (a *App) GetSharedChannelRemotesStatus(channelID string) ([]*model.SharedCh
|
|||||||
func (a *App) NotifySharedChannelUserUpdate(user *model.User) {
|
func (a *App) NotifySharedChannelUserUpdate(user *model.User) {
|
||||||
a.sendUpdatedUserEvent(*user)
|
a.sendUpdatedUserEvent(*user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// onUserProfileChange is called when a user's profile has changed
|
||||||
|
// (username, email, profile image, ...)
|
||||||
|
func (a *App) onUserProfileChange(userID string) {
|
||||||
|
syncService := a.Srv().GetSharedChannelSyncService()
|
||||||
|
if syncService == nil || !syncService.Active() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
syncService.NotifyUserProfileChanged(userID)
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ func TestServerSyncSharedChannelHandler(t *testing.T) {
|
|||||||
|
|
||||||
mockService := NewMockSharedChannelService(nil)
|
mockService := NewMockSharedChannelService(nil)
|
||||||
mockService.active = false
|
mockService.active = false
|
||||||
th.App.srv.sharedChannelService = mockService
|
th.App.srv.SetSharedChannelSyncService(mockService)
|
||||||
|
|
||||||
th.App.srv.SharedChannelSyncHandler(&model.WebSocketEvent{})
|
th.App.srv.SharedChannelSyncHandler(&model.WebSocketEvent{})
|
||||||
assert.Empty(t, mockService.notifications)
|
assert.Empty(t, mockService.channelNotifications)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("sync service active and broadcast envelope has ineligible event, it does nothing", func(t *testing.T) {
|
t.Run("sync service active and broadcast envelope has ineligible event, it does nothing", func(t *testing.T) {
|
||||||
@@ -30,13 +30,13 @@ func TestServerSyncSharedChannelHandler(t *testing.T) {
|
|||||||
|
|
||||||
mockService := NewMockSharedChannelService(nil)
|
mockService := NewMockSharedChannelService(nil)
|
||||||
mockService.active = true
|
mockService.active = true
|
||||||
th.App.srv.sharedChannelService = mockService
|
th.App.srv.SetSharedChannelSyncService(mockService)
|
||||||
channel := th.CreateChannel(th.BasicTeam, WithShared(true))
|
channel := th.CreateChannel(th.BasicTeam, WithShared(true))
|
||||||
|
|
||||||
websocketEvent := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_ADDED_TO_TEAM, model.NewId(), channel.Id, "", nil)
|
websocketEvent := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_ADDED_TO_TEAM, model.NewId(), channel.Id, "", nil)
|
||||||
|
|
||||||
th.App.srv.SharedChannelSyncHandler(websocketEvent)
|
th.App.srv.SharedChannelSyncHandler(websocketEvent)
|
||||||
assert.Empty(t, mockService.notifications)
|
assert.Empty(t, mockService.channelNotifications)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("sync service active and broadcast envelope has eligible event but channel does not exist, it does nothing", func(t *testing.T) {
|
t.Run("sync service active and broadcast envelope has eligible event but channel does not exist, it does nothing", func(t *testing.T) {
|
||||||
@@ -45,12 +45,12 @@ func TestServerSyncSharedChannelHandler(t *testing.T) {
|
|||||||
|
|
||||||
mockService := NewMockSharedChannelService(nil)
|
mockService := NewMockSharedChannelService(nil)
|
||||||
mockService.active = true
|
mockService.active = true
|
||||||
th.App.srv.sharedChannelService = mockService
|
th.App.srv.SetSharedChannelSyncService(mockService)
|
||||||
|
|
||||||
websocketEvent := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POSTED, model.NewId(), model.NewId(), "", nil)
|
websocketEvent := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POSTED, model.NewId(), model.NewId(), "", nil)
|
||||||
|
|
||||||
th.App.srv.SharedChannelSyncHandler(websocketEvent)
|
th.App.srv.SharedChannelSyncHandler(websocketEvent)
|
||||||
assert.Empty(t, mockService.notifications)
|
assert.Empty(t, mockService.channelNotifications)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("sync service active when received eligible event, it triggers a shared channel content sync", func(t *testing.T) {
|
t.Run("sync service active when received eligible event, it triggers a shared channel content sync", func(t *testing.T) {
|
||||||
@@ -59,13 +59,13 @@ func TestServerSyncSharedChannelHandler(t *testing.T) {
|
|||||||
|
|
||||||
mockService := NewMockSharedChannelService(nil)
|
mockService := NewMockSharedChannelService(nil)
|
||||||
mockService.active = true
|
mockService.active = true
|
||||||
th.App.srv.sharedChannelService = mockService
|
th.App.srv.SetSharedChannelSyncService(mockService)
|
||||||
|
|
||||||
channel := th.CreateChannel(th.BasicTeam, WithShared(true))
|
channel := th.CreateChannel(th.BasicTeam, WithShared(true))
|
||||||
websocketEvent := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POSTED, model.NewId(), channel.Id, "", nil)
|
websocketEvent := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POSTED, model.NewId(), channel.Id, "", nil)
|
||||||
|
|
||||||
th.App.srv.SharedChannelSyncHandler(websocketEvent)
|
th.App.srv.SharedChannelSyncHandler(websocketEvent)
|
||||||
assert.Len(t, mockService.notifications, 1)
|
assert.Len(t, mockService.channelNotifications, 1)
|
||||||
assert.Equal(t, channel.Id, mockService.notifications[0])
|
assert.Equal(t, channel.Id, mockService.channelNotifications[0])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ type SharedChannelServiceIFace interface {
|
|||||||
Shutdown() error
|
Shutdown() error
|
||||||
Start() error
|
Start() error
|
||||||
NotifyChannelChanged(channelId string)
|
NotifyChannelChanged(channelId string)
|
||||||
|
NotifyUserProfileChanged(userID string)
|
||||||
SendChannelInvite(channel *model.Channel, userId string, rc *model.RemoteCluster, options ...sharedchannel.InviteOption) error
|
SendChannelInvite(channel *model.Channel, userId string, rc *model.RemoteCluster, options ...sharedchannel.InviteOption) error
|
||||||
Active() bool
|
Active() bool
|
||||||
}
|
}
|
||||||
@@ -26,7 +27,7 @@ func MockOptionSharedChannelServiceWithActive(active bool) MockOptionSharedChann
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewMockSharedChannelService(service SharedChannelServiceIFace, options ...MockOptionSharedChannelService) *mockSharedChannelService {
|
func NewMockSharedChannelService(service SharedChannelServiceIFace, options ...MockOptionSharedChannelService) *mockSharedChannelService {
|
||||||
mrcs := &mockSharedChannelService{service, true, []string{}, 0}
|
mrcs := &mockSharedChannelService{service, true, []string{}, []string{}, 0}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(mrcs)
|
option(mrcs)
|
||||||
}
|
}
|
||||||
@@ -35,13 +36,18 @@ func NewMockSharedChannelService(service SharedChannelServiceIFace, options ...M
|
|||||||
|
|
||||||
type mockSharedChannelService struct {
|
type mockSharedChannelService struct {
|
||||||
SharedChannelServiceIFace
|
SharedChannelServiceIFace
|
||||||
active bool
|
active bool
|
||||||
notifications []string
|
channelNotifications []string
|
||||||
numInvitations int
|
userProfileNotifications []string
|
||||||
|
numInvitations int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mrcs *mockSharedChannelService) NotifyChannelChanged(channelId string) {
|
func (mrcs *mockSharedChannelService) NotifyChannelChanged(channelId string) {
|
||||||
mrcs.notifications = append(mrcs.notifications, channelId)
|
mrcs.channelNotifications = append(mrcs.channelNotifications, channelId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mrcs *mockSharedChannelService) NotifyUserProfileChanged(userId string) {
|
||||||
|
mrcs.userProfileNotifications = append(mrcs.userProfileNotifications, userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mrcs *mockSharedChannelService) Shutdown() error {
|
func (mrcs *mockSharedChannelService) Shutdown() error {
|
||||||
|
|||||||
@@ -981,6 +981,7 @@ func (a *App) SetProfileImageFromFile(userID string, file io.Reader) *model.AppE
|
|||||||
mlog.Warn("Error with updating last picture update", mlog.Err(err))
|
mlog.Warn("Error with updating last picture update", mlog.Err(err))
|
||||||
}
|
}
|
||||||
a.invalidateUserCacheAndPublish(userID)
|
a.invalidateUserCacheAndPublish(userID)
|
||||||
|
a.onUserProfileChange(userID)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -1311,6 +1312,7 @@ func (a *App) UpdateUser(user *model.User, sendNotifications bool) (*model.User,
|
|||||||
}
|
}
|
||||||
|
|
||||||
a.InvalidateCacheForUser(user.Id)
|
a.InvalidateCacheForUser(user.Id)
|
||||||
|
a.onUserProfileChange(user.Id)
|
||||||
|
|
||||||
return userUpdate.New, nil
|
return userUpdate.New, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,15 +241,15 @@ type GetPostsSinceOptions struct {
|
|||||||
SortAscending bool
|
SortAscending bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetPostsSinceForSyncCursor struct {
|
||||||
|
LastPostUpdateAt int64
|
||||||
|
LastPostId string
|
||||||
|
}
|
||||||
|
|
||||||
type GetPostsSinceForSyncOptions struct {
|
type GetPostsSinceForSyncOptions struct {
|
||||||
ChannelId string
|
ChannelId string
|
||||||
Since int64 // inclusive
|
|
||||||
Until int64 // inclusive
|
|
||||||
SortDescending bool
|
|
||||||
ExcludeRemoteId string
|
ExcludeRemoteId string
|
||||||
IncludeDeleted bool
|
IncludeDeleted bool
|
||||||
Limit int
|
|
||||||
Offset int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetPostsOptions struct {
|
type GetPostsOptions struct {
|
||||||
@@ -472,6 +472,14 @@ func (o *Post) IsRemote() bool {
|
|||||||
return o.RemoteId != nil && *o.RemoteId != ""
|
return o.RemoteId != nil && *o.RemoteId != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRemoteID safely returns the remoteID or empty string if not remote.
|
||||||
|
func (o *Post) GetRemoteID() string {
|
||||||
|
if o.RemoteId != nil {
|
||||||
|
return *o.RemoteId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (o *Post) IsJoinLeaveMessage() bool {
|
func (o *Post) IsJoinLeaveMessage() bool {
|
||||||
return o.Type == POST_JOIN_LEAVE ||
|
return o.Type == POST_JOIN_LEAVE ||
|
||||||
o.Type == POST_ADD_REMOVE ||
|
o.Type == POST_ADD_REMOVE ||
|
||||||
|
|||||||
@@ -112,7 +112,8 @@ type SharedChannelRemote struct {
|
|||||||
IsInviteAccepted bool `json:"is_invite_accepted"`
|
IsInviteAccepted bool `json:"is_invite_accepted"`
|
||||||
IsInviteConfirmed bool `json:"is_invite_confirmed"`
|
IsInviteConfirmed bool `json:"is_invite_confirmed"`
|
||||||
RemoteId string `json:"remote_id"`
|
RemoteId string `json:"remote_id"`
|
||||||
NextSyncAt int64 `json:"next_sync_at"`
|
LastPostUpdateAt int64 `json:"last_post_update_at"`
|
||||||
|
LastPostId string `json:"last_post_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *SharedChannelRemote) ToJson() string {
|
func (sc *SharedChannelRemote) ToJson() string {
|
||||||
@@ -211,6 +212,12 @@ func (scu *SharedChannelUser) IsValid() *AppError {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetUsersForSyncFilter struct {
|
||||||
|
CheckProfileImage bool
|
||||||
|
ChannelID string
|
||||||
|
Limit uint64
|
||||||
|
}
|
||||||
|
|
||||||
// SharedChannelAttachment stores a lastSyncAt timestamp on behalf of a remote cluster for
|
// SharedChannelAttachment stores a lastSyncAt timestamp on behalf of a remote cluster for
|
||||||
// each file attachment that has been synchronized.
|
// each file attachment that has been synchronized.
|
||||||
type SharedChannelAttachment struct {
|
type SharedChannelAttachment struct {
|
||||||
|
|||||||
@@ -15,23 +15,6 @@ import (
|
|||||||
"github.com/mattermost/mattermost-server/v5/shared/mlog"
|
"github.com/mattermost/mattermost-server/v5/shared/mlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// postToAttachments returns the file attachments for a post that need to be synchronized.
|
|
||||||
func (scs *Service) postToAttachments(post *model.Post, rc *model.RemoteCluster) ([]*model.FileInfo, error) {
|
|
||||||
infos := make([]*model.FileInfo, 0)
|
|
||||||
|
|
||||||
fis, err := scs.server.GetStore().FileInfo().GetForPost(post.Id, false, true, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not get file info for attachment: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fi := range fis {
|
|
||||||
if scs.shouldSyncAttachment(fi, rc) {
|
|
||||||
infos = append(infos, fi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return infos, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// postsToAttachments returns the file attachments for a slice of posts that need to be synchronized.
|
// postsToAttachments returns the file attachments for a slice of posts that need to be synchronized.
|
||||||
func (scs *Service) shouldSyncAttachment(fi *model.FileInfo, rc *model.RemoteCluster) bool {
|
func (scs *Service) shouldSyncAttachment(fi *model.FileInfo, rc *model.RemoteCluster) bool {
|
||||||
sca, err := scs.server.GetStore().SharedChannel().GetAttachment(fi.Id, rc.RemoteId)
|
sca, err := scs.server.GetStore().SharedChannel().GetAttachment(fi.Id, rc.RemoteId)
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
package sharedchannel
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
|
||||||
"github.com/mattermost/mattermost-server/v5/shared/mlog"
|
|
||||||
)
|
|
||||||
|
|
||||||
type sinceResult struct {
|
|
||||||
posts []*model.Post
|
|
||||||
hasMore bool
|
|
||||||
nextSince int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPostsSince fetches posts that need to be synchronized with a remote cluster.
|
|
||||||
// There is a soft cap on the number of posts that will be synchronized in a single pass (MaxPostsPerSync).
|
|
||||||
//
|
|
||||||
// There is a special case where multiple posts have the same UpdateAt value. It is vital that this method
|
|
||||||
// include all posts within that millisecond so that subsequent calls can use an incremented `since`. If this
|
|
||||||
// method were to be called repeatedly with the same `since` value the same records would be returned each time
|
|
||||||
// and the sync would never move forward.
|
|
||||||
//
|
|
||||||
// A boolean is also returned to indicate if there are more posts to be synchronized (true) or not (false).
|
|
||||||
func (scs *Service) getPostsSince(channelId string, rc *model.RemoteCluster, since int64) (sinceResult, error) {
|
|
||||||
opts := model.GetPostsSinceForSyncOptions{
|
|
||||||
ChannelId: channelId,
|
|
||||||
Since: since,
|
|
||||||
IncludeDeleted: true,
|
|
||||||
Limit: MaxPostsPerSync + 1, // ask for 1 more than needed to peek at first post in next batch
|
|
||||||
}
|
|
||||||
posts, err := scs.server.GetStore().Post().GetPostsSinceForSync(opts, true)
|
|
||||||
if err != nil {
|
|
||||||
return sinceResult{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(posts) == 0 {
|
|
||||||
return sinceResult{nextSince: since}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var hasMore bool
|
|
||||||
if len(posts) > MaxPostsPerSync {
|
|
||||||
hasMore = true
|
|
||||||
peekUpdateAt := posts[len(posts)-1].UpdateAt
|
|
||||||
posts = posts[:MaxPostsPerSync] // trim the peeked at record
|
|
||||||
|
|
||||||
// If the last post to be synchronized has the same Update value as the first post in the next batch
|
|
||||||
// then we need to grab the rest of the posts for that millisecond to ensure the next call can have an
|
|
||||||
// incremented `since`.
|
|
||||||
if peekUpdateAt == posts[len(posts)-1].UpdateAt {
|
|
||||||
opts.Since = peekUpdateAt
|
|
||||||
opts.Until = opts.Since
|
|
||||||
opts.Limit = 1000
|
|
||||||
opts.Offset = countPostsAtMillisecond(posts, peekUpdateAt)
|
|
||||||
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "getPostsSince handling updateAt collision",
|
|
||||||
mlog.String("remote", rc.DisplayName),
|
|
||||||
mlog.Int64("update_at", peekUpdateAt),
|
|
||||||
mlog.Int("offset", opts.Offset),
|
|
||||||
)
|
|
||||||
|
|
||||||
morePosts, err := scs.server.GetStore().Post().GetPostsSinceForSync(opts, true)
|
|
||||||
if err != nil {
|
|
||||||
return sinceResult{}, err
|
|
||||||
}
|
|
||||||
posts = append(posts, morePosts...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sinceResult{posts: posts, hasMore: hasMore, nextSince: posts[len(posts)-1].UpdateAt + 1}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func countPostsAtMillisecond(posts []*model.Post, milli int64) int {
|
|
||||||
// walk backward through the slice until we find a post with UpdateAt that differs from milli.
|
|
||||||
var count int
|
|
||||||
for i := len(posts) - 1; i >= 0; i-- {
|
|
||||||
if posts[i].UpdateAt != milli {
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
@@ -4,28 +4,29 @@
|
|||||||
package sharedchannel
|
package sharedchannel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
"github.com/mattermost/mattermost-server/v5/services/remotecluster"
|
|
||||||
"github.com/mattermost/mattermost-server/v5/shared/mlog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// syncMsg represents a change in content (post add/edit/delete, reaction add/remove, users).
|
// syncMsg represents a change in content (post add/edit/delete, reaction add/remove, users).
|
||||||
// It is sent to remote clusters as the payload of a `RemoteClusterMsg`.
|
// It is sent to remote clusters as the payload of a `RemoteClusterMsg`.
|
||||||
type syncMsg struct {
|
type syncMsg struct {
|
||||||
ChannelId string `json:"channel_id"`
|
Id string `json:"id"`
|
||||||
PostId string `json:"post_id"`
|
ChannelId string `json:"channel_id"`
|
||||||
Post *model.Post `json:"post"`
|
Users map[string]*model.User `json:"users,omitempty"`
|
||||||
Users []*model.User `json:"users"`
|
Posts []*model.Post `json:"posts,omitempty"`
|
||||||
Reactions []*model.Reaction `json:"reactions"`
|
Reactions []*model.Reaction `json:"reactions,omitempty"`
|
||||||
Attachments []*model.FileInfo `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sm syncMsg) ToJSON() ([]byte, error) {
|
func newSyncMsg(channelID string) *syncMsg {
|
||||||
|
return &syncMsg{
|
||||||
|
Id: model.NewId(),
|
||||||
|
ChannelId: channelID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *syncMsg) ToJSON() ([]byte, error) {
|
||||||
b, err := json.Marshal(sm)
|
b, err := json.Marshal(sm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -33,296 +34,10 @@ func (sm syncMsg) ToJSON() ([]byte, error) {
|
|||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sm syncMsg) String() string {
|
func (sm *syncMsg) String() string {
|
||||||
json, err := sm.ToJSON()
|
json, err := sm.ToJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return string(json)
|
return string(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
type userCache map[string]struct{}
|
|
||||||
|
|
||||||
func (u userCache) Has(id string) bool {
|
|
||||||
_, ok := u[id]
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u userCache) Add(id string) {
|
|
||||||
u[id] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// postsToSyncMessages takes a slice of posts and converts to a `RemoteClusterMsg` which can be
|
|
||||||
// sent to a remote cluster.
|
|
||||||
func (scs *Service) postsToSyncMessages(posts []*model.Post, channelID string, rc *model.RemoteCluster, nextSyncAt int64) ([]syncMsg, error) {
|
|
||||||
syncMessages := make([]syncMsg, 0, len(posts))
|
|
||||||
|
|
||||||
var teamID string
|
|
||||||
uCache := make(userCache)
|
|
||||||
|
|
||||||
for _, p := range posts {
|
|
||||||
if p.IsSystemMessage() { // don't sync system messages
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// lookup team id once
|
|
||||||
if teamID == "" {
|
|
||||||
sc, err := scs.server.GetStore().SharedChannel().Get(p.ChannelId)
|
|
||||||
if err != nil {
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Could not get shared channel for post",
|
|
||||||
mlog.String("post_id", p.Id),
|
|
||||||
mlog.Err(err),
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
teamID = sc.TeamId
|
|
||||||
}
|
|
||||||
|
|
||||||
// any reactions originating from the remote cluster are filtered out
|
|
||||||
reactions, err := scs.server.GetStore().Reaction().GetForPostSince(p.Id, nextSyncAt, rc.RemoteId, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
postSync := p
|
|
||||||
|
|
||||||
// Don't resend an existing post where only the reactions changed.
|
|
||||||
// Posts we must send:
|
|
||||||
// - new posts (EditAt == 0)
|
|
||||||
// - edited posts (EditAt >= nextSyncAt)
|
|
||||||
// - deleted posts (DeleteAt > 0)
|
|
||||||
if p.EditAt > 0 && p.EditAt < nextSyncAt && p.DeleteAt == 0 {
|
|
||||||
postSync = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't send a deleted post if it is just the original copy from an edit.
|
|
||||||
if p.DeleteAt > 0 && p.OriginalId != "" {
|
|
||||||
postSync = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't sync a post back to the remote it came from.
|
|
||||||
if p.RemoteId != nil && *p.RemoteId == rc.RemoteId {
|
|
||||||
postSync = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var attachments []*model.FileInfo
|
|
||||||
if postSync != nil {
|
|
||||||
// parse out all permalinks in the message.
|
|
||||||
postSync.Message = scs.processPermalinkToRemote(postSync)
|
|
||||||
|
|
||||||
// get any file attachments
|
|
||||||
attachments, err = scs.postToAttachments(postSync, rc)
|
|
||||||
if err != nil {
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Could not fetch attachments for post",
|
|
||||||
mlog.String("post_id", postSync.Id),
|
|
||||||
mlog.Err(err),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// any users originating from the remote cluster are filtered out
|
|
||||||
users := scs.usersForPost(postSync, reactions, channelID, teamID, rc, uCache)
|
|
||||||
|
|
||||||
// if everything was filtered out then don't send an empty message.
|
|
||||||
if postSync == nil && len(reactions) == 0 && len(users) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
sm := syncMsg{
|
|
||||||
ChannelId: p.ChannelId,
|
|
||||||
PostId: p.Id,
|
|
||||||
Post: postSync,
|
|
||||||
Users: users,
|
|
||||||
Reactions: reactions,
|
|
||||||
Attachments: attachments,
|
|
||||||
}
|
|
||||||
syncMessages = append(syncMessages, sm)
|
|
||||||
}
|
|
||||||
return syncMessages, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// usersForPost provides a list of Users associated with the post that need to be synchronized.
|
|
||||||
// The user cache ensures the same user is not synchronized redundantly if they appear in multiple
|
|
||||||
// posts for this sync batch.
|
|
||||||
func (scs *Service) usersForPost(post *model.Post, reactions []*model.Reaction, channelID string, teamID string, rc *model.RemoteCluster, uCache userCache) []*model.User {
|
|
||||||
userIds := make([]string, 0)
|
|
||||||
var mentionMap model.UserMentionMap
|
|
||||||
|
|
||||||
if post != nil && !uCache.Has(post.UserId) {
|
|
||||||
userIds = append(userIds, post.UserId)
|
|
||||||
uCache.Add(post.UserId)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, r := range reactions {
|
|
||||||
if !uCache.Has(r.UserId) {
|
|
||||||
userIds = append(userIds, r.UserId)
|
|
||||||
uCache.Add(r.UserId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get mentions and userids for each mention
|
|
||||||
if post != nil {
|
|
||||||
mentionMap = scs.app.MentionsToTeamMembers(post.Message, teamID)
|
|
||||||
for mention, id := range mentionMap {
|
|
||||||
if !uCache.Has(id) {
|
|
||||||
userIds = append(userIds, id)
|
|
||||||
uCache.Add(id)
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Found mention",
|
|
||||||
mlog.String("mention", mention),
|
|
||||||
mlog.String("user_id", id),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
users := make([]*model.User, 0)
|
|
||||||
|
|
||||||
for _, id := range userIds {
|
|
||||||
user, err := scs.server.GetStore().User().Get(context.Background(), id)
|
|
||||||
if err != nil {
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error checking if user should sync",
|
|
||||||
mlog.String("user_id", id),
|
|
||||||
mlog.Err(err),
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
sync, syncImage, err2 := scs.shouldUserSync(user, channelID, rc)
|
|
||||||
if err2 != nil {
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Could not find user for post",
|
|
||||||
mlog.String("user_id", id),
|
|
||||||
mlog.Err(err2),
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if sync {
|
|
||||||
users = append(users, sanitizeUserForSync(user))
|
|
||||||
}
|
|
||||||
|
|
||||||
if syncImage {
|
|
||||||
scs.syncProfileImage(user, channelID, rc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if this was a mention then put the real username in place of the username+remotename, but only
|
|
||||||
// when sending to the remote that the user belongs to.
|
|
||||||
if user.RemoteId != nil && *user.RemoteId == rc.RemoteId {
|
|
||||||
fixMention(post, mentionMap, user)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return users
|
|
||||||
}
|
|
||||||
|
|
||||||
// fixMention replaces any mentions in a post for the user with the user's real username.
|
|
||||||
func fixMention(post *model.Post, mentionMap model.UserMentionMap, user *model.User) {
|
|
||||||
if post == nil || len(mentionMap) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
realUsername, ok := user.GetProp(KeyRemoteUsername)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// there may be more than one mention for each user so we have to walk the whole map.
|
|
||||||
for mention, id := range mentionMap {
|
|
||||||
if id == user.Id && strings.Contains(mention, ":") {
|
|
||||||
post.Message = strings.ReplaceAll(post.Message, "@"+mention, "@"+realUsername)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sanitizeUserForSync(user *model.User) *model.User {
|
|
||||||
user.Password = model.NewId()
|
|
||||||
user.AuthData = nil
|
|
||||||
user.AuthService = ""
|
|
||||||
user.Roles = "system_user"
|
|
||||||
user.AllowMarketing = false
|
|
||||||
user.NotifyProps = model.StringMap{}
|
|
||||||
user.LastPasswordUpdate = 0
|
|
||||||
user.LastPictureUpdate = 0
|
|
||||||
user.FailedAttempts = 0
|
|
||||||
user.MfaActive = false
|
|
||||||
user.MfaSecret = ""
|
|
||||||
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
|
|
||||||
// shouldUserSync determines if a user needs to be synchronized.
|
|
||||||
// User should be synchronized if it has no entry in the SharedChannelUsers table for the specified channel,
|
|
||||||
// or there is an entry but the LastSyncAt is less than user.UpdateAt
|
|
||||||
func (scs *Service) shouldUserSync(user *model.User, channelID string, rc *model.RemoteCluster) (sync bool, syncImage bool, err error) {
|
|
||||||
// don't sync users with the remote they originated from.
|
|
||||||
if user.RemoteId != nil && *user.RemoteId == rc.RemoteId {
|
|
||||||
return false, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
scu, err := scs.server.GetStore().SharedChannel().GetUser(user.Id, channelID, rc.RemoteId)
|
|
||||||
if err != nil {
|
|
||||||
if _, ok := err.(errNotFound); !ok {
|
|
||||||
return false, false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// user not in the SharedChannelUsers table, so we must add them.
|
|
||||||
scu = &model.SharedChannelUser{
|
|
||||||
UserId: user.Id,
|
|
||||||
RemoteId: rc.RemoteId,
|
|
||||||
ChannelId: channelID,
|
|
||||||
}
|
|
||||||
if _, err = scs.server.GetStore().SharedChannel().SaveUser(scu); err != nil {
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error adding user to shared channel users",
|
|
||||||
mlog.String("remote_id", rc.RemoteId),
|
|
||||||
mlog.String("user_id", user.Id),
|
|
||||||
mlog.String("channel_id", user.Id),
|
|
||||||
mlog.Err(err),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return true, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return user.UpdateAt > scu.LastSyncAt, user.LastPictureUpdate > scu.LastSyncAt, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (scs *Service) syncProfileImage(user *model.User, channelID string, rc *model.RemoteCluster) {
|
|
||||||
rcs := scs.server.GetRemoteClusterService()
|
|
||||||
if rcs == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
rcs.SendProfileImage(ctx, user.Id, rc, scs.app, func(userId string, rc *model.RemoteCluster, resp *remotecluster.Response, err error) {
|
|
||||||
if resp.IsSuccess() {
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Users profile image synchronized",
|
|
||||||
mlog.String("remote_id", rc.RemoteId),
|
|
||||||
mlog.String("user_id", user.Id),
|
|
||||||
)
|
|
||||||
|
|
||||||
scu, err := scs.server.GetStore().SharedChannel().GetUser(user.Id, channelID, rc.RemoteId)
|
|
||||||
if err != nil {
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error fetching shared channel user while updating users LastSyncTime after profile image update",
|
|
||||||
mlog.String("remote_id", rc.RemoteId),
|
|
||||||
mlog.String("user_id", user.Id),
|
|
||||||
mlog.String("channel_id", channelID),
|
|
||||||
mlog.Err(err),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = scs.server.GetStore().SharedChannel().UpdateUserLastSyncAt(scu.Id, model.GetMillis()); err != nil {
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error updating users LastSyncTime after profile image update",
|
|
||||||
mlog.String("remote_id", rc.RemoteId),
|
|
||||||
mlog.String("user_id", user.Id),
|
|
||||||
mlog.Err(err),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error synchronizing users profile image",
|
|
||||||
mlog.String("remote_id", rc.RemoteId),
|
|
||||||
mlog.String("user_id", user.Id),
|
|
||||||
mlog.String("Err", resp.Err),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,7 +4,13 @@
|
|||||||
package sharedchannel
|
package sharedchannel
|
||||||
|
|
||||||
type SyncResponse struct {
|
type SyncResponse struct {
|
||||||
LastSyncAt int64 `json:"last_sync_at"`
|
UsersLastUpdateAt int64 `json:"users_last_update_at"`
|
||||||
PostErrors []string `json:"post_errors"`
|
UserErrors []string `json:"user_errors"`
|
||||||
UsersSyncd []string `json:"users_syncd"`
|
UsersSyncd []string `json:"users_syncd"`
|
||||||
|
|
||||||
|
PostsLastUpdateAt int64 `json:"posts_last_update_at"`
|
||||||
|
PostErrors []string `json:"post_errors"`
|
||||||
|
|
||||||
|
ReactionsLastUpdateAt int64 `json:"reactions_last_update_at"`
|
||||||
|
ReactionErrors []string `json:"reaction_errors"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,11 @@ const (
|
|||||||
TopicUploadCreate = "sharedchannel_upload"
|
TopicUploadCreate = "sharedchannel_upload"
|
||||||
MaxRetries = 3
|
MaxRetries = 3
|
||||||
MaxPostsPerSync = 12 // a bit more than one typical screenfull of posts
|
MaxPostsPerSync = 12 // a bit more than one typical screenfull of posts
|
||||||
|
MaxUsersPerSync = 25
|
||||||
NotifyRemoteOfflineThreshold = time.Second * 10
|
NotifyRemoteOfflineThreshold = time.Second * 10
|
||||||
NotifyMinimumDelay = time.Second * 2
|
NotifyMinimumDelay = time.Second * 2
|
||||||
MaxUpsertRetries = 25
|
MaxUpsertRetries = 25
|
||||||
|
ProfileImageSyncTimeout = time.Second * 5
|
||||||
KeyRemoteUsername = "RemoteUsername"
|
KeyRemoteUsername = "RemoteUsername"
|
||||||
KeyRemoteEmail = "RemoteEmail"
|
KeyRemoteEmail = "RemoteEmail"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -33,136 +33,131 @@ func (scs *Service) onReceiveSyncMessage(msg model.RemoteClusterMsg, rc *model.R
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var syncMessages []syncMsg
|
var sm syncMsg
|
||||||
|
|
||||||
if err := json.Unmarshal(msg.Payload, &syncMessages); err != nil {
|
if err := json.Unmarshal(msg.Payload, &sm); err != nil {
|
||||||
return fmt.Errorf("invalid sync message: %w", err)
|
return fmt.Errorf("invalid sync message: %w", err)
|
||||||
}
|
}
|
||||||
|
return scs.processSyncMessage(&sm, rc, response)
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Batch of sync messages received",
|
|
||||||
mlog.String("remote", rc.DisplayName),
|
|
||||||
mlog.Int("sync_msg_count", len(syncMessages)),
|
|
||||||
)
|
|
||||||
|
|
||||||
return scs.processSyncMessages(syncMessages, rc, response)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (scs *Service) processSyncMessages(syncMessages []syncMsg, rc *model.RemoteCluster, response *remotecluster.Response) error {
|
func (scs *Service) processSyncMessage(syncMsg *syncMsg, rc *model.RemoteCluster, response *remotecluster.Response) error {
|
||||||
var channel *model.Channel
|
var channel *model.Channel
|
||||||
var team *model.Team
|
var team *model.Team
|
||||||
|
|
||||||
postErrors := make([]string, 0)
|
|
||||||
usersSyncd := make([]string, 0)
|
|
||||||
var lastSyncAt int64
|
|
||||||
var err error
|
var err error
|
||||||
|
syncResp := SyncResponse{
|
||||||
|
UserErrors: make([]string, 0),
|
||||||
|
UsersSyncd: make([]string, 0),
|
||||||
|
PostErrors: make([]string, 0),
|
||||||
|
ReactionErrors: make([]string, 0),
|
||||||
|
}
|
||||||
|
|
||||||
for _, sm := range syncMessages {
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Sync msg received",
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Sync msg received",
|
mlog.String("remote", rc.Name),
|
||||||
mlog.String("post_id", sm.PostId),
|
mlog.String("channel_id", syncMsg.ChannelId),
|
||||||
mlog.String("channel_id", sm.ChannelId),
|
mlog.Int("user_count", len(syncMsg.Users)),
|
||||||
mlog.Int("reaction_count", len(sm.Reactions)),
|
mlog.Int("post_count", len(syncMsg.Posts)),
|
||||||
mlog.Int("user_count", len(sm.Users)),
|
mlog.Int("reaction_count", len(syncMsg.Reactions)),
|
||||||
mlog.Bool("has_post", sm.Post != nil),
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if channel == nil {
|
if channel, err = scs.server.GetStore().Channel().Get(syncMsg.ChannelId, true); err != nil {
|
||||||
if channel, err = scs.server.GetStore().Channel().Get(sm.ChannelId, true); err != nil {
|
// if the channel doesn't exist then none of these sync items are going to work.
|
||||||
// if the channel doesn't exist then none of these sync messages are going to work.
|
return fmt.Errorf("channel not found processing sync message: %w", err)
|
||||||
return fmt.Errorf("channel not found processing sync messages: %w", err)
|
}
|
||||||
}
|
|
||||||
}
|
// add/update users before posts
|
||||||
|
for _, user := range syncMsg.Users {
|
||||||
// add/update users before posts
|
if userSaved, err := scs.upsertSyncUser(user, channel, rc); err != nil {
|
||||||
for _, user := range sm.Users {
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error upserting sync user",
|
||||||
if userSaved, err := scs.upsertSyncUser(user, channel, rc); err != nil {
|
mlog.String("remote", rc.Name),
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error upserting sync user",
|
mlog.String("channel_id", syncMsg.ChannelId),
|
||||||
mlog.String("post_id", sm.PostId),
|
mlog.String("user_id", user.Id),
|
||||||
mlog.String("channel_id", sm.ChannelId),
|
mlog.Err(err))
|
||||||
mlog.String("user_id", user.Id),
|
} else {
|
||||||
mlog.Err(err))
|
syncResp.UsersSyncd = append(syncResp.UsersSyncd, userSaved.Id)
|
||||||
} else {
|
if syncResp.UsersLastUpdateAt < user.UpdateAt {
|
||||||
usersSyncd = append(usersSyncd, userSaved.Id)
|
syncResp.UsersLastUpdateAt = user.UpdateAt
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "User upserted via sync",
|
|
||||||
mlog.String("post_id", sm.PostId),
|
|
||||||
mlog.String("channel_id", sm.ChannelId),
|
|
||||||
mlog.String("user_id", user.Id),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sm.Post != nil {
|
|
||||||
if sm.ChannelId != sm.Post.ChannelId {
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "ChannelId mismatch",
|
|
||||||
mlog.String("sm.ChannelId", sm.ChannelId),
|
|
||||||
mlog.String("sm.Post.ChannelId", sm.Post.ChannelId),
|
|
||||||
mlog.String("PostId", sm.Post.Id),
|
|
||||||
)
|
|
||||||
postErrors = append(postErrors, sm.Post.Id)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if channel.Type != model.CHANNEL_DIRECT && team == nil {
|
|
||||||
var err2 error
|
|
||||||
team, err2 = scs.server.GetStore().Channel().GetTeamForChannel(sm.ChannelId)
|
|
||||||
if err2 != nil {
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error getting Team for Channel",
|
|
||||||
mlog.String("ChannelId", sm.Post.ChannelId),
|
|
||||||
mlog.String("PostId", sm.Post.Id),
|
|
||||||
mlog.Err(err2),
|
|
||||||
)
|
|
||||||
postErrors = append(postErrors, sm.Post.Id)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// process perma-links for remote
|
|
||||||
if team != nil {
|
|
||||||
sm.Post.Message = scs.processPermalinkFromRemote(sm.Post, team)
|
|
||||||
}
|
|
||||||
|
|
||||||
// add/update post
|
|
||||||
rpost, err := scs.upsertSyncPost(sm.Post, channel, rc)
|
|
||||||
if err != nil {
|
|
||||||
postErrors = append(postErrors, sm.Post.Id)
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error upserting sync post",
|
|
||||||
mlog.String("post_id", sm.Post.Id),
|
|
||||||
mlog.String("channel_id", sm.Post.ChannelId),
|
|
||||||
mlog.Err(err),
|
|
||||||
)
|
|
||||||
} else if lastSyncAt < rpost.UpdateAt {
|
|
||||||
lastSyncAt = rpost.UpdateAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add/remove reactions
|
|
||||||
for _, reaction := range sm.Reactions {
|
|
||||||
if _, err := scs.upsertSyncReaction(reaction, rc); err != nil {
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error upserting sync reaction",
|
|
||||||
mlog.String("user_id", reaction.UserId),
|
|
||||||
mlog.String("post_id", reaction.PostId),
|
|
||||||
mlog.String("emoji", reaction.EmojiName),
|
|
||||||
mlog.Int64("delete_at", reaction.DeleteAt),
|
|
||||||
mlog.Err(err),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Reaction upserted via sync",
|
|
||||||
mlog.String("user_id", reaction.UserId),
|
|
||||||
mlog.String("post_id", reaction.PostId),
|
|
||||||
mlog.String("emoji", reaction.EmojiName),
|
|
||||||
mlog.Int64("delete_at", reaction.DeleteAt),
|
|
||||||
)
|
|
||||||
|
|
||||||
if lastSyncAt < reaction.UpdateAt {
|
|
||||||
lastSyncAt = reaction.UpdateAt
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "User upserted via sync",
|
||||||
|
mlog.String("remote", rc.Name),
|
||||||
|
mlog.String("channel_id", syncMsg.ChannelId),
|
||||||
|
mlog.String("user_id", user.Id),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
syncResp := SyncResponse{
|
for _, post := range syncMsg.Posts {
|
||||||
LastSyncAt: lastSyncAt, // might be zero
|
if syncMsg.ChannelId != post.ChannelId {
|
||||||
PostErrors: postErrors, // might be empty
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "ChannelId mismatch",
|
||||||
UsersSyncd: usersSyncd, // might be empty
|
mlog.String("remote", rc.Name),
|
||||||
|
mlog.String("sm.ChannelId", syncMsg.ChannelId),
|
||||||
|
mlog.String("sm.Post.ChannelId", post.ChannelId),
|
||||||
|
mlog.String("PostId", post.Id),
|
||||||
|
)
|
||||||
|
syncResp.PostErrors = append(syncResp.PostErrors, post.Id)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if channel.Type != model.CHANNEL_DIRECT && team == nil {
|
||||||
|
var err2 error
|
||||||
|
team, err2 = scs.server.GetStore().Channel().GetTeamForChannel(syncMsg.ChannelId)
|
||||||
|
if err2 != nil {
|
||||||
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error getting Team for Channel",
|
||||||
|
mlog.String("ChannelId", post.ChannelId),
|
||||||
|
mlog.String("PostId", post.Id),
|
||||||
|
mlog.String("remote", rc.Name),
|
||||||
|
mlog.Err(err2),
|
||||||
|
)
|
||||||
|
syncResp.PostErrors = append(syncResp.PostErrors, post.Id)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// process perma-links for remote
|
||||||
|
if team != nil {
|
||||||
|
post.Message = scs.processPermalinkFromRemote(post, team)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add/update post
|
||||||
|
rpost, err := scs.upsertSyncPost(post, channel, rc)
|
||||||
|
if err != nil {
|
||||||
|
syncResp.PostErrors = append(syncResp.PostErrors, post.Id)
|
||||||
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error upserting sync post",
|
||||||
|
mlog.String("post_id", post.Id),
|
||||||
|
mlog.String("channel_id", post.ChannelId),
|
||||||
|
mlog.String("remote", rc.Name),
|
||||||
|
mlog.Err(err),
|
||||||
|
)
|
||||||
|
} else if syncResp.PostsLastUpdateAt < rpost.UpdateAt {
|
||||||
|
syncResp.PostsLastUpdateAt = rpost.UpdateAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add/remove reactions
|
||||||
|
for _, reaction := range syncMsg.Reactions {
|
||||||
|
if _, err := scs.upsertSyncReaction(reaction, rc); err != nil {
|
||||||
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error upserting sync reaction",
|
||||||
|
mlog.String("remote", rc.Name),
|
||||||
|
mlog.String("user_id", reaction.UserId),
|
||||||
|
mlog.String("post_id", reaction.PostId),
|
||||||
|
mlog.String("emoji", reaction.EmojiName),
|
||||||
|
mlog.Int64("delete_at", reaction.DeleteAt),
|
||||||
|
mlog.Err(err),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Reaction upserted via sync",
|
||||||
|
mlog.String("remote", rc.Name),
|
||||||
|
mlog.String("user_id", reaction.UserId),
|
||||||
|
mlog.String("post_id", reaction.PostId),
|
||||||
|
mlog.String("emoji", reaction.EmojiName),
|
||||||
|
mlog.Int64("delete_at", reaction.DeleteAt),
|
||||||
|
)
|
||||||
|
|
||||||
|
if syncResp.ReactionsLastUpdateAt < reaction.UpdateAt {
|
||||||
|
syncResp.ReactionsLastUpdateAt = reaction.UpdateAt
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response.SetPayload(syncResp)
|
response.SetPayload(syncResp)
|
||||||
@@ -345,24 +340,30 @@ func (scs *Service) upsertSyncPost(post *model.Post, channel *model.Channel, rc
|
|||||||
if rpost == nil {
|
if rpost == nil {
|
||||||
// post doesn't exist; create new one
|
// post doesn't exist; create new one
|
||||||
rpost, appErr = scs.app.CreatePost(request.EmptyContext(), post, channel, true, true)
|
rpost, appErr = scs.app.CreatePost(request.EmptyContext(), post, channel, true, true)
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Created sync post",
|
if appErr == nil {
|
||||||
mlog.String("post_id", post.Id),
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Created sync post",
|
||||||
mlog.String("channel_id", post.ChannelId),
|
mlog.String("post_id", post.Id),
|
||||||
)
|
mlog.String("channel_id", post.ChannelId),
|
||||||
|
)
|
||||||
|
}
|
||||||
} else if post.DeleteAt > 0 {
|
} else if post.DeleteAt > 0 {
|
||||||
// delete post
|
// delete post
|
||||||
rpost, appErr = scs.app.DeletePost(post.Id, post.UserId)
|
rpost, appErr = scs.app.DeletePost(post.Id, post.UserId)
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Deleted sync post",
|
if appErr == nil {
|
||||||
mlog.String("post_id", post.Id),
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Deleted sync post",
|
||||||
mlog.String("channel_id", post.ChannelId),
|
mlog.String("post_id", post.Id),
|
||||||
)
|
mlog.String("channel_id", post.ChannelId),
|
||||||
|
)
|
||||||
|
}
|
||||||
} else if post.EditAt > rpost.EditAt || post.Message != rpost.Message {
|
} else if post.EditAt > rpost.EditAt || post.Message != rpost.Message {
|
||||||
// update post
|
// update post
|
||||||
rpost, appErr = scs.app.UpdatePost(request.EmptyContext(), post, false)
|
rpost, appErr = scs.app.UpdatePost(request.EmptyContext(), post, false)
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Updated sync post",
|
if appErr == nil {
|
||||||
mlog.String("post_id", post.Id),
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Updated sync post",
|
||||||
mlog.String("channel_id", post.ChannelId),
|
mlog.String("post_id", post.Id),
|
||||||
)
|
mlog.String("channel_id", post.ChannelId),
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// nothing to update
|
// nothing to update
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Update to sync post ignored",
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Update to sync post ignored",
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ package sharedchannel
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
@@ -18,25 +16,25 @@ import (
|
|||||||
|
|
||||||
type syncTask struct {
|
type syncTask struct {
|
||||||
id string
|
id string
|
||||||
channelId string
|
channelID string
|
||||||
remoteId string
|
remoteID string
|
||||||
AddedAt time.Time
|
AddedAt time.Time
|
||||||
retryCount int
|
retryCount int
|
||||||
retryPost *model.Post
|
retryMsg *syncMsg
|
||||||
schedule time.Time
|
schedule time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSyncTask(channelId string, remoteId string, retryPost *model.Post) syncTask {
|
func newSyncTask(channelID string, remoteID string, retryMsg *syncMsg) syncTask {
|
||||||
var postId string
|
var retryID string
|
||||||
if retryPost != nil {
|
if retryMsg != nil {
|
||||||
postId = retryPost.Id
|
retryID = retryMsg.Id
|
||||||
}
|
}
|
||||||
|
|
||||||
return syncTask{
|
return syncTask{
|
||||||
id: channelId + remoteId + postId, // combination of ids to avoid duplicates
|
id: channelID + remoteID + retryID, // combination of ids to avoid duplicates
|
||||||
channelId: channelId,
|
channelID: channelID,
|
||||||
remoteId: remoteId, // empty means update all remote clusters
|
remoteID: remoteID, // empty means update all remote clusters
|
||||||
retryPost: retryPost,
|
retryMsg: retryMsg,
|
||||||
schedule: time.Now(),
|
schedule: time.Now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,16 +47,52 @@ func (st *syncTask) incRetry() bool {
|
|||||||
|
|
||||||
// NotifyChannelChanged is called to indicate that a shared channel has been modified,
|
// NotifyChannelChanged is called to indicate that a shared channel has been modified,
|
||||||
// thus triggering an update to all remote clusters.
|
// thus triggering an update to all remote clusters.
|
||||||
func (scs *Service) NotifyChannelChanged(channelId string) {
|
func (scs *Service) NotifyChannelChanged(channelID string) {
|
||||||
if rcs := scs.server.GetRemoteClusterService(); rcs == nil {
|
if rcs := scs.server.GetRemoteClusterService(); rcs == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
task := newSyncTask(channelId, "", nil)
|
task := newSyncTask(channelID, "", nil)
|
||||||
task.schedule = time.Now().Add(NotifyMinimumDelay)
|
task.schedule = time.Now().Add(NotifyMinimumDelay)
|
||||||
scs.addTask(task)
|
scs.addTask(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NotifyUserProfileChanged is called to indicate that a user belonging to at least one
|
||||||
|
// shared channel has modified their user profile (name, username, email, custom status, profile image)
|
||||||
|
func (scs *Service) NotifyUserProfileChanged(userID string) {
|
||||||
|
if rcs := scs.server.GetRemoteClusterService(); rcs == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scusers, err := scs.server.GetStore().SharedChannel().GetUsersForUser(userID)
|
||||||
|
if err != nil {
|
||||||
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Failed to fetch shared channel users",
|
||||||
|
mlog.String("userID", userID),
|
||||||
|
mlog.Err(err),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(scusers) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
notified := make(map[string]struct{})
|
||||||
|
|
||||||
|
for _, user := range scusers {
|
||||||
|
// update every channel + remote combination they belong to.
|
||||||
|
// Redundant updates (ie. to same remote for multiple channels) will be
|
||||||
|
// filtered out.
|
||||||
|
combo := user.ChannelId + user.RemoteId
|
||||||
|
if _, ok := notified[combo]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
notified[combo] = struct{}{}
|
||||||
|
task := newSyncTask(user.ChannelId, user.RemoteId, nil)
|
||||||
|
task.schedule = time.Now().Add(NotifyMinimumDelay)
|
||||||
|
scs.addTask(task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ForceSyncForRemote causes all channels shared with the remote to be synchronized.
|
// ForceSyncForRemote causes all channels shared with the remote to be synchronized.
|
||||||
func (scs *Service) ForceSyncForRemote(rc *model.RemoteCluster) {
|
func (scs *Service) ForceSyncForRemote(rc *model.RemoteCluster) {
|
||||||
if rcs := scs.server.GetRemoteClusterService(); rcs == nil {
|
if rcs := scs.server.GetRemoteClusterService(); rcs == nil {
|
||||||
@@ -155,8 +189,8 @@ func (scs *Service) doSync() time.Duration {
|
|||||||
scs.addTask(task)
|
scs.addTask(task)
|
||||||
} else {
|
} else {
|
||||||
scs.server.GetLogger().Error("Failed to synchronize shared channel",
|
scs.server.GetLogger().Error("Failed to synchronize shared channel",
|
||||||
mlog.String("channelId", task.channelId),
|
mlog.String("channelId", task.channelID),
|
||||||
mlog.String("remoteId", task.remoteId),
|
mlog.String("remoteId", task.remoteID),
|
||||||
mlog.Err(err),
|
mlog.Err(err),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -204,9 +238,9 @@ func (scs *Service) processTask(task syncTask) error {
|
|||||||
var err error
|
var err error
|
||||||
var remotes []*model.RemoteCluster
|
var remotes []*model.RemoteCluster
|
||||||
|
|
||||||
if task.remoteId == "" {
|
if task.remoteID == "" {
|
||||||
filter := model.RemoteClusterQueryFilter{
|
filter := model.RemoteClusterQueryFilter{
|
||||||
InChannel: task.channelId,
|
InChannel: task.channelID,
|
||||||
OnlyConfirmed: true,
|
OnlyConfirmed: true,
|
||||||
}
|
}
|
||||||
remotes, err = scs.server.GetStore().RemoteCluster().GetAll(filter)
|
remotes, err = scs.server.GetStore().RemoteCluster().GetAll(filter)
|
||||||
@@ -214,28 +248,27 @@ func (scs *Service) processTask(task syncTask) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rc, err := scs.server.GetStore().RemoteCluster().Get(task.remoteId)
|
rc, err := scs.server.GetStore().RemoteCluster().Get(task.remoteID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !rc.IsOnline() {
|
if !rc.IsOnline() {
|
||||||
return fmt.Errorf("Failed updating shared channel '%s' for offline remote cluster '%s'", task.channelId, rc.DisplayName)
|
return fmt.Errorf("Failed updating shared channel '%s' for offline remote cluster '%s'", task.channelID, rc.DisplayName)
|
||||||
}
|
}
|
||||||
remotes = []*model.RemoteCluster{rc}
|
remotes = []*model.RemoteCluster{rc}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rc := range remotes {
|
for _, rc := range remotes {
|
||||||
rtask := task
|
rtask := task
|
||||||
rtask.remoteId = rc.RemoteId
|
rtask.remoteID = rc.RemoteId
|
||||||
if err := scs.updateForRemote(rtask, rc); err != nil {
|
if err := scs.syncForRemote(rtask, rc); err != nil {
|
||||||
// retry...
|
// retry...
|
||||||
if rtask.incRetry() {
|
if rtask.incRetry() {
|
||||||
scs.addTask(rtask)
|
scs.addTask(rtask)
|
||||||
} else {
|
} else {
|
||||||
scs.server.GetLogger().Error("Failed to synchronize shared channel for remote cluster",
|
scs.server.GetLogger().Error("Failed to synchronize shared channel for remote cluster",
|
||||||
mlog.String("channelId", rtask.channelId),
|
mlog.String("channelId", rtask.channelID),
|
||||||
mlog.String("remote", rc.DisplayName),
|
mlog.String("remote", rc.DisplayName),
|
||||||
mlog.String("remoteId", rtask.remoteId),
|
|
||||||
mlog.Err(err),
|
mlog.Err(err),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -244,160 +277,8 @@ func (scs *Service) processTask(task syncTask) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateForRemote updates a remote cluster with any new posts/reactions for a specific
|
|
||||||
// channel. If many changes are found, only the oldest X changes are sent and the channel
|
|
||||||
// is re-added to the task map. This ensures no channels are starved for updates even if some
|
|
||||||
// channels are very active.
|
|
||||||
func (scs *Service) updateForRemote(task syncTask, rc *model.RemoteCluster) error {
|
|
||||||
rcs := scs.server.GetRemoteClusterService()
|
|
||||||
if rcs == nil {
|
|
||||||
return fmt.Errorf("cannot update remote cluster for channel id %s; Remote Cluster Service not enabled", task.channelId)
|
|
||||||
}
|
|
||||||
|
|
||||||
scr, err := scs.server.GetStore().SharedChannel().GetRemoteByIds(task.channelId, rc.RemoteId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var posts []*model.Post
|
|
||||||
var repeat bool
|
|
||||||
nextSince := scr.NextSyncAt
|
|
||||||
|
|
||||||
if task.retryPost != nil {
|
|
||||||
posts = []*model.Post{task.retryPost}
|
|
||||||
} else {
|
|
||||||
result, err2 := scs.getPostsSince(task.channelId, rc, scr.NextSyncAt)
|
|
||||||
if err2 != nil {
|
|
||||||
return err2
|
|
||||||
}
|
|
||||||
posts = result.posts
|
|
||||||
repeat = result.hasMore
|
|
||||||
nextSince = result.nextSince
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(posts) == 0 {
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "sync task found zero posts; skipping sync",
|
|
||||||
mlog.String("remote", rc.DisplayName),
|
|
||||||
mlog.String("channel_id", task.channelId),
|
|
||||||
mlog.Int64("lastSyncAt", scr.NextSyncAt),
|
|
||||||
mlog.Int64("nextSince", nextSince),
|
|
||||||
mlog.Bool("repeat", repeat),
|
|
||||||
)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "sync task found posts to sync",
|
|
||||||
mlog.String("remote", rc.DisplayName),
|
|
||||||
mlog.String("channel_id", task.channelId),
|
|
||||||
mlog.Int64("lastSyncAt", scr.NextSyncAt),
|
|
||||||
mlog.Int64("nextSince", nextSince),
|
|
||||||
mlog.Int("count", len(posts)),
|
|
||||||
mlog.Bool("repeat", repeat),
|
|
||||||
)
|
|
||||||
|
|
||||||
if !rc.IsOnline() {
|
|
||||||
scs.notifyRemoteOffline(posts, rc)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
syncMessages, err := scs.postsToSyncMessages(posts, task.channelId, rc, scr.NextSyncAt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(syncMessages) == 0 {
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "sync task, all messages filtered out; skipping sync",
|
|
||||||
mlog.String("remote", rc.DisplayName),
|
|
||||||
mlog.String("channel_id", task.channelId),
|
|
||||||
mlog.Bool("repeat", repeat),
|
|
||||||
)
|
|
||||||
|
|
||||||
// All posts were filtered out, meaning no need to send them. Fast forward SharedChannelRemote's NextSyncAt.
|
|
||||||
scs.updateNextSyncForRemote(scr.Id, rc, nextSince)
|
|
||||||
|
|
||||||
// if there are more posts eligible to sync then schedule another sync
|
|
||||||
if repeat {
|
|
||||||
scs.addTask(newSyncTask(task.channelId, task.remoteId, nil))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
scs.sendAttachments(syncMessages, rc)
|
|
||||||
|
|
||||||
b, err := json.Marshal(syncMessages)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
msg := model.NewRemoteClusterMsg(TopicSync, b)
|
|
||||||
|
|
||||||
if scs.server.GetLogger().IsLevelEnabled(mlog.LvlSharedChannelServiceMessagesOutbound) {
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceMessagesOutbound, "outbound message",
|
|
||||||
mlog.String("remote", rc.DisplayName),
|
|
||||||
mlog.Int64("NextSyncAt", scr.NextSyncAt),
|
|
||||||
mlog.String("msg", string(b)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), remotecluster.SendTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(1)
|
|
||||||
|
|
||||||
err = rcs.SendMsg(ctx, msg, rc, func(msg model.RemoteClusterMsg, rc *model.RemoteCluster, resp *remotecluster.Response, err error) {
|
|
||||||
defer wg.Done()
|
|
||||||
if err != nil {
|
|
||||||
return // this means the response could not be parsed; already logged
|
|
||||||
}
|
|
||||||
|
|
||||||
var syncResp SyncResponse
|
|
||||||
if err2 := json.Unmarshal(resp.Payload, &syncResp); err2 != nil {
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "invalid sync response after update shared channel",
|
|
||||||
mlog.String("remote", rc.DisplayName),
|
|
||||||
mlog.Err(err2),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Any Post(s) that failed to save on remote side are included in an array of post ids in the Response payload.
|
|
||||||
// Handle each error by retrying the post a fixed number of times before giving up.
|
|
||||||
for _, p := range syncResp.PostErrors {
|
|
||||||
scs.handlePostError(p, task, rc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// update NextSyncAt for all the users that were synchronized
|
|
||||||
scs.updateSyncUsers(syncResp.UsersSyncd, task.channelId, rc, nextSince)
|
|
||||||
})
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
// Optimistically update SharedChannelRemote's NextSyncAt; if any posts failed they will be retried.
|
|
||||||
scs.updateNextSyncForRemote(scr.Id, rc, nextSince)
|
|
||||||
}
|
|
||||||
|
|
||||||
if repeat {
|
|
||||||
scs.addTask(newSyncTask(task.channelId, task.remoteId, nil))
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (scs *Service) sendAttachments(syncMessages []syncMsg, rc *model.RemoteCluster) {
|
|
||||||
for _, sm := range syncMessages {
|
|
||||||
for _, fi := range sm.Attachments {
|
|
||||||
if err := scs.sendAttachmentForRemote(fi, sm.Post, rc); err != nil {
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "error syncing attachment for post",
|
|
||||||
mlog.String("remote", rc.DisplayName),
|
|
||||||
mlog.String("post_id", sm.Post.Id),
|
|
||||||
mlog.String("file_id", fi.Id),
|
|
||||||
mlog.Err(err),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (scs *Service) handlePostError(postId string, task syncTask, rc *model.RemoteCluster) {
|
func (scs *Service) handlePostError(postId string, task syncTask, rc *model.RemoteCluster) {
|
||||||
if task.retryPost != nil && task.retryPost.Id == postId {
|
if task.retryMsg != nil && len(task.retryMsg.Posts) == 1 && task.retryMsg.Posts[0].Id == postId {
|
||||||
// this was a retry for specific post that failed previously. Try again if within MaxRetries.
|
// this was a retry for specific post that failed previously. Try again if within MaxRetries.
|
||||||
if task.incRetry() {
|
if task.incRetry() {
|
||||||
scs.addTask(task)
|
scs.addTask(task)
|
||||||
@@ -419,7 +300,11 @@ func (scs *Service) handlePostError(postId string, task syncTask, rc *model.Remo
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
scs.addTask(newSyncTask(task.channelId, task.remoteId, post))
|
|
||||||
|
syncMsg := newSyncMsg(task.channelID)
|
||||||
|
syncMsg.Posts = []*model.Post{post}
|
||||||
|
|
||||||
|
scs.addTask(newSyncTask(task.channelID, task.remoteID, syncMsg))
|
||||||
}
|
}
|
||||||
|
|
||||||
// notifyRemoteOffline creates an ephemeral post to the author for any posts created recently to remotes
|
// notifyRemoteOffline creates an ephemeral post to the author for any posts created recently to remotes
|
||||||
@@ -452,54 +337,22 @@ func (scs *Service) notifyRemoteOffline(posts []*model.Post, rc *model.RemoteClu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (scs *Service) updateNextSyncForRemote(scrId string, rc *model.RemoteCluster, nextSyncAt int64) {
|
func (scs *Service) updateCursorForRemote(scrId string, rc *model.RemoteCluster, cursor model.GetPostsSinceForSyncCursor) {
|
||||||
if nextSyncAt == 0 {
|
if err := scs.server.GetStore().SharedChannel().UpdateRemoteCursor(scrId, cursor); err != nil {
|
||||||
return
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "error updating cursor for shared channel remote",
|
||||||
}
|
|
||||||
if err := scs.server.GetStore().SharedChannel().UpdateRemoteNextSyncAt(scrId, nextSyncAt); err != nil {
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "error updating NextSyncAt for shared channel remote",
|
|
||||||
mlog.String("remote", rc.DisplayName),
|
mlog.String("remote", rc.DisplayName),
|
||||||
mlog.Err(err),
|
mlog.Err(err),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "updated NextSyncAt for remote",
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "updated cursor for remote",
|
||||||
mlog.String("remote_id", rc.RemoteId),
|
mlog.String("remote_id", rc.RemoteId),
|
||||||
mlog.String("remote", rc.DisplayName),
|
mlog.String("remote", rc.DisplayName),
|
||||||
mlog.Int64("next_update_at", nextSyncAt),
|
mlog.Int64("last_post_update_at", cursor.LastPostUpdateAt),
|
||||||
|
mlog.String("last_post_id", cursor.LastPostId),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (scs *Service) updateSyncUsers(userIds []string, channelID string, rc *model.RemoteCluster, lastSyncAt int64) {
|
|
||||||
for _, uid := range userIds {
|
|
||||||
scu, err := scs.server.GetStore().SharedChannel().GetUser(uid, channelID, rc.RemoteId)
|
|
||||||
if err != nil {
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "error getting user for lastSyncAt update",
|
|
||||||
mlog.String("remote", rc.DisplayName),
|
|
||||||
mlog.String("user_id", uid),
|
|
||||||
mlog.Err(err),
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := scs.server.GetStore().SharedChannel().UpdateUserLastSyncAt(scu.Id, lastSyncAt); err != nil {
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "error updating lastSyncAt for user",
|
|
||||||
mlog.String("remote", rc.DisplayName),
|
|
||||||
mlog.String("user_id", uid),
|
|
||||||
mlog.String("channel_id", channelID),
|
|
||||||
mlog.Err(err),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "updated lastSyncAt for user",
|
|
||||||
mlog.String("remote", rc.DisplayName),
|
|
||||||
mlog.String("user_id", scu.UserId),
|
|
||||||
mlog.String("channel_id", channelID),
|
|
||||||
mlog.Int64("last_update_at", lastSyncAt),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (scs *Service) getUserTranslations(userId string) i18n.TranslateFunc {
|
func (scs *Service) getUserTranslations(userId string) i18n.TranslateFunc {
|
||||||
var locale string
|
var locale string
|
||||||
user, err := scs.server.GetStore().User().Get(context.Background(), userId)
|
user, err := scs.server.GetStore().User().Get(context.Background(), userId)
|
||||||
@@ -512,3 +365,72 @@ func (scs *Service) getUserTranslations(userId string) i18n.TranslateFunc {
|
|||||||
}
|
}
|
||||||
return i18n.GetUserTranslations(locale)
|
return i18n.GetUserTranslations(locale)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shouldUserSync determines if a user needs to be synchronized.
|
||||||
|
// User should be synchronized if it has no entry in the SharedChannelUsers table for the specified channel,
|
||||||
|
// or there is an entry but the LastSyncAt is less than user.UpdateAt
|
||||||
|
func (scs *Service) shouldUserSync(user *model.User, channelID string, rc *model.RemoteCluster) (sync bool, syncImage bool, err error) {
|
||||||
|
// don't sync users with the remote they originated from.
|
||||||
|
if user.RemoteId != nil && *user.RemoteId == rc.RemoteId {
|
||||||
|
return false, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
scu, err := scs.server.GetStore().SharedChannel().GetSingleUser(user.Id, channelID, rc.RemoteId)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(errNotFound); !ok {
|
||||||
|
return false, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// user not in the SharedChannelUsers table, so we must add them.
|
||||||
|
scu = &model.SharedChannelUser{
|
||||||
|
UserId: user.Id,
|
||||||
|
RemoteId: rc.RemoteId,
|
||||||
|
ChannelId: channelID,
|
||||||
|
}
|
||||||
|
if _, err = scs.server.GetStore().SharedChannel().SaveUser(scu); err != nil {
|
||||||
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error adding user to shared channel users",
|
||||||
|
mlog.String("remote_id", rc.RemoteId),
|
||||||
|
mlog.String("user_id", user.Id),
|
||||||
|
mlog.String("channel_id", user.Id),
|
||||||
|
mlog.Err(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return true, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.UpdateAt > scu.LastSyncAt, user.LastPictureUpdate > scu.LastSyncAt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scs *Service) syncProfileImage(user *model.User, channelID string, rc *model.RemoteCluster) {
|
||||||
|
rcs := scs.server.GetRemoteClusterService()
|
||||||
|
if rcs == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), ProfileImageSyncTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
rcs.SendProfileImage(ctx, user.Id, rc, scs.app, func(userId string, rc *model.RemoteCluster, resp *remotecluster.Response, err error) {
|
||||||
|
if resp.IsSuccess() {
|
||||||
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Users profile image synchronized",
|
||||||
|
mlog.String("remote_id", rc.RemoteId),
|
||||||
|
mlog.String("user_id", user.Id),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err2 := scs.server.GetStore().SharedChannel().UpdateUserLastSyncAt(user.Id, channelID, rc.RemoteId); err2 != nil {
|
||||||
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error updating users LastSyncTime after profile image update",
|
||||||
|
mlog.String("remote_id", rc.RemoteId),
|
||||||
|
mlog.String("user_id", user.Id),
|
||||||
|
mlog.Err(err2),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error synchronizing users profile image",
|
||||||
|
mlog.String("remote_id", rc.RemoteId),
|
||||||
|
mlog.String("user_id", user.Id),
|
||||||
|
mlog.Err(err),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
533
services/sharedchannel/sync_send_remote.go
Обычный файл
533
services/sharedchannel/sync_send_remote.go
Обычный файл
@@ -0,0 +1,533 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package sharedchannel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/wiggin77/merror"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/services/remotecluster"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/shared/mlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sendSyncMsgResultFunc func(syncResp SyncResponse, err error)
|
||||||
|
|
||||||
|
type attachment struct {
|
||||||
|
fi *model.FileInfo
|
||||||
|
post *model.Post
|
||||||
|
}
|
||||||
|
|
||||||
|
type syncData struct {
|
||||||
|
task syncTask
|
||||||
|
rc *model.RemoteCluster
|
||||||
|
scr *model.SharedChannelRemote
|
||||||
|
|
||||||
|
users map[string]*model.User
|
||||||
|
profileImages map[string]*model.User
|
||||||
|
posts []*model.Post
|
||||||
|
reactions []*model.Reaction
|
||||||
|
attachments []attachment
|
||||||
|
|
||||||
|
resultRepeat bool
|
||||||
|
resultNextCursor model.GetPostsSinceForSyncCursor
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSyncData(task syncTask, rc *model.RemoteCluster, scr *model.SharedChannelRemote) *syncData {
|
||||||
|
return &syncData{
|
||||||
|
task: task,
|
||||||
|
rc: rc,
|
||||||
|
scr: scr,
|
||||||
|
users: make(map[string]*model.User),
|
||||||
|
profileImages: make(map[string]*model.User),
|
||||||
|
resultNextCursor: model.GetPostsSinceForSyncCursor{LastPostUpdateAt: scr.LastPostUpdateAt, LastPostId: scr.LastPostId},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sd *syncData) isCursorChanged() bool {
|
||||||
|
return sd.scr.LastPostUpdateAt != sd.resultNextCursor.LastPostUpdateAt || sd.scr.LastPostId != sd.resultNextCursor.LastPostId
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncForRemote updates a remote cluster with any new posts/reactions for a specific
|
||||||
|
// channel. If many changes are found, only the oldest X changes are sent and the channel
|
||||||
|
// is re-added to the task map. This ensures no channels are starved for updates even if some
|
||||||
|
// channels are very active.
|
||||||
|
// Returning an error forces a retry on the task.
|
||||||
|
func (scs *Service) syncForRemote(task syncTask, rc *model.RemoteCluster) error {
|
||||||
|
rcs := scs.server.GetRemoteClusterService()
|
||||||
|
if rcs == nil {
|
||||||
|
return fmt.Errorf("cannot update remote cluster %s for channel id %s; Remote Cluster Service not enabled", rc.Name, task.channelID)
|
||||||
|
}
|
||||||
|
|
||||||
|
scr, err := scs.server.GetStore().SharedChannel().GetRemoteByIds(task.channelID, rc.RemoteId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if this is retrying a failed msg, just send it again.
|
||||||
|
if task.retryMsg != nil {
|
||||||
|
sd := newSyncData(task, rc, scr)
|
||||||
|
sd.users = task.retryMsg.Users
|
||||||
|
sd.posts = task.retryMsg.Posts
|
||||||
|
sd.reactions = task.retryMsg.Reactions
|
||||||
|
return scs.sendSyncData(sd)
|
||||||
|
}
|
||||||
|
|
||||||
|
sd := newSyncData(task, rc, scr)
|
||||||
|
|
||||||
|
// schedule another sync if the repeat flag is set at some point.
|
||||||
|
defer func(rpt *bool) {
|
||||||
|
if *rpt {
|
||||||
|
scs.addTask(newSyncTask(task.channelID, task.remoteID, nil))
|
||||||
|
}
|
||||||
|
}(&sd.resultRepeat)
|
||||||
|
|
||||||
|
// fetch new posts or retry post.
|
||||||
|
if err := scs.fetchPostsForSync(sd); err != nil {
|
||||||
|
return fmt.Errorf("cannot fetch posts for sync %v: %w", sd, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !rc.IsOnline() {
|
||||||
|
if len(sd.posts) != 0 {
|
||||||
|
scs.notifyRemoteOffline(sd.posts, rc)
|
||||||
|
}
|
||||||
|
sd.resultRepeat = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch users that have updated their user profile or image.
|
||||||
|
if err := scs.fetchUsersForSync(sd); err != nil {
|
||||||
|
return fmt.Errorf("cannot fetch users for sync %v: %w", sd, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch reactions for posts
|
||||||
|
if err := scs.fetchReactionsForSync(sd); err != nil {
|
||||||
|
return fmt.Errorf("cannot fetch reactions 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter out any posts that don't need to be sent.
|
||||||
|
scs.filterPostsForSync(sd)
|
||||||
|
|
||||||
|
// fetch attachments for posts
|
||||||
|
if err := scs.fetchPostAttachmentsForSync(sd); err != nil {
|
||||||
|
return fmt.Errorf("cannot fetch post attachments for sync %v: %w", sd, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sd.isEmpty() {
|
||||||
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Not sending sync data; everything filtered out",
|
||||||
|
mlog.String("remote", rc.DisplayName),
|
||||||
|
mlog.String("channel_id", task.channelID),
|
||||||
|
mlog.Bool("repeat", sd.resultRepeat),
|
||||||
|
)
|
||||||
|
if sd.isCursorChanged() {
|
||||||
|
scs.updateCursorForRemote(sd.scr.Id, sd.rc, sd.resultNextCursor)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Sending sync data",
|
||||||
|
mlog.String("remote", rc.DisplayName),
|
||||||
|
mlog.String("channel_id", task.channelID),
|
||||||
|
mlog.Bool("repeat", sd.resultRepeat),
|
||||||
|
mlog.Int("users", len(sd.users)),
|
||||||
|
mlog.Int("images", len(sd.profileImages)),
|
||||||
|
mlog.Int("posts", len(sd.posts)),
|
||||||
|
mlog.Int("reactions", len(sd.reactions)),
|
||||||
|
mlog.Int("attachments", len(sd.attachments)),
|
||||||
|
)
|
||||||
|
|
||||||
|
return scs.sendSyncData(sd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchUsersForSync populates the sync data with any channel users who updated their user profile
|
||||||
|
// since the last sync.
|
||||||
|
func (scs *Service) fetchUsersForSync(sd *syncData) error {
|
||||||
|
filter := model.GetUsersForSyncFilter{
|
||||||
|
ChannelID: sd.task.channelID,
|
||||||
|
Limit: MaxUsersPerSync,
|
||||||
|
}
|
||||||
|
users, err := scs.server.GetStore().SharedChannel().GetUsersForSync(filter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, u := range users {
|
||||||
|
if u.GetRemoteID() != sd.rc.RemoteId {
|
||||||
|
sd.users[u.Id] = u
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.CheckProfileImage = true
|
||||||
|
usersImage, err := scs.server.GetStore().SharedChannel().GetUsersForSync(filter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, u := range usersImage {
|
||||||
|
if u.GetRemoteID() != sd.rc.RemoteId {
|
||||||
|
sd.profileImages[u.Id] = u
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchPostsForSync populates the sync data with any new posts since the last sync.
|
||||||
|
func (scs *Service) fetchPostsForSync(sd *syncData) error {
|
||||||
|
options := model.GetPostsSinceForSyncOptions{
|
||||||
|
ChannelId: sd.task.channelID,
|
||||||
|
IncludeDeleted: true,
|
||||||
|
}
|
||||||
|
cursor := model.GetPostsSinceForSyncCursor{
|
||||||
|
LastPostUpdateAt: sd.scr.LastPostUpdateAt,
|
||||||
|
LastPostId: sd.scr.LastPostId,
|
||||||
|
}
|
||||||
|
|
||||||
|
posts, nextCursor, err := scs.server.GetStore().Post().GetPostsSinceForSync(options, cursor, MaxPostsPerSync)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not fetch new posts for sync: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the posts individually, checking for root posts that might appear later in the list.
|
||||||
|
// This is due to the UpdateAt collision handling algorithm where the order of posts is not based
|
||||||
|
// on UpdateAt or CreateAt when the posts have the same UpdateAt value. Here we are guarding
|
||||||
|
// against a root post with the same UpdateAt (and probably the same CreateAt) appearing later
|
||||||
|
// in the list and must be sync'd before the child post. This is and edge case that likely only
|
||||||
|
// happens during load testing or bulk imports.
|
||||||
|
for _, p := range posts {
|
||||||
|
if p.RootId != "" {
|
||||||
|
root, err := scs.server.GetStore().Post().GetSingle(p.RootId, true)
|
||||||
|
if err == nil {
|
||||||
|
if (root.CreateAt >= cursor.LastPostUpdateAt || root.UpdateAt >= cursor.LastPostUpdateAt) && !containsPost(sd.posts, root) {
|
||||||
|
sd.posts = append(sd.posts, root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sd.posts = append(sd.posts, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
sd.resultNextCursor = nextCursor
|
||||||
|
sd.resultRepeat = len(posts) == MaxPostsPerSync
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsPost(posts []*model.Post, post *model.Post) bool {
|
||||||
|
for _, p := range posts {
|
||||||
|
if p.Id == post.Id {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchReactionsForSync populates the sync data with any new reactions since the last sync.
|
||||||
|
func (scs *Service) fetchReactionsForSync(sd *syncData) error {
|
||||||
|
merr := merror.New()
|
||||||
|
for _, post := range sd.posts {
|
||||||
|
// any reactions originating from the remote cluster are filtered out
|
||||||
|
reactions, err := scs.server.GetStore().Reaction().GetForPostSince(post.Id, sd.scr.LastPostUpdateAt, sd.rc.RemoteId, true)
|
||||||
|
if err != nil {
|
||||||
|
merr.Append(fmt.Errorf("could not get reactions for post %s: %w", post.Id, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sd.reactions = append(sd.reactions, reactions...)
|
||||||
|
}
|
||||||
|
return merr.ErrorOrNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchPostUsersForSync populates the sync data with all users associated with posts.
|
||||||
|
func (scs *Service) fetchPostUsersForSync(sd *syncData) error {
|
||||||
|
sc, err := scs.server.GetStore().SharedChannel().Get(sd.task.channelID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot determine teamID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type p2mm struct {
|
||||||
|
post *model.Post
|
||||||
|
mentionMap model.UserMentionMap
|
||||||
|
}
|
||||||
|
|
||||||
|
userIDs := make(map[string]p2mm)
|
||||||
|
|
||||||
|
for _, reaction := range sd.reactions {
|
||||||
|
userIDs[reaction.UserId] = p2mm{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, post := range sd.posts {
|
||||||
|
// add author
|
||||||
|
userIDs[post.UserId] = p2mm{}
|
||||||
|
|
||||||
|
// get mentions and users for each mention
|
||||||
|
mentionMap := scs.app.MentionsToTeamMembers(post.Message, sc.TeamId)
|
||||||
|
for _, userID := range mentionMap {
|
||||||
|
userIDs[userID] = p2mm{
|
||||||
|
post: post,
|
||||||
|
mentionMap: mentionMap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
merr := merror.New()
|
||||||
|
|
||||||
|
for userID, v := range userIDs {
|
||||||
|
user, err := scs.server.GetStore().User().Get(context.Background(), userID)
|
||||||
|
if err != nil {
|
||||||
|
merr.Append(fmt.Errorf("could not get user %s: %w", userID, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
sync, syncImage, err2 := scs.shouldUserSync(user, sd.task.channelID, sd.rc)
|
||||||
|
if err2 != nil {
|
||||||
|
merr.Append(fmt.Errorf("could not check should sync user %s: %w", userID, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if sync {
|
||||||
|
sd.users[user.Id] = sanitizeUserForSync(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
if syncImage {
|
||||||
|
sd.profileImages[user.Id] = sanitizeUserForSync(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if this was a mention then put the real username in place of the username+remotename, but only
|
||||||
|
// when sending to the remote that the user belongs to.
|
||||||
|
if v.post != nil && user.RemoteId != nil && *user.RemoteId == sd.rc.RemoteId {
|
||||||
|
fixMention(v.post, v.mentionMap, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return merr.ErrorOrNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchPostAttachmentsForSync populates the sync data with any file attachments for new posts.
|
||||||
|
func (scs *Service) fetchPostAttachmentsForSync(sd *syncData) error {
|
||||||
|
merr := merror.New()
|
||||||
|
for _, post := range sd.posts {
|
||||||
|
fis, err := scs.server.GetStore().FileInfo().GetForPost(post.Id, false, true, true)
|
||||||
|
if err != nil {
|
||||||
|
merr.Append(fmt.Errorf("could not get file attachment info for post %s: %w", post.Id, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fi := range fis {
|
||||||
|
if scs.shouldSyncAttachment(fi, sd.rc) {
|
||||||
|
sd.attachments = append(sd.attachments, attachment{fi: fi, post: post})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return merr.ErrorOrNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
// filterPostsforSync removes any posts that do not need to sync.
|
||||||
|
func (scs *Service) filterPostsForSync(sd *syncData) {
|
||||||
|
filtered := make([]*model.Post, 0, len(sd.posts))
|
||||||
|
|
||||||
|
for _, p := range sd.posts {
|
||||||
|
// Don't resend an existing post where only the reactions changed.
|
||||||
|
// Posts we must send:
|
||||||
|
// - new posts (EditAt == 0)
|
||||||
|
// - edited posts (EditAt >= LastPostUpdateAt)
|
||||||
|
// - deleted posts (DeleteAt > 0)
|
||||||
|
if p.EditAt > 0 && p.EditAt < sd.scr.LastPostUpdateAt && p.DeleteAt == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't send a deleted post if it is just the original copy from an edit.
|
||||||
|
if p.DeleteAt > 0 && p.OriginalId != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't sync a post back to the remote it came from.
|
||||||
|
if p.GetRemoteID() == sd.rc.RemoteId {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse out all permalinks in the message.
|
||||||
|
p.Message = scs.processPermalinkToRemote(p)
|
||||||
|
|
||||||
|
filtered = append(filtered, p)
|
||||||
|
}
|
||||||
|
sd.posts = filtered
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSyncData sends all the collected users, posts, reactions, images, and attachments to the
|
||||||
|
// remote cluster.
|
||||||
|
// The order of items sent is important: users -> attachments -> posts -> reactions -> profile images
|
||||||
|
func (scs *Service) sendSyncData(sd *syncData) error {
|
||||||
|
merr := merror.New()
|
||||||
|
|
||||||
|
// send users
|
||||||
|
if len(sd.users) != 0 {
|
||||||
|
if err := scs.sendUserSyncData(sd); err != nil {
|
||||||
|
merr.Append(fmt.Errorf("cannot send user sync data: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send attachments
|
||||||
|
if len(sd.attachments) != 0 {
|
||||||
|
scs.sendAttachmentSyncData(sd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// send posts
|
||||||
|
if len(sd.posts) != 0 {
|
||||||
|
if err := scs.sendPostSyncData(sd); err != nil {
|
||||||
|
merr.Append(fmt.Errorf("cannot send post sync data: %w", err))
|
||||||
|
}
|
||||||
|
} else if sd.isCursorChanged() {
|
||||||
|
scs.updateCursorForRemote(sd.scr.Id, sd.rc, sd.resultNextCursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// send reactions
|
||||||
|
if len(sd.reactions) != 0 {
|
||||||
|
if err := scs.sendReactionSyncData(sd); err != nil {
|
||||||
|
merr.Append(fmt.Errorf("cannot send reaction sync data: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send user profile images
|
||||||
|
if len(sd.profileImages) != 0 {
|
||||||
|
scs.sendProfileImageSyncData(sd)
|
||||||
|
}
|
||||||
|
|
||||||
|
return merr.ErrorOrNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendUserSyncData sends the collected user updates to the remote cluster.
|
||||||
|
func (scs *Service) sendUserSyncData(sd *syncData) error {
|
||||||
|
msg := newSyncMsg(sd.task.channelID)
|
||||||
|
msg.Users = sd.users
|
||||||
|
|
||||||
|
err := scs.sendSyncMsgToRemote(msg, sd.rc, func(syncResp SyncResponse, errResp error) {
|
||||||
|
for _, userID := range syncResp.UsersSyncd {
|
||||||
|
if err := scs.server.GetStore().SharedChannel().UpdateUserLastSyncAt(userID, sd.task.channelID, sd.rc.RemoteId); err != nil {
|
||||||
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Cannot update shared channel user LastSyncAt",
|
||||||
|
mlog.String("user_id", userID),
|
||||||
|
mlog.String("channel_id", sd.task.channelID),
|
||||||
|
mlog.String("remote_id", sd.rc.RemoteId),
|
||||||
|
mlog.Err(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(syncResp.UserErrors) != 0 {
|
||||||
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Response indicates error for user(s) sync",
|
||||||
|
mlog.String("channel_id", sd.task.channelID),
|
||||||
|
mlog.String("remote_id", sd.rc.RemoteId),
|
||||||
|
mlog.Any("users", syncResp.UserErrors),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendAttachmentSyncData sends the collected post updates to the remote cluster.
|
||||||
|
func (scs *Service) sendAttachmentSyncData(sd *syncData) {
|
||||||
|
for _, a := range sd.attachments {
|
||||||
|
if err := scs.sendAttachmentForRemote(a.fi, a.post, sd.rc); err != nil {
|
||||||
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Cannot sync post attachment",
|
||||||
|
mlog.String("post_id", a.post.Id),
|
||||||
|
mlog.String("channel_id", sd.task.channelID),
|
||||||
|
mlog.String("remote_id", sd.rc.RemoteId),
|
||||||
|
mlog.Err(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// updating SharedChannelAttachments with LastSyncAt is already done.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendPostSyncData sends the collected post updates to the remote cluster.
|
||||||
|
func (scs *Service) sendPostSyncData(sd *syncData) error {
|
||||||
|
msg := newSyncMsg(sd.task.channelID)
|
||||||
|
msg.Posts = sd.posts
|
||||||
|
|
||||||
|
return scs.sendSyncMsgToRemote(msg, sd.rc, func(syncResp SyncResponse, errResp error) {
|
||||||
|
if len(syncResp.PostErrors) != 0 {
|
||||||
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Response indicates error for post(s) sync",
|
||||||
|
mlog.String("channel_id", sd.task.channelID),
|
||||||
|
mlog.String("remote_id", sd.rc.RemoteId),
|
||||||
|
mlog.Any("posts", syncResp.PostErrors),
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, postID := range syncResp.PostErrors {
|
||||||
|
scs.handlePostError(postID, sd.task, sd.rc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scs.updateCursorForRemote(sd.scr.Id, sd.rc, sd.resultNextCursor)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendReactionSyncData sends the collected reaction updates to the remote cluster.
|
||||||
|
func (scs *Service) sendReactionSyncData(sd *syncData) error {
|
||||||
|
msg := newSyncMsg(sd.task.channelID)
|
||||||
|
msg.Reactions = sd.reactions
|
||||||
|
|
||||||
|
return scs.sendSyncMsgToRemote(msg, sd.rc, func(syncResp SyncResponse, errResp error) {
|
||||||
|
if len(syncResp.ReactionErrors) != 0 {
|
||||||
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Response indicates error for reactions(s) sync",
|
||||||
|
mlog.String("channel_id", sd.task.channelID),
|
||||||
|
mlog.String("remote_id", sd.rc.RemoteId),
|
||||||
|
mlog.Any("reaction_posts", syncResp.ReactionErrors),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendProfileImageSyncData sends the collected user profile image updates to the remote cluster.
|
||||||
|
func (scs *Service) sendProfileImageSyncData(sd *syncData) {
|
||||||
|
for _, user := range sd.profileImages {
|
||||||
|
scs.syncProfileImage(user, sd.task.channelID, sd.rc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSyncMsgToRemote synchronously sends the sync message to the remote cluster.
|
||||||
|
func (scs *Service) sendSyncMsgToRemote(msg *syncMsg, rc *model.RemoteCluster, f sendSyncMsgResultFunc) error {
|
||||||
|
rcs := scs.server.GetRemoteClusterService()
|
||||||
|
if rcs == nil {
|
||||||
|
return fmt.Errorf("cannot update remote cluster %s for channel id %s; Remote Cluster Service not enabled", rc.Name, msg.ChannelId)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rcMsg := model.NewRemoteClusterMsg(TopicSync, b)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), remotecluster.SendTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
err = rcs.SendMsg(ctx, rcMsg, rc, func(rcMsg model.RemoteClusterMsg, rc *model.RemoteCluster, rcResp *remotecluster.Response, errResp error) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
var syncResp SyncResponse
|
||||||
|
if err2 := json.Unmarshal(rcResp.Payload, &syncResp); err2 != nil {
|
||||||
|
scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Invalid sync msg response from remote cluster",
|
||||||
|
mlog.String("remote", rc.Name),
|
||||||
|
mlog.String("channel_id", msg.ChannelId),
|
||||||
|
mlog.Err(err2),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if f != nil {
|
||||||
|
f(syncResp, errResp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -10,6 +10,41 @@ import (
|
|||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// fixMention replaces any mentions in a post for the user with the user's real username.
|
||||||
|
func fixMention(post *model.Post, mentionMap model.UserMentionMap, user *model.User) {
|
||||||
|
if post == nil || len(mentionMap) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
realUsername, ok := user.GetProp(KeyRemoteUsername)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// there may be more than one mention for each user so we have to walk the whole map.
|
||||||
|
for mention, id := range mentionMap {
|
||||||
|
if id == user.Id && strings.Contains(mention, ":") {
|
||||||
|
post.Message = strings.ReplaceAll(post.Message, "@"+mention, "@"+realUsername)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sanitizeUserForSync(user *model.User) *model.User {
|
||||||
|
user.Password = model.NewId()
|
||||||
|
user.AuthData = nil
|
||||||
|
user.AuthService = ""
|
||||||
|
user.Roles = "system_user"
|
||||||
|
user.AllowMarketing = false
|
||||||
|
user.NotifyProps = model.StringMap{}
|
||||||
|
user.LastPasswordUpdate = 0
|
||||||
|
user.LastPictureUpdate = 0
|
||||||
|
user.FailedAttempts = 0
|
||||||
|
user.MfaActive = false
|
||||||
|
user.MfaSecret = ""
|
||||||
|
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
// mungUsername creates a new username by combining username and remote cluster name, plus
|
// mungUsername creates a new username by combining username and remote cluster name, plus
|
||||||
// a suffix to create uniqueness. If the resulting username exceeds the max length then
|
// a suffix to create uniqueness. If the resulting username exceeds the max length then
|
||||||
// it is truncated and ellipses added.
|
// it is truncated and ellipses added.
|
||||||
|
|||||||
@@ -5394,7 +5394,7 @@ func (s *OpenTracingLayerPostStore) GetPostsSince(options model.GetPostsSinceOpt
|
|||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OpenTracingLayerPostStore) GetPostsSinceForSync(options model.GetPostsSinceForSyncOptions, allowFromCache bool) ([]*model.Post, error) {
|
func (s *OpenTracingLayerPostStore) GetPostsSinceForSync(options model.GetPostsSinceForSyncOptions, cursor model.GetPostsSinceForSyncCursor, limit int) ([]*model.Post, model.GetPostsSinceForSyncCursor, error) {
|
||||||
origCtx := s.Root.Store.Context()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetPostsSinceForSync")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetPostsSinceForSync")
|
||||||
s.Root.Store.SetContext(newCtx)
|
s.Root.Store.SetContext(newCtx)
|
||||||
@@ -5403,13 +5403,13 @@ func (s *OpenTracingLayerPostStore) GetPostsSinceForSync(options model.GetPostsS
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
result, err := s.PostStore.GetPostsSinceForSync(options, allowFromCache)
|
result, resultVar1, err := s.PostStore.GetPostsSinceForSync(options, cursor, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
span.LogFields(spanlog.Error(err))
|
span.LogFields(spanlog.Error(err))
|
||||||
ext.Error.Set(span, true)
|
ext.Error.Set(span, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, err
|
return result, resultVar1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OpenTracingLayerPostStore) GetRepliesForExport(parentID string) ([]*model.ReplyForExport, error) {
|
func (s *OpenTracingLayerPostStore) GetRepliesForExport(parentID string) ([]*model.ReplyForExport, error) {
|
||||||
@@ -7274,16 +7274,52 @@ func (s *OpenTracingLayerSharedChannelStore) GetRemotesStatus(channelId string)
|
|||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OpenTracingLayerSharedChannelStore) GetUser(userID string, channelID string, remoteID string) (*model.SharedChannelUser, error) {
|
func (s *OpenTracingLayerSharedChannelStore) GetSingleUser(userID string, channelID string, remoteID string) (*model.SharedChannelUser, error) {
|
||||||
origCtx := s.Root.Store.Context()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SharedChannelStore.GetUser")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SharedChannelStore.GetSingleUser")
|
||||||
s.Root.Store.SetContext(newCtx)
|
s.Root.Store.SetContext(newCtx)
|
||||||
defer func() {
|
defer func() {
|
||||||
s.Root.Store.SetContext(origCtx)
|
s.Root.Store.SetContext(origCtx)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
result, err := s.SharedChannelStore.GetUser(userID, channelID, remoteID)
|
result, err := s.SharedChannelStore.GetSingleUser(userID, channelID, remoteID)
|
||||||
|
if err != nil {
|
||||||
|
span.LogFields(spanlog.Error(err))
|
||||||
|
ext.Error.Set(span, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OpenTracingLayerSharedChannelStore) GetUsersForSync(filter model.GetUsersForSyncFilter) ([]*model.User, error) {
|
||||||
|
origCtx := s.Root.Store.Context()
|
||||||
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SharedChannelStore.GetUsersForSync")
|
||||||
|
s.Root.Store.SetContext(newCtx)
|
||||||
|
defer func() {
|
||||||
|
s.Root.Store.SetContext(origCtx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer span.Finish()
|
||||||
|
result, err := s.SharedChannelStore.GetUsersForSync(filter)
|
||||||
|
if err != nil {
|
||||||
|
span.LogFields(spanlog.Error(err))
|
||||||
|
ext.Error.Set(span, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OpenTracingLayerSharedChannelStore) GetUsersForUser(userID string) ([]*model.SharedChannelUser, error) {
|
||||||
|
origCtx := s.Root.Store.Context()
|
||||||
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SharedChannelStore.GetUsersForUser")
|
||||||
|
s.Root.Store.SetContext(newCtx)
|
||||||
|
defer func() {
|
||||||
|
s.Root.Store.SetContext(origCtx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer span.Finish()
|
||||||
|
result, err := s.SharedChannelStore.GetUsersForUser(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
span.LogFields(spanlog.Error(err))
|
span.LogFields(spanlog.Error(err))
|
||||||
ext.Error.Set(span, true)
|
ext.Error.Set(span, true)
|
||||||
@@ -7454,16 +7490,16 @@ func (s *OpenTracingLayerSharedChannelStore) UpdateRemote(remote *model.SharedCh
|
|||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OpenTracingLayerSharedChannelStore) UpdateRemoteNextSyncAt(id string, syncTime int64) error {
|
func (s *OpenTracingLayerSharedChannelStore) UpdateRemoteCursor(id string, cursor model.GetPostsSinceForSyncCursor) error {
|
||||||
origCtx := s.Root.Store.Context()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SharedChannelStore.UpdateRemoteNextSyncAt")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SharedChannelStore.UpdateRemoteCursor")
|
||||||
s.Root.Store.SetContext(newCtx)
|
s.Root.Store.SetContext(newCtx)
|
||||||
defer func() {
|
defer func() {
|
||||||
s.Root.Store.SetContext(origCtx)
|
s.Root.Store.SetContext(origCtx)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
err := s.SharedChannelStore.UpdateRemoteNextSyncAt(id, syncTime)
|
err := s.SharedChannelStore.UpdateRemoteCursor(id, cursor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
span.LogFields(spanlog.Error(err))
|
span.LogFields(spanlog.Error(err))
|
||||||
ext.Error.Set(span, true)
|
ext.Error.Set(span, true)
|
||||||
@@ -7472,7 +7508,7 @@ func (s *OpenTracingLayerSharedChannelStore) UpdateRemoteNextSyncAt(id string, s
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OpenTracingLayerSharedChannelStore) UpdateUserLastSyncAt(id string, syncTime int64) error {
|
func (s *OpenTracingLayerSharedChannelStore) UpdateUserLastSyncAt(userID string, channelID string, remoteID string) error {
|
||||||
origCtx := s.Root.Store.Context()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SharedChannelStore.UpdateUserLastSyncAt")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SharedChannelStore.UpdateUserLastSyncAt")
|
||||||
s.Root.Store.SetContext(newCtx)
|
s.Root.Store.SetContext(newCtx)
|
||||||
@@ -7481,7 +7517,7 @@ func (s *OpenTracingLayerSharedChannelStore) UpdateUserLastSyncAt(id string, syn
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
err := s.SharedChannelStore.UpdateUserLastSyncAt(id, syncTime)
|
err := s.SharedChannelStore.UpdateUserLastSyncAt(userID, channelID, remoteID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
span.LogFields(spanlog.Error(err))
|
span.LogFields(spanlog.Error(err))
|
||||||
ext.Error.Set(span, true)
|
ext.Error.Set(span, true)
|
||||||
|
|||||||
@@ -5824,21 +5824,21 @@ func (s *RetryLayerPostStore) GetPostsSince(options model.GetPostsSinceOptions,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RetryLayerPostStore) GetPostsSinceForSync(options model.GetPostsSinceForSyncOptions, allowFromCache bool) ([]*model.Post, error) {
|
func (s *RetryLayerPostStore) GetPostsSinceForSync(options model.GetPostsSinceForSyncOptions, cursor model.GetPostsSinceForSyncCursor, limit int) ([]*model.Post, model.GetPostsSinceForSyncCursor, error) {
|
||||||
|
|
||||||
tries := 0
|
tries := 0
|
||||||
for {
|
for {
|
||||||
result, err := s.PostStore.GetPostsSinceForSync(options, allowFromCache)
|
result, resultVar1, err := s.PostStore.GetPostsSinceForSync(options, cursor, limit)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return result, nil
|
return result, resultVar1, nil
|
||||||
}
|
}
|
||||||
if !isRepeatableError(err) {
|
if !isRepeatableError(err) {
|
||||||
return result, err
|
return result, resultVar1, err
|
||||||
}
|
}
|
||||||
tries++
|
tries++
|
||||||
if tries >= 3 {
|
if tries >= 3 {
|
||||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||||
return result, err
|
return result, resultVar1, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7896,11 +7896,51 @@ func (s *RetryLayerSharedChannelStore) GetRemotesStatus(channelId string) ([]*mo
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RetryLayerSharedChannelStore) GetUser(userID string, channelID string, remoteID string) (*model.SharedChannelUser, error) {
|
func (s *RetryLayerSharedChannelStore) GetSingleUser(userID string, channelID string, remoteID string) (*model.SharedChannelUser, error) {
|
||||||
|
|
||||||
tries := 0
|
tries := 0
|
||||||
for {
|
for {
|
||||||
result, err := s.SharedChannelStore.GetUser(userID, channelID, remoteID)
|
result, err := s.SharedChannelStore.GetSingleUser(userID, channelID, remoteID)
|
||||||
|
if err == nil {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
if !isRepeatableError(err) {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
tries++
|
||||||
|
if tries >= 3 {
|
||||||
|
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RetryLayerSharedChannelStore) GetUsersForSync(filter model.GetUsersForSyncFilter) ([]*model.User, error) {
|
||||||
|
|
||||||
|
tries := 0
|
||||||
|
for {
|
||||||
|
result, err := s.SharedChannelStore.GetUsersForSync(filter)
|
||||||
|
if err == nil {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
if !isRepeatableError(err) {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
tries++
|
||||||
|
if tries >= 3 {
|
||||||
|
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RetryLayerSharedChannelStore) GetUsersForUser(userID string) ([]*model.SharedChannelUser, error) {
|
||||||
|
|
||||||
|
tries := 0
|
||||||
|
for {
|
||||||
|
result, err := s.SharedChannelStore.GetUsersForUser(userID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
@@ -8096,11 +8136,11 @@ func (s *RetryLayerSharedChannelStore) UpdateRemote(remote *model.SharedChannelR
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RetryLayerSharedChannelStore) UpdateRemoteNextSyncAt(id string, syncTime int64) error {
|
func (s *RetryLayerSharedChannelStore) UpdateRemoteCursor(id string, cursor model.GetPostsSinceForSyncCursor) error {
|
||||||
|
|
||||||
tries := 0
|
tries := 0
|
||||||
for {
|
for {
|
||||||
err := s.SharedChannelStore.UpdateRemoteNextSyncAt(id, syncTime)
|
err := s.SharedChannelStore.UpdateRemoteCursor(id, cursor)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -8116,11 +8156,11 @@ func (s *RetryLayerSharedChannelStore) UpdateRemoteNextSyncAt(id string, syncTim
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RetryLayerSharedChannelStore) UpdateUserLastSyncAt(id string, syncTime int64) error {
|
func (s *RetryLayerSharedChannelStore) UpdateUserLastSyncAt(userID string, channelID string, remoteID string) error {
|
||||||
|
|
||||||
tries := 0
|
tries := 0
|
||||||
for {
|
for {
|
||||||
err := s.SharedChannelStore.UpdateUserLastSyncAt(id, syncTime)
|
err := s.SharedChannelStore.UpdateUserLastSyncAt(userID, channelID, remoteID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1004,26 +1004,16 @@ func (s *SqlPostStore) HasAutoResponsePostByUserSince(options model.GetPostsSinc
|
|||||||
return exist > 0, nil
|
return exist > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SqlPostStore) GetPostsSinceForSync(options model.GetPostsSinceForSyncOptions, _ /* allowFromCache */ bool) ([]*model.Post, error) {
|
func (s *SqlPostStore) GetPostsSinceForSync(options model.GetPostsSinceForSyncOptions, cursor model.GetPostsSinceForSyncCursor, limit int) ([]*model.Post, model.GetPostsSinceForSyncCursor, error) {
|
||||||
if options.Limit < 0 || options.Limit > 1000 {
|
|
||||||
return nil, store.NewErrInvalidInput("Post", "<options.Limit>", options.Limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
order := " ASC"
|
|
||||||
if options.SortDescending {
|
|
||||||
order = " DESC"
|
|
||||||
}
|
|
||||||
|
|
||||||
query := s.getQueryBuilder().
|
query := s.getQueryBuilder().
|
||||||
Select("*").
|
Select("*").
|
||||||
From("Posts").
|
From("Posts").
|
||||||
Where(sq.GtOrEq{"UpdateAt": options.Since}).
|
Where(sq.Or{sq.Gt{"UpdateAt": cursor.LastPostUpdateAt}, sq.And{sq.Eq{"UpdateAt": cursor.LastPostUpdateAt}, sq.Gt{"Id": cursor.LastPostId}}}).
|
||||||
Where(sq.Eq{"ChannelId": options.ChannelId}).
|
OrderBy("UpdateAt", "Id").
|
||||||
Limit(uint64(options.Limit)).
|
Limit(uint64(limit))
|
||||||
OrderBy("CreateAt"+order, "DeleteAt", "Id")
|
|
||||||
|
|
||||||
if options.Until > 0 {
|
if options.ChannelId != "" {
|
||||||
query = query.Where(sq.LtOrEq{"UpdateAt": options.Until})
|
query = query.Where(sq.Eq{"ChannelId": options.ChannelId})
|
||||||
}
|
}
|
||||||
|
|
||||||
if !options.IncludeDeleted {
|
if !options.IncludeDeleted {
|
||||||
@@ -1034,24 +1024,22 @@ func (s *SqlPostStore) GetPostsSinceForSync(options model.GetPostsSinceForSyncOp
|
|||||||
query = query.Where(sq.NotEq{"COALESCE(Posts.RemoteId,'')": options.ExcludeRemoteId})
|
query = query.Where(sq.NotEq{"COALESCE(Posts.RemoteId,'')": options.ExcludeRemoteId})
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.Offset > 0 {
|
|
||||||
query = query.Offset(uint64(options.Offset))
|
|
||||||
}
|
|
||||||
|
|
||||||
queryString, args, err := query.ToSql()
|
queryString, args, err := query.ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "getpostssinceforsync_tosql")
|
return nil, cursor, errors.Wrap(err, "getpostssinceforsync_tosql")
|
||||||
}
|
}
|
||||||
|
|
||||||
var posts []*model.Post
|
var posts []*model.Post
|
||||||
|
|
||||||
_, err = s.GetReplica().Select(&posts, queryString, args...)
|
_, err = s.GetReplica().Select(&posts, queryString, args...)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "failed to find Posts with channelId=%s", options.ChannelId)
|
return nil, cursor, errors.Wrapf(err, "error getting Posts with channelId=%s", options.ChannelId)
|
||||||
}
|
}
|
||||||
|
|
||||||
return posts, nil
|
if len(posts) != 0 {
|
||||||
|
cursor.LastPostUpdateAt = posts[len(posts)-1].UpdateAt
|
||||||
|
cursor.LastPostId = posts[len(posts)-1].Id
|
||||||
|
}
|
||||||
|
return posts, cursor, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SqlPostStore) GetPostsBefore(options model.GetPostsOptions) (*model.PostList, error) {
|
func (s *SqlPostStore) GetPostsBefore(options model.GetPostsOptions) (*model.PostList, error) {
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultGetUsersForSyncLimit = 100
|
||||||
|
)
|
||||||
|
|
||||||
type SqlSharedChannelStore struct {
|
type SqlSharedChannelStore struct {
|
||||||
*SqlStore
|
*SqlStore
|
||||||
}
|
}
|
||||||
@@ -40,6 +44,7 @@ func newSqlSharedChannelStore(sqlStore *SqlStore) store.SharedChannelStore {
|
|||||||
tableSharedChannelRemotes.ColMap("ChannelId").SetMaxSize(26)
|
tableSharedChannelRemotes.ColMap("ChannelId").SetMaxSize(26)
|
||||||
tableSharedChannelRemotes.ColMap("CreatorId").SetMaxSize(26)
|
tableSharedChannelRemotes.ColMap("CreatorId").SetMaxSize(26)
|
||||||
tableSharedChannelRemotes.ColMap("RemoteId").SetMaxSize(26)
|
tableSharedChannelRemotes.ColMap("RemoteId").SetMaxSize(26)
|
||||||
|
tableSharedChannelRemotes.ColMap("LastPostId").SetMaxSize(26)
|
||||||
tableSharedChannelRemotes.SetUniqueTogether("ChannelId", "RemoteId")
|
tableSharedChannelRemotes.SetUniqueTogether("ChannelId", "RemoteId")
|
||||||
|
|
||||||
tableSharedChannelUsers := db.AddTableWithName(model.SharedChannelUser{}, "SharedChannelUsers").SetKeys(false, "Id")
|
tableSharedChannelUsers := db.AddTableWithName(model.SharedChannelUser{}, "SharedChannelUsers").SetKeys(false, "Id")
|
||||||
@@ -467,20 +472,21 @@ func (s SqlSharedChannelStore) GetRemoteForUser(remoteId string, userId string)
|
|||||||
return &rc, nil
|
return &rc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRemoteNextSyncAt updates the NextSyncAt timestamp for the specified SharedChannelRemote.
|
// UpdateRemoteCursor updates the LastPostUpdateAt timestamp and LastPostId for the specified SharedChannelRemote.
|
||||||
func (s SqlSharedChannelStore) UpdateRemoteNextSyncAt(id string, syncTime int64) error {
|
func (s SqlSharedChannelStore) UpdateRemoteCursor(id string, cursor model.GetPostsSinceForSyncCursor) error {
|
||||||
squery, args, err := s.getQueryBuilder().
|
squery, args, err := s.getQueryBuilder().
|
||||||
Update("SharedChannelRemotes").
|
Update("SharedChannelRemotes").
|
||||||
Set("NextSyncAt", syncTime).
|
Set("LastPostUpdateAt", cursor.LastPostUpdateAt).
|
||||||
|
Set("LastPostId", cursor.LastPostId).
|
||||||
Where(sq.Eq{"Id": id}).
|
Where(sq.Eq{"Id": id}).
|
||||||
ToSql()
|
ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "update_shared_channel_remote_next_sync_at_tosql")
|
return errors.Wrap(err, "update_shared_channel_remote_cursor_tosql")
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := s.GetMaster().Exec(squery, args...)
|
result, err := s.GetMaster().Exec(squery, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to update NextSyncAt for SharedChannelRemote")
|
return errors.Wrap(err, "failed to update cursor for SharedChannelRemote")
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err := result.RowsAffected()
|
count, err := result.RowsAffected()
|
||||||
@@ -556,8 +562,8 @@ func (s SqlSharedChannelStore) SaveUser(scUser *model.SharedChannelUser) (*model
|
|||||||
return scUser, nil
|
return scUser, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUser fetches a shared channel user based on user_id and remoteId.
|
// GetSingleUser fetches a shared channel user based on userID, channelID and remoteID.
|
||||||
func (s SqlSharedChannelStore) GetUser(userID string, channelID string, remoteID string) (*model.SharedChannelUser, error) {
|
func (s SqlSharedChannelStore) GetSingleUser(userID string, channelID string, remoteID string) (*model.SharedChannelUser, error) {
|
||||||
var scu model.SharedChannelUser
|
var scu model.SharedChannelUser
|
||||||
|
|
||||||
squery, args, err := s.getQueryBuilder().
|
squery, args, err := s.getQueryBuilder().
|
||||||
@@ -569,7 +575,7 @@ func (s SqlSharedChannelStore) GetUser(userID string, channelID string, remoteID
|
|||||||
ToSql()
|
ToSql()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "getsharedchanneluser_tosql")
|
return nil, errors.Wrapf(err, "getsharedchannelsingleuser_tosql")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.GetReplica().SelectOne(&scu, squery, args...); err != nil {
|
if err := s.GetReplica().SelectOne(&scu, squery, args...); err != nil {
|
||||||
@@ -581,20 +587,104 @@ func (s SqlSharedChannelStore) GetUser(userID string, channelID string, remoteID
|
|||||||
return &scu, nil
|
return &scu, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUserLastSyncAt updates the LastSyncAt timestamp for the specified SharedChannelUser.
|
// GetUsersForUser fetches all shared channel user records based on userID.
|
||||||
func (s SqlSharedChannelStore) UpdateUserLastSyncAt(id string, syncTime int64) error {
|
func (s SqlSharedChannelStore) GetUsersForUser(userID string) ([]*model.SharedChannelUser, error) {
|
||||||
squery, args, err := s.getQueryBuilder().
|
squery, args, err := s.getQueryBuilder().
|
||||||
Update("SharedChannelUsers").
|
Select("*").
|
||||||
Set("LastSyncAt", syncTime).
|
From("SharedChannelUsers").
|
||||||
Where(sq.Eq{"Id": id}).
|
Where(sq.Eq{"SharedChannelUsers.UserId": userID}).
|
||||||
ToSql()
|
ToSql()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "update_shared_channel_user_last_sync_at_tosql")
|
return nil, errors.Wrapf(err, "getsharedchanneluser_tosql")
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := s.GetMaster().Exec(squery, args...)
|
var users []*model.SharedChannelUser
|
||||||
|
if _, err := s.GetReplica().Select(&users, squery, args...); err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return make([]*model.SharedChannelUser, 0), nil
|
||||||
|
}
|
||||||
|
return nil, errors.Wrapf(err, "failed to find shared channel user with UserId=%s", userID)
|
||||||
|
}
|
||||||
|
return users, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUsersForSync fetches all shared channel users that need to be synchronized, meaning their
|
||||||
|
// `SharedChannelUsers.LastSyncAt` is less than or equal to `User.UpdateAt`.
|
||||||
|
func (s SqlSharedChannelStore) GetUsersForSync(filter model.GetUsersForSyncFilter) ([]*model.User, error) {
|
||||||
|
if filter.Limit <= 0 {
|
||||||
|
filter.Limit = DefaultGetUsersForSyncLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
query := s.getQueryBuilder().
|
||||||
|
Select("u.*").
|
||||||
|
Distinct().
|
||||||
|
From("Users AS u").
|
||||||
|
Join("SharedChannelUsers AS scu ON u.Id = scu.UserId").
|
||||||
|
OrderBy("u.Id").
|
||||||
|
Limit(filter.Limit)
|
||||||
|
|
||||||
|
if filter.CheckProfileImage {
|
||||||
|
query = query.Where("scu.LastSyncAt < u.LastPictureUpdate")
|
||||||
|
} else {
|
||||||
|
query = query.Where("scu.LastSyncAt < u.UpdateAt")
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.ChannelID != "" {
|
||||||
|
query = query.Where(sq.Eq{"scu.ChannelId": filter.ChannelID})
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlQuery, args, err := query.ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to update LastSycnAt for SharedChannelUser")
|
return nil, errors.Wrapf(err, "getsharedchannelusersforsync_tosql")
|
||||||
|
}
|
||||||
|
|
||||||
|
var users []*model.User
|
||||||
|
if _, err := s.GetReplica().Select(&users, sqlQuery, args...); err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return make([]*model.User, 0), nil
|
||||||
|
}
|
||||||
|
return nil, errors.Wrapf(err, "failed to fetch shared channel users with ChannelId=%s",
|
||||||
|
filter.ChannelID)
|
||||||
|
}
|
||||||
|
return users, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserLastSyncAt updates the LastSyncAt timestamp for the specified SharedChannelUser.
|
||||||
|
func (s SqlSharedChannelStore) UpdateUserLastSyncAt(userID string, channelID string, remoteID string) error {
|
||||||
|
args := map[string]interface{}{"UserId": userID, "ChannelId": channelID, "RemoteId": remoteID}
|
||||||
|
|
||||||
|
var query string
|
||||||
|
if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
|
||||||
|
query = `
|
||||||
|
UPDATE
|
||||||
|
SharedChannelUsers AS scu
|
||||||
|
SET
|
||||||
|
LastSyncAt = GREATEST(Users.UpdateAt, Users.LastPictureUpdate)
|
||||||
|
FROM
|
||||||
|
Users
|
||||||
|
WHERE
|
||||||
|
Users.Id = scu.UserId AND scu.UserId = :UserId AND scu.ChannelId = :ChannelId AND scu.RemoteId = :RemoteId
|
||||||
|
`
|
||||||
|
} else if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
|
||||||
|
query = `
|
||||||
|
UPDATE
|
||||||
|
SharedChannelUsers AS scu
|
||||||
|
INNER JOIN
|
||||||
|
Users ON scu.UserId = Users.Id
|
||||||
|
SET
|
||||||
|
LastSyncAt = GREATEST(Users.UpdateAt, Users.LastPictureUpdate)
|
||||||
|
WHERE
|
||||||
|
scu.UserId = :UserId AND scu.ChannelId = :ChannelId AND scu.RemoteId = :RemoteId
|
||||||
|
`
|
||||||
|
} else {
|
||||||
|
return errors.New("unsupported DB driver " + s.DriverName())
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := s.GetMaster().Exec(query, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update LastSyncAt for SharedChannelUser with userId=%s, channelId=%s, remoteId=%s: %w",
|
||||||
|
userID, channelID, remoteID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err := result.RowsAffected()
|
count, err := result.RowsAffected()
|
||||||
@@ -602,7 +692,7 @@ func (s SqlSharedChannelStore) UpdateUserLastSyncAt(id string, syncTime int64) e
|
|||||||
return errors.Wrap(err, "failed to determine rows affected")
|
return errors.Wrap(err, "failed to determine rows affected")
|
||||||
}
|
}
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
return fmt.Errorf("id not found: %s", id)
|
return fmt.Errorf("SharedChannelUser not found: userId=%s, channelId=%s, remoteId=%s", userID, channelID, remoteID)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1067,6 +1067,8 @@ func upgradeDatabaseToVersion536(sqlStore *SqlStore) {
|
|||||||
//if shouldPerformUpgrade(sqlStore, Version5350, Version5360) {
|
//if shouldPerformUpgrade(sqlStore, Version5350, Version5360) {
|
||||||
|
|
||||||
sqlStore.CreateColumnIfNotExists("SharedChannelUsers", "ChannelId", "VARCHAR(26)", "VARCHAR(26)", "")
|
sqlStore.CreateColumnIfNotExists("SharedChannelUsers", "ChannelId", "VARCHAR(26)", "VARCHAR(26)", "")
|
||||||
|
sqlStore.CreateColumnIfNotExists("SharedChannelRemotes", "LastPostUpdateAt", "bigint", "bigint", "0")
|
||||||
|
sqlStore.CreateColumnIfNotExists("SharedChannelRemotes", "LastPostId", "VARCHAR(26)", "VARCHAR(26)", "")
|
||||||
|
|
||||||
// timed dnd status support
|
// timed dnd status support
|
||||||
sqlStore.CreateColumnIfNotExistsNoDefault("Status", "DNDEndTime", "BIGINT", "BIGINT")
|
sqlStore.CreateColumnIfNotExistsNoDefault("Status", "DNDEndTime", "BIGINT", "BIGINT")
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ type PostStore interface {
|
|||||||
SearchPostsInTeamForUser(paramsList []*model.SearchParams, userID, teamID string, page, perPage int) (*model.PostSearchResults, error)
|
SearchPostsInTeamForUser(paramsList []*model.SearchParams, userID, teamID string, page, perPage int) (*model.PostSearchResults, error)
|
||||||
GetOldestEntityCreationTime() (int64, error)
|
GetOldestEntityCreationTime() (int64, error)
|
||||||
HasAutoResponsePostByUserSince(options model.GetPostsSinceOptions, userId string) (bool, error)
|
HasAutoResponsePostByUserSince(options model.GetPostsSinceOptions, userId string) (bool, error)
|
||||||
GetPostsSinceForSync(options model.GetPostsSinceForSyncOptions, allowFromCache bool) ([]*model.Post, error)
|
GetPostsSinceForSync(options model.GetPostsSinceForSyncOptions, cursor model.GetPostsSinceForSyncCursor, limit int) ([]*model.Post, model.GetPostsSinceForSyncCursor, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserStore interface {
|
type UserStore interface {
|
||||||
@@ -853,13 +853,15 @@ type SharedChannelStore interface {
|
|||||||
GetRemoteForUser(remoteId string, userId string) (*model.RemoteCluster, error)
|
GetRemoteForUser(remoteId string, userId string) (*model.RemoteCluster, error)
|
||||||
GetRemoteByIds(channelId string, remoteId string) (*model.SharedChannelRemote, error)
|
GetRemoteByIds(channelId string, remoteId string) (*model.SharedChannelRemote, error)
|
||||||
GetRemotes(opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error)
|
GetRemotes(opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error)
|
||||||
UpdateRemoteNextSyncAt(id string, syncTime int64) error
|
UpdateRemoteCursor(id string, cursor model.GetPostsSinceForSyncCursor) error
|
||||||
DeleteRemote(remoteId string) (bool, error)
|
DeleteRemote(remoteId string) (bool, error)
|
||||||
GetRemotesStatus(channelId string) ([]*model.SharedChannelRemoteStatus, error)
|
GetRemotesStatus(channelId string) ([]*model.SharedChannelRemoteStatus, error)
|
||||||
|
|
||||||
SaveUser(remote *model.SharedChannelUser) (*model.SharedChannelUser, error)
|
SaveUser(remote *model.SharedChannelUser) (*model.SharedChannelUser, error)
|
||||||
GetUser(userID string, channelID string, remoteID string) (*model.SharedChannelUser, error)
|
GetSingleUser(userID string, channelID string, remoteID string) (*model.SharedChannelUser, error)
|
||||||
UpdateUserLastSyncAt(id string, syncTime int64) error
|
GetUsersForUser(userID string) ([]*model.SharedChannelUser, error)
|
||||||
|
GetUsersForSync(filter model.GetUsersForSyncFilter) ([]*model.User, error)
|
||||||
|
UpdateUserLastSyncAt(userID string, channelID string, remoteID string) error
|
||||||
|
|
||||||
SaveAttachment(remote *model.SharedChannelAttachment) (*model.SharedChannelAttachment, error)
|
SaveAttachment(remote *model.SharedChannelAttachment) (*model.SharedChannelAttachment, error)
|
||||||
UpsertAttachment(remote *model.SharedChannelAttachment) (string, error)
|
UpsertAttachment(remote *model.SharedChannelAttachment) (string, error)
|
||||||
|
|||||||
@@ -538,27 +538,34 @@ func (_m *PostStore) GetPostsSince(options model.GetPostsSinceOptions, allowFrom
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPostsSinceForSync provides a mock function with given fields: options, allowFromCache
|
// GetPostsSinceForSync provides a mock function with given fields: options, cursor, limit
|
||||||
func (_m *PostStore) GetPostsSinceForSync(options model.GetPostsSinceForSyncOptions, allowFromCache bool) ([]*model.Post, error) {
|
func (_m *PostStore) GetPostsSinceForSync(options model.GetPostsSinceForSyncOptions, cursor model.GetPostsSinceForSyncCursor, limit int) ([]*model.Post, model.GetPostsSinceForSyncCursor, error) {
|
||||||
ret := _m.Called(options, allowFromCache)
|
ret := _m.Called(options, cursor, limit)
|
||||||
|
|
||||||
var r0 []*model.Post
|
var r0 []*model.Post
|
||||||
if rf, ok := ret.Get(0).(func(model.GetPostsSinceForSyncOptions, bool) []*model.Post); ok {
|
if rf, ok := ret.Get(0).(func(model.GetPostsSinceForSyncOptions, model.GetPostsSinceForSyncCursor, int) []*model.Post); ok {
|
||||||
r0 = rf(options, allowFromCache)
|
r0 = rf(options, cursor, limit)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).([]*model.Post)
|
r0 = ret.Get(0).([]*model.Post)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var r1 error
|
var r1 model.GetPostsSinceForSyncCursor
|
||||||
if rf, ok := ret.Get(1).(func(model.GetPostsSinceForSyncOptions, bool) error); ok {
|
if rf, ok := ret.Get(1).(func(model.GetPostsSinceForSyncOptions, model.GetPostsSinceForSyncCursor, int) model.GetPostsSinceForSyncCursor); ok {
|
||||||
r1 = rf(options, allowFromCache)
|
r1 = rf(options, cursor, limit)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Get(1).(model.GetPostsSinceForSyncCursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r0, r1
|
var r2 error
|
||||||
|
if rf, ok := ret.Get(2).(func(model.GetPostsSinceForSyncOptions, model.GetPostsSinceForSyncCursor, int) error); ok {
|
||||||
|
r2 = rf(options, cursor, limit)
|
||||||
|
} else {
|
||||||
|
r2 = ret.Error(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1, r2
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRepliesForExport provides a mock function with given fields: parentID
|
// GetRepliesForExport provides a mock function with given fields: parentID
|
||||||
|
|||||||
@@ -261,8 +261,8 @@ func (_m *SharedChannelStore) GetRemotesStatus(channelId string) ([]*model.Share
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUser provides a mock function with given fields: userID, channelID, remoteID
|
// GetSingleUser provides a mock function with given fields: userID, channelID, remoteID
|
||||||
func (_m *SharedChannelStore) GetUser(userID string, channelID string, remoteID string) (*model.SharedChannelUser, error) {
|
func (_m *SharedChannelStore) GetSingleUser(userID string, channelID string, remoteID string) (*model.SharedChannelUser, error) {
|
||||||
ret := _m.Called(userID, channelID, remoteID)
|
ret := _m.Called(userID, channelID, remoteID)
|
||||||
|
|
||||||
var r0 *model.SharedChannelUser
|
var r0 *model.SharedChannelUser
|
||||||
@@ -284,6 +284,52 @@ func (_m *SharedChannelStore) GetUser(userID string, channelID string, remoteID
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUsersForSync provides a mock function with given fields: filter
|
||||||
|
func (_m *SharedChannelStore) GetUsersForSync(filter model.GetUsersForSyncFilter) ([]*model.User, error) {
|
||||||
|
ret := _m.Called(filter)
|
||||||
|
|
||||||
|
var r0 []*model.User
|
||||||
|
if rf, ok := ret.Get(0).(func(model.GetUsersForSyncFilter) []*model.User); ok {
|
||||||
|
r0 = rf(filter)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]*model.User)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(model.GetUsersForSyncFilter) error); ok {
|
||||||
|
r1 = rf(filter)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUsersForUser provides a mock function with given fields: userID
|
||||||
|
func (_m *SharedChannelStore) GetUsersForUser(userID string) ([]*model.SharedChannelUser, error) {
|
||||||
|
ret := _m.Called(userID)
|
||||||
|
|
||||||
|
var r0 []*model.SharedChannelUser
|
||||||
|
if rf, ok := ret.Get(0).(func(string) []*model.SharedChannelUser); ok {
|
||||||
|
r0 = rf(userID)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]*model.SharedChannelUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||||
|
r1 = rf(userID)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
// HasChannel provides a mock function with given fields: channelID
|
// HasChannel provides a mock function with given fields: channelID
|
||||||
func (_m *SharedChannelStore) HasChannel(channelID string) (bool, error) {
|
func (_m *SharedChannelStore) HasChannel(channelID string) (bool, error) {
|
||||||
ret := _m.Called(channelID)
|
ret := _m.Called(channelID)
|
||||||
@@ -478,13 +524,13 @@ func (_m *SharedChannelStore) UpdateRemote(remote *model.SharedChannelRemote) (*
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRemoteNextSyncAt provides a mock function with given fields: id, syncTime
|
// UpdateRemoteCursor provides a mock function with given fields: id, cursor
|
||||||
func (_m *SharedChannelStore) UpdateRemoteNextSyncAt(id string, syncTime int64) error {
|
func (_m *SharedChannelStore) UpdateRemoteCursor(id string, cursor model.GetPostsSinceForSyncCursor) error {
|
||||||
ret := _m.Called(id, syncTime)
|
ret := _m.Called(id, cursor)
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
|
if rf, ok := ret.Get(0).(func(string, model.GetPostsSinceForSyncCursor) error); ok {
|
||||||
r0 = rf(id, syncTime)
|
r0 = rf(id, cursor)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
r0 = ret.Error(0)
|
||||||
}
|
}
|
||||||
@@ -492,13 +538,13 @@ func (_m *SharedChannelStore) UpdateRemoteNextSyncAt(id string, syncTime int64)
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUserLastSyncAt provides a mock function with given fields: id, syncTime
|
// UpdateUserLastSyncAt provides a mock function with given fields: userID, channelID, remoteID
|
||||||
func (_m *SharedChannelStore) UpdateUserLastSyncAt(id string, syncTime int64) error {
|
func (_m *SharedChannelStore) UpdateUserLastSyncAt(userID string, channelID string, remoteID string) error {
|
||||||
ret := _m.Called(id, syncTime)
|
ret := _m.Called(userID, channelID, remoteID)
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
|
if rf, ok := ret.Get(0).(func(string, string, string) error); ok {
|
||||||
r0 = rf(id, syncTime)
|
r0 = rf(userID, channelID, remoteID)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
r0 = ret.Error(0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ func TestPostStore(t *testing.T, ss store.Store, s SqlStore) {
|
|||||||
t.Run("GetDirectPostParentsForExportAfterBatched", func(t *testing.T) { testPostStoreGetDirectPostParentsForExportAfterBatched(t, ss, s) })
|
t.Run("GetDirectPostParentsForExportAfterBatched", func(t *testing.T) { testPostStoreGetDirectPostParentsForExportAfterBatched(t, ss, s) })
|
||||||
t.Run("GetForThread", func(t *testing.T) { testPostStoreGetForThread(t, ss) })
|
t.Run("GetForThread", func(t *testing.T) { testPostStoreGetForThread(t, ss) })
|
||||||
t.Run("HasAutoResponsePostByUserSince", func(t *testing.T) { testHasAutoResponsePostByUserSince(t, ss) })
|
t.Run("HasAutoResponsePostByUserSince", func(t *testing.T) { testHasAutoResponsePostByUserSince(t, ss) })
|
||||||
|
t.Run("GetPostsSinceForSync", func(t *testing.T) { testGetPostsSinceForSync(t, ss, s) })
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPostStoreSave(t *testing.T, ss store.Store) {
|
func testPostStoreSave(t *testing.T, ss store.Store) {
|
||||||
@@ -3013,3 +3014,118 @@ func testHasAutoResponsePostByUserSince(t *testing.T, ss store.Store) {
|
|||||||
assert.False(t, exists)
|
assert.False(t, exists)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testGetPostsSinceForSync(t *testing.T, ss store.Store, s SqlStore) {
|
||||||
|
// create some posts.
|
||||||
|
channelID := model.NewId()
|
||||||
|
remoteID := model.NewString(model.NewId())
|
||||||
|
first := model.GetMillis()
|
||||||
|
|
||||||
|
data := []*model.Post{
|
||||||
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 0"},
|
||||||
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 1"},
|
||||||
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 2"},
|
||||||
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 3", RemoteId: remoteID},
|
||||||
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 4", RemoteId: remoteID},
|
||||||
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 5", RemoteId: remoteID},
|
||||||
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 6", RemoteId: remoteID},
|
||||||
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 7"},
|
||||||
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 8", DeleteAt: model.GetMillis()},
|
||||||
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 9", DeleteAt: model.GetMillis()},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, p := range data {
|
||||||
|
p.UpdateAt = first + (int64(i) * 300000)
|
||||||
|
if p.RemoteId == nil {
|
||||||
|
p.RemoteId = model.NewString(model.NewId())
|
||||||
|
}
|
||||||
|
_, err := ss.Post().Save(p)
|
||||||
|
require.NoError(t, err, "couldn't save post")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Invalid channel id", func(t *testing.T) {
|
||||||
|
opt := model.GetPostsSinceForSyncOptions{
|
||||||
|
ChannelId: model.NewId(),
|
||||||
|
}
|
||||||
|
cursor := model.GetPostsSinceForSyncCursor{}
|
||||||
|
posts, cursorOut, err := ss.Post().GetPostsSinceForSync(opt, cursor, 100)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Empty(t, posts, "should return zero posts")
|
||||||
|
require.Equal(t, cursor, cursorOut)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Get by channel, exclude remotes, exclude deleted", func(t *testing.T) {
|
||||||
|
opt := model.GetPostsSinceForSyncOptions{
|
||||||
|
ChannelId: channelID,
|
||||||
|
ExcludeRemoteId: *remoteID,
|
||||||
|
}
|
||||||
|
cursor := model.GetPostsSinceForSyncCursor{}
|
||||||
|
posts, _, err := ss.Post().GetPostsSinceForSync(opt, cursor, 100)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.ElementsMatch(t, getPostIds(data[0:3], data[7]), getPostIds(posts))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Include deleted", func(t *testing.T) {
|
||||||
|
opt := model.GetPostsSinceForSyncOptions{
|
||||||
|
ChannelId: channelID,
|
||||||
|
IncludeDeleted: true,
|
||||||
|
}
|
||||||
|
cursor := model.GetPostsSinceForSyncCursor{}
|
||||||
|
posts, _, err := ss.Post().GetPostsSinceForSync(opt, cursor, 100)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.ElementsMatch(t, getPostIds(data), getPostIds(posts))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Limit and cursor", func(t *testing.T) {
|
||||||
|
opt := model.GetPostsSinceForSyncOptions{
|
||||||
|
ChannelId: channelID,
|
||||||
|
}
|
||||||
|
cursor := model.GetPostsSinceForSyncCursor{}
|
||||||
|
posts1, cursor, err := ss.Post().GetPostsSinceForSync(opt, cursor, 5)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, posts1, 5, "should get 5 posts")
|
||||||
|
|
||||||
|
posts2, _, err := ss.Post().GetPostsSinceForSync(opt, cursor, 5)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, posts2, 3, "should get 3 posts")
|
||||||
|
|
||||||
|
require.ElementsMatch(t, getPostIds(data[0:8]), getPostIds(posts1, posts2...))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("UpdateAt collisions", func(t *testing.T) {
|
||||||
|
// this test requires all the UpdateAt timestamps to be the same.
|
||||||
|
args := map[string]interface{}{"UpdateAt": model.GetMillis()}
|
||||||
|
result, err := s.GetMaster().Exec("UPDATE Posts SET UpdateAt = :UpdateAt", args)
|
||||||
|
require.NoError(t, err)
|
||||||
|
rows, err := result.RowsAffected()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Greater(t, rows, int64(0))
|
||||||
|
|
||||||
|
opt := model.GetPostsSinceForSyncOptions{
|
||||||
|
ChannelId: channelID,
|
||||||
|
}
|
||||||
|
cursor := model.GetPostsSinceForSyncCursor{}
|
||||||
|
posts1, cursor, err := ss.Post().GetPostsSinceForSync(opt, cursor, 5)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, posts1, 5, "should get 5 posts")
|
||||||
|
|
||||||
|
posts2, _, err := ss.Post().GetPostsSinceForSync(opt, cursor, 5)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, posts2, 3, "should get 3 posts")
|
||||||
|
|
||||||
|
require.ElementsMatch(t, getPostIds(data[0:8]), getPostIds(posts1, posts2...))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPostIds(posts []*model.Post, morePosts ...*model.Post) []string {
|
||||||
|
ids := make([]string, 0, len(posts)+len(morePosts))
|
||||||
|
for _, p := range posts {
|
||||||
|
ids = append(ids, p.Id)
|
||||||
|
}
|
||||||
|
for _, p := range morePosts {
|
||||||
|
ids = append(ids, p.Id)
|
||||||
|
}
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,11 +30,13 @@ func TestSharedChannelStore(t *testing.T, ss store.Store, s SqlStore) {
|
|||||||
t.Run("GetSharedChannelRemotes", func(t *testing.T) { testGetSharedChannelRemotes(t, ss) })
|
t.Run("GetSharedChannelRemotes", func(t *testing.T) { testGetSharedChannelRemotes(t, ss) })
|
||||||
t.Run("HasRemote", func(t *testing.T) { testHasRemote(t, ss) })
|
t.Run("HasRemote", func(t *testing.T) { testHasRemote(t, ss) })
|
||||||
t.Run("GetRemoteForUser", func(t *testing.T) { testGetRemoteForUser(t, ss) })
|
t.Run("GetRemoteForUser", func(t *testing.T) { testGetRemoteForUser(t, ss) })
|
||||||
t.Run("UpdateSharedChannelRemoteNextSyncAt", func(t *testing.T) { testUpdateSharedChannelRemoteNextSyncAt(t, ss) })
|
t.Run("UpdateSharedChannelRemoteNextSyncAt", func(t *testing.T) { testUpdateSharedChannelRemoteCursor(t, ss) })
|
||||||
t.Run("DeleteSharedChannelRemote", func(t *testing.T) { testDeleteSharedChannelRemote(t, ss) })
|
t.Run("DeleteSharedChannelRemote", func(t *testing.T) { testDeleteSharedChannelRemote(t, ss) })
|
||||||
|
|
||||||
t.Run("SaveSharedChannelUser", func(t *testing.T) { testSaveSharedChannelUser(t, ss) })
|
t.Run("SaveSharedChannelUser", func(t *testing.T) { testSaveSharedChannelUser(t, ss) })
|
||||||
|
t.Run("GetSharedChannelSingleUser", func(t *testing.T) { testGetSingleSharedChannelUser(t, ss) })
|
||||||
t.Run("GetSharedChannelUser", func(t *testing.T) { testGetSharedChannelUser(t, ss) })
|
t.Run("GetSharedChannelUser", func(t *testing.T) { testGetSharedChannelUser(t, ss) })
|
||||||
|
t.Run("GetSharedChannelUsersForSync", func(t *testing.T) { testGetSharedChannelUsersForSync(t, ss) })
|
||||||
t.Run("UpdateSharedChannelUserLastSyncAt", func(t *testing.T) { testUpdateSharedChannelUserLastSyncAt(t, ss) })
|
t.Run("UpdateSharedChannelUserLastSyncAt", func(t *testing.T) { testUpdateSharedChannelUserLastSyncAt(t, ss) })
|
||||||
|
|
||||||
t.Run("SaveSharedChannelAttachment", func(t *testing.T) { testSaveSharedChannelAttachment(t, ss) })
|
t.Run("SaveSharedChannelAttachment", func(t *testing.T) { testSaveSharedChannelAttachment(t, ss) })
|
||||||
@@ -714,7 +716,7 @@ func testGetRemoteForUser(t *testing.T, ss store.Store) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testUpdateSharedChannelRemoteNextSyncAt(t *testing.T, ss store.Store) {
|
func testUpdateSharedChannelRemoteCursor(t *testing.T, ss store.Store) {
|
||||||
channel, err := createTestChannel(ss, "test_remote_update_next_sync_at")
|
channel, err := createTestChannel(ss, "test_remote_update_next_sync_at")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -728,18 +730,25 @@ func testUpdateSharedChannelRemoteNextSyncAt(t *testing.T, ss store.Store) {
|
|||||||
require.NoError(t, err, "couldn't save remote", err)
|
require.NoError(t, err, "couldn't save remote", err)
|
||||||
|
|
||||||
future := model.GetMillis() + 3600000 // 1 hour in the future
|
future := model.GetMillis() + 3600000 // 1 hour in the future
|
||||||
|
postID := model.NewId()
|
||||||
|
|
||||||
|
cursor := model.GetPostsSinceForSyncCursor{
|
||||||
|
LastPostUpdateAt: future,
|
||||||
|
LastPostId: postID,
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("Update NextSyncAt for remote", func(t *testing.T) {
|
t.Run("Update NextSyncAt for remote", func(t *testing.T) {
|
||||||
err := ss.SharedChannel().UpdateRemoteNextSyncAt(remoteSaved.Id, future)
|
err := ss.SharedChannel().UpdateRemoteCursor(remoteSaved.Id, cursor)
|
||||||
require.NoError(t, err, "update NextSyncAt should not error", err)
|
require.NoError(t, err, "update NextSyncAt should not error", err)
|
||||||
|
|
||||||
r, err := ss.SharedChannel().GetRemote(remoteSaved.Id)
|
r, err := ss.SharedChannel().GetRemote(remoteSaved.Id)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, future, r.NextSyncAt)
|
require.Equal(t, future, r.LastPostUpdateAt)
|
||||||
|
require.Equal(t, postID, r.LastPostId)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Update NextSyncAt for non-existent shared channel remote", func(t *testing.T) {
|
t.Run("Update NextSyncAt for non-existent shared channel remote", func(t *testing.T) {
|
||||||
err := ss.SharedChannel().UpdateRemoteNextSyncAt(model.NewId(), future)
|
err := ss.SharedChannel().UpdateRemoteCursor(model.NewId(), cursor)
|
||||||
require.Error(t, err, "update non-existent remote should error", err)
|
require.Error(t, err, "update non-existent remote should error", err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -862,7 +871,7 @@ func testSaveSharedChannelUser(t *testing.T, ss store.Store) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testGetSharedChannelUser(t *testing.T, ss store.Store) {
|
func testGetSingleSharedChannelUser(t *testing.T, ss store.Store) {
|
||||||
scUser := &model.SharedChannelUser{
|
scUser := &model.SharedChannelUser{
|
||||||
UserId: model.NewId(),
|
UserId: model.NewId(),
|
||||||
RemoteId: model.NewId(),
|
RemoteId: model.NewId(),
|
||||||
@@ -873,7 +882,7 @@ func testGetSharedChannelUser(t *testing.T, ss store.Store) {
|
|||||||
require.NoError(t, err, "could not save user", err)
|
require.NoError(t, err, "could not save user", err)
|
||||||
|
|
||||||
t.Run("Get existing shared channel user", func(t *testing.T) {
|
t.Run("Get existing shared channel user", func(t *testing.T) {
|
||||||
r, err := ss.SharedChannel().GetUser(userSaved.UserId, userSaved.ChannelId, userSaved.RemoteId)
|
r, err := ss.SharedChannel().GetSingleUser(userSaved.UserId, userSaved.ChannelId, userSaved.RemoteId)
|
||||||
require.NoError(t, err, "couldn't get shared channel user", err)
|
require.NoError(t, err, "couldn't get shared channel user", err)
|
||||||
|
|
||||||
require.Equal(t, userSaved.Id, r.Id)
|
require.Equal(t, userSaved.Id, r.Id)
|
||||||
@@ -883,35 +892,174 @@ func testGetSharedChannelUser(t *testing.T, ss store.Store) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Get non-existent shared channel user", func(t *testing.T) {
|
t.Run("Get non-existent shared channel user", func(t *testing.T) {
|
||||||
u, err := ss.SharedChannel().GetUser(model.NewId(), model.NewId(), model.NewId())
|
u, err := ss.SharedChannel().GetSingleUser(model.NewId(), model.NewId(), model.NewId())
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Nil(t, u)
|
require.Nil(t, u)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testUpdateSharedChannelUserLastSyncAt(t *testing.T, ss store.Store) {
|
func testGetSharedChannelUser(t *testing.T, ss store.Store) {
|
||||||
scUser := &model.SharedChannelUser{
|
userId := model.NewId()
|
||||||
UserId: model.NewId(),
|
for i := 0; i < 10; i++ {
|
||||||
RemoteId: model.NewId(),
|
scUser := &model.SharedChannelUser{
|
||||||
ChannelId: model.NewId(),
|
UserId: userId,
|
||||||
|
RemoteId: model.NewId(),
|
||||||
|
ChannelId: model.NewId(),
|
||||||
|
}
|
||||||
|
_, err := ss.SharedChannel().SaveUser(scUser)
|
||||||
|
require.NoError(t, err, "could not save user", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
userSaved, err := ss.SharedChannel().SaveUser(scUser)
|
t.Run("Get existing shared channel user", func(t *testing.T) {
|
||||||
|
scus, err := ss.SharedChannel().GetUsersForUser(userId)
|
||||||
|
require.NoError(t, err, "couldn't get shared channel user", err)
|
||||||
|
|
||||||
|
require.Len(t, scus, 10, "should be 10 shared channel user records")
|
||||||
|
require.Equal(t, userId, scus[0].UserId)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Get non-existent shared channel user", func(t *testing.T) {
|
||||||
|
scus, err := ss.SharedChannel().GetUsersForUser(model.NewId())
|
||||||
|
require.NoError(t, err, "should not error when not found")
|
||||||
|
require.Empty(t, scus, "should be empty")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGetSharedChannelUsersForSync(t *testing.T, ss store.Store) {
|
||||||
|
channelID := model.NewId()
|
||||||
|
remoteID := model.NewId()
|
||||||
|
earlier := model.GetMillis() - 300000
|
||||||
|
later := model.GetMillis() + 300000
|
||||||
|
|
||||||
|
var users []*model.User
|
||||||
|
for i := 0; i < 10; i++ { // need real users
|
||||||
|
u := &model.User{
|
||||||
|
Username: model.NewId(),
|
||||||
|
Email: model.NewId() + "@example.com",
|
||||||
|
LastPictureUpdate: model.GetMillis(),
|
||||||
|
}
|
||||||
|
u, err := ss.User().Save(u)
|
||||||
|
require.NoError(t, err)
|
||||||
|
users = append(users, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := []model.SharedChannelUser{
|
||||||
|
{UserId: users[0].Id, ChannelId: model.NewId(), RemoteId: model.NewId(), LastSyncAt: later},
|
||||||
|
{UserId: users[1].Id, ChannelId: model.NewId(), RemoteId: model.NewId(), LastSyncAt: earlier},
|
||||||
|
{UserId: users[1].Id, ChannelId: model.NewId(), RemoteId: model.NewId(), LastSyncAt: earlier},
|
||||||
|
{UserId: users[1].Id, ChannelId: channelID, RemoteId: remoteID, LastSyncAt: later},
|
||||||
|
{UserId: users[2].Id, ChannelId: channelID, RemoteId: model.NewId(), LastSyncAt: later},
|
||||||
|
{UserId: users[3].Id, ChannelId: channelID, RemoteId: model.NewId(), LastSyncAt: earlier},
|
||||||
|
{UserId: users[4].Id, ChannelId: channelID, RemoteId: model.NewId(), LastSyncAt: later},
|
||||||
|
{UserId: users[5].Id, ChannelId: channelID, RemoteId: remoteID, LastSyncAt: earlier},
|
||||||
|
{UserId: users[6].Id, ChannelId: channelID, RemoteId: remoteID, LastSyncAt: later},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, u := range data {
|
||||||
|
scu := &model.SharedChannelUser{
|
||||||
|
UserId: u.UserId,
|
||||||
|
ChannelId: u.ChannelId,
|
||||||
|
RemoteId: u.RemoteId,
|
||||||
|
LastSyncAt: u.LastSyncAt,
|
||||||
|
}
|
||||||
|
_, err := ss.SharedChannel().SaveUser(scu)
|
||||||
|
require.NoError(t, err, "could not save user #", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Filter by channelId", func(t *testing.T) {
|
||||||
|
filter := model.GetUsersForSyncFilter{
|
||||||
|
CheckProfileImage: false,
|
||||||
|
ChannelID: channelID,
|
||||||
|
}
|
||||||
|
usersFound, err := ss.SharedChannel().GetUsersForSync(filter)
|
||||||
|
require.NoError(t, err, "shouldn't error getting users", err)
|
||||||
|
require.Len(t, usersFound, 2)
|
||||||
|
for _, user := range usersFound {
|
||||||
|
require.Contains(t, []string{users[3].Id, users[5].Id}, user.Id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Filter by channelId for profile image", func(t *testing.T) {
|
||||||
|
filter := model.GetUsersForSyncFilter{
|
||||||
|
CheckProfileImage: true,
|
||||||
|
ChannelID: channelID,
|
||||||
|
}
|
||||||
|
usersFound, err := ss.SharedChannel().GetUsersForSync(filter)
|
||||||
|
require.NoError(t, err, "shouldn't error getting users", err)
|
||||||
|
require.Len(t, usersFound, 2)
|
||||||
|
for _, user := range usersFound {
|
||||||
|
require.Contains(t, []string{users[3].Id, users[5].Id}, user.Id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Filter by channelId with Limit", func(t *testing.T) {
|
||||||
|
filter := model.GetUsersForSyncFilter{
|
||||||
|
CheckProfileImage: true,
|
||||||
|
ChannelID: channelID,
|
||||||
|
Limit: 1,
|
||||||
|
}
|
||||||
|
usersFound, err := ss.SharedChannel().GetUsersForSync(filter)
|
||||||
|
require.NoError(t, err, "shouldn't error getting users", err)
|
||||||
|
require.Len(t, usersFound, 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUpdateSharedChannelUserLastSyncAt(t *testing.T, ss store.Store) {
|
||||||
|
u1 := &model.User{
|
||||||
|
Username: model.NewId(),
|
||||||
|
Email: model.NewId() + "@example.com",
|
||||||
|
LastPictureUpdate: model.GetMillis() - 300000, // 5 mins
|
||||||
|
}
|
||||||
|
u1, err := ss.User().Save(u1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
u2 := &model.User{
|
||||||
|
Username: model.NewId(),
|
||||||
|
Email: model.NewId() + "@example.com",
|
||||||
|
LastPictureUpdate: model.GetMillis() + 300000,
|
||||||
|
}
|
||||||
|
u2, err = ss.User().Save(u2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
channelID := model.NewId()
|
||||||
|
remoteID := model.NewId()
|
||||||
|
|
||||||
|
scUser1 := &model.SharedChannelUser{
|
||||||
|
UserId: u1.Id,
|
||||||
|
RemoteId: remoteID,
|
||||||
|
ChannelId: channelID,
|
||||||
|
}
|
||||||
|
_, err = ss.SharedChannel().SaveUser(scUser1)
|
||||||
require.NoError(t, err, "couldn't save user", err)
|
require.NoError(t, err, "couldn't save user", err)
|
||||||
|
|
||||||
future := model.GetMillis() + 3600000 // 1 hour in the future
|
scUser2 := &model.SharedChannelUser{
|
||||||
|
UserId: u2.Id,
|
||||||
|
RemoteId: remoteID,
|
||||||
|
ChannelId: channelID,
|
||||||
|
}
|
||||||
|
_, err = ss.SharedChannel().SaveUser(scUser2)
|
||||||
|
require.NoError(t, err, "couldn't save user", err)
|
||||||
|
|
||||||
t.Run("Update LastSyncAt for user", func(t *testing.T) {
|
t.Run("Update LastSyncAt for user via UpdateAt", func(t *testing.T) {
|
||||||
err := ss.SharedChannel().UpdateUserLastSyncAt(userSaved.Id, future)
|
err := ss.SharedChannel().UpdateUserLastSyncAt(u1.Id, channelID, remoteID)
|
||||||
require.NoError(t, err, "updateLastSyncAt should not error", err)
|
require.NoError(t, err, "updateLastSyncAt should not error", err)
|
||||||
|
|
||||||
u, err := ss.SharedChannel().GetUser(userSaved.UserId, userSaved.ChannelId, userSaved.RemoteId)
|
scu, err := ss.SharedChannel().GetSingleUser(u1.Id, channelID, remoteID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, future, u.LastSyncAt)
|
require.Equal(t, u1.UpdateAt, scu.LastSyncAt)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Update LastSyncAt for user via LastPictureUpdate", func(t *testing.T) {
|
||||||
|
err := ss.SharedChannel().UpdateUserLastSyncAt(u2.Id, channelID, remoteID)
|
||||||
|
require.NoError(t, err, "updateLastSyncAt should not error", err)
|
||||||
|
|
||||||
|
scu, err := ss.SharedChannel().GetSingleUser(u2.Id, channelID, remoteID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, u2.LastPictureUpdate, scu.LastSyncAt)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Update LastSyncAt for non-existent shared channel user", func(t *testing.T) {
|
t.Run("Update LastSyncAt for non-existent shared channel user", func(t *testing.T) {
|
||||||
err := ss.SharedChannel().UpdateUserLastSyncAt(model.NewId(), future)
|
err := ss.SharedChannel().UpdateUserLastSyncAt(model.NewId(), channelID, remoteID)
|
||||||
require.Error(t, err, "update non-existent user should error", err)
|
require.Error(t, err, "update non-existent user should error", err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4890,10 +4890,10 @@ func (s *TimerLayerPostStore) GetPostsSince(options model.GetPostsSinceOptions,
|
|||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TimerLayerPostStore) GetPostsSinceForSync(options model.GetPostsSinceForSyncOptions, allowFromCache bool) ([]*model.Post, error) {
|
func (s *TimerLayerPostStore) GetPostsSinceForSync(options model.GetPostsSinceForSyncOptions, cursor model.GetPostsSinceForSyncCursor, limit int) ([]*model.Post, model.GetPostsSinceForSyncCursor, error) {
|
||||||
start := timemodule.Now()
|
start := timemodule.Now()
|
||||||
|
|
||||||
result, err := s.PostStore.GetPostsSinceForSync(options, allowFromCache)
|
result, resultVar1, err := s.PostStore.GetPostsSinceForSync(options, cursor, limit)
|
||||||
|
|
||||||
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
||||||
if s.Root.Metrics != nil {
|
if s.Root.Metrics != nil {
|
||||||
@@ -4903,7 +4903,7 @@ func (s *TimerLayerPostStore) GetPostsSinceForSync(options model.GetPostsSinceFo
|
|||||||
}
|
}
|
||||||
s.Root.Metrics.ObserveStoreMethodDuration("PostStore.GetPostsSinceForSync", success, elapsed)
|
s.Root.Metrics.ObserveStoreMethodDuration("PostStore.GetPostsSinceForSync", success, elapsed)
|
||||||
}
|
}
|
||||||
return result, err
|
return result, resultVar1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TimerLayerPostStore) GetRepliesForExport(parentID string) ([]*model.ReplyForExport, error) {
|
func (s *TimerLayerPostStore) GetRepliesForExport(parentID string) ([]*model.ReplyForExport, error) {
|
||||||
@@ -6568,10 +6568,10 @@ func (s *TimerLayerSharedChannelStore) GetRemotesStatus(channelId string) ([]*mo
|
|||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TimerLayerSharedChannelStore) GetUser(userID string, channelID string, remoteID string) (*model.SharedChannelUser, error) {
|
func (s *TimerLayerSharedChannelStore) GetSingleUser(userID string, channelID string, remoteID string) (*model.SharedChannelUser, error) {
|
||||||
start := timemodule.Now()
|
start := timemodule.Now()
|
||||||
|
|
||||||
result, err := s.SharedChannelStore.GetUser(userID, channelID, remoteID)
|
result, err := s.SharedChannelStore.GetSingleUser(userID, channelID, remoteID)
|
||||||
|
|
||||||
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
||||||
if s.Root.Metrics != nil {
|
if s.Root.Metrics != nil {
|
||||||
@@ -6579,7 +6579,39 @@ func (s *TimerLayerSharedChannelStore) GetUser(userID string, channelID string,
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
success = "true"
|
success = "true"
|
||||||
}
|
}
|
||||||
s.Root.Metrics.ObserveStoreMethodDuration("SharedChannelStore.GetUser", success, elapsed)
|
s.Root.Metrics.ObserveStoreMethodDuration("SharedChannelStore.GetSingleUser", success, elapsed)
|
||||||
|
}
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TimerLayerSharedChannelStore) GetUsersForSync(filter model.GetUsersForSyncFilter) ([]*model.User, error) {
|
||||||
|
start := timemodule.Now()
|
||||||
|
|
||||||
|
result, err := s.SharedChannelStore.GetUsersForSync(filter)
|
||||||
|
|
||||||
|
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
||||||
|
if s.Root.Metrics != nil {
|
||||||
|
success := "false"
|
||||||
|
if err == nil {
|
||||||
|
success = "true"
|
||||||
|
}
|
||||||
|
s.Root.Metrics.ObserveStoreMethodDuration("SharedChannelStore.GetUsersForSync", success, elapsed)
|
||||||
|
}
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TimerLayerSharedChannelStore) GetUsersForUser(userID string) ([]*model.SharedChannelUser, error) {
|
||||||
|
start := timemodule.Now()
|
||||||
|
|
||||||
|
result, err := s.SharedChannelStore.GetUsersForUser(userID)
|
||||||
|
|
||||||
|
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
||||||
|
if s.Root.Metrics != nil {
|
||||||
|
success := "false"
|
||||||
|
if err == nil {
|
||||||
|
success = "true"
|
||||||
|
}
|
||||||
|
s.Root.Metrics.ObserveStoreMethodDuration("SharedChannelStore.GetUsersForUser", success, elapsed)
|
||||||
}
|
}
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
@@ -6728,10 +6760,10 @@ func (s *TimerLayerSharedChannelStore) UpdateRemote(remote *model.SharedChannelR
|
|||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TimerLayerSharedChannelStore) UpdateRemoteNextSyncAt(id string, syncTime int64) error {
|
func (s *TimerLayerSharedChannelStore) UpdateRemoteCursor(id string, cursor model.GetPostsSinceForSyncCursor) error {
|
||||||
start := timemodule.Now()
|
start := timemodule.Now()
|
||||||
|
|
||||||
err := s.SharedChannelStore.UpdateRemoteNextSyncAt(id, syncTime)
|
err := s.SharedChannelStore.UpdateRemoteCursor(id, cursor)
|
||||||
|
|
||||||
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
||||||
if s.Root.Metrics != nil {
|
if s.Root.Metrics != nil {
|
||||||
@@ -6739,15 +6771,15 @@ func (s *TimerLayerSharedChannelStore) UpdateRemoteNextSyncAt(id string, syncTim
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
success = "true"
|
success = "true"
|
||||||
}
|
}
|
||||||
s.Root.Metrics.ObserveStoreMethodDuration("SharedChannelStore.UpdateRemoteNextSyncAt", success, elapsed)
|
s.Root.Metrics.ObserveStoreMethodDuration("SharedChannelStore.UpdateRemoteCursor", success, elapsed)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TimerLayerSharedChannelStore) UpdateUserLastSyncAt(id string, syncTime int64) error {
|
func (s *TimerLayerSharedChannelStore) UpdateUserLastSyncAt(userID string, channelID string, remoteID string) error {
|
||||||
start := timemodule.Now()
|
start := timemodule.Now()
|
||||||
|
|
||||||
err := s.SharedChannelStore.UpdateUserLastSyncAt(id, syncTime)
|
err := s.SharedChannelStore.UpdateUserLastSyncAt(userID, channelID, remoteID)
|
||||||
|
|
||||||
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
||||||
if s.Root.Metrics != nil {
|
if s.Root.Metrics != nil {
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user