~bigbes/sr-ht-spec

ref: b7d1bf89a0bba51749babd45ce9f9c14b72f2470 sr-ht-spec/db/space.go -rw-r--r-- 3.4 KiB
b7d1bf89 — Eugene Blikh login: decode the unified-login cookie through ecore 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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package db

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

	"github.com/lib/pq"

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

// Space is one bare git repo: the unit of ownership, ACL and review policy.
// The repo on disk is the real thing; this row exists so spaces can be listed
// and so index bookkeeping has something to hang off. Ref.Owner never carries
// the leading '~'.
type Space struct {
	ID      int
	Ref     core.SpaceRef
	Created time.Time
}

const spaceSelect = `SELECT id, owner, name, created FROM space`

func scanSpace(sc rowScanner) (*Space, error) {
	var sp Space
	if err := sc.Scan(&sp.ID, &sp.Ref.Owner, &sp.Ref.Name, &sp.Created); err != nil {
		return nil, err
	}
	return &sp, nil
}

// CreateSpace inserts a space row. The name is validated with core before it
// reaches SQL: an owner or space name that is not a safe path segment would
// become a directory under the repos root, so it is rejected at every door
// rather than at whichever one happens to check.
//
// A uq_space_owner_name violation maps to ErrSpaceExists.
func (s *Store) CreateSpace(ctx context.Context, ref core.SpaceRef) (*Space, error) {
	if err := core.ValidateOwner(ref.Owner); err != nil {
		return nil, err
	}
	if err := core.ValidateSpaceName(ref.Name); err != nil {
		return nil, err
	}
	const q = `
INSERT INTO space (owner, name, created)
VALUES ($1, $2, $3)
RETURNING id, created`
	var sp Space
	sp.Ref = ref
	err := s.q.QueryRowContext(ctx, q, ref.Owner, ref.Name, time.Now().UTC()).
		Scan(&sp.ID, &sp.Created)
	if err != nil {
		var pqErr *pq.Error
		if errors.As(err, &pqErr) && pqErr.Code == "23505" {
			return nil, fmt.Errorf("%w: %s", ErrSpaceExists, ref)
		}
		return nil, fmt.Errorf("insert space %s: %w", ref, err)
	}
	return &sp, nil
}

// GetSpace resolves a space by owner and name. Returns ErrNotFound if no such
// space exists.
func (s *Store) GetSpace(ctx context.Context, ref core.SpaceRef) (*Space, error) {
	q := spaceSelect + ` WHERE owner = $1 AND name = $2`
	sp, err := scanSpace(s.q.QueryRowContext(ctx, q, ref.Owner, ref.Name))
	if errors.Is(err, sql.ErrNoRows) {
		return nil, ErrNotFound
	}
	if err != nil {
		return nil, fmt.Errorf("get space %s: %w", ref, err)
	}
	return sp, nil
}

// GetSpaceByID resolves a space by its primary key — the form every other table
// references it by. Returns ErrNotFound if no such space exists.
func (s *Store) GetSpaceByID(ctx context.Context, id int) (*Space, error) {
	q := spaceSelect + ` WHERE id = $1`
	sp, err := scanSpace(s.q.QueryRowContext(ctx, q, id))
	if errors.Is(err, sql.ErrNoRows) {
		return nil, ErrNotFound
	}
	if err != nil {
		return nil, fmt.Errorf("get space %d: %w", id, err)
	}
	return sp, nil
}

// ListSpaces returns every space, ordered by owner then name. There is one human
// on this instance and no visibility levels, so there is nothing to filter by:
// the list is the whole corpus, which is also exactly what the meta-project (a
// filter that excludes nothing) needs.
func (s *Store) ListSpaces(ctx context.Context) ([]*Space, error) {
	q := spaceSelect + ` ORDER BY owner, name`
	rows, err := s.q.QueryContext(ctx, q)
	if err != nil {
		return nil, fmt.Errorf("list spaces: %w", err)
	}
	defer rows.Close()
	var spaces []*Space
	for rows.Next() {
		sp, err := scanSpace(rows)
		if err != nil {
			return nil, fmt.Errorf("scan space: %w", err)
		}
		spaces = append(spaces, sp)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate spaces: %w", err)
	}
	return spaces, nil
}