484 lines
18 KiB
Go
484 lines
18 KiB
Go
package db
|
||
|
||
import (
|
||
"database/sql"
|
||
"encoding/json"
|
||
"fmt"
|
||
"log"
|
||
"os"
|
||
"time"
|
||
|
||
_ "github.com/lib/pq"
|
||
)
|
||
|
||
// ============================================
|
||
// STRUCTURES
|
||
// ============================================
|
||
|
||
// Database encapsule la connexion à la base de données
|
||
type Database struct {
|
||
*sql.DB
|
||
}
|
||
|
||
// DB est l'instance globale de la base de données
|
||
var DB *Database
|
||
|
||
// ============================================
|
||
// INITIALISATION DE LA BASE DE DONNÉES
|
||
// ============================================
|
||
|
||
// InitDB initialise la connexion à PostgreSQL et crée les tables
|
||
func InitDB() *Database {
|
||
// Récupérer les paramètres de connexion
|
||
host := getEnv("DB_HOST", "localhost")
|
||
port := getEnv("DB_PORT", "5432")
|
||
user := getEnv("DB_USER", "postgres")
|
||
password := getEnv("DB_PASSWORD", "postgres")
|
||
dbname := getEnv("DB_NAME", "gestion_db")
|
||
sslmode := getEnv("DB_SSLMODE", "disable")
|
||
|
||
// Construire la chaîne de connexion
|
||
connStr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
|
||
host, port, user, password, dbname, sslmode)
|
||
|
||
// Ouvrir la connexion
|
||
db, err := sql.Open("postgres", connStr)
|
||
if err != nil {
|
||
log.Fatalf("❌ Erreur lors de l'ouverture de la base de données: %v", err)
|
||
}
|
||
|
||
// Configuration du pool de connexions
|
||
db.SetMaxOpenConns(50)
|
||
db.SetMaxIdleConns(10)
|
||
db.SetConnMaxLifetime(5 * time.Minute)
|
||
|
||
// Tester la connexion
|
||
if err = db.Ping(); err != nil {
|
||
log.Fatalf("❌ Erreur de connexion à la base de données: %v", err)
|
||
}
|
||
|
||
log.Println("✅ Connexion à PostgreSQL établie avec succès")
|
||
|
||
// Créer l'instance Database
|
||
database := &Database{db}
|
||
|
||
// Assigner à la variable globale
|
||
DB = database
|
||
|
||
// Créer les tables
|
||
if err = database.createTables(); err != nil {
|
||
log.Fatalf("❌ Erreur lors de la création des tables: %v", err)
|
||
}
|
||
|
||
log.Println("✅ Tables créées avec succès")
|
||
|
||
// Migration: ajouter colonne must_change_password si elle n'existe pas (DEFAULT FALSE pour les clients existants)
|
||
if _, err = database.Exec(`ALTER TABLE clients ADD COLUMN IF NOT EXISTS must_change_password BOOLEAN NOT NULL DEFAULT FALSE`); err != nil {
|
||
log.Fatalf("❌ Erreur migration must_change_password: %v", err)
|
||
}
|
||
|
||
// Migration: ajouter colonne push_token pour les notifications push (clients)
|
||
if _, err = database.Exec(`ALTER TABLE clients ADD COLUMN IF NOT EXISTS push_token TEXT`); err != nil {
|
||
log.Fatalf("❌ Erreur migration push_token: %v", err)
|
||
}
|
||
|
||
// Migration: ajouter colonne push_token pour les notifications push (livreurs/users)
|
||
if _, err = database.Exec(`ALTER TABLE users ADD COLUMN IF NOT EXISTS push_token TEXT`); err != nil {
|
||
log.Fatalf("❌ Erreur migration push_token users: %v", err)
|
||
}
|
||
|
||
// Migration: proposition de modification d'adresse par admin/cabine
|
||
if _, err = database.Exec(`ALTER TABLE commandes ADD COLUMN IF NOT EXISTS proposed_address TEXT`); err != nil {
|
||
log.Fatalf("❌ Erreur migration proposed_address: %v", err)
|
||
}
|
||
if _, err = database.Exec(`ALTER TABLE commandes ADD COLUMN IF NOT EXISTS address_proposal_status VARCHAR(20) NOT NULL DEFAULT 'none'`); err != nil {
|
||
log.Fatalf("❌ Erreur migration address_proposal_status: %v", err)
|
||
}
|
||
|
||
// Migration: ajouter colonne unit pour l'unité de mesure des produits (kg, g, bag, l, cl, pcs, u)
|
||
if _, err = database.Exec(`ALTER TABLE products ADD COLUMN IF NOT EXISTS unit VARCHAR(10) NOT NULL DEFAULT 'u'`); err != nil {
|
||
log.Fatalf("❌ Erreur migration unit products: %v", err)
|
||
}
|
||
|
||
// Migration: baskets.quantity INTEGER → NUMERIC(10,3) pour supporter les quantités fractionnaires (ex: 0.5g)
|
||
if _, err = database.Exec(`
|
||
DO $$
|
||
BEGIN
|
||
IF EXISTS (
|
||
SELECT 1 FROM information_schema.columns
|
||
WHERE table_name = 'baskets' AND column_name = 'quantity'
|
||
AND data_type = 'integer'
|
||
) THEN
|
||
ALTER TABLE baskets ALTER COLUMN quantity TYPE NUMERIC(10,3) USING quantity::NUMERIC(10,3);
|
||
END IF;
|
||
END
|
||
$$;
|
||
`); err != nil {
|
||
log.Fatalf("❌ Erreur migration baskets.quantity: %v", err)
|
||
}
|
||
|
||
// Migration: command_items.quantite INTEGER → NUMERIC(10,3) pour supporter les quantités fractionnaires
|
||
if _, err = database.Exec(`
|
||
DO $$
|
||
BEGIN
|
||
IF EXISTS (
|
||
SELECT 1 FROM information_schema.columns
|
||
WHERE table_name = 'command_items' AND column_name = 'quantite'
|
||
AND data_type = 'integer'
|
||
) THEN
|
||
ALTER TABLE command_items ALTER COLUMN quantite TYPE NUMERIC(10,3) USING quantite::NUMERIC(10,3);
|
||
END IF;
|
||
END
|
||
$$;
|
||
`); err != nil {
|
||
log.Fatalf("❌ Erreur migration command_items.quantite: %v", err)
|
||
}
|
||
|
||
// Migration: ajouter colonne color pour les catégories
|
||
if _, err = database.Exec(`ALTER TABLE categories ADD COLUMN IF NOT EXISTS color VARCHAR(7) NOT NULL DEFAULT '#7c3aed'`); err != nil {
|
||
log.Fatalf("❌ Erreur migration categories.color: %v", err)
|
||
}
|
||
|
||
// Migration: ajouter colonne is_coming_soon pour les catégories
|
||
if _, err = database.Exec(`ALTER TABLE categories ADD COLUMN IF NOT EXISTS is_coming_soon BOOLEAN NOT NULL DEFAULT FALSE`); err != nil {
|
||
log.Fatalf("❌ Erreur migration categories.is_coming_soon: %v", err)
|
||
}
|
||
|
||
// Migration: table paramètres globaux de l'application
|
||
if _, err = database.Exec(`CREATE TABLE IF NOT EXISTS app_settings (
|
||
key VARCHAR(100) PRIMARY KEY,
|
||
value TEXT NOT NULL
|
||
)`); err != nil {
|
||
log.Fatalf("❌ Erreur migration app_settings: %v", err)
|
||
}
|
||
|
||
// Migration: ajouter colonne message pour les alertes police (type d'alerte)
|
||
if _, err = database.Exec(`ALTER TABLE alerte_policy ADD COLUMN IF NOT EXISTS message VARCHAR(200) NOT NULL DEFAULT ''`); err != nil {
|
||
log.Fatalf("❌ Erreur migration alerte_policy.message: %v", err)
|
||
}
|
||
|
||
// Migration: montant de parrainage utilisé pour la commande
|
||
if _, err = database.Exec(`ALTER TABLE commandes ADD COLUMN IF NOT EXISTS referral_used FLOAT NOT NULL DEFAULT 0`); err != nil {
|
||
log.Fatalf("❌ Erreur migration commandes.referral_used: %v", err)
|
||
}
|
||
|
||
// Migration: points extra pour tous les pools de points (stockage dynamique par clé)
|
||
if _, err = database.Exec(`ALTER TABLE clients ADD COLUMN IF NOT EXISTS points_extra JSONB NOT NULL DEFAULT '{}'::jsonb`); err != nil {
|
||
log.Fatalf("❌ Erreur migration clients.points_extra: %v", err)
|
||
}
|
||
|
||
// Migration: copier point/point_zipette → points_extra[pool.Key] selon les clés admin
|
||
{
|
||
var poolsJSON string
|
||
_ = database.QueryRow(`SELECT value FROM app_settings WHERE key = 'points_pools'`).Scan(&poolsJSON)
|
||
if poolsJSON != "" {
|
||
var pools []struct {
|
||
Key string `json:"key"`
|
||
}
|
||
if jsonErr := json.Unmarshal([]byte(poolsJSON), &pools); jsonErr == nil {
|
||
if len(pools) > 0 && pools[0].Key != "" {
|
||
k0 := pools[0].Key
|
||
if _, mErr := database.Exec(`UPDATE clients SET points_extra = points_extra || jsonb_build_object($1::text, point) WHERE point > 0 AND NOT (points_extra ? $1)`, k0); mErr != nil {
|
||
log.Printf("⚠️ Migration points_extra pool[0] (%s): %v", k0, mErr)
|
||
} else {
|
||
log.Printf("✅ Migration points_extra pool[0] key=%s", k0)
|
||
}
|
||
}
|
||
if len(pools) > 1 && pools[1].Key != "" {
|
||
k1 := pools[1].Key
|
||
if _, mErr := database.Exec(`UPDATE clients SET points_extra = points_extra || jsonb_build_object($1::text, point_zipette) WHERE point_zipette > 0 AND NOT (points_extra ? $1)`, k1); mErr != nil {
|
||
log.Printf("⚠️ Migration points_extra pool[1] (%s): %v", k1, mErr)
|
||
} else {
|
||
log.Printf("✅ Migration points_extra pool[1] key=%s", k1)
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
log.Printf("ℹ️ Migration points_extra: aucun pool configuré (app_settings vide)")
|
||
}
|
||
}
|
||
|
||
// Lancer le nettoyage périodique des tokens expirés
|
||
go database.cleanExpiredTokensPeriodically()
|
||
|
||
return database
|
||
}
|
||
|
||
// ============================================
|
||
// CRÉATION DES TABLES
|
||
// ============================================
|
||
|
||
// createTables crée toutes les tables nécessaires
|
||
func (db *Database) createTables() error {
|
||
queries := []string{
|
||
|
||
// ============================
|
||
// TABLE users
|
||
// ============================
|
||
`CREATE TABLE IF NOT EXISTS users (
|
||
id SERIAL PRIMARY KEY,
|
||
username VARCHAR(255) UNIQUE NOT NULL,
|
||
password TEXT NOT NULL,
|
||
role VARCHAR(50) NOT NULL DEFAULT 'user',
|
||
total NUMERIC(10,2) DEFAULT 0.0,
|
||
livraison NUMERIC(10,2) DEFAULT 0.0,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);`,
|
||
|
||
// ============================
|
||
// TABLE clients - ✅ AVEC COLONNES DE TRACKING DES ANNULATIONS
|
||
// ============================
|
||
`CREATE TABLE IF NOT EXISTS clients (
|
||
id SERIAL PRIMARY KEY,
|
||
username VARCHAR(255) UNIQUE NOT NULL,
|
||
password TEXT NOT NULL,
|
||
nom VARCHAR(100) NOT NULL,
|
||
prenom VARCHAR(100) NOT NULL,
|
||
telephone VARCHAR(20) NOT NULL UNIQUE,
|
||
command INTEGER DEFAULT 0,
|
||
point INTEGER DEFAULT 0,
|
||
point_zipette INTEGER DEFAULT 0,
|
||
amende NUMERIC(10,2) DEFAULT 0.0,
|
||
cancel_commande INTEGER DEFAULT 0,
|
||
cancellations_count INTEGER DEFAULT 0 NOT NULL,
|
||
last_penalty_reason TEXT DEFAULT NULL,
|
||
referral_balance NUMERIC(10,2) DEFAULT 0.0,
|
||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);`,
|
||
`ALTER TABLE clients ADD COLUMN IF NOT EXISTS referral_balance NUMERIC(10,2) DEFAULT 0.0;`,
|
||
|
||
// ============================
|
||
// TABLE jwt_tokens
|
||
// ============================
|
||
`CREATE TABLE IF NOT EXISTS jwt_tokens (
|
||
id SERIAL PRIMARY KEY,
|
||
user_id INTEGER NOT NULL,
|
||
user_type VARCHAR(20) NOT NULL CHECK (user_type IN ('client', 'admin', 'cabine', 'livreur')),
|
||
token TEXT NOT NULL,
|
||
date_save TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
date_fin TIMESTAMP NOT NULL,
|
||
CHECK (date_fin > date_save)
|
||
);`,
|
||
|
||
// ============================
|
||
// TABLE categories
|
||
// ============================
|
||
`CREATE TABLE IF NOT EXISTS categories (
|
||
id SERIAL PRIMARY KEY,
|
||
name VARCHAR(100) NOT NULL UNIQUE,
|
||
color VARCHAR(7) NOT NULL DEFAULT '#7c3aed',
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);`,
|
||
|
||
// ============================
|
||
// TABLE products
|
||
// ============================
|
||
`CREATE TABLE IF NOT EXISTS products (
|
||
id SERIAL PRIMARY KEY,
|
||
name VARCHAR(255) NOT NULL,
|
||
category VARCHAR(100) NOT NULL,
|
||
stock DECIMAL(10,2) NOT NULL DEFAULT 0,
|
||
unit VARCHAR(10) NOT NULL DEFAULT 'u',
|
||
description TEXT,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);`,
|
||
|
||
// ============================
|
||
// TABLE product_prices
|
||
// ============================
|
||
`CREATE TABLE IF NOT EXISTS product_prices (
|
||
id SERIAL PRIMARY KEY,
|
||
product_id INTEGER NOT NULL REFERENCES products(id) ON DELETE CASCADE,
|
||
quantity NUMERIC(10,3) NOT NULL,
|
||
price NUMERIC(10,2) NOT NULL,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
UNIQUE(product_id, quantity)
|
||
);`,
|
||
|
||
// ============================
|
||
// TABLE media
|
||
// ============================
|
||
`CREATE TABLE IF NOT EXISTS media (
|
||
id SERIAL PRIMARY KEY,
|
||
product_id INTEGER NOT NULL REFERENCES products(id) ON DELETE CASCADE,
|
||
url TEXT NOT NULL,
|
||
type VARCHAR(50) NOT NULL,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);`,
|
||
|
||
// ============================
|
||
// TABLE commandes
|
||
// ============================
|
||
`CREATE TABLE IF NOT EXISTS commandes (
|
||
id SERIAL PRIMARY KEY,
|
||
username VARCHAR(255) NOT NULL,
|
||
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
||
livreur_assign VARCHAR(50),
|
||
adresse TEXT DEFAULT 'Adresse non spécifiée',
|
||
total_prix NUMERIC(10,2) NOT NULL DEFAULT 0.0,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);`,
|
||
|
||
// ============================
|
||
// TABLE command_items
|
||
// ============================
|
||
`CREATE TABLE IF NOT EXISTS command_items (
|
||
id SERIAL PRIMARY KEY,
|
||
command_id INTEGER NOT NULL REFERENCES commandes(id) ON DELETE CASCADE,
|
||
produit VARCHAR(255) NOT NULL,
|
||
product_id INTEGER REFERENCES products(id) ON DELETE SET NULL,
|
||
quantite INTEGER NOT NULL,
|
||
prix NUMERIC(10,2) NOT NULL,
|
||
-- Nouvelles colonnes pour les infos client
|
||
client_username VARCHAR(255),
|
||
client_nom VARCHAR(100),
|
||
client_prenom VARCHAR(100),
|
||
client_telephone VARCHAR(20),
|
||
delivery_address TEXT,
|
||
status VARCHAR(50) DEFAULT 'pending',
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);`,
|
||
|
||
// ============================
|
||
// TABLE baskets
|
||
// ============================
|
||
`CREATE TABLE IF NOT EXISTS baskets (
|
||
id SERIAL PRIMARY KEY,
|
||
username VARCHAR(255) NOT NULL,
|
||
product_id INTEGER NOT NULL REFERENCES products(id) ON DELETE CASCADE,
|
||
quantity NUMERIC(10,3) NOT NULL,
|
||
price NUMERIC(10,2) NOT NULL,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);`,
|
||
|
||
// ============================
|
||
// TABLE command_logs
|
||
// ============================
|
||
`CREATE TABLE IF NOT EXISTS command_logs (
|
||
id SERIAL PRIMARY KEY,
|
||
command_id INTEGER NOT NULL REFERENCES commandes(id) ON DELETE CASCADE,
|
||
status VARCHAR(50) NOT NULL,
|
||
message TEXT NOT NULL,
|
||
author VARCHAR(255) NOT NULL,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);`,
|
||
|
||
// ============================
|
||
// TABLE delivery_issues
|
||
// ============================
|
||
`CREATE TABLE IF NOT EXISTS delivery_issues (
|
||
id SERIAL PRIMARY KEY,
|
||
command_id INTEGER NOT NULL REFERENCES commandes(id) ON DELETE CASCADE,
|
||
issue_type VARCHAR(50) NOT NULL,
|
||
description TEXT NOT NULL,
|
||
status VARCHAR(20) NOT NULL DEFAULT 'open',
|
||
reported_by VARCHAR(100) NOT NULL,
|
||
resolved_by VARCHAR(100),
|
||
resolution TEXT,
|
||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||
);`,
|
||
|
||
`CREATE TABLE IF NOT EXISTS alerte_policy (
|
||
id SERIAL PRIMARY KEY,
|
||
username VARCHAR(100) NOT NULL,
|
||
status VARCHAR(20) NOT NULL DEFAULT 'false',
|
||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||
);`,
|
||
|
||
`CREATE TABLE IF NOT EXISTS adresse_correction (
|
||
id SERIAL PRIMARY KEY,
|
||
invalid_address VARCHAR(255) NOT NULL UNIQUE,
|
||
correct_address VARCHAR(255) NOT NULL,
|
||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||
);`,
|
||
// ============================
|
||
// INDEXES - ✅ AJOUT D'INDEX POUR CANCELLATIONS_COUNT
|
||
// ============================
|
||
`CREATE INDEX IF NOT EXISTS idx_command_items_command_id ON command_items(command_id);`,
|
||
`CREATE INDEX IF NOT EXISTS idx_command_items_client_username ON command_items(client_username);`,
|
||
`CREATE INDEX IF NOT EXISTS idx_command_items_status ON command_items(status);`,
|
||
`CREATE INDEX IF NOT EXISTS idx_command_items_produit ON command_items(produit);`,
|
||
`CREATE INDEX IF NOT EXISTS idx_clients_point_zipette ON clients(point_zipette) WHERE point_zipette > 0;`,
|
||
`CREATE INDEX IF NOT EXISTS idx_jwt_user_id ON jwt_tokens(user_id);`,
|
||
`CREATE INDEX IF NOT EXISTS idx_jwt_user_type ON jwt_tokens(user_type);`,
|
||
`CREATE INDEX IF NOT EXISTS idx_jwt_token ON jwt_tokens(token);`,
|
||
`CREATE INDEX IF NOT EXISTS idx_jwt_date_fin ON jwt_tokens(date_fin);`,
|
||
`CREATE INDEX IF NOT EXISTS idx_clients_telephone ON clients(telephone);`,
|
||
`CREATE INDEX IF NOT EXISTS idx_clients_cancellations ON clients(cancellations_count);`,
|
||
`CREATE INDEX IF NOT EXISTS idx_clients_amende ON clients(amende) WHERE amende > 0;`,
|
||
`CREATE INDEX IF NOT EXISTS idx_cmd_username ON commandes(username);`,
|
||
`CREATE INDEX IF NOT EXISTS idx_cmd_status ON commandes(status);`,
|
||
`CREATE INDEX IF NOT EXISTS idx_cmd_livreur ON commandes(livreur_assign);`,
|
||
|
||
`CREATE INDEX IF NOT EXISTS idx_products_category ON products(category);`,
|
||
`CREATE INDEX IF NOT EXISTS idx_media_product_id ON media(product_id);`,
|
||
`CREATE INDEX IF NOT EXISTS idx_price_product_id ON product_prices(product_id);`,
|
||
`CREATE INDEX IF NOT EXISTS idx_items_command_id ON command_items(command_id);`,
|
||
`CREATE INDEX IF NOT EXISTS idx_items_product_id ON command_items(product_id);`,
|
||
`CREATE INDEX IF NOT EXISTS idx_baskets_username ON baskets(username);`,
|
||
`CREATE INDEX IF NOT EXISTS idx_baskets_product_id ON baskets(product_id);`,
|
||
`CREATE INDEX IF NOT EXISTS idx_logs_command_id ON command_logs(command_id);`,
|
||
|
||
`CREATE INDEX IF NOT EXISTS idx_issues_command ON delivery_issues(command_id);`,
|
||
`CREATE INDEX IF NOT EXISTS idx_issues_status ON delivery_issues(status);`,
|
||
`CREATE INDEX IF NOT EXISTS idx_issues_reported_by ON delivery_issues(reported_by);`,
|
||
}
|
||
|
||
for _, query := range queries {
|
||
if _, err := db.Exec(query); err != nil {
|
||
return fmt.Errorf("❌ Erreur SQL: %v\nRequête: %s", err, query)
|
||
}
|
||
}
|
||
|
||
log.Println("✅ Toutes les tables PostgreSQL créées avec succès.")
|
||
return nil
|
||
}
|
||
|
||
// ============================================
|
||
// MÉTHODES POUR LES TOKENS JWT
|
||
// ============================================
|
||
|
||
// CleanExpiredTokens supprime les tokens JWT expirés
|
||
func (d *Database) CleanExpiredTokens() error {
|
||
query := `DELETE FROM jwt_tokens WHERE date_fin < $1`
|
||
result, err := d.Exec(query, time.Now())
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
rowsAffected, _ := result.RowsAffected()
|
||
if rowsAffected > 0 {
|
||
log.Printf("🧹 %d token(s) expiré(s) supprimé(s)", rowsAffected)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// cleanExpiredTokensPeriodically nettoie les tokens expirés toutes les heures
|
||
func (db *Database) cleanExpiredTokensPeriodically() {
|
||
ticker := time.NewTicker(1 * time.Hour)
|
||
defer ticker.Stop()
|
||
|
||
for range ticker.C {
|
||
if err := db.CleanExpiredTokens(); err != nil {
|
||
log.Printf("⚠️ Erreur lors du nettoyage des tokens: %v", err)
|
||
}
|
||
}
|
||
}
|
||
|
||
// getEnv récupère une variable d'environnement ou retourne une valeur par défaut
|
||
func getEnv(key, defaultValue string) string {
|
||
if value := os.Getenv(key); value != "" {
|
||
return value
|
||
}
|
||
return defaultValue
|
||
}
|