Files
omnex/control-plane/api/internal/sub/handler.go
T
Nuxgrid fb10d25ee2
ci-api / test (push) Successful in 22m30s
ci-web / test (push) Successful in 13m15s
chore: update
2026-08-01 23:33:11 +02:00

140 lines
3.8 KiB
Go

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"})
}