package sub import ( "errors" "sync" "time" "github.com/google/uuid" "github.com/omnex/control-plane/api/internal/auth" "gorm.io/gorm" ) // Store définit les opérations de persistance pour les codes de souscription. type Store interface { // CreateCodeForSub crée un nouveau code pour un utilisateur. CreateCodeForSub(username, code string) (CodeBuySub, error) // GetCodeForSub récupère un code pour un utilisateur. GetCodeForSub(username, code string) (CodeBuySub, error) // ListCodes liste tous les codes. ListCodes() ([]CodeBuySub, error) AddCodeForSub(username, code string) (auth.User, error) } // GormStore implémente Store avec PostgreSQL via GORM. type GormStore struct { db *gorm.DB } // NewGormStore crée un nouveau GormStore. func NewGormStore(db *gorm.DB) *GormStore { return &GormStore{db: db} } // CreateCodeForSub crée un nouveau code pour un utilisateur. func (s *GormStore) CreateCodeForSub(username, code string) (CodeBuySub, error) { c := CodeBuySub{ ID: uuid.NewString(), Username: auth.NormalizeUsername(username), CodeBuy: code, } if err := s.db.Create(&c).Error; err != nil { return CodeBuySub{}, err } // S'assurer que CreatedAt est bien rempli if c.CreatedAt.IsZero() { c.CreatedAt = time.Now().UTC() if err := s.db.Save(&c).Error; err != nil { return CodeBuySub{}, err } } return c, nil } // GetCodeForSub récupère un code pour un utilisateur. func (s *GormStore) GetCodeForSub(username, code string) (CodeBuySub, error) { var c CodeBuySub if err := s.db.Where("username = ? AND code_buy = ?", username, code).First(&c).Error; err != nil { return CodeBuySub{}, err } return c, nil } func (s *GormStore) AddCodeForSub(username, code string) (auth.User, error) { var c CodeBuySub err := s.db.Where("username = ? AND code_buy = ?", username, code).First(&c).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return auth.User{}, errors.New("code invalide") } return auth.User{}, err } var u auth.User if err := s.db.Where("username = ?", auth.NormalizeUsername(username)).First(&u).Error; err != nil { return auth.User{}, err } u.TypeAbo = "premium" u.ExpiredAt = time.Now().UTC().AddDate(0, 0, 30) if err := s.db.Model(&u).Updates(map[string]interface{}{ "type_abo": u.TypeAbo, "expired_at": u.ExpiredAt, }).Error; err != nil { return auth.User{}, err } if err := s.db.Delete(&c).Error; err != nil { return auth.User{}, err } return u, nil } // ListCodes liste tous les codes, triés par date de création. func (s *GormStore) ListCodes() ([]CodeBuySub, error) { var codes []CodeBuySub // Essayer avec tri par date, sinon sans tri pour compatibilité if err := s.db.Order("created_at desc").Find(&codes).Error; err != nil { // Si colonne manquante, on liste sans tri if orderErr := s.db.Find(&codes).Error; orderErr != nil { return nil, orderErr } } return codes, nil } // MemStore implémente Store en mémoire (pour dev/tests). type MemStore struct { mu sync.RWMutex items map[string]CodeBuySub byCode map[string]CodeBuySub } // NewMemStore crée un nouveau MemStore. func NewMemStore() *MemStore { return &MemStore{ items: make(map[string]CodeBuySub), byCode: make(map[string]CodeBuySub), } } // CreateCodeForSub crée un nouveau code pour un utilisateur. func (m *MemStore) CreateCodeForSub(username, code string) (CodeBuySub, error) { m.mu.Lock() defer m.mu.Unlock() c := CodeBuySub{ ID: uuid.NewString(), Username: username, CodeBuy: code, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(), } m.items[c.ID] = c m.byCode[code] = c return c, nil } // GetCodeForSub récupère un code pour un utilisateur. func (m *MemStore) GetCodeForSub(username, code string) (CodeBuySub, error) { m.mu.RLock() defer m.mu.RUnlock() c, ok := m.byCode[code] if !ok { return CodeBuySub{}, nil } return c, nil } // ListCodes liste tous les codes. func (m *MemStore) ListCodes() ([]CodeBuySub, error) { m.mu.RLock() defer m.mu.RUnlock() out := make([]CodeBuySub, 0, len(m.items)) for _, c := range m.items { out = append(out, c) } return out, nil } func (m *MemStore) AddCodeForSub(username, code string) (auth.User, error) { m.mu.Lock() defer m.mu.Unlock() c, ok := m.byCode[code] if !ok || c.Username != username { return auth.User{}, errors.New("code invalide") } delete(m.byCode, code) delete(m.items, c.ID) // Le MemStore ne connaît pas les utilisateurs (pas de référence à memUsers ici) : // il ne peut donc pas mettre à jour TypeAbo lui-même. Cf. point ci-dessous. return auth.User{Username: username, TypeAbo: "premium"}, nil }