summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGeorge Goldberg <george@gberg.me>2017-09-04 18:54:24 +0100
committerGitHub <noreply@github.com>2017-09-04 18:54:24 +0100
commitba2868775d2476813fb8f48156d5f232a101f39d (patch)
tree78dfe43062f63a34a1749854ea99fb5a8e4542ff
parent1e78ff831e8aba2ff98dcd85709f06eca006f0a4 (diff)
downloadchat-ba2868775d2476813fb8f48156d5f232a101f39d.tar.gz
chat-ba2868775d2476813fb8f48156d5f232a101f39d.tar.bz2
chat-ba2868775d2476813fb8f48156d5f232a101f39d.zip
PLT-7216: CLI Command to move channels between teams. (#7149)
* PLT-7216: CLI Command to move channels between teams. * Add comment
-rw-r--r--app/channel.go30
-rw-r--r--app/channel_test.go43
-rw-r--r--cmd/platform/channel.go77
-rw-r--r--i18n/en.json4
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"
},