diff options
author | Joram Wilander <jwawilander@gmail.com> | 2017-02-02 09:04:36 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-02-02 09:04:36 -0500 |
commit | 609d4f43d9eef504d852fbf02af5473b0d1424c8 (patch) | |
tree | 63fe24f08d5364501d3afed7c44b6739719d86e3 | |
parent | 2ac4f36587b8b217dcfd53e67c650c8ad27c75df (diff) | |
download | chat-609d4f43d9eef504d852fbf02af5473b0d1424c8.tar.gz chat-609d4f43d9eef504d852fbf02af5473b0d1424c8.tar.bz2 chat-609d4f43d9eef504d852fbf02af5473b0d1424c8.zip |
Implement POST /channels endpoint for APIv4 (#5241)
-rw-r--r-- | api/channel.go | 31 | ||||
-rw-r--r-- | api/channel_test.go | 6 | ||||
-rw-r--r-- | api4/api.go | 1 | ||||
-rw-r--r-- | api4/apitestlib.go | 30 | ||||
-rw-r--r-- | api4/channel.go | 45 | ||||
-rw-r--r-- | api4/channel_test.go | 169 | ||||
-rw-r--r-- | app/channel.go | 33 | ||||
-rw-r--r-- | i18n/en.json | 6 | ||||
-rw-r--r-- | model/client4.go | 19 | ||||
-rw-r--r-- | store/sql_channel_store.go | 5 |
10 files changed, 309 insertions, 36 deletions
diff --git a/api/channel.go b/api/channel.go index c5ec36d4b..33e083f14 100644 --- a/api/channel.go +++ b/api/channel.go @@ -6,7 +6,6 @@ package api import ( "net/http" "strconv" - "strings" l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" @@ -48,9 +47,7 @@ func InitChannel() { } func createChannel(c *Context, w http.ResponseWriter, r *http.Request) { - channel := model.ChannelFromJson(r.Body) - if channel == nil { c.SetInvalidParam("createChannel", "channel") return @@ -60,16 +57,6 @@ func createChannel(c *Context, w http.ResponseWriter, r *http.Request) { channel.TeamId = c.TeamId } - if channel.Type == model.CHANNEL_DIRECT { - c.Err = model.NewLocAppError("createDirectChannel", "api.channel.create_channel.direct_channel.app_error", nil, "") - return - } - - if strings.Index(channel.Name, "__") > 0 { - c.Err = model.NewLocAppError("createDirectChannel", "api.channel.create_channel.invalid_character.app_error", nil, "") - return - } - if channel.Type == model.CHANNEL_OPEN && !app.SessionHasPermissionToTeam(c.Session, channel.TeamId, model.PERMISSION_CREATE_PUBLIC_CHANNEL) { c.SetPermissionError(model.PERMISSION_CREATE_PUBLIC_CHANNEL) return @@ -80,23 +67,7 @@ func createChannel(c *Context, w http.ResponseWriter, r *http.Request) { return } - if channel.TeamId == c.TeamId { - - // Get total number of channels on current team - if count, err := app.GetNumberOfChannelsOnTeam(channel.TeamId); err != nil { - c.Err = model.NewLocAppError("createChannel", "api.channel.get_channels.error", nil, err.Error()) - return - } else { - if int64(count+1) > *utils.Cfg.TeamSettings.MaxChannelsPerTeam { - c.Err = model.NewLocAppError("createChannel", "api.channel.create_channel.max_channel_limit.app_error", map[string]interface{}{"MaxChannelsPerTeam": *utils.Cfg.TeamSettings.MaxChannelsPerTeam}, "") - return - } - } - } - - channel.CreatorId = c.Session.UserId - - if sc, err := app.CreateChannel(channel, true); err != nil { + if sc, err := app.CreateChannelWithUser(channel, c.Session.UserId); err != nil { c.Err = err return } else { diff --git a/api/channel_test.go b/api/channel_test.go index 450c5556e..6e6883047 100644 --- a/api/channel_test.go +++ b/api/channel_test.go @@ -55,7 +55,7 @@ func TestCreateChannel(t *testing.T) { rchannel.Data.(*model.Channel).Id = "" if _, err := Client.CreateChannel(rchannel.Data.(*model.Channel)); err != nil { - if err.Message != "A channel with that URL already exists" { + if err.Id != "store.sql_channel.save_channel.exists.app_error" { t.Fatal(err) } } @@ -1768,7 +1768,9 @@ func TestAutocompleteChannels(t *testing.T) { channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) channel3 := &model.Channel{DisplayName: "BadChannelC", Name: "c" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: model.NewId()} - channel3 = th.SystemAdminClient.Must(th.SystemAdminClient.CreateChannel(channel3)).Data.(*model.Channel) + if _, err := th.SystemAdminClient.CreateChannel(channel3); err == nil { + t.Fatal("channel must have valid team id") + } channel4 := &model.Channel{DisplayName: "BadChannelD", Name: "d" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} channel4 = Client.Must(Client.CreateChannel(channel4)).Data.(*model.Channel) diff --git a/api4/api.go b/api4/api.go index ace5de30a..2293cdec5 100644 --- a/api4/api.go +++ b/api4/api.go @@ -139,6 +139,7 @@ func InitApi(full bool) { InitUser() InitTeam() + InitChannel() // REMOVE CONDITION WHEN APIv3 REMOVED if full { diff --git a/api4/apitestlib.go b/api4/apitestlib.go index 6b129dd8f..d5706bf2b 100644 --- a/api4/apitestlib.go +++ b/api4/apitestlib.go @@ -25,6 +25,7 @@ type TestHelper struct { BasicUser2 *model.User TeamAdminUser *model.User BasicTeam *model.Team + BasicChannel *model.Channel SystemAdminClient *model.Client4 SystemAdminUser *model.User @@ -63,6 +64,7 @@ func (me *TestHelper) InitBasic() *TestHelper { me.TeamAdminUser = me.CreateUser() me.LoginTeamAdmin() me.BasicTeam = me.CreateTeam() + me.BasicChannel = me.CreatePublicChannel() me.BasicUser = me.CreateUser() LinkUserToTeam(me.BasicUser, me.BasicTeam) me.BasicUser2 = me.CreateUser() @@ -128,6 +130,30 @@ func (me *TestHelper) CreateUserWithClient(client *model.Client4) *model.User { return ruser } +func (me *TestHelper) CreatePublicChannel() *model.Channel { + return me.CreateChannelWithClient(me.Client, model.CHANNEL_OPEN) +} + +func (me *TestHelper) CreatePrivateChannel() *model.Channel { + return me.CreateChannelWithClient(me.Client, model.CHANNEL_PRIVATE) +} + +func (me *TestHelper) CreateChannelWithClient(client *model.Client4, channelType string) *model.Channel { + id := model.NewId() + + channel := &model.Channel{ + DisplayName: "dn_" + id, + Name: GenerateTestChannelName(), + Type: channelType, + TeamId: me.BasicTeam.Id, + } + + utils.DisableDebugLogForTest() + rchannel, _ := client.CreateChannel(channel) + utils.EnableDebugLogForTest() + return rchannel +} + func (me *TestHelper) LoginBasic() { me.LoginBasicWithClient(me.Client) } @@ -194,6 +220,10 @@ func GenerateTestTeamName() string { return "faketeam" + model.NewId() } +func GenerateTestChannelName() string { + return "fakechannel" + model.NewId() +} + func VerifyUserEmail(userId string) { store.Must(app.Srv.Store.User().VerifyEmail(userId)) } diff --git a/api4/channel.go b/api4/channel.go new file mode 100644 index 000000000..2ce9e23e5 --- /dev/null +++ b/api4/channel.go @@ -0,0 +1,45 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api4 + +import ( + "net/http" + + l4g "github.com/alecthomas/log4go" + "github.com/mattermost/platform/app" + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" +) + +func InitChannel() { + l4g.Debug(utils.T("api.channel.init.debug")) + + BaseRoutes.Channels.Handle("", ApiSessionRequired(createChannel)).Methods("POST") +} + +func createChannel(c *Context, w http.ResponseWriter, r *http.Request) { + channel := model.ChannelFromJson(r.Body) + if channel == nil { + c.SetInvalidParam("channel") + return + } + + if channel.Type == model.CHANNEL_OPEN && !app.SessionHasPermissionToTeam(c.Session, channel.TeamId, model.PERMISSION_CREATE_PUBLIC_CHANNEL) { + c.SetPermissionError(model.PERMISSION_CREATE_PUBLIC_CHANNEL) + return + } + + if channel.Type == model.CHANNEL_PRIVATE && !app.SessionHasPermissionToTeam(c.Session, channel.TeamId, model.PERMISSION_CREATE_PRIVATE_CHANNEL) { + c.SetPermissionError(model.PERMISSION_CREATE_PRIVATE_CHANNEL) + return + } + + if sc, err := app.CreateChannelWithUser(channel, c.Session.UserId); err != nil { + c.Err = err + return + } else { + c.LogAudit("name=" + channel.Name) + w.Write([]byte(sc.ToJson())) + } +} diff --git a/api4/channel_test.go b/api4/channel_test.go new file mode 100644 index 000000000..e3d0a85bd --- /dev/null +++ b/api4/channel_test.go @@ -0,0 +1,169 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api4 + +import ( + "net/http" + "strconv" + "testing" + + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" +) + +func TestCreateChannel(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + Client := th.Client + team := th.BasicTeam + + channel := &model.Channel{DisplayName: "Test API Name", Name: GenerateTestChannelName(), Type: model.CHANNEL_OPEN, TeamId: team.Id} + private := &model.Channel{DisplayName: "Test API Name", Name: GenerateTestChannelName(), Type: model.CHANNEL_PRIVATE, TeamId: team.Id} + + rchannel, resp := Client.CreateChannel(channel) + CheckNoError(t, resp) + + if rchannel.Name != channel.Name { + t.Fatal("names did not match") + } + + if rchannel.DisplayName != channel.DisplayName { + t.Fatal("display names did not match") + } + + if rchannel.TeamId != channel.TeamId { + t.Fatal("team ids did not match") + } + + rprivate, resp := Client.CreateChannel(private) + CheckNoError(t, resp) + + if rprivate.Name != private.Name { + t.Fatal("names did not match") + } + + if rprivate.Type != model.CHANNEL_PRIVATE { + t.Fatal("wrong channel type") + } + + if rprivate.CreatorId != th.BasicUser.Id { + t.Fatal("wrong creator id") + } + + _, resp = Client.CreateChannel(channel) + CheckErrorMessage(t, resp, "store.sql_channel.save_channel.exists.app_error") + CheckBadRequestStatus(t, resp) + + direct := &model.Channel{DisplayName: "Test API Name", Name: GenerateTestChannelName(), Type: model.CHANNEL_DIRECT, TeamId: team.Id} + _, resp = Client.CreateChannel(direct) + CheckErrorMessage(t, resp, "api.channel.create_channel.direct_channel.app_error") + CheckBadRequestStatus(t, resp) + + Client.Logout() + _, resp = Client.CreateChannel(channel) + CheckUnauthorizedStatus(t, resp) + + userNotOnTeam := th.CreateUser() + Client.Login(userNotOnTeam.Email, userNotOnTeam.Password) + + _, resp = Client.CreateChannel(channel) + CheckForbiddenStatus(t, resp) + + _, resp = Client.CreateChannel(private) + CheckForbiddenStatus(t, resp) + + th.LoginBasic() + + // Check permissions with policy config changes + isLicensed := utils.IsLicensed + license := utils.License + restrictPublicChannel := *utils.Cfg.TeamSettings.RestrictPublicChannelManagement + restrictPrivateChannel := *utils.Cfg.TeamSettings.RestrictPrivateChannelManagement + defer func() { + *utils.Cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel + *utils.Cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel + utils.IsLicensed = isLicensed + utils.License = license + utils.SetDefaultRolesBasedOnConfig() + }() + *utils.Cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL + *utils.Cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL + utils.SetDefaultRolesBasedOnConfig() + utils.IsLicensed = true + utils.License = &model.License{Features: &model.Features{}} + utils.License.Features.SetDefaults() + + channel.Name = GenerateTestChannelName() + _, resp = Client.CreateChannel(channel) + CheckNoError(t, resp) + + private.Name = GenerateTestChannelName() + _, resp = Client.CreateChannel(private) + CheckNoError(t, resp) + + *utils.Cfg.TeamSettings.RestrictPublicChannelCreation = model.PERMISSIONS_TEAM_ADMIN + *utils.Cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_TEAM_ADMIN + utils.SetDefaultRolesBasedOnConfig() + + _, resp = Client.CreateChannel(channel) + CheckForbiddenStatus(t, resp) + + _, resp = Client.CreateChannel(private) + CheckForbiddenStatus(t, resp) + + th.LoginTeamAdmin() + + channel.Name = GenerateTestChannelName() + _, resp = Client.CreateChannel(channel) + CheckNoError(t, resp) + + private.Name = GenerateTestChannelName() + _, resp = Client.CreateChannel(private) + CheckNoError(t, resp) + + channel.Name = GenerateTestChannelName() + _, resp = th.SystemAdminClient.CreateChannel(channel) + CheckNoError(t, resp) + + private.Name = GenerateTestChannelName() + _, resp = th.SystemAdminClient.CreateChannel(private) + CheckNoError(t, resp) + + *utils.Cfg.TeamSettings.RestrictPublicChannelCreation = model.PERMISSIONS_SYSTEM_ADMIN + *utils.Cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_SYSTEM_ADMIN + utils.SetDefaultRolesBasedOnConfig() + + th.LoginBasic() + + _, resp = Client.CreateChannel(channel) + CheckForbiddenStatus(t, resp) + + _, resp = Client.CreateChannel(private) + CheckForbiddenStatus(t, resp) + + th.LoginTeamAdmin() + + _, resp = Client.CreateChannel(channel) + CheckForbiddenStatus(t, resp) + + _, resp = Client.CreateChannel(private) + CheckForbiddenStatus(t, resp) + + channel.Name = GenerateTestChannelName() + _, resp = th.SystemAdminClient.CreateChannel(channel) + CheckNoError(t, resp) + + private.Name = GenerateTestChannelName() + _, resp = th.SystemAdminClient.CreateChannel(private) + CheckNoError(t, resp) + + if r, err := Client.DoApiPost("/channels", "garbage"); err == nil { + t.Fatal("should have errored") + } else { + if r.StatusCode != http.StatusBadRequest { + t.Log("actual: " + strconv.Itoa(r.StatusCode)) + t.Log("expected: " + strconv.Itoa(http.StatusBadRequest)) + t.Fatal("wrong status code") + } + } +} diff --git a/app/channel.go b/app/channel.go index 818e241b7..aba65143b 100644 --- a/app/channel.go +++ b/app/channel.go @@ -6,6 +6,7 @@ package app import ( "fmt" "net/http" + "strings" l4g "github.com/alecthomas/log4go" "github.com/mattermost/platform/model" @@ -129,6 +130,38 @@ func JoinDefaultChannels(teamId string, user *model.User, channelRole string) *m return err } +func CreateChannelWithUser(channel *model.Channel, userId string) (*model.Channel, *model.AppError) { + if channel.Type == model.CHANNEL_DIRECT { + return nil, model.NewAppError("CreateChannelWithUser", "api.channel.create_channel.direct_channel.app_error", nil, "", http.StatusBadRequest) + } + + if strings.Index(channel.Name, "__") > 0 { + return nil, model.NewAppError("CreateChannelWithUser", "api.channel.create_channel.invalid_character.app_error", nil, "", http.StatusBadRequest) + } + + if len(channel.TeamId) == 0 { + return nil, model.NewAppError("CreateChannelWithUser", "app.channel.create_channel.no_team_id.app_error", nil, "", http.StatusBadRequest) + } + + // Get total number of channels on current team + if count, err := GetNumberOfChannelsOnTeam(channel.TeamId); err != nil { + return nil, err + } else { + if int64(count+1) > *utils.Cfg.TeamSettings.MaxChannelsPerTeam { + return nil, model.NewAppError("CreateChannelWithUser", "api.channel.create_channel.max_channel_limit.app_error", map[string]interface{}{"MaxChannelsPerTeam": *utils.Cfg.TeamSettings.MaxChannelsPerTeam}, "", http.StatusBadRequest) + } + } + + channel.CreatorId = userId + + rchannel, err := CreateChannel(channel, true) + if err != nil { + return nil, err + } + + return rchannel, nil +} + func CreateChannel(channel *model.Channel, addMember bool) (*model.Channel, *model.AppError) { if result := <-Srv.Store.Channel().Save(channel); result.Err != nil { return nil, result.Err diff --git a/i18n/en.json b/i18n/en.json index 59ed5583c..cd109f860 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -208,6 +208,10 @@ "translation": "Cannot create more than {{.MaxChannelsPerTeam}} channels for current team" }, { + "id": "app.channel.create_channel.no_team_id.app_error", + "translation": "Must specify the team ID to create a channel" + }, + { "id": "api.channel.create_default_channels.off_topic", "translation": "Off-Topic" }, @@ -4569,7 +4573,7 @@ }, { "id": "store.sql_channel.save_channel.exists.app_error", - "translation": "A channel with that URL already exists" + "translation": "A channel with that name already exists on the same team" }, { "id": "store.sql_channel.save_channel.limit.app_error", diff --git a/model/client4.go b/model/client4.go index f0adfd382..c82f5ce0e 100644 --- a/model/client4.go +++ b/model/client4.go @@ -60,6 +60,14 @@ func (c *Client4) GetTeamsRoute() string { return fmt.Sprintf("/teams") } +func (c *Client4) GetChannelsRoute() string { + return fmt.Sprintf("/channels") +} + +func (c *Client4) GetChannelRoute(channelId string) string { + return fmt.Sprintf(c.GetChannelsRoute()+"/%v", channelId) +} + func (c *Client4) GetTeamRoute(teamId string) string { return fmt.Sprintf(c.GetTeamsRoute()+"/%v", teamId) } @@ -236,7 +244,16 @@ func (c *Client4) CreateTeam(team *Team) (*Team, *Response) { } // Channel Section -// to be filled in.. + +// CreateChannel creates a channel based on the provided channel struct. +func (c *Client4) CreateChannel(channel *Channel) (*Channel, *Response) { + if r, err := c.DoApiPost(c.GetChannelsRoute(), channel.ToJson()); err != nil { + return nil, &Response{StatusCode: r.StatusCode, Error: err} + } else { + defer closeBody(r) + return ChannelFromJson(r.Body), BuildResponse(r) + } +} // Post Section // to be filled in.. diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go index 7e90a6d27..a8474be80 100644 --- a/store/sql_channel_store.go +++ b/store/sql_channel_store.go @@ -6,6 +6,7 @@ package store import ( "database/sql" "fmt" + "net/http" "strconv" "strings" @@ -223,7 +224,7 @@ func (s SqlChannelStore) saveChannelT(transaction *gorp.Transaction, channel *mo if dupChannel.DeleteAt > 0 { result.Err = model.NewLocAppError("SqlChannelStore.Save", "store.sql_channel.save_channel.previously.app_error", nil, "id="+channel.Id+", "+err.Error()) } else { - result.Err = model.NewLocAppError("SqlChannelStore.Save", CHANNEL_EXISTS_ERROR, nil, "id="+channel.Id+", "+err.Error()) + result.Err = model.NewAppError("SqlChannelStore.Save", CHANNEL_EXISTS_ERROR, nil, "id="+channel.Id+", "+err.Error(), http.StatusBadRequest) result.Data = &dupChannel } } else { @@ -427,7 +428,7 @@ func (s SqlChannelStore) GetChannels(teamId string, userId string) StoreChannel result.Err = model.NewLocAppError("SqlChannelStore.GetChannels", "store.sql_channel.get_channels.get.app_error", nil, "teamId="+teamId+", userId="+userId+", err="+err.Error()) } else { if len(*data) == 0 { - result.Err = model.NewLocAppError("SqlChannelStore.GetChannels", "store.sql_channel.get_channels.not_found.app_error", nil, "teamId="+teamId+", userId="+userId) + result.Err = model.NewAppError("SqlChannelStore.GetChannels", "store.sql_channel.get_channels.not_found.app_error", nil, "teamId="+teamId+", userId="+userId, http.StatusBadRequest) } else { result.Data = data } |