~netlandish/links

2df15f5b826057b71c6b5efff4b9582b4dfa9952 — Peter Sanchez 2 days ago 7d0d0bc
Starting work to move int values to string/enum types for data clairty
M admin/input.go => admin/input.go +2 -2
@@ 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).

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

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

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

M api/graph/schema.graphqls => api/graph/schema.graphqls +15 -9
@@ 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

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

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

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

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

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

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