package sub import ( "crypto/rand" "log" "net/http" "strings" "github.com/gin-gonic/gin" "github.com/omnex/control-plane/api/internal/auth" "github.com/omnex/control-plane/api/internal/demos" ) // Handler gère les requêtes HTTP pour les codes de souscription. type Handler struct { store Store demos *demos.Service // optionnel : nil => pas de transfert démo automatique } // NewHandler crée un nouveau Handler. func NewHandler(store Store, demoSvc *demos.Service) *Handler { return &Handler{store: store, demos: demoSvc} } // createCodeRequest représente la requête pour créer un code. type createCodeRequest struct { Username string `json:"username" binding:"required,min=3,max=64,alphanum"` } type addCodeRequest struct { Code string `json:"code_verif" binding:"required,len=19"` } func GenerateCode() (string, error) { const length = 16 const groupSize = 4 b := make([]byte, length) if _, err := rand.Read(b); err != nil { return "", err } var sb strings.Builder for i := 0; i < length; i++ { if i > 0 && i%groupSize == 0 { sb.WriteByte('-') } sb.WriteByte(codeCharset[int(b[i])%len(codeCharset)]) } return sb.String(), nil } const codeCharset = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" // ListCodes retourne la liste de tous les codes de souscription. // Réservé aux administrateurs. func (h *Handler) ListCodes(c *gin.Context) { p := auth.PrincipalFrom(c) if p == nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "non authentifié"}) return } if p.Role != "admin" { c.JSON(http.StatusUnauthorized, gin.H{"error": "role non correcte"}) return } codes, err := h.store.ListCodes() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur serveur"}) return } c.JSON(http.StatusOK, gin.H{"items": codes}) } // CreateCodeForBuy crée un nouveau code de souscription pour un utilisateur. // Réservé aux administrateurs. func (h *Handler) CreateCodeForBuy(c *gin.Context) { // Vérification de l'authentification et du rôle sera faite par le middleware p := auth.PrincipalFrom(c) if p == nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "non authentifié"}) return } if p.Role != "admin" { c.JSON(http.StatusUnauthorized, gin.H{"error": "role non correcte"}) return } var req createCodeRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "requête invalide"}) return } code, err := GenerateCode() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur serveur"}) return } codeSub, err := h.store.CreateCodeForSub(req.Username, code) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur serveur"}) return } c.JSON(http.StatusCreated, gin.H{"code": code, "code_id": codeSub.ID, "username": codeSub.Username}) } func (h *Handler) AddCode(c *gin.Context) { p := auth.PrincipalFrom(c) if p == nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "non authentifié"}) return } var req addCodeRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "requête invalide"}) return } _, err := h.store.AddCodeForSub(p.Username, req.Code) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "code invalide"}) return } // Transfert de la démo active (si elle existe) vers la version payante : // n'expire plus + bascule stockage persistant en tâche de fond. Ne fait // jamais échouer la validation du code — le client est déjà premium // même si sa démo (optionnelle) pose problème. if h.demos != nil { if err := h.demos.TransferToPaid(p.Username); err != nil { log.Printf("Transfert démo vers payant échoué pour %s: %v", p.Username, err) } } c.JSON(http.StatusOK, gin.H{"success": "vous passez en abonnement premium"}) } // GetPremiumUser : GET /premium — liste des clients premium (nom, date de // saisie du code, date de fin d'abonnement) pour l'admin. func (h *Handler) GetPremiumUser(c *gin.Context) { p := auth.PrincipalFrom(c) if p == nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "non authentifié"}) return } if p.Role != "admin" { c.JSON(http.StatusForbidden, gin.H{"error": "vous n'avez pas les droits"}) return } items, err := h.store.GetUserPremium() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur serveur"}) return } c.JSON(http.StatusOK, gin.H{"items": items}) }