~netlandish/links

14b83d0ffa8acff66789998b94dd11d1771ce396 — Peter Sanchez a day ago 9d56239
Moving to DomainService values to enum instead of int.
M admin/input.go => admin/input.go +2 -2
@@ 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).

M admin/routes.go => admin/routes.go +3 -3
@@ 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"),

M api/api_test.go => api/api_test.go +4 -4
@@ 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)

M api/graph/generated.go => api/graph/generated.go +77 -20
@@ 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

M api/graph/model/models_gen.go => api/graph/model/models_gen.go +73 -30
@@ 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()))
}

M api/graph/schema.graphqls => api/graph/schema.graphqls +13 -6
@@ 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)

M api/graph/schema.resolvers.go => api/graph/schema.resolvers.go +21 -19
@@ 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

M cmd/domains/main.go => cmd/domains/main.go +1 -1
@@ 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
		}

M cmd/server.go => cmd/server.go +1 -1
@@ 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

M core/inputs.go => core/inputs.go +2 -2
@@ 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 {

M core/routes.go => core/routes.go +1 -1
@@ 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"),

M core/routes_test.go => core/routes_test.go +2 -2
@@ 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)

M core/samples/create_domain.json => core/samples/create_domain.json +1 -1
@@ 3,7 3,7 @@
        "addDomain": {
            "id": 1,
            "lookup_name": "app.links.org",
            "service": 0,
            "service": "LINKS",
            "org_id": 1
        }
    }

M core/samples/domain_list.json => core/samples/domain_list.json +2 -2
@@ 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"
            }
        ]
    }

M domain/logic.go => domain/logic.go +22 -13
@@ 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
}

M domain/middleware.go => domain/middleware.go +1 -1
@@ 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()

M migrations/0001_initial.down.sql => migrations/0001_initial.down.sql +1 -0
@@ 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;

M migrations/0001_initial.up.sql => migrations/0001_initial.up.sql +12 -1
@@ 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,

M migrations/test_migration.up.sql => migrations/test_migration.up.sql +2 -2
@@ 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);

M models/domains.go => models/domains.go +6 -3
@@ 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").

M models/models.go => models/models.go +1 -1
@@ 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"`

M models/schema.sql => models/schema.sql +6 -1
@@ 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,

M templates/domain_list.html => templates/domain_list.html +3 -3
@@ 25,11 25,11 @@
                        <td>{{.Name}}</td>
                        <td>{{.LookupName}}</td>
                        <td>
                            {{ 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}}
                        </td>