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}