diff --git a/api/app.js b/api/app.js index ee5b6c0..6ab858f 100644 --- a/api/app.js +++ b/api/app.js @@ -26,6 +26,7 @@ app.use('/users', require('./routes/users')); app.use('/admin', require('./routes/admin')); app.use('/favorites', require('./routes/favorites')); app.use('/comments', require('./routes/comments')); +app.use('/quotes', require('./routes/quotes')); /* Errors Handling */ app.use((_req, _res, next) => errorHandling(next, { statusCode: 404, message: "La route n'existe pas!" })); // 404 @@ -41,6 +42,7 @@ const Categories = require('./models/categories'); const Users = require('./models/users'); const Favorites = require('./models/favorites'); const Comments = require('./models/comments'); +const Quotes = require('./models/quotes'); // A function has a category Categories.hasOne(Functions, { constraints: true, onDelete: 'CASCADE'}); @@ -58,6 +60,10 @@ Comments.belongsTo(Users, { constraints: false }); Functions.hasMany(Comments); Comments.belongsTo(Functions, { constraints: false }); +// Users can suggest new quotes +Users.hasMany(Quotes); +Quotes.belongsTo(Users, { constraints: false }); + /* Server */ // sequelize.sync({ force: true }) sequelize.sync() diff --git a/api/assets/functions/functionObject.js b/api/assets/functions/functionObject.js index fe5e824..ba8ebe6 100644 --- a/api/assets/functions/functionObject.js +++ b/api/assets/functions/functionObject.js @@ -8,6 +8,7 @@ const convertCurrencyOutput = require('./main/convertCurrency'); const calculateAgeOutput = require('./main/calculateAge'); const heapAlgorithmOutput = require('./main/heapAlgorithm'); const convertEncodingOutput = require('./main/convertEncoding'); +const randomQuote = require('./main/randomQuote'); const functionObject = { randomNumber : randomNumberOutput, @@ -19,7 +20,8 @@ const functionObject = { convertCurrency : convertCurrencyOutput, calculateAge : calculateAgeOutput, heapAlgorithm : heapAlgorithmOutput, - convertEncoding : convertEncodingOutput + convertEncoding : convertEncodingOutput, + randomQuote : randomQuote, }; // Choisi la fonction à exécuter diff --git a/api/assets/functions/main/randomQuote.js b/api/assets/functions/main/randomQuote.js new file mode 100644 index 0000000..66c3027 --- /dev/null +++ b/api/assets/functions/main/randomQuote.js @@ -0,0 +1,23 @@ +const errorHandling = require('../../utils/errorHandling'); +const { serverError } = require('../../config/errors'); +const Quotes = require('../../../models/quotes'); +const Users = require('../../../models/users'); +const sequelize = require('../../utils/database'); + +module.exports = randomQuote = async ({ res, next }, _argsObject) => { + try { + const quote = await Quotes.findOne({ + order: sequelize.random(), + include: [ + { model: Users, attributes: ["name", "logo"] } + ], + attributes: { + exclude: ["isValidated"] + }, + }); + return res.status(200).json(quote); + } catch (error) { + console.log(error); + return errorHandling(next, serverError); + } +} \ No newline at end of file diff --git a/api/controllers/admin.js b/api/controllers/admin.js index 36f9819..1495f6e 100644 --- a/api/controllers/admin.js +++ b/api/controllers/admin.js @@ -5,6 +5,8 @@ const errorHandling = require('../assets/utils/errorHandling'); const { serverError } = require('../assets/config/errors'); const Functions = require('../models/functions'); const Categories = require('../models/categories'); +const Quotes = require('../models/quotes'); +const Users = require('../models/users'); const helperQueryNumber = require('../assets/utils/helperQueryNumber'); const Sequelize = require('sequelize'); const deleteFilesNameStartWith = require('../assets/utils/deleteFilesNameStartWith'); @@ -248,7 +250,7 @@ exports.putCategory = async (req, res, next) => { category.name = name; category.color = color; const result = await category.save(); - return res.status(200).json({ message: "La catégorie a bien été modifié!", result }); + return res.status(200).json({ message: "La catégorie a bien été modifiée!", result }); } catch (error) { console.log(error); return errorHandling(next, serverError); @@ -263,9 +265,60 @@ exports.deleteCategory = async (req, res, next) => { return errorHandling(next, { message: "La catégorie n'existe pas." }); } await category.destroy(); - return res.status(200).json({ message: "La catégorie a bien été supprimé!" }); + return res.status(200).json({ message: "La catégorie a bien été supprimée!" }); } catch (error) { console.log(error); return errorHandling(next, serverError); } +} + +exports.getQuotes = (req, res, next) => { + const page = helperQueryNumber(req.query.page, 1); + const limit = helperQueryNumber(req.query.limit, 10); + const offset = (page - 1) * limit; + Quotes.findAndCountAll({ + limit, + offset, + where: { + isValidated: 0, + }, + include: [ + { model: Users, attributes: ["name", "logo"] } + ], + order: [['createdAt', 'DESC']] + }) + .then((result) => { + const { count, rows } = result; + const hasMore = (page * limit) < count; + return res.status(200).json({ totalItems: count, hasMore, rows }); + }) + .catch((error) => { + console.log(error); + return errorHandling(next, serverError); + }); +} + +exports.putQuote = async (req, res, next) => { + const { id } = req.params; + const { isValid } = req.body; + try { + if (typeof isValid !== 'boolean') { + return errorHandling(next, { message: "isValid doit être un booléen.", statusCode: 400 }); + } + const quote = await Quotes.findOne({ where: { id, isValidated: 0 } }); + if (!quote) { + return errorHandling(next, { message: "La citation n'existe pas (ou est déjà validé).", statusCode: 404 }); + } + if (isValid) { + quote.isValidated = true; + const result = await quote.save(); + return res.status(200).json({ isValidated: true, message: "La citation a bien été validée!", result }); + } + + await quote.destroy(); + return res.status(200).json({ isValidated: false, message: "La citation a bien été supprimée!" }); + } catch (error) { + console.log(error); + return errorHandling(next, serverError); + } } \ No newline at end of file diff --git a/api/controllers/quotes.js b/api/controllers/quotes.js new file mode 100644 index 0000000..dd88681 --- /dev/null +++ b/api/controllers/quotes.js @@ -0,0 +1,46 @@ +const errorHandling = require('../assets/utils/errorHandling'); +const { serverError } = require('../assets/config/errors'); +const Quotes = require('../models/quotes'); +const Users = require('../models/users'); +const helperQueryNumber = require('../assets/utils/helperQueryNumber'); + +exports.getQuotes = (req, res, next) => { + const page = helperQueryNumber(req.query.page, 1); + const limit = helperQueryNumber(req.query.limit, 10); + const offset = (page - 1) * limit; + Quotes.findAndCountAll({ + limit, + offset, + where: { + isValidated: 1, + }, + include: [ + { model: Users, attributes: ["name", "logo"] } + ], + attributes: { + exclude: ["isValidated"] + }, + order: [['createdAt', 'DESC']] + }) + .then((result) => { + const { count, rows } = result; + const hasMore = (page * limit) < count; + return res.status(200).json({ totalItems: count, hasMore, rows }); + }) + .catch((error) => { + console.log(error); + return errorHandling(next, serverError); + }); +} + +exports.postQuote = (req, res, next) => { + const { quote, author } = req.body; + Quotes.create({ quote, author, userId: req.userId }) + .then((_result) => { + return res.status(200).json({ message: "La citation a bien été ajoutée, elle est en attente de confirmation d'un administrateur." }); + }) + .catch((error) => { + console.log(error); + return errorHandling(next, serverError); + }); +} \ No newline at end of file diff --git a/api/models/quotes.js b/api/models/quotes.js new file mode 100644 index 0000000..d1a9df5 --- /dev/null +++ b/api/models/quotes.js @@ -0,0 +1,24 @@ +const Sequelize = require('sequelize'); +const sequelize = require('../assets/utils/database'); + +module.exports = sequelize.define('quote', { + id: { + type: Sequelize.INTEGER, + allowNull: false, + autoIncrement: true, + primaryKey: true + }, + quote: { + type: Sequelize.STRING, + allowNull: false, + }, + author: { + type: Sequelize.STRING, + allowNull: false, + }, + isValidated: { + type: Sequelize.BOOLEAN, + allowNull: false, + defaultValue: 0 + } +}); \ No newline at end of file diff --git a/api/routes/admin.js b/api/routes/admin.js index 8cfc7a6..dffc4ec 100644 --- a/api/routes/admin.js +++ b/api/routes/admin.js @@ -196,4 +196,14 @@ AdminRouter.route('/categories/:id') // Supprime une catégorie avec son id .delete(isAuth, isAdmin, adminController.deleteCategory); +AdminRouter.route('/quotes') + + // Récupère les citations pas encore validées + .get(isAuth, isAdmin, adminController.getQuotes); + +AdminRouter.route('/quotes/:id') + + // Valide ou supprime une citation + .put(isAuth, isAdmin, adminController.putQuote); + module.exports = AdminRouter; \ No newline at end of file diff --git a/api/routes/quotes.js b/api/routes/quotes.js new file mode 100644 index 0000000..296d96a --- /dev/null +++ b/api/routes/quotes.js @@ -0,0 +1,15 @@ +const { Router } = require('express'); +const quotesController = require('../controllers/quotes'); +const isAuth = require('../middlewares/isAuth'); + +const QuotesRouter = Router(); + +QuotesRouter.route('/') + + // Récupère les citations + .get(quotesController.getQuotes) + + // Proposer une citation + .post(isAuth, quotesController.postQuote); + +module.exports = QuotesRouter; \ No newline at end of file