From 65b536bc1d89972cb901b632baccad6a89e42403 Mon Sep 17 00:00:00 2001 From: Peter Sanchez Date: Thu, 30 May 2024 07:30:37 -0600 Subject: [PATCH] Removing `is_active` from User model as it's not used anywhere. Instead we rely on `is_verified` and `is_locked` for access control. --- admin/input.go | 8 +- admin/routes.go | 27 ++++-- api/gqlgen.yml | 1 - api/graph/generated.go | 147 ++++++++++++++----------------- api/graph/model/models_gen.go | 8 +- api/graph/schema.graphqls | 6 +- api/graph/schema.resolvers.go | 29 +++--- migrations/0001_initial.up.sql | 1 - models/models.go | 4 +- models/organization.go | 132 +++++++++++++-------------- models/schema.sql | 1 - models/user.go | 10 +-- templates/admin_dashboard.html | 16 +++- templates/admin_user_detail.html | 20 ++++- templates/admin_user_edit.html | 20 ++--- templates/admin_user_list.html | 4 +- 16 files changed, 232 insertions(+), 202 deletions(-) diff --git a/admin/input.go b/admin/input.go index 87e0da2..7cadb46 100644 --- a/admin/input.go +++ b/admin/input.go @@ -21,9 +21,9 @@ func (l *UserLockForm) Validate(c echo.Context) error { } type UserForm struct { - Name string `form:"name" validate:"required"` - Email string `form:"email" validate:"required,email"` - IsActive bool `form:"is_active"` + Name string `form:"name" validate:"required"` + Email string `form:"email" validate:"required,email"` + IsVerified bool `form:"is_verified"` } func (u *UserForm) Validate(c echo.Context) error { @@ -31,7 +31,7 @@ func (u *UserForm) Validate(c echo.Context) error { FailFast(false). String("name", &u.Name). String("email", &u.Email). - Bool("is_active", &u.IsActive). + Bool("is_verified", &u.IsVerified). BindErrors() if errs != nil { return validate.GetInputErrors(errs) diff --git a/admin/routes.go b/admin/routes.go index b99e9e5..e472b53 100644 --- a/admin/routes.go +++ b/admin/routes.go @@ -89,6 +89,8 @@ func (s *Service) Dashboard(c echo.Context) error { pd.Data["users"] = lt.Translate("Users") pd.Data["no_users"] = lt.Translate("No Users") pd.Data["is_active"] = lt.Translate("Is Active") + pd.Data["is_verified"] = lt.Translate("Is Verified") + pd.Data["is_locked"] = lt.Translate("Is Locked") pd.Data["name"] = lt.Translate("Name") pd.Data["created_on"] = lt.Translate("Created On") pd.Data["see_more"] = lt.Translate("See more") @@ -130,7 +132,8 @@ func (s *Service) Dashboard(c echo.Context) error { id name email - isActive + isEmailVerified + isLocked createdOn } } @@ -392,6 +395,9 @@ func (s *Service) OrgDetail(c echo.Context) error { op.Var("id", id) err = links.Execute(c.Request().Context(), op, &result) if err != nil { + if graphError, ok := err.(*gqlclient.Error); ok { + return links.ParseInputErrors(c, graphError, gobwebs.Map{}) + } return err } @@ -1352,6 +1358,9 @@ func (s *Service) UserDetail(c echo.Context) error { pd.Data["email"] = lt.Translate("Email") pd.Data["created_on"] = lt.Translate("Created On") pd.Data["is_active"] = lt.Translate("Is Active") + pd.Data["is_verified"] = lt.Translate("Is Verified") + pd.Data["is_locked"] = lt.Translate("Is Locked") + pd.Data["lock_reason"] = lt.Translate("Lock Reason") pd.Data["name"] = lt.Translate("Name") pd.Data["yes"] = lt.Translate("Yes") pd.Data["edit"] = lt.Translate("Edit") @@ -1382,8 +1391,9 @@ func (s *Service) UserDetail(c echo.Context) error { name email createdOn - isActive + isEmailVerified isLocked + lockReason } }`) op.Var("id", id) @@ -1456,11 +1466,11 @@ func (s *Service) UserUpdate(c echo.Context) error { lt := localizer.GetSessionLocalizer(c) pd := localizer.NewPageData(lt.Translate("Edit User")) pd.Data["save"] = lt.Translate("Save") - pd.Data["is_active"] = lt.Translate("Is Active") pd.Data["name"] = lt.Translate("Name") pd.Data["email"] = lt.Translate("Email") pd.Data["back"] = lt.Translate("Back") pd.Data["cancel"] = lt.Translate("Cancel") + pd.Data["is_verified"] = lt.Translate("Is Verified") gmap := gobwebs.Map{ "user": user, "pd": pd, @@ -1485,8 +1495,8 @@ func (s *Service) UserUpdate(c echo.Context) error { } var result GraphQLResponse op := gqlclient.NewOperation(` - mutation UpdateUser($id: Int!, $isActive: Boolean!, $name: String!, $email: String!) { - updateAdminUser(input: {id: $id, isActive: $isActive, email: $email, name: $name}) { + mutation UpdateUser($id: Int!, $isVerified: Boolean!, $name: String!, $email: String!) { + updateAdminUser(input: {id: $id, isVerified: $isVerified, email: $email, name: $name}) { id } } @@ -1494,7 +1504,7 @@ func (s *Service) UserUpdate(c echo.Context) error { op.Var("id", id) op.Var("name", form.Name) op.Var("email", form.Email) - op.Var("isActive", form.IsActive) + op.Var("isVerified", form.IsVerified) err = links.Execute(c.Request().Context(), op, &result) if err != nil { if graphError, ok := err.(*gqlclient.Error); ok { @@ -1511,9 +1521,9 @@ func (s *Service) UserUpdate(c echo.Context) error { messages.Success(c, lt.Translate("User updated successfully")) return c.Redirect(http.StatusMovedPermanently, c.Echo().Reverse(s.RouteName("user_detail"), id)) } - form.IsActive = user.IsActive form.Name = user.Name form.Email = user.Email + form.IsVerified = user.IsVerified() return links.Render(c, http.StatusOK, "admin_user_edit.html", gmap) } @@ -1571,6 +1581,7 @@ func (s *Service) UserList(c echo.Context) error { pd := localizer.NewPageData(lt.Translate("User List")) pd.Data["user_list"] = lt.Translate("User List") pd.Data["is_active"] = lt.Translate("Is Active") + pd.Data["is_verified"] = lt.Translate("Is Verified") pd.Data["name"] = lt.Translate("Name") pd.Data["created_on"] = lt.Translate("Created On") pd.Data["next"] = lt.Translate("Next") @@ -1600,7 +1611,7 @@ func (s *Service) UserList(c echo.Context) error { id name email - isActive + isEmailVerified isLocked createdOn } diff --git a/api/gqlgen.yml b/api/gqlgen.yml index 180abfd..0c5c4f0 100644 --- a/api/gqlgen.yml +++ b/api/gqlgen.yml @@ -53,7 +53,6 @@ models: Cursor: model: - links/api/graph/model.Cursor - ID: model: - github.com/99designs/gqlgen/graphql.ID diff --git a/api/graph/generated.go b/api/graph/generated.go index dc10c26..dd8ffde 100644 --- a/api/graph/generated.go +++ b/api/graph/generated.go @@ -405,13 +405,13 @@ type ComplexityRoot struct { } User struct { - CreatedOn func(childComplexity int) int - Email func(childComplexity int) int - ID func(childComplexity int) int - IsActive func(childComplexity int) int - IsLocked func(childComplexity int) int - LockReadon func(childComplexity int) int - Name func(childComplexity int) int + CreatedOn func(childComplexity int) int + Email func(childComplexity int) int + ID func(childComplexity int) int + IsEmailVerified func(childComplexity int) int + IsLocked func(childComplexity int) int + LockReason func(childComplexity int) int + Name func(childComplexity int) int } UserCursor struct { @@ -501,8 +501,6 @@ type QueryResolver interface { } type UserResolver interface { ID(ctx context.Context, obj *models.User) (int, error) - - LockReadon(ctx context.Context, obj *models.User) (string, error) } type executableSchema struct { @@ -2470,12 +2468,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.User.ID(childComplexity), true - case "User.isActive": - if e.complexity.User.IsActive == nil { + case "User.isEmailVerified": + if e.complexity.User.IsEmailVerified == nil { break } - return e.complexity.User.IsActive(childComplexity), true + return e.complexity.User.IsEmailVerified(childComplexity), true case "User.isLocked": if e.complexity.User.IsLocked == nil { @@ -2484,12 +2482,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.User.IsLocked(childComplexity), true - case "User.lockReadon": - if e.complexity.User.LockReadon == nil { + case "User.lockReason": + if e.complexity.User.LockReason == nil { break } - return e.complexity.User.LockReadon(childComplexity), true + return e.complexity.User.LockReason(childComplexity), true case "User.name": if e.complexity.User.Name == nil { @@ -9558,12 +9556,12 @@ func (ec *executionContext) fieldContext_Mutation_register(ctx context.Context, return ec.fieldContext_User_email(ctx, field) case "createdOn": return ec.fieldContext_User_createdOn(ctx, field) - case "isActive": - return ec.fieldContext_User_isActive(ctx, field) + case "isEmailVerified": + return ec.fieldContext_User_isEmailVerified(ctx, field) case "isLocked": return ec.fieldContext_User_isLocked(ctx, field) - case "lockReadon": - return ec.fieldContext_User_lockReadon(ctx, field) + case "lockReason": + return ec.fieldContext_User_lockReason(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type User", field.Name) }, @@ -9657,12 +9655,12 @@ func (ec *executionContext) fieldContext_Mutation_completeRegister(ctx context.C return ec.fieldContext_User_email(ctx, field) case "createdOn": return ec.fieldContext_User_createdOn(ctx, field) - case "isActive": - return ec.fieldContext_User_isActive(ctx, field) + case "isEmailVerified": + return ec.fieldContext_User_isEmailVerified(ctx, field) case "isLocked": return ec.fieldContext_User_isLocked(ctx, field) - case "lockReadon": - return ec.fieldContext_User_lockReadon(ctx, field) + case "lockReason": + return ec.fieldContext_User_lockReason(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type User", field.Name) }, @@ -9756,12 +9754,12 @@ func (ec *executionContext) fieldContext_Mutation_updateProfile(ctx context.Cont return ec.fieldContext_User_email(ctx, field) case "createdOn": return ec.fieldContext_User_createdOn(ctx, field) - case "isActive": - return ec.fieldContext_User_isActive(ctx, field) + case "isEmailVerified": + return ec.fieldContext_User_isEmailVerified(ctx, field) case "isLocked": return ec.fieldContext_User_isLocked(ctx, field) - case "lockReadon": - return ec.fieldContext_User_lockReadon(ctx, field) + case "lockReason": + return ec.fieldContext_User_lockReason(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type User", field.Name) }, @@ -11710,12 +11708,12 @@ func (ec *executionContext) fieldContext_Mutation_updateAdminUser(ctx context.Co return ec.fieldContext_User_email(ctx, field) case "createdOn": return ec.fieldContext_User_createdOn(ctx, field) - case "isActive": - return ec.fieldContext_User_isActive(ctx, field) + case "isEmailVerified": + return ec.fieldContext_User_isEmailVerified(ctx, field) case "isLocked": return ec.fieldContext_User_isLocked(ctx, field) - case "lockReadon": - return ec.fieldContext_User_lockReadon(ctx, field) + case "lockReason": + return ec.fieldContext_User_lockReason(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type User", field.Name) }, @@ -15774,12 +15772,12 @@ func (ec *executionContext) fieldContext_Query_me(ctx context.Context, field gra return ec.fieldContext_User_email(ctx, field) case "createdOn": return ec.fieldContext_User_createdOn(ctx, field) - case "isActive": - return ec.fieldContext_User_isActive(ctx, field) + case "isEmailVerified": + return ec.fieldContext_User_isEmailVerified(ctx, field) case "isLocked": return ec.fieldContext_User_isLocked(ctx, field) - case "lockReadon": - return ec.fieldContext_User_lockReadon(ctx, field) + case "lockReason": + return ec.fieldContext_User_lockReason(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type User", field.Name) }, @@ -15951,12 +15949,12 @@ func (ec *executionContext) fieldContext_Query_getUser(ctx context.Context, fiel return ec.fieldContext_User_email(ctx, field) case "createdOn": return ec.fieldContext_User_createdOn(ctx, field) - case "isActive": - return ec.fieldContext_User_isActive(ctx, field) + case "isEmailVerified": + return ec.fieldContext_User_isEmailVerified(ctx, field) case "isLocked": return ec.fieldContext_User_isLocked(ctx, field) - case "lockReadon": - return ec.fieldContext_User_lockReadon(ctx, field) + case "lockReason": + return ec.fieldContext_User_lockReason(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type User", field.Name) }, @@ -16657,12 +16655,12 @@ func (ec *executionContext) fieldContext_Query_getOrgMembers(ctx context.Context return ec.fieldContext_User_email(ctx, field) case "createdOn": return ec.fieldContext_User_createdOn(ctx, field) - case "isActive": - return ec.fieldContext_User_isActive(ctx, field) + case "isEmailVerified": + return ec.fieldContext_User_isEmailVerified(ctx, field) case "isLocked": return ec.fieldContext_User_isLocked(ctx, field) - case "lockReadon": - return ec.fieldContext_User_lockReadon(ctx, field) + case "lockReason": + return ec.fieldContext_User_lockReason(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type User", field.Name) }, @@ -18930,8 +18928,8 @@ func (ec *executionContext) fieldContext_User_createdOn(ctx context.Context, fie return fc, nil } -func (ec *executionContext) _User_isActive(ctx context.Context, field graphql.CollectedField, obj *models.User) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_User_isActive(ctx, field) +func (ec *executionContext) _User_isEmailVerified(ctx context.Context, field graphql.CollectedField, obj *models.User) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_User_isEmailVerified(ctx, field) if err != nil { return graphql.Null } @@ -18945,7 +18943,7 @@ func (ec *executionContext) _User_isActive(ctx context.Context, field graphql.Co resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { directive0 := func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.IsActive, nil + return obj.IsEmailVerified, nil } directive1 := func(ctx context.Context) (interface{}, error) { scope, err := ec.unmarshalNAccessScope2linksᚋapiᚋgraphᚋmodelᚐAccessScope(ctx, "PROFILE") @@ -18989,7 +18987,7 @@ func (ec *executionContext) _User_isActive(ctx context.Context, field graphql.Co return ec.marshalNBoolean2bool(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_User_isActive(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_User_isEmailVerified(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "User", Field: field, @@ -19074,8 +19072,8 @@ func (ec *executionContext) fieldContext_User_isLocked(ctx context.Context, fiel return fc, nil } -func (ec *executionContext) _User_lockReadon(ctx context.Context, field graphql.CollectedField, obj *models.User) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_User_lockReadon(ctx, field) +func (ec *executionContext) _User_lockReason(ctx context.Context, field graphql.CollectedField, obj *models.User) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_User_lockReason(ctx, field) if err != nil { return graphql.Null } @@ -19089,7 +19087,7 @@ func (ec *executionContext) _User_lockReadon(ctx context.Context, field graphql. resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { directive0 := func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.User().LockReadon(rctx, obj) + return obj.LockReason, nil } directive1 := func(ctx context.Context) (interface{}, error) { scope, err := ec.unmarshalNAccessScope2linksᚋapiᚋgraphᚋmodelᚐAccessScope(ctx, "PROFILE") @@ -19133,12 +19131,12 @@ func (ec *executionContext) _User_lockReadon(ctx context.Context, field graphql. return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_User_lockReadon(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_User_lockReason(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "User", Field: field, - IsMethod: true, - IsResolver: true, + IsMethod: false, + IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { return nil, errors.New("field of type String does not have child fields") }, @@ -19193,12 +19191,12 @@ func (ec *executionContext) fieldContext_UserCursor_result(ctx context.Context, return ec.fieldContext_User_email(ctx, field) case "createdOn": return ec.fieldContext_User_createdOn(ctx, field) - case "isActive": - return ec.fieldContext_User_isActive(ctx, field) + case "isEmailVerified": + return ec.fieldContext_User_isEmailVerified(ctx, field) case "isLocked": return ec.fieldContext_User_isLocked(ctx, field) - case "lockReadon": - return ec.fieldContext_User_lockReadon(ctx, field) + case "lockReason": + return ec.fieldContext_User_lockReason(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type User", field.Name) }, @@ -23388,7 +23386,7 @@ func (ec *executionContext) unmarshalInputUpdateUserInput(ctx context.Context, o asMap[k] = v } - fieldsInOrder := [...]string{"id", "email", "name", "isActive"} + fieldsInOrder := [...]string{"id", "email", "name", "isVerified"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -23419,11 +23417,11 @@ func (ec *executionContext) unmarshalInputUpdateUserInput(ctx context.Context, o if err != nil { return it, err } - case "isActive": + case "isVerified": var err error - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("isActive")) - it.IsActive, err = ec.unmarshalNBoolean2bool(ctx, v) + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("isVerified")) + it.IsVerified, err = ec.unmarshalNBoolean2bool(ctx, v) if err != nil { return it, err } @@ -26432,9 +26430,9 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj if out.Values[i] == graphql.Null { atomic.AddUint32(&invalids, 1) } - case "isActive": + case "isEmailVerified": - out.Values[i] = ec._User_isActive(ctx, field, obj) + out.Values[i] = ec._User_isEmailVerified(ctx, field, obj) if out.Values[i] == graphql.Null { atomic.AddUint32(&invalids, 1) @@ -26446,26 +26444,13 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj if out.Values[i] == graphql.Null { atomic.AddUint32(&invalids, 1) } - case "lockReadon": - field := field - - innerFunc := func(ctx context.Context) (res graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - } - }() - res = ec._User_lockReadon(ctx, field, obj) - if res == graphql.Null { - atomic.AddUint32(&invalids, 1) - } - return res - } + case "lockReason": - out.Concurrently(i, func() graphql.Marshaler { - return innerFunc(ctx) + out.Values[i] = ec._User_lockReason(ctx, field, obj) - }) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&invalids, 1) + } default: panic("unknown field " + strconv.Quote(field.Name)) } diff --git a/api/graph/model/models_gen.go b/api/graph/model/models_gen.go index 190a068..2f1fde4 100644 --- a/api/graph/model/models_gen.go +++ b/api/graph/model/models_gen.go @@ -430,10 +430,10 @@ type UpdateOrganizationInput struct { } type UpdateUserInput struct { - ID int `json:"id"` - Email string `json:"email"` - Name string `json:"name"` - IsActive bool `json:"isActive"` + ID int `json:"id"` + Email string `json:"email"` + Name string `json:"name"` + IsVerified bool `json:"isVerified"` } type UserCursor struct { diff --git a/api/graph/schema.graphqls b/api/graph/schema.graphqls index ca2377b..d7c2285 100644 --- a/api/graph/schema.graphqls +++ b/api/graph/schema.graphqls @@ -87,9 +87,9 @@ type User { name: String! email: String! createdOn: Time! @access(scope: PROFILE, kind: RO) - isActive: Boolean! @access(scope: PROFILE, kind: RO) + isEmailVerified: Boolean! @access(scope: PROFILE, kind: RO) isLocked: Boolean! @access(scope: PROFILE, kind: RO) - lockReadon: String! @access(scope: PROFILE, kind: RO) + lockReason: String! @access(scope: PROFILE, kind: RO) } type BillingSettings { @@ -576,7 +576,7 @@ input UpdateUserInput { id: Int! email: String! name: String! - isActive: Boolean! + isVerified: Boolean! } input CompleteRegisterInput { diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index b582380..9841c9c 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -3994,9 +3994,9 @@ func (r *mutationResolver) UpdateAdminUser(ctx context.Context, input *model.Upd return nil, nil } currUser := users[0] - currUser.IsActive = input.IsActive currUser.Email = input.Email currUser.Name = input.Name + currUser.SetVerified(input.IsVerified) err = currUser.Store(ctx) if err != nil { return nil, err @@ -4278,11 +4278,15 @@ func (r *queryResolver) GetOrganization(ctx context.Context, id int) (*models.Or lang := links.GetLangFromRequest(server.EchoForContext(ctx).Request(), user) lt := localizer.GetLocalizer(lang) opts := &database.FilterOptions{ - Filter: sq.And{ - sq.Eq{"o.id": id}, + Filter: sq.Eq{"o.id": id}, + Limit: 1, + } + if !user.IsSuperUser() { + // XXX Remove this? Not sure why we have this filter in place. + opts.Filter = sq.And{ + opts.Filter, sq.Eq{"o.is_active": true}, - }, - Limit: 1, + } } orgs, err := models.GetOrganizations(ctx, opts) if err != nil { @@ -6540,11 +6544,6 @@ func (r *userResolver) ID(ctx context.Context, obj *models.User) (int, error) { return int(obj.ID), nil } -// LockReadon is the resolver for the lockReadon field. -func (r *userResolver) LockReadon(ctx context.Context, obj *models.User) (string, error) { - panic(fmt.Errorf("not implemented: LockReadon - lockReadon")) -} - // Domain returns DomainResolver implementation. func (r *Resolver) Domain() DomainResolver { return &domainResolver{r} } @@ -6569,3 +6568,13 @@ type orgLinkResolver struct{ *Resolver } type organizationResolver struct{ *Resolver } type queryResolver struct{ *Resolver } type userResolver struct{ *Resolver } + +// !!! WARNING !!! +// The code below was going to be deleted when updating resolvers. It has been copied here so you have +// one last chance to move it out of harms way if you want. There are two reasons this happens: +// - When renaming or deleting a resolver the old code will be put in here. You can safely delete +// it when you're done. +// - You have helper methods in this file. Move them out to keep these resolver files clean. +func (r *userResolver) LockReadon(ctx context.Context, obj *models.User) (string, error) { + panic(fmt.Errorf("not implemented: LockReadon - lockReadon")) +} diff --git a/migrations/0001_initial.up.sql b/migrations/0001_initial.up.sql index f44fac5..249aa52 100644 --- a/migrations/0001_initial.up.sql +++ b/migrations/0001_initial.up.sql @@ -17,7 +17,6 @@ CREATE TABLE users ( email VARCHAR ( 255 ) UNIQUE NOT NULL, picture VARCHAR(1024) DEFAULT '', settings JSONB DEFAULT '{}', - is_active BOOLEAN DEFAULT TRUE, is_verified BOOLEAN DEFAULT FALSE, is_superuser BOOLEAN DEFAULT FALSE, is_staff BOOLEAN DEFAULT FALSE, diff --git a/models/models.go b/models/models.go index 13a8bff..3c69bcc 100644 --- a/models/models.go +++ b/models/models.go @@ -26,11 +26,11 @@ type User struct { accounts.BaseUser Name string `db:"full_name" json:"name"` Settings UserSettings `db:"settings"` - IsActive bool `db:"is_active"` IsLocked bool `db:"is_locked" json:"isLocked"` LockReason string `db:"lock_reason" json:"lockReason"` - OrgSlug sql.NullString `db:"-" json:"-"` // This field counts like the username for personal organization context + OrgSlug sql.NullString `db:"-" json:"-"` // This field counts like the username for personal organization context + IsEmailVerified bool `db:"-" json:"isEmailVerified"` } // Organization is... diff --git a/models/organization.go b/models/organization.go index cfa4097..d76baca 100644 --- a/models/organization.go +++ b/models/organization.go @@ -55,15 +55,65 @@ func (os *OrganizationSettings) Scan(value interface{}) error { return json.Unmarshal(b, &os) } -// ToLocalTZ convert UTC date to local tz -func (o *Organization) ToLocalTZ(tz string) error { - loc, err := time.LoadLocation(tz) - if err != nil { - return err +// GetOrganizations ... +func GetOrganizations(ctx context.Context, opts *database.FilterOptions) ([]*Organization, error) { + if opts == nil { + opts = &database.FilterOptions{} } - o.CreatedOn = o.CreatedOn.In(loc) - o.UpdatedOn = o.UpdatedOn.In(loc) - return nil + tz := timezone.ForContext(ctx) + orgs := make([]*Organization, 0) + if err := database.WithTx(ctx, database.TxOptionsRO, func(tx *sql.Tx) error { + q := opts.GetBuilder(nil) + rows, err := q. + Columns("o.id", "o.owner_id", "o.org_type", "o.name", "o.slug", "o.image", "o.timezone", + "o.settings", "o.is_active", "o.created_on", "o.updated_on", "u.full_name", "sc.id", + "mc.id"). + From("organizations o"). + Join("users u ON o.owner_id = u.id"). + LeftJoin("organization_users ou ON o.id = ou.org_id"). + LeftJoin("slack_connections sc ON o.id = sc.org_id"). + LeftJoin("mattermost_connections mc ON o.id = mc.org_id"). + LeftJoin("domains d ON o.id = d.org_id"). + LeftJoin("followers f ON f.org_id = o.id"). + Distinct(). + PlaceholderFormat(sq.Dollar). + RunWith(tx). + QueryContext(ctx) + if err != nil { + if err == sql.ErrNoRows { + return nil + } + return err + } + defer rows.Close() + + for rows.Next() { + var org Organization + if err = rows.Scan(&org.ID, &org.OwnerID, &org.OrgType, &org.Name, &org.Slug, + &org.Image, &org.Timezone, &org.Settings, &org.IsActive, &org.CreatedOn, &org.UpdatedOn, + &org.OwnerName, &org.SlackConnID, &org.MattermostConnID, + ); err != nil { + return err + } + + err = org.ToLocalTZ(tz) + if err != nil { + return err + } + orgs = append(orgs, &org) + } + return nil + }); err != nil { + return nil, err + } + return orgs, nil +} + +// GetOrganization ... +func GetOrganization(ctx context.Context, id int) (*Organization, error) { + o := &Organization{ID: id} + err := o.Load(ctx) + return o, err } // Load organization @@ -153,65 +203,15 @@ func (o *Organization) Delete(ctx context.Context) error { return err } -// GetOrganization ... -func GetOrganization(ctx context.Context, id int) (*Organization, error) { - o := &Organization{ID: id} - err := o.Load(ctx) - return o, err -} - -// GetOrganizations ... -func GetOrganizations(ctx context.Context, opts *database.FilterOptions) ([]*Organization, error) { - if opts == nil { - opts = &database.FilterOptions{} - } - tz := timezone.ForContext(ctx) - orgs := make([]*Organization, 0) - if err := database.WithTx(ctx, database.TxOptionsRO, func(tx *sql.Tx) error { - q := opts.GetBuilder(nil) - rows, err := q. - Columns("o.id", "o.owner_id", "o.org_type", "o.name", "o.slug", "o.image", "o.timezone", - "o.settings", "o.is_active", "o.created_on", "o.updated_on", "u.full_name", "sc.id", - "mc.id"). - From("organizations o"). - Join("users u ON o.owner_id = u.id"). - LeftJoin("organization_users ou ON o.id = ou.org_id"). - LeftJoin("slack_connections sc ON o.id = sc.org_id"). - LeftJoin("mattermost_connections mc ON o.id = mc.org_id"). - LeftJoin("domains d ON o.id = d.org_id"). - LeftJoin("followers f ON f.org_id = o.id"). - Distinct(). - PlaceholderFormat(sq.Dollar). - RunWith(tx). - QueryContext(ctx) - if err != nil { - if err == sql.ErrNoRows { - return nil - } - return err - } - defer rows.Close() - - for rows.Next() { - var org Organization - if err = rows.Scan(&org.ID, &org.OwnerID, &org.OrgType, &org.Name, &org.Slug, - &org.Image, &org.Timezone, &org.Settings, &org.IsActive, &org.CreatedOn, &org.UpdatedOn, - &org.OwnerName, &org.SlackConnID, &org.MattermostConnID, - ); err != nil { - return err - } - - err = org.ToLocalTZ(tz) - if err != nil { - return err - } - orgs = append(orgs, &org) - } - return nil - }); err != nil { - return nil, err +// ToLocalTZ convert UTC date to local tz +func (o *Organization) ToLocalTZ(tz string) error { + loc, err := time.LoadLocation(tz) + if err != nil { + return err } - return orgs, nil + o.CreatedOn = o.CreatedOn.In(loc) + o.UpdatedOn = o.UpdatedOn.In(loc) + return nil } func (o *Organization) permCheck(ctx context.Context, user *User, perm int) bool { diff --git a/models/schema.sql b/models/schema.sql index f44fac5..249aa52 100644 --- a/models/schema.sql +++ b/models/schema.sql @@ -17,7 +17,6 @@ CREATE TABLE users ( email VARCHAR ( 255 ) UNIQUE NOT NULL, picture VARCHAR(1024) DEFAULT '', settings JSONB DEFAULT '{}', - is_active BOOLEAN DEFAULT TRUE, is_verified BOOLEAN DEFAULT FALSE, is_superuser BOOLEAN DEFAULT FALSE, is_staff BOOLEAN DEFAULT FALSE, diff --git a/models/user.go b/models/user.go index 410ea39..9452d19 100644 --- a/models/user.go +++ b/models/user.go @@ -72,7 +72,7 @@ func GetUser(ctx context.Context, uid any, markAuth bool) (*User, error) { err := sq. Select(). Columns("u.id", "u.full_name", "u.password", "u.email", "u.settings", "u.is_verified", - "u.is_superuser", "u.is_staff", "u.created_on", "u.last_login", "u.is_active", + "u.is_superuser", "u.is_staff", "u.created_on", "u.last_login", "u.is_locked", "u.lock_reason", "o.slug"). From("users u"). LeftJoin("organizations o ON o.owner_id = u.id"). @@ -90,7 +90,6 @@ func GetUser(ctx context.Context, uid any, markAuth bool) (*User, error) { &staff, &user.CreatedOn, &user.LastLogin, - &user.IsActive, &user.IsLocked, &user.LockReason, &user.OrgSlug, @@ -112,6 +111,7 @@ func GetUser(ctx context.Context, uid any, markAuth bool) (*User, error) { user.SetSuperUser(superuser) user.SetStaff(staff) user.SetAuthenticated(markAuth) + user.IsEmailVerified = user.IsVerified() // hack return user, nil } @@ -127,7 +127,7 @@ func GetUsers(ctx context.Context, opts *database.FilterOptions) ([]*User, error rows, err := q. Columns("u.id", "u.full_name", "u.password", "u.email", "u.settings", "u.is_verified", "u.is_superuser", "u.is_staff", "u.created_on", - "u.last_login", "u.is_active", "u.is_locked", "u.lock_reason", "o.slug"). + "u.last_login", "u.is_locked", "u.lock_reason", "o.slug"). From("users u"). LeftJoin("organizations o ON o.owner_id = u.id"). LeftJoin("organization_users ou ON u.id = ou.user_id"). @@ -149,7 +149,7 @@ func GetUsers(ctx context.Context, opts *database.FilterOptions) ([]*User, error verified, superuser, staff bool ) if err = rows.Scan(&u.ID, &u.Name, &u.Password, &u.Email, &u.Settings, - &verified, &superuser, &staff, &u.CreatedOn, &u.LastLogin, &u.IsActive, + &verified, &superuser, &staff, &u.CreatedOn, &u.LastLogin, &u.IsLocked, &u.LockReason, &u.OrgSlug, ); err != nil { return err @@ -157,6 +157,7 @@ func GetUsers(ctx context.Context, opts *database.FilterOptions) ([]*User, error u.SetVerified(verified) u.SetSuperUser(superuser) u.SetStaff(staff) + u.IsEmailVerified = u.IsVerified() // hack err = u.ToLocalTZ(tz) if err != nil { return err @@ -198,7 +199,6 @@ func (u *User) Store(ctx context.Context) error { Set("is_verified", u.IsVerified()). Set("is_superuser", u.IsSuperUser()). Set("is_staff", u.IsStaff()). - Set("is_active", u.IsActive). Set("is_locked", u.IsLocked). Set("lock_reason", u.LockReason). Where("id = ?", u.GetID()). diff --git a/templates/admin_dashboard.html b/templates/admin_dashboard.html index 9b1af55..84a9ec8 100644 --- a/templates/admin_dashboard.html +++ b/templates/admin_dashboard.html @@ -22,7 +22,8 @@ {{.pd.Data.name}} Email {{.pd.Data.created_on}} - {{.pd.Data.is_active}} + {{.pd.Data.is_verified}} + {{.pd.Data.is_locked}} @@ -34,7 +35,18 @@ {{.Email}} {{formatDate .CreatedOn}} - {{if .IsActive}} + {{if .IsEmailVerified}} + + + + {{else}} + + + + {{end}} + + + {{if .IsLocked}} diff --git a/templates/admin_user_detail.html b/templates/admin_user_detail.html index e3dcac5..c1df845 100644 --- a/templates/admin_user_detail.html +++ b/templates/admin_user_detail.html @@ -29,10 +29,10 @@ {{formatDate .user.CreatedOn}} - {{.pd.Data.is_active}} + {{.pd.Data.is_verified}}

