Files
projet_gestion_commande/backend/gestion/db/db_init.go
T
Xor290 74f3d4815e
Frontend Web - Build & Lint / build (push) Has been cancelled
Backend - Build & Lint / build (push) Has been cancelled
chore: build backend frontend web
2026-06-20 20:14:18 +02:00

598 lines
22 KiB
Go

package db
import (
"database/sql"
"fmt"
"log"
"os"
"time"
_ "github.com/lib/pq"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// ============================================
// STRUCTURES
// ============================================
// Database encapsule la connexion à la base de données
type Database struct {
*sql.DB
GDB *gorm.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")
// Initialiser GORM en réutilisant la connexion sql.DB existante
gormDB, err := gorm.Open(postgres.New(postgres.Config{
Conn: db,
}), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
log.Fatalf("❌ Erreur initialisation GORM: %v", err)
}
// Créer l'instance Database
database := &Database{db, gormDB}
// 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: 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: baskets.is_reward — marquer les articles issus d'une récompense points
if _, err = database.Exec(`ALTER TABLE baskets ADD COLUMN IF NOT EXISTS is_reward BOOLEAN NOT NULL DEFAULT FALSE`); err != nil {
log.Fatalf("❌ Erreur migration baskets.is_reward: %v", err)
}
// Migration: baskets.reward_pool_key — pool de points utilisé pour la récompense
if _, err = database.Exec(`ALTER TABLE baskets ADD COLUMN IF NOT EXISTS reward_pool_key VARCHAR(100) NOT NULL DEFAULT ''`); err != nil {
log.Fatalf("❌ Erreur migration baskets.reward_pool_key: %v", err)
}
// Migration: command_items.is_reward + reward_pool_key
if _, err = database.Exec(`ALTER TABLE command_items ADD COLUMN IF NOT EXISTS is_reward BOOLEAN NOT NULL DEFAULT FALSE`); err != nil {
log.Fatalf("❌ Erreur migration command_items.is_reward: %v", err)
}
if _, err = database.Exec(`ALTER TABLE command_items ADD COLUMN IF NOT EXISTS reward_pool_key VARCHAR(100) NOT NULL DEFAULT ''`); err != nil {
log.Fatalf("❌ Erreur migration command_items.reward_pool_key: %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: position d'affichage des catégories
if _, err = database.Exec(`ALTER TABLE categories ADD COLUMN IF NOT EXISTS position INTEGER NOT NULL DEFAULT 0`); err != nil {
log.Fatalf("❌ Erreur migration categories.position: %v", err)
}
// Backfill: attribuer des positions aux catégories existantes (ordre alphabétique)
if _, err = database.Exec(`
UPDATE categories c
SET position = sub.rn
FROM (
SELECT id, ROW_NUMBER() OVER (ORDER BY name ASC) AS rn
FROM categories
) sub
WHERE c.id = sub.id AND c.position = 0
`); err != nil {
log.Fatalf("❌ Erreur backfill categories.position: %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: méthode de paiement (cash par défaut, crypto si paiement NowPayments)
if _, err = database.Exec(`ALTER TABLE commandes ADD COLUMN IF NOT EXISTS payment_method VARCHAR(20) NOT NULL DEFAULT 'cash'`); err != nil {
log.Fatalf("❌ Erreur migration commandes.payment_method: %v", err)
}
// Migration: identifiant de commande par client (numérotation indépendante par client, commence à 1)
if _, err = database.Exec(`ALTER TABLE commandes ADD COLUMN IF NOT EXISTS client_order_id INTEGER NOT NULL DEFAULT 0`); err != nil {
log.Fatalf("❌ Erreur migration commandes.client_order_id: %v", err)
}
// Backfill: numéroter les commandes existantes par client dans l'ordre d'insertion
if _, err = database.Exec(`
UPDATE commandes c
SET client_order_id = sub.rn
FROM (
SELECT id, ROW_NUMBER() OVER (PARTITION BY username ORDER BY id) AS rn
FROM commandes
) sub
WHERE c.id = sub.id AND c.client_order_id = 0
`); err != nil {
log.Fatalf("❌ Erreur backfill commandes.client_order_id: %v", err)
}
// Migration: raison d'annulation par le client
if _, err = database.Exec(`ALTER TABLE commandes ADD COLUMN IF NOT EXISTS cancel_reason TEXT`); err != nil {
log.Fatalf("❌ Erreur migration commandes.cancel_reason: %v", err)
}
// Migration: coordonnées GPS de destination et du livreur
if _, err = database.Exec(`ALTER TABLE commandes ADD COLUMN IF NOT EXISTS dest_latitude DOUBLE PRECISION`); err != nil {
log.Fatalf("❌ Erreur migration commandes.dest_latitude: %v", err)
}
if _, err = database.Exec(`ALTER TABLE commandes ADD COLUMN IF NOT EXISTS dest_longitude DOUBLE PRECISION`); err != nil {
log.Fatalf("❌ Erreur migration commandes.dest_longitude: %v", err)
}
if _, err = database.Exec(`ALTER TABLE commandes ADD COLUMN IF NOT EXISTS livreur_latitude DOUBLE PRECISION`); err != nil {
log.Fatalf("❌ Erreur migration commandes.livreur_latitude: %v", err)
}
if _, err = database.Exec(`ALTER TABLE commandes ADD COLUMN IF NOT EXISTS livreur_longitude DOUBLE PRECISION`); err != nil {
log.Fatalf("❌ Erreur migration commandes.livreur_longitude: %v", err)
}
// Migration: table de suivi des paiements crypto
if _, err = database.Exec(`
CREATE TABLE IF NOT EXISTS crypto_payments (
id SERIAL PRIMARY KEY,
command_id INTEGER NOT NULL REFERENCES commandes(id) ON DELETE CASCADE,
nowpayment_id TEXT NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'waiting',
price_amount NUMERIC(10,2) NOT NULL,
price_currency VARCHAR(10) NOT NULL DEFAULT 'eur',
pay_currency VARCHAR(20) NOT NULL,
pay_address TEXT NOT NULL,
pay_amount NUMERIC(20,8) DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`); err != nil {
log.Fatalf("❌ Erreur migration crypto_payments: %v", err)
}
if _, err = database.Exec(`CREATE INDEX IF NOT EXISTS idx_crypto_payments_command_id ON crypto_payments(command_id)`); err != nil {
log.Fatalf("❌ Erreur index crypto_payments.command_id: %v", err)
}
if _, err = database.Exec(`CREATE INDEX IF NOT EXISTS idx_crypto_payments_nowpayment_id ON crypto_payments(nowpayment_id)`); err != nil {
log.Fatalf("❌ Erreur index crypto_payments.nowpayment_id: %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: récompenses réclamées par pool (nb de fois que la récompense a été obtenue)
if _, err = database.Exec(`ALTER TABLE clients ADD COLUMN IF NOT EXISTS points_redeemed JSONB NOT NULL DEFAULT '{}'::jsonb`); err != nil {
log.Fatalf("❌ Erreur migration clients.points_redeemed: %v", err)
}
// Migration: flag "à venir" sur les produits
if _, err = database.Exec(`ALTER TABLE products ADD COLUMN IF NOT EXISTS coming_soon BOOLEAN NOT NULL DEFAULT FALSE`); err != nil {
log.Fatalf("❌ Erreur migration products.coming_soon: %v", err)
}
// Migration: prix actif/inactif sur les prix de produits
if _, err = database.Exec(`ALTER TABLE product_prices ADD COLUMN IF NOT EXISTS active_price BOOLEAN NOT NULL DEFAULT TRUE`); err != nil {
log.Fatalf("❌ Erreur migration product_prices.active_price: %v", err)
}
// Migration: table contacts (SAV Telegram)
if _, err = database.Exec(`CREATE TABLE IF NOT EXISTS contacts (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL
)`); err != nil {
log.Fatalf("❌ Erreur migration contacts: %v", err)
}
// 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,
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
);`,
// ============================
// 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_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);`,
// ============================
// TABLE contacts
// ============================
`CREATE TABLE IF NOT EXISTS contacts (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL
);`,
// ============================
// TABLE livreur_ratings
// ============================
`CREATE TABLE IF NOT EXISTS livreur_ratings (
id SERIAL PRIMARY KEY,
order_id INTEGER NOT NULL UNIQUE REFERENCES commandes(id) ON DELETE CASCADE,
livreur_username VARCHAR(255) NOT NULL,
client_username VARCHAR(255) NOT NULL,
rating SMALLINT NOT NULL CHECK (rating BETWEEN 1 AND 5),
comment TEXT NOT NULL DEFAULT '',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);`,
`CREATE INDEX IF NOT EXISTS idx_ratings_livreur ON livreur_ratings(livreur_username);`,
}
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
}