M accounts/routes.go => accounts/routes.go +3 -1
@@ 278,7 278,9 @@ func (s *Service) Settings(c echo.Context) error {
return s.Render(c, http.StatusOK, "settings.html", gmap)
}
-// CompleteRegister ...
+// CompleteRegister is used when a user has been invited to an organization but does not
+// yet have an account. For normal user registration "completion", see
+// gobwebs/accounts/routes.go
func (s *Service) CompleteRegister(c echo.Context) error {
if !links.IsRegistrationEnabled(c) {
return echo.NotFoundHandler(c)
M api/api_test.go => api/api_test.go +4 -4
@@ 182,7 182,7 @@ func TestDirective(t *testing.T) {
}
var result GraphQLResponse
op := gqlclient.NewOperation(
- `mutation AddMember($slug: String!, $email: String!, $perm: Int!) {
+ `mutation AddMember($slug: String!, $email: String!, $perm: MemberPermission!) {
addMember(input: {orgSlug: $slug, email: $email, permission: $perm}) {
success
message
@@ 752,7 752,7 @@ func TestAPI(t *testing.T) {
orgUser := &models.OrgUser{
OrgID: 2,
UserID: 2,
- Permission: 0,
+ Permission: "READ",
}
err = orgUser.Store(dbCtx)
c.NoError(err)
@@ 775,7 775,7 @@ func TestAPI(t *testing.T) {
var result GraphQLResponse
op := gqlclient.NewOperation(
- `mutation AddMember($slug: String!, $email: String!, $perm: Int!) {
+ `mutation AddMember($slug: String!, $email: String!, $perm: MemberPermission!) {
addMember(input: {orgSlug: $slug, email: $email, permission: $perm}) {
success
message
@@ 825,7 825,7 @@ func TestAPI(t *testing.T) {
var result GraphQLResponse
op = gqlclient.NewOperation(
- `mutation AddMember($slug: String!, $email: String!, $perm: Int!) {
+ `mutation AddMember($slug: String!, $email: String!, $perm: MemberPermission!) {
addMember(input: {orgSlug: $slug, email: $email, permission: $perm}) {
success
message
M api/graph/generated.go => api/graph/generated.go +11 -1
@@ 22798,7 22798,7 @@ func (ec *executionContext) unmarshalInputMemberInput(ctx context.Context, obj i
it.Email = data
case "permission":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("permission"))
- data, err := ec.unmarshalNInt2int(ctx, v)
+ data, err := ec.unmarshalNMemberPermission2linksᚋapiᚋgraphᚋmodelᚐMemberPermission(ctx, v)
if err != nil {
return it, err
}
@@ 27892,6 27892,16 @@ func (ec *executionContext) marshalNListingLinkCursor2ᚖlinksᚋapiᚋgraphᚋm
return ec._ListingLinkCursor(ctx, sel, v)
}
+func (ec *executionContext) unmarshalNMemberPermission2linksᚋapiᚋgraphᚋmodelᚐMemberPermission(ctx context.Context, v interface{}) (model.MemberPermission, error) {
+ var res model.MemberPermission
+ err := res.UnmarshalGQL(v)
+ return res, graphql.ErrorOnPath(ctx, err)
+}
+
+func (ec *executionContext) marshalNMemberPermission2linksᚋapiᚋgraphᚋmodelᚐMemberPermission(ctx context.Context, sel ast.SelectionSet, v model.MemberPermission) graphql.Marshaler {
+ return v
+}
+
func (ec *executionContext) marshalNMetadata2linksᚋmodelsᚐMetadata(ctx context.Context, sel ast.SelectionSet, v models.Metadata) graphql.Marshaler {
return ec._Metadata(ctx, sel, &v)
}
M api/graph/model/models_gen.go => api/graph/model/models_gen.go +46 -3
@@ 260,9 260,9 @@ type ListingLinkCursor struct {
}
type MemberInput struct {
- OrgSlug string `json:"orgSlug"`
- Email string `json:"email"`
- Permission int `json:"permission"`
+ OrgSlug string `json:"orgSlug"`
+ Email string `json:"email"`
+ Permission MemberPermission `json:"permission"`
}
type Mutation struct {
@@ 766,3 766,46 @@ func (e *LinkVisibility) UnmarshalGQL(v interface{}) error {
func (e LinkVisibility) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
+
+type MemberPermission string
+
+const (
+ MemberPermissionRead MemberPermission = "READ"
+ MemberPermissionWrite MemberPermission = "WRITE"
+ MemberPermissionAdminWrite MemberPermission = "ADMIN_WRITE"
+)
+
+var AllMemberPermission = []MemberPermission{
+ MemberPermissionRead,
+ MemberPermissionWrite,
+ MemberPermissionAdminWrite,
+}
+
+func (e MemberPermission) IsValid() bool {
+ switch e {
+ case MemberPermissionRead, MemberPermissionWrite, MemberPermissionAdminWrite:
+ return true
+ }
+ return false
+}
+
+func (e MemberPermission) String() string {
+ return string(e)
+}
+
+func (e *MemberPermission) UnmarshalGQL(v interface{}) error {
+ str, ok := v.(string)
+ if !ok {
+ return fmt.Errorf("enums must be strings")
+ }
+
+ *e = MemberPermission(str)
+ if !e.IsValid() {
+ return fmt.Errorf("%s is not a valid MemberPermission", str)
+ }
+ return nil
+}
+
+func (e MemberPermission) MarshalGQL(w io.Writer) {
+ fmt.Fprint(w, strconv.Quote(e.String()))
+}
M api/graph/schema.graphqls => api/graph/schema.graphqls +6 -1
@@ 91,6 91,11 @@ enum LinkType {
NOTE
}
+enum MemberPermission {
+ READ
+ WRITE
+ ADMIN_WRITE
+}
# Considering removing these Null* fields:
# https://todo.code.netlandish.com/~netlandish/links/75
@@ 594,7 599,7 @@ input UpdateListingLinkInput {
input MemberInput {
orgSlug: String!
email: String!
- permission: Int!
+ permission: MemberPermission!
}
input ProfileInput {
M api/graph/schema.resolvers.go => api/graph/schema.resolvers.go +18 -25
@@ 776,8 776,7 @@ func (r *mutationResolver) AddMember(ctx context.Context, input *model.MemberInp
validator.Expect(emailRegex.MatchString(input.Email), lt.Translate("Invalid email format")).
WithField("userEmail").
WithCode(valid.ErrValidationCode)
- validator.Expect(input.Permission >= models.OrgUserPermissionRead ||
- input.Permission <= models.OrgUserPermissionAdminWrite,
+ validator.Expect(models.ValidatePermission(string(input.Permission)),
lt.Translate("Invalid Permission")).
WithField("permission").
WithCode(valid.ErrValidationCode)
@@ 874,7 873,7 @@ func (r *mutationResolver) AddMember(ctx context.Context, input *model.MemberInp
models.MEMBERINVITATIONCONF, user, time.Now().In(time.UTC).Add(time.Hour*72))
conf.ConfirmationTarget = sql.NullString{
- String: fmt.Sprintf(`{"org": %d, "perm": %d}`, org.ID, input.Permission),
+ String: fmt.Sprintf(`{"org": %d, "perm": "%s"}`, org.ID, input.Permission),
Valid: true,
}
if err := conf.Store(ctx); err != nil {
@@ 1028,25 1027,27 @@ func (r *mutationResolver) ConfirmMember(ctx context.Context, key string) (*mode
}
var org *models.Organization
- var perm int
if !conf.ConfirmationTarget.Valid {
validator.Error(lt.Translate("Invalid Confirmation Target")).
WithCode(valid.ErrNotFoundCode)
return nil, nil
}
- var data map[string]int
+ var data = struct {
+ Org int `json:"org"`
+ Perm string `json:"perm"`
+ }{}
err = json.Unmarshal([]byte(conf.ConfirmationTarget.String), &data)
if err != nil {
return nil, err
}
- orgID, ok := data["org"]
- if !ok {
+ orgID := data.Org
+ if orgID <= 0 {
validator.Error(lt.Translate("Organization Not Found")).
WithCode(valid.ErrNotFoundCode)
return nil, nil
}
- perm, ok = data["perm"]
- if !ok {
+ perm := data.Perm
+ if !models.ValidatePermission(perm) {
validator.Error(lt.Translate("Permission Not Found")).
WithCode(valid.ErrNotFoundCode)
return nil, nil
@@ 1321,25 1322,27 @@ func (r *mutationResolver) CompleteRegister(ctx context.Context, input *model.Co
return nil, nil
}
- var perm int
if !conf.ConfirmationTarget.Valid {
validator.Error(lt.Translate("Invalid Confirmation Target")).
WithCode(valid.ErrNotFoundCode)
return nil, nil
}
- var data map[string]int
+ var data = struct {
+ Org int `json:"org"`
+ Perm string `json:"perm"`
+ }{}
err = json.Unmarshal([]byte(conf.ConfirmationTarget.String), &data)
if err != nil {
return nil, err
}
- targetOrgID, ok := data["org"]
- if !ok {
+ targetOrgID := data.Org
+ if targetOrgID <= 0 {
validator.Error(lt.Translate("Organization Not Found")).
WithCode(valid.ErrNotFoundCode)
return nil, nil
}
- perm, ok = data["perm"]
- if !ok {
+ perm := data.Perm
+ if !models.ValidatePermission(perm) {
validator.Error(lt.Translate("Permission Not Found")).
WithCode(valid.ErrNotFoundCode)
return nil, nil
@@ 6716,13 6719,3 @@ type organizationResolver struct{ *Resolver }
type organizationSettingsResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
type userResolver struct{ *Resolver }
-
-// !!! WARNING !!!
-// The code below was going to be deleted when updating resolvers. It has been copied here so you have
-// one last chance to move it out of harms way if you want. There are two reasons this happens:
-// - When renaming or deleting a resolver the old code will be put in here. You can safely delete
-// it when you're done.
-// - You have helper methods in this file. Move them out to keep these resolver files clean.
-func (r *userResolver) LockReadon(ctx context.Context, obj *models.User) (string, error) {
- panic(fmt.Errorf("not implemented: LockReadon - lockReadon"))
-}
M core/inputs.go => core/inputs.go +2 -2
@@ 76,7 76,7 @@ func (uo *UpdateOrganizationForm) Validate(c echo.Context) error {
// AddMemberForm ...
type AddMemberForm struct {
Email string `form:"email" validate:"required,email"`
- Permission int `form:"permission" validate:"number,gte=0,lte=2"`
+ Permission string `form:"permission" validate:"oneof=READ WRITE ADMIN_WRITE"`
}
// Validate ...
@@ 85,7 85,7 @@ func (a *AddMemberForm) Validate(c echo.Context) error {
errs := validate.FormFieldBinder(c, a).
FailFast(false).
String("email", &a.Email).
- Int("permission", &a.Permission).
+ String("permission", &a.Permission).
BindErrors()
if errs != nil {
return validate.GetInputErrors(errs)
M core/routes.go => core/routes.go +9 -3
@@ 918,7 918,7 @@ func (s *Service) OrgUpdate(c echo.Context) error {
}
var result GraphQLResponse
op := gqlclient.NewOperation(
- `mutation UpdateOrganization($currentSlug: String!, $name: String!, $perm: Int,
+ `mutation UpdateOrganization($currentSlug: String!, $name: String!, $perm: LinkVisibility,
$slug: String!, $image: Upload, $deleteImg: Boolean,
$isActive: Boolean) {
updateOrganization(input: {
@@ 1017,17 1017,23 @@ func (s *Service) OrgMembersAdd(c echo.Context) error {
pd.Data["back"] = lt.Translate("Back")
pd.Data["cancel"] = lt.Translate("Cancel")
pd.Data["continue_to_upgrade"] = lt.Translate("Continue to Upgrade")
- permissions := map[int]string{
+ permissions := map[string]string{
models.OrgUserPermissionRead: lt.Translate("Read"),
models.OrgUserPermissionWrite: lt.Translate("Write"),
models.OrgUserPermissionAdminWrite: lt.Translate("Admin Write"),
}
+ permOrder := []string{
+ models.OrgUserPermissionRead,
+ models.OrgUserPermissionWrite,
+ models.OrgUserPermissionAdminWrite,
+ }
gmap := gobwebs.Map{
"pd": pd,
"settingSection": true,
"navFlag": "settings",
"org": org,
"permissions": permissions,
+ "permOrder": permOrder,
}
form := &AddMemberForm{}
@@ 1053,7 1059,7 @@ func (s *Service) OrgMembersAdd(c echo.Context) error {
slug := c.Param("slug")
var result GraphQLResponse
op := gqlclient.NewOperation(
- `mutation AddMember($slug: String!, $email: String!, $perm: Int!) {
+ `mutation AddMember($slug: String!, $email: String!, $perm: MemberPermission!) {
addMember(input: {orgSlug: $slug, email: $email, permission: $perm}) {
success
message
M migrations/0001_initial.down.sql => migrations/0001_initial.down.sql +1 -0
@@ 38,3 38,4 @@ DROP TYPE IF EXISTS domain_status;
DROP TYPE IF EXISTS invoice_status;
DROP TYPE IF EXISTS org_link_visibility;
DROP TYPE IF EXISTS org_link_type;
+DROP TYPE IF EXISTS org_user_perm;
M migrations/0001_initial.up.sql => migrations/0001_initial.up.sql +10 -1
@@ 70,6 70,15 @@ EXCEPTION
WHEN duplicate_object THEN null;
END $$;
+DO $$ BEGIN
+ CREATE TYPE org_link_perm AS ENUM (
+ 'READ',
+ 'WRITE',
+ 'ADMIN_WRITE'
+ );
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
CREATE TABLE users (
@@ 185,7 194,7 @@ CREATE TABLE organization_users (
id SERIAL PRIMARY KEY,
org_id INT REFERENCES organizations (id) ON DELETE CASCADE NOT NULL,
user_id INT REFERENCES users (id) ON DELETE CASCADE NOT NULL,
- permission INT DEFAULT 0,
+ permission org_link_perm default 'READ',
is_active BOOLEAN DEFAULT TRUE,
created_on TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_on TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
M models/models.go => models/models.go +1 -1
@@ 97,7 97,7 @@ type OrgUser struct {
ID int `db:"id"`
OrgID int `db:"org_id"`
UserID int `db:"user_id"`
- Permission int `db:"permission"`
+ Permission string `db:"permission"`
IsActive bool `db:"is_active"`
CreatedOn time.Time `db:"created_on"`
UpdatedOn time.Time `db:"updated_on"`
M models/org_link.go => models/org_link.go +0 -1
@@ 414,5 414,4 @@ func ValidateLinkVisibility(vis string) bool {
}
_, ok := visMap[vis]
return ok
-
}
M models/org_user.go => models/org_user.go +14 -3
@@ 13,11 13,11 @@ import (
const (
// OrgUserPermissionRead can read org data
- OrgUserPermissionRead int = iota
+ OrgUserPermissionRead string = "READ"
// OrgUserPermissionWrite can write org links and notes
- OrgUserPermissionWrite
+ OrgUserPermissionWrite string = "WRITE"
// OrgUserPermissionAdminWrite can write org management items (members, domains, etc.)
- OrgUserPermissionAdminWrite
+ OrgUserPermissionAdminWrite string = "ADMIN_WRITE"
)
// GetOrgUsers ...
@@ 169,3 169,14 @@ func ToggleOrgUserBatch(ctx context.Context, opts *database.FilterOptions, flag
})
return err
}
+
+// ValidatePermission ensures proper value for OrgLink.Visibility
+func ValidatePermission(perm string) bool {
+ perMap := map[string]bool{
+ OrgUserPermissionRead: true,
+ OrgUserPermissionWrite: true,
+ OrgUserPermissionAdminWrite: true,
+ }
+ _, ok := perMap[perm]
+ return ok
+}
M models/organization.go => models/organization.go +1 -1
@@ 214,7 214,7 @@ func (o *Organization) ToLocalTZ(tz string) error {
return nil
}
-func (o *Organization) permCheck(ctx context.Context, user *User, perm int) bool {
+func (o *Organization) permCheck(ctx context.Context, user *User, perm string) bool {
if o.OwnerID == int(user.ID) {
return true
}
M models/schema.sql => models/schema.sql +6 -1
@@ 45,6 45,11 @@ CREATE TYPE org_link_type AS ENUM (
'NOTE'
);
+CREATE TYPE org_link_perm AS ENUM (
+ 'READ',
+ 'WRITE',
+ 'ADMIN_WRITE'
+);
CREATE TABLE users (
id SERIAL PRIMARY KEY,
@@ 160,7 165,7 @@ CREATE TABLE organization_users (
id SERIAL PRIMARY KEY,
org_id INT REFERENCES organizations (id) ON DELETE CASCADE NOT NULL,
user_id INT REFERENCES users (id) ON DELETE CASCADE NOT NULL,
- permission INT DEFAULT 0,
+ permission org_link_perm default 'READ',
is_active BOOLEAN DEFAULT TRUE,
created_on TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_on TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
M models/user.go => models/user.go +3 -3
@@ 227,7 227,7 @@ func (u *User) WritePassword(ctx context.Context) error {
}
// GetOrgs ...
-func (u *User) GetOrgs(ctx context.Context, perm int) ([]*Organization, error) {
+func (u *User) GetOrgs(ctx context.Context, perm string) ([]*Organization, error) {
if u.ID == 0 {
return nil, fmt.Errorf("User object not populated")
}
@@ 254,7 254,7 @@ func (u *User) GetOrgs(ctx context.Context, perm int) ([]*Organization, error) {
}
// GetOrgsSlug ...
-func (u *User) GetOrgsSlug(ctx context.Context, perm int, slug string) (*Organization, error) {
+func (u *User) GetOrgsSlug(ctx context.Context, perm string, slug string) (*Organization, error) {
orgs, err := u.GetOrgs(ctx, perm)
if err != nil {
return nil, err
@@ 268,7 268,7 @@ func (u *User) GetOrgsSlug(ctx context.Context, perm int, slug string) (*Organiz
}
// GetOrgsID ...
-func (u *User) GetOrgsID(ctx context.Context, perm int, id int) (*Organization, error) {
+func (u *User) GetOrgsID(ctx context.Context, perm string, id int) (*Organization, error) {
orgs, err := u.GetOrgs(ctx, perm)
if err != nil {
return nil, err
M templates/member_add.html => templates/member_add.html +2 -2
@@ 27,8 27,8 @@
<div>
<label for="permission">{{.pd.Data.permission}}</label>
<select name="permission" required>
- {{ range $key, $val := .permissions }}
- <option value="{{ $key }}"{{if eq $key $.form.Permission}} selected{{end}}>{{$val}}</option>
+ {{ range $key := .permOrder }}
+ <option value="{{ $key }}"{{if eq $key $.form.Permission}} selected{{end}}>{{ index $.permissions $key}}</option>
{{end}}
</select>
{{ with .errors.Permission }}