M admin/input.go => admin/input.go +2 -2
@@ 85,13 85,13 @@ func (d *DomainForm) Validate(c echo.Context) error {
}
type OrgTypeForm struct {
- OrgType int `form:"org_type" validate:"oneof=0 1 2 3 4"`
+ OrgType string `form:"org_type" validate:"oneof=FREE PERSONAL BUSINESS OPEN_SOURCE SPONSORED"`
}
func (ot *OrgTypeForm) Validate(c echo.Context) error {
errs := validate.FormFieldBinder(c, ot).
FailFast(false).
- Int("org_type", &ot.OrgType).
+ String("org_type", &ot.OrgType).
BindErrors()
if errs != nil {
return validate.GetInputErrors(errs)
M admin/routes.go => admin/routes.go +1 -1
@@ 250,7 250,7 @@ func (s *Service) UpdateOrgType(c echo.Context) error {
pd.Data["save"] = lt.Translate("Save")
pd.Data["cancel"] = lt.Translate("Cancel")
- orgTypes := map[int]string{
+ orgTypes := map[string]string{
models.BillingStatusFree: lt.Translate("Free"),
models.BillingStatusPersonal: lt.Translate("Personal"),
models.BillingStatusBusiness: lt.Translate("Business"),
M analytics/routes.go => analytics/routes.go +1 -1
@@ 83,7 83,7 @@ func (s *Service) Detail(c echo.Context) error {
}
var isRestricted bool
- if org.IsRestricted([]int{models.BillingStatusFree, models.BillingStatusOpenSource}) {
+ if org.IsRestricted([]string{models.BillingStatusFree, models.BillingStatusOpenSource}) {
isRestricted = true
}
M api/graph/generated.go => api/graph/generated.go +60 -15
@@ 41,6 41,7 @@ type Config struct {
}
type ResolverRoot interface {
+ BillingSettings() BillingSettingsResolver
Domain() DomainResolver
Mutation() MutationResolver
OrgLink() OrgLinkResolver
@@ 237,7 238,7 @@ type ComplexityRoot struct {
SendRegisterInvitation func(childComplexity int, toEmail string) int
Unfollow func(childComplexity int, orgSlug string) int
UpdateAdminDomain func(childComplexity int, input model.UpdateAdminDomainInput) int
- UpdateAdminOrgType func(childComplexity int, orgSlug string, orgType int) int
+ UpdateAdminOrgType func(childComplexity int, orgSlug string, orgType model.OrgBillingStatus) int
UpdateAdminUser func(childComplexity int, input *model.UpdateUserInput) int
UpdateLink func(childComplexity int, input *model.UpdateLinkInput) int
UpdateLinkShort func(childComplexity int, input *model.UpdateLinkShortInput) int
@@ 431,6 432,9 @@ type ComplexityRoot struct {
}
}
+type BillingSettingsResolver interface {
+ Status(ctx context.Context, obj *models.BillingSettings) (model.OrgBillingStatus, error)
+}
type DomainResolver interface {
OrgID(ctx context.Context, obj *models.Domain) (*model.NullInt, error)
OrgSlug(ctx context.Context, obj *models.Domain) (*model.NullString, error)
@@ 466,7 470,7 @@ type MutationResolver interface {
DeleteQRCode(ctx context.Context, id int) (*model.DeletePayload, error)
Follow(ctx context.Context, orgSlug string) (*model.FollowPayload, error)
Unfollow(ctx context.Context, orgSlug string) (*model.FollowPayload, error)
- UpdateAdminOrgType(ctx context.Context, orgSlug string, orgType int) (*models.Organization, error)
+ UpdateAdminOrgType(ctx context.Context, orgSlug string, orgType model.OrgBillingStatus) (*models.Organization, error)
AddAdminDomain(ctx context.Context, input model.AdminDomainInput) (*models.Domain, error)
UpdateAdminDomain(ctx context.Context, input model.UpdateAdminDomainInput) (*models.Domain, error)
UpdateAdminUser(ctx context.Context, input *model.UpdateUserInput) (*models.User, error)
@@ 1509,7 1513,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return 0, false
}
- return e.complexity.Mutation.UpdateAdminOrgType(childComplexity, args["orgSlug"].(string), args["orgType"].(int)), true
+ return e.complexity.Mutation.UpdateAdminOrgType(childComplexity, args["orgSlug"].(string), args["orgType"].(model.OrgBillingStatus)), true
case "Mutation.updateAdminUser":
if e.complexity.Mutation.UpdateAdminUser == nil {
@@ 3153,10 3157,10 @@ func (ec *executionContext) field_Mutation_updateAdminOrgType_args(ctx context.C
}
}
args["orgSlug"] = arg0
- var arg1 int
+ var arg1 model.OrgBillingStatus
if tmp, ok := rawArgs["orgType"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("orgType"))
- arg1, err = ec.unmarshalNInt2int(ctx, tmp)
+ arg1, err = ec.unmarshalNOrgBillingStatus2linksᚋapiᚋgraphᚋmodelᚐOrgBillingStatus(ctx, tmp)
if err != nil {
return nil, err
}
@@ 5228,7 5232,7 @@ func (ec *executionContext) _BillingSettings_status(ctx context.Context, field g
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
- return obj.Status, nil
+ return ec.resolvers.BillingSettings().Status(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
@@ 5240,19 5244,19 @@ func (ec *executionContext) _BillingSettings_status(ctx context.Context, field g
}
return graphql.Null
}
- res := resTmp.(int)
+ res := resTmp.(model.OrgBillingStatus)
fc.Result = res
- return ec.marshalNInt2int(ctx, field.Selections, res)
+ return ec.marshalNOrgBillingStatus2linksᚋapiᚋgraphᚋmodelᚐOrgBillingStatus(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_BillingSettings_status(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "BillingSettings",
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 OrgBillingStatus does not have child fields")
},
}
return fc, nil
@@ 11609,7 11613,7 @@ func (ec *executionContext) _Mutation_updateAdminOrgType(ctx context.Context, fi
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.Mutation().UpdateAdminOrgType(rctx, fc.Args["orgSlug"].(string), fc.Args["orgType"].(int))
+ return ec.resolvers.Mutation().UpdateAdminOrgType(rctx, fc.Args["orgSlug"].(string), fc.Args["orgType"].(model.OrgBillingStatus))
}
directive1 := func(ctx context.Context) (interface{}, error) {
if ec.directives.Admin == nil {
@@ 23943,10 23947,41 @@ func (ec *executionContext) _BillingSettings(ctx context.Context, sel ast.Select
case "__typename":
out.Values[i] = graphql.MarshalString("BillingSettings")
case "status":
- out.Values[i] = ec._BillingSettings_status(ctx, field, obj)
- if out.Values[i] == graphql.Null {
- out.Invalids++
+ 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._BillingSettings_status(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) })
default:
panic("unknown field " + strconv.Quote(field.Name))
}
@@ 27967,6 28002,16 @@ func (ec *executionContext) marshalNNullString2ᚖlinksᚋapiᚋgraphᚋmodelᚐ
return ec._NullString(ctx, sel, v)
}
+func (ec *executionContext) unmarshalNOrgBillingStatus2linksᚋapiᚋgraphᚋmodelᚐOrgBillingStatus(ctx context.Context, v interface{}) (model.OrgBillingStatus, error) {
+ var res model.OrgBillingStatus
+ err := res.UnmarshalGQL(v)
+ return res, graphql.ErrorOnPath(ctx, err)
+}
+
+func (ec *executionContext) marshalNOrgBillingStatus2linksᚋapiᚋgraphᚋmodelᚐOrgBillingStatus(ctx context.Context, sel ast.SelectionSet, v model.OrgBillingStatus) graphql.Marshaler {
+ return v
+}
+
func (ec *executionContext) marshalNOrgLink2linksᚋmodelsᚐOrgLink(ctx context.Context, sel ast.SelectionSet, v models.OrgLink) graphql.Marshaler {
return ec._OrgLink(ctx, sel, &v)
}
M api/graph/model/models_gen.go => api/graph/model/models_gen.go +47 -0
@@ 810,6 810,53 @@ func (e MemberPermission) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
+type OrgBillingStatus string
+
+const (
+ OrgBillingStatusFree OrgBillingStatus = "FREE"
+ OrgBillingStatusPersonal OrgBillingStatus = "PERSONAL"
+ OrgBillingStatusBusiness OrgBillingStatus = "BUSINESS"
+ OrgBillingStatusOpenSource OrgBillingStatus = "OPEN_SOURCE"
+ OrgBillingStatusSponsored OrgBillingStatus = "SPONSORED"
+)
+
+var AllOrgBillingStatus = []OrgBillingStatus{
+ OrgBillingStatusFree,
+ OrgBillingStatusPersonal,
+ OrgBillingStatusBusiness,
+ OrgBillingStatusOpenSource,
+ OrgBillingStatusSponsored,
+}
+
+func (e OrgBillingStatus) IsValid() bool {
+ switch e {
+ case OrgBillingStatusFree, OrgBillingStatusPersonal, OrgBillingStatusBusiness, OrgBillingStatusOpenSource, OrgBillingStatusSponsored:
+ return true
+ }
+ return false
+}
+
+func (e OrgBillingStatus) String() string {
+ return string(e)
+}
+
+func (e *OrgBillingStatus) UnmarshalGQL(v interface{}) error {
+ str, ok := v.(string)
+ if !ok {
+ return fmt.Errorf("enums must be strings")
+ }
+
+ *e = OrgBillingStatus(str)
+ if !e.IsValid() {
+ return fmt.Errorf("%s is not a valid OrgBillingStatus", str)
+ }
+ return nil
+}
+
+func (e OrgBillingStatus) MarshalGQL(w io.Writer) {
+ fmt.Fprint(w, strconv.Quote(e.String()))
+}
+
type OrgType string
const (
M api/graph/schema.graphqls => api/graph/schema.graphqls +10 -2
@@ 102,6 102,14 @@ enum OrgType {
NORMAL
}
+enum OrgBillingStatus {
+ FREE
+ PERSONAL
+ BUSINESS
+ OPEN_SOURCE
+ SPONSORED
+}
+
# Considering removing these Null* fields:
# https://todo.code.netlandish.com/~netlandish/links/75
@@ 130,7 138,7 @@ type User {
}
type BillingSettings {
- status: Int!
+ status: OrgBillingStatus!
}
type OrganizationSettings {
@@ 840,7 848,7 @@ type Mutation {
# Admin only. Not open to public calls
#
- updateAdminOrgType(orgSlug: String!, orgType: Int!): Organization! @admin
+ updateAdminOrgType(orgSlug: String!, orgType: OrgBillingStatus!): Organization! @admin
addAdminDomain(input: AdminDomainInput!): Domain! @admin
updateAdminDomain(input: UpdateAdminDomainInput!): Domain! @admin
updateAdminUser(input: UpdateUserInput): User! @admin
M api/graph/schema.resolvers.go => api/graph/schema.resolvers.go +52 -22
@@ 50,6 50,11 @@ import (
"netlandish.com/x/gobwebs/validate"
)
+// Status is the resolver for the status field.
+func (r *billingSettingsResolver) Status(ctx context.Context, obj *models.BillingSettings) (model.OrgBillingStatus, error) {
+ return model.OrgBillingStatus(obj.Status), nil
+}
+
// OrgID is the resolver for the orgId field.
func (r *domainResolver) OrgID(ctx context.Context, obj *models.Domain) (*model.NullInt, error) {
return &model.NullInt{Int64: int(obj.OrgID.Int64), Valid: obj.OrgID.Valid}, nil
@@ 107,7 112,7 @@ func (r *mutationResolver) AddOrganization(ctx context.Context, input model.Orga
Filter: sq.And{
sq.Eq{"o.owner_id": user.ID},
sq.Eq{"o.is_active": true},
- sq.Eq{"(o.settings->'billing'->'status')::integer": models.BillingStatusFree},
+ sq.Eq{"(o.settings->'billing'->>'status')": models.BillingStatusFree},
},
}
@@ 154,7 159,12 @@ func (r *mutationResolver) AddOrganization(ctx context.Context, input model.Orga
Name: input.Name,
Slug: slug,
IsActive: true,
- Settings: models.OrganizationSettings{DefaultPerm: models.OrgLinkVisibilityPublic},
+ Settings: models.OrganizationSettings{
+ DefaultPerm: models.OrgLinkVisibilityPublic,
+ Billing: models.BillingSettings{
+ Status: models.BillingStatusFree,
+ },
+ },
}
if input.Image != nil {
@@ 262,7 272,7 @@ func (r *mutationResolver) AddLink(ctx context.Context, input *model.LinkInput)
srv := server.ForContext(ctx)
// We want to restrict private links only to paid users
if visibility == models.OrgLinkVisibilityPrivate &&
- links.BillingEnabled(srv.Config) && org.IsRestricted([]int{models.BillingStatusFree}) {
+ links.BillingEnabled(srv.Config) && org.IsRestricted([]string{models.BillingStatusFree}) {
validator.Error(lt.Translate("Free organizations are not allowed to create private links. Please upgrade")).
WithField("visibility").
WithCode(valid.ErrValidationCode)
@@ 441,7 451,7 @@ func (r *mutationResolver) UpdateLink(ctx context.Context, input *model.UpdateLi
// We want to restrict private links only to paid users
if string(*input.Visibility) == models.OrgLinkVisibilityPrivate &&
links.BillingEnabled(srv.Config) &&
- org.IsRestricted([]int{models.BillingStatusFree}) {
+ org.IsRestricted([]string{models.BillingStatusFree}) {
validator.Error(lt.Translate("Free organizations are not allowed to create private links. Please upgrade")).
WithField("visibility").
WithCode(valid.ErrValidationCode)
@@ 684,7 694,7 @@ func (r *mutationResolver) AddNote(ctx context.Context, input *model.NoteInput)
srv := server.ForContext(ctx)
// We want to restrict private links only to paid users
if string(input.Visibility) == models.OrgLinkVisibilityPrivate &&
- links.BillingEnabled(srv.Config) && org.IsRestricted([]int{models.BillingStatusFree}) {
+ links.BillingEnabled(srv.Config) && org.IsRestricted([]string{models.BillingStatusFree}) {
validator.Error(lt.Translate("Free organizations are not allowed to create private notes. Please upgrade")).
WithField("visibility").
WithCode(valid.ErrValidationCode)
@@ 819,7 829,7 @@ func (r *mutationResolver) AddMember(ctx context.Context, input *model.MemberInp
}
srv := server.ForContext(ctx)
- if links.BillingEnabled(srv.Config) && org.IsRestricted([]int{models.BillingStatusFree,
+ if links.BillingEnabled(srv.Config) && org.IsRestricted([]string{models.BillingStatusFree,
models.BillingStatusOpenSource, models.BillingStatusPersonal}) {
validator.Error(lt.Translate("This function is only allowed for business users.")).
WithCode(valid.ErrRestrictedCode)
@@ 1056,7 1066,7 @@ func (r *mutationResolver) ConfirmMember(ctx context.Context, key string) (*mode
opts := &database.FilterOptions{
Filter: sq.And{
sq.Eq{"o.id": orgID},
- sq.NotEq{"(o.settings->'billing'->'status')::integer": models.BillingStatusFree},
+ sq.NotEq{"(o.settings->'billing'->>'status')": models.BillingStatusFree},
sq.Eq{"o.is_active": true},
},
Limit: 1,
@@ 1238,6 1248,12 @@ func (r *mutationResolver) Register(ctx context.Context, input *model.RegisterIn
Name: user.Name,
Slug: slug,
IsActive: true,
+ Settings: models.OrganizationSettings{
+ DefaultPerm: models.OrgLinkVisibilityPublic,
+ Billing: models.BillingSettings{
+ Status: models.BillingStatusFree,
+ },
+ },
}
if err = org.Store(ctx); err != nil {
@@ 1277,6 1293,9 @@ func (r *mutationResolver) Register(ctx context.Context, input *model.RegisterIn
}
// CompleteRegister is the resolver for the completeRegister field.
+// This is used when adding a member to an existing organization but the email account
+// is not registered. When completing their acceptance they will register an account
+// during the process. This method is for that process specifically.
func (r *mutationResolver) CompleteRegister(ctx context.Context, input *model.CompleteRegisterInput) (*models.User, error) {
lang := links.GetLangFromRequest(server.EchoForContext(ctx).Request(), nil)
lt := localizer.GetLocalizer(lang)
@@ 1352,7 1371,7 @@ func (r *mutationResolver) CompleteRegister(ctx context.Context, input *model.Co
Filter: sq.And{
sq.Eq{"o.id": targetOrgID},
sq.Eq{"o.is_active": true},
- sq.NotEq{"(o.settings->'billing'->'status')::integer": models.BillingStatusFree},
+ sq.NotEq{"(o.settings->'billing'->>'status')": models.BillingStatusFree},
},
Limit: 1,
}
@@ 1417,6 1436,12 @@ func (r *mutationResolver) CompleteRegister(ctx context.Context, input *model.Co
Name: user.Name,
Slug: slug,
IsActive: true,
+ Settings: models.OrganizationSettings{
+ DefaultPerm: models.OrgLinkVisibilityPublic,
+ Billing: models.BillingSettings{
+ Status: models.BillingStatusFree,
+ },
+ },
}
if err = userOrg.Store(ctx); err != nil {
@@ 1679,7 1704,7 @@ func (r *mutationResolver) UpdateOrganization(ctx context.Context, input *model.
sq.NotEq{"o.id": org.ID},
sq.Eq{"o.owner_id": user.ID},
sq.Eq{"o.is_active": true},
- sq.Eq{"(o.settings->'billing'->'status')::integer": models.BillingStatusFree},
+ sq.Eq{"(o.settings->'billing'->>'status')": models.BillingStatusFree},
},
}
@@ 1783,7 1808,7 @@ func (r *mutationResolver) AddDomain(ctx context.Context, input model.DomainInpu
}
srv := server.ForContext(ctx)
- if links.BillingEnabled(srv.Config) && org.IsRestricted([]int{models.BillingStatusFree}) {
+ if links.BillingEnabled(srv.Config) && org.IsRestricted([]string{models.BillingStatusFree}) {
validator.Error(lt.Translate("This function is only allowed for paid users.")).
WithCode(valid.ErrRestrictedCode)
return nil, nil
@@ 2431,7 2456,7 @@ func (r *mutationResolver) AddListing(ctx context.Context, input *model.AddListi
srv := server.ForContext(ctx)
// NOTE should we include BillinStatusOpenSource?
- if links.BillingEnabled(srv.Config) && org.IsRestricted([]int{models.BillingStatusFree}) {
+ if links.BillingEnabled(srv.Config) && org.IsRestricted([]string{models.BillingStatusFree}) {
opts := &database.FilterOptions{
Filter: sq.Eq{"l.org_id": org.ID},
Limit: 1,
@@ 3343,7 3368,7 @@ func (r *mutationResolver) AddQRCode(ctx context.Context, input model.AddQRCodeI
if input.Image != nil {
if links.BillingEnabled(srv.Config) && org.IsRestricted(
- []int{models.BillingStatusFree, models.BillingStatusOpenSource}) {
+ []string{models.BillingStatusFree, models.BillingStatusOpenSource}) {
validator.Error(lt.Translate("This function is only allowed for paid users.")).
WithField("image").
WithCode(valid.ErrValidationCode)
@@ 3390,7 3415,7 @@ func (r *mutationResolver) AddQRCode(ctx context.Context, input model.AddQRCodeI
options = append(options, standard.WithLogoImage(qrLogo))
}
} else if links.BillingEnabled(srv.Config) && org.IsRestricted(
- []int{models.BillingStatusFree, models.BillingStatusOpenSource}) {
+ []string{models.BillingStatusFree, models.BillingStatusOpenSource}) {
// If not custom image is provided and the org is restricted
// we want to add the app logo
@@ 3636,7 3661,7 @@ func (r *mutationResolver) Unfollow(ctx context.Context, orgSlug string) (*model
}
// UpdateAdminOrgType is the resolver for the updateAdminOrgType field.
-func (r *mutationResolver) UpdateAdminOrgType(ctx context.Context, orgSlug string, orgType int) (*models.Organization, error) {
+func (r *mutationResolver) UpdateAdminOrgType(ctx context.Context, orgSlug string, orgType model.OrgBillingStatus) (*models.Organization, error) {
tokenUser := oauth2.ForContext(ctx)
if tokenUser == nil {
return nil, valid.ErrAuthorization
@@ 3650,11 3675,12 @@ func (r *mutationResolver) UpdateAdminOrgType(ctx context.Context, orgSlug strin
WithCode(valid.ErrNotFoundCode)
return nil, nil
}
- if orgType != models.BillingStatusFree &&
- orgType != models.BillingStatusPersonal &&
- orgType != models.BillingStatusBusiness &&
- orgType != models.BillingStatusOpenSource &&
- orgType != models.BillingStatusSponsored {
+ _orgType := string(orgType)
+ if _orgType != models.BillingStatusFree &&
+ _orgType != models.BillingStatusPersonal &&
+ _orgType != models.BillingStatusBusiness &&
+ _orgType != models.BillingStatusOpenSource &&
+ _orgType != models.BillingStatusSponsored {
validator.Error(lt.Translate("Invalid orgType")).
WithField("orgType").
WithCode(valid.ErrValidationCode)
@@ 3679,7 3705,7 @@ func (r *mutationResolver) UpdateAdminOrgType(ctx context.Context, orgSlug strin
return nil, nil
}
org := orgs[0]
- org.Settings.Billing.Status = orgType
+ org.Settings.Billing.Status = _orgType
err = org.Store(ctx)
if err != nil {
return nil, err
@@ 5731,7 5757,7 @@ func (r *queryResolver) Analytics(ctx context.Context, input model.AnalyticsInpu
}
var isRestricted bool
- if org.IsRestricted([]int{models.BillingStatusFree, models.BillingStatusOpenSource}) {
+ if org.IsRestricted([]string{models.BillingStatusFree, models.BillingStatusOpenSource}) {
isRestricted = true
}
@@ 5965,7 5991,7 @@ func (r *queryResolver) GetFeed(ctx context.Context, input *model.GetFeedInput)
sq.And{
sq.Eq{"ou.user_id": user.ID},
sq.Eq{"o.is_active": true},
- sq.Eq{"(o.settings->'billing'->'status')::integer": models.BillingStatusBusiness},
+ sq.Eq{"(o.settings->'billing'->>'status')": models.BillingStatusBusiness},
},
},
OrderBy: "ol.id DESC",
@@ 6694,6 6720,9 @@ func (r *userResolver) ID(ctx context.Context, obj *models.User) (int, error) {
return int(obj.ID), nil
}
+// BillingSettings returns BillingSettingsResolver implementation.
+func (r *Resolver) BillingSettings() BillingSettingsResolver { return &billingSettingsResolver{r} }
+
// Domain returns DomainResolver implementation.
func (r *Resolver) Domain() DomainResolver { return &domainResolver{r} }
@@ 6717,6 6746,7 @@ func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }
// User returns UserResolver implementation.
func (r *Resolver) User() UserResolver { return &userResolver{r} }
+type billingSettingsResolver struct{ *Resolver }
type domainResolver struct{ *Resolver }
type mutationResolver struct{ *Resolver }
type orgLinkResolver struct{ *Resolver }
M billing/processors.go => billing/processors.go +1 -1
@@ 274,7 274,7 @@ func ProcessSubscriptionDeletedTask(ctx context.Context, srv *server.Server, dat
sq.Eq{"o.is_active": true},
sq.Eq{"o.owner_id": org.OwnerID},
sq.NotEq{"o.id": org.ID},
- sq.Eq{"(o.settings->'billing'->'status')::integer": models.BillingStatusFree},
+ sq.Eq{"(o.settings->'billing'->>'status')": models.BillingStatusFree},
},
}
freeOrgs, err := models.GetOrganizations(ctx, opts)
M core/import.go => core/import.go +1 -1
@@ 265,7 265,7 @@ func processOrgLinks(obj importObj, baseURLMap map[string]int,
var vis string
if obj.IsPublic() {
vis = models.OrgLinkVisibilityPublic
- } else if billEnabled && org.IsRestricted([]int{models.BillingStatusFree}) {
+ } else if billEnabled && org.IsRestricted([]string{models.BillingStatusFree}) {
vis = models.OrgLinkVisibilityRestricted
} else {
vis = models.OrgLinkVisibilityPrivate
M core/routes.go => core/routes.go +6 -6
@@ 530,7 530,7 @@ func (s *Service) DomainCreate(c echo.Context) error {
}
// NOTE should we include BillinStatusOpenSource?
- if links.BillingEnabled(gctx.Server.Config) && curOrg.IsRestricted([]int{models.BillingStatusFree}) {
+ if links.BillingEnabled(gctx.Server.Config) && curOrg.IsRestricted([]string{models.BillingStatusFree}) {
return links.RenderRestrictedTemplate(c)
}
return s.Render(c, http.StatusOK, "domain_create.html", gmap)
@@ 812,7 812,7 @@ func (s *Service) OrgCreate(c echo.Context) error {
Filter: sq.And{
sq.Eq{"o.is_active": true},
sq.Eq{"o.owner_id": gctx.User.GetID()},
- sq.Eq{"(o.settings->'billing'->'status')::integer": models.BillingStatusFree},
+ sq.Eq{"(o.settings->'billing'->>'status')": models.BillingStatusFree},
},
}
@@ 1092,7 1092,7 @@ func (s *Service) OrgMembersAdd(c echo.Context) error {
}
var isRestricted bool
- if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]int{models.BillingStatusFree,
+ if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]string{models.BillingStatusFree,
models.BillingStatusOpenSource, models.BillingStatusPersonal}) {
isRestricted = true
}
@@ 1290,7 1290,7 @@ func (s *Service) OrgMembersList(c echo.Context) error {
org := orgs[0]
var isRestricted bool
- if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]int{models.BillingStatusFree,
+ if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]string{models.BillingStatusFree,
models.BillingStatusOpenSource, models.BillingStatusPersonal}) {
isRestricted = true
}
@@ 2604,7 2604,7 @@ func (s *Service) QRRedirect(c echo.Context) error {
}
org := orgs[0]
- if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]int{
+ if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]string{
models.BillingStatusFree, models.BillingStatusOpenSource}) {
lt := localizer.GetSessionLocalizer(c)
pd := localizer.NewPageData(lt.Translate("QR Codes powered by Link Taco!"))
@@ 2897,7 2897,7 @@ func (s *Service) Integrations(c echo.Context) error {
pd.Data["connected"] = lt.Translate("Connected")
pd.Data["back"] = lt.Translate("Back")
var isRestricted bool
- if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]int{models.BillingStatusFree}) {
+ if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]string{models.BillingStatusFree}) {
isRestricted = true
}
M core/samples/org_list.json => core/samples/org_list.json +3 -3
@@ 8,7 8,7 @@
"isActive": true,
"settings": {
"billing": {
- "status": 1
+ "status": "PERSONAL"
}
}
},
@@ 19,7 19,7 @@
"isActive": true,
"settings": {
"billing": {
- "status": 2
+ "status": "BUSINESS"
}
}
},
@@ 30,7 30,7 @@
"isActive": true,
"settings": {
"billing": {
- "status": 0
+ "status": "FREE"
}
}
}
M helpers.go => helpers.go +1 -1
@@ 578,7 578,7 @@ func GetDisabledElementsByOrg(ctx context.Context, org *models.Organization) (*C
sq.Eq{"o.is_active": true},
sq.Eq{"o.owner_id": org.OwnerID},
sq.NotEq{"o.id": org.ID},
- sq.Eq{"(o.settings->'billing'->'status')::integer": models.BillingStatusFree},
+ sq.Eq{"(o.settings->'billing'->>'status')": models.BillingStatusFree},
},
}
freeOrgs, err := models.GetOrganizations(ctx, opts)
M list/routes.go => list/routes.go +1 -1
@@ 933,7 933,7 @@ func (s *Service) ListingCreate(c echo.Context) error {
c.Echo().Reverse(s.RouteName("listing_list"), org.Slug))
}
// NOTE should we include BillinStatusOpenSource?
- if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]int{models.BillingStatusFree}) {
+ if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]string{models.BillingStatusFree}) {
opts := &database.FilterOptions{
Filter: sq.Eq{"l.org_id": org.ID},
Limit: 1,
M mattermost/routes.go => mattermost/routes.go +5 -5
@@ 146,7 146,7 @@ func (s *Service) Connect(c echo.Context) error {
"org": org,
"teamID": teamID,
}
- if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]int{models.BillingStatusFree}) {
+ if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]string{models.BillingStatusFree}) {
gmap["isRestricted"] = true
return s.Render(c, http.StatusOK, "connect_mattermost.html", gmap)
}
@@ 194,7 194,7 @@ func (s *Service) ConnectUser(c echo.Context) error {
org := orgs[0]
gctx := c.(*server.Context)
- if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]int{models.BillingStatusFree}) {
+ if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]string{models.BillingStatusFree}) {
lt := localizer.GetSessionLocalizer(c)
messages.Error(c, lt.Translate("Sorry, free accounts do not support Mattermost Integration. Please upgrade to continue"))
return c.Redirect(http.StatusMovedPermanently, c.Echo().Reverse("billing:create_subscription", org.Slug))
@@ 291,7 291,7 @@ func (s *Service) SearchCommand(c echo.Context) error {
return fmt.Errorf("No org found")
}
org := orgs[0]
- if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]int{models.BillingStatusFree}) {
+ if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]string{models.BillingStatusFree}) {
msg := lt.Translate("Sorry, free accounts do not support Mattermost Integration. Please upgrade to continue")
return c.JSON(http.StatusOK, apps.NewTextResponse(msg))
}
@@ 402,7 402,7 @@ func (s *Service) ShortCommand(c echo.Context) error {
gctx := c.(*server.Context)
org := orgs[0]
- if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]int{models.BillingStatusFree}) {
+ if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]string{models.BillingStatusFree}) {
msg := lt.Translate("Sorry, free accounts do not support Mattermost Integration. Please upgrade to continue")
return c.JSON(http.StatusOK, apps.NewTextResponse(msg))
}
@@ 543,7 543,7 @@ func (s *Service) AddCommand(c echo.Context) error {
gctx := c.(*server.Context)
org := orgs[0]
- if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]int{models.BillingStatusFree}) {
+ if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]string{models.BillingStatusFree}) {
lt := localizer.GetSessionLocalizer(c)
msg := lt.Translate("Sorry, free accounts do not support Mattermost Integration. Please upgrade to continue")
return c.JSON(http.StatusOK, apps.NewTextResponse(msg))
M migrations/0001_initial.down.sql => migrations/0001_initial.down.sql +1 -0
@@ 40,3 40,4 @@ DROP TYPE IF EXISTS org_link_visibility;
DROP TYPE IF EXISTS org_link_type;
DROP TYPE IF EXISTS org_user_perm;
DROP TYPE IF EXISTS org_type;
+DROP TYPE IF EXISTS subscription_type;
M migrations/0001_initial.up.sql => migrations/0001_initial.up.sql +10 -1
@@ 89,6 89,15 @@ EXCEPTION
WHEN duplicate_object THEN null;
END $$;
+DO $$ BEGIN
+ CREATE TYPE subscription_type AS ENUM (
+ 'PERSONAL',
+ 'BUSINESS'
+ );
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+
CREATE TABLE users (
id SERIAL PRIMARY KEY,
@@ 519,7 528,7 @@ CREATE TABLE subscription_plans (
plan_id VARCHAR(150) NOT NULL,
stripe_price_id VARCHAR(150) NOT NULL,
price INT NOT NULL CHECK (price > 0),
- "type" INT NOT NULL DEFAULT 0,
+ "type" subscription_type NOT NULL DEFAULT 'PERSONAL',
is_active BOOLEAN DEFAULT true,
created_on TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_on TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
M migrations/test_migration.up.sql => migrations/test_migration.up.sql +5 -5
@@ 2,10 2,10 @@ INSERT INTO users (full_name, password, email, is_verified) VALUES ('user', 'qwe
INSERT INTO users (full_name, password, email, is_verified) VALUES ('test_api_user', 'qwerty', 'test@api.com', true);
INSERT INTO users (full_name, password, email, is_verified, is_superuser) VALUES ('superuser', 'qwerty', 'superuser@api.com', true, true);
-INSERT INTO organizations (owner_id, name, slug) VALUES (1, 'personal org', 'personal-org');
-INSERT INTO organizations (owner_id, name, slug, org_type) VALUES (1, 'business org', 'business_org', 'NORMAL');
+INSERT INTO organizations (owner_id, name, slug, settings) VALUES (1, 'personal org', 'personal-org', '{"billing": {"status": "FREE"}, "default_perm": "PUBLIC"}');
+INSERT INTO organizations (owner_id, name, slug, org_type, settings) VALUES (1, 'business org', 'business_org', 'NORMAL', '{"billing": {"status": "FREE"}, "default_perm": "PUBLIC"}');
-INSERT INTO organizations (owner_id, name, slug) VALUES (2, 'api test org', 'api-test-org');
+INSERT INTO organizations (owner_id, name, slug, settings) VALUES (2, 'api test org', 'api-test-org', '{"billing": {"status": "FREE"}, "default_perm": "PUBLIC"}');
INSERT INTO base_urls (url, hash) VALUES ('http://base.com', 'abcdefg');
@@ 25,5 25,5 @@ INSERT INTO listing_links (id, listing_id, url, description, link_order, user_id
INSERT INTO qr_codes (id, hash_id, url, org_id, code_type, image_path, user_id, title) VALUES (100, 'a10x', 'http://short.com/abc', 1, 1, 'path/qr.png', 1, 'title');
INSERT INTO qr_codes (id, hash_id, url, org_id, code_type, image_path, user_id, title) VALUES (101, 'a99z', 'http://list.com/abc', 1, 0, 'path/qr.png', 1, 'title');
-INSERT INTO subscription_plans (id, name, plan_id, stripe_price_id, price, type) VALUES (1, 'Personal', 'plan_personal', 'price_personal', 1000, 1);
-INSERT INTO subscription_plans (id, name, plan_id, stripe_price_id, price, type) VALUES (2, 'Business', 'plan_business', 'price_business', 2000, 2);
+INSERT INTO subscription_plans (id, name, plan_id, stripe_price_id, price, type) VALUES (1, 'Personal', 'plan_personal', 'price_personal', 1000, 'PERSONAL');
+INSERT INTO subscription_plans (id, name, plan_id, stripe_price_id, price, type) VALUES (2, 'Business', 'plan_business', 'price_business', 2000, 'BUSINESS');
M models/models.go => models/models.go +1 -1
@@ 375,7 375,7 @@ type SubscriptionPlan struct {
PlanID string `db:"plan_id"`
StripePriceID string `db:"stripe_price_id"`
Price int `db:"price"`
- Type int `db:"type"`
+ Type string `db:"type"`
IsActive bool `db:"is_active"`
CreatedOn time.Time `db:"created_on"`
UpdatedOn time.Time `db:"updated_on"`
M models/organization.go => models/organization.go +8 -8
@@ 28,15 28,15 @@ const (
// Billing status, used for org billing setting/metadata
// and plan type
const (
- BillingStatusFree int = iota
- BillingStatusPersonal
- BillingStatusBusiness
- BillingStatusOpenSource
- BillingStatusSponsored
+ BillingStatusFree string = "FREE"
+ BillingStatusPersonal string = "PERSONAL"
+ BillingStatusBusiness string = "BUSINESS"
+ BillingStatusOpenSource string = "OPEN_SOURCE"
+ BillingStatusSponsored string = "SPONSORED"
)
type BillingSettings struct {
- Status int `json:"status"` // BillingStatus<X>
+ Status string `json:"status"` // BillingStatus<X>
}
// OrganizationSettings ...
@@ 253,7 253,7 @@ func (o *Organization) CanAdminWrite(ctx context.Context, user *User) bool {
return o.permCheck(ctx, user, OrgUserPermissionAdminWrite)
}
-func (o *Organization) IsRestricted(restrictedStatus []int) bool {
+func (o *Organization) IsRestricted(restrictedStatus []string) bool {
status := o.Settings.Billing.Status
for _, i := range restrictedStatus {
if i == status {
@@ 269,7 269,7 @@ func (o *Organization) IsFreeAccount() bool {
func (o *Organization) DisplayBillingStatus(c echo.Context) string {
lt := localizer.GetSessionLocalizer(c)
- status := map[int]string{
+ status := map[string]string{
BillingStatusFree: lt.Translate("Free"),
BillingStatusPersonal: lt.Translate("Personal"),
BillingStatusBusiness: lt.Translate("Business"),
M models/schema.sql => models/schema.sql +6 -1
@@ 56,6 56,11 @@ CREATE TYPE org_type AS ENUM (
'NORMAL'
);
+CREATE TYPE subscription_type AS ENUM (
+ 'PERSONAL',
+ 'BUSINESS'
+);
+
CREATE TABLE users (
id SERIAL PRIMARY KEY,
@@ 486,7 491,7 @@ CREATE TABLE subscription_plans (
plan_id VARCHAR(150) NOT NULL,
stripe_price_id VARCHAR(150) NOT NULL,
price INT NOT NULL CHECK (price > 0),
- "type" INT NOT NULL DEFAULT 0,
+ "type" subscription_type NOT NULL DEFAULT 'PERSONAL',
is_active BOOLEAN DEFAULT true,
created_on TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_on TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
M models/subscription_plan.go => models/subscription_plan.go +2 -2
@@ 12,8 12,8 @@ import (
)
const (
- SubscriptionPlanTypePersonal int = 1
- SubscriptionPlanTypeBusiness = 2
+ SubscriptionPlanTypePersonal string = "PERSONAL"
+ SubscriptionPlanTypeBusiness string = "BUSINESS"
)
func (s *SubscriptionPlan) ToLocalTZ(tz string) error {
M short/routes.go => short/routes.go +1 -1
@@ 883,7 883,7 @@ func (r *RedirectService) LinkShort(c echo.Context) error {
org := orgs[0]
if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted(
- []int{models.BillingStatusFree, models.BillingStatusOpenSource}) {
+ []string{models.BillingStatusFree, models.BillingStatusOpenSource}) {
lt := localizer.GetSessionLocalizer(c)
pd := localizer.NewPageData(lt.Translate("URL Shortening powered by Link Taco!"))
pd.Data["redirected"] = lt.Translate("You will be redirected in 10 seconds.")
M slack/routes.go => slack/routes.go +3 -3
@@ 117,7 117,7 @@ func (s *Service) ConnectUser(c echo.Context) error {
org := orgs[0]
gctx := c.(*server.Context)
- if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]int{models.BillingStatusFree}) {
+ if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]string{models.BillingStatusFree}) {
lt := localizer.GetSessionLocalizer(c)
messages.Error(c, lt.Translate("Sorry, free accounts do not support Slack Integration. Please upgrade to continue"))
return c.Redirect(http.StatusMovedPermanently, c.Echo().Reverse("billing:create_subscription", org.Slug))
@@ 206,7 206,7 @@ func (s *Service) SlashCommand(c echo.Context) error {
return fmt.Errorf("No org found")
}
org := orgs[0]
- if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]int{models.BillingStatusFree}) {
+ if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]string{models.BillingStatusFree}) {
lt := localizer.GetSessionLocalizer(c)
msg := lt.Translate("Sorry, free accounts do not support Slack Integration. Please upgrade to continue")
return c.JSON(http.StatusOK, sendErrorMsg(msg))
@@ 307,7 307,7 @@ func (s *Service) ConnectSlack(c echo.Context) error {
org := orgs[0]
if links.BillingEnabled(gctx.Server.Config) &&
- org.IsRestricted([]int{models.BillingStatusFree}) {
+ org.IsRestricted([]string{models.BillingStatusFree}) {
lt := localizer.GetSessionLocalizer(c)
messages.Error(c, lt.Translate("Sorry, free accounts do not support Slack Integration. Please upgrade to continue"))
return c.Redirect(http.StatusMovedPermanently, c.Echo().Reverse("billing:create_subscription", org.Slug))