From 50d069e86db90e26c65124dee2e55ab9b317d771 Mon Sep 17 00:00:00 2001 From: Nick Misasi Date: Tue, 24 May 2022 09:16:24 -0400 Subject: [PATCH] [MM-44307] On Limits Change, archive or restore teams according to the limits (#20247) Automatic Merge --- api4/cloud.go | 2 + api4/team.go | 18 ++ app/app_iface.go | 3 + app/cloud.go | 11 + app/opentracing/opentracing_layer.go | 66 ++++++ app/team.go | 101 +++++++++ app/team_test.go | 191 ++++++++++++++++++ .../000085_add_cloud_limits_archived.down.sql | 14 ++ .../000085_add_cloud_limits_archived.up.sql | 14 ++ .../000085_add_cloud_limits_archived.down.sql | 1 + .../000085_add_cloud_limits_archived.up.sql | 1 + model/team.go | 52 ++--- store/sqlstore/team_store.go | 6 +- 13 files changed, 454 insertions(+), 26 deletions(-) create mode 100644 db/migrations/mysql/000085_add_cloud_limits_archived.down.sql create mode 100644 db/migrations/mysql/000085_add_cloud_limits_archived.up.sql create mode 100644 db/migrations/postgres/000085_add_cloud_limits_archived.down.sql create mode 100644 db/migrations/postgres/000085_add_cloud_limits_archived.up.sql diff --git a/api4/cloud.go b/api4/cloud.go index 0f7ea1e1d4..62d9a1350f 100644 --- a/api4/cloud.go +++ b/api4/cloud.go @@ -536,7 +536,9 @@ func handleCWSWebhook(c *Context, w http.ResponseWriter, r *http.Request) { return true }, plugin.OnCloudLimitsUpdatedID) } + c.App.AdjustInProductLimits(event.ProductLimits, event.Subscription) } + if err := c.App.Cloud().UpdateSubscriptionFromHook(event.ProductLimits, event.Subscription); err != nil { c.Err = model.NewAppError("Api4.handleCWSWebhook", "api.cloud.subscription.update_error", nil, err.Error(), http.StatusInternalServerError) return diff --git a/api4/team.go b/api4/team.go index eb10537732..5a337cae5e 100644 --- a/api4/team.go +++ b/api4/team.go @@ -43,6 +43,7 @@ func (api *API) InitTeam() { api.BaseRoutes.Team.Handle("", api.APISessionRequired(getTeam)).Methods("GET") api.BaseRoutes.Team.Handle("", api.APISessionRequired(updateTeam)).Methods("PUT") api.BaseRoutes.Team.Handle("", api.APISessionRequired(deleteTeam)).Methods("DELETE") + api.BaseRoutes.Team.Handle("/except", api.APISessionRequired(softDeleteTeamsExcept)).Methods("DELETE") api.BaseRoutes.Team.Handle("/patch", api.APISessionRequired(patchTeam)).Methods("PUT") api.BaseRoutes.Team.Handle("/restore", api.APISessionRequired(restoreTeam)).Methods("POST") api.BaseRoutes.Team.Handle("/privacy", api.APISessionRequired(updateTeamPrivacy)).Methods("PUT") @@ -402,6 +403,23 @@ func deleteTeam(c *Context, w http.ResponseWriter, r *http.Request) { ReturnStatusOK(w) } +func softDeleteTeamsExcept(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireTeamId() + if c.Err != nil { + return + } + + if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PermissionManageTeam) { + c.SetPermissionError(model.PermissionManageTeam) + return + } + + err := c.App.SoftDeleteAllTeamsExcept(c.Params.TeamId) + if err != nil { + c.Err = err + } +} + func getTeamsForUser(c *Context, w http.ResponseWriter, r *http.Request) { c.RequireUserId() if c.Err != nil { diff --git a/app/app_iface.go b/app/app_iface.go index bfcd595fb8..587d65b3ff 100644 --- a/app/app_iface.go +++ b/app/app_iface.go @@ -387,6 +387,8 @@ type AppIface interface { AddUserToTeamByTeamId(c *request.Context, teamID string, user *model.User) *model.AppError AddUserToTeamByToken(c *request.Context, userID string, tokenID string) (*model.Team, *model.TeamMember, *model.AppError) AdjustImage(file io.Reader) (*bytes.Buffer, *model.AppError) + AdjustInProductLimits(limits *model.ProductLimits, subscription *model.Subscription) *model.AppError + AdjustTeamsFromProductLimits(teamLimits *model.TeamsLimits) *model.AppError AllowOAuthAppAccessToUser(userID string, authRequest *model.AuthorizeRequest) (string, *model.AppError) AppendFile(fr io.Reader, path string) (int64, *model.AppError) AsymmetricSigningKey() *ecdsa.PrivateKey @@ -1022,6 +1024,7 @@ type AppIface interface { SetTeamIconFromFile(team *model.Team, file io.Reader) *model.AppError SetTeamIconFromMultiPartFile(teamID string, file multipart.File) *model.AppError SlackImport(c *request.Context, fileData multipart.File, fileSize int64, teamID string) (*model.AppError, *bytes.Buffer) + SoftDeleteAllTeamsExcept(teamID string) *model.AppError SoftDeleteTeam(teamID string) *model.AppError Srv() *Server SubmitInteractiveDialog(c *request.Context, request model.SubmitDialogRequest) (*model.SubmitDialogResponse, *model.AppError) diff --git a/app/cloud.go b/app/cloud.go index 2d26806c0e..e84ca3531e 100644 --- a/app/cloud.go +++ b/app/cloud.go @@ -37,6 +37,17 @@ func (a *App) SendPaymentFailedEmail(failedPayment *model.FailedPayment) *model. return nil } +func (a *App) AdjustInProductLimits(limits *model.ProductLimits, subscription *model.Subscription) *model.AppError { + if limits.Teams != nil && limits.Teams.Active != nil && *limits.Teams.Active > 0 { + err := a.AdjustTeamsFromProductLimits(limits.Teams) + if err != nil { + return err + } + } + + return nil +} + func (a *App) SendUpgradeConfirmationEmail() *model.AppError { sysAdmins, e := a.getSysAdminsEmailRecipients() if e != nil { diff --git a/app/opentracing/opentracing_layer.go b/app/opentracing/opentracing_layer.go index d87942d047..d36dde4a5a 100644 --- a/app/opentracing/opentracing_layer.go +++ b/app/opentracing/opentracing_layer.go @@ -625,6 +625,50 @@ func (a *OpenTracingAppLayer) AdjustImage(file io.Reader) (*bytes.Buffer, *model return resultVar0, resultVar1 } +func (a *OpenTracingAppLayer) AdjustInProductLimits(limits *model.ProductLimits, subscription *model.Subscription) *model.AppError { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.AdjustInProductLimits") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0 := a.app.AdjustInProductLimits(limits, subscription) + + if resultVar0 != nil { + span.LogFields(spanlog.Error(resultVar0)) + ext.Error.Set(span, true) + } + + return resultVar0 +} + +func (a *OpenTracingAppLayer) AdjustTeamsFromProductLimits(teamLimits *model.TeamsLimits) *model.AppError { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.AdjustTeamsFromProductLimits") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0 := a.app.AdjustTeamsFromProductLimits(teamLimits) + + if resultVar0 != nil { + span.LogFields(spanlog.Error(resultVar0)) + ext.Error.Set(span, true) + } + + return resultVar0 +} + func (a *OpenTracingAppLayer) AllowOAuthAppAccessToUser(userID string, authRequest *model.AuthorizeRequest) (string, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.AllowOAuthAppAccessToUser") @@ -15583,6 +15627,28 @@ func (a *OpenTracingAppLayer) SlackImport(c *request.Context, fileData multipart return resultVar0, resultVar1 } +func (a *OpenTracingAppLayer) SoftDeleteAllTeamsExcept(teamID string) *model.AppError { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SoftDeleteAllTeamsExcept") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0 := a.app.SoftDeleteAllTeamsExcept(teamID) + + if resultVar0 != nil { + span.LogFields(spanlog.Error(resultVar0)) + ext.Error.Set(span, true) + } + + return resultVar0 +} + func (a *OpenTracingAppLayer) SoftDeleteTeam(teamID string) *model.AppError { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SoftDeleteTeam") diff --git a/app/team.go b/app/team.go index 2281ca5775..ce1d3bda7b 100644 --- a/app/team.go +++ b/app/team.go @@ -14,6 +14,7 @@ import ( "mime/multipart" "net/http" "net/url" + "sort" "strings" "github.com/mattermost/mattermost-server/v6/app/email" @@ -29,6 +30,106 @@ import ( "github.com/mattermost/mattermost-server/v6/store/sqlstore" ) +func (a *App) AdjustTeamsFromProductLimits(teamLimits *model.TeamsLimits) *model.AppError { + maxActiveTeams := *teamLimits.Active + teams, appErr := a.GetAllTeams() + if appErr != nil { + return appErr + } + + if teams == nil { + return nil + } + // Sort the list of teams based on their creation date + sort.Slice(teams, func(i, j int) bool { + return teams[i].CreateAt < teams[j].CreateAt + }) + + var activeTeams []*model.Team + var cloudArchivedTeams []*model.Team + for _, team := range teams { + if team.DeleteAt == 0 { + activeTeams = append(activeTeams, team) + } + if team.DeleteAt > 0 && team.CloudLimitsArchived { + cloudArchivedTeams = append(cloudArchivedTeams, team) + } + } + + if len(activeTeams) > maxActiveTeams { + // If there are more active teams than allowed, we must archive them + // Remove the first n elements (where n is the allowed number of teams) so they aren't archived + + teamsToArchive := activeTeams[maxActiveTeams:] + + for _, team := range teamsToArchive { + cloudLimitsArchived := true + // Archive the remainder + patch := model.TeamPatch{CloudLimitsArchived: &cloudLimitsArchived} + _, err := a.PatchTeam(team.Id, &patch) + if err != nil { + return err + } + err = a.SoftDeleteTeam(team.Id) + if err != nil { + return err + } + } + } else if len(activeTeams) < maxActiveTeams && len(cloudArchivedTeams) > 0 { + // If the number of activeTeams is less than the allowed limit, and there are some cloudArchivedTeams, we can restore these cloudArchivedTeams + activeTeamsBeforeLimit := maxActiveTeams - len(activeTeams) + teamsToRestore := cloudArchivedTeams + // If the number of active teams remaining before the limit is hit is fewer than the number of cloudArchivedTeams, trim the list (still according to CreateAt) + // Otherwise, we can restore all of the cloudArchivedTeams without hitting the limit, so don't filter the list + if activeTeamsBeforeLimit < len(cloudArchivedTeams) { + teamsToRestore = cloudArchivedTeams[:(activeTeamsBeforeLimit)] + } + + cloudLimitsArchived := false + patch := &model.TeamPatch{CloudLimitsArchived: &cloudLimitsArchived} + for _, team := range teamsToRestore { + err := a.RestoreTeam(team.Id) + if err != nil { + return err + } + + _, err = a.PatchTeam(team.Id, patch) + if err != nil { + return err + } + } + } + + return nil +} + +func (a *App) SoftDeleteAllTeamsExcept(teamID string) *model.AppError { + teams, appErr := a.GetAllTeams() + if appErr != nil { + return appErr + } + + if teams == nil { + return nil + } + cloudLimitsArchived := true + patch := &model.TeamPatch{CloudLimitsArchived: &cloudLimitsArchived} + for _, team := range teams { + if team.Id != teamID { + _, err := a.PatchTeam(team.Id, patch) + if err != nil { + return err + } + + err = a.SoftDeleteTeam(team.Id) + if err != nil { + return err + } + } + } + return nil +} + func (a *App) CreateTeam(c *request.Context, team *model.Team) (*model.Team, *model.AppError) { rteam, err := a.ch.srv.teamService.CreateTeam(team) if err != nil { diff --git a/app/team_test.go b/app/team_test.go index 91afedbef0..a41e8e9ef2 100644 --- a/app/team_test.go +++ b/app/team_test.go @@ -466,6 +466,197 @@ func TestAddUserToTeamByTeamId(t *testing.T) { } +func TestSoftDeleteAllTeamsExcept(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + teams := []*model.Team{ + { + DisplayName: "team-1", + Name: "team-1", + Email: "foo@foo.com", + Type: model.TeamOpen, + }, + } + teamId := "" + for _, create := range teams { + team, err := th.App.CreateTeam(th.Context, create) + require.Nil(t, err) + teamId = team.Id + } + + err := th.App.SoftDeleteAllTeamsExcept(teamId) + assert.Nil(t, err) + allTeams, err := th.App.GetAllTeams() + require.Nil(t, err) + for _, team := range allTeams { + if team.Id == teamId { + require.Equal(t, int64(0), team.DeleteAt) + require.Equal(t, false, team.CloudLimitsArchived) + } else { + require.NotEqual(t, int64(0), team.DeleteAt) + require.Equal(t, true, team.CloudLimitsArchived) + } + } + +} + +func TestAdjustTeamsFromProductLimits(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + teams := []*model.Team{ + { + DisplayName: "team-1", + Name: "team-1", + Email: "foo@foo.com", + Type: model.TeamOpen, + }, + { + DisplayName: "team-2", + Name: "team-2", + Email: "foo@foo.com", + Type: model.TeamOpen, + }, + { + DisplayName: "team-3", + Name: "team-3", + Email: "foo@foo.com", + Type: model.TeamOpen, + }, + } + teamIds := []string{} + for _, create := range teams { + team, err := th.App.CreateTeam(th.Context, create) + require.Nil(t, err) + teamIds = append(teamIds, team.Id) + } + t.Run("Should soft delete teams if there are more teams than the limit", func(t *testing.T) { + activeLimit := 1 + teamLimits := &model.TeamsLimits{Active: &activeLimit} + + err := th.App.AdjustTeamsFromProductLimits(teamLimits) + require.Nil(t, err) + + teamsList, err := th.App.GetTeams(teamIds) + + require.Nil(t, err) + + // Sort the list of teams based on their creation date + sort.Slice(teamsList, func(i, j int) bool { + return teamsList[i].CreateAt < teamsList[j].CreateAt + }) + + for i := range teamsList { + require.Equal(t, teamsList[i].DisplayName, teams[i].DisplayName) + require.NotEqual(t, 0, teamsList[i].DeleteAt) + require.Equal(t, true, teamsList[i].CloudLimitsArchived) + } + }) + + t.Run("Should not do anything if the amount of teams is equal to the limit", func(t *testing.T) { + + expectedTeamsList, err := th.App.GetAllTeams() + + var expectedActiveTeams []*model.Team + var expectedCloudArchivedTeams []*model.Team + for _, team := range expectedTeamsList { + if team.DeleteAt == 0 { + expectedActiveTeams = append(expectedActiveTeams, team) + } + if team.DeleteAt > 0 && team.CloudLimitsArchived { + expectedCloudArchivedTeams = append(expectedCloudArchivedTeams, team) + } + } + + require.Nil(t, err) + + activeLimit := len(expectedActiveTeams) + teamLimits := &model.TeamsLimits{Active: &activeLimit} + err = th.App.AdjustTeamsFromProductLimits(teamLimits) + require.Nil(t, err) + + actualTeamsList, err := th.App.GetAllTeams() + + require.Nil(t, err) + var actualActiveTeams []*model.Team + var actualCloudArchivedTeams []*model.Team + for _, team := range actualTeamsList { + if team.DeleteAt == 0 { + actualActiveTeams = append(actualActiveTeams, team) + } + if team.DeleteAt > 0 && team.CloudLimitsArchived { + actualCloudArchivedTeams = append(actualCloudArchivedTeams, team) + } + } + + require.Equal(t, len(expectedActiveTeams), len(actualActiveTeams)) + require.Equal(t, len(expectedCloudArchivedTeams), len(actualCloudArchivedTeams)) + }) + + t.Run("Should restore archived teams if limit increases", func(t *testing.T) { + activeLimit := 1 + teamLimits := &model.TeamsLimits{Active: &activeLimit} + + err := th.App.AdjustTeamsFromProductLimits(teamLimits) + require.Nil(t, err) + activeLimit = 10000 // make the limit extremely high so all teams are enabled + teamLimits = &model.TeamsLimits{Active: &activeLimit} + + err = th.App.AdjustTeamsFromProductLimits(teamLimits) + require.Nil(t, err) + + teamsList, err := th.App.GetTeams(teamIds) + + require.Nil(t, err) + + // Sort the list of teams based on their creation date + sort.Slice(teamsList, func(i, j int) bool { + return teamsList[i].CreateAt < teamsList[j].CreateAt + }) + + for i := range teamsList { + require.Equal(t, teamsList[i].DisplayName, teams[i].DisplayName) + require.Equal(t, int64(0), teamsList[i].DeleteAt) + require.Equal(t, false, teamsList[i].CloudLimitsArchived) + } + }) + + t.Run("Should only restore teams that were archived by cloud limits", func(t *testing.T) { + + activeLimit := 1 + teamLimits := &model.TeamsLimits{Active: &activeLimit} + + err := th.App.AdjustTeamsFromProductLimits(teamLimits) + require.Nil(t, err) + + cloudLimitsArchived := false + patch := &model.TeamPatch{CloudLimitsArchived: &cloudLimitsArchived} + team, err := th.App.PatchTeam(teamIds[0], patch) + require.Nil(t, err) + require.Equal(t, false, team.CloudLimitsArchived) + + activeLimit = 10000 // make the limit extremely high so all teams are enabled + teamLimits = &model.TeamsLimits{Active: &activeLimit} + + err = th.App.AdjustTeamsFromProductLimits(teamLimits) + require.Nil(t, err) + + teamsList, err := th.App.GetTeams(teamIds) + + require.Nil(t, err) + + // Sort the list of teams based on their creation date + sort.Slice(teamsList, func(i, j int) bool { + return teamsList[i].CreateAt < teamsList[j].CreateAt + }) + + require.NotEqual(t, int64(0), teamsList[0].DeleteAt) + require.Equal(t, int64(0), teamsList[1].DeleteAt) + require.Equal(t, int64(0), teamsList[2].DeleteAt) + }) + +} + func TestPermanentDeleteTeam(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() diff --git a/db/migrations/mysql/000085_add_cloud_limits_archived.down.sql b/db/migrations/mysql/000085_add_cloud_limits_archived.down.sql new file mode 100644 index 0000000000..f3f646a1b1 --- /dev/null +++ b/db/migrations/mysql/000085_add_cloud_limits_archived.down.sql @@ -0,0 +1,14 @@ +SET @preparedStatement = (SELECT IF( + EXISTS ( + SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Teams' + AND table_schema = DATABASE() + AND column_name = 'CloudLimitsArchived' + ), + 'ALTER TABLE Teams DROP COLUMN CloudLimitsArchived;', + 'SELECT 1' +)); + +PREPARE alterIfExists FROM @preparedStatement; +EXECUTE alterIfExists; +DEALLOCATE PREPARE alterIfExists; diff --git a/db/migrations/mysql/000085_add_cloud_limits_archived.up.sql b/db/migrations/mysql/000085_add_cloud_limits_archived.up.sql new file mode 100644 index 0000000000..eb5d01dc23 --- /dev/null +++ b/db/migrations/mysql/000085_add_cloud_limits_archived.up.sql @@ -0,0 +1,14 @@ +SET @preparedStatement = (SELECT IF( + NOT EXISTS( + SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Teams' + AND table_schema = DATABASE() + AND column_name = 'CloudLimitsArchived' + ), + 'ALTER TABLE Teams ADD COLUMN CloudLimitsArchived BOOLEAN NOT NULL DEFAULT FALSE;', + 'SELECT 1' +)); + +PREPARE alterIfNotExists FROM @preparedStatement; +EXECUTE alterIfNotExists; +DEALLOCATE PREPARE alterIfNotExists; diff --git a/db/migrations/postgres/000085_add_cloud_limits_archived.down.sql b/db/migrations/postgres/000085_add_cloud_limits_archived.down.sql new file mode 100644 index 0000000000..c5d14049d8 --- /dev/null +++ b/db/migrations/postgres/000085_add_cloud_limits_archived.down.sql @@ -0,0 +1 @@ +ALTER TABLE teams DROP COLUMN IF EXISTS CloudLimitsArchived; diff --git a/db/migrations/postgres/000085_add_cloud_limits_archived.up.sql b/db/migrations/postgres/000085_add_cloud_limits_archived.up.sql new file mode 100644 index 0000000000..8c10c23063 --- /dev/null +++ b/db/migrations/postgres/000085_add_cloud_limits_archived.up.sql @@ -0,0 +1 @@ +ALTER TABLE teams ADD COLUMN IF NOT EXISTS CloudLimitsArchived bool NOT NULL DEFAULT FALSE; diff --git a/model/team.go b/model/team.go index 92b6f7fc5a..d37fdd05df 100644 --- a/model/team.go +++ b/model/team.go @@ -24,32 +24,34 @@ const ( ) type Team struct { - Id string `json:"id"` - CreateAt int64 `json:"create_at"` - UpdateAt int64 `json:"update_at"` - DeleteAt int64 `json:"delete_at"` - DisplayName string `json:"display_name"` - Name string `json:"name"` - Description string `json:"description"` - Email string `json:"email"` - Type string `json:"type"` - CompanyName string `json:"company_name"` - AllowedDomains string `json:"allowed_domains"` - InviteId string `json:"invite_id"` - AllowOpenInvite bool `json:"allow_open_invite"` - LastTeamIconUpdate int64 `json:"last_team_icon_update,omitempty"` - SchemeId *string `json:"scheme_id"` - GroupConstrained *bool `json:"group_constrained"` - PolicyID *string `json:"policy_id"` + Id string `json:"id"` + CreateAt int64 `json:"create_at"` + UpdateAt int64 `json:"update_at"` + DeleteAt int64 `json:"delete_at"` + DisplayName string `json:"display_name"` + Name string `json:"name"` + Description string `json:"description"` + Email string `json:"email"` + Type string `json:"type"` + CompanyName string `json:"company_name"` + AllowedDomains string `json:"allowed_domains"` + InviteId string `json:"invite_id"` + AllowOpenInvite bool `json:"allow_open_invite"` + LastTeamIconUpdate int64 `json:"last_team_icon_update,omitempty"` + SchemeId *string `json:"scheme_id"` + GroupConstrained *bool `json:"group_constrained"` + PolicyID *string `json:"policy_id"` + CloudLimitsArchived bool `json:"cloud_limits_archived"` } type TeamPatch struct { - DisplayName *string `json:"display_name"` - Description *string `json:"description"` - CompanyName *string `json:"company_name"` - AllowedDomains *string `json:"allowed_domains"` - AllowOpenInvite *bool `json:"allow_open_invite"` - GroupConstrained *bool `json:"group_constrained"` + DisplayName *string `json:"display_name"` + Description *string `json:"description"` + CompanyName *string `json:"company_name"` + AllowedDomains *string `json:"allowed_domains"` + AllowOpenInvite *bool `json:"allow_open_invite"` + GroupConstrained *bool `json:"group_constrained"` + CloudLimitsArchived *bool `json:"cloud_limits_archived"` } type TeamForExport struct { @@ -246,6 +248,10 @@ func (o *Team) Patch(patch *TeamPatch) { if patch.GroupConstrained != nil { o.GroupConstrained = patch.GroupConstrained } + + if patch.CloudLimitsArchived != nil { + o.CloudLimitsArchived = *patch.CloudLimitsArchived + } } func (o *Team) IsGroupConstrained() bool { diff --git a/store/sqlstore/team_store.go b/store/sqlstore/team_store.go index de2462c467..76851f7289 100644 --- a/store/sqlstore/team_store.go +++ b/store/sqlstore/team_store.go @@ -227,10 +227,10 @@ func (s SqlTeamStore) Save(team *model.Team) (*model.Team, error) { if _, err := s.GetMasterX().NamedExec(`INSERT INTO Teams (Id, CreateAt, UpdateAt, DeleteAt, DisplayName, Name, Description, Email, Type, CompanyName, AllowedDomains, - InviteId, AllowOpenInvite, LastTeamIconUpdate, SchemeId, GroupConstrained) + InviteId, AllowOpenInvite, LastTeamIconUpdate, SchemeId, GroupConstrained, CloudLimitsArchived) VALUES (:Id, :CreateAt, :UpdateAt, :DeleteAt, :DisplayName, :Name, :Description, :Email, :Type, :CompanyName, :AllowedDomains, - :InviteId, :AllowOpenInvite, :LastTeamIconUpdate, :SchemeId, :GroupConstrained)`, team); err != nil { + :InviteId, :AllowOpenInvite, :LastTeamIconUpdate, :SchemeId, :GroupConstrained, :CloudLimitsArchived)`, team); err != nil { if IsUniqueConstraintError(err, []string{"Name", "teams_name_key"}) { return nil, store.NewErrInvalidInput("Team", "id", team.Id) } @@ -268,7 +268,7 @@ func (s SqlTeamStore) Update(team *model.Team) (*model.Team, error) { SET CreateAt=:CreateAt, UpdateAt=:UpdateAt, DeleteAt=:DeleteAt, DisplayName=:DisplayName, Name=:Name, Description=:Description, Email=:Email, Type=:Type, CompanyName=:CompanyName, AllowedDomains=:AllowedDomains, InviteId=:InviteId, AllowOpenInvite=:AllowOpenInvite, LastTeamIconUpdate=:LastTeamIconUpdate, - SchemeId=:SchemeId, GroupConstrained=:GroupConstrained + SchemeId=:SchemeId, GroupConstrained=:GroupConstrained, CloudLimitsArchived=:CloudLimitsArchived WHERE Id=:Id`, team) if err != nil { return nil, errors.Wrapf(err, "failed to update Team with id=%s", team.Id)