Rename main server just for now

This commit is contained in:
omaroughriss 2025-07-22 17:27:28 +02:00
parent 5db40fe9ca
commit ad26f210ed

624
src/main-server.js Normal file
View File

@ -0,0 +1,624 @@
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
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
});
}
});
//------------------------------------ SMS Section -----------------------------------------
const configSms = {
// OVH config
OVH_APP_KEY: process.env.OVH_APP_KEY,
OVH_APP_SECRET: process.env.OVH_APP_SECRET,
OVH_CONSUMER_KEY: process.env.OVH_CONSUMER_KEY,
OVH_SMS_SERVICE_NAME: process.env.OVH_SMS_SERVICE_NAME,
// SMS Factor config
SMS_FACTOR_TOKEN: process.env.SMS_FACTOR_TOKEN,
PORT: process.env.PORT || 8080
};
// Codes storage
const verificationCodes = new Map();
// Service SMS
class SmsService {
static generateCode() {
return Math.floor(100000 + Math.random() * 900000);
}
// OVH Service
static sendSmsWithOvh(phoneNumber, message) {
return new Promise((resolve, reject) => {
const ovhClient = ovh({
appKey: configSms.OVH_APP_KEY,
appSecret: configSms.OVH_APP_SECRET,
consumerKey: configSms.OVH_CONSUMER_KEY
});
ovhClient.request('POST', `/sms/${configSms.OVH_SMS_SERVICE_NAME}/jobs`, {
message: message,
receivers: [phoneNumber],
senderForResponse: false,
sender: "not.IT Fact",
noStopClause: true
}, (error, result) => {
if (error) {
console.error('Erreur OVH SMS:', error);
resolve({ success: false, error: 'Échec de l\'envoi du SMS via OVH' });
} else {
resolve({ success: true });
}
});
});
}
// SMS Factor Service
static async sendSmsWithSmsFactor(phoneNumber, message) {
try {
const url = new URL('https://api.smsfactor.com/send/simulate');
url.searchParams.append('to', phoneNumber);
url.searchParams.append('text', message);
url.searchParams.append('sender', 'LeCoffre');
url.searchParams.append('token', configSms.SMS_FACTOR_TOKEN);
const response = await fetch(url.toString());
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return { success: true };
} catch (error) {
console.error('Erreur SMS Factor:', error);
return { success: false, error: 'Échec de l\'envoi du SMS via SMS Factor' };
}
}
// Main method
static async sendSms(phoneNumber, message) {
// Try first with OVH
const ovhResult = await this.sendSmsWithOvh(phoneNumber, message);
if (ovhResult.success) {
return ovhResult;
}
// If OVH fails, try with SMS Factor
console.log('OVH SMS failed, trying SMS Factor...');
return await this.sendSmsWithSmsFactor(phoneNumber, message);
}
}
// Phone number validation middleware
const validatePhoneNumber = (req, res, next) => {
const { phoneNumber } = req.body;
if (!phoneNumber) {
return res.status(400).json({
success: false,
message: 'Le numéro de téléphone est requis'
});
}
// Validation basique du format
const phoneRegex = /^\+?[1-9]\d{1,14}$/;
if (!phoneRegex.test(phoneNumber)) {
return res.status(400).json({
success: false,
message: 'Format de numéro de téléphone invalide'
});
}
next();
};
// Routes
app.post('/api/send-code', validatePhoneNumber, async (req, res) => {
const { phoneNumber } = req.body;
try {
// Check if a code already exists and is not expired
const existingVerification = verificationCodes.get(phoneNumber);
if (existingVerification) {
const timeSinceLastSend = Date.now() - existingVerification.timestamp;
if (timeSinceLastSend < 30000) { // 30 secondes
return res.status(429).json({
success: false,
message: 'Veuillez attendre 30 secondes avant de demander un nouveau code'
});
}
}
// Generate a new code
const code = SmsService.generateCode();
// Store the code
verificationCodes.set(phoneNumber, {
code,
timestamp: Date.now(),
attempts: 0
});
// Send the SMS
const message = `Votre code de vérification LeCoffre est : ${code}`;
const result = await SmsService.sendSms(phoneNumber, message);
if (result.success) {
res.json({
success: true,
message: 'Code envoyé avec succès',
});
} else {
res.status(500).json({
success: false,
message: 'Échec de l\'envoi du SMS via les deux fournisseurs'
});
}
} catch (error) {
console.error('Erreur:', error);
res.status(500).json({
success: false,
message: 'Erreur serveur lors de l\'envoi du code'
});
}
});
app.post('/api/verify-code', validatePhoneNumber, (req, res) => {
const { phoneNumber, code } = req.body;
if (!code) {
return res.status(400).json({
success: false,
message: 'Le code est requis'
});
}
const verification = verificationCodes.get(phoneNumber);
if (!verification) {
return res.status(400).json({
success: false,
message: 'Aucun code n\'a été envoyé à ce numéro'
});
}
// Check if the code has not expired (5 minutes)
if (Date.now() - verification.timestamp > 5 * 60 * 1000) {
verificationCodes.delete(phoneNumber);
return res.status(400).json({
success: false,
message: 'Le code a expiré'
});
}
// Check if the code is correct
if (verification.code.toString() === code.toString()) {
verificationCodes.delete(phoneNumber);
res.json({
success: true,
message: 'Code vérifié avec succès'
});
} else {
verification.attempts += 1;
if (verification.attempts >= 3) {
verificationCodes.delete(phoneNumber);
res.status(400).json({
success: false,
message: 'Trop de tentatives. Veuillez demander un nouveau code'
});
} else {
res.status(400).json({
success: false,
message: 'Code incorrect'
});
}
}
});
//------------------------------------ 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}`);
});