~bigbes/sr-ht-dolt

ref: b4fd2233c89ca5c415284b61b83d41eb3d4982c1 sr-ht-dolt/db/access.go -rw-r--r-- 3.3 KiB
b4fd2233 — Eugene Blikh log: take the logrus bridge from auxilia 9 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package db

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"time"

	"sourcecraft.dev/bigbes/sr-ht-dolt/core"
)

// ACLEntry is one row of the access table, joined with the grantee's username
// for display in the settings UI.
type ACLEntry struct {
	ID       int
	RepoID   int
	UserID   int
	Username string
	Mode     core.AccessMode
	Created  time.Time
	Updated  time.Time
}

// EffectiveAccess returns the ACL grant a user holds on a repository, or nil if
// the user has no access entry. This is exactly the aclMode input that
// core.Allowed expects: it reflects only explicit ACL grants, never ownership or
// visibility (those are the caller's to combine via core.Allowed). A nil result
// with a nil error means "no grant", which is not an error condition.
func (s *Store) EffectiveAccess(ctx context.Context, userID, repoID int) (*core.AccessMode, error) {
	const q = `SELECT mode FROM access WHERE user_id = $1 AND repo_id = $2`
	var mode string
	err := s.q.QueryRowContext(ctx, q, userID, repoID).Scan(&mode)
	if errors.Is(err, sql.ErrNoRows) {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("effective access user=%d repo=%d: %w", userID, repoID, err)
	}
	m := core.AccessMode(mode)
	return &m, nil
}

// ListACL returns every access entry for a repository, ordered by username, with
// the grantee's username resolved for display.
func (s *Store) ListACL(ctx context.Context, repoID int) ([]*ACLEntry, error) {
	const q = `
SELECT a.id, a.repo_id, a.user_id, COALESCE(u.username, ''), a.mode, a.created, a.updated
FROM access a
JOIN "user" u ON u.id = a.user_id
WHERE a.repo_id = $1
ORDER BY u.username ASC, a.id ASC`
	rows, err := s.q.QueryContext(ctx, q, repoID)
	if err != nil {
		return nil, fmt.Errorf("list acl repo=%d: %w", repoID, err)
	}
	defer rows.Close()
	var entries []*ACLEntry
	for rows.Next() {
		var (
			e    ACLEntry
			mode string
		)
		if err := rows.Scan(&e.ID, &e.RepoID, &e.UserID, &e.Username,
			&mode, &e.Created, &e.Updated); err != nil {
			return nil, fmt.Errorf("scan acl: %w", err)
		}
		e.Mode = core.AccessMode(mode)
		entries = append(entries, &e)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate acl: %w", err)
	}
	return entries, nil
}

// UpsertACL grants (or updates) userID's access mode on repoID. If a grant
// already exists it is updated in place (and updated is bumped); otherwise a new
// row is inserted.
func (s *Store) UpsertACL(ctx context.Context, repoID, userID int, mode core.AccessMode) error {
	now := time.Now().UTC()
	const q = `
INSERT INTO access (created, updated, repo_id, user_id, mode)
VALUES ($1, $1, $2, $3, $4)
ON CONFLICT ON CONSTRAINT uq_access_user_id_repo_id
DO UPDATE SET mode = EXCLUDED.mode, updated = EXCLUDED.updated`
	_, err := s.q.ExecContext(ctx, q, now, repoID, userID, string(mode))
	if err != nil {
		return fmt.Errorf("upsert acl repo=%d user=%d: %w", repoID, userID, err)
	}
	return nil
}

// DeleteACL revokes userID's access on repoID. Returns ErrNotFound if no grant
// existed.
func (s *Store) DeleteACL(ctx context.Context, repoID, userID int) error {
	res, err := s.q.ExecContext(ctx,
		`DELETE FROM access WHERE repo_id = $1 AND user_id = $2`, repoID, userID)
	if err != nil {
		return fmt.Errorf("delete acl repo=%d user=%d: %w", repoID, userID, err)
	}
	return requireOne(res, "delete acl")
}