Files
poupoule/server.js
T
2026-09-25 11:02:14 +02:00

584 lines
14 KiB
JavaScript

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);
});