MM-52600: [Shared Channels] Shared channels do not sync channel membership (#30976)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
0082e3e94d
Коммит
fa1c77d9b0
@@ -1711,6 +1711,13 @@ func (a *App) addUserToChannel(c request.CTX, user *model.User, channel *model.C
|
||||
a.Srv().Platform().InvalidateChannelCacheForUser(user.Id)
|
||||
a.invalidateCacheForChannelMembers(channel.Id)
|
||||
|
||||
// Synchronize membership change for shared channels
|
||||
if channel.IsShared() {
|
||||
if scs := a.Srv().Platform().GetSharedChannelService(); scs != nil {
|
||||
scs.HandleMembershipChange(channel.Id, user.Id, true, user.GetRemoteID())
|
||||
}
|
||||
}
|
||||
|
||||
return newMember, nil
|
||||
}
|
||||
|
||||
@@ -2236,7 +2243,12 @@ func (s *Server) getChannelMemberLastViewedAt(c request.CTX, channelID string, u
|
||||
}
|
||||
|
||||
func (a *App) GetChannelMembersPage(c request.CTX, channelID string, page, perPage int) (model.ChannelMembers, *model.AppError) {
|
||||
channelMembers, err := a.Srv().Store().Channel().GetMembers(channelID, page*perPage, perPage)
|
||||
opts := model.ChannelMembersGetOptions{
|
||||
ChannelID: channelID,
|
||||
Offset: page * perPage,
|
||||
Limit: perPage,
|
||||
}
|
||||
channelMembers, err := a.Srv().Store().Channel().GetMembers(opts)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetChannelMembersPage", "app.channel.get_members.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
@@ -2740,6 +2752,14 @@ func (a *App) removeUserFromChannel(c request.CTX, userIDToRemove string, remove
|
||||
userMsg.Add("remover_id", removerUserId)
|
||||
a.Publish(userMsg)
|
||||
|
||||
// Synchronize membership change for shared channels
|
||||
if channel.IsShared() {
|
||||
// isAdd=false, empty remoteId means locally initiated
|
||||
if scs := a.Srv().Platform().GetSharedChannelService(); scs != nil {
|
||||
scs.HandleMembershipChange(channel.Id, userIDToRemove, false, "")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3639,7 +3659,12 @@ func (a *App) forEachChannelMember(c request.CTX, channelID string, f func(model
|
||||
page := 0
|
||||
|
||||
for {
|
||||
channelMembers, err := a.Srv().Store().Channel().GetMembers(channelID, page*perPage, perPage)
|
||||
opts := model.ChannelMembersGetOptions{
|
||||
ChannelID: channelID,
|
||||
Offset: page * perPage,
|
||||
Limit: perPage,
|
||||
}
|
||||
channelMembers, err := a.Srv().Store().Channel().GetMembers(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2513,8 +2513,16 @@ func TestClearChannelMembersCache(t *testing.T) {
|
||||
ChannelId: "1",
|
||||
})
|
||||
}
|
||||
mockChannelStore.On("GetMembers", "channelID", 0, 100).Return(cms, nil)
|
||||
mockChannelStore.On("GetMembers", "channelID", 100, 100).Return(model.ChannelMembers{
|
||||
mockChannelStore.On("GetMembers", model.ChannelMembersGetOptions{
|
||||
ChannelID: "channelID",
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
}).Return(cms, nil)
|
||||
mockChannelStore.On("GetMembers", model.ChannelMembersGetOptions{
|
||||
ChannelID: "channelID",
|
||||
Offset: 100,
|
||||
Limit: 100,
|
||||
}).Return(model.ChannelMembers{
|
||||
model.ChannelMember{
|
||||
ChannelId: "1",
|
||||
},
|
||||
|
||||
@@ -23,6 +23,7 @@ type SharedChannelServiceIFace interface {
|
||||
CheckChannelNotShared(channelID string) error
|
||||
CheckChannelIsShared(channelID string) error
|
||||
CheckCanInviteToSharedChannel(channelId string) error
|
||||
HandleMembershipChange(channelID, userID string, isAdd bool, remoteID string)
|
||||
}
|
||||
|
||||
type MockOptionSharedChannelService func(service *mockSharedChannelService)
|
||||
@@ -77,3 +78,7 @@ func (mrcs *mockSharedChannelService) SendChannelInvite(channel *model.Channel,
|
||||
func (mrcs *mockSharedChannelService) NumInvitations() int {
|
||||
return mrcs.numInvitations
|
||||
}
|
||||
|
||||
func (mrcs *mockSharedChannelService) HandleMembershipChange(channelID, userID string, isAdd bool, remoteID string) {
|
||||
// This is a mock implementation - it doesn't need to do anything
|
||||
}
|
||||
|
||||
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
@@ -26,6 +26,7 @@ type SharedChannelServiceIFace interface {
|
||||
CheckChannelNotShared(channelID string) error
|
||||
CheckChannelIsShared(channelID string) error
|
||||
CheckCanInviteToSharedChannel(channelId string) error
|
||||
HandleMembershipChange(channelID, userID string, isAdd bool, remoteID string)
|
||||
}
|
||||
|
||||
func NewMockSharedChannelService(service SharedChannelServiceIFace) *mockSharedChannelService {
|
||||
@@ -91,3 +92,9 @@ func (mrcs *mockSharedChannelService) SendChannelInvite(channel *model.Channel,
|
||||
func (mrcs *mockSharedChannelService) NumInvitations() int {
|
||||
return mrcs.numInvitations
|
||||
}
|
||||
|
||||
func (mrcs *mockSharedChannelService) HandleMembershipChange(channelID, userID string, isAdd bool, remoteID string) {
|
||||
if mrcs.SharedChannelServiceIFace != nil {
|
||||
mrcs.SharedChannelServiceIFace.HandleMembershipChange(channelID, userID, isAdd, remoteID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,27 @@ func (h *SelfReferentialSyncHandler) HandleRequest(w http.ResponseWriter, r *htt
|
||||
}
|
||||
}
|
||||
|
||||
// Handle membership sync using unified field
|
||||
if len(syncMsg.MembershipChanges) > 0 {
|
||||
batch := make([]string, 0)
|
||||
for _, change := range syncMsg.MembershipChanges {
|
||||
if change.IsAdd {
|
||||
syncResp.UsersSyncd = append(syncResp.UsersSyncd, change.UserId)
|
||||
batch = append(batch, change.UserId)
|
||||
}
|
||||
}
|
||||
|
||||
// Call appropriate callback
|
||||
if len(batch) > 0 {
|
||||
if h.OnBatchSync != nil {
|
||||
h.OnBatchSync(batch, currentCall)
|
||||
}
|
||||
if len(batch) == 1 && h.OnIndividualSync != nil {
|
||||
h.OnIndividualSync(batch[0], currentCall)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = response.SetPayload(syncResp)
|
||||
}
|
||||
}
|
||||
@@ -135,28 +156,36 @@ func (h *SelfReferentialSyncHandler) GetSyncMessageCount() int32 {
|
||||
return atomic.LoadInt32(h.syncMessageCount)
|
||||
}
|
||||
|
||||
// GetUsersFromSyncMsg extracts user IDs from a sync message
|
||||
func GetUsersFromSyncMsg(msg model.SyncMsg) []string {
|
||||
var userIds []string
|
||||
|
||||
// Extract from users field
|
||||
for userId := range msg.Users {
|
||||
userIds = append(userIds, userId)
|
||||
}
|
||||
|
||||
return userIds
|
||||
}
|
||||
|
||||
// EnsureCleanState ensures a clean test state by removing all shared channels, remote clusters,
|
||||
// and extra team/channel members. This helps prevent state pollution between tests.
|
||||
func EnsureCleanState(t *testing.T, th *TestHelper, ss store.Store) {
|
||||
t.Helper()
|
||||
|
||||
// First, wait for any pending async tasks to complete, then shutdown services
|
||||
scsInterface := th.App.Srv().GetSharedChannelSyncService()
|
||||
if scsInterface != nil && scsInterface.Active() {
|
||||
// Cast to concrete type to access testing methods
|
||||
if service, ok := scsInterface.(*sharedchannel.Service); ok {
|
||||
// Wait for any pending tasks from previous tests to complete
|
||||
require.Eventually(t, func() bool {
|
||||
return !service.HasPendingTasksForTesting()
|
||||
}, 10*time.Second, 100*time.Millisecond, "All pending sync tasks should complete before cleanup")
|
||||
}
|
||||
|
||||
// Shutdown the shared channel service to stop any async operations
|
||||
_ = scsInterface.Shutdown()
|
||||
|
||||
// Wait for shutdown to complete with more time
|
||||
require.Eventually(t, func() bool {
|
||||
return !scsInterface.Active()
|
||||
}, 5*time.Second, 100*time.Millisecond, "Shared channel service should be inactive after shutdown")
|
||||
}
|
||||
|
||||
// Clear all shared channels and remotes from previous tests
|
||||
allSharedChannels, _ := ss.SharedChannel().GetAll(0, 1000, model.SharedChannelFilterOpts{})
|
||||
for _, sc := range allSharedChannels {
|
||||
// Delete all remotes for this channel
|
||||
remotes, _ := ss.SharedChannel().GetRemotes(0, 100, model.SharedChannelRemoteFilterOpts{ChannelId: sc.ChannelId})
|
||||
remotes, _ := ss.SharedChannel().GetRemotes(0, 999999, model.SharedChannelRemoteFilterOpts{ChannelId: sc.ChannelId})
|
||||
for _, remote := range remotes {
|
||||
_, _ = ss.SharedChannel().DeleteRemote(remote.Id)
|
||||
}
|
||||
@@ -170,13 +199,32 @@ func EnsureCleanState(t *testing.T, th *TestHelper, ss store.Store) {
|
||||
_, _ = ss.RemoteCluster().Delete(rc.RemoteId)
|
||||
}
|
||||
|
||||
// Clear all SharedChannelUsers sync state - this is critical for test isolation
|
||||
// The SharedChannelUsers table tracks per-user sync timestamps that can interfere between tests
|
||||
_, _ = th.SQLStore.GetMaster().Exec("DELETE FROM SharedChannelUsers WHERE 1=1")
|
||||
|
||||
// Clear all SharedChannelAttachments sync state
|
||||
_, _ = th.SQLStore.GetMaster().Exec("DELETE FROM SharedChannelAttachments WHERE 1=1")
|
||||
|
||||
// Reset sync cursors in any remaining SharedChannelRemotes (before they get deleted)
|
||||
// This ensures cursors don't persist if deletion fails
|
||||
_, _ = th.SQLStore.GetMaster().Exec(`UPDATE SharedChannelRemotes SET
|
||||
LastPostCreateAt = 0,
|
||||
LastPostCreateId = '',
|
||||
LastPostUpdateAt = 0,
|
||||
LastPostId = '',
|
||||
LastMembersSyncAt = 0
|
||||
WHERE 1=1`)
|
||||
|
||||
// Remove all channel members from test channels (except the basic team/channel setup)
|
||||
channels, _ := ss.Channel().GetAll(th.BasicTeam.Id)
|
||||
for _, channel := range channels {
|
||||
// Skip direct message and group channels, and skip the default channels
|
||||
if channel.Type != model.ChannelTypeDirect && channel.Type != model.ChannelTypeGroup &&
|
||||
channel.Id != th.BasicChannel.Id {
|
||||
members, _ := ss.Channel().GetMembers(channel.Id, 0, 10000)
|
||||
members, _ := ss.Channel().GetMembers(model.ChannelMembersGetOptions{
|
||||
ChannelID: channel.Id,
|
||||
})
|
||||
for _, member := range members {
|
||||
_ = ss.Channel().RemoveMember(th.Context, channel.Id, member.UserId)
|
||||
}
|
||||
@@ -228,12 +276,16 @@ func EnsureCleanState(t *testing.T, th *TestHelper, ss store.Store) {
|
||||
cfg.ConnectedWorkspacesSettings.GlobalUserSyncBatchSize = &defaultBatchSize
|
||||
})
|
||||
|
||||
// Ensure services are running and ready
|
||||
scsInterface := th.App.Srv().GetSharedChannelSyncService()
|
||||
if scs, ok := scsInterface.(*sharedchannel.Service); ok {
|
||||
require.Eventually(t, func() bool {
|
||||
return scs.Active()
|
||||
}, 2*time.Second, 100*time.Millisecond, "Shared channel service should be active")
|
||||
// Restart services and ensure they are running and ready
|
||||
if scsInterface != nil {
|
||||
// Restart the shared channel service
|
||||
_ = scsInterface.Start()
|
||||
|
||||
if scs, ok := scsInterface.(*sharedchannel.Service); ok {
|
||||
require.Eventually(t, func() bool {
|
||||
return scs.Active()
|
||||
}, 5*time.Second, 100*time.Millisecond, "Shared channel service should be active after restart")
|
||||
}
|
||||
}
|
||||
|
||||
rcService := th.App.Srv().GetRemoteClusterService()
|
||||
@@ -243,6 +295,6 @@ func EnsureCleanState(t *testing.T, th *TestHelper, ss store.Store) {
|
||||
}
|
||||
require.Eventually(t, func() bool {
|
||||
return rcService.Active()
|
||||
}, 2*time.Second, 100*time.Millisecond, "Remote cluster service should be active")
|
||||
}, 5*time.Second, 100*time.Millisecond, "Remote cluster service should be active")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ func setupSharedChannels(tb testing.TB) *TestHelper {
|
||||
return SetupConfig(tb, func(cfg *model.Config) {
|
||||
*cfg.ConnectedWorkspacesSettings.EnableRemoteClusterService = true
|
||||
*cfg.ConnectedWorkspacesSettings.EnableSharedChannels = true
|
||||
cfg.FeatureFlags.EnableSharedChannelsMemberSync = true
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -275,6 +275,8 @@ channels/db/migrations/mysql/000138_add_default_category_name_to_channel.down.sq
|
||||
channels/db/migrations/mysql/000138_add_default_category_name_to_channel.up.sql
|
||||
channels/db/migrations/mysql/000139_remoteclusters_add_last_global_user_sync_at.down.sql
|
||||
channels/db/migrations/mysql/000139_remoteclusters_add_last_global_user_sync_at.up.sql
|
||||
channels/db/migrations/mysql/000140_add_lastmemberssyncat_to_sharedchannelremotes.down.sql
|
||||
channels/db/migrations/mysql/000140_add_lastmemberssyncat_to_sharedchannelremotes.up.sql
|
||||
channels/db/migrations/postgres/000001_create_teams.down.sql
|
||||
channels/db/migrations/postgres/000001_create_teams.up.sql
|
||||
channels/db/migrations/postgres/000002_create_team_members.down.sql
|
||||
@@ -551,3 +553,5 @@ channels/db/migrations/postgres/000138_add_default_category_name_to_channel.down
|
||||
channels/db/migrations/postgres/000138_add_default_category_name_to_channel.up.sql
|
||||
channels/db/migrations/postgres/000139_remoteclusters_add_last_global_user_sync_at.down.sql
|
||||
channels/db/migrations/postgres/000139_remoteclusters_add_last_global_user_sync_at.up.sql
|
||||
channels/db/migrations/postgres/000140_add_lastmemberssyncat_to_sharedchannelremotes.down.sql
|
||||
channels/db/migrations/postgres/000140_add_lastmemberssyncat_to_sharedchannelremotes.up.sql
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'SharedChannelRemotes'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'LastMembersSyncAt'
|
||||
) > 0,
|
||||
'ALTER TABLE SharedChannelRemotes DROP COLUMN LastMembersSyncAt;',
|
||||
'SELECT 1'
|
||||
));
|
||||
|
||||
PREPARE alterIfExists FROM @preparedStatement;
|
||||
EXECUTE alterIfExists;
|
||||
DEALLOCATE PREPARE alterIfExists;
|
||||
|
||||
SET @preparedStatement2 = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'SharedChannelUsers'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'LastMembershipSyncAt'
|
||||
) > 0,
|
||||
'ALTER TABLE SharedChannelUsers DROP COLUMN LastMembershipSyncAt;',
|
||||
'SELECT 1'
|
||||
));
|
||||
|
||||
PREPARE alterIfExists2 FROM @preparedStatement2;
|
||||
EXECUTE alterIfExists2;
|
||||
DEALLOCATE PREPARE alterIfExists2;
|
||||
@@ -0,0 +1,29 @@
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'SharedChannelRemotes'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'LastMembersSyncAt'
|
||||
) > 0,
|
||||
'SELECT 1',
|
||||
'ALTER TABLE SharedChannelRemotes ADD LastMembersSyncAt bigint DEFAULT 0;'
|
||||
));
|
||||
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
SET @preparedStatement2 = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'SharedChannelUsers'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'LastMembershipSyncAt'
|
||||
) > 0,
|
||||
'SELECT 1',
|
||||
'ALTER TABLE SharedChannelUsers ADD LastMembershipSyncAt bigint DEFAULT 0;'
|
||||
));
|
||||
|
||||
PREPARE alterIfNotExists2 FROM @preparedStatement2;
|
||||
EXECUTE alterIfNotExists2;
|
||||
DEALLOCATE PREPARE alterIfNotExists2;
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE sharedchannelremotes DROP COLUMN IF EXISTS lastmemberssyncat;
|
||||
ALTER TABLE sharedchannelusers DROP COLUMN IF EXISTS lastmembershipsyncat;
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE sharedchannelremotes ADD COLUMN IF NOT EXISTS lastmemberssyncat bigint DEFAULT 0;
|
||||
ALTER TABLE sharedchannelusers ADD COLUMN IF NOT EXISTS lastmembershipsyncat bigint DEFAULT 0;
|
||||
@@ -2081,11 +2081,11 @@ func (s *RetryLayerChannelStore) GetMemberLastViewedAt(ctx context.Context, chan
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerChannelStore) GetMembers(channelID string, offset int, limit int) (model.ChannelMembers, error) {
|
||||
func (s *RetryLayerChannelStore) GetMembers(opts model.ChannelMembersGetOptions) (model.ChannelMembers, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.ChannelStore.GetMembers(channelID, offset, limit)
|
||||
result, err := s.ChannelStore.GetMembers(opts)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
@@ -11642,6 +11642,27 @@ func (s *RetryLayerSharedChannelStore) GetSingleUser(userID string, channelID st
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerSharedChannelStore) GetUserChanges(userID string, channelID string, afterTime int64) ([]*model.SharedChannelUser, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.SharedChannelStore.GetUserChanges(userID, channelID, afterTime)
|
||||
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
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerSharedChannelStore) GetUsersForSync(filter model.GetUsersForSyncFilter) ([]*model.User, error) {
|
||||
|
||||
tries := 0
|
||||
@@ -11894,6 +11915,48 @@ func (s *RetryLayerSharedChannelStore) UpdateRemoteCursor(id string, cursor mode
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerSharedChannelStore) UpdateRemoteMembershipCursor(id string, syncTime int64) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.SharedChannelStore.UpdateRemoteMembershipCursor(id, syncTime)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerSharedChannelStore) UpdateUserLastMembershipSyncAt(userID string, channelID string, remoteID string, syncTime int64) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.SharedChannelStore.UpdateUserLastMembershipSyncAt(userID, channelID, remoteID, syncTime)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerSharedChannelStore) UpdateUserLastSyncAt(userID string, channelID string, remoteID string) error {
|
||||
|
||||
tries := 0
|
||||
|
||||
@@ -2070,22 +2070,34 @@ func (s SqlChannelStore) PatchMultipleMembersNotifyProps(members []*model.Channe
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) GetMembers(channelID string, offset, limit int) (model.ChannelMembers, error) {
|
||||
sql, args, err := s.channelMembersForTeamWithSchemeSelectQuery.
|
||||
func (s SqlChannelStore) GetMembers(opts model.ChannelMembersGetOptions) (model.ChannelMembers, error) {
|
||||
query := s.channelMembersForTeamWithSchemeSelectQuery.
|
||||
Where(sq.Eq{
|
||||
"ChannelId": channelID,
|
||||
}).
|
||||
Limit(uint64(limit)).
|
||||
Offset(uint64(offset)).
|
||||
ToSql()
|
||||
"ChannelId": opts.ChannelID,
|
||||
})
|
||||
|
||||
if opts.UpdatedAfter > 0 {
|
||||
query = query.Where(sq.Gt{"ChannelMembers.LastUpdateAt": opts.UpdatedAfter})
|
||||
query = query.OrderBy("ChannelMembers.LastUpdateAt")
|
||||
}
|
||||
|
||||
if opts.Limit > 0 {
|
||||
query = query.Limit(uint64(opts.Limit))
|
||||
}
|
||||
|
||||
if opts.Offset > 0 {
|
||||
query = query.Offset(uint64(opts.Offset))
|
||||
}
|
||||
|
||||
sql, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "GetMember_ToSql ChannelID=%s", channelID)
|
||||
return nil, errors.Wrapf(err, "GetMember_ToSql ChannelID=%s", opts.ChannelID)
|
||||
}
|
||||
|
||||
dbMembers := channelMemberWithSchemeRolesList{}
|
||||
err = s.GetReplica().Select(&dbMembers, sql, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get ChannelMembers with channelId=%s", channelID)
|
||||
return nil, errors.Wrapf(err, "failed to get ChannelMembers with channelId=%s", opts.ChannelID)
|
||||
}
|
||||
|
||||
return dbMembers.ToModel(), nil
|
||||
|
||||
@@ -417,6 +417,7 @@ func sharedChannelRemoteFields(prefix string) []string {
|
||||
"COALESCE(" + prefix + "LastPostCreateID,'') AS LastPostCreateID",
|
||||
prefix + "LastPostUpdateAt",
|
||||
"COALESCE(" + prefix + "LastPostId,'') AS LastPostUpdateID",
|
||||
prefix + "LastMembersSyncAt",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -708,6 +709,7 @@ func sharedChannelUserFields(prefix string) []string {
|
||||
prefix + "RemoteId",
|
||||
prefix + "CreateAt",
|
||||
prefix + "LastSyncAt",
|
||||
prefix + "LastMembershipSyncAt",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -720,7 +722,7 @@ func (s SqlSharedChannelStore) SaveUser(scUser *model.SharedChannelUser) (*model
|
||||
|
||||
query, args, err := s.getQueryBuilder().Insert("SharedChannelUsers").
|
||||
Columns(sharedChannelUserFields("")...).
|
||||
Values(scUser.Id, scUser.UserId, scUser.ChannelId, scUser.RemoteId, scUser.CreateAt, scUser.LastSyncAt).
|
||||
Values(scUser.Id, scUser.UserId, scUser.ChannelId, scUser.RemoteId, scUser.CreateAt, scUser.LastSyncAt, scUser.LastMembershipSyncAt).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "savesharedchanneluser_tosql")
|
||||
@@ -853,6 +855,25 @@ func (s SqlSharedChannelStore) UpdateUserLastSyncAt(userID string, channelID str
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateUserLastMembershipSyncAt updates the LastMembershipSyncAt timestamp for the specified SharedChannelUser using the provided sync time.
|
||||
func (s SqlSharedChannelStore) UpdateUserLastMembershipSyncAt(userID string, channelID string, remoteID string, syncTime int64) error {
|
||||
query := s.getQueryBuilder().
|
||||
Update("SharedChannelUsers AS scu").
|
||||
Set("LastMembershipSyncAt", sq.Expr("GREATEST(scu.LastMembershipSyncAt, ?)", syncTime)).
|
||||
Where(sq.Eq{
|
||||
"scu.UserId": userID,
|
||||
"scu.ChannelId": channelID,
|
||||
"scu.RemoteId": remoteID,
|
||||
})
|
||||
|
||||
_, err := s.GetMaster().ExecBuilder(query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update LastMembershipSyncAt for SharedChannelUser with userId=%s, channelId=%s, remoteId=%s: %w",
|
||||
userID, channelID, remoteID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sharedChannelAttachementFields(prefix string) []string {
|
||||
if prefix != "" && !strings.HasSuffix(prefix, ".") {
|
||||
prefix = prefix + "."
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
sq "github.com/mattermost/squirrel"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// UpdateRemoteMembershipCursor updates the LastMembersSyncAt timestamp for the specified SharedChannelRemote,
|
||||
// but only if the new timestamp is greater than the current value.
|
||||
func (s SqlSharedChannelStore) UpdateRemoteMembershipCursor(id string, syncTime int64) error {
|
||||
query := s.getQueryBuilder().
|
||||
Update("SharedChannelRemotes")
|
||||
|
||||
query = query.Set("LastMembersSyncAt", sq.Expr("GREATEST(LastMembersSyncAt, ?)", syncTime))
|
||||
|
||||
query = query.Where(sq.Eq{"Id": id})
|
||||
|
||||
result, err := s.GetMaster().ExecBuilder(query)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to update membership cursor for SharedChannelRemote")
|
||||
}
|
||||
|
||||
count, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to determine rows affected")
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
return fmt.Errorf("id not found: %s", id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserChanges gets all SharedChannelUser changes for a given user, channel after a specific time.
|
||||
// This is used to detect if there are conflicting membership changes.
|
||||
func (s SqlSharedChannelStore) GetUserChanges(userID string, channelID string, afterTime int64) ([]*model.SharedChannelUser, error) {
|
||||
squery, args, err := s.getQueryBuilder().
|
||||
Select(sharedChannelUserFields("")...).
|
||||
From("SharedChannelUsers").
|
||||
Where(sq.Eq{"SharedChannelUsers.UserId": userID}).
|
||||
Where(sq.Eq{"SharedChannelUsers.ChannelId": channelID}).
|
||||
Where(sq.Gt{"SharedChannelUsers.LastSyncAt": afterTime}).
|
||||
ToSql()
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "getsharedchanneluserchanges_tosql")
|
||||
}
|
||||
|
||||
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 changes with UserId=%s, ChannelId=%s, afterTime=%d",
|
||||
userID, channelID, afterTime)
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
@@ -232,7 +232,7 @@ type ChannelStore interface {
|
||||
// It replaces existing fields and creates new ones which don't exist.
|
||||
UpdateMemberNotifyProps(channelID, userID string, props map[string]string) (*model.ChannelMember, error)
|
||||
PatchMultipleMembersNotifyProps(members []*model.ChannelMemberIdentifier, notifyProps map[string]string) ([]*model.ChannelMember, error)
|
||||
GetMembers(channelID string, offset, limit int) (model.ChannelMembers, error)
|
||||
GetMembers(opts model.ChannelMembersGetOptions) (model.ChannelMembers, error)
|
||||
GetMember(ctx context.Context, channelID string, userID string) (*model.ChannelMember, error)
|
||||
GetMemberLastViewedAt(ctx context.Context, channelID string, userID string) (int64, error)
|
||||
GetChannelMembersTimezones(channelID string) ([]model.StringMap, error)
|
||||
@@ -1014,6 +1014,7 @@ type SharedChannelStore interface {
|
||||
GetRemoteByIds(channelID string, remoteID string) (*model.SharedChannelRemote, error)
|
||||
GetRemotes(offset, limit int, opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error)
|
||||
UpdateRemoteCursor(id string, cursor model.GetPostsSinceForSyncCursor) error
|
||||
UpdateRemoteMembershipCursor(id string, syncTime int64) error
|
||||
DeleteRemote(remoteID string) (bool, error)
|
||||
GetRemotesStatus(channelID string) ([]*model.SharedChannelRemoteStatus, error)
|
||||
|
||||
@@ -1021,7 +1022,9 @@ type SharedChannelStore interface {
|
||||
GetSingleUser(userID string, channelID string, remoteID string) (*model.SharedChannelUser, error)
|
||||
GetUsersForUser(userID string) ([]*model.SharedChannelUser, error)
|
||||
GetUsersForSync(filter model.GetUsersForSyncFilter) ([]*model.User, error)
|
||||
GetUserChanges(userID string, channelID string, afterTime int64) ([]*model.SharedChannelUser, error)
|
||||
UpdateUserLastSyncAt(userID string, channelID string, remoteID string) error
|
||||
UpdateUserLastMembershipSyncAt(userID string, channelID string, remoteID string, syncTime int64) error
|
||||
|
||||
SaveAttachment(remote *model.SharedChannelAttachment) (*model.SharedChannelAttachment, error)
|
||||
UpsertAttachment(remote *model.SharedChannelAttachment) (string, error)
|
||||
|
||||
@@ -73,6 +73,7 @@ func TestChannelStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore
|
||||
t.Run("Save", func(t *testing.T) { testChannelStoreSave(t, rctx, ss) })
|
||||
t.Run("SaveDirectChannel", func(t *testing.T) { testChannelStoreSaveDirectChannel(t, rctx, ss, s) })
|
||||
t.Run("CreateDirectChannel", func(t *testing.T) { testChannelStoreCreateDirectChannel(t, rctx, ss) })
|
||||
t.Run("GetMembersWithCursorPagination", func(t *testing.T) { testChannelStoreGetMembersWithCursorPagination(t, rctx, ss) })
|
||||
t.Run("Update", func(t *testing.T) { testChannelStoreUpdate(t, rctx, ss) })
|
||||
t.Run("GetChannelUnread", func(t *testing.T) { testGetChannelUnread(t, rctx, ss) })
|
||||
t.Run("Get", func(t *testing.T) { testChannelStoreGet(t, rctx, ss, s) })
|
||||
@@ -265,7 +266,7 @@ func testChannelStoreSaveDirectChannel(t *testing.T, rctx request.CTX, ss store.
|
||||
_, nErr = ss.Channel().SaveDirectChannel(rctx, &o1, &m1, &m2)
|
||||
require.NoError(t, nErr, "couldn't save direct channel", nErr)
|
||||
|
||||
members, nErr := ss.Channel().GetMembers(o1.Id, 0, 100)
|
||||
members, nErr := ss.Channel().GetMembers(model.ChannelMembersGetOptions{ChannelID: o1.Id, Offset: 0, Limit: 100})
|
||||
require.NoError(t, nErr)
|
||||
require.Len(t, members, 2, "should have saved 2 members")
|
||||
|
||||
@@ -305,7 +306,7 @@ func testChannelStoreSaveDirectChannel(t *testing.T, rctx request.CTX, ss store.
|
||||
_, nErr = ss.Channel().SaveDirectChannel(rctx, &o1, &m1, &m1)
|
||||
require.NoError(t, nErr, "couldn't save direct channel", nErr)
|
||||
|
||||
members, nErr = ss.Channel().GetMembers(o1.Id, 0, 100)
|
||||
members, nErr = ss.Channel().GetMembers(model.ChannelMembersGetOptions{ChannelID: o1.Id, Offset: 0, Limit: 100})
|
||||
require.NoError(t, nErr)
|
||||
require.Len(t, members, 1, "should have saved just 1 member")
|
||||
|
||||
@@ -341,11 +342,72 @@ func testChannelStoreCreateDirectChannel(t *testing.T, rctx request.CTX, ss stor
|
||||
ss.Channel().PermanentDelete(rctx, c1.Id)
|
||||
}()
|
||||
|
||||
members, nErr := ss.Channel().GetMembers(c1.Id, 0, 100)
|
||||
members, nErr := ss.Channel().GetMembers(model.ChannelMembersGetOptions{ChannelID: c1.Id, Offset: 0, Limit: 100})
|
||||
require.NoError(t, nErr)
|
||||
require.Len(t, members, 2, "should have saved 2 members")
|
||||
}
|
||||
|
||||
// testChannelStoreGetMembersWithCursorPagination tests the cursor-based pagination functionality
|
||||
// of the GetMembers method, using the UpdatedAfter parameter to return only members that were
|
||||
// updated after a specific timestamp.
|
||||
func testChannelStoreGetMembersWithCursorPagination(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
// Create two users
|
||||
u1 := &model.User{}
|
||||
u1.Email = MakeEmail()
|
||||
u1.Nickname = model.NewId()
|
||||
_, err := ss.User().Save(rctx, u1)
|
||||
require.NoError(t, err)
|
||||
_, nErr := ss.Team().SaveMember(rctx, &model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}, -1)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
u2 := &model.User{}
|
||||
u2.Email = MakeEmail()
|
||||
u2.Nickname = model.NewId()
|
||||
_, err = ss.User().Save(rctx, u2)
|
||||
require.NoError(t, err)
|
||||
_, nErr = ss.Team().SaveMember(rctx, &model.TeamMember{TeamId: model.NewId(), UserId: u2.Id}, -1)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
// Create direct channel between the users
|
||||
c1, nErr := ss.Channel().CreateDirectChannel(rctx, u1, u2)
|
||||
require.NoError(t, nErr, "couldn't create direct channel", nErr)
|
||||
defer func() {
|
||||
ss.Channel().PermanentDeleteMembersByChannel(rctx, c1.Id)
|
||||
ss.Channel().PermanentDelete(rctx, c1.Id)
|
||||
}()
|
||||
|
||||
// First get all members
|
||||
members, nErr := ss.Channel().GetMembers(model.ChannelMembersGetOptions{ChannelID: c1.Id, Offset: 0, Limit: 100})
|
||||
require.NoError(t, nErr)
|
||||
require.Len(t, members, 2, "should have saved 2 members")
|
||||
|
||||
// Ensure members have different LastUpdateAt values by updating one of them after a short delay
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
member := members[0]
|
||||
_, err = ss.Channel().UpdateMember(rctx, &member)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Get members again after the update
|
||||
members, nErr = ss.Channel().GetMembers(model.ChannelMembersGetOptions{ChannelID: c1.Id, Offset: 0, Limit: 100})
|
||||
require.NoError(t, nErr)
|
||||
require.Len(t, members, 2, "should have 2 members")
|
||||
|
||||
// Find member with smaller LastUpdateAt
|
||||
sort.Slice(members, func(i, j int) bool {
|
||||
return members[i].LastUpdateAt < members[j].LastUpdateAt
|
||||
})
|
||||
updateTime := members[0].LastUpdateAt
|
||||
|
||||
// Test cursor-based pagination by querying for members updated after that timestamp
|
||||
membersAfter, nErr := ss.Channel().GetMembers(model.ChannelMembersGetOptions{
|
||||
ChannelID: c1.Id,
|
||||
UpdatedAfter: updateTime,
|
||||
Limit: 100,
|
||||
})
|
||||
require.NoError(t, nErr)
|
||||
require.Len(t, membersAfter, 1, "should have found only 1 member created after the timestamp")
|
||||
}
|
||||
|
||||
func testChannelStoreUpdate(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
o1 := model.Channel{}
|
||||
o1.TeamId = model.NewId()
|
||||
@@ -7867,7 +7929,7 @@ func testChannelStoreRemoveAllDeactivatedMembers(t *testing.T, rctx request.CTX,
|
||||
require.NoError(t, err)
|
||||
|
||||
// Get all the channel members. Check there are 3.
|
||||
d1, err := ss.Channel().GetMembers(c1.Id, 0, 1000)
|
||||
d1, err := ss.Channel().GetMembers(model.ChannelMembersGetOptions{ChannelID: c1.Id, Offset: 0, Limit: 1000})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, d1, 3)
|
||||
|
||||
@@ -7887,7 +7949,7 @@ func testChannelStoreRemoveAllDeactivatedMembers(t *testing.T, rctx request.CTX,
|
||||
assert.NoError(t, ss.Channel().RemoveAllDeactivatedMembers(rctx, c1.Id))
|
||||
|
||||
// Get all the channel members. Check there is now only 1: m3.
|
||||
d2, err := ss.Channel().GetMembers(c1.Id, 0, 1000)
|
||||
d2, err := ss.Channel().GetMembers(model.ChannelMembersGetOptions{ChannelID: c1.Id, Offset: 0, Limit: 1000})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, d2, 1)
|
||||
assert.Equal(t, u3.Id, d2[0].UserId)
|
||||
|
||||
@@ -5340,7 +5340,7 @@ func groupTestpUpdateMembersRoleChannel(t *testing.T, rctx request.CTX, ss store
|
||||
}
|
||||
assert.ElementsMatch(t, tt.expectedUpdatedUsers, updatedUserIDs)
|
||||
|
||||
members, err := ss.Channel().GetMembers(channel.Id, 0, 100)
|
||||
members, err := ss.Channel().GetMembers(model.ChannelMembersGetOptions{ChannelID: channel.Id, Offset: 0, Limit: 100})
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, len(members), 4) // sanity check for channel membership
|
||||
|
||||
|
||||
@@ -1580,9 +1580,9 @@ func (_m *ChannelStore) GetMemberLastViewedAt(ctx context.Context, channelID str
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetMembers provides a mock function with given fields: channelID, offset, limit
|
||||
func (_m *ChannelStore) GetMembers(channelID string, offset int, limit int) (model.ChannelMembers, error) {
|
||||
ret := _m.Called(channelID, offset, limit)
|
||||
// GetMembers provides a mock function with given fields: opts
|
||||
func (_m *ChannelStore) GetMembers(opts model.ChannelMembersGetOptions) (model.ChannelMembers, error) {
|
||||
ret := _m.Called(opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetMembers")
|
||||
@@ -1590,19 +1590,19 @@ func (_m *ChannelStore) GetMembers(channelID string, offset int, limit int) (mod
|
||||
|
||||
var r0 model.ChannelMembers
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string, int, int) (model.ChannelMembers, error)); ok {
|
||||
return rf(channelID, offset, limit)
|
||||
if rf, ok := ret.Get(0).(func(model.ChannelMembersGetOptions) (model.ChannelMembers, error)); ok {
|
||||
return rf(opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, int, int) model.ChannelMembers); ok {
|
||||
r0 = rf(channelID, offset, limit)
|
||||
if rf, ok := ret.Get(0).(func(model.ChannelMembersGetOptions) model.ChannelMembers); ok {
|
||||
r0 = rf(opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(model.ChannelMembers)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, int, int) error); ok {
|
||||
r1 = rf(channelID, offset, limit)
|
||||
if rf, ok := ret.Get(1).(func(model.ChannelMembersGetOptions) error); ok {
|
||||
r1 = rf(opts)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
@@ -368,6 +368,36 @@ func (_m *SharedChannelStore) GetSingleUser(userID string, channelID string, rem
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetUserChanges provides a mock function with given fields: userID, channelID, afterTime
|
||||
func (_m *SharedChannelStore) GetUserChanges(userID string, channelID string, afterTime int64) ([]*model.SharedChannelUser, error) {
|
||||
ret := _m.Called(userID, channelID, afterTime)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetUserChanges")
|
||||
}
|
||||
|
||||
var r0 []*model.SharedChannelUser
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, int64) ([]*model.SharedChannelUser, error)); ok {
|
||||
return rf(userID, channelID, afterTime)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, string, int64) []*model.SharedChannelUser); ok {
|
||||
r0 = rf(userID, channelID, afterTime)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.SharedChannelUser)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, string, int64) error); ok {
|
||||
r1 = rf(userID, channelID, afterTime)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -700,6 +730,42 @@ func (_m *SharedChannelStore) UpdateRemoteCursor(id string, cursor model.GetPost
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdateRemoteMembershipCursor provides a mock function with given fields: id, syncTime
|
||||
func (_m *SharedChannelStore) UpdateRemoteMembershipCursor(id string, syncTime int64) error {
|
||||
ret := _m.Called(id, syncTime)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateRemoteMembershipCursor")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
|
||||
r0 = rf(id, syncTime)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdateUserLastMembershipSyncAt provides a mock function with given fields: userID, channelID, remoteID, syncTime
|
||||
func (_m *SharedChannelStore) UpdateUserLastMembershipSyncAt(userID string, channelID string, remoteID string, syncTime int64) error {
|
||||
ret := _m.Called(userID, channelID, remoteID, syncTime)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateUserLastMembershipSyncAt")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, int64) error); ok {
|
||||
r0 = rf(userID, channelID, remoteID, syncTime)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdateUserLastSyncAt provides a mock function with given fields: userID, channelID, remoteID
|
||||
func (_m *SharedChannelStore) UpdateUserLastSyncAt(userID string, channelID string, remoteID string) error {
|
||||
ret := _m.Called(userID, channelID, remoteID)
|
||||
|
||||
@@ -1729,10 +1729,10 @@ func (s *TimerLayerChannelStore) GetMemberLastViewedAt(ctx context.Context, chan
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerChannelStore) GetMembers(channelID string, offset int, limit int) (model.ChannelMembers, error) {
|
||||
func (s *TimerLayerChannelStore) GetMembers(opts model.ChannelMembersGetOptions) (model.ChannelMembers, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.ChannelStore.GetMembers(channelID, offset, limit)
|
||||
result, err := s.ChannelStore.GetMembers(opts)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
@@ -9173,6 +9173,22 @@ func (s *TimerLayerSharedChannelStore) GetSingleUser(userID string, channelID st
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerSharedChannelStore) GetUserChanges(userID string, channelID string, afterTime int64) ([]*model.SharedChannelUser, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.SharedChannelStore.GetUserChanges(userID, channelID, afterTime)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("SharedChannelStore.GetUserChanges", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerSharedChannelStore) GetUsersForSync(filter model.GetUsersForSyncFilter) ([]*model.User, error) {
|
||||
start := time.Now()
|
||||
|
||||
@@ -9365,6 +9381,38 @@ func (s *TimerLayerSharedChannelStore) UpdateRemoteCursor(id string, cursor mode
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerSharedChannelStore) UpdateRemoteMembershipCursor(id string, syncTime int64) error {
|
||||
start := time.Now()
|
||||
|
||||
err := s.SharedChannelStore.UpdateRemoteMembershipCursor(id, syncTime)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("SharedChannelStore.UpdateRemoteMembershipCursor", success, elapsed)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerSharedChannelStore) UpdateUserLastMembershipSyncAt(userID string, channelID string, remoteID string, syncTime int64) error {
|
||||
start := time.Now()
|
||||
|
||||
err := s.SharedChannelStore.UpdateUserLastMembershipSyncAt(userID, channelID, remoteID, syncTime)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("SharedChannelStore.UpdateUserLastMembershipSyncAt", success, elapsed)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerSharedChannelStore) UpdateUserLastSyncAt(userID string, channelID string, remoteID string) error {
|
||||
start := time.Now()
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user