From 5032fa3134d6241b0de5a18feb73f4c574fffa57 Mon Sep 17 00:00:00 2001 From: Omar Oughriss Date: Fri, 18 Jul 2025 16:25:35 +0200 Subject: [PATCH] Move Mail feature to the main file --- src/main-server.js | 179 ---------------------- src/server.js | 373 +++++++++++++++++++++++++++++++++------------ 2 files changed, 272 insertions(+), 280 deletions(-) delete mode 100644 src/main-server.js diff --git a/src/main-server.js b/src/main-server.js deleted file mode 100644 index 957a2a1..0000000 --- a/src/main-server.js +++ /dev/null @@ -1,179 +0,0 @@ -const express = require('express'); -const cors = require('cors'); -const fetch = require('node-fetch'); - -// Initialisation de l'application Express -const app = express(); -const PORT = process.env.PORT || 8080; - -// Configuration CORS -const corsOptions = { - origin: ['http://local.lecoffreio.4nkweb:3000', 'http://localhost:3000'], - methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], - allowedHeaders: ['Content-Type', 'Authorization'] -}; - -app.use(cors(corsOptions)); -app.use(express.json()); - -function getOfficeStatus(statusName) { - switch (statusName) { - case 'Pourvu': - return 'ACTIVATED'; - case 'Pourvu mais décédé': - return 'ACTIVATED'; - case 'Sans titulaire': - return 'ACTIVATED'; - case 'Vacance': - return 'ACTIVATED'; - case 'En activité': - return 'ACTIVATED'; - default: - return 'DESACTIVATED'; - } -} - -function getRole(roleName) { - switch (roleName) { - case 'Notaire titulaire': - return { name: 'admin', label: 'Administrateur' }; - case 'Notaire associé': - return { name: 'admin', label: 'Administrateur' }; - case 'Notaire salarié': - return { name: 'notary', label: 'Notaire' }; - case 'Collaborateur': - return { name: 'notary', label: 'Notaire' }; - case 'Suppléant': - return { name: 'notary', label: 'Notaire' }; - case 'Administrateur': - return { name: 'admin', label: 'Administrateur' }; - case 'Curateur': - return { name: 'notary', label: 'Notaire' }; - default: - return { name: 'default', label: 'Défaut' }; - } -} - -function getCivility(civility) { - switch (civility) { - case 'Monsieur': - return 'MALE'; - case 'Madame': - return 'FEMALE'; - default: - return 'OTHERS'; - } -} - -app.get('/api/v1/health', (req, res) => { - res.json({ message: 'OK' }); -}); - -app.post('/api/v1/idnot/user/:code', async (req, res) => { - const code = req.params.code; - - try { - const params = { - client_id: 'B3CE56353EDB15A9', - client_secret: '3F733549E879878344B6C949B366BB5CDBB2DB5B7F7AB7EBBEBB0F0DD0776D1C', - redirect_uri: 'http://local.lecoffreio.4nkweb:3000/authorized-client', - grant_type: 'authorization_code', - code: code - }; - - const tokens = await ( - await fetch('https://qual-connexion.idnot.fr/user/IdPOAuth2/token/idnot_idp_v1', { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: new URLSearchParams(params).toString() - }) - ).json(); - - const jwt = tokens.id_token; - if (!jwt) { - console.error('jwt not defined'); - return null; - } - const payload = JSON.parse(Buffer.from(jwt.split('.')[1], 'base64').toString('utf8')); - - const searchParams = new URLSearchParams({ - key: 'ba557f84-0bf6-4dbf-844f-df2767555e3e' - }); - - let userData; - try { - userData = await ( - await fetch(`https://qual-api.notaires.fr/annuaire/api/pp/v2/rattachements/${payload.profile_idn}?` + searchParams, { - method: 'GET' - }) - ).json(); - } catch (error) { - console.error('Error fetching ' + `https://qual-api.notaires.fr/annuaire/api/pp/v2/rattachements/${payload.profile_idn}`, error); - return null; - } - if (!userData || !userData.statutDuRattachement || userData.entite.typeEntite.name !== 'office') { - console.error('User not attached to an office (May be a partner)'); - return null; - } - - let officeLocationData; - try { - officeLocationData = (await ( - await fetch(`https://qual-api.notaires.fr/annuaire${userData.entite.locationsUrl}?` + searchParams, - { - method: 'GET' - }) - ).json()); - } catch (error) { - console.error('Error fetching' + `https://qual-api.notaires.fr/annuaire${userData.entite.locationsUrl}`, error); - return null; - } - if (!officeLocationData || !officeLocationData.result || officeLocationData.result.length === 0) { - console.error('Office location data not found'); - return null; - } - - const idnotUser = { - idNot: payload.sub, - office: { - idNot: payload.entity_idn, - name: userData.entite.denominationSociale ?? userData.entite.codeCrpcen, - crpcen: userData.entite.codeCrpcen, - office_status: getOfficeStatus(userData.entite.statutEntite.name), - address: { - address: officeLocationData.result[0].adrGeo4, - city: officeLocationData.result[0].adrGeoVille.split(' ')[0] ?? officeLocationData.result[0].adrGeoVille, - zip_code: Number(officeLocationData.result[0].adrGeoCodePostal) - }, - status: 'ACTIVE' - }, - role: getRole(userData.typeLien.name), - contact: { - first_name: userData.personne.prenom, - last_name: userData.personne.nomUsuel, - email: userData.mailRattachement, - phone_number: userData.numeroTelephone, - cell_phone_number: userData.numeroMobile ?? userData.numeroTelephone, - civility: getCivility(userData.personne.civilite) - } - }; - - if (!idnotUser.contact.email) { - console.error("User pro email empty"); - return null; - } - - res.json(idnotUser); - } catch (error) { - res.status(500).json({ - error: 'Internal Server Error', - message: error.message - }); - } -}); - -app.listen(PORT, () => { - console.log(`Server is running on port ${PORT}`); -}); diff --git a/src/server.js b/src/server.js index 207a416..2562aaf 100644 --- a/src/server.js +++ b/src/server.js @@ -1,25 +1,197 @@ const express = require('express'); +const cors = require('cors'); const fetch = require('node-fetch'); const mailchimp = require('@mailchimp/mailchimp_transactional'); require('dotenv').config(); +// Initialisation de l'application Express const app = express(); -app.use(express.json()); +const PORT = process.env.PORT || 8080; -// Configuration -const config = { - MAILCHIMP_API_KEY: process.env.MAILCHIMP_API_KEY, - MAILCHIMP_KEY: process.env.MAILCHIMP_KEY, - MAILCHIMP_LIST_ID: process.env.MAILCHIMP_LIST_ID, - PORT: process.env.PORT || 8080, - FROM_EMAIL: 'no-reply@lecoffre.io', - FROM_NAME: 'LeCoffre.io' +// Configuration CORS +const corsOptions = { + origin: ['http://local.lecoffreio.4nkweb:3000', 'http://localhost:3000'], + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization'] }; -// Stockage temporaire des emails en attente +app.use(cors(corsOptions)); +app.use(express.json()); + +function getOfficeStatus(statusName) { + switch (statusName) { + case 'Pourvu': + return 'ACTIVATED'; + case 'Pourvu mais décédé': + return 'ACTIVATED'; + case 'Sans titulaire': + return 'ACTIVATED'; + case 'Vacance': + return 'ACTIVATED'; + case 'En activité': + return 'ACTIVATED'; + default: + return 'DESACTIVATED'; + } +} + +function getRole(roleName) { + switch (roleName) { + case 'Notaire titulaire': + return { name: 'admin', label: 'Administrateur' }; + case 'Notaire associé': + return { name: 'admin', label: 'Administrateur' }; + case 'Notaire salarié': + return { name: 'notary', label: 'Notaire' }; + case 'Collaborateur': + return { name: 'notary', label: 'Notaire' }; + case 'Suppléant': + return { name: 'notary', label: 'Notaire' }; + case 'Administrateur': + return { name: 'admin', label: 'Administrateur' }; + case 'Curateur': + return { name: 'notary', label: 'Notaire' }; + default: + return { name: 'default', label: 'Défaut' }; + } +} + +function getCivility(civility) { + switch (civility) { + case 'Monsieur': + return 'MALE'; + case 'Madame': + return 'FEMALE'; + default: + return 'OTHERS'; + } +} + +app.get('/api/v1/health', (req, res) => { + res.json({ message: 'OK' }); +}); + +app.post('/api/v1/idnot/user/:code', async (req, res) => { + const code = req.params.code; + + try { + const params = { + client_id: 'B3CE56353EDB15A9', + client_secret: '3F733549E879878344B6C949B366BB5CDBB2DB5B7F7AB7EBBEBB0F0DD0776D1C', + redirect_uri: 'http://local.lecoffreio.4nkweb:3000/authorized-client', + grant_type: 'authorization_code', + code: code + }; + + const tokens = await ( + await fetch('https://qual-connexion.idnot.fr/user/IdPOAuth2/token/idnot_idp_v1', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams(params).toString() + }) + ).json(); + + const jwt = tokens.id_token; + if (!jwt) { + console.error('jwt not defined'); + return null; + } + const payload = JSON.parse(Buffer.from(jwt.split('.')[1], 'base64').toString('utf8')); + + const searchParams = new URLSearchParams({ + key: 'ba557f84-0bf6-4dbf-844f-df2767555e3e' + }); + + let userData; + try { + userData = await ( + await fetch(`https://qual-api.notaires.fr/annuaire/api/pp/v2/rattachements/${payload.profile_idn}?` + searchParams, { + method: 'GET' + }) + ).json(); + } catch (error) { + console.error('Error fetching ' + `https://qual-api.notaires.fr/annuaire/api/pp/v2/rattachements/${payload.profile_idn}`, error); + return null; + } + if (!userData || !userData.statutDuRattachement || userData.entite.typeEntite.name !== 'office') { + console.error('User not attached to an office (May be a partner)'); + return null; + } + + let officeLocationData; + try { + officeLocationData = (await ( + await fetch(`https://qual-api.notaires.fr/annuaire${userData.entite.locationsUrl}?` + searchParams, + { + method: 'GET' + }) + ).json()); + } catch (error) { + console.error('Error fetching' + `https://qual-api.notaires.fr/annuaire${userData.entite.locationsUrl}`, error); + return null; + } + if (!officeLocationData || !officeLocationData.result || officeLocationData.result.length === 0) { + console.error('Office location data not found'); + return null; + } + + const idnotUser = { + idNot: payload.sub, + office: { + idNot: payload.entity_idn, + name: userData.entite.denominationSociale ?? userData.entite.codeCrpcen, + crpcen: userData.entite.codeCrpcen, + office_status: getOfficeStatus(userData.entite.statutEntite.name), + address: { + address: officeLocationData.result[0].adrGeo4, + city: officeLocationData.result[0].adrGeoVille.split(' ')[0] ?? officeLocationData.result[0].adrGeoVille, + zip_code: Number(officeLocationData.result[0].adrGeoCodePostal) + }, + status: 'ACTIVE' + }, + role: getRole(userData.typeLien.name), + contact: { + first_name: userData.personne.prenom, + last_name: userData.personne.nomUsuel, + email: userData.mailRattachement, + phone_number: userData.numeroTelephone, + cell_phone_number: userData.numeroMobile ?? userData.numeroTelephone, + civility: getCivility(userData.personne.civilite) + } + }; + + if (!idnotUser.contact.email) { + console.error("User pro email empty"); + return null; + } + + res.json(idnotUser); + } catch (error) { + res.status(500).json({ + error: 'Internal Server Error', + message: error.message + }); + } +}); + +//------------------------------------ Email Section ------------------------------------ + + +const config = { + MAILCHIMP_API_KEY: process.env.MAILCHIMP_API_KEY, + MAILCHIMP_KEY: process.env.MAILCHIMP_KEY, + MAILCHIMP_LIST_ID: process.env.MAILCHIMP_LIST_ID, + PORT: process.env.PORT || 8080, + FROM_EMAIL: 'no-reply@lecoffre.io', + FROM_NAME: 'LeCoffre.io' +}; + +// Email storage const pendingEmails = new Map(); -// Service Email +// Email service class EmailService { static async sendTransactionalEmail(to, templateName, subject, templateVariables) { try { @@ -57,7 +229,7 @@ class EmailService { })); } - // Ajout à la liste Mailchimp de diffusion + // Add to Mailchimp diffusion list static async addToMailchimpList(email) { try { const url = `https://us17.api.mailchimp.com/3.0/lists/${config.MAILCHIMP_LIST_ID}/members`; @@ -120,115 +292,114 @@ class EmailService { } } -// Middleware de validation +// Email validation middleware const validateEmail = (req, res, next) => { - const { email } = req.body; - - if (!email) { - return res.status(400).json({ - success: false, - message: 'L\'adresse email est requise' - }); - } + const { email } = req.body; + + if (!email) { + return res.status(400).json({ + success: false, + message: 'L\'adresse email est requise' + }); + } - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!emailRegex.test(email)) { - return res.status(400).json({ - success: false, - message: 'Format d\'email invalide' - }); - } + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + return res.status(400).json({ + success: false, + message: 'Format d\'email invalide' + }); + } - next(); + next(); }; -// Templates +// Email templates const ETemplates = { - DOCUMENT_ASKED: "DOCUMENT_ASKED", - DOCUMENT_REFUSED: "DOCUMENT_REFUSED", - DOCUMENT_RECAP: "DOCUMENT_RECAP", - SUBSCRIPTION_INVITATION: "SUBSCRIPTION_INVITATION", - DOCUMENT_REMINDER: "DOCUMENT_REMINDER", - DOCUMENT_SEND: "DOCUMENT_SEND", +DOCUMENT_ASKED: "DOCUMENT_ASKED", +DOCUMENT_REFUSED: "DOCUMENT_REFUSED", +DOCUMENT_RECAP: "DOCUMENT_RECAP", +SUBSCRIPTION_INVITATION: "SUBSCRIPTION_INVITATION", +DOCUMENT_REMINDER: "DOCUMENT_REMINDER", +DOCUMENT_SEND: "DOCUMENT_SEND", }; // Routes app.post('/api/send-email', validateEmail, async (req, res) => { - const { email, firstName, lastName, officeName, template } = req.body; + const { email, firstName, lastName, officeName, template } = req.body; - try { - const templateVariables = { - first_name: firstName || '', - last_name: lastName || '', - office_name: officeName || '', - link: `${process.env.APP_HOST}` - }; + try { + const templateVariables = { + first_name: firstName || '', + last_name: lastName || '', + office_name: officeName || '', + link: `${process.env.APP_HOST}` + }; - const result = await EmailService.sendTransactionalEmail( - email, - ETemplates[template], - 'Votre notaire vous envoie un message', - templateVariables - ); + const result = await EmailService.sendTransactionalEmail( + email, + ETemplates[template], + 'Votre notaire vous envoie un message', + templateVariables + ); - if (!result.success) { - // Ajouter aux emails en attente pour réessayer plus tard - const emailId = `${email}-${Date.now()}`; - pendingEmails.set(emailId, { - to: email, - templateName: ETemplates[template], - subject: 'Votre notaire vous envoie un message', - templateVariables, - attempts: 1, - lastAttempt: Date.now() - }); - } + if (!result.success) { + // Add to pending emails to retry later + const emailId = `${email}-${Date.now()}`; + pendingEmails.set(emailId, { + to: email, + templateName: ETemplates[template], + subject: 'Votre notaire vous envoie un message', + templateVariables, + attempts: 1, + lastAttempt: Date.now() + }); + } - res.json({ - success: true, - message: 'Email envoyé avec succès' - }); - } catch (error) { - console.error('Erreur:', error); - res.status(500).json({ - success: false, - message: 'Erreur serveur lors de l\'envoi de l\'email' - }); - } + res.json({ + success: true, + message: 'Email envoyé avec succès' + }); + } catch (error) { + console.error('Erreur:', error); + res.status(500).json({ + success: false, + message: 'Erreur serveur lors de l\'envoi de l\'email' + }); + } }); app.post('/api/subscribe-to-list', validateEmail, async (req, res) => { - const { email } = req.body; + const { email } = req.body; - try { - const result = await EmailService.addToMailchimpList(email); + try { + const result = await EmailService.addToMailchimpList(email); - if (result.success) { - res.json({ - success: true, - message: 'Inscription à la liste réussie' - }); - } else { - res.status(500).json({ - success: false, - message: 'Échec de l\'inscription à la liste' - }); - } - } catch (error) { - console.error('Erreur:', error); - res.status(500).json({ - success: false, - message: 'Erreur serveur lors de l\'inscription' - }); - } + if (result.success) { + res.json({ + success: true, + message: 'Inscription à la liste réussie' + }); + } else { + res.status(500).json({ + success: false, + message: 'Échec de l\'inscription à la liste' + }); + } + } catch (error) { + console.error('Erreur:', error); + res.status(500).json({ + success: false, + message: 'Erreur serveur lors de l\'inscription' + }); + } }); -// Système de retry automatique +// Automatic retry system setInterval(() => { - EmailService.retryFailedEmails(); -}, 60000); // Vérifie toutes les minutes + EmailService.retryFailedEmails(); +}, 60000); // Check every minute -// Démarrage du serveur -app.listen(config.PORT, () => { - console.log(`Serveur démarré sur le port ${config.PORT}`); -}); \ No newline at end of file +app.listen(PORT, () => { + console.log(`Server is running on port ${PORT}`); +});