A community based topic aggregation platform built on atproto
1package carstore 2 3import ( 4 "context" 5 "fmt" 6 "sync" 7 8 "github.com/bluesky-social/indigo/models" 9 "gorm.io/gorm" 10) 11 12// UserMapping manages the mapping between DIDs and numeric UIDs required by Indigo's carstore 13type UserMapping struct { 14 db *gorm.DB 15 mu sync.RWMutex 16 didToUID map[string]models.Uid 17 uidToDID map[models.Uid]string 18 nextUID models.Uid 19} 20 21// UserMap represents the database model for DID to UID mapping 22type UserMap struct { 23 UID models.Uid `gorm:"primaryKey;autoIncrement"` 24 DID string `gorm:"column:did;uniqueIndex;not null"` 25 CreatedAt int64 26 UpdatedAt int64 27} 28 29// NewUserMapping creates a new UserMapping instance 30func NewUserMapping(db *gorm.DB) (*UserMapping, error) { 31 // Auto-migrate the user mapping table 32 if err := db.AutoMigrate(&UserMap{}); err != nil { 33 return nil, fmt.Errorf("migrating user mapping table: %w", err) 34 } 35 36 um := &UserMapping{ 37 db: db, 38 didToUID: make(map[string]models.Uid), 39 uidToDID: make(map[models.Uid]string), 40 nextUID: 1, 41 } 42 43 // Load existing mappings 44 if err := um.loadMappings(); err != nil { 45 return nil, fmt.Errorf("loading user mappings: %w", err) 46 } 47 48 return um, nil 49} 50 51// loadMappings loads all existing DID to UID mappings from the database 52func (um *UserMapping) loadMappings() error { 53 var mappings []UserMap 54 if err := um.db.Find(&mappings).Error; err != nil { 55 return fmt.Errorf("querying user mappings: %w", err) 56 } 57 58 um.mu.Lock() 59 defer um.mu.Unlock() 60 61 for _, m := range mappings { 62 um.didToUID[m.DID] = m.UID 63 um.uidToDID[m.UID] = m.DID 64 if m.UID >= um.nextUID { 65 um.nextUID = m.UID + 1 66 } 67 } 68 69 return nil 70} 71 72// GetOrCreateUID gets or creates a UID for a given DID 73func (um *UserMapping) GetOrCreateUID(ctx context.Context, did string) (models.Uid, error) { 74 um.mu.RLock() 75 if uid, exists := um.didToUID[did]; exists { 76 um.mu.RUnlock() 77 return uid, nil 78 } 79 um.mu.RUnlock() 80 81 // Need to create a new mapping 82 um.mu.Lock() 83 defer um.mu.Unlock() 84 85 // Double-check in case another goroutine created it 86 if uid, exists := um.didToUID[did]; exists { 87 return uid, nil 88 } 89 90 // Create new mapping 91 userMap := &UserMap{ 92 DID: did, 93 } 94 95 if err := um.db.Create(userMap).Error; err != nil { 96 return 0, fmt.Errorf("creating user mapping for DID %s: %w", did, err) 97 } 98 99 um.didToUID[did] = userMap.UID 100 um.uidToDID[userMap.UID] = did 101 102 return userMap.UID, nil 103} 104 105// GetUID returns the UID for a DID, or an error if not found 106func (um *UserMapping) GetUID(did string) (models.Uid, error) { 107 um.mu.RLock() 108 defer um.mu.RUnlock() 109 110 uid, exists := um.didToUID[did] 111 if !exists { 112 return 0, fmt.Errorf("UID not found for DID: %s", did) 113 } 114 return uid, nil 115} 116 117// GetDID returns the DID for a UID, or an error if not found 118func (um *UserMapping) GetDID(uid models.Uid) (string, error) { 119 um.mu.RLock() 120 defer um.mu.RUnlock() 121 122 did, exists := um.uidToDID[uid] 123 if !exists { 124 return "", fmt.Errorf("DID not found for UID: %d", uid) 125 } 126 return did, nil 127}