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}