~edwargix/git.sr.ht

04dac14600d9eca50de49915fea77428a47efefc — Drew DeVault 3 years ago 7791ce7
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.
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')