diff options
-rw-r--r-- | api4/api.go | 5 | ||||
-rw-r--r-- | api4/context.go | 11 | ||||
-rw-r--r-- | api4/params.go | 8 | ||||
-rw-r--r-- | api4/scheme.go | 211 | ||||
-rw-r--r-- | api4/scheme_test.go | 664 | ||||
-rw-r--r-- | app/scheme.go | 101 | ||||
-rw-r--r-- | i18n/en.json | 8 | ||||
-rw-r--r-- | model/client4.go | 80 | ||||
-rw-r--r-- | model/scheme.go | 39 | ||||
-rw-r--r-- | store/layered_store.go | 6 | ||||
-rw-r--r-- | store/layered_store_supplier.go | 1 | ||||
-rw-r--r-- | store/local_cache_supplier_schemes.go | 4 | ||||
-rw-r--r-- | store/redis_supplier_schemes.go | 5 | ||||
-rw-r--r-- | store/sqlstore/channel_store.go | 2 | ||||
-rw-r--r-- | store/sqlstore/scheme_supplier.go | 19 | ||||
-rw-r--r-- | store/store.go | 1 | ||||
-rw-r--r-- | store/storetest/channel_store.go | 6 | ||||
-rw-r--r-- | store/storetest/mocks/LayeredStoreDatabaseLayer.go | 23 | ||||
-rw-r--r-- | store/storetest/mocks/LayeredStoreSupplier.go | 23 | ||||
-rw-r--r-- | store/storetest/mocks/SchemeStore.go | 16 | ||||
-rw-r--r-- | store/storetest/mocks/SqlStore.go | 14 | ||||
-rw-r--r-- | store/storetest/scheme_store.go | 61 |
22 files changed, 1303 insertions, 5 deletions
diff --git a/api4/api.go b/api4/api.go index d36c3e3ee..f2821b42e 100644 --- a/api4/api.go +++ b/api4/api.go @@ -99,7 +99,8 @@ type Routes struct { Reactions *mux.Router // 'api/v4/reactions' - Roles *mux.Router // 'api/v4/roles' + Roles *mux.Router // 'api/v4/roles' + Schemes *mux.Router // 'api/v4/schemes' Emojis *mux.Router // 'api/v4/emoji' Emoji *mux.Router // 'api/v4/emoji/{emoji_id:[A-Za-z0-9]+}' @@ -200,6 +201,7 @@ func Init(a *app.App, root *mux.Router, full bool) *API { api.BaseRoutes.OpenGraph = api.BaseRoutes.ApiRoot.PathPrefix("/opengraph").Subrouter() api.BaseRoutes.Roles = api.BaseRoutes.ApiRoot.PathPrefix("/roles").Subrouter() + api.BaseRoutes.Schemes = api.BaseRoutes.ApiRoot.PathPrefix("/schemes").Subrouter() api.BaseRoutes.Image = api.BaseRoutes.ApiRoot.PathPrefix("/image").Subrouter() @@ -229,6 +231,7 @@ func Init(a *app.App, root *mux.Router, full bool) *API { api.InitOpenGraph() api.InitPlugin() api.InitRole() + api.InitScheme() api.InitImage() root.Handle("/api/v4/{anything:.*}", http.HandlerFunc(Handle404)) diff --git a/api4/context.go b/api4/context.go index c965e1d80..6afb964ce 100644 --- a/api4/context.go +++ b/api4/context.go @@ -650,6 +650,17 @@ func (c *Context) RequireRoleId() *Context { return c } +func (c *Context) RequireSchemeId() *Context { + if c.Err != nil { + return c + } + + if len(c.Params.SchemeId) != 26 { + c.SetInvalidUrlParam("scheme_id") + } + return c +} + func (c *Context) RequireRoleName() *Context { if c.Err != nil { return c diff --git a/api4/params.go b/api4/params.go index e8e3f25e7..35f21e0ec 100644 --- a/api4/params.go +++ b/api4/params.go @@ -47,6 +47,8 @@ type ApiParams struct { ActionId string RoleId string RoleName string + SchemeId string + Scope string Page int PerPage int LogsPerPage int @@ -167,6 +169,12 @@ func ApiParamsFromRequest(r *http.Request) *ApiParams { params.RoleName = val } + if val, ok := props["scheme_id"]; ok { + params.SchemeId = val + } + + params.Scope = query.Get("scope") + if val, err := strconv.Atoi(query.Get("page")); err != nil || val < 0 { params.Page = PAGE_DEFAULT } else { diff --git a/api4/scheme.go b/api4/scheme.go new file mode 100644 index 000000000..bdfe69870 --- /dev/null +++ b/api4/scheme.go @@ -0,0 +1,211 @@ +// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api4 + +import ( + "net/http" + + "github.com/mattermost/mattermost-server/model" +) + +func (api *API) InitScheme() { + api.BaseRoutes.Schemes.Handle("", api.ApiSessionRequired(getSchemes)).Methods("GET") + api.BaseRoutes.Schemes.Handle("", api.ApiSessionRequired(createScheme)).Methods("POST") + api.BaseRoutes.Schemes.Handle("/{scheme_id:[A-Za-z0-9]+}", api.ApiSessionRequired(deleteScheme)).Methods("DELETE") + api.BaseRoutes.Schemes.Handle("/{scheme_id:[A-Za-z0-9]+}", api.ApiSessionRequiredTrustRequester(getScheme)).Methods("GET") + api.BaseRoutes.Schemes.Handle("/{scheme_id:[A-Za-z0-9]+}/patch", api.ApiSessionRequired(patchScheme)).Methods("PUT") + api.BaseRoutes.Schemes.Handle("/{scheme_id:[A-Za-z0-9]+}/teams", api.ApiSessionRequiredTrustRequester(getTeamsForScheme)).Methods("GET") + api.BaseRoutes.Schemes.Handle("/{scheme_id:[A-Za-z0-9]+}/channels", api.ApiSessionRequiredTrustRequester(getChannelsForScheme)).Methods("GET") +} + +func createScheme(c *Context, w http.ResponseWriter, r *http.Request) { + scheme := model.SchemeFromJson(r.Body) + if scheme == nil { + c.SetInvalidParam("scheme") + return + } + + if c.App.License() == nil { + c.Err = model.NewAppError("Api4.CreateScheme", "api.scheme.create_scheme.license.error", nil, "", http.StatusNotImplemented) + return + } + + if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + var err *model.AppError + if scheme, err = c.App.CreateScheme(scheme); err != nil { + c.Err = err + return + } else { + w.WriteHeader(http.StatusCreated) + w.Write([]byte(scheme.ToJson())) + } +} + +func getScheme(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireSchemeId() + if c.Err != nil { + return + } + + if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + if scheme, err := c.App.GetScheme(c.Params.SchemeId); err != nil { + c.Err = err + return + } else { + w.Write([]byte(scheme.ToJson())) + } +} + +func getSchemes(c *Context, w http.ResponseWriter, r *http.Request) { + if c.Err != nil { + return + } + + if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + scope := c.Params.Scope + if scope != "" && scope != model.SCHEME_SCOPE_TEAM && scope != model.SCHEME_SCOPE_CHANNEL { + c.SetInvalidParam("scope") + return + } + + if schemes, err := c.App.GetSchemesPage(c.Params.Scope, c.Params.Page, c.Params.PerPage); err != nil { + c.Err = err + return + } else { + w.Write([]byte(model.SchemesToJson(schemes))) + } +} + +func getTeamsForScheme(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireSchemeId() + if c.Err != nil { + return + } + + if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + scheme, err := c.App.GetScheme(c.Params.SchemeId) + if err != nil { + c.Err = err + return + } + + if scheme.Scope != model.SCHEME_SCOPE_TEAM { + c.Err = model.NewAppError("Api4.GetTeamsForScheme", "api.scheme.get_teams_for_scheme.scope.error", nil, "", http.StatusBadRequest) + return + } + + if teams, err := c.App.GetTeamsForSchemePage(scheme, c.Params.Page, c.Params.PerPage); err != nil { + c.Err = err + return + } else { + w.Write([]byte(model.TeamListToJson(teams))) + } +} + +func getChannelsForScheme(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireSchemeId() + if c.Err != nil { + return + } + + if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + scheme, err := c.App.GetScheme(c.Params.SchemeId) + if err != nil { + c.Err = err + return + } + + if scheme.Scope != model.SCHEME_SCOPE_CHANNEL { + c.Err = model.NewAppError("Api4.GetChannelsForScheme", "api.scheme.get_channels_for_scheme.scope.error", nil, "", http.StatusBadRequest) + return + } + + if channels, err := c.App.GetChannelsForSchemePage(scheme, c.Params.Page, c.Params.PerPage); err != nil { + c.Err = err + return + } else { + w.Write([]byte(channels.ToJson())) + } +} + +func patchScheme(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireSchemeId() + if c.Err != nil { + return + } + + patch := model.SchemePatchFromJson(r.Body) + if patch == nil { + c.SetInvalidParam("scheme") + return + } + + if c.App.License() == nil { + c.Err = model.NewAppError("Api4.PatchScheme", "api.scheme.patch_scheme.license.error", nil, "", http.StatusNotImplemented) + return + } + + scheme, err := c.App.GetScheme(c.Params.SchemeId) + if err != nil { + c.Err = err + return + } + + if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + if scheme, err = c.App.PatchScheme(scheme, patch); err != nil { + c.Err = err + return + } else { + c.LogAudit("") + w.Write([]byte(scheme.ToJson())) + } +} + +func deleteScheme(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireSchemeId() + if c.Err != nil { + return + } + + if c.App.License() == nil { + c.Err = model.NewAppError("Api4.DeleteScheme", "api.scheme.delete_scheme.license.error", nil, "", http.StatusNotImplemented) + return + } + + if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + if _, err := c.App.DeleteScheme(c.Params.SchemeId); err != nil { + c.Err = err + return + } + + ReturnStatusOK(w) +} diff --git a/api4/scheme_test.go b/api4/scheme_test.go new file mode 100644 index 000000000..a0ea1e9b0 --- /dev/null +++ b/api4/scheme_test.go @@ -0,0 +1,664 @@ +// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api4 + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/mattermost/mattermost-server/model" +) + +func TestCreateScheme(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + th.App.SetLicense(model.NewTestLicense("")) + + // Basic test of creating a team scheme. + scheme1 := &model.Scheme{ + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + + s1, r1 := th.SystemAdminClient.CreateScheme(scheme1) + CheckNoError(t, r1) + + assert.Equal(t, s1.Name, scheme1.Name) + assert.Equal(t, s1.Description, scheme1.Description) + assert.NotZero(t, s1.CreateAt) + assert.Equal(t, s1.CreateAt, s1.UpdateAt) + assert.Zero(t, s1.DeleteAt) + assert.Equal(t, s1.Scope, scheme1.Scope) + assert.NotZero(t, len(s1.DefaultTeamAdminRole)) + assert.NotZero(t, len(s1.DefaultTeamUserRole)) + assert.NotZero(t, len(s1.DefaultChannelAdminRole)) + assert.NotZero(t, len(s1.DefaultChannelUserRole)) + + // Check the default roles have been created. + _, roleRes1 := th.SystemAdminClient.GetRole(s1.DefaultTeamAdminRole) + CheckNoError(t, roleRes1) + _, roleRes2 := th.SystemAdminClient.GetRole(s1.DefaultTeamUserRole) + CheckNoError(t, roleRes2) + _, roleRes3 := th.SystemAdminClient.GetRole(s1.DefaultChannelAdminRole) + CheckNoError(t, roleRes3) + _, roleRes4 := th.SystemAdminClient.GetRole(s1.DefaultChannelUserRole) + CheckNoError(t, roleRes4) + + // Basic Test of a Channel scheme. + scheme2 := &model.Scheme{ + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_CHANNEL, + } + + s2, r2 := th.SystemAdminClient.CreateScheme(scheme2) + CheckNoError(t, r2) + + assert.Equal(t, s2.Name, scheme2.Name) + assert.Equal(t, s2.Description, scheme2.Description) + assert.NotZero(t, s2.CreateAt) + assert.Equal(t, s2.CreateAt, s2.UpdateAt) + assert.Zero(t, s2.DeleteAt) + assert.Equal(t, s2.Scope, scheme2.Scope) + assert.Zero(t, len(s2.DefaultTeamAdminRole)) + assert.Zero(t, len(s2.DefaultTeamUserRole)) + assert.NotZero(t, len(s2.DefaultChannelAdminRole)) + assert.NotZero(t, len(s2.DefaultChannelUserRole)) + + // Check the default roles have been created. + _, roleRes5 := th.SystemAdminClient.GetRole(s2.DefaultChannelAdminRole) + CheckNoError(t, roleRes5) + _, roleRes6 := th.SystemAdminClient.GetRole(s2.DefaultChannelUserRole) + CheckNoError(t, roleRes6) + + // Try and create a scheme with an invalid scope. + scheme3 := &model.Scheme{ + Name: model.NewId(), + Description: model.NewId(), + Scope: model.NewId(), + } + + _, r3 := th.SystemAdminClient.CreateScheme(scheme3) + CheckBadRequestStatus(t, r3) + + // Try and create a scheme with an invalid name. + scheme4 := &model.Scheme{ + Name: strings.Repeat(model.NewId(), 100), + Description: model.NewId(), + Scope: model.NewId(), + } + _, r4 := th.SystemAdminClient.CreateScheme(scheme4) + CheckBadRequestStatus(t, r4) + + // Try and create a scheme without the appropriate permissions. + scheme5 := &model.Scheme{ + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + _, r5 := th.Client.CreateScheme(scheme5) + CheckForbiddenStatus(t, r5) + + // Try and create a scheme without a license. + th.App.SetLicense(nil) + scheme6 := &model.Scheme{ + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + _, r6 := th.SystemAdminClient.CreateScheme(scheme6) + CheckNotImplementedStatus(t, r6) +} + +func TestGetScheme(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + th.App.SetLicense(model.NewTestLicense("")) + + // Basic test of creating a team scheme. + scheme1 := &model.Scheme{ + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + + s1, r1 := th.SystemAdminClient.CreateScheme(scheme1) + CheckNoError(t, r1) + + assert.Equal(t, s1.Name, scheme1.Name) + assert.Equal(t, s1.Description, scheme1.Description) + assert.NotZero(t, s1.CreateAt) + assert.Equal(t, s1.CreateAt, s1.UpdateAt) + assert.Zero(t, s1.DeleteAt) + assert.Equal(t, s1.Scope, scheme1.Scope) + assert.NotZero(t, len(s1.DefaultTeamAdminRole)) + assert.NotZero(t, len(s1.DefaultTeamUserRole)) + assert.NotZero(t, len(s1.DefaultChannelAdminRole)) + assert.NotZero(t, len(s1.DefaultChannelUserRole)) + + s2, r2 := th.SystemAdminClient.GetScheme(s1.Id) + CheckNoError(t, r2) + + assert.Equal(t, s1, s2) + + _, r3 := th.SystemAdminClient.GetScheme(model.NewId()) + CheckNotFoundStatus(t, r3) + + _, r4 := th.SystemAdminClient.GetScheme("12345") + CheckBadRequestStatus(t, r4) + + th.SystemAdminClient.Logout() + _, r5 := th.SystemAdminClient.GetScheme(s1.Id) + CheckUnauthorizedStatus(t, r5) + + th.SystemAdminClient.Login(th.SystemAdminUser.Username, th.SystemAdminUser.Password) + th.App.SetLicense(nil) + _, r6 := th.SystemAdminClient.GetScheme(s1.Id) + CheckNoError(t, r6) + + _, r7 := th.Client.GetScheme(s1.Id) + CheckForbiddenStatus(t, r7) +} + +func TestGetSchemes(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + th.App.SetLicense(model.NewTestLicense("")) + + scheme1 := &model.Scheme{ + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + + scheme2 := &model.Scheme{ + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_CHANNEL, + } + + _, r1 := th.SystemAdminClient.CreateScheme(scheme1) + CheckNoError(t, r1) + _, r2 := th.SystemAdminClient.CreateScheme(scheme2) + CheckNoError(t, r2) + + l3, r3 := th.SystemAdminClient.GetSchemes("", 0, 100) + CheckNoError(t, r3) + + assert.NotZero(t, len(l3)) + + l4, r4 := th.SystemAdminClient.GetSchemes("team", 0, 100) + CheckNoError(t, r4) + + for _, s := range l4 { + assert.Equal(t, "team", s.Scope) + } + + l5, r5 := th.SystemAdminClient.GetSchemes("channel", 0, 100) + CheckNoError(t, r5) + + for _, s := range l5 { + assert.Equal(t, "channel", s.Scope) + } + + _, r6 := th.SystemAdminClient.GetSchemes("asdf", 0, 100) + CheckBadRequestStatus(t, r6) + + th.Client.Logout() + _, r7 := th.Client.GetSchemes("", 0, 100) + CheckUnauthorizedStatus(t, r7) + + th.Client.Login(th.BasicUser.Username, th.BasicUser.Password) + _, r8 := th.Client.GetSchemes("", 0, 100) + CheckForbiddenStatus(t, r8) +} + +func TestGetTeamsForScheme(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + th.App.SetLicense(model.NewTestLicense("")) + + scheme1 := &model.Scheme{ + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + scheme1, r1 := th.SystemAdminClient.CreateScheme(scheme1) + CheckNoError(t, r1) + + team1 := &model.Team{ + Name: GenerateTestUsername(), + DisplayName: "A Test Team", + Type: model.TEAM_OPEN, + } + + result1 := <-th.App.Srv.Store.Team().Save(team1) + assert.Nil(t, result1.Err) + team1 = result1.Data.(*model.Team) + + l2, r2 := th.SystemAdminClient.GetTeamsForScheme(scheme1.Id, 0, 100) + CheckNoError(t, r2) + assert.Zero(t, len(l2)) + + team1.SchemeId = &scheme1.Id + result2 := <-th.App.Srv.Store.Team().Update(team1) + assert.Nil(t, result2.Err) + team1 = result2.Data.(*model.Team) + + l3, r3 := th.SystemAdminClient.GetTeamsForScheme(scheme1.Id, 0, 100) + CheckNoError(t, r3) + assert.Len(t, l3, 1) + assert.Equal(t, team1.Id, l3[0].Id) + + team2 := &model.Team{ + Name: GenerateTestUsername(), + DisplayName: "B Test Team", + Type: model.TEAM_OPEN, + SchemeId: &scheme1.Id, + } + result3 := <-th.App.Srv.Store.Team().Save(team2) + assert.Nil(t, result3.Err) + team2 = result3.Data.(*model.Team) + + l4, r4 := th.SystemAdminClient.GetTeamsForScheme(scheme1.Id, 0, 100) + CheckNoError(t, r4) + assert.Len(t, l4, 2) + assert.Equal(t, team1.Id, l4[0].Id) + assert.Equal(t, team2.Id, l4[1].Id) + + l5, r5 := th.SystemAdminClient.GetTeamsForScheme(scheme1.Id, 1, 1) + CheckNoError(t, r5) + assert.Len(t, l5, 1) + assert.Equal(t, team2.Id, l5[0].Id) + + // Check various error cases. + _, ri1 := th.SystemAdminClient.GetTeamsForScheme(model.NewId(), 0, 100) + CheckNotFoundStatus(t, ri1) + + _, ri2 := th.SystemAdminClient.GetTeamsForScheme("", 0, 100) + CheckBadRequestStatus(t, ri2) + + th.Client.Logout() + _, ri3 := th.Client.GetTeamsForScheme(model.NewId(), 0, 100) + CheckUnauthorizedStatus(t, ri3) + + th.Client.Login(th.BasicUser.Username, th.BasicUser.Password) + _, ri4 := th.Client.GetTeamsForScheme(model.NewId(), 0, 100) + CheckForbiddenStatus(t, ri4) + + scheme2 := &model.Scheme{ + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_CHANNEL, + } + scheme2, rs2 := th.SystemAdminClient.CreateScheme(scheme2) + CheckNoError(t, rs2) + + _, ri5 := th.SystemAdminClient.GetTeamsForScheme(scheme2.Id, 0, 100) + CheckBadRequestStatus(t, ri5) +} + +func TestGetChannelsForScheme(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + th.App.SetLicense(model.NewTestLicense("")) + + scheme1 := &model.Scheme{ + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_CHANNEL, + } + scheme1, r1 := th.SystemAdminClient.CreateScheme(scheme1) + CheckNoError(t, r1) + + channel1 := &model.Channel{ + TeamId: model.NewId(), + DisplayName: "A Name", + Name: model.NewId(), + Type: model.CHANNEL_OPEN, + } + + result1 := <-th.App.Srv.Store.Channel().Save(channel1, 1000000) + assert.Nil(t, result1.Err) + channel1 = result1.Data.(*model.Channel) + + l2, r2 := th.SystemAdminClient.GetChannelsForScheme(scheme1.Id, 0, 100) + CheckNoError(t, r2) + assert.Zero(t, len(l2)) + + channel1.SchemeId = &scheme1.Id + result2 := <-th.App.Srv.Store.Channel().Update(channel1) + assert.Nil(t, result2.Err) + channel1 = result2.Data.(*model.Channel) + + l3, r3 := th.SystemAdminClient.GetChannelsForScheme(scheme1.Id, 0, 100) + CheckNoError(t, r3) + assert.Len(t, l3, 1) + assert.Equal(t, channel1.Id, l3[0].Id) + + channel2 := &model.Channel{ + TeamId: model.NewId(), + DisplayName: "B Name", + Name: model.NewId(), + Type: model.CHANNEL_OPEN, + SchemeId: &scheme1.Id, + } + result3 := <-th.App.Srv.Store.Channel().Save(channel2, 1000000) + assert.Nil(t, result3.Err) + channel2 = result3.Data.(*model.Channel) + + l4, r4 := th.SystemAdminClient.GetChannelsForScheme(scheme1.Id, 0, 100) + CheckNoError(t, r4) + assert.Len(t, l4, 2) + assert.Equal(t, channel1.Id, l4[0].Id) + assert.Equal(t, channel2.Id, l4[1].Id) + + l5, r5 := th.SystemAdminClient.GetChannelsForScheme(scheme1.Id, 1, 1) + CheckNoError(t, r5) + assert.Len(t, l5, 1) + assert.Equal(t, channel2.Id, l5[0].Id) + + // Check various error cases. + _, ri1 := th.SystemAdminClient.GetChannelsForScheme(model.NewId(), 0, 100) + CheckNotFoundStatus(t, ri1) + + _, ri2 := th.SystemAdminClient.GetChannelsForScheme("", 0, 100) + CheckBadRequestStatus(t, ri2) + + th.Client.Logout() + _, ri3 := th.Client.GetChannelsForScheme(model.NewId(), 0, 100) + CheckUnauthorizedStatus(t, ri3) + + th.Client.Login(th.BasicUser.Username, th.BasicUser.Password) + _, ri4 := th.Client.GetChannelsForScheme(model.NewId(), 0, 100) + CheckForbiddenStatus(t, ri4) + + scheme2 := &model.Scheme{ + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + scheme2, rs2 := th.SystemAdminClient.CreateScheme(scheme2) + CheckNoError(t, rs2) + + _, ri5 := th.SystemAdminClient.GetChannelsForScheme(scheme2.Id, 0, 100) + CheckBadRequestStatus(t, ri5) +} + +func TestPatchScheme(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + th.App.SetLicense(model.NewTestLicense("")) + + // Basic test of creating a team scheme. + scheme1 := &model.Scheme{ + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + + s1, r1 := th.SystemAdminClient.CreateScheme(scheme1) + CheckNoError(t, r1) + + assert.Equal(t, s1.Name, scheme1.Name) + assert.Equal(t, s1.Description, scheme1.Description) + assert.NotZero(t, s1.CreateAt) + assert.Equal(t, s1.CreateAt, s1.UpdateAt) + assert.Zero(t, s1.DeleteAt) + assert.Equal(t, s1.Scope, scheme1.Scope) + assert.NotZero(t, len(s1.DefaultTeamAdminRole)) + assert.NotZero(t, len(s1.DefaultTeamUserRole)) + assert.NotZero(t, len(s1.DefaultChannelAdminRole)) + assert.NotZero(t, len(s1.DefaultChannelUserRole)) + + s2, r2 := th.SystemAdminClient.GetScheme(s1.Id) + CheckNoError(t, r2) + + assert.Equal(t, s1, s2) + + // Test with a valid patch. + schemePatch := &model.SchemePatch{ + Name: new(string), + Description: new(string), + } + *schemePatch.Name = model.NewId() + *schemePatch.Description = model.NewId() + + s3, r3 := th.SystemAdminClient.PatchScheme(s2.Id, schemePatch) + CheckNoError(t, r3) + assert.Equal(t, s3.Id, s2.Id) + assert.Equal(t, s3.Name, *schemePatch.Name) + assert.Equal(t, s3.Description, *schemePatch.Description) + + s4, r4 := th.SystemAdminClient.GetScheme(s3.Id) + CheckNoError(t, r4) + assert.Equal(t, s3, s4) + + // Test with a partial patch. + *schemePatch.Name = model.NewId() + schemePatch.Description = nil + + s5, r5 := th.SystemAdminClient.PatchScheme(s4.Id, schemePatch) + CheckNoError(t, r5) + assert.Equal(t, s5.Id, s4.Id) + assert.Equal(t, s5.Name, *schemePatch.Name) + assert.Equal(t, s5.Description, s4.Description) + + s6, r6 := th.SystemAdminClient.GetScheme(s5.Id) + CheckNoError(t, r6) + assert.Equal(t, s5, s6) + + // Test with invalid patch. + *schemePatch.Name = strings.Repeat(model.NewId(), 20) + _, r7 := th.SystemAdminClient.PatchScheme(s6.Id, schemePatch) + CheckBadRequestStatus(t, r7) + + // Test with unknown ID. + *schemePatch.Name = model.NewId() + _, r8 := th.SystemAdminClient.PatchScheme(model.NewId(), schemePatch) + CheckNotFoundStatus(t, r8) + + // Test with invalid ID. + _, r9 := th.SystemAdminClient.PatchScheme("12345", schemePatch) + CheckBadRequestStatus(t, r9) + + // Test without required permissions. + _, r10 := th.Client.PatchScheme(s6.Id, schemePatch) + CheckForbiddenStatus(t, r10) + + // Test without license. + th.App.SetLicense(nil) + _, r11 := th.SystemAdminClient.PatchScheme(s6.Id, schemePatch) + CheckNotImplementedStatus(t, r11) +} + +func TestDeleteScheme(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + t.Run("ValidTeamScheme", func(t *testing.T) { + th.App.SetLicense(model.NewTestLicense("")) + + // Create a team scheme. + scheme1 := &model.Scheme{ + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + + s1, r1 := th.SystemAdminClient.CreateScheme(scheme1) + CheckNoError(t, r1) + + // Retrieve the roles and check they are not deleted. + role1, roleRes1 := th.SystemAdminClient.GetRole(s1.DefaultTeamAdminRole) + CheckNoError(t, roleRes1) + role2, roleRes2 := th.SystemAdminClient.GetRole(s1.DefaultTeamUserRole) + CheckNoError(t, roleRes2) + role3, roleRes3 := th.SystemAdminClient.GetRole(s1.DefaultChannelAdminRole) + CheckNoError(t, roleRes3) + role4, roleRes4 := th.SystemAdminClient.GetRole(s1.DefaultChannelUserRole) + CheckNoError(t, roleRes4) + + assert.Zero(t, role1.DeleteAt) + assert.Zero(t, role2.DeleteAt) + assert.Zero(t, role3.DeleteAt) + assert.Zero(t, role4.DeleteAt) + + // Make sure this scheme is in use by a team. + res := <-th.App.Srv.Store.Team().Save(&model.Team{ + Name: model.NewId(), + DisplayName: model.NewId(), + Email: model.NewId() + "@nowhere.com", + Type: model.TEAM_OPEN, + SchemeId: &s1.Id, + }) + assert.Nil(t, res.Err) + team := res.Data.(*model.Team) + + // Try and fail to delete the scheme. + _, r2 := th.SystemAdminClient.DeleteScheme(s1.Id) + CheckInternalErrorStatus(t, r2) + + role1, roleRes1 = th.SystemAdminClient.GetRole(s1.DefaultTeamAdminRole) + CheckNoError(t, roleRes1) + role2, roleRes2 = th.SystemAdminClient.GetRole(s1.DefaultTeamUserRole) + CheckNoError(t, roleRes2) + role3, roleRes3 = th.SystemAdminClient.GetRole(s1.DefaultChannelAdminRole) + CheckNoError(t, roleRes3) + role4, roleRes4 = th.SystemAdminClient.GetRole(s1.DefaultChannelUserRole) + CheckNoError(t, roleRes4) + + assert.Zero(t, role1.DeleteAt) + assert.Zero(t, role2.DeleteAt) + assert.Zero(t, role3.DeleteAt) + assert.Zero(t, role4.DeleteAt) + + // Change the team using it to a different scheme. + emptyString := "" + team.SchemeId = &emptyString + res = <-th.App.Srv.Store.Team().Update(team) + + // Delete the Scheme. + _, r3 := th.SystemAdminClient.DeleteScheme(s1.Id) + CheckNoError(t, r3) + + // Check the roles were deleted. + role1, roleRes1 = th.SystemAdminClient.GetRole(s1.DefaultTeamAdminRole) + CheckNoError(t, roleRes1) + role2, roleRes2 = th.SystemAdminClient.GetRole(s1.DefaultTeamUserRole) + CheckNoError(t, roleRes2) + role3, roleRes3 = th.SystemAdminClient.GetRole(s1.DefaultChannelAdminRole) + CheckNoError(t, roleRes3) + role4, roleRes4 = th.SystemAdminClient.GetRole(s1.DefaultChannelUserRole) + CheckNoError(t, roleRes4) + + assert.NotZero(t, role1.DeleteAt) + assert.NotZero(t, role2.DeleteAt) + assert.NotZero(t, role3.DeleteAt) + assert.NotZero(t, role4.DeleteAt) + }) + + t.Run("ValidChannelScheme", func(t *testing.T) { + th.App.SetLicense(model.NewTestLicense("")) + + // Create a channel scheme. + scheme1 := &model.Scheme{ + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_CHANNEL, + } + + s1, r1 := th.SystemAdminClient.CreateScheme(scheme1) + CheckNoError(t, r1) + + // Retrieve the roles and check they are not deleted. + role3, roleRes3 := th.SystemAdminClient.GetRole(s1.DefaultChannelAdminRole) + CheckNoError(t, roleRes3) + role4, roleRes4 := th.SystemAdminClient.GetRole(s1.DefaultChannelUserRole) + CheckNoError(t, roleRes4) + + assert.Zero(t, role3.DeleteAt) + assert.Zero(t, role4.DeleteAt) + + // Make sure this scheme is in use by a team. + res := <-th.App.Srv.Store.Channel().Save(&model.Channel{ + TeamId: model.NewId(), + DisplayName: model.NewId(), + Name: model.NewId(), + Type: model.CHANNEL_OPEN, + SchemeId: &s1.Id, + }, -1) + assert.Nil(t, res.Err) + channel := res.Data.(*model.Channel) + + // Try and fail to delete the scheme. + _, r2 := th.SystemAdminClient.DeleteScheme(s1.Id) + CheckInternalErrorStatus(t, r2) + + role3, roleRes3 = th.SystemAdminClient.GetRole(s1.DefaultChannelAdminRole) + CheckNoError(t, roleRes3) + role4, roleRes4 = th.SystemAdminClient.GetRole(s1.DefaultChannelUserRole) + CheckNoError(t, roleRes4) + + assert.Zero(t, role3.DeleteAt) + assert.Zero(t, role4.DeleteAt) + + // Change the team using it to a different scheme. + emptyString := "" + channel.SchemeId = &emptyString + res = <-th.App.Srv.Store.Channel().Update(channel) + + // Delete the Scheme. + _, r3 := th.SystemAdminClient.DeleteScheme(s1.Id) + CheckNoError(t, r3) + + // Check the roles were deleted. + role3, roleRes3 = th.SystemAdminClient.GetRole(s1.DefaultChannelAdminRole) + CheckNoError(t, roleRes3) + role4, roleRes4 = th.SystemAdminClient.GetRole(s1.DefaultChannelUserRole) + CheckNoError(t, roleRes4) + + assert.NotZero(t, role3.DeleteAt) + assert.NotZero(t, role4.DeleteAt) + }) + + t.Run("FailureCases", func(t *testing.T) { + th.App.SetLicense(model.NewTestLicense("")) + + scheme1 := &model.Scheme{ + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_CHANNEL, + } + + s1, r1 := th.SystemAdminClient.CreateScheme(scheme1) + CheckNoError(t, r1) + + // Test with unknown ID. + _, r2 := th.SystemAdminClient.DeleteScheme(model.NewId()) + CheckNotFoundStatus(t, r2) + + // Test with invalid ID. + _, r3 := th.SystemAdminClient.DeleteScheme("12345") + CheckBadRequestStatus(t, r3) + + // Test without required permissions. + _, r4 := th.Client.DeleteScheme(s1.Id) + CheckForbiddenStatus(t, r4) + + // Test without license. + th.App.SetLicense(nil) + _, r5 := th.SystemAdminClient.DeleteScheme(s1.Id) + CheckNotImplementedStatus(t, r5) + }) +} diff --git a/app/scheme.go b/app/scheme.go index 26ec6cd2a..b43914eb8 100644 --- a/app/scheme.go +++ b/app/scheme.go @@ -12,3 +12,104 @@ func (a *App) GetScheme(id string) (*model.Scheme, *model.AppError) { return result.Data.(*model.Scheme), nil } } + +func (a *App) GetSchemesPage(scope string, page int, perPage int) ([]*model.Scheme, *model.AppError) { + return a.GetSchemes(scope, page*perPage, perPage) +} + +func (a *App) GetSchemes(scope string, offset int, limit int) ([]*model.Scheme, *model.AppError) { + if result := <-a.Srv.Store.Scheme().GetAllPage(scope, offset, limit); result.Err != nil { + return nil, result.Err + } else { + return result.Data.([]*model.Scheme), nil + } +} + +func (a *App) CreateScheme(scheme *model.Scheme) (*model.Scheme, *model.AppError) { + if err := a.IsPhase2MigrationCompleted(); err != nil { + return nil, err + } + + // Clear any user-provided values for trusted properties. + scheme.DefaultTeamAdminRole = "" + scheme.DefaultTeamUserRole = "" + scheme.DefaultChannelAdminRole = "" + scheme.DefaultChannelUserRole = "" + scheme.CreateAt = 0 + scheme.UpdateAt = 0 + scheme.DeleteAt = 0 + + if result := <-a.Srv.Store.Scheme().Save(scheme); result.Err != nil { + return nil, result.Err + } else { + return scheme, nil + } +} + +func (a *App) PatchScheme(scheme *model.Scheme, patch *model.SchemePatch) (*model.Scheme, *model.AppError) { + if err := a.IsPhase2MigrationCompleted(); err != nil { + return nil, err + } + + scheme.Patch(patch) + scheme, err := a.UpdateScheme(scheme) + if err != nil { + return nil, err + } + + return scheme, err +} + +func (a *App) UpdateScheme(scheme *model.Scheme) (*model.Scheme, *model.AppError) { + if err := a.IsPhase2MigrationCompleted(); err != nil { + return nil, err + } + + if result := <-a.Srv.Store.Scheme().Save(scheme); result.Err != nil { + return nil, result.Err + } else { + return scheme, nil + } +} + +func (a *App) DeleteScheme(schemeId string) (*model.Scheme, *model.AppError) { + if err := a.IsPhase2MigrationCompleted(); err != nil { + return nil, err + } + + if result := <-a.Srv.Store.Scheme().Delete(schemeId); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.Scheme), nil + } +} + +func (a *App) GetTeamsForSchemePage(scheme *model.Scheme, page int, perPage int) ([]*model.Team, *model.AppError) { + return a.GetTeamsForScheme(scheme, page*perPage, perPage) +} + +func (a *App) GetTeamsForScheme(scheme *model.Scheme, offset int, limit int) ([]*model.Team, *model.AppError) { + if result := <-a.Srv.Store.Team().GetTeamsByScheme(scheme.Id, offset, limit); result.Err != nil { + return nil, result.Err + } else { + return result.Data.([]*model.Team), nil + } +} + +func (a *App) GetChannelsForSchemePage(scheme *model.Scheme, page int, perPage int) (model.ChannelList, *model.AppError) { + return a.GetChannelsForScheme(scheme, page*perPage, perPage) +} + +func (a *App) GetChannelsForScheme(scheme *model.Scheme, offset int, limit int) (model.ChannelList, *model.AppError) { + if result := <-a.Srv.Store.Channel().GetChannelsByScheme(scheme.Id, offset, limit); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(model.ChannelList), nil + } +} + +func (a *App) IsPhase2MigrationCompleted() *model.AppError { + // TODO: Actually check the Phase 2 migration has completed before permitting these actions. + + return nil +} diff --git a/i18n/en.json b/i18n/en.json index 0ff5e5378..c993b0411 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -6655,6 +6655,14 @@ "translation": "Unable to delete reaction" }, { + "id": "api.scheme.get_teams_for_scheme.scope.error", + "translation": "Unable to get the teams for scheme because the supplied scheme is not a team scheme." + }, + { + "id": "api.scheme.get_channels_for_scheme.scope.error", + "translation": "Unable to get the channels for scheme because the supplied scheme is not a channel scheme." + }, + { "id": "store.sql_reaction.delete_all_with_emoj_name.delete_reactions.app_error", "translation": "Unable to delete reactions with the given emoji name" }, diff --git a/model/client4.go b/model/client4.go index f17bb089a..d4410a5c3 100644 --- a/model/client4.go +++ b/model/client4.go @@ -318,6 +318,14 @@ func (c *Client4) GetRolesRoute() string { return fmt.Sprintf("/roles") } +func (c *Client4) GetSchemesRoute() string { + return fmt.Sprintf("/schemes") +} + +func (c *Client4) GetSchemeRoute(id string) string { + return c.GetSchemesRoute() + fmt.Sprintf("/%v", id) +} + func (c *Client4) GetAnalyticsRoute() string { return fmt.Sprintf("/analytics") } @@ -3420,6 +3428,78 @@ func (c *Client4) PatchRole(roleId string, patch *RolePatch) (*Role, *Response) } } +// Schemes Section + +// CreateScheme creates a new Scheme. +func (c *Client4) CreateScheme(scheme *Scheme) (*Scheme, *Response) { + if r, err := c.DoApiPost(c.GetSchemesRoute(), scheme.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return SchemeFromJson(r.Body), BuildResponse(r) + } +} + +// GetScheme gets a single scheme by ID. +func (c *Client4) GetScheme(id string) (*Scheme, *Response) { + if r, err := c.DoApiGet(c.GetSchemeRoute(id), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return SchemeFromJson(r.Body), BuildResponse(r) + } +} + +// Get all schemes, sorted with the most recently created first, optionally filtered by scope. +func (c *Client4) GetSchemes(scope string, page int, perPage int) ([]*Scheme, *Response) { + if r, err := c.DoApiGet(c.GetSchemesRoute()+fmt.Sprintf("?scope=%v&page=%v&per_page=%v", scope, page, perPage), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return SchemesFromJson(r.Body), BuildResponse(r) + } +} + +// DeleteScheme deletes a single scheme by ID. +func (c *Client4) DeleteScheme(id string) (bool, *Response) { + if r, err := c.DoApiDelete(c.GetSchemeRoute(id)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// PatchScheme partially updates a scheme in the system. Any missing fields are not updated. +func (c *Client4) PatchScheme(id string, patch *SchemePatch) (*Scheme, *Response) { + if r, err := c.DoApiPut(c.GetSchemeRoute(id)+"/patch", patch.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return SchemeFromJson(r.Body), BuildResponse(r) + } +} + +// Get the teams using this scheme, sorted alphabetically by display name. +func (c *Client4) GetTeamsForScheme(schemeId string, page int, perPage int) ([]*Team, *Response) { + if r, err := c.DoApiGet(c.GetSchemeRoute(schemeId)+fmt.Sprintf("/teams?page=%v&per_page=%v", page, perPage), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TeamListFromJson(r.Body), BuildResponse(r) + } +} + +// Get the channels using this scheme, sorted alphabetically by display name. +func (c *Client4) GetChannelsForScheme(schemeId string, page int, perPage int) (ChannelList, *Response) { + if r, err := c.DoApiGet(c.GetSchemeRoute(schemeId)+fmt.Sprintf("/channels?page=%v&per_page=%v", page, perPage), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return *ChannelListFromJson(r.Body), BuildResponse(r) + } +} + // Plugin Section // UploadPlugin takes an io.Reader stream pointing to the contents of a .tar.gz plugin. diff --git a/model/scheme.go b/model/scheme.go index c3ae7f15d..f949d9122 100644 --- a/model/scheme.go +++ b/model/scheme.go @@ -29,6 +29,11 @@ type Scheme struct { DefaultChannelUserRole string `json:"default_channel_user_role"` } +type SchemePatch struct { + Name *string `json:"name"` + Description *string `json:"description"` +} + type SchemeIDPatch struct { SchemeID *string `json:"scheme_id"` } @@ -44,6 +49,20 @@ func SchemeFromJson(data io.Reader) *Scheme { return scheme } +func SchemesToJson(schemes []*Scheme) string { + b, _ := json.Marshal(schemes) + return string(b) +} + +func SchemesFromJson(data io.Reader) []*Scheme { + var schemes []*Scheme + if err := json.NewDecoder(data).Decode(&schemes); err == nil { + return schemes + } else { + return nil + } +} + func (scheme *Scheme) IsValid() bool { if len(scheme.Id) != 26 { return false @@ -98,6 +117,26 @@ func (scheme *Scheme) IsValidForCreate() bool { return true } +func (scheme *Scheme) Patch(patch *SchemePatch) { + if patch.Name != nil { + scheme.Name = *patch.Name + } + if patch.Description != nil { + scheme.Description = *patch.Description + } +} + +func (patch *SchemePatch) ToJson() string { + b, _ := json.Marshal(patch) + return string(b) +} + +func SchemePatchFromJson(data io.Reader) *SchemePatch { + var patch *SchemePatch + json.NewDecoder(data).Decode(&patch) + return patch +} + func SchemeIDFromJson(data io.Reader) *string { var p *SchemeIDPatch json.NewDecoder(data).Decode(&p) diff --git a/store/layered_store.go b/store/layered_store.go index 603976fc0..cbabe9d22 100644 --- a/store/layered_store.go +++ b/store/layered_store.go @@ -292,3 +292,9 @@ func (s *LayeredSchemeStore) Delete(schemeId string) StoreChannel { return supplier.SchemeDelete(s.TmpContext, schemeId) }) } + +func (s *LayeredSchemeStore) GetAllPage(scope string, offset int, limit int) StoreChannel { + return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult { + return supplier.SchemeGetAllPage(s.TmpContext, scope, offset, limit) + }) +} diff --git a/store/layered_store_supplier.go b/store/layered_store_supplier.go index 04fa26fd3..4f57004bb 100644 --- a/store/layered_store_supplier.go +++ b/store/layered_store_supplier.go @@ -42,4 +42,5 @@ type LayeredStoreSupplier interface { SchemeSave(ctx context.Context, scheme *model.Scheme, hints ...LayeredStoreHint) *LayeredStoreSupplierResult SchemeGet(ctx context.Context, schemeId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult SchemeDelete(ctx context.Context, schemeId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult + SchemeGetAllPage(ctx context.Context, scope string, offset int, limit int, hints ...LayeredStoreHint) *LayeredStoreSupplierResult } diff --git a/store/local_cache_supplier_schemes.go b/store/local_cache_supplier_schemes.go index 2a8f73a71..809c60510 100644 --- a/store/local_cache_supplier_schemes.go +++ b/store/local_cache_supplier_schemes.go @@ -42,3 +42,7 @@ func (s *LocalCacheSupplier) SchemeDelete(ctx context.Context, schemeId string, return s.Next().SchemeDelete(ctx, schemeId, hints...) } + +func (s *LocalCacheSupplier) SchemeGetAllPage(ctx context.Context, scope string, offset int, limit int, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + return s.Next().SchemeGetAllPage(ctx, scope, offset, limit, hints...) +} diff --git a/store/redis_supplier_schemes.go b/store/redis_supplier_schemes.go index 4c05e9329..3bd747044 100644 --- a/store/redis_supplier_schemes.go +++ b/store/redis_supplier_schemes.go @@ -23,3 +23,8 @@ func (s *RedisSupplier) SchemeDelete(ctx context.Context, schemeId string, hints // TODO: Redis caching. return s.Next().SchemeDelete(ctx, schemeId, hints...) } + +func (s *RedisSupplier) SchemeGetAllPage(ctx context.Context, scope string, offset int, limit int, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + // TODO: Redis caching. + return s.Next().SchemeGetAllPage(ctx, scope, offset, limit, hints...) +} diff --git a/store/sqlstore/channel_store.go b/store/sqlstore/channel_store.go index b12c553a4..beef1be80 100644 --- a/store/sqlstore/channel_store.go +++ b/store/sqlstore/channel_store.go @@ -1730,7 +1730,7 @@ func (s SqlChannelStore) GetMembersByIds(channelId string, userIds []string) sto func (s SqlChannelStore) GetChannelsByScheme(schemeId string, offset int, limit int) store.StoreChannel { return store.Do(func(result *store.StoreResult) { - var channels []*model.Channel + var channels model.ChannelList _, err := s.GetReplica().Select(&channels, "SELECT * FROM Channels WHERE SchemeId = :SchemeId ORDER BY DisplayName LIMIT :Limit OFFSET :Offset", map[string]interface{}{"SchemeId": schemeId, "Offset": offset, "Limit": limit}) if err != nil { result.Err = model.NewAppError("SqlChannelStore.GetChannelsByScheme", "store.sql_channel.get_by_scheme.app_error", nil, "schemeId="+schemeId+" "+err.Error(), http.StatusInternalServerError) diff --git a/store/sqlstore/scheme_supplier.go b/store/sqlstore/scheme_supplier.go index 278d1a3c4..448e5a92f 100644 --- a/store/sqlstore/scheme_supplier.go +++ b/store/sqlstore/scheme_supplier.go @@ -270,3 +270,22 @@ func (s *SqlSupplier) SchemeDelete(ctx context.Context, schemeId string, hints . return result } + +func (s *SqlSupplier) SchemeGetAllPage(ctx context.Context, scope string, offset int, limit int, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { + result := store.NewSupplierResult() + + var schemes []*model.Scheme + + scopeClause := "" + if len(scope) > 0 { + scopeClause = " AND Scope=:Scope " + } + + if _, err := s.GetReplica().Select(&schemes, "SELECT * from Schemes WHERE DeleteAt = 0 "+scopeClause+" ORDER BY CreateAt DESC LIMIT :Limit OFFSET :Offset", map[string]interface{}{"Limit": limit, "Offset": offset, "Scope": scope}); err != nil { + result.Err = model.NewAppError("SqlSchemeStore.Get", "store.sql_scheme.get.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + result.Data = schemes + + return result +} diff --git a/store/store.go b/store/store.go index 1085198bd..dd149fbe9 100644 --- a/store/store.go +++ b/store/store.go @@ -485,5 +485,6 @@ type RoleStore interface { type SchemeStore interface { Save(scheme *model.Scheme) StoreChannel Get(schemeId string) StoreChannel + GetAllPage(scope string, offset int, limit int) StoreChannel Delete(schemeId string) StoreChannel } diff --git a/store/storetest/channel_store.go b/store/storetest/channel_store.go index 7427c816c..d90a0ae1e 100644 --- a/store/storetest/channel_store.go +++ b/store/storetest/channel_store.go @@ -2239,18 +2239,18 @@ func testChannelStoreGetChannelsByScheme(t *testing.T, ss store.Store) { // Get the channels by a valid Scheme ID. res1 := <-ss.Channel().GetChannelsByScheme(s1.Id, 0, 100) assert.Nil(t, res1.Err) - d1 := res1.Data.([]*model.Channel) + d1 := res1.Data.(model.ChannelList) assert.Len(t, d1, 2) // Get the channels by a valid Scheme ID where there aren't any matching Channel. res2 := <-ss.Channel().GetChannelsByScheme(s2.Id, 0, 100) assert.Nil(t, res2.Err) - d2 := res2.Data.([]*model.Channel) + d2 := res2.Data.(model.ChannelList) assert.Len(t, d2, 0) // Get the channels by an invalid Scheme ID. res3 := <-ss.Channel().GetChannelsByScheme(model.NewId(), 0, 100) assert.Nil(t, res3.Err) - d3 := res3.Data.([]*model.Channel) + d3 := res3.Data.(model.ChannelList) assert.Len(t, d3, 0) } diff --git a/store/storetest/mocks/LayeredStoreDatabaseLayer.go b/store/storetest/mocks/LayeredStoreDatabaseLayer.go index 6f6776b47..a505b6434 100644 --- a/store/storetest/mocks/LayeredStoreDatabaseLayer.go +++ b/store/storetest/mocks/LayeredStoreDatabaseLayer.go @@ -632,6 +632,29 @@ func (_m *LayeredStoreDatabaseLayer) SchemeGet(ctx context.Context, schemeId str return r0 } +// SchemeGetAllPage provides a mock function with given fields: ctx, scope, offset, limit, hints +func (_m *LayeredStoreDatabaseLayer) SchemeGetAllPage(ctx context.Context, scope string, offset int, limit int, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { + _va := make([]interface{}, len(hints)) + for _i := range hints { + _va[_i] = hints[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, scope, offset, limit) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *store.LayeredStoreSupplierResult + if rf, ok := ret.Get(0).(func(context.Context, string, int, int, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok { + r0 = rf(ctx, scope, offset, limit, hints...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*store.LayeredStoreSupplierResult) + } + } + + return r0 +} + // SchemeSave provides a mock function with given fields: ctx, scheme, hints func (_m *LayeredStoreDatabaseLayer) SchemeSave(ctx context.Context, scheme *model.Scheme, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { _va := make([]interface{}, len(hints)) diff --git a/store/storetest/mocks/LayeredStoreSupplier.go b/store/storetest/mocks/LayeredStoreSupplier.go index 8e1920d17..18dbe3af1 100644 --- a/store/storetest/mocks/LayeredStoreSupplier.go +++ b/store/storetest/mocks/LayeredStoreSupplier.go @@ -329,6 +329,29 @@ func (_m *LayeredStoreSupplier) SchemeGet(ctx context.Context, schemeId string, return r0 } +// SchemeGetAllPage provides a mock function with given fields: ctx, scope, offset, limit, hints +func (_m *LayeredStoreSupplier) SchemeGetAllPage(ctx context.Context, scope string, offset int, limit int, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { + _va := make([]interface{}, len(hints)) + for _i := range hints { + _va[_i] = hints[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, scope, offset, limit) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *store.LayeredStoreSupplierResult + if rf, ok := ret.Get(0).(func(context.Context, string, int, int, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok { + r0 = rf(ctx, scope, offset, limit, hints...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*store.LayeredStoreSupplierResult) + } + } + + return r0 +} + // SchemeSave provides a mock function with given fields: ctx, scheme, hints func (_m *LayeredStoreSupplier) SchemeSave(ctx context.Context, scheme *model.Scheme, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { _va := make([]interface{}, len(hints)) diff --git a/store/storetest/mocks/SchemeStore.go b/store/storetest/mocks/SchemeStore.go index 00eeb0573..2868521b3 100644 --- a/store/storetest/mocks/SchemeStore.go +++ b/store/storetest/mocks/SchemeStore.go @@ -45,6 +45,22 @@ func (_m *SchemeStore) Get(schemeId string) store.StoreChannel { return r0 } +// GetAllPage provides a mock function with given fields: scope, offset, limit +func (_m *SchemeStore) GetAllPage(scope string, offset int, limit int) store.StoreChannel { + ret := _m.Called(scope, offset, limit) + + var r0 store.StoreChannel + if rf, ok := ret.Get(0).(func(string, int, int) store.StoreChannel); ok { + r0 = rf(scope, offset, limit) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(store.StoreChannel) + } + } + + return r0 +} + // Save provides a mock function with given fields: scheme func (_m *SchemeStore) Save(scheme *model.Scheme) store.StoreChannel { ret := _m.Called(scheme) diff --git a/store/storetest/mocks/SqlStore.go b/store/storetest/mocks/SqlStore.go index baf112e87..021baa7d3 100644 --- a/store/storetest/mocks/SqlStore.go +++ b/store/storetest/mocks/SqlStore.go @@ -143,6 +143,20 @@ func (_m *SqlStore) CreateColumnIfNotExists(tableName string, columnName string, return r0 } +// CreateColumnIfNotExistsNoDefault provides a mock function with given fields: tableName, columnName, mySqlColType, postgresColType +func (_m *SqlStore) CreateColumnIfNotExistsNoDefault(tableName string, columnName string, mySqlColType string, postgresColType string) bool { + ret := _m.Called(tableName, columnName, mySqlColType, postgresColType) + + var r0 bool + if rf, ok := ret.Get(0).(func(string, string, string, string) bool); ok { + r0 = rf(tableName, columnName, mySqlColType, postgresColType) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + // CreateCompositeIndexIfNotExists provides a mock function with given fields: indexName, tableName, columnNames func (_m *SqlStore) CreateCompositeIndexIfNotExists(indexName string, tableName string, columnNames []string) bool { ret := _m.Called(indexName, tableName, columnNames) diff --git a/store/storetest/scheme_store.go b/store/storetest/scheme_store.go index 45d136d3e..c0cbe5deb 100644 --- a/store/storetest/scheme_store.go +++ b/store/storetest/scheme_store.go @@ -17,6 +17,7 @@ func TestSchemeStore(t *testing.T, ss store.Store) { t.Run("Save", func(t *testing.T) { testSchemeStoreSave(t, ss) }) t.Run("Get", func(t *testing.T) { testSchemeStoreGet(t, ss) }) + t.Run("GetAllPage", func(t *testing.T) { testSchemeStoreGetAllPage(t, ss) }) t.Run("Delete", func(t *testing.T) { testSchemeStoreDelete(t, ss) }) } @@ -171,6 +172,66 @@ func testSchemeStoreGet(t *testing.T, ss store.Store) { assert.NotNil(t, res3.Err) } +func testSchemeStoreGetAllPage(t *testing.T, ss store.Store) { + // Save a scheme to test with. + schemes := []*model.Scheme{ + { + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + }, + { + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_CHANNEL, + }, + { + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + }, + { + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_CHANNEL, + }, + } + + for _, scheme := range schemes { + store.Must(ss.Scheme().Save(scheme)) + } + + r1 := <-ss.Scheme().GetAllPage("", 0, 2) + assert.Nil(t, r1.Err) + s1 := r1.Data.([]*model.Scheme) + assert.Len(t, s1, 2) + + r2 := <-ss.Scheme().GetAllPage("", 2, 2) + assert.Nil(t, r2.Err) + s2 := r2.Data.([]*model.Scheme) + assert.Len(t, s2, 2) + assert.NotEqual(t, s1[0].Name, s2[0].Name) + assert.NotEqual(t, s1[0].Name, s2[1].Name) + assert.NotEqual(t, s1[1].Name, s2[0].Name) + assert.NotEqual(t, s1[1].Name, s2[1].Name) + + r3 := <-ss.Scheme().GetAllPage("team", 0, 1000) + assert.Nil(t, r3.Err) + s3 := r3.Data.([]*model.Scheme) + assert.NotZero(t, len(s3)) + for _, s := range s3 { + assert.Equal(t, "team", s.Scope) + } + + r4 := <-ss.Scheme().GetAllPage("channel", 0, 1000) + assert.Nil(t, r4.Err) + s4 := r4.Data.([]*model.Scheme) + assert.NotZero(t, len(s4)) + for _, s := range s4 { + assert.Equal(t, "channel", s.Scope) + } +} + func testSchemeStoreDelete(t *testing.T, ss store.Store) { // Save a new scheme. s1 := &model.Scheme{ |