A community based topic aggregation platform built on atproto
1package carstore 2 3import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 9 "github.com/bluesky-social/indigo/models" 10 "github.com/ipfs/go-cid" 11 "gorm.io/gorm" 12) 13 14// RepoStore combines CarStore with UserMapping to provide DID-based repository storage 15type RepoStore struct { 16 cs *CarStore 17 mapping *UserMapping 18} 19 20// NewRepoStore creates a new RepoStore instance 21func NewRepoStore(db *gorm.DB, carDirs []string) (*RepoStore, error) { 22 // Create carstore 23 cs, err := NewCarStore(db, carDirs) 24 if err != nil { 25 return nil, fmt.Errorf("creating carstore: %w", err) 26 } 27 28 // Create user mapping 29 mapping, err := NewUserMapping(db) 30 if err != nil { 31 return nil, fmt.Errorf("creating user mapping: %w", err) 32 } 33 34 return &RepoStore{ 35 cs: cs, 36 mapping: mapping, 37 }, nil 38} 39 40// ImportRepo imports a repository CAR file for a DID 41func (rs *RepoStore) ImportRepo(ctx context.Context, did string, carData io.Reader) (cid.Cid, error) { 42 uid, err := rs.mapping.GetOrCreateUID(ctx, did) 43 if err != nil { 44 return cid.Undef, fmt.Errorf("getting UID for DID %s: %w", did, err) 45 } 46 47 // Read all data from the reader 48 data, err := io.ReadAll(carData) 49 if err != nil { 50 return cid.Undef, fmt.Errorf("reading CAR data: %w", err) 51 } 52 53 return rs.cs.ImportSlice(ctx, uid, nil, data) 54} 55 56// ReadRepo reads a repository CAR file for a DID 57func (rs *RepoStore) ReadRepo(ctx context.Context, did string, sinceRev string) ([]byte, error) { 58 uid, err := rs.mapping.GetUID(did) 59 if err != nil { 60 return nil, fmt.Errorf("getting UID for DID %s: %w", did, err) 61 } 62 63 var buf bytes.Buffer 64 err = rs.cs.ReadUserCar(ctx, uid, sinceRev, false, &buf) 65 if err != nil { 66 return nil, fmt.Errorf("reading repo for DID %s: %w", did, err) 67 } 68 69 return buf.Bytes(), nil 70} 71 72// GetRepoHead gets the latest repository head CID for a DID 73func (rs *RepoStore) GetRepoHead(ctx context.Context, did string) (cid.Cid, error) { 74 uid, err := rs.mapping.GetUID(did) 75 if err != nil { 76 return cid.Undef, fmt.Errorf("getting UID for DID %s: %w", did, err) 77 } 78 79 return rs.cs.GetUserRepoHead(ctx, uid) 80} 81 82// CompactRepo performs garbage collection for a DID's repository 83func (rs *RepoStore) CompactRepo(ctx context.Context, did string) error { 84 uid, err := rs.mapping.GetUID(did) 85 if err != nil { 86 return fmt.Errorf("getting UID for DID %s: %w", did, err) 87 } 88 89 return rs.cs.CompactUserShards(ctx, uid, false) 90} 91 92// DeleteRepo removes all data for a DID's repository 93func (rs *RepoStore) DeleteRepo(ctx context.Context, did string) error { 94 uid, err := rs.mapping.GetUID(did) 95 if err != nil { 96 return fmt.Errorf("getting UID for DID %s: %w", did, err) 97 } 98 99 return rs.cs.WipeUserData(ctx, uid) 100} 101 102// HasRepo checks if a repository exists for a DID 103func (rs *RepoStore) HasRepo(ctx context.Context, did string) (bool, error) { 104 uid, err := rs.mapping.GetUID(did) 105 if err != nil { 106 // If no UID mapping exists, repo doesn't exist 107 return false, nil 108 } 109 110 // Try to get the repo head 111 head, err := rs.cs.GetUserRepoHead(ctx, uid) 112 if err != nil { 113 return false, nil 114 } 115 116 return head.Defined(), nil 117} 118 119// GetOrCreateUID gets or creates a UID for a DID 120func (rs *RepoStore) GetOrCreateUID(ctx context.Context, did string) (models.Uid, error) { 121 return rs.mapping.GetOrCreateUID(ctx, did) 122}