From 14b83d0ffa8acff66789998b94dd11d1771ce396 Mon Sep 17 00:00:00 2001 From: Peter Sanchez Date: Fri, 22 Nov 2024 14:24:03 -0600 Subject: [PATCH] Moving to DomainService values to enum instead of int. --- admin/input.go | 4 +- admin/routes.go | 6 +- api/api_test.go | 8 +-- api/graph/generated.go | 97 +++++++++++++++++++++++------ api/graph/model/models_gen.go | 103 ++++++++++++++++++++++--------- api/graph/schema.graphqls | 19 ++++-- api/graph/schema.resolvers.go | 40 ++++++------ cmd/domains/main.go | 2 +- cmd/server.go | 2 +- core/inputs.go | 4 +- core/routes.go | 2 +- core/routes_test.go | 4 +- core/samples/create_domain.json | 2 +- core/samples/domain_list.json | 4 +- domain/logic.go | 35 +++++++---- domain/middleware.go | 2 +- migrations/0001_initial.down.sql | 1 + migrations/0001_initial.up.sql | 13 +++- migrations/test_migration.up.sql | 4 +- models/domains.go | 9 ++- models/models.go | 2 +- models/schema.sql | 7 ++- templates/domain_list.html | 6 +- 23 files changed, 257 insertions(+), 119 deletions(-) diff --git a/admin/input.go b/admin/input.go index 287899a..f3c4dcd 100644 --- a/admin/input.go +++ b/admin/input.go @@ -57,7 +57,7 @@ func (ui *UserInvitationForm) Validate(c echo.Context) error { 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"` + Service string `form:"service" validate:"oneof=LINKS SHORT LIST"` Level string `form:"level" validate:"oneof=SYSTEM USER"` Status int `form:"status" validate:"oneof=0 1 2"` IsActive bool `form:"is_active"` @@ -71,7 +71,7 @@ func (d *DomainForm) Validate(c echo.Context) error { FailFast(false). String("name", &d.Name). String("lookup_name", &d.LookupName). - Int("service", &d.Service). + String("service", &d.Service). String("level", &d.Level). Int("status", &d.Status). String("org_slug", &d.OrgSlug). diff --git a/admin/routes.go b/admin/routes.go index f47b754..d0340d6 100644 --- a/admin/routes.go +++ b/admin/routes.go @@ -608,7 +608,7 @@ func (s *Service) DomainUpdate(c echo.Context) error { pd.Data["migrate_short_links"] = lt.Translate("Migrate Short Links") pd.Data["migrate_warning"] = lt.Translate("Old links won't work. All code will be moved to the new domain.") pd.Data["is_active"] = lt.Translate("Is Active") - serviceOpt := map[int]string{ + serviceOpt := map[string]string{ models.DomainServiceLinks: lt.Translate("Links"), models.DomainServiceShort: lt.Translate("Link Shortner"), models.DomainServiceList: lt.Translate("Link Listing"), @@ -737,7 +737,7 @@ func (s *Service) DomainCreate(c echo.Context) error { pd.Data["migrate_short_links"] = lt.Translate("Migrate Short Links") pd.Data["migrate_warning"] = lt.Translate("Old links won't work. All code will be moved to the new domain.") pd.Data["is_active"] = lt.Translate("Is Active") - serviceOpt := map[int]string{ + serviceOpt := map[string]string{ models.DomainServiceLinks: lt.Translate("Links"), models.DomainServiceShort: lt.Translate("Link Shortner"), models.DomainServiceList: lt.Translate("Link Listing"), @@ -1061,7 +1061,7 @@ func (s *Service) DomainList(c echo.Context) error { models.DomainLevelSystem: lt.Translate("System"), models.DomainLevelUser: lt.Translate("User"), } - filterService := map[int]string{ + filterService := map[string]string{ models.DomainServiceLinks: lt.Translate("Links"), models.DomainServiceShort: lt.Translate("Shorts"), models.DomainServiceList: lt.Translate("List"), diff --git a/api/api_test.go b/api/api_test.go index 3daa88f..7379722 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -952,7 +952,7 @@ func TestAPI(t *testing.T) { } op := gqlclient.NewOperation( - `mutation AddDomain($name: String!, $lookupName: String!, $orgSlug: String!, $service: Int!) { + `mutation AddDomain($name: String!, $lookupName: String!, $orgSlug: String!, $service: DomainService!) { addDomain(input: {name: $name, lookupName: $lookupName, orgSlug: $orgSlug, service: $service}) { id } @@ -960,7 +960,7 @@ func TestAPI(t *testing.T) { op.Var("name", "Domain") op.Var("lookupName", "app.testing.com") op.Var("orgSlug", "personal-org") - op.Var("service", 0) + op.Var("service", "LINKS") var result GraphQLResponse err := links.Execute(ctx, op, &result) @@ -2095,7 +2095,7 @@ func TestAPI(t *testing.T) { } op := gqlclient.NewOperation( - `mutation AddDomain($name: String!, $lookupName: String!, $orgSlug: String!, $service: Int!) { + `mutation AddDomain($name: String!, $lookupName: String!, $orgSlug: String!, $service: DomainService!) { addDomain(input: {name: $name, lookupName: $lookupName, orgSlug: $orgSlug, service: $service}) { id } @@ -2103,7 +2103,7 @@ func TestAPI(t *testing.T) { op.Var("name", "Domain 2") op.Var("lookupName", "app.domain.com") op.Var("orgSlug", "personal-org") - op.Var("service", 0) + op.Var("service", "LIST") var result GraphQLResponse err := links.Execute(ctx, op, &result) diff --git a/api/graph/generated.go b/api/graph/generated.go index 5143357..bbee43f 100644 --- a/api/graph/generated.go +++ b/api/graph/generated.go @@ -365,7 +365,7 @@ type ComplexityRoot struct { GetAdminOrgStats func(childComplexity int, id int) int GetAdminOrganizations func(childComplexity int, input *model.GetAdminOrganizationsInput) int GetDomain func(childComplexity int, id int) int - GetDomains func(childComplexity int, orgSlug *string, service *int) int + GetDomains func(childComplexity int, orgSlug *string, service *model.DomainService) int GetFeed func(childComplexity int, input *model.GetFeedInput) int GetFeedFollowing func(childComplexity int) int GetLinkShort func(childComplexity int, shortCode string, domainID *int) int @@ -433,7 +433,7 @@ 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) - + Service(ctx context.Context, obj *models.Domain) (model.DomainService, error) Level(ctx context.Context, obj *models.Domain) (model.DomainLevel, error) } type MutationResolver interface { @@ -486,7 +486,7 @@ type QueryResolver interface { GetOrgLink(ctx context.Context, hash string) (*models.OrgLink, error) GetOrgLinks(ctx context.Context, input *model.GetLinkInput) (*model.OrgLinkCursor, error) GetOrgMembers(ctx context.Context, orgSlug string) ([]*models.User, error) - GetDomains(ctx context.Context, orgSlug *string, service *int) ([]*models.Domain, error) + GetDomains(ctx context.Context, orgSlug *string, service *model.DomainService) ([]*models.Domain, error) GetDomain(ctx context.Context, id int) (*models.Domain, error) GetLinkShorts(ctx context.Context, input *model.GetLinkShortInput) (*model.LinkShortCursor, error) GetLinkShort(ctx context.Context, shortCode string, domainID *int) (*models.LinkShort, error) @@ -2177,7 +2177,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Query.GetDomains(childComplexity, args["orgSlug"].(*string), args["service"].(*int)), true + return e.complexity.Query.GetDomains(childComplexity, args["orgSlug"].(*string), args["service"].(*model.DomainService)), true case "Query.getFeed": if e.complexity.Query.GetFeed == nil { @@ -3376,10 +3376,10 @@ func (ec *executionContext) field_Query_getDomains_args(ctx context.Context, raw } } args["orgSlug"] = arg0 - var arg1 *int + var arg1 *model.DomainService if tmp, ok := rawArgs["service"]; ok { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("service")) - arg1, err = ec.unmarshalOInt2ᚖint(ctx, tmp) + arg1, err = ec.unmarshalODomainService2ᚖlinksᚋapiᚋgraphᚋmodelᚐDomainService(ctx, tmp) if err != nil { return nil, err } @@ -5637,7 +5637,7 @@ func (ec *executionContext) _Domain_service(ctx context.Context, field graphql.C }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Service, nil + return ec.resolvers.Domain().Service(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -5649,19 +5649,19 @@ func (ec *executionContext) _Domain_service(ctx context.Context, field graphql.C } return graphql.Null } - res := resTmp.(int) + res := resTmp.(model.DomainService) fc.Result = res - return ec.marshalNInt2int(ctx, field.Selections, res) + return ec.marshalNDomainService2linksᚋapiᚋgraphᚋmodelᚐDomainService(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Domain_service(_ 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 DomainService does not have child fields") }, } return fc, nil @@ -16758,7 +16758,7 @@ func (ec *executionContext) _Query_getDomains(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.Query().GetDomains(rctx, fc.Args["orgSlug"].(*string), fc.Args["service"].(*int)) + return ec.resolvers.Query().GetDomains(rctx, fc.Args["orgSlug"].(*string), fc.Args["service"].(*model.DomainService)) } directive1 := func(ctx context.Context) (interface{}, error) { scope, err := ec.unmarshalNAccessScope2linksᚋapiᚋgraphᚋmodelᚐAccessScope(ctx, "DOMAINS") @@ -21762,7 +21762,7 @@ func (ec *executionContext) unmarshalInputAdminDomainInput(ctx context.Context, it.OrgSlug = data case "service": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("service")) - data, err := ec.unmarshalNInt2int(ctx, v) + data, err := ec.unmarshalNDomainService2linksᚋapiᚋgraphᚋmodelᚐDomainService(ctx, v) if err != nil { return it, err } @@ -21948,7 +21948,7 @@ func (ec *executionContext) unmarshalInputDomainInput(ctx context.Context, obj i it.OrgSlug = data case "service": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("service")) - data, err := ec.unmarshalNInt2int(ctx, v) + data, err := ec.unmarshalNDomainService2linksᚋapiᚋgraphᚋmodelᚐDomainService(ctx, v) if err != nil { return it, err } @@ -22017,7 +22017,7 @@ func (ec *executionContext) unmarshalInputGetAdminDomainInput(ctx context.Contex it.FilterLevel = data case "filterService": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("filterService")) - data, err := ec.unmarshalOInt2ᚖint(ctx, v) + data, err := ec.unmarshalODomainService2ᚖlinksᚋapiᚋgraphᚋmodelᚐDomainService(ctx, v) if err != nil { return it, err } @@ -23070,7 +23070,7 @@ func (ec *executionContext) unmarshalInputUpdateAdminDomainInput(ctx context.Con it.OrgSlug = data case "service": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("service")) - data, err := ec.unmarshalNInt2int(ctx, v) + data, err := ec.unmarshalNDomainService2linksᚋapiᚋgraphᚋmodelᚐDomainService(ctx, v) if err != nil { return it, err } @@ -24074,10 +24074,41 @@ func (ec *executionContext) _Domain(ctx context.Context, sel ast.SelectionSet, o out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) case "service": - out.Values[i] = ec._Domain_service(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_service(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 "level": field := field @@ -27418,6 +27449,16 @@ func (ec *executionContext) marshalNDomainLevel2linksᚋapiᚋgraphᚋmodelᚐDo return v } +func (ec *executionContext) unmarshalNDomainService2linksᚋapiᚋgraphᚋmodelᚐDomainService(ctx context.Context, v interface{}) (model.DomainService, error) { + var res model.DomainService + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNDomainService2linksᚋapiᚋgraphᚋmodelᚐDomainService(ctx context.Context, sel ast.SelectionSet, v model.DomainService) 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) } @@ -28534,6 +28575,22 @@ func (ec *executionContext) marshalODomainLevel2ᚖlinksᚋapiᚋgraphᚋmodel return v } +func (ec *executionContext) unmarshalODomainService2ᚖlinksᚋapiᚋgraphᚋmodelᚐDomainService(ctx context.Context, v interface{}) (*model.DomainService, error) { + if v == nil { + return nil, nil + } + var res = new(model.DomainService) + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalODomainService2ᚖlinksᚋapiᚋgraphᚋmodelᚐDomainService(ctx context.Context, sel ast.SelectionSet, v *model.DomainService) 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 5f4ec89..45b1c98 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 DomainLevel `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 DomainService `json:"service"` + Level DomainLevel `json:"level"` + Status int `json:"status"` + IsActive bool `json:"isActive"` + MigrateLinks *bool `json:"migrateLinks,omitempty"` } type AnalyticData struct { @@ -120,11 +120,11 @@ type DomainCursor struct { } type DomainInput struct { - Name string `json:"name"` - LookupName string `json:"lookupName"` - OrgSlug string `json:"orgSlug"` - Service int `json:"service"` - MigrateLinks *bool `json:"migrateLinks,omitempty"` + Name string `json:"name"` + LookupName string `json:"lookupName"` + OrgSlug string `json:"orgSlug"` + Service DomainService `json:"service"` + MigrateLinks *bool `json:"migrateLinks,omitempty"` } type FollowPayload 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 *DomainLevel `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 *DomainService `json:"filterService,omitempty"` + FilterActive *bool `json:"filterActive,omitempty"` + OrgSlug *string `json:"orgSlug,omitempty"` } type GetAdminOrganizationsInput struct { @@ -372,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 DomainLevel `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 DomainService `json:"service"` + Level DomainLevel `json:"level"` + Status int `json:"status"` + IsActive bool `json:"isActive"` + MigrateLinks *bool `json:"migrateLinks,omitempty"` } type UpdateLinkInput struct { @@ -596,3 +596,46 @@ func (e *DomainLevel) UnmarshalGQL(v interface{}) error { func (e DomainLevel) MarshalGQL(w io.Writer) { fmt.Fprint(w, strconv.Quote(e.String())) } + +type DomainService string + +const ( + DomainServiceLinks DomainService = "LINKS" + DomainServiceShort DomainService = "SHORT" + DomainServiceList DomainService = "LIST" +) + +var AllDomainService = []DomainService{ + DomainServiceLinks, + DomainServiceShort, + DomainServiceList, +} + +func (e DomainService) IsValid() bool { + switch e { + case DomainServiceLinks, DomainServiceShort, DomainServiceList: + return true + } + return false +} + +func (e DomainService) String() string { + return string(e) +} + +func (e *DomainService) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = DomainService(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid DomainService", str) + } + return nil +} + +func (e DomainService) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} diff --git a/api/graph/schema.graphqls b/api/graph/schema.graphqls index c441631..06c1d0e 100644 --- a/api/graph/schema.graphqls +++ b/api/graph/schema.graphqls @@ -68,6 +68,13 @@ enum DomainLevel { USER } +enum DomainService { + LINKS + SHORT + LIST +} + + # Considering removing these Null* fields: # https://todo.code.netlandish.com/~netlandish/links/75 @@ -247,7 +254,7 @@ type Domain { lookupName: String! orgId: NullInt! @access(scope: DOMAINS, kind: RO) orgSlug: NullString! @access(scope: DOMAINS, kind: RO) - service: Int! + service: DomainService! level: DomainLevel! status: Int! isActive: Boolean! @access(scope: DOMAINS, kind: RO) @@ -455,7 +462,7 @@ input GetAdminDomainInput { before: Cursor search: String filterLevel: DomainLevel - filterService: Int + filterService: DomainService filterActive: Boolean orgSlug: String } @@ -624,7 +631,7 @@ input DomainInput { name: String! lookupName: String! orgSlug: String! - service: Int! + service: DomainService! migrateLinks: Boolean } @@ -632,7 +639,7 @@ input AdminDomainInput { name: String! lookupName: String! orgSlug: String - service: Int! + service: DomainService! level: DomainLevel! status: Int! isActive: Boolean! @@ -644,7 +651,7 @@ input UpdateAdminDomainInput { name: String! lookupName: String! orgSlug: String - service: Int! + service: DomainService! level: DomainLevel! status: Int! isActive: Boolean! @@ -703,7 +710,7 @@ type Query { getOrgMembers(orgSlug: String!): [User]! @access(scope: ORGS, kind: RO) "Returns custom domains of an organization" - getDomains(orgSlug: String, service: Int): [Domain]! @access(scope: DOMAINS, kind: RO) + getDomains(orgSlug: String, service: DomainService): [Domain]! @access(scope: DOMAINS, kind: RO) "Returns a specific domain" getDomain(id: Int!): Domain! @access(scope: DOMAINS, kind: RO) diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index 1a5b1a8..0a529ed 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -60,9 +60,14 @@ func (r *domainResolver) OrgSlug(ctx context.Context, obj *models.Domain) (*mode return &model.NullString{String: obj.OrgSlug.String, Valid: obj.OrgSlug.Valid}, nil } +// Service is the resolver for the service field. +func (r *domainResolver) Service(ctx context.Context, obj *models.Domain) (model.DomainService, error) { + return model.DomainService(obj.Service), 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")) + return model.DomainLevel(obj.Level), nil } // AddOrganization is the resolver for the addOrganization field. @@ -1735,8 +1740,7 @@ func (r *mutationResolver) AddDomain(ctx context.Context, input model.DomainInpu lt.Translate("Invalid domain name.")). WithField("lookupName"). WithCode(valid.ErrValidationCode) - validator.Expect(input.Service >= models.DomainServiceLinks && - input.Service <= models.DomainServiceList, + validator.Expect(domain.ValidateDomainService(string(input.Service)), lt.Translate("Invalid service value.")). WithField("service"). WithCode(valid.ErrValidationCode) @@ -1818,7 +1822,7 @@ func (r *mutationResolver) AddDomain(ctx context.Context, input model.DomainInpu return nil, nil } - dnsOK, err := domain.CheckDomainDNS(ctx, host, input.Service) + dnsOK, err := domain.CheckDomainDNS(ctx, host, string(input.Service)) if err != nil { validator.Error( lt.Translate("Error checking the DNS entry for domain. Please try again later")). @@ -1838,7 +1842,7 @@ func (r *mutationResolver) AddDomain(ctx context.Context, input model.DomainInpu LookupName: host, OrgID: sql.NullInt64{Int64: int64(org.ID), Valid: true}, Level: models.DomainLevelUser, - Service: input.Service, + Service: string(input.Service), Status: models.DomainStatusApproved, IsActive: true, } @@ -1847,7 +1851,7 @@ func (r *mutationResolver) AddDomain(ctx context.Context, input model.DomainInpu return nil, err } - if input.MigrateLinks != nil && *input.MigrateLinks && input.Service == models.DomainServiceShort { + if input.MigrateLinks != nil && *input.MigrateLinks && string(input.Service) == models.DomainServiceShort { err = models.MigrateLinkShorts(ctx, domain.ID, int(domain.OrgID.Int64)) if err != nil { return nil, err @@ -3701,8 +3705,7 @@ func (r *mutationResolver) AddAdminDomain(ctx context.Context, input model.Admin lt.Translate("Invalid domain name.")). WithField("lookupName"). WithCode(valid.ErrValidationCode) - validator.Expect(input.Service >= models.DomainServiceLinks && - input.Service <= models.DomainServiceList, + validator.Expect(domain.ValidateDomainService(string(input.Service)), lt.Translate("Invalid service value.")). WithField("service"). WithCode(valid.ErrValidationCode) @@ -3712,7 +3715,7 @@ func (r *mutationResolver) AddAdminDomain(ctx context.Context, input model.Admin WithField("level"). WithCode(valid.ErrValidationCode) validator.Expect(input.Status >= models.DomainStatusPending && - input.Service <= models.DomainStatusError, + input.Status <= models.DomainStatusError, lt.Translate("Invalid status value.")). WithField("status"). WithCode(valid.ErrValidationCode) @@ -3810,7 +3813,7 @@ func (r *mutationResolver) AddAdminDomain(ctx context.Context, input model.Admin } } - dnsOK, err := domain.CheckDomainDNS(ctx, host, input.Service) + dnsOK, err := domain.CheckDomainDNS(ctx, host, string(input.Service)) if err != nil { validator.Error( lt.Translate("Error checking the DNS entry for domain. Please try again later")). @@ -3829,7 +3832,7 @@ func (r *mutationResolver) AddAdminDomain(ctx context.Context, input model.Admin Name: input.Name, LookupName: host, Level: string(input.Level), - Service: input.Service, + Service: string(input.Service), Status: input.Status, IsActive: input.IsActive, } @@ -3841,7 +3844,7 @@ func (r *mutationResolver) AddAdminDomain(ctx context.Context, input model.Admin return nil, err } - if input.MigrateLinks != nil && *input.MigrateLinks && input.Service == models.DomainServiceShort { + if input.MigrateLinks != nil && *input.MigrateLinks && string(input.Service) == models.DomainServiceShort { err = models.MigrateLinkShorts(ctx, domain.ID, int(domain.OrgID.Int64)) if err != nil { return nil, err @@ -3877,8 +3880,7 @@ func (r *mutationResolver) UpdateAdminDomain(ctx context.Context, input model.Up lt.Translate("Invalid ID ")). WithField("id"). WithCode(valid.ErrValidationCode) - validator.Expect(input.Service >= models.DomainServiceLinks && - input.Service <= models.DomainServiceList, + validator.Expect(domain.ValidateDomainService(string(input.Service)), lt.Translate("Invalid service value.")). WithField("service"). WithCode(valid.ErrValidationCode) @@ -3888,7 +3890,7 @@ func (r *mutationResolver) UpdateAdminDomain(ctx context.Context, input model.Up WithField("level"). WithCode(valid.ErrValidationCode) validator.Expect(input.Status >= models.DomainStatusPending && - input.Service <= models.DomainStatusError, + input.Status <= models.DomainStatusError, lt.Translate("Invalid status value.")). WithField("status"). WithCode(valid.ErrValidationCode) @@ -4006,7 +4008,7 @@ func (r *mutationResolver) UpdateAdminDomain(ctx context.Context, input model.Up } } - dnsOK, err := domain.CheckDomainDNS(ctx, host, input.Service) + dnsOK, err := domain.CheckDomainDNS(ctx, host, string(input.Service)) if err != nil { validator.Error( lt.Translate("Error checking the DNS entry for domain. Please try again later")). @@ -4024,7 +4026,7 @@ func (r *mutationResolver) UpdateAdminDomain(ctx context.Context, input model.Up ldomain.Name = input.Name ldomain.LookupName = host ldomain.Level = string(input.Level) - ldomain.Service = input.Service + ldomain.Service = string(input.Service) ldomain.Status = input.Status ldomain.IsActive = input.IsActive @@ -4036,7 +4038,7 @@ func (r *mutationResolver) UpdateAdminDomain(ctx context.Context, input model.Up return nil, err } - if input.MigrateLinks != nil && *input.MigrateLinks && input.Service == models.DomainServiceShort { + if input.MigrateLinks != nil && *input.MigrateLinks && string(input.Service) == models.DomainServiceShort { err = models.MigrateLinkShorts(ctx, ldomain.ID, int(ldomain.OrgID.Int64)) if err != nil { return nil, err @@ -4815,7 +4817,7 @@ func (r *queryResolver) GetOrgMembers(ctx context.Context, orgSlug string) ([]*m } // GetDomains is the resolver for the getDomains field. -func (r *queryResolver) GetDomains(ctx context.Context, orgSlug *string, service *int) ([]*models.Domain, error) { +func (r *queryResolver) GetDomains(ctx context.Context, orgSlug *string, service *model.DomainService) ([]*models.Domain, error) { tokenUser := oauth2.ForContext(ctx) if tokenUser == nil { return nil, valid.ErrAuthorization diff --git a/cmd/domains/main.go b/cmd/domains/main.go index d223b27..f74ca87 100644 --- a/cmd/domains/main.go +++ b/cmd/domains/main.go @@ -56,7 +56,7 @@ func run() error { if dom == "" { return c.NoContent(http.StatusBadRequest) } - domains, err := domain.ValidDomain(c.Request().Context(), dom, -1, true) + domains, err := domain.ValidDomain(c.Request().Context(), dom, "", true) if err != nil { return err } diff --git a/cmd/server.go b/cmd/server.go index 2a978ac..eae660a 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -189,7 +189,7 @@ func LoadStorageService(config *config.Config) (storage.Service, error) { } // LoadAutoTLS ... -func LoadAutoTLS(config *config.Config, db *sql.DB, service int) *autocert.Manager { +func LoadAutoTLS(config *config.Config, db *sql.DB, service string) *autocert.Manager { autotls, ok := config.File.Get("links", "auto-tls") if ok && autotls == "false" { // Enabled by default diff --git a/core/inputs.go b/core/inputs.go index e30bef7..58c823d 100644 --- a/core/inputs.go +++ b/core/inputs.go @@ -142,7 +142,7 @@ func (l *LinkForm) Validate(c echo.Context) error { 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"` + Service string `form:"service" validate:"oneof=LINKS SHORT LIST"` MigrateLinks bool `form:"migrate_links"` } @@ -152,7 +152,7 @@ func (d *DomainForm) Validate(c echo.Context) error { FailFast(false). String("name", &d.Name). String("lookup_name", &d.LookupName). - Int("service", &d.Service). + String("service", &d.Service). Bool("migrate_links", &d.MigrateLinks). BindErrors() if errs != nil { diff --git a/core/routes.go b/core/routes.go index 6479c2e..1d87377 100644 --- a/core/routes.go +++ b/core/routes.go @@ -466,7 +466,7 @@ func (s *Service) DomainCreate(c echo.Context) error { pd.Data["for"] = lt.Translate("for") pd.Data["migrate_short_links"] = lt.Translate("Migrate Short Links") pd.Data["migrate_warning"] = lt.Translate("Old links won't work. All code will be moved to the new domain.") - serviceOpt := map[int]string{ + serviceOpt := map[string]string{ models.DomainServiceLinks: lt.Translate("Links"), models.DomainServiceShort: lt.Translate("Link Shortner"), models.DomainServiceList: lt.Translate("Link Listing"), diff --git a/core/routes_test.go b/core/routes_test.go index 1f4eece..d661bb8 100644 --- a/core/routes_test.go +++ b/core/routes_test.go @@ -344,7 +344,7 @@ func TestHandlers(t *testing.T) { f := make(url.Values) f.Set("lookup_name", "app.links.org") - f.Set("service", "1") + f.Set("service", "SHORT") f.Set("name", "app links") request := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(f.Encode())) request.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm) @@ -371,7 +371,7 @@ func TestHandlers(t *testing.T) { f := make(url.Values) f.Set("lookup_name", "app.links.org") - f.Set("service", "1") + f.Set("service", "SHORT") f.Set("name", "app links") request := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(f.Encode())) request.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm) diff --git a/core/samples/create_domain.json b/core/samples/create_domain.json index 5718085..e69cdd1 100644 --- a/core/samples/create_domain.json +++ b/core/samples/create_domain.json @@ -3,7 +3,7 @@ "addDomain": { "id": 1, "lookup_name": "app.links.org", - "service": 0, + "service": "LINKS", "org_id": 1 } } diff --git a/core/samples/domain_list.json b/core/samples/domain_list.json index 273164d..b08c610 100644 --- a/core/samples/domain_list.json +++ b/core/samples/domain_list.json @@ -6,14 +6,14 @@ "name": "Domain one", "lookup_name": "app.links.com", "org_id": 1, - "service": 0 + "service": "LINKS" }, { "id": 2, "name": "Domain two", "lookup_name": "app.links_two.com", "org_id": 1, - "service": 1 + "service": "SHORT" } ] } diff --git a/domain/logic.go b/domain/logic.go index a30d870..cfa342e 100644 --- a/domain/logic.go +++ b/domain/logic.go @@ -18,7 +18,7 @@ import ( var ipMap map[string]net.IP func ValidDomain( - ctx context.Context, host string, service int, active bool) ([]*models.Domain, error) { + ctx context.Context, host string, service string, active bool) ([]*models.Domain, error) { // Remove :PORT from host name host = strings.SplitN(host, ":", 2)[0] h, err := idna.Lookup.ToASCII(host) @@ -32,7 +32,7 @@ func ValidDomain( sq.Eq{"d.status": models.DomainStatusApproved}, }, } - if service >= 0 { + if service != "" { opts.Filter = sq.And{ opts.Filter, sq.Eq{"d.service": service}, @@ -53,7 +53,7 @@ func ValidDomain( // DomainHostPolicy returns a autocert manager HostPolicy instance to work // with confiugred domains for various services -func DomainHostPolicy(db *sql.DB, service int) autocert.HostPolicy { +func DomainHostPolicy(db *sql.DB, service string) autocert.HostPolicy { return func(ctx context.Context, host string) error { ctx = database.Context(ctx, db) domains, err := ValidDomain(ctx, host, service, true) @@ -68,9 +68,11 @@ func DomainHostPolicy(db *sql.DB, service int) autocert.HostPolicy { } // CheckDomainDNS will verify a domain has a proper CNAME set -func CheckDomainDNS(ctx context.Context, domain string, service int) (bool, error) { - if service < models.DomainServiceLinks || service > models.DomainServiceList { - return false, fmt.Errorf("invalid service type given") +func CheckDomainDNS(ctx context.Context, domain string, service string) (bool, error) { + serviceMap := map[string]string{ + models.DomainServiceLinks: "links-cname-domain", + models.DomainServiceShort: "short-cname-domain", + models.DomainServiceList: "list-cname-domain", } var ( @@ -85,13 +87,9 @@ func CheckDomainDNS(ctx context.Context, domain string, service int) (bool, erro return true, nil } - switch service { - case models.DomainServiceLinks: - cval = "links-cname-domain" - case models.DomainServiceShort: - cval = "short-cname-domain" - case models.DomainServiceList: - cval = "list-cname-domain" + cval, ok = serviceMap[service] + if !ok { + return false, fmt.Errorf("invalid service type given") } expName, ok = srv.Config.File.Get("links", cval) @@ -128,3 +126,14 @@ func CheckDomainDNS(ctx context.Context, domain string, service int) (bool, erro return true, nil } + +// ValidateDomainService is a helper to verify the service given is valid +func ValidateDomainService(service string) bool { + serviceMap := map[string]bool{ + models.DomainServiceLinks: true, + models.DomainServiceShort: true, + models.DomainServiceList: true, + } + _, ok := serviceMap[service] + return ok +} diff --git a/domain/middleware.go b/domain/middleware.go index c6f48b0..5400dd3 100644 --- a/domain/middleware.go +++ b/domain/middleware.go @@ -49,7 +49,7 @@ func badDomainRedirect(c echo.Context, d string) error { } // DomainContext adds the current domain to request context. -func DomainContext(service int) echo.MiddlewareFunc { +func DomainContext(service string) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { req := c.Request() diff --git a/migrations/0001_initial.down.sql b/migrations/0001_initial.down.sql index 45749b0..e7ed75b 100644 --- a/migrations/0001_initial.down.sql +++ b/migrations/0001_initial.down.sql @@ -33,3 +33,4 @@ DROP TABLE IF EXISTS organizations; DROP TABLE IF EXISTS users; DROP TYPE IF EXISTS domain_level; +DROP TYPE IF EXISTS domain_service; diff --git a/migrations/0001_initial.up.sql b/migrations/0001_initial.up.sql index 9051d38..5ddaf92 100644 --- a/migrations/0001_initial.up.sql +++ b/migrations/0001_initial.up.sql @@ -20,6 +20,17 @@ EXCEPTION WHEN duplicate_object THEN null; END $$; +DO $$ BEGIN + CREATE TYPE domain_service AS ENUM ( + 'LINKS', + 'SHORT', + 'LIST' + ); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; + + CREATE TABLE users ( id SERIAL PRIMARY KEY, @@ -153,7 +164,7 @@ CREATE TABLE domains ( lookup_name VARCHAR (500) NOT NULL, -- padded in case... org_id INT REFERENCES organizations (id) ON DELETE CASCADE, level domain_level NOT NULL, - service INT NOT NULL, + service domain_service NOT NULL, status INT DEFAULT 0 NOT NULL, is_active BOOLEAN DEFAULT TRUE, last_action TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, diff --git a/migrations/test_migration.up.sql b/migrations/test_migration.up.sql index 19d5e9a..2f67674 100644 --- a/migrations/test_migration.up.sql +++ b/migrations/test_migration.up.sql @@ -15,8 +15,8 @@ INSERT INTO org_links (title, url, base_url_id, user_id, org_id, visibility, has INSERT INTO org_links (title, url, base_url_id, user_id, org_id, visibility, hash) VALUES ('Private Business url', 'http://base.com?vis=private', 1, 1, 2, 1, 'hash2'); -INSERT INTO domains (name, lookup_name, org_id, level, service, status) VALUES ('short domain', 'short.domain.org', 1, 'SYSTEM', 1, 1); -INSERT INTO domains (name, lookup_name, org_id, service, status, level) VALUES ('listing domain', 'list.domain.org', 1, 2, 1, 'USER'); +INSERT INTO domains (name, lookup_name, org_id, level, service, status) VALUES ('short domain', 'short.domain.org', 1, 'SYSTEM', 'SHORT', 1); +INSERT INTO domains (name, lookup_name, org_id, service, status, level) VALUES ('listing domain', 'list.domain.org', 1, 'LIST', 1, 'USER'); INSERT INTO link_shorts (id, title, url, domain_id, org_id, short_code, user_id) VALUES (100, 'testing short', 'http://test.domain.org', 1, 1, 'foo', 1); INSERT INTO listings (id, title, slug, domain_id, is_default, user_id, org_id) VALUES (100, 'test listing', 'test-listing-slug', 2, false, 1, 1); diff --git a/models/domains.go b/models/domains.go index d4389ac..732d7cd 100644 --- a/models/domains.go +++ b/models/domains.go @@ -22,11 +22,11 @@ const ( const ( // DomainServiceLinks ... - DomainServiceLinks int = iota + DomainServiceLinks string = "LINKS" // DomainServiceShort ... - DomainServiceShort + DomainServiceShort string = "SHORT" // DomainServiceList ... - DomainServiceList + DomainServiceList string = "LIST" ) const ( @@ -137,6 +137,9 @@ func (d *Domain) Store(ctx context.Context) error { if d.Level == "" { d.Level = DomainLevelSystem } + if d.Service == "" { + d.Service = DomainServiceLinks + } 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 9b8ff88..66f9c32 100644 --- a/models/models.go +++ b/models/models.go @@ -110,7 +110,7 @@ type Domain struct { LookupName string `db:"lookup_name"` OrgID sql.NullInt64 `db:"org_id"` Level string `db:"level"` - Service int `db:"service"` + Service string `db:"service"` Status int `db:"status"` IsActive bool `db:"is_active"` LastAction time.Time `db:"last_action"` diff --git a/models/schema.sql b/models/schema.sql index 33ff69e..e762afa 100644 --- a/models/schema.sql +++ b/models/schema.sql @@ -15,6 +15,11 @@ CREATE TYPE domain_level AS ENUM ( 'USER' ); +CREATE TYPE domain_service AS ENUM ( + 'LINKS', + 'SHORT', + 'LIST' +); CREATE TABLE users ( id SERIAL PRIMARY KEY, @@ -148,7 +153,7 @@ CREATE TABLE domains ( lookup_name VARCHAR (500) NOT NULL, -- padded in case... org_id INT REFERENCES organizations (id) ON DELETE CASCADE, level domain_level NOT NULL, - service INT NOT NULL, + service domain_service NOT NULL, status INT DEFAULT 0 NOT NULL, is_active BOOLEAN DEFAULT TRUE, last_action TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, diff --git a/templates/domain_list.html b/templates/domain_list.html index 8072486..0a59edb 100644 --- a/templates/domain_list.html +++ b/templates/domain_list.html @@ -25,11 +25,11 @@ {{.Name}} {{.LookupName}} - {{ if eq .Service 0 }} + {{ if .IsServiceLink }} {{$.pd.Data.links}} - {{ else if eq .Service 1 }} + {{ else if .IsServiceShort }} {{$.pd.Data.link_shortner}} - {{ else if eq .Service 2 }} + {{ else if .IsServiceList }} {{$.pd.Data.link_listing}} {{end}} -- 2.45.2