+9
knotserver/db/init.go
+9
knotserver/db/init.go
···
43
43
id integer primary key autoincrement,
44
44
last_time_us integer not null
45
45
);
46
+
47
+
create table if not exists oplog (
48
+
tid text primary key,
49
+
did text not null,
50
+
repo text not null,
51
+
old_sha text not null,
52
+
new_sha text not null,
53
+
ref text not null
54
+
);
46
55
`)
47
56
if err != nil {
48
57
return nil, err
+63
knotserver/db/oplog.go
+63
knotserver/db/oplog.go
···
1
+
package db
2
+
3
+
import (
4
+
"fmt"
5
+
)
6
+
7
+
type Op struct {
8
+
Tid string // time based ID, easy to enumerate & monotonic
9
+
Did string // did of pusher
10
+
Repo string // <did/repo> fully qualified repo
11
+
OldSha string // old sha of reference being updated
12
+
NewSha string // new sha of reference being updated
13
+
Ref string // the reference being updated
14
+
}
15
+
16
+
func (d *DB) InsertOp(op Op) error {
17
+
_, err := d.db.Exec(
18
+
`insert into oplog (tid, did, repo, old_sha, new_sha, ref) values (?, ?, ?, ?, ?, ?)`,
19
+
op.Tid,
20
+
op.Did,
21
+
op.Repo,
22
+
op.OldSha,
23
+
op.NewSha,
24
+
op.Ref,
25
+
)
26
+
return err
27
+
}
28
+
29
+
func (d *DB) GetOps(cursor string) ([]Op, error) {
30
+
whereClause := ""
31
+
args := []any{}
32
+
if cursor != "" {
33
+
whereClause = "where tid > ?"
34
+
args = append(args, cursor)
35
+
}
36
+
37
+
query := fmt.Sprintf(`
38
+
select tid, did, repo, old_sha, new_sha, ref
39
+
from oplog
40
+
%s
41
+
order by tid asc
42
+
limit 100
43
+
`, whereClause)
44
+
45
+
rows, err := d.db.Query(query, args...)
46
+
if err != nil {
47
+
return nil, err
48
+
}
49
+
defer rows.Close()
50
+
51
+
var ops []Op
52
+
for rows.Next() {
53
+
var op Op
54
+
rows.Scan(&op.Tid, &op.Did, &op.Repo, &op.OldSha, &op.NewSha, &op.Ref)
55
+
ops = append(ops, op)
56
+
}
57
+
58
+
if err := rows.Err(); err != nil {
59
+
return nil, err
60
+
}
61
+
62
+
return ops, nil
63
+
}
+56
knotserver/internal.go
+56
knotserver/internal.go
···
1
1
package knotserver
2
2
3
3
import (
4
+
"bufio"
4
5
"context"
5
6
"log/slog"
6
7
"net/http"
8
+
"path/filepath"
9
+
"strings"
7
10
8
11
"github.com/go-chi/chi/v5"
9
12
"github.com/go-chi/chi/v5/middleware"
···
54
57
return
55
58
}
56
59
60
+
func (h *InternalHandle) PostReceiveHook(w http.ResponseWriter, r *http.Request) {
61
+
l := h.l.With("handler", "PostReceiveHook")
62
+
63
+
gitAbsoluteDir := r.Header.Get("X-Git-Dir")
64
+
gitRelativeDir, err := filepath.Rel(h.c.Repo.ScanPath, gitAbsoluteDir)
65
+
if err != nil {
66
+
l.Error("failed to calculate relative git dir", "scanPath", h.c.Repo.ScanPath, "gitAbsoluteDir", gitAbsoluteDir)
67
+
return
68
+
}
69
+
gitUserDid := r.Header.Get("X-Git-User-Did")
70
+
71
+
var ops []db.Op
72
+
scanner := bufio.NewScanner(r.Body)
73
+
for scanner.Scan() {
74
+
line := scanner.Text()
75
+
parts := strings.SplitN(line, " ", 3)
76
+
if len(parts) != 3 {
77
+
l.Error("invalid payload", "parts", parts)
78
+
continue
79
+
}
80
+
81
+
tid := TID()
82
+
oldSha := parts[0]
83
+
newSha := parts[1]
84
+
ref := parts[2]
85
+
op := db.Op{
86
+
Tid: tid,
87
+
Did: gitUserDid,
88
+
Repo: gitRelativeDir,
89
+
OldSha: oldSha,
90
+
NewSha: newSha,
91
+
Ref: ref,
92
+
}
93
+
ops = append(ops, op)
94
+
}
95
+
96
+
if err := scanner.Err(); err != nil {
97
+
l.Error("failed to read payload", "err", err)
98
+
return
99
+
}
100
+
101
+
for _, op := range ops {
102
+
err := h.db.InsertOp(op)
103
+
if err != nil {
104
+
l.Error("failed to insert op", "err", err, "op", op)
105
+
continue
106
+
}
107
+
}
108
+
109
+
return
110
+
}
111
+
57
112
func Internal(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, l *slog.Logger) http.Handler {
58
113
r := chi.NewRouter()
59
114
···
66
121
67
122
r.Get("/push-allowed", h.PushAllowed)
68
123
r.Get("/keys", h.InternalKeys)
124
+
r.Post("/hooks/post-receive", h.PostReceiveHook)
69
125
r.Mount("/debug", middleware.Profiler())
70
126
71
127
return r
+7
knotserver/util.go
+7
knotserver/util.go
···
5
5
"os"
6
6
"path/filepath"
7
7
8
+
"github.com/bluesky-social/indigo/atproto/syntax"
8
9
securejoin "github.com/cyphar/filepath-securejoin"
9
10
"github.com/go-chi/chi/v5"
10
11
"github.com/microcosm-cc/bluemonday"
···
43
44
func setMIME(w http.ResponseWriter, mime string) {
44
45
w.Header().Add("Content-Type", mime)
45
46
}
47
+
48
+
var TIDClock = syntax.NewTIDClock(0)
49
+
50
+
func TID() string {
51
+
return TIDClock.Next().String()
52
+
}