- {{if .user.IsActive}} + {{if .user.IsEmailVerified}} {{else}} @@ -40,6 +40,22 @@

+ + {{.pd.Data.is_locked}} + + {{if .user.IsLocked}} + + {{else}} + + {{end}} + + + {{if .user.IsLocked}} + + {{.pd.Data.lock_reason}} + {{.user.LockReason}} + + {{ end }}

{{.pd.Data.organizations}}

diff --git a/templates/admin_user_edit.html b/templates/admin_user_edit.html index 0e387ed..0237e3e 100644 --- a/templates/admin_user_edit.html +++ b/templates/admin_user_edit.html @@ -28,16 +28,16 @@ {{ end }}
- - - {{ with .errors.IsActive }} -

{{ . }}

- {{ end }} -
+ + + {{ with .errors.IsVerified }} +

{{ . }}

+ {{ end }} + + - {{template "base_footer" .}} diff --git a/templates/admin_user_list.html b/templates/admin_user_list.html index b9a32a6..781057b 100644 --- a/templates/admin_user_list.html +++ b/templates/admin_user_list.html @@ -24,7 +24,7 @@ {{.pd.Data.name}} Email {{.pd.Data.created_on}} - {{.pd.Data.is_active}} + {{.pd.Data.is_verified}} @@ -35,7 +35,7 @@ {{.Email}} {{formatDate .CreatedOn}} - {{if .IsActive}} + {{if .IsEmailVerified}} -- 2.45.2