alpha
Login
or
Join now
willdot.net
/
distributed-pds
forked from
willdot.net/cocoon
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
A fork of the Cocoon PDS but being made more distributed.
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
initial commit (squashed)
author
Hailey
date
1 year ago
(Mar 28, 2025, 11:10 PM -0700)
commit
96f71046
96f7104622c6a00912f981ff1a686af7e9761725
+4565
52 changed files
Expand all
Collapse all
Unified
Split
.env.example
.gitignore
Makefile
README.md
blockstore
blockstore.go
cmd
admin
main.go
cocoon
main.go
go.mod
go.sum
identity
identity.go
mem_cache.go
passport.go
internal
helpers
helpers.go
models
models.go
plc
client.go
server
common.go
handle_actor_get_preferences.go
handle_actor_put_preferences.go
handle_health.go
handle_identity_update_handle.go
handle_proxy.go
handle_repo_apply_writes.go
handle_repo_create_record.go
handle_repo_describe_repo.go
handle_repo_get_record.go
handle_repo_list_records.go
handle_repo_list_repos.go
handle_repo_put_record.go
handle_repo_upload_blob.go
handle_robots.go
handle_root.go
handle_server_create_account.go
handle_server_create_invite_code.go
handle_server_create_session.go
handle_server_delete_session.go
handle_server_describe_server.go
handle_server_get_session.go
handle_server_refresh_session.go
handle_server_resolve_handle.go
handle_sync_get_blob.go
handle_sync_get_blocks.go
handle_sync_get_latest_commit.go
handle_sync_get_record.go
handle_sync_get_repo.go
handle_sync_get_repo_status.go
handle_sync_list_blobs.go
handle_sync_subscribe_repos.go
handle_well_known.go
repo.go
server.go
session.go
test.go
+2
.env.example
Reviewed
···
1
1
+
COCOON_DID=
2
2
+
COCOON_HOSTNAME=
+4
.gitignore
Reviewed
···
1
1
+
*.db
2
2
+
.env
3
3
+
/cocoon
4
4
+
*.key
+42
Makefile
Reviewed
···
1
1
+
SHELL = /bin/bash
2
2
+
.SHELLFLAGS = -o pipefail -c
3
3
+
GIT_TAG := $(shell git describe --tags --exact-match 2>/dev/null)
4
4
+
GIT_COMMIT := $(shell git rev-parse --short=9 HEAD)
5
5
+
VERSION := $(if $(GIT_TAG),$(GIT_TAG),dev-$(GIT_COMMIT))
6
6
+
7
7
+
.PHONY: help
8
8
+
help: ## Print info about all commands
9
9
+
@echo "Commands:"
10
10
+
@echo
11
11
+
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[01;32m%-20s\033[0m %s\n", $$1, $$2}'
12
12
+
13
13
+
.PHONY: build
14
14
+
build: ## Build all executables
15
15
+
go build -ldflags "-X main.Version=$(VERSION)" -o cocoon ./cmd/cocoon
16
16
+
17
17
+
.PHONY: run
18
18
+
run:
19
19
+
go build -ldflags "-X main.Version=dev-local" -o cocoon ./cmd/cocoon && ./cocoon run
20
20
+
21
21
+
.PHONY: all
22
22
+
all: build
23
23
+
24
24
+
.PHONY: test
25
25
+
test: ## Run tests
26
26
+
go clean -testcache && go test -v ./...
27
27
+
28
28
+
.PHONY: lint
29
29
+
lint: ## Verify code style and run static checks
30
30
+
go vet ./...
31
31
+
test -z $(gofmt -l ./...)
32
32
+
33
33
+
.PHONY: fmt
34
34
+
fmt: ## Run syntax re-formatting (modify in place)
35
35
+
go fmt ./...
36
36
+
37
37
+
.PHONY: check
38
38
+
check: ## Compile everything, checking syntax (does not output binaries)
39
39
+
go build ./...
40
40
+
41
41
+
.env:
42
42
+
if [ ! -f ".env" ]; then cp example.dev.env .env; fi
+63
README.md
Reviewed
···
1
1
+
# Cocoon
2
2
+
3
3
+
> [!WARNING]
4
4
+
You should not use this PDS. You should not rely on this code as a reference for a PDS implementation. You should not trust this code. Using this PDS implementation may result in data loss, corruption, etc.
5
5
+
6
6
+
Cocoon is a PDS implementation in Go. It is highly experimental, and is not ready for any production use.
7
7
+
8
8
+
### Impmlemented Endpoints
9
9
+
10
10
+
- [ ] com.atproto.identity.getRecommendedDidCredentials
11
11
+
- [ ] com.atproto.identity.requestPlcOperationSignature
12
12
+
- [x] com.atproto.identity.resolveHandle
13
13
+
- [ ] com.atproto.identity.signPlcOperation
14
14
+
- [ ] com.atproto.identity.submitPlcOperatioin
15
15
+
- [ ] com.atproto.identity.updateHandle
16
16
+
- [ ] com.atproto.label.queryLabels
17
17
+
- [ ] com.atproto.moderation.createReport
18
18
+
19
19
+
- [ ] com.atproto.repo.applyWrites
20
20
+
- [x] com.atproto.repo.createRecord
21
21
+
- [x] com.atproto.repo.putRecord
22
22
+
- [ ] com.atproto.repo.deleteRecord
23
23
+
- [x] com.atproto.repo.describeRepo
24
24
+
- [x] com.atproto.repo.getRecord
25
25
+
- [ ] com.atproto.repo.importRepo
26
26
+
- [ ] com.atproto.repo.listMissingBlobs
27
27
+
- [x] com.atproto.repo.listRecords
28
28
+
- [ ] com.atproto.repo.listMissingBlobs
29
29
+
30
30
+
31
31
+
- [ ] com.atproto.server.activateAccount
32
32
+
- [ ] com.atproto.server.checkAccountStatus
33
33
+
- [ ] com.atproto.server.confirmEmail
34
34
+
- [x] com.atproto.server.createAccount
35
35
+
- [ ] com.atproto.server.deactivateAccount
36
36
+
- [ ] com.atproto.server.deleteAccount
37
37
+
- [x] com.atproto.server.deleteSession
38
38
+
- [x] com.atproto.server.describeServer
39
39
+
- [ ] com.atproto.server.getAccountInviteCodes
40
40
+
- [ ] com.atproto.server.getServiceAuth
41
41
+
- [ ] com.atproto.server.listAppPasswords
42
42
+
- [x] com.atproto.server.refreshSession
43
43
+
- [ ] com.atproto.server.requestAccountDelete
44
44
+
- [ ] com.atproto.server.requestEmailConfirmation
45
45
+
- [ ] com.atproto.server.requestEmailUpdate
46
46
+
- [ ] com.atproto.server.requestPasswordReset
47
47
+
- [ ] com.atproto.server.reserveSigningKey
48
48
+
- [ ] com.atproto.server.resetPassword
49
49
+
- [ ] com.atproto.server.revokeAppPassword
50
50
+
- [ ] com.atproto.server.updateEmail
51
51
+
52
52
+
- [ ] com.atproto.sync.getBlob
53
53
+
- [x] com.atproto.sync.getBlocks
54
54
+
- [x] com.atproto.sync.getLatestCommit
55
55
+
- [x] com.atproto.sync.getRecord
56
56
+
- [x] com.atproto.sync.getRepoStatus
57
57
+
- [x] com.atproto.sync.getRepo
58
58
+
- [ ] com.atproto.sync.listBlobs
59
59
+
- [x] com.atproto.sync.listRepos
60
60
+
- [ ] com.atproto.sync.notifyOfUpdate - BGS doesn't even have this implemented lol
61
61
+
- [x] com.atproto.sync.requestCrawl
62
62
+
- [x] com.atproto.sync.subscribeRepos
63
63
+
+126
blockstore/blockstore.go
Reviewed
···
1
1
+
package blockstore
2
2
+
3
3
+
import (
4
4
+
"context"
5
5
+
"fmt"
6
6
+
7
7
+
"github.com/bluesky-social/indigo/atproto/syntax"
8
8
+
"github.com/haileyok/cocoon/models"
9
9
+
blocks "github.com/ipfs/go-block-format"
10
10
+
"github.com/ipfs/go-cid"
11
11
+
"gorm.io/gorm"
12
12
+
"gorm.io/gorm/clause"
13
13
+
)
14
14
+
15
15
+
type SqliteBlockstore struct {
16
16
+
db *gorm.DB
17
17
+
did string
18
18
+
readonly bool
19
19
+
inserts []blocks.Block
20
20
+
}
21
21
+
22
22
+
func New(did string, db *gorm.DB) *SqliteBlockstore {
23
23
+
return &SqliteBlockstore{
24
24
+
did: did,
25
25
+
db: db,
26
26
+
readonly: false,
27
27
+
inserts: []blocks.Block{},
28
28
+
}
29
29
+
}
30
30
+
31
31
+
func NewReadOnly(did string, db *gorm.DB) *SqliteBlockstore {
32
32
+
return &SqliteBlockstore{
33
33
+
did: did,
34
34
+
db: db,
35
35
+
readonly: true,
36
36
+
inserts: []blocks.Block{},
37
37
+
}
38
38
+
}
39
39
+
40
40
+
func (bs *SqliteBlockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) {
41
41
+
var block models.Block
42
42
+
if err := bs.db.Raw("SELECT * FROM blocks WHERE did = ? AND cid = ?", bs.did, cid.Bytes()).Scan(&block).Error; err != nil {
43
43
+
return nil, err
44
44
+
}
45
45
+
46
46
+
b, err := blocks.NewBlockWithCid(block.Value, cid)
47
47
+
if err != nil {
48
48
+
return nil, err
49
49
+
}
50
50
+
51
51
+
return b, nil
52
52
+
}
53
53
+
54
54
+
func (bs *SqliteBlockstore) Put(ctx context.Context, block blocks.Block) error {
55
55
+
bs.inserts = append(bs.inserts, block)
56
56
+
57
57
+
if bs.readonly {
58
58
+
return nil
59
59
+
}
60
60
+
61
61
+
b := models.Block{
62
62
+
Did: bs.did,
63
63
+
Cid: block.Cid().Bytes(),
64
64
+
Rev: syntax.NewTIDNow(0).String(), // TODO: WARN, this is bad. don't do this
65
65
+
Value: block.RawData(),
66
66
+
}
67
67
+
68
68
+
if err := bs.db.Clauses(clause.OnConflict{
69
69
+
Columns: []clause.Column{{Name: "did"}, {Name: "cid"}},
70
70
+
UpdateAll: true,
71
71
+
}).Create(&b).Error; err != nil {
72
72
+
return err
73
73
+
}
74
74
+
75
75
+
return nil
76
76
+
}
77
77
+
78
78
+
func (bs *SqliteBlockstore) DeleteBlock(context.Context, cid.Cid) error {
79
79
+
panic("not implemented")
80
80
+
}
81
81
+
82
82
+
func (bs *SqliteBlockstore) Has(context.Context, cid.Cid) (bool, error) {
83
83
+
panic("not implemented")
84
84
+
}
85
85
+
86
86
+
func (bs *SqliteBlockstore) GetSize(context.Context, cid.Cid) (int, error) {
87
87
+
panic("not implemented")
88
88
+
}
89
89
+
90
90
+
func (bs *SqliteBlockstore) PutMany(context.Context, []blocks.Block) error {
91
91
+
panic("not implemented")
92
92
+
}
93
93
+
94
94
+
func (bs *SqliteBlockstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) {
95
95
+
panic("not implemented")
96
96
+
}
97
97
+
98
98
+
func (bs *SqliteBlockstore) HashOnRead(enabled bool) {
99
99
+
panic("not implemented")
100
100
+
}
101
101
+
102
102
+
func (bs *SqliteBlockstore) UpdateRepo(ctx context.Context, root cid.Cid, rev string) error {
103
103
+
if err := bs.db.Exec("UPDATE repos SET root = ?, rev = ? WHERE did = ?", root.Bytes(), rev, bs.did).Error; err != nil {
104
104
+
return err
105
105
+
}
106
106
+
107
107
+
return nil
108
108
+
}
109
109
+
110
110
+
func (bs *SqliteBlockstore) Execute(ctx context.Context) error {
111
111
+
if !bs.readonly {
112
112
+
return fmt.Errorf("blockstore was not readonly")
113
113
+
}
114
114
+
115
115
+
bs.readonly = false
116
116
+
for _, b := range bs.inserts {
117
117
+
bs.Put(ctx, b)
118
118
+
}
119
119
+
bs.readonly = true
120
120
+
121
121
+
return nil
122
122
+
}
123
123
+
124
124
+
func (bs *SqliteBlockstore) GetLog() []blocks.Block {
125
125
+
return bs.inserts
126
126
+
}
+94
cmd/admin/main.go
Reviewed
···
1
1
+
package main
2
2
+
3
3
+
import (
4
4
+
"crypto/ecdsa"
5
5
+
"crypto/elliptic"
6
6
+
"crypto/rand"
7
7
+
"encoding/json"
8
8
+
"fmt"
9
9
+
"os"
10
10
+
"time"
11
11
+
12
12
+
"github.com/bluesky-social/indigo/atproto/crypto"
13
13
+
"github.com/lestrrat-go/jwx/v2/jwk"
14
14
+
"github.com/urfave/cli/v2"
15
15
+
)
16
16
+
17
17
+
func main() {
18
18
+
app := cli.App{
19
19
+
Name: "admin",
20
20
+
Commands: cli.Commands{
21
21
+
runCreateRotationKey,
22
22
+
runCreatePrivateJwk,
23
23
+
},
24
24
+
ErrWriter: os.Stdout,
25
25
+
}
26
26
+
27
27
+
app.Run(os.Args)
28
28
+
}
29
29
+
30
30
+
var runCreateRotationKey = &cli.Command{
31
31
+
Name: "create-rotation-key",
32
32
+
Usage: "creates a rotation key for your pds",
33
33
+
Flags: []cli.Flag{
34
34
+
&cli.StringFlag{
35
35
+
Name: "out",
36
36
+
Required: true,
37
37
+
Usage: "output file for your rotation key",
38
38
+
},
39
39
+
},
40
40
+
Action: func(cmd *cli.Context) error {
41
41
+
key, err := crypto.GeneratePrivateKeyK256()
42
42
+
if err != nil {
43
43
+
return err
44
44
+
}
45
45
+
46
46
+
bytes := key.Bytes()
47
47
+
48
48
+
if err := os.WriteFile(cmd.String("out"), bytes, 0644); err != nil {
49
49
+
return err
50
50
+
}
51
51
+
52
52
+
return nil
53
53
+
},
54
54
+
}
55
55
+
56
56
+
var runCreatePrivateJwk = &cli.Command{
57
57
+
Name: "create-private-jwk",
58
58
+
Usage: "creates a private jwk for your pds",
59
59
+
Flags: []cli.Flag{
60
60
+
&cli.StringFlag{
61
61
+
Name: "out",
62
62
+
Required: true,
63
63
+
Usage: "output file for your jwk",
64
64
+
},
65
65
+
},
66
66
+
Action: func(cmd *cli.Context) error {
67
67
+
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
68
68
+
if err != nil {
69
69
+
return err
70
70
+
}
71
71
+
72
72
+
key, err := jwk.FromRaw(privKey)
73
73
+
if err != nil {
74
74
+
return err
75
75
+
}
76
76
+
77
77
+
kid := fmt.Sprintf("%d", time.Now().Unix())
78
78
+
79
79
+
if err := key.Set(jwk.KeyIDKey, kid); err != nil {
80
80
+
return err
81
81
+
}
82
82
+
83
83
+
b, err := json.Marshal(key)
84
84
+
if err != nil {
85
85
+
return err
86
86
+
}
87
87
+
88
88
+
if err := os.WriteFile(cmd.String("out"), b, 0644); err != nil {
89
89
+
return err
90
90
+
}
91
91
+
92
92
+
return nil
93
93
+
},
94
94
+
}
+97
cmd/cocoon/main.go
Reviewed
···
1
1
+
package main
2
2
+
3
3
+
import (
4
4
+
"fmt"
5
5
+
"os"
6
6
+
7
7
+
"github.com/haileyok/cocoon/server"
8
8
+
_ "github.com/joho/godotenv/autoload"
9
9
+
"github.com/urfave/cli/v2"
10
10
+
)
11
11
+
12
12
+
var Version = "dev"
13
13
+
14
14
+
func main() {
15
15
+
app := &cli.App{
16
16
+
Name: "cocoon",
17
17
+
Usage: "An atproto PDS",
18
18
+
Flags: []cli.Flag{
19
19
+
&cli.StringFlag{
20
20
+
Name: "addr",
21
21
+
Value: ":8080",
22
22
+
EnvVars: []string{"COCOON_ADDR"},
23
23
+
},
24
24
+
&cli.StringFlag{
25
25
+
Name: "db-name",
26
26
+
Value: "cocoon.db",
27
27
+
EnvVars: []string{"COCOON_DB_NAME"},
28
28
+
},
29
29
+
&cli.StringFlag{
30
30
+
Name: "did",
31
31
+
Required: true,
32
32
+
EnvVars: []string{"COCOON_DID"},
33
33
+
},
34
34
+
&cli.StringFlag{
35
35
+
Name: "hostname",
36
36
+
Required: true,
37
37
+
EnvVars: []string{"COCOON_HOSTNAME"},
38
38
+
},
39
39
+
&cli.StringFlag{
40
40
+
Name: "rotation-key-path",
41
41
+
Required: true,
42
42
+
EnvVars: []string{"COCOON_ROTATION_KEY_PATH"},
43
43
+
},
44
44
+
&cli.StringFlag{
45
45
+
Name: "jwk-path",
46
46
+
Required: true,
47
47
+
EnvVars: []string{"COCOON_JWK_PATH"},
48
48
+
},
49
49
+
&cli.StringFlag{
50
50
+
Name: "contact-email",
51
51
+
Required: true,
52
52
+
EnvVars: []string{"COCOON_CONTACT_EMAIL"},
53
53
+
},
54
54
+
&cli.StringSliceFlag{
55
55
+
Name: "relays",
56
56
+
Required: true,
57
57
+
EnvVars: []string{"COCOON_RELAYS"},
58
58
+
},
59
59
+
},
60
60
+
Commands: []*cli.Command{
61
61
+
run,
62
62
+
},
63
63
+
ErrWriter: os.Stdout,
64
64
+
Version: Version,
65
65
+
}
66
66
+
67
67
+
app.Run(os.Args)
68
68
+
}
69
69
+
70
70
+
var run = &cli.Command{
71
71
+
Name: "run",
72
72
+
Usage: "Start the cocoon PDS",
73
73
+
Flags: []cli.Flag{},
74
74
+
Action: func(cmd *cli.Context) error {
75
75
+
s, err := server.New(&server.Args{
76
76
+
Addr: cmd.String("addr"),
77
77
+
DbName: cmd.String("db-name"),
78
78
+
Did: cmd.String("did"),
79
79
+
Hostname: cmd.String("hostname"),
80
80
+
RotationKeyPath: cmd.String("rotation-key-path"),
81
81
+
JwkPath: cmd.String("jwk-path"),
82
82
+
ContactEmail: cmd.String("contact-email"),
83
83
+
Version: Version,
84
84
+
Relays: cmd.StringSlice("relays"),
85
85
+
})
86
86
+
if err != nil {
87
87
+
return err
88
88
+
}
89
89
+
90
90
+
if err := s.Serve(cmd.Context); err != nil {
91
91
+
fmt.Printf("error starting cocoon: %v", err)
92
92
+
return err
93
93
+
}
94
94
+
95
95
+
return nil
96
96
+
},
97
97
+
}
+135
go.mod
Reviewed
···
1
1
+
module github.com/haileyok/cocoon
2
2
+
3
3
+
go 1.24.1
4
4
+
5
5
+
require (
6
6
+
github.com/Azure/go-autorest/autorest/to v0.4.1
7
7
+
github.com/bluesky-social/indigo v0.0.0-20250322011324-8e3fa7af986a
8
8
+
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792
9
9
+
github.com/go-playground/validator v9.31.0+incompatible
10
10
+
github.com/golang-jwt/jwt/v4 v4.5.2
11
11
+
github.com/google/uuid v1.4.0
12
12
+
github.com/ipfs/go-block-format v0.2.0
13
13
+
github.com/ipfs/go-cid v0.4.1
14
14
+
github.com/ipfs/go-ipld-cbor v0.1.0
15
15
+
github.com/ipld/go-car v0.6.1-0.20230509095817-92d28eb23ba4
16
16
+
github.com/joho/godotenv v1.5.1
17
17
+
github.com/labstack/echo/v4 v4.13.3
18
18
+
github.com/lestrrat-go/jwx/v2 v2.0.12
19
19
+
github.com/samber/slog-echo v1.16.1
20
20
+
github.com/urfave/cli/v2 v2.27.6
21
21
+
golang.org/x/crypto v0.36.0
22
22
+
gorm.io/driver/sqlite v1.5.7
23
23
+
gorm.io/gorm v1.25.12
24
24
+
)
25
25
+
26
26
+
require (
27
27
+
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
28
28
+
github.com/RussellLuo/slidingwindow v0.0.0-20200528002341-535bb99d338b // indirect
29
29
+
github.com/beorn7/perks v1.0.1 // indirect
30
30
+
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect
31
31
+
github.com/carlmjohnson/versioninfo v0.22.5 // indirect
32
32
+
github.com/cespare/xxhash/v2 v2.2.0 // indirect
33
33
+
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
34
34
+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
35
35
+
github.com/felixge/httpsnoop v1.0.4 // indirect
36
36
+
github.com/go-logr/logr v1.4.2 // indirect
37
37
+
github.com/go-logr/stdr v1.2.2 // indirect
38
38
+
github.com/go-playground/locales v0.14.1 // indirect
39
39
+
github.com/go-playground/universal-translator v0.18.1 // indirect
40
40
+
github.com/goccy/go-json v0.10.2 // indirect
41
41
+
github.com/gocql/gocql v1.7.0 // indirect
42
42
+
github.com/gogo/protobuf v1.3.2 // indirect
43
43
+
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
44
44
+
github.com/golang/snappy v0.0.4 // indirect
45
45
+
github.com/gorilla/websocket v1.5.1 // indirect
46
46
+
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
47
47
+
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
48
48
+
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
49
49
+
github.com/hashicorp/golang-lru v1.0.2 // indirect
50
50
+
github.com/hashicorp/golang-lru/arc/v2 v2.0.6 // indirect
51
51
+
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
52
52
+
github.com/ipfs/bbloom v0.0.4 // indirect
53
53
+
github.com/ipfs/go-blockservice v0.5.2 // indirect
54
54
+
github.com/ipfs/go-datastore v0.6.0 // indirect
55
55
+
github.com/ipfs/go-ipfs-blockstore v1.3.1 // indirect
56
56
+
github.com/ipfs/go-ipfs-ds-help v1.1.1 // indirect
57
57
+
github.com/ipfs/go-ipfs-exchange-interface v0.2.1 // indirect
58
58
+
github.com/ipfs/go-ipfs-util v0.0.3 // indirect
59
59
+
github.com/ipfs/go-ipld-format v0.6.0 // indirect
60
60
+
github.com/ipfs/go-ipld-legacy v0.2.1 // indirect
61
61
+
github.com/ipfs/go-libipfs v0.7.0 // indirect
62
62
+
github.com/ipfs/go-log v1.0.5 // indirect
63
63
+
github.com/ipfs/go-log/v2 v2.5.1 // indirect
64
64
+
github.com/ipfs/go-merkledag v0.11.0 // indirect
65
65
+
github.com/ipfs/go-metrics-interface v0.0.1 // indirect
66
66
+
github.com/ipfs/go-verifcid v0.0.3 // indirect
67
67
+
github.com/ipld/go-car/v2 v2.13.1 // indirect
68
68
+
github.com/ipld/go-codec-dagpb v1.6.0 // indirect
69
69
+
github.com/ipld/go-ipld-prime v0.21.0 // indirect
70
70
+
github.com/jackc/pgpassfile v1.0.0 // indirect
71
71
+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
72
72
+
github.com/jackc/pgx/v5 v5.5.0 // indirect
73
73
+
github.com/jackc/puddle/v2 v2.2.1 // indirect
74
74
+
github.com/jbenet/goprocess v0.1.4 // indirect
75
75
+
github.com/jinzhu/inflection v1.0.0 // indirect
76
76
+
github.com/jinzhu/now v1.1.5 // indirect
77
77
+
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
78
78
+
github.com/labstack/gommon v0.4.2 // indirect
79
79
+
github.com/leodido/go-urn v1.4.0 // indirect
80
80
+
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
81
81
+
github.com/lestrrat-go/httpcc v1.0.1 // indirect
82
82
+
github.com/lestrrat-go/httprc v1.0.4 // indirect
83
83
+
github.com/lestrrat-go/iter v1.0.2 // indirect
84
84
+
github.com/lestrrat-go/option v1.0.1 // indirect
85
85
+
github.com/mattn/go-colorable v0.1.13 // indirect
86
86
+
github.com/mattn/go-isatty v0.0.20 // indirect
87
87
+
github.com/mattn/go-sqlite3 v1.14.22 // indirect
88
88
+
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
89
89
+
github.com/minio/sha256-simd v1.0.1 // indirect
90
90
+
github.com/mr-tron/base58 v1.2.0 // indirect
91
91
+
github.com/multiformats/go-base32 v0.1.0 // indirect
92
92
+
github.com/multiformats/go-base36 v0.2.0 // indirect
93
93
+
github.com/multiformats/go-multibase v0.2.0 // indirect
94
94
+
github.com/multiformats/go-multicodec v0.9.0 // indirect
95
95
+
github.com/multiformats/go-multihash v0.2.3 // indirect
96
96
+
github.com/multiformats/go-varint v0.0.7 // indirect
97
97
+
github.com/opentracing/opentracing-go v1.2.0 // indirect
98
98
+
github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 // indirect
99
99
+
github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect
100
100
+
github.com/prometheus/client_golang v1.17.0 // indirect
101
101
+
github.com/prometheus/client_model v0.5.0 // indirect
102
102
+
github.com/prometheus/common v0.45.0 // indirect
103
103
+
github.com/prometheus/procfs v0.12.0 // indirect
104
104
+
github.com/russross/blackfriday/v2 v2.1.0 // indirect
105
105
+
github.com/samber/lo v1.49.1 // indirect
106
106
+
github.com/segmentio/asm v1.2.0 // indirect
107
107
+
github.com/spaolacci/murmur3 v1.1.0 // indirect
108
108
+
github.com/valyala/bytebufferpool v1.0.0 // indirect
109
109
+
github.com/valyala/fasttemplate v1.2.2 // indirect
110
110
+
github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 // indirect
111
111
+
github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e // indirect
112
112
+
github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6 // indirect
113
113
+
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
114
114
+
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect
115
115
+
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
116
116
+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
117
117
+
go.opentelemetry.io/otel v1.29.0 // indirect
118
118
+
go.opentelemetry.io/otel/metric v1.29.0 // indirect
119
119
+
go.opentelemetry.io/otel/trace v1.29.0 // indirect
120
120
+
go.uber.org/atomic v1.11.0 // indirect
121
121
+
go.uber.org/multierr v1.11.0 // indirect
122
122
+
go.uber.org/zap v1.26.0 // indirect
123
123
+
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
124
124
+
golang.org/x/net v0.33.0 // indirect
125
125
+
golang.org/x/sync v0.12.0 // indirect
126
126
+
golang.org/x/sys v0.31.0 // indirect
127
127
+
golang.org/x/text v0.23.0 // indirect
128
128
+
golang.org/x/time v0.8.0 // indirect
129
129
+
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
130
130
+
google.golang.org/protobuf v1.33.0 // indirect
131
131
+
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
132
132
+
gopkg.in/inf.v0 v0.9.1 // indirect
133
133
+
gorm.io/driver/postgres v1.5.7 // indirect
134
134
+
lukechampine.com/blake3 v1.2.1 // indirect
135
135
+
)
+501
go.sum
Reviewed
···
1
1
+
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
2
2
+
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
3
3
+
github.com/Azure/go-autorest/autorest/to v0.4.1 h1:CxNHBqdzTr7rLtdrtb5CMjJcDut+WNGCVv7OmS5+lTc=
4
4
+
github.com/Azure/go-autorest/autorest/to v0.4.1/go.mod h1:EtaofgU4zmtvn1zT2ARsjRFdq9vXx0YWtmElwL+GZ9M=
5
5
+
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
6
6
+
github.com/RussellLuo/slidingwindow v0.0.0-20200528002341-535bb99d338b h1:5/++qT1/z812ZqBvqQt6ToRswSuPZ/B33m6xVHRzADU=
7
7
+
github.com/RussellLuo/slidingwindow v0.0.0-20200528002341-535bb99d338b/go.mod h1:4+EPqMRApwwE/6yo6CxiHoSnBzjRr3jsqer7frxP8y4=
8
8
+
github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5 h1:iW0a5ljuFxkLGPNem5Ui+KBjFJzKg4Fv2fnxe4dvzpM=
9
9
+
github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5/go.mod h1:Y2QMoi1vgtOIfc+6DhrMOGkLoGzqSV2rKp4Sm+opsyA=
10
10
+
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
11
11
+
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
12
12
+
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
13
13
+
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
14
14
+
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
15
15
+
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
16
16
+
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
17
17
+
github.com/bluesky-social/indigo v0.0.0-20250322011324-8e3fa7af986a h1:clnSZRgkiifbvfqu9++OHfIh2DWuIoZ8CucxLueQxO0=
18
18
+
github.com/bluesky-social/indigo v0.0.0-20250322011324-8e3fa7af986a/go.mod h1:NVBwZvbBSa93kfyweAmKwOLYawdVHdwZ9s+GZtBBVLA=
19
19
+
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
20
20
+
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
21
21
+
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous=
22
22
+
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
23
23
+
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc=
24
24
+
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
25
25
+
github.com/carlmjohnson/versioninfo v0.22.5 h1:O00sjOLUAFxYQjlN/bzYTuZiS0y6fWDQjMRvwtKgwwc=
26
26
+
github.com/carlmjohnson/versioninfo v0.22.5/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8=
27
27
+
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
28
28
+
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
29
29
+
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
30
30
+
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
31
31
+
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
32
32
+
github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0=
33
33
+
github.com/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TIRis=
34
34
+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
35
35
+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
36
36
+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
37
37
+
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
38
38
+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
39
39
+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
40
40
+
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
41
41
+
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
42
42
+
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
43
43
+
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
44
44
+
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
45
45
+
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
46
46
+
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
47
47
+
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
48
48
+
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
49
49
+
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
50
50
+
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
51
51
+
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
52
52
+
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
53
53
+
github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA=
54
54
+
github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig=
55
55
+
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
56
56
+
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
57
57
+
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
58
58
+
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
59
59
+
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
60
60
+
github.com/gocql/gocql v1.7.0 h1:O+7U7/1gSN7QTEAaMEsJc1Oq2QHXvCWoF3DFK9HDHus=
61
61
+
github.com/gocql/gocql v1.7.0/go.mod h1:vnlvXyFZeLBF0Wy+RS8hrOdbn0UWsWtdg07XJnFxZ+4=
62
62
+
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
63
63
+
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
64
64
+
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
65
65
+
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
66
66
+
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
67
67
+
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
68
68
+
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
69
69
+
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
70
70
+
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
71
71
+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
72
72
+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
73
73
+
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
74
74
+
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
75
75
+
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
76
76
+
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
77
77
+
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
78
78
+
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
79
79
+
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
80
80
+
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
81
81
+
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
82
82
+
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
83
83
+
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
84
84
+
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
85
85
+
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
86
86
+
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
87
87
+
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
88
88
+
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
89
89
+
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
90
90
+
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
91
91
+
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
92
92
+
github.com/hashicorp/golang-lru/arc/v2 v2.0.6 h1:4NU7uP5vSoK6TbaMj3NtY478TTAWLso/vL1gpNrInHg=
93
93
+
github.com/hashicorp/golang-lru/arc/v2 v2.0.6/go.mod h1:cfdDIX05DWvYV6/shsxDfa/OVcRieOt+q4FnM8x+Xno=
94
94
+
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
95
95
+
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
96
96
+
github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ=
97
97
+
github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y=
98
98
+
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
99
99
+
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
100
100
+
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
101
101
+
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
102
102
+
github.com/ipfs/go-bitswap v0.11.0 h1:j1WVvhDX1yhG32NTC9xfxnqycqYIlhzEzLXG/cU1HyQ=
103
103
+
github.com/ipfs/go-bitswap v0.11.0/go.mod h1:05aE8H3XOU+LXpTedeAS0OZpcO1WFsj5niYQH9a1Tmk=
104
104
+
github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs=
105
105
+
github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM=
106
106
+
github.com/ipfs/go-blockservice v0.5.2 h1:in9Bc+QcXwd1apOVM7Un9t8tixPKdaHQFdLSUM1Xgk8=
107
107
+
github.com/ipfs/go-blockservice v0.5.2/go.mod h1:VpMblFEqG67A/H2sHKAemeH9vlURVavlysbdUI632yk=
108
108
+
github.com/ipfs/go-bs-sqlite3 v0.0.0-20221122195556-bfcee1be620d h1:9V+GGXCuOfDiFpdAHz58q9mKLg447xp0cQKvqQrAwYE=
109
109
+
github.com/ipfs/go-bs-sqlite3 v0.0.0-20221122195556-bfcee1be620d/go.mod h1:pMbnFyNAGjryYCLCe59YDLRv/ujdN+zGJBT1umlvYRM=
110
110
+
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
111
111
+
github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
112
112
+
github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk=
113
113
+
github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8=
114
114
+
github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk=
115
115
+
github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps=
116
116
+
github.com/ipfs/go-ds-flatfs v0.5.1 h1:ZCIO/kQOS/PSh3vcF1H6a8fkRGS7pOfwfPdx4n/KJH4=
117
117
+
github.com/ipfs/go-ds-flatfs v0.5.1/go.mod h1:RWTV7oZD/yZYBKdbVIFXTX2fdY2Tbvl94NsWqmoyAX4=
118
118
+
github.com/ipfs/go-ipfs-blockstore v1.3.1 h1:cEI9ci7V0sRNivqaOr0elDsamxXFxJMMMy7PTTDQNsQ=
119
119
+
github.com/ipfs/go-ipfs-blockstore v1.3.1/go.mod h1:KgtZyc9fq+P2xJUiCAzbRdhhqJHvsw8u2Dlqy2MyRTE=
120
120
+
github.com/ipfs/go-ipfs-blocksutil v0.0.1 h1:Eh/H4pc1hsvhzsQoMEP3Bke/aW5P5rVM1IWFJMcGIPQ=
121
121
+
github.com/ipfs/go-ipfs-blocksutil v0.0.1/go.mod h1:Yq4M86uIOmxmGPUHv/uI7uKqZNtLb449gwKqXjIsnRk=
122
122
+
github.com/ipfs/go-ipfs-chunker v0.0.5 h1:ojCf7HV/m+uS2vhUGWcogIIxiO5ubl5O57Q7NapWLY8=
123
123
+
github.com/ipfs/go-ipfs-chunker v0.0.5/go.mod h1:jhgdF8vxRHycr00k13FM8Y0E+6BoalYeobXmUyTreP8=
124
124
+
github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ=
125
125
+
github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw=
126
126
+
github.com/ipfs/go-ipfs-ds-help v1.1.1 h1:B5UJOH52IbcfS56+Ul+sv8jnIV10lbjLF5eOO0C66Nw=
127
127
+
github.com/ipfs/go-ipfs-ds-help v1.1.1/go.mod h1:75vrVCkSdSFidJscs8n4W+77AtTpCIAdDGAwjitJMIo=
128
128
+
github.com/ipfs/go-ipfs-exchange-interface v0.2.1 h1:jMzo2VhLKSHbVe+mHNzYgs95n0+t0Q69GQ5WhRDZV/s=
129
129
+
github.com/ipfs/go-ipfs-exchange-interface v0.2.1/go.mod h1:MUsYn6rKbG6CTtsDp+lKJPmVt3ZrCViNyH3rfPGsZ2E=
130
130
+
github.com/ipfs/go-ipfs-exchange-offline v0.3.0 h1:c/Dg8GDPzixGd0MC8Jh6mjOwU57uYokgWRFidfvEkuA=
131
131
+
github.com/ipfs/go-ipfs-exchange-offline v0.3.0/go.mod h1:MOdJ9DChbb5u37M1IcbrRB02e++Z7521fMxqCNRrz9s=
132
132
+
github.com/ipfs/go-ipfs-pq v0.0.3 h1:YpoHVJB+jzK15mr/xsWC574tyDLkezVrDNeaalQBsTE=
133
133
+
github.com/ipfs/go-ipfs-pq v0.0.3/go.mod h1:btNw5hsHBpRcSSgZtiNm/SLj5gYIZ18AKtv3kERkRb4=
134
134
+
github.com/ipfs/go-ipfs-routing v0.3.0 h1:9W/W3N+g+y4ZDeffSgqhgo7BsBSJwPMcyssET9OWevc=
135
135
+
github.com/ipfs/go-ipfs-routing v0.3.0/go.mod h1:dKqtTFIql7e1zYsEuWLyuOU+E0WJWW8JjbTPLParDWo=
136
136
+
github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0=
137
137
+
github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs=
138
138
+
github.com/ipfs/go-ipld-cbor v0.1.0 h1:dx0nS0kILVivGhfWuB6dUpMa/LAwElHPw1yOGYopoYs=
139
139
+
github.com/ipfs/go-ipld-cbor v0.1.0/go.mod h1:U2aYlmVrJr2wsUBU67K4KgepApSZddGRDWBYR0H4sCk=
140
140
+
github.com/ipfs/go-ipld-format v0.6.0 h1:VEJlA2kQ3LqFSIm5Vu6eIlSxD/Ze90xtc4Meten1F5U=
141
141
+
github.com/ipfs/go-ipld-format v0.6.0/go.mod h1:g4QVMTn3marU3qXchwjpKPKgJv+zF+OlaKMyhJ4LHPg=
142
142
+
github.com/ipfs/go-ipld-legacy v0.2.1 h1:mDFtrBpmU7b//LzLSypVrXsD8QxkEWxu5qVxN99/+tk=
143
143
+
github.com/ipfs/go-ipld-legacy v0.2.1/go.mod h1:782MOUghNzMO2DER0FlBR94mllfdCJCkTtDtPM51otM=
144
144
+
github.com/ipfs/go-libipfs v0.7.0 h1:Mi54WJTODaOL2/ZSm5loi3SwI3jI2OuFWUrQIkJ5cpM=
145
145
+
github.com/ipfs/go-libipfs v0.7.0/go.mod h1:KsIf/03CqhICzyRGyGo68tooiBE2iFbI/rXW7FhAYr0=
146
146
+
github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8=
147
147
+
github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo=
148
148
+
github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g=
149
149
+
github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY=
150
150
+
github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI=
151
151
+
github.com/ipfs/go-merkledag v0.11.0 h1:DgzwK5hprESOzS4O1t/wi6JDpyVQdvm9Bs59N/jqfBY=
152
152
+
github.com/ipfs/go-merkledag v0.11.0/go.mod h1:Q4f/1ezvBiJV0YCIXvt51W/9/kqJGH4I1LsA7+djsM4=
153
153
+
github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg=
154
154
+
github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY=
155
155
+
github.com/ipfs/go-peertaskqueue v0.8.1 h1:YhxAs1+wxb5jk7RvS0LHdyiILpNmRIRnZVztekOF0pg=
156
156
+
github.com/ipfs/go-peertaskqueue v0.8.1/go.mod h1:Oxxd3eaK279FxeydSPPVGHzbwVeHjatZ2GA8XD+KbPU=
157
157
+
github.com/ipfs/go-unixfsnode v1.8.0 h1:yCkakzuE365glu+YkgzZt6p38CSVEBPgngL9ZkfnyQU=
158
158
+
github.com/ipfs/go-unixfsnode v1.8.0/go.mod h1:HxRu9HYHOjK6HUqFBAi++7DVoWAHn0o4v/nZ/VA+0g8=
159
159
+
github.com/ipfs/go-verifcid v0.0.3 h1:gmRKccqhWDocCRkC+a59g5QW7uJw5bpX9HWBevXa0zs=
160
160
+
github.com/ipfs/go-verifcid v0.0.3/go.mod h1:gcCtGniVzelKrbk9ooUSX/pM3xlH73fZZJDzQJRvOUw=
161
161
+
github.com/ipld/go-car v0.6.1-0.20230509095817-92d28eb23ba4 h1:oFo19cBmcP0Cmg3XXbrr0V/c+xU9U1huEZp8+OgBzdI=
162
162
+
github.com/ipld/go-car v0.6.1-0.20230509095817-92d28eb23ba4/go.mod h1:6nkFF8OmR5wLKBzRKi7/YFJpyYR7+oEn1DX+mMWnlLA=
163
163
+
github.com/ipld/go-car/v2 v2.13.1 h1:KnlrKvEPEzr5IZHKTXLAEub+tPrzeAFQVRlSQvuxBO4=
164
164
+
github.com/ipld/go-car/v2 v2.13.1/go.mod h1:QkdjjFNGit2GIkpQ953KBwowuoukoM75nP/JI1iDJdo=
165
165
+
github.com/ipld/go-codec-dagpb v1.6.0 h1:9nYazfyu9B1p3NAgfVdpRco3Fs2nFC72DqVsMj6rOcc=
166
166
+
github.com/ipld/go-codec-dagpb v1.6.0/go.mod h1:ANzFhfP2uMJxRBr8CE+WQWs5UsNa0pYtmKZ+agnUw9s=
167
167
+
github.com/ipld/go-ipld-prime v0.21.0 h1:n4JmcpOlPDIxBcY037SVfpd1G+Sj1nKZah0m6QH9C2E=
168
168
+
github.com/ipld/go-ipld-prime v0.21.0/go.mod h1:3RLqy//ERg/y5oShXXdx5YIp50cFGOanyMctpPjsvxQ=
169
169
+
github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20230102063945-1a409dc236dd h1:gMlw/MhNr2Wtp5RwGdsW23cs+yCuj9k2ON7i9MiJlRo=
170
170
+
github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20230102063945-1a409dc236dd/go.mod h1:wZ8hH8UxeryOs4kJEJaiui/s00hDSbE37OKsL47g+Sw=
171
171
+
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
172
172
+
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
173
173
+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
174
174
+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
175
175
+
github.com/jackc/pgx/v5 v5.5.0 h1:NxstgwndsTRy7eq9/kqYc/BZh5w2hHJV86wjvO+1xPw=
176
176
+
github.com/jackc/pgx/v5 v5.5.0/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
177
177
+
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
178
178
+
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
179
179
+
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
180
180
+
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
181
181
+
github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA=
182
182
+
github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o=
183
183
+
github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4=
184
184
+
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
185
185
+
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
186
186
+
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
187
187
+
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
188
188
+
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
189
189
+
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
190
190
+
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
191
191
+
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
192
192
+
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
193
193
+
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
194
194
+
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
195
195
+
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
196
196
+
github.com/koron/go-ssdp v0.0.3 h1:JivLMY45N76b4p/vsWGOKewBQu6uf39y8l+AQ7sDKx8=
197
197
+
github.com/koron/go-ssdp v0.0.3/go.mod h1:b2MxI6yh02pKrsyNoQUsk4+YNikaGhe4894J+Q5lDvA=
198
198
+
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
199
199
+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
200
200
+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
201
201
+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
202
202
+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
203
203
+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
204
204
+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
205
205
+
github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
206
206
+
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
207
207
+
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
208
208
+
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
209
209
+
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
210
210
+
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
211
211
+
github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
212
212
+
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
213
213
+
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
214
214
+
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
215
215
+
github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8=
216
216
+
github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
217
217
+
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
218
218
+
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
219
219
+
github.com/lestrrat-go/jwx/v2 v2.0.12 h1:3d589+5w/b9b7S3DneICPW16AqTyYXB7VRjgluSDWeA=
220
220
+
github.com/lestrrat-go/jwx/v2 v2.0.12/go.mod h1:Mq4KN1mM7bp+5z/W5HS8aCNs5RKZ911G/0y2qUjAQuQ=
221
221
+
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
222
222
+
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
223
223
+
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
224
224
+
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
225
225
+
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
226
226
+
github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c=
227
227
+
github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic=
228
228
+
github.com/libp2p/go-libp2p v0.25.1 h1:YK+YDCHpYyTvitKWVxa5PfElgIpOONU01X5UcLEwJGA=
229
229
+
github.com/libp2p/go-libp2p v0.25.1/go.mod h1:xnK9/1d9+jeQCVvi/f1g12KqtVi/jP/SijtKV1hML3g=
230
230
+
github.com/libp2p/go-libp2p-asn-util v0.2.0 h1:rg3+Os8jbnO5DxkC7K/Utdi+DkY3q/d1/1q+8WeNAsw=
231
231
+
github.com/libp2p/go-libp2p-asn-util v0.2.0/go.mod h1:WoaWxbHKBymSN41hWSq/lGKJEca7TNm58+gGJi2WsLI=
232
232
+
github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0=
233
233
+
github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk=
234
234
+
github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA=
235
235
+
github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg=
236
236
+
github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0=
237
237
+
github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM=
238
238
+
github.com/libp2p/go-nat v0.1.0 h1:MfVsH6DLcpa04Xr+p8hmVRG4juse0s3J8HyNWYHffXg=
239
239
+
github.com/libp2p/go-nat v0.1.0/go.mod h1:X7teVkwRHNInVNWQiO/tAiAVRwSr5zoRz4YSTC3uRBM=
240
240
+
github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU=
241
241
+
github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ=
242
242
+
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
243
243
+
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
244
244
+
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
245
245
+
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
246
246
+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
247
247
+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
248
248
+
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
249
249
+
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
250
250
+
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
251
251
+
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
252
252
+
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
253
253
+
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
254
254
+
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
255
255
+
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
256
256
+
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
257
257
+
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
258
258
+
github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
259
259
+
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
260
260
+
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
261
261
+
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
262
262
+
github.com/multiformats/go-multiaddr v0.8.0 h1:aqjksEcqK+iD/Foe1RRFsGZh8+XFiGo7FgUCZlpv3LU=
263
263
+
github.com/multiformats/go-multiaddr v0.8.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs=
264
264
+
github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A=
265
265
+
github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk=
266
266
+
github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E=
267
267
+
github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo=
268
268
+
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
269
269
+
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
270
270
+
github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=
271
271
+
github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=
272
272
+
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
273
273
+
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
274
274
+
github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo=
275
275
+
github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q=
276
276
+
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
277
277
+
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
278
278
+
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
279
279
+
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
280
280
+
github.com/orandin/slog-gorm v1.3.2 h1:C0lKDQPAx/pF+8K2HL7bdShPwOEJpPM0Bn80zTzxU1g=
281
281
+
github.com/orandin/slog-gorm v1.3.2/go.mod h1:MoZ51+b7xE9lwGNPYEhxcUtRNrYzjdcKvA8QXQQGEPA=
282
282
+
github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 h1:1/WtZae0yGtPq+TI6+Tv1WTxkukpXeMlviSxvL7SRgk=
283
283
+
github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9/go.mod h1:x3N5drFsm2uilKKuuYo6LdyD8vZAW55sH/9w+pbo1sw=
284
284
+
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
285
285
+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
286
286
+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
287
287
+
github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f h1:VXTQfuJj9vKR4TCkEuWIckKvdHFeJH/huIFJ9/cXOB0=
288
288
+
github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw=
289
289
+
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
290
290
+
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
291
291
+
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
292
292
+
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
293
293
+
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
294
294
+
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
295
295
+
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
296
296
+
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
297
297
+
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
298
298
+
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
299
299
+
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
300
300
+
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
301
301
+
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
302
302
+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
303
303
+
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
304
304
+
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
305
305
+
github.com/samber/slog-echo v1.16.1 h1:5Q5IUROkFqKcu/qJM/13AP1d3gd1RS+Q/4EvKQU1fuo=
306
306
+
github.com/samber/slog-echo v1.16.1/go.mod h1:f+B3WR06saRXcaGRZ/I/UPCECDPqTUqadRIf7TmyRhI=
307
307
+
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
308
308
+
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
309
309
+
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
310
310
+
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
311
311
+
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
312
312
+
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
313
313
+
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
314
314
+
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
315
315
+
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
316
316
+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
317
317
+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
318
318
+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
319
319
+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
320
320
+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
321
321
+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
322
322
+
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
323
323
+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
324
324
+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
325
325
+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
326
326
+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
327
327
+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
328
328
+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
329
329
+
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
330
330
+
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
331
331
+
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
332
332
+
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
333
333
+
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
334
334
+
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
335
335
+
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
336
336
+
github.com/warpfork/go-testmark v0.12.1 h1:rMgCpJfwy1sJ50x0M0NgyphxYYPMOODIJHhsXyEHU0s=
337
337
+
github.com/warpfork/go-testmark v0.12.1/go.mod h1:kHwy7wfvGSPh1rQJYKayD4AbtNaeyZdcGi9tNJTaa5Y=
338
338
+
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ=
339
339
+
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
340
340
+
github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 h1:5HZfQkwe0mIfyDmc1Em5GqlNRzcdtlv4HTNmdpt7XH0=
341
341
+
github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11/go.mod h1:Wlo/SzPmxVp6vXpGt/zaXhHH0fn4IxgqZc82aKg6bpQ=
342
342
+
github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e h1:28X54ciEwwUxyHn9yrZfl5ojgF4CBNLWX7LR0rvBkf4=
343
343
+
github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so=
344
344
+
github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E=
345
345
+
github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8=
346
346
+
github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6 h1:yJ9/LwIGIk/c0CdoavpC9RNSGSruIspSZtxG3Nnldic=
347
347
+
github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6/go.mod h1:39U9RRVr4CKbXpXYopWn+FSH5s+vWu6+RmguSPWAq5s=
348
348
+
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
349
349
+
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
350
350
+
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
351
351
+
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
352
352
+
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
353
353
+
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
354
354
+
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA=
355
355
+
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8=
356
356
+
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q=
357
357
+
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I=
358
358
+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
359
359
+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
360
360
+
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
361
361
+
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
362
362
+
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
363
363
+
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
364
364
+
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
365
365
+
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
366
366
+
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
367
367
+
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
368
368
+
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
369
369
+
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
370
370
+
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
371
371
+
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
372
372
+
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
373
373
+
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
374
374
+
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
375
375
+
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
376
376
+
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
377
377
+
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
378
378
+
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
379
379
+
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
380
380
+
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
381
381
+
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
382
382
+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
383
383
+
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
384
384
+
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
385
385
+
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
386
386
+
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
387
387
+
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
388
388
+
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
389
389
+
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
390
390
+
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
391
391
+
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
392
392
+
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
393
393
+
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
394
394
+
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
395
395
+
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
396
396
+
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
397
397
+
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
398
398
+
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
399
399
+
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
400
400
+
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
401
401
+
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
402
402
+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
403
403
+
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
404
404
+
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
405
405
+
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
406
406
+
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
407
407
+
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
408
408
+
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
409
409
+
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
410
410
+
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
411
411
+
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
412
412
+
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
413
413
+
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
414
414
+
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
415
415
+
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
416
416
+
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
417
417
+
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
418
418
+
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
419
419
+
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
420
420
+
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
421
421
+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
422
422
+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
423
423
+
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
424
424
+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
425
425
+
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
426
426
+
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
427
427
+
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
428
428
+
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
429
429
+
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
430
430
+
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
431
431
+
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
432
432
+
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
433
433
+
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
434
434
+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
435
435
+
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
436
436
+
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
437
437
+
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
438
438
+
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
439
439
+
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
440
440
+
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
441
441
+
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
442
442
+
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
443
443
+
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
444
444
+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
445
445
+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
446
446
+
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
447
447
+
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
448
448
+
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
449
449
+
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
450
450
+
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
451
451
+
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
452
452
+
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
453
453
+
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
454
454
+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
455
455
+
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
456
456
+
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
457
457
+
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
458
458
+
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
459
459
+
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
460
460
+
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
461
461
+
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
462
462
+
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
463
463
+
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
464
464
+
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
465
465
+
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
466
466
+
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
467
467
+
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
468
468
+
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
469
469
+
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
470
470
+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
471
471
+
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
472
472
+
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
473
473
+
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
474
474
+
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
475
475
+
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
476
476
+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
477
477
+
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
478
478
+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
479
479
+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
480
480
+
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
481
481
+
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
482
482
+
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
483
483
+
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
484
484
+
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
485
485
+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
486
486
+
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
487
487
+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
488
488
+
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
489
489
+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
490
490
+
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
491
491
+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
492
492
+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
493
493
+
gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM=
494
494
+
gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
495
495
+
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
496
496
+
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
497
497
+
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
498
498
+
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
499
499
+
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
500
500
+
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
501
501
+
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
+238
identity/identity.go
Reviewed
···
1
1
+
package identity
2
2
+
3
3
+
import (
4
4
+
"context"
5
5
+
"encoding/json"
6
6
+
"fmt"
7
7
+
"io"
8
8
+
"net"
9
9
+
"net/http"
10
10
+
"strings"
11
11
+
12
12
+
"github.com/bluesky-social/indigo/atproto/syntax"
13
13
+
)
14
14
+
15
15
+
func ResolveHandle(ctx context.Context, handle string) (string, error) {
16
16
+
var did string
17
17
+
18
18
+
_, err := syntax.ParseHandle(handle)
19
19
+
if err != nil {
20
20
+
return "", err
21
21
+
}
22
22
+
23
23
+
recs, err := net.LookupTXT(fmt.Sprintf("_atproto.%s", handle))
24
24
+
if err == nil {
25
25
+
for _, rec := range recs {
26
26
+
if strings.HasPrefix(rec, "did=") {
27
27
+
did = strings.Split(rec, "did=")[1]
28
28
+
break
29
29
+
}
30
30
+
}
31
31
+
} else {
32
32
+
fmt.Printf("erorr getting txt records: %v\n", err)
33
33
+
}
34
34
+
35
35
+
if did == "" {
36
36
+
req, err := http.NewRequestWithContext(
37
37
+
ctx,
38
38
+
"GET",
39
39
+
fmt.Sprintf("https://%s/.well-known/atproto-did", handle),
40
40
+
nil,
41
41
+
)
42
42
+
if err != nil {
43
43
+
return "", nil
44
44
+
}
45
45
+
46
46
+
resp, err := http.DefaultClient.Do(req)
47
47
+
if err != nil {
48
48
+
return "", nil
49
49
+
}
50
50
+
defer resp.Body.Close()
51
51
+
52
52
+
if resp.StatusCode != http.StatusOK {
53
53
+
io.Copy(io.Discard, resp.Body)
54
54
+
return "", fmt.Errorf("unable to resolve handle")
55
55
+
}
56
56
+
57
57
+
b, err := io.ReadAll(resp.Body)
58
58
+
if err != nil {
59
59
+
return "", err
60
60
+
}
61
61
+
62
62
+
maybeDid := string(b)
63
63
+
64
64
+
if _, err := syntax.ParseDID(maybeDid); err != nil {
65
65
+
return "", fmt.Errorf("unable to resolve handle")
66
66
+
}
67
67
+
68
68
+
did = maybeDid
69
69
+
}
70
70
+
71
71
+
return did, nil
72
72
+
}
73
73
+
74
74
+
type DidDoc struct {
75
75
+
Context []string `json:"@context"`
76
76
+
Id string `json:"id"`
77
77
+
AlsoKnownAs []string `json:"alsoKnownAs"`
78
78
+
VerificationMethods []DidDocVerificationMethod `json:"verificationMethods"`
79
79
+
Service []DidDocService `json:"service"`
80
80
+
}
81
81
+
82
82
+
type DidDocVerificationMethod struct {
83
83
+
Id string `json:"id"`
84
84
+
Type string `json:"type"`
85
85
+
Controller string `json:"controller"`
86
86
+
PublicKeyMultibase string `json:"publicKeyMultibase"`
87
87
+
}
88
88
+
89
89
+
type DidDocService struct {
90
90
+
Id string `json:"id"`
91
91
+
Type string `json:"type"`
92
92
+
ServiceEndpoint string `json:"serviceEndpoint"`
93
93
+
}
94
94
+
95
95
+
type DidData struct {
96
96
+
Did string `json:"did"`
97
97
+
VerificationMethods map[string]string `json:"verificationMethods"`
98
98
+
RotationKeys []string `json:"rotationKeys"`
99
99
+
AlsoKnownAs []string `json:"alsoKnownAs"`
100
100
+
Services map[string]DidDataService `json:"services"`
101
101
+
}
102
102
+
103
103
+
type DidDataService struct {
104
104
+
Type string `json:"type"`
105
105
+
Endpoint string `json:"endpoint"`
106
106
+
}
107
107
+
108
108
+
type DidLog []DidLogEntry
109
109
+
110
110
+
type DidLogEntry struct {
111
111
+
Sig string `json:"sig"`
112
112
+
Prev *string `json:"prev"`
113
113
+
Type string `json:"string"`
114
114
+
Services map[string]DidDataService `json:"services"`
115
115
+
AlsoKnownAs []string `json:"alsoKnownAs"`
116
116
+
RotationKeys []string `json:"rotationKeys"`
117
117
+
VerificationMethods map[string]string `json:"verificationMethods"`
118
118
+
}
119
119
+
120
120
+
type DidAuditEntry struct {
121
121
+
Did string `json:"did"`
122
122
+
Operation DidLogEntry `json:"operation"`
123
123
+
Cid string `json:"cid"`
124
124
+
Nullified bool `json:"nullified"`
125
125
+
CreatedAt string `json:"createdAt"`
126
126
+
}
127
127
+
128
128
+
type DidAuditLog []DidAuditEntry
129
129
+
130
130
+
func FetchDidDoc(ctx context.Context, did string) (*DidDoc, error) {
131
131
+
var ustr string
132
132
+
if strings.HasPrefix(did, "did:plc:") {
133
133
+
ustr = fmt.Sprintf("https://plc.directory/%s", did)
134
134
+
} else if strings.HasPrefix(did, "did:web:") {
135
135
+
ustr = fmt.Sprintf("https://%s/.well-known/did.json", strings.TrimPrefix(did, "did:web:"))
136
136
+
} else {
137
137
+
return nil, fmt.Errorf("did was not a supported did type")
138
138
+
}
139
139
+
140
140
+
req, err := http.NewRequestWithContext(ctx, "GET", ustr, nil)
141
141
+
if err != nil {
142
142
+
return nil, err
143
143
+
}
144
144
+
145
145
+
resp, err := http.DefaultClient.Do(req)
146
146
+
if err != nil {
147
147
+
return nil, err
148
148
+
}
149
149
+
defer resp.Body.Close()
150
150
+
151
151
+
if resp.StatusCode != 200 {
152
152
+
io.Copy(io.Discard, resp.Body)
153
153
+
return nil, fmt.Errorf("could not find identity in plc registry")
154
154
+
}
155
155
+
156
156
+
var diddoc DidDoc
157
157
+
if err := json.NewDecoder(resp.Body).Decode(&diddoc); err != nil {
158
158
+
return nil, err
159
159
+
}
160
160
+
161
161
+
return &diddoc, nil
162
162
+
}
163
163
+
164
164
+
func FetchDidData(ctx context.Context, did string) (*DidData, error) {
165
165
+
var ustr string
166
166
+
ustr = fmt.Sprintf("https://plc.directory/%s/data", did)
167
167
+
168
168
+
req, err := http.NewRequestWithContext(ctx, "GET", ustr, nil)
169
169
+
if err != nil {
170
170
+
return nil, err
171
171
+
}
172
172
+
173
173
+
resp, err := http.DefaultClient.Do(req)
174
174
+
if err != nil {
175
175
+
return nil, err
176
176
+
}
177
177
+
defer resp.Body.Close()
178
178
+
179
179
+
if resp.StatusCode != 200 {
180
180
+
io.Copy(io.Discard, resp.Body)
181
181
+
return nil, fmt.Errorf("could not find identity in plc registry")
182
182
+
}
183
183
+
184
184
+
var diddata DidData
185
185
+
if err := json.NewDecoder(resp.Body).Decode(&diddata); err != nil {
186
186
+
return nil, err
187
187
+
}
188
188
+
189
189
+
return &diddata, nil
190
190
+
}
191
191
+
192
192
+
func FetchDidAuditLog(ctx context.Context, did string) (DidAuditLog, error) {
193
193
+
var ustr string
194
194
+
ustr = fmt.Sprintf("https://plc.directory/%s/log/audit", did)
195
195
+
196
196
+
req, err := http.NewRequestWithContext(ctx, "GET", ustr, nil)
197
197
+
if err != nil {
198
198
+
return nil, err
199
199
+
}
200
200
+
201
201
+
resp, err := http.DefaultClient.Do(req)
202
202
+
if err != nil {
203
203
+
return nil, err
204
204
+
}
205
205
+
defer resp.Body.Close()
206
206
+
207
207
+
if resp.StatusCode != 200 {
208
208
+
io.Copy(io.Discard, resp.Body)
209
209
+
return nil, fmt.Errorf("could not find identity in plc registry")
210
210
+
}
211
211
+
212
212
+
var didlog DidAuditLog
213
213
+
if err := json.NewDecoder(resp.Body).Decode(&didlog); err != nil {
214
214
+
return nil, err
215
215
+
}
216
216
+
217
217
+
return didlog, nil
218
218
+
}
219
219
+
220
220
+
func ResolveService(ctx context.Context, did string) (string, error) {
221
221
+
diddoc, err := FetchDidDoc(ctx, did)
222
222
+
if err != nil {
223
223
+
return "", err
224
224
+
}
225
225
+
226
226
+
var service string
227
227
+
for _, svc := range diddoc.Service {
228
228
+
if svc.Id == "#atproto_pds" {
229
229
+
service = svc.ServiceEndpoint
230
230
+
}
231
231
+
}
232
232
+
233
233
+
if service == "" {
234
234
+
return "", fmt.Errorf("could not find atproto_pds service in identity services")
235
235
+
}
236
236
+
237
237
+
return service, nil
238
238
+
}
+50
identity/mem_cache.go
Reviewed
···
1
1
+
package identity
2
2
+
3
3
+
import (
4
4
+
"time"
5
5
+
6
6
+
"github.com/hashicorp/golang-lru/v2/expirable"
7
7
+
)
8
8
+
9
9
+
type MemCache struct {
10
10
+
docCache *expirable.LRU[string, *DidDoc]
11
11
+
didCache *expirable.LRU[string, string]
12
12
+
}
13
13
+
14
14
+
func NewMemCache(size int) *MemCache {
15
15
+
docCache := expirable.NewLRU[string, *DidDoc](size, nil, 5*time.Minute)
16
16
+
didCache := expirable.NewLRU[string, string](size, nil, 5*time.Minute)
17
17
+
18
18
+
return &MemCache{
19
19
+
docCache: docCache,
20
20
+
didCache: didCache,
21
21
+
}
22
22
+
}
23
23
+
24
24
+
func (mc *MemCache) GetDoc(did string) (*DidDoc, bool) {
25
25
+
return mc.docCache.Get(did)
26
26
+
}
27
27
+
28
28
+
func (mc *MemCache) PutDoc(did string, doc *DidDoc) error {
29
29
+
mc.docCache.Add(did, doc)
30
30
+
return nil
31
31
+
}
32
32
+
33
33
+
func (mc *MemCache) BustDoc(did string) error {
34
34
+
mc.docCache.Remove(did)
35
35
+
return nil
36
36
+
}
37
37
+
38
38
+
func (mc *MemCache) GetDid(handle string) (string, bool) {
39
39
+
return mc.didCache.Get(handle)
40
40
+
}
41
41
+
42
42
+
func (mc *MemCache) PutDid(handle string, did string) error {
43
43
+
mc.didCache.Add(handle, did)
44
44
+
return nil
45
45
+
}
46
46
+
47
47
+
func (mc *MemCache) BustDid(handle string) error {
48
48
+
mc.didCache.Remove(handle)
49
49
+
return nil
50
50
+
}
+79
identity/passport.go
Reviewed
···
1
1
+
package identity
2
2
+
3
3
+
import (
4
4
+
"context"
5
5
+
"sync"
6
6
+
)
7
7
+
8
8
+
type BackingCache interface {
9
9
+
GetDoc(did string) (*DidDoc, bool)
10
10
+
PutDoc(did string, doc *DidDoc) error
11
11
+
BustDoc(did string) error
12
12
+
13
13
+
GetDid(handle string) (string, bool)
14
14
+
PutDid(handle string, did string) error
15
15
+
BustDid(handle string) error
16
16
+
}
17
17
+
18
18
+
type Passport struct {
19
19
+
bc BackingCache
20
20
+
lk sync.Mutex
21
21
+
}
22
22
+
23
23
+
func NewPassport(bc BackingCache) *Passport {
24
24
+
return &Passport{
25
25
+
bc: bc,
26
26
+
lk: sync.Mutex{},
27
27
+
}
28
28
+
}
29
29
+
30
30
+
func (p *Passport) FetchDoc(ctx context.Context, did string) (*DidDoc, error) {
31
31
+
skipCache, _ := ctx.Value("skip-cache").(bool)
32
32
+
33
33
+
if !skipCache {
34
34
+
cached, ok := p.bc.GetDoc(did)
35
35
+
if ok {
36
36
+
return cached, nil
37
37
+
}
38
38
+
}
39
39
+
40
40
+
p.lk.Lock() // this is pretty pathetic, and i should rethink this. but for now, fuck it
41
41
+
defer p.lk.Unlock()
42
42
+
43
43
+
doc, err := FetchDidDoc(ctx, did)
44
44
+
if err != nil {
45
45
+
return nil, err
46
46
+
}
47
47
+
48
48
+
p.bc.PutDoc(did, doc)
49
49
+
50
50
+
return doc, nil
51
51
+
}
52
52
+
53
53
+
func (p *Passport) ResolveHandle(ctx context.Context, handle string) (string, error) {
54
54
+
skipCache, _ := ctx.Value("skip-cache").(bool)
55
55
+
56
56
+
if !skipCache {
57
57
+
cached, ok := p.bc.GetDid(handle)
58
58
+
if ok {
59
59
+
return cached, nil
60
60
+
}
61
61
+
}
62
62
+
63
63
+
did, err := ResolveHandle(ctx, handle)
64
64
+
if err != nil {
65
65
+
return "", err
66
66
+
}
67
67
+
68
68
+
p.bc.PutDid(handle, did)
69
69
+
70
70
+
return did, nil
71
71
+
}
72
72
+
73
73
+
func (p *Passport) BustDoc(ctx context.Context, did string) error {
74
74
+
return p.bc.BustDoc(did)
75
75
+
}
76
76
+
77
77
+
func (p *Passport) BustDid(ctx context.Context, handle string) error {
78
78
+
return p.bc.BustDid(handle)
79
79
+
}
+25
internal/helpers/helpers.go
Reviewed
···
1
1
+
package helpers
2
2
+
3
3
+
import "github.com/labstack/echo/v4"
4
4
+
5
5
+
func InputError(e echo.Context, custom *string) error {
6
6
+
msg := "InvalidRequest"
7
7
+
if custom != nil {
8
8
+
msg = *custom
9
9
+
}
10
10
+
return genericError(e, 400, msg)
11
11
+
}
12
12
+
13
13
+
func ServerError(e echo.Context, suffix *string) error {
14
14
+
msg := "Internal server error"
15
15
+
if suffix != nil {
16
16
+
msg += ". " + *suffix
17
17
+
}
18
18
+
return genericError(e, 400, msg)
19
19
+
}
20
20
+
21
21
+
func genericError(e echo.Context, code int, msg string) error {
22
22
+
return e.JSON(code, map[string]string{
23
23
+
"error": msg,
24
24
+
})
25
25
+
}
+96
models/models.go
Reviewed
···
1
1
+
package models
2
2
+
3
3
+
import (
4
4
+
"context"
5
5
+
"time"
6
6
+
7
7
+
"github.com/bluesky-social/indigo/atproto/crypto"
8
8
+
)
9
9
+
10
10
+
type Repo struct {
11
11
+
Did string `gorm:"primaryKey"`
12
12
+
CreatedAt time.Time
13
13
+
Email string `gorm:"uniqueIndex"`
14
14
+
EmailConfirmedAt *time.Time
15
15
+
Password string
16
16
+
SigningKey []byte
17
17
+
Rev string
18
18
+
Root []byte
19
19
+
Preferences []byte
20
20
+
}
21
21
+
22
22
+
func (r *Repo) SignFor(ctx context.Context, did string, msg []byte) ([]byte, error) {
23
23
+
k, err := crypto.ParsePrivateBytesK256(r.SigningKey)
24
24
+
if err != nil {
25
25
+
return nil, err
26
26
+
}
27
27
+
28
28
+
sig, err := k.HashAndSign(msg)
29
29
+
if err != nil {
30
30
+
return nil, err
31
31
+
}
32
32
+
33
33
+
return sig, nil
34
34
+
}
35
35
+
36
36
+
type Actor struct {
37
37
+
Did string `gorm:"primaryKey"`
38
38
+
Handle string `gorm:"uniqueIndex"`
39
39
+
}
40
40
+
41
41
+
type RepoActor struct {
42
42
+
Repo
43
43
+
Actor
44
44
+
}
45
45
+
46
46
+
type InviteCode struct {
47
47
+
Code string `gorm:"primaryKey"`
48
48
+
Did string `gorm:"index"`
49
49
+
RemainingUseCount int
50
50
+
}
51
51
+
52
52
+
type Token struct {
53
53
+
Token string `gorm:"primaryKey"`
54
54
+
Did string `gorm:"index"`
55
55
+
RefreshToken string `gorm:"index"`
56
56
+
CreatedAt time.Time
57
57
+
ExpiresAt time.Time `gorm:"index:,sort:asc"`
58
58
+
}
59
59
+
60
60
+
type RefreshToken struct {
61
61
+
Token string `gorm:"primaryKey"`
62
62
+
Did string `gorm:"index"`
63
63
+
CreatedAt time.Time
64
64
+
ExpiresAt time.Time `gorm:"index:,sort:asc"`
65
65
+
}
66
66
+
67
67
+
type Record struct {
68
68
+
Did string `gorm:"primaryKey:idx_record_did_created_at;index:idx_record_did_nsid"`
69
69
+
CreatedAt string `gorm:"index;index:idx_record_did_created_at,sort:desc"`
70
70
+
Nsid string `gorm:"primaryKey;index:idx_record_did_nsid"`
71
71
+
Rkey string `gorm:"primaryKey"`
72
72
+
Cid string
73
73
+
Value []byte
74
74
+
}
75
75
+
76
76
+
type Block struct {
77
77
+
Did string `gorm:"primaryKey;index:idx_blocks_by_rev"`
78
78
+
Cid []byte `gorm:"primaryKey"`
79
79
+
Rev string `gorm:"index:idx_blocks_by_rev,sort:desc"`
80
80
+
Value []byte
81
81
+
}
82
82
+
83
83
+
type Blob struct {
84
84
+
ID uint
85
85
+
CreatedAt string `gorm:"index"`
86
86
+
Did string `gorm:"index;index:idx_blob_did_cid"`
87
87
+
Cid []byte `gorm:"index;index:idx_blob_did_cid"`
88
88
+
RefCount int
89
89
+
}
90
90
+
91
91
+
type BlobPart struct {
92
92
+
Blob Blob
93
93
+
BlobID uint `gorm:"primaryKey"`
94
94
+
Idx int `gorm:"primaryKey"`
95
95
+
Data []byte
96
96
+
}
+182
plc/client.go
Reviewed
···
1
1
+
package plc
2
2
+
3
3
+
import (
4
4
+
"bytes"
5
5
+
"context"
6
6
+
"crypto/sha256"
7
7
+
"encoding/base32"
8
8
+
"encoding/base64"
9
9
+
"encoding/json"
10
10
+
"fmt"
11
11
+
"io"
12
12
+
"net/http"
13
13
+
"net/url"
14
14
+
"strings"
15
15
+
"time"
16
16
+
17
17
+
"github.com/bluesky-social/indigo/atproto/crypto"
18
18
+
"github.com/bluesky-social/indigo/atproto/data"
19
19
+
"github.com/bluesky-social/indigo/did"
20
20
+
"github.com/bluesky-social/indigo/plc"
21
21
+
"github.com/bluesky-social/indigo/util"
22
22
+
)
23
23
+
24
24
+
type Client struct {
25
25
+
plc.CachingDidResolver
26
26
+
27
27
+
h *http.Client
28
28
+
29
29
+
service string
30
30
+
rotationKey *crypto.PrivateKeyK256
31
31
+
recoveryKey string
32
32
+
pdsHostname string
33
33
+
}
34
34
+
35
35
+
type ClientArgs struct {
36
36
+
Service string
37
37
+
RotationKey []byte
38
38
+
RecoveryKey string
39
39
+
PdsHostname string
40
40
+
}
41
41
+
42
42
+
func NewClient(args *ClientArgs) (*Client, error) {
43
43
+
if args.Service == "" {
44
44
+
args.Service = "https://plc.directory"
45
45
+
}
46
46
+
47
47
+
rk, err := crypto.ParsePrivateBytesK256([]byte(args.RotationKey))
48
48
+
if err != nil {
49
49
+
return nil, err
50
50
+
}
51
51
+
52
52
+
resolver := did.NewMultiResolver()
53
53
+
return &Client{
54
54
+
CachingDidResolver: *plc.NewCachingDidResolver(resolver, 5*time.Minute, 100_000),
55
55
+
h: util.RobustHTTPClient(),
56
56
+
service: args.Service,
57
57
+
rotationKey: rk,
58
58
+
recoveryKey: args.RecoveryKey,
59
59
+
pdsHostname: args.PdsHostname,
60
60
+
}, nil
61
61
+
}
62
62
+
63
63
+
func (c *Client) CreateDID(ctx context.Context, sigkey *crypto.PrivateKeyK256, recovery string, handle string) (string, map[string]any, error) {
64
64
+
pubrotkey, err := c.rotationKey.PublicKey()
65
65
+
if err != nil {
66
66
+
return "", nil, err
67
67
+
}
68
68
+
69
69
+
// todo
70
70
+
rotationKeys := []string{pubrotkey.DIDKey()}
71
71
+
if c.recoveryKey != "" {
72
72
+
rotationKeys = []string{c.recoveryKey, rotationKeys[0]}
73
73
+
}
74
74
+
if recovery != "" {
75
75
+
rotationKeys = func(recovery string) []string {
76
76
+
newRotationKeys := []string{recovery}
77
77
+
for _, k := range rotationKeys {
78
78
+
newRotationKeys = append(newRotationKeys, k)
79
79
+
}
80
80
+
return newRotationKeys
81
81
+
}(recovery)
82
82
+
}
83
83
+
84
84
+
op, err := c.FormatAndSignAtprotoOp(sigkey, handle, rotationKeys, nil)
85
85
+
if err != nil {
86
86
+
return "", nil, err
87
87
+
}
88
88
+
89
89
+
did, err := didForCreateOp(op)
90
90
+
if err != nil {
91
91
+
return "", nil, err
92
92
+
}
93
93
+
94
94
+
return did, op, nil
95
95
+
}
96
96
+
97
97
+
func (c *Client) UpdateUserHandle(ctx context.Context, didstr string, nhandle string) error {
98
98
+
return nil
99
99
+
}
100
100
+
101
101
+
func (c *Client) FormatAndSignAtprotoOp(sigkey *crypto.PrivateKeyK256, handle string, rotationKeys []string, prev *string) (map[string]any, error) {
102
102
+
pubsigkey, err := sigkey.PublicKey()
103
103
+
if err != nil {
104
104
+
return nil, err
105
105
+
}
106
106
+
107
107
+
op := map[string]any{
108
108
+
"type": "plc_operation",
109
109
+
"verificationMethods": map[string]string{
110
110
+
"atproto": pubsigkey.DIDKey(),
111
111
+
},
112
112
+
"rotationKeys": rotationKeys,
113
113
+
"alsoKnownAs": []string{"at://" + handle},
114
114
+
"services": map[string]any{
115
115
+
"atproto_pds": map[string]string{
116
116
+
"type": "AtprotoPersonalDataServer",
117
117
+
"endpoint": "https://" + c.pdsHostname,
118
118
+
},
119
119
+
},
120
120
+
"prev": prev,
121
121
+
}
122
122
+
123
123
+
b, err := data.MarshalCBOR(op)
124
124
+
if err != nil {
125
125
+
return nil, err
126
126
+
}
127
127
+
128
128
+
sig, err := c.rotationKey.HashAndSign(b)
129
129
+
if err != nil {
130
130
+
return nil, err
131
131
+
}
132
132
+
133
133
+
op["sig"] = base64.RawURLEncoding.EncodeToString(sig)
134
134
+
135
135
+
return op, nil
136
136
+
}
137
137
+
138
138
+
func didForCreateOp(op map[string]any) (string, error) {
139
139
+
b, err := data.MarshalCBOR(op)
140
140
+
if err != nil {
141
141
+
return "", err
142
142
+
}
143
143
+
144
144
+
h := sha256.New()
145
145
+
h.Write(b)
146
146
+
bs := h.Sum(nil)
147
147
+
148
148
+
b32 := strings.ToLower(base32.StdEncoding.EncodeToString(bs))
149
149
+
150
150
+
return "did:plc:" + b32[0:24], nil
151
151
+
}
152
152
+
153
153
+
func (c *Client) SendOperation(ctx context.Context, did string, op any) error {
154
154
+
b, err := json.Marshal(op)
155
155
+
if err != nil {
156
156
+
return err
157
157
+
}
158
158
+
159
159
+
req, err := http.NewRequestWithContext(ctx, "POST", c.service+"/"+url.QueryEscape(did), bytes.NewBuffer(b))
160
160
+
if err != nil {
161
161
+
return err
162
162
+
}
163
163
+
164
164
+
req.Header.Add("content-type", "application/json")
165
165
+
166
166
+
resp, err := c.h.Do(req)
167
167
+
if err != nil {
168
168
+
return err
169
169
+
}
170
170
+
defer resp.Body.Close()
171
171
+
172
172
+
fmt.Println(resp.StatusCode)
173
173
+
174
174
+
b, err = io.ReadAll(resp.Body)
175
175
+
if err != nil {
176
176
+
return err
177
177
+
}
178
178
+
179
179
+
fmt.Println(string(b))
180
180
+
181
181
+
return nil
182
182
+
}
+29
server/common.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"github.com/haileyok/cocoon/models"
5
5
+
)
6
6
+
7
7
+
func (s *Server) getActorByHandle(handle string) (*models.Actor, error) {
8
8
+
var actor models.Actor
9
9
+
if err := s.db.First(&actor, models.Actor{Handle: handle}).Error; err != nil {
10
10
+
return nil, err
11
11
+
}
12
12
+
return &actor, nil
13
13
+
}
14
14
+
15
15
+
func (s *Server) getRepoByEmail(email string) (*models.Repo, error) {
16
16
+
var repo models.Repo
17
17
+
if err := s.db.First(&repo, models.Repo{Email: email}).Error; err != nil {
18
18
+
return nil, err
19
19
+
}
20
20
+
return &repo, nil
21
21
+
}
22
22
+
23
23
+
func (s *Server) getRepoActorByDid(did string) (*models.RepoActor, error) {
24
24
+
var repo models.RepoActor
25
25
+
if err := s.db.Raw("SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.did = ?", did).Scan(&repo).Error; err != nil {
26
26
+
return nil, err
27
27
+
}
28
28
+
return &repo, nil
29
29
+
}
+24
server/handle_actor_get_preferences.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"encoding/json"
5
5
+
6
6
+
"github.com/haileyok/cocoon/models"
7
7
+
"github.com/labstack/echo/v4"
8
8
+
)
9
9
+
10
10
+
// This is kinda lame. Not great to implement app.bsky in the pds, but alas
11
11
+
12
12
+
func (s *Server) handleActorGetPreferences(e echo.Context) error {
13
13
+
repo := e.Get("repo").(*models.RepoActor)
14
14
+
15
15
+
var prefs map[string]any
16
16
+
err := json.Unmarshal(repo.Preferences, &prefs)
17
17
+
if err != nil {
18
18
+
prefs = map[string]any{
19
19
+
"preferences": map[string]any{},
20
20
+
}
21
21
+
}
22
22
+
23
23
+
return e.JSON(200, prefs)
24
24
+
}
+30
server/handle_actor_put_preferences.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"encoding/json"
5
5
+
6
6
+
"github.com/haileyok/cocoon/models"
7
7
+
"github.com/labstack/echo/v4"
8
8
+
)
9
9
+
10
10
+
// This is kinda lame. Not great to implement app.bsky in the pds, but alas
11
11
+
12
12
+
func (s *Server) handleActorPutPreferences(e echo.Context) error {
13
13
+
repo := e.Get("repo").(*models.RepoActor)
14
14
+
15
15
+
var prefs map[string]any
16
16
+
if err := json.NewDecoder(e.Request().Body).Decode(&prefs); err != nil {
17
17
+
return err
18
18
+
}
19
19
+
20
20
+
b, err := json.Marshal(prefs)
21
21
+
if err != nil {
22
22
+
return err
23
23
+
}
24
24
+
25
25
+
if err := s.db.Exec("UPDATE repos SET preferences = ? WHERE did = ?", b, repo.Repo.Did).Error; err != nil {
26
26
+
return err
27
27
+
}
28
28
+
29
29
+
return nil
30
30
+
}
+9
server/handle_health.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import "github.com/labstack/echo/v4"
4
4
+
5
5
+
func (s *Server) handleHealth(e echo.Context) error {
6
6
+
return e.JSON(200, map[string]string{
7
7
+
"version": "cocoon " + s.config.Version,
8
8
+
})
9
9
+
}
+89
server/handle_identity_update_handle.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"context"
5
5
+
"strings"
6
6
+
"time"
7
7
+
8
8
+
"github.com/Azure/go-autorest/autorest/to"
9
9
+
"github.com/bluesky-social/indigo/api/atproto"
10
10
+
"github.com/bluesky-social/indigo/atproto/crypto"
11
11
+
"github.com/bluesky-social/indigo/events"
12
12
+
"github.com/bluesky-social/indigo/util"
13
13
+
"github.com/haileyok/cocoon/identity"
14
14
+
"github.com/haileyok/cocoon/internal/helpers"
15
15
+
"github.com/haileyok/cocoon/models"
16
16
+
"github.com/labstack/echo/v4"
17
17
+
)
18
18
+
19
19
+
type ComAtprotoIdentityUpdateHandleRequest struct {
20
20
+
Handle string `json:"handle" validate:"atproto-handle"`
21
21
+
}
22
22
+
23
23
+
func (s *Server) handleIdentityUpdateHandle(e echo.Context) error {
24
24
+
repo := e.Get("repo").(*models.RepoActor)
25
25
+
26
26
+
var req ComAtprotoIdentityUpdateHandleRequest
27
27
+
if err := e.Bind(&req); err != nil {
28
28
+
s.logger.Error("error binding", "error", err)
29
29
+
return helpers.ServerError(e, nil)
30
30
+
}
31
31
+
32
32
+
req.Handle = strings.ToLower(req.Handle)
33
33
+
34
34
+
if err := e.Validate(req); err != nil {
35
35
+
return helpers.InputError(e, nil)
36
36
+
}
37
37
+
38
38
+
ctx := context.WithValue(e.Request().Context(), "skip-cache", true)
39
39
+
40
40
+
if strings.HasPrefix(repo.Repo.Did, "did:plc:") {
41
41
+
log, err := identity.FetchDidAuditLog(ctx, repo.Repo.Did)
42
42
+
if err != nil {
43
43
+
s.logger.Error("error fetching doc", "error", err)
44
44
+
return helpers.ServerError(e, nil)
45
45
+
}
46
46
+
47
47
+
latest := log[len(log)-1]
48
48
+
49
49
+
k, err := crypto.ParsePrivateBytesK256(repo.SigningKey)
50
50
+
if err != nil {
51
51
+
s.logger.Error("error parsing signing key", "error", err)
52
52
+
return helpers.ServerError(e, nil)
53
53
+
}
54
54
+
55
55
+
op, err := s.plcClient.FormatAndSignAtprotoOp(k, req.Handle, latest.Operation.RotationKeys, &latest.Cid)
56
56
+
if err != nil {
57
57
+
return err
58
58
+
}
59
59
+
60
60
+
if err := s.plcClient.SendOperation(context.TODO(), repo.Repo.Did, op); err != nil {
61
61
+
return err
62
62
+
}
63
63
+
}
64
64
+
65
65
+
s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{
66
66
+
RepoHandle: &atproto.SyncSubscribeRepos_Handle{
67
67
+
Did: repo.Repo.Did,
68
68
+
Handle: req.Handle,
69
69
+
Seq: time.Now().UnixMicro(), // TODO: no
70
70
+
Time: time.Now().Format(util.ISO8601),
71
71
+
},
72
72
+
})
73
73
+
74
74
+
s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{
75
75
+
RepoIdentity: &atproto.SyncSubscribeRepos_Identity{
76
76
+
Did: repo.Repo.Did,
77
77
+
Handle: to.StringPtr(req.Handle),
78
78
+
Seq: time.Now().UnixMicro(), // TODO: no
79
79
+
Time: time.Now().Format(util.ISO8601),
80
80
+
},
81
81
+
})
82
82
+
83
83
+
if err := s.db.Exec("UPDATE actors SET handle = ? WHERE did = ?", req.Handle, repo.Repo.Did).Error; err != nil {
84
84
+
s.logger.Error("error updating handle in db", "error", err)
85
85
+
return helpers.ServerError(e, nil)
86
86
+
}
87
87
+
88
88
+
return nil
89
89
+
}
+144
server/handle_proxy.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"crypto/rand"
5
5
+
"crypto/sha256"
6
6
+
"encoding/base64"
7
7
+
"encoding/json"
8
8
+
"fmt"
9
9
+
"net/http"
10
10
+
"strings"
11
11
+
"time"
12
12
+
13
13
+
"github.com/google/uuid"
14
14
+
"github.com/haileyok/cocoon/internal/helpers"
15
15
+
"github.com/haileyok/cocoon/models"
16
16
+
"github.com/labstack/echo/v4"
17
17
+
secp256k1secec "gitlab.com/yawning/secp256k1-voi/secec"
18
18
+
)
19
19
+
20
20
+
func (s *Server) handleProxy(e echo.Context) error {
21
21
+
repo, isAuthed := e.Get("repo").(*models.RepoActor)
22
22
+
23
23
+
pts := strings.Split(e.Request().URL.Path, "/")
24
24
+
if len(pts) != 3 {
25
25
+
return fmt.Errorf("incorrect number of parts")
26
26
+
}
27
27
+
28
28
+
svc := e.Request().Header.Get("atproto-proxy")
29
29
+
if svc == "" {
30
30
+
svc = "did:web:api.bsky.app#bsky_appview" // TODO: should be a config var probably
31
31
+
}
32
32
+
33
33
+
svcPts := strings.Split(svc, "#")
34
34
+
if len(svcPts) != 2 {
35
35
+
return fmt.Errorf("invalid service header")
36
36
+
}
37
37
+
38
38
+
svcDid := svcPts[0]
39
39
+
svcId := "#" + svcPts[1]
40
40
+
41
41
+
doc, err := s.passport.FetchDoc(e.Request().Context(), svcDid)
42
42
+
if err != nil {
43
43
+
return err
44
44
+
}
45
45
+
46
46
+
var endpoint string
47
47
+
for _, s := range doc.Service {
48
48
+
if s.Id == svcId {
49
49
+
endpoint = s.ServiceEndpoint
50
50
+
}
51
51
+
}
52
52
+
53
53
+
requrl := e.Request().URL
54
54
+
requrl.Host = strings.TrimPrefix(endpoint, "https://")
55
55
+
requrl.Scheme = "https"
56
56
+
57
57
+
body := e.Request().Body
58
58
+
if e.Request().Method == "GET" {
59
59
+
body = nil
60
60
+
}
61
61
+
62
62
+
req, err := http.NewRequest(e.Request().Method, requrl.String(), body)
63
63
+
if err != nil {
64
64
+
return err
65
65
+
}
66
66
+
67
67
+
req.Header = e.Request().Header.Clone()
68
68
+
69
69
+
if isAuthed {
70
70
+
// this is a little dumb. i should probably figure out a better way to do this, and use
71
71
+
// a single way of creating/signing jwts throughout the pds. kinda limited here because
72
72
+
// im using the atproto crypto lib for this though. will come back to it
73
73
+
74
74
+
header := map[string]string{
75
75
+
"alg": "ES256K",
76
76
+
"crv": "secp256k1",
77
77
+
"typ": "JWT",
78
78
+
}
79
79
+
hj, err := json.Marshal(header)
80
80
+
if err != nil {
81
81
+
s.logger.Error("error marshaling header", "error", err)
82
82
+
return helpers.ServerError(e, nil)
83
83
+
}
84
84
+
85
85
+
encheader := strings.TrimRight(base64.RawURLEncoding.EncodeToString(hj), "=")
86
86
+
87
87
+
payload := map[string]any{
88
88
+
"iss": repo.Repo.Did,
89
89
+
"aud": svcDid,
90
90
+
"lxm": pts[2],
91
91
+
"jti": uuid.NewString(),
92
92
+
"exp": time.Now().Add(1 * time.Minute).UTC().Unix(),
93
93
+
}
94
94
+
pj, err := json.Marshal(payload)
95
95
+
if err != nil {
96
96
+
s.logger.Error("error marashaling payload", "error", err)
97
97
+
return helpers.ServerError(e, nil)
98
98
+
}
99
99
+
100
100
+
encpayload := strings.TrimRight(base64.RawURLEncoding.EncodeToString(pj), "=")
101
101
+
102
102
+
input := fmt.Sprintf("%s.%s", encheader, encpayload)
103
103
+
hash := sha256.Sum256([]byte(input))
104
104
+
105
105
+
sk, err := secp256k1secec.NewPrivateKey(repo.SigningKey)
106
106
+
if err != nil {
107
107
+
s.logger.Error("can't load private key", "error", err)
108
108
+
return err
109
109
+
}
110
110
+
111
111
+
R, S, _, err := sk.SignRaw(rand.Reader, hash[:])
112
112
+
if err != nil {
113
113
+
s.logger.Error("error signing", "error", err)
114
114
+
}
115
115
+
116
116
+
rBytes := R.Bytes()
117
117
+
sBytes := S.Bytes()
118
118
+
119
119
+
rPadded := make([]byte, 32)
120
120
+
sPadded := make([]byte, 32)
121
121
+
copy(rPadded[32-len(rBytes):], rBytes)
122
122
+
copy(sPadded[32-len(sBytes):], sBytes)
123
123
+
124
124
+
rawsig := append(rPadded, sPadded...)
125
125
+
encsig := strings.TrimRight(base64.RawURLEncoding.EncodeToString(rawsig), "=")
126
126
+
token := fmt.Sprintf("%s.%s", input, encsig)
127
127
+
128
128
+
req.Header.Set("authorization", "Bearer "+token)
129
129
+
} else {
130
130
+
req.Header.Del("authorization")
131
131
+
}
132
132
+
133
133
+
resp, err := http.DefaultClient.Do(req)
134
134
+
if err != nil {
135
135
+
return err
136
136
+
}
137
137
+
defer resp.Body.Close()
138
138
+
139
139
+
for k, v := range resp.Header {
140
140
+
e.Response().Header().Set(k, strings.Join(v, ","))
141
141
+
}
142
142
+
143
143
+
return e.Stream(resp.StatusCode, e.Response().Header().Get("content-type"), resp.Body)
144
144
+
}
+58
server/handle_repo_apply_writes.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"github.com/haileyok/cocoon/internal/helpers"
5
5
+
"github.com/haileyok/cocoon/models"
6
6
+
"github.com/labstack/echo/v4"
7
7
+
)
8
8
+
9
9
+
type ComAtprotoRepoApplyWritesRequest struct {
10
10
+
Repo string `json:"repo" validate:"required,atproto-did"`
11
11
+
Validate *bool `json:"bool,omitempty"`
12
12
+
Writes []ComAtprotoRepoApplyWritesItem `json:"writes"`
13
13
+
SwapCommit *string `json:"swapCommit"`
14
14
+
}
15
15
+
16
16
+
type ComAtprotoRepoApplyWritesItem struct {
17
17
+
Type string `json:"$type"`
18
18
+
Collection string `json:"collection"`
19
19
+
Rkey string `json:"rkey"`
20
20
+
Value *MarshalableMap `json:"value,omitempty"`
21
21
+
}
22
22
+
23
23
+
func (s *Server) handleApplyWrites(e echo.Context) error {
24
24
+
repo := e.Get("repo").(*models.RepoActor)
25
25
+
26
26
+
var req ComAtprotoRepoApplyWritesRequest
27
27
+
if err := e.Bind(&req); err != nil {
28
28
+
s.logger.Error("error binding", "error", err)
29
29
+
return helpers.ServerError(e, nil)
30
30
+
}
31
31
+
32
32
+
if err := e.Validate(req); err != nil {
33
33
+
s.logger.Error("error validating", "error", err)
34
34
+
return helpers.InputError(e, nil)
35
35
+
}
36
36
+
37
37
+
if repo.Repo.Did != req.Repo {
38
38
+
s.logger.Warn("mismatched repo/auth")
39
39
+
return helpers.InputError(e, nil)
40
40
+
}
41
41
+
42
42
+
ops := []Op{}
43
43
+
for _, item := range req.Writes {
44
44
+
ops = append(ops, Op{
45
45
+
Type: OpType(item.Type),
46
46
+
Collection: item.Collection,
47
47
+
Rkey: &item.Rkey,
48
48
+
Record: item.Value,
49
49
+
})
50
50
+
}
51
51
+
52
52
+
if err := s.repoman.applyWrites(repo.Repo, ops, req.SwapCommit); err != nil {
53
53
+
s.logger.Error("error applying writes", "error", err)
54
54
+
return helpers.ServerError(e, nil)
55
55
+
}
56
56
+
57
57
+
return nil
58
58
+
}
+58
server/handle_repo_create_record.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"github.com/haileyok/cocoon/internal/helpers"
5
5
+
"github.com/haileyok/cocoon/models"
6
6
+
"github.com/labstack/echo/v4"
7
7
+
)
8
8
+
9
9
+
type ComAtprotoRepoCreateRecordRequest struct {
10
10
+
Repo string `json:"repo" validate:"required,atproto-did"`
11
11
+
Collection string `json:"collection" validate:"required,atproto-nsid"`
12
12
+
Rkey *string `json:"rkey,omitempty"`
13
13
+
Validate *bool `json:"bool,omitempty"`
14
14
+
Record MarshalableMap `json:"record" validate:"required"`
15
15
+
SwapRecord *string `json:"swapRecord"`
16
16
+
SwapCommit *string `json:"swapCommit"`
17
17
+
}
18
18
+
19
19
+
func (s *Server) handleCreateRecord(e echo.Context) error {
20
20
+
repo := e.Get("repo").(*models.RepoActor)
21
21
+
22
22
+
var req ComAtprotoRepoCreateRecordRequest
23
23
+
if err := e.Bind(&req); err != nil {
24
24
+
s.logger.Error("error binding", "error", err)
25
25
+
return helpers.ServerError(e, nil)
26
26
+
}
27
27
+
28
28
+
if err := e.Validate(req); err != nil {
29
29
+
s.logger.Error("error validating", "error", err)
30
30
+
return helpers.InputError(e, nil)
31
31
+
}
32
32
+
33
33
+
if repo.Repo.Did != req.Repo {
34
34
+
s.logger.Warn("mismatched repo/auth")
35
35
+
return helpers.InputError(e, nil)
36
36
+
}
37
37
+
38
38
+
optype := OpTypeCreate
39
39
+
if req.SwapRecord != nil {
40
40
+
optype = OpTypeUpdate
41
41
+
}
42
42
+
43
43
+
if err := s.repoman.applyWrites(repo.Repo, []Op{
44
44
+
{
45
45
+
Type: optype,
46
46
+
Collection: req.Collection,
47
47
+
Rkey: req.Rkey,
48
48
+
Validate: req.Validate,
49
49
+
Record: &req.Record,
50
50
+
SwapRecord: req.SwapRecord,
51
51
+
},
52
52
+
}, req.SwapCommit); err != nil {
53
53
+
s.logger.Error("error applying writes", "error", err)
54
54
+
return helpers.ServerError(e, nil)
55
55
+
}
56
56
+
57
57
+
return nil
58
58
+
}
+84
server/handle_repo_describe_repo.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"strings"
5
5
+
6
6
+
"github.com/Azure/go-autorest/autorest/to"
7
7
+
"github.com/haileyok/cocoon/identity"
8
8
+
"github.com/haileyok/cocoon/internal/helpers"
9
9
+
"github.com/haileyok/cocoon/models"
10
10
+
"github.com/labstack/echo/v4"
11
11
+
"gorm.io/gorm"
12
12
+
)
13
13
+
14
14
+
type ComAtprotoRepoDescribeRepoResponse struct {
15
15
+
Did string `json:"did"`
16
16
+
Handle string `json:"handle"`
17
17
+
DidDoc identity.DidDoc `json:"didDoc"`
18
18
+
Collections []string `json:"collections"`
19
19
+
HandleIsCorrect bool `json:"handleIsCorrect"`
20
20
+
}
21
21
+
22
22
+
func (s *Server) handleDescribeRepo(e echo.Context) error {
23
23
+
did := e.QueryParam("repo")
24
24
+
repo, err := s.getRepoActorByDid(did)
25
25
+
if err != nil {
26
26
+
if err == gorm.ErrRecordNotFound {
27
27
+
return helpers.InputError(e, to.StringPtr("RepoNotFound"))
28
28
+
}
29
29
+
30
30
+
s.logger.Error("error looking up repo", "error", err)
31
31
+
return helpers.ServerError(e, nil)
32
32
+
}
33
33
+
34
34
+
handleIsCorrect := true
35
35
+
36
36
+
diddoc, err := s.passport.FetchDoc(e.Request().Context(), repo.Repo.Did)
37
37
+
if err != nil {
38
38
+
s.logger.Error("error fetching diddoc", "error", err)
39
39
+
return helpers.ServerError(e, nil)
40
40
+
}
41
41
+
42
42
+
dochandle := ""
43
43
+
for _, aka := range diddoc.AlsoKnownAs {
44
44
+
if strings.HasPrefix(aka, "at://") {
45
45
+
dochandle = strings.TrimPrefix(aka, "at://")
46
46
+
break
47
47
+
}
48
48
+
}
49
49
+
50
50
+
if repo.Handle != dochandle {
51
51
+
handleIsCorrect = false
52
52
+
}
53
53
+
54
54
+
if handleIsCorrect {
55
55
+
resolvedDid, err := s.passport.ResolveHandle(e.Request().Context(), repo.Handle)
56
56
+
if err != nil {
57
57
+
e.Logger().Error("error resolving handle", "error", err)
58
58
+
return helpers.ServerError(e, nil)
59
59
+
}
60
60
+
61
61
+
if resolvedDid != repo.Repo.Did {
62
62
+
handleIsCorrect = false
63
63
+
}
64
64
+
}
65
65
+
66
66
+
var records []models.Record
67
67
+
if err := s.db.Raw("SELECT DISTINCT(nsid) FROM records WHERE did = ?", repo.Repo.Did).Scan(&records).Error; err != nil {
68
68
+
s.logger.Error("error getting collections", "error", err)
69
69
+
return helpers.ServerError(e, nil)
70
70
+
}
71
71
+
72
72
+
var collections []string
73
73
+
for _, r := range records {
74
74
+
collections = append(collections, r.Nsid)
75
75
+
}
76
76
+
77
77
+
return e.JSON(200, ComAtprotoRepoDescribeRepoResponse{
78
78
+
Did: repo.Repo.Did,
79
79
+
Handle: repo.Handle,
80
80
+
DidDoc: *diddoc,
81
81
+
Collections: collections,
82
82
+
HandleIsCorrect: handleIsCorrect,
83
83
+
})
84
84
+
}
+50
server/handle_repo_get_record.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"github.com/bluesky-social/indigo/atproto/data"
5
5
+
"github.com/bluesky-social/indigo/atproto/syntax"
6
6
+
"github.com/haileyok/cocoon/models"
7
7
+
"github.com/labstack/echo/v4"
8
8
+
)
9
9
+
10
10
+
type ComAtprotoRepoGetRecordResponse struct {
11
11
+
Uri string `json:"uri"`
12
12
+
Cid string `json:"cid"`
13
13
+
Value map[string]any `json:"value"`
14
14
+
}
15
15
+
16
16
+
func (s *Server) handleRepoGetRecord(e echo.Context) error {
17
17
+
repo := e.QueryParam("repo")
18
18
+
collection := e.QueryParam("collection")
19
19
+
rkey := e.QueryParam("rkey")
20
20
+
cidstr := e.QueryParam("cid")
21
21
+
22
22
+
params := []any{repo, collection, rkey}
23
23
+
cidquery := ""
24
24
+
25
25
+
if cidstr != "" {
26
26
+
c, err := syntax.ParseCID(cidstr)
27
27
+
if err != nil {
28
28
+
return err
29
29
+
}
30
30
+
params = append(params, c.String())
31
31
+
cidquery = " AND cid = ?"
32
32
+
}
33
33
+
34
34
+
var record models.Record
35
35
+
if err := s.db.Raw("SELECT * FROM records WHERE did = ? AND nsid = ? AND rkey = ?"+cidquery, params...).Scan(&record).Error; err != nil {
36
36
+
// TODO: handle error nicely
37
37
+
return err
38
38
+
}
39
39
+
40
40
+
val, err := data.UnmarshalCBOR(record.Value)
41
41
+
if err != nil {
42
42
+
return s.handleProxy(e) // TODO: this should be getting handled like...if we don't find it in the db. why doesn't it throw error up there?
43
43
+
}
44
44
+
45
45
+
return e.JSON(200, ComAtprotoRepoGetRecordResponse{
46
46
+
Uri: "at://" + record.Did + "/" + record.Nsid + "/" + record.Rkey,
47
47
+
Cid: record.Cid,
48
48
+
Value: val,
49
49
+
})
50
50
+
}
+95
server/handle_repo_list_records.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"strconv"
5
5
+
"strings"
6
6
+
7
7
+
"github.com/Azure/go-autorest/autorest/to"
8
8
+
"github.com/bluesky-social/indigo/atproto/data"
9
9
+
"github.com/haileyok/cocoon/internal/helpers"
10
10
+
"github.com/haileyok/cocoon/models"
11
11
+
"github.com/labstack/echo/v4"
12
12
+
)
13
13
+
14
14
+
type ComAtprotoRepoListRecordsResponse struct {
15
15
+
Cursor *string `json:"cursor,omitempty"`
16
16
+
Records []ComAtprotoRepoListRecordsRecordItem `json:"records"`
17
17
+
}
18
18
+
19
19
+
type ComAtprotoRepoListRecordsRecordItem struct {
20
20
+
Uri string `json:"uri"`
21
21
+
Cid string `json:"cid"`
22
22
+
Value map[string]any `json:"value"`
23
23
+
}
24
24
+
25
25
+
func getLimitFromContext(e echo.Context, def int) (int, error) {
26
26
+
limit := def
27
27
+
limitstr := e.QueryParam("limit")
28
28
+
29
29
+
if limitstr != "" {
30
30
+
l64, err := strconv.ParseInt(limitstr, 10, 32)
31
31
+
if err != nil {
32
32
+
return 0, err
33
33
+
}
34
34
+
limit = int(l64)
35
35
+
}
36
36
+
37
37
+
return limit, nil
38
38
+
}
39
39
+
40
40
+
func (s *Server) handleListRecords(e echo.Context) error {
41
41
+
did := e.QueryParam("repo")
42
42
+
collection := e.QueryParam("collection")
43
43
+
cursor := e.QueryParam("cursor")
44
44
+
reverse := e.QueryParam("reverse")
45
45
+
limit, err := getLimitFromContext(e, 50)
46
46
+
if err != nil {
47
47
+
return helpers.InputError(e, nil)
48
48
+
}
49
49
+
50
50
+
sort := "DESC"
51
51
+
dir := "<"
52
52
+
cursorquery := ""
53
53
+
54
54
+
if strings.ToLower(reverse) == "true" {
55
55
+
sort = "ASC"
56
56
+
dir = ">"
57
57
+
}
58
58
+
59
59
+
params := []any{did, collection}
60
60
+
if cursor != "" {
61
61
+
params = append(params, cursor)
62
62
+
cursorquery = "AND created_at " + dir + " ?"
63
63
+
}
64
64
+
params = append(params, limit)
65
65
+
66
66
+
var records []models.Record
67
67
+
if err := s.db.Raw("SELECT * FROM records WHERE did = ? AND nsid = ? "+cursorquery+" ORDER BY created_at "+sort+" limit ?", params...).Scan(&records).Error; err != nil {
68
68
+
s.logger.Error("error getting records", "error", err)
69
69
+
return helpers.ServerError(e, nil)
70
70
+
}
71
71
+
72
72
+
items := []ComAtprotoRepoListRecordsRecordItem{}
73
73
+
for _, r := range records {
74
74
+
val, err := data.UnmarshalCBOR(r.Value)
75
75
+
if err != nil {
76
76
+
return err
77
77
+
}
78
78
+
79
79
+
items = append(items, ComAtprotoRepoListRecordsRecordItem{
80
80
+
Uri: "at://" + r.Did + "/" + r.Nsid + "/" + r.Rkey,
81
81
+
Cid: r.Cid,
82
82
+
Value: val,
83
83
+
})
84
84
+
}
85
85
+
86
86
+
var newcursor *string
87
87
+
if len(records) == 50 {
88
88
+
newcursor = to.StringPtr(records[len(records)-1].CreatedAt)
89
89
+
}
90
90
+
91
91
+
return e.JSON(200, ComAtprotoRepoListRecordsResponse{
92
92
+
Cursor: newcursor,
93
93
+
Records: items,
94
94
+
})
95
95
+
}
+49
server/handle_repo_list_repos.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"github.com/haileyok/cocoon/models"
5
5
+
"github.com/ipfs/go-cid"
6
6
+
"github.com/labstack/echo/v4"
7
7
+
)
8
8
+
9
9
+
type ComAtprotoSyncListReposResponse struct {
10
10
+
Cursor *string `json:"cursor,omitempty"`
11
11
+
Repos []ComAtprotoSyncListReposRepoItem `json:"repos"`
12
12
+
}
13
13
+
14
14
+
type ComAtprotoSyncListReposRepoItem struct {
15
15
+
Did string `json:"did"`
16
16
+
Head string `json:"head"`
17
17
+
Rev string `json:"rev"`
18
18
+
Active bool `json:"active"`
19
19
+
Status *string `json:"status,omitempty"`
20
20
+
}
21
21
+
22
22
+
// TODO: paginate this bitch
23
23
+
func (s *Server) handleListRepos(e echo.Context) error {
24
24
+
var repos []models.Repo
25
25
+
if err := s.db.Raw("SELECT * FROM repos ORDER BY created_at DESC LIMIT 500").Scan(&repos).Error; err != nil {
26
26
+
return err
27
27
+
}
28
28
+
29
29
+
var items []ComAtprotoSyncListReposRepoItem
30
30
+
for _, r := range repos {
31
31
+
c, err := cid.Cast(r.Root)
32
32
+
if err != nil {
33
33
+
return err
34
34
+
}
35
35
+
36
36
+
items = append(items, ComAtprotoSyncListReposRepoItem{
37
37
+
Did: r.Did,
38
38
+
Head: c.String(),
39
39
+
Rev: r.Rev,
40
40
+
Active: true,
41
41
+
Status: nil,
42
42
+
})
43
43
+
}
44
44
+
45
45
+
return e.JSON(200, ComAtprotoSyncListReposResponse{
46
46
+
Cursor: nil,
47
47
+
Repos: items,
48
48
+
})
49
49
+
}
+58
server/handle_repo_put_record.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"github.com/haileyok/cocoon/internal/helpers"
5
5
+
"github.com/haileyok/cocoon/models"
6
6
+
"github.com/labstack/echo/v4"
7
7
+
)
8
8
+
9
9
+
type ComAtprotoRepoPutRecordRequest struct {
10
10
+
Repo string `json:"repo" validate:"required,atproto-did"`
11
11
+
Collection string `json:"collection" validate:"required,atproto-nsid"`
12
12
+
Rkey string `json:"rkey" validate:"required,atproto-rkey"`
13
13
+
Validate *bool `json:"bool,omitempty"`
14
14
+
Record MarshalableMap `json:"record" validate:"required"`
15
15
+
SwapRecord *string `json:"swapRecord"`
16
16
+
SwapCommit *string `json:"swapCommit"`
17
17
+
}
18
18
+
19
19
+
func (s *Server) handlePutRecord(e echo.Context) error {
20
20
+
repo := e.Get("repo").(*models.RepoActor)
21
21
+
22
22
+
var req ComAtprotoRepoPutRecordRequest
23
23
+
if err := e.Bind(&req); err != nil {
24
24
+
s.logger.Error("error binding", "error", err)
25
25
+
return helpers.ServerError(e, nil)
26
26
+
}
27
27
+
28
28
+
if err := e.Validate(req); err != nil {
29
29
+
s.logger.Error("error validating", "error", err)
30
30
+
return helpers.InputError(e, nil)
31
31
+
}
32
32
+
33
33
+
if repo.Repo.Did != req.Repo {
34
34
+
s.logger.Warn("mismatched repo/auth")
35
35
+
return helpers.InputError(e, nil)
36
36
+
}
37
37
+
38
38
+
optype := OpTypeCreate
39
39
+
if req.SwapRecord != nil {
40
40
+
optype = OpTypeUpdate
41
41
+
}
42
42
+
43
43
+
if err := s.repoman.applyWrites(repo.Repo, []Op{
44
44
+
{
45
45
+
Type: optype,
46
46
+
Collection: req.Collection,
47
47
+
Rkey: &req.Rkey,
48
48
+
Validate: req.Validate,
49
49
+
Record: &req.Record,
50
50
+
SwapRecord: req.SwapRecord,
51
51
+
},
52
52
+
}, req.SwapCommit); err != nil {
53
53
+
s.logger.Error("error applying writes", "error", err)
54
54
+
return helpers.ServerError(e, nil)
55
55
+
}
56
56
+
57
57
+
return nil
58
58
+
}
+105
server/handle_repo_upload_blob.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"bytes"
5
5
+
"io"
6
6
+
7
7
+
"github.com/haileyok/cocoon/internal/helpers"
8
8
+
"github.com/haileyok/cocoon/models"
9
9
+
"github.com/ipfs/go-cid"
10
10
+
"github.com/labstack/echo/v4"
11
11
+
"github.com/multiformats/go-multihash"
12
12
+
)
13
13
+
14
14
+
const (
15
15
+
blockSize = 0x10000
16
16
+
)
17
17
+
18
18
+
type ComAtprotoRepoUploadBlobResponse struct {
19
19
+
Blob struct {
20
20
+
Type string `json:"$type"`
21
21
+
Ref struct {
22
22
+
Link string `json:"$link"`
23
23
+
} `json:"ref"`
24
24
+
MimeType string `json:"mimeType"`
25
25
+
Size int `json:"size"`
26
26
+
} `json:"blob"`
27
27
+
}
28
28
+
29
29
+
func (s *Server) handleRepoUploadBlob(e echo.Context) error {
30
30
+
urepo := e.Get("repo").(*models.RepoActor)
31
31
+
32
32
+
mime := e.Request().Header.Get("content-type")
33
33
+
if mime == "" {
34
34
+
mime = "application/octet-stream"
35
35
+
}
36
36
+
37
37
+
blob := models.Blob{
38
38
+
Did: urepo.Repo.Did,
39
39
+
RefCount: 0,
40
40
+
CreatedAt: s.repoman.clock.Next().String(),
41
41
+
}
42
42
+
43
43
+
if err := s.db.Create(&blob).Error; err != nil {
44
44
+
s.logger.Error("error creating new blob in db", "error", err)
45
45
+
return helpers.ServerError(e, nil)
46
46
+
}
47
47
+
48
48
+
read := 0
49
49
+
part := 0
50
50
+
51
51
+
buf := make([]byte, 0x10000)
52
52
+
fulldata := new(bytes.Buffer)
53
53
+
54
54
+
for {
55
55
+
n, err := io.ReadFull(e.Request().Body, buf)
56
56
+
if err == io.ErrUnexpectedEOF || err == io.EOF {
57
57
+
if n == 0 {
58
58
+
break
59
59
+
}
60
60
+
} else if err != nil && err != io.ErrUnexpectedEOF {
61
61
+
s.logger.Error("error reading blob", "error", err)
62
62
+
return helpers.ServerError(e, nil)
63
63
+
}
64
64
+
65
65
+
data := buf[:n]
66
66
+
read += n
67
67
+
fulldata.Write(data)
68
68
+
69
69
+
blobPart := models.BlobPart{
70
70
+
BlobID: blob.ID,
71
71
+
Idx: part,
72
72
+
Data: data,
73
73
+
}
74
74
+
75
75
+
if err := s.db.Create(&blobPart).Error; err != nil {
76
76
+
s.logger.Error("error adding blob part to db", "error", err)
77
77
+
return helpers.ServerError(e, nil)
78
78
+
}
79
79
+
part++
80
80
+
81
81
+
if n < blockSize {
82
82
+
break
83
83
+
}
84
84
+
}
85
85
+
86
86
+
c, err := cid.NewPrefixV1(cid.Raw, multihash.SHA2_256).Sum(fulldata.Bytes())
87
87
+
if err != nil {
88
88
+
s.logger.Error("error creating cid prefix", "error", err)
89
89
+
return helpers.ServerError(e, nil)
90
90
+
}
91
91
+
92
92
+
if err := s.db.Exec("UPDATE blobs SET cid = ? WHERE id = ?", c.Bytes(), blob.ID).Error; err != nil {
93
93
+
// there should probably be somme handling here if this fails...
94
94
+
s.logger.Error("error updating blob", "error", err)
95
95
+
return helpers.ServerError(e, nil)
96
96
+
}
97
97
+
98
98
+
resp := ComAtprotoRepoUploadBlobResponse{}
99
99
+
resp.Blob.Type = "blob"
100
100
+
resp.Blob.Ref.Link = c.String()
101
101
+
resp.Blob.MimeType = mime
102
102
+
resp.Blob.Size = read
103
103
+
104
104
+
return e.JSON(200, resp)
105
105
+
}
+7
server/handle_robots.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import "github.com/labstack/echo/v4"
4
4
+
5
5
+
func (s *Server) handleRobots(e echo.Context) error {
6
6
+
return e.String(200, "# Beep boop beep boop\n\n# Crawl me 🥺\nUser-agent: *\nAllow: /")
7
7
+
}
+40
server/handle_root.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import "github.com/labstack/echo/v4"
4
4
+
5
5
+
func (s *Server) handleRoot(e echo.Context) error {
6
6
+
return e.String(200, `
7
7
+
8
8
+
....-*%%%#####
9
9
+
.%#+++****#%%%%%%%%%#+:....
10
10
+
.%+++**++++*%%%%.....
11
11
+
.%+++*****#%%%%#.. %#%...
12
12
+
***+*****%%%%%... =..
13
13
+
*****%%%%.. +=++..
14
14
+
%%%%%... .+----==++.
15
15
+
.-::----===++
16
16
+
.=-:.------==+++
17
17
+
+-:::-:----===++..
18
18
+
=-::-----:-==+++-.
19
19
+
.==*=------==++++.
20
20
+
+-:--=++===*=--++.
21
21
+
+:::--:=++=----=+..
22
22
+
*::::---=+#----=+.
23
23
+
=::::----=+#---=+..
24
24
+
.::::----==+=--=+..
25
25
+
.-::-----==++=-=+..
26
26
+
-::-----==++===+..
27
27
+
=::-----==++==++
28
28
+
+::----:==++=+++
29
29
+
:-:----:==+++++.
30
30
+
.=:=----=+++++.
31
31
+
+=-=====+++..
32
32
+
=====++.
33
33
+
=++...
34
34
+
35
35
+
36
36
+
This is an AT Protocol Personal Data Server (aka, an atproto PDS)
37
37
+
38
38
+
Code: https://github.com/haileyok/cocoon
39
39
+
Version: `+s.config.Version+"\n")
40
40
+
}
+208
server/handle_server_create_account.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"context"
5
5
+
"errors"
6
6
+
"strings"
7
7
+
"time"
8
8
+
9
9
+
"github.com/Azure/go-autorest/autorest/to"
10
10
+
"github.com/bluesky-social/indigo/api/atproto"
11
11
+
"github.com/bluesky-social/indigo/atproto/crypto"
12
12
+
"github.com/bluesky-social/indigo/events"
13
13
+
"github.com/bluesky-social/indigo/repo"
14
14
+
"github.com/bluesky-social/indigo/util"
15
15
+
"github.com/haileyok/cocoon/blockstore"
16
16
+
"github.com/haileyok/cocoon/internal/helpers"
17
17
+
"github.com/haileyok/cocoon/models"
18
18
+
"github.com/labstack/echo/v4"
19
19
+
"golang.org/x/crypto/bcrypt"
20
20
+
"gorm.io/gorm"
21
21
+
)
22
22
+
23
23
+
type ComAtprotoServerCreateAccountRequest struct {
24
24
+
Email string `json:"email" validate:"required,email"`
25
25
+
Handle string `json:"handle" validate:"required,atproto-handle"`
26
26
+
Did *string `json:"did" validate:"atproto-did"`
27
27
+
Password string `json:"password" validate:"required"`
28
28
+
InviteCode string `json:"inviteCode" validate:"required"`
29
29
+
}
30
30
+
31
31
+
type ComAtprotoServerCreateAccountResponse struct {
32
32
+
AccessJwt string `json:"accessJwt"`
33
33
+
RefreshJwt string `json:"refreshJwt"`
34
34
+
Handle string `json:"handle"`
35
35
+
Did string `json:"did"`
36
36
+
}
37
37
+
38
38
+
func (s *Server) handleCreateAccount(e echo.Context) error {
39
39
+
var request ComAtprotoServerCreateAccountRequest
40
40
+
41
41
+
if err := e.Bind(&request); err != nil {
42
42
+
s.logger.Error("error receiving request", "endpoint", "com.atproto.server.createAccount", "error", err)
43
43
+
return helpers.ServerError(e, nil)
44
44
+
}
45
45
+
46
46
+
request.Handle = strings.ToLower(request.Handle)
47
47
+
48
48
+
if err := e.Validate(request); err != nil {
49
49
+
s.logger.Error("error validating request", "endpoint", "com.atproto.server.createAccount", "error", err)
50
50
+
51
51
+
var verr ValidationError
52
52
+
if errors.As(err, &verr) {
53
53
+
if verr.Field == "Email" {
54
54
+
// TODO: what is this supposed to be? `InvalidEmail` isn't listed in doc
55
55
+
return helpers.InputError(e, to.StringPtr("InvalidEmail"))
56
56
+
}
57
57
+
58
58
+
if verr.Field == "Handle" {
59
59
+
return helpers.InputError(e, to.StringPtr("InvalidHandle"))
60
60
+
}
61
61
+
62
62
+
if verr.Field == "Password" {
63
63
+
return helpers.InputError(e, to.StringPtr("InvalidPassword"))
64
64
+
}
65
65
+
66
66
+
if verr.Field == "InviteCode" {
67
67
+
return helpers.InputError(e, to.StringPtr("InvalidInviteCode"))
68
68
+
}
69
69
+
}
70
70
+
}
71
71
+
72
72
+
// see if the handle is already taken
73
73
+
_, err := s.getActorByHandle(request.Handle)
74
74
+
if err != nil && err != gorm.ErrRecordNotFound {
75
75
+
s.logger.Error("error looking up handle in db", "endpoint", "com.atproto.server.createAccount", "error", err)
76
76
+
return helpers.ServerError(e, nil)
77
77
+
}
78
78
+
if err == nil {
79
79
+
return helpers.InputError(e, to.StringPtr("HandleNotAvailable"))
80
80
+
}
81
81
+
82
82
+
if did, err := s.passport.ResolveHandle(e.Request().Context(), request.Handle); err == nil && did != "" {
83
83
+
return helpers.InputError(e, to.StringPtr("HandleNotAvailable"))
84
84
+
}
85
85
+
86
86
+
var ic models.InviteCode
87
87
+
if err := s.db.Raw("SELECT * FROM invite_codes WHERE code = ?", request.InviteCode).Scan(&ic).Error; err != nil {
88
88
+
if err == gorm.ErrRecordNotFound {
89
89
+
return helpers.InputError(e, to.StringPtr("InvalidInviteCode"))
90
90
+
}
91
91
+
s.logger.Error("error getting invite code from db", "error", err)
92
92
+
return helpers.ServerError(e, nil)
93
93
+
}
94
94
+
95
95
+
if ic.RemainingUseCount < 1 {
96
96
+
return helpers.InputError(e, to.StringPtr("InvalidInviteCode"))
97
97
+
}
98
98
+
99
99
+
// see if the email is already taken
100
100
+
_, err = s.getRepoByEmail(request.Email)
101
101
+
if err != nil && err != gorm.ErrRecordNotFound {
102
102
+
s.logger.Error("error looking up email in db", "endpoint", "com.atproto.server.createAccount", "error", err)
103
103
+
return helpers.ServerError(e, nil)
104
104
+
}
105
105
+
if err == nil {
106
106
+
return helpers.InputError(e, to.StringPtr("EmailNotAvailable"))
107
107
+
}
108
108
+
109
109
+
// TODO: unsupported domains
110
110
+
111
111
+
// TODO: did stuff
112
112
+
113
113
+
k, err := crypto.GeneratePrivateKeyK256()
114
114
+
if err != nil {
115
115
+
s.logger.Error("error creating signing key", "endpoint", "com.atproto.server.createAccount", "error", err)
116
116
+
return helpers.ServerError(e, nil)
117
117
+
}
118
118
+
119
119
+
did, op, err := s.plcClient.CreateDID(e.Request().Context(), k, "", request.Handle)
120
120
+
if err != nil {
121
121
+
s.logger.Error("error creating operation", "endpoint", "com.atproto.server.createAccount", "error", err)
122
122
+
return helpers.ServerError(e, nil)
123
123
+
}
124
124
+
125
125
+
if err := s.plcClient.SendOperation(e.Request().Context(), did, op); err != nil {
126
126
+
s.logger.Error("error sending plc op", "endpoint", "com.atproto.server.createAccount", "error", err)
127
127
+
return helpers.ServerError(e, nil)
128
128
+
}
129
129
+
130
130
+
hashed, err := bcrypt.GenerateFromPassword([]byte(request.Password), 10)
131
131
+
if err != nil {
132
132
+
s.logger.Error("error hashing password", "error", err)
133
133
+
return helpers.ServerError(e, nil)
134
134
+
}
135
135
+
136
136
+
urepo := models.Repo{
137
137
+
Did: did,
138
138
+
CreatedAt: time.Now(),
139
139
+
Email: request.Email,
140
140
+
Password: string(hashed),
141
141
+
SigningKey: k.Bytes(),
142
142
+
}
143
143
+
144
144
+
actor := models.Actor{
145
145
+
Did: did,
146
146
+
Handle: request.Handle,
147
147
+
}
148
148
+
149
149
+
if err := s.db.Create(&urepo).Error; err != nil {
150
150
+
s.logger.Error("error inserting new repo", "error", err)
151
151
+
return helpers.ServerError(e, nil)
152
152
+
}
153
153
+
154
154
+
bs := blockstore.New(did, s.db)
155
155
+
r := repo.NewRepo(context.TODO(), did, bs)
156
156
+
157
157
+
root, rev, err := r.Commit(context.TODO(), urepo.SignFor)
158
158
+
if err != nil {
159
159
+
s.logger.Error("error committing", "error", err)
160
160
+
return helpers.ServerError(e, nil)
161
161
+
}
162
162
+
163
163
+
if err := bs.UpdateRepo(context.TODO(), root, rev); err != nil {
164
164
+
s.logger.Error("error updating repo after commit", "error", err)
165
165
+
return helpers.ServerError(e, nil)
166
166
+
}
167
167
+
168
168
+
s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{
169
169
+
RepoHandle: &atproto.SyncSubscribeRepos_Handle{
170
170
+
Did: urepo.Did,
171
171
+
Handle: request.Handle,
172
172
+
Seq: time.Now().UnixMicro(), // TODO: no
173
173
+
Time: time.Now().Format(util.ISO8601),
174
174
+
},
175
175
+
})
176
176
+
177
177
+
s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{
178
178
+
RepoIdentity: &atproto.SyncSubscribeRepos_Identity{
179
179
+
Did: urepo.Did,
180
180
+
Handle: to.StringPtr(request.Handle),
181
181
+
Seq: time.Now().UnixMicro(), // TODO: no
182
182
+
Time: time.Now().Format(util.ISO8601),
183
183
+
},
184
184
+
})
185
185
+
186
186
+
if err := s.db.Create(&actor).Error; err != nil {
187
187
+
s.logger.Error("error inserting new actor", "error", err)
188
188
+
return helpers.ServerError(e, nil)
189
189
+
}
190
190
+
191
191
+
if err := s.db.Raw("UPDATE invite_codes SET remaining_use_count = remaining_use_count - 1 WHERE code = ?", request.InviteCode).Scan(&ic).Error; err != nil {
192
192
+
s.logger.Error("error decrementing use count", "error", err)
193
193
+
return helpers.ServerError(e, nil)
194
194
+
}
195
195
+
196
196
+
sess, err := s.createSession(&urepo)
197
197
+
if err != nil {
198
198
+
s.logger.Error("error creating new session", "error", err)
199
199
+
return helpers.ServerError(e, nil)
200
200
+
}
201
201
+
202
202
+
return e.JSON(200, ComAtprotoServerCreateAccountResponse{
203
203
+
AccessJwt: sess.AccessToken,
204
204
+
RefreshJwt: sess.RefreshToken,
205
205
+
Handle: request.Handle,
206
206
+
Did: did,
207
207
+
})
208
208
+
}
+17
server/handle_server_create_invite_code.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"github.com/google/uuid"
5
5
+
"github.com/haileyok/cocoon/models"
6
6
+
"github.com/labstack/echo/v4"
7
7
+
)
8
8
+
9
9
+
func (s *Server) handleCreateInviteCode(e echo.Context) error {
10
10
+
ic := models.InviteCode{
11
11
+
Code: uuid.NewString(),
12
12
+
}
13
13
+
14
14
+
return e.JSON(200, map[string]string{
15
15
+
"code": ic.Code,
16
16
+
})
17
17
+
}
+108
server/handle_server_create_session.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"errors"
5
5
+
"strings"
6
6
+
7
7
+
"github.com/Azure/go-autorest/autorest/to"
8
8
+
"github.com/bluesky-social/indigo/atproto/syntax"
9
9
+
"github.com/haileyok/cocoon/internal/helpers"
10
10
+
"github.com/haileyok/cocoon/models"
11
11
+
"github.com/labstack/echo/v4"
12
12
+
"golang.org/x/crypto/bcrypt"
13
13
+
"gorm.io/gorm"
14
14
+
)
15
15
+
16
16
+
type ComAtprotoServerCreateSessionRequest struct {
17
17
+
Identifier string `json:"identifier" validate:"required"`
18
18
+
Password string `json:"password" validate:"required"`
19
19
+
AuthFactorToken *string `json:"authFactorToken,omitempty"`
20
20
+
}
21
21
+
22
22
+
type ComAtprotoServerCreateSessionResponse struct {
23
23
+
AccessJwt string `json:"accessJwt"`
24
24
+
RefreshJwt string `json:"refreshJwt"`
25
25
+
Handle string `json:"handle"`
26
26
+
Did string `json:"did"`
27
27
+
Email string `json:"email"`
28
28
+
EmailConfirmed bool `json:"emailConfirmed"`
29
29
+
EmailAuthFactor bool `json:"emailAuthFactor"`
30
30
+
Active bool `json:"active"`
31
31
+
Status *string `json:"status,omitempty"`
32
32
+
}
33
33
+
34
34
+
func (s *Server) handleCreateSession(e echo.Context) error {
35
35
+
var req ComAtprotoServerCreateSessionRequest
36
36
+
if err := e.Bind(&req); err != nil {
37
37
+
s.logger.Error("error binding request", "endpoint", "com.atproto.server.serverCreateSession", "error", err)
38
38
+
return helpers.ServerError(e, nil)
39
39
+
}
40
40
+
41
41
+
if err := e.Validate(req); err != nil {
42
42
+
var verr ValidationError
43
43
+
if errors.As(err, &verr) {
44
44
+
if verr.Field == "Identifier" {
45
45
+
return helpers.InputError(e, to.StringPtr("InvalidRequest"))
46
46
+
}
47
47
+
48
48
+
if verr.Field == "Password" {
49
49
+
return helpers.InputError(e, to.StringPtr("InvalidRequest"))
50
50
+
}
51
51
+
}
52
52
+
}
53
53
+
54
54
+
req.Identifier = strings.ToLower(req.Identifier)
55
55
+
var idtype string
56
56
+
if _, err := syntax.ParseDID(req.Identifier); err == nil {
57
57
+
idtype = "did"
58
58
+
} else if _, err := syntax.ParseHandle(req.Identifier); err == nil {
59
59
+
idtype = "handle"
60
60
+
} else {
61
61
+
idtype = "email"
62
62
+
}
63
63
+
64
64
+
var repo models.RepoActor
65
65
+
var err error
66
66
+
switch idtype {
67
67
+
case "did":
68
68
+
err = s.db.Raw("SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE r.did = ?", req.Identifier).Scan(&repo).Error
69
69
+
case "handle":
70
70
+
err = s.db.Raw("SELECT r.*, a.* FROM actors a LEFT JOIN repos r ON a.did = r.did WHERE a.handle = ?", req.Identifier).Scan(&repo).Error
71
71
+
case "email":
72
72
+
err = s.db.Raw("SELECT r.*, a.* FROM repos r LEFT JOIN actors a ON r.did = a.did WHERE a.email = ?", req.Identifier).Scan(&repo).Error
73
73
+
}
74
74
+
75
75
+
if err != nil {
76
76
+
if err == gorm.ErrRecordNotFound {
77
77
+
return helpers.InputError(e, to.StringPtr("InvalidRequest"))
78
78
+
}
79
79
+
80
80
+
s.logger.Error("erorr looking up repo", "endpoint", "com.atproto.server.createSession", "error", err)
81
81
+
return helpers.ServerError(e, nil)
82
82
+
}
83
83
+
84
84
+
if err := bcrypt.CompareHashAndPassword([]byte(repo.Password), []byte(req.Password)); err != nil {
85
85
+
if err != bcrypt.ErrMismatchedHashAndPassword {
86
86
+
s.logger.Error("erorr comparing hash and password", "error", err)
87
87
+
}
88
88
+
return helpers.InputError(e, to.StringPtr("InvalidRequest"))
89
89
+
}
90
90
+
91
91
+
sess, err := s.createSession(&repo.Repo)
92
92
+
if err != nil {
93
93
+
s.logger.Error("error creating session", "error", err)
94
94
+
return helpers.ServerError(e, nil)
95
95
+
}
96
96
+
97
97
+
return e.JSON(200, ComAtprotoServerCreateSessionResponse{
98
98
+
AccessJwt: sess.AccessToken,
99
99
+
RefreshJwt: sess.RefreshToken,
100
100
+
Handle: repo.Handle,
101
101
+
Did: repo.Repo.Did,
102
102
+
Email: repo.Email,
103
103
+
EmailConfirmed: repo.EmailConfirmedAt != nil,
104
104
+
EmailAuthFactor: false,
105
105
+
Active: true, // TODO: eventually do takedowns
106
106
+
Status: nil, // TODO eventually do takedowns
107
107
+
})
108
108
+
}
+24
server/handle_server_delete_session.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"github.com/haileyok/cocoon/internal/helpers"
5
5
+
"github.com/haileyok/cocoon/models"
6
6
+
"github.com/labstack/echo/v4"
7
7
+
)
8
8
+
9
9
+
func (s *Server) handleDeleteSession(e echo.Context) error {
10
10
+
token := e.Get("token").(string)
11
11
+
12
12
+
var acctok models.Token
13
13
+
if err := s.db.Raw("DELETE FROM tokens WHERE token = ? RETURNING *", token).Scan(&acctok).Error; err != nil {
14
14
+
s.logger.Error("error deleting access token from db", "error", err)
15
15
+
return helpers.ServerError(e, nil)
16
16
+
}
17
17
+
18
18
+
if err := s.db.Exec("DELETE FROM refresh_tokens WHERE token = ?", acctok.RefreshToken).Error; err != nil {
19
19
+
s.logger.Error("error deleting refresh token from db", "error", err)
20
20
+
return helpers.ServerError(e, nil)
21
21
+
}
22
22
+
23
23
+
return e.NoContent(200)
24
24
+
}
+37
server/handle_server_describe_server.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import "github.com/labstack/echo/v4"
4
4
+
5
5
+
type ComAtprotoServerDescribeServerResponseLinks struct {
6
6
+
PrivacyPolicy *string `json:"privacyPolicy,omitempty"`
7
7
+
TermsOfService *string `json:"termsOfService,omitempty"`
8
8
+
}
9
9
+
10
10
+
type ComAtprotoServerDescribeServerResponseContact struct {
11
11
+
Email string `json:"email"`
12
12
+
}
13
13
+
14
14
+
type ComAtprotoServerDescribeServerResponse struct {
15
15
+
InviteCodeRequired bool `json:"inviteCodeRequired"`
16
16
+
PhoneVerificationRequired bool `json:"phoneVerificationRequired"`
17
17
+
AvailableUserDomains []string `json:"availableUserDomains"`
18
18
+
Links ComAtprotoServerDescribeServerResponseLinks `json:"links"`
19
19
+
Contact ComAtprotoServerDescribeServerResponseContact `json:"contact"`
20
20
+
Did string `json:"did"`
21
21
+
}
22
22
+
23
23
+
func (s *Server) handleDescribeServer(e echo.Context) error {
24
24
+
return e.JSON(200, ComAtprotoServerDescribeServerResponse{
25
25
+
InviteCodeRequired: true,
26
26
+
PhoneVerificationRequired: false,
27
27
+
AvailableUserDomains: []string{"." + s.config.Hostname}, // TODO: more
28
28
+
Links: ComAtprotoServerDescribeServerResponseLinks{
29
29
+
PrivacyPolicy: nil,
30
30
+
TermsOfService: nil,
31
31
+
},
32
32
+
Contact: ComAtprotoServerDescribeServerResponseContact{
33
33
+
Email: s.config.ContactEmail,
34
34
+
},
35
35
+
Did: s.config.Did,
36
36
+
})
37
37
+
}
+30
server/handle_server_get_session.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"github.com/haileyok/cocoon/models"
5
5
+
"github.com/labstack/echo/v4"
6
6
+
)
7
7
+
8
8
+
type ComAtprotoServerGetSessionResponse struct {
9
9
+
Handle string `json:"handle"`
10
10
+
Did string `json:"did"`
11
11
+
Email string `json:"email"`
12
12
+
EmailConfirmed bool `json:"emailConfirmed"`
13
13
+
EmailAuthFactor bool `json:"emailAuthFactor"`
14
14
+
Active bool `json:"active"`
15
15
+
Status *string `json:"status,omitempty"`
16
16
+
}
17
17
+
18
18
+
func (s *Server) handleGetSession(e echo.Context) error {
19
19
+
repo := e.Get("repo").(*models.RepoActor)
20
20
+
21
21
+
return e.JSON(200, ComAtprotoServerGetSessionResponse{
22
22
+
Handle: repo.Handle,
23
23
+
Did: repo.Repo.Did,
24
24
+
Email: repo.Email,
25
25
+
EmailConfirmed: repo.EmailConfirmedAt != nil,
26
26
+
EmailAuthFactor: false, // TODO: todo todo
27
27
+
Active: true,
28
28
+
Status: nil,
29
29
+
})
30
30
+
}
+46
server/handle_server_refresh_session.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"github.com/haileyok/cocoon/internal/helpers"
5
5
+
"github.com/haileyok/cocoon/models"
6
6
+
"github.com/labstack/echo/v4"
7
7
+
)
8
8
+
9
9
+
type ComAtprotoServerRefreshSessionResponse struct {
10
10
+
AccessJwt string `json:"accessJwt"`
11
11
+
RefreshJwt string `json:"refreshJwt"`
12
12
+
Handle string `json:"handle"`
13
13
+
Did string `json:"did"`
14
14
+
Active bool `json:"active"`
15
15
+
Status *string `json:"status,omitempty"`
16
16
+
}
17
17
+
18
18
+
func (s *Server) handleRefreshSession(e echo.Context) error {
19
19
+
token := e.Get("token").(string)
20
20
+
repo := e.Get("repo").(*models.RepoActor)
21
21
+
22
22
+
if err := s.db.Exec("DELETE FROM refresh_tokens WHERE token = ?", token).Error; err != nil {
23
23
+
s.logger.Error("error getting refresh token from db", "error", err)
24
24
+
return helpers.ServerError(e, nil)
25
25
+
}
26
26
+
27
27
+
if err := s.db.Exec("DELETE FROM tokens WHERE refresh_token = ?", token).Error; err != nil {
28
28
+
s.logger.Error("error deleting access token from db", "error", err)
29
29
+
return helpers.ServerError(e, nil)
30
30
+
}
31
31
+
32
32
+
sess, err := s.createSession(&repo.Repo)
33
33
+
if err != nil {
34
34
+
s.logger.Error("error creating new session for refresh", "error", err)
35
35
+
return helpers.ServerError(e, nil)
36
36
+
}
37
37
+
38
38
+
return e.JSON(200, ComAtprotoServerRefreshSessionResponse{
39
39
+
AccessJwt: sess.AccessToken,
40
40
+
RefreshJwt: sess.RefreshToken,
41
41
+
Handle: repo.Handle,
42
42
+
Did: repo.Repo.Did,
43
43
+
Active: true,
44
44
+
Status: nil,
45
45
+
})
46
46
+
}
+38
server/handle_server_resolve_handle.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"context"
5
5
+
6
6
+
"github.com/Azure/go-autorest/autorest/to"
7
7
+
"github.com/bluesky-social/indigo/atproto/syntax"
8
8
+
"github.com/haileyok/cocoon/internal/helpers"
9
9
+
"github.com/labstack/echo/v4"
10
10
+
)
11
11
+
12
12
+
func (s *Server) handleResolveHandle(e echo.Context) error {
13
13
+
type Resp struct {
14
14
+
Did string `json:"did"`
15
15
+
}
16
16
+
17
17
+
handle := e.QueryParam("handle")
18
18
+
19
19
+
if handle == "" {
20
20
+
return helpers.InputError(e, to.StringPtr("Handle must be supplied in request."))
21
21
+
}
22
22
+
23
23
+
parsed, err := syntax.ParseHandle(handle)
24
24
+
if err != nil {
25
25
+
return helpers.InputError(e, to.StringPtr("Invalid handle."))
26
26
+
}
27
27
+
28
28
+
ctx := context.WithValue(e.Request().Context(), "skip-cache", true)
29
29
+
did, err := s.passport.ResolveHandle(ctx, parsed.String())
30
30
+
if err != nil {
31
31
+
s.logger.Error("error resolving handle", "error", err)
32
32
+
return helpers.ServerError(e, nil)
33
33
+
}
34
34
+
35
35
+
return e.JSON(200, Resp{
36
36
+
Did: did,
37
37
+
})
38
38
+
}
+48
server/handle_sync_get_blob.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"bytes"
5
5
+
6
6
+
"github.com/haileyok/cocoon/internal/helpers"
7
7
+
"github.com/haileyok/cocoon/models"
8
8
+
"github.com/ipfs/go-cid"
9
9
+
"github.com/labstack/echo/v4"
10
10
+
)
11
11
+
12
12
+
func (s *Server) handleSyncGetBlob(e echo.Context) error {
13
13
+
did := e.QueryParam("did")
14
14
+
if did == "" {
15
15
+
return helpers.InputError(e, nil)
16
16
+
}
17
17
+
18
18
+
cstr := e.QueryParam("cid")
19
19
+
if cstr == "" {
20
20
+
return helpers.InputError(e, nil)
21
21
+
}
22
22
+
23
23
+
c, err := cid.Parse(cstr)
24
24
+
if err != nil {
25
25
+
return helpers.InputError(e, nil)
26
26
+
}
27
27
+
28
28
+
var blob models.Blob
29
29
+
if err := s.db.Raw("SELECT * FROM blobs WHERE did = ? AND cid = ?", did, c.Bytes()).Scan(&blob).Error; err != nil {
30
30
+
s.logger.Error("error looking up blob", "error", err)
31
31
+
return helpers.ServerError(e, nil)
32
32
+
}
33
33
+
34
34
+
buf := new(bytes.Buffer)
35
35
+
36
36
+
var parts []models.BlobPart
37
37
+
if err := s.db.Raw("SELECT * FROM blob_parts WHERE blob_id = ? ORDER BY idx", blob.ID).Scan(&parts).Error; err != nil {
38
38
+
s.logger.Error("error getting blob parts", "error", err)
39
39
+
return helpers.ServerError(e, nil)
40
40
+
}
41
41
+
42
42
+
// TODO: we can just stream this, don't need to make a buffer
43
43
+
for _, p := range parts {
44
44
+
buf.Write(p.Data)
45
45
+
}
46
46
+
47
47
+
return e.Stream(200, "application/octet-stream", buf)
48
48
+
}
+71
server/handle_sync_get_blocks.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"bytes"
5
5
+
"context"
6
6
+
"strings"
7
7
+
8
8
+
"github.com/bluesky-social/indigo/carstore"
9
9
+
"github.com/haileyok/cocoon/blockstore"
10
10
+
"github.com/haileyok/cocoon/internal/helpers"
11
11
+
"github.com/ipfs/go-cid"
12
12
+
cbor "github.com/ipfs/go-ipld-cbor"
13
13
+
"github.com/ipld/go-car"
14
14
+
"github.com/labstack/echo/v4"
15
15
+
)
16
16
+
17
17
+
func (s *Server) handleGetBlocks(e echo.Context) error {
18
18
+
did := e.QueryParam("did")
19
19
+
cidsstr := e.QueryParam("cids")
20
20
+
if did == "" {
21
21
+
return helpers.InputError(e, nil)
22
22
+
}
23
23
+
24
24
+
cidstrs := strings.Split(cidsstr, ",")
25
25
+
cids := []cid.Cid{}
26
26
+
27
27
+
for _, cs := range cidstrs {
28
28
+
c, err := cid.Cast([]byte(cs))
29
29
+
if err != nil {
30
30
+
return err
31
31
+
}
32
32
+
33
33
+
cids = append(cids, c)
34
34
+
}
35
35
+
36
36
+
urepo, err := s.getRepoActorByDid(did)
37
37
+
if err != nil {
38
38
+
return helpers.ServerError(e, nil)
39
39
+
}
40
40
+
41
41
+
buf := new(bytes.Buffer)
42
42
+
rc, err := cid.Cast(urepo.Root)
43
43
+
if err != nil {
44
44
+
return err
45
45
+
}
46
46
+
47
47
+
hb, err := cbor.DumpObject(&car.CarHeader{
48
48
+
Roots: []cid.Cid{rc},
49
49
+
Version: 1,
50
50
+
})
51
51
+
52
52
+
if _, err := carstore.LdWrite(buf, hb); err != nil {
53
53
+
s.logger.Error("error writing to car", "error", err)
54
54
+
return helpers.ServerError(e, nil)
55
55
+
}
56
56
+
57
57
+
bs := blockstore.New(urepo.Repo.Did, s.db)
58
58
+
59
59
+
for _, c := range cids {
60
60
+
b, err := bs.Get(context.TODO(), c)
61
61
+
if err != nil {
62
62
+
return err
63
63
+
}
64
64
+
65
65
+
if _, err := carstore.LdWrite(buf, b.Cid().Bytes(), b.RawData()); err != nil {
66
66
+
return err
67
67
+
}
68
68
+
}
69
69
+
70
70
+
return e.Stream(200, "application/vnd.ipld.car", bytes.NewReader(buf.Bytes()))
71
71
+
}
+34
server/handle_sync_get_latest_commit.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"github.com/haileyok/cocoon/internal/helpers"
5
5
+
"github.com/ipfs/go-cid"
6
6
+
"github.com/labstack/echo/v4"
7
7
+
)
8
8
+
9
9
+
type ComAtprotoSyncGetLatestCommitResponse struct {
10
10
+
Cid string `json:"string"`
11
11
+
Rev string `json:"rev"`
12
12
+
}
13
13
+
14
14
+
func (s *Server) handleSyncGetLatestCommit(e echo.Context) error {
15
15
+
did := e.QueryParam("did")
16
16
+
if did == "" {
17
17
+
return helpers.InputError(e, nil)
18
18
+
}
19
19
+
20
20
+
urepo, err := s.getRepoActorByDid(did)
21
21
+
if err != nil {
22
22
+
return err
23
23
+
}
24
24
+
25
25
+
c, err := cid.Cast(urepo.Root)
26
26
+
if err != nil {
27
27
+
return err
28
28
+
}
29
29
+
30
30
+
return e.JSON(200, ComAtprotoSyncGetLatestCommitResponse{
31
31
+
Cid: c.String(),
32
32
+
Rev: urepo.Rev,
33
33
+
})
34
34
+
}
+51
server/handle_sync_get_record.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"bytes"
5
5
+
6
6
+
"github.com/bluesky-social/indigo/carstore"
7
7
+
"github.com/haileyok/cocoon/internal/helpers"
8
8
+
"github.com/haileyok/cocoon/models"
9
9
+
"github.com/ipfs/go-cid"
10
10
+
cbor "github.com/ipfs/go-ipld-cbor"
11
11
+
"github.com/ipld/go-car"
12
12
+
"github.com/labstack/echo/v4"
13
13
+
)
14
14
+
15
15
+
func (s *Server) handleSyncGetRecord(e echo.Context) error {
16
16
+
did := e.QueryParam("did")
17
17
+
collection := e.QueryParam("collection")
18
18
+
rkey := e.QueryParam("rkey")
19
19
+
20
20
+
var urepo models.Repo
21
21
+
if err := s.db.Raw("SELECT * FROM repos WHERE did = ?", did).Scan(&urepo).Error; err != nil {
22
22
+
s.logger.Error("error getting repo", "error", err)
23
23
+
return helpers.ServerError(e, nil)
24
24
+
}
25
25
+
26
26
+
root, blocks, err := s.repoman.getRecordProof(urepo, collection, rkey)
27
27
+
if err != nil {
28
28
+
return err
29
29
+
}
30
30
+
31
31
+
buf := new(bytes.Buffer)
32
32
+
33
33
+
hb, err := cbor.DumpObject(&car.CarHeader{
34
34
+
Roots: []cid.Cid{root},
35
35
+
Version: 1,
36
36
+
})
37
37
+
38
38
+
if _, err := carstore.LdWrite(buf, hb); err != nil {
39
39
+
s.logger.Error("error writing to car", "error", err)
40
40
+
return helpers.ServerError(e, nil)
41
41
+
}
42
42
+
43
43
+
for _, blk := range blocks {
44
44
+
if _, err := carstore.LdWrite(buf, blk.Cid().Bytes(), blk.RawData()); err != nil {
45
45
+
s.logger.Error("error writing to car", "error", err)
46
46
+
return helpers.ServerError(e, nil)
47
47
+
}
48
48
+
}
49
49
+
50
50
+
return e.Stream(200, "application/vnd.ipld.car", bytes.NewReader(buf.Bytes()))
51
51
+
}
+55
server/handle_sync_get_repo.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"bytes"
5
5
+
6
6
+
"github.com/bluesky-social/indigo/carstore"
7
7
+
"github.com/haileyok/cocoon/internal/helpers"
8
8
+
"github.com/haileyok/cocoon/models"
9
9
+
"github.com/ipfs/go-cid"
10
10
+
cbor "github.com/ipfs/go-ipld-cbor"
11
11
+
"github.com/ipld/go-car"
12
12
+
"github.com/labstack/echo/v4"
13
13
+
)
14
14
+
15
15
+
func (s *Server) handleSyncGetRepo(e echo.Context) error {
16
16
+
did := e.QueryParam("did")
17
17
+
if did == "" {
18
18
+
return helpers.InputError(e, nil)
19
19
+
}
20
20
+
21
21
+
urepo, err := s.getRepoActorByDid(did)
22
22
+
if err != nil {
23
23
+
return err
24
24
+
}
25
25
+
26
26
+
rc, err := cid.Cast(urepo.Root)
27
27
+
if err != nil {
28
28
+
return err
29
29
+
}
30
30
+
31
31
+
hb, err := cbor.DumpObject(&car.CarHeader{
32
32
+
Roots: []cid.Cid{rc},
33
33
+
Version: 1,
34
34
+
})
35
35
+
36
36
+
buf := new(bytes.Buffer)
37
37
+
38
38
+
if _, err := carstore.LdWrite(buf, hb); err != nil {
39
39
+
s.logger.Error("error writing to car", "error", err)
40
40
+
return helpers.ServerError(e, nil)
41
41
+
}
42
42
+
43
43
+
var blocks []models.Block
44
44
+
if err := s.db.Raw("SELECT * FROM blocks WHERE did = ? ORDER BY rev ASC", urepo.Repo.Did).Scan(&blocks).Error; err != nil {
45
45
+
return err
46
46
+
}
47
47
+
48
48
+
for _, block := range blocks {
49
49
+
if _, err := carstore.LdWrite(buf, block.Cid, block.Value); err != nil {
50
50
+
return err
51
51
+
}
52
52
+
}
53
53
+
54
54
+
return e.Stream(200, "application/vnd.ipld.car", bytes.NewReader(buf.Bytes()))
55
55
+
}
+33
server/handle_sync_get_repo_status.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"github.com/haileyok/cocoon/internal/helpers"
5
5
+
"github.com/labstack/echo/v4"
6
6
+
)
7
7
+
8
8
+
type ComAtprotoSyncGetRepoStatusResponse struct {
9
9
+
Did string `json:"did"`
10
10
+
Active bool `json:"active"`
11
11
+
Status *string `json:"status,omitempty"`
12
12
+
Rev *string `json:"rev,omitempty"`
13
13
+
}
14
14
+
15
15
+
// TODO: make this actually do the right thing
16
16
+
func (s *Server) handleSyncGetRepoStatus(e echo.Context) error {
17
17
+
did := e.QueryParam("did")
18
18
+
if did == "" {
19
19
+
return helpers.InputError(e, nil)
20
20
+
}
21
21
+
22
22
+
urepo, err := s.getRepoActorByDid(did)
23
23
+
if err != nil {
24
24
+
return err
25
25
+
}
26
26
+
27
27
+
return e.JSON(200, ComAtprotoSyncGetRepoStatusResponse{
28
28
+
Did: urepo.Repo.Did,
29
29
+
Active: true,
30
30
+
Status: nil,
31
31
+
Rev: &urepo.Rev,
32
32
+
})
33
33
+
}
+62
server/handle_sync_list_blobs.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"github.com/haileyok/cocoon/internal/helpers"
5
5
+
"github.com/haileyok/cocoon/models"
6
6
+
"github.com/ipfs/go-cid"
7
7
+
"github.com/labstack/echo/v4"
8
8
+
)
9
9
+
10
10
+
type ComAtprotoSyncListBlobsResponse struct {
11
11
+
Cursor *string `json:"cursor,omitempty"`
12
12
+
Cids []string `json:"cids"`
13
13
+
}
14
14
+
15
15
+
func (s *Server) handleSyncListBlobs(e echo.Context) error {
16
16
+
did := e.QueryParam("did")
17
17
+
if did == "" {
18
18
+
return helpers.InputError(e, nil)
19
19
+
}
20
20
+
21
21
+
// TODO: add tid param
22
22
+
cursor := e.QueryParam("cursor")
23
23
+
limit, err := getLimitFromContext(e, 50)
24
24
+
if err != nil {
25
25
+
return helpers.InputError(e, nil)
26
26
+
}
27
27
+
28
28
+
cursorquery := ""
29
29
+
30
30
+
params := []any{did}
31
31
+
if cursor != "" {
32
32
+
params = append(params, cursor)
33
33
+
cursorquery = "AND created_at < ?"
34
34
+
}
35
35
+
params = append(params, limit)
36
36
+
37
37
+
var blobs []models.Blob
38
38
+
if err := s.db.Raw("SELECT * FROM blobs WHERE did = ? "+cursorquery+" ORDER BY created_at DESC LIMIT ?", params...).Scan(&blobs).Error; err != nil {
39
39
+
s.logger.Error("error getting records", "error", err)
40
40
+
return helpers.ServerError(e, nil)
41
41
+
}
42
42
+
43
43
+
var cstrs []string
44
44
+
for _, b := range blobs {
45
45
+
c, err := cid.Cast(b.Cid)
46
46
+
if err != nil {
47
47
+
s.logger.Error("error casting cid", "error", err)
48
48
+
return helpers.ServerError(e, nil)
49
49
+
}
50
50
+
cstrs = append(cstrs, c.String())
51
51
+
}
52
52
+
53
53
+
var newcursor *string
54
54
+
if len(blobs) == 50 {
55
55
+
newcursor = &blobs[len(blobs)-1].CreatedAt
56
56
+
}
57
57
+
58
58
+
return e.JSON(200, ComAtprotoSyncListBlobsResponse{
59
59
+
Cursor: newcursor,
60
60
+
Cids: cstrs,
61
61
+
})
62
62
+
}
+93
server/handle_sync_subscribe_repos.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"fmt"
5
5
+
"net/http"
6
6
+
7
7
+
"github.com/bluesky-social/indigo/events"
8
8
+
"github.com/bluesky-social/indigo/lex/util"
9
9
+
"github.com/btcsuite/websocket"
10
10
+
"github.com/labstack/echo/v4"
11
11
+
)
12
12
+
13
13
+
var upgrader = websocket.Upgrader{
14
14
+
ReadBufferSize: 1024,
15
15
+
WriteBufferSize: 1024,
16
16
+
CheckOrigin: func(r *http.Request) bool {
17
17
+
return true
18
18
+
},
19
19
+
}
20
20
+
21
21
+
func (s *Server) handleSyncSubscribeRepos(e echo.Context) error {
22
22
+
conn, err := websocket.Upgrade(e.Response().Writer, e.Request(), e.Response().Header(), 1<<10, 1<<10)
23
23
+
if err != nil {
24
24
+
return err
25
25
+
}
26
26
+
27
27
+
s.logger.Info("new connection", "ua", e.Request().UserAgent())
28
28
+
29
29
+
ctx := e.Request().Context()
30
30
+
31
31
+
ident := e.RealIP() + "-" + e.Request().UserAgent()
32
32
+
33
33
+
evts, cancel, err := s.evtman.Subscribe(ctx, ident, func(evt *events.XRPCStreamEvent) bool {
34
34
+
return true
35
35
+
}, nil)
36
36
+
if err != nil {
37
37
+
return err
38
38
+
}
39
39
+
defer cancel()
40
40
+
41
41
+
header := events.EventHeader{Op: events.EvtKindMessage}
42
42
+
for evt := range evts {
43
43
+
wc, err := conn.NextWriter(websocket.BinaryMessage)
44
44
+
if err != nil {
45
45
+
return err
46
46
+
}
47
47
+
48
48
+
var obj util.CBOR
49
49
+
50
50
+
switch {
51
51
+
case evt.Error != nil:
52
52
+
header.Op = events.EvtKindErrorFrame
53
53
+
obj = evt.Error
54
54
+
case evt.RepoCommit != nil:
55
55
+
header.MsgType = "#commit"
56
56
+
obj = evt.RepoCommit
57
57
+
case evt.RepoHandle != nil:
58
58
+
header.MsgType = "#handle"
59
59
+
obj = evt.RepoHandle
60
60
+
case evt.RepoIdentity != nil:
61
61
+
header.MsgType = "#identity"
62
62
+
obj = evt.RepoIdentity
63
63
+
case evt.RepoAccount != nil:
64
64
+
header.MsgType = "#account"
65
65
+
obj = evt.RepoAccount
66
66
+
case evt.RepoInfo != nil:
67
67
+
header.MsgType = "#info"
68
68
+
obj = evt.RepoInfo
69
69
+
case evt.RepoMigrate != nil:
70
70
+
header.MsgType = "#migrate"
71
71
+
obj = evt.RepoMigrate
72
72
+
case evt.RepoTombstone != nil:
73
73
+
header.MsgType = "#tombstone"
74
74
+
obj = evt.RepoTombstone
75
75
+
default:
76
76
+
return fmt.Errorf("unrecognized event kind")
77
77
+
}
78
78
+
79
79
+
if err := header.MarshalCBOR(wc); err != nil {
80
80
+
return fmt.Errorf("failed to write header: %w", err)
81
81
+
}
82
82
+
83
83
+
if err := obj.MarshalCBOR(wc); err != nil {
84
84
+
return fmt.Errorf("failed to write event: %w", err)
85
85
+
}
86
86
+
87
87
+
if err := wc.Close(); err != nil {
88
88
+
return fmt.Errorf("failed to flush-close our event write: %w", err)
89
89
+
}
90
90
+
}
91
91
+
92
92
+
return nil
93
93
+
}
+21
server/handle_well_known.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"github.com/labstack/echo/v4"
5
5
+
)
6
6
+
7
7
+
func (s *Server) handleWellKnown(e echo.Context) error {
8
8
+
return e.JSON(200, map[string]any{
9
9
+
"@context": []string{
10
10
+
"https://www.w3.org/ns/did/v1",
11
11
+
},
12
12
+
"id": s.config.Did,
13
13
+
"service": []map[string]string{
14
14
+
{
15
15
+
"id": "#atproto_pds",
16
16
+
"type": "AtprotoPersonalDataServer",
17
17
+
"serviceEndpoint": "https://" + s.config.Hostname,
18
18
+
},
19
19
+
},
20
20
+
})
21
21
+
}
+329
server/repo.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"bytes"
5
5
+
"context"
6
6
+
"fmt"
7
7
+
"io"
8
8
+
"time"
9
9
+
10
10
+
"github.com/Azure/go-autorest/autorest/to"
11
11
+
"github.com/bluesky-social/indigo/api/atproto"
12
12
+
"github.com/bluesky-social/indigo/atproto/data"
13
13
+
"github.com/bluesky-social/indigo/atproto/syntax"
14
14
+
"github.com/bluesky-social/indigo/carstore"
15
15
+
"github.com/bluesky-social/indigo/events"
16
16
+
lexutil "github.com/bluesky-social/indigo/lex/util"
17
17
+
"github.com/bluesky-social/indigo/repo"
18
18
+
"github.com/bluesky-social/indigo/util"
19
19
+
"github.com/haileyok/cocoon/blockstore"
20
20
+
"github.com/haileyok/cocoon/models"
21
21
+
blocks "github.com/ipfs/go-block-format"
22
22
+
"github.com/ipfs/go-cid"
23
23
+
cbor "github.com/ipfs/go-ipld-cbor"
24
24
+
"github.com/ipld/go-car"
25
25
+
"gorm.io/gorm"
26
26
+
"gorm.io/gorm/clause"
27
27
+
)
28
28
+
29
29
+
type RepoMan struct {
30
30
+
db *gorm.DB
31
31
+
s *Server
32
32
+
clock *syntax.TIDClock
33
33
+
}
34
34
+
35
35
+
func NewRepoMan(s *Server) *RepoMan {
36
36
+
clock := syntax.NewTIDClock(0)
37
37
+
38
38
+
return &RepoMan{
39
39
+
s: s,
40
40
+
db: s.db,
41
41
+
clock: &clock,
42
42
+
}
43
43
+
}
44
44
+
45
45
+
type OpType string
46
46
+
47
47
+
var (
48
48
+
OpTypeCreate = OpType("com.atproto.repo.applyWrites#create")
49
49
+
OpTypeUpdate = OpType("com.atproto.repo.applyWrites#update")
50
50
+
OpTypeDelete = OpType("com.atproto.repo.applyWrites#delete")
51
51
+
)
52
52
+
53
53
+
func (ot OpType) String() string {
54
54
+
return ot.String()
55
55
+
}
56
56
+
57
57
+
type Op struct {
58
58
+
Type OpType `json:"$type"`
59
59
+
Collection string `json:"collection"`
60
60
+
Rkey *string `json:"rkey,omitempty"`
61
61
+
Validate *bool `json:"validate,omitempty"`
62
62
+
SwapRecord *string `json:"swapRecord,omitempty"`
63
63
+
Record *MarshalableMap `json:"record,omitempty"`
64
64
+
}
65
65
+
66
66
+
type MarshalableMap map[string]any
67
67
+
68
68
+
type FirehoseOp struct {
69
69
+
Cid cid.Cid
70
70
+
Path string
71
71
+
Action string
72
72
+
}
73
73
+
74
74
+
func (mm *MarshalableMap) MarshalCBOR(w io.Writer) error {
75
75
+
data, err := data.MarshalCBOR(*mm)
76
76
+
if err != nil {
77
77
+
return err
78
78
+
}
79
79
+
80
80
+
w.Write(data)
81
81
+
82
82
+
return nil
83
83
+
}
84
84
+
85
85
+
// TODO make use of swap commit
86
86
+
func (rm *RepoMan) applyWrites(urepo models.Repo, writes []Op, swapCommit *string) error {
87
87
+
rootcid, err := cid.Cast(urepo.Root)
88
88
+
if err != nil {
89
89
+
return err
90
90
+
}
91
91
+
92
92
+
dbs := blockstore.New(urepo.Did, rm.db)
93
93
+
r, err := repo.OpenRepo(context.TODO(), dbs, rootcid)
94
94
+
95
95
+
entries := []models.Record{}
96
96
+
97
97
+
for i, op := range writes {
98
98
+
if op.Type != OpTypeCreate && op.Rkey == nil {
99
99
+
return fmt.Errorf("invalid rkey")
100
100
+
} else if op.Rkey == nil {
101
101
+
op.Rkey = to.StringPtr(rm.clock.Next().String())
102
102
+
writes[i].Rkey = op.Rkey
103
103
+
}
104
104
+
105
105
+
_, err := syntax.ParseRecordKey(*op.Rkey)
106
106
+
if err != nil {
107
107
+
return err
108
108
+
}
109
109
+
110
110
+
switch op.Type {
111
111
+
case OpTypeCreate:
112
112
+
nc, err := r.PutRecord(context.TODO(), op.Collection+"/"+*op.Rkey, op.Record)
113
113
+
if err != nil {
114
114
+
return err
115
115
+
}
116
116
+
117
117
+
d, _ := data.MarshalCBOR(*op.Record)
118
118
+
entries = append(entries, models.Record{
119
119
+
Did: urepo.Did,
120
120
+
CreatedAt: rm.clock.Next().String(),
121
121
+
Nsid: op.Collection,
122
122
+
Rkey: *op.Rkey,
123
123
+
Cid: nc.String(),
124
124
+
Value: d,
125
125
+
})
126
126
+
case OpTypeDelete:
127
127
+
err := r.DeleteRecord(context.TODO(), op.Collection+"/"+*op.Rkey)
128
128
+
if err != nil {
129
129
+
return err
130
130
+
}
131
131
+
case OpTypeUpdate:
132
132
+
nc, err := r.UpdateRecord(context.TODO(), op.Collection+"/"+*op.Rkey, op.Record)
133
133
+
if err != nil {
134
134
+
return err
135
135
+
}
136
136
+
137
137
+
d, _ := data.MarshalCBOR(*op.Record)
138
138
+
entries = append(entries, models.Record{
139
139
+
Did: urepo.Did,
140
140
+
CreatedAt: rm.clock.Next().String(),
141
141
+
Nsid: op.Collection,
142
142
+
Rkey: *op.Rkey,
143
143
+
Cid: nc.String(),
144
144
+
Value: d,
145
145
+
})
146
146
+
}
147
147
+
}
148
148
+
149
149
+
newroot, rev, err := r.Commit(context.TODO(), urepo.SignFor)
150
150
+
if err != nil {
151
151
+
return err
152
152
+
}
153
153
+
154
154
+
buf := new(bytes.Buffer)
155
155
+
156
156
+
hb, err := cbor.DumpObject(&car.CarHeader{
157
157
+
Roots: []cid.Cid{newroot},
158
158
+
Version: 1,
159
159
+
})
160
160
+
161
161
+
if _, err := carstore.LdWrite(buf, hb); err != nil {
162
162
+
return err
163
163
+
}
164
164
+
165
165
+
diffops, err := r.DiffSince(context.TODO(), rootcid)
166
166
+
if err != nil {
167
167
+
return err
168
168
+
}
169
169
+
170
170
+
ops := make([]*atproto.SyncSubscribeRepos_RepoOp, 0, len(diffops))
171
171
+
172
172
+
for _, op := range diffops {
173
173
+
switch op.Op {
174
174
+
case "add", "mut":
175
175
+
kind := "create"
176
176
+
if op.Op == "mut" {
177
177
+
kind = "update"
178
178
+
}
179
179
+
180
180
+
ll := lexutil.LexLink(op.NewCid)
181
181
+
ops = append(ops, &atproto.SyncSubscribeRepos_RepoOp{
182
182
+
Action: kind,
183
183
+
Path: op.Rpath,
184
184
+
Cid: &ll,
185
185
+
})
186
186
+
187
187
+
case "del":
188
188
+
ops = append(ops, &atproto.SyncSubscribeRepos_RepoOp{
189
189
+
Action: "delete",
190
190
+
Path: op.Rpath,
191
191
+
Cid: nil,
192
192
+
})
193
193
+
}
194
194
+
195
195
+
blk, err := dbs.Get(context.TODO(), op.NewCid)
196
196
+
if err != nil {
197
197
+
return err
198
198
+
}
199
199
+
200
200
+
if _, err := carstore.LdWrite(buf, blk.Cid().Bytes(), blk.RawData()); err != nil {
201
201
+
return err
202
202
+
}
203
203
+
}
204
204
+
205
205
+
for _, op := range dbs.GetLog() {
206
206
+
if _, err := carstore.LdWrite(buf, op.Cid().Bytes(), op.RawData()); err != nil {
207
207
+
return err
208
208
+
}
209
209
+
}
210
210
+
211
211
+
var blobs []lexutil.LexLink
212
212
+
for _, entry := range entries {
213
213
+
if err := rm.s.db.Clauses(clause.OnConflict{
214
214
+
Columns: []clause.Column{{Name: "did"}, {Name: "nsid"}, {Name: "rkey"}},
215
215
+
UpdateAll: true,
216
216
+
}).Create(&entry).Error; err != nil {
217
217
+
return err
218
218
+
}
219
219
+
220
220
+
// we should actually check the type (i.e. delete, create,., update) here but we'll do it later
221
221
+
cids, err := rm.incrementBlobRefs(urepo, entry.Value)
222
222
+
if err != nil {
223
223
+
return err
224
224
+
}
225
225
+
226
226
+
for _, c := range cids {
227
227
+
blobs = append(blobs, lexutil.LexLink(c))
228
228
+
}
229
229
+
}
230
230
+
231
231
+
rm.s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{
232
232
+
RepoCommit: &atproto.SyncSubscribeRepos_Commit{
233
233
+
Repo: urepo.Did,
234
234
+
Blocks: buf.Bytes(),
235
235
+
Blobs: blobs,
236
236
+
Rev: rev,
237
237
+
Since: &urepo.Rev,
238
238
+
Commit: lexutil.LexLink(newroot),
239
239
+
Time: time.Now().Format(util.ISO8601),
240
240
+
Ops: ops,
241
241
+
TooBig: false,
242
242
+
},
243
243
+
})
244
244
+
245
245
+
if err := dbs.UpdateRepo(context.TODO(), newroot, rev); err != nil {
246
246
+
return err
247
247
+
}
248
248
+
249
249
+
return nil
250
250
+
}
251
251
+
252
252
+
func (rm *RepoMan) getRecordProof(urepo models.Repo, collection, rkey string) (cid.Cid, []blocks.Block, error) {
253
253
+
c, err := cid.Cast(urepo.Root)
254
254
+
if err != nil {
255
255
+
return cid.Undef, nil, err
256
256
+
}
257
257
+
258
258
+
dbs := blockstore.New(urepo.Did, rm.db)
259
259
+
bs := util.NewLoggingBstore(dbs)
260
260
+
261
261
+
r, err := repo.OpenRepo(context.TODO(), bs, c)
262
262
+
if err != nil {
263
263
+
return cid.Undef, nil, err
264
264
+
}
265
265
+
266
266
+
_, _, err = r.GetRecordBytes(context.TODO(), collection+"/"+rkey)
267
267
+
if err != nil {
268
268
+
return cid.Undef, nil, err
269
269
+
}
270
270
+
271
271
+
return c, bs.GetLoggedBlocks(), nil
272
272
+
}
273
273
+
274
274
+
func (rm *RepoMan) incrementBlobRefs(urepo models.Repo, cbor []byte) ([]cid.Cid, error) {
275
275
+
cids, err := getBlobCidsFromCbor(cbor)
276
276
+
if err != nil {
277
277
+
return nil, err
278
278
+
}
279
279
+
280
280
+
for _, c := range cids {
281
281
+
if err := rm.db.Exec("UPDATE blobs SET ref_count = ref_count + 1 WHERE did = ? AND cid = ?", urepo.Did, c.Bytes()).Error; err != nil {
282
282
+
return nil, err
283
283
+
}
284
284
+
}
285
285
+
286
286
+
return cids, nil
287
287
+
}
288
288
+
289
289
+
// to be honest, we could just store both the cbor and non-cbor in []entries above to avoid an additional
290
290
+
// unmarshal here. this will work for now though
291
291
+
func getBlobCidsFromCbor(cbor []byte) ([]cid.Cid, error) {
292
292
+
var cids []cid.Cid
293
293
+
294
294
+
decoded, err := data.UnmarshalCBOR(cbor)
295
295
+
if err != nil {
296
296
+
return nil, fmt.Errorf("error unmarshaling cbor: %w", err)
297
297
+
}
298
298
+
299
299
+
var deepiter func(interface{}) error
300
300
+
deepiter = func(item interface{}) error {
301
301
+
switch val := item.(type) {
302
302
+
case map[string]interface{}:
303
303
+
if val["$type"] == "blob" {
304
304
+
if ref, ok := val["ref"].(string); ok {
305
305
+
c, err := cid.Parse(ref)
306
306
+
if err != nil {
307
307
+
return err
308
308
+
}
309
309
+
cids = append(cids, c)
310
310
+
}
311
311
+
for _, v := range val {
312
312
+
return deepiter(v)
313
313
+
}
314
314
+
}
315
315
+
case []interface{}:
316
316
+
for _, v := range val {
317
317
+
deepiter(v)
318
318
+
}
319
319
+
}
320
320
+
321
321
+
return nil
322
322
+
}
323
323
+
324
324
+
if err := deepiter(decoded); err != nil {
325
325
+
return nil, err
326
326
+
}
327
327
+
328
328
+
return cids, nil
329
329
+
}
+402
server/server.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"context"
5
5
+
"crypto/ecdsa"
6
6
+
"errors"
7
7
+
"fmt"
8
8
+
"log/slog"
9
9
+
"net/http"
10
10
+
"os"
11
11
+
"strings"
12
12
+
"time"
13
13
+
14
14
+
"github.com/Azure/go-autorest/autorest/to"
15
15
+
"github.com/bluesky-social/indigo/api/atproto"
16
16
+
"github.com/bluesky-social/indigo/atproto/syntax"
17
17
+
"github.com/bluesky-social/indigo/events"
18
18
+
"github.com/bluesky-social/indigo/xrpc"
19
19
+
"github.com/go-playground/validator"
20
20
+
"github.com/golang-jwt/jwt/v4"
21
21
+
"github.com/haileyok/cocoon/identity"
22
22
+
"github.com/haileyok/cocoon/internal/helpers"
23
23
+
"github.com/haileyok/cocoon/models"
24
24
+
"github.com/haileyok/cocoon/plc"
25
25
+
"github.com/labstack/echo/v4"
26
26
+
"github.com/labstack/echo/v4/middleware"
27
27
+
"github.com/lestrrat-go/jwx/v2/jwk"
28
28
+
slogecho "github.com/samber/slog-echo"
29
29
+
"gorm.io/driver/sqlite"
30
30
+
"gorm.io/gorm"
31
31
+
)
32
32
+
33
33
+
type Server struct {
34
34
+
httpd *http.Server
35
35
+
echo *echo.Echo
36
36
+
db *gorm.DB
37
37
+
plcClient *plc.Client
38
38
+
logger *slog.Logger
39
39
+
config *config
40
40
+
privateKey *ecdsa.PrivateKey
41
41
+
repoman *RepoMan
42
42
+
evtman *events.EventManager
43
43
+
passport *identity.Passport
44
44
+
}
45
45
+
46
46
+
type Args struct {
47
47
+
Addr string
48
48
+
DbName string
49
49
+
Logger *slog.Logger
50
50
+
Version string
51
51
+
Did string
52
52
+
Hostname string
53
53
+
RotationKeyPath string
54
54
+
JwkPath string
55
55
+
ContactEmail string
56
56
+
Relays []string
57
57
+
}
58
58
+
59
59
+
type config struct {
60
60
+
Version string
61
61
+
Did string
62
62
+
Hostname string
63
63
+
ContactEmail string
64
64
+
EnforcePeering bool
65
65
+
Relays []string
66
66
+
}
67
67
+
68
68
+
type CustomValidator struct {
69
69
+
validator *validator.Validate
70
70
+
}
71
71
+
72
72
+
type ValidationError struct {
73
73
+
error
74
74
+
Field string
75
75
+
Tag string
76
76
+
}
77
77
+
78
78
+
func (cv *CustomValidator) Validate(i any) error {
79
79
+
if err := cv.validator.Struct(i); err != nil {
80
80
+
var validateErrors validator.ValidationErrors
81
81
+
if errors.As(err, &validateErrors) && len(validateErrors) > 0 {
82
82
+
first := validateErrors[0]
83
83
+
return ValidationError{
84
84
+
error: err,
85
85
+
Field: first.Field(),
86
86
+
Tag: first.Tag(),
87
87
+
}
88
88
+
}
89
89
+
90
90
+
return err
91
91
+
}
92
92
+
93
93
+
return nil
94
94
+
}
95
95
+
96
96
+
func (s *Server) handleSessionMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
97
97
+
return func(e echo.Context) error {
98
98
+
authheader := e.Request().Header.Get("authorization")
99
99
+
if authheader == "" {
100
100
+
return e.JSON(401, map[string]string{"error": "Unauthorized"})
101
101
+
}
102
102
+
103
103
+
pts := strings.Split(authheader, " ")
104
104
+
if len(pts) != 2 {
105
105
+
return helpers.ServerError(e, nil)
106
106
+
}
107
107
+
108
108
+
tokenstr := pts[1]
109
109
+
110
110
+
token, err := new(jwt.Parser).Parse(tokenstr, func(t *jwt.Token) (any, error) {
111
111
+
if _, ok := t.Method.(*jwt.SigningMethodECDSA); !ok {
112
112
+
return nil, fmt.Errorf("unsupported signing method: %v", t.Header["alg"])
113
113
+
}
114
114
+
115
115
+
return s.privateKey.Public(), nil
116
116
+
})
117
117
+
if err != nil {
118
118
+
s.logger.Error("error parsing jwt", "error", err)
119
119
+
return helpers.InputError(e, to.StringPtr("InvalidToken"))
120
120
+
}
121
121
+
122
122
+
claims, ok := token.Claims.(jwt.MapClaims)
123
123
+
if !ok || !token.Valid {
124
124
+
return helpers.InputError(e, to.StringPtr("InvalidToken"))
125
125
+
}
126
126
+
127
127
+
isRefresh := e.Request().URL.Path == "/xrpc/com.atproto.server.refreshSession"
128
128
+
scope := claims["scope"].(string)
129
129
+
130
130
+
if isRefresh && scope != "com.atproto.refresh" {
131
131
+
return helpers.InputError(e, to.StringPtr("InvalidToken"))
132
132
+
} else if !isRefresh && scope != "com.atproto.access" {
133
133
+
return helpers.InputError(e, to.StringPtr("InvalidToken"))
134
134
+
}
135
135
+
136
136
+
table := "tokens"
137
137
+
if isRefresh {
138
138
+
table = "refresh_tokens"
139
139
+
}
140
140
+
141
141
+
type Result struct {
142
142
+
Found bool
143
143
+
}
144
144
+
var result Result
145
145
+
if err := s.db.Raw("SELECT EXISTS(SELECT 1 FROM "+table+" WHERE token = ?) AS found", tokenstr).Scan(&result).Error; err != nil {
146
146
+
if err == gorm.ErrRecordNotFound {
147
147
+
return helpers.InputError(e, to.StringPtr("InvalidToken"))
148
148
+
}
149
149
+
150
150
+
s.logger.Error("error getting token from db", "error", err)
151
151
+
return helpers.ServerError(e, nil)
152
152
+
}
153
153
+
154
154
+
if !result.Found {
155
155
+
return helpers.InputError(e, to.StringPtr("InvalidToken"))
156
156
+
}
157
157
+
158
158
+
exp, ok := claims["exp"].(float64)
159
159
+
if !ok {
160
160
+
s.logger.Error("error getting iat from token")
161
161
+
return helpers.ServerError(e, nil)
162
162
+
}
163
163
+
164
164
+
if exp < float64(time.Now().UTC().Unix()) {
165
165
+
return helpers.InputError(e, to.StringPtr("ExpiredToken"))
166
166
+
}
167
167
+
168
168
+
e.Set("did", claims["sub"])
169
169
+
170
170
+
repo, err := s.getRepoActorByDid(claims["sub"].(string))
171
171
+
if err != nil {
172
172
+
s.logger.Error("error fetching repo", "error", err)
173
173
+
return helpers.ServerError(e, nil)
174
174
+
}
175
175
+
e.Set("repo", repo)
176
176
+
177
177
+
e.Set("token", tokenstr)
178
178
+
179
179
+
if err := next(e); err != nil {
180
180
+
e.Error(err)
181
181
+
}
182
182
+
183
183
+
return nil
184
184
+
}
185
185
+
}
186
186
+
187
187
+
func New(args *Args) (*Server, error) {
188
188
+
if args.Addr == "" {
189
189
+
return nil, fmt.Errorf("addr must be set")
190
190
+
}
191
191
+
192
192
+
if args.DbName == "" {
193
193
+
return nil, fmt.Errorf("db name must be set")
194
194
+
}
195
195
+
196
196
+
if args.Did == "" {
197
197
+
return nil, fmt.Errorf("cocoon did must be set")
198
198
+
}
199
199
+
200
200
+
if args.ContactEmail == "" {
201
201
+
return nil, fmt.Errorf("cocoon contact email is required")
202
202
+
}
203
203
+
204
204
+
if _, err := syntax.ParseDID(args.Did); err != nil {
205
205
+
return nil, fmt.Errorf("error parsing cocoon did: %w", err)
206
206
+
}
207
207
+
208
208
+
if args.Hostname == "" {
209
209
+
return nil, fmt.Errorf("cocoon hostname must be set")
210
210
+
}
211
211
+
212
212
+
if args.Logger == nil {
213
213
+
args.Logger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{}))
214
214
+
}
215
215
+
216
216
+
e := echo.New()
217
217
+
218
218
+
e.Pre(middleware.RemoveTrailingSlash())
219
219
+
e.Pre(slogecho.New(args.Logger))
220
220
+
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
221
221
+
AllowOrigins: []string{"*"},
222
222
+
AllowHeaders: []string{"*"},
223
223
+
AllowMethods: []string{"*"},
224
224
+
AllowCredentials: true,
225
225
+
MaxAge: 100_000_000,
226
226
+
}))
227
227
+
228
228
+
vdtor := validator.New()
229
229
+
vdtor.RegisterValidation("atproto-handle", func(fl validator.FieldLevel) bool {
230
230
+
if _, err := syntax.ParseHandle(fl.Field().String()); err != nil {
231
231
+
return false
232
232
+
}
233
233
+
return true
234
234
+
})
235
235
+
vdtor.RegisterValidation("atproto-did", func(fl validator.FieldLevel) bool {
236
236
+
if _, err := syntax.ParseDID(fl.Field().String()); err != nil {
237
237
+
return false
238
238
+
}
239
239
+
return true
240
240
+
})
241
241
+
vdtor.RegisterValidation("atproto-rkey", func(fl validator.FieldLevel) bool {
242
242
+
if _, err := syntax.ParseRecordKey(fl.Field().String()); err != nil {
243
243
+
return false
244
244
+
}
245
245
+
return true
246
246
+
})
247
247
+
vdtor.RegisterValidation("atproto-nsid", func(fl validator.FieldLevel) bool {
248
248
+
if _, err := syntax.ParseNSID(fl.Field().String()); err != nil {
249
249
+
return false
250
250
+
}
251
251
+
return true
252
252
+
})
253
253
+
254
254
+
e.Validator = &CustomValidator{validator: vdtor}
255
255
+
256
256
+
httpd := &http.Server{
257
257
+
Addr: args.Addr,
258
258
+
Handler: e,
259
259
+
}
260
260
+
261
261
+
db, err := gorm.Open(sqlite.Open("cocoon.db"), &gorm.Config{})
262
262
+
if err != nil {
263
263
+
return nil, err
264
264
+
}
265
265
+
266
266
+
rkbytes, err := os.ReadFile(args.RotationKeyPath)
267
267
+
if err != nil {
268
268
+
return nil, err
269
269
+
}
270
270
+
271
271
+
plcClient, err := plc.NewClient(&plc.ClientArgs{
272
272
+
Service: "https://plc.directory",
273
273
+
PdsHostname: args.Hostname,
274
274
+
RotationKey: rkbytes,
275
275
+
})
276
276
+
if err != nil {
277
277
+
return nil, err
278
278
+
}
279
279
+
280
280
+
jwkbytes, err := os.ReadFile(args.JwkPath)
281
281
+
if err != nil {
282
282
+
return nil, err
283
283
+
}
284
284
+
285
285
+
key, err := jwk.ParseKey(jwkbytes)
286
286
+
if err != nil {
287
287
+
return nil, err
288
288
+
}
289
289
+
290
290
+
var pkey ecdsa.PrivateKey
291
291
+
if err := key.Raw(&pkey); err != nil {
292
292
+
return nil, err
293
293
+
}
294
294
+
295
295
+
s := &Server{
296
296
+
httpd: httpd,
297
297
+
echo: e,
298
298
+
logger: args.Logger,
299
299
+
db: db,
300
300
+
plcClient: plcClient,
301
301
+
privateKey: &pkey,
302
302
+
config: &config{
303
303
+
Version: args.Version,
304
304
+
Did: args.Did,
305
305
+
Hostname: args.Hostname,
306
306
+
ContactEmail: args.ContactEmail,
307
307
+
EnforcePeering: false,
308
308
+
Relays: args.Relays,
309
309
+
},
310
310
+
evtman: events.NewEventManager(events.NewMemPersister()),
311
311
+
passport: identity.NewPassport(identity.NewMemCache(10_000)),
312
312
+
}
313
313
+
314
314
+
s.repoman = NewRepoMan(s) // TODO: this is way too lazy, stop it
315
315
+
316
316
+
return s, nil
317
317
+
}
318
318
+
319
319
+
func (s *Server) addRoutes() {
320
320
+
s.echo.GET("/", s.handleRoot)
321
321
+
s.echo.GET("/xrpc/_health", s.handleHealth)
322
322
+
s.echo.GET("/.well-known/did.json", s.handleWellKnown)
323
323
+
s.echo.GET("/robots.txt", s.handleRobots)
324
324
+
325
325
+
// public
326
326
+
s.echo.GET("/xrpc/com.atproto.identity.resolveHandle", s.handleResolveHandle)
327
327
+
s.echo.POST("/xrpc/com.atproto.server.createAccount", s.handleCreateAccount)
328
328
+
s.echo.POST("/xrpc/com.atproto.server.createAccount", s.handleCreateAccount)
329
329
+
s.echo.POST("/xrpc/com.atproto.server.createSession", s.handleCreateSession)
330
330
+
s.echo.GET("/xrpc/com.atproto.server.describeServer", s.handleDescribeServer)
331
331
+
332
332
+
s.echo.GET("/xrpc/com.atproto.repo.describeRepo", s.handleDescribeRepo)
333
333
+
s.echo.GET("/xrpc/com.atproto.sync.listRepos", s.handleListRepos)
334
334
+
s.echo.GET("/xrpc/com.atproto.repo.listRecords", s.handleListRecords)
335
335
+
s.echo.GET("/xrpc/com.atproto.repo.getRecord", s.handleRepoGetRecord)
336
336
+
s.echo.GET("/xrpc/com.atproto.sync.getRecord", s.handleSyncGetRecord)
337
337
+
s.echo.GET("/xrpc/com.atproto.sync.getBlocks", s.handleGetBlocks)
338
338
+
s.echo.GET("/xrpc/com.atproto.sync.getLatestCommit", s.handleSyncGetLatestCommit)
339
339
+
s.echo.GET("/xrpc/com.atproto.sync.getRepoStatus", s.handleSyncGetRepoStatus)
340
340
+
s.echo.GET("/xrpc/com.atproto.sync.getRepo", s.handleSyncGetRepo)
341
341
+
s.echo.GET("/xrpc/com.atproto.sync.subscribeRepos", s.handleSyncSubscribeRepos)
342
342
+
s.echo.GET("/xrpc/com.atproto.sync.listBlobs", s.handleSyncListBlobs)
343
343
+
s.echo.GET("/xrpc/com.atproto.sync.getBlob", s.handleSyncGetBlob)
344
344
+
345
345
+
// authed
346
346
+
s.echo.GET("/xrpc/com.atproto.server.getSession", s.handleGetSession, s.handleSessionMiddleware)
347
347
+
s.echo.POST("/xrpc/com.atproto.server.refreshSession", s.handleRefreshSession, s.handleSessionMiddleware)
348
348
+
s.echo.POST("/xrpc/com.atproto.server.deleteSession", s.handleDeleteSession, s.handleSessionMiddleware)
349
349
+
s.echo.POST("/xrpc/com.atproto.identity.updateHandle", s.handleIdentityUpdateHandle, s.handleSessionMiddleware)
350
350
+
351
351
+
// repo
352
352
+
s.echo.POST("/xrpc/com.atproto.repo.createRecord", s.handleCreateRecord, s.handleSessionMiddleware)
353
353
+
s.echo.POST("/xrpc/com.atproto.repo.putRecord", s.handlePutRecord, s.handleSessionMiddleware)
354
354
+
s.echo.POST("/xrpc/com.atproto.repo.applyWrites", s.handleApplyWrites, s.handleSessionMiddleware)
355
355
+
s.echo.POST("/xrpc/com.atproto.repo.uploadBlob", s.handleRepoUploadBlob, s.handleSessionMiddleware)
356
356
+
357
357
+
// stupid silly endpoints
358
358
+
s.echo.GET("/xrpc/app.bsky.actor.getPreferences", s.handleActorGetPreferences, s.handleSessionMiddleware)
359
359
+
s.echo.POST("/xrpc/app.bsky.actor.putPreferences", s.handleActorPutPreferences, s.handleSessionMiddleware)
360
360
+
361
361
+
s.echo.GET("/xrpc/*", s.handleProxy, s.handleSessionMiddleware)
362
362
+
s.echo.POST("/xrpc/*", s.handleProxy, s.handleSessionMiddleware)
363
363
+
}
364
364
+
365
365
+
func (s *Server) Serve(ctx context.Context) error {
366
366
+
s.addRoutes()
367
367
+
368
368
+
s.logger.Info("migrating...")
369
369
+
370
370
+
s.db.AutoMigrate(
371
371
+
&models.Actor{},
372
372
+
&models.Repo{},
373
373
+
&models.InviteCode{},
374
374
+
&models.Token{},
375
375
+
&models.RefreshToken{},
376
376
+
&models.Block{},
377
377
+
&models.Record{},
378
378
+
&models.Blob{},
379
379
+
&models.BlobPart{},
380
380
+
)
381
381
+
382
382
+
s.logger.Info("starting cocoon")
383
383
+
384
384
+
go func() {
385
385
+
if err := s.httpd.ListenAndServe(); err != nil {
386
386
+
panic(err)
387
387
+
}
388
388
+
}()
389
389
+
390
390
+
for _, relay := range s.config.Relays {
391
391
+
cli := xrpc.Client{Host: relay}
392
392
+
atproto.SyncRequestCrawl(context.TODO(), &cli, &atproto.SyncRequestCrawl_Input{
393
393
+
Hostname: s.config.Hostname,
394
394
+
})
395
395
+
}
396
396
+
397
397
+
<-ctx.Done()
398
398
+
399
399
+
fmt.Println("shut down")
400
400
+
401
401
+
return nil
402
402
+
}
+75
server/session.go
Reviewed
···
1
1
+
package server
2
2
+
3
3
+
import (
4
4
+
"time"
5
5
+
6
6
+
"github.com/golang-jwt/jwt/v4"
7
7
+
"github.com/google/uuid"
8
8
+
"github.com/haileyok/cocoon/models"
9
9
+
)
10
10
+
11
11
+
type Session struct {
12
12
+
AccessToken string
13
13
+
RefreshToken string
14
14
+
}
15
15
+
16
16
+
func (s *Server) createSession(repo *models.Repo) (*Session, error) {
17
17
+
now := time.Now()
18
18
+
accexp := now.Add(3 * time.Hour)
19
19
+
refexp := now.Add(7 * 24 * time.Hour)
20
20
+
jti := uuid.NewString()
21
21
+
22
22
+
accessClaims := jwt.MapClaims{
23
23
+
"scope": "com.atproto.access",
24
24
+
"aud": s.config.Did,
25
25
+
"sub": repo.Did,
26
26
+
"iat": now.UTC().Unix(),
27
27
+
"exp": accexp.UTC().Unix(),
28
28
+
"jti": jti,
29
29
+
}
30
30
+
31
31
+
accessToken := jwt.NewWithClaims(jwt.SigningMethodES256, accessClaims)
32
32
+
accessString, err := accessToken.SignedString(s.privateKey)
33
33
+
if err != nil {
34
34
+
return nil, err
35
35
+
}
36
36
+
37
37
+
refreshClaims := jwt.MapClaims{
38
38
+
"scope": "com.atproto.refresh",
39
39
+
"aud": s.config.Did,
40
40
+
"sub": repo.Did,
41
41
+
"iat": now.UTC().Unix(),
42
42
+
"exp": refexp.UTC().Unix(),
43
43
+
"jti": jti,
44
44
+
}
45
45
+
46
46
+
refreshToken := jwt.NewWithClaims(jwt.SigningMethodES256, refreshClaims)
47
47
+
refreshString, err := refreshToken.SignedString(s.privateKey)
48
48
+
if err != nil {
49
49
+
return nil, err
50
50
+
}
51
51
+
52
52
+
if err := s.db.Create(&models.Token{
53
53
+
Token: accessString,
54
54
+
Did: repo.Did,
55
55
+
RefreshToken: refreshString,
56
56
+
CreatedAt: now,
57
57
+
ExpiresAt: accexp,
58
58
+
}).Error; err != nil {
59
59
+
return nil, err
60
60
+
}
61
61
+
62
62
+
if err := s.db.Create(&models.RefreshToken{
63
63
+
Token: refreshString,
64
64
+
Did: repo.Did,
65
65
+
CreatedAt: now,
66
66
+
ExpiresAt: refexp,
67
67
+
}).Error; err != nil {
68
68
+
return nil, err
69
69
+
}
70
70
+
71
71
+
return &Session{
72
72
+
AccessToken: accessString,
73
73
+
RefreshToken: refreshString,
74
74
+
}, nil
75
75
+
}
+120
test.go
Reviewed
···
1
1
+
package main
2
2
+
3
3
+
import (
4
4
+
"bytes"
5
5
+
"context"
6
6
+
"fmt"
7
7
+
"log/slog"
8
8
+
"net/http"
9
9
+
"net/url"
10
10
+
"strings"
11
11
+
12
12
+
"github.com/bluesky-social/indigo/api/atproto"
13
13
+
"github.com/bluesky-social/indigo/atproto/syntax"
14
14
+
"github.com/bluesky-social/indigo/events"
15
15
+
"github.com/bluesky-social/indigo/events/schedulers/parallel"
16
16
+
lexutil "github.com/bluesky-social/indigo/lex/util"
17
17
+
"github.com/bluesky-social/indigo/repo"
18
18
+
"github.com/bluesky-social/indigo/repomgr"
19
19
+
"github.com/gorilla/websocket"
20
20
+
)
21
21
+
22
22
+
func main() {
23
23
+
runFirehoseConsumer("ws://localhost:8080")
24
24
+
}
25
25
+
26
26
+
func runFirehoseConsumer(relayHost string) error {
27
27
+
dialer := websocket.DefaultDialer
28
28
+
u, err := url.Parse("wss://cocoon.hailey.at")
29
29
+
if err != nil {
30
30
+
return fmt.Errorf("invalid relayHost: %w", err)
31
31
+
}
32
32
+
33
33
+
u.Path = "xrpc/com.atproto.sync.subscribeRepos"
34
34
+
conn, _, err := dialer.Dial(u.String(), http.Header{
35
35
+
"User-Agent": []string{fmt.Sprintf("hot-topic/0.0.0")},
36
36
+
})
37
37
+
if err != nil {
38
38
+
return fmt.Errorf("subscribing to firehose failed (dialing): %w", err)
39
39
+
}
40
40
+
41
41
+
rsc := &events.RepoStreamCallbacks{
42
42
+
RepoCommit: func(evt *atproto.SyncSubscribeRepos_Commit) error {
43
43
+
fmt.Println(evt.Repo)
44
44
+
return handleRepoCommit(evt)
45
45
+
},
46
46
+
RepoIdentity: func(evt *atproto.SyncSubscribeRepos_Identity) error {
47
47
+
fmt.Println(evt.Did, evt.Handle)
48
48
+
return nil
49
49
+
},
50
50
+
}
51
51
+
52
52
+
var scheduler events.Scheduler
53
53
+
parallelism := 700
54
54
+
scheduler = parallel.NewScheduler(parallelism, 1000, relayHost, rsc.EventHandler)
55
55
+
56
56
+
return events.HandleRepoStream(context.TODO(), conn, scheduler, slog.Default())
57
57
+
}
58
58
+
59
59
+
func splitRepoPath(path string) (syntax.NSID, syntax.RecordKey, error) {
60
60
+
parts := strings.SplitN(path, "/", 3)
61
61
+
if len(parts) != 2 {
62
62
+
return "", "", fmt.Errorf("invalid record path: %s", path)
63
63
+
}
64
64
+
collection, err := syntax.ParseNSID(parts[0])
65
65
+
if err != nil {
66
66
+
return "", "", err
67
67
+
}
68
68
+
rkey, err := syntax.ParseRecordKey(parts[1])
69
69
+
if err != nil {
70
70
+
return "", "", err
71
71
+
}
72
72
+
return collection, rkey, nil
73
73
+
}
74
74
+
75
75
+
func handleRepoCommit(evt *atproto.SyncSubscribeRepos_Commit) error {
76
76
+
if evt.TooBig {
77
77
+
return nil
78
78
+
}
79
79
+
80
80
+
did, err := syntax.ParseDID(evt.Repo)
81
81
+
if err != nil {
82
82
+
panic(err)
83
83
+
}
84
84
+
85
85
+
rr, err := repo.ReadRepoFromCar(context.TODO(), bytes.NewReader(evt.Blocks))
86
86
+
if err != nil {
87
87
+
panic(err)
88
88
+
}
89
89
+
90
90
+
for _, op := range evt.Ops {
91
91
+
collection, rkey, err := splitRepoPath(op.Path)
92
92
+
if err != nil {
93
93
+
panic(err)
94
94
+
}
95
95
+
96
96
+
ek := repomgr.EventKind(op.Action)
97
97
+
98
98
+
go func() {
99
99
+
switch ek {
100
100
+
case repomgr.EvtKindCreateRecord, repomgr.EvtKindUpdateRecord:
101
101
+
rc, recordCBOR, err := rr.GetRecordBytes(context.TODO(), op.Path)
102
102
+
if err != nil {
103
103
+
panic(err)
104
104
+
}
105
105
+
106
106
+
if op.Cid == nil || lexutil.LexLink(rc) != *op.Cid {
107
107
+
panic("nocid")
108
108
+
}
109
109
+
110
110
+
_ = collection
111
111
+
_ = rkey
112
112
+
_ = recordCBOR
113
113
+
_ = did
114
114
+
115
115
+
}
116
116
+
}()
117
117
+
}
118
118
+
119
119
+
return nil
120
120
+
}