M api/graph/generated.go => api/graph/generated.go +80 -3
@@ 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)
M api/graph/model/models_gen.go => api/graph/model/models_gen.go +2 -0
@@ 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 {
M api/graph/schema.graphqls => api/graph/schema.graphqls +3 -1
@@ 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 {
M api/graph/schema.resolvers.go => api/graph/schema.resolvers.go +16 -0
@@ 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
M core/inputs.go => core/inputs.go +4 -0
@@ 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)
M core/routes.go => core/routes.go +26 -3
@@ 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)
M helpers.go => helpers.go +4 -1
@@ 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
M models/models.go => models/models.go +0 -10
@@ 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
M models/organization.go => models/organization.go +16 -5
@@ 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<X>
+}
+
// OrganizationSettings ...
type OrganizationSettings struct {
- Billing BillingSettings `json:"billing"`
+ DefaultPerm int `json:"default_perm"` // default: OrgLinkVisibilityPublic
+ Billing BillingSettings `json:"billing"`
}
// Value ...
M slack/commands.go => slack/commands.go +37 -13
@@ 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},
M templates/org_create.html => templates/org_create.html +11 -0
@@ 30,6 30,17 @@
{{ end }}
</div>
<div>
+ <label for="default_perm">{{.pd.Data.visibility}}</label>
+ <select name="default_perm">
+ {{range $key, $value := .visibilityOpt}}
+ <option value="{{$key}}"{{if eq $key $.form.DefaultPerm}}selected{{end}}>{{$value}}</option>
+ {{end}}
+ </select>
+ {{ with .errors.DefaultPerm }}
+ <p class="error">{{ . }}</p>
+ {{ end }}
+ </div>
+ <div>
<label for="image">{{.pd.Data.image}}</label>
<input type="file" name="image" />
{{ with .errors.Image }}
M templates/org_edit.html => templates/org_edit.html +11 -0
@@ 30,6 30,17 @@
</div>
{{ end }}
<div>
+ <label for="default_perm">{{.pd.Data.visibility}}</label>
+ <select name="default_perm">
+ {{range $key, $value := .visibilityOpt}}
+ <option value="{{$key}}"{{if eq $key $.form.DefaultPerm}}selected{{end}}>{{$value}}</option>
+ {{end}}
+ </select>
+ {{ with .errors.DefaultPerm }}
+ <p class="error">{{ . }}</p>
+ {{ end }}
+ </div>
+ <div>
<label for="image">{{.pd.Data.image}}</label>
{{ if .org.Image }}
<img class="mb-2" width="100" heigh="100" src="{{mediaURL .org.Image}}"/>