#!/usr/bin/env node /** * CLI pour créer un ou plusieurs établissements N3WT. * * Usage interactif : * node scripts/create-establishment.js * * Usage batch (fichier JSON) : * node scripts/create-establishment.js --file batch.json * * Format du fichier batch : * { * "backendUrl": "http://localhost:8080", // optionnel (fallback : URL_DJANGO dans conf/backend.env) * "apiKey": "TOk3n1234!!", // optionnel (fallback : WEBHOOK_API_KEY dans conf/backend.env) * "establishments": [ * { * "name": "École Dupont", * "address": "1 rue de la Paix, Paris", * "total_capacity": 300, * "establishment_type": [1, 2], // 1=Maternelle, 2=Primaire, 3=Secondaire * "evaluation_frequency": 1, // 1=Trimestre, 2=Semestre, 3=Année * "licence_code": "LIC001", // optionnel * "directeur": { * "email": "directeur@dupont.fr", * "password": "motdepasse123", * "last_name": "Dupont", * "first_name": "Jean" * } * } * ] * } */ const readline = require("readline"); const http = require("http"); const https = require("https"); const fs = require("fs"); const path = require("path"); // ── Lecture de conf/backend.env ─────────────────────────────────────────────── function loadBackendEnv() { const envPath = path.join(__dirname, "..", "conf", "backend.env"); if (!fs.existsSync(envPath)) return; const lines = fs.readFileSync(envPath, "utf8").split("\n"); for (const line of lines) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith("#")) continue; const eqIdx = trimmed.indexOf("="); if (eqIdx === -1) continue; const key = trimmed.slice(0, eqIdx).trim(); let value = trimmed.slice(eqIdx + 1).trim(); if ( (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")) ) { value = value.slice(1, -1); } // Ne pas écraser les variables déjà définies dans l'environnement if (!(key in process.env)) { process.env[key] = value; } } } // ── Helpers readline ────────────────────────────────────────────────────────── let rl; function getRL() { if (!rl) { rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); } return rl; } function ask(question, defaultValue) { const suffix = defaultValue != null && defaultValue !== "" ? ` (${defaultValue})` : ""; return new Promise((resolve) => { getRL().question(`${question}${suffix}: `, (answer) => { resolve( answer.trim() || (defaultValue != null ? String(defaultValue) : ""), ); }); }); } function askRequired(question) { return new Promise((resolve) => { const prompt = () => { getRL().question(`${question}: `, (answer) => { if (!answer.trim()) { console.log(" ⚠ Ce champ est obligatoire."); prompt(); } else { resolve(answer.trim()); } }); }; prompt(); }); } function askChoices(question, choices) { return new Promise((resolve) => { console.log(`\n${question}`); choices.forEach((c, i) => console.log(` ${i + 1}. ${c.label}`)); const prompt = () => { getRL().question( "Choix (numéros séparés par des virgules): ", (answer) => { const nums = answer .split(",") .map((s) => parseInt(s.trim(), 10)) .filter((n) => n >= 1 && n <= choices.length); if (nums.length === 0) { console.log(" ⚠ Sélectionnez au moins une option."); prompt(); } else { resolve(nums.map((n) => choices[n - 1].value)); } }, ); }; prompt(); }); } function askChoice(question, choices, defaultIndex) { return new Promise((resolve) => { console.log(`\n${question}`); choices.forEach((c, i) => console.log( ` ${i + 1}. ${c.label}${i === defaultIndex ? " (défaut)" : ""}`, ), ); const prompt = () => { getRL().question(`Choix (1-${choices.length}): `, (answer) => { if (!answer.trim() && defaultIndex != null) { resolve(choices[defaultIndex].value); return; } const n = parseInt(answer.trim(), 10); if (n >= 1 && n <= choices.length) { resolve(choices[n - 1].value); } else { console.log(" ⚠ Choix invalide."); prompt(); } }); }; prompt(); }); } // ── HTTP ────────────────────────────────────────────────────────────────────── function postJSON(url, data, apiKey) { return new Promise((resolve, reject) => { const parsed = new URL(url); const mod = parsed.protocol === "https:" ? https : http; const body = JSON.stringify(data); const req = mod.request( { hostname: parsed.hostname, port: parsed.port, path: parsed.pathname, method: "POST", headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body), "X-API-Key": apiKey, }, }, (res) => { let raw = ""; res.on("data", (chunk) => (raw += chunk)); res.on("end", () => { try { resolve({ status: res.statusCode, data: JSON.parse(raw) }); } catch { resolve({ status: res.statusCode, data: raw }); } }); }, ); req.on("error", reject); req.write(body); req.end(); }); } // ── Création d'un établissement ─────────────────────────────────────────────── async function createEstablishment(backendUrl, apiKey, payload) { const url = `${backendUrl.replace(/\/$/, "")}/Establishment/establishments`; const res = await postJSON(url, payload, apiKey); return res; } // ── Validation basique d'un enregistrement batch ────────────────────────────── function validateRecord(record, index) { const errors = []; const label = record.name ? `"${record.name}"` : `#${index + 1}`; if (!record.name) errors.push("name manquant"); if (!record.address) errors.push("address manquant"); if (!record.total_capacity) errors.push("total_capacity manquant"); if ( !Array.isArray(record.establishment_type) || record.establishment_type.length === 0 ) errors.push("establishment_type manquant ou vide"); if (!record.directeur) errors.push("directeur manquant"); else { if (!record.directeur.email) errors.push("directeur.email manquant"); if (!record.directeur.password) errors.push("directeur.password manquant"); if (!record.directeur.last_name) errors.push("directeur.last_name manquant"); if (!record.directeur.first_name) errors.push("directeur.first_name manquant"); } if (errors.length > 0) { throw new Error(`Établissement ${label} invalide : ${errors.join(", ")}`); } } // ── Mode batch ──────────────────────────────────────────────────────────────── async function runBatch(filePath) { const absPath = path.resolve(filePath); if (!fs.existsSync(absPath)) { console.error(`❌ Fichier introuvable : ${absPath}`); process.exit(1); } let batch; try { batch = JSON.parse(fs.readFileSync(absPath, "utf8")); } catch (err) { console.error(`❌ Fichier JSON invalide : ${err.message}`); process.exit(1); } const backendUrl = batch.backendUrl || process.env.URL_DJANGO || "http://localhost:8080"; const apiKey = batch.apiKey || process.env.WEBHOOK_API_KEY; if (!apiKey) { console.error( "❌ apiKey manquant dans le fichier batch ou la variable WEBHOOK_API_KEY.", ); process.exit(1); } const establishments = batch.establishments; if (!Array.isArray(establishments) || establishments.length === 0) { console.error("❌ Le fichier batch ne contient aucun établissement."); process.exit(1); } // Validation préalable de tous les enregistrements establishments.forEach((record, i) => validateRecord(record, i)); console.log( `\n📋 Batch : ${establishments.length} établissement(s) à créer sur ${backendUrl}\n`, ); let success = 0; let failure = 0; for (let i = 0; i < establishments.length; i++) { const record = establishments[i]; const label = `[${i + 1}/${establishments.length}] ${record.name}`; process.stdout.write(` ${label} ... `); try { const res = await createEstablishment(backendUrl, apiKey, record); if (res.status === 201) { console.log(`✅ (ID: ${res.data.id})`); success++; } else { console.log(`❌ HTTP ${res.status}`); console.error(` ${JSON.stringify(res.data)}`); failure++; } } catch (err) { console.log(`❌ Erreur réseau : ${err.message}`); failure++; } } console.log(`\nRésultat : ${success} créé(s), ${failure} échec(s).`); if (failure > 0) process.exit(1); } // ── Mode interactif ─────────────────────────────────────────────────────────── async function runInteractive() { console.log("╔══════════════════════════════════════════╗"); console.log("║ Création d'un établissement N3WT ║"); console.log("╚══════════════════════════════════════════╝\n"); const backendUrl = await ask( "URL du backend Django", process.env.URL_DJANGO || "http://localhost:8080", ); const apiKey = await ask( "Clé API webhook (WEBHOOK_API_KEY)", process.env.WEBHOOK_API_KEY || "", ); if (!apiKey) { console.error("❌ La clé API est obligatoire."); getRL().close(); process.exit(1); } // --- Établissement --- console.log("\n── Établissement ──"); const name = await askRequired("Nom de l'établissement"); const address = await askRequired("Adresse"); const totalCapacity = parseInt(await askRequired("Capacité totale"), 10); const establishmentType = await askChoices("Type(s) de structure:", [ { label: "Maternelle", value: 1 }, { label: "Primaire", value: 2 }, { label: "Secondaire", value: 3 }, ]); const evaluationFrequency = await askChoice( "Fréquence d'évaluation:", [ { label: "Trimestre", value: 1 }, { label: "Semestre", value: 2 }, { label: "Année", value: 3 }, ], 0, ); const licenceCode = await ask("Code licence (optionnel)", ""); // --- Directeur (admin) --- console.log("\n── Compte directeur (admin) ──"); const directorEmail = await askRequired("Email du directeur"); const directorPassword = await askRequired("Mot de passe"); const directorLastName = await askRequired("Nom de famille"); const directorFirstName = await askRequired("Prénom"); // --- Récapitulatif --- console.log("\n── Récapitulatif ──"); console.log(` Établissement : ${name}`); console.log(` Adresse : ${address}`); console.log(` Capacité : ${totalCapacity}`); console.log(` Type(s) : ${establishmentType.join(", ")}`); console.log(` Évaluation : ${evaluationFrequency}`); console.log( ` Directeur : ${directorFirstName} ${directorLastName} <${directorEmail}>`, ); const confirm = await ask("\nConfirmer la création ? (o/n)", "o"); if (confirm.toLowerCase() !== "o") { console.log("Annulé."); getRL().close(); process.exit(0); } const payload = { name, address, total_capacity: totalCapacity, establishment_type: establishmentType, evaluation_frequency: evaluationFrequency, ...(licenceCode && { licence_code: licenceCode }), directeur: { email: directorEmail, password: directorPassword, last_name: directorLastName, first_name: directorFirstName, }, }; console.log("\nCréation en cours..."); try { const res = await createEstablishment(backendUrl, apiKey, payload); if (res.status === 201) { console.log("\n✅ Établissement créé avec succès !"); console.log(` ID : ${res.data.id}`); console.log(` Nom : ${res.data.name}`); } else { console.error(`\n❌ Erreur (HTTP ${res.status}):`); console.error(JSON.stringify(res.data, null, 2)); process.exit(1); } } catch (err) { console.error("\n❌ Erreur réseau:", err.message); process.exit(1); } getRL().close(); } // ── Point d'entrée ──────────────────────────────────────────────────────────── async function main() { loadBackendEnv(); const args = process.argv.slice(2); const fileIndex = args.indexOf("--file"); if (fileIndex !== -1) { const filePath = args[fileIndex + 1]; if (!filePath) { console.error( "❌ Argument --file manquant. Usage : --file ", ); process.exit(1); } await runBatch(filePath); } else { await runInteractive(); } } main().catch((err) => { console.error("❌ Erreur inattendue:", err.message); process.exit(1); });