From 04dac14600d9eca50de49915fea77428a47efefc Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 27 Nov 2020 10:43:51 -0500 Subject: [PATCH] API: Implement mutation { updateACL } This also adds a unique constraint on repo_id, user_id for the ACL and uses an upsert for the mutation accordingly. This is missing support for fetching unknown users from meta.sr.ht when adding them to the repository. --- api/graph/api/generated.go | 20 ++++----- api/graph/model/acl.go | 22 +++++++--- api/graph/schema.resolvers.go | 42 ++++++++++++++++++- ...a19185fec_add_unique_constraint_to_acls.py | 23 ++++++++++ 4 files changed, 87 insertions(+), 20 deletions(-) create mode 100644 gitsrht/alembic/versions/85ba19185fec_add_unique_constraint_to_acls.py diff --git a/api/graph/api/generated.go b/api/graph/api/generated.go index 1b2a1e6..60938d4 100644 --- a/api/graph/api/generated.go +++ b/api/graph/api/generated.go @@ -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 } diff --git a/api/graph/model/acl.go b/api/graph/model/acl.go index 4024397..c324bd8 100644 --- a/api/graph/model/acl.go +++ b/api/graph/model/acl.go @@ -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 }, diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index 2682c72..0d78a71 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -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) { diff --git a/gitsrht/alembic/versions/85ba19185fec_add_unique_constraint_to_acls.py b/gitsrht/alembic/versions/85ba19185fec_add_unique_constraint_to_acls.py new file mode 100644 index 0000000..81bd8f5 --- /dev/null +++ b/gitsrht/alembic/versions/85ba19185fec_add_unique_constraint_to_acls.py @@ -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') -- 2.38.4