A community based topic aggregation platform built on atproto
1package repo
2
3import (
4 "bytes"
5 "context"
6 "fmt"
7
8 "github.com/bluesky-social/indigo/mst"
9 "github.com/bluesky-social/indigo/repo"
10 "github.com/ipfs/go-cid"
11 blockstore "github.com/ipfs/go-ipfs-blockstore"
12 cbornode "github.com/ipfs/go-ipld-cbor"
13 cbg "github.com/whyrusleeping/cbor-gen"
14)
15
16// Wrapper provides a thin wrapper around Indigo's repo package
17type Wrapper struct {
18 repo *repo.Repo
19 blockstore blockstore.Blockstore
20}
21
22// NewWrapper creates a new wrapper for a repository with the provided blockstore
23func NewWrapper(did string, signingKey interface{}, bs blockstore.Blockstore) (*Wrapper, error) {
24 // Create new repository with the provided blockstore
25 r := repo.NewRepo(context.Background(), did, bs)
26
27 return &Wrapper{
28 repo: r,
29 blockstore: bs,
30 }, nil
31}
32
33// OpenWrapper opens an existing repository from CAR data with the provided blockstore
34func OpenWrapper(carData []byte, signingKey interface{}, bs blockstore.Blockstore) (*Wrapper, error) {
35 r, err := repo.ReadRepoFromCar(context.Background(), bytes.NewReader(carData))
36 if err != nil {
37 return nil, fmt.Errorf("failed to read repo from CAR: %w", err)
38 }
39
40 return &Wrapper{
41 repo: r,
42 blockstore: bs,
43 }, nil
44}
45
46// CreateRecord adds a new record to the repository
47func (w *Wrapper) CreateRecord(collection string, recordKey string, record cbg.CBORMarshaler) (cid.Cid, string, error) {
48 // The repo.CreateRecord generates its own key, so we'll use that
49 recordCID, rkey, err := w.repo.CreateRecord(context.Background(), collection, record)
50 if err != nil {
51 return cid.Undef, "", fmt.Errorf("failed to create record: %w", err)
52 }
53
54 // If a specific key was requested, we'd need to use PutRecord instead
55 if recordKey != "" {
56 // Use PutRecord for specific keys
57 path := fmt.Sprintf("%s/%s", collection, recordKey)
58 recordCID, err = w.repo.PutRecord(context.Background(), path, record)
59 if err != nil {
60 return cid.Undef, "", fmt.Errorf("failed to put record with key: %w", err)
61 }
62 return recordCID, recordKey, nil
63 }
64
65 return recordCID, rkey, nil
66}
67
68// GetRecord retrieves a record from the repository
69func (w *Wrapper) GetRecord(collection string, recordKey string) (cid.Cid, []byte, error) {
70 path := fmt.Sprintf("%s/%s", collection, recordKey)
71
72 recordCID, rec, err := w.repo.GetRecord(context.Background(), path)
73 if err != nil {
74 return cid.Undef, nil, fmt.Errorf("failed to get record: %w", err)
75 }
76
77 // Encode record to CBOR
78 buf := new(bytes.Buffer)
79 if err := rec.(cbg.CBORMarshaler).MarshalCBOR(buf); err != nil {
80 return cid.Undef, nil, fmt.Errorf("failed to encode record: %w", err)
81 }
82
83 return recordCID, buf.Bytes(), nil
84}
85
86// UpdateRecord updates an existing record in the repository
87func (w *Wrapper) UpdateRecord(collection string, recordKey string, record cbg.CBORMarshaler) (cid.Cid, error) {
88 path := fmt.Sprintf("%s/%s", collection, recordKey)
89
90 // Check if record exists
91 _, _, err := w.repo.GetRecord(context.Background(), path)
92 if err != nil {
93 return cid.Undef, fmt.Errorf("record not found: %w", err)
94 }
95
96 // Update the record
97 recordCID, err := w.repo.UpdateRecord(context.Background(), path, record)
98 if err != nil {
99 return cid.Undef, fmt.Errorf("failed to update record: %w", err)
100 }
101
102 return recordCID, nil
103}
104
105// DeleteRecord removes a record from the repository
106func (w *Wrapper) DeleteRecord(collection string, recordKey string) error {
107 path := fmt.Sprintf("%s/%s", collection, recordKey)
108
109 if err := w.repo.DeleteRecord(context.Background(), path); err != nil {
110 return fmt.Errorf("failed to delete record: %w", err)
111 }
112
113 return nil
114}
115
116// ListRecords returns all records in a collection
117func (w *Wrapper) ListRecords(collection string) ([]RecordInfo, error) {
118 var records []RecordInfo
119
120 err := w.repo.ForEach(context.Background(), collection, func(k string, v cid.Cid) error {
121 // Skip if not in the requested collection
122 if len(k) <= len(collection)+1 || k[:len(collection)] != collection || k[len(collection)] != '/' {
123 return nil
124 }
125
126 recordKey := k[len(collection)+1:]
127 records = append(records, RecordInfo{
128 Collection: collection,
129 RecordKey: recordKey,
130 CID: v,
131 })
132
133 return nil
134 })
135
136 if err != nil {
137 return nil, fmt.Errorf("failed to list records: %w", err)
138 }
139
140 return records, nil
141}
142
143// Commit creates a new signed commit
144func (w *Wrapper) Commit(did string, signingKey interface{}) (*repo.SignedCommit, error) {
145 // The commit function expects a signing function with context
146 signingFunc := func(ctx context.Context, did string, data []byte) ([]byte, error) {
147 // TODO: Implement proper signing based on signingKey type
148 return []byte("mock-signature"), nil
149 }
150
151 _, _, err := w.repo.Commit(context.Background(), signingFunc)
152 if err != nil {
153 return nil, fmt.Errorf("failed to commit: %w", err)
154 }
155
156 // Return the signed commit from the repo
157 sc := w.repo.SignedCommit()
158
159 return &sc, nil
160}
161
162// GetHeadCID returns the CID of the current repository head
163func (w *Wrapper) GetHeadCID() (cid.Cid, error) {
164 // TODO: Implement this properly
165 // The repo package doesn't expose a direct way to get the head CID
166 return cid.Undef, fmt.Errorf("not implemented")
167}
168
169// Export exports the repository as a CAR file
170func (w *Wrapper) Export() ([]byte, error) {
171 // TODO: Implement proper CAR export using Indigo's carstore functionality
172 // For now, return a placeholder
173 return nil, fmt.Errorf("CAR export not yet implemented")
174}
175
176// GetMST returns the underlying Merkle Search Tree
177func (w *Wrapper) GetMST() (*mst.MerkleSearchTree, error) {
178 // TODO: Implement MST access
179 return nil, fmt.Errorf("not implemented")
180}
181
182// RecordInfo contains information about a record
183type RecordInfo struct {
184 Collection string
185 RecordKey string
186 CID cid.Cid
187}
188
189// DecodeRecord decodes CBOR data into a record structure
190func DecodeRecord(data []byte, v interface{}) error {
191 return cbornode.DecodeInto(data, v)
192}
193
194// EncodeRecord encodes a record structure into CBOR data
195func EncodeRecord(v cbg.CBORMarshaler) ([]byte, error) {
196 buf := new(bytes.Buffer)
197 if err := v.MarshalCBOR(buf); err != nil {
198 return nil, err
199 }
200 return buf.Bytes(), nil
201}