diff options
-rw-r--r-- | app/channel.go | 30 | ||||
-rw-r--r-- | app/channel_test.go | 43 | ||||
-rw-r--r-- | cmd/platform/channel.go | 77 | ||||
-rw-r--r-- | i18n/en.json | 4 |
4 files changed, 154 insertions, 0 deletions
diff --git a/app/channel.go b/app/channel.go index 3c8eaf771..8da0ca61c 100644 --- a/app/channel.go +++ b/app/channel.go @@ -1201,6 +1201,36 @@ func PermanentDeleteChannel(channel *model.Channel) *model.AppError { return nil } +// This function is intended for use from the CLI. It is not robust against people joining the channel while the move +// is in progress, and therefore should not be used from the API without first fixing this potential race condition. +func MoveChannel(team *model.Team, channel *model.Channel) *model.AppError { + // Check that all channel members are in the destination team. + if channelMembers, err := GetChannelMembersPage(channel.Id, 0, 10000000); err != nil { + return err + } else { + channelMemberIds := []string{} + for _, channelMember := range *channelMembers { + channelMemberIds = append(channelMemberIds, channelMember.UserId) + } + + if teamMembers, err2 := GetTeamMembersByIds(team.Id, channelMemberIds); err != nil { + return err2 + } else { + if len(teamMembers) != len(*channelMembers) { + return model.NewAppError("MoveChannel", "app.channel.move_channel.members_do_not_match.error", nil, "", http.StatusInternalServerError) + } + } + } + + // Change the Team ID of the channel. + channel.TeamId = team.Id + if result := <-Srv.Store.Channel().Update(channel); result.Err != nil { + return result.Err + } + + return nil +} + func GetPinnedPosts(channelId string) (*model.PostList, *model.AppError) { if result := <-Srv.Store.Channel().GetPinnedPosts(channelId); result.Err != nil { return nil, result.Err diff --git a/app/channel_test.go b/app/channel_test.go index 438eb959b..b43207b00 100644 --- a/app/channel_test.go +++ b/app/channel_test.go @@ -64,3 +64,46 @@ func TestPermanentDeleteChannel(t *testing.T) { t.Error("outgoing webhook wasn't deleted") } } + +func TestMoveChannel(t *testing.T) { + th := Setup().InitBasic() + + sourceTeam := th.CreateTeam() + targetTeam := th.CreateTeam() + channel1 := th.CreateChannel(sourceTeam) + defer func() { + PermanentDeleteChannel(channel1) + PermanentDeleteTeam(sourceTeam) + PermanentDeleteTeam(targetTeam) + }() + + if _, err := AddUserToTeam(sourceTeam.Id, th.BasicUser.Id, ""); err != nil { + t.Fatal(err) + } + if _, err := AddUserToTeam(sourceTeam.Id, th.BasicUser2.Id, ""); err != nil { + t.Fatal(err) + } + + if _, err := AddUserToTeam(targetTeam.Id, th.BasicUser.Id, ""); err != nil { + t.Fatal(err) + } + + if _, err := AddUserToChannel(th.BasicUser, channel1); err != nil { + t.Fatal(err) + } + if _, err := AddUserToChannel(th.BasicUser2, channel1); err != nil { + t.Fatal(err) + } + + if err := MoveChannel(targetTeam, channel1); err == nil { + t.Fatal("Should have failed due to mismatched members.") + } + + if _, err := AddUserToTeam(targetTeam.Id, th.BasicUser2.Id, ""); err != nil { + t.Fatal(err) + } + + if err := MoveChannel(targetTeam, channel1); err != nil { + t.Fatal(err) + } +} diff --git a/cmd/platform/channel.go b/cmd/platform/channel.go index 215d0c863..c9256a37f 100644 --- a/cmd/platform/channel.go +++ b/cmd/platform/channel.go @@ -71,6 +71,16 @@ Archived channels are appended with ' (archived)'.`, RunE: listChannelsCmdF, } +var moveChannelsCmd = &cobra.Command{ + Use: "move [team] [channels]", + Short: "Moves channels to the specified team", + Long: `Moves the provided channels to the specified team. +Validates that all users in the channel belong to the target team. Incoming/Outgoing webhooks are moved along with the channel. +Channels can be specified by [team]:[channel]. ie. myteam:mychannel or by channel ID.`, + Example: " channel move newteam oldteam:mychannel", + RunE: moveChannelsCmdF, +} + var restoreChannelsCmd = &cobra.Command{ Use: "restore [channels]", Short: "Restore some channels", @@ -109,6 +119,7 @@ func init() { archiveChannelsCmd, deleteChannelsCmd, listChannelsCmd, + moveChannelsCmd, restoreChannelsCmd, modifyChannelCmd, ) @@ -300,6 +311,72 @@ func deleteChannel(channel *model.Channel) *model.AppError { return app.PermanentDeleteChannel(channel) } +func moveChannelsCmdF(cmd *cobra.Command, args []string) error { + if err := initDBCommandContextCobra(cmd); err != nil { + return err + } + + if len(args) < 2 { + return errors.New("Enter the destination team and at least one channel to move.") + } + + team := getTeamFromTeamArg(args[0]) + if team == nil { + return errors.New("Unable to find destination team '" + args[0] + "'") + } + + channels := getChannelsFromChannelArgs(args[1:]) + for i, channel := range channels { + if channel == nil { + CommandPrintErrorln("Unable to find channel '" + args[i] + "'") + continue + } + if err := moveChannel(team, channel); err != nil { + CommandPrintErrorln("Unable to move channel '" + channel.Name + "' error: " + err.Error()) + } else { + CommandPrettyPrintln("Moved channel '" + channel.Name + "'") + } + } + + return nil +} + +func moveChannel(team *model.Team, channel *model.Channel) *model.AppError { + oldTeamId := channel.TeamId + + if err := app.MoveChannel(team, channel); err != nil { + return err + } + + if incomingWebhooks, err := app.GetIncomingWebhooksForTeamPage(oldTeamId, 0, 10000000); err != nil { + return err + } else { + for _, webhook := range incomingWebhooks { + if webhook.ChannelId == channel.Id { + webhook.TeamId = team.Id + if result := <-app.Srv.Store.Webhook().UpdateIncoming(webhook); result.Err != nil { + CommandPrintErrorln("Failed to move incoming webhook '" + webhook.Id + "' to new team.") + } + } + } + } + + if outgoingWebhooks, err := app.GetOutgoingWebhooksForTeamPage(oldTeamId, 0, 10000000); err != nil { + return err + } else { + for _, webhook := range outgoingWebhooks { + if webhook.ChannelId == channel.Id { + webhook.TeamId = team.Id + if result := <-app.Srv.Store.Webhook().UpdateOutgoing(webhook); result.Err != nil { + CommandPrintErrorln("Failed to move outgoing webhook '" + webhook.Id + "' to new team.") + } + } + } + } + + return nil +} + func listChannelsCmdF(cmd *cobra.Command, args []string) error { if err := initDBCommandContextCobra(cmd); err != nil { return err diff --git a/i18n/en.json b/i18n/en.json index da2b50bbd..138241c5a 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -3060,6 +3060,10 @@ "translation": "Must specify the team ID to create a channel" }, { + "id": "app.channel.move_channel.members_do_not_match.error", + "translation": "Cannot move a channel unless all its members are already members of the destination team." + }, + { "id": "app.channel.post_update_channel_purpose_message.post.error", "translation": "Failed to post channel purpose message" }, |