Créer un schéma de base de données implique de définir la structure des tables (ou collections dans le cas de MongoDB) et leurs relations. Voici un schéma conceptuel pour une base de données MongoDB qui pourrait être utilisé pour une application de gestion de recettes comme décrit dans votre exercice.

 

 

Structure du Projet

La première chose à faire pour mettre en place votre  code c'est de créer une structure de répertoires standard pour ranger vos fichiers.

Voici un exemple standard qui peut être prit en exemple.

my-recipe-app/
|-- controllers/
|   |-- authController.js
|   |-- recipeController.js
|   |-- userController.js
|-- models/
|   |-- userModel.js
|   |-- recipeModel.js
|   |-- ingredientModel.js
|-- routes/
|   |-- authRoutes.js
|   |-- recipeRoutes.js
|   |-- userRoutes.js
|-- middleware/
|   |-- authMiddleware.js
|-- views/
|-- utils/
|-- app.js
|-- server.js

Collections MongoDB

Avec Node.js et MongoDB, la création des modèles qui représentent les documents JSON stockés dans MongoDB est une étape cruciale. Dans MongoDB, on recommande souvent d'utiliser le moins de collections possible, une approche qui se distingue des bases de données relationnelles où la normalisation (c'est-à-dire, la division des données en plusieurs tables reliées) est la norme. Voici quelques points clés à considérer :

1. Pourquoi Minimiser le Nombre de Collections ?

  • Performance : Moins il y a de collections, moins il y a de jointures (ou de 'populations' dans le cas de Mongoose) nécessaires, ce qui peut améliorer les performances de la base de données.
  • Maintenance : Une structure plus simple avec moins de collections est généralement plus facile à maintenir.
  • Dénormalisation : MongoDB est optimisé pour le stockage de documents dénormalisés, où les données liées sont souvent stockées ensemble.

2. Créer des Modèles pour MongoDB

  • Définir des Schémas : Utilisez des schémas pour définir la structure des documents dans chaque collection. Par exemple, le schéma pour Users inclut des champs tels que username et password.
  • Modèles avec Mongoose : Dans Node.js, utilisez Mongoose pour créer des modèles basés sur ces schémas. Ces modèles sont des représentations programmables de vos documents MongoDB.

3. Exemple de Modèles dans Votre Contexte

  • Collection Users : Stocke des informations sur les utilisateurs, y compris leur rôle et leurs informations d'authentification.
  • Collection Recipes : Contient des recettes, y compris des détails comme les ingrédients et les étapes de préparation. Vous pouvez inclure une référence à Users pour l'auteur de la recette.
  • Collection Ingredients : Détails sur les ingrédients, y compris des informations nutritionnelles. Les recettes peuvent référencer ces ingrédients.

4. Considérations sur la Structure de Données

  • Références vs Sous-documents : Choisissez entre stocker des références à d'autres documents ou inclure des sous-documents directement. Par exemple, les ingrédients d'une recette peuvent être des sous-documents au sein de la collection Recipes.
  • Taille du Document : Gardez à l'esprit la limite de taille des documents dans MongoDB (16 MB). Si vos documents deviennent très volumineux, envisagez de les diviser.

5. Bonnes Pratiques

  • Sécurité des Données : Assurez-vous de stocker les mots de passe sous une forme hashée.
  • Indexation : Utilisez des index pour améliorer les performances des requêtes, en particulier sur des champs fréquemment recherchés ou triés.
  • Modularité : Gardez votre code propre et modulaire. Chaque modèle doit être dans son propre fichier pour faciliter la maintenance et la compréhension.

 

Avec Node.js et MongoDB, il est essentiel de comprendre comment structurer et interagir avec les données. Vous avez décrit trois collections MongoDB : Users, Recipes, et Ingredients. Voici une explication de la manière dont vous pouvez les utiliser et les créer :

1. Comprendre le Modèle de Données

  • Collections MongoDB : MongoDB est une base de données NoSQL orientée document. Ici, les collections Users, Recipes, et Ingredients représentent différents types de données.
  • Schémas : Chaque collection a un schéma qui définit la structure des données. Par exemple, la collection Users a des champs comme username, email, et role.

2. Création des Modèles avec Mongoose

  • Mongoose : C'est une bibliothèque ODM (Object Data Modeling) pour MongoDB et Node.js. Elle permet de définir des schémas avec des types de données stricts, des validations, et des méthodes d'instance.
  • Définir des Schémas : Vous créez des schémas en définissant la structure de vos collections. Par exemple, le schéma User aura des champs comme username et password.
  • Modèles Mongoose : Après avoir défini vos schémas, vous les compilez en modèles. Ces modèles sont utilisés pour interagir avec la base de données.

3. Relations entre les Collections

  • Références : MongoDB permet de créer des relations entre différentes collections. Par exemple, dans Recipes, le champ author peut être une référence à un document dans la collection Users.
  • Population : Mongoose offre la méthode populate pour résoudre automatiquement ces références en objets complets lors de la récupération des données.

4. Bonnes Pratiques

  • Stockage de Mots de Passe : Les mots de passe doivent être stockés sous forme hashée pour la sécurité.
  • Normalisation vs Dénormalisation : MongoDB encourage la dénormalisation, mais il est important de trouver un équilibre. Trop de dénormalisation peut mener à des données redondantes et difficiles à maintenir.

5. Interaction avec Node.js

  • Connexion à MongoDB : Utilisez Mongoose pour vous connecter à votre base de données MongoDB depuis votre application Node.js.
  • CRUD Operations : Avec les modèles Mongoose, vous pouvez facilement effectuer des opérations CRUD (Create, Read, Update, Delete) sur vos collections.

6. Exemple de Code

Voici un exemple simplifié de la manière de définir un modèle avec Mongoose en Node.js :

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  username: String,
  // autres champs...
});

const User = mongoose.model('User', userSchema);

// Utilisation du modèle User pour créer un nouvel utilisateur
const newUser = new User({ username: 'johndoe' });
newUser.save();

En résumé, la création de modèles dans MongoDB implique de définir des schémas avec Mongoose, de les compiler en modèles, et ensuite d'utiliser ces modèles pour interagir avec la base de données dans une application Node.js. Il est également crucial de comprendre comment les données sont reliées entre différentes collections et de suivre les meilleures pratiques pour la sécurité et la maintenance des données.

Users Collection

Users {
  _id: ObjectId,
  username: String,
  email: String,
  password: String, // Stocké sous forme de hash
  role: String, // Valeurs possibles: ['user', 'admin', 'author']
  createdAt: Date,
  updatedAt: Date
}

Recipes Collection

Recipes {
  _id: ObjectId,
  title: String,
  description: String,
  ingredients: [
    {
      name: String,
      quantity: String
    }
  ],
  steps: [String],
  prepTime: Number,
  images: [String], // Liens vers les images stockées
  comments: [
    {
      user: ObjectId, // Référence à l'ID de l'utilisateur
      text: String,
      rating: Number,
      createdAt: Date
    }
  ],
  category: String, // Valeurs possibles: ['vegetarian', 'dessert', 'quick', etc.]
  author: ObjectId, // Référence à l'ID de l'utilisateur
  createdAt: Date,
  updatedAt: Date
}

Ingredients Collection

J'ai ajouter une collection pour Ingredients pour montrer le principe de populate, dans ce projet nous aurions pu ajouter un tableau ingredients[] dans la collection Recipes.

Ingredients {
  _id: ObjectId,
  name: String,
  nutritionInfo: {
    calories: Number,
    protein: Number,
    fat: Number,
    // Autres informations nutritionnelles
  },
  recipes: [ObjectId], // Liste des ID de recettes utilisant cet ingrédient
  createdAt: Date,
  updatedAt: Date
}

Relations

  • Chaque recette est créée par un utilisateur et peut avoir plusieurs commentaires. Chaque commentaire est posté par un utilisateur.
  • Les ingrédients sont utilisés dans plusieurs recettes. Cette relation est représentée dans la collection des ingrédients par un tableau d'ID de recettes, permettant de savoir dans quelles recettes l'ingrédient est utilisé.

Schéma Relationnel

Si vous deviez représenter cela sous forme de schéma relationnel pour une base de données SQL, cela pourrait ressembler à ceci :

Tables SQL

Users Table

Column Name Data Type Constraints
id INT PRIMARY KEY, AUTO_INCREMENT
username VARCHAR NOT NULL
email VARCHAR NOT NULL, UNIQUE
password VARCHAR NOT NULL
role ENUM NOT NULL
createdAt TIMESTAMP  
updatedAt TIMESTAMP  

 

Recipes Table

Column Name Data Type Constraints
id INT PRIMARY KEY, AUTO_INCREMENT
title VARCHAR NOT NULL
description TEXT  
prepTime INT  
category ENUM  
authorId INT FOREIGN KEY REFERENCES Users(id)
createdAt TIMESTAMP  
updatedAt TIMESTAMP  

 

Ingredients Table

Column Name Data Type Constraints
id INT PRIMARY KEY, AUTO_INCREMENT
name VARCHAR NOT NULL
calories INT  
protein INT  
fat INT  
createdAt TIMESTAMP  
updatedAt TIMESTAMP  

 

RecipeIngredients Table (Junction Table for many-to-many relationship)

Column Name Data Type Constraints
recipeId INT FOREIGN KEY REFERENCES Recipes(id)
ingredientId INT FOREIGN KEY REFERENCES Ingredients(id)
quantity VARCHAR NOT NULL

 

Comments Table

Column Name Data Type Constraints
id INT PRIMARY KEY, AUTO_INCREMENT
recipeId INT FOREIGN KEY REFERENCES Recipes(id)
userId INT FOREIGN KEY REFERENCES Users(id)
text TEXT NOT NULL
rating INT  
createdAt TIMESTAMP  

 

Notes

  • Dans MongoDB, il n'est pas nécessaire de créer explicitement des relations comme dans les bases de données relationnelles, car vous pouvez stocker des documents imbriqués ou des références (comme des ObjectId).
  • Dans une base de données relationnelle, vous auriez besoin de clés étrangères pour relier les tables entre elles et de tables de jonction pour les relations plusieurs-à-plusieurs (many-to-many), comme entre les recettes et les ingrédients.
  • Les champs createdAt et updatedAt peuvent être gérés automatiquement par MongoDB et de nombreux frameworks ORM SQL avec des fonctionnalités de timestamping automatique.

Ce schéma est un point de départ et peut être étendu ou modifié pour répondre à des besoins spécifiques, comme ajouter des fonctionnalités supplémentaires ou prendre en compte des contraintes de performance.


La réalisation d'un tel projet implique plusieurs étapes et composants. Voici un aperçu de haut niveau de la manière dont vous pourriez structurer et implémenter ces fonctionnalités en utilisant Node.js, Express et MongoDB.

Modèles MongoDB

Vous aurez besoin de modèles Mongoose pour vos utilisateurs, recettes et ingrédients.

Bcrypt permetde hasher les mot de passe pour sécuriser la base de données.

userModel.js

// Importe la bibliothèque Mongoose, utilisée pour la modélisation des données avec MongoDB.
const mongoose = require('mongoose');
// Importe la bibliothèque bcryptjs pour le hachage des mots de passe.
const bcrypt = require('bcryptjs');

// Définit un schéma pour les utilisateurs.
const userSchema = new mongoose.Schema({
  // Champ 'username' : unique et obligatoire.
  username: { type: String, required: true, unique: true },
  // Champ 'email' : unique et obligatoire.
  email: { type: String, required: true, unique: true },
  // Champ 'password' : obligatoire.
  password: { type: String, required: true },
  // Champ 'role' : définit les rôles utilisateurs et fixe 'user' comme valeur par défaut.
  role: { type: String, enum: ['user', 'admin', 'author'], default: 'user' },
  // Ajoutez ici d'autres champs si nécessaire.
});

// Hook Mongoose qui s'exécute avant l'enregistrement ('save') d'un document.
userSchema.pre('save', async function (next) {
  // Vérifie si le mot de passe a été modifié.
  if (this.isModified('password')) {
    // Si oui, hash le mot de passe avec bcrypt avant de l'enregistrer.
    this.password = await bcrypt.hash(this.password, 12);
  }
  // Passe à la prochaine middleware dans la chaîne.
  next();
});

// Méthode pour comparer le mot de passe saisi avec le mot de passe hashé stocké.
userSchema.methods.correctPassword = async function (candidatePassword, userPassword) {
  // Utilise bcrypt pour comparer les mots de passe.
  return await bcrypt.compare(candidatePassword, userPassword);
};

// Crée un modèle Mongoose 'User' en utilisant userSchema.
const User = mongoose.model('User', userSchema);

// Exporte le modèle pour utilisation dans d'autres fichiers de l'application.
module.exports = User;

Ce fichier définit un schéma pour les utilisateurs dans votre base de données MongoDB et utilise la bibliothèque bcryptjs pour sécuriser les mots de passe. Le schéma inclut des champs pour le nom d'utilisateur, l'email, le mot de passe, et le rôle de l'utilisateur. Il utilise également des hooks Mongoose (pre('save', ...)) pour hacher automatiquement les mots de passe avant de les enregistrer dans la base de données, et une méthode (correctPassword) pour comparer les mots de passe. Le modèle User est ensuite créé à partir de ce schéma et exporté pour être utilisé ailleurs dans l'application.

