From d3d59a18baeb9100b1a5674ab3cc9d890e668077 Mon Sep 17 00:00:00 2001 From: Peter Sanchez Date: Wed, 22 May 2024 17:27:53 -0600 Subject: [PATCH] Adding default org and link permissions --- api/graph/generated.go | 83 +++++++++++++++++++++++++++++++++-- api/graph/model/models_gen.go | 2 + api/graph/schema.graphqls | 4 +- api/graph/schema.resolvers.go | 16 +++++++ core/inputs.go | 4 ++ core/routes.go | 29 ++++++++++-- helpers.go | 5 ++- models/models.go | 10 ----- models/organization.go | 21 ++++++--- slack/commands.go | 50 +++++++++++++++------ templates/org_create.html | 11 +++++ templates/org_edit.html | 11 +++++ 12 files changed, 210 insertions(+), 36 deletions(-) diff --git a/api/graph/generated.go b/api/graph/generated.go index a2b7643..dc10c26 100644 --- a/api/graph/generated.go +++ b/api/graph/generated.go @@ -301,7 +301,8 @@ type ComplexityRoot struct { } OrganizationSettings struct { - Billing func(childComplexity int) int + Billing func(childComplexity int) int + DefaultPerm func(childComplexity int) int } OrganizationStats struct { @@ -1843,6 +1844,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.OrganizationSettings.Billing(childComplexity), true + case "OrganizationSettings.defaultPerm": + if e.complexity.OrganizationSettings.DefaultPerm == nil { + break + } + + return e.complexity.OrganizationSettings.DefaultPerm(childComplexity), true + case "OrganizationStats.links": if e.complexity.OrganizationStats.Links == nil { break @@ -13563,6 +13571,8 @@ func (ec *executionContext) fieldContext_Organization_settings(ctx context.Conte IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { + case "defaultPerm": + return ec.fieldContext_OrganizationSettings_defaultPerm(ctx, field) case "billing": return ec.fieldContext_OrganizationSettings_billing(ctx, field) } @@ -13923,6 +13933,50 @@ func (ec *executionContext) fieldContext_OrganizationCursor_pageInfo(ctx context return fc, nil } +func (ec *executionContext) _OrganizationSettings_defaultPerm(ctx context.Context, field graphql.CollectedField, obj *models.OrganizationSettings) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_OrganizationSettings_defaultPerm(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.DefaultPerm, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(int) + fc.Result = res + return ec.marshalNInt2int(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_OrganizationSettings_defaultPerm(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "OrganizationSettings", + Field: field, + IsMethod: false, + IsResolver: false, + 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 fc, nil +} + func (ec *executionContext) _OrganizationSettings_billing(ctx context.Context, field graphql.CollectedField, obj *models.OrganizationSettings) (ret graphql.Marshaler) { fc, err := ec.fieldContext_OrganizationSettings_billing(ctx, field) if err != nil { @@ -22606,7 +22660,7 @@ func (ec *executionContext) unmarshalInputOrganizationInput(ctx context.Context, asMap[k] = v } - fieldsInOrder := [...]string{"name", "orgUsername", "image"} + fieldsInOrder := [...]string{"name", "orgUsername", "image", "defaultPerm"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -22637,6 +22691,14 @@ func (ec *executionContext) unmarshalInputOrganizationInput(ctx context.Context, if err != nil { return it, err } + case "defaultPerm": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("defaultPerm")) + it.DefaultPerm, err = ec.unmarshalNInt2int(ctx, v) + if err != nil { + return it, err + } } } @@ -23250,7 +23312,7 @@ func (ec *executionContext) unmarshalInputUpdateOrganizationInput(ctx context.Co asMap[k] = v } - fieldsInOrder := [...]string{"currentSlug", "name", "slug", "image", "isActive", "deleteImg"} + fieldsInOrder := [...]string{"currentSlug", "name", "slug", "image", "isActive", "deleteImg", "defaultPerm"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -23305,6 +23367,14 @@ func (ec *executionContext) unmarshalInputUpdateOrganizationInput(ctx context.Co if err != nil { return it, err } + case "defaultPerm": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("defaultPerm")) + it.DefaultPerm, err = ec.unmarshalOInt2áš–int(ctx, v) + if err != nil { + return it, err + } } } @@ -25167,6 +25237,13 @@ func (ec *executionContext) _OrganizationSettings(ctx context.Context, sel ast.S switch field.Name { 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 { + invalids++ + } case "billing": out.Values[i] = ec._OrganizationSettings_billing(ctx, field, obj) diff --git a/api/graph/model/models_gen.go b/api/graph/model/models_gen.go index c9f35e3..190a068 100644 --- a/api/graph/model/models_gen.go +++ b/api/graph/model/models_gen.go @@ -294,6 +294,7 @@ type OrganizationInput struct { Name string `json:"name"` OrgUsername string `json:"orgUsername"` Image *graphql.Upload `json:"image,omitempty"` + DefaultPerm int `json:"defaultPerm"` } type OrganizationStats struct { @@ -425,6 +426,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"` } type UpdateUserInput struct { diff --git a/api/graph/schema.graphqls b/api/graph/schema.graphqls index 094004c..ca2377b 100644 --- a/api/graph/schema.graphqls +++ b/api/graph/schema.graphqls @@ -97,6 +97,7 @@ type BillingSettings { } type OrganizationSettings { + defaultPerm: Int! billing: BillingSettings } @@ -589,7 +590,7 @@ input OrganizationInput { name: String! orgUsername: String! image: Upload - + defaultPerm: Int! } input UpdateOrganizationInput { @@ -599,6 +600,7 @@ input UpdateOrganizationInput { image: Upload isActive: Boolean deleteImg: Boolean + defaultPerm: Int } input RegisterInput { diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index 152adde..b582380 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -82,6 +82,10 @@ func (r *mutationResolver) AddOrganization(ctx context.Context, input model.Orga validator.Expect(len(input.OrgUsername) < 150, lt.Translate("Org username may not exceed 150 characters")). WithField("orgUsername"). WithCode(valid.ErrValidationCode) + validator.Expect(input.DefaultPerm >= models.OrgLinkVisibilityPublic && + input.DefaultPerm <= models.OrgLinkVisibilityPrivate, lt.Translate("Invalid default permission value")). + WithField("defaultPerm"). + WithCode(valid.ErrValidationCode) if !validator.Ok() { return nil, nil @@ -138,6 +142,7 @@ func (r *mutationResolver) AddOrganization(ctx context.Context, input model.Orga Name: input.Name, Slug: slug, IsActive: true, + Settings: models.OrganizationSettings{DefaultPerm: input.DefaultPerm}, } if input.Image != nil { @@ -1569,6 +1574,17 @@ func (r *mutationResolver) UpdateOrganization(ctx context.Context, input *model. org.IsActive = *input.IsActive } + if input.DefaultPerm != nil { + if *input.DefaultPerm < models.OrgLinkVisibilityPublic || + *input.DefaultPerm > models.OrgLinkVisibilityPrivate { + validator.Error(lt.Translate("Invalid default permission value")). + WithField("defaultPerm"). + WithCode(valid.ErrValidationCode) + return nil, nil + } + org.Settings.DefaultPerm = *input.DefaultPerm + } + err = org.Store(ctx) if err != nil { return nil, err diff --git a/core/inputs.go b/core/inputs.go index 3f6cae9..9cb8e33 100644 --- a/core/inputs.go +++ b/core/inputs.go @@ -35,6 +35,7 @@ func (p *PlaygroundForm) Validate(c echo.Context) error { type OrganizationForm struct { Name string `form:"name" validate:"required"` OrgUsername string `form:"org_username" validate:"required"` + DefaultPerm int `form:"default_perm" validate:"number,gte=0"` } // Validate ... @@ -43,6 +44,7 @@ func (o *OrganizationForm) Validate(c echo.Context) error { FailFast(false). String("name", &o.Name). String("org_username", &o.OrgUsername). + Int("default_perm", &o.DefaultPerm). BindErrors() if errs != nil { return validate.GetInputErrors(errs) @@ -55,6 +57,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"` } // Validate ... @@ -64,6 +67,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). BindErrors() if errs != nil { return validate.GetInputErrors(errs) diff --git a/core/routes.go b/core/routes.go index ffa3073..f89d418 100644 --- a/core/routes.go +++ b/core/routes.go @@ -728,11 +728,18 @@ func (s *Service) OrgCreate(c echo.Context) error { pd.Data["image"] = lt.Translate("Image") pd.Data["org_username"] = lt.Translate("Org Username") pd.Data["save"] = lt.Translate("Save") + pd.Data["visibility"] = lt.Translate("Default Bookmark Visibility") pd.Data["you_have_exceeded"] = lt.Translate( "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{ + models.OrgLinkVisibilityPublic: lt.Translate("Public"), + models.OrgLinkVisibilityPrivate: lt.Translate("Private"), + } gmap := gobwebs.Map{ "pd": pd, + "visibilityOpt": visibilityOpt, "settingSection": true, "navFlag": "settings", "navSubFlag": "orgAdd", @@ -754,13 +761,19 @@ func (s *Service) OrgCreate(c echo.Context) error { Org models.Organization `json:"addBusinessOrganization"` } op := gqlclient.NewOperation( - `mutation AddOrganization($name: String!, $image: Upload, $username: String!) { - addOrganization(input: {name: $name, image: $image, orgUsername: $username}) { + `mutation AddOrganization($name: String!, $image: Upload, $username: String!, $perm: Int!) { + addOrganization(input: { + name: $name, + image: $image, + orgUsername: $username, + defaultPerm: $perm, + }) { id } }`) op.Var("name", form.Name) op.Var("username", form.OrgUsername) + op.Var("perm", form.DefaultPerm) image, err := c.FormFile("image") if err != nil { @@ -868,8 +881,13 @@ func (s *Service) OrgUpdate(c echo.Context) error { pd.Data["save"] = lt.Translate("Save") pd.Data["back"] = lt.Translate("Back") pd.Data["cancel"] = lt.Translate("Cancel") + pd.Data["visibility"] = lt.Translate("Default Bookmark Visibility") form := &UpdateOrganizationForm{} + visibilityOpt := map[int]string{ + models.OrgLinkVisibilityPublic: lt.Translate("Public"), + models.OrgLinkVisibilityPrivate: lt.Translate("Private"), + } var isNormal bool if org.OrgType == models.OrgTypeNormal { @@ -877,6 +895,7 @@ func (s *Service) OrgUpdate(c echo.Context) error { } gmap := gobwebs.Map{ "pd": pd, + "visibilityOpt": visibilityOpt, "org": org, "settingSection": true, "navFlag": "settings", @@ -900,7 +919,7 @@ func (s *Service) OrgUpdate(c echo.Context) error { } var result GraphQLResponse op := gqlclient.NewOperation( - `mutation UpdateOrganization($currentSlug: String!, $name: String!, + `mutation UpdateOrganization($currentSlug: String!, $name: String!, $perm: Int, $slug: String!, $image: Upload, $deleteImg: Boolean, $isActive: Boolean) { updateOrganization(input: { @@ -910,6 +929,7 @@ func (s *Service) OrgUpdate(c echo.Context) error { image: $image, deleteImg: $deleteImg, isActive: $isActive, + defaultPerm: $perm, }) { id } @@ -918,6 +938,8 @@ func (s *Service) OrgUpdate(c echo.Context) error { op.Var("slug", form.Slug) op.Var("deleteImg", form.DeleteImage) op.Var("currentSlug", org.Slug) + op.Var("perm", form.DefaultPerm) + if isNormal { op.Var("isActive", form.Enabled) } @@ -955,6 +977,7 @@ func (s *Service) OrgUpdate(c echo.Context) error { form.Slug = org.Slug form.Enabled = org.IsActive + form.DefaultPerm = org.Settings.DefaultPerm gmap["form"] = form return links.Render(c, http.StatusOK, "org_edit.html", gmap) diff --git a/helpers.go b/helpers.go index c4dda3a..7d1fbb2 100644 --- a/helpers.go +++ b/helpers.go @@ -53,13 +53,15 @@ var tzKey = "user.tz" // and parse them into InputErrors to be displayed in the html form // if the error code is `ErrNotFoundCode` the handler will return 404 func ParseInputErrors(c echo.Context, graphError *gqlclient.Error, fMap gobwebs.Map) error { - inputErrs := validate.NewInputErrors() if len(graphError.Extensions) == 0 { return graphError } var ext ErrorExtension err := json.Unmarshal(graphError.Extensions, &ext) if err != nil { + // Need to address invalid query errors here as they show as + // json parsing errors when it's valid json. Ie, + // {"code":"GRAPHQL_VALIDATION_FAILED"} return err } @@ -67,6 +69,7 @@ func ParseInputErrors(c echo.Context, graphError *gqlclient.Error, fMap gobwebs. return echo.NotFoundHandler(c) } + inputErrs := validate.NewInputErrors() if ext.Code == valid.ErrValidationGlobalCode { inputErrs["_global_"] = []string{graphError.Message} return inputErrs diff --git a/models/models.go b/models/models.go index 0d6c4ef..13a8bff 100644 --- a/models/models.go +++ b/models/models.go @@ -10,16 +10,6 @@ import ( "netlandish.com/x/gobwebs/accounts" ) -// Billing status, used for org billing setting/metadata -// and plan type -const ( - BillingStatusFree int = iota - BillingStatusPersonal - BillingStatusBusiness - BillingStatusOpenSource - BillingStatusSponsored -) - // MEMBERINVITATIONCONF is sent when a new member is invited to join an org // REGISTRATIONINVITECONF is sent when a super user send a invitation for registration diff --git a/models/organization.go b/models/organization.go index f31e273..cfa4097 100644 --- a/models/organization.go +++ b/models/organization.go @@ -16,18 +16,29 @@ import ( "netlandish.com/x/gobwebs/timezone" ) -type BillingSettings struct { - Status int `json:"status"` -} - const ( OrgTypeUser int = iota OrgTypeNormal ) +// Billing status, used for org billing setting/metadata +// and plan type +const ( + BillingStatusFree int = iota + BillingStatusPersonal + BillingStatusBusiness + BillingStatusOpenSource + BillingStatusSponsored +) + +type BillingSettings struct { + Status int `json:"status"` // BillingStatus +} + // OrganizationSettings ... type OrganizationSettings struct { - Billing BillingSettings `json:"billing"` + DefaultPerm int `json:"default_perm"` // default: OrgLinkVisibilityPublic + Billing BillingSettings `json:"billing"` } // Value ... diff --git a/slack/commands.go b/slack/commands.go index 0c58376..dcfa1ec 100644 --- a/slack/commands.go +++ b/slack/commands.go @@ -221,6 +221,7 @@ func linkAdd(c echo.Context, url, teamID, slackUser, text string) (*SlackCommand return nil, err } + lt := localizer.GetSessionLocalizer(c) opts = &database.FilterOptions{ Filter: sq.And{ sq.Eq{"slack_conn_id": slackConn.ID}, @@ -229,7 +230,6 @@ func linkAdd(c echo.Context, url, teamID, slackUser, text string) (*SlackCommand Limit: 1, } - lt := localizer.GetSessionLocalizer(c) slackUsers, err := models.GetSlackUsers(ctx, opts) if err != nil { return nil, err @@ -243,7 +243,18 @@ func linkAdd(c echo.Context, url, teamID, slackUser, text string) (*SlackCommand if err != nil { return nil, err } - ctx = auth.Context(ctx, user) + + opts = &database.FilterOptions{ + Filter: sq.Eq{"o.id": slackConn.OrgID}, + } + orgs, err := models.GetOrganizations(ctx, opts) + if err != nil { + return nil, err + } + if len(orgs) == 0 { + return nil, fmt.Errorf("No valid organization found") + } + // index 0 is url // index 1 is title input := strings.Split(text, " ") @@ -251,29 +262,42 @@ func linkAdd(c echo.Context, url, teamID, slackUser, text string) (*SlackCommand Link models.OrgLink `json:"addLink"` } + ctx = auth.Context(ctx, user) var result GraphQLResponse op := gqlclient.NewOperation( - `mutation AddLink($title: String!, $url: String!, $visibility: Int!, $slug: String!) { - addLink(input: { - title: $title, - url: $url, - visibility: $visibility, - orgSlug: $slug}) { - id - } - }`) + `mutation AddLink($title: String!, $url: String!, $visibility: Int!, + $unread: Boolean!, $starred: Boolean!, + $archive: Boolean! $slug: String!) { + addLink(input: { + title: $title, + url: $url, + visibility: $visibility, + unread: $unread, + starred: $starred, + archive: $archive, + orgSlug: $slug}) { + hash + } + }`) op.Var("title", strings.TrimSpace(input[1])) op.Var("url", strings.TrimSpace(input[0])) op.Var("slug", slackConn.OrgSlug) - op.Var("visibility", models.OrgLinkVisibilityPublic) + op.Var("visibility", orgs[0].Settings.DefaultPerm) + op.Var("starred", false) + op.Var("archive", false) + op.Var("unread", true) err = links.Execute(ctx, op, &result) if err != nil { return nil, err } + + lurl := links.GetLinksDomainURL(c) + lurl.Path = c.Echo().Reverse("core:link_detail", result.Link.Hash) + block := SlackBlock{} block.Type = "section" block.Text.Type = "mrkdwn" - block.Text.Text = lt.Translate("Your link was successfully created") + block.Text.Text = lt.Translate("Your link was successfully saved. Details here: %s", lurl.String()) commandResp := &SlackCommandResponse{ Text: "Add Link", Blocks: []SlackBlock{block}, diff --git a/templates/org_create.html b/templates/org_create.html index 8b249b1..9b8a02a 100644 --- a/templates/org_create.html +++ b/templates/org_create.html @@ -29,6 +29,17 @@

{{ . }}

{{ end }} +
+ + + {{ with .errors.DefaultPerm }} +

{{ . }}

+ {{ end }} +
diff --git a/templates/org_edit.html b/templates/org_edit.html index 43d5c09..235516b 100644 --- a/templates/org_edit.html +++ b/templates/org_edit.html @@ -29,6 +29,17 @@ {{ end }}
{{ end }} +
+ + + {{ with .errors.DefaultPerm }} +

{{ . }}

+ {{ end }} +
{{ if .org.Image }} -- 2.45.2