total_1ère

This commit is contained in:
2026-07-20 16:33:04 +02:00
parent dd49ba357b
commit d49cf88d99
275 changed files with 63607 additions and 5599 deletions

View File

@@ -0,0 +1,374 @@
/////////////////////////////////////////////////
// APPELS DES MODULES
/////////////////////////////////////////////////
const path = require("path");
// CALCUL DE DATE
const moment = require('moment');
// uuid
const { v4: uuidv4 } = require('uuid');
const mysql = require('mysql2');
const mysql_promise = require('mysql2/promise');
const fetch = require('node-fetch');
const fs = require('fs/promises');
const nodemailer = require('nodemailer');
/////////////////////////////////////////////////
// APPELS DES MODULES PERSOS
/////////////////////////////////////////////////
// loggings
const logger = require("../public/js/app/logger_adhesion");
// DB
const { connect_DB_promise } = require('../modules/db/databaseconnect');
////////////////////////////////////////////////////
// FILE NAME
////////////////////////////////////////////////////
const filename = path.parse(__filename).base;
const runSync_prof_strapi = async function(req, res) {
// LOG - Récupération du numéro de session web
// en mode CRON il n'y a pas de session web, donc pas de req.session, donc on met "cron" à la place
const sid = req?.session?.id || "cron";
logger.info(`sid=${sid}`, { numsession: sid, label: filename });
logger.info("======================================", { numsession: sid, label: filename });
logger.info("======== FUNCTION sync_prof_strapi ==========", { numsession: sid, label: filename });
logger.info("======================================", { numsession: sid, label: filename });
////////////////////////////////////////////////////
// VARIABLES
////////////////////////////////////////////////////
// On récupère les variables de ENV
const {
DOMAIN_STRAPI,
strapi_token,
db_connection_host,
db_connection_port,
db_connection_user,
db_connection_password,
db_connection_database
} = process.env;
logger.info(`DOMAIN_STRAPI=${DOMAIN_STRAPI}, db_connection_host=${db_connection_host}`, { numsession: sid, label: filename });
////////////////////////////////////////////////////
// CONNEXION DATABASE
////////////////////////////////////////////////////
// Initialisation de la connexion avec la BD
const db_connection_promise = await mysql_promise.createConnection({
host: db_connection_host,
port: db_connection_port,
user: db_connection_user,
password: db_connection_password,
database: db_connection_database
});
//console.log("db_connection_promise = ", db_connection_promise);
////////////////////////////////////////////////////
// FONCTIONS
////////////////////////////////////////////////////
// Récupération de la lat, long et région, de toutes les villes de France et Dom-TOM, depuis le json
// pour l'ajout de la lat, long et region dans les données de strapi pour le prof
let CP_MAP = null;
async function loadCpMap(filePath = "src/data/cp_region_lat_long.json") {
if (CP_MAP) return CP_MAP;
const buf = await fs.readFile(filePath, "utf8");
CP_MAP = JSON.parse(buf);
return CP_MAP;
}
// ==== GEO helpers ====
// Fonctions pour normaliser le code postal saisie dans le formulaire par ladhérent
function normalizeCp(cp) {
if (!cp) return null;
const s = String(cp).trim().replace(/\s+/g, "");
return /^\d{5}$/.test(s) ? s : null;
}
function pickProfPostalCode(cp) {
return (
normalizeCp(cp) || null
);
}
// helper pour convertir en nombre sûr (gère "48,85" et strings)
const toNum = (v) => {
if (v == null) return null;
const n = (typeof v === "number") ? v : parseFloat(String(v).replace(",", "."));
return Number.isFinite(n) ? n : null;
};
// ==== FONCTION D'AJOUT DES PROFS DANS STRAPI ====
const upsert = async (payload) => {
logger.info("======== FUNCTION upsert ==========", { numsession: sid, label: filename });
// on cherche les professeurs dont adherent_mail est égal à payload.data.adherent_mail. Si pas demail, on met "".
// URLSearchParams classe native de JavaScript (dans Node.js et dans les navigateurs) qui sert à construire et manipuler des chaînes de requête (?clé=valeur&clé2=valeur2)
// ex de sortie q = filters%5Badherent_mail%5D%5B%24eq%5D=julie%40test.com
logger.info(`search = ${payload.data.adherent_mail}`, { numsession: sid, label: filename });
const q = new URLSearchParams({ "filters[adherent_mail][$eq]": payload.data.adherent_mail || "" });
// URL d'interogration strapi
const getUrl = `${DOMAIN_STRAPI}/api/adherent-professeurs?${q.toString()}`;
// Recherche dans Strapi
const r0 = await fetch(getUrl, { headers: { Authorization: `Bearer ${strapi_token}` }});
// lis le corps de la réponse HTTP et convertis-le depuis du JSON en objet JavaScript utilisable
const j0 = await r0.json();
const found = j0?.data?.[0];
logger.info(`found = ${found?.attributes?.adherent_mail}`, { numsession: sid, label: filename });
//console.log("found = ", found);
if (found?.id) {
// déjà présent → on NE LÈVE PAS derreur, on signale juste un skip
return { action: "skip_existing", id: found.id };
} else {
const postUrl = `${DOMAIN_STRAPI}/api/adherent-professeurs`;
logger.info(`postUrl = ${postUrl}`, { numsession: sid, label: filename });
const r = await fetch(postUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${strapi_token}`
},
body: JSON.stringify(payload)
});
if (!r.ok) {
const txt = await r.text();
throw new Error(`POST ${postUrl} -> ${r.status} ${txt}`);
}
const j = await r.json();
return { action: "create", id: j?.data?.id };
}
};
////////////////////////////////////////////////////
// LANCEMENT DES FONCTIONS
////////////////////////////////////////////////////
try {
// ==== AJOUT DES PROFS DANS STRAPI ====
let creates = 0, errors = 0, attempted = 0, skipped_existing = 0;
// ==== RÉCUPÉRATION DES DONNÉES SUR LES PROFS DANS LA DB ADUZUKI (SQL) ====
const [rows] = await db_connection_promise.query(`
SELECT
ID,
association_suzuki,
adherent_nom,
adherent_prenom,
adherent_mail,
adherent_telephone_mobile,
adherent_prof_instrument1,
adherent_prof_instrument1_esa,
adherent_prof_instrument2,
adherent_prof_instrument2_esa,
adherent_prof_sece,
adherent_prof_ecole1,
adherent_prof_ecole1_mail,
adherent_prof_ecole1_web,
adherent_prof_ecole1_cp,
adherent_prof_ecole1_ville,
adherent_prof_ecole2,
adherent_prof_ecole2_mail,
adherent_prof_ecole2_web,
adherent_prof_ecole2_cp,
adherent_prof_ecole2_ville,
adherent_prof_ecole3,
adherent_prof_ecole3_mail,
adherent_prof_ecole3_web,
adherent_prof_ecole3_adresse,
adherent_prof_ecole3_cp,
adherent_prof_ecole3_ville
FROM adherent_professeur
`);
//console.log('rows = ', rows);
// Lancement de la récupération des lat, long et régions depuis le json
const cpMap = await loadCpMap("src/data/cp_region_lat_long.json");
//console.log("CP_MAP = ", CP_MAP);
// TRAITEMENT DE CHAQUE PROF DE LA BD POUR L'AJOUTER DANS STRAPI
for (const r of rows) {
// 3.1 choisir le CP prioritaire
console.log(`r.adherent_prof_ecole1_cp = ${r.adherent_prof_ecole1_cp}`);
const cp_1 = pickProfPostalCode(r.adherent_prof_ecole1_cp);
const cp_2 = pickProfPostalCode(r.adherent_prof_ecole2_cp);
const cp_3 = pickProfPostalCode(r.adherent_prof_ecole3_cp);
console.log(`cp_1 = ${cp_1}, cp_2 = ${cp_2} `);
let geo_lat_1 = null, geo_lng_1 = null, geo_region_1 = null;
let geo_lat_2 = null, geo_lng_2 = null, geo_region_2 = null;
let geo_lat_3 = null, geo_lng_3 = null, geo_region_3 = null;
console.log("cpMap[cp_1] =", cpMap[cp_1]);
if (cp_1 && cpMap[cp_1]) {
console.log("cpMap[cp_1] =", cpMap[cp_1]);
const meta = cpMap[cp_1];
// tolérance: on ne renseigne que si lat/lng sont présents
geo_lat_1 = toNum(meta.lat);
geo_lng_1 = toNum(meta.lng);
geo_region_1 = meta.region ? String(meta.region) : null;
}
console.log("cpMap[cp_2] =", cpMap[cp_2]);
if (cp_2 && cpMap[cp_2]) {
console.log("cpMap[cp_2] =", cpMap[cp_2]);
const meta = cpMap[cp_2];
// tolérance: on ne renseigne que si lat/lng sont présents
geo_lat_2 = toNum(meta.lat);
geo_lng_2 = toNum(meta.lng);
geo_region_2 = meta.region ? String(meta.region) : null;
}
if (cp_3 && cpMap[cp_2]) {
const meta = cpMap[cp_3];
// tolérance: on ne renseigne que si lat/lng sont présents
geo_lat_3 = toNum(meta.lat);
geo_lng_3 = toNum(meta.lng);
geo_region_3 = meta.region ? String(meta.region) : null;
}
// 3.2 payload de base
console.log("geo_lat_2 =", geo_lat_2);
const data = {
association_suzuki: r.association_suzuki?.trim() || null,
adherent_nom: r.adherent_nom?.trim() || null,
adherent_prenom: r.adherent_prenom?.trim() || null,
adherent_mail: r.adherent_mail?.toString().trim().toLowerCase() || null,
adherent_telephone_mobile: r.adherent_telephone_mobile?.trim() || null,
adherent_prof_instrument1: r.adherent_prof_instrument1?.trim() || null,
adherent_prof_instrument1_niveau: r.adherent_prof_instrument1_esa?.trim() || null,
adherent_prof_instrument2: r.adherent_prof_instrument2?.trim() || null,
adherent_prof_instrument2_niveau: r.adherent_prof_instrument2_esa?.trim() || null,
adherent_prof_sece: r.adherent_prof_sece?.trim() || null,
adherent_prof_ecole1: r.adherent_prof_ecole1?.trim() || null,
adherent_prof_ecole1_mail: r.adherent_prof_ecole1_mail?.trim() || null,
adherent_prof_ecole1_web: r.adherent_prof_ecole1_web?.trim() || null,
adherent_prof_ecole1_cp: r.adherent_prof_ecole1_cp?.trim() || null,
adherent_prof_ecole1_ville: r.adherent_prof_ecole1_ville?.trim() || null,
adherent_prof_ecole2: r.adherent_prof_ecole2?.trim() || null,
adherent_prof_ecole2_mail: r.adherent_prof_ecole2_mail?.trim() || null,
adherent_prof_ecole2_web: r.adherent_prof_ecole2_web?.trim() || null,
adherent_prof_ecole2_cp: r.adherent_prof_ecole2_cp?.trim() || null,
adherent_prof_ecole2_ville: r.adherent_prof_ecole2_ville?.trim() || null,
adherent_prof_ecole3: r.adherent_prof_ecole3?.trim() || null,
adherent_prof_ecole3_mail: r.adherent_prof_ecole3_mail?.trim() || null,
adherent_prof_ecole3_web: r.adherent_prof_ecole3_web?.trim() || null,
adherent_prof_ecole3_adresse: r.adherent_prof_ecole3_adresse?.trim() || null,
adherent_prof_ecole3_cp: r.adherent_prof_ecole3_cp?.trim() || null,
adherent_prof_ecole3_ville: r.adherent_prof_ecole3_ville?.trim() || null,
adherent_prof_ecole3_ville: r.adherent_prof_ecole3_ville?.trim() || null,
Saison: "2025/2026",
// >>> Injecte UNIQUEMENT des scalaires, si valides
...(geo_region_1 ? { adherent_prof_ecole1_region: geo_region_1 } : {}),
...(geo_lat_1 != null ? { adherent_prof_ecole1_lat: geo_lat_1 } : {}),
...(geo_lng_1 != null ? { adherent_prof_ecole1_long: geo_lng_1 } : {}),
...(geo_region_2 ? { adherent_prof_ecole2_region: geo_region_2 } : {}),
...(geo_lat_2 != null ? { adherent_prof_ecole2_lat: geo_lat_2 } : {}),
...(geo_lng_2 != null ? { adherent_prof_ecole2_long: geo_lng_2 } : {}),
...(geo_region_3 ? { adherent_prof_ecole3_region: geo_region_3 } : {}),
...(geo_lat_3 != null ? { adherent_prof_ecole3_lat: geo_lat_3 } : {}),
...(geo_lng_3 != null ? { adherent_prof_ecole3_long: geo_lng_3 } : {})
};
console.log("data", data);
// champs obligatoires minimum
if (!data.adherent_mail || !data.adherent_nom || !data.adherent_prenom) {
console.warn(`skip: mail/nom/prenom manquants`);
continue;
}
try {
attempted++;
const res = await upsert({ data });
if (res.action === "create") creates++;
else if (res.action === "skip_existing") skipped_existing++;
} catch (e) {
errors++;
console.error(`ETL error ->`, e.message);
}
}
await db_connection_promise.end();
logger.info(`Done. attempted=${attempted} skipped_existing=${skipped_existing} created=${creates} errors=${errors}`, { numsession: sid, label: filename });
///////////////////////////////////////////////////////////////////////
// ENVOIE MAIL APRES EXECUTION
logger.info("MAIL MAJ ANNUAIRE PROF ENVOI AVANT", { numsession: sid });
var transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.nodemailer_user,
pass: process.env.nodemailer_pass
}
});
var mail_objet = "ADUZUKI - CRON MAJ ANNUAIRE PROF";
mail_html = "<p>Pour l'administrateur de l'application ADUZUKI,</p><br><p>Ceci est le mail d'envoi pour confirmer l'exécution du Webhook de mise à jour de l'annuaire des profs qui se fait la nuit à 4h.</p>";
mail_html += "<p>- nombre de <strong>professeurs à ajouter</strong> : ";
mail_html += attempted;
mail_html += "</p>";
mail_html += "<p>- nombre de <strong>professeurs créés</strong> : ";
mail_html += creates;
mail_html += "</p>";
mail_html += "<p>- nombre de <strong>professeurs non ajoutés</strong> pour cause probable de doublon : ";
mail_html += skipped_existing;
mail_html += "</p>";
mail_html += "<p>- nombre d'<strong>erreurs</strong> : ";
mail_html += errors;
mail_html += "</p>";
mail_html += "<br><p>Julie Chaumard</p><p>Développeuse web de l'agence Parisweb.art</p>";
var mailOptions = {
from: 'afps.inscription@gmail.com',
to: "projet@parisweb.art",
subject: mail_objet,
html: mail_html
};
transporter.sendMail(mailOptions, function(error, info){
if (error) {
logger.info("error" + error, { numsession: sid });
} else {
logger.info("Email envoyé: " + info.response, { numsession: sid });
}
});
logger.info("MAIL MAJ ANNUAIRE PROF ENVOI APRES", { numsession: sid });
// --- Réponse HTTP IMMÉDIATE
const retour = {
ok: true,
attempted,
creates,
skipped_existing,
errors
};
logger.info("sync_prof_strapi terminé", { label: filename, numsession: sid });
res.status(200).json(retour);
} catch (e) {
logger.error("sync_prof_strapi FAILED", {
label: filename,
numsession: sid,
message: e.message,
stack: e.stack
});
// Réponse derreur JSON claire
return res.status(500).json({ ok: false, error: e.message });
}
}
/////////////////////////////////////////////////
// EXPORT DE MODULE POUR QU'IL SOIT LU DANS LES AUTRES FICHIERS JS
/////////////////////////////////////////////////
module.exports = {
runSync_prof_strapi
}