recipeModel.js

// Importe la bibliothèque Mongoose pour interagir avec MongoDB.
const mongoose = require('mongoose');

// Définit un schéma pour les commentaires qui seront intégrés dans les recettes.
const commentSchema = new mongoose.Schema({
  // Référence à l'utilisateur qui a fait le commentaire.
  user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
  // Texte du commentaire.
  text: String,
  // Note donnée par l'utilisateur.
  rating: Number,
  // Date de création du commentaire.
  createdAt: Date
});

// Définit un schéma pour les recettes.
const recipeSchema = new mongoose.Schema({
  // Titre de la recette, obligatoire.
  title: { type: String, required: true },
  // Description de la recette.
  description: String,
  // Liste des ingrédients avec référence à un autre modèle ou collection.
  ingredients: [{
    ingredient: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'Ingredient'
    },
    // Quantité de chaque ingrédient.
    quantity: String
  }],
  // Étapes de préparation de la recette.
  steps: [String],
  // Temps de préparation estimé.
  prepTime: Number,
  // Liens vers les images de la recette.
  images: [String],
  // Les commentaires associés à la recette, basés sur commentSchema.
  comments: [commentSchema],
  // Catégorie(s) de la recette.
  category: [String],
   // Ajoutez ici d'autres champs si nécessaire.
});

// Crée un modèle Mongoose 'Recipe' en utilisant recipeSchema.
const Recipe = mongoose.model('Recipe', recipeSchema);

// Exporte le modèle pour qu'il puisse être utilisé dans d'autres parties de l'application.
module.exports = Recipe;

Ce fichier définit deux schémas : commentSchema pour les commentaires et recipeSchema pour les recettes. Chaque schéma spécifie la structure et les types de données des documents correspondants dans MongoDB. Le modèle Recipe est ensuite créé à partir de recipeSchema et est exporté pour être utilisé dans d'autres parties de l'application.

ingredientModel.js

// Importe la bibliothèque Mongoose, qui est utilisée pour interagir avec MongoDB.
const mongoose = require('mongoose');

// Définit un schéma pour les ingrédients.
const ingredientSchema = new mongoose.Schema({
  // Champ 'name' : obligatoire, stocke le nom de l'ingrédient.
  name: { type: String, required: true },
  // Champ 'quantite' : stocke la quantité de cet ingrédient en stock.
  quantite: Number,
  // Sous-document 'nutritionInfo' pour stocker les informations nutritionnelles.
  nutritionInfo: {
    // Nombre de calories.
    calories: Number,
    // Quantité de protéines.
    protein: Number,
    // Quantité de matières grasses.
    fat: Number,
    // Ajoutez ici d'autres champs relatifs aux informations nutritionnelles si nécessaire.
  },
  // Vous pouvez ajouter ici d'autres champs nécessaires pour l'ingrédient.
});

// Crée un modèle Mongoose 'Ingredient' à partir du schéma défini.
const Ingredient = mongoose.model('Ingredient', ingredientSchema);

// Exporte le modèle pour qu'il soit utilisable dans d'autres parties de l'application.
module.exports = Ingredient;

Ce fichier définit un schéma pour les ingrédients dans votre base de données MongoDB. Le schéma ingredientSchema comprend le nom de l'ingrédient, sa quantité en stock, et un sous-document nutritionInfo qui stocke les informations nutritionnelles comme les calories, les protéines, et les matières grasses. Vous pouvez ajouter d'autres champs selon les besoins de votre application. Le modèle Ingredient est ensuite créé à partir de ce schéma et exporté pour être utilisé dans d'autres parties de l'application.

 

Fichier d'environement

L'utilisation d'un fichier .env avec la bibliothèque dotenv dans des projets Node.js, comme ceux impliquant MongoDB, offre plusieurs avantages importants en matière de sécurité, de flexibilité et de gestion des configurations. Voici les raisons principales :

1. Sécurité

  • Cacher les Informations Sensibles : Les fichiers .env sont utilisés pour stocker des données sensibles comme les clés d'API, les mots de passe de bases de données, et d'autres secrets. En les gardant hors du code source, vous réduisez le risque de les exposer accidentellement, par exemple, en les poussant sur un dépôt Git public.
  • Prévenir les Fuites de Données : En utilisant dotenv pour charger les informations sensibles depuis un fichier .env qui n'est pas suivi par le contrôle de version, vous empêchez la fuite accidentelle de ces données.

2. Flexibilité et Environnement de Développement

  • Différents Environnements : Les applications nécessitent souvent des configurations différentes pour les environnements de développement, de test et de production (par exemple, des chaînes de connexion à la base de données différentes). dotenv vous permet de définir ces configurations séparément pour chaque environnement.
  • Facilité de Modification : Les développeurs peuvent facilement modifier les paramètres d'environnement dans le fichier .env sans toucher au code source. Cela est particulièrement utile pour les ajustements rapides ou les tests.

3. Organisation et Maintenance

  • Centralisation des Configurations : Le fichier .env sert de point central pour la gestion des paramètres de configuration, ce qui rend leur localisation et leur modification plus simples et plus intuitives.
  • Code Plus Propre : En retirant les informations de configuration du code source, vous gardez votre code plus propre et plus facile à comprendre et à maintenir.

4. Portabilité

  • Facilité de Partage du Code : Lorsque vous partagez votre code avec d'autres développeurs ou lorsque vous le déployez, vous n'avez pas besoin de partager les détails sensibles de configuration. Chaque utilisateur ou environnement peut avoir son propre fichier .env.

5. Utilisation avec Node.js et MongoDB

  • Pour un projet utilisant Node.js et MongoDB, vous pouvez stocker des variables telles que la chaîne de connexion à la base de données MongoDB, les clés secrètes pour les tokens d'authentification ou les mots de passe d'API externes dans le fichier .env. Cela permet de connecter votre application à différentes bases de données (développement, test, production) ou d'utiliser différentes clés d'API selon l'environnement sans changer le code source.

Comment ça Marche ?

Pour utiliser dotenv dans un projet Node.js :

  1. Installez le paquet dotenv via NPM.
  2. Créez un fichier .env à la racine de votre projet.
  3. Ajoutez vos variables d'environnement au fichier .env.
  4. Au début de votre fichier principal (comme app.js ou server.js), ajoutez require('dotenv').config(); pour charger les variables d'environnement.
  5. Utilisez process.env.NOM_DE_VARIABLE pour accéder aux valeurs dans votre code.

En résumé, dotenv et le fichier .env sont essentiels pour gérer de manière sécurisée et efficace les configurations dans les projets Node.js, particulièrement ceux qui impliquent des bases de données et des informations sensibles comme les clés d'API.

# Chaîne de connexion à la base de données MongoDB.
# 'mongodb://127.0.0.1:27017/recettes' indique que MongoDB est hébergé localement
# (127.0.0.1 est l'adresse IP locale), sur le port 27017 (port par défaut de MongoDB),
# et la base de données utilisée s'appelle 'recettes'.
DATABASE=mongodb://127.0.0.1:27017/recettes

# Clé secrète utilisée pour signer les jetons JWT (JSON Web Tokens).
# Cette clé est essentielle pour la sécurité car elle assure que les jetons sont valides
# et n'ont pas été altérés. 'maCleSecreteIci' devrait être une chaîne complexe et unique
# pour garantir la sécurité.
JWT_SECRET=maCleSecreteIci

# Durée de validité du jeton JWT. '90d' signifie que le jeton expire après 90 jours.
# Cela définit la durée pendant laquelle un utilisateur reste connecté avant de devoir
# se reconnecter ou rafraîchir son jeton.
JWT_EXPIRES_IN=90d

Explications supplémentaires :

  1. DATABASE: Cette variable stocke la chaîne de connexion à votre base de données MongoDB. La structure de cette chaîne est standard pour MongoDB et elle pointe vers votre instance locale de MongoDB. Cela est crucial pour connecter votre application Node.js à la base de données MongoDB.

  2. JWT_SECRET: Cette variable est utilisée pour définir une clé secrète pour la signature des tokens JWT. Les tokens JWT sont utilisés pour l'authentification et la sécurisation des échanges entre le client et le serveur. Il est important que cette clé reste secrète pour garantir la sécurité de l'application.

  3. JWT_EXPIRES_IN: Cette variable définit la période de validité des tokens JWT. Dans ce cas, les tokens expirent après 90 jours. Cela signifie qu'un utilisateur restera connecté avec ce token pendant cette période, après quoi il devra se reconnecter ou obtenir un nouveau token.

Pour chaque variable dans un fichier .env, il est essentiel de s'assurer que les valeurs sont sécurisées et ne sont pas exposées publiquement, en particulier pour les variables qui contiennent des informations sensibles comme les clés secrètes ou les détails de connexion à la base de données.

DATABASE=mongodb://127.0.0.1:27017/recettes
JWT_SECRET=maCleSecreteIci
JWT_EXPIRES_IN=90d

 

Authentification et Gestion des Utilisateurs

Vous utiliserez passport ou jsonwebtoken pour gérer l'authentification.

authController.js

//controllers/authController.js

const User = require('../models/userModel');
const jwt = require('jsonwebtoken');

exports.signup = async (req, res) => {
  const newUser = await User.create(req.body);
  const token = jwt.sign({ id: newUser._id }, process.env.JWT_SECRET, {
    expiresIn: process.env.JWT_EXPIRES_IN,
  });

  res.status(201).json({
    status: 'success',
    token,
    data: {
      user: newUser,
    },
  });
};

exports.login = async (req, res) => {
  const { email, password } = req.body;

  const user = await User.findOne({ email }).select('+password');

  if (!user || !(await user.correctPassword(password, user.password))) {
    return res.status(401).json({
      status: 'fail',
      message: 'Incorrect email or password',
    });
  }

  const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, {
    expiresIn: process.env.JWT_EXPIRES_IN,
  });

  res.status(200).json({
    status: 'success',
    token,
  });
};

exports.verifyToken = (req, res) => {
  // Récupérer le token du header d'autorisation
  const token = req.headers.authorization.split(' ')[1];

  if (!token) {
    return res.status(401).json({
      status: 'fail',
      message: 'No token provided',
    });
  }

  jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
    if (err) {
      return res.status(401).json({
        status: 'fail',
        message: 'Invalid token',
      });
    }

    // Si le token est valide, renvoyer une réponse positive
    res.status(200).json({
      status: 'success',
      message: 'Token is valid',
      data: {
        userId: decoded.id,
      },
    });
  });
};

Explications :

  • Inscription (signup) : Cette fonction crée un nouvel utilisateur dans la base de données en utilisant les données fournies dans la requête. Après la création de l'utilisateur, elle génère un token JWT qui est envoyé au client. Ce token est utilisé pour authentifier l'utilisateur dans les requêtes futures.
  • Connexion (login) : Cette fonction vérifie les identifiants de l'utilisateur (email et mot de passe). Si les identifiants sont valides, elle génère un token JWT, tout comme dans la fonction d'inscription, et l'envoie au client pour les sessions authentifiées.
  • Verification du token (verifyToken):  permet au client de vérifier si le token existe sur le serveur.

Ce contrôleur joue un rôle clé dans la gestion de l'authentification des utilisateurs dans une application Node.js, en utilisant des tokens JWT pour sécuriser les sessions utilisateur.

 

Middleware

Le fichier authMiddleware.js contient deux middlewares, protect et restrictTo, conçus pour être utilisés dans une application Node.js. Ces middlewares aident à sécuriser les routes en vérifiant l'authentification et les autorisations des utilisateurs. Voici l'explication de chaque partie du code avec des commentaires :

// Importe la bibliothèque jsonwebtoken pour la vérification des tokens JWT.
const jwt = require('jsonwebtoken');
// Importe le modèle User pour interagir avec la base de données des utilisateurs.
const User = require('../models/userModel');

// Middleware pour protéger les routes qui nécessitent une authentification.
exports.protect = async (req, res, next) => {
  let token;
  // Vérifie si le token est fourni dans l'en-tête d'autorisation et commence par 'Bearer'.
  if (
    req.headers.authorization &&
    req.headers.authorization.startsWith('Bearer')
  ) {
    // Extrait le token de l'en-tête d'autorisation.
    token = req.headers.authorization.split(' ')[1];
  }

  // Si aucun token n'est fourni, envoie une réponse indiquant que l'utilisateur n'est pas connecté.
  if (!token) {
    return res.status(401).json({
      status: 'fail',
      message: 'You are not logged in!',
    });
  }

  // Vérifie le token et extrait les informations de l'utilisateur.
  const decoded = jwt.verify(token, process.env.JWT_SECRET);

  // Recherche l'utilisateur correspondant à l'ID extrait du token.
  const currentUser = await User.findById(decoded.id);
  // Si l'utilisateur n'existe pas ou n'est plus valide, envoie une erreur.
  if (!currentUser) {
    return res.status(401).json({
      status: 'fail',
      message: 'The user belonging to this token does no longer exist.',
    });
  }

  // Ajoute l'utilisateur aux données de la requête pour une utilisation dans les middlewares/routeurs suivants.
  req.user = currentUser;
  // Passe au prochain middleware dans la pile.
  next();
};

