fix: multiple error
This commit is contained in:
@@ -118,12 +118,18 @@ func main() {
|
||||
}
|
||||
|
||||
// Traefik partagé + WAF Coraza : installé une fois pour toutes les démos
|
||||
// (idempotent). Non-fatal : si ça échoue, l'API démarre quand même, mais
|
||||
// les démos provisionnées ensuite ne seront ni routées ni protégées tant
|
||||
// que ce n'est pas corrigé.
|
||||
if err := helmProv.EnsureSharedInfra(); err != nil {
|
||||
log.Printf("ATTENTION: infra partagée (Traefik/WAF) indisponible: %v", err)
|
||||
}
|
||||
// (idempotent). En arrière-plan et non-fatal : "helm upgrade --install
|
||||
// --wait --timeout 5m" peut bloquer plusieurs minutes si le cluster est
|
||||
// lent/injoignable, ce qui retardait le démarrage du serveur HTTP au
|
||||
// point de faire échouer le healthcheck Docker avant même que l'API
|
||||
// n'écoute sur le port. Ici ça ne retarde plus rien : les démos
|
||||
// provisionnées avant la fin de cet appel ne seront juste ni routées ni
|
||||
// protégées tant qu'il n'a pas fini.
|
||||
go func() {
|
||||
if err := helmProv.EnsureSharedInfra(); err != nil {
|
||||
log.Printf("ATTENTION: infra partagée (Traefik/WAF) indisponible: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Service de démos avec le provisioner Helm
|
||||
demoSvc := demos.NewService(demoStore, demoPool, helmProv, demos.Config{
|
||||
|
||||
@@ -24,11 +24,14 @@ type createRequest struct {
|
||||
}
|
||||
|
||||
type DetailDemoUserRequest struct {
|
||||
Username string `json:"username" binding:"required,min=3,max=64,alphanum"`
|
||||
// Namespace identifie la démo de façon unique. Le username ne suffit
|
||||
// pas : un même client peut avoir plusieurs démos (historique, relances),
|
||||
// et interroger par username renvoyait toujours la même (la plus
|
||||
// ancienne), jamais celle réellement sélectionnée dans l'UI.
|
||||
Namespace string `json:"namespace" binding:"required,min=3,max=63"`
|
||||
}
|
||||
|
||||
type DemoDetailsResponse struct {
|
||||
Username string `json:"username"`
|
||||
Namespace string `json:"namespace"`
|
||||
State ResourceState `json:"state"`
|
||||
}
|
||||
@@ -42,11 +45,14 @@ func (h *Handler) Create(c *gin.Context) {
|
||||
}
|
||||
d, err := h.svc.Create(req.LeadID, req.Username)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrCapacityReached) {
|
||||
switch {
|
||||
case errors.Is(err, ErrCapacityReached):
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "capacité maximale de démos atteinte"})
|
||||
return
|
||||
case errors.Is(err, ErrUserHasActiveDemo):
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "ce client a déjà une démo active"})
|
||||
default:
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur serveur"})
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur serveur"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusAccepted, d)
|
||||
@@ -62,6 +68,21 @@ func (h *Handler) List(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"items": items})
|
||||
}
|
||||
|
||||
// ListMine : GET /demos/mine — démo(s) rattachée(s) au client authentifié.
|
||||
func (h *Handler) ListMine(c *gin.Context) {
|
||||
p := auth.PrincipalFrom(c)
|
||||
if p == nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "non authentifié"})
|
||||
return
|
||||
}
|
||||
items, err := h.svc.ListForUser(p.Username)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur serveur"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"items": items})
|
||||
}
|
||||
|
||||
func (h *Handler) ListDetails(c *gin.Context) {
|
||||
p := auth.PrincipalFrom(c)
|
||||
if p == nil {
|
||||
@@ -74,26 +95,19 @@ func (h *Handler) ListDetails(c *gin.Context) {
|
||||
}
|
||||
|
||||
var req DetailDemoUserRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil || req.Username == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "username requis"})
|
||||
if err := c.ShouldBindJSON(&req); err != nil || req.Namespace == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "namespace requis"})
|
||||
return
|
||||
}
|
||||
|
||||
demo, err := h.svc.store.GetDemoByUsername(req.Username)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "démo introuvable"})
|
||||
return
|
||||
}
|
||||
|
||||
state, err := h.helm.GetResourceState(c.Request.Context(), demo.Namespace)
|
||||
state, err := h.helm.GetResourceState(c.Request.Context(), req.Namespace)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "impossible de récupérer l'état des ressources"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, DemoDetailsResponse{
|
||||
Username: req.Username,
|
||||
Namespace: demo.Namespace,
|
||||
Namespace: req.Namespace,
|
||||
State: state,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,9 +11,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCapacityReached = errors.New("capacité maximale de démos atteinte")
|
||||
ErrNotFound = errors.New("démo introuvable")
|
||||
ErrNotExtendable = errors.New("démo non prolongeable dans son état actuel")
|
||||
ErrCapacityReached = errors.New("capacité maximale de démos atteinte")
|
||||
ErrNotFound = errors.New("démo introuvable")
|
||||
ErrNotExtendable = errors.New("démo non prolongeable dans son état actuel")
|
||||
ErrUserHasActiveDemo = errors.New("ce client a déjà une démo active")
|
||||
)
|
||||
|
||||
// Config du service (injectable pour les tests).
|
||||
@@ -50,8 +51,22 @@ func NewService(store Store, pool Pool, prov Provisioner, cfg Config) *Service {
|
||||
}
|
||||
|
||||
// Create réserve la capacité + le pool, persiste la démo et déclenche le worker.
|
||||
// username (optionnel) rattache la démo à un client existant.
|
||||
// username (optionnel) rattache la démo à un client existant. Un client n'a
|
||||
// droit qu'à une seule démo active à la fois.
|
||||
func (s *Service) Create(leadID, username string) (Demo, error) {
|
||||
normalizedUsername := auth.NormalizeUsername(username)
|
||||
if normalizedUsername != "" {
|
||||
existing, err := s.store.ListByUsername(normalizedUsername)
|
||||
if err != nil {
|
||||
return Demo{}, err
|
||||
}
|
||||
for _, d := range existing {
|
||||
if d.Status.Active() {
|
||||
return Demo{}, ErrUserHasActiveDemo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
active, err := s.store.CountActive()
|
||||
if err != nil {
|
||||
return Demo{}, err
|
||||
@@ -63,7 +78,7 @@ func (s *Service) Create(leadID, username string) (Demo, error) {
|
||||
id := uuid.NewString()
|
||||
demo := Demo{
|
||||
ID: id,
|
||||
Username: auth.NormalizeUsername(username),
|
||||
Username: normalizedUsername,
|
||||
LeadID: leadID,
|
||||
Status: StatusPending,
|
||||
Namespace: "demo-" + shortID(id),
|
||||
|
||||
@@ -35,13 +35,13 @@ func (h *Handler) UpdateUsernameById(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
id, ok := c.MustGet("id").(string)
|
||||
if !ok {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "id invalide"})
|
||||
p := auth.PrincipalFrom(c)
|
||||
if p == nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "non authentifié"})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.store.UpdateUsername(id, u.Username)
|
||||
user, err := h.store.UpdateUsername(p.UserID, u.Username)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "erreur serveur"})
|
||||
return
|
||||
@@ -57,9 +57,9 @@ func (h *Handler) UpdatePasswordById(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
id, ok := c.MustGet("id").(string)
|
||||
if !ok {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "id invalide"})
|
||||
p := auth.PrincipalFrom(c)
|
||||
if p == nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "non authentifié"})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ func (h *Handler) UpdatePasswordById(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.store.UpdatePassword(id, hash)
|
||||
user, err := h.store.UpdatePassword(p.UserID, hash)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "requête invalide"})
|
||||
return
|
||||
@@ -79,12 +79,12 @@ func (h *Handler) UpdatePasswordById(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (h *Handler) GetTelegramById(c *gin.Context) {
|
||||
id, ok := c.MustGet("id").(string)
|
||||
if !ok {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "id invalide"})
|
||||
p := auth.PrincipalFrom(c)
|
||||
if p == nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "non authentifié"})
|
||||
return
|
||||
}
|
||||
user, err := h.store.GetTelegram(id)
|
||||
user, err := h.store.GetTelegram(p.UserID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "requête invalide"})
|
||||
return
|
||||
@@ -98,12 +98,12 @@ func (h *Handler) SetTelegramById(c *gin.Context) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "requête invalide"})
|
||||
return
|
||||
}
|
||||
id, ok := c.MustGet("id").(string)
|
||||
if !ok {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "id invalide"})
|
||||
p := auth.PrincipalFrom(c)
|
||||
if p == nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "non authentifié"})
|
||||
return
|
||||
}
|
||||
user, err := h.store.SetTelegram(id, t.Telegram)
|
||||
user, err := h.store.SetTelegram(p.UserID, t.Telegram)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "erreur serveur"})
|
||||
return
|
||||
|
||||
@@ -63,6 +63,9 @@ func New(d Deps) *gin.Engine {
|
||||
client.GET("/leads", d.LeadsH.List)
|
||||
client.PATCH("/leads/:id/status", d.LeadsH.SetStatus)
|
||||
client.POST("/subscription", d.SubH.AddCode)
|
||||
if d.DemosH != nil {
|
||||
client.GET("/demos/mine", d.DemosH.ListMine)
|
||||
}
|
||||
}
|
||||
// Espace admin : provisioning des démos (admin uniquement).
|
||||
admin := authed.Group("")
|
||||
|
||||
Reference in New Issue
Block a user