From b1513dccdded50e3d8cefc7fe370200cf1e294d5 Mon Sep 17 00:00:00 2001 From: Rampeur Date: Tue, 19 Aug 2025 05:13:24 +0200 Subject: [PATCH] =?UTF-8?q?Mise=20=C3=A0=20jour=20de=20controleurs,=20mode?= =?UTF-8?q?ls=20et=20routes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.js | 2 +- src/config/passport-local.js | 3 +- src/controllers/article.controller.js | 326 +++++++++++++++++++++----- src/controllers/product.controller.js | 277 ++++++++++++++++++++++ src/controllers/user.controller.js | 92 +++++++- src/database/models/mongo/User.js | 6 +- src/database/models/mysql/Product.js | 82 +++++++ src/database/models/mysql/User.js | 41 ++-- src/database/relationships/index.js | 22 ++ src/middlewares/asyncHandler.js | 7 + src/routes/api/article.routes.js | 11 - src/routes/api/articles.routes.js | 25 ++ src/routes/api/index.js | 3 +- src/routes/api/products.routes.js | 25 ++ src/routes/api/user.routes.js | 9 +- src/routes/api/users.js | 1 + src/routes/auth.js | 12 - src/services/article.service.js | 24 ++ src/services/user.service.js | 6 +- src/utils/errorResponse.js | 8 + src/utils/helpers.js | 36 +++ 21 files changed, 905 insertions(+), 113 deletions(-) create mode 100644 src/controllers/product.controller.js create mode 100644 src/database/models/mysql/Product.js create mode 100644 src/middlewares/asyncHandler.js delete mode 100644 src/routes/api/article.routes.js create mode 100644 src/routes/api/articles.routes.js create mode 100644 src/routes/api/products.routes.js create mode 100644 src/utils/errorResponse.js create mode 100644 src/utils/helpers.js diff --git a/app.js b/app.js index e03f3a4..e97840b 100644 --- a/app.js +++ b/app.js @@ -55,7 +55,7 @@ if (appEnv !== 'production') { require('./src/database/models/mongo'); config.initAuth(); -config.initApiKey(); +//config.initApiKey(); connectDb.connectAllDb(); app.use(require('./src/routes')); diff --git a/src/config/passport-local.js b/src/config/passport-local.js index 6a41297..e082bcd 100644 --- a/src/config/passport-local.js +++ b/src/config/passport-local.js @@ -7,9 +7,8 @@ passport.use('local', new LocalStrategy({ usernameField: 'user[email]', passwordField: 'user[password]' }, function (email, password, done) { - UserService.getUserByEmail({ email }).then(function (user) { + UserService.getUserByEmail(email).then(function (user) { //User.findOne({ email: email }).then(function (user) { - //console.log(user); if (!user || !user.validPassword(password)) { return done(null, false, { errors: { 'email ou mot de passe': 'invalide' } }); } diff --git a/src/controllers/article.controller.js b/src/controllers/article.controller.js index c079ded..e7b30ea 100644 --- a/src/controllers/article.controller.js +++ b/src/controllers/article.controller.js @@ -1,60 +1,278 @@ const { ArticleService } = require('../services'); +//const { where } = require("sequelize"); +const asyncHandler = require("../middlewares/asyncHandler"); +const Article = require("../database/models/mysql/Article"); +const Tag = require("../database/models/mysql/Tag"); +const User = require("../database/models/mysql/User"); +const ErrorResponse = require("../utils/errorResponse"); +const slug = require("slug"); -const createArticle = async (req, res) => { - try { - const { slug, title, description, body, authorId } = req.body; +const { + appendFollowers, + appendFavorites, + appendTagList, +} = require("../utils/helpers"); - const article = await ArticleService.createArticle({ - slug, - title, - description, - body, - authorId - }); +const includeOptions = [ + { + model: Tag, + as: "tagLists", + attributes: ["name"], + through: { attributes: [] }, + }, + { model: User, as: "author", attributes: { exclude: ["email", "password"] } }, +]; - res.status(201).json({ - message: 'Article created successfully', - data: article, - }); - } catch (err) { - res.status(500).json({ - message: 'something went wrong while creating article', - error: err.message, - }); +module.exports.getArticle = asyncHandler(async (req, res, next) => { + const { slug } = req.params; + + let article = await ArticleService.getArticleBySlug(slug); + + if (!article) { + return next(new ErrorResponse("Article not found", 404)); + } + let tagList = article.tagNameTagTagLists; + //let author = article.author; + article = article.toJSONFor(); + article.tagList = tagList; + //article.author = author + + res.status(200).json({ article: article }); +}); + +module.exports.getArticles = asyncHandler(async (req, res) => { + const { tag, author, favorited, limit = 20, offset = 0 } = req.query; + const { loggedUser } = req; + + const searchOptions = { + include: [ + { + model: Tag, + as: "tagLists", + attributes: ["name"], + through: { attributes: [] }, // ? this will remove the rows from the join table + // where: tag ? { name: tag } : {}, + ...(tag && { where: { name: tag } }), + }, + { + model: User, + as: "author", + attributes: { exclude: ["password", "email"] }, + // where: author ? { username: author } : {}, + ...(author && { where: { username: author } }), + }, + ], + limit: parseInt(limit), + offset: parseInt(offset), + order: [["createdAt", "DESC"]], + distinct: true, + }; + + let articles = { rows: [], count: 0 }; + + if (favorited) { + const user = await User.findOne({ where: { username: favorited } }); + + articles.rows = await user.getFavorites(searchOptions); + articles.count = await user.countFavorites(); + } else { + articles = await Article.findAndCountAll(searchOptions); + } + + for (let article of articles.rows) { + const articleTags = await article.getTagLists(); + // const articleTags = article.tagLists; + appendTagList(articleTags, article); + await appendFollowers(loggedUser, article); + await appendFavorites(loggedUser, article); + + delete article.dataValues.Favorites; + } + + res + .status(200) + .json({ articles: articles.rows, articlesCount: articles.count }); +}); + +module.exports.createArticle = asyncHandler(async (req, res, next) => { + const { loggedUser } = req; + + fieldValidation(req.body.article.title, next); + fieldValidation(req.body.article.description, next); + fieldValidation(req.body.article.body, next); + + const { title, description, body, tagList } = req.body.article; + const slugInDB = await Article.findOne({ where: { slug: slug(`${title}`) } }); + if (slugInDB) { + return next(new ErrorResponse("Title already exists", 400)); + } + + const article = await Article.create({ + title: title, + description: description, + body: body, + }); + + for (const tag of tagList) { + const tagInDB = await Tag.findByPk(tag.trim()); + + if (tagInDB) { + await article.addTagList(tagInDB); + } else if (tag.length > 2) { + const newTag = await Tag.create({ name: tag.trim() }); + await article.addTagList(newTag); + } + } + delete loggedUser.dataValues.token; + + article.dataValues.tagList = tagList; + article.setAuthor(loggedUser); + article.dataValues.author = loggedUser; + await appendFollowers(loggedUser, loggedUser); + await appendFavorites(loggedUser, article); + + res.status(201).json({ article }); +}); + +module.exports.deleteArticle = asyncHandler(async (req, res, next) => { + const { slug } = req.params; + const { loggedUser } = req; + + const article = await Article.findOne({ + where: { slug: slug }, + include: includeOptions, + }); + + if (!article) { + return next(new ErrorResponse("Article not found", 404)); + } + + if (article.authorId !== loggedUser.id) { + return next(new ErrorResponse("Unauthorized", 401)); + } + + await article.destroy(); + + res.status(200).json({ article }); +}); + +module.exports.updateArticle = asyncHandler(async (req, res, next) => { + const { slug } = req.params; + const { loggedUser } = req; + + const article = await Article.findOne({ + where: { slug: slug }, + include: includeOptions, + }); + + if (!article) { + return next(new ErrorResponse("Article not found", 404)); + } + + if (article.authorId !== loggedUser.id) { + return next(new ErrorResponse("Unauthorized", 401)); + } + + const { title, description, body } = req.body.article; + + const slugInDB = await Article.findOne({ + where: { + slug: slug(title ? title : article.title) + }, + }); + + if (slugInDB && slugInDB.slug !== slug) { + return next(new ErrorResponse("Title already exists", 400)); + } + + await article.update({ + title: title ? title : article.title, + description: description ? description : article.description, + body: body ? body : article.body, + }); + + const articleTags = await article.getTagLists(); + appendTagList(articleTags, article); + await appendFollowers(loggedUser, article); + await appendFavorites(loggedUser, article); + + res.status(200).json({ article }); +}); + +module.exports.articlesFeed = asyncHandler(async (req, res) => { + const { loggedUser } = req; + + const { limit = 3, offset = 0 } = req.query; + const authors = await loggedUser.getFollowing(); + + const articles = await Article.findAndCountAll({ + include: includeOptions, + limit: parseInt(limit), + offset: offset * limit, + order: [["createdAt", "DESC"]], + where: { authorId: authors.map((author) => author.id) }, + distinct: true, + }); + + for (const article of articles.rows) { + const articleTags = await article.getTagLists(); + + appendTagList(articleTags, article); + await appendFollowers(loggedUser, article); + await appendFavorites(loggedUser, article); + } + + res.json({ articles: articles.rows, articlesCount: articles.count }); +}); + +module.exports.addFavoriteArticle = asyncHandler(async (req, res, next) => { + const { loggedUser } = req; + const { slug } = req.params; + + const article = await Article.findOne({ + where: { slug: slug }, + include: includeOptions, + }); + + if (!article) { + return next(new ErrorResponse("Article not found", 404)); + } + + await loggedUser.addFavorite(article); + + const articleTags = await article.getTagLists(); + appendTagList(articleTags, article); + await appendFollowers(loggedUser, article); + await appendFavorites(loggedUser, article); + + res.status(200).json({ article }); +}); + +module.exports.deleteFavoriteArticle = asyncHandler(async (req, res, next) => { + const { loggedUser } = req; + const { slug } = req.params; + + const article = await Article.findOne({ + where: { slug: slug }, + include: includeOptions, + }); + + if (!article) { + return next(new ErrorResponse("Article not found", 404)); + } + + await loggedUser.removeFavorite(article); + + const articleTags = await article.getTagLists(); + appendTagList(articleTags, article); + await appendFollowers(loggedUser, article); + await appendFavorites(loggedUser, article); + + res.status(200).json({ article }); +}); + +const fieldValidation = (field, next) => { + if (!field) { + return next(new ErrorResponse(`Missing fields`, 400)); } }; - -const getAllArticles = async (req, res) => { - try { - const articles = await ArticleService.getAllArticles(); - - res.status(200).json({ - data: articles, - }); - } catch (err) { - res.status(500).json({ - message: 'something went wrong while fetching articles', - error: err.message, - }); - } -}; - -const getArticlesByUserId = async (req, res) => { - try { - const { authorId } = req.params; - - const articles = await ArticleService.getArticlesByUserId(authorId); - - res.status(200).json({ - data: articles, - }); - } catch (err) { - console.log(err); - res.status(500).json({ - message: 'something went wrong while fetching articles', - error: err.message, - }); - } -}; - -module.exports = { createArticle, getAllArticles, getArticlesByUserId }; \ No newline at end of file diff --git a/src/controllers/product.controller.js b/src/controllers/product.controller.js new file mode 100644 index 0000000..5c04750 --- /dev/null +++ b/src/controllers/product.controller.js @@ -0,0 +1,277 @@ +//const { ProductService } = require('../services'); +//const { where } = require("sequelize"); +const asyncHandler = require("../middlewares/asyncHandler"); +const Product = require("../database/models/mysql/Product"); +const Tag = require("../database/models/mysql/Tag"); +const User = require("../database/models/mysql/User"); +const ErrorResponse = require("../utils/errorResponse"); +const slug = require("slug"); + +const { + appendFollowers, + appendFavorites, + appendTagList, +} = require("../utils/helpers"); + +const includeOptions = [ + { + model: Tag, + as: "tagLists", + attributes: ["name"], + through: { attributes: [] }, + }, + { model: User, as: "author", attributes: { exclude: ["email", "password"] } }, +]; + +module.exports.getProducts = asyncHandler(async (req, res) => { + const { tag, author, favorited, limit = 20, offset = 0 } = req.query; + const { loggedUser } = req; + + const searchOptions = { + include: [ + { + model: Tag, + as: "tagLists", + attributes: ["name"], + through: { attributes: [] }, // ? this will remove the rows from the join table + // where: tag ? { name: tag } : {}, + ...(tag && { where: { name: tag } }), + }, + { + model: User, + as: "author", + attributes: { exclude: ["password", "email"] }, + // where: author ? { username: author } : {}, + ...(author && { where: { username: author } }), + }, + ], + limit: parseInt(limit), + offset: parseInt(offset), + order: [["createdAt", "DESC"]], + distinct: true, + }; + + let products = { rows: [], count: 0 }; + + if (favorited) { + const user = await User.findOne({ where: { username: favorited } }); + + products.rows = await user.getFavorites(searchOptions); + products.count = await user.countFavorites(); + } else { + products = await Product.findAndCountAll(searchOptions); + } + + for (let product of products.rows) { + const productTags = await product.getTagLists(); + // const productTags = product.tagLists; + appendTagList(productTags, product); + await appendFollowers(loggedUser, product); + await appendFavorites(loggedUser, product); + + delete product.dataValues.Favorites; + } + + res + .status(200) + .json({ products: products.rows, productsCount: products.count }); +}); + +module.exports.createProduct = asyncHandler(async (req, res, next) => { + const { loggedUser } = req; + + fieldValidation(req.body.product.title, next); + fieldValidation(req.body.product.description, next); + fieldValidation(req.body.product.body, next); + + const { title, description, body, tagList } = req.body.product; + const slugInDB = await Product.findOne({ where: { slug: slug(`${title}`) } }); + if (slugInDB) { + next(new ErrorResponse("Title already exists", 400)); + } + + const product = await Product.create({ + title: title, + description: description, + body: body, + }); + + for (const tag of tagList) { + const tagInDB = await Tag.findByPk(tag.trim()); + + if (tagInDB) { + await product.addTagList(tagInDB); + } else if (tag.length > 2) { + const newTag = await Tag.create({ name: tag.trim() }); + await product.addTagList(newTag); + } + } + delete loggedUser.dataValues.token; + + product.dataValues.tagList = tagList; + product.setAuthor(loggedUser); + product.dataValues.author = loggedUser; + await appendFollowers(loggedUser, loggedUser); + await appendFavorites(loggedUser, product); + + res.status(201).json({ product }); +}); + +module.exports.deleteProduct = asyncHandler(async (req, res, next) => { + const { slug } = req.params; + const { loggedUser } = req; + + const product = await Product.findOne({ + where: { slug: slug }, + include: includeOptions, + }); + + if (!product) { + next(new ErrorResponse("Product not found", 404)); + } + + if (product.authorId !== loggedUser.id) { + return next(new ErrorResponse("Unauthorized", 401)); + } + + await product.destroy(); + + res.status(200).json({ product }); +}); + +module.exports.updateProduct = asyncHandler(async (req, res, next) => { + const { slug } = req.params; + const { loggedUser } = req; + + const product = await Product.findOne({ + where: { slug: slug }, + include: includeOptions, + }); + + if (!product) { + next(new ErrorResponse("Product not found", 404)); + } + if (product.authorId !== loggedUser.id) { + return next(new ErrorResponse("Unauthorized", 401)); + } + + const { title, description, body } = req.body.product; + const slugInDB = await Product.findOne({ + where: { slug: slug(title ? title : product.title) }, + }); + if (slugInDB && slugInDB.slug !== slug) { + return next(new ErrorResponse("Title already exists", 400)); + } + + await product.update({ + title: title ? title : product.title, + description: description ? description : product.description, + body: body ? body : product.body, + }); + + const productTags = await product.getTagLists(); + appendTagList(productTags, product); + await appendFollowers(loggedUser, product); + await appendFavorites(loggedUser, product); + + res.status(200).json({ product }); +}); + +module.exports.productsFeed = asyncHandler(async (req, res) => { + const { loggedUser } = req; + + const { limit = 3, offset = 0 } = req.query; + const authors = await loggedUser.getFollowing(); + + const products = await Product.findAndCountAll({ + include: includeOptions, + limit: parseInt(limit), + offset: offset * limit, + order: [["createdAt", "DESC"]], + where: { authorId: authors.map((author) => author.id) }, + distinct: true, + }); + + for (const product of products.rows) { + const productTags = await product.getTagLists(); + + appendTagList(productTags, product); + await appendFollowers(loggedUser, product); + await appendFavorites(loggedUser, product); + } + + res.json({ products: products.rows, productsCount: products.count }); +}); + +module.exports.getProduct = asyncHandler(async (req, res, next) => { + const { loggedUser } = req; + const { slug } = req.params; + + const product = await Product.findOne({ + where: { slug: slug }, + include: includeOptions, + }); + + if (!product) { + return next(new ErrorResponse("Product not found", 404)); + } + + const productTags = await product.getTagLists(); + appendTagList(productTags, product); + await appendFollowers(loggedUser, product); + await appendFavorites(loggedUser, product); + + res.status(200).json({ product }); +}); + +module.exports.addFavoriteProduct = asyncHandler(async (req, res, next) => { + const { loggedUser } = req; + const { slug } = req.params; + + const product = await Product.findOne({ + where: { slug: slug }, + include: includeOptions, + }); + + if (!product) { + return next(new ErrorResponse("Product not found", 404)); + } + + await loggedUser.addFavorite(product); + + const productTags = await product.getTagLists(); + appendTagList(productTags, product); + await appendFollowers(loggedUser, product); + await appendFavorites(loggedUser, product); + + res.status(200).json({ product }); +}); + +module.exports.deleteFavoriteProduct = asyncHandler(async (req, res, next) => { + const { loggedUser } = req; + const { slug } = req.params; + + const product = await Product.findOne({ + where: { slug: slug }, + include: includeOptions, + }); + + if (!product) { + return next(new ErrorResponse("Product not found", 404)); + } + + await loggedUser.removeFavorite(product); + + const productTags = await product.getTagLists(); + appendTagList(productTags, product); + await appendFollowers(loggedUser, product); + await appendFavorites(loggedUser, product); + + res.status(200).json({ product }); +}); + +const fieldValidation = (field, next) => { + if (!field) { + return next(new ErrorResponse(`Missing fields`, 400)); + } +}; diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js index 2b12657..d83206f 100644 --- a/src/controllers/user.controller.js +++ b/src/controllers/user.controller.js @@ -2,11 +2,35 @@ const { UserService } = require('../services'); const passport = require('passport'); +const authenticate = async (req, res, next) => { + try { + passport.authenticate('headerapikey', { session: false }, function (err, application, info) { + if (err) { + return res.status(err.status || 500).json({message: "An authentication error occured.", err: err.message}); + //return next(err); + } + if (!application) { + return res.status(401).json({message: "Unauthorized - You are not allowed to access this resource.", err: err, info: info}); + } + if (application.author) { + application.author.token = application.author.generateJWT(); + return res.json({ user: application.author.toAuthJSON(), application: application.toAuthJSON() }); + } else { + return res.status(422).json(info); + } + })(req, res, next); + } catch (err) { + res.status(500).json({ + message: 'something went wrong while creating user', + error: err.message, + }); + } +}; + const createUser = async (req, res) => { try { - const { name, email } = req.body; - - const user = await UserService.createUser({ name, email }); + const { username, firstname, lastname, email } = req.body.user; + const user = await UserService.createUser({ username, firstname, lastname, email }); res.status(201).json({ message: 'User created successfully', @@ -38,6 +62,9 @@ const getAllUsers = async (req, res) => { const getUser = async (req, res) => { try { const user = await UserService.getUserById(req.payload.id); + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } res.status(200).json({ user: user.toAuthJSON(), @@ -71,10 +98,65 @@ const loginAsUser = async (req, res, next) => { })(req, res, next); } catch (err) { res.status(500).json({ - message: 'something went wrong while fetching users', + message: 'something went wrong while processing login', error: err.message, }); } }; -module.exports = { createUser, getAllUsers, getUser, loginAsUser }; \ No newline at end of file +const updateUser = async (req, res) => { + try { + let user = await UserService.getUserById(req.payload.id); + if (!user) { + return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + } + if (typeof req.body.user.username !== 'undefined') { + user.username = req.body.user.username; + } + if (typeof req.body.user.email !== 'undefined') { + user.email = req.body.user.email; + } + if (typeof req.body.user.firstname !== 'undefined') { + user.firstname = req.body.user.firstname; + } + if (typeof req.body.user.lastname !== 'undefined') { + user.lastname = req.body.user.lastname; + } + if (typeof req.body.user.phone !== 'undefined') { + user.phone = req.body.user.phone; + } + if (typeof req.body.user.licence !== 'undefined') { + user.licence = req.body.user.licence; + } + if (typeof req.body.user.poids !== 'undefined') { + user.poids = req.body.user.poids; + } + if (typeof req.body.user.image !== 'undefined') { + user.image = req.body.user.image; + } + if (typeof req.body.user.bg_image !== 'undefined') { + user.bg_image = req.body.user.bg_image; + } + if (typeof req.body.user.password !== 'undefined') { + user.setPassword(req.body.user.password); + } + if (typeof req.body.user.role !== 'undefined') { + user.role = req.body.user.role; + } + + /*user = await UserService.updateUser(req.body.user); + res.status(200).json({ + user: user.toAuthJSON(), + });*/ + return user.save().then(function () { + return res.json({ user: user.toAuthJSON() }); + }); + } catch (err) { + res.status(500).json({ + message: 'something went wrong while updating user', + error: err.message, + }); + } +}; + +module.exports = { authenticate, createUser, getAllUsers, getUser, loginAsUser, updateUser }; \ No newline at end of file diff --git a/src/database/models/mongo/User.js b/src/database/models/mongo/User.js index 9d2e0e4..edd5315 100644 --- a/src/database/models/mongo/User.js +++ b/src/database/models/mongo/User.js @@ -56,7 +56,7 @@ UserSchema.methods.toAuthJSON = function () { licence: this.licence, poids: this.poids, username: this.username, - image: this.image || '/assets/images/users/user.jpg', + image: this.image || '/assets/images/avatars/default.jpg', bg_image: this.bg_image || '/assets/images/users/bg_user.jpg' }; }; @@ -71,7 +71,7 @@ UserSchema.methods.toJSONFor = function () { licence: this.licence, poids: this.poids, username: this.username, - image: this.image || '/assets/images/users/user.jpg', + image: this.image || '/assets/images/avatars/default.jpg', bg_image: this.bg_image || '/assets/images/users/bg_user.jpg' }; }; @@ -85,7 +85,7 @@ UserSchema.methods.toProfileJSONFor = function (user) { licence: this.licence, poids: this.poids, username: this.username, - image: this.image || '/assets/images/users/user.jpg', + image: this.image || '/assets/images/avatars/default.jpg', bg_image: this.bg_image || '/assets/images/users/bg_user.jpg', following: user ? user.isFollowing(this._id) : false }; diff --git a/src/database/models/mysql/Product.js b/src/database/models/mysql/Product.js new file mode 100644 index 0000000..49ad1b1 --- /dev/null +++ b/src/database/models/mysql/Product.js @@ -0,0 +1,82 @@ +const { DataTypes, Model } = require("sequelize"); +const sequelize = require("../../config/sequelize"); +const slug = require("slug"); + +class Product extends Model { } + +const ProductModel = () => { + Product.init( + { + id: { + autoIncrement: true, + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true + }, + slug: { + type: DataTypes.STRING(255), + allowNull: false + }, + title: { + type: DataTypes.STRING(255), + allowNull: false + }, + description: { + type: DataTypes.TEXT, + allowNull: true + }, + body: { + type: DataTypes.TEXT, + allowNull: false + }, + authorId: { + type: DataTypes.STRING(24), + allowNull: true, + references: { + model: 'user', + key: 'id' + } + } + }, + { + sequelize, + tableName: 'product', + timestamps: true, + indexes: [ + { + name: "PRIMARY", + unique: true, + using: "BTREE", + fields: [ + { name: "id" }, + ] + }, + { + name: "authorId", + using: "BTREE", + fields: [ + { name: "authorId" }, + ] + }, + ] + } + ); + + Product.beforeValidate((product) => { + product.slug = slug(`${product.title}`) + '-' + (Math.random() * Math.pow(36, 6) | 0).toString(36); + }); + + Product.prototype.toJSONFor = function () { + return { + id: this.id, + slug: this.slug, + title: this.title, + description: this.description, + body: this.body + }; + }; + + return Product; +}; + +module.exports = ProductModel; diff --git a/src/database/models/mysql/User.js b/src/database/models/mysql/User.js index 13201bc..28d575b 100644 --- a/src/database/models/mysql/User.js +++ b/src/database/models/mysql/User.js @@ -33,16 +33,16 @@ const UserModel = () => { type: DataTypes.STRING(255), allowNull: true }, - bio: { - type: DataTypes.TEXT, + phone: { + type: DataTypes.STRING(20), allowNull: true }, image: { - type: DataTypes.TEXT, + type: DataTypes.STRING(128), allowNull: true }, role: { - type: DataTypes.STRING(255), + type: DataTypes.STRING(20), allowNull: true }, salt: { @@ -87,6 +87,14 @@ const UserModel = () => { } ); + User.beforeCreate((user) => { + if (typeof user.username === 'undefined') { + user.username = `${user.firstname}_${user.lastname}`; + } + user.setPassword(user.password); + user.role = 'User'; + }); + User.prototype.validPassword = function (password) { let hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex'); return this.hash === hash; @@ -112,16 +120,13 @@ const UserModel = () => { return { id: this.id, email: this.email, - role: this.role, - token: this.generateJWT(), + username: this.username, firstname: this.firstname, lastname: this.lastname, - /*phone: this.phone, - licence: this.licence, - poids: this.poids,*/ - username: this.username, - image: this.image || '/assets/images/users/user.jpg', - bg_image: this.bg_image || '/assets/images/users/bg_user.jpg' + phone: this.phone, + image: this.image || '/assets/images/avatars/default.jpg', + role: this.role, + token: this.generateJWT() }; }; @@ -132,24 +137,20 @@ const UserModel = () => { username: this.username, firstname: this.firstname, lastname: this.lastname, - bio: this.bio, + phone: this.phone, image: this.image, role: this.role }; }; - User.prototype.toProfileJSONFor = function (user) { + User.prototype.toProfileJSONFor = function () { return { email: this.email, + username: this.username, firstname: this.firstname, lastname: this.lastname, phone: this.phone, - licence: this.licence, - poids: this.poids, - username: this.username, - image: this.image || '/assets/images/users/user.jpg', - bg_image: this.bg_image || '/assets/images/users/bg_user.jpg', - following: user ? user.isFollowing(this.id) : false + image: this.image || '/assets/images/avatars/default.jpg' }; }; diff --git a/src/database/relationships/index.js b/src/database/relationships/index.js index 8e7939e..5db41b4 100644 --- a/src/database/relationships/index.js +++ b/src/database/relationships/index.js @@ -42,6 +42,28 @@ const associate = () => { DB.User.hasMany(DB.Follower, { as: "followers", foreignKey: "userId" }); DB.Follower.belongsTo(DB.User, { as: "follower", foreignKey: "followerId" }); DB.User.hasMany(DB.Follower, { as: "followerFollowers", foreignKey: "followerId" }); + +/* +// Relations +DB.User.belongsToMany(DB.User, { as: "followers", through: "Followers", foreignKey: "userId", timestamps: false }); +DB.User.belongsToMany(DB.User, { as: "following", through: "Followers", foreignKey: "followerId", timestamps: false }); + +DB.User.hasMany(DB.Article, { foreignKey: "authorId", onDelete: "CASCADE" }); +DB.Article.belongsTo(DB.User, { as: "author", foreignKey: "authorId" }); + +DB.User.hasMany(DB.Comment, { foreignKey: "authorId", onDelete: "CASCADE" }); +DB.Comment.belongsTo(DB.User, { as: "author", foreignKey: "authorId" }); + +DB.Article.hasMany(DB.Comment, { foreignKey: "articleId", onDelete: "CASCADE" }); +DB.Comment.belongsTo(DB.Article, { foreignKey: "articleId" }); + +DB.User.belongsToMany(DB.Article, { as: "favorites", through: "Favorites", timestamps: false }); +DB.Article.belongsToMany(DB.User, { through: "Favorites", foreignKey: "articleId", timestamps: false }); + +DB.Article.belongsToMany(DB.Tag, { through: "TagLists", as: "tagLists", foreignKey: "articleId", timestamps: false, onDelete: "CASCADE" }); +DB.Tag.belongsToMany(DB.Article, { through: "ArticleTags", uniqueKey: false, timestamps: false }); +*/ + }; module.exports = associate; diff --git a/src/middlewares/asyncHandler.js b/src/middlewares/asyncHandler.js new file mode 100644 index 0000000..f3c8b1f --- /dev/null +++ b/src/middlewares/asyncHandler.js @@ -0,0 +1,7 @@ +const asyncHandler = (fn) => (req, res, next) => { + Promise.resolve(fn(req, res, next)).catch((err) => { + next(err); + }); +}; + +module.exports = asyncHandler; diff --git a/src/routes/api/article.routes.js b/src/routes/api/article.routes.js deleted file mode 100644 index a9ac9f7..0000000 --- a/src/routes/api/article.routes.js +++ /dev/null @@ -1,11 +0,0 @@ -const express = require('express'); -const { createArticle, getAllArticles, getArticlesByUserId } = require('../../controllers/article.controller'); -const auth = require('../auth'); - -const articleRouter = express.Router(); - -articleRouter.post('/', auth.required, createArticle); -articleRouter.get('/', auth.optional, getAllArticles); -articleRouter.get('/:authorId', auth.optional, getArticlesByUserId); - -module.exports = articleRouter; diff --git a/src/routes/api/articles.routes.js b/src/routes/api/articles.routes.js new file mode 100644 index 0000000..40b2f83 --- /dev/null +++ b/src/routes/api/articles.routes.js @@ -0,0 +1,25 @@ +const express = require('express'); +const { + getArticle, + getArticles, + createArticle, + articlesFeed, + deleteArticle, + updateArticle, + addFavoriteArticle, + deleteFavoriteArticle +} = require('../../controllers/article.controller'); +const auth = require('../auth'); + +const articleRouter = express.Router(); + +articleRouter.get('/', auth.optional, getArticles); +articleRouter.post('/', auth.required, createArticle); +articleRouter.get('/feed', auth.required, articlesFeed); +articleRouter.get('/:slug', auth.optional, getArticle); +articleRouter.put('/:slug', auth.required, updateArticle); +articleRouter.delete('/:slug', auth.required, deleteArticle); +articleRouter.post('/:slug/favorite', auth.required, addFavoriteArticle); +articleRouter.delete('/:slug/favorite', auth.required, deleteFavoriteArticle); + +module.exports = articleRouter; diff --git a/src/routes/api/index.js b/src/routes/api/index.js index 0563b71..466ad4b 100644 --- a/src/routes/api/index.js +++ b/src/routes/api/index.js @@ -14,8 +14,9 @@ routes.use('/pages', require('./pages')); routes.use('/qcm', require('./qcm')); routes.use('/user', require('./user.routes')); -routes.use('/article', require('./article.routes')); +routes.use('/articles', require('./articles.routes')); routes.use('/comment', require('./comment.routes')); +routes.use('/products', require('./products.routes')); routes.use('/tag', require('./tag.routes')); routes.use(function (err, req, res, next) { diff --git a/src/routes/api/products.routes.js b/src/routes/api/products.routes.js new file mode 100644 index 0000000..c4f9781 --- /dev/null +++ b/src/routes/api/products.routes.js @@ -0,0 +1,25 @@ +const express = require('express'); +const { + getProduct, + getProducts, + createProduct, + productsFeed, + addFavoriteProduct, + deleteFavoriteProduct, + deleteProduct, + updateProduct, +} = require('../../controllers/product.controller'); +const auth = require('../auth'); + +const productRouter = express.Router(); + +productRouter.get('/', auth.optional, getProducts); +productRouter.post('/', auth.required, createProduct); +productRouter.get('/feed', auth.required, productsFeed); +productRouter.get('/:slug', auth.optional, getProduct); +productRouter.put('/:slug', auth.required, updateProduct); +productRouter.delete('/:slug', auth.required, deleteProduct); +productRouter.post('/:slug/favorite', auth.required, addFavoriteProduct); +productRouter.delete('/:slug/favorite', auth.required, deleteFavoriteProduct); + +module.exports = productRouter; diff --git a/src/routes/api/user.routes.js b/src/routes/api/user.routes.js index b2642b5..a691e7f 100644 --- a/src/routes/api/user.routes.js +++ b/src/routes/api/user.routes.js @@ -1,12 +1,15 @@ const express = require('express'); -const { createUser, getAllUsers, getUser, loginAsUser } = require('../../controllers/user.controller'); +const { authenticate, createUser, getUser, loginAsUser, updateUser } = require('../../controllers/user.controller'); const auth = require('../auth'); const userRouter = express.Router(); userRouter.get('/', auth.required, getUser); -userRouter.get('/users', auth.required, getAllUsers); +userRouter.put('/', auth.required, updateUser); +userRouter.post('/', auth.optional, createUser); +userRouter.get('/authenticate', auth.optional, authenticate); +//userRouter.get('/users', auth.required, getAllUsers); userRouter.post('/login', auth.optional, loginAsUser); -userRouter.post('/register', auth.optional, createUser); +//userRouter.post('/register', auth.optional, createUser); module.exports = userRouter; diff --git a/src/routes/api/users.js b/src/routes/api/users.js index 1ba7768..4f703d3 100644 --- a/src/routes/api/users.js +++ b/src/routes/api/users.js @@ -96,6 +96,7 @@ router.get('/authenticate', function (req, res, next) { } })(req, res, next); }); + router.post('/users', function (req, res, next) { let user = new User(); if (typeof req.body.user.username !== 'undefined') { diff --git a/src/routes/auth.js b/src/routes/auth.js index 28107dc..c1b6652 100644 --- a/src/routes/auth.js +++ b/src/routes/auth.js @@ -32,8 +32,6 @@ const auth = { if(req.headers.authorization && req.headers.authorization.split(' ')[0] === process.env.AUTHORIZATION_PREFIX) { passport.authenticate('headerapikey', { session: false }, function (err, application, info) { if (err) { - //return res.json({message: "An authentication error occured (required).", err: err.message}); - //return res.status(err.status || 500).json({message: "An authentication error occured (required).", err: err.message}); return next({message: "An authentication error occured.", err: err.message}); } if (!application) { @@ -41,17 +39,7 @@ const auth = { } req.application = application; req.payload = {id: application.author._id}; - //console.log(application); - //console.log(application.author._id); return next(); - /* - return jwt({ - secret: secret, - algorithms: ['HS256'], - requestProperty: 'payload', - getToken: application.author.toAuthJSON().token - })(req, res, next); - */ })(req, res, next); } else { return jwt({ diff --git a/src/services/article.service.js b/src/services/article.service.js index ca2f9a7..23a7cc8 100644 --- a/src/services/article.service.js +++ b/src/services/article.service.js @@ -26,6 +26,30 @@ class ArticleService { order: [['createdAt', 'DESC']], }); } + + static async getArticleBySlug(slug) { + const articles = await Article.findAll({ + where: { slug }, + include: [ + { model: DB.Tag, as: "tagNameTagTagLists", attributes: ["name"], through: { attributes: [] } }, + { model: DB.User, as: "author", attributes: { exclude: ["email", "phone", "salt", "hash"] } }, + //{ model: DB.User, as: "author", attributes: { include: ["id", "username", "image", "role"] } }, + ], + order: [['createdAt', 'DESC']], + }); + /* + .then(data => { + data.tagList = data.tagNameTagTagLists; + delete data.tagNameTagTagLists; + return data; + }); + */ + //let article = articles[0]; + //article.tagList = article.tagNameTagTagLists; + //delete article.tagNameTagTagLists; + + return articles[0]; + } } module.exports = ArticleService; \ No newline at end of file diff --git a/src/services/user.service.js b/src/services/user.service.js index 089e380..58f024a 100644 --- a/src/services/user.service.js +++ b/src/services/user.service.js @@ -18,7 +18,11 @@ class UserService { } static async getUserByEmail(email) { - return User.findOne(email); + return User.findOne({ where: { email: email } }); + } + + static async updateUser(data) { + return User.update(data); } } diff --git a/src/utils/errorResponse.js b/src/utils/errorResponse.js new file mode 100644 index 0000000..29d4ac9 --- /dev/null +++ b/src/utils/errorResponse.js @@ -0,0 +1,8 @@ +class ErrorResponse extends Error { + constructor(message, statusCode) { + super(message); + this.statusCode = statusCode; + } +} + +module.exports = ErrorResponse; diff --git a/src/utils/helpers.js b/src/utils/helpers.js new file mode 100644 index 0000000..762024c --- /dev/null +++ b/src/utils/helpers.js @@ -0,0 +1,36 @@ +const appendTagList = (articleTags, article) => { + const tagList = articleTags.map((tag) => tag.name); + + if (!article) return tagList; + delete article.dataValues.tagLists; + article.dataValues.tagList = tagList; +}; + +const appendFavorites = async (loggedUser, article) => { + const favorited = await article.hasUser(loggedUser ? loggedUser : null); + article.dataValues.favorited = loggedUser ? favorited : false; + + const favoritesCount = await article.countUsers(); + article.dataValues.favoritesCount = favoritesCount; +}; + +const appendFollowers = async (loggedUser, toAppend) => { + if (toAppend?.author) { + const author = await toAppend.getAuthor(); + const following = await author.hasFollower(loggedUser ? loggedUser : null); + toAppend.author.dataValues.following = loggedUser ? following : false; + + const followersCount = await author.countFollowers(); + toAppend.author.dataValues.followersCount = followersCount; + } else { + const following = await toAppend.hasFollower( + loggedUser ? loggedUser : null + ); + toAppend.dataValues.following = loggedUser ? following : false; + + const followersCount = await toAppend.countFollowers(); + toAppend.dataValues.followersCount = followersCount; + } +}; + +module.exports = { appendTagList, appendFavorites, appendFollowers };