+201
-91
appview/ingester.go
+201
-91
appview/ingester.go
···
3
import (
4
"context"
5
"encoding/json"
6
-
"errors"
7
"fmt"
8
-
"io"
9
-
"log"
10
-
"net/http"
11
-
"strings"
12
"time"
13
14
"github.com/bluesky-social/indigo/atproto/syntax"
···
16
"github.com/go-git/go-git/v5/plumbing"
17
"github.com/ipfs/go-cid"
18
"tangled.sh/tangled.sh/core/api/tangled"
19
"tangled.sh/tangled.sh/core/appview/db"
20
"tangled.sh/tangled.sh/core/rbac"
21
)
22
23
-
type Ingester func(ctx context.Context, e *models.Event) error
24
25
-
func Ingest(d db.DbWrapper, enforcer *rbac.Enforcer) Ingester {
26
return func(ctx context.Context, e *models.Event) error {
27
var err error
28
defer func() {
29
eventTime := e.TimeUS
30
lastTimeUs := eventTime + 1
31
-
if err := d.SaveLastTimeUs(lastTimeUs); err != nil {
32
err = fmt.Errorf("(deferred) failed to save last time us: %w", err)
33
}
34
}()
···
39
40
switch e.Commit.Collection {
41
case tangled.GraphFollowNSID:
42
-
ingestFollow(&d, e)
43
case tangled.FeedStarNSID:
44
-
ingestStar(&d, e)
45
case tangled.PublicKeyNSID:
46
-
ingestPublicKey(&d, e)
47
case tangled.RepoArtifactNSID:
48
-
ingestArtifact(&d, e, enforcer)
49
case tangled.ActorProfileNSID:
50
-
ingestProfile(&d, e)
51
case tangled.SpindleMemberNSID:
52
-
ingestSpindleMember(&d, e, enforcer)
53
case tangled.SpindleNSID:
54
-
ingestSpindle(&d, e, true) // TODO: change this to dynamic
55
}
56
57
return err
58
}
59
}
60
61
-
func ingestStar(d *db.DbWrapper, e *models.Event) error {
62
var err error
63
did := e.Did
64
65
switch e.Commit.Operation {
66
case models.CommitOperationCreate, models.CommitOperationUpdate:
67
var subjectUri syntax.ATURI
···
70
record := tangled.FeedStar{}
71
err := json.Unmarshal(raw, &record)
72
if err != nil {
73
-
log.Println("invalid record")
74
return err
75
}
76
77
subjectUri, err = syntax.ParseATURI(record.Subject)
78
if err != nil {
79
-
log.Println("invalid record")
80
return err
81
}
82
-
err = db.AddStar(d, did, subjectUri, e.Commit.RKey)
83
case models.CommitOperationDelete:
84
-
err = db.DeleteStarByRkey(d, did, e.Commit.RKey)
85
}
86
87
if err != nil {
···
91
return nil
92
}
93
94
-
func ingestFollow(d *db.DbWrapper, e *models.Event) error {
95
var err error
96
did := e.Did
97
98
switch e.Commit.Operation {
99
case models.CommitOperationCreate, models.CommitOperationUpdate:
···
101
record := tangled.GraphFollow{}
102
err = json.Unmarshal(raw, &record)
103
if err != nil {
104
-
log.Println("invalid record")
105
return err
106
}
107
108
subjectDid := record.Subject
109
-
err = db.AddFollow(d, did, subjectDid, e.Commit.RKey)
110
case models.CommitOperationDelete:
111
-
err = db.DeleteFollowByRkey(d, did, e.Commit.RKey)
112
}
113
114
if err != nil {
···
118
return nil
119
}
120
121
-
func ingestPublicKey(d *db.DbWrapper, e *models.Event) error {
122
did := e.Did
123
var err error
124
125
switch e.Commit.Operation {
126
case models.CommitOperationCreate, models.CommitOperationUpdate:
127
-
log.Println("processing add of pubkey")
128
raw := json.RawMessage(e.Commit.Record)
129
record := tangled.PublicKey{}
130
err = json.Unmarshal(raw, &record)
131
if err != nil {
132
-
log.Printf("invalid record: %s", err)
133
return err
134
}
135
136
name := record.Name
137
key := record.Key
138
-
err = db.AddPublicKey(d, did, name, key, e.Commit.RKey)
139
case models.CommitOperationDelete:
140
-
log.Println("processing delete of pubkey")
141
-
err = db.DeletePublicKeyByRkey(d, did, e.Commit.RKey)
142
}
143
144
if err != nil {
···
148
return nil
149
}
150
151
-
func ingestArtifact(d *db.DbWrapper, e *models.Event, enforcer *rbac.Enforcer) error {
152
did := e.Did
153
var err error
154
155
switch e.Commit.Operation {
156
case models.CommitOperationCreate, models.CommitOperationUpdate:
157
raw := json.RawMessage(e.Commit.Record)
158
record := tangled.RepoArtifact{}
159
err = json.Unmarshal(raw, &record)
160
if err != nil {
161
-
log.Printf("invalid record: %s", err)
162
return err
163
}
164
···
167
return err
168
}
169
170
-
repo, err := db.GetRepoByAtUri(d, repoAt.String())
171
if err != nil {
172
return err
173
}
174
175
-
ok, err := enforcer.E.Enforce(did, repo.Knot, repo.DidSlashRepo(), "repo:push")
176
if err != nil || !ok {
177
return err
178
}
···
194
MimeType: record.Artifact.MimeType,
195
}
196
197
-
err = db.AddArtifact(d, artifact)
198
case models.CommitOperationDelete:
199
-
err = db.DeleteArtifact(d, db.FilterEq("did", did), db.FilterEq("rkey", e.Commit.RKey))
200
}
201
202
if err != nil {
···
206
return nil
207
}
208
209
-
func ingestProfile(d *db.DbWrapper, e *models.Event) error {
210
did := e.Did
211
var err error
212
213
if e.Commit.RKey != "self" {
214
return fmt.Errorf("ingestProfile only ingests `self` record")
···
220
record := tangled.ActorProfile{}
221
err = json.Unmarshal(raw, &record)
222
if err != nil {
223
-
log.Printf("invalid record: %s", err)
224
return err
225
}
226
···
267
PinnedRepos: pinned,
268
}
269
270
-
ddb, ok := d.Execer.(*db.DB)
271
if !ok {
272
return fmt.Errorf("failed to index profile record, invalid db cast")
273
}
···
284
285
err = db.UpsertProfile(tx, &profile)
286
case models.CommitOperationDelete:
287
-
err = db.DeleteArtifact(d, db.FilterEq("did", did), db.FilterEq("rkey", e.Commit.RKey))
288
}
289
290
if err != nil {
···
294
return nil
295
}
296
297
-
func ingestSpindleMember(_ *db.DbWrapper, e *models.Event, enforcer *rbac.Enforcer) error {
298
did := e.Did
299
var err error
300
301
switch e.Commit.Operation {
302
case models.CommitOperationCreate:
303
raw := json.RawMessage(e.Commit.Record)
304
record := tangled.SpindleMember{}
305
err = json.Unmarshal(raw, &record)
306
if err != nil {
307
-
log.Printf("invalid record: %s", err)
308
return err
309
}
310
311
// only spindle owner can invite to spindles
312
-
ok, err := enforcer.IsSpindleInviteAllowed(did, record.Instance)
313
if err != nil || !ok {
314
return fmt.Errorf("failed to enforce permissions: %w", err)
315
}
316
317
-
err = enforcer.AddSpindleMember(record.Instance, record.Subject)
318
if err != nil {
319
-
return fmt.Errorf("failed to add member: %w", err)
320
}
321
}
322
323
return nil
324
}
325
326
-
func ingestSpindle(d *db.DbWrapper, e *models.Event, dev bool) error {
327
did := e.Did
328
var err error
329
330
switch e.Commit.Operation {
331
case models.CommitOperationCreate:
332
raw := json.RawMessage(e.Commit.Record)
333
record := tangled.Spindle{}
334
err = json.Unmarshal(raw, &record)
335
if err != nil {
336
-
log.Printf("invalid record: %s", err)
337
return err
338
}
339
340
-
// this is a special record whose rkey is the instance of the spindle itself
341
instance := e.Commit.RKey
342
343
-
owner, err := fetchOwner(context.TODO(), instance, dev)
344
if err != nil {
345
-
log.Printf("failed to verify owner of %s: %s", instance, err)
346
return err
347
}
348
349
-
// verify that the spindle owner points back to this did
350
-
if owner != did {
351
-
log.Printf("incorrect owner for domain: %s, %s != %s", instance, owner, did)
352
return err
353
}
354
355
-
// mark this spindle as registered
356
-
ddb, ok := d.Execer.(*db.DB)
357
if !ok {
358
return fmt.Errorf("failed to index profile record, invalid db cast")
359
}
360
361
-
_, err = db.VerifySpindle(
362
-
ddb,
363
db.FilterEq("owner", did),
364
db.FilterEq("instance", instance),
365
)
366
367
-
return err
368
-
}
369
370
-
return nil
371
-
}
372
373
-
func fetchOwner(ctx context.Context, domain string, dev bool) (string, error) {
374
-
scheme := "https"
375
-
if dev {
376
-
scheme = "http"
377
-
}
378
-
379
-
url := fmt.Sprintf("%s://%s/owner", scheme, domain)
380
-
req, err := http.NewRequest("GET", url, nil)
381
-
if err != nil {
382
-
return "", err
383
-
}
384
-
385
-
client := &http.Client{
386
-
Timeout: 1 * time.Second,
387
-
}
388
-
389
-
resp, err := client.Do(req.WithContext(ctx))
390
-
if err != nil || resp.StatusCode != 200 {
391
-
return "", errors.New("failed to fetch /owner")
392
-
}
393
-
394
-
body, err := io.ReadAll(io.LimitReader(resp.Body, 1024)) // read atmost 1kb of data
395
-
if err != nil {
396
-
return "", fmt.Errorf("failed to read /owner response: %w", err)
397
-
}
398
-
399
-
did := strings.TrimSpace(string(body))
400
-
if did == "" {
401
-
return "", errors.New("empty DID in /owner response")
402
}
403
404
-
return did, nil
405
}
···
3
import (
4
"context"
5
"encoding/json"
6
"fmt"
7
+
"log/slog"
8
"time"
9
10
"github.com/bluesky-social/indigo/atproto/syntax"
···
12
"github.com/go-git/go-git/v5/plumbing"
13
"github.com/ipfs/go-cid"
14
"tangled.sh/tangled.sh/core/api/tangled"
15
+
"tangled.sh/tangled.sh/core/appview/config"
16
"tangled.sh/tangled.sh/core/appview/db"
17
+
"tangled.sh/tangled.sh/core/appview/idresolver"
18
+
"tangled.sh/tangled.sh/core/appview/spindleverify"
19
"tangled.sh/tangled.sh/core/rbac"
20
)
21
22
+
type Ingester struct {
23
+
Db db.DbWrapper
24
+
Enforcer *rbac.Enforcer
25
+
IdResolver *idresolver.Resolver
26
+
Config *config.Config
27
+
Logger *slog.Logger
28
+
}
29
+
30
+
type processFunc func(ctx context.Context, e *models.Event) error
31
32
+
func (i *Ingester) Ingest() processFunc {
33
return func(ctx context.Context, e *models.Event) error {
34
var err error
35
defer func() {
36
eventTime := e.TimeUS
37
lastTimeUs := eventTime + 1
38
+
if err := i.Db.SaveLastTimeUs(lastTimeUs); err != nil {
39
err = fmt.Errorf("(deferred) failed to save last time us: %w", err)
40
}
41
}()
···
46
47
switch e.Commit.Collection {
48
case tangled.GraphFollowNSID:
49
+
err = i.ingestFollow(e)
50
case tangled.FeedStarNSID:
51
+
err = i.ingestStar(e)
52
case tangled.PublicKeyNSID:
53
+
err = i.ingestPublicKey(e)
54
case tangled.RepoArtifactNSID:
55
+
err = i.ingestArtifact(e)
56
case tangled.ActorProfileNSID:
57
+
err = i.ingestProfile(e)
58
case tangled.SpindleMemberNSID:
59
+
err = i.ingestSpindleMember(e)
60
case tangled.SpindleNSID:
61
+
err = i.ingestSpindle(e)
62
+
}
63
+
64
+
if err != nil {
65
+
l := i.Logger.With("nsid", e.Commit.Collection)
66
+
l.Error("error ingesting record", "err", err)
67
}
68
69
return err
70
}
71
}
72
73
+
func (i *Ingester) ingestStar(e *models.Event) error {
74
var err error
75
did := e.Did
76
77
+
l := i.Logger.With("handler", "ingestStar")
78
+
l = l.With("nsid", e.Commit.Collection)
79
+
80
switch e.Commit.Operation {
81
case models.CommitOperationCreate, models.CommitOperationUpdate:
82
var subjectUri syntax.ATURI
···
85
record := tangled.FeedStar{}
86
err := json.Unmarshal(raw, &record)
87
if err != nil {
88
+
l.Error("invalid record", "err", err)
89
return err
90
}
91
92
subjectUri, err = syntax.ParseATURI(record.Subject)
93
if err != nil {
94
+
l.Error("invalid record", "err", err)
95
return err
96
}
97
+
err = db.AddStar(i.Db, did, subjectUri, e.Commit.RKey)
98
case models.CommitOperationDelete:
99
+
err = db.DeleteStarByRkey(i.Db, did, e.Commit.RKey)
100
}
101
102
if err != nil {
···
106
return nil
107
}
108
109
+
func (i *Ingester) ingestFollow(e *models.Event) error {
110
var err error
111
did := e.Did
112
+
113
+
l := i.Logger.With("handler", "ingestFollow")
114
+
l = l.With("nsid", e.Commit.Collection)
115
116
switch e.Commit.Operation {
117
case models.CommitOperationCreate, models.CommitOperationUpdate:
···
119
record := tangled.GraphFollow{}
120
err = json.Unmarshal(raw, &record)
121
if err != nil {
122
+
l.Error("invalid record", "err", err)
123
return err
124
}
125
126
subjectDid := record.Subject
127
+
err = db.AddFollow(i.Db, did, subjectDid, e.Commit.RKey)
128
case models.CommitOperationDelete:
129
+
err = db.DeleteFollowByRkey(i.Db, did, e.Commit.RKey)
130
}
131
132
if err != nil {
···
136
return nil
137
}
138
139
+
func (i *Ingester) ingestPublicKey(e *models.Event) error {
140
did := e.Did
141
var err error
142
143
+
l := i.Logger.With("handler", "ingestPublicKey")
144
+
l = l.With("nsid", e.Commit.Collection)
145
+
146
switch e.Commit.Operation {
147
case models.CommitOperationCreate, models.CommitOperationUpdate:
148
+
l.Debug("processing add of pubkey")
149
raw := json.RawMessage(e.Commit.Record)
150
record := tangled.PublicKey{}
151
err = json.Unmarshal(raw, &record)
152
if err != nil {
153
+
l.Error("invalid record", "err", err)
154
return err
155
}
156
157
name := record.Name
158
key := record.Key
159
+
err = db.AddPublicKey(i.Db, did, name, key, e.Commit.RKey)
160
case models.CommitOperationDelete:
161
+
l.Debug("processing delete of pubkey")
162
+
err = db.DeletePublicKeyByRkey(i.Db, did, e.Commit.RKey)
163
}
164
165
if err != nil {
···
169
return nil
170
}
171
172
+
func (i *Ingester) ingestArtifact(e *models.Event) error {
173
did := e.Did
174
var err error
175
176
+
l := i.Logger.With("handler", "ingestArtifact")
177
+
l = l.With("nsid", e.Commit.Collection)
178
+
179
switch e.Commit.Operation {
180
case models.CommitOperationCreate, models.CommitOperationUpdate:
181
raw := json.RawMessage(e.Commit.Record)
182
record := tangled.RepoArtifact{}
183
err = json.Unmarshal(raw, &record)
184
if err != nil {
185
+
l.Error("invalid record", "err", err)
186
return err
187
}
188
···
191
return err
192
}
193
194
+
repo, err := db.GetRepoByAtUri(i.Db, repoAt.String())
195
if err != nil {
196
return err
197
}
198
199
+
ok, err := i.Enforcer.E.Enforce(did, repo.Knot, repo.DidSlashRepo(), "repo:push")
200
if err != nil || !ok {
201
return err
202
}
···
218
MimeType: record.Artifact.MimeType,
219
}
220
221
+
err = db.AddArtifact(i.Db, artifact)
222
case models.CommitOperationDelete:
223
+
err = db.DeleteArtifact(i.Db, db.FilterEq("did", did), db.FilterEq("rkey", e.Commit.RKey))
224
}
225
226
if err != nil {
···
230
return nil
231
}
232
233
+
func (i *Ingester) ingestProfile(e *models.Event) error {
234
did := e.Did
235
var err error
236
+
237
+
l := i.Logger.With("handler", "ingestProfile")
238
+
l = l.With("nsid", e.Commit.Collection)
239
240
if e.Commit.RKey != "self" {
241
return fmt.Errorf("ingestProfile only ingests `self` record")
···
247
record := tangled.ActorProfile{}
248
err = json.Unmarshal(raw, &record)
249
if err != nil {
250
+
l.Error("invalid record", "err", err)
251
return err
252
}
253
···
294
PinnedRepos: pinned,
295
}
296
297
+
ddb, ok := i.Db.Execer.(*db.DB)
298
if !ok {
299
return fmt.Errorf("failed to index profile record, invalid db cast")
300
}
···
311
312
err = db.UpsertProfile(tx, &profile)
313
case models.CommitOperationDelete:
314
+
err = db.DeleteArtifact(i.Db, db.FilterEq("did", did), db.FilterEq("rkey", e.Commit.RKey))
315
}
316
317
if err != nil {
···
321
return nil
322
}
323
324
+
func (i *Ingester) ingestSpindleMember(e *models.Event) error {
325
did := e.Did
326
var err error
327
328
+
l := i.Logger.With("handler", "ingestSpindleMember")
329
+
l = l.With("nsid", e.Commit.Collection)
330
+
331
switch e.Commit.Operation {
332
case models.CommitOperationCreate:
333
raw := json.RawMessage(e.Commit.Record)
334
record := tangled.SpindleMember{}
335
err = json.Unmarshal(raw, &record)
336
if err != nil {
337
+
l.Error("invalid record", "err", err)
338
return err
339
}
340
341
// only spindle owner can invite to spindles
342
+
ok, err := i.Enforcer.IsSpindleInviteAllowed(did, record.Instance)
343
if err != nil || !ok {
344
return fmt.Errorf("failed to enforce permissions: %w", err)
345
}
346
347
+
memberId, err := i.IdResolver.ResolveIdent(context.Background(), record.Subject)
348
if err != nil {
349
+
return err
350
+
}
351
+
352
+
if memberId.Handle.IsInvalidHandle() {
353
+
return err
354
+
}
355
+
356
+
ddb, ok := i.Db.Execer.(*db.DB)
357
+
if !ok {
358
+
return fmt.Errorf("failed to index profile record, invalid db cast")
359
+
}
360
+
361
+
err = db.AddSpindleMember(ddb, db.SpindleMember{
362
+
Did: syntax.DID(did),
363
+
Rkey: e.Commit.RKey,
364
+
Instance: record.Instance,
365
+
Subject: memberId.DID,
366
+
})
367
+
if !ok {
368
+
return fmt.Errorf("failed to add to db: %w", err)
369
+
}
370
+
371
+
err = i.Enforcer.AddSpindleMember(record.Instance, memberId.DID.String())
372
+
if err != nil {
373
+
return fmt.Errorf("failed to update ACLs: %w", err)
374
+
}
375
+
case models.CommitOperationDelete:
376
+
rkey := e.Commit.RKey
377
+
378
+
ddb, ok := i.Db.Execer.(*db.DB)
379
+
if !ok {
380
+
return fmt.Errorf("failed to index profile record, invalid db cast")
381
+
}
382
+
383
+
// get record from db first
384
+
members, err := db.GetSpindleMembers(
385
+
ddb,
386
+
db.FilterEq("did", did),
387
+
db.FilterEq("rkey", rkey),
388
+
)
389
+
if err != nil || len(members) != 1 {
390
+
return fmt.Errorf("failed to get member: %w, len(members) = %d", err, len(members))
391
+
}
392
+
member := members[0]
393
+
394
+
tx, err := ddb.Begin()
395
+
if err != nil {
396
+
return fmt.Errorf("failed to start txn: %w", err)
397
+
}
398
+
399
+
// remove record by rkey && update enforcer
400
+
if err = db.RemoveSpindleMember(
401
+
tx,
402
+
db.FilterEq("did", did),
403
+
db.FilterEq("rkey", rkey),
404
+
); err != nil {
405
+
return fmt.Errorf("failed to remove from db: %w", err)
406
+
}
407
+
408
+
// update enforcer
409
+
err = i.Enforcer.RemoveSpindleMember(member.Instance, member.Subject.String())
410
+
if err != nil {
411
+
return fmt.Errorf("failed to update ACLs: %w", err)
412
+
}
413
+
414
+
if err = tx.Commit(); err != nil {
415
+
return fmt.Errorf("failed to commit txn: %w", err)
416
+
}
417
+
418
+
if err = i.Enforcer.E.SavePolicy(); err != nil {
419
+
return fmt.Errorf("failed to save ACLs: %w", err)
420
}
421
}
422
423
return nil
424
}
425
426
+
func (i *Ingester) ingestSpindle(e *models.Event) error {
427
did := e.Did
428
var err error
429
430
+
l := i.Logger.With("handler", "ingestSpindle")
431
+
l = l.With("nsid", e.Commit.Collection)
432
+
433
switch e.Commit.Operation {
434
case models.CommitOperationCreate:
435
raw := json.RawMessage(e.Commit.Record)
436
record := tangled.Spindle{}
437
err = json.Unmarshal(raw, &record)
438
if err != nil {
439
+
l.Error("invalid record", "err", err)
440
return err
441
}
442
443
instance := e.Commit.RKey
444
445
+
ddb, ok := i.Db.Execer.(*db.DB)
446
+
if !ok {
447
+
return fmt.Errorf("failed to index profile record, invalid db cast")
448
+
}
449
+
450
+
err := db.AddSpindle(ddb, db.Spindle{
451
+
Owner: syntax.DID(did),
452
+
Instance: instance,
453
+
})
454
if err != nil {
455
+
l.Error("failed to add spindle to db", "err", err, "instance", instance)
456
return err
457
}
458
459
+
err = spindleverify.RunVerification(context.Background(), instance, did, i.Config.Core.Dev)
460
+
if err != nil {
461
+
l.Error("failed to add spindle to db", "err", err, "instance", instance)
462
return err
463
}
464
465
+
_, err = spindleverify.MarkVerified(ddb, i.Enforcer, instance, did)
466
+
if err != nil {
467
+
return fmt.Errorf("failed to mark verified: %w", err)
468
+
}
469
+
470
+
return nil
471
+
472
+
case models.CommitOperationDelete:
473
+
instance := e.Commit.RKey
474
+
475
+
ddb, ok := i.Db.Execer.(*db.DB)
476
if !ok {
477
return fmt.Errorf("failed to index profile record, invalid db cast")
478
}
479
480
+
tx, err := ddb.Begin()
481
+
if err != nil {
482
+
return err
483
+
}
484
+
defer func() {
485
+
tx.Rollback()
486
+
i.Enforcer.E.LoadPolicy()
487
+
}()
488
+
489
+
err = db.DeleteSpindle(
490
+
tx,
491
db.FilterEq("owner", did),
492
db.FilterEq("instance", instance),
493
)
494
+
if err != nil {
495
+
return err
496
+
}
497
498
+
err = i.Enforcer.RemoveSpindle(instance)
499
+
if err != nil {
500
+
return err
501
+
}
502
503
+
err = tx.Commit()
504
+
if err != nil {
505
+
return err
506
+
}
507
508
+
err = i.Enforcer.E.SavePolicy()
509
+
if err != nil {
510
+
return err
511
+
}
512
}
513
514
+
return nil
515
}
+12
-1
appview/state/state.go
+12
-1
appview/state/state.go
···
31
"tangled.sh/tangled.sh/core/eventconsumer"
32
"tangled.sh/tangled.sh/core/jetstream"
33
"tangled.sh/tangled.sh/core/knotclient"
34
"tangled.sh/tangled.sh/core/rbac"
35
)
36
···
93
tangled.PublicKeyNSID,
94
tangled.RepoArtifactNSID,
95
tangled.ActorProfileNSID,
96
},
97
nil,
98
slog.Default(),
···
106
if err != nil {
107
return nil, fmt.Errorf("failed to create jetstream client: %w", err)
108
}
109
-
err = jc.StartJetstream(ctx, appview.Ingest(wrapper, enforcer))
110
if err != nil {
111
return nil, fmt.Errorf("failed to start jetstream watcher: %w", err)
112
}
···
31
"tangled.sh/tangled.sh/core/eventconsumer"
32
"tangled.sh/tangled.sh/core/jetstream"
33
"tangled.sh/tangled.sh/core/knotclient"
34
+
tlog "tangled.sh/tangled.sh/core/log"
35
"tangled.sh/tangled.sh/core/rbac"
36
)
37
···
94
tangled.PublicKeyNSID,
95
tangled.RepoArtifactNSID,
96
tangled.ActorProfileNSID,
97
+
tangled.SpindleMemberNSID,
98
+
tangled.SpindleNSID,
99
},
100
nil,
101
slog.Default(),
···
109
if err != nil {
110
return nil, fmt.Errorf("failed to create jetstream client: %w", err)
111
}
112
+
113
+
ingester := appview.Ingester{
114
+
Db: wrapper,
115
+
Enforcer: enforcer,
116
+
IdResolver: res,
117
+
Config: config,
118
+
Logger: tlog.New("ingester"),
119
+
}
120
+
err = jc.StartJetstream(ctx, ingester.Ingest())
121
if err != nil {
122
return nil, fmt.Errorf("failed to start jetstream watcher: %w", err)
123
}