const path = require("path"); const express = require("express"); const session = require("express-session"); const bcrypt = require("bcryptjs"); const multer = require("multer"); const { lireJson, ecrireJson, genererId } = require("./lib/donnees"); const app = express(); const port = process.env.PORT || 3000; /* ---------- Configuration ---------- */ app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname, "public"))); app.use("/uploads", express.static(path.join(__dirname, "uploads"))); app.use(session({ secret: "mon-poulailler-secret-local", resave: false, saveUninitialized: false, cookie: { maxAge: 24 * 60 * 60 * 1000 } })); /* Stockage des photos uploadées */ const stockage = multer.diskStorage({ destination: function (req, file, callback) { callback(null, path.join(__dirname, "uploads")); }, filename: function (req, file, callback) { const extension = path.extname(file.originalname); callback(null, genererId() + extension); } }); const upload = multer({ storage: stockage, limits: { fileSize: 5 * 1024 * 1024 }, fileFilter: function (req, file, callback) { if (file.mimetype.startsWith("image/")) { callback(null, true); } else { callback(new Error("Seules les images sont acceptées")); } } }); /* ---------- Middlewares d'accès ---------- */ function exigerConnexion(req, res, next) { if (!req.session.utilisateur) { return res.status(401).json({ erreur: "Vous devez être connecté" }); } next(); } function exigerAdmin(req, res, next) { if (!req.session.utilisateur || req.session.utilisateur.profil !== "admin") { return res.status(403).json({ erreur: "Réservé à l'administrateur" }); } next(); } /* ---------- Authentification ---------- */ app.get("/api/session", function (req, res) { if (req.session.utilisateur) { return res.json({ connecte: true, utilisateur: req.session.utilisateur }); } res.json({ connecte: false }); }); app.post("/api/connexion", function (req, res) { const login = req.body.login; const motDePasse = req.body.motDePasse; const utilisateurs = lireJson("utilisateurs.json", []); const utilisateur = utilisateurs.find(function (u) { return u.login === login; }); if (!utilisateur) { return res.status(401).json({ erreur: "Identifiant ou mot de passe incorrect" }); } const motDePasseOk = bcrypt.compareSync(motDePasse, utilisateur.motDePasseHash); if (!motDePasseOk) { return res.status(401).json({ erreur: "Identifiant ou mot de passe incorrect" }); } req.session.utilisateur = { id: utilisateur.id, login: utilisateur.login, profil: utilisateur.profil }; res.json({ message: "Connexion réussie", utilisateur: req.session.utilisateur }); }); app.post("/api/deconnexion", function (req, res) { req.session.destroy(function () { res.json({ message: "Déconnexion réussie" }); }); }); app.post("/api/utilisateurs", exigerConnexion, exigerAdmin, function (req, res) { const login = req.body.login; const motDePasse = req.body.motDePasse; const profil = req.body.profil || "utilisateur"; if (!login || !motDePasse) { return res.status(400).json({ erreur: "Login et mot de passe obligatoires" }); } if (profil !== "admin" && profil !== "utilisateur") { return res.status(400).json({ erreur: "Profil invalide" }); } const utilisateurs = lireJson("utilisateurs.json", []); const existeDeja = utilisateurs.some(function (u) { return u.login === login; }); if (existeDeja) { return res.status(400).json({ erreur: "Ce login existe déjà" }); } const nouvelUtilisateur = { id: genererId(), login: login, motDePasseHash: bcrypt.hashSync(motDePasse, 10), profil: profil }; utilisateurs.push(nouvelUtilisateur); ecrireJson("utilisateurs.json", utilisateurs); res.status(201).json({ message: "Compte créé", utilisateur: { id: nouvelUtilisateur.id, login: nouvelUtilisateur.login, profil: nouvelUtilisateur.profil } }); }); app.get("/api/utilisateurs", exigerConnexion, exigerAdmin, function (req, res) { const utilisateurs = lireJson("utilisateurs.json", []); const liste = utilisateurs.map(function (u) { return { id: u.id, login: u.login, profil: u.profil }; }); res.json(liste); }); /* ---------- Animaux ---------- */ app.get("/api/animaux", exigerConnexion, function (req, res) { const actifsSeulement = req.query.actifs === "oui"; const archivesSeulement = req.query.archives === "oui"; let animaux = lireJson("animaux.json", []); if (actifsSeulement) { animaux = animaux.filter(function (a) { return a.actif === true; }); } if (archivesSeulement) { animaux = animaux.filter(function (a) { return a.actif === false; }); } res.json(animaux); }); app.get("/api/animaux/:id", exigerConnexion, function (req, res) { const animaux = lireJson("animaux.json", []); const animal = animaux.find(function (a) { return a.id === req.params.id; }); if (!animal) { return res.status(404).json({ erreur: "Animal introuvable" }); } const periodesPonte = lireJson("periodesPonte.json", []).filter(function (p) { return p.idAnimal === animal.id; }); const periodesCouvage = lireJson("periodesCouvage.json", []).filter(function (p) { return p.idAnimal === animal.id; }); res.json({ animal: animal, periodesPonte: periodesPonte, periodesCouvage: periodesCouvage }); }); app.post("/api/animaux", exigerConnexion, upload.single("photo"), function (req, res) { const type = req.body.type; const couleurBague = req.body.couleurBague || ""; const numeroBague = req.body.numeroBague || ""; const dateNaissance = req.body.dateNaissance || ""; const etatSante = req.body.etatSante || "bon"; if (type !== "poule" && type !== "coq") { return res.status(400).json({ erreur: "Le type doit être poule ou coq" }); } const animaux = lireJson("animaux.json", []); const nouvelAnimal = { id: genererId(), type: type, photo: req.file ? "/uploads/" + req.file.filename : "", couleurBague: couleurBague, numeroBague: numeroBague, dateNaissance: dateNaissance, etatSante: etatSante, dateFinDeVie: null, actif: true }; animaux.push(nouvelAnimal); ecrireJson("animaux.json", animaux); res.status(201).json(nouvelAnimal); }); app.put("/api/animaux/:id", exigerConnexion, upload.single("photo"), function (req, res) { const animaux = lireJson("animaux.json", []); const index = animaux.findIndex(function (a) { return a.id === req.params.id; }); if (index === -1) { return res.status(404).json({ erreur: "Animal introuvable" }); } const animal = animaux[index]; if (req.body.type === "poule" || req.body.type === "coq") { animal.type = req.body.type; } if (req.body.couleurBague !== undefined) { animal.couleurBague = req.body.couleurBague; } if (req.body.numeroBague !== undefined) { animal.numeroBague = req.body.numeroBague; } if (req.body.dateNaissance !== undefined) { animal.dateNaissance = req.body.dateNaissance; } if (req.body.etatSante !== undefined) { animal.etatSante = req.body.etatSante; } if (req.file) { animal.photo = "/uploads/" + req.file.filename; } animaux[index] = animal; ecrireJson("animaux.json", animaux); res.json(animal); }); app.post("/api/animaux/:id/deces", exigerConnexion, function (req, res) { const animaux = lireJson("animaux.json", []); const index = animaux.findIndex(function (a) { return a.id === req.params.id; }); if (index === -1) { return res.status(404).json({ erreur: "Animal introuvable" }); } const animal = animaux[index]; animal.actif = false; animal.dateFinDeVie = req.body.dateFinDeVie || new Date().toISOString().slice(0, 10); /* Clôturer les périodes en cours */ const periodesPonte = lireJson("periodesPonte.json", []); periodesPonte.forEach(function (periode) { if (periode.idAnimal === animal.id && !periode.dateFin) { periode.dateFin = animal.dateFinDeVie; } }); ecrireJson("periodesPonte.json", periodesPonte); const periodesCouvage = lireJson("periodesCouvage.json", []); periodesCouvage.forEach(function (periode) { if (periode.idAnimal === animal.id && !periode.dateFin) { periode.dateFin = animal.dateFinDeVie; } }); ecrireJson("periodesCouvage.json", periodesCouvage); animaux[index] = animal; ecrireJson("animaux.json", animaux); res.json(animal); }); /* ---------- Périodes de ponte ---------- */ app.post("/api/periodes-ponte", exigerConnexion, function (req, res) { const idAnimal = req.body.idAnimal; const dateDebut = req.body.dateDebut; const dateFin = req.body.dateFin || null; const remarque = req.body.remarque || ""; if (!idAnimal || !dateDebut) { return res.status(400).json({ erreur: "idAnimal et dateDebut sont obligatoires" }); } const animaux = lireJson("animaux.json", []); const animal = animaux.find(function (a) { return a.id === idAnimal; }); if (!animal) { return res.status(404).json({ erreur: "Animal introuvable" }); } if (animal.type !== "poule") { return res.status(400).json({ erreur: "La ponte concerne uniquement les poules" }); } const periodes = lireJson("periodesPonte.json", []); const nouvellePeriode = { id: genererId(), idAnimal: idAnimal, dateDebut: dateDebut, dateFin: dateFin, remarque: remarque }; periodes.push(nouvellePeriode); ecrireJson("periodesPonte.json", periodes); res.status(201).json(nouvellePeriode); }); app.put("/api/periodes-ponte/:id", exigerConnexion, function (req, res) { const periodes = lireJson("periodesPonte.json", []); const index = periodes.findIndex(function (p) { return p.id === req.params.id; }); if (index === -1) { return res.status(404).json({ erreur: "Période introuvable" }); } if (req.body.dateDebut !== undefined) { periodes[index].dateDebut = req.body.dateDebut; } if (req.body.dateFin !== undefined) { periodes[index].dateFin = req.body.dateFin || null; } if (req.body.remarque !== undefined) { periodes[index].remarque = req.body.remarque; } ecrireJson("periodesPonte.json", periodes); res.json(periodes[index]); }); /* ---------- Périodes de couvage ---------- */ app.post("/api/periodes-couvage", exigerConnexion, function (req, res) { const idAnimal = req.body.idAnimal; const dateDebut = req.body.dateDebut; const dateFin = req.body.dateFin || null; const remarque = req.body.remarque || ""; if (!idAnimal || !dateDebut) { return res.status(400).json({ erreur: "idAnimal et dateDebut sont obligatoires" }); } const animaux = lireJson("animaux.json", []); const animal = animaux.find(function (a) { return a.id === idAnimal; }); if (!animal) { return res.status(404).json({ erreur: "Animal introuvable" }); } if (animal.type !== "poule") { return res.status(400).json({ erreur: "Le couvage concerne uniquement les poules" }); } const periodes = lireJson("periodesCouvage.json", []); const nouvellePeriode = { id: genererId(), idAnimal: idAnimal, dateDebut: dateDebut, dateFin: dateFin, remarque: remarque }; periodes.push(nouvellePeriode); ecrireJson("periodesCouvage.json", periodes); res.status(201).json(nouvellePeriode); }); app.put("/api/periodes-couvage/:id", exigerConnexion, function (req, res) { const periodes = lireJson("periodesCouvage.json", []); const index = periodes.findIndex(function (p) { return p.id === req.params.id; }); if (index === -1) { return res.status(404).json({ erreur: "Période introuvable" }); } if (req.body.dateDebut !== undefined) { periodes[index].dateDebut = req.body.dateDebut; } if (req.body.dateFin !== undefined) { periodes[index].dateFin = req.body.dateFin || null; } if (req.body.remarque !== undefined) { periodes[index].remarque = req.body.remarque; } ecrireJson("periodesCouvage.json", periodes); res.json(periodes[index]); }); /* ---------- Statistiques ---------- */ app.get("/api/statistiques", exigerConnexion, function (req, res) { const animaux = lireJson("animaux.json", []); const actifs = animaux.filter(function (a) { return a.actif === true; }); const poulesActives = actifs.filter(function (a) { return a.type === "poule"; }); const coqsActifs = actifs.filter(function (a) { return a.type === "coq"; }); const periodesPonte = lireJson("periodesPonte.json", []); const periodesCouvage = lireJson("periodesCouvage.json", []); const idsPoules = poulesActives.map(function (p) { return p.id; }); const enPonte = idsPoules.filter(function (id) { return periodesPonte.some(function (periode) { return periode.idAnimal === id && !periode.dateFin; }); }); const enCouvage = idsPoules.filter(function (id) { return periodesCouvage.some(function (periode) { return periode.idAnimal === id && !periode.dateFin; }); }); res.json({ nombrePoulesActives: poulesActives.length, nombreCoqsActifs: coqsActifs.length, nombreTotalActifs: actifs.length, nombreEnCouvage: enCouvage.length, nombreEnPonte: enPonte.length }); }); /* ---------- Démarrage ---------- */ app.listen(port, function () { console.log("Mon Poulailler écoute sur http://localhost:" + port); });