// Middleware pour restreindre l'accès à certaines routes basé sur les rôles des utilisateurs.
exports.restrictTo = (...roles) => {
  return (req, res, next) => {
    // Vérifie si le rôle de l'utilisateur actuel est inclus dans les rôles autorisés.
    if (!roles.includes(req.user.role)) {
      // Si l'utilisateur n'a pas le bon rôle, envoie une erreur d'interdiction.
      return res.status(403).json({
        status: 'fail',
        message: 'You do not have permission to perform this action',
      });
    }

    // Si l'utilisateur a le bon rôle, passe au prochain middleware.
    next();
  };
};

Explications :

  • Middleware protect : Ce middleware vérifie si l'utilisateur est authentifié. Il extrait le token JWT de l'en-tête d'autorisation, le vérifie, puis récupère l'utilisateur correspondant de la base de données. Si le token est absent, invalide ou si l'utilisateur n'existe pas, le middleware envoie une réponse d'erreur.

  • Middleware restrictTo : Ce middleware est utilisé pour restreindre l'accès à certaines routes en fonction du rôle de l'utilisateur. Il accepte un ensemble de rôles et vérifie si le rôle de l'utilisateur connecté correspond à l'un des rôles autorisés. Si l'utilisateur n'a pas le bon rôle, une erreur d'interdiction est renvoyée.

Ces middlewares sont essentiels pour sécuriser les routes dans des applications Node.js, en s'assurant que seuls les utilisateurs authentifiés (et avec les bons rôles) puissent accéder à certaines fonctionnalités ou données.

TODO: ajouter un exemple d'utilisation.

 

Les Routes

Définissez les routes pour l'authentification et la gestion des recettes.

userRoutes.js

// Importe le framework Express pour créer des routeurs.
const express = require('express');
// Importe le contrôleur d'utilisateurs qui contient la logique métier pour les actions liées aux utilisateurs.
const userController = require('../controllers/userController');


// Crée un nouvel objet Router d'Express pour définir les routes d'utilisateurs.
const router = express.Router();

// créer une route pour lister tous les users
router.get('/', userController.getAllUsers);

// Définit une route POST pour créer un nouvel utilisateur. 
// Lorsqu'une requête POST est envoyée à '/', le middleware createUser du userController est appelé.
router.post('/users', userController.createUser);

// Définit une route GET pour récupérer un utilisateur spécifique par son ID.
// Lorsqu'une requête GET est envoyée à '/users/:userId', le middleware getUser du userController est utilisé.
router.get('/users/:userId', userController.getUser);

// Définit une route PATCH pour mettre à jour un utilisateur existant par son ID.
// Lorsqu'une requête PATCH est envoyée à '/users/:userId', le middleware updateUser du userController est appelé.
router.patch('/users/:userId', userController.updateUser);

// Définit une route DELETE pour supprimer un utilisateur spécifique par son ID.
// Lorsqu'une requête DELETE est envoyée à '/users/:userId', le middleware deleteUser du userController est utilisé.
router.delete('/users/:userId', userController.deleteUser);

// Commentaire indiquant qu'il est possible d'ajouter d'autres routes si nécessaire.
// Add more routes as needed

// Exporte le routeur configuré pour qu'il puisse être utilisé dans l'application principale.
module.exports = router;

Explications :

  • Importations et Création du Routeur : Le fichier commence par importer Express et le userController. Le userController contient les fonctions nécessaires pour gérer les utilisateurs. Un objet Router est créé pour définir des routes spécifiques aux utilisateurs.

  • Définition des Routes :

    • Route /users (POST) : Cette route est utilisée pour créer un nouvel utilisateur. La fonction createUser du userController est appelée lorsqu'une requête POST est envoyée à cette URL.
    • Route /users/:userId (GET) : Permet de récupérer les détails d'un utilisateur spécifique par son ID. La fonction getUser gère cette requête.
    • Route /users/:userId (PATCH) : Utilisée pour mettre à jour les informations d'un utilisateur existant. La requête PATCH à cette URL appelle la fonction updateUser.
    • Route /users/:userId (DELETE) : Permet de supprimer un utilisateur spécifique. La fonction deleteUser est exécutée en réponse à une requête DELETE.
  • Commentaire Additionnel : Un commentaire est ajouté pour indiquer qu'il est possible d'ajouter plus de routes selon les besoins de l'application.

  • Exportation du Routeur : Le routeur est exporté pour être intégré dans l'application principale, permettant ainsi l'utilisation de ces routes pour gérer les utilisateurs.

Ce fichier joue un rôle crucial dans la définition des points d'entrée (endpoints) pour les opérations liées aux utilisateurs, facilitant la gestion des utilisateurs dans l'application.

authRoutes.js

// Importe le framework Express pour créer des routeurs.
const express = require('express');
// Importe le contrôleur d'authentification qui contient la logique métier pour les actions d'authentification.
const authController = require('../controllers/authController');
// Crée un nouvel objet Router d'Express pour définir les routes d'authentification.
const router = express.Router();

// Définit une route POST pour l'inscription. Lorsqu'une requête POST est envoyée à '/signup',
// le middleware signup du authController est appelé pour gérer cette requête.
router.post('/signup', authController.signup);

// Définit une route POST pour la connexion. De la même manière, lorsque la requête POST est envoyée à '/login',
// le middleware login du authController est utilisé pour traiter la requête.
router.post('/login', authController.login);

// Exporte le routeur configuré pour qu'il puisse être utilisé dans l'application principale.
module.exports = router;

Explications :

  • Importations : Le fichier commence par importer le framework Express, nécessaire pour la création de routes, ainsi que le contrôleur d'authentification (authController), qui contient les fonctions de logique métier pour l'inscription (signup) et la connexion (login).

  • Création du Router : Ensuite, il utilise express.Router() pour créer une nouvelle instance de Router, un objet d'Express qui permet de définir des routes.

  • Définition des Routes :

    • Route /signup : Cette route est définie pour écouter les requêtes POST à l'URL /signup. Lorsqu'une telle requête est reçue, la fonction signup du authController est appelée. Cette fonction s'occupe de l'inscription d'un nouvel utilisateur, comme expliqué dans le fichier authController.js.
    • Route /login : De manière similaire, la route /login est configurée pour gérer les requêtes POST à l'URL /login en utilisant la fonction login du authController. Cette fonction gère le processus de connexion d'un utilisateur existant.
  • Exportation du Router : Enfin, le routeur configuré est exporté afin qu'il puisse être intégré dans l'application principale, généralement dans le fichier principal du serveur (comme app.js ou server.js).

Ce fichier fait partie intégrante de la gestion de l'authentification dans une application Node.js, en définissant les points d'entrée (endpoints) spécifiques pour les actions liées à l'authentification des utilisateurs.

 

recipeRoutes.js

// Importe le framework Express pour créer des routeurs.
const express = require('express');
// Importe le contrôleur de recettes qui contient la logique métier pour les actions liées aux recettes.
const recipeController = require('../controllers/recipeController');
// Crée un nouvel objet Router d'Express pour définir les routes de recettes.
const router = express.Router();

// Définit une route GET pour récupérer toutes les recettes. 
// Lorsqu'une requête GET est envoyée à '/', le middleware getAllRecipes du recipeController est appelé.
router.get('/', recipeController.getAllRecipes);

// Définit une route GET pour récupérer une recette spécifique par son ID.
// Lorsqu'une requête GET est envoyée à '/recipe/:id', le middleware getRecipe du recipeController est utilisé.
router.get('/recipe/:id', recipeController.getRecipe);

// Définit une route POST pour ajouter une nouvelle recette.
// Lorsqu'une requête POST est envoyée à '/recipe', le middleware addRecipe du recipeController est appelé.
router.post('/recipe', recipeController.addRecipe);

// Définit une route PATCH pour mettre à jour une recette existante par son ID.
// Lorsqu'une requête PATCH est envoyée à '/recipe/:id', le middleware updateRecipe du recipeController est utilisé.
router.patch('/recipe/:id', recipeController.updateRecipe);

// Définit une route DELETE pour supprimer une recette spécifique par son ID.
// Lorsqu'une requête DELETE est envoyée à '/recipe/:id', le middleware deleteRecipe du recipeController est appelé.
router.delete('/recipe/:id', recipeController.deleteRecipe);

// Exporte le routeur configuré pour qu'il puisse être utilisé dans l'application principale.
module.exports = router;

Explications :

  • Importations et Routeur : Le fichier commence par importer Express et le recipeController, qui contient les fonctions pour gérer les recettes. Un objet Router est ensuite créé pour définir les routes.

  • Définition des Routes :

    • Route / (GET) : Cette route est configurée pour récupérer toutes les recettes. Quand l'URL racine avec une requête GET est appelée, la fonction getAllRecipes du recipeController est exécutée.
    • Route /recipe/:id (GET) : Elle permet de récupérer une recette spécifique en utilisant son ID. La fonction getRecipe est appelée avec l'ID spécifié dans l'URL.
    • Route /recipe (POST) : Cette route permet d'ajouter une nouvelle recette. Lorsqu'une requête POST est faite à cette URL, la fonction addRecipe est exécutée.
    • Route /recipe/:id (PATCH) : Permet de mettre à jour une recette existante. La fonction updateRecipe est appelée avec l'ID de la recette à modifier.
    • Route /recipe/:id (DELETE) : Cette route est utilisée pour supprimer une recette spécifique. La requête DELETE appelle la fonction deleteRecipe avec l'ID de la recette à supprimer.
  • Exportation : Enfin, le routeur configuré est exporté pour être intégré et utilisé dans l'application principale.

Ce fichier joue un rôle clé dans la définition des routes de l'API pour les opérations liées aux recettes, facilitant ainsi la gestion des recettes dans l'application.

 

categoryRouter

// Importe le framework Express pour créer des routeurs.
const express = require('express');
// Importe le contrôleur de catégories qui contient la logique métier pour les actions liées aux catégories.
const categoryController = require('../controllers/categoryController');
// Crée un nouvel objet Router d'Express pour définir les routes de catégories.
const router = express.Router();

// Commentaire indiquant qu'il est possible d'ajouter d'autres routes ici si nécessaire.
// Add other routes here as needed

// Définit une route GET pour lister toutes les catégories.
// Lorsqu'une requête GET est envoyée à '/categories', le middleware listCategories du categoryController est appelé.
router.get('/categories', categoryController.listCategories);

// Définit une route GET pour lister toutes les catégories associées à une recette spécifique.
// Lorsqu'une requête GET est envoyée à '/:recipeId/categories', le middleware listRecipesCategories du categoryController est utilisé.
router.get('/:recipeId/categories', categoryController.listRecipesCategories);

// Définit une route POST pour ajouter une catégorie à une recette spécifique.
// Lorsqu'une requête POST est envoyée à '/:recipeId/category', le middleware addCategoryToRecipe du categoryController est appelé.
router.post('/:recipeId/category', categoryController.addCategoryToRecipe);

// Définit une route DELETE pour supprimer une catégorie d'une recette spécifique.
// Lorsqu'une requête DELETE est envoyée à '/:recipeId/category/:categoryId', le middleware removeCategoryFromRecipe du categoryController est utilisé.
router.delete('/:recipeId/category/:categoryId', categoryController.removeCategoryFromRecipe);

// Exporte le routeur configuré pour qu'il puisse être utilisé dans l'application principale.
module.exports = router;

Explications :

  • Importations et Création du Routeur : Le fichier commence par importer Express et le categoryController, qui contient les fonctions nécessaires pour la gestion des catégories. Un objet Router est créé pour définir les routes spécifiques aux catégories.

  • Définition des Routes :

    • Route /categories (GET) : Permet de lister toutes les catégories disponibles. La fonction listCategories du categoryController est appelée pour cette opération.
    • Route /:recipeId/categories (GET) : Utilisée pour lister toutes les catégories associées à une recette spécifique, identifiée par recipeId. La fonction listRecipesCategories gère cette requête.
    • Route /:recipeId/category (POST) : Permet d'ajouter une catégorie à une recette spécifique. La fonction addCategoryToRecipe est exécutée en réponse à une requête POST.
    • Route /:recipeId/category/:categoryId (DELETE) : Utilisée pour supprimer une catégorie d'une recette spécifique. La fonction removeCategoryFromRecipe est appelée pour cette opération.
  • Exportation du Routeur : Le routeur est ensuite exporté pour être intégré dans l'application principale. Cela permet d'utiliser ces routes pour la gestion des catégories en relation avec les recettes.

Ce fichier est essentiel pour définir l'interface de programmation des routes de catégories dans une application de gestion de recettes, facilitant ainsi les opérations CRUD liées aux catégories.

commentRoutes.js

// Importe le framework Express pour créer des routeurs.
const express = require('express');
// Crée un nouvel objet Router d'Express pour définir des routes spécifiques aux commentaires.
const router = express.Router();
// Importe le contrôleur de commentaires qui contient la logique métier pour les actions liées aux commentaires.
const commentController = require('../controllers/commentController');

// Définit une route GET pour lister tous les commentaires associés à une recette spécifique.
// Lorsqu'une requête GET est envoyée à '/:recipeId/comments', le middleware listComments du commentController est appelé.
router.get('/:recipeId/comments', commentController.listComments);

// Définit une route POST pour ajouter un commentaire à une recette spécifique.
// Lorsqu'une requête POST est envoyée à '/:recipeId/comments', le middleware addComment du commentController est utilisé.
router.post('/:recipeId/comments', commentController.addComment);

// Définit une route PUT pour mettre à jour un commentaire spécifique associé à une recette.
// Lorsqu'une requête PUT est envoyée à '/:recipeId/comments/:commentId', le middleware updateComment du commentController est appelé.
router.put('/:recipeId/comments/:commentId', commentController.updateComment);

// Définit une route DELETE pour supprimer un commentaire spécifique d'une recette.
// Lorsqu'une requête DELETE est envoyée à '/:recipeId/comments/:commentId', le middleware deleteComment du commentController est utilisé.
router.delete('/:recipeId/comments/:commentId', commentController.deleteComment);

// Exporte le routeur configuré pour qu'il puisse être utilisé dans l'application principale.
module.exports = router;

 

imageRoutes.js

// Importe le framework Express pour créer des routeurs.
const express = require('express');
// Importe le contrôleur d'images qui contient la logique métier pour les actions liées aux images des recettes.
const imageController = require('../controllers/imageController');
// Crée un nouvel objet Router d'Express pour définir les routes d'images.
const router = express.Router();

// Commentaire indiquant qu'il est possible d'ajouter d'autres routes pour d'autres opérations sur les images.
// Add more routes as needed for other image operations

// Définit une route GET pour récupérer toutes les images associées à une recette spécifique.
// Lorsqu'une requête GET est envoyée à '/:recipeId/images', le middleware getImagesFromRecipe du imageController est appelé.
router.get('/:recipeId/images', imageController.getImagesFromRecipe);

// Définit une route POST pour ajouter une image à une recette spécifique.
// Lorsqu'une requête POST est envoyée à '/:recipeId/images', le middleware addImageToRecipe du imageController est utilisé.
router.post('/:recipeId/images', imageController.addImageToRecipe);

// Définit une route PATCH pour mettre à jour une image spécifique dans une recette.
// Lorsqu'une requête PATCH est envoyée à '/:recipeId/images/:imageIndex', le middleware updateImageInRecipe du imageController est appelé.
router.patch('/:recipeId/images/:imageIndex', imageController.updateImageInRecipe);

// Définit une route DELETE pour supprimer une image spécifique d'une recette.
// Lorsqu'une requête DELETE est envoyée à '/:recipeId/images/:imageIndex', le middleware deleteImageFromRecipe du imageController est utilisé.
router.delete('/:recipeId/images/:imageIndex', imageController.deleteImageFromRecipe);

// Exporte le routeur configuré pour qu'il puisse être utilisé dans l'application principale.
module.exports = router;

 

stepRoutes.js

// Importe le framework Express, qui est essentiel pour la création de routeurs dans une application Node.js.
const express = require('express');
// Importe le contrôleur pour les étapes, qui contient la logique métier pour les actions liées aux étapes des recettes.
const stepController = require('../controllers/stepController');
// Crée un nouvel objet Router d'Express, utilisé pour définir les routes pour les étapes de recettes.
const router = express.Router();

// Définit une route GET pour récupérer toutes les étapes d'une recette spécifique.
// Lorsqu'une requête GET est envoyée à '/:recipeId/steps', le middleware getStepsFromRecipe du stepController est appelé.
router.get('/:recipeId/steps', stepController.getStepsFromRecipe);

// Définit une route POST pour ajouter une nouvelle étape à une recette spécifique.
// Lorsqu'une requête POST est envoyée à '/:recipeId/steps', le middleware addStepToRecipe du stepController est utilisé.
router.post('/:recipeId/steps', stepController.addStepToRecipe);

// Définit une route PATCH pour mettre à jour une étape spécifique dans une recette.
// L'index de l'étape à mettre à jour est spécifié dans l'URL. 
// Lorsqu'une requête PATCH est envoyée à '/:recipeId/steps/:stepIndex', le middleware updateStepInRecipe du stepController est appelé.
router.patch('/:recipeId/steps/:stepIndex', stepController.updateStepInRecipe);

// Définit une route DELETE pour supprimer une étape spécifique d'une recette.
// L'index de l'étape à supprimer est également spécifié dans l'URL.
// Lorsqu'une requête DELETE est envoyée à '/:recipeId/steps/:stepIndex', le middleware deleteStepFromRecipe du stepController est utilisé.
router.delete('/:recipeId/steps/:stepIndex', stepController.deleteStepFromRecipe);

// Exporte le routeur configuré pour qu'il puisse être intégré et utilisé dans l'application principale.
module.exports = router;

 

ingredientRoute?js

// Importe le framework Express pour créer des routeurs.
const express = require('express');
// Importe le contrôleur d'ingrédients, qui contient la logique métier pour les actions liées aux ingrédients.
const ingredientController = require('../controllers/ingredientController'); // Assurez-vous que le chemin est correct
// Crée un nouvel objet Router d'Express pour définir les routes pour les ingrédients.
const router = express.Router();

// Définit une route GET pour récupérer la liste de tous les ingrédients.
// Lorsqu'une requête GET est envoyée à '/', le middleware getAllIngredients du ingredientController est appelé.
router.get('/', ingredientController.getAllIngredients);

// Définit une route POST pour ajouter un nouvel ingrédient à la base de données.
// Lorsqu'une requête POST est envoyée à '/ingredient', le middleware addIngredient du ingredientController est utilisé.
router.post('/ingredient', ingredientController.addIngredient);

// Définit une route GET pour récupérer un ingrédient spécifique par son ID.
// Lorsqu'une requête GET est envoyée à '/ingredient/:id', le middleware getIngredient du ingredientController est appelé.
router.get('/ingredient/:id', ingredientController.getIngredient);

// Définit une route PATCH pour mettre à jour les informations d'un ingrédient spécifique.
// Lorsqu'une requête PATCH est envoyée à '/ingredient/:id', le middleware updateIngredient du ingredientController est utilisé.
router.patch('/ingredient/:id', ingredientController.updateIngredient);

// Définit une route DELETE pour supprimer un ingrédient spécifique de la base de données.
// Lorsqu'une requête DELETE est envoyée à '/ingredient/:id', le middleware deleteIngredient du ingredientController est appelé.
router.delete('/ingredient/:id', ingredientController.deleteIngredient);

// Exporte le routeur configuré pour qu'il puisse être intégré et utilisé dans l'application principale.
module.exports = router;

 

Les Controlleurs

userController.js

// Importe le modèle User, qui est une représentation de la collection d'utilisateurs dans la base de données.
const User = require('../models/userModel');

// Fonction pour créer un nouvel utilisateur.
exports.createUser = async (req, res) => {
    try {
        // Crée un nouvel utilisateur en utilisant les données reçues dans le corps de la requête.
        const newUser = await User.create(req.body);
        // Envoie une réponse de succès avec les données de l'utilisateur créé.
        res.status(201).json({
            status: 'success',
            data: {
                user: newUser
            }
        });
    } catch (error) {
        // En cas d'erreur, envoie une réponse d'erreur.
        res.status(500).send(error.message);
    }
};

// Fonction pour récupérer un utilisateur spécifique par son ID.
exports.getUser = async (req, res) => {
    try {
        // Cherche l'utilisateur par son ID.
        const user = await User.findById(req.params.userId);
        // Si l'utilisateur n'est pas trouvé, renvoie une erreur 404.
        if (!user) {
            return res.status(404).send('User not found');
        }
        // Sinon, envoie une réponse de succès avec les données de l'utilisateur.
        res.status(200).json({
            status: 'success',
            data: {
                user
            }
        });
    } catch (error) {
        // En cas d'erreur, envoie une réponse d'erreur.
        res.status(500).send(error.message);
    }
};

// Fonction pour mettre à jour un utilisateur.
exports.updateUser = async (req, res) => {
    try {
        // Met à jour l'utilisateur spécifié par son ID avec les nouvelles données reçues.
        const user = await User.findByIdAndUpdate(req.params.userId, req.body, {
            new: true, // Renvoie les données de l'utilisateur après mise à jour.
            runValidators: true // Exécute les validateurs définis dans le modèle User.
        });
        // Si l'utilisateur n'est pas trouvé, renvoie une erreur 404.
        if (!user) {
            return res.status(404).send('User not found');
        }
        // Sinon, envoie une réponse de succès avec les données de l'utilisateur mis à jour.
        res.status(200).json({
            status: 'success',
            data: {
                user
            }
        });
    } catch (error) {
        // En cas d'erreur, envoie une réponse d'erreur.
        res.status(500).send(error.message);
    }
};

// Fonction pour supprimer un utilisateur.
exports.deleteUser = async (req, res) => {
    try {
        // Supprime l'utilisateur spécifié par son ID.
        const user = await User.findByIdAndDelete(req.params.userId);
        // Si l'utilisateur n'est pas trouvé, renvoie une erreur 404.
        if (!user) {
            return res.status(404).send('User not found');
        }
        // Envoie une réponse de succès sans contenu (204).
        res.status(204).send();
    } catch (error) {
        // En cas d'erreur, envoie une réponse d'erreur.
        res.status(500).send(error.message);
    }
};

// Commentaire indiquant qu'il est possible d'ajouter d'autres fonctions si nécessaire.
// Add more functions as needed

// Fonction pour récupérer tous les utilisateurs.
exports.getAllUsers = async (req, res) => {
    try {
        // Récupère tous les utilisateurs.
        const users = await User.find({});
        // Envoie une réponse de succès avec la liste des utilisateurs.
        res.status(200).json({
            status: 'success',
            results: users.length,
            data: {
                users
            }
        });
    } catch (error) {
        // En cas d'erreur, envoie une réponse d'erreur.
        res.status(500).send(error.message);
    }
};

Chaque fonction de ce contrôleur s'occupe d'une opération spécifique sur les données des utilisateurs dans la base de données. Les fonctions createUser, getUser, updateUser, deleteUser, et getAllUsers permettent respectivement de créer, lire, mettre à jour, supprimer un utilisateur et de lire tous les utilisateurs. Les réponses appropriées sont envoyées en fonction du résultat de chaque opération, avec une gestion d'erreurs pour capturer et renvoyer les problèmes potentiels qui pourraient survenir.

authController.js

// Importe le modèle User pour interagir avec la base de données et la bibliothèque jsonwebtoken pour la création de tokens JWT.
const User = require('../models/userModel');
const jwt = require('jsonwebtoken');

// Fonction pour inscrire (créer) un nouvel utilisateur.
exports.signup = async (req, res) => {
  // Crée un nouvel utilisateur en utilisant les données reçues dans la requête (req.body).
  const newUser = await User.create(req.body);

  // Crée un token JWT signé avec l'identifiant unique de l'utilisateur et la clé secrète stockée dans les variables d'environnement.
  const token = jwt.sign({ id: newUser._id }, process.env.JWT_SECRET, {
    expiresIn: process.env.JWT_EXPIRES_IN,
  });

  // Envoie une réponse avec le statut 201 (créé), le token JWT, et les données de l'utilisateur.
  res.status(201).json({
    status: 'success',
    token,
    data: {
      user: newUser,
    },
  });
};

// Fonction pour connecter un utilisateur existant.
exports.login = async (req, res) => {
  // Récupère l'email et le mot de passe à partir de la requête.
  const { email, password } = req.body;

  // Cherche l'utilisateur par son email. Le mot de passe est également sélectionné pour la vérification.
  const user = await User.findOne({ email }).select('+password');

  // Vérifie si l'utilisateur existe et si le mot de passe est correct.
  if (!user || !(await user.correctPassword(password, user.password))) {
    // Si les informations d'identification sont incorrectes, renvoie une réponse 401 (non autorisé).
    return res.status(401).json({
      status: 'fail',
      message: 'Incorrect email or password',
    });
  }

  // Si l'utilisateur est trouvé et le mot de passe est correct, crée un nouveau token JWT.
  const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, {
    expiresIn: process.env.JWT_EXPIRES_IN,
  });

  // Envoie une réponse avec le statut 200 (succès) et le token JWT.
  res.status(200).json({
    status: 'success',
    token,
  });
};

 

les fonctions, signup et login, sont responsables de la gestion de l'inscription et de la connexion des utilisateurs. 

  • Inscription (signup) : Cette fonction crée un nouvel utilisateur dans la base de données en utilisant les données fournies dans la requête. Après la création de l'utilisateur, elle génère un token JWT qui est envoyé au client. Ce token est utilisé pour authentifier l'utilisateur dans les requêtes futures.

  • Connexion (login) : Cette fonction vérifie les identifiants de l'utilisateur (email et mot de passe). Si les identifiants sont valides, elle génère un token JWT, tout comme dans la fonction d'inscription, et l'envoie au client pour les sessions authentifiées.

Ce contrôleur joue un rôle clé dans la gestion de l'authentification des utilisateurs dans une application Node.js, en utilisant des tokens JWT pour sécuriser les sessions utilisateur.

 
// Importe le modèle Recipe, qui représente la collection de recettes dans la base de données.
const Recipe = require('../models/recipeModel');

// Fonction pour ajouter une nouvelle recette.
exports.addRecipe = async (req, res) => {
  try {
    // Prépare les données de la nouvelle recette, en s'assurant que les catégories et les commentaires sont des tableaux.
    const newRecipeData = {
      ...req.body,
      categories: Array.isArray(req.body.categories) ? req.body.categories : [req.body.categories],
      comments: Array.isArray(req.body.comments) ? req.body.comments : [req.body.comments],
    };

    // Crée la nouvelle recette dans la base de données.
    const newRecipe = await Recipe.create(newRecipeData);
    // Envoie une réponse de succès avec les données de la recette créée.
    res.status(201).json({
      status: 'success',
      data: {
        recipe: newRecipe
      }
    });
  } catch (err) {
    // Envoie une réponse d'erreur si une erreur survient.
    res.status(400).json({
      status: 'fail',
      message: err.message
    });
  }
};

// Fonction pour récupérer toutes les recettes.
exports.getAllRecipes = async (req, res) => {
  try {
    // Récupère toutes les recettes de la base de données, en peuplant les détails des ingrédients.
    const recipes = await Recipe.find().populate('ingredients.ingredient');
    // Envoie une réponse de succès avec la liste des recettes.
    res.status(200).json({
      status: 'success',
      results: recipes.length,
      data: {
        recipes
      }
    });
  } catch (err) {
    // Envoie une réponse d'erreur si une erreur survient.
    res.status(404).json({
      status: 'fail',
      message: err.message
    });
  }
};

// Fonction pour récupérer une recette spécifique par son ID.
exports.getRecipe = async (req, res) => {
  try {
    // Cherche la recette spécifiée par son ID et peuple les détails des ingrédients.
    const recipe = await Recipe.findById(req.params.id).populate('ingredients.ingredient');
    // Envoie une réponse de succès avec les détails de la recette.
    res.status(200).json({
      status: 'success',
      data: {
        recipe
      }
    });
  } catch (err) {
    // Envoie une réponse d'erreur si la recette n'est pas trouvée.
    res.status(404).json({
      status: 'fail',
      message: 'Recette non trouvée'
    });
  }
};

// Fonction pour mettre à jour une recette.
exports.updateRecipe = async (req, res) => {
  try {
    // Met à jour la recette spécifiée par son ID avec les nouvelles données reçues et peuple les détails des ingrédients.
    const recipe = await Recipe.findByIdAndUpdate(req.params.id, req.body, {
      new: true, // Renvoie les données de la recette après mise à jour.
      runValidators: true // Exécute les validateurs définis dans le modèle Recipe.
    }).populate('ingredients.ingredient');
    // Envoie une réponse de succès avec les données de la recette mise à jour.
    res.status(200).json({
      status: 'success',
      data: {
        recipe
      }
    });
  } catch (err) {
    // Envoie une réponse d'erreur si une erreur survient.
    res.status(400).json({
      status: 'fail',
      message: err.message
    });
  }
};

// Fonction pour supprimer une recette.
exports.deleteRecipe = async (req, res) => {
  try {
    // Supprime la recette spécifiée par son ID.
    await Recipe.findByIdAndDelete(req.params.id);
    // Envoie une réponse de succès sans contenu.
    res.status(204).json({
      status: 'success',
      data: null
    });
  } catch (err) {
    // Envoie une réponse d'erreur si la recette n'est pas trouvée.
    res.status(404).json({
      status: 'fail',
      message: 'Recette non trouvée'
    });
  }
};
recipeController.js
// Crée une nouvelle recette en utilisant les données fournies dans la requête.
exports.addRecipe = async (req, res) => {
  try {
    // Prépare les données de la recette, en s'assurant que les catégories et les commentaires sont des tableaux.
    const newRecipeData = {
      ...req.body,
      categories: Array.isArray(req.body.categories) ? req.body.categories : [req.body.categories],
      comments: Array.isArray(req.body.comments) ? req.body.comments : [req.body.comments],
    };

    // Crée la nouvelle recette dans la base de données.
    const newRecipe = await Recipe.create(newRecipeData);
    // Envoie une réponse de succès avec les données de la nouvelle recette.
    res.status(201).json({
      status: 'success',
      data: {
        recipe: newRecipe
      }
    });
  } catch (err) {
    // En cas d'erreur, envoie une réponse d'erreur.
    res.status(400).json({
      status: 'fail',
      message: err.message
    });
  }
};

// Récupère toutes les recettes existantes.
exports.getAllRecipes = async (req, res) => {
  try {
    // Trouve toutes les recettes et peuple les ingrédients référencés.
    const recipes = await Recipe.find().populate('ingredients.ingredient');
    // Envoie une réponse de succès avec les recettes trouvées.
    res.status(200).json({
      status: 'success',
      results: recipes.length,
      data: {
        recipes
      }
    });
  } catch (err) {
    // En cas d'erreur, envoie une réponse d'erreur.
    res.status(404).json({
      status: 'fail',
      message: err.message
    });
  }
};

// Récupère une recette spécifique par son ID.
exports.getRecipe = async (req, res) => {
  try {
    // Trouve la recette par son ID et peuple les ingrédients référencés.
    const recipe = await Recipe.findById(req.params.id).populate('ingredients.ingredient');
    // Envoie une réponse de succès avec la recette trouvée.
    res.status(200).json({
      status: 'success',
      data: {
        recipe
      }
    });
  } catch (err) {
    // En cas d'erreur, envoie une réponse d'erreur.
    res.status(404).json({
      status: 'fail',
      message: 'Recette non trouvée'
    });
  }
};

// Met à jour une recette existante.
exports.updateRecipe = async (req, res) => {
  try {
    // Met à jour la recette par son ID avec les nouvelles données et renvoie la recette mise à jour.
    const recipe = await Recipe.findByIdAndUpdate(req.params.id, req.body, {
      new: true,
      runValidators: true
    }).populate('ingredients.ingredient');
    // Envoie une réponse de succès avec la recette mise à jour.
    res.status(200).json({
      status: 'success',
      data: {
        recipe
      }
    });
  } catch (err) {
    // En cas d'erreur, envoie une réponse d'erreur.
    res.status(400).json({
      status: 'fail',
      message: err.message
    });
  }
};

// Supprime une recette par son ID.
exports.deleteRecipe = async (req, res) => {
  try {
    // Supprime la recette par son ID.
    await Recipe.findByIdAndDelete(req.params.id);
    // Envoie une réponse de succès sans contenu.
    res.status(204).json({
      status: 'success',
      data: null
    });
  } catch (err) {
    // En cas d'erreur, envoie une réponse d'erreur.
    res.status(404).json({
      status: 'fail',
      message: 'Recette non trouvée'
    });
  }
};


Chaque fonction de ce contrôleur effectue une opération spécifique sur les recettes stockées dans MongoDB, en utilisant le modèle Recipe. Les fonctions addRecipe, getAllRecipes, getRecipe, updateRecipe, et deleteRecipe gèrent respectivement l'ajout, la récupération, la mise à jour et la suppression de recettes. Ces fonctions utilisent un traitement asynchrone pour interagir avec la base de données et renvoient des réponses appropriées en fonction des résultats de ces opérations.

Dans le contexte de MongoDB et Mongoose (une bibliothèque ODM pour MongoDB utilisée avec Node.js), populate() est une méthode puissante qui vous permet de référencer automatiquement des documents dans d'autres collections. Fondamentalement, elle est utilisée pour remplacer automatiquement les champs spécifiés dans le document par des documents provenant d'autres collections. Cela est particulièrement utile pour gérer les relations de type "un-à-plusieurs" ou "plusieurs-à-plusieurs".

Dans vos exemples de contrôleur de recettes, populate est utilisé pour enrichir les informations sur les recettes en incluant les détails complets des ingrédients, plutôt que de simplement renvoyer leurs identifiants. Voici comment cela fonctionne :

Utilisation de populate dans les exemples

Dans getAllRecipes et getRecipe :

Recipe.find().populate('ingredients.ingredient');
Recipe.findById(req.params.id).populate('ingredients.ingredient');

Ici, populate('ingredients.ingredient') indique à Mongoose de remplacer chaque ingredient (qui est probablement juste un ID dans la collection Recipe) par le document d'ingrédient correspondant de la collection Ingredient. Cela permet de retourner des recettes avec des détails complets sur les ingrédients, au lieu de simplement avoir des références ou des ID.

Dans updateRecipe :

Recipe.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true }).populate('ingredients.ingredient');

Après la mise à jour d'une recette, populate est utilisé pour récupérer la recette mise à jour avec des informations complètes sur les ingrédients.

Avantages de populate

  • Simplicité de Requêtes : populate simplifie la récupération et l'agrégation de données liées. Sans populate, vous devriez effectuer des requêtes supplémentaires pour récupérer les détails de chaque ingrédient après avoir obtenu la recette.
  • Lisibilité du Code : Le code devient plus lisible et facile à comprendre, car la logique de jointure des données est gérée par Mongoose.
  • Performance : Bien que populate puisse être moins performant que certaines opérations d'agrégation native de MongoDB pour de grandes quantités de données, il offre un bon équilibre entre performance et facilité de développement pour de nombreux cas d'usage courants.

Points à Considérer

  • Surcharge : L'utilisation excessive de populate peut entraîner une surcharge, en particulier si vous peuplez de nombreux champs ou si les documents référencés sont volumineux.
  • Conception de Base de Données : Une utilisation judicieuse de populate dépend de la conception de votre base de données et de la manière dont les données sont référencées entre les collections.

En résumé, populate est un outil très utile dans Mongoose pour travailler avec des données liées dans MongoDB, permettant de récupérer facilement des données complètes et liées en une seule requête.

 

recipeController.js

// Ajoute une catégorie à une recette spécifique.
exports.addCategoryToRecipe = async (req, res) => {
    try {
        const { recipeId } = req.params; // Récupère l'ID de la recette à partir des paramètres de la requête.
        const { category } = req.body; // Récupère la catégorie à ajouter à partir du corps de la requête.

        // Recherche la recette par son ID.
        const recipe = await Recipe.findById(recipeId);
        // Si la recette n'est pas trouvée, renvoie une erreur 404.
        if (!recipe) {
            return res.status(404).send('Recipe not found');
        }

        // Vérifie si la catégorie existe déjà dans la recette.
        if (recipe.category.includes(category)) {
            return res.status(400).send('Category already exists');
        }

        // Ajoute la catégorie à la recette et sauvegarde les changements.
        recipe.category.push(category);
        await recipe.save();
        
        // Envoie une réponse de succès avec la recette mise à jour.
        res.status(200).json({
            status: 'success',
            data: {
                recipe
            }
        });
    } catch (error) {
        // En cas d'erreur, envoie une réponse d'erreur.
        res.status(500).send(error.message);
    }
};

// Supprime une catégorie d'une recette spécifique.
exports.removeCategoryFromRecipe = async (req, res) => {
    try {
        const { recipeId } = req.params; // Récupère l'ID de la recette.
        const { category } = req.body; // Récupère la catégorie à supprimer.

        // Recherche la recette par son ID.
        const recipe = await Recipe.findById(recipeId);
        // Si la recette n'est pas trouvée, renvoie une erreur 404.
        if (!recipe) {
            return res.status(404).send('Recipe not found');
        }

        // Filtre la catégorie à supprimer et sauvegarde les changements.
        recipe.category = recipe.category.filter(cat => cat !== category);
        await recipe.save();
        
        // Envoie une réponse de succès avec la recette mise à jour.
        res.status(200).json({
            status: 'success',
            data: {
                recipe
            }
        });
    } catch (error) {
        // En cas d'erreur, envoie une réponse d'erreur.
        res.status(500).send(error.message);
    }
};

// Liste toutes les catégories uniques parmi toutes les recettes.
exports.listCategories = async (req, res) => {
    try {
        // Utilise l'agrégation pour collecter toutes les catégories de toutes les recettes.
        const categories = await Recipe.aggregate([
            { $unwind: '$category' }, // Décompose l'array des catégories.
            { $group: { _id: '$category' } }, // Groupe par catégorie.
            { $sort: { _id: 1 } } // Trie les catégories alphabétiquement.
        ]);

        // Transforme les résultats en un tableau de noms de catégories.
        const categoryList = categories.map(cat => cat._id);

        // Envoie une réponse de succès avec la liste des catégories.
        res.status(200).json({
            status: 'success',
            data: {
                categories: categoryList
            }
        });
    } catch (error) {
        // En cas d'erreur, envoie une réponse d'erreur.
        res.status(500).send(error.message);
    }
};

// Liste les catégories d'une recette spécifique.
exports.listRecipesCategories = async (req, res) => {
    try {
        const { recipeId } = req.params; // Récupère l'ID de la recette à partir des paramètres de la requête.

        // Trouve la recette par son ID.
        const recipe = await Recipe.findById(recipeId);

        // Vérifie si la recette existe.
        if (!recipe) {
            return res.status(404).json({ message: "Recipe not found" });
        }

        // Répond avec les catégories de la recette.
        res.status(200).json({
            status: 'success',
            data: {
                categories: recipe.category
            }
        });
    } catch (error) {
        // En cas d'erreur, envoie une réponse d'erreur.
        res.status(500).send(error.message);
    }
};



Chaque fonction dans ce contrôleur joue un rôle spécifique dans la gestion des catégories de recettes. Les fonctions addCategoryToRecipe et removeCategoryFromRecipe permettent d'ajouter et de supprimer des catégories dans des recettes individuelles. listCategories fournit une liste unique de toutes les catégories disponibles parmi toutes les recettes, tandis que listRecipesCategories renvoie les catégories pour une recette spécifique. Ces fonctions utilisent des opérations asynchrones pour interagir avec la base de données MongoDB et gèrent les erreurs de manière appropriée.

les opérateurs $unwind, $group, et $sort sont utilisés dans les pipelines d'agrégation de MongoDB pour transformer et organiser les données. Voici une explication détaillée de chacun :

$unwind

  • Usage : $unwind est utilisé pour décomposer les tableaux dans les documents d'une collection. Il crée un nouveau document pour chaque élément du tableau.
  • Exemple : Si vous avez un document avec un champ category qui est un tableau, $unwind sur category générera un nouveau document pour chaque catégorie dans ce tableau. Cela est utile pour des opérations ultérieures qui nécessitent que chaque élément du tableau soit traité comme un document distinct.

$group

  • Usage : $group est utilisé pour regrouper les documents par un ou plusieurs champs spécifiés et peut effectuer diverses opérations de calcul sur les données regroupées.
  • Exemple : Vous pouvez utiliser $group pour regrouper les documents par catégorie (ou tout autre champ) et compter le nombre de documents dans chaque groupe, ou calculer des sommes, des moyennes, etc. Dans le pipeline d'agrégation, $group est souvent utilisé après $unwind pour reconsolider les documents décomposés.

$sort

  • Usage : $sort est utilisé pour trier les documents dans un ordre spécifique (croissant ou décroissant) selon un ou plusieurs champs.
  • Exemple : Après avoir groupé des documents, vous pouvez utiliser $sort pour organiser les résultats de l'agrégation dans un ordre particulier, comme trier les catégories par ordre alphabétique ou trier les données par valeurs numériques (comme le nombre d'occurrences d'une catégorie).

Dans le contexte de la gestion des catégories de recettes, ces opérateurs sont utiles pour transformer des données structurées complexes (comme des recettes avec des catégories sous forme de tableaux) en informations plus simples et plus facilement analysables. Par exemple, vous pourriez décomposer toutes les catégories de toutes les recettes, les regrouper pour éliminer les doublons, puis les trier pour présenter une liste ordonnée.

// controllers/commentController.js

const Recipe = require('../models/recipeModel');

// List all comments for a specific recipe
exports.listComments = async (req, res) => {
    try {
        const { recipeId } = req.params;
        const recipe = await Recipe.findById(recipeId).populate('comments.user');
        res.status(200).json(recipe.comments);
    } catch (error) {
        res.status(500).send(error.message);
    }
};

// Add a comment to a recipe
exports.addComment = async (req, res) => {
    try {
        const { recipeId } = req.params;
        const newComment = { ...req.body, createdAt: new Date() };
        const recipe = await Recipe.findById(recipeId);
        recipe.comments.push(newComment);
        await recipe.save();
        res.status(201).send('Comment added successfully');
    } catch (error) {
        res.status(500).send(error.message);
    }
};

// Update a comment
exports.updateComment = async (req, res) => {
    try {
        const { recipeId, commentId } = req.params;
        const recipe = await Recipe.findById(recipeId);
        const commentIndex = recipe.comments.findIndex(c => c._id.toString() === commentId);
        if (commentIndex === -1) {
            return res.status(404).send('Comment not found');
        }
        recipe.comments[commentIndex] = { ...recipe.comments[commentIndex].toObject(), ...req.body };
        await recipe.save();
        res.status(200).send('Comment updated successfully');
    } catch (error) {
        res.status(500).send(error.message);
    }
};

// Delete a comment
exports.deleteComment = async (req, res) => {
    try {
        const { recipeId, commentId } = req.params;
        const recipe = await Recipe.findById(recipeId);
        recipe.comments = recipe.comments.filter(c => c._id.toString() !== commentId);
        await recipe.save();
        res.status(200).send('Comment deleted successfully');
    } catch (error) {
        res.status(500).send(error.message);
    }
};

imageController.js

const Recipe = require('../models/recipeModel');


exports.getImagesFromRecipe = async (req, res) => {
    try {
        const { recipeId } = req.params;

        const recipe = await Recipe.findById(recipeId);
        if (!recipe) {
            return res.status(404).send('Recipe not found');
        }

        res.status(200).json({
            status: 'success',
            data: {
                images: recipe.images
            }
        });
    } catch (error) {
        res.status(500).send(error.message);
    }
};


exports.addImageToRecipe = async (req, res) => {
    try {
        const { recipeId } = req.params;
        const { imageUrl } = req.body;

        const recipe = await Recipe.findById(recipeId);
        if (!recipe) {
            return res.status(404).send('Recipe not found');
        }

        recipe.images.push(imageUrl);
        await recipe.save();

        res.status(200).json({
            status: 'success',
            data: {
                recipe
            }
        });
    } catch (error) {
        res.status(500).send(error.message);
    }
};

exports.updateImageInRecipe = async (req, res) => {
    try {
        const { recipeId, imageIndex } = req.params;
        const { newImageUrl } = req.body;

        const recipe = await Recipe.findById(recipeId);
        if (!recipe) {
            return res.status(404).send('Recipe not found');
        }

        if (imageIndex < 0 || imageIndex >= recipe.images.length) {
            return res.status(400).send('Invalid image index');
        }

        recipe.images[imageIndex] = newImageUrl;
        await recipe.save();

        res.status(200).json({
            status: 'success',
            data: {
                recipe
            }
        });
    } catch (error) {
        res.status(500).send(error.message);
    }
};


exports.deleteImageFromRecipe = async (req, res) => {
    try {
        const { recipeId, imageIndex } = req.params;

        const recipe = await Recipe.findById(recipeId);
        if (!recipe) {
            return res.status(404).send('Recipe not found');
        }

        if (imageIndex < 0 || imageIndex >= recipe.images.length) {
            return res.status(400).send('Invalid image index');
        }

        recipe.images.splice(imageIndex, 1);
        await recipe.save();

        res.status(200).json({
            status: 'success',
            data: {
                recipe
            }
        });
    } catch (error) {
        res.status(500).send(error.message);
    }
};

// Add more functions as needed for other image operations

exports.listAllImages = async (req, res) => {
    try {
        // Trouver toutes les recettes
        const recipes = await Recipe.find({});

        // Collecter toutes les images de toutes les recettes
        let allImages = [];
        recipes.forEach(recipe => {
            allImages = allImages.concat(recipe.images);
        });

        res.status(200).json({
            status: 'success',
            data: {
                images: allImages
            }
        });
    } catch (error) {
        res.status(500).send(error.message);
    }
};

 

stepController.js

const Recipe = require('../models/recipeModel');

exports.getStepsFromRecipe = async (req, res) => {
    try {
        const { recipeId } = req.params;

        const recipe = await Recipe.findById(recipeId);
        if (!recipe) {
            return res.status(404).send('Recipe not found');
        }

        res.status(200).json({
            status: 'success',
            data: {
                steps: recipe.steps
            }
        });
    } catch (error) {
        res.status(500).send(error.message);
    }
};


exports.addStepToRecipe = async (req, res) => {
    try {
        const { recipeId } = req.params;
        const { step } = req.body;

        const recipe = await Recipe.findById(recipeId);
        if (!recipe) {
            return res.status(404).send('Recipe not found');
        }

        recipe.steps.push(step);
        await recipe.save();

        res.status(200).json({
            status: 'success',
            data: {
                recipe
            }
        });
    } catch (error) {
        res.status(500).send(error.message);
    }
};

exports.updateStepInRecipe = async (req, res) => {
    try {
        const { recipeId, stepIndex } = req.params;
        const { step } = req.body;

        const recipe = await Recipe.findById(recipeId);
        if (!recipe) {
            return res.status(404).send('Recipe not found');
        }

        if (stepIndex >= recipe.steps.length) {
            return res.status(400).send('Invalid step index');
        }

        recipe.steps[stepIndex] = step;
        await recipe.save();

        res.status(200).json({
            status: 'success',
            data: {
                recipe
            }
        });
    } catch (error) {
        res.status(500).send(error.message);
    }
};

exports.deleteStepFromRecipe = async (req, res) => {
    try {
        const { recipeId, stepIndex } = req.params;

        const recipe = await Recipe.findById(recipeId);
        if (!recipe) {
            return res.status(404).send('Recipe not found');
        }

        if (stepIndex >= recipe.steps.length) {
            return res.status(400).send('Invalid step index');
        }

        recipe.steps.splice(stepIndex, 1);
        await recipe.save();

        res.status(200).json({
            status: 'success',
            data: {
                recipe
            }
        });
    } catch (error) {
        res.status(500).send(error.message);
    }
};

 

Serveur et Application Principale

app.js

// Importation des modules nécessaires pour l'application.
const express = require('express'); // Importe le framework Express.
const mongoose = require('mongoose'); // Importe Mongoose pour interagir avec MongoDB.
const dotenv = require('dotenv'); // Importe dotenv pour gérer les variables d'environnement.
const cors = require('cors'); // Importe le package CORS pour gérer la politique de partage de ressources entre origines.

// Configuration de dotenv pour lire le fichier .env.
dotenv.config({ path: './config.env' });

// Importation des routes pour différentes parties de l'application.
const userRoutes = require('./routes/userRoutes'); // Routes pour la gestion des utilisateurs.
const authRoutes = require('./routes/authRoutes'); // Routes pour l'authentification (login, signup).
const recipeRoutes = require('./routes/recipeRoutes'); // Routes pour la gestion des recettes.
const commentsRoutes = require('./routes/commentRoutes'); // Routes pour la gestion des commentaires sur les recettes.
const categoryRoutes = require('./routes/categoryRoutes'); // Routes pour la gestion des catégories de recettes.
const imageRoutes = require('./routes/imageRoutes'); // Routes pour la gestion des images dans les recettes.
const stepRoutes = require('./routes/stepRoutes'); // Routes pour la gestion des étapes des recettes.
const ingredientRoutes = require('./routes/ingredientRoutes'); // Routes pour la gestion des ingrédients.

// Création d'une nouvelle application Express.
const app = express();

// Middleware pour analyser le corps des requêtes en JSON.
app.use(express.json());

// Configuration du CORS pour autoriser les requêtes depuis un domaine spécifique (ici, 'http://localhost:5173').
app.use(cors({
    origin: 'http://localhost:5173' // Autorise les requêtes de ce domaine.
}));

// Configuration des routes de l'application avec les chemins de base.
app.use('/api/v1/users', userRoutes); // Routes pour les utilisateurs.
app.use('/api/v1/auth', authRoutes); // Routes pour l'authentification.
app.use('/api/v1/recipes', recipeRoutes); // Routes pour les recettes.
app.use('/api/v1/recipes', commentsRoutes); // Routes pour les commentaires des recettes.
app.use('/api/v1/recipes', categoryRoutes); // Routes pour les catégories des recettes.
app.use('/api/v1/recipes', imageRoutes); // Routes pour les images des recettes.
app.use('/api/v1/recipes', stepRoutes); // Routes pour les étapes des recettes.
app.use('/api/v1/ingredients', ingredientRoutes); // Routes pour les ingrédients.

// Gestion des erreurs globales peut être ajoutée ici.

// Exporte l'application pour une utilisation dans d'autres fichiers, comme le point d'entrée du serveur.
module.exports = app;

Ce code configure une application Express avec des routes spécifiques pour gérer différentes entités comme les utilisateurs, les recettes, les ingrédients, etc. Il utilise dotenv pour charger les configurations et cors pour gérer les requêtes cross-origin, ce qui est particulièrement important lors du développement en local ou dans des environnements où le frontend et le backend sont hébergés séparément.

server.js

// Importe l'application Express configurée depuis le fichier 'app.js'.
const app = require('./app');

// Configure dotenv pour charger les variables d'environnement depuis un fichier .env.
require('dotenv').config();

// Importe Mongoose, une bibliothèque pour interagir avec MongoDB.
const mongoose = require('mongoose');

// Établit une connexion à la base de données MongoDB en utilisant l'URI fournie dans les variables d'environnement.
mongoose.connect(process.env.DATABASE, {
  useNewUrlParser: true, // Utilise le nouvel analyseur d'URL de MongoDB.
  useUnifiedTopology: true, // Utilise le nouveau moteur de découverte et de surveillance de serveur.
}).then(() => console.log('DB connection successful!')); // Log une confirmation une fois la connexion à la base de données réussie.

// Définit le port de l'application. Utilise la variable d'environnement PORT, ou 3000 par défaut.
const port = process.env.PORT || 3000;

// Démarre le serveur sur le port spécifié et log un message de confirmation.
app.listen(port, () => {
  console.log(`App running on port ${port}...`);
});

Dans ce code, vous configurez et démarrez un serveur Express. Vous chargez d'abord les variables d'environnement, puis vous établissez une connexion avec MongoDB en utilisant Mongoose. Une fois la connexion à la base de données établie, vous démarrez l'application Express pour qu'elle écoute sur un port spécifique. Ce fichier est typiquement exécuté pour démarrer l'application.

 

Sécurité et Validation

N'oubliez pas d'ajouter des mesures de sécurité telles que la validation des entrées, l'utilisation de HTTPS, la protection contre les attaques CSRF, etc. Utilisez des packages comme helmet, cors, express-rate-limit, et hpp pour renforcer la sécurité de votre application.

Conclusion

Cet aperçu vous donne une structure de base pour commencer à construire votre application de gestion de recettes avec Node.js, Express et MongoDB. Vous devrez étendre et personnaliser chaque partie selon les besoins spécifiques de votre application, y compris la gestion des erreurs, la validation des données, et l'ajout de fonctionnalités supplémentaires comme la recherche et la catégorisation des recettes.

 

Utilitaires pour ajouter des données dans la base

Arrêter le server.js pour lancer ce script , ils sont autonomes.

createCollections.js

// Importe le client MongoDB du package 'mongodb'.
const { MongoClient } = require('mongodb');

// Définit une fonction asynchrone pour créer des collections.
async function createCollections() {
    // URL pour se connecter à votre instance MongoDB locale.
    const url = 'mongodb://localhost:27017'; // Remplacez par votre URL de MongoDB si différente.
    // Crée une instance du client MongoDB.
    const client = new MongoClient(url);

    try {
        // Tente de se connecter à la base de données.
        await client.connect();
        console.log('Connecté à MongoDB');

        // Sélectionne la base de données 'recettes'.
        const db = client.db('recettes');

        // Crée la collection 'users' dans la base de données.
        await db.createCollection('users');
        console.log('Collection users créée');

        // Crée la collection 'recipes' dans la base de données.
        await db.createCollection('recipes');
        console.log('Collection recipes créée');

        // Crée la collection 'ingredients' dans la base de données.
        await db.createCollection('ingredients');
        console.log('Collection ingredients créée');
    } catch (e) {
        // En cas d'erreur, affiche l'erreur dans la console.
        console.error('Erreur lors de la création des collections:', e);
    } finally {
        // Ferme la connexion au client MongoDB.
        await client.close();
    }
}

// Exécute la fonction pour créer les collections.
createCollections();

Dans ce script, vous utilisez le client MongoDB pour vous connecter à une base de données MongoDB et créer trois collections : users, recipes et ingredients. Ce script utilise des opérations asynchrones (async/await) pour s'assurer que les actions s'exécutent dans l'ordre prévu. Après avoir créé les collections, il ferme la connexion à la base de données.

addUsers.js

// Importe Mongoose, une bibliothèque ODM (Object Data Modeling) pour MongoDB.
const mongoose = require('mongoose');
// Importe bcryptjs pour le hachage de mot de passe.
const bcrypt = require('bcryptjs');
// Importe le modèle User, assurez-vous que le chemin d'accès est correct.
const User = require('../models/userModel'); 

// Configuration de la connexion à la base de données MongoDB.
const db = 'mongodb://127.0.0.1:27017/recettes'; // Remplacez par votre URL de MongoDB si différente.
mongoose.connect(db)
  .then(() => console.log('Connecté à la base de données MongoDB')) // Log une confirmation en cas de connexion réussie.
  .catch(err => console.error('Erreur de connexion à MongoDB', err)); // Log une erreur en cas d'échec de connexion.

// Définit une liste d'utilisateurs à ajouter dans la base de données.
const users = [
  { username: 'Alice', email: 'alice@example.com', password: 'password123', role: 'user' },
  { username: 'Bob', email: 'bob@example.com', password: 'password456', role: 'admin' },
  { username: 'Charlie', email: 'charlie@example.com', password: 'password789', role: 'author' }
  // Vous pouvez ajouter d'autres utilisateurs ici si nécessaire.
];

// Fonction asynchrone pour ajouter des utilisateurs à la base de données.
async function addUsers() {
  try {
    // Parcourt chaque utilisateur et hash son mot de passe.
    for (let user of users) {
      user.password = await bcrypt.hash(user.password, 12); // Hash les mots de passe avec bcrypt.
    }

    // Insère tous les utilisateurs dans la base de données en une seule opération.
    const insertedUsers = await User.insertMany(users);
    console.log(`${insertedUsers.length} utilisateurs ajoutés avec succès`); // Log le nombre d'utilisateurs ajoutés.
  } catch (err) {
    // Log l'erreur si quelque chose ne va pas pendant l'insertion.
    console.error('Erreur lors de l\'ajout des utilisateurs', err);
  } finally {
    // Ferme la connexion à la base de données après l'opération.
    await mongoose.disconnect();
  }
}

// Exécute la fonction pour ajouter les utilisateurs.
addUsers();

Ce script utilise Mongoose pour se connecter à une base de données MongoDB et ajouter une liste d'utilisateurs. Il hash les mots de passe avant de les insérer pour des raisons de sécurité. La fonction addUsers est asynchrone, ce qui permet d'attendre que les opérations de hachage et d'insertion soient terminées avant de continuer. Après avoir ajouté les utilisateurs, il ferme la connexion à la base de données.

addRecipe.js

// Importe Mongoose, une bibliothèque ODM pour MongoDB, et le modèle de recette.
const mongoose = require('mongoose');
const Recipe = require('../models/recipeModel'); // Assurez-vous que le chemin vers votre modèle Recipe est correct.

// Configure la connexion à la base de données MongoDB.
const db = 'mongodb://127.0.0.1:27017/recettes'; // Remplacez par votre URL de MongoDB si différente.
mongoose.connect(db)
  .then(() => console.log('Connecté à la base de données MongoDB')) // Affiche un message en cas de connexion réussie.
  .catch(err => console.error('Erreur de connexion à MongoDB', err)); // Affiche un message en cas d'échec de connexion.

// Liste des recettes à ajouter dans la base de données.
// Assurez-vous que les ObjectId des ingrédients correspondent à de vrais objets dans votre base de données.
const recipes = [
  {
    // Détails de la première recette.
    title: 'Recette de Tomate',
    description: 'Une délicieuse recette de tomate...',
    ingredients: [ 
      { ingredient: '6573158a027ed738bd0e1010', quantity: '2' }, // ObjectId de l'ingrédient 'Tomate'.
      { ingredient: '6573158a027ed738bd0e1013', quantity: '4' }  // ObjectId de l'ingrédient 'Laitue'.
    ],
    steps: ['Couper les tomates', 'Cuire les tomates'],
    prepTime: 30,
    images: ['image1.jpg', 'image2.jpg'],
    comments: [
      // Commentaires de la recette.
      {
        user: '65719f72a85ab5047e72a3eb', // Remplacez par un ObjectId valide d'un utilisateur.
        text: 'Super mega recette !',
        rating: 5,
        createdAt: new Date()
      }
    ],
    category: [ 'vegetarian', 'salad']
  },
  // ... Autres recettes ...
];

// Fonction asynchrone pour ajouter les recettes dans la base de données.
async function addRecipes() {
  try {
    // Parcourt chaque recette et les sauvegarde dans la base de données.
    for (const recipe of recipes) {
      const newRecipe = new Recipe(recipe);
      await newRecipe.save();
    }
    console.log('Recettes ajoutées avec succès');
  } catch (err) {
    // Affiche une erreur si l'ajout des recettes échoue.
    console.error('Erreur lors de l\'ajout des recettes', err);
  } finally {
    // Ferme la connexion à la base de données.
    mongoose.disconnect();
  }
}

// Exécute la fonction pour ajouter les recettes.
addRecipes();

Ce script se connecte à MongoDB, crée une liste de recettes avec des détails spécifiques, et utilise ensuite une fonction asynchrone pour ajouter ces recettes dans la base de données. Chaque recette est créée en tant qu'instance du modèle Recipe et sauvegardée dans la base de données. Après avoir terminé l'opération, le script ferme la connexion à MongoDB.

addIngredients.js

// Importe Mongoose, une bibliothèque ODM pour MongoDB, et le modèle d'ingrédient.
const mongoose = require('mongoose');
const Ingredient = require('../models/ingredientModel'); // Assurez-vous que le chemin vers votre modèle Ingredient est correct.
console.log("Ingredient: "+Ingredient); // Affiche l'objet Ingredient pour vérifier s'il est correctement créé.

// Configure la connexion à la base de données MongoDB.
const db = 'mongodb://127.0.0.1:27017/recettes'; // Remplacez par votre URL de MongoDB si différente.
mongoose.connect(db)
  .then(() => console.log('Connecté à la base de données MongoDB')) // Affiche un message en cas de connexion réussie.
  .catch(err => console.error('Erreur de connexion à MongoDB', err)); // Affiche un message en cas d'échec de connexion.

// Liste des ingrédients à ajouter dans la base de données.
const ingredients = [
  { name: 'Tomate', quantite: 1, nutritionInfo: { calories: 18, protein: 0.9, fat: 0.2 } },
  { name: 'Pomme de terre', quantite: 1.0, nutritionInfo: { calories: 77, protein: 2.0, fat: 0.1 } },
  { name: 'Chocolat',quantite: 2.0, nutritionInfo: { calories: 150.0, protein: 5.0, fat: 2.1 } },
  { name: 'Laitue romaine',quantite: 1.0, nutritionInfo: { calories: 25.0, protein: 0.1, fat: 0.0 } }  
];

// Fonction asynchrone pour ajouter les ingrédients dans la base de données.
async function addIngredients() {
  try {
    // Insère tous les ingrédients en une seule opération en utilisant `insertMany`.
    const insertedIngredients = await Ingredient.insertMany(ingredients);
    console.log(`${insertedIngredients.length} ingrédients ajoutés avec succès`); // Affiche le nombre d'ingrédients ajoutés.
  } catch (err) {
    // Affiche une erreur si l'ajout des ingrédients échoue.
    console.error('Erreur lors de l\'ajout des ingrédients', err);
  } finally {
    // Ferme la connexion à la base de données après l'opération.
    await mongoose.disconnect();
  }
}

// Exécute la fonction pour ajouter les ingrédients.
addIngredients();

Ce script se connecte à MongoDB, crée une liste d'ingrédients avec des détails spécifiques, et utilise ensuite une fonction asynchrone pour ajouter ces ingrédients dans la base de données. Chaque ingrédient est créé en tant qu'instance du modèle Ingredient et inséré en utilisant insertMany pour l'efficacité. Après avoir terminé l'opération, le script ferme la connexion à MongoDB.

 

REST Client

###
# Authenticate and login a user. This request sends the user's email and password to the server.
POST http://localhost:3000/api/v1/auth/login
Content-Type: application/json

{
  "email": "admin@example.com", 
  "password": "admin"
}

###
# Register a new user. This request includes the necessary information to create a new user account.
POST http://localhost:3000/api/v1/auth/signup
Content-Type: application/json

{
  "username": "admin",
  "email": "admin@example.com",
  "password": "admin",
  "role": "admin" 
}

###
# Create a new user. This endpoint is used to add a new user to the system.
POST http://localhost:3000/api/v1/users
Content-Type: application/json

{
    "username": "newUser5",
    "email": "newuser5@example.com",
    "password": "password123",
    "role": "user"
}

###
# Get a specific user by their user ID. Replace {userId} with the actual ID of the user.
GET http://localhost:3000/api/v1/users/{userId}
Content-Type: application/json

###
# Update a specific user's details. Replace {userId} with the ID of the user you want to update.
PATCH http://localhost:3000/api/v1/users/{userId}
Content-Type: application/json

{
    "username": "updatedUser",
    "email": "updateduser@example.com"
}

###
# Delete a specific user. Replace {userId} with the ID of the user to be deleted.
DELETE http://localhost:3000/api/v1/users/{userId}
Content-Type: application/json

###
# List all users in the system. This endpoint retrieves a list of all users.
GET http://localhost:3000/api/v1/users/

 

# Lister toutes les recettes
GET http://localhost:3000/api/v1/recipes
Content-Type: application/json

# Obtenir une recette spécifique
GET http://localhost:3000/api/v1/recipes/recipe/6573207411605f7f3dcf28be
Content-Type: application/json

# Créer une nouvelle recette (Gâteau au Kaki)
POST http://localhost:3000/api/v1/recipes/recipe
Content-Type: application/json

{
  "title": "Gâteau au Kaki",
  "description": "Un délicieux gâteau au Kaki étrange et riche.",
  "ingredients": [
    {
      "ingredient": "6573158a027ed738bd0e1010",
      "quantity": "200g"
    },
    {
      "ingredient": "6573158a027ed738bd0e1010",
      "quantity": "200g"
    }
  ],
  "steps": [
    "Préchauffer le four à 180°C.",
    "Faire fondre le Kaki et le beurre.",
    "Ajouter le sucre et les œufs, puis incorporer la farine.",
    "Verser la pâte dans un moule et cuire pendant 25 minutes."
  ],
  "prepTime": 60,
  "images": ["url_image_gateau_chocolat.jpg"],
  "comments": [
    {
      "user": "65719f72a85ab5047e72a3eb",
      "text": "Meilleur gâteau au Kaki de la planète !",
      "rating": 5,
      "createdAt": "2023-12-06T00:00:00.000Z"
    }, 
    {
      "user": "65719f72a85ab5047e72a3eb",
      "text": "Meilleur gâteau que j'ai jamais fait !",
      "rating": 5,
      "createdAt": "2023-12-06T00:00:00.000Z"
    }
  ],
  "category": ["dessert", "fruit"]
}

# Créer une autre nouvelle recette (Gâteau au chocolat swizz)
POST http://localhost:3000/api/v1/recipes/recipe
Content-Type: application/json

{
  "title": "Gâteau au chocolat swizz",
  "description": "Un délicieux gâteau au chocolat moelleux et riche.",
  "ingredients": [
    {
      "name": "Chocolat",
      "marque": "swizzmax",
      "quantity": "200g"
    },
    {
      "name": "Beurre",
      "quantity": "100g"
    },
    {
      "name": "Sucre",
      "quantity": "150g"
    },
    {
      "name": "Oeufs",
      "quantity": "3"
    },
    {
      "name": "Farine",
      "quantity": "200g"
    }
  ],
  "steps": [
    "Préchauffer le four à 180°C.",
    "Faire fondre le chocolat et le beurre.",
    "Ajouter le sucre et les œufs, puis incorporer la farine.",
    "Verser la pâte dans un moule et cuire pendant 25 minutes."
  ],
  "prepTime": 60,
  "images": ["url_image_gateau_chocolat.jpg"],
  "comments": {
    "user": "65719f72a85ab5047e72a3eb",
    "text": "Meilleur gâteau au chocolat que j'ai jamais fait !",
    "rating": 5,
    "createdAt": "2023-12-06T00:00:00.000Z"
  },
  "category": "dessert"
}
# Lister tous les ingrédients
###
GET http://localhost:3000/api/v1/ingredients
Content-Type: application/json  # Indique que le contenu de la requête est au format JSON
###

# Obtenir un ingrédient spécifique
###
GET http://localhost:3000/api/v1/ingredients/ingredient/6573158a027ed738bd0e1010
Content-Type: application/json  # Requête GET pour obtenir un ingrédient spécifique par son ID
###

# Ajouter un nouvel ingrédient
###
POST http://localhost:3000/api/v1/ingredients/ingredient
Content-Type: application/json  # Indique que le contenu de la requête est au format JSON

{
  "name": "Farine de blé",           # Nom de l'ingrédient
  "quantite": 100,                   # Quantité de l'ingrédient
  "nutritionInfo": {                 # Informations nutritionnelles
    "calories": 364,                 # Nombre de calories
    "protein": 10,                   # Quantité de protéines
    "fat": 1                         # Quantité de matières grasses
  }
}
###

# Mettre à jour un ingrédient spécifique
###
PATCH http://localhost:3000/api/v1/ingredients/ingredient/{ingredientId}
Content-Type: application/json  # Indique que le contenu de la requête est au format JSON

{
  "name": "Farine de blé mise à jour",  # Nouveau nom de l'ingrédient
  "quantite": 150,                      # Nouvelle quantité
  "nutritionInfo": {                    # Nouvelles informations nutritionnelles
    "calories": 350,                    # Nouveau nombre de calories
    "protein": 11,                      # Nouvelle quantité de protéines
    "fat": 0.8                          # Nouvelle quantité de matières grasses
  }
}
###

# Supprimer un ingrédient spécifique
###
DELETE http://localhost:3000/api/v1/ingredients/ingredient/{ingredientId}
Content-Type: application/json  # Indique que le contenu de la requête est au format JSON
###

 

# Lister toutes les catégories dans toutes les recettes
###
GET http://localhost:3000/api/v1/recipes/categories
Content-Type: application/json  # Indique que le contenu de la requête est au format JSON
# Cette requête récupère une liste de toutes les catégories utilisées dans l'ensemble des recettes
###

# Lister toutes les catégories dans une recette spécifique
###
GET http://localhost:3000/api/v1/recipes/657899855dcdd31f040f2ea8/categories
Content-Type: application/json  # Indique que le contenu de la requête est au format JSON
# Cette requête récupère la liste des catégories pour une recette spécifique, identifiée par son ID
###

# Ajouter une catégorie dans une recette
###
POST http://localhost:3000/api/v1/recipes/6578d0a45dcdd31f040f3312/category
Content-Type: application/json  # Indique que le contenu de la requête est au format JSON

{
    "category": "toto"  # Nom de la catégorie à ajouter à la recette spécifiée
}
# Cette requête ajoute une nouvelle catégorie à une recette spécifique, identifiée par son ID
###
# Lister toutes les catégories dans toutes les recettes
###
GET http://localhost:3000/api/v1/recipes/categories
Content-Type: application/json  # Indique que le contenu de la requête est au format JSON
# Cette requête récupère une liste de toutes les catégories utilisées dans l'ensemble des recettes
###

# Lister toutes les catégories dans une recette spécifique
###
GET http://localhost:3000/api/v1/recipes/657899855dcdd31f040f2ea8/categories
Content-Type: application/json  # Indique que le contenu de la requête est au format JSON
# Cette requête récupère la liste des catégories pour une recette spécifique, identifiée par son ID
###

# Ajouter une catégorie dans une recette
###
POST http://localhost:3000/api/v1/recipes/6578d0a45dcdd31f040f3312/category
Content-Type: application/json  # Indique que le contenu de la requête est au format JSON

{
    "category": "toto"  # Nom de la catégorie à ajouter à la recette spécifiée
}
# Cette requête ajoute une nouvelle catégorie à une recette spécifique, identifiée par son ID
###

 

###
# listing images to a specific recipe
// GET http://localhost:3000/api/v1/recipes/{recipeId}/images
GET http://localhost:3000/api/v1/recipes/65786fcffc0fa8fd67dc04a8/images

###
GET http://localhost:3000/api/v1/recipes/all-images


###
# Add an image to a specific recipe
POST http://localhost:3000/api/v1/recipes/{recipeId}/images
Content-Type: application/json

{
    "imageUrl": "/images/path-to-your-image.jpg"
}

###
# Add an image to a specific recipe
PUT http://localhost:3000/api/v1/recipes/{recipeId}/images
Content-Type: application/json

{
    "imageUrl": "/images/path-to-your-image.jpg"
}

###
# Delete an image from a specific recipe
DELETE http://localhost:3000/api/v1/recipes/{recipeId}/images/{imageIndex}
Content-Type: application/json

 

 

Mettre en place la protection par role

 

  1. Définir un Tableau de Rôles Autorisés : Vous pouvez définir un tableau de rôles autorisés, soit directement dans le fichier de route, soit dans un fichier de configuration séparé que vous importerez.

    Exemple dans le fichier de route :

    const allowedRoles = ['admin', 'user'];

    Ou dans un fichier de configuration (par exemple config/roleConfig.js) :

    // config/roleConfig.js module.exports = { allowedRoles: ['admin', 'user'] };

    Puis importez-le dans votre fichier de route :

    const { allowedRoles } = require('../config/roleConfig');
  2. Utiliser le Tableau dans restrictTo : Lorsque vous configurez vos routes, utilisez le spread operator (...) pour passer le tableau de rôles à la fonction restrictTo.

    router.get('/your-protected-route', protect, restrictTo(...allowedRoles), yourControllerFunction);

    Avec cette méthode, restrictTo recevra chaque élément du tableau allowedRoles comme un argument séparé.

  3. Flexibilité et Maintenance : Ce format rend la modification des rôles autorisés beaucoup plus simple. Si vous devez ajouter ou supprimer des rôles, vous pouvez le faire en mettant à jour le tableau allowedRoles, sans avoir besoin de changer le code de chaque route individuellement.

En utilisant un tableau pour gérer les rôles autorisés, vous augmentez la flexibilité de votre application et réduisez la duplication du code, facilitant ainsi la maintenance et les éventuelles modifications futures.

 

créer un répertoire  config à la racine de votre server

crée le fichier config/roleConfig.js

// config/roleConfig.js
module.exports = {
    allowedRoles: ['admin', 'user'],
    allowedRolesUser: ['admin'],
    allowedRolesRecipe: ['admin', 'user'],
    allowedRolesIngredient: ['admin', 'user']
  };

 

Mise en place de la protection des routes avec userRoute.js  comme exemple 

// Importe le module Express pour créer un routeur
const express = require('express');

// Importe le contrôleur utilisateur pour gérer les actions liées aux utilisateurs
const userController = require('../controllers/userController');

// Importe les middlewares d'authentification et de restriction de rôle
const { protect, restrictTo } = require('../middleware/authMiddleware');

// Importe la configuration des rôles autorisés depuis un fichier de configuration
const { allowedRoles } = require('../config/roleConfig');

// Crée un nouvel objet Router d'Express pour définir des routes liées aux utilisateurs
const router = express.Router();

// Route pour obtenir tous les utilisateurs
// Utilise 'protect' pour s'assurer que l'utilisateur est authentifié
// Utilise 'restrictTo' pour restreindre l'accès aux rôles spécifiés dans 'allowedRoles'
router.get('/', protect, restrictTo(...allowedRolesUser), userController.getAllUsers);

// Route pour créer un nouvel utilisateur
// Utilise 'protect' et 'restrictTo' de la même manière que ci-dessus
router.post('/', protect, restrictTo(...allowedRoles), userController.createUser);

// Route pour obtenir un utilisateur spécifique par son ID
// ':userId' est un paramètre de route qui capture l'ID de l'utilisateur
router.get('/:userId', protect, restrictTo(...allowedRoles), userController.getUser);

// Route pour mettre à jour un utilisateur spécifique par son ID
router.patch('/:userId', protect, restrictTo(...allowedRolesUser), userController.updateUser);

// Route pour supprimer un utilisateur spécifique par son ID
router.delete('/:userId', protect, restrictTo(...allowedRolesUser), userController.deleteUser);

// Emplacement pour ajouter plus de routes si nécessaire

// Exporte le routeur pour l'utiliser dans d'autres parties de l'application
module.exports = router;

 

 

Le nouveau fichier modidfier pour user Rest Client

Mainteant, nous devons ajouter le token jwt pour accéder au routes pour user.

###
POST http://localhost:3000/api/v1/auth/login
Content-Type: application/json

{
  "email": "admin@example.com", 
  "password": "admin"
}

###
POST http://localhost:3000/api/v1/auth/signup
Content-Type: application/json

{
  "username": "admin",
  "email": "admin@example.com",
  "password": "admin",
  "role": "admin" 
}



###
# Create a new user
POST http://localhost:3000/api/v1/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1NzE5ZjcyYTg1YWI1MDQ3ZTcyYTNlYiIsImlhdCI6MTcwMjYzNjY0MSwiZXhwIjoxNzEwNDEyNjQxfQ.d-i-YfukCQrGvh7JAj8FtXnAgH-YyuK0i0_2tPy4pcg
Content-Type: application/json

{
    "username": "newUser5",
    "email": "newuse5r@example.com",
    "password": "password123",
    "role": "user"
}
###

###
# Get a specific user
GET http://localhost:3000/api/v1/users/{userId}
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1NzE5ZjcyYTg1YWI1MDQ3ZTcyYTNlYiIsImlhdCI6MTcwMjYzNjY0MSwiZXhwIjoxNzEwNDEyNjQxfQ.d-i-YfukCQrGvh7JAj8FtXnAgH-YyuK0i0_2tPy4pcg
Content-Type: application/json


###
# Update a specific user
PATCH http://localhost:3000/api/v1/users/{userId}
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1NzE5ZjcyYTg1YWI1MDQ3ZTcyYTNlYiIsImlhdCI6MTcwMjYzNjY0MSwiZXhwIjoxNzEwNDEyNjQxfQ.d-i-YfukCQrGvh7JAj8FtXnAgH-YyuK0i0_2tPy4pcg
Content-Type: application/json

{
    "username": "updatedUser",
    "email": "updateduser@example.com"
}


###
# Delete a specific user
DELETE http://localhost:3000/api/v1/users/{userId}
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1NzE5ZjcyYTg1YWI1MDQ3ZTcyYTNlYiIsImlhdCI6MTcwMjYzNjY0MSwiZXhwIjoxNzEwNDEyNjQxfQ.d-i-YfukCQrGvh7JAj8FtXnAgH-YyuK0i0_2tPy4pcg
Content-Type: application/json


###
# list all users
GET http://localhost:3000/api/v1/users/
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1NzE5ZjcyYTg1YWI1MDQ3ZTcyYTNlYiIsImlhdCI6MTcwMjYzNjY0MSwiZXhwIjoxNzEwNDEyNjQxfQ.d-i-YfukCQrGvh7JAj8FtXnAgH-YyuK0i0_2tPy4pcg
Content-Type: application/json

 

Quand vous cliquez sur send request pour le login  login votre token vous sera renvoyé.  Il faut remplacer celui existant  par le votre.

Modifié le: vendredi 22 décembre 2023, 04:16