diff --git a/src/server.js b/src/server.js index dd7f302..244cd69 100644 --- a/src/server.js +++ b/src/server.js @@ -2,6 +2,7 @@ const express = require('express'); const cors = require('cors'); const fetch = require('node-fetch'); const ovh = require('ovh'); +const mailchimp = require('@mailchimp/mailchimp_transactional'); require('dotenv').config(); // Initialisation de l'application Express @@ -176,9 +177,9 @@ app.post('/api/v1/idnot/user/:code', async (req, res) => { } }); -//------------------------------------ SMS Section ------------------------------------ +//------------------------------------ SMS Section ----------------------------------------- -const config = { +const configSms = { // OVH config OVH_APP_KEY: process.env.OVH_APP_KEY, OVH_APP_SECRET: process.env.OVH_APP_SECRET, @@ -204,12 +205,12 @@ class SmsService { static sendSmsWithOvh(phoneNumber, message) { return new Promise((resolve, reject) => { const ovhClient = ovh({ - appKey: config.OVH_APP_KEY, - appSecret: config.OVH_APP_SECRET, - consumerKey: config.OVH_CONSUMER_KEY + appKey: configSms.OVH_APP_KEY, + appSecret: configSms.OVH_APP_SECRET, + consumerKey: configSms.OVH_CONSUMER_KEY }); - ovhClient.request('POST', `/sms/${config.OVH_SMS_SERVICE_NAME}/jobs`, { + ovhClient.request('POST', `/sms/${configSms.OVH_SMS_SERVICE_NAME}/jobs`, { message: message, receivers: [phoneNumber], senderForResponse: false, @@ -233,7 +234,7 @@ class SmsService { url.searchParams.append('to', phoneNumber); url.searchParams.append('text', message); url.searchParams.append('sender', 'LeCoffre'); - url.searchParams.append('token', config.SMS_FACTOR_TOKEN); + url.searchParams.append('token', configSms.SMS_FACTOR_TOKEN); const response = await fetch(url.toString()); @@ -390,6 +391,234 @@ app.post('/api/verify-code', validatePhoneNumber, (req, res) => { } }); +//------------------------------------ End of SMS Section ------------------------------------ + +//------------------------------------ Email Section ----------------------------------------- + + +const configEmail = { + 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(); + +// Email service +class EmailService { + static async sendTransactionalEmail(to, templateName, subject, templateVariables) { + try { + const mailchimpClient = mailchimp(configEmail.MAILCHIMP_API_KEY); + + const message = { + template_name: templateName, + template_content: [], + message: { + global_merge_vars: this.buildVariables(templateVariables), + from_email: configEmail.FROM_EMAIL, + from_name: configEmail.FROM_NAME, + subject: subject, + to: [ + { + email: to, + type: 'to' + } + ] + } + }; + + const result = await mailchimpClient.messages.sendTemplate(message); + return { success: true, result }; + } catch (error) { + console.error('Erreur envoi email:', error); + return { success: false, error: 'Échec de l\'envoi de l\'email' }; + } + } + + static buildVariables(templateVariables) { + return Object.keys(templateVariables).map(key => ({ + name: key, + content: templateVariables[key] + })); + } + + // Add to Mailchimp diffusion list + static async addToMailchimpList(email) { + try { + const url = `https://us17.api.mailchimp.com/3.0/lists/${configEmail.MAILCHIMP_LIST_ID}/members`; + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Authorization': `apikey ${configEmail.MAILCHIMP_KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + email_address: email, + status: 'subscribed' + }) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return { success: true, data }; + } catch (error) { + console.error('Erreur ajout à la liste:', error); + return { success: false, error: 'Échec de l\'ajout à la liste Mailchimp' }; + } + } + + static async retryFailedEmails() { + for (const [emailId, emailData] of pendingEmails) { + if (emailData.attempts >= 10) { + pendingEmails.delete(emailId); + continue; + } + + const nextRetryDate = new Date(emailData.lastAttempt); + nextRetryDate.setMinutes(nextRetryDate.getMinutes() + Math.pow(emailData.attempts, 2)); + + if (Date.now() >= nextRetryDate) { + try { + const result = await this.sendTransactionalEmail( + emailData.to, + emailData.templateName, + emailData.subject, + emailData.templateVariables + ); + + if (result.success) { + pendingEmails.delete(emailId); + } else { + emailData.attempts += 1; + emailData.lastAttempt = Date.now(); + } + } catch (error) { + emailData.attempts += 1; + emailData.lastAttempt = Date.now(); + } + } + } + } +} + +// 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 emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + return res.status(400).json({ + success: false, + message: 'Format d\'email invalide' + }); + } + + next(); +}; + +// 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", +}; + +// Routes +app.post('/api/send-email', validateEmail, async (req, res) => { + 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}` + }; + + const result = await EmailService.sendTransactionalEmail( + email, + ETemplates[template], + 'Votre notaire vous envoie un message', + templateVariables + ); + + 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' + }); + } +}); + +app.post('/api/subscribe-to-list', validateEmail, async (req, res) => { + const { email } = req.body; + + 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' + }); + } +}); + +// Automatic retry system +setInterval(() => { + EmailService.retryFailedEmails(); +}, 60000); // Check every minute + +//------------------------------------ End of Email Section ------------------------------------ + app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });