+44
-7
blockstore/blockstore.go
+44
-7
blockstore/blockstore.go
···
16
db *gorm.DB
17
did string
18
readonly bool
19
-
inserts []blocks.Block
20
}
21
22
func New(did string, db *gorm.DB) *SqliteBlockstore {
···
24
did: did,
25
db: db,
26
readonly: false,
27
-
inserts: []blocks.Block{},
28
}
29
}
30
···
33
did: did,
34
db: db,
35
readonly: true,
36
-
inserts: []blocks.Block{},
37
}
38
}
39
40
func (bs *SqliteBlockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) {
41
var block models.Block
42
if err := bs.db.Raw("SELECT * FROM blocks WHERE did = ? AND cid = ?", bs.did, cid.Bytes()).Scan(&block).Error; err != nil {
43
return nil, err
44
}
···
52
}
53
54
func (bs *SqliteBlockstore) Put(ctx context.Context, block blocks.Block) error {
55
-
bs.inserts = append(bs.inserts, block)
56
57
if bs.readonly {
58
return nil
···
87
panic("not implemented")
88
}
89
90
-
func (bs *SqliteBlockstore) PutMany(context.Context, []blocks.Block) error {
91
-
panic("not implemented")
92
}
93
94
func (bs *SqliteBlockstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) {
···
121
return nil
122
}
123
124
-
func (bs *SqliteBlockstore) GetLog() []blocks.Block {
125
return bs.inserts
126
}
···
16
db *gorm.DB
17
did string
18
readonly bool
19
+
inserts map[cid.Cid]blocks.Block
20
}
21
22
func New(did string, db *gorm.DB) *SqliteBlockstore {
···
24
did: did,
25
db: db,
26
readonly: false,
27
+
inserts: map[cid.Cid]blocks.Block{},
28
}
29
}
30
···
33
did: did,
34
db: db,
35
readonly: true,
36
+
inserts: map[cid.Cid]blocks.Block{},
37
}
38
}
39
40
func (bs *SqliteBlockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) {
41
var block models.Block
42
+
43
+
maybeBlock, ok := bs.inserts[cid]
44
+
if ok {
45
+
return maybeBlock, nil
46
+
}
47
+
48
if err := bs.db.Raw("SELECT * FROM blocks WHERE did = ? AND cid = ?", bs.did, cid.Bytes()).Scan(&block).Error; err != nil {
49
return nil, err
50
}
···
58
}
59
60
func (bs *SqliteBlockstore) Put(ctx context.Context, block blocks.Block) error {
61
+
bs.inserts[block.Cid()] = block
62
63
if bs.readonly {
64
return nil
···
93
panic("not implemented")
94
}
95
96
+
func (bs *SqliteBlockstore) PutMany(ctx context.Context, blocks []blocks.Block) error {
97
+
tx := bs.db.Begin()
98
+
99
+
for _, block := range blocks {
100
+
bs.inserts[block.Cid()] = block
101
+
102
+
if bs.readonly {
103
+
continue
104
+
}
105
+
106
+
b := models.Block{
107
+
Did: bs.did,
108
+
Cid: block.Cid().Bytes(),
109
+
Rev: syntax.NewTIDNow(0).String(), // TODO: WARN, this is bad. don't do this
110
+
Value: block.RawData(),
111
+
}
112
+
113
+
if err := tx.Clauses(clause.OnConflict{
114
+
Columns: []clause.Column{{Name: "did"}, {Name: "cid"}},
115
+
UpdateAll: true,
116
+
}).Create(&b).Error; err != nil {
117
+
tx.Rollback()
118
+
return err
119
+
}
120
+
}
121
+
122
+
if bs.readonly {
123
+
return nil
124
+
}
125
+
126
+
tx.Commit()
127
+
128
+
return nil
129
}
130
131
func (bs *SqliteBlockstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) {
···
158
return nil
159
}
160
161
+
func (bs *SqliteBlockstore) GetLog() map[cid.Cid]blocks.Block {
162
return bs.inserts
163
}
+116
server/handle_import_repo.go
+116
server/handle_import_repo.go
···
···
1
+
package server
2
+
3
+
import (
4
+
"bytes"
5
+
"context"
6
+
"io"
7
+
"slices"
8
+
"strings"
9
+
10
+
"github.com/bluesky-social/indigo/atproto/syntax"
11
+
"github.com/bluesky-social/indigo/repo"
12
+
"github.com/haileyok/cocoon/blockstore"
13
+
"github.com/haileyok/cocoon/internal/helpers"
14
+
"github.com/haileyok/cocoon/models"
15
+
blocks "github.com/ipfs/go-block-format"
16
+
"github.com/ipfs/go-cid"
17
+
"github.com/ipld/go-car"
18
+
"github.com/labstack/echo/v4"
19
+
)
20
+
21
+
func (s *Server) handleRepoImportRepo(e echo.Context) error {
22
+
urepo := e.Get("repo").(*models.RepoActor)
23
+
24
+
b, err := io.ReadAll(e.Request().Body)
25
+
if err != nil {
26
+
s.logger.Error("could not read bytes in import request", "error", err)
27
+
return helpers.ServerError(e, nil)
28
+
}
29
+
30
+
bs := blockstore.New(urepo.Repo.Did, s.db)
31
+
32
+
cs, err := car.NewCarReader(bytes.NewReader(b))
33
+
if err != nil {
34
+
s.logger.Error("could not read car in import request", "error", err)
35
+
return helpers.ServerError(e, nil)
36
+
}
37
+
38
+
orderedBlocks := []blocks.Block{}
39
+
currBlock, err := cs.Next()
40
+
if err != nil {
41
+
s.logger.Error("could not get first block from car", "error", err)
42
+
return helpers.ServerError(e, nil)
43
+
}
44
+
currBlockCt := 1
45
+
46
+
for currBlock != nil {
47
+
s.logger.Info("someone is importing their repo", "block", currBlockCt)
48
+
orderedBlocks = append(orderedBlocks, currBlock)
49
+
next, _ := cs.Next()
50
+
currBlock = next
51
+
currBlockCt++
52
+
}
53
+
54
+
slices.Reverse(orderedBlocks)
55
+
56
+
if err := bs.PutMany(context.TODO(), orderedBlocks); err != nil {
57
+
s.logger.Error("could not insert blocks", "error", err)
58
+
return helpers.ServerError(e, nil)
59
+
}
60
+
61
+
r, err := repo.OpenRepo(context.TODO(), bs, cs.Header.Roots[0])
62
+
if err != nil {
63
+
s.logger.Error("could not open repo", "error", err)
64
+
return helpers.ServerError(e, nil)
65
+
}
66
+
67
+
tx := s.db.Begin()
68
+
69
+
clock := syntax.NewTIDClock(0)
70
+
71
+
if err := r.ForEach(context.TODO(), "", func(key string, cid cid.Cid) error {
72
+
pts := strings.Split(key, "/")
73
+
nsid := pts[0]
74
+
rkey := pts[1]
75
+
cidStr := cid.String()
76
+
b, err := bs.Get(context.TODO(), cid)
77
+
if err != nil {
78
+
s.logger.Error("record bytes don't exist in blockstore", "error", err)
79
+
return helpers.ServerError(e, nil)
80
+
}
81
+
82
+
rec := models.Record{
83
+
Did: urepo.Repo.Did,
84
+
CreatedAt: clock.Next().String(),
85
+
Nsid: nsid,
86
+
Rkey: rkey,
87
+
Cid: cidStr,
88
+
Value: b.RawData(),
89
+
}
90
+
91
+
if err := tx.Create(rec).Error; err != nil {
92
+
return err
93
+
}
94
+
95
+
return nil
96
+
}); err != nil {
97
+
tx.Rollback()
98
+
s.logger.Error("record bytes don't exist in blockstore", "error", err)
99
+
return helpers.ServerError(e, nil)
100
+
}
101
+
102
+
tx.Commit()
103
+
104
+
root, rev, err := r.Commit(context.TODO(), urepo.SignFor)
105
+
if err != nil {
106
+
s.logger.Error("error committing", "error", err)
107
+
return helpers.ServerError(e, nil)
108
+
}
109
+
110
+
if err := bs.UpdateRepo(context.TODO(), root, rev); err != nil {
111
+
s.logger.Error("error updating repo after commit", "error", err)
112
+
return helpers.ServerError(e, nil)
113
+
}
114
+
115
+
return nil
116
+
}
+62
-42
server/handle_server_create_account.go
+62
-42
server/handle_server_create_account.go
···
10
"github.com/Azure/go-autorest/autorest/to"
11
"github.com/bluesky-social/indigo/api/atproto"
12
"github.com/bluesky-social/indigo/atproto/crypto"
13
"github.com/bluesky-social/indigo/events"
14
"github.com/bluesky-social/indigo/repo"
15
"github.com/bluesky-social/indigo/util"
···
38
39
func (s *Server) handleCreateAccount(e echo.Context) error {
40
var request ComAtprotoServerCreateAccountRequest
41
42
if err := e.Bind(&request); err != nil {
43
s.logger.Error("error receiving request", "endpoint", "com.atproto.server.createAccount", "error", err)
···
109
110
// TODO: unsupported domains
111
112
-
// TODO: did stuff
113
-
114
k, err := crypto.GeneratePrivateKeyK256()
115
if err != nil {
116
s.logger.Error("error creating signing key", "endpoint", "com.atproto.server.createAccount", "error", err)
117
return helpers.ServerError(e, nil)
118
}
119
120
-
did, op, err := s.plcClient.CreateDID(k, "", request.Handle)
121
-
if err != nil {
122
-
s.logger.Error("error creating operation", "endpoint", "com.atproto.server.createAccount", "error", err)
123
-
return helpers.ServerError(e, nil)
124
-
}
125
126
-
if err := s.plcClient.SendOperation(e.Request().Context(), did, op); err != nil {
127
-
s.logger.Error("error sending plc op", "endpoint", "com.atproto.server.createAccount", "error", err)
128
-
return helpers.ServerError(e, nil)
129
}
130
131
hashed, err := bcrypt.GenerateFromPassword([]byte(request.Password), 10)
···
135
}
136
137
urepo := models.Repo{
138
-
Did: did,
139
CreatedAt: time.Now(),
140
Email: request.Email,
141
EmailVerificationCode: to.StringPtr(fmt.Sprintf("%s-%s", helpers.RandomVarchar(6), helpers.RandomVarchar(6))),
···
144
}
145
146
actor := models.Actor{
147
-
Did: did,
148
Handle: request.Handle,
149
}
150
···
153
return helpers.ServerError(e, nil)
154
}
155
156
-
bs := blockstore.New(did, s.db)
157
-
r := repo.NewRepo(context.TODO(), did, bs)
158
-
159
-
root, rev, err := r.Commit(context.TODO(), urepo.SignFor)
160
-
if err != nil {
161
-
s.logger.Error("error committing", "error", err)
162
return helpers.ServerError(e, nil)
163
}
164
165
-
if err := bs.UpdateRepo(context.TODO(), root, rev); err != nil {
166
-
s.logger.Error("error updating repo after commit", "error", err)
167
-
return helpers.ServerError(e, nil)
168
-
}
169
170
-
s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{
171
-
RepoHandle: &atproto.SyncSubscribeRepos_Handle{
172
-
Did: urepo.Did,
173
-
Handle: request.Handle,
174
-
Seq: time.Now().UnixMicro(), // TODO: no
175
-
Time: time.Now().Format(util.ISO8601),
176
-
},
177
-
})
178
179
-
s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{
180
-
RepoIdentity: &atproto.SyncSubscribeRepos_Identity{
181
-
Did: urepo.Did,
182
-
Handle: to.StringPtr(request.Handle),
183
-
Seq: time.Now().UnixMicro(), // TODO: no
184
-
Time: time.Now().Format(util.ISO8601),
185
-
},
186
-
})
187
188
-
if err := s.db.Create(&actor).Error; err != nil {
189
-
s.logger.Error("error inserting new actor", "error", err)
190
-
return helpers.ServerError(e, nil)
191
}
192
193
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 {
···
214
AccessJwt: sess.AccessToken,
215
RefreshJwt: sess.RefreshToken,
216
Handle: request.Handle,
217
-
Did: did,
218
})
219
}
···
10
"github.com/Azure/go-autorest/autorest/to"
11
"github.com/bluesky-social/indigo/api/atproto"
12
"github.com/bluesky-social/indigo/atproto/crypto"
13
+
"github.com/bluesky-social/indigo/atproto/syntax"
14
"github.com/bluesky-social/indigo/events"
15
"github.com/bluesky-social/indigo/repo"
16
"github.com/bluesky-social/indigo/util"
···
39
40
func (s *Server) handleCreateAccount(e echo.Context) error {
41
var request ComAtprotoServerCreateAccountRequest
42
+
43
+
var signupDid string
44
+
customDidHeader := e.Request().Header.Get("authorization")
45
+
if customDidHeader != "" {
46
+
pts := strings.Split(customDidHeader, " ")
47
+
if len(pts) != 2 {
48
+
return helpers.InputError(e, to.StringPtr("InvalidDid"))
49
+
}
50
+
51
+
_, err := syntax.ParseDID(pts[1])
52
+
if err != nil {
53
+
return helpers.InputError(e, to.StringPtr("InvalidDid"))
54
+
}
55
+
56
+
signupDid = pts[1]
57
+
}
58
59
if err := e.Bind(&request); err != nil {
60
s.logger.Error("error receiving request", "endpoint", "com.atproto.server.createAccount", "error", err)
···
126
127
// TODO: unsupported domains
128
129
k, err := crypto.GeneratePrivateKeyK256()
130
if err != nil {
131
s.logger.Error("error creating signing key", "endpoint", "com.atproto.server.createAccount", "error", err)
132
return helpers.ServerError(e, nil)
133
}
134
135
+
if signupDid == "" {
136
+
did, op, err := s.plcClient.CreateDID(k, "", request.Handle)
137
+
if err != nil {
138
+
s.logger.Error("error creating operation", "endpoint", "com.atproto.server.createAccount", "error", err)
139
+
return helpers.ServerError(e, nil)
140
+
}
141
142
+
if err := s.plcClient.SendOperation(e.Request().Context(), did, op); err != nil {
143
+
s.logger.Error("error sending plc op", "endpoint", "com.atproto.server.createAccount", "error", err)
144
+
return helpers.ServerError(e, nil)
145
+
}
146
+
signupDid = did
147
}
148
149
hashed, err := bcrypt.GenerateFromPassword([]byte(request.Password), 10)
···
153
}
154
155
urepo := models.Repo{
156
+
Did: signupDid,
157
CreatedAt: time.Now(),
158
Email: request.Email,
159
EmailVerificationCode: to.StringPtr(fmt.Sprintf("%s-%s", helpers.RandomVarchar(6), helpers.RandomVarchar(6))),
···
162
}
163
164
actor := models.Actor{
165
+
Did: signupDid,
166
Handle: request.Handle,
167
}
168
···
171
return helpers.ServerError(e, nil)
172
}
173
174
+
if err := s.db.Create(&actor).Error; err != nil {
175
+
s.logger.Error("error inserting new actor", "error", err)
176
return helpers.ServerError(e, nil)
177
}
178
179
+
if customDidHeader == "" {
180
+
bs := blockstore.New(signupDid, s.db)
181
+
r := repo.NewRepo(context.TODO(), signupDid, bs)
182
183
+
root, rev, err := r.Commit(context.TODO(), urepo.SignFor)
184
+
if err != nil {
185
+
s.logger.Error("error committing", "error", err)
186
+
return helpers.ServerError(e, nil)
187
+
}
188
189
+
if err := bs.UpdateRepo(context.TODO(), root, rev); err != nil {
190
+
s.logger.Error("error updating repo after commit", "error", err)
191
+
return helpers.ServerError(e, nil)
192
+
}
193
194
+
s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{
195
+
RepoHandle: &atproto.SyncSubscribeRepos_Handle{
196
+
Did: urepo.Did,
197
+
Handle: request.Handle,
198
+
Seq: time.Now().UnixMicro(), // TODO: no
199
+
Time: time.Now().Format(util.ISO8601),
200
+
},
201
+
})
202
+
203
+
s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{
204
+
RepoIdentity: &atproto.SyncSubscribeRepos_Identity{
205
+
Did: urepo.Did,
206
+
Handle: to.StringPtr(request.Handle),
207
+
Seq: time.Now().UnixMicro(), // TODO: no
208
+
Time: time.Now().Format(util.ISO8601),
209
+
},
210
+
})
211
}
212
213
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 {
···
234
AccessJwt: sess.AccessToken,
235
RefreshJwt: sess.RefreshToken,
236
Handle: request.Handle,
237
+
Did: signupDid,
238
})
239
}
+7
-2
server/server.go
+7
-2
server/server.go
···
121
if err := next(e); err != nil {
122
e.Error(err)
123
}
124
-
125
return nil
126
}
127
}
···
293
httpd := &http.Server{
294
Addr: args.Addr,
295
Handler: e,
296
}
297
298
db, err := gorm.Open(sqlite.Open("cocoon.db"), &gorm.Config{})
···
417
s.echo.POST("/xrpc/com.atproto.repo.deleteRecord", s.handleDeleteRecord, s.handleSessionMiddleware)
418
s.echo.POST("/xrpc/com.atproto.repo.applyWrites", s.handleApplyWrites, s.handleSessionMiddleware)
419
s.echo.POST("/xrpc/com.atproto.repo.uploadBlob", s.handleRepoUploadBlob, s.handleSessionMiddleware)
420
421
// stupid silly endpoints
422
s.echo.GET("/xrpc/app.bsky.actor.getPreferences", s.handleActorGetPreferences, s.handleSessionMiddleware)
···
425
// are there any routes that we should be allowing without auth? i dont think so but idk
426
s.echo.GET("/xrpc/*", s.handleProxy, s.handleSessionMiddleware)
427
s.echo.POST("/xrpc/*", s.handleProxy, s.handleSessionMiddleware)
428
-
429
// admin routes
430
s.echo.POST("/xrpc/com.atproto.server.createInviteCode", s.handleCreateInviteCode, s.handleAdminMiddleware)
431
s.echo.POST("/xrpc/com.atproto.server.createInviteCodes", s.handleCreateInviteCodes, s.handleAdminMiddleware)
···
121
if err := next(e); err != nil {
122
e.Error(err)
123
}
124
+
125
return nil
126
}
127
}
···
293
httpd := &http.Server{
294
Addr: args.Addr,
295
Handler: e,
296
+
// shitty defaults but okay for now, needed for import repo
297
+
ReadTimeout: 5 * time.Minute,
298
+
WriteTimeout: 5 * time.Minute,
299
+
IdleTimeout: 5 * time.Minute,
300
}
301
302
db, err := gorm.Open(sqlite.Open("cocoon.db"), &gorm.Config{})
···
421
s.echo.POST("/xrpc/com.atproto.repo.deleteRecord", s.handleDeleteRecord, s.handleSessionMiddleware)
422
s.echo.POST("/xrpc/com.atproto.repo.applyWrites", s.handleApplyWrites, s.handleSessionMiddleware)
423
s.echo.POST("/xrpc/com.atproto.repo.uploadBlob", s.handleRepoUploadBlob, s.handleSessionMiddleware)
424
+
s.echo.POST("/xrpc/com.atproto.repo.importRepo", s.handleRepoImportRepo, s.handleSessionMiddleware)
425
426
// stupid silly endpoints
427
s.echo.GET("/xrpc/app.bsky.actor.getPreferences", s.handleActorGetPreferences, s.handleSessionMiddleware)
···
430
// are there any routes that we should be allowing without auth? i dont think so but idk
431
s.echo.GET("/xrpc/*", s.handleProxy, s.handleSessionMiddleware)
432
s.echo.POST("/xrpc/*", s.handleProxy, s.handleSessionMiddleware)
433
+
434
// admin routes
435
s.echo.POST("/xrpc/com.atproto.server.createInviteCode", s.handleCreateInviteCode, s.handleAdminMiddleware)
436
s.echo.POST("/xrpc/com.atproto.server.createInviteCodes", s.handleCreateInviteCodes, s.handleAdminMiddleware)