M api/graph/api/generated.go => api/graph/api/generated.go +7 -13
@@ 2334,14 2334,14 @@ func (ec *executionContext) _ACL_mode(ctx context.Context, field graphql.Collect
Object: "ACL",
Field: field,
Args: nil,
- IsMethod: false,
+ IsMethod: true,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
- return obj.Mode, nil
+ return obj.Mode(), nil
})
if err != nil {
ec.Error(ctx, err)
@@ 2350,9 2350,9 @@ func (ec *executionContext) _ACL_mode(ctx context.Context, field graphql.Collect
if resTmp == nil {
return graphql.Null
}
- res := resTmp.(*model.AccessMode)
+ res := resTmp.(model.AccessMode)
fc.Result = res
- return ec.marshalOAccessMode2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐAccessMode(ctx, field.Selections, res)
+ return ec.marshalOAccessMode2gitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐAccessMode(ctx, field.Selections, res)
}
func (ec *executionContext) _ACLCursor_results(ctx context.Context, field graphql.CollectedField, obj *model.ACLCursor) (ret graphql.Marshaler) {
@@ 10318,19 10318,13 @@ func (ec *executionContext) marshalOACL2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsr
return ec._ACL(ctx, sel, v)
}
-func (ec *executionContext) unmarshalOAccessMode2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐAccessMode(ctx context.Context, v interface{}) (*model.AccessMode, error) {
- if v == nil {
- return nil, nil
- }
- var res = new(model.AccessMode)
+func (ec *executionContext) unmarshalOAccessMode2gitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐAccessMode(ctx context.Context, v interface{}) (model.AccessMode, error) {
+ var res model.AccessMode
err := res.UnmarshalGQL(v)
return res, graphql.ErrorOnPath(ctx, err)
}
-func (ec *executionContext) marshalOAccessMode2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐAccessMode(ctx context.Context, sel ast.SelectionSet, v *model.AccessMode) graphql.Marshaler {
- if v == nil {
- return graphql.Null
- }
+func (ec *executionContext) marshalOAccessMode2gitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐAccessMode(ctx context.Context, sel ast.SelectionSet, v model.AccessMode) graphql.Marshaler {
return v
}
M api/graph/model/acl.go => api/graph/model/acl.go +16 -6
@@ 3,7 3,9 @@ package model
import (
"context"
"database/sql"
+ "fmt"
"strconv"
+ "strings"
"time"
sq "github.com/Masterminds/squirrel"
@@ 14,17 16,25 @@ import (
// TODO: Drop updated column from database
type ACL struct {
- ID int `json:"id"`
- Created time.Time `json:"created"`
- Mode *AccessMode `json:"mode"`
+ ID int `json:"id"`
+ Created time.Time `json:"created"`
- RepoID int
- UserID int
+ RawAccessMode string
+ RepoID int
+ UserID int
alias string
fields *database.ModelFields
}
+func (acl *ACL) Mode() AccessMode {
+ mode := AccessMode(strings.ToUpper(acl.RawAccessMode))
+ if !mode.IsValid() {
+ panic(fmt.Errorf("Invalid access mode '%s'", acl.RawAccessMode)) // Invariant
+ }
+ return mode
+}
+
func (acl *ACL) As(alias string) *ACL {
acl.alias = alias
return acl
@@ 46,7 56,7 @@ func (acl *ACL) Fields() *database.ModelFields {
Fields: []*database.FieldMap{
{ "id", "id", &acl.ID },
{ "created", "created", &acl.Created },
- { "mode", "mode", &acl.Mode },
+ { "mode", "mode", &acl.RawAccessMode },
// Always fetch:
{ "id", "", &acl.ID },
M api/graph/schema.resolvers.go => api/graph/schema.resolvers.go +41 -1
@@ 318,7 318,47 @@ func (r *mutationResolver) DeleteRepository(ctx context.Context, id int) (*model
}
func (r *mutationResolver) UpdateACL(ctx context.Context, repoID int, mode model.AccessMode, entity string) (*model.ACL, error) {
- panic(fmt.Errorf("updateACL: not implemented"))
+ if entity[0] != '~' {
+ return nil, fmt.Errorf("Unknown entity %s", entity)
+ }
+ entity = entity[1:]
+
+ if entity == auth.ForContext(ctx).Username {
+ return nil, fmt.Errorf("Cannot edit your own access modes")
+ }
+
+ var acl model.ACL
+ if err := database.WithTx(ctx, nil, func(tx *sql.Tx) error {
+ row := tx.QueryRowContext(ctx, `
+ WITH grantee AS (
+ SELECT u.id uid, repo.id rid
+ FROM "user" u, repository repo
+ WHERE u.username = $3 AND repo.id = $1 AND repo.owner_id = $2
+ )
+ INSERT INTO access (created, updated, mode, user_id, repo_id)
+ SELECT
+ NOW() at time zone 'utc', NOW() at time zone 'utc',
+ $4, grantee.uid, grantee.rid
+ FROM grantee
+ ON CONFLICT ON CONSTRAINT uq_access_user_id_repo_id
+ DO UPDATE SET mode = $4, updated = NOW() at time zone 'utc'
+ RETURNING id, created, mode, repo_id, user_id;`,
+ repoID, auth.ForContext(ctx).UserID,
+ entity, strings.ToLower(string(mode)))
+ if err := row.Scan(&acl.ID, &acl.Created, &acl.RawAccessMode,
+ &acl.RepoID, &acl.UserID); err != nil {
+ if err == sql.ErrNoRows {
+ // TODO: Fetch user details from meta.sr.ht
+ return fmt.Errorf("No such repository or user found")
+ }
+ return err
+ }
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+
+ return &acl, nil
}
func (r *mutationResolver) DeleteACL(ctx context.Context, repoID int, entity string) (*model.ACL, error) {
A gitsrht/alembic/versions/85ba19185fec_add_unique_constraint_to_acls.py => gitsrht/alembic/versions/85ba19185fec_add_unique_constraint_to_acls.py +23 -0
@@ 0,0 1,23 @@
+"""Add unique constraint to ACLs
+
+Revision ID: 85ba19185fec
+Revises: c167cf8a1271
+Create Date: 2020-11-27 10:28:15.303415
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '85ba19185fec'
+down_revision = 'c167cf8a1271'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ op.create_unique_constraint('uq_access_user_id_repo_id', 'access',
+ ['user_id', 'repo_id'])
+
+
+def downgrade():
+ op.drop_constraint('uq_access_user_id_repo_id', 'access')