+9
-2
knotserver/db/oplog.go
+9
-2
knotserver/db/oplog.go
···
2
2
3
3
import (
4
4
"fmt"
5
+
6
+
"tangled.sh/tangled.sh/core/knotserver/notifier"
5
7
)
6
8
7
9
type Op struct {
···
13
15
Ref string // the reference being updated
14
16
}
15
17
16
-
func (d *DB) InsertOp(op Op) error {
18
+
func (d *DB) InsertOp(op Op, notifier *notifier.Notifier) error {
17
19
_, err := d.db.Exec(
18
20
`insert into oplog (tid, did, repo, old_sha, new_sha, ref) values (?, ?, ?, ?, ?, ?)`,
19
21
op.Tid,
···
23
25
op.NewSha,
24
26
op.Ref,
25
27
)
26
-
return err
28
+
if err != nil {
29
+
return err
30
+
}
31
+
32
+
notifier.NotifyAll()
33
+
return nil
27
34
}
28
35
29
36
func (d *DB) GetOps(cursor string) ([]Op, error) {
+4
-1
knotserver/handler.go
+4
-1
knotserver/handler.go
···
11
11
"tangled.sh/tangled.sh/core/jetstream"
12
12
"tangled.sh/tangled.sh/core/knotserver/config"
13
13
"tangled.sh/tangled.sh/core/knotserver/db"
14
+
"tangled.sh/tangled.sh/core/knotserver/notifier"
14
15
"tangled.sh/tangled.sh/core/rbac"
15
16
)
16
17
···
24
25
jc *jetstream.JetstreamClient
25
26
e *rbac.Enforcer
26
27
l *slog.Logger
28
+
n *notifier.Notifier
27
29
28
30
// init is a channel that is closed when the knot has been initailized
29
31
// i.e. when the first user (knot owner) has been added.
···
31
33
knotInitialized bool
32
34
}
33
35
34
-
func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, jc *jetstream.JetstreamClient, l *slog.Logger) (http.Handler, error) {
36
+
func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, jc *jetstream.JetstreamClient, l *slog.Logger, n *notifier.Notifier) (http.Handler, error) {
35
37
r := chi.NewRouter()
36
38
37
39
h := Handle{
···
40
42
e: e,
41
43
l: l,
42
44
jc: jc,
45
+
n: n,
43
46
init: make(chan struct{}),
44
47
}
45
48
+5
-2
knotserver/internal.go
+5
-2
knotserver/internal.go
···
12
12
"github.com/go-chi/chi/v5/middleware"
13
13
"tangled.sh/tangled.sh/core/knotserver/config"
14
14
"tangled.sh/tangled.sh/core/knotserver/db"
15
+
"tangled.sh/tangled.sh/core/knotserver/notifier"
15
16
"tangled.sh/tangled.sh/core/rbac"
16
17
)
17
18
···
20
21
c *config.Config
21
22
e *rbac.Enforcer
22
23
l *slog.Logger
24
+
n *notifier.Notifier
23
25
}
24
26
25
27
func (h *InternalHandle) PushAllowed(w http.ResponseWriter, r *http.Request) {
···
99
101
}
100
102
101
103
for _, op := range ops {
102
-
err := h.db.InsertOp(op)
104
+
err := h.db.InsertOp(op, h.n)
103
105
if err != nil {
104
106
l.Error("failed to insert op", "err", err, "op", op)
105
107
continue
···
109
111
return
110
112
}
111
113
112
-
func Internal(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, l *slog.Logger) http.Handler {
114
+
func Internal(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, l *slog.Logger, n *notifier.Notifier) http.Handler {
113
115
r := chi.NewRouter()
114
116
115
117
h := InternalHandle{
···
117
119
c,
118
120
e,
119
121
l,
122
+
n,
120
123
}
121
124
122
125
r.Get("/push-allowed", h.PushAllowed)
+43
knotserver/notifier/notifier.go
+43
knotserver/notifier/notifier.go
···
1
+
package notifier
2
+
3
+
import (
4
+
"sync"
5
+
)
6
+
7
+
type Notifier struct {
8
+
subscribers map[chan struct{}]struct{}
9
+
mu sync.Mutex
10
+
}
11
+
12
+
func New() Notifier {
13
+
return Notifier{
14
+
subscribers: make(map[chan struct{}]struct{}),
15
+
}
16
+
}
17
+
18
+
func (n *Notifier) Subscribe() chan struct{} {
19
+
ch := make(chan struct{}, 1)
20
+
n.mu.Lock()
21
+
n.subscribers[ch] = struct{}{}
22
+
n.mu.Unlock()
23
+
return ch
24
+
}
25
+
26
+
func (n *Notifier) Unsubscribe(ch chan struct{}) {
27
+
n.mu.Lock()
28
+
delete(n.subscribers, ch)
29
+
close(ch)
30
+
n.mu.Unlock()
31
+
}
32
+
33
+
func (n *Notifier) NotifyAll() {
34
+
n.mu.Lock()
35
+
for ch := range n.subscribers {
36
+
select {
37
+
case ch <- struct{}{}:
38
+
default:
39
+
// avoid blocking if channel is full
40
+
}
41
+
}
42
+
n.mu.Unlock()
43
+
}
+6
-2
knotserver/server.go
+6
-2
knotserver/server.go
···
11
11
"tangled.sh/tangled.sh/core/jetstream"
12
12
"tangled.sh/tangled.sh/core/knotserver/config"
13
13
"tangled.sh/tangled.sh/core/knotserver/db"
14
+
"tangled.sh/tangled.sh/core/knotserver/notifier"
14
15
"tangled.sh/tangled.sh/core/log"
15
16
"tangled.sh/tangled.sh/core/rbac"
16
17
)
···
79
80
logger.Error("failed to setup jetstream", "error", err)
80
81
}
81
82
82
-
mux, err := Setup(ctx, c, db, e, jc, logger)
83
+
notifier := notifier.New()
84
+
85
+
mux, err := Setup(ctx, c, db, e, jc, logger, ¬ifier)
83
86
if err != nil {
84
87
return fmt.Errorf("failed to setup server: %w", err)
85
88
}
86
-
imux := Internal(ctx, c, db, e, iLogger)
89
+
90
+
imux := Internal(ctx, c, db, e, iLogger, ¬ifier)
87
91
88
92
logger.Info("starting internal server", "address", c.Server.InternalListenAddr)
89
93
go http.ListenAndServe(c.Server.InternalListenAddr, imux)