mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Shared channels auto-share DM & group messages (#26097)
* option for auto inviting plugin to all shared channels. * auto-invite remotes to shared channels when flag set
This commit is contained in:
parent
7e419e98ee
commit
7b7bcff821
@ -157,6 +157,8 @@ func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, ent
|
|||||||
th.App.SetSearchEngine(searchEngine)
|
th.App.SetSearchEngine(searchEngine)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
th.App.Srv().SetLicense(getLicense(enterprise, memoryConfig))
|
||||||
|
|
||||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||||
*cfg.TeamSettings.MaxUsersPerTeam = 50
|
*cfg.TeamSettings.MaxUsersPerTeam = 50
|
||||||
*cfg.RateLimitSettings.Enable = false
|
*cfg.RateLimitSettings.Enable = false
|
||||||
@ -186,12 +188,6 @@ func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, ent
|
|||||||
web.New(th.App.Srv())
|
web.New(th.App.Srv())
|
||||||
wsapi.Init(th.App.Srv())
|
wsapi.Init(th.App.Srv())
|
||||||
|
|
||||||
if enterprise {
|
|
||||||
th.App.Srv().SetLicense(model.NewTestLicense())
|
|
||||||
} else {
|
|
||||||
th.App.Srv().SetLicense(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
th.Client = th.CreateClient()
|
th.Client = th.CreateClient()
|
||||||
th.SystemAdminClient = th.CreateClient()
|
th.SystemAdminClient = th.CreateClient()
|
||||||
th.SystemManagerClient = th.CreateClient()
|
th.SystemManagerClient = th.CreateClient()
|
||||||
@ -215,6 +211,16 @@ func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, ent
|
|||||||
return th
|
return th
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getLicense(enterprise bool, cfg *model.Config) *model.License {
|
||||||
|
if *cfg.ExperimentalSettings.EnableRemoteClusterService || *cfg.ExperimentalSettings.EnableSharedChannels {
|
||||||
|
return model.NewTestLicenseSKU(model.LicenseShortSkuProfessional)
|
||||||
|
}
|
||||||
|
if enterprise {
|
||||||
|
return model.NewTestLicense()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func SetupEnterprise(tb testing.TB, options ...app.Option) *TestHelper {
|
func SetupEnterprise(tb testing.TB, options ...app.Option) *TestHelper {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
tb.SkipNow()
|
tb.SkipNow()
|
||||||
@ -285,6 +291,7 @@ func SetupConfig(tb testing.TB, updateConfig func(cfg *model.Config)) *TestHelpe
|
|||||||
dbStore := mainHelper.GetStore()
|
dbStore := mainHelper.GetStore()
|
||||||
dbStore.DropAllTables()
|
dbStore.DropAllTables()
|
||||||
dbStore.MarkSystemRanUnitTests()
|
dbStore.MarkSystemRanUnitTests()
|
||||||
|
mainHelper.PreloadMigrations()
|
||||||
searchEngine := mainHelper.GetSearchEngine()
|
searchEngine := mainHelper.GetSearchEngine()
|
||||||
th := setupTestHelper(dbStore, searchEngine, false, true, updateConfig, nil)
|
th := setupTestHelper(dbStore, searchEngine, false, true, updateConfig, nil)
|
||||||
th.InitLogin()
|
th.InitLogin()
|
||||||
|
@ -15,23 +15,26 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost/server/public/model"
|
"github.com/mattermost/mattermost/server/public/model"
|
||||||
"github.com/mattermost/mattermost/server/v8/channels/app"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
|
rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func setupForSharedChannels(tb testing.TB) *TestHelper {
|
||||||
|
return SetupConfig(tb, func(cfg *model.Config) {
|
||||||
|
*cfg.ExperimentalSettings.EnableRemoteClusterService = true
|
||||||
|
*cfg.ExperimentalSettings.EnableSharedChannels = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetAllSharedChannels(t *testing.T) {
|
func TestGetAllSharedChannels(t *testing.T) {
|
||||||
th := Setup(t).InitBasic()
|
th := setupForSharedChannels(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
const pages = 3
|
const pages = 3
|
||||||
const pageSize = 7
|
const pageSize = 7
|
||||||
|
|
||||||
mockService := app.NewMockRemoteClusterService(nil, app.MockOptionRemoteClusterServiceWithActive(true))
|
|
||||||
th.App.Srv().SetRemoteClusterService(mockService)
|
|
||||||
|
|
||||||
savedIds := make([]string, 0, pages*pageSize)
|
savedIds := make([]string, 0, pages*pageSize)
|
||||||
|
|
||||||
// make some shared channels
|
// make some shared channels
|
||||||
@ -45,6 +48,7 @@ func TestGetAllSharedChannels(t *testing.T) {
|
|||||||
CreatorId: th.BasicChannel.CreatorId,
|
CreatorId: th.BasicChannel.CreatorId,
|
||||||
RemoteId: model.NewId(),
|
RemoteId: model.NewId(),
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := th.App.ShareChannel(th.Context, sc)
|
_, err := th.App.ShareChannel(th.Context, sc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
savedIds = append(savedIds, channel.Id)
|
savedIds = append(savedIds, channel.Id)
|
||||||
@ -96,12 +100,9 @@ func randomBool() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetRemoteClusterById(t *testing.T) {
|
func TestGetRemoteClusterById(t *testing.T) {
|
||||||
th := Setup(t).InitBasic()
|
th := setupForSharedChannels(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
mockService := app.NewMockRemoteClusterService(nil, app.MockOptionRemoteClusterServiceWithActive(true))
|
|
||||||
th.App.Srv().SetRemoteClusterService(mockService)
|
|
||||||
|
|
||||||
// for this test we need a user that belongs to a channel that
|
// for this test we need a user that belongs to a channel that
|
||||||
// is shared with the requested remote id.
|
// is shared with the requested remote id.
|
||||||
|
|
||||||
@ -155,7 +156,7 @@ func TestGetRemoteClusterById(t *testing.T) {
|
|||||||
|
|
||||||
func TestCreateDirectChannelWithRemoteUser(t *testing.T) {
|
func TestCreateDirectChannelWithRemoteUser(t *testing.T) {
|
||||||
t.Run("creates a local DM channel that is shared", func(t *testing.T) {
|
t.Run("creates a local DM channel that is shared", func(t *testing.T) {
|
||||||
th := Setup(t).InitBasic()
|
th := setupForSharedChannels(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
client := th.Client
|
client := th.Client
|
||||||
defer client.Logout(context.Background())
|
defer client.Logout(context.Background())
|
||||||
@ -175,16 +176,14 @@ func TestCreateDirectChannelWithRemoteUser(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("sends a shared channel invitation to the remote", func(t *testing.T) {
|
t.Run("sends a shared channel invitation to the remote", func(t *testing.T) {
|
||||||
th := Setup(t).InitBasic()
|
th := setupForSharedChannels(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
client := th.Client
|
client := th.Client
|
||||||
defer client.Logout(context.Background())
|
defer client.Logout(context.Background())
|
||||||
|
|
||||||
mockService := app.NewMockSharedChannelService(nil, app.MockOptionSharedChannelServiceWithActive(true))
|
|
||||||
th.App.Srv().SetSharedChannelSyncService(mockService)
|
|
||||||
|
|
||||||
localUser := th.BasicUser
|
localUser := th.BasicUser
|
||||||
remoteUser := th.CreateUser()
|
remoteUser := th.CreateUser()
|
||||||
|
|
||||||
rc := &model.RemoteCluster{
|
rc := &model.RemoteCluster{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Token: model.NewId(),
|
Token: model.NewId(),
|
||||||
@ -203,21 +202,17 @@ func TestCreateDirectChannelWithRemoteUser(t *testing.T) {
|
|||||||
channelName := model.GetDMNameFromIds(localUser.Id, remoteUser.Id)
|
channelName := model.GetDMNameFromIds(localUser.Id, remoteUser.Id)
|
||||||
require.Equal(t, channelName, dm.Name, "dm name didn't match")
|
require.Equal(t, channelName, dm.Name, "dm name didn't match")
|
||||||
require.True(t, dm.IsShared())
|
require.True(t, dm.IsShared())
|
||||||
|
|
||||||
assert.Equal(t, 1, mockService.NumInvitations())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("does not send a shared channel invitation to the remote when creator is remote", func(t *testing.T) {
|
t.Run("does not send a shared channel invitation to the remote when creator is remote", func(t *testing.T) {
|
||||||
th := Setup(t).InitBasic()
|
th := setupForSharedChannels(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
client := th.Client
|
client := th.Client
|
||||||
defer client.Logout(context.Background())
|
defer client.Logout(context.Background())
|
||||||
|
|
||||||
mockService := app.NewMockSharedChannelService(nil, app.MockOptionSharedChannelServiceWithActive(true))
|
|
||||||
th.App.Srv().SetSharedChannelSyncService(mockService)
|
|
||||||
|
|
||||||
localUser := th.BasicUser
|
localUser := th.BasicUser
|
||||||
remoteUser := th.CreateUser()
|
remoteUser := th.CreateUser()
|
||||||
|
|
||||||
rc := &model.RemoteCluster{
|
rc := &model.RemoteCluster{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Token: model.NewId(),
|
Token: model.NewId(),
|
||||||
@ -236,7 +231,5 @@ func TestCreateDirectChannelWithRemoteUser(t *testing.T) {
|
|||||||
channelName := model.GetDMNameFromIds(localUser.Id, remoteUser.Id)
|
channelName := model.GetDMNameFromIds(localUser.Id, remoteUser.Id)
|
||||||
require.Equal(t, channelName, dm.Name, "dm name didn't match")
|
require.Equal(t, channelName, dm.Name, "dm name didn't match")
|
||||||
require.True(t, dm.IsShared())
|
require.True(t, dm.IsShared())
|
||||||
|
|
||||||
assert.Zero(t, mockService.NumInvitations())
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -913,7 +913,7 @@ type AppIface interface {
|
|||||||
InviteGuestsToChannelsGracefully(teamID string, guestsInvite *model.GuestsInvite, senderId string) ([]*model.EmailInviteWithError, *model.AppError)
|
InviteGuestsToChannelsGracefully(teamID string, guestsInvite *model.GuestsInvite, senderId string) ([]*model.EmailInviteWithError, *model.AppError)
|
||||||
InviteNewUsersToTeam(emailList []string, teamID, senderId string) *model.AppError
|
InviteNewUsersToTeam(emailList []string, teamID, senderId string) *model.AppError
|
||||||
InviteNewUsersToTeamGracefully(memberInvite *model.MemberInvite, teamID, senderId string, reminderInterval string) ([]*model.EmailInviteWithError, *model.AppError)
|
InviteNewUsersToTeamGracefully(memberInvite *model.MemberInvite, teamID, senderId string, reminderInterval string) ([]*model.EmailInviteWithError, *model.AppError)
|
||||||
InviteRemoteToChannel(channelID, remoteID, userID string) error
|
InviteRemoteToChannel(channelID, remoteID, userID string, shareIfNotShared bool) error
|
||||||
IsCRTEnabledForUser(c request.CTX, userID string) bool
|
IsCRTEnabledForUser(c request.CTX, userID string) bool
|
||||||
IsConfigReadOnly() bool
|
IsConfigReadOnly() bool
|
||||||
IsFirstUserAccount() bool
|
IsFirstUserAccount() bool
|
||||||
|
@ -50,7 +50,8 @@ type TestHelper struct {
|
|||||||
tempWorkspace string
|
tempWorkspace string
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer bool, options []Option, tb testing.TB) *TestHelper {
|
func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer bool,
|
||||||
|
updateConfig func(*model.Config), options []Option, tb testing.TB) *TestHelper {
|
||||||
tempWorkspace, err := os.MkdirTemp("", "apptest")
|
tempWorkspace, err := os.MkdirTemp("", "apptest")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -66,6 +67,9 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
|
|||||||
*memoryConfig.LogSettings.ConsoleLevel = mlog.LvlStdLog.Name
|
*memoryConfig.LogSettings.ConsoleLevel = mlog.LvlStdLog.Name
|
||||||
*memoryConfig.AnnouncementSettings.AdminNoticesEnabled = false
|
*memoryConfig.AnnouncementSettings.AdminNoticesEnabled = false
|
||||||
*memoryConfig.AnnouncementSettings.UserNoticesEnabled = false
|
*memoryConfig.AnnouncementSettings.UserNoticesEnabled = false
|
||||||
|
if updateConfig != nil {
|
||||||
|
updateConfig(memoryConfig)
|
||||||
|
}
|
||||||
configStore.Set(memoryConfig)
|
configStore.Set(memoryConfig)
|
||||||
|
|
||||||
buffer := &mlog.Buffer{}
|
buffer := &mlog.Buffer{}
|
||||||
@ -105,6 +109,8 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
|
|||||||
ConfigStore: configStore,
|
ConfigStore: configStore,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
th.App.Srv().SetLicense(getLicense(enterprise, memoryConfig))
|
||||||
|
|
||||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.MaxUsersPerTeam = 50 })
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.MaxUsersPerTeam = 50 })
|
||||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.RateLimitSettings.Enable = false })
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.RateLimitSettings.Enable = false })
|
||||||
prevListenAddress := *th.App.Config().ServiceSettings.ListenAddress
|
prevListenAddress := *th.App.Config().ServiceSettings.ListenAddress
|
||||||
@ -131,12 +137,6 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
|
|||||||
*cfg.PasswordSettings.Number = false
|
*cfg.PasswordSettings.Number = false
|
||||||
})
|
})
|
||||||
|
|
||||||
if enterprise {
|
|
||||||
th.App.Srv().SetLicense(model.NewTestLicense())
|
|
||||||
} else {
|
|
||||||
th.App.Srv().SetLicense(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if th.tempWorkspace == "" {
|
if th.tempWorkspace == "" {
|
||||||
th.tempWorkspace = tempWorkspace
|
th.tempWorkspace = tempWorkspace
|
||||||
}
|
}
|
||||||
@ -144,6 +144,16 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
|
|||||||
return th
|
return th
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getLicense(enterprise bool, cfg *model.Config) *model.License {
|
||||||
|
if *cfg.ExperimentalSettings.EnableRemoteClusterService || *cfg.ExperimentalSettings.EnableSharedChannels {
|
||||||
|
return model.NewTestLicenseSKU(model.LicenseShortSkuProfessional)
|
||||||
|
}
|
||||||
|
if enterprise {
|
||||||
|
return model.NewTestLicense()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func Setup(tb testing.TB, options ...Option) *TestHelper {
|
func Setup(tb testing.TB, options ...Option) *TestHelper {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
tb.SkipNow()
|
tb.SkipNow()
|
||||||
@ -153,7 +163,19 @@ func Setup(tb testing.TB, options ...Option) *TestHelper {
|
|||||||
dbStore.MarkSystemRanUnitTests()
|
dbStore.MarkSystemRanUnitTests()
|
||||||
mainHelper.PreloadMigrations()
|
mainHelper.PreloadMigrations()
|
||||||
|
|
||||||
return setupTestHelper(dbStore, false, true, options, tb)
|
return setupTestHelper(dbStore, false, true, nil, options, tb)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetupConfig(tb testing.TB, updateConfig func(cfg *model.Config)) *TestHelper {
|
||||||
|
if testing.Short() {
|
||||||
|
tb.SkipNow()
|
||||||
|
}
|
||||||
|
dbStore := mainHelper.GetStore()
|
||||||
|
dbStore.DropAllTables()
|
||||||
|
dbStore.MarkSystemRanUnitTests()
|
||||||
|
mainHelper.PreloadMigrations()
|
||||||
|
|
||||||
|
return setupTestHelper(dbStore, false, true, updateConfig, nil, tb)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupWithoutPreloadMigrations(tb testing.TB) *TestHelper {
|
func SetupWithoutPreloadMigrations(tb testing.TB) *TestHelper {
|
||||||
@ -164,13 +186,13 @@ func SetupWithoutPreloadMigrations(tb testing.TB) *TestHelper {
|
|||||||
dbStore.DropAllTables()
|
dbStore.DropAllTables()
|
||||||
dbStore.MarkSystemRanUnitTests()
|
dbStore.MarkSystemRanUnitTests()
|
||||||
|
|
||||||
return setupTestHelper(dbStore, false, true, nil, tb)
|
return setupTestHelper(dbStore, false, true, nil, nil, tb)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupWithStoreMock(tb testing.TB) *TestHelper {
|
func SetupWithStoreMock(tb testing.TB) *TestHelper {
|
||||||
mockStore := testlib.GetMockStoreForSetupFunctions()
|
mockStore := testlib.GetMockStoreForSetupFunctions()
|
||||||
setupOptions := []Option{SkipProductsInitialization()}
|
setupOptions := []Option{SkipProductsInitialization()}
|
||||||
th := setupTestHelper(mockStore, false, false, setupOptions, tb)
|
th := setupTestHelper(mockStore, false, false, nil, setupOptions, tb)
|
||||||
statusMock := mocks.StatusStore{}
|
statusMock := mocks.StatusStore{}
|
||||||
statusMock.On("UpdateExpiredDNDStatuses").Return([]*model.Status{}, nil)
|
statusMock.On("UpdateExpiredDNDStatuses").Return([]*model.Status{}, nil)
|
||||||
statusMock.On("Get", "user1").Return(&model.Status{UserId: "user1", Status: model.StatusOnline}, nil)
|
statusMock.On("Get", "user1").Return(&model.Status{UserId: "user1", Status: model.StatusOnline}, nil)
|
||||||
@ -192,7 +214,7 @@ func SetupWithStoreMock(tb testing.TB) *TestHelper {
|
|||||||
func SetupEnterpriseWithStoreMock(tb testing.TB) *TestHelper {
|
func SetupEnterpriseWithStoreMock(tb testing.TB) *TestHelper {
|
||||||
mockStore := testlib.GetMockStoreForSetupFunctions()
|
mockStore := testlib.GetMockStoreForSetupFunctions()
|
||||||
setupOptions := []Option{SkipProductsInitialization()}
|
setupOptions := []Option{SkipProductsInitialization()}
|
||||||
th := setupTestHelper(mockStore, true, false, setupOptions, tb)
|
th := setupTestHelper(mockStore, true, false, nil, setupOptions, tb)
|
||||||
statusMock := mocks.StatusStore{}
|
statusMock := mocks.StatusStore{}
|
||||||
statusMock.On("UpdateExpiredDNDStatuses").Return([]*model.Status{}, nil)
|
statusMock.On("UpdateExpiredDNDStatuses").Return([]*model.Status{}, nil)
|
||||||
statusMock.On("Get", "user1").Return(&model.Status{UserId: "user1", Status: model.StatusOnline}, nil)
|
statusMock.On("Get", "user1").Return(&model.Status{UserId: "user1", Status: model.StatusOnline}, nil)
|
||||||
@ -214,7 +236,7 @@ func SetupWithClusterMock(tb testing.TB, cluster einterfaces.ClusterInterface) *
|
|||||||
dbStore.MarkSystemRanUnitTests()
|
dbStore.MarkSystemRanUnitTests()
|
||||||
mainHelper.PreloadMigrations()
|
mainHelper.PreloadMigrations()
|
||||||
|
|
||||||
return setupTestHelper(dbStore, true, true, []Option{SetCluster(cluster)}, tb)
|
return setupTestHelper(dbStore, true, true, nil, []Option{SetCluster(cluster)}, tb)
|
||||||
}
|
}
|
||||||
|
|
||||||
var initBasicOnce sync.Once
|
var initBasicOnce sync.Once
|
||||||
|
@ -11964,7 +11964,7 @@ func (a *OpenTracingAppLayer) InviteNewUsersToTeamGracefully(memberInvite *model
|
|||||||
return resultVar0, resultVar1
|
return resultVar0, resultVar1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *OpenTracingAppLayer) InviteRemoteToChannel(channelID string, remoteID string, userID string) error {
|
func (a *OpenTracingAppLayer) InviteRemoteToChannel(channelID string, remoteID string, userID string, shareIfNotShared bool) error {
|
||||||
origCtx := a.ctx
|
origCtx := a.ctx
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.InviteRemoteToChannel")
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.InviteRemoteToChannel")
|
||||||
|
|
||||||
@ -11976,7 +11976,7 @@ func (a *OpenTracingAppLayer) InviteRemoteToChannel(channelID string, remoteID s
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
resultVar0 := a.app.InviteRemoteToChannel(channelID, remoteID, userID)
|
resultVar0 := a.app.InviteRemoteToChannel(channelID, remoteID, userID, shareIfNotShared)
|
||||||
|
|
||||||
if resultVar0 != nil {
|
if resultVar0 != nil {
|
||||||
span.LogFields(spanlog.Error(resultVar0))
|
span.LogFields(spanlog.Error(resultVar0))
|
||||||
|
@ -78,7 +78,31 @@ func handleContentSync(ps *PlatformService, syncService SharedChannelServiceIFac
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if channel != nil && channel.IsShared() {
|
shouldNotify := channel.IsShared()
|
||||||
|
|
||||||
|
// check if any remotes need to be auto-subscribed to this channel. Remotes are auto-subscribed to DM/GM's if they registered
|
||||||
|
// with the AutoShareDMs flag set.
|
||||||
|
if channel.Type == model.ChannelTypeDirect || channel.Type == model.ChannelTypeGroup {
|
||||||
|
filter := model.RemoteClusterQueryFilter{
|
||||||
|
NotInChannel: channel.Id,
|
||||||
|
OnlyConfirmed: true,
|
||||||
|
RequireOptions: model.BitflagOptionAutoShareDMs,
|
||||||
|
}
|
||||||
|
remotes, err := ps.Store.RemoteCluster().GetAll(filter) // empty list returned if none found, no error
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot fetch remote clusters: %w", err)
|
||||||
|
}
|
||||||
|
for _, remote := range remotes {
|
||||||
|
// invite remote to channel (will share the channel if not already shared)
|
||||||
|
if err := syncService.InviteRemoteToChannel(channel.Id, remote.RemoteId, remote.CreatorId, true); err != nil {
|
||||||
|
return fmt.Errorf("cannot invite remote to channel %s: %w", channel.Id, err)
|
||||||
|
}
|
||||||
|
shouldNotify = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// notify
|
||||||
|
if shouldNotify {
|
||||||
syncService.NotifyChannelChanged(channel.Id)
|
syncService.NotifyChannelChanged(channel.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,12 @@ type SharedChannelServiceIFace interface {
|
|||||||
NotifyUserProfileChanged(userID 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
|
||||||
|
InviteRemoteToChannel(channelID, remoteID, userID string, shareIfNotShared bool) error
|
||||||
|
UninviteRemoteFromChannel(channelID, remoteID string) error
|
||||||
|
ShareChannel(sc *model.SharedChannel) (*model.SharedChannel, error)
|
||||||
|
CheckChannelNotShared(channelID string) error
|
||||||
|
CheckChannelIsShared(channelID string) error
|
||||||
|
CheckCanInviteToSharedChannel(channelId string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type MockOptionSharedChannelService func(service *mockSharedChannelService)
|
type MockOptionSharedChannelService func(service *mockSharedChannelService)
|
||||||
|
@ -1305,7 +1305,7 @@ func (api *PluginAPI) UnregisterPluginForSharedChannels(pluginID string) error {
|
|||||||
|
|
||||||
func (api *PluginAPI) ShareChannel(sc *model.SharedChannel) (*model.SharedChannel, error) {
|
func (api *PluginAPI) ShareChannel(sc *model.SharedChannel) (*model.SharedChannel, error) {
|
||||||
scShared, err := api.app.ShareChannel(api.ctx, sc)
|
scShared, err := api.app.ShareChannel(api.ctx, sc)
|
||||||
if errors.Is(err, ErrChannelAlreadyShared) {
|
if errors.Is(err, model.ErrChannelAlreadyShared) {
|
||||||
// sharing an already shared channel is not an error; treat as idempotent and return the existing shared channel
|
// sharing an already shared channel is not an error; treat as idempotent and return the existing shared channel
|
||||||
return api.app.GetSharedChannel(sc.ChannelId)
|
return api.app.GetSharedChannel(sc.ChannelId)
|
||||||
}
|
}
|
||||||
@ -1328,8 +1328,8 @@ func (api *PluginAPI) SyncSharedChannel(channelID string) error {
|
|||||||
return api.app.SyncSharedChannel(channelID)
|
return api.app.SyncSharedChannel(channelID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *PluginAPI) InviteRemoteToChannel(channelID string, remoteID, userID string) error {
|
func (api *PluginAPI) InviteRemoteToChannel(channelID string, remoteID, userID string, shareIfNotShared bool) error {
|
||||||
return api.app.InviteRemoteToChannel(channelID, remoteID, userID)
|
return api.app.InviteRemoteToChannel(channelID, remoteID, userID, shareIfNotShared)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *PluginAPI) UninviteRemoteFromChannel(channelID string, remoteID string) error {
|
func (api *PluginAPI) UninviteRemoteFromChannel(channelID string, remoteID string) error {
|
||||||
|
@ -2985,11 +2985,11 @@ func TestReplyToPostWithLag(t *testing.T) {
|
|||||||
|
|
||||||
func TestSharedChannelSyncForPostActions(t *testing.T) {
|
func TestSharedChannelSyncForPostActions(t *testing.T) {
|
||||||
t.Run("creating a post in a shared channel performs a content sync when sync service is running on that node", func(t *testing.T) {
|
t.Run("creating a post in a shared channel performs a content sync when sync service is running on that node", func(t *testing.T) {
|
||||||
th := Setup(t).InitBasic()
|
th := setupSharedChannels(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
remoteClusterService := NewMockSharedChannelService(nil)
|
sharedChannelService := NewMockSharedChannelService(th.Server.GetSharedChannelSyncService())
|
||||||
th.Server.SetSharedChannelSyncService(remoteClusterService)
|
th.Server.SetSharedChannelSyncService(sharedChannelService)
|
||||||
testCluster := &testlib.FakeClusterInterface{}
|
testCluster := &testlib.FakeClusterInterface{}
|
||||||
th.Server.Platform().SetCluster(testCluster)
|
th.Server.Platform().SetCluster(testCluster)
|
||||||
|
|
||||||
@ -3004,16 +3004,16 @@ 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")
|
||||||
|
|
||||||
require.Len(t, remoteClusterService.channelNotifications, 1)
|
require.Len(t, sharedChannelService.channelNotifications, 1)
|
||||||
assert.Equal(t, channel.Id, remoteClusterService.channelNotifications[0])
|
assert.Equal(t, channel.Id, sharedChannelService.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) {
|
||||||
th := Setup(t).InitBasic()
|
th := setupSharedChannels(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
remoteClusterService := NewMockSharedChannelService(nil)
|
sharedChannelService := NewMockSharedChannelService(th.Server.GetSharedChannelSyncService())
|
||||||
th.Server.SetSharedChannelSyncService(remoteClusterService)
|
th.Server.SetSharedChannelSyncService(sharedChannelService)
|
||||||
testCluster := &testlib.FakeClusterInterface{}
|
testCluster := &testlib.FakeClusterInterface{}
|
||||||
th.Server.Platform().SetCluster(testCluster)
|
th.Server.Platform().SetCluster(testCluster)
|
||||||
|
|
||||||
@ -3031,17 +3031,17 @@ 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")
|
||||||
|
|
||||||
require.Len(t, remoteClusterService.channelNotifications, 2)
|
require.Len(t, sharedChannelService.channelNotifications, 2)
|
||||||
assert.Equal(t, channel.Id, remoteClusterService.channelNotifications[0])
|
assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[0])
|
||||||
assert.Equal(t, channel.Id, remoteClusterService.channelNotifications[1])
|
assert.Equal(t, channel.Id, sharedChannelService.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) {
|
||||||
th := Setup(t).InitBasic()
|
th := setupSharedChannels(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
remoteClusterService := NewMockSharedChannelService(nil)
|
sharedChannelService := NewMockSharedChannelService(th.Server.GetSharedChannelSyncService())
|
||||||
th.Server.SetSharedChannelSyncService(remoteClusterService)
|
th.Server.SetSharedChannelSyncService(sharedChannelService)
|
||||||
testCluster := &testlib.FakeClusterInterface{}
|
testCluster := &testlib.FakeClusterInterface{}
|
||||||
th.Server.Platform().SetCluster(testCluster)
|
th.Server.Platform().SetCluster(testCluster)
|
||||||
|
|
||||||
@ -3060,10 +3060,10 @@ 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
|
||||||
require.Len(t, remoteClusterService.channelNotifications, 3)
|
require.Len(t, sharedChannelService.channelNotifications, 3)
|
||||||
assert.Equal(t, channel.Id, remoteClusterService.channelNotifications[0])
|
assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[0])
|
||||||
assert.Equal(t, channel.Id, remoteClusterService.channelNotifications[1])
|
assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[1])
|
||||||
assert.Equal(t, channel.Id, remoteClusterService.channelNotifications[2])
|
assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[2])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,9 +98,9 @@ func TestSaveReactionForPost(t *testing.T) {
|
|||||||
|
|
||||||
func TestSharedChannelSyncForReactionActions(t *testing.T) {
|
func TestSharedChannelSyncForReactionActions(t *testing.T) {
|
||||||
t.Run("adding a reaction in a shared channel performs a content sync when sync service is running on that node", func(t *testing.T) {
|
t.Run("adding a reaction in a shared channel performs a content sync when sync service is running on that node", func(t *testing.T) {
|
||||||
th := Setup(t).InitBasic()
|
th := setupSharedChannels(t).InitBasic()
|
||||||
|
|
||||||
sharedChannelService := NewMockSharedChannelService(nil)
|
sharedChannelService := NewMockSharedChannelService(th.Server.GetSharedChannelSyncService())
|
||||||
th.Server.SetSharedChannelSyncService(sharedChannelService)
|
th.Server.SetSharedChannelSyncService(sharedChannelService)
|
||||||
testCluster := &testlib.FakeClusterInterface{}
|
testCluster := &testlib.FakeClusterInterface{}
|
||||||
th.Server.Platform().SetCluster(testCluster)
|
th.Server.Platform().SetCluster(testCluster)
|
||||||
@ -133,9 +133,9 @@ func TestSharedChannelSyncForReactionActions(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) {
|
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) {
|
||||||
th := Setup(t).InitBasic()
|
th := setupSharedChannels(t).InitBasic()
|
||||||
|
|
||||||
sharedChannelService := NewMockSharedChannelService(nil)
|
sharedChannelService := NewMockSharedChannelService(th.Server.GetSharedChannelSyncService())
|
||||||
th.Server.SetSharedChannelSyncService(sharedChannelService)
|
th.Server.SetSharedChannelSyncService(sharedChannelService)
|
||||||
testCluster := &testlib.FakeClusterInterface{}
|
testCluster := &testlib.FakeClusterInterface{}
|
||||||
th.Server.Platform().SetCluster(testCluster)
|
th.Server.Platform().SetCluster(testCluster)
|
||||||
|
@ -1,75 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/mattermost/mattermost/server/public/model"
|
|
||||||
"github.com/mattermost/mattermost/server/v8/platform/services/remotecluster"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockOptionRemoteClusterService a mock of the remote cluster service
|
|
||||||
type MockOptionRemoteClusterService func(service *mockRemoteClusterService)
|
|
||||||
|
|
||||||
func MockOptionRemoteClusterServiceWithActive(active bool) MockOptionRemoteClusterService {
|
|
||||||
return func(mrcs *mockRemoteClusterService) {
|
|
||||||
mrcs.active = active
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMockRemoteClusterService(service remotecluster.RemoteClusterServiceIFace, options ...MockOptionRemoteClusterService) *mockRemoteClusterService {
|
|
||||||
mrcs := &mockRemoteClusterService{service, true}
|
|
||||||
for _, option := range options {
|
|
||||||
option(mrcs)
|
|
||||||
}
|
|
||||||
return mrcs
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockRemoteClusterService struct {
|
|
||||||
remotecluster.RemoteClusterServiceIFace
|
|
||||||
active bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mrcs *mockRemoteClusterService) Shutdown() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mrcs *mockRemoteClusterService) Start() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mrcs *mockRemoteClusterService) Active() bool {
|
|
||||||
return mrcs.active
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mrcs *mockRemoteClusterService) AddTopicListener(topic string, listener remotecluster.TopicListener) string {
|
|
||||||
return model.NewId()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mrcs *mockRemoteClusterService) RemoveTopicListener(listenerId string) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mrcs *mockRemoteClusterService) AddConnectionStateListener(listener remotecluster.ConnectionStateListener) string {
|
|
||||||
return model.NewId()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mrcs *mockRemoteClusterService) RemoveConnectionStateListener(listenerId string) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mrcs *mockRemoteClusterService) SendMsg(ctx context.Context, msg model.RemoteClusterMsg, rc *model.RemoteCluster, f remotecluster.SendMsgResultFunc) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mrcs *mockRemoteClusterService) SendFile(ctx context.Context, us *model.UploadSession, fi *model.FileInfo, rc *model.RemoteCluster, rp remotecluster.ReaderProvider, f remotecluster.SendFileResultFunc) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mrcs *mockRemoteClusterService) AcceptInvitation(invite *model.RemoteClusterInvite, name string, displayName string, creatorId string, teamId string, siteURL string) (*model.RemoteCluster, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mrcs *mockRemoteClusterService) ReceiveIncomingMsg(rc *model.RemoteCluster, msg model.RemoteClusterMsg) remotecluster.Response {
|
|
||||||
return remotecluster.Response{}
|
|
||||||
}
|
|
@ -13,8 +13,14 @@ import (
|
|||||||
"github.com/mattermost/mattermost/server/public/shared/i18n"
|
"github.com/mattermost/mattermost/server/public/shared/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func setupRemoteCluster(tb testing.TB) *TestHelper {
|
||||||
|
return SetupConfig(tb, func(cfg *model.Config) {
|
||||||
|
*cfg.ExperimentalSettings.EnableRemoteClusterService = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestAddRemoteCluster(t *testing.T) {
|
func TestAddRemoteCluster(t *testing.T) {
|
||||||
th := Setup(t).InitBasic()
|
th := setupRemoteCluster(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
t.Run("adding remote cluster with duplicate site url and remote team id", func(t *testing.T) {
|
t.Run("adding remote cluster with duplicate site url and remote team id", func(t *testing.T) {
|
||||||
@ -68,7 +74,7 @@ func TestAddRemoteCluster(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateRemoteCluster(t *testing.T) {
|
func TestUpdateRemoteCluster(t *testing.T) {
|
||||||
th := Setup(t).InitBasic()
|
th := setupRemoteCluster(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
t.Run("update remote cluster with an already existing site url and team id", func(t *testing.T) {
|
t.Run("update remote cluster with an already existing site url and team id", func(t *testing.T) {
|
||||||
|
@ -651,7 +651,7 @@ func (s *Server) startInterClusterServices(license *model.License) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
scs, err := sharedchannel.NewSharedChannelService(s, appInstance)
|
scs, err := sharedchannel.NewSharedChannelService(s, s.Platform(), appInstance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,14 @@ import (
|
|||||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func (a *App) getSharedChannelsService() (SharedChannelServiceIFace, error) {
|
||||||
errNotFound = errors.New("not found")
|
scService := a.Srv().GetSharedChannelSyncService()
|
||||||
ErrChannelAlreadyShared = errors.New("channel is already shared")
|
if scService == nil || !scService.Active() {
|
||||||
)
|
return nil, model.NewAppError("InviteRemoteToChannel", "api.command_share.service_disabled",
|
||||||
|
nil, "", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
return scService, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) checkChannelNotShared(c request.CTX, channelId string) error {
|
func (a *App) checkChannelNotShared(c request.CTX, channelId string) error {
|
||||||
// check that channel exists.
|
// check that channel exists.
|
||||||
@ -28,7 +32,7 @@ func (a *App) checkChannelNotShared(c request.CTX, channelId string) error {
|
|||||||
|
|
||||||
// Check channel is not already shared.
|
// Check channel is not already shared.
|
||||||
if _, err := a.GetSharedChannel(channelId); err == nil {
|
if _, err := a.GetSharedChannel(channelId); err == nil {
|
||||||
return ErrChannelAlreadyShared
|
return model.ErrChannelAlreadyShared
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -46,42 +50,21 @@ func (a *App) checkChannelIsShared(channelId string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) CheckCanInviteToSharedChannel(channelId string) error {
|
func (a *App) CheckCanInviteToSharedChannel(channelId string) error {
|
||||||
sc, err := a.GetSharedChannel(channelId)
|
scService, err := a.getSharedChannelsService()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *store.ErrNotFound
|
return err
|
||||||
if errors.As(err, &errNotFound) {
|
|
||||||
return fmt.Errorf("channel is not shared: %w", err)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("cannot find channel: %w", err)
|
|
||||||
}
|
}
|
||||||
|
return scService.CheckCanInviteToSharedChannel(channelId)
|
||||||
if !sc.Home {
|
|
||||||
return errors.New("channel is homed on a remote cluster")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) notifyClientsForSharedChannelUpdate(teamID, channelID string) {
|
|
||||||
messageWs := model.NewWebSocketEvent(model.WebsocketEventChannelConverted, teamID, "", "", nil, "")
|
|
||||||
messageWs.Add("channel_id", channelID)
|
|
||||||
a.Publish(messageWs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SharedChannels
|
// SharedChannels
|
||||||
|
|
||||||
func (a *App) ShareChannel(c request.CTX, sc *model.SharedChannel) (*model.SharedChannel, error) {
|
func (a *App) ShareChannel(c request.CTX, sc *model.SharedChannel) (*model.SharedChannel, error) {
|
||||||
if err := a.checkChannelNotShared(c, sc.ChannelId); err != nil {
|
scService, err := a.getSharedChannelsService()
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// stores a SharedChannel and set the share flag on the channel.
|
|
||||||
scNew, err := a.Srv().Store().SharedChannel().Save(sc)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return scService.ShareChannel(sc)
|
||||||
a.notifyClientsForSharedChannelUpdate(scNew.TeamId, scNew.ChannelId)
|
|
||||||
return scNew, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) GetSharedChannel(channelID string) (*model.SharedChannel, error) {
|
func (a *App) GetSharedChannel(channelID string) (*model.SharedChannel, error) {
|
||||||
@ -105,103 +88,37 @@ func (a *App) GetSharedChannelsCount(opts model.SharedChannelFilterOpts) (int64,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) UpdateSharedChannel(sc *model.SharedChannel) (*model.SharedChannel, error) {
|
func (a *App) UpdateSharedChannel(sc *model.SharedChannel) (*model.SharedChannel, error) {
|
||||||
scUpdated, err := a.Srv().Store().SharedChannel().Update(sc)
|
scService, err := a.getSharedChannelsService()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
a.notifyClientsForSharedChannelUpdate(scUpdated.TeamId, scUpdated.ChannelId)
|
return scService.UpdateSharedChannel(sc)
|
||||||
return scUpdated, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) UnshareChannel(channelID string) (bool, error) {
|
func (a *App) UnshareChannel(channelID string) (bool, error) {
|
||||||
// fetch the SharedChannel first
|
scService, err := a.getSharedChannelsService()
|
||||||
sc, err := a.GetSharedChannel(channelID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
return scService.UnshareChannel(channelID)
|
||||||
// deletes the ShareChannel, unsets the share flag on the channel, deletes all remotes for the channel
|
|
||||||
deleted, err := a.Srv().Store().SharedChannel().Delete(channelID)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
a.notifyClientsForSharedChannelUpdate(sc.TeamId, sc.ChannelId)
|
|
||||||
return deleted, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SharedChannelRemotes
|
// SharedChannelRemotes
|
||||||
|
|
||||||
func (a *App) InviteRemoteToChannel(channelID, remoteID, userID string) error {
|
func (a *App) InviteRemoteToChannel(channelID, remoteID, userID string, shareIfNotShared bool) error {
|
||||||
syncService := a.Srv().GetSharedChannelSyncService()
|
ssService, err := a.getSharedChannelsService()
|
||||||
if syncService == nil || !syncService.Active() {
|
|
||||||
return model.NewAppError("InviteRemoteToChannel", "api.command_share.service_disabled",
|
|
||||||
nil, "", http.StatusBadRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
hasRemote, err := a.HasRemote(channelID, remoteID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return model.NewAppError("InviteRemoteToChannel", "api.command_share.fetch_remote.error",
|
return err
|
||||||
map[string]any{"Error": err.Error()}, "", http.StatusInternalServerError)
|
|
||||||
}
|
}
|
||||||
if hasRemote {
|
return ssService.InviteRemoteToChannel(channelID, remoteID, userID, shareIfNotShared)
|
||||||
// already invited
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if channel is shared or not.
|
|
||||||
hasChan, err := a.HasSharedChannel(channelID)
|
|
||||||
if err != nil {
|
|
||||||
return model.NewAppError("InviteRemoteToChannel", "api.command_share.check_channel_exist.error",
|
|
||||||
map[string]any{"ChannelID": channelID, "Error": err.Error()}, "", http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
if !hasChan {
|
|
||||||
return model.NewAppError("InviteRemoteToChannel", "api.command_share.channel_not_shared.error",
|
|
||||||
map[string]any{"ChannelID": channelID}, "", http.StatusBadRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't allow invitation to shared channel originating from remote.
|
|
||||||
// (also blocks cyclic invitations)
|
|
||||||
if err := a.CheckCanInviteToSharedChannel(channelID); err != nil {
|
|
||||||
return model.NewAppError("InviteRemoteToChannel", "api.command_share.channel_invite_not_home.error", nil, "", http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
rc, appErr := a.GetRemoteCluster(remoteID)
|
|
||||||
if appErr != nil {
|
|
||||||
return model.NewAppError("InviteRemoteToChannel", "api.command_share.remote_id_invalid.error",
|
|
||||||
map[string]any{"Error": appErr.Error()}, "", http.StatusInternalServerError).Wrap(appErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
channel, errApp := a.GetChannel(request.EmptyContext(a.Log()), channelID)
|
|
||||||
if errApp != nil {
|
|
||||||
return model.NewAppError("InviteRemoteToChannel", "api.command_share.channel_invite.error",
|
|
||||||
map[string]any{"Name": rc.DisplayName, "Error": errApp.Error()}, "", http.StatusInternalServerError).Wrap(appErr)
|
|
||||||
}
|
|
||||||
// send channel invite to remote cluster. Will notify clients of channel change.
|
|
||||||
if err := syncService.SendChannelInvite(channel, userID, rc); err != nil {
|
|
||||||
return model.NewAppError("InviteRemoteToChannel", "api.command_share.channel_invite.error",
|
|
||||||
map[string]any{"Name": rc.DisplayName, "Error": err.Error()}, "", http.StatusInternalServerError).Wrap(err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) UninviteRemoteFromChannel(channelID, remoteID string) error {
|
func (a *App) UninviteRemoteFromChannel(channelID, remoteID string) error {
|
||||||
scr, err := a.GetSharedChannelRemoteByIds(channelID, remoteID)
|
ssService, err := a.getSharedChannelsService()
|
||||||
if err != nil || scr.ChannelId != channelID {
|
if err != nil {
|
||||||
return model.NewAppError("UninviteRemoteFromChannel", "api.command_share.channel_remote_id_not_exists",
|
return err
|
||||||
map[string]any{"RemoteId": remoteID}, "", http.StatusInternalServerError)
|
|
||||||
}
|
}
|
||||||
|
return ssService.UninviteRemoteFromChannel(channelID, remoteID)
|
||||||
deleted, err := a.Srv().Store().SharedChannel().DeleteRemote(scr.Id)
|
|
||||||
if err != nil || !deleted {
|
|
||||||
code := http.StatusInternalServerError
|
|
||||||
if err == nil {
|
|
||||||
err = errNotFound
|
|
||||||
code = http.StatusBadRequest
|
|
||||||
}
|
|
||||||
return model.NewAppError("UninviteRemoteFromChannel", "api.command_share.could_not_uninvite.error",
|
|
||||||
map[string]any{"RemoteId": remoteID, "Error": err.Error()}, "", code)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) SaveSharedChannelRemote(remote *model.SharedChannelRemote) (*model.SharedChannelRemote, error) {
|
func (a *App) SaveSharedChannelRemote(remote *model.SharedChannelRemote) (*model.SharedChannelRemote, error) {
|
||||||
|
@ -17,27 +17,28 @@ type SharedChannelServiceIFace interface {
|
|||||||
NotifyUserProfileChanged(userID 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
|
||||||
|
InviteRemoteToChannel(channelID, remoteID, userID string, shareIfNotShared bool) error
|
||||||
|
UninviteRemoteFromChannel(channelID, remoteID string) error
|
||||||
|
ShareChannel(sc *model.SharedChannel) (*model.SharedChannel, error)
|
||||||
|
UpdateSharedChannel(sc *model.SharedChannel) (*model.SharedChannel, error)
|
||||||
|
UnshareChannel(channelID string) (bool, error)
|
||||||
|
CheckChannelNotShared(channelID string) error
|
||||||
|
CheckChannelIsShared(channelID string) error
|
||||||
|
CheckCanInviteToSharedChannel(channelId string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type MockOptionSharedChannelService func(service *mockSharedChannelService)
|
func NewMockSharedChannelService(service SharedChannelServiceIFace) *mockSharedChannelService {
|
||||||
|
mrcs := &mockSharedChannelService{
|
||||||
func MockOptionSharedChannelServiceWithActive(active bool) MockOptionSharedChannelService {
|
SharedChannelServiceIFace: service,
|
||||||
return func(mrcs *mockSharedChannelService) {
|
channelNotifications: []string{},
|
||||||
mrcs.active = active
|
userProfileNotifications: []string{},
|
||||||
}
|
numInvitations: 0,
|
||||||
}
|
|
||||||
|
|
||||||
func NewMockSharedChannelService(service SharedChannelServiceIFace, options ...MockOptionSharedChannelService) *mockSharedChannelService {
|
|
||||||
mrcs := &mockSharedChannelService{service, true, []string{}, []string{}, 0}
|
|
||||||
for _, option := range options {
|
|
||||||
option(mrcs)
|
|
||||||
}
|
}
|
||||||
return mrcs
|
return mrcs
|
||||||
}
|
}
|
||||||
|
|
||||||
type mockSharedChannelService struct {
|
type mockSharedChannelService struct {
|
||||||
SharedChannelServiceIFace
|
SharedChannelServiceIFace
|
||||||
active bool
|
|
||||||
channelNotifications []string
|
channelNotifications []string
|
||||||
userProfileNotifications []string
|
userProfileNotifications []string
|
||||||
numInvitations int
|
numInvitations int
|
||||||
@ -45,26 +46,44 @@ type mockSharedChannelService struct {
|
|||||||
|
|
||||||
func (mrcs *mockSharedChannelService) NotifyChannelChanged(channelId string) {
|
func (mrcs *mockSharedChannelService) NotifyChannelChanged(channelId string) {
|
||||||
mrcs.channelNotifications = append(mrcs.channelNotifications, channelId)
|
mrcs.channelNotifications = append(mrcs.channelNotifications, channelId)
|
||||||
|
if mrcs.SharedChannelServiceIFace != nil {
|
||||||
|
mrcs.SharedChannelServiceIFace.NotifyChannelChanged(channelId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mrcs *mockSharedChannelService) NotifyUserProfileChanged(userId string) {
|
func (mrcs *mockSharedChannelService) NotifyUserProfileChanged(userId string) {
|
||||||
mrcs.userProfileNotifications = append(mrcs.userProfileNotifications, userId)
|
mrcs.userProfileNotifications = append(mrcs.userProfileNotifications, userId)
|
||||||
|
if mrcs.SharedChannelServiceIFace != nil {
|
||||||
|
mrcs.SharedChannelServiceIFace.NotifyUserProfileChanged(userId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mrcs *mockSharedChannelService) Shutdown() error {
|
func (mrcs *mockSharedChannelService) Shutdown() error {
|
||||||
|
if mrcs.SharedChannelServiceIFace != nil {
|
||||||
|
return mrcs.SharedChannelServiceIFace.Shutdown()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mrcs *mockSharedChannelService) Start() error {
|
func (mrcs *mockSharedChannelService) Start() error {
|
||||||
|
if mrcs.SharedChannelServiceIFace != nil {
|
||||||
|
return mrcs.SharedChannelServiceIFace.Start()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mrcs *mockSharedChannelService) Active() bool {
|
func (mrcs *mockSharedChannelService) Active() bool {
|
||||||
return mrcs.active
|
if mrcs.SharedChannelServiceIFace != nil {
|
||||||
|
return mrcs.SharedChannelServiceIFace.Active()
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mrcs *mockSharedChannelService) SendChannelInvite(channel *model.Channel, userId string, rc *model.RemoteCluster, options ...sharedchannel.InviteOption) error {
|
func (mrcs *mockSharedChannelService) SendChannelInvite(channel *model.Channel, userId string, rc *model.RemoteCluster, options ...sharedchannel.InviteOption) error {
|
||||||
mrcs.numInvitations += 1
|
mrcs.numInvitations += 1
|
||||||
|
if mrcs.SharedChannelServiceIFace != nil {
|
||||||
|
return mrcs.SharedChannelServiceIFace.SendChannelInvite(channel, userId, rc, options...)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,8 +12,15 @@ import (
|
|||||||
"github.com/mattermost/mattermost/server/public/model"
|
"github.com/mattermost/mattermost/server/public/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func setupSharedChannels(tb testing.TB) *TestHelper {
|
||||||
|
return SetupConfig(tb, func(cfg *model.Config) {
|
||||||
|
*cfg.ExperimentalSettings.EnableRemoteClusterService = true
|
||||||
|
*cfg.ExperimentalSettings.EnableSharedChannels = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestApp_CheckCanInviteToSharedChannel(t *testing.T) {
|
func TestApp_CheckCanInviteToSharedChannel(t *testing.T) {
|
||||||
th := Setup(t).InitBasic()
|
th := setupSharedChannels(t).InitBasic()
|
||||||
|
|
||||||
channel1 := th.CreateChannel(th.Context, th.BasicTeam)
|
channel1 := th.CreateChannel(th.Context, th.BasicTeam)
|
||||||
channel2 := th.CreateChannel(th.Context, th.BasicTeam)
|
channel2 := th.CreateChannel(th.Context, th.BasicTeam)
|
||||||
|
@ -232,6 +232,8 @@ func (sp *ShareProvider) doInviteRemote(a *app.App, c request.CTX, args *model.C
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if channel is shared or not.
|
// Check if channel is shared or not.
|
||||||
|
// TODO: have the share channels service generate the "channel has been shared post" and this section can be removed since
|
||||||
|
// since `a.InviteRemoteToChannel` will share the channel automatically.
|
||||||
hasChan, err := a.HasSharedChannel(args.ChannelId)
|
hasChan, err := a.HasSharedChannel(args.ChannelId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return responsef(args.T("api.command_share.check_channel_exist.error", map[string]any{"ChannelID": args.ChannelId, "Error": err.Error()}))
|
return responsef(args.T("api.command_share.check_channel_exist.error", map[string]any{"ChannelID": args.ChannelId, "Error": err.Error()}))
|
||||||
@ -251,7 +253,7 @@ func (sp *ShareProvider) doInviteRemote(a *app.App, c request.CTX, args *model.C
|
|||||||
return responsef(args.T("api.command_share.remote_id_invalid.error", map[string]any{"Error": appErr.Error()}))
|
return responsef(args.T("api.command_share.remote_id_invalid.error", map[string]any{"Error": appErr.Error()}))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = a.InviteRemoteToChannel(args.ChannelId, remoteID, args.UserId); err != nil {
|
if err = a.InviteRemoteToChannel(args.ChannelId, remoteID, args.UserId, true); err != nil {
|
||||||
return responsef(appErr.Error())
|
return responsef(appErr.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,33 +12,32 @@ import (
|
|||||||
"github.com/mattermost/mattermost/server/v8/channels/testlib"
|
"github.com/mattermost/mattermost/server/v8/channels/testlib"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost/server/v8/channels/app"
|
"github.com/mattermost/mattermost/server/v8/channels/app"
|
||||||
"github.com/mattermost/mattermost/server/v8/platform/services/remotecluster"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost/server/public/model"
|
"github.com/mattermost/mattermost/server/public/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func setupForSharedChannels(tb testing.TB) *TestHelper {
|
||||||
|
return setupConfig(tb, func(cfg *model.Config) {
|
||||||
|
*cfg.ExperimentalSettings.EnableRemoteClusterService = true
|
||||||
|
*cfg.ExperimentalSettings.EnableSharedChannels = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestShareProviderDoCommand(t *testing.T) {
|
func TestShareProviderDoCommand(t *testing.T) {
|
||||||
t.Run("share command sends a websocket channel converted event", func(t *testing.T) {
|
t.Run("share command sends a websocket channel converted event", func(t *testing.T) {
|
||||||
th := setup(t).initBasic()
|
th := setupForSharedChannels(t).initBasic()
|
||||||
defer th.tearDown()
|
defer th.tearDown()
|
||||||
|
|
||||||
th.addPermissionToRole(model.PermissionManageSharedChannels.Id, th.BasicUser.Roles)
|
th.addPermissionToRole(model.PermissionManageSharedChannels.Id, th.BasicUser.Roles)
|
||||||
|
|
||||||
mockSyncService := app.NewMockSharedChannelService(nil, app.MockOptionSharedChannelServiceWithActive(true))
|
mockSyncService := app.NewMockSharedChannelService(th.Server.GetSharedChannelSyncService())
|
||||||
th.Server.SetSharedChannelSyncService(mockSyncService)
|
th.Server.SetSharedChannelSyncService(mockSyncService)
|
||||||
remoteClusterService, err := remotecluster.NewRemoteClusterService(th.Server, th.App)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
th.Server.SetRemoteClusterService(remoteClusterService)
|
|
||||||
testCluster := &testlib.FakeClusterInterface{}
|
testCluster := &testlib.FakeClusterInterface{}
|
||||||
th.Server.Platform().SetCluster(testCluster)
|
th.Server.Platform().SetCluster(testCluster)
|
||||||
|
|
||||||
err = remoteClusterService.Start()
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer remoteClusterService.Shutdown()
|
|
||||||
|
|
||||||
commandProvider := ShareProvider{}
|
commandProvider := ShareProvider{}
|
||||||
channel := th.CreateChannel(th.BasicTeam, WithShared(false))
|
channel := th.CreateChannel(th.BasicTeam, WithShared(false))
|
||||||
|
|
||||||
@ -61,24 +60,17 @@ func TestShareProviderDoCommand(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("unshare command sends a websocket channel converted event", func(t *testing.T) {
|
t.Run("unshare command sends a websocket channel converted event", func(t *testing.T) {
|
||||||
th := setup(t).initBasic()
|
th := setupForSharedChannels(t).initBasic()
|
||||||
defer th.tearDown()
|
defer th.tearDown()
|
||||||
|
|
||||||
th.addPermissionToRole(model.PermissionManageSharedChannels.Id, th.BasicUser.Roles)
|
th.addPermissionToRole(model.PermissionManageSharedChannels.Id, th.BasicUser.Roles)
|
||||||
|
|
||||||
mockSyncService := app.NewMockSharedChannelService(nil)
|
mockSyncService := app.NewMockSharedChannelService(th.Server.GetSharedChannelSyncService())
|
||||||
th.Server.SetSharedChannelSyncService(mockSyncService)
|
th.Server.SetSharedChannelSyncService(mockSyncService)
|
||||||
remoteClusterService, err := remotecluster.NewRemoteClusterService(th.Server, th.App)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
th.Server.SetRemoteClusterService(remoteClusterService)
|
|
||||||
testCluster := &testlib.FakeClusterInterface{}
|
testCluster := &testlib.FakeClusterInterface{}
|
||||||
th.Server.Platform().SetCluster(testCluster)
|
th.Server.Platform().SetCluster(testCluster)
|
||||||
|
|
||||||
err = remoteClusterService.Start()
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer remoteClusterService.Shutdown()
|
|
||||||
|
|
||||||
commandProvider := ShareProvider{}
|
commandProvider := ShareProvider{}
|
||||||
channel := th.CreateChannel(th.BasicTeam, WithShared(true))
|
channel := th.CreateChannel(th.BasicTeam, WithShared(true))
|
||||||
args := &model.CommandArgs{
|
args := &model.CommandArgs{
|
||||||
|
@ -92,6 +92,18 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
|
|||||||
IncludeCacheLayer: includeCacheLayer,
|
IncludeCacheLayer: includeCacheLayer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if enterprise {
|
||||||
|
th.App.Srv().Jobs.StopWorkers()
|
||||||
|
th.App.Srv().Jobs.StopSchedulers()
|
||||||
|
|
||||||
|
th.App.Srv().SetLicense(model.NewTestLicense())
|
||||||
|
|
||||||
|
th.App.Srv().Jobs.StartWorkers()
|
||||||
|
th.App.Srv().Jobs.StartSchedulers()
|
||||||
|
} else {
|
||||||
|
th.App.Srv().SetLicense(getLicense(false, memoryConfig))
|
||||||
|
}
|
||||||
|
|
||||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.MaxUsersPerTeam = 50 })
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.MaxUsersPerTeam = 50 })
|
||||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.RateLimitSettings.Enable = false })
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.RateLimitSettings.Enable = false })
|
||||||
prevListenAddress := *th.App.Config().ServiceSettings.ListenAddress
|
prevListenAddress := *th.App.Config().ServiceSettings.ListenAddress
|
||||||
@ -118,18 +130,6 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
|
|||||||
*cfg.PasswordSettings.Number = false
|
*cfg.PasswordSettings.Number = false
|
||||||
})
|
})
|
||||||
|
|
||||||
if enterprise {
|
|
||||||
th.App.Srv().Jobs.StopWorkers()
|
|
||||||
th.App.Srv().Jobs.StopSchedulers()
|
|
||||||
|
|
||||||
th.App.Srv().SetLicense(model.NewTestLicense())
|
|
||||||
|
|
||||||
th.App.Srv().Jobs.StartWorkers()
|
|
||||||
th.App.Srv().Jobs.StartSchedulers()
|
|
||||||
} else {
|
|
||||||
th.App.Srv().SetLicense(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if th.tempWorkspace == "" {
|
if th.tempWorkspace == "" {
|
||||||
th.tempWorkspace = tempWorkspace
|
th.tempWorkspace = tempWorkspace
|
||||||
}
|
}
|
||||||
@ -137,6 +137,16 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
|
|||||||
return th
|
return th
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getLicense(enterprise bool, cfg *model.Config) *model.License {
|
||||||
|
if *cfg.ExperimentalSettings.EnableRemoteClusterService || *cfg.ExperimentalSettings.EnableSharedChannels {
|
||||||
|
return model.NewTestLicenseSKU(model.LicenseShortSkuProfessional)
|
||||||
|
}
|
||||||
|
if enterprise {
|
||||||
|
return model.NewTestLicense()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func setup(tb testing.TB) *TestHelper {
|
func setup(tb testing.TB) *TestHelper {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
tb.SkipNow()
|
tb.SkipNow()
|
||||||
@ -148,6 +158,17 @@ func setup(tb testing.TB) *TestHelper {
|
|||||||
return setupTestHelper(dbStore, false, true, tb, nil)
|
return setupTestHelper(dbStore, false, true, tb, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setupConfig(tb testing.TB, updateConfig func(cfg *model.Config)) *TestHelper {
|
||||||
|
if testing.Short() {
|
||||||
|
tb.SkipNow()
|
||||||
|
}
|
||||||
|
dbStore := mainHelper.GetStore()
|
||||||
|
dbStore.DropAllTables()
|
||||||
|
dbStore.MarkSystemRanUnitTests()
|
||||||
|
|
||||||
|
return setupTestHelper(dbStore, false, true, tb, updateConfig)
|
||||||
|
}
|
||||||
|
|
||||||
var initBasicOnce sync.Once
|
var initBasicOnce sync.Once
|
||||||
var userCache struct {
|
var userCache struct {
|
||||||
SystemAdminUser *model.User
|
SystemAdminUser *model.User
|
||||||
|
@ -288,11 +288,6 @@ func (_m *MockAppIface) GetProfileImage(user *model.User) ([]byte, bool, *model.
|
|||||||
return r0, r1, r2
|
return r0, r1, r2
|
||||||
}
|
}
|
||||||
|
|
||||||
// InvalidateCacheForUser provides a mock function with given fields: userID
|
|
||||||
func (_m *MockAppIface) InvalidateCacheForUser(userID string) {
|
|
||||||
_m.Called(userID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MentionsToTeamMembers provides a mock function with given fields: c, message, teamID
|
// MentionsToTeamMembers provides a mock function with given fields: c, message, teamID
|
||||||
func (_m *MockAppIface) MentionsToTeamMembers(c request.CTX, message string, teamID string) model.UserMentionMap {
|
func (_m *MockAppIface) MentionsToTeamMembers(c request.CTX, message string, teamID string) model.UserMentionMap {
|
||||||
ret := _m.Called(c, message, teamID)
|
ret := _m.Called(c, message, teamID)
|
||||||
@ -410,6 +405,11 @@ func (_m *MockAppIface) PermanentDeleteChannel(c request.CTX, channel *model.Cha
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Publish provides a mock function with given fields: message
|
||||||
|
func (_m *MockAppIface) Publish(message *model.WebSocketEvent) {
|
||||||
|
_m.Called(message)
|
||||||
|
}
|
||||||
|
|
||||||
// SaveReactionForPost provides a mock function with given fields: c, reaction
|
// SaveReactionForPost provides a mock function with given fields: c, reaction
|
||||||
func (_m *MockAppIface) SaveReactionForPost(c request.CTX, reaction *model.Reaction) (*model.Reaction, *model.AppError) {
|
func (_m *MockAppIface) SaveReactionForPost(c request.CTX, reaction *model.Reaction) (*model.Reaction, *model.AppError) {
|
||||||
ret := _m.Called(c, reaction)
|
ret := _m.Called(c, reaction)
|
||||||
|
@ -44,6 +44,11 @@ type ServerIface interface {
|
|||||||
GetRemoteClusterService() remotecluster.RemoteClusterServiceIFace
|
GetRemoteClusterService() remotecluster.RemoteClusterServiceIFace
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PlatformIface interface {
|
||||||
|
InvalidateCacheForUser(userID string)
|
||||||
|
InvalidateCacheForChannel(channel *model.Channel)
|
||||||
|
}
|
||||||
|
|
||||||
type AppIface interface {
|
type AppIface interface {
|
||||||
SendEphemeralPost(c request.CTX, userId string, post *model.Post) *model.Post
|
SendEphemeralPost(c request.CTX, userId string, post *model.Post) *model.Post
|
||||||
CreateChannelWithUser(c request.CTX, channel *model.Channel, userId string) (*model.Channel, *model.AppError)
|
CreateChannelWithUser(c request.CTX, channel *model.Channel, userId string) (*model.Channel, *model.AppError)
|
||||||
@ -61,11 +66,11 @@ type AppIface interface {
|
|||||||
FileReader(path string) (filestore.ReadCloseSeeker, *model.AppError)
|
FileReader(path string) (filestore.ReadCloseSeeker, *model.AppError)
|
||||||
MentionsToTeamMembers(c request.CTX, message, teamID string) model.UserMentionMap
|
MentionsToTeamMembers(c request.CTX, message, teamID string) model.UserMentionMap
|
||||||
GetProfileImage(user *model.User) ([]byte, bool, *model.AppError)
|
GetProfileImage(user *model.User) ([]byte, bool, *model.AppError)
|
||||||
InvalidateCacheForUser(userID string)
|
|
||||||
NotifySharedChannelUserUpdate(user *model.User)
|
NotifySharedChannelUserUpdate(user *model.User)
|
||||||
OnSharedChannelsSyncMsg(msg *model.SyncMsg, rc *model.RemoteCluster) (model.SyncResponse, error)
|
OnSharedChannelsSyncMsg(msg *model.SyncMsg, rc *model.RemoteCluster) (model.SyncResponse, error)
|
||||||
OnSharedChannelsAttachmentSyncMsg(fi *model.FileInfo, post *model.Post, rc *model.RemoteCluster) error
|
OnSharedChannelsAttachmentSyncMsg(fi *model.FileInfo, post *model.Post, rc *model.RemoteCluster) error
|
||||||
OnSharedChannelsProfileImageSyncMsg(user *model.User, rc *model.RemoteCluster) error
|
OnSharedChannelsProfileImageSyncMsg(user *model.User, rc *model.RemoteCluster) error
|
||||||
|
Publish(message *model.WebSocketEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// errNotFound allows checking against Store.ErrNotFound errors without making Store a dependency.
|
// errNotFound allows checking against Store.ErrNotFound errors without making Store a dependency.
|
||||||
@ -76,6 +81,7 @@ type errNotFound interface {
|
|||||||
// Service provides shared channel synchronization.
|
// Service provides shared channel synchronization.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
server ServerIface
|
server ServerIface
|
||||||
|
platform PlatformIface
|
||||||
app AppIface
|
app AppIface
|
||||||
changeSignal chan struct{}
|
changeSignal chan struct{}
|
||||||
|
|
||||||
@ -93,9 +99,10 @@ type Service struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewSharedChannelService creates a RemoteClusterService instance.
|
// NewSharedChannelService creates a RemoteClusterService instance.
|
||||||
func NewSharedChannelService(server ServerIface, app AppIface) (*Service, error) {
|
func NewSharedChannelService(server ServerIface, platform PlatformIface, app AppIface) (*Service, error) {
|
||||||
service := &Service{
|
service := &Service{
|
||||||
server: server,
|
server: server,
|
||||||
|
platform: platform,
|
||||||
app: app,
|
app: app,
|
||||||
changeSignal: make(chan struct{}, 1),
|
changeSignal: make(chan struct{}, 1),
|
||||||
tasks: make(map[string]syncTask),
|
tasks: make(map[string]syncTask),
|
||||||
@ -245,3 +252,17 @@ func (scs *Service) onConnectionStateChange(rc *model.RemoteCluster, online bool
|
|||||||
mlog.Bool("online", online),
|
mlog.Bool("online", online),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (scs *Service) notifyClientsForSharedChannelConverted(channel *model.Channel) {
|
||||||
|
scs.platform.InvalidateCacheForChannel(channel)
|
||||||
|
messageWs := model.NewWebSocketEvent(model.WebsocketEventChannelConverted, channel.TeamId, "", "", nil, "")
|
||||||
|
messageWs.Add("channel_id", channel.Id)
|
||||||
|
scs.app.Publish(messageWs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scs *Service) notifyClientsForSharedChannelUpdate(channel *model.Channel) {
|
||||||
|
scs.platform.InvalidateCacheForChannel(channel)
|
||||||
|
messageWs := model.NewWebSocketEvent(model.WebsocketEventChannelUpdated, channel.TeamId, "", "", nil, "")
|
||||||
|
messageWs.Add("channel_id", channel.Id)
|
||||||
|
scs.app.Publish(messageWs)
|
||||||
|
}
|
||||||
|
231
server/platform/services/sharedchannel/service_api.go
Normal file
231
server/platform/services/sharedchannel/service_api.go
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package sharedchannel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost/server/public/model"
|
||||||
|
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||||
|
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ShareChannel marks a local channel as shared. If the channel is already shared this method has
|
||||||
|
// no effect and returns without error.
|
||||||
|
// TeamId, type, displayname, purpose, and header are fetched from the channel if not provided.
|
||||||
|
func (scs *Service) ShareChannel(sc *model.SharedChannel) (*model.SharedChannel, error) {
|
||||||
|
channel, err := scs.server.GetStore().Channel().Get(sc.ChannelId, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot fetch channel while sharing channel %s: %w", sc.ChannelId, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if channel is already shared
|
||||||
|
scExisting, err := scs.server.GetStore().SharedChannel().Get(sc.ChannelId)
|
||||||
|
if err == nil {
|
||||||
|
// already shared, nothing to do
|
||||||
|
return scExisting, nil
|
||||||
|
}
|
||||||
|
if !isNotFoundError(err) {
|
||||||
|
return nil, fmt.Errorf("cannot check if channel %s is shared: %w", sc.ChannelId, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sc.TeamId == "" {
|
||||||
|
sc.TeamId = channel.TeamId
|
||||||
|
}
|
||||||
|
if sc.Type == "" {
|
||||||
|
sc.Type = channel.Type
|
||||||
|
}
|
||||||
|
if sc.ShareName == "" {
|
||||||
|
sc.ShareName = channel.Name
|
||||||
|
}
|
||||||
|
if sc.ShareDisplayName == "" {
|
||||||
|
sc.ShareDisplayName = channel.DisplayName
|
||||||
|
}
|
||||||
|
if sc.SharePurpose == "" {
|
||||||
|
sc.SharePurpose = channel.Purpose
|
||||||
|
}
|
||||||
|
if sc.ShareHeader == "" {
|
||||||
|
sc.ShareHeader = channel.Header
|
||||||
|
}
|
||||||
|
if sc.CreatorId == "" {
|
||||||
|
sc.CreatorId = channel.CreatorId
|
||||||
|
}
|
||||||
|
|
||||||
|
// stores the SharedChannel and sets the share flag on the channel.
|
||||||
|
scNew, err := scs.server.GetStore().SharedChannel().Save(sc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scs.notifyClientsForSharedChannelConverted(channel)
|
||||||
|
return scNew, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSharedChannel updates the shared channel details such as displayname, purpose, or header
|
||||||
|
func (scs *Service) UpdateSharedChannel(sc *model.SharedChannel) (*model.SharedChannel, error) {
|
||||||
|
channel, err := scs.server.GetStore().Channel().Get(sc.ChannelId, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scUpdated, err := scs.server.GetStore().SharedChannel().Update(sc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scs.notifyClientsForSharedChannelUpdate(channel)
|
||||||
|
return scUpdated, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnshareChannel unshared the channel by deleting the SharedChannels record and unsets the Channel `shared` flag.
|
||||||
|
// Returns true if a shared channel existed and was deleted.
|
||||||
|
func (scs *Service) UnshareChannel(channelID string) (bool, error) {
|
||||||
|
channel, err := scs.server.GetStore().Channel().Get(channelID, true)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// deletes the ShareChannel, unsets the share flag on the channel, deletes all records in SharedChannelRemotes for the channel.
|
||||||
|
deleted, err := scs.server.GetStore().SharedChannel().Delete(channelID)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scs.notifyClientsForSharedChannelConverted(channel)
|
||||||
|
return deleted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InviteRemoteToChannel sends an invite to the remote to a shared channel. If `shareIfNotShared` is true
|
||||||
|
// then the channel is marked as `shared` first if needed.
|
||||||
|
func (scs *Service) InviteRemoteToChannel(channelID, remoteID, userID string, shareIfNotShared bool) error {
|
||||||
|
scStore := scs.server.GetStore().SharedChannel()
|
||||||
|
rcStore := scs.server.GetStore().RemoteCluster()
|
||||||
|
|
||||||
|
// check if remote already invited to channel
|
||||||
|
hasRemote, err := scStore.HasRemote(channelID, remoteID)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewAppError("InviteRemoteToChannel", "api.command_share.fetch_remote.error",
|
||||||
|
map[string]any{"Error": err.Error()}, "", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
if hasRemote {
|
||||||
|
// already invited, nothing to do
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the channel `shared` flag if needed
|
||||||
|
if shareIfNotShared {
|
||||||
|
if _, err = scs.ShareChannel(&model.SharedChannel{ChannelId: channelID, CreatorId: userID}); err != nil {
|
||||||
|
return model.NewAppError("InviteRemoteToChannel", "api.command_share.share_channel.error",
|
||||||
|
map[string]any{"Error": err.Error()}, "", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err = scs.CheckChannelIsShared(channelID); err != nil {
|
||||||
|
return model.NewAppError("InviteRemoteToChannel", "api.command_share.channel_not_shared.error",
|
||||||
|
map[string]any{"ChannelID": channelID}, "", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rc, err := rcStore.Get(remoteID)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewAppError("InviteRemoteToChannel", "api.command_share.remote_id_invalid.error",
|
||||||
|
map[string]any{"Error": err.Error()}, "", http.StatusInternalServerError).Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't allow invitation to shared channel originating from remote.
|
||||||
|
// (also blocks cyclic invitations)
|
||||||
|
if err = scs.CheckCanInviteToSharedChannel(channelID); err != nil {
|
||||||
|
if errors.Is(err, model.ErrChannelHomedOnRemote) {
|
||||||
|
return model.NewAppError("InviteRemoteToChannel", "api.command_share.channel_invite_not_home.error", nil, "", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
scs.server.Log().Debug("InviteRemoteToChannel failed to check if can-invite",
|
||||||
|
mlog.String("name", rc.Name),
|
||||||
|
mlog.String("channel_id", channelID),
|
||||||
|
mlog.Err(err),
|
||||||
|
)
|
||||||
|
return model.NewAppError("InviteRemoteToChannel", "api.command_share.channel_invite.error",
|
||||||
|
map[string]any{"Name": rc.DisplayName, "Error": err.Error()}, "CheckCanInviteToSharedChannel", http.StatusInternalServerError).Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
channel, err := scs.server.GetStore().Channel().Get(channelID, true)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewAppError("InviteRemoteToChannel", "api.command_share.channel_invite.error",
|
||||||
|
map[string]any{"Name": rc.DisplayName, "Error": err.Error()}, "", http.StatusInternalServerError).Wrap(err)
|
||||||
|
}
|
||||||
|
// send channel invite to remote cluster. Will notify clients of channel change.
|
||||||
|
if err := scs.SendChannelInvite(channel, userID, rc); err != nil {
|
||||||
|
return model.NewAppError("InviteRemoteToChannel", "api.command_share.channel_invite.error",
|
||||||
|
map[string]any{"Name": rc.DisplayName, "Error": err.Error()}, "", http.StatusInternalServerError).Wrap(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scs *Service) UninviteRemoteFromChannel(channelID, remoteID string) error {
|
||||||
|
scr, err := scs.server.GetStore().SharedChannel().GetRemoteByIds(channelID, remoteID)
|
||||||
|
if err != nil || scr.ChannelId != channelID {
|
||||||
|
return model.NewAppError("UninviteRemoteFromChannel", "api.command_share.channel_remote_id_not_exists",
|
||||||
|
map[string]any{"RemoteId": remoteID}, "", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleted, err := scs.server.GetStore().SharedChannel().DeleteRemote(scr.Id)
|
||||||
|
if err != nil || !deleted {
|
||||||
|
code := http.StatusInternalServerError
|
||||||
|
if err == nil {
|
||||||
|
err = errors.New("not found")
|
||||||
|
code = http.StatusBadRequest
|
||||||
|
}
|
||||||
|
return model.NewAppError("UninviteRemoteFromChannel", "api.command_share.could_not_uninvite.error",
|
||||||
|
map[string]any{"RemoteId": remoteID, "Error": err.Error()}, "", code)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckChannelNotShared returns nil only if the channel is not already shared. Otherwise ErrChannelAlreadyShared is
|
||||||
|
// returned if the channel is shared, or database error.
|
||||||
|
func (scs *Service) CheckChannelNotShared(channelID string) error {
|
||||||
|
// check that channel exists.
|
||||||
|
if _, err := scs.server.GetStore().Channel().Get(channelID, true); err != nil {
|
||||||
|
return fmt.Errorf("cannot find channel %s: %w", channelID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check channel is not already shared.
|
||||||
|
if _, err := scs.server.GetStore().SharedChannel().Get(channelID); err == nil {
|
||||||
|
return model.ErrChannelAlreadyShared
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckChannelIsShared returns nil only if the channel is shared. Otherwise a store.ErrNotFound is returned
|
||||||
|
// or database error.
|
||||||
|
func (scs *Service) CheckChannelIsShared(channelID string) error {
|
||||||
|
if _, err := scs.server.GetStore().SharedChannel().Get(channelID); err != nil {
|
||||||
|
var errNotFound *store.ErrNotFound
|
||||||
|
if errors.As(err, &errNotFound) {
|
||||||
|
return fmt.Errorf("channel is not shared: %w", errNotFound)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("cannot check if channel %s is shared: %w", channelID, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckCanInviteToSharedChannel checks if an invitation can be sent for the specified channel.
|
||||||
|
// - don't allow invitations to a shared channel originating from remote.
|
||||||
|
// - block cyclic invitations
|
||||||
|
// - the channel must exist
|
||||||
|
func (scs *Service) CheckCanInviteToSharedChannel(channelId string) error {
|
||||||
|
sc, err := scs.server.GetStore().SharedChannel().Get(channelId)
|
||||||
|
if err != nil {
|
||||||
|
if isNotFoundError(err) {
|
||||||
|
return fmt.Errorf("channel is not shared: %w", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("cannot find channel: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sc.Home {
|
||||||
|
return model.ErrChannelHomedOnRemote
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -317,7 +317,7 @@ func (scs *Service) updateSyncUser(rctx request.CTX, patch *model.UserPatch, use
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scs.app.InvalidateCacheForUser(update.New.Id)
|
scs.platform.InvalidateCacheForUser(update.New.Id)
|
||||||
scs.app.NotifySharedChannelUserUpdate(update.New)
|
scs.app.NotifySharedChannelUserUpdate(update.New)
|
||||||
return update.New, nil
|
return update.New, nil
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,13 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrChannelAlreadyShared = errors.New("channel is already shared")
|
||||||
|
ErrChannelHomedOnRemote = errors.New("channel is homed on a remote cluster")
|
||||||
)
|
)
|
||||||
|
|
||||||
// SharedChannel represents a channel that can be synchronized with a remote cluster.
|
// SharedChannel represents a channel that can be synchronized with a remote cluster.
|
||||||
|
@ -1279,10 +1279,11 @@ type API interface {
|
|||||||
|
|
||||||
// InviteRemoteToChannel invites a remote, or this plugin, as a target for synchronizing. Once invited, the
|
// InviteRemoteToChannel invites a remote, or this plugin, as a target for synchronizing. Once invited, the
|
||||||
// remote will start to receive synchronization messages for any changed content in the specified channel.
|
// remote will start to receive synchronization messages for any changed content in the specified channel.
|
||||||
|
// If `shareIfNotShared` is true, the channel's shared flag will be set, if not already.
|
||||||
//
|
//
|
||||||
// @tag SharedChannels
|
// @tag SharedChannels
|
||||||
// Minimum server version: 9.5
|
// Minimum server version: 9.5
|
||||||
InviteRemoteToChannel(channelID string, remoteID string, userID string) error
|
InviteRemoteToChannel(channelID string, remoteID string, userID string, shareIfNotShared bool) error
|
||||||
|
|
||||||
// UninviteRemoteFromChannel uninvites a remote, or this plugin, such that it will stop receiving sychronization
|
// UninviteRemoteFromChannel uninvites a remote, or this plugin, such that it will stop receiving sychronization
|
||||||
// messages for the channel.
|
// messages for the channel.
|
||||||
|
@ -1351,9 +1351,9 @@ func (api *apiTimerLayer) SyncSharedChannel(channelID string) error {
|
|||||||
return _returnsA
|
return _returnsA
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *apiTimerLayer) InviteRemoteToChannel(channelID string, remoteID string, userID string) error {
|
func (api *apiTimerLayer) InviteRemoteToChannel(channelID string, remoteID string, userID string, shareIfNotShared bool) error {
|
||||||
startTime := timePkg.Now()
|
startTime := timePkg.Now()
|
||||||
_returnsA := api.apiImpl.InviteRemoteToChannel(channelID, remoteID, userID)
|
_returnsA := api.apiImpl.InviteRemoteToChannel(channelID, remoteID, userID, shareIfNotShared)
|
||||||
api.recordTime(startTime, "InviteRemoteToChannel", _returnsA == nil)
|
api.recordTime(startTime, "InviteRemoteToChannel", _returnsA == nil)
|
||||||
return _returnsA
|
return _returnsA
|
||||||
}
|
}
|
||||||
|
@ -6478,14 +6478,15 @@ type Z_InviteRemoteToChannelArgs struct {
|
|||||||
A string
|
A string
|
||||||
B string
|
B string
|
||||||
C string
|
C string
|
||||||
|
D bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Z_InviteRemoteToChannelReturns struct {
|
type Z_InviteRemoteToChannelReturns struct {
|
||||||
A error
|
A error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *apiRPCClient) InviteRemoteToChannel(channelID string, remoteID string, userID string) error {
|
func (g *apiRPCClient) InviteRemoteToChannel(channelID string, remoteID string, userID string, shareIfNotShared bool) error {
|
||||||
_args := &Z_InviteRemoteToChannelArgs{channelID, remoteID, userID}
|
_args := &Z_InviteRemoteToChannelArgs{channelID, remoteID, userID, shareIfNotShared}
|
||||||
_returns := &Z_InviteRemoteToChannelReturns{}
|
_returns := &Z_InviteRemoteToChannelReturns{}
|
||||||
if err := g.client.Call("Plugin.InviteRemoteToChannel", _args, _returns); err != nil {
|
if err := g.client.Call("Plugin.InviteRemoteToChannel", _args, _returns); err != nil {
|
||||||
log.Printf("RPC call to InviteRemoteToChannel API failed: %s", err.Error())
|
log.Printf("RPC call to InviteRemoteToChannel API failed: %s", err.Error())
|
||||||
@ -6495,9 +6496,9 @@ func (g *apiRPCClient) InviteRemoteToChannel(channelID string, remoteID string,
|
|||||||
|
|
||||||
func (s *apiRPCServer) InviteRemoteToChannel(args *Z_InviteRemoteToChannelArgs, returns *Z_InviteRemoteToChannelReturns) error {
|
func (s *apiRPCServer) InviteRemoteToChannel(args *Z_InviteRemoteToChannelArgs, returns *Z_InviteRemoteToChannelReturns) error {
|
||||||
if hook, ok := s.impl.(interface {
|
if hook, ok := s.impl.(interface {
|
||||||
InviteRemoteToChannel(channelID string, remoteID string, userID string) error
|
InviteRemoteToChannel(channelID string, remoteID string, userID string, shareIfNotShared bool) error
|
||||||
}); ok {
|
}); ok {
|
||||||
returns.A = hook.InviteRemoteToChannel(args.A, args.B, args.C)
|
returns.A = hook.InviteRemoteToChannel(args.A, args.B, args.C, args.D)
|
||||||
returns.A = encodableError(returns.A)
|
returns.A = encodableError(returns.A)
|
||||||
} else {
|
} else {
|
||||||
return encodableError(fmt.Errorf("API InviteRemoteToChannel called but not implemented."))
|
return encodableError(fmt.Errorf("API InviteRemoteToChannel called but not implemented."))
|
||||||
|
@ -2806,13 +2806,13 @@ func (_m *API) InstallPlugin(file io.Reader, replace bool) (*model.Manifest, *mo
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// InviteRemoteToChannel provides a mock function with given fields: channelID, remoteID, userID
|
// InviteRemoteToChannel provides a mock function with given fields: channelID, remoteID, userID, shareIfNotShared
|
||||||
func (_m *API) InviteRemoteToChannel(channelID string, remoteID string, userID string) error {
|
func (_m *API) InviteRemoteToChannel(channelID string, remoteID string, userID string, shareIfNotShared bool) error {
|
||||||
ret := _m.Called(channelID, remoteID, userID)
|
ret := _m.Called(channelID, remoteID, userID, shareIfNotShared)
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(string, string, string) error); ok {
|
if rf, ok := ret.Get(0).(func(string, string, string, bool) error); ok {
|
||||||
r0 = rf(channelID, remoteID, userID)
|
r0 = rf(channelID, remoteID, userID, shareIfNotShared)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
r0 = ret.Error(0)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user