From 2df15f5b826057b71c6b5efff4b9582b4dfa9952 Mon Sep 17 00:00:00 2001 From: Peter Sanchez Date: Fri, 22 Nov 2024 07:46:52 -0600 Subject: [PATCH] Starting work to move int values to string/enum types for data clairty --- admin/input.go | 4 +- admin/routes.go | 8 +-- api/graph/generated.go | 83 ++++++++++++++++++++++++---- api/graph/model/models_gen.go | 94 ++++++++++++++++++++++---------- api/graph/schema.graphqls | 24 +++++--- api/graph/schema.resolvers.go | 23 +++++--- migrations/0001_initial.down.sql | 2 + migrations/0001_initial.up.sql | 13 ++++- models/domains.go | 9 ++- models/models.go | 2 +- models/schema.sql | 8 ++- 11 files changed, 201 insertions(+), 69 deletions(-) diff --git a/admin/input.go b/admin/input.go index 7cadb46..287899a 100644 --- a/admin/input.go +++ b/admin/input.go @@ -58,7 +58,7 @@ type DomainForm struct { Name string `form:"name" validate:"required"` LookupName string `form:"lookup_name" validate:"required"` Service int `form:"service" validate:"oneof=0 1 2"` - Level int `form:"level" validate:"oneof=0 1"` + Level string `form:"level" validate:"oneof=SYSTEM USER"` Status int `form:"status" validate:"oneof=0 1 2"` IsActive bool `form:"is_active"` OrgSlug string `form:"org_slug"` @@ -72,7 +72,7 @@ func (d *DomainForm) Validate(c echo.Context) error { String("name", &d.Name). String("lookup_name", &d.LookupName). Int("service", &d.Service). - Int("level", &d.Level). + String("level", &d.Level). Int("status", &d.Status). String("org_slug", &d.OrgSlug). Bool("is_active", &d.IsActive). diff --git a/admin/routes.go b/admin/routes.go index 3f3d352..f47b754 100644 --- a/admin/routes.go +++ b/admin/routes.go @@ -614,7 +614,7 @@ func (s *Service) DomainUpdate(c echo.Context) error { models.DomainServiceList: lt.Translate("Link Listing"), } - levelOpt := map[int]string{ + levelOpt := map[string]string{ models.DomainLevelSystem: lt.Translate("System"), models.DomainLevelUser: lt.Translate("User"), } @@ -743,7 +743,7 @@ func (s *Service) DomainCreate(c echo.Context) error { models.DomainServiceList: lt.Translate("Link Listing"), } - levelOpt := map[int]string{ + levelOpt := map[string]string{ models.DomainLevelSystem: lt.Translate("System"), models.DomainLevelUser: lt.Translate("User"), } @@ -1057,7 +1057,7 @@ func (s *Service) DomainList(c echo.Context) error { pd.Data["clear"] = lt.Translate("Clear") pd.Data["add"] = lt.Translate("Add") - filterType := map[int]string{ + filterType := map[string]string{ models.DomainLevelSystem: lt.Translate("System"), models.DomainLevelUser: lt.Translate("User"), } @@ -1092,7 +1092,7 @@ func (s *Service) DomainList(c echo.Context) error { } var result GraphQLResponse op := gqlclient.NewOperation( - `query GetAdminDomains($search: String, $after: Cursor, $before: Cursor, $filterLevel: Int, $filterService: Int, $filterActive: Boolean) { + `query GetAdminDomains($search: String, $after: Cursor, $before: Cursor, $filterLevel: String, $filterService: Int, $filterActive: Boolean) { getAdminDomains(input: { search: $search, after: $after, diff --git a/api/graph/generated.go b/api/graph/generated.go index a81a031..5143357 100644 --- a/api/graph/generated.go +++ b/api/graph/generated.go @@ -433,6 +433,8 @@ type ComplexityRoot struct { type DomainResolver interface { OrgID(ctx context.Context, obj *models.Domain) (*model.NullInt, error) OrgSlug(ctx context.Context, obj *models.Domain) (*model.NullString, error) + + Level(ctx context.Context, obj *models.Domain) (model.DomainLevel, error) } type MutationResolver interface { AddOrganization(ctx context.Context, input model.OrganizationInput) (*models.Organization, error) @@ -5679,7 +5681,7 @@ func (ec *executionContext) _Domain_level(ctx context.Context, field graphql.Col }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Level, nil + return ec.resolvers.Domain().Level(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -5691,19 +5693,19 @@ func (ec *executionContext) _Domain_level(ctx context.Context, field graphql.Col } return graphql.Null } - res := resTmp.(int) + res := resTmp.(model.DomainLevel) fc.Result = res - return ec.marshalNInt2int(ctx, field.Selections, res) + return ec.marshalNDomainLevel2linksᚋapiᚋgraphᚋmodelᚐDomainLevel(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Domain_level(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Domain", Field: field, - IsMethod: false, - IsResolver: false, + IsMethod: true, + IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type Int does not have child fields") + return nil, errors.New("field of type DomainLevel does not have child fields") }, } return fc, nil @@ -21767,7 +21769,7 @@ func (ec *executionContext) unmarshalInputAdminDomainInput(ctx context.Context, it.Service = data case "level": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("level")) - data, err := ec.unmarshalNInt2int(ctx, v) + data, err := ec.unmarshalNDomainLevel2linksᚋapiᚋgraphᚋmodelᚐDomainLevel(ctx, v) if err != nil { return it, err } @@ -22008,7 +22010,7 @@ func (ec *executionContext) unmarshalInputGetAdminDomainInput(ctx context.Contex it.Search = data case "filterLevel": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("filterLevel")) - data, err := ec.unmarshalOInt2ᚖint(ctx, v) + data, err := ec.unmarshalODomainLevel2ᚖlinksᚋapiᚋgraphᚋmodelᚐDomainLevel(ctx, v) if err != nil { return it, err } @@ -23075,7 +23077,7 @@ func (ec *executionContext) unmarshalInputUpdateAdminDomainInput(ctx context.Con it.Service = data case "level": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("level")) - data, err := ec.unmarshalNInt2int(ctx, v) + data, err := ec.unmarshalNDomainLevel2linksᚋapiᚋgraphᚋmodelᚐDomainLevel(ctx, v) if err != nil { return it, err } @@ -24077,10 +24079,41 @@ func (ec *executionContext) _Domain(ctx context.Context, sel ast.SelectionSet, o atomic.AddUint32(&out.Invalids, 1) } case "level": - out.Values[i] = ec._Domain_level(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Domain_level(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) case "status": out.Values[i] = ec._Domain_status(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -27375,6 +27408,16 @@ func (ec *executionContext) unmarshalNDomainInput2linksᚋapiᚋgraphᚋmodelᚐ return res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) unmarshalNDomainLevel2linksᚋapiᚋgraphᚋmodelᚐDomainLevel(ctx context.Context, v interface{}) (model.DomainLevel, error) { + var res model.DomainLevel + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNDomainLevel2linksᚋapiᚋgraphᚋmodelᚐDomainLevel(ctx context.Context, sel ast.SelectionSet, v model.DomainLevel) graphql.Marshaler { + return v +} + func (ec *executionContext) marshalNFollowPayload2linksᚋapiᚋgraphᚋmodelᚐFollowPayload(ctx context.Context, sel ast.SelectionSet, v model.FollowPayload) graphql.Marshaler { return ec._FollowPayload(ctx, sel, &v) } @@ -28475,6 +28518,22 @@ func (ec *executionContext) marshalODomain2ᚖlinksᚋmodelsᚐDomain(ctx contex return ec._Domain(ctx, sel, v) } +func (ec *executionContext) unmarshalODomainLevel2ᚖlinksᚋapiᚋgraphᚋmodelᚐDomainLevel(ctx context.Context, v interface{}) (*model.DomainLevel, error) { + if v == nil { + return nil, nil + } + var res = new(model.DomainLevel) + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalODomainLevel2ᚖlinksᚋapiᚋgraphᚋmodelᚐDomainLevel(ctx context.Context, sel ast.SelectionSet, v *model.DomainLevel) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return v +} + func (ec *executionContext) unmarshalOGetAdminDomainInput2ᚖlinksᚋapiᚋgraphᚋmodelᚐGetAdminDomainInput(ctx context.Context, v interface{}) (*model.GetAdminDomainInput, error) { if v == nil { return nil, nil diff --git a/api/graph/model/models_gen.go b/api/graph/model/models_gen.go index 2fede96..5f4ec89 100644 --- a/api/graph/model/models_gen.go +++ b/api/graph/model/models_gen.go @@ -67,14 +67,14 @@ type AdminBillingStats struct { } type AdminDomainInput struct { - Name string `json:"name"` - LookupName string `json:"lookupName"` - OrgSlug *string `json:"orgSlug,omitempty"` - Service int `json:"service"` - Level int `json:"level"` - Status int `json:"status"` - IsActive bool `json:"isActive"` - MigrateLinks *bool `json:"migrateLinks,omitempty"` + Name string `json:"name"` + LookupName string `json:"lookupName"` + OrgSlug *string `json:"orgSlug,omitempty"` + Service int `json:"service"` + Level DomainLevel `json:"level"` + Status int `json:"status"` + IsActive bool `json:"isActive"` + MigrateLinks *bool `json:"migrateLinks,omitempty"` } type AnalyticData struct { @@ -133,14 +133,14 @@ type FollowPayload struct { } type GetAdminDomainInput struct { - Limit *int `json:"limit,omitempty"` - After *Cursor `json:"after,omitempty"` - Before *Cursor `json:"before,omitempty"` - Search *string `json:"search,omitempty"` - FilterLevel *int `json:"filterLevel,omitempty"` - FilterService *int `json:"filterService,omitempty"` - FilterActive *bool `json:"filterActive,omitempty"` - OrgSlug *string `json:"orgSlug,omitempty"` + Limit *int `json:"limit,omitempty"` + After *Cursor `json:"after,omitempty"` + Before *Cursor `json:"before,omitempty"` + Search *string `json:"search,omitempty"` + FilterLevel *DomainLevel `json:"filterLevel,omitempty"` + FilterService *int `json:"filterService,omitempty"` + FilterActive *bool `json:"filterActive,omitempty"` + OrgSlug *string `json:"orgSlug,omitempty"` } type GetAdminOrganizationsInput struct { @@ -277,9 +277,6 @@ type NoteInput struct { OrgSlug string `json:"orgSlug"` } -// Considering removing these Null* fields: -// -// https://todo.code.netlandish.com/~netlandish/links/75 type NullInt struct { Int64 int `json:"int64"` Valid bool `json:"valid"` @@ -375,15 +372,15 @@ type RegisterInvitation struct { } type UpdateAdminDomainInput struct { - ID int `json:"id"` - Name string `json:"name"` - LookupName string `json:"lookupName"` - OrgSlug *string `json:"orgSlug,omitempty"` - Service int `json:"service"` - Level int `json:"level"` - Status int `json:"status"` - IsActive bool `json:"isActive"` - MigrateLinks *bool `json:"migrateLinks,omitempty"` + ID int `json:"id"` + Name string `json:"name"` + LookupName string `json:"lookupName"` + OrgSlug *string `json:"orgSlug,omitempty"` + Service int `json:"service"` + Level DomainLevel `json:"level"` + Status int `json:"status"` + IsActive bool `json:"isActive"` + MigrateLinks *bool `json:"migrateLinks,omitempty"` } type UpdateLinkInput struct { @@ -558,3 +555,44 @@ func (e *AccessScope) UnmarshalGQL(v interface{}) error { func (e AccessScope) MarshalGQL(w io.Writer) { fmt.Fprint(w, strconv.Quote(e.String())) } + +type DomainLevel string + +const ( + DomainLevelSystem DomainLevel = "SYSTEM" + DomainLevelUser DomainLevel = "USER" +) + +var AllDomainLevel = []DomainLevel{ + DomainLevelSystem, + DomainLevelUser, +} + +func (e DomainLevel) IsValid() bool { + switch e { + case DomainLevelSystem, DomainLevelUser: + return true + } + return false +} + +func (e DomainLevel) String() string { + return string(e) +} + +func (e *DomainLevel) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = DomainLevel(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid DomainLevel", str) + } + return nil +} + +func (e DomainLevel) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} diff --git a/api/graph/schema.graphqls b/api/graph/schema.graphqls index bebe566..c441631 100644 --- a/api/graph/schema.graphqls +++ b/api/graph/schema.graphqls @@ -63,11 +63,14 @@ type Version { deprecationDate: Time } -""" -Considering removing these Null* fields: +enum DomainLevel { + SYSTEM + USER +} -https://todo.code.netlandish.com/~netlandish/links/75 -""" + +# Considering removing these Null* fields: +# https://todo.code.netlandish.com/~netlandish/links/75 type NullInt { int64: Int! valid: Boolean! @@ -245,7 +248,7 @@ type Domain { orgId: NullInt! @access(scope: DOMAINS, kind: RO) orgSlug: NullString! @access(scope: DOMAINS, kind: RO) service: Int! - level: Int! + level: DomainLevel! status: Int! isActive: Boolean! @access(scope: DOMAINS, kind: RO) createdOn: Time! @access(scope: DOMAINS, kind: RO) @@ -451,7 +454,7 @@ input GetAdminDomainInput { after: Cursor before: Cursor search: String - filterLevel: Int + filterLevel: DomainLevel filterService: Int filterActive: Boolean orgSlug: String @@ -630,7 +633,7 @@ input AdminDomainInput { lookupName: String! orgSlug: String service: Int! - level: Int! + level: DomainLevel! status: Int! isActive: Boolean! migrateLinks: Boolean @@ -642,7 +645,7 @@ input UpdateAdminDomainInput { lookupName: String! orgSlug: String service: Int! - level: Int! + level: DomainLevel! status: Int! isActive: Boolean! migrateLinks: Boolean @@ -796,7 +799,10 @@ type Mutation { follow(orgSlug: String!): FollowPayload! @access(scope: PROFILE, kind: RW) unfollow(orgSlug: String!): FollowPayload! @access(scope: PROFILE, kind: RW) - "Admin only. Not open to public calls" + # + # Admin only. Not open to public calls + # + updateAdminOrgType(orgSlug: String!, orgType: Int!): Organization! @admin addAdminDomain(input: AdminDomainInput!): Domain! @admin updateAdminDomain(input: UpdateAdminDomainInput!): Domain! @admin diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index 726e558..1a5b1a8 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -39,7 +39,7 @@ import ( "golang.org/x/image/draw" "golang.org/x/net/idna" "netlandish.com/x/gobwebs" - oauth2 "netlandish.com/x/gobwebs-oauth2" + "netlandish.com/x/gobwebs-oauth2" gaccounts "netlandish.com/x/gobwebs/accounts" "netlandish.com/x/gobwebs/crypto" "netlandish.com/x/gobwebs/database" @@ -60,6 +60,11 @@ func (r *domainResolver) OrgSlug(ctx context.Context, obj *models.Domain) (*mode return &model.NullString{String: obj.OrgSlug.String, Valid: obj.OrgSlug.Valid}, nil } +// Level is the resolver for the level field. +func (r *domainResolver) Level(ctx context.Context, obj *models.Domain) (model.DomainLevel, error) { + panic(fmt.Errorf("not implemented: Level - level")) +} + // AddOrganization is the resolver for the addOrganization field. func (r *mutationResolver) AddOrganization(ctx context.Context, input model.OrganizationInput) (*models.Organization, error) { tokenUser := oauth2.ForContext(ctx) @@ -3701,8 +3706,8 @@ func (r *mutationResolver) AddAdminDomain(ctx context.Context, input model.Admin lt.Translate("Invalid service value.")). WithField("service"). WithCode(valid.ErrValidationCode) - validator.Expect(input.Level == models.DomainLevelSystem || - input.Level == models.DomainLevelUser, + validator.Expect(string(input.Level) == models.DomainLevelSystem || + string(input.Level) == models.DomainLevelUser, lt.Translate("Invalid level value.")). WithField("level"). WithCode(valid.ErrValidationCode) @@ -3736,7 +3741,7 @@ func (r *mutationResolver) AddAdminDomain(ctx context.Context, input model.Admin } host = strings.ToLower(host) - if input.Level == models.DomainLevelUser && input.OrgSlug == nil { + if string(input.Level) == models.DomainLevelUser && input.OrgSlug == nil { validator.Error(lt.Translate("Organization Slug is required for user level domains")). WithField("orgSlug"). WithCode(valid.ErrValidationCode) @@ -3823,7 +3828,7 @@ func (r *mutationResolver) AddAdminDomain(ctx context.Context, input model.Admin domain := &models.Domain{ Name: input.Name, LookupName: host, - Level: input.Level, + Level: string(input.Level), Service: input.Service, Status: input.Status, IsActive: input.IsActive, @@ -3877,8 +3882,8 @@ func (r *mutationResolver) UpdateAdminDomain(ctx context.Context, input model.Up lt.Translate("Invalid service value.")). WithField("service"). WithCode(valid.ErrValidationCode) - validator.Expect(input.Level == models.DomainLevelSystem || - input.Level == models.DomainLevelUser, + validator.Expect(string(input.Level) == models.DomainLevelSystem || + string(input.Level) == models.DomainLevelUser, lt.Translate("Invalid level value.")). WithField("level"). WithCode(valid.ErrValidationCode) @@ -3912,7 +3917,7 @@ func (r *mutationResolver) UpdateAdminDomain(ctx context.Context, input model.Up } host = strings.ToLower(host) - if input.Level == models.DomainLevelUser && input.OrgSlug == nil { + if string(input.Level) == models.DomainLevelUser && input.OrgSlug == nil { validator.Error(lt.Translate("Organization Slug is required for user level domains")). WithField("orgSlug"). WithCode(valid.ErrValidationCode) @@ -4018,7 +4023,7 @@ func (r *mutationResolver) UpdateAdminDomain(ctx context.Context, input model.Up ldomain.Name = input.Name ldomain.LookupName = host - ldomain.Level = input.Level + ldomain.Level = string(input.Level) ldomain.Service = input.Service ldomain.Status = input.Status ldomain.IsActive = input.IsActive diff --git a/migrations/0001_initial.down.sql b/migrations/0001_initial.down.sql index c2ce723..45749b0 100644 --- a/migrations/0001_initial.down.sql +++ b/migrations/0001_initial.down.sql @@ -31,3 +31,5 @@ DROP TABLE IF EXISTS domains; DROP TABLE IF EXISTS followers; DROP TABLE IF EXISTS organizations; DROP TABLE IF EXISTS users; + +DROP TYPE IF EXISTS domain_level; diff --git a/migrations/0001_initial.up.sql b/migrations/0001_initial.up.sql index ae8023b..9051d38 100644 --- a/migrations/0001_initial.up.sql +++ b/migrations/0001_initial.up.sql @@ -10,6 +10,17 @@ BEGIN END; $$ language 'plpgsql'; +-- Wrap in exceptions here for tests +DO $$ BEGIN + CREATE TYPE domain_level AS ENUM ( + 'SYSTEM', + 'USER' + ); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; + + CREATE TABLE users ( id SERIAL PRIMARY KEY, full_name VARCHAR ( 150 ) NOT NULL, @@ -141,7 +152,7 @@ CREATE TABLE domains ( name VARCHAR (255) NOT NULL, lookup_name VARCHAR (500) NOT NULL, -- padded in case... org_id INT REFERENCES organizations (id) ON DELETE CASCADE, - level INT DEFAULT 0 NOT NULL, + level domain_level NOT NULL, service INT NOT NULL, status INT DEFAULT 0 NOT NULL, is_active BOOLEAN DEFAULT TRUE, diff --git a/models/domains.go b/models/domains.go index feb4271..d4389ac 100644 --- a/models/domains.go +++ b/models/domains.go @@ -15,9 +15,9 @@ import ( const ( // DomainLevelSystem ... - DomainLevelSystem int = iota + DomainLevelSystem string = "SYSTEM" // DomainLevelUser ... - DomainLevelUser + DomainLevelUser string = "USER" ) const ( @@ -132,6 +132,11 @@ func (d *Domain) Store(ctx context.Context) error { return err } } + + // Added for fail safes after migration to enums. Can be removed later + if d.Level == "" { + d.Level = DomainLevelSystem + } err = sq. Insert("domains"). Columns("name", "lookup_name", "org_id", "level", "service", "status", "is_active"). diff --git a/models/models.go b/models/models.go index 3c69bcc..9b8ff88 100644 --- a/models/models.go +++ b/models/models.go @@ -109,7 +109,7 @@ type Domain struct { Name string `db:"name"` LookupName string `db:"lookup_name"` OrgID sql.NullInt64 `db:"org_id"` - Level int `db:"level"` + Level string `db:"level"` Service int `db:"service"` Status int `db:"status"` IsActive bool `db:"is_active"` diff --git a/models/schema.sql b/models/schema.sql index ae8023b..33ff69e 100644 --- a/models/schema.sql +++ b/models/schema.sql @@ -10,6 +10,12 @@ BEGIN END; $$ language 'plpgsql'; +CREATE TYPE domain_level AS ENUM ( + 'SYSTEM', + 'USER' +); + + CREATE TABLE users ( id SERIAL PRIMARY KEY, full_name VARCHAR ( 150 ) NOT NULL, @@ -141,7 +147,7 @@ CREATE TABLE domains ( name VARCHAR (255) NOT NULL, lookup_name VARCHAR (500) NOT NULL, -- padded in case... org_id INT REFERENCES organizations (id) ON DELETE CASCADE, - level INT DEFAULT 0 NOT NULL, + level domain_level NOT NULL, service INT NOT NULL, status INT DEFAULT 0 NOT NULL, is_active BOOLEAN DEFAULT TRUE, -- 2.45.2