chore: build
ci-web / test (push) Canceled after 0s
ci-api / test (push) Canceled after 24s

This commit is contained in:
Xor290
2026-08-22 21:58:39 +02:00
parent f407bc3dd0
commit 0f28d4757e
9 changed files with 164 additions and 11 deletions
@@ -77,6 +77,7 @@ func New(d Deps) *gin.Engine {
admin.GET("/profile/alerts", d.ProfileH.GetAlerts)
admin.POST("/profile/alerts", d.ProfileH.SetAlerts)
admin.POST("/profile/alerts/test", d.ProfileH.TestAlerts)
admin.GET("/premium", d.SubH.GetPremiumUser)
if d.DemosH != nil {
admin.POST("/demos", d.DemosH.Create)
admin.GET("/demos", d.DemosH.List)
+21
View File
@@ -137,3 +137,24 @@ func (h *Handler) AddCode(c *gin.Context) {
}
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})
}
+11
View File
@@ -15,3 +15,14 @@ type CodeBuySub struct {
// TableName définit explicitement le nom de la table.
func (CodeBuySub) TableName() string { return "code_buy_subs" }
// PremiumUser : client actuellement en abonnement premium, tel qu'affiché
// à l'admin sur la page des codes. ActivatedAt (date de saisie du code)
// n'est pas stockée telle quelle — le code est supprimé une fois consommé
// (voir Store.AddCodeForSub) — mais l'abonnement dure toujours exactement
// PremiumDuration, donc on la déduit de ExpiredAt (voir GormStore).
type PremiumUser struct {
Username string `json:"username"`
ActivatedAt time.Time `json:"activated_at"`
ExpiredAt time.Time `json:"expired_at"`
}
+34 -1
View File
@@ -10,6 +10,11 @@ import (
"gorm.io/gorm"
)
// PremiumDuration : durée d'un abonnement premium débloqué par un code
// (30 jours). Sert à la fois à fixer ExpiredAt lors de l'activation et à
// retrouver la date de saisie du code (ExpiredAt - PremiumDuration).
const PremiumDuration = 30 * 24 * time.Hour
// 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.
@@ -19,6 +24,8 @@ type Store interface {
// ListCodes liste tous les codes.
ListCodes() ([]CodeBuySub, error)
AddCodeForSub(username, code string) (auth.User, error)
// GetUserPremium liste les clients actuellement en abonnement premium.
GetUserPremium() ([]PremiumUser, error)
}
// GormStore implémente Store avec PostgreSQL via GORM.
@@ -78,7 +85,7 @@ func (s *GormStore) AddCodeForSub(username, code string) (auth.User, error) {
}
u.TypeAbo = "premium"
u.ExpiredAt = time.Now().UTC().AddDate(0, 0, 30)
u.ExpiredAt = time.Now().UTC().Add(PremiumDuration)
if err := s.db.Model(&u).Updates(map[string]interface{}{
"type_abo": u.TypeAbo,
"expired_at": u.ExpiredAt,
@@ -106,6 +113,25 @@ func (s *GormStore) ListCodes() ([]CodeBuySub, error) {
return codes, nil
}
// GetUserPremium liste les clients dont l'abonnement est premium, avec la
// date de saisie du code (déduite : ExpiredAt - PremiumDuration) et la date
// de fin d'abonnement (ExpiredAt). Trié par échéance la plus proche.
func (s *GormStore) GetUserPremium() ([]PremiumUser, error) {
var users []auth.User
if err := s.db.Where("type_abo = ?", "premium").Order("expired_at asc").Find(&users).Error; err != nil {
return nil, err
}
out := make([]PremiumUser, 0, len(users))
for _, u := range users {
out = append(out, PremiumUser{
Username: u.Username,
ActivatedAt: u.ExpiredAt.Add(-PremiumDuration),
ExpiredAt: u.ExpiredAt,
})
}
return out, nil
}
// MemStore implémente Store en mémoire (pour dev/tests).
type MemStore struct {
mu sync.RWMutex
@@ -174,3 +200,10 @@ func (m *MemStore) AddCodeForSub(username, code string) (auth.User, error) {
// il ne peut donc pas mettre à jour TypeAbo lui-même. Cf. point ci-dessous.
return auth.User{Username: username, TypeAbo: "premium"}, nil
}
// GetUserPremium : le MemStore (dev/tests) ne connaît pas la table des
// utilisateurs (voir AddCodeForSub) — il ne peut donc pas lister les clients
// premium. Renvoie une liste vide plutôt qu'une erreur.
func (m *MemStore) GetUserPremium() ([]PremiumUser, error) {
return []PremiumUser{}, nil
}