From 02e7a59b57be1c16dd3dbc126e26abb0747dc5c9 Mon Sep 17 00:00:00 2001 From: Peter Sanchez Date: Mon, 25 Nov 2024 16:29:48 -0600 Subject: [PATCH] Moving org links to enums --- api/api_test.go | 22 ++-- api/graph/generated.go | 168 ++++++++++++++++++++++++++----- api/graph/model/models_gen.go | 91 ++++++++++++----- api/graph/schema.graphqls | 18 ++-- api/graph/schema.resolvers.go | 67 +++++++----- core/import.go | 2 +- core/inputs.go | 12 +-- core/routes.go | 28 +++--- core/routes_test.go | 8 +- core/samples/create_link.json | 2 +- core/samples/detail_link.json | 2 +- core/samples/update_link.json | 2 +- migrations/0001_initial.down.sql | 1 + migrations/0001_initial.up.sql | 12 ++- migrations/test_migration.up.sql | 4 +- models/models.go | 2 +- models/org_link.go | 24 ++++- models/organization.go | 2 +- models/schema.sql | 7 ++ 19 files changed, 348 insertions(+), 126 deletions(-) diff --git a/api/api_test.go b/api/api_test.go index 7379722..e0555fb 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -142,7 +142,7 @@ func TestDirective(t *testing.T) { var resultAddLink GraphQLResponseAddLink q = `mutation AddLink($title: String!, $url: String!, $description: String, - $visibility: Int!, $unread: Boolean!, $starred: Boolean!, + $visibility: LinkVisibility!, $unread: Boolean!, $starred: Boolean!, $archive: Boolean! $slug: String!, $tags: String) { addLink(input: { title: $title, @@ -391,7 +391,7 @@ func TestAPI(t *testing.T) { var result GraphQLResponse q := `mutation AddLink($title: String!, $url: String!, $description: String, - $visibility: Int!, $unread: Boolean!, $starred: Boolean!, + $visibility: LinkVisibility!, $unread: Boolean!, $starred: Boolean!, $archive: Boolean! $slug: String!, $tags: String) { addLink(input: { title: $title, @@ -440,7 +440,7 @@ func TestAPI(t *testing.T) { op = gqlclient.NewOperation(q) op.Var("title", "testing link") op.Var("url", "https://netlandish.com") - op.Var("visibility", 100) + op.Var("visibility", "NOT_VALID") op.Var("tags", "one, two") op.Var("slug", "personal-org") op.Var("unread", true) @@ -448,7 +448,7 @@ func TestAPI(t *testing.T) { op.Var("archive", false) err = links.Execute(ctx, op, &result) c.Error(err) - c.Equal("gqlclient: server failure: Invalid Visibility value.", err.Error()) + c.Equal("gqlclient: HTTP server error (422 Unprocessable Entity): gqlclient: server failure: NOT_VALID is not a valid LinkVisibility", err.Error()) }) t.Run("create link", func(t *testing.T) { @@ -458,7 +458,7 @@ func TestAPI(t *testing.T) { var result GraphQLResponse q := `mutation AddLink($title: String!, $url: String!, $description: String, - $visibility: Int!, $unread: Boolean!, $starred: Boolean!, + $visibility: LinkVisibility!, $unread: Boolean!, $starred: Boolean!, $archive: Boolean! $slug: String!, $tags: String) { addLink(input: { title: $title, @@ -547,7 +547,7 @@ func TestAPI(t *testing.T) { } var result GraphQLResponse q := `mutation UpdateLink($hash: String!, $title: String!, - $url: String!, $visibility: Int!, + $url: String!, $visibility: LinkVisibility!, $tags: String) { updateLink(input: { title: $title, @@ -581,11 +581,11 @@ func TestAPI(t *testing.T) { op.Var("title", "New Title") op.Var("hash", ol.Hash) op.Var("url", "https://netlandish.com") - op.Var("visibility", 100) + op.Var("visibility", "NOT_VALID") op.Var("tags", "one, two, three") err = links.Execute(ctx, op, &result) c.Error(err) - c.Equal("gqlclient: server failure: Invalid Visibility value.", err.Error()) + c.Equal("gqlclient: HTTP server error (422 Unprocessable Entity): gqlclient: server failure: NOT_VALID is not a valid LinkVisibility", err.Error()) }) t.Run("org link update", func(t *testing.T) { @@ -602,7 +602,7 @@ func TestAPI(t *testing.T) { } var result GraphQLResponse q := `mutation UpdateLink($hash: String!, $title: String!, - $url: String!, $visibility: Int!, + $url: String!, $visibility: LinkVisibility!, $tags: String) { updateLink(input: { title: $title, @@ -1852,7 +1852,7 @@ func TestAPI(t *testing.T) { var result GraphQLResponse q := `mutation AddLink($title: String!, $url: String!, $description: String, - $visibility: Int!, $unread: Boolean!, $starred: Boolean!, + $visibility: LinkVisibility!, $unread: Boolean!, $starred: Boolean!, $archive: Boolean! $slug: String!, $tags: String) { addLink(input: { title: $title, @@ -1892,7 +1892,7 @@ func TestAPI(t *testing.T) { } var result GraphQLResponse q := `mutation UpdateLink($hash: String!, $title: String!, - $url: String!, $visibility: Int!, + $url: String!, $visibility: LinkVisibility!, $tags: String) { updateLink(input: { title: $title, diff --git a/api/graph/generated.go b/api/graph/generated.go index 7980e43..f18e6f0 100644 --- a/api/graph/generated.go +++ b/api/graph/generated.go @@ -45,6 +45,7 @@ type ResolverRoot interface { Mutation() MutationResolver OrgLink() OrgLinkResolver Organization() OrganizationResolver + OrganizationSettings() OrganizationSettingsResolver Query() QueryResolver User() UserResolver } @@ -473,10 +474,15 @@ type MutationResolver interface { } type OrgLinkResolver interface { BaseURLID(ctx context.Context, obj *models.OrgLink) (*model.NullInt, error) + + Visibility(ctx context.Context, obj *models.OrgLink) (model.LinkVisibility, error) } type OrganizationResolver interface { Image(ctx context.Context, obj *models.Organization) (*graphql.Upload, error) } +type OrganizationSettingsResolver interface { + DefaultPerm(ctx context.Context, obj *models.OrganizationSettings) (model.LinkVisibility, error) +} type QueryResolver interface { Version(ctx context.Context) (*model.Version, error) Me(ctx context.Context) (*models.User, error) @@ -12681,8 +12687,36 @@ func (ec *executionContext) _OrgLink_visibility(ctx context.Context, field graph } }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.Visibility, nil + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.OrgLink().Visibility(rctx, obj) + } + directive1 := func(ctx context.Context) (interface{}, error) { + scope, err := ec.unmarshalNAccessScope2linksᚋapiᚋgraphᚋmodelᚐAccessScope(ctx, "LINKS") + if err != nil { + return nil, err + } + kind, err := ec.unmarshalNAccessKind2linksᚋapiᚋgraphᚋmodelᚐAccessKind(ctx, "RO") + if err != nil { + return nil, err + } + if ec.directives.Access == nil { + return nil, errors.New("directive access is not implemented") + } + return ec.directives.Access(ctx, obj, directive0, scope, kind) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(model.LinkVisibility); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be links/api/graph/model.LinkVisibility`, tmp) }) if err != nil { ec.Error(ctx, err) @@ -12694,19 +12728,19 @@ func (ec *executionContext) _OrgLink_visibility(ctx context.Context, field graph } return graphql.Null } - res := resTmp.(int) + res := resTmp.(model.LinkVisibility) fc.Result = res - return ec.marshalNInt2int(ctx, field.Selections, res) + return ec.marshalNLinkVisibility2linksᚋapiᚋgraphᚋmodelᚐLinkVisibility(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_OrgLink_visibility(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "OrgLink", 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 LinkVisibility does not have child fields") }, } return fc, nil @@ -14187,7 +14221,7 @@ func (ec *executionContext) _OrganizationSettings_defaultPerm(ctx context.Contex }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.DefaultPerm, nil + return ec.resolvers.OrganizationSettings().DefaultPerm(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -14199,19 +14233,19 @@ func (ec *executionContext) _OrganizationSettings_defaultPerm(ctx context.Contex } return graphql.Null } - res := resTmp.(int) + res := resTmp.(model.LinkVisibility) fc.Result = res - return ec.marshalNInt2int(ctx, field.Selections, res) + return ec.marshalNLinkVisibility2linksᚋapiᚋgraphᚋmodelᚐLinkVisibility(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_OrganizationSettings_defaultPerm(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "OrganizationSettings", 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 LinkVisibility does not have child fields") }, } return fc, nil @@ -22617,7 +22651,7 @@ func (ec *executionContext) unmarshalInputLinkInput(ctx context.Context, obj int it.Description = data case "visibility": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("visibility")) - data, err := ec.unmarshalOInt2ᚖint(ctx, v) + data, err := ec.unmarshalOLinkVisibility2ᚖlinksᚋapiᚋgraphᚋmodelᚐLinkVisibility(ctx, v) if err != nil { return it, err } @@ -22803,7 +22837,7 @@ func (ec *executionContext) unmarshalInputNoteInput(ctx context.Context, obj int it.Description = data case "visibility": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("visibility")) - data, err := ec.unmarshalNInt2int(ctx, v) + data, err := ec.unmarshalNLinkVisibility2linksᚋapiᚋgraphᚋmodelᚐLinkVisibility(ctx, v) if err != nil { return it, err } @@ -23154,7 +23188,7 @@ func (ec *executionContext) unmarshalInputUpdateLinkInput(ctx context.Context, o it.URL = data case "visibility": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("visibility")) - data, err := ec.unmarshalOInt2ᚖint(ctx, v) + data, err := ec.unmarshalOLinkVisibility2ᚖlinksᚋapiᚋgraphᚋmodelᚐLinkVisibility(ctx, v) if err != nil { return it, err } @@ -23479,7 +23513,7 @@ func (ec *executionContext) unmarshalInputUpdateOrganizationInput(ctx context.Co it.DeleteImg = data case "defaultPerm": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("defaultPerm")) - data, err := ec.unmarshalOInt2ᚖint(ctx, v) + data, err := ec.unmarshalOLinkVisibility2ᚖlinksᚋapiᚋgraphᚋmodelᚐLinkVisibility(ctx, v) if err != nil { return it, err } @@ -25228,10 +25262,41 @@ func (ec *executionContext) _OrgLink(ctx context.Context, sel ast.SelectionSet, case "userId": out.Values[i] = ec._OrgLink_userId(ctx, field, obj) case "visibility": - out.Values[i] = ec._OrgLink_visibility(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._OrgLink_visibility(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 "unread": out.Values[i] = ec._OrgLink_unread(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -25521,10 +25586,41 @@ func (ec *executionContext) _OrganizationSettings(ctx context.Context, sel ast.S case "__typename": out.Values[i] = graphql.MarshalString("OrganizationSettings") case "defaultPerm": - out.Values[i] = ec._OrganizationSettings_defaultPerm(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._OrganizationSettings_defaultPerm(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 "billing": out.Values[i] = ec._OrganizationSettings_billing(ctx, field, obj) default: @@ -27611,6 +27707,16 @@ func (ec *executionContext) marshalNLinkShortCursor2ᚖlinksᚋapiᚋgraphᚋmod return ec._LinkShortCursor(ctx, sel, v) } +func (ec *executionContext) unmarshalNLinkVisibility2linksᚋapiᚋgraphᚋmodelᚐLinkVisibility(ctx context.Context, v interface{}) (model.LinkVisibility, error) { + var res model.LinkVisibility + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNLinkVisibility2linksᚋapiᚋgraphᚋmodelᚐLinkVisibility(ctx context.Context, sel ast.SelectionSet, v model.LinkVisibility) graphql.Marshaler { + return v +} + func (ec *executionContext) marshalNListing2linksᚋmodelsᚐListing(ctx context.Context, sel ast.SelectionSet, v models.Listing) graphql.Marshaler { return ec._Listing(ctx, sel, &v) } @@ -28766,6 +28872,22 @@ func (ec *executionContext) unmarshalOLinkShortInput2ᚖlinksᚋapiᚋgraphᚋmo return &res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) unmarshalOLinkVisibility2ᚖlinksᚋapiᚋgraphᚋmodelᚐLinkVisibility(ctx context.Context, v interface{}) (*model.LinkVisibility, error) { + if v == nil { + return nil, nil + } + var res = new(model.LinkVisibility) + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOLinkVisibility2ᚖlinksᚋapiᚋgraphᚋmodelᚐLinkVisibility(ctx context.Context, sel ast.SelectionSet, v *model.LinkVisibility) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return v +} + func (ec *executionContext) marshalOListing2ᚖlinksᚋmodelsᚐListing(ctx context.Context, sel ast.SelectionSet, v *models.Listing) graphql.Marshaler { if v == nil { return graphql.Null diff --git a/api/graph/model/models_gen.go b/api/graph/model/models_gen.go index 4912e36..d69714a 100644 --- a/api/graph/model/models_gen.go +++ b/api/graph/model/models_gen.go @@ -223,15 +223,15 @@ type GetUserInput struct { } type LinkInput struct { - URL string `json:"url"` - Description *string `json:"description,omitempty"` - Visibility *int `json:"visibility,omitempty"` - Unread bool `json:"unread"` - Starred bool `json:"starred"` - Archive bool `json:"archive"` - OrgSlug string `json:"orgSlug"` - Title string `json:"title"` - Tags *string `json:"tags,omitempty"` + URL string `json:"url"` + Description *string `json:"description,omitempty"` + Visibility *LinkVisibility `json:"visibility,omitempty"` + Unread bool `json:"unread"` + Starred bool `json:"starred"` + Archive bool `json:"archive"` + OrgSlug string `json:"orgSlug"` + Title string `json:"title"` + Tags *string `json:"tags,omitempty"` } type LinkShortCursor struct { @@ -269,12 +269,12 @@ type Mutation struct { } type NoteInput struct { - Title string `json:"title"` - Description string `json:"description"` - Visibility int `json:"visibility"` - Tags *string `json:"tags,omitempty"` - Starred bool `json:"starred"` - OrgSlug string `json:"orgSlug"` + Title string `json:"title"` + Description string `json:"description"` + Visibility LinkVisibility `json:"visibility"` + Tags *string `json:"tags,omitempty"` + Starred bool `json:"starred"` + OrgSlug string `json:"orgSlug"` } type NullInt struct { @@ -384,14 +384,14 @@ type UpdateAdminDomainInput struct { } type UpdateLinkInput struct { - Hash string `json:"hash"` - Title *string `json:"title,omitempty"` - Description *string `json:"description,omitempty"` - URL *string `json:"url,omitempty"` - Visibility *int `json:"visibility,omitempty"` - Unread *bool `json:"unread,omitempty"` - Starred *bool `json:"starred,omitempty"` - Tags *string `json:"tags,omitempty"` + Hash string `json:"hash"` + Title *string `json:"title,omitempty"` + Description *string `json:"description,omitempty"` + URL *string `json:"url,omitempty"` + Visibility *LinkVisibility `json:"visibility,omitempty"` + Unread *bool `json:"unread,omitempty"` + Starred *bool `json:"starred,omitempty"` + Tags *string `json:"tags,omitempty"` } type UpdateLinkShortInput struct { @@ -435,7 +435,7 @@ type UpdateOrganizationInput struct { Image *graphql.Upload `json:"image,omitempty"` IsActive *bool `json:"isActive,omitempty"` DeleteImg *bool `json:"deleteImg,omitempty"` - DefaultPerm *int `json:"defaultPerm,omitempty"` + DefaultPerm *LinkVisibility `json:"defaultPerm,omitempty"` } type UpdateUserInput struct { @@ -682,3 +682,46 @@ func (e *DomainStatus) UnmarshalGQL(v interface{}) error { func (e DomainStatus) MarshalGQL(w io.Writer) { fmt.Fprint(w, strconv.Quote(e.String())) } + +type LinkVisibility string + +const ( + LinkVisibilityPublic LinkVisibility = "PUBLIC" + LinkVisibilityPrivate LinkVisibility = "PRIVATE" + LinkVisibilityRestricted LinkVisibility = "RESTRICTED" +) + +var AllLinkVisibility = []LinkVisibility{ + LinkVisibilityPublic, + LinkVisibilityPrivate, + LinkVisibilityRestricted, +} + +func (e LinkVisibility) IsValid() bool { + switch e { + case LinkVisibilityPublic, LinkVisibilityPrivate, LinkVisibilityRestricted: + return true + } + return false +} + +func (e LinkVisibility) String() string { + return string(e) +} + +func (e *LinkVisibility) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = LinkVisibility(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid LinkVisibility", str) + } + return nil +} + +func (e LinkVisibility) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} diff --git a/api/graph/schema.graphqls b/api/graph/schema.graphqls index d301fb0..d7b30c5 100644 --- a/api/graph/schema.graphqls +++ b/api/graph/schema.graphqls @@ -80,6 +80,12 @@ enum DomainStatus { ERROR } +enum LinkVisibility { + PUBLIC + PRIVATE + RESTRICTED +} + # Considering removing these Null* fields: # https://todo.code.netlandish.com/~netlandish/links/75 @@ -112,7 +118,7 @@ type BillingSettings { } type OrganizationSettings { - defaultPerm: Int! + defaultPerm: LinkVisibility! billing: BillingSettings } @@ -184,7 +190,7 @@ type OrgLink { baseUrlId: NullInt! @access(scope: LINKS, kind: RO) orgId: Int! @access(scope: LINKS, kind: RO) userId: Int @access(scope: LINKS, kind: RO) - visibility: Int! + visibility: LinkVisibility! @access(scope: LINKS, kind: RO) unread: Boolean! @access(scope: LINKS, kind: RO) starred: Boolean! @access(scope: LINKS, kind: RO) archiveUrl: String! @@ -373,7 +379,7 @@ type QRObject { input LinkInput { url: String! description: String - visibility: Int + visibility: LinkVisibility unread: Boolean! starred: Boolean! archive: Boolean! @@ -385,7 +391,7 @@ input LinkInput { input NoteInput { title: String! description: String! - visibility: Int! + visibility: LinkVisibility! tags: String starred: Boolean! orgSlug: String! @@ -540,7 +546,7 @@ input UpdateLinkInput{ title: String description: String url: String - visibility: Int + visibility: LinkVisibility unread: Boolean starred: Boolean tags: String @@ -621,7 +627,7 @@ input UpdateOrganizationInput { image: Upload isActive: Boolean deleteImg: Boolean - defaultPerm: Int + defaultPerm: LinkVisibility } input RegisterInput { diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index ccc4103..1e1c359 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -186,8 +186,7 @@ func (r *mutationResolver) AddLink(ctx context.Context, input *model.LinkInput) validator := valid.New(ctx) if input.Visibility != nil { - validator.Expect(*input.Visibility == models.OrgLinkVisibilityPublic || - *input.Visibility == models.OrgLinkVisibilityPrivate, + validator.Expect(models.ValidateLinkVisibility(string(*input.Visibility)), lt.Translate("Invalid Visibility value.")). WithField("visibility"). WithCode(valid.ErrValidationCode) @@ -253,11 +252,11 @@ func (r *mutationResolver) AddLink(ctx context.Context, input *model.LinkInput) return nil, nil } - var visibility int + var visibility string if input.Visibility == nil { visibility = org.Settings.DefaultPerm } else { - visibility = *input.Visibility + visibility = string(*input.Visibility) } srv := server.ForContext(ctx) @@ -358,8 +357,7 @@ func (r *mutationResolver) UpdateLink(ctx context.Context, input *model.UpdateLi } if input.Visibility != nil { - validator.Expect(*input.Visibility == models.OrgLinkVisibilityPublic || - *input.Visibility == models.OrgLinkVisibilityPrivate, + validator.Expect(models.ValidateLinkVisibility(string(*input.Visibility)), lt.Translate("Invalid Visibility value.")). WithField("visibility"). WithCode(valid.ErrValidationCode) @@ -441,14 +439,14 @@ func (r *mutationResolver) UpdateLink(ctx context.Context, input *model.UpdateLi if input.Visibility != nil { // We want to restrict private links only to paid users - if *input.Visibility == models.OrgLinkVisibilityPrivate && + if string(*input.Visibility) == models.OrgLinkVisibilityPrivate && links.BillingEnabled(srv.Config) && org.IsRestricted([]int{models.BillingStatusFree}) { validator.Error(lt.Translate("Free organizations are not allowed to create private links. Please upgrade")). WithField("visibility"). WithCode(valid.ErrValidationCode) return nil, nil - } else if *input.Visibility == models.OrgLinkVisibilityPublic && orgLink.Type == models.NoteType { + } else if string(*input.Visibility) == models.OrgLinkVisibilityPublic && orgLink.Type == models.NoteType { // NOTE: when making a note public we want to process its url and creata a base url obj BaseURL := &models.BaseURL{ URL: orgLink.URL, @@ -463,7 +461,7 @@ func (r *mutationResolver) UpdateLink(ctx context.Context, input *model.UpdateLi } srv.QueueTask("general", core.ParseBaseURLTask(srv, BaseURL)) } - orgLink.Visibility = *input.Visibility + orgLink.Visibility = string(*input.Visibility) } if input.Title != nil { @@ -634,8 +632,7 @@ func (r *mutationResolver) AddNote(ctx context.Context, input *model.NoteInput) validator.Expect(input.Description != "", lt.Translate("Description is required.")). WithField("description"). WithCode(valid.ErrValidationCode) - validator.Expect(input.Visibility == models.OrgLinkVisibilityPublic || - input.Visibility == models.OrgLinkVisibilityPrivate, + validator.Expect(models.ValidateLinkVisibility(string(input.Visibility)), lt.Translate("Invalid Visibility value.")). WithField("visibility"). WithCode(valid.ErrValidationCode) @@ -686,7 +683,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 input.Visibility == models.OrgLinkVisibilityPrivate && + if string(input.Visibility) == models.OrgLinkVisibilityPrivate && links.BillingEnabled(srv.Config) && org.IsRestricted([]int{models.BillingStatusFree}) { validator.Error(lt.Translate("Free organizations are not allowed to create private notes. Please upgrade")). WithField("visibility"). @@ -699,14 +696,15 @@ func (r *mutationResolver) AddNote(ctx context.Context, input *model.NoteInput) // If the note is public we create a based link var BaseURL *models.BaseURL - if input.Visibility == models.OrgLinkVisibilityPublic { - BaseURL = &models.BaseURL{ - URL: noteURL, - } - err = BaseURL.Store(ctx) - if err != nil { - return nil, err - } + BaseURL = &models.BaseURL{ + URL: noteURL, + } + err = BaseURL.Store(ctx) + if err != nil { + return nil, err + } + + if string(input.Visibility) == models.OrgLinkVisibilityPublic { err = BaseURL.UpdateCounter(ctx, true) if err != nil { return nil, err @@ -718,7 +716,7 @@ func (r *mutationResolver) AddNote(ctx context.Context, input *model.NoteInput) OrgID: org.ID, Description: input.Description, BaseURLID: sql.NullInt64{Valid: BaseURL.ID > 0, Int64: int64(BaseURL.ID)}, - Visibility: input.Visibility, + Visibility: string(input.Visibility), Starred: input.Starred, URL: noteURL, UserID: int(user.ID), @@ -1702,7 +1700,7 @@ func (r *mutationResolver) UpdateOrganization(ctx context.Context, input *model. if input.DefaultPerm != nil { if org.Settings.Billing.Status == models.BillingStatusFree && - *input.DefaultPerm == models.OrgLinkVisibilityPrivate { + string(*input.DefaultPerm) == models.OrgLinkVisibilityPrivate { validator.Error(lt.Translate( "Free organizations can not use Private permission. Please upgrade to use " + "Private permission")). @@ -1710,14 +1708,13 @@ func (r *mutationResolver) UpdateOrganization(ctx context.Context, input *model. WithCode(valid.ErrValidationCode) return nil, nil } - if *input.DefaultPerm < models.OrgLinkVisibilityPublic || - *input.DefaultPerm > models.OrgLinkVisibilityPrivate { + if !models.ValidateLinkVisibility(string(*input.DefaultPerm)) { validator.Error(lt.Translate("Invalid default permission value")). WithField("defaultPerm"). WithCode(valid.ErrValidationCode) return nil, nil } - org.Settings.DefaultPerm = *input.DefaultPerm + org.Settings.DefaultPerm = string(*input.DefaultPerm) } err = org.Store(ctx) @@ -4195,11 +4192,21 @@ func (r *orgLinkResolver) BaseURLID(ctx context.Context, obj *models.OrgLink) (* return &model.NullInt{Int64: int(obj.BaseURLID.Int64), Valid: obj.BaseURLID.Valid}, nil } +// Visibility is the resolver for the visibility field. +func (r *orgLinkResolver) Visibility(ctx context.Context, obj *models.OrgLink) (model.LinkVisibility, error) { + return model.LinkVisibility(obj.Visibility), nil +} + // Image is the resolver for the image field. func (r *organizationResolver) Image(ctx context.Context, obj *models.Organization) (*graphql.Upload, error) { panic(fmt.Errorf("not implemented: Image - image")) } +// DefaultPerm is the resolver for the defaultPerm field. +func (r *organizationSettingsResolver) DefaultPerm(ctx context.Context, obj *models.OrganizationSettings) (model.LinkVisibility, error) { + return model.LinkVisibility(obj.DefaultPerm), nil +} + // Version is the resolver for the version field. func (r *queryResolver) Version(ctx context.Context) (*model.Version, error) { return &model.Version{ @@ -4554,7 +4561,7 @@ func (r *queryResolver) GetOrgLink(ctx context.Context, hash string) (*models.Or sq.And{ sq.Eq{"ou.user_id": user.ID}, sq.Eq{"ou.is_active": true}, - sq.Eq{"ol.visibility": []int{ + sq.Eq{"ol.visibility": []string{ models.OrgLinkVisibilityPublic, models.OrgLinkVisibilityPrivate}, }, @@ -4630,7 +4637,7 @@ func (r *queryResolver) GetOrgLinks(ctx context.Context, input *model.GetLinkInp if user.IsAuthenticated() && org.CanRead(ctx, user) { linkOpts.Filter = sq.And{ linkOpts.Filter, - sq.Eq{"ol.visibility": []int{ + sq.Eq{"ol.visibility": []string{ models.OrgLinkVisibilityPrivate, models.OrgLinkVisibilityPublic}}, } } else { @@ -6686,6 +6693,11 @@ func (r *Resolver) OrgLink() OrgLinkResolver { return &orgLinkResolver{r} } // Organization returns OrganizationResolver implementation. func (r *Resolver) Organization() OrganizationResolver { return &organizationResolver{r} } +// OrganizationSettings returns OrganizationSettingsResolver implementation. +func (r *Resolver) OrganizationSettings() OrganizationSettingsResolver { + return &organizationSettingsResolver{r} +} + // Query returns QueryResolver implementation. func (r *Resolver) Query() QueryResolver { return &queryResolver{r} } @@ -6696,6 +6708,7 @@ type domainResolver struct{ *Resolver } type mutationResolver struct{ *Resolver } type orgLinkResolver struct{ *Resolver } type organizationResolver struct{ *Resolver } +type organizationSettingsResolver struct{ *Resolver } type queryResolver struct{ *Resolver } type userResolver struct{ *Resolver } diff --git a/core/import.go b/core/import.go index 760d428..11dff40 100644 --- a/core/import.go +++ b/core/import.go @@ -262,7 +262,7 @@ func processOrgLinks(obj importObj, baseURLMap map[string]int, return nil } - var vis int + var vis string if obj.IsPublic() { vis = models.OrgLinkVisibilityPublic } else if billEnabled && org.IsRestricted([]int{models.BillingStatusFree}) { diff --git a/core/inputs.go b/core/inputs.go index 58c823d..6f24054 100644 --- a/core/inputs.go +++ b/core/inputs.go @@ -55,7 +55,7 @@ type UpdateOrganizationForm struct { Slug string `form:"slug" validate:"required"` Enabled bool `from:"is_enabled"` DeleteImage bool `form:"delete"` - DefaultPerm int `form:"default_perm" validate:"number,gte=0"` + DefaultPerm string `form:"default_perm" validate:"oneof=PUBLIC PRIVATE RESTRICTED"` } // Validate ... @@ -65,7 +65,7 @@ func (uo *UpdateOrganizationForm) Validate(c echo.Context) error { String("slug", &uo.Slug). Bool("is_enabled", &uo.Enabled). Bool("delete", &uo.DeleteImage). - Int("default_perm", &uo.DefaultPerm). + String("default_perm", &uo.DefaultPerm). BindErrors() if errs != nil { return validate.GetInputErrors(errs) @@ -100,7 +100,7 @@ type LinkForm struct { Title string `form:"title" validate:"required"` Description string `form:"description"` OrgSlug string `form:"org_slug" validate:"required"` - Visibility int `form:"visibility" validate:"number,gte=0"` + Visibility string `form:"visibility" validate:"oneof=PUBLIC PRIVATE RESTRICTED"` Unread bool `form:"unread"` Starred bool `form:"starred"` Archive bool `form:"archive"` @@ -122,7 +122,7 @@ func (l *LinkForm) Validate(c echo.Context) error { Bool("unread", &l.Unread). Bool("starred", &l.Starred). Bool("archive", &l.Archive). - Int("visibility", &l.Visibility). + String("visibility", &l.Visibility). BindErrors() if errs != nil { return validate.GetInputErrors(errs) @@ -230,7 +230,7 @@ type NoteForm struct { Title string `form:"title" validate:"required"` Description string `form:"description" validate:"required"` OrgSlug string `form:"org_slug" validate:"required"` - Visibility int `form:"visibility" validate:"number,gte=0"` + Visibility string `form:"visibility" validate:"oneof=PUBLIC PRIVATE RESTRICTED"` Starred bool `form:"starred"` Tags string `form:"tags"` } @@ -243,7 +243,7 @@ func (n *NoteForm) Validate(c echo.Context) error { String("org_slug", &n.OrgSlug). String("tags", &n.Tags). Bool("starred", &n.Starred). - Int("visibility", &n.Visibility). + String("visibility", &n.Visibility). BindErrors() if errs != nil { return validate.GetInputErrors(errs) diff --git a/core/routes.go b/core/routes.go index 93deb8d..59d4c47 100644 --- a/core/routes.go +++ b/core/routes.go @@ -734,7 +734,7 @@ func (s *Service) OrgCreate(c echo.Context) error { "Sorry, you have exceeded the amount of free accounts available. Please update your current free account to create one more") form := &OrganizationForm{} - visibilityOpt := map[int]string{ + visibilityOpt := map[string]string{ models.OrgLinkVisibilityPublic: lt.Translate("Public"), models.OrgLinkVisibilityPrivate: lt.Translate("Private"), } @@ -883,7 +883,7 @@ func (s *Service) OrgUpdate(c echo.Context) error { pd.Data["visibility"] = lt.Translate("Default Bookmark Visibility") form := &UpdateOrganizationForm{} - visibilityOpt := map[int]string{ + visibilityOpt := map[string]string{ models.OrgLinkVisibilityPublic: lt.Translate("Public"), models.OrgLinkVisibilityPrivate: lt.Translate("Private"), } @@ -1376,7 +1376,7 @@ func (s *Service) OrgLinksCreate(c echo.Context) error { pd.Data["back"] = lt.Translate("Back") pd.Data["archive"] = lt.Translate("Archive URL") - visibilityOpt := map[int]string{ + visibilityOpt := map[string]string{ models.OrgLinkVisibilityPublic: lt.Translate("Public"), models.OrgLinkVisibilityPrivate: lt.Translate("Private"), } @@ -1410,7 +1410,7 @@ func (s *Service) OrgLinksCreate(c echo.Context) error { var result GraphQLResponse op := gqlclient.NewOperation( `mutation AddLink($title: String!, $url: String!, $description: String, - $visibility: Int!, $unread: Boolean!, $starred: Boolean!, + $visibility: LinkVisibility!, $unread: Boolean!, $starred: Boolean!, $archive: Boolean! $slug: String!, $tags: String) { addLink(input: { title: $title, @@ -2274,6 +2274,7 @@ func (s *Service) OrgLinkDetail(c echo.Context) error { userId type hash + visibility createdOn tags { id @@ -2453,7 +2454,7 @@ func (s *Service) OrgLinkUpdate(c echo.Context) error { pd.Data["confirm_message"] = lt.Translate("Do you want to delete") pd.Data["archive"] = lt.Translate("Archive") pd.Data["tags_helper"] = lt.Translate("Use commas to separate your tags. Example: tag 1, tag 2, tag 3") - visibilityOpt := map[int]string{ + visibilityOpt := map[string]string{ models.OrgLinkVisibilityPublic: lt.Translate("Public"), models.OrgLinkVisibilityPrivate: lt.Translate("Private"), } @@ -2487,7 +2488,7 @@ func (s *Service) OrgLinkUpdate(c echo.Context) error { var result GraphQLResponse op := gqlclient.NewOperation( `mutation UpdateLink($hash: String!, $title: String!, $description: String, - $url: String!, $visibility: Int!, $unread: Boolean, + $url: String!, $visibility: LinkVisibility!, $unread: Boolean, $starred: Boolean, $tags: String) { updateLink(input: { title: $title, @@ -3273,7 +3274,7 @@ func (s *Service) NoteUpdate(c echo.Context) error { pd.Data["starred"] = lt.Translate("Starred") pd.Data["confirm_message"] = lt.Translate("Do you want to delete") pd.Data["tags_helper"] = lt.Translate("Use commas to separate your tags. Example: tag 1, tag 2, tag 3") - visibilityOpt := map[int]string{ + visibilityOpt := map[string]string{ models.OrgLinkVisibilityPublic: lt.Translate("Public"), models.OrgLinkVisibilityPrivate: lt.Translate("Private"), } @@ -3307,7 +3308,7 @@ func (s *Service) NoteUpdate(c echo.Context) error { var result GraphQLResponse op := gqlclient.NewOperation( `mutation UpdateLink($hash: String!, $title: String!, $description: String, - $visibility: Int!, $starred: Boolean, $tags: String) { + $visibility: LinkVisibility!, $starred: Boolean, $tags: String) { updateLink(input: { hash: $hash, title: $title, @@ -3317,6 +3318,7 @@ func (s *Service) NoteUpdate(c echo.Context) error { tags: $tags, }) { id + hash url } }`) @@ -3340,7 +3342,7 @@ func (s *Service) NoteUpdate(c echo.Context) error { return err } messages.Success(c, lt.Translate("Link successfully updated.")) - return c.Redirect(http.StatusMovedPermanently, result.Link.URL) + return c.Redirect(http.StatusMovedPermanently, c.Echo().Reverse("core:note_detail", result.Link.Hash)) } var canEdit bool @@ -3385,6 +3387,7 @@ func (s *Service) NoteDetail(c echo.Context) error { userId type visibility + hash createdOn updatedOn tags { @@ -3470,7 +3473,7 @@ func (s *Service) NoteCreate(c echo.Context) error { pd.Data["back"] = lt.Translate("Back") pd.Data["organization"] = lt.Translate("Organization") - visibilityOpt := map[int]string{ + visibilityOpt := map[string]string{ models.OrgLinkVisibilityPublic: lt.Translate("Public"), models.OrgLinkVisibilityPrivate: lt.Translate("Private"), } @@ -3502,7 +3505,7 @@ func (s *Service) NoteCreate(c echo.Context) error { var result GraphQLResponse op := gqlclient.NewOperation( - `mutation AddNote($title: String!, $description: String!, $visibility: Int!, + `mutation AddNote($title: String!, $description: String!, $visibility: LinkVisibility!, $starred: Boolean!, $slug: String!, $tags: String) { addNote(input: { title: $title, @@ -3512,6 +3515,7 @@ func (s *Service) NoteCreate(c echo.Context) error { orgSlug: $slug, tags: $tags}) { id + hash url } }`) @@ -3536,7 +3540,7 @@ func (s *Service) NoteCreate(c echo.Context) error { } messages.Success(c, lt.Translate("An note was successfully created.")) - return c.Redirect(http.StatusMovedPermanently, result.Link.URL) + return c.Redirect(http.StatusMovedPermanently, c.Echo().Reverse("core:note_detail", result.Link.Hash)) } form := &NoteForm{ diff --git a/core/routes_test.go b/core/routes_test.go index d661bb8..98bf6c0 100644 --- a/core/routes_test.go +++ b/core/routes_test.go @@ -164,7 +164,7 @@ func TestHandlers(t *testing.T) { f.Set("url", "http://foo.com") f.Set("title", "Title") f.Set("org_slug", "slug") - f.Set("visibility", "1") + f.Set("visibility", "PRIVATE") request := httptest.NewRequest(http.MethodPost, "/add", strings.NewReader(f.Encode())) request.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm) recorder := httptest.NewRecorder() @@ -192,7 +192,7 @@ func TestHandlers(t *testing.T) { f.Set("url", "http://foo.com") f.Set("title", "Title") f.Set("org_slug", "slug") - f.Set("visibility", "1") + f.Set("visibility", "PRIVATE") request := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(f.Encode())) request.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm) recorder := httptest.NewRecorder() @@ -234,7 +234,7 @@ func TestHandlers(t *testing.T) { f.Set("url", "http://foo.bar.com") f.Set("title", "Title") f.Set("org_slug", "slug") - f.Set("visibility", "1") + f.Set("visibility", "PRIVATE") request := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(f.Encode())) request.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm) recorder := httptest.NewRecorder() @@ -269,7 +269,7 @@ func TestHandlers(t *testing.T) { f.Set("url", "http://foo.com") f.Set("title", "Title") f.Set("org_slug", "slug") - f.Set("visibility", "1") + f.Set("visibility", "PRIVATE") request := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(f.Encode())) request.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm) recorder := httptest.NewRecorder() diff --git a/core/samples/create_link.json b/core/samples/create_link.json index 8456355..0d2b6d5 100644 --- a/core/samples/create_link.json +++ b/core/samples/create_link.json @@ -9,7 +9,7 @@ }, "orgId": 1, "userId": 1, - "visibility": 0, + "visibility": "PUBLIC", "createdOn": "2023-06-08T22:44:32.35373Z", "updatedOn": "2023-06-08T22:44:32.35373Z" } diff --git a/core/samples/detail_link.json b/core/samples/detail_link.json index 4570b2a..91dd593 100644 --- a/core/samples/detail_link.json +++ b/core/samples/detail_link.json @@ -12,7 +12,7 @@ }, "orgId": 1, "userId": 1, - "visibility": 0, + "visibility": "PUBLIC", "createdOn": "2023-05-30T18:41:38.891196Z", "updatedOn": "2023-05-30T18:41:38.891196Z" } diff --git a/core/samples/update_link.json b/core/samples/update_link.json index 6562369..358b454 100644 --- a/core/samples/update_link.json +++ b/core/samples/update_link.json @@ -9,7 +9,7 @@ }, "orgId": 1, "userId": 1, - "visibility": 0, + "visibility": "PUBLIC", "createdOn": "2023-05-23T17:27:21.013293Z", "updatedOn": "2023-06-08T23:47:11.521328Z" } diff --git a/migrations/0001_initial.down.sql b/migrations/0001_initial.down.sql index 5a803d4..d94aa78 100644 --- a/migrations/0001_initial.down.sql +++ b/migrations/0001_initial.down.sql @@ -36,3 +36,4 @@ DROP TYPE IF EXISTS domain_level; DROP TYPE IF EXISTS domain_service; DROP TYPE IF EXISTS domain_status; DROP TYPE IF EXISTS invoice_status; +DROP TYPE IF EXISTS org_link_visibility; diff --git a/migrations/0001_initial.up.sql b/migrations/0001_initial.up.sql index 80d56cb..47f5dec 100644 --- a/migrations/0001_initial.up.sql +++ b/migrations/0001_initial.up.sql @@ -51,6 +51,16 @@ EXCEPTION WHEN duplicate_object THEN null; END $$; +DO $$ BEGIN + CREATE TYPE org_link_visibility AS ENUM ( + 'PUBLIC', + 'PRIVATE', + 'RESTRICTED' + ); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; + CREATE TABLE users ( id SERIAL PRIMARY KEY, @@ -146,7 +156,7 @@ CREATE TABLE org_links ( base_url_id INT REFERENCES base_urls (id) ON DELETE CASCADE, org_id INT REFERENCES organizations (id) ON DELETE CASCADE NOT NULL, user_id INT REFERENCES users (id) ON DELETE SET NULL, - visibility INT DEFAULT 0, + visibility org_link_visibility DEFAULT 'PUBLIC', unread BOOLEAN DEFAULT FALSE NOT NULL, starred BOOLEAN DEFAULT FALSE NOT NULL, archive_url TEXT DEFAULT '', diff --git a/migrations/test_migration.up.sql b/migrations/test_migration.up.sql index ddacd2c..91f7cf0 100644 --- a/migrations/test_migration.up.sql +++ b/migrations/test_migration.up.sql @@ -10,10 +10,10 @@ INSERT INTO organizations (owner_id, name, slug) VALUES (2, 'api test org', 'api INSERT INTO base_urls (url, hash) VALUES ('http://base.com', 'abcdefg'); INSERT INTO org_links (title, url, base_url_id, user_id, org_id, visibility, hash) VALUES - ('Public Business url', 'http://base.com?vis=public', 1, 1, 2, 0, 'hash1'); + ('Public Business url', 'http://base.com?vis=public', 1, 1, 2, 'PUBLIC', 'hash1'); 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'); + ('Private Business url', 'http://base.com?vis=private', 1, 1, 2, 'PRIVATE', 'hash2'); INSERT INTO domains (name, lookup_name, org_id, level, service, status) VALUES ('short domain', 'short.domain.org', 1, 'SYSTEM', 'SHORT', 'APPROVED'); INSERT INTO domains (name, lookup_name, org_id, service, status, level) VALUES ('listing domain', 'list.domain.org', 1, 'LIST', 'APPROVED', 'USER'); diff --git a/models/models.go b/models/models.go index 9841afb..c4b88be 100644 --- a/models/models.go +++ b/models/models.go @@ -76,7 +76,7 @@ type OrgLink struct { BaseURLID sql.NullInt64 `db:"base_url_id" json:"baseURLId"` OrgID int `db:"org_id" json:"orgId"` UserID int `db:"user_id" json:"userId"` - Visibility int `db:"visibility" json:"visibility"` + Visibility string `db:"visibility" json:"visibility"` Unread bool `db:"unread" json:"unread"` Starred bool `db:"starred" json:"starred"` ArchiveURL string `db:"archive_url" json:"archiveUrl"` diff --git a/models/org_link.go b/models/org_link.go index 0f21cc6..f0eb234 100644 --- a/models/org_link.go +++ b/models/org_link.go @@ -16,9 +16,9 @@ import ( ) const ( - OrgLinkVisibilityPublic int = iota - OrgLinkVisibilityPrivate - OrgLinkVisibilityRestricted + OrgLinkVisibilityPublic string = "PUBLIC" + OrgLinkVisibilityPrivate string = "PRIVATE" + OrgLinkVisibilityRestricted string = "RESTRICTED" ) const ( @@ -134,6 +134,10 @@ func (o *OrgLink) Store(ctx context.Context) error { if o.Hash == "" { o.Hash = ksuid.New().String() } + // Added for fail safes after migration to enums. Can be removed later + if o.Visibility == "" { + o.Visibility = OrgLinkVisibilityPublic + } err = sq. Insert("org_links"). Columns("title", "url", "description", "base_url_id", "org_id", "user_id", "visibility", @@ -348,7 +352,7 @@ func ExportOrgLinks(ctx context.Context, opts *database.FilterOptions) ([]*Expor return links, nil } -func ToggleOrgLinksVisibilityBatch(ctx context.Context, opts *database.FilterOptions, vis int) error { +func ToggleOrgLinksVisibilityBatch(ctx context.Context, opts *database.FilterOptions, vis string) error { err := database.WithTx(ctx, nil, func(tx *sql.Tx) error { var err error _, err = sq. @@ -396,3 +400,15 @@ func GetOrgLinksCount(ctx context.Context, opts *database.FilterOptions) (int, e } return count, nil } + +// ValidateLinkVisibility ensures proper value for OrgLink.Visibility +func ValidateLinkVisibility(vis string) bool { + visMap := map[string]bool{ + OrgLinkVisibilityPublic: true, + OrgLinkVisibilityPrivate: true, + OrgLinkVisibilityRestricted: true, + } + _, ok := visMap[vis] + return ok + +} diff --git a/models/organization.go b/models/organization.go index d76baca..6390ec2 100644 --- a/models/organization.go +++ b/models/organization.go @@ -37,7 +37,7 @@ type BillingSettings struct { // OrganizationSettings ... type OrganizationSettings struct { - DefaultPerm int `json:"default_perm"` // default: OrgLinkVisibilityPublic + DefaultPerm string `json:"default_perm"` // default: OrgLinkVisibilityPublic Billing BillingSettings `json:"billing"` } diff --git a/models/schema.sql b/models/schema.sql index dc38ea8..075c781 100644 --- a/models/schema.sql +++ b/models/schema.sql @@ -34,6 +34,12 @@ CREATE TYPE invoice_status AS ENUM ( 'PARTIAL_REFUND' ); +CREATE TYPE org_link_visibility AS ENUM ( + 'PUBLIC', + 'PRIVATE', + 'RESTRICTED' +); + CREATE TABLE users ( id SERIAL PRIMARY KEY, full_name VARCHAR ( 150 ) NOT NULL, @@ -129,6 +135,7 @@ CREATE TABLE org_links ( org_id INT REFERENCES organizations (id) ON DELETE CASCADE NOT NULL, user_id INT REFERENCES users (id) ON DELETE SET NULL, visibility INT DEFAULT 0, + visibility org_link_visibility DEFAULT 'PUBLIC', unread BOOLEAN DEFAULT FALSE NOT NULL, starred BOOLEAN DEFAULT FALSE NOT NULL, archive_url TEXT DEFAULT '', -- 2.45.2