Adds support for GMs in shared channels (#31403)
* Adds support for GMs in shared channels * Fix linter * Remove creatorID from slack call --------- Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
07edaa875b
Коммит
43018759e5
@@ -13,6 +13,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost/server/v8/channels/utils"
|
"github.com/mattermost/mattermost/server/v8/channels/utils"
|
||||||
|
"github.com/mattermost/mattermost/server/v8/platform/services/sharedchannel"
|
||||||
"github.com/mattermost/mattermost/server/v8/platform/services/telemetry"
|
"github.com/mattermost/mattermost/server/v8/platform/services/telemetry"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost/server/public/model"
|
"github.com/mattermost/mattermost/server/public/model"
|
||||||
@@ -503,8 +504,8 @@ func (a *App) createDirectChannelWithUser(c request.CTX, user, otherUser *model.
|
|||||||
return channel, nil
|
return channel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) CreateGroupChannel(c request.CTX, userIDs []string, creatorId string) (*model.Channel, *model.AppError) {
|
func (a *App) CreateGroupChannel(c request.CTX, userIDs []string, creatorId string, channelOptions ...model.ChannelOption) (*model.Channel, *model.AppError) {
|
||||||
channel, err := a.createGroupChannel(c, userIDs)
|
channel, err := a.createGroupChannel(c, userIDs, creatorId, channelOptions...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Id == store.ChannelExistsError {
|
if err.Id == store.ChannelExistsError {
|
||||||
return channel, nil
|
return channel, nil
|
||||||
@@ -524,7 +525,11 @@ func (a *App) CreateGroupChannel(c request.CTX, userIDs []string, creatorId stri
|
|||||||
return channel, nil
|
return channel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) createGroupChannel(c request.CTX, userIDs []string) (*model.Channel, *model.AppError) {
|
// creatorId is used to determine if the group channel should have a
|
||||||
|
// shared channel record attached. It can be empty if the caller
|
||||||
|
// doesn't know who the creator is (e.g. the import process) and the
|
||||||
|
// resulting group channel will not be shared
|
||||||
|
func (a *App) createGroupChannel(c request.CTX, userIDs []string, creatorID string, channelOptions ...model.ChannelOption) (*model.Channel, *model.AppError) {
|
||||||
if len(userIDs) > model.ChannelGroupMaxUsers || len(userIDs) < model.ChannelGroupMinUsers {
|
if len(userIDs) > model.ChannelGroupMaxUsers || len(userIDs) < model.ChannelGroupMinUsers {
|
||||||
return nil, model.NewAppError("CreateGroupChannel", "api.channel.create_group.bad_size.app_error", nil, "", http.StatusBadRequest)
|
return nil, model.NewAppError("CreateGroupChannel", "api.channel.create_group.bad_size.app_error", nil, "", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
@@ -538,7 +543,21 @@ func (a *App) createGroupChannel(c request.CTX, userIDs []string) (*model.Channe
|
|||||||
return nil, model.NewAppError("CreateGroupChannel", "api.channel.create_group.bad_user.app_error", nil, "user_ids="+model.ArrayToJSON(userIDs), http.StatusBadRequest)
|
return nil, model.NewAppError("CreateGroupChannel", "api.channel.create_group.bad_user.app_error", nil, "user_ids="+model.ArrayToJSON(userIDs), http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !a.Config().FeatureFlags.EnableSharedChannelsDMs {
|
// extracts the creator and the remotes involved in the GM to
|
||||||
|
// decide how to handle the shared part of the creation
|
||||||
|
var creator *model.User
|
||||||
|
remoteIDs := map[string]bool{}
|
||||||
|
for _, user := range users {
|
||||||
|
if user.Id == creatorID {
|
||||||
|
creator = user
|
||||||
|
}
|
||||||
|
if user.IsRemote() {
|
||||||
|
remoteIDs[*user.RemoteId] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
channelIsShared := len(remoteIDs) > 0
|
||||||
|
|
||||||
|
if channelIsShared && !a.Config().FeatureFlags.EnableSharedChannelsDMs {
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
if user.IsRemote() {
|
if user.IsRemote() {
|
||||||
return nil, model.NewAppError("createGroupChannel", "api.channel.create_group.remote_restricted.app_error", nil, "", http.StatusForbidden)
|
return nil, model.NewAppError("createGroupChannel", "api.channel.create_group.remote_restricted.app_error", nil, "", http.StatusForbidden)
|
||||||
@@ -550,9 +569,10 @@ func (a *App) createGroupChannel(c request.CTX, userIDs []string) (*model.Channe
|
|||||||
Name: model.GetGroupNameFromUserIds(userIDs),
|
Name: model.GetGroupNameFromUserIds(userIDs),
|
||||||
DisplayName: model.GetGroupDisplayNameFromUsers(users, true),
|
DisplayName: model.GetGroupDisplayNameFromUsers(users, true),
|
||||||
Type: model.ChannelTypeGroup,
|
Type: model.ChannelTypeGroup,
|
||||||
|
Shared: model.NewPointer(channelIsShared),
|
||||||
}
|
}
|
||||||
|
|
||||||
channel, nErr := a.Srv().Store().Channel().Save(c, group, *a.Config().TeamSettings.MaxChannelsPerTeam)
|
channel, nErr := a.Srv().Store().Channel().Save(c, group, *a.Config().TeamSettings.MaxChannelsPerTeam, channelOptions...)
|
||||||
if nErr != nil {
|
if nErr != nil {
|
||||||
var invErr *store.ErrInvalidInput
|
var invErr *store.ErrInvalidInput
|
||||||
var cErr *store.ErrConflict
|
var cErr *store.ErrConflict
|
||||||
@@ -608,6 +628,48 @@ func (a *App) createGroupChannel(c request.CTX, userIDs []string) (*model.Channe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When the newly created channel is shared, the creator is local
|
||||||
|
// and one of the participants is remote create a local shared
|
||||||
|
// channel record
|
||||||
|
if channel.IsShared() && creator != nil && !creator.IsRemote() {
|
||||||
|
sc := &model.SharedChannel{
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
TeamId: channel.TeamId,
|
||||||
|
Home: true,
|
||||||
|
ReadOnly: false,
|
||||||
|
ShareName: channel.Name,
|
||||||
|
ShareDisplayName: channel.DisplayName,
|
||||||
|
SharePurpose: channel.Purpose,
|
||||||
|
ShareHeader: channel.Header,
|
||||||
|
CreatorId: creatorID,
|
||||||
|
Type: channel.Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := a.ShareChannel(c, sc); err != nil {
|
||||||
|
c.Logger().Error("Failed to share newly created group channel", mlog.String("channel_id", channel.Id), mlog.Err(err))
|
||||||
|
} else {
|
||||||
|
// if we could successfully share the channel, we invite
|
||||||
|
// the remotes involved to it
|
||||||
|
if sc, _ := a.getSharedChannelsService(); sc != nil {
|
||||||
|
for remoteID := range remoteIDs {
|
||||||
|
rc, err := a.Srv().Store().RemoteCluster().Get(remoteID, false)
|
||||||
|
if err != nil {
|
||||||
|
c.Logger().Error("Failed to send invite to group message channel, can't retrieve remote cluster", mlog.String("channel_id", channel.Id), mlog.String("remote_id", remoteID), mlog.Err(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := []sharedchannel.InviteOption{sharedchannel.WithCreator(creatorID)}
|
||||||
|
for _, user := range users {
|
||||||
|
opts = append(opts, sharedchannel.WithDirectParticipant(user, remoteID))
|
||||||
|
}
|
||||||
|
if err := sc.SendChannelInvite(channel, creatorID, rc, opts...); err != nil {
|
||||||
|
c.Logger().Error("Failed to send invite to group message channel, error sending the invite", mlog.String("channel_id", channel.Id), mlog.String("remote_id", remoteID), mlog.Err(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
a.Srv().Go(func() {
|
a.Srv().Go(func() {
|
||||||
pluginContext := pluginContext(c)
|
pluginContext := pluginContext(c)
|
||||||
a.ch.RunMultiHook(func(hooks plugin.Hooks, _ *model.Manifest) bool {
|
a.ch.RunMultiHook(func(hooks plugin.Hooks, _ *model.Manifest) bool {
|
||||||
|
|||||||
@@ -2105,7 +2105,7 @@ func (a *App) importDirectChannel(rctx request.CTX, data *imports.DirectChannelI
|
|||||||
}
|
}
|
||||||
channel = ch
|
channel = ch
|
||||||
} else {
|
} else {
|
||||||
ch, err2 := a.createGroupChannel(rctx, userIDs)
|
ch, err2 := a.createGroupChannel(rctx, userIDs, "")
|
||||||
if err2 != nil && err2.Id != store.ChannelExistsError {
|
if err2 != nil && err2.Id != store.ChannelExistsError {
|
||||||
return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_group_channel.error", nil, "", http.StatusBadRequest).Wrap(err2)
|
return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_group_channel.error", nil, "", http.StatusBadRequest).Wrap(err2)
|
||||||
}
|
}
|
||||||
@@ -2350,7 +2350,7 @@ func (a *App) importMultipleDirectPostLines(rctx request.CTX, lines []imports.Li
|
|||||||
}
|
}
|
||||||
channel = ch
|
channel = ch
|
||||||
} else if len(userIDs) > 2 {
|
} else if len(userIDs) > 2 {
|
||||||
ch, err = a.createGroupChannel(rctx, userIDs)
|
ch, err = a.createGroupChannel(rctx, userIDs, "")
|
||||||
if err != nil && err.Id != store.ChannelExistsError {
|
if err != nil && err.Id != store.ChannelExistsError {
|
||||||
return line.LineNumber, model.NewAppError("BulkImport", "app.import.import_direct_post.create_group_channel.error", nil, "", http.StatusBadRequest).Wrap(err)
|
return line.LineNumber, model.NewAppError("BulkImport", "app.import.import_direct_post.create_group_channel.error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4050,7 +4050,7 @@ func TestImportImportDirectChannel(t *testing.T) {
|
|||||||
th.BasicUser2.Id,
|
th.BasicUser2.Id,
|
||||||
user3.Id,
|
user3.Id,
|
||||||
}
|
}
|
||||||
channel, appErr := th.App.createGroupChannel(th.Context, userIDs)
|
channel, appErr := th.App.createGroupChannel(th.Context, userIDs, th.BasicUser.Id)
|
||||||
require.Equal(t, appErr.Id, store.ChannelExistsError)
|
require.Equal(t, appErr.Id, store.ChannelExistsError)
|
||||||
require.Equal(t, channel.Header, *data.Header)
|
require.Equal(t, channel.Header, *data.Header)
|
||||||
})
|
})
|
||||||
@@ -4677,7 +4677,7 @@ func TestImportImportDirectPost(t *testing.T) {
|
|||||||
th.BasicUser2.Id,
|
th.BasicUser2.Id,
|
||||||
user3.Id,
|
user3.Id,
|
||||||
}
|
}
|
||||||
channel, appErr = th.App.createGroupChannel(th.Context, userIDs)
|
channel, appErr = th.App.createGroupChannel(th.Context, userIDs, th.BasicUser.Id)
|
||||||
require.Equal(t, appErr.Id, store.ChannelExistsError)
|
require.Equal(t, appErr.Id, store.ChannelExistsError)
|
||||||
groupChannel = channel
|
groupChannel = channel
|
||||||
|
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ func handleInvitation(ps *PlatformService, syncService SharedChannelServiceIFace
|
|||||||
return errors.Wrap(err, fmt.Sprintf("couldn't find remote cluster %s, for creating shared channel invitation for a DM", *participant.RemoteId))
|
return errors.Wrap(err, fmt.Sprintf("couldn't find remote cluster %s, for creating shared channel invitation for a DM", *participant.RemoteId))
|
||||||
}
|
}
|
||||||
|
|
||||||
return syncService.SendChannelInvite(channel, creator.Id, rc, sharedchannel.WithDirectParticipant(creator), sharedchannel.WithDirectParticipant(participant))
|
return syncService.SendChannelInvite(channel, creator.Id, rc, sharedchannel.WithDirectParticipant(creator, rc.RemoteId), sharedchannel.WithDirectParticipant(participant, rc.RemoteId))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUserFromEvent(ps *PlatformService, event *model.WebSocketEvent, key string) (*model.User, error) {
|
func getUserFromEvent(ps *PlatformService, event *model.WebSocketEvent, key string) (*model.User, error) {
|
||||||
|
|||||||
@@ -681,7 +681,7 @@ func TestPreparePostForClient(t *testing.T) {
|
|||||||
directChannel, err := th.App.createDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
|
directChannel, err := th.App.createDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
groupChannel, err := th.App.createGroupChannel(th.Context, []string{th.BasicUser.Id, th.BasicUser2.Id, th.CreateUser().Id})
|
groupChannel, err := th.App.createGroupChannel(th.Context, []string{th.BasicUser.Id, th.BasicUser2.Id, th.CreateUser().Id}, th.BasicUser.Id)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
|
|||||||
@@ -1162,7 +1162,7 @@ func TestCreatePost(t *testing.T) {
|
|||||||
user1 := th.CreateUser()
|
user1 := th.CreateUser()
|
||||||
user2 := th.CreateUser()
|
user2 := th.CreateUser()
|
||||||
user3 := th.CreateUser()
|
user3 := th.CreateUser()
|
||||||
gm, appErr := th.App.createGroupChannel(th.Context, []string{user1.Id, user2.Id, user3.Id})
|
gm, appErr := th.App.createGroupChannel(th.Context, []string{user1.Id, user2.Id, user3.Id}, user1.Id)
|
||||||
require.Nil(t, appErr)
|
require.Nil(t, appErr)
|
||||||
require.NotNil(t, gm)
|
require.NotNil(t, gm)
|
||||||
|
|
||||||
@@ -2423,7 +2423,7 @@ func TestCountMentionsFromPost(t *testing.T) {
|
|||||||
user2 := th.BasicUser2
|
user2 := th.BasicUser2
|
||||||
user3 := th.SystemAdminUser
|
user3 := th.SystemAdminUser
|
||||||
|
|
||||||
channel, err := th.App.createGroupChannel(th.Context, []string{user1.Id, user2.Id, user3.Id})
|
channel, err := th.App.createGroupChannel(th.Context, []string{user1.Id, user2.Id, user3.Id}, user1.Id)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
||||||
|
|||||||
@@ -2858,11 +2858,11 @@ func (s *RetryLayerChannelStore) Restore(channelID string, timestamp int64) erro
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RetryLayerChannelStore) Save(rctx request.CTX, channel *model.Channel, maxChannelsPerTeam int64) (*model.Channel, error) {
|
func (s *RetryLayerChannelStore) Save(rctx request.CTX, channel *model.Channel, maxChannelsPerTeam int64, channelOptions ...model.ChannelOption) (*model.Channel, error) {
|
||||||
|
|
||||||
tries := 0
|
tries := 0
|
||||||
for {
|
for {
|
||||||
result, err := s.ChannelStore.Save(rctx, channel, maxChannelsPerTeam)
|
result, err := s.ChannelStore.Save(rctx, channel, maxChannelsPerTeam, channelOptions...)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ func (c *SearchChannelStore) indexChannel(rctx request.CTX, channel *model.Chann
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *SearchChannelStore) Save(rctx request.CTX, channel *model.Channel, maxChannels int64) (*model.Channel, error) {
|
func (c *SearchChannelStore) Save(rctx request.CTX, channel *model.Channel, maxChannels int64, channelOptions ...model.ChannelOption) (*model.Channel, error) {
|
||||||
newChannel, err := c.ChannelStore.Save(rctx, channel, maxChannels)
|
newChannel, err := c.ChannelStore.Save(rctx, channel, maxChannels, channelOptions...)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.indexChannel(rctx, newChannel)
|
c.indexChannel(rctx, newChannel)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -616,7 +616,11 @@ func (s SqlChannelStore) upsertPublicChannelT(transaction *sqlxTxWrapper, channe
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save writes the (non-direct) channel to the database.
|
// Save writes the (non-direct) channel to the database.
|
||||||
func (s SqlChannelStore) Save(rctx request.CTX, channel *model.Channel, maxChannelsPerTeam int64) (_ *model.Channel, err error) {
|
func (s SqlChannelStore) Save(rctx request.CTX, channel *model.Channel, maxChannelsPerTeam int64, channelOptions ...model.ChannelOption) (_ *model.Channel, err error) {
|
||||||
|
for _, option := range channelOptions {
|
||||||
|
option(channel)
|
||||||
|
}
|
||||||
|
|
||||||
if channel.DeleteAt != 0 {
|
if channel.DeleteAt != 0 {
|
||||||
return nil, store.NewErrInvalidInput("Channel", "DeleteAt", channel.DeleteAt)
|
return nil, store.NewErrInvalidInput("Channel", "DeleteAt", channel.DeleteAt)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ type TeamStore interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ChannelStore interface {
|
type ChannelStore interface {
|
||||||
Save(rctx request.CTX, channel *model.Channel, maxChannelsPerTeam int64) (*model.Channel, error)
|
Save(rctx request.CTX, channel *model.Channel, maxChannelsPerTeam int64, channelOptions ...model.ChannelOption) (*model.Channel, error)
|
||||||
CreateDirectChannel(ctx request.CTX, userID *model.User, otherUserID *model.User, channelOptions ...model.ChannelOption) (*model.Channel, error)
|
CreateDirectChannel(ctx request.CTX, userID *model.User, otherUserID *model.User, channelOptions ...model.ChannelOption) (*model.Channel, error)
|
||||||
SaveDirectChannel(ctx request.CTX, channel *model.Channel, member1 *model.ChannelMember, member2 *model.ChannelMember) (*model.Channel, error)
|
SaveDirectChannel(ctx request.CTX, channel *model.Channel, member1 *model.ChannelMember, member2 *model.ChannelMember) (*model.Channel, error)
|
||||||
Update(ctx request.CTX, channel *model.Channel) (*model.Channel, error)
|
Update(ctx request.CTX, channel *model.Channel) (*model.Channel, error)
|
||||||
|
|||||||
@@ -2537,9 +2537,16 @@ func (_m *ChannelStore) Restore(channelID string, timestamp int64) error {
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save provides a mock function with given fields: rctx, channel, maxChannelsPerTeam
|
// Save provides a mock function with given fields: rctx, channel, maxChannelsPerTeam, channelOptions
|
||||||
func (_m *ChannelStore) Save(rctx request.CTX, channel *model.Channel, maxChannelsPerTeam int64) (*model.Channel, error) {
|
func (_m *ChannelStore) Save(rctx request.CTX, channel *model.Channel, maxChannelsPerTeam int64, channelOptions ...model.ChannelOption) (*model.Channel, error) {
|
||||||
ret := _m.Called(rctx, channel, maxChannelsPerTeam)
|
_va := make([]interface{}, len(channelOptions))
|
||||||
|
for _i := range channelOptions {
|
||||||
|
_va[_i] = channelOptions[_i]
|
||||||
|
}
|
||||||
|
var _ca []interface{}
|
||||||
|
_ca = append(_ca, rctx, channel, maxChannelsPerTeam)
|
||||||
|
_ca = append(_ca, _va...)
|
||||||
|
ret := _m.Called(_ca...)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
panic("no return value specified for Save")
|
panic("no return value specified for Save")
|
||||||
@@ -2547,19 +2554,19 @@ func (_m *ChannelStore) Save(rctx request.CTX, channel *model.Channel, maxChanne
|
|||||||
|
|
||||||
var r0 *model.Channel
|
var r0 *model.Channel
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(0).(func(request.CTX, *model.Channel, int64) (*model.Channel, error)); ok {
|
if rf, ok := ret.Get(0).(func(request.CTX, *model.Channel, int64, ...model.ChannelOption) (*model.Channel, error)); ok {
|
||||||
return rf(rctx, channel, maxChannelsPerTeam)
|
return rf(rctx, channel, maxChannelsPerTeam, channelOptions...)
|
||||||
}
|
}
|
||||||
if rf, ok := ret.Get(0).(func(request.CTX, *model.Channel, int64) *model.Channel); ok {
|
if rf, ok := ret.Get(0).(func(request.CTX, *model.Channel, int64, ...model.ChannelOption) *model.Channel); ok {
|
||||||
r0 = rf(rctx, channel, maxChannelsPerTeam)
|
r0 = rf(rctx, channel, maxChannelsPerTeam, channelOptions...)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).(*model.Channel)
|
r0 = ret.Get(0).(*model.Channel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(request.CTX, *model.Channel, int64) error); ok {
|
if rf, ok := ret.Get(1).(func(request.CTX, *model.Channel, int64, ...model.ChannelOption) error); ok {
|
||||||
r1 = rf(rctx, channel, maxChannelsPerTeam)
|
r1 = rf(rctx, channel, maxChannelsPerTeam, channelOptions...)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2394,10 +2394,10 @@ func (s *TimerLayerChannelStore) Restore(channelID string, timestamp int64) erro
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TimerLayerChannelStore) Save(rctx request.CTX, channel *model.Channel, maxChannelsPerTeam int64) (*model.Channel, error) {
|
func (s *TimerLayerChannelStore) Save(rctx request.CTX, channel *model.Channel, maxChannelsPerTeam int64, channelOptions ...model.ChannelOption) (*model.Channel, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
result, err := s.ChannelStore.Save(rctx, channel, maxChannelsPerTeam)
|
result, err := s.ChannelStore.Save(rctx, channel, maxChannelsPerTeam, channelOptions...)
|
||||||
|
|
||||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||||
if s.Root.Metrics != nil {
|
if s.Root.Metrics != nil {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ type channelInviteMsg struct {
|
|||||||
Header string `json:"header"`
|
Header string `json:"header"`
|
||||||
Purpose string `json:"purpose"`
|
Purpose string `json:"purpose"`
|
||||||
Type model.ChannelType `json:"type"`
|
Type model.ChannelType `json:"type"`
|
||||||
|
CreatorID string `json:"creator_id"`
|
||||||
DirectParticipantIDs []string `json:"direct_participant_ids"`
|
DirectParticipantIDs []string `json:"direct_participant_ids"`
|
||||||
DirectParticipants []*model.User `json:"direct_participants"`
|
DirectParticipants []*model.User `json:"direct_participants"`
|
||||||
}
|
}
|
||||||
@@ -41,16 +42,24 @@ func (cim channelInviteMsg) DirectParticipantsMap() map[string]*model.User {
|
|||||||
|
|
||||||
type InviteOption func(msg *channelInviteMsg)
|
type InviteOption func(msg *channelInviteMsg)
|
||||||
|
|
||||||
func WithDirectParticipant(participant *model.User) InviteOption {
|
func WithDirectParticipant(participant *model.User, remoteID string) InviteOption {
|
||||||
return func(msg *channelInviteMsg) {
|
return func(msg *channelInviteMsg) {
|
||||||
msg.DirectParticipantIDs = append(msg.DirectParticipantIDs, participant.Id)
|
msg.DirectParticipantIDs = append(msg.DirectParticipantIDs, participant.Id)
|
||||||
// if the participant is local, send it as part of the invite payload
|
// if the participant doesn't belong to the remote we're
|
||||||
if !participant.IsRemote() {
|
// sending the invite to, send it as part of the invite
|
||||||
|
// payload
|
||||||
|
if participant.GetRemoteID() != remoteID {
|
||||||
msg.DirectParticipants = append(msg.DirectParticipants, sanitizeUserForSync(participant))
|
msg.DirectParticipants = append(msg.DirectParticipants, sanitizeUserForSync(participant))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithCreator(creatorID string) InviteOption {
|
||||||
|
return func(msg *channelInviteMsg) {
|
||||||
|
msg.CreatorID = creatorID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SendChannelInvite asynchronously sends a channel invite to a remote cluster. The remote cluster is
|
// SendChannelInvite asynchronously sends a channel invite to a remote cluster. The remote cluster is
|
||||||
// expected to create a new channel with the same channel id, and respond with status OK.
|
// expected to create a new channel with the same channel id, and respond with status OK.
|
||||||
// If an error occurs on the remote cluster then an ephemeral message is posted to in the channel for userId.
|
// If an error occurs on the remote cluster then an ephemeral message is posted to in the channel for userId.
|
||||||
@@ -241,9 +250,9 @@ func (scs *Service) onReceiveChannelInvite(msg model.RemoteClusterMsg, rc *model
|
|||||||
// sanity check to ensure the channel returned has the expected id. Otherwise sync will not work as expected and will fail
|
// sanity check to ensure the channel returned has the expected id. Otherwise sync will not work as expected and will fail
|
||||||
// silently.
|
// silently.
|
||||||
if invite.ChannelId != channel.Id {
|
if invite.ChannelId != channel.Id {
|
||||||
// as of this writing, this scenario should only be possible if the invite included a DM channel invitation with a
|
// as of this writing, this scenario should only be possible if the invite included a DM or GM channel
|
||||||
// combination of two user ids (one remote, one local) that already have a DM on this server. Very unlikely unless
|
// invitation with a combination of user ids that already have a DM or GM on this server. Very unlikely
|
||||||
// the remote is compromised AND has knowledge of the local user id.
|
// unless the remote is compromised AND has knowledge of the local user ids.
|
||||||
// Another possibility would be an actual user ID collision between two servers, where the likelihood is
|
// Another possibility would be an actual user ID collision between two servers, where the likelihood is
|
||||||
// infinitesimally small
|
// infinitesimally small
|
||||||
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "Channel invite failed - channel created/fetched with wrong id",
|
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "Channel invite failed - channel created/fetched with wrong id",
|
||||||
@@ -254,7 +263,7 @@ func (scs *Service) onReceiveChannelInvite(msg model.RemoteClusterMsg, rc *model
|
|||||||
mlog.String("team_id", invite.TeamId),
|
mlog.String("team_id", invite.TeamId),
|
||||||
mlog.Array("dm_partics", invite.DirectParticipantIDs),
|
mlog.Array("dm_partics", invite.DirectParticipantIDs),
|
||||||
)
|
)
|
||||||
return fmt.Errorf("cannot create shared channel (DM channel_id=%s): %w", invite.ChannelId, model.ErrChannelAlreadyExists)
|
return fmt.Errorf("cannot create shared channel (channel_id=%s channel_type=%s): %w", invite.ChannelId, invite.Type, model.ErrChannelAlreadyExists)
|
||||||
}
|
}
|
||||||
|
|
||||||
// mark the newly created channel read-only if requested in the invite
|
// mark the newly created channel read-only if requested in the invite
|
||||||
@@ -303,10 +312,14 @@ func (scs *Service) onReceiveChannelInvite(msg model.RemoteClusterMsg, rc *model
|
|||||||
return fmt.Errorf("cannot restore deleted shared channel remote (channel_id=%s): %w", invite.ChannelId, err)
|
return fmt.Errorf("cannot restore deleted shared channel remote (channel_id=%s): %w", invite.ChannelId, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
creatorID := channel.CreatorId
|
||||||
|
if creatorID == "" {
|
||||||
|
creatorID = invite.CreatorID
|
||||||
|
}
|
||||||
scr := &model.SharedChannelRemote{
|
scr := &model.SharedChannelRemote{
|
||||||
Id: model.NewId(),
|
Id: model.NewId(),
|
||||||
ChannelId: channel.Id,
|
ChannelId: channel.Id,
|
||||||
CreatorId: channel.CreatorId,
|
CreatorId: creatorID,
|
||||||
IsInviteAccepted: true,
|
IsInviteAccepted: true,
|
||||||
IsInviteConfirmed: true,
|
IsInviteConfirmed: true,
|
||||||
RemoteId: rc.RemoteId,
|
RemoteId: rc.RemoteId,
|
||||||
@@ -335,6 +348,10 @@ func (scs *Service) handleChannelCreation(invite channelInviteMsg, rc *model.Rem
|
|||||||
return scs.createDirectChannel(invite, rc)
|
return scs.createDirectChannel(invite, rc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if invite.Type == model.ChannelTypeGroup {
|
||||||
|
return scs.createGroupChannel(invite, rc)
|
||||||
|
}
|
||||||
|
|
||||||
teamId := rc.DefaultTeamId
|
teamId := rc.DefaultTeamId
|
||||||
// if the remote doesn't have a teamId associated and until the
|
// if the remote doesn't have a teamId associated and until the
|
||||||
// acceptance of an invite includes selecting a team, we use the
|
// acceptance of an invite includes selecting a team, we use the
|
||||||
@@ -472,3 +489,62 @@ func (scs *Service) createDirectChannel(invite channelInviteMsg, rc *model.Remot
|
|||||||
|
|
||||||
return channel, true, nil
|
return channel, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createGroupChannel creates a DM channel, or fetches an existing channel, and returns the channel plus a boolean
|
||||||
|
// indicating if the channel is new.
|
||||||
|
func (scs *Service) createGroupChannel(invite channelInviteMsg, rc *model.RemoteCluster) (*model.Channel, bool, error) {
|
||||||
|
if len(invite.DirectParticipantIDs) > model.ChannelGroupMaxUsers || len(invite.DirectParticipantIDs) < model.ChannelGroupMinUsers {
|
||||||
|
return nil, false, fmt.Errorf("cannot create group channel `%s` bad participant count `%d`", invite.ChannelId, len(invite.DirectParticipantIDs))
|
||||||
|
}
|
||||||
|
|
||||||
|
participantsMap := invite.DirectParticipantsMap()
|
||||||
|
|
||||||
|
remoteIDMap := map[string]bool{}
|
||||||
|
hasLocalUsers := false
|
||||||
|
for _, participantID := range invite.DirectParticipantIDs {
|
||||||
|
user, err := scs.getOrCreateUser(participantID, participantsMap, rc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("cannot create group channel `%s` from invite: %w", invite.ChannelId, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we keep track of the origin of the users to check if the
|
||||||
|
// invite is valid
|
||||||
|
if user.IsRemote() {
|
||||||
|
remoteIDMap[user.GetRemoteID()] = true
|
||||||
|
} else {
|
||||||
|
hasLocalUsers = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the invite doesn't contain remote users, GM should not be created via remote invite
|
||||||
|
if len(remoteIDMap) == 0 {
|
||||||
|
return nil, false, fmt.Errorf("cannot create group channel `%s` there are no remote users", invite.ChannelId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the channel doesn't contain local users, the GM channel doesn't belong to this server
|
||||||
|
if !hasLocalUsers {
|
||||||
|
return nil, false, fmt.Errorf("cannot create group channel `%s` there are no local users", invite.ChannelId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if this DM already exists.
|
||||||
|
channelName := model.GetGroupNameFromUserIds(invite.DirectParticipantIDs)
|
||||||
|
channelExists, err := scs.server.GetStore().Channel().GetByName("", channelName, true)
|
||||||
|
if err != nil && !isNotFoundError(err) {
|
||||||
|
return nil, false, fmt.Errorf("cannot check GM channel exists (%s): %w", channelName, err)
|
||||||
|
}
|
||||||
|
if channelExists != nil {
|
||||||
|
if channelExists.Id == invite.ChannelId {
|
||||||
|
return channelExists, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false, fmt.Errorf("cannot create group channel `%s`: channel exists with wrong id", channelName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the channel
|
||||||
|
channel, appErr := scs.app.CreateGroupChannel(request.EmptyContext(scs.server.Log()), invite.DirectParticipantIDs, invite.CreatorID, model.WithID(invite.ChannelId))
|
||||||
|
if appErr != nil {
|
||||||
|
return nil, false, fmt.Errorf("cannot create group channel `%s`: %w", invite.ChannelId, appErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return channel, true, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -102,6 +102,45 @@ func (_m *MockAppIface) CreateChannelWithUser(c request.CTX, channel *model.Chan
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateGroupChannel provides a mock function with given fields: c, userIDs, creatorId, channelOptions
|
||||||
|
func (_m *MockAppIface) CreateGroupChannel(c request.CTX, userIDs []string, creatorId string, channelOptions ...model.ChannelOption) (*model.Channel, *model.AppError) {
|
||||||
|
_va := make([]interface{}, len(channelOptions))
|
||||||
|
for _i := range channelOptions {
|
||||||
|
_va[_i] = channelOptions[_i]
|
||||||
|
}
|
||||||
|
var _ca []interface{}
|
||||||
|
_ca = append(_ca, c, userIDs, creatorId)
|
||||||
|
_ca = append(_ca, _va...)
|
||||||
|
ret := _m.Called(_ca...)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for CreateGroupChannel")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 *model.Channel
|
||||||
|
var r1 *model.AppError
|
||||||
|
if rf, ok := ret.Get(0).(func(request.CTX, []string, string, ...model.ChannelOption) (*model.Channel, *model.AppError)); ok {
|
||||||
|
return rf(c, userIDs, creatorId, channelOptions...)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(request.CTX, []string, string, ...model.ChannelOption) *model.Channel); ok {
|
||||||
|
r0 = rf(c, userIDs, creatorId, channelOptions...)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(*model.Channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(request.CTX, []string, string, ...model.ChannelOption) *model.AppError); ok {
|
||||||
|
r1 = rf(c, userIDs, creatorId, channelOptions...)
|
||||||
|
} else {
|
||||||
|
if ret.Get(1) != nil {
|
||||||
|
r1 = ret.Get(1).(*model.AppError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
// CreatePost provides a mock function with given fields: c, post, channel, flags
|
// CreatePost provides a mock function with given fields: c, post, channel, flags
|
||||||
func (_m *MockAppIface) CreatePost(c request.CTX, post *model.Post, channel *model.Channel, flags model.CreatePostFlags) (*model.Post, *model.AppError) {
|
func (_m *MockAppIface) CreatePost(c request.CTX, post *model.Post, channel *model.Channel, flags model.CreatePostFlags) (*model.Post, *model.AppError) {
|
||||||
ret := _m.Called(c, post, channel, flags)
|
ret := _m.Called(c, post, channel, flags)
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ 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)
|
||||||
GetOrCreateDirectChannel(c request.CTX, userId, otherUserId string, channelOptions ...model.ChannelOption) (*model.Channel, *model.AppError)
|
GetOrCreateDirectChannel(c request.CTX, userId, otherUserId string, channelOptions ...model.ChannelOption) (*model.Channel, *model.AppError)
|
||||||
|
CreateGroupChannel(c request.CTX, userIDs []string, creatorId string, channelOptions ...model.ChannelOption) (*model.Channel, *model.AppError)
|
||||||
UserCanSeeOtherUser(c request.CTX, userID string, otherUserId string) (bool, *model.AppError)
|
UserCanSeeOtherUser(c request.CTX, userID string, otherUserId string) (bool, *model.AppError)
|
||||||
AddUserToChannel(c request.CTX, user *model.User, channel *model.Channel, skipTeamMemberIntegrityCheck bool) (*model.ChannelMember, *model.AppError)
|
AddUserToChannel(c request.CTX, user *model.User, channel *model.Channel, skipTeamMemberIntegrityCheck bool) (*model.ChannelMember, *model.AppError)
|
||||||
AddUserToTeamByTeamId(c request.CTX, teamId string, user *model.User) *model.AppError
|
AddUserToTeamByTeamId(c request.CTX, teamId string, user *model.User) *model.AppError
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ func (scs *Service) processSyncMessage(c request.CTX, syncMsg *model.SyncMsg, rc
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if targetChannel.Type != model.ChannelTypeDirect && team == nil {
|
if (targetChannel.Type != model.ChannelTypeDirect && targetChannel.Type != model.ChannelTypeGroup) && team == nil {
|
||||||
var err2 error
|
var err2 error
|
||||||
team, err2 = scs.server.GetStore().Channel().GetTeamForChannel(syncMsg.ChannelId)
|
team, err2 = scs.server.GetStore().Channel().GetTeamForChannel(syncMsg.ChannelId)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ type Actions struct {
|
|||||||
AddUserToChannel func(request.CTX, *model.User, *model.Channel, bool) (*model.ChannelMember, *model.AppError)
|
AddUserToChannel func(request.CTX, *model.User, *model.Channel, bool) (*model.ChannelMember, *model.AppError)
|
||||||
JoinUserToTeam func(*model.Team, *model.User, string) (*model.TeamMember, *model.AppError)
|
JoinUserToTeam func(*model.Team, *model.User, string) (*model.TeamMember, *model.AppError)
|
||||||
CreateDirectChannel func(request.CTX, string, string, ...model.ChannelOption) (*model.Channel, *model.AppError)
|
CreateDirectChannel func(request.CTX, string, string, ...model.ChannelOption) (*model.Channel, *model.AppError)
|
||||||
CreateGroupChannel func(request.CTX, []string) (*model.Channel, *model.AppError)
|
CreateGroupChannel func(request.CTX, []string, string, ...model.ChannelOption) (*model.Channel, *model.AppError)
|
||||||
CreateChannel func(*model.Channel, bool) (*model.Channel, *model.AppError)
|
CreateChannel func(*model.Channel, bool) (*model.Channel, *model.AppError)
|
||||||
DoUploadFile func(time.Time, string, string, string, string, []byte) (*model.FileInfo, *model.AppError)
|
DoUploadFile func(time.Time, string, string, string, string, []byte) (*model.FileInfo, *model.AppError)
|
||||||
GenerateThumbnailImage func(request.CTX, image.Image, string, string)
|
GenerateThumbnailImage func(request.CTX, image.Image, string, string)
|
||||||
@@ -764,7 +764,7 @@ func (si *SlackImporter) oldImportChannel(rctx request.CTX, channel *model.Chann
|
|||||||
if creator == nil {
|
if creator == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
sc, err := si.actions.CreateGroupChannel(rctx, members)
|
sc, err := si.actions.CreateGroupChannel(rctx, members, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user