581 lines
22 KiB
Go
581 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: 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
|
|
}
|