Début du projet
This commit is contained in:
commit
43b1f166d7
28 files changed
+4284
No files matched your search
@@ -0,0 +1,583 @@
|
||||
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);
|
||||
});
|
||||