From bbb5b5ae3666b8cb93267302f1425d50fe1332ad Mon Sep 17 00:00:00 2001 From: Anthony Janin Date: Mon, 11 Aug 2025 16:29:39 +0200 Subject: [PATCH] Add Database connection --- .env.exemple | 14 ++++-- package.json | 1 + src/database.js | 105 ++++++++++++++++++++++++++++++++++++++++++ src/server.js | 120 ++++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 226 insertions(+), 14 deletions(-) create mode 100644 src/database.js diff --git a/.env.exemple b/.env.exemple index a0347fb..af7e537 100644 --- a/.env.exemple +++ b/.env.exemple @@ -7,13 +7,12 @@ OVH_SMS_SERVICE_NAME= # Configuration SMS Factor SMS_FACTOR_TOKEN= -#Configuration Mailchimp +# Configuration Mailchimp MAILCHIMP_API_KEY= MAILCHIMP_KEY= MAILCHIMP_LIST_ID= - -#Configuration Stripe +# Configuration Stripe STRIPE_SECRET_KEY= STRIPE_WEBHOOK_SECRET= STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID= @@ -21,10 +20,17 @@ STRIPE_STANDARD_ANNUAL_SUBSCRIPTION_PRICE_ID= STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID= STRIPE_UNLIMITED_ANNUAL_SUBSCRIPTION_PRICE_ID= -#Cartes de test Stripe +# Cartes de test Stripe SUCCES= 4242 4242 4242 4242 #Paiement réussi DECLINED= 4000 0025 0000 3155 #Paiement refusé # Configuration serveur APP_HOST= PORT= + +# Configuration PostgreSQL +DB_HOST= +DB_PORT= +DB_NAME= +DB_USER= +DB_PASSWORD= diff --git a/package.json b/package.json index 6bfd57c..70090a5 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "express": "^4.18.2", "node-fetch": "^2.6.7", "ovh": "^2.0.3", + "pg": "^8.11.3", "stripe": "^18.3.0", "uuid": "^11.1.0" }, diff --git a/src/database.js b/src/database.js new file mode 100644 index 0000000..0d4848f --- /dev/null +++ b/src/database.js @@ -0,0 +1,105 @@ +const { Pool } = require('pg'); +require('dotenv').config(); + +/** + * Configuration de la base de données PostgreSQL + */ +const dbConfig = { + user: process.env.DB_USER || 'postgres', + host: process.env.DB_HOST || 'localhost', + database: process.env.DB_NAME || 'prd', + password: process.env.DB_PASSWORD || 'admin', + port: process.env.DB_PORT || 5432, + + // Configuration du pool de connexions + max: parseInt(process.env.DB_POOL_MAX) || 20, // Nombre maximum de connexions dans le pool + min: parseInt(process.env.DB_POOL_MIN) || 2, // Nombre minimum de connexions maintenues + idleTimeoutMillis: parseInt(process.env.DB_IDLE_TIMEOUT) || 30000, // Temps d'inactivité avant fermeture + connectionTimeoutMillis: parseInt(process.env.DB_CONNECTION_TIMEOUT) || 2000, // Timeout pour établir une connexion + acquireTimeoutMillis: parseInt(process.env.DB_ACQUIRE_TIMEOUT) || 60000, // Timeout pour acquérir une connexion + + // Configuration SSL si nécessaire + ssl: process.env.DB_SSL === 'true' ? { + rejectUnauthorized: process.env.DB_SSL_REJECT_UNAUTHORIZED !== 'false' + } : false +}; + +/** + * Pool de connexions PostgreSQL + */ +const pool = new Pool(dbConfig); + +/** + * Gestionnaire d'erreur pour le pool + */ +pool.on('error', (err) => { + console.error('PostgreSQL Error:', err); +}); + +/** + * Classe pour gérer les opérations de base de données + */ +class Database { + + /** + * Exécute une requête SQL avec des paramètres + * @param {string} text - La requête SQL + * @param {Array} params - Les paramètres de la requête + * @returns {Promise} - Le résultat de la requête + */ + static async query(text, params) { + try { + return await pool.query(text, params); + } catch (error) { + console.error('Error executing query:', error); + throw error; + } + } + + /** + * Teste la connexion à la base de données + * @returns {Promise} - True si la connexion est réussie + */ + static async testConnection() { + try { + const result = await this.query('SELECT NOW() as current_time'); + console.log('Database connection successful:', result.rows[0].current_time); + return true; + } catch (error) { + console.error('Database connection failed:', error.message); + return false; + } + } + + /** + * Ferme toutes les connexions du pool + * @returns {Promise} + */ + static async close() { + try { + await pool.end(); + console.log('PostgreSQL connection pool closed'); + } catch (error) { + console.error('Error closing connection pool:', error); + } + } +} + +/** + * Gestion propre de l'arrêt de l'application + */ +process.on('SIGINT', async () => { + console.log('SIGINT signal received, closing connections...'); + await Database.close(); + process.exit(0); +}); + +process.on('SIGTERM', async () => { + console.log('SIGTERM signal received, closing connections...'); + await Database.close(); + process.exit(0); +}); + +module.exports = { + Database +}; diff --git a/src/server.js b/src/server.js index 6abc018..004cf32 100644 --- a/src/server.js +++ b/src/server.js @@ -5,6 +5,7 @@ const { v4: uuidv4 } = require('uuid'); const ovh = require('ovh'); const mailchimp = require('@mailchimp/mailchimp_transactional'); const Stripe = require('stripe'); +const { Database } = require('./database'); require('dotenv').config(); // Initialisation de l'application Express @@ -121,6 +122,82 @@ app.get('/api/v1/health', (req, res) => { res.json({ message: 'OK' }); }); +app.get('/api/v1/db/:tableName', async (req, res) => { + try { + const { tableName } = req.params; + const page = parseInt(req.query.page) || 1; + const limit = parseInt(req.query.limit) || 10; + const offset = (page - 1) * limit; + + // Validation du nom de table pour éviter les injections SQL + if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) { + return res.status(400).json({ + success: false, + message: 'Nom de table invalide' + }); + } + + // Compter le total d'enregistrements + const countResult = await Database.query(`SELECT COUNT(*) as total FROM ${tableName}`); + const total = parseInt(countResult.rows[0].total); + + if (tableName === 'rules_groups') { + const rulesGroups = await Promise.all((await Database.query(`SELECT * FROM ${tableName} ORDER BY 1 LIMIT $1 OFFSET $2`, [limit, offset])).rows.map(async (ruleGroup) => { + const result = await Database.query(`SELECT a.* FROM rules AS a JOIN "_RulesGroupsHasRules" as b ON a.uid = b."A" AND b."B" = '${ruleGroup.uid}';`); + return { + uid: ruleGroup.uid, + name: ruleGroup.name, + rules: result.rows.map((rule) => { + return { + uid: rule.uid + } + }), + created_at: ruleGroup.created_at, + updated_at: ruleGroup.updated_at + }; + })); + + res.json({ + success: true, + data: rulesGroups, + pagination: { + page, + limit, + total, + totalPages: Math.ceil(total / limit), + hasNextPage: page < Math.ceil(total / limit), + hasPrevPage: page > 1 + }, + table: tableName, + timestamp: new Date().toISOString() + }); + } else { + const result = await Database.query(`SELECT * FROM ${tableName} ORDER BY 1 LIMIT $1 OFFSET $2`, [limit, offset]); + + res.json({ + success: true, + data: result.rows, + pagination: { + page, + limit, + total, + totalPages: Math.ceil(total / limit), + hasNextPage: page < Math.ceil(total / limit), + hasPrevPage: page > 1 + }, + table: tableName, + timestamp: new Date().toISOString() + }); + } + } catch (error) { + res.status(500).json({ + success: false, + message: 'Erreur lors de l\'exécution de la requête', + error: error.message + }); + } +}); + app.post('/api/v1/idnot/user/:code', async (req, res) => { const code = req.params.code; @@ -383,7 +460,7 @@ app.post('/api/send-code', validatePhoneNumber, async (req, res) => { }); } } catch (error) { - console.error('Erreur:', error); + console.error('Error:', error); res.status(500).json({ success: false, message: 'Erreur serveur lors de l\'envoi du code' @@ -853,7 +930,7 @@ app.post('/api/subscriptions/checkout', validateSubscription, async (req, res) = const session = await stripeService.createCheckoutSession(req.body, req.body.frequency); res.json({ success: true, sessionId: session.id }); } catch (error) { - console.error('Erreur création checkout:', error); + console.error('Error creating checkout:', error); res.status(500).json({ success: false, message: 'Erreur lors de la création de la session de paiement' @@ -903,7 +980,7 @@ app.post('/api/webhooks/stripe', express.raw({ type: 'application/json' }), asyn if (session.status === 'complete') { const subscription = JSON.parse(session.metadata.subscription); // Stock subscription (create process) - console.log('Nouvel abonnement:', subscription); + console.log('New subscription:', subscription); } break; @@ -912,29 +989,52 @@ app.post('/api/webhooks/stripe', express.raw({ type: 'application/json' }), asyn if (['subscription_update', 'subscription_cycle'].includes(invoice.billing_reason)) { const subscription = await stripeService.getSubscription(invoice.subscription); // Update subscription (update process) - console.log('Mise à jour abonnement:', subscription); + console.log('Subscription update:', subscription); } break; case 'customer.subscription.deleted': const deletedSubscription = event.data.object; // Delete subscription (update process to delete) - console.log('Suppression abonnement:', deletedSubscription.id); + console.log('Subscription deleted:', deletedSubscription.id); break; } res.json({ received: true }); } catch (error) { - console.error('Erreur webhook:', error); + console.error('Webhook error:', error); res.status(500).json({ success: false, - message: 'Erreur lors du traitement du webhook' + message: 'Error processing webhook' }); } }); //------------------------------------ End of Stripe Section ----------------------------------- -app.listen(PORT, () => { - console.log(`Server is running on port ${PORT}`); -}); +// Initialisation et démarrage du serveur +async function startServer() { + try { + // Test de la connexion à la base de données au démarrage + console.log('Initializing database connection...'); + const isDbConnected = await Database.testConnection(); + + if (!isDbConnected) { + console.warn('Warning: Database connection failed, but the server will start anyway'); + } else { + console.log('Database connection established successfully'); + } + + // Démarrage du serveur + app.listen(PORT, () => { + console.log(`Server started on port ${PORT}`); + }); + + } catch (error) { + console.error('Error starting the server:', error); + process.exit(1); + } +} + +// Démarrage de l'application +startServer();