From 9355431730873d5b6717aa2d2af017a94d8b87d2 Mon Sep 17 00:00:00 2001 From: Julien Gautier Date: Sun, 30 Nov 2025 17:54:01 +0100 Subject: [PATCH] =?UTF-8?q?Mise=20=C3=A0=20jour=20et=20ajout=20de=20contro?= =?UTF-8?q?llers,=20routes,=20services=20et=20utilities?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/article.controller.js | 450 ++++++++---------- src/controllers/clan.controller.js | 105 ++-- src/controllers/comment.controller.js | 25 +- src/controllers/herowars.controller.js | 60 +++ src/controllers/member.controller.js | 75 ++- src/controllers/product.controller.js | 8 +- src/controllers/tag.controller.js | 75 ++- src/controllers/user.controller.js | 109 +++-- src/database/connectDb.js | 75 ++- src/database/models/mysql/Article.js | 14 +- src/database/models/mysql/init-models.js | 65 --- src/database/relationships/index.js | 18 +- src/routes/api/index.js | 4 +- src/routes/api/v1/jumps.js | 21 +- src/routes/api/v2/articles.routes.js | 6 +- .../{comment.routes.js => comments.routes.js} | 0 .../api/v2/{tag.routes.js => tags.routes.js} | 3 +- src/routes/api/v2/user.routes.js | 7 +- src/routes/utils.js | 83 ---- src/services/article.service.js | 94 ---- src/services/index.js | 22 +- .../clan.service.js} | 8 +- src/services/mongo/index.js | 5 + .../member.service.js} | 8 +- src/services/mysql/article.service.js | 201 ++++++++ src/services/{ => mysql}/comment.service.js | 2 +- src/services/mysql/index.js | 8 + src/services/{ => mysql}/product.service.js | 2 +- src/services/mysql/tag.service.js | 36 ++ src/services/mysql/user.service.js | 62 +++ src/services/tag.service.js | 19 - src/services/user.service.js | 29 -- src/utils/dateUtilities.js | 33 ++ src/utils/errorResponse.js | 9 +- src/utils/helpers.js | 43 +- src/utils/index.js | 36 +- 36 files changed, 978 insertions(+), 842 deletions(-) create mode 100644 src/controllers/herowars.controller.js delete mode 100644 src/database/models/mysql/init-models.js rename src/routes/api/v2/{comment.routes.js => comments.routes.js} (100%) rename src/routes/api/v2/{tag.routes.js => tags.routes.js} (62%) delete mode 100644 src/routes/utils.js delete mode 100644 src/services/article.service.js rename src/services/{hwclan.service.js => mongo/clan.service.js} (90%) create mode 100644 src/services/mongo/index.js rename src/services/{hwmember.service.js => mongo/member.service.js} (90%) create mode 100644 src/services/mysql/article.service.js rename src/services/{ => mysql}/comment.service.js (77%) create mode 100644 src/services/mysql/index.js rename src/services/{ => mysql}/product.service.js (97%) create mode 100644 src/services/mysql/tag.service.js create mode 100644 src/services/mysql/user.service.js delete mode 100644 src/services/tag.service.js delete mode 100644 src/services/user.service.js create mode 100644 src/utils/dateUtilities.js diff --git a/src/controllers/article.controller.js b/src/controllers/article.controller.js index 3fb5f73..58fcd38 100644 --- a/src/controllers/article.controller.js +++ b/src/controllers/article.controller.js @@ -1,99 +1,205 @@ -const { ArticleService } = require('../services'); -//const { where } = require("sequelize"); +const { ArticleService, TagService, UserService } = require('../services'); 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 DB = require('../database/mysql'); -const { Article, Tag, User } = DB; -/*const { - appendFollowers, - appendFavorites, - appendTagList, -} = require("../utils/helpers");*/ -const includeOptions = [ - { - model: Tag, - as: "articleTags", - attributes: ["name"], - through: { attributes: [] }, - }, - { model: User, as: "author", attributes: { exclude: ["email", "password"] } }, -]; +const includeOptions = ArticleService.getArticleIncludeAssoc(); + +module.exports.createArticle = asyncHandler(async (req, res, next) => { + try { + const loggedUser = await UserService.getLoggedUserById(req.payload.id); + + 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 articleSlug = ArticleService.slugify(title); + const slugInDB = await ArticleService.isSlugUsed(articleSlug); + if (slugInDB) { + return next(new ErrorResponse("Slug already exists", 400)); + } + + const article = await ArticleService.createArticle({ + slug: articleSlug, + title: title, + description: description, + body: body, + }); + + for (const tag of tagList) { + const tagInDB = await TagService.getTagByPk(tag.trim()); + + if (tagInDB) { + await article.addArticleTag(tagInDB); + } else if (tag.length > 2) { + const newTag = await TagService.createTag(tag.trim()); + await article.addArticleTag(newTag); + } + } + delete loggedUser.dataValues.token; + + article.dataValues.tagList = tagList; + article.setAuthor(loggedUser); + article.dataValues.author = loggedUser; + await ArticleService.appendFollowers(loggedUser, loggedUser); + await ArticleService.appendFavorites(loggedUser, article); + + res.status(201).json({ + message: 'Article created successfully.', + data: article.toJSONFor(), + }); + } catch (err) { + return next(new ErrorResponse('Something went wrong while creating article.', 500, err.message)); + } +}); + +module.exports.deleteArticle = asyncHandler(async (req, res, next) => { + try { + const { slug } = req.params; + const loggedUser = await UserService.getLoggedUserById(req.payload.id); + + const article = await ArticleService.getArticleBySlug(slug); + + if (!article) { + return next(new ErrorResponse("Article not found", 404)); + } + + if (article.authorId !== loggedUser.id) { + return next(new ErrorResponse("Unauthorized", 401, "You are not allowed to access this resource.")); + } + + let data = await ArticleService.deleteArticle(article); + res.status(200).json({ + message: 'Article deleted successfully.', + data: data.slug + }); + } catch (err) { + return next(new ErrorResponse('Something went wrong while deleting article.', 500, err.message)); + } +}); module.exports.getArticle = asyncHandler(async (req, res, next) => { - const { slug } = req.params; + try { + const { slug } = req.params; - let article = await ArticleService.getArticleBySlug(slug); + let article = await ArticleService.getArticleBySlugWithTagList(slug); - if (!article) { - return next(new ErrorResponse("Article not found", 404)); + if (!article) { + return next(new ErrorResponse("Article not found", 404)); + } + + const loggedUser = await UserService.getLoggedUserById(req.payload?.id || null); + //await ArticleService.appendTagList(article); + await ArticleService.appendFollowers(loggedUser, article); + await ArticleService.appendFavorites(loggedUser, article); + + res.status(200).json({ article: article.toJSONFor() }); + } catch (err) { + return next(new ErrorResponse('Something went wrong while fetching article.', 500, err.message)); } - let tagList = article.articleTags; - //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, next) => { try { - const { tag, author, favorited, limit = 20, offset = 0 } = req.query; - //const { loggedUser } = req; - const loggedUser = await User.findOne({ - attributes: { exclude: ["password", "email", "hash", "salt"] }, - where: { id: req.payload.id }, - }); - - const searchOptions = { - include: [ - { - model: Tag, - as: "articleTags", - 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", "hash", "salt"] }, - // where: author ? { username: author } : {}, - ...(author && { where: { username: author } }), - }, - ], - limit: parseInt(limit), - offset: parseInt(offset), - order: [["createdAt", "DESC"]], - distinct: true, - }; - let articles = { rows: [], count: 0 }; + const { tag, author, favorited, limit = 20, offset = 0 } = req.query; + const loggedUser = await UserService.getLoggedUserById(req.payload?.id || null); + const searchOptions = await ArticleService.getSearchOptionsArticles(tag, author, limit, offset); if (favorited) { - const user = await User.findOne({ where: { username: favorited } }); - - articles.rows = await user.getFavorites(searchOptions); - articles.count = await user.countFavorites(); + const user = await UserService.getUserByUsername(favorited); + if (!user) { + return next(new ErrorResponse("User not found", 404)); + } + articles.rows = await UserService.getFavorites(user, searchOptions); + articles.count = await UserService.getFavoritesCount(user); } else { - articles = await Article.findAndCountAll(searchOptions); + articles = await ArticleService.getAllArticlesAndCount(searchOptions); } for (let article of articles.rows) { - const articleTags = await article.getTagLists(); - //console.log("articleTags: ", articleTags); - // const articleTags = article.tagLists; - ArticleService.appendTagList(articleTags, article); + await ArticleService.appendTagList(article); await ArticleService.appendFollowers(loggedUser, article); await ArticleService.appendFavorites(loggedUser, article); + } + const data = articles.rows.map((article) => { + return article.toJSONFor(); + }); - //delete article.dataValues.Favorites; + res.status(200).json({ + data: { + articles: data, + articlesCount: articles.count + } + }); + } catch (err) { + return next(new ErrorResponse('Something went wrong while fetching articles.', 500, err.message)); + } +}); + +module.exports.updateArticle = asyncHandler(async (req, res, next) => { + try { + const { slug } = req.params; + const loggedUser = await UserService.getLoggedUserById(req.payload.id); + + const article = await ArticleService.getArticleBySlug(slug); + + if (!article) { + return next(new ErrorResponse("Article not found", 404)); + } + + if (article.authorId !== loggedUser.id) { + return next(new ErrorResponse("Unauthorized", 401, "You are not allowed to access this resource.")); + } + + const { title, description, body } = req.body.article; + const articleSlug = ArticleService.slugify(title ? title : article.title); + const slugInDB = await ArticleService.isSlugUsed(articleSlug); + if (slugInDB && articleSlug !== slug) { + return next(new ErrorResponse("Title already exists", 400)); + } + + //await article.update({ + await ArticleService.updateArticleById({ + slug: articleSlug ? articleSlug : article.slug, + title: title ? title : article.title, + description: description ? description : article.description, + body: body ? body : article.body, + }, article.id); + + const update = await ArticleService.getArticleByIdWithTagList(article.id); + await ArticleService.appendFollowers(loggedUser, update); + await ArticleService.appendFavorites(loggedUser, update); + res.status(200).json({ + message: 'Article updated successfully.', + article: update.toJSONFor() + //article: article + }); + } catch (err) { + return next(new ErrorResponse('Something went wrong while updating article.', 500, err.message)); + } +}); + +module.exports.articlesFeed = asyncHandler(async (req, res, next) => { + try { + const { limit = 3, offset = 0 } = req.query; + const loggedUser = await UserService.getLoggedUserById(req.payload.id); + const authors = await UserService.getUserFollowed(loggedUser); + + const searchOptions = { + include: includeOptions, + limit: parseInt(limit), + offset: offset * limit, + order: [["createdAt", "DESC"]], + where: { authorId: authors.map((author) => author.id) }, + distinct: true, + } + const articles = await ArticleService.getAllArticlesAndCount(searchOptions); + + for (const article of articles.rows) { + await ArticleService.appendTagList(article); + await ArticleService.appendFollowers(loggedUser, article); + await ArticleService.appendFavorites(loggedUser, article); } res.status(200).json({ @@ -103,185 +209,49 @@ module.exports.getArticles = asyncHandler(async (req, res, next) => { } }); } catch (err) { - return next(new ErrorResponse(`Something went wrong while fetching articles - ${err.message}`, 500)); + return next(new ErrorResponse('Something went wrong while fetching articles feed.', 500, err.message)); } }); -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 ArticleService.appendFollowers(loggedUser, loggedUser); - await ArticleService.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(); - ArticleService.appendTagList(articleTags, article); - await ArticleService.appendFollowers(loggedUser, article); - await ArticleService.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(); - - ArticleService.appendTagList(articleTags, article); - await ArticleService.appendFollowers(loggedUser, article); - await ArticleService.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; + try { + const { slug } = req.params; + const loggedUser = await UserService.getLoggedUserById(req.payload.id); + const article = await ArticleService.getArticleBySlugWithTagList(slug); - const article = await Article.findOne({ - where: { slug: slug }, - include: includeOptions, - }); + if (!article) { + return next(new ErrorResponse("Article not found", 404)); + } - if (!article) { - return next(new ErrorResponse("Article not found", 404)); + await UserService.addFavoriteArticle(loggedUser, article); + await ArticleService.appendFollowers(loggedUser, article); + await ArticleService.appendFavorites(loggedUser, article); + + res.status(200).json({ article }); + } catch (err) { + return next(new ErrorResponse('Something went wrong while adding a favorite.', 500, err.message)); } - - await loggedUser.addFavorite(article); - - const articleTags = await article.getTagLists(); - ArticleService.appendTagList(articleTags, article); - await ArticleService.appendFollowers(loggedUser, article); - await ArticleService.appendFavorites(loggedUser, article); - - res.status(200).json({ article }); }); module.exports.deleteFavoriteArticle = asyncHandler(async (req, res, next) => { - const { loggedUser } = req; - const { slug } = req.params; + try { + const { slug } = req.params; + const loggedUser = await UserService.getLoggedUserById(req.payload.id); - const article = await Article.findOne({ - where: { slug: slug }, - include: includeOptions, - }); + const article = await ArticleService.getArticleBySlugWithTagList(slug); - if (!article) { - return next(new ErrorResponse("Article not found", 404)); + if (!article) { + return next(new ErrorResponse("Article not found", 404)); + } + + await UserService.removeFavoriteArticle(loggedUser, article); + await ArticleService.appendFollowers(loggedUser, article); + await ArticleService.appendFavorites(loggedUser, article); + + res.status(200).json({ article }); + } catch (err) { + return next(new ErrorResponse('Something went wrong while removing a favorite.', 500, err.message)); } - - await loggedUser.removeFavorite(article); - - const articleTags = await article.getTagLists(); - ArticleService.appendTagList(articleTags, article); - await ArticleService.appendFollowers(loggedUser, article); - await ArticleService.appendFavorites(loggedUser, article); - - res.status(200).json({ article }); }); const fieldValidation = (field, next) => { diff --git a/src/controllers/clan.controller.js b/src/controllers/clan.controller.js index 814b5b4..b7a94ac 100644 --- a/src/controllers/clan.controller.js +++ b/src/controllers/clan.controller.js @@ -1,32 +1,49 @@ -const { HWClanService } = require('../services'); +const { ClanService, UserService } = require('../services'); const asyncHandler = require("../middlewares/asyncHandler"); const ErrorResponse = require("../utils/errorResponse"); -var mongoose = require('mongoose'), - User = mongoose.model('User'); module.exports.createClan = asyncHandler(async (req, res, next) => { try { + const user = await UserService.getUserById(req.payload.id); + if (!user) { + return next(new ErrorResponse("Unauthorized", 401, "Authentication required.")); + } const { id, title, country, description, disbanding, frameId, icon, level, members, membersCount, minLevel, ownerId, serverId, topActivity, topDungeon } = req.body; + const author = user.id; - const author = await User.findById(req.payload.id).then(function (user) { - return user._id; - }); - - const clan = await HWClanService.createClan({ + const clan = await ClanService.createClan({ id, title, country, description, disbanding, frameId, icon, level, members, membersCount, minLevel, ownerId, serverId, topActivity, topDungeon, author }); //console.log(clan); res.status(201).json({ - message: 'Clan created successfully', + message: 'Clan created successfully.', data: clan.toJSONFor(), }); } catch (err) { - return next(new ErrorResponse(`Something went wrong while creating clan - ${err.message}`, 500)); + return next(new ErrorResponse(`Something went wrong while creating clan.`, 500, err.message)); + } +}); + +module.exports.deleteClan = asyncHandler(async (req, res, next) => { + try { + const { clanId } = req.params; + let clan = await ClanService.getClanById(clanId); + if (req.payload.id !== clan.author.id) { + return next(new ErrorResponse("Unauthorized", 401, "Authentication required.")); + } + + let data = await ClanService.deleteClan(clan); + res.status(200).json({ + message: 'Clan deleted successfully.', + data: data.id + }); + } catch (err) { + return next(new ErrorResponse(`Something went wrong while deleting clan.`, 500, err.message)); } }); @@ -44,7 +61,7 @@ module.exports.getAllClans = asyncHandler(async (req, res, next) => { } } - const data = await HWClanService.getAllClans(query, limit, offset); + const data = await ClanService.getAllClans(query, limit, offset); res.status(200).json({ data: { clans: data.clans.map(function (clan) { @@ -54,86 +71,40 @@ module.exports.getAllClans = asyncHandler(async (req, res, next) => { } }); } catch (err) { - return next(new ErrorResponse(`Something went wrong while fetching clans - ${err.message}`, 500)); + return next(new ErrorResponse(`Something went wrong while fetching clans.`, 500, err.message)); } }); module.exports.getClan = asyncHandler(async (req, res, next) => { try { - //console.log("Clan controller getClan"); const { clanId } = req.params; - let clan = await HWClanService.getClanById(clanId); + let clan = await ClanService.getClanById(clanId); if (!clan) { return next(new ErrorResponse("Clan not found", 404)); } res.status(200).json({ clan: clan.toJSONFor() }); } catch (err) { - return next(new ErrorResponse(`Something went wrong while fetching clan - ${err.message}`, 500)); + return next(new ErrorResponse(`Something went wrong while fetching clan.`, 500, err.message)); } }); module.exports.updateClan = asyncHandler(async (req, res, next) => { try { - //console.log("Clan controller updateClan", req.payload); const { clanId } = req.params; - let clan = await HWClanService.getClanById(clanId); - //console.log(req.body.clan, clan); + let clan = await ClanService.getClanById(clanId); - if (req.payload.id !== clan.author._id.toString()) { - return next(new ErrorResponse("Unauthorized", 401)); + console.log(clan.author.id, clan.author); + if (req.payload.id !== clan.author.id) { + return next(new ErrorResponse("Unauthorized", 401, "Authentication required.")); } Object.assign(clan, req.body.clan); - - let data = await HWClanService.updateClan(clan); - //res.status(200).json({ clan: data.toJSONFor() }); + let data = await ClanService.updateClan(clan); res.status(200).json({ - message: 'Clan updated successfully', + message: 'Clan updated successfully.', clan: data.toJSONFor() }); } catch (err) { - return next(new ErrorResponse(`Something went wrong while updating clan - ${err.message}`, 500)); + return next(new ErrorResponse(`Something went wrong while updating clan.`, 500, err.message)); } }); - -module.exports.deleteClan = asyncHandler(async (req, res, next) => { - try { - const { clanId } = req.params; - let clan = await HWClanService.getClanById(clanId); - //console.log(clanId, clan.author._id.toString(), req.payload.id); - if (req.payload.id !== clan.author._id.toString()) { - return next(new ErrorResponse("Unauthorized", 401)); - } - - let data = await HWClanService.deleteClan(clan); - res.status(200).json({ - message: 'Clan deleted successfully', - data: data.id - }); - } catch (err) { - return next(new ErrorResponse(`Something went wrong while deleting clan - ${err.message}`, 500)); - } -}); -/* -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 }); -}); -*/ diff --git a/src/controllers/comment.controller.js b/src/controllers/comment.controller.js index e95a1b7..4f45078 100644 --- a/src/controllers/comment.controller.js +++ b/src/controllers/comment.controller.js @@ -1,9 +1,10 @@ const { CommentService } = require('../services'); +const asyncHandler = require("../middlewares/asyncHandler"); +const ErrorResponse = require("../utils/errorResponse"); -const createComment = async (req, res) => { +module.exports.createComment = asyncHandler(async (req, res, next) => { try { const { slug, title, description, body, authorId } = req.body; - const comment = await CommentService.createComment({ slug, title, @@ -13,18 +14,15 @@ const createComment = async (req, res) => { }); res.status(201).json({ - message: 'Comment created successfully', + message: 'Comment created successfully.', data: comment, }); } catch (err) { - res.status(500).json({ - message: 'something went wrong while creating comment', - error: err.message, - }); + return next(new ErrorResponse('Something went wrong while creating comment.', 500, err.message)); } -}; +}); -const getAllComments = async (req, res) => { +module.exports.getAllComments = asyncHandler(async (req, res, next) => { try { const comments = await CommentService.getAllComments(); @@ -32,11 +30,6 @@ const getAllComments = async (req, res) => { data: comments, }); } catch (err) { - res.status(500).json({ - message: 'something went wrong while fetching comments', - error: err.message, - }); + return next(new ErrorResponse('Something went wrong while fetching comments.', 500, err.message)); } -}; - -module.exports = { createComment, getAllComments }; \ No newline at end of file +}); diff --git a/src/controllers/herowars.controller.js b/src/controllers/herowars.controller.js new file mode 100644 index 0000000..3fb23d4 --- /dev/null +++ b/src/controllers/herowars.controller.js @@ -0,0 +1,60 @@ +const { HeroWarService } = require('../services'); +const asyncHandler = require("../middlewares/asyncHandler"); +const ErrorResponse = require("../utils/errorResponse"); + +module.exports.createMember = asyncHandler(async (req, res, next) => { + try { + const { slug, title, description, body, authorId } = req.body; + const member = await HeroWarService.createMember({ + slug, + title, + description, + body, + authorId + }); + + res.status(201).json({ + message: 'Member created successfully.', + data: member, + }); + } catch (err) { + return next(new ErrorResponse(`Something went wrong while creating member.`, 500, err.message)); + } +}); + +module.exports.getAllMembers = asyncHandler(async (req, res, next) => { + try { + const members = await HeroWarService.getAllMembers(); + + res.status(200).json({ data: members }); + } catch (err) { + return next(new ErrorResponse(`Something went wrong while fetching members.`, 500, err.message)); + } +}); + +module.exports.getMember = asyncHandler(async (req, res, next) => { + try { + let member = await HeroWarService.getMemberById(req.member.id); + if (!member) { + return next(new ErrorResponse("Article not found", 404)); + } + } catch (err) { + return next(new ErrorResponse(`Something went wrong while fetching member.`, 500, err.message)); + } +}); + +module.exports.updateMember = asyncHandler(async (req, res, next) => { + try { + const { loggedUser } = req; + if (!loggedUser.id) { + return next(new ErrorResponse("Unauthorized", 401, "Authentication required.")); + } + + let member = await HeroWarService.getMemberById(req.payload.id); + return member.save().then(function () { + return res.json({ user: member.toAuthJSON() }); + }); + } catch (err) { + return next(new ErrorResponse(`Something went wrong while updating member.`, 500, err.message)); + } +}); diff --git a/src/controllers/member.controller.js b/src/controllers/member.controller.js index b55dc31..fa17d43 100644 --- a/src/controllers/member.controller.js +++ b/src/controllers/member.controller.js @@ -1,31 +1,48 @@ -const { HWMemberService } = require('../services'); +const { MemberService, UserService } = require('../services'); const asyncHandler = require("../middlewares/asyncHandler"); const ErrorResponse = require("../utils/errorResponse"); -var mongoose = require('mongoose'), - User = mongoose.model('User'); module.exports.createMember = asyncHandler(async (req, res, next) => { try { + const user = await UserService.getUserById(req.payload.id); + if (!user) { + return next(new ErrorResponse("Unauthorized", 401, "Authentication required.")); + } const { id, allowPm, avatarId, clanIcon, clanId, clanRole, clanTitle, commander, frameId, isChatModerator, lastLoginTime, leagueId, level, name, serverId } = req.body; + const author = user.id; - const author = await User.findById(req.payload.id).then(function (user) { - return user._id; - }); - - const member = await HWMemberService.createMember({ + const member = await MemberService.createMember({ id, allowPm, avatarId, clanIcon, clanId, clanRole, clanTitle, commander, frameId, isChatModerator, lastLoginTime, leagueId, level, name, serverId, author }); res.status(201).json({ - message: 'Member created successfully', + message: 'Member created successfully.', data: member.toJSONFor(), }); } catch (err) { - return next(new ErrorResponse(`Something went wrong while creating member - ${err.message}`, 500)); + return next(new ErrorResponse(`Something went wrong while creating member.`, 500, err.message)); + } +}); + +module.exports.deleteMember = asyncHandler(async (req, res, next) => { + try { + const { memberId } = req.params; + let member = await MemberService.getMemberById(memberId); + if (req.payload.id !== member.author.id) { + return next(new ErrorResponse("Unauthorized", 401, "Authentication required.")); + } + + let data = await MemberService.deleteMember(member); + res.status(200).json({ + message: 'Member deleted successfully.', + data: data.id + }); + } catch (err) { + return next(new ErrorResponse(`Something went wrong while deleting member.`, 500, err.message)); } }); @@ -43,7 +60,7 @@ module.exports.getAllMembers = asyncHandler(async (req, res, next) => { } } - const data = await HWMemberService.getAllMembers(query, limit, offset); + const data = await MemberService.getAllMembers(query, limit, offset); res.status(200).json({ data: { members: data.members.map(function (member) { @@ -53,58 +70,40 @@ module.exports.getAllMembers = asyncHandler(async (req, res, next) => { } }); } catch (err) { - return next(new ErrorResponse(`Something went wrong while fetching members - ${err.message}`, 500)); + return next(new ErrorResponse(`Something went wrong while fetching members.`, 500, err.message)); } }); module.exports.getMember = asyncHandler(async (req, res, next) => { try { const { memberId } = req.params; - let member = await HWMemberService.getMemberById(memberId); + let member = await MemberService.getMemberById(memberId); if (!member) { return next(new ErrorResponse("Member not found", 404)); } res.status(200).json({ member: member.toJSONFor() }); } catch (err) { - return next(new ErrorResponse(`Something went wrong while updating member - ${err.message}`, 500)); + return next(new ErrorResponse(`Something went wrong while updating member.`, 500, err.message)); } }); module.exports.updateMember = asyncHandler(async (req, res, next) => { try { const { memberId } = req.params; - let member = await HWMemberService.getMemberById(memberId); + let member = await MemberService.getMemberById(memberId); - if (req.payload.id !== member.author._id.toString()) { - return next(new ErrorResponse("Unauthorized", 401)); + if (req.payload.id !== member.author.id) { + return next(new ErrorResponse("Unauthorized", 401, "Authentication required.")); } Object.assign(member, req.body.member); - let data = await HWMemberService.updateMember(member); + let data = await MemberService.updateMember(member); res.status(200).json({ - message: 'Member updated successfully', + message: 'Member updated successfully.', member: data.toJSONFor() }); } catch (err) { - return next(new ErrorResponse(`Something went wrong while updating member - ${err.message}`, 500)); - } -}); - -module.exports.deleteMember = asyncHandler(async (req, res, next) => { - try { - const { memberId } = req.params; - let member = await HWMemberService.getMemberById(memberId); - if (req.payload.id !== member.author._id.toString()) { - return next(new ErrorResponse("Unauthorized", 401)); - } - - let data = await HWMemberService.deleteMember(member); - res.status(200).json({ - message: 'Member deleted successfully', - data: data.id - }); - } catch (err) { - return next(new ErrorResponse(`Something went wrong while deleting member - ${err.message}`, 500)); + return next(new ErrorResponse(`Something went wrong while updating member.`, 500, err.message)); } }); diff --git a/src/controllers/product.controller.js b/src/controllers/product.controller.js index fbfac7a..9be5469 100644 --- a/src/controllers/product.controller.js +++ b/src/controllers/product.controller.js @@ -135,10 +135,10 @@ module.exports.createProduct = asyncHandler(async (req, res, next) => { const tagInDB = await Tag.findByPk(tag.trim()); if (tagInDB) { - await product.addTagList(tagInDB); + await product.addProductTag(tagInDB); } else if (tag.length > 2) { const newTag = await Tag.create({ name: tag.trim() }); - await product.addTagList(newTag); + await product.addProductTag(newTag); } } delete loggedUser.dataValues.token; @@ -166,7 +166,7 @@ module.exports.deleteProduct = asyncHandler(async (req, res, next) => { } if (product.authorId !== loggedUser.id) { - return next(new ErrorResponse("Unauthorized", 401)); + return next(new ErrorResponse("Unauthorized", 401, "Authentication required.")); } await product.destroy(); @@ -187,7 +187,7 @@ module.exports.updateProduct = asyncHandler(async (req, res, next) => { next(new ErrorResponse("Product not found", 404)); } if (product.authorId !== loggedUser.id) { - return next(new ErrorResponse("Unauthorized", 401)); + return next(new ErrorResponse("Unauthorized", 401, "Authentication required.")); } const { title, description, body } = req.body.product; diff --git a/src/controllers/tag.controller.js b/src/controllers/tag.controller.js index 5e3665e..21ed105 100644 --- a/src/controllers/tag.controller.js +++ b/src/controllers/tag.controller.js @@ -1,42 +1,67 @@ -const { TagService } = require('../services'); +const { TagService, UserService } = require('../services'); +const asyncHandler = require("../middlewares/asyncHandler"); +const ErrorResponse = require("../utils/errorResponse"); -const createTag = async (req, res) => { +module.exports.createTag = asyncHandler(async (req, res, next) => { try { - const { slug, title, description, body, authorId } = req.body; - const tag = await TagService.createTag({ - slug, - title, - description, - body, - authorId - }); + fieldValidation(req.body.tag.name, next); + const { name } = req.body.tag; + const tagInDB = await TagService.tagIsInDB(name); + if (tagInDB) { + return next(new ErrorResponse("Tag already exists", 400)); + } + + const tag = await TagService.createTag(name); res.status(201).json({ - message: 'Tag created successfully', - data: tag, + message: 'Tag created successfully.', + data: tag.toJSONFor(), }); } catch (err) { - res.status(500).json({ - message: 'something went wrong while creating tag', - error: err.message, - }); + return next(new ErrorResponse('Something went wrong while creating tag.', 500, err.message)); } -}; +}); -const getAllTags = async (req, res) => { +module.exports.deleteTag = asyncHandler(async (req, res, next) => { + try { + const { name } = req.params; + const tag = await TagService.getTagByName(name); + if (!tag) { + return next(new ErrorResponse("Tag not found", 404)); + } + const loggedUser = await UserService.getLoggedUserById(req.payload.id); + if (loggedUser.role !== "Admin") { + return next(new ErrorResponse("Unauthorized", 401, "Authentication required.")); + } + + let data = await TagService.deleteTag(tag); + res.status(200).json({ + message: 'Tag deleted successfully.', + data: data.name + }); + } catch (err) { + return next(new ErrorResponse('Something went wrong while fetching tags.', 500, err.message)); + } +}); + +module.exports.getAllTags = asyncHandler(async (req, res, next) => { try { const tags = await TagService.getAllTags(); res.status(200).json({ - data: tags, + data: { + tags: tags, + tagsCount: tags.length + } }); } catch (err) { - res.status(500).json({ - message: 'something went wrong while fetching tags', - error: err.message, - }); + return next(new ErrorResponse('Something went wrong while fetching tags.', 500, err.message)); + } +}); + +const fieldValidation = (field, next) => { + if (!field) { + return next(new ErrorResponse(`Missing fields`, 400)); } }; - -module.exports = { createTag, getAllTags }; \ No newline at end of file diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js index d83206f..5f993ee 100644 --- a/src/controllers/user.controller.js +++ b/src/controllers/user.controller.js @@ -1,50 +1,54 @@ -//const { UserService } = require('../services/user.service'); const { UserService } = require('../services'); +const asyncHandler = require("../middlewares/asyncHandler"); +const ErrorResponse = require("../utils/errorResponse"); const passport = require('passport'); -const authenticate = async (req, res, next) => { +module.exports.authenticate = asyncHandler(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); + //return res.status(err.status || 500).json({message: "An authentication error occured.", err: err.message}); + return next(new ErrorResponse('Something went wrong while authenticating.', err.status || 500, err.message)); } if (!application) { - return res.status(401).json({message: "Unauthorized - You are not allowed to access this resource.", err: err, info: info}); + return next(new ErrorResponse("Unauthorized", 401, "You are not allowed to access this resource.")); } if (application.author) { application.author.token = application.author.generateJWT(); - return res.json({ user: application.author.toAuthJSON(), application: application.toAuthJSON() }); + 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, - }); + //res.status(500).json({ message: 'Something went wrong while authenticating.', error: err.message }); + return next(new ErrorResponse('Something went wrong while authenticating.', 500, err.message)); } -}; +}); -const createUser = async (req, res) => { +module.exports.createUser = asyncHandler(async (req, res, next) => { try { const { username, firstname, lastname, email } = req.body.user; - const user = await UserService.createUser({ username, firstname, lastname, email }); + const user = await UserService.createUser({ + username, + firstname, + lastname, + email + }); res.status(201).json({ - message: 'User created successfully', + message: 'User created successfully.', data: user, }); } catch (err) { - res.status(500).json({ - message: 'something went wrong while creating user', - error: err.message, - }); + return next(new ErrorResponse('Something went wrong while creating user.', 500, err.message)); } -}; +}); -const getAllUsers = async (req, res) => { +module.exports.getAllUsers = asyncHandler(async (req, res, next) => { try { const users = await UserService.getAllUsers(); @@ -52,32 +56,26 @@ const getAllUsers = async (req, res) => { data: users, }); } catch (err) { - res.status(500).json({ - message: 'something went wrong while fetching users', - error: err.message, - }); + return next(new ErrorResponse('Something went wrong while fetching user.', 500, err.message)); } -}; +}); -const getUser = async (req, res) => { +module.exports.getUser = asyncHandler(async (req, res, next) => { try { const user = await UserService.getUserById(req.payload.id); if (!user) { - return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + return next(new ErrorResponse("Unauthorized", 401, "Authentication required.")); } res.status(200).json({ user: user.toAuthJSON(), }); } catch (err) { - res.status(500).json({ - message: 'something went wrong while fetching user', - error: err.message, - }); + return next(new ErrorResponse('Something went wrong while fetching user.', 500, err.message)); } -}; +}); -const loginAsUser = async (req, res, next) => { +module.exports.loginAsUser = asyncHandler(async (req, res, next) => { try { if (!req.body.user.email) { return res.status(422).json({ errors: { email: "email can't be blank" } }); @@ -97,18 +95,15 @@ const loginAsUser = async (req, res, next) => { } })(req, res, next); } catch (err) { - res.status(500).json({ - message: 'something went wrong while processing login', - error: err.message, - }); + return next(new ErrorResponse('Something went wrong while processing login.', 500, err.message)); } -}; +}); -const updateUser = async (req, res) => { +module.exports.updateUser = asyncHandler(async (req, res, next) => { try { let user = await UserService.getUserById(req.payload.id); if (!user) { - return res.status(401).json({ errors: { "Unauthorized": "autentification requise" } }); + return next(new ErrorResponse("Unauthorized", 401, "You are not allowed to access this resource.")); } if (typeof req.body.user.username !== 'undefined') { user.username = req.body.user.username; @@ -149,14 +144,34 @@ const updateUser = async (req, res) => { user: user.toAuthJSON(), });*/ return user.save().then(function () { - return res.json({ user: user.toAuthJSON() }); + return res.json({ + message: 'User updated successfully.', + user: user.toAuthJSON() + }); }); } catch (err) { - res.status(500).json({ - message: 'something went wrong while updating user', - error: err.message, - }); + return next(new ErrorResponse('Something went wrong while updating user.', 500, err.message)); } -}; +}); -module.exports = { authenticate, createUser, getAllUsers, getUser, loginAsUser, updateUser }; \ No newline at end of file +module.exports.addFollowUser = asyncHandler(async (req, res, next) => { + try { + return res.status(200).json({ + message: 'The user is now being followed.', + user: true + }); + } catch (err) { + return next(new ErrorResponse('Something went wrong while following a user.', 500, err.message)); + } +}); + +module.exports.deleteFollowUser = asyncHandler(async (req, res, next) => { + try { + return res.status(200).json({ + message: 'The user is no longer being followed.', + user: true + }); + } catch (err) { + return next(new ErrorResponse('Something went wrong while unfollowing a user.', 500, err.message)); + } +}); diff --git a/src/database/connectDb.js b/src/database/connectDb.js index 97ad25e..1ca08c5 100644 --- a/src/database/connectDb.js +++ b/src/database/connectDb.js @@ -5,25 +5,25 @@ const mongoose = require('mongoose'); const DB = require('./mysql'); const sequelize = require('./config/sequelize'); const associate = require('./relationships'); -const utils = require('../utils'); +const DateUtilities = require('../utils/dateUtilities'); + + // Connect to the MongoDB database and log a message to the console const connectMongoDb = async () => { try { if (process.env.APP_ENV === 'production') { - mongoose.connect(process.env.MONGODB_URI); + await mongoose.connect(process.env.MONGODB_URI); + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.green('MongoDB connection has been established successfully 🟢')); } else { mongoose.set('strictQuery', true); - mongoose.connect(process.env.MONGODB_URI).then(() => { - console.log(`${utils.getDateTimeISOString()}`, chalk.green('MongoDB connection has been established successfully 🟢')); - }).catch(() => { - console.log(`${utils.getDateTimeISOString()}`, chalk.red('Unable to connect to the MongoDB database 🔴')); - }); + await mongoose.connect(process.env.MONGODB_URI); + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.green('MongoDB connection has been established successfully 🟢')); mongoose.set('debug', process.env.DEBUG); - console.log(`${utils.getDateTimeISOString()}`, chalk.red('debug :'), chalk.yellow(process.env.DEBUG)); + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.red('debug :'), chalk.yellow(process.env.DEBUG)); } } catch (error) { - console.log(`${utils.getDateTimeISOString()}`, chalk.red('Unable to connect to the MongoDB database 🔴')); + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.red('Unable to connect to the MongoDB database 🔴')); console.error('MongoDB connection error:', error); // Exit the process if the connection is not successful process.exit(1); @@ -34,18 +34,54 @@ const connectMongoDb = async () => { const connectMysqlDb = async () => { try { await sequelize.authenticate(); - console.log(`${utils.getDateTimeISOString()}`, chalk.green('MySQL connection has been established successfully 🟢')); + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.green('MySQL connection has been established successfully 🟢')); - // Synchronize the database with the models without need of dropping the tables - await DB.sequelize.sync({ - force: false, + // Check that required tables exist instead of synchronizing (no auto-create) + const queryInterface = sequelize.getQueryInterface(); + let existingTables = []; + try { + existingTables = await queryInterface.showAllTables(); + } catch (err) { + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.red('Unable to list tables from MySQL 🔴')); + console.error(err); + // Exit immediately if unable to list tables + process.exit(1); + } + + // Build expected table list from models exported in DB + const expectedTables = []; + Object.keys(DB).forEach((key) => { + const model = DB[key]; + if (model && typeof model.getTableName === 'function') { + try { + const t = model.getTableName(); + const tableName = typeof t === 'string' ? t : t.tableName; + if (tableName) expectedTables.push(tableName); + // eslint-disable-next-line no-unused-vars + } catch (err) { + // ignore + } + } }); - // Call the associate function to create the relationships between the models + // Normalize table names to strings for comparison + const existingNormalized = existingTables.map(t => (typeof t === 'string' ? t : t.tableName)).map(t => t.toString()); + + const missing = expectedTables.filter(t => !existingNormalized.includes(t) && !existingNormalized.includes(t.toLowerCase())); + + if (missing.length > 0) { + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.red('Database schema validation failed - missing tables:'), chalk.yellow(missing.join(', '))); + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.red('Run migrations to create the schema: npx sequelize-cli db:migrate')); + // Exit immediately if schema is incomplete + process.exit(1); + } + + // If all expected tables are present, create associations associate(); + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.green('Database schema validated successfully 🟢')); } catch (error) { - console.log(`${utils.getDateTimeISOString()}`, chalk.red('Unable to connect to the MySQL database 🔴')); + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.red('Unable to connect to the MySQL database 🔴')); console.error('MySQL connection error:', error); // Exit the process if the connection is not successful process.exit(1); @@ -55,8 +91,13 @@ const connectMysqlDb = async () => { // Connect to all database and log a message to the console const connectAllDb = async () => { - connectMongoDb(); - connectMysqlDb(); + try { + await connectMongoDb(); + await connectMysqlDb(); + } catch (error) { + console.error('Failed to connect to databases:', error); + process.exit(1); + } }; diff --git a/src/database/models/mysql/Article.js b/src/database/models/mysql/Article.js index 5350907..f493c92 100644 --- a/src/database/models/mysql/Article.js +++ b/src/database/models/mysql/Article.js @@ -63,7 +63,10 @@ const ArticleModel = () => { ); Article.beforeValidate((article) => { - article.slug = slug(`${article.title}`) + '-' + (Math.random() * Math.pow(36, 6) | 0).toString(36); + if (!article.slug) { + //article.slug = slug(`${article.title}`) + '-' + (Math.random() * Math.pow(36, 6) | 0).toString(36); + article.slug = slug(`${article.title}`); + } }); Article.prototype.toJSONFor = function () { @@ -72,7 +75,14 @@ const ArticleModel = () => { slug: this.slug, title: this.title, description: this.description, - body: this.body + body: this.body, + favorited: this.dataValues.favorited, + favoritesCount: this.dataValues.favoritesCount, + tagList: this.dataValues.tagList, + articleTags: this.dataValues.articleTags, + createdAt: this.createdAt, + updatedAt: this.updatedAt, + author: this.dataValues.author }; }; diff --git a/src/database/models/mysql/init-models.js b/src/database/models/mysql/init-models.js deleted file mode 100644 index 057d68c..0000000 --- a/src/database/models/mysql/init-models.js +++ /dev/null @@ -1,65 +0,0 @@ -var DataTypes = require("sequelize").DataTypes; -var _Article = require("./Article"); -var _ArticleTag = require("./ArticleTag"); -var _Comment = require("./Comment"); -var _Favorite = require("./Favorite"); -var _Follower = require("./Follower"); -var _Tag = require("./Tag"); -var _TagList = require("./TagList"); -var _User = require("./User"); - -function initModels(sequelize) { - var Article = _Article(sequelize, DataTypes); - var ArticleTag = _ArticleTag(sequelize, DataTypes); - var Comment = _Comment(sequelize, DataTypes); - var Favorite = _Favorite(sequelize, DataTypes); - var Follower = _Follower(sequelize, DataTypes); - var Tag = _Tag(sequelize, DataTypes); - var TagList = _TagList(sequelize, DataTypes); - var User = _User(sequelize, DataTypes); - - Article.belongsToMany(Tag, { as: 'tagNameTags', through: ArticleTag, foreignKey: "articleId", otherKey: "tagName" }); - Article.belongsToMany(Tag, { as: 'tagNameTagTagLists', through: TagList, foreignKey: "articleId", otherKey: "tagName" }); - Article.belongsToMany(User, { as: 'userIdUsers', through: Favorite, foreignKey: "articleId", otherKey: "userId" }); - Tag.belongsToMany(Article, { as: 'articleIdArticles', through: ArticleTag, foreignKey: "tagName", otherKey: "articleId" }); - Tag.belongsToMany(Article, { as: 'articleIdArticleTagLists', through: TagList, foreignKey: "tagName", otherKey: "articleId" }); - User.belongsToMany(Article, { as: 'articleIdArticleFavorites', through: Favorite, foreignKey: "userId", otherKey: "articleId" }); - User.belongsToMany(User, { as: 'followerIdUsers', through: Follower, foreignKey: "userId", otherKey: "followerId" }); - User.belongsToMany(User, { as: 'userIdUserFollowers', through: Follower, foreignKey: "followerId", otherKey: "userId" }); - ArticleTag.belongsTo(Article, { as: "article", foreignKey: "articleId"}); - Article.hasMany(ArticleTag, { as: "articleTags", foreignKey: "articleId"}); - Comment.belongsTo(Article, { as: "article", foreignKey: "articleId"}); - Article.hasMany(Comment, { as: "comments", foreignKey: "articleId"}); - Favorite.belongsTo(Article, { as: "article", foreignKey: "articleId"}); - Article.hasMany(Favorite, { as: "favorites", foreignKey: "articleId"}); - TagList.belongsTo(Article, { as: "article", foreignKey: "articleId"}); - Article.hasMany(TagList, { as: "tagLists", foreignKey: "articleId"}); - ArticleTag.belongsTo(Tag, { as: "tagNameTag", foreignKey: "tagName"}); - Tag.hasMany(ArticleTag, { as: "articleTags", foreignKey: "tagName"}); - TagList.belongsTo(Tag, { as: "tagNameTag", foreignKey: "tagName"}); - Tag.hasMany(TagList, { as: "tagLists", foreignKey: "tagName"}); - Article.belongsTo(User, { as: "author", foreignKey: "authorId"}); - User.hasMany(Article, { as: "articles", foreignKey: "authorId"}); - Comment.belongsTo(User, { as: "author", foreignKey: "authorId"}); - User.hasMany(Comment, { as: "comments", foreignKey: "authorId"}); - Favorite.belongsTo(User, { as: "user", foreignKey: "userId"}); - User.hasMany(Favorite, { as: "favorites", foreignKey: "userId"}); - Follower.belongsTo(User, { as: "user", foreignKey: "userId"}); - User.hasMany(Follower, { as: "followers", foreignKey: "userId"}); - Follower.belongsTo(User, { as: "follower", foreignKey: "followerId"}); - User.hasMany(Follower, { as: "followerFollowers", foreignKey: "followerId"}); - - return { - Article, - ArticleTag, - Comment, - Favorite, - Follower, - Tag, - TagList, - User - }; -} -module.exports = initModels; -module.exports.initModels = initModels; -module.exports.default = initModels; diff --git a/src/database/relationships/index.js b/src/database/relationships/index.js index 0cb2ae4..3e38faf 100644 --- a/src/database/relationships/index.js +++ b/src/database/relationships/index.js @@ -1,11 +1,11 @@ const DB = require('../mysql'); const associate = () => { - DB.Article.belongsToMany(DB.Tag, { as: 'articleTags', through: DB.TagList, foreignKey: "articleId", otherKey: "tagName" }); - DB.Article.belongsToMany(DB.User, { as: 'userIdUsers', through: DB.Favorite, foreignKey: "articleId", otherKey: "userId" }); + DB.Article.belongsToMany(DB.Tag, { as: 'articleTags', through: DB.TagList, foreignKey: "articleId", otherKey: "tagName", onDelete: "CASCADE" }); + DB.Product.belongsToMany(DB.Tag, { as: 'productTags', through: DB.ProductTagXref, foreignKey: "productId", otherKey: "tagName", onDelete: "CASCADE" }); + DB.Article.belongsToMany(DB.User, { as: 'articleFavoriteUsers', through: DB.Favorite, foreignKey: "articleId", otherKey: "userId", onDelete: "CASCADE" }); DB.Category.belongsToMany(DB.Product, { as: 'productIdProducts', through: DB.ProductCategoryXref, foreignKey: "categoryId", otherKey: "productId" }); DB.Product.belongsToMany(DB.Category, { as: 'productCategories', through: DB.ProductCategoryXref, foreignKey: "productId", otherKey: "categoryId" }); - DB.Product.belongsToMany(DB.Tag, { as: 'productTags', through: DB.ProductTagXref, foreignKey: "productId", otherKey: "tagName" }); DB.Role.belongsToMany(DB.User, { as: 'userIdUserUserRoleXrefs', through: DB.UserRoleXref, foreignKey: "roleId", otherKey: "userId" }); DB.Tag.belongsToMany(DB.Article, { as: 'articleIdArticleTagLists', through: DB.TagList, foreignKey: "tagName", otherKey: "articleId" }); DB.Tag.belongsToMany(DB.Product, { as: 'productIdProductProductTagXrefs', through: DB.ProductTagXref, foreignKey: "tagName", otherKey: "productId" }); @@ -14,11 +14,11 @@ const associate = () => { DB.User.belongsToMany(DB.User, { as: 'followerIdUsers', through: DB.Follower, foreignKey: "userId", otherKey: "followerId" }); DB.User.belongsToMany(DB.User, { as: 'userIdUserFollowers', through: DB.Follower, foreignKey: "followerId", otherKey: "userId" }); DB.Comment.belongsTo(DB.Article, { as: "article", foreignKey: "articleId"}); - DB.Article.hasMany(DB.Comment, { as: "comments", foreignKey: "articleId"}); + DB.Article.hasMany(DB.Comment, { as: "comments", foreignKey: "articleId", onDelete: "CASCADE"}); DB.Favorite.belongsTo(DB.Article, { as: "article", foreignKey: "articleId"}); - DB.Article.hasMany(DB.Favorite, { as: "favorites", foreignKey: "articleId"}); + DB.Article.hasMany(DB.Favorite, { as: "favorites", foreignKey: "articleId", onDelete: "CASCADE"}); DB.TagList.belongsTo(DB.Article, { as: "article", foreignKey: "articleId"}); - DB.Article.hasMany(DB.TagList, { as: "tagLists", foreignKey: "articleId"}); + DB.Article.hasMany(DB.TagList, { as: "tagLists", foreignKey: "articleId", onDelete: "CASCADE"}); DB.Product.belongsTo(DB.Brand, { as: "brand", foreignKey: "brandId"}); DB.Brand.hasMany(DB.Product, { as: "products", foreignKey: "brandId"}); DB.ProductCategoryXref.belongsTo(DB.Category, { as: "category", foreignKey: "categoryId"}); @@ -34,11 +34,11 @@ const associate = () => { DB.ProductTagXref.belongsTo(DB.Tag, { as: "tagNameTag", foreignKey: "tagName"}); DB.Tag.hasMany(DB.ProductTagXref, { as: "productTagXrefs", foreignKey: "tagName"}); DB.TagList.belongsTo(DB.Tag, { as: "tagNameTag", foreignKey: "tagName"}); - DB.Tag.hasMany(DB.TagList, { as: "tagLists", foreignKey: "tagName"}); + DB.Tag.hasMany(DB.TagList, { as: "tagLists", foreignKey: "tagName", onDelete: "CASCADE"}); DB.Article.belongsTo(DB.User, { as: "author", foreignKey: "authorId"}); - DB.User.hasMany(DB.Article, { as: "articles", foreignKey: "authorId"}); + DB.User.hasMany(DB.Article, { as: "articles", foreignKey: "authorId", onDelete: "CASCADE"}); DB.Comment.belongsTo(DB.User, { as: "author", foreignKey: "authorId"}); - DB.User.hasMany(DB.Comment, { as: "comments", foreignKey: "authorId"}); + DB.User.hasMany(DB.Comment, { as: "comments", foreignKey: "authorId", onDelete: "CASCADE"}); DB.Favorite.belongsTo(DB.User, { as: "user", foreignKey: "userId"}); DB.User.hasMany(DB.Favorite, { as: "favorites", foreignKey: "userId"}); DB.Follower.belongsTo(DB.User, { as: "user", foreignKey: "userId"}); diff --git a/src/routes/api/index.js b/src/routes/api/index.js index 825f97e..a0dd461 100644 --- a/src/routes/api/index.js +++ b/src/routes/api/index.js @@ -11,10 +11,10 @@ routes.use('/qcm', require('./v1/qcm')); routes.use('/user', require('./v2/user.routes')); routes.use('/articles', require('./v2/articles.routes')); -routes.use('/comment', require('./v2/comment.routes')); +routes.use('/comments', require('./v2/comments.routes')); routes.use('/product', require('./v2/product.routes')); routes.use('/products', require('./v2/products.routes')); -routes.use('/tag', require('./v2/tag.routes')); +routes.use('/tags', require('./v2/tags.routes')); routes.use('/clans', require('./v3/clans.routes')); diff --git a/src/routes/api/v1/jumps.js b/src/routes/api/v1/jumps.js index 40fd55c..d8168eb 100644 --- a/src/routes/api/v1/jumps.js +++ b/src/routes/api/v1/jumps.js @@ -6,8 +6,9 @@ var router = require('express').Router(), X2DataLog = mongoose.model('X2DataLog'); const auth = require('../../../middlewares/auth'), chalk = require('chalk'), - queries = require('../../queries'), - utils = require('../../utils'); + queries = require('../../queries'); +const DateUtilities = require('../../../utils/dateUtilities'); +const utils = require('../../../utils'); // Preload jump objects on routes with ':jump' router.param('jump', function (req, res, next, slug) { @@ -280,9 +281,9 @@ router.get('/allFromSkydiverIdApi', auth.required, function (req, res, next) { } utils.fetchSkydiverIdApi('jumps', req.query).then(result => { if (result.errors === undefined) { - console.log(`${utils.getDateTimeISOString()}`, chalk.green(`Réception des sauts SkydiverId`)); + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.green(`Réception des sauts SkydiverId`)); } else { - console.log(`${utils.getDateTimeISOString()}`, chalk.red(`Une erreur ${result.errors.code} '${result.errors.type}' est survenue lors de la réception des sauts SkydiverId : ${result.errors.name} ${result.errors.message}`)); + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.red(`Une erreur ${result.errors.code} '${result.errors.type}' est survenue lors de la réception des sauts SkydiverId : ${result.errors.name} ${result.errors.message}`)); } return res.json({ jumps: result, @@ -491,11 +492,11 @@ router.post('/kml/:jump', auth.required, function (req, res, next) { //console.log(chalk.yellow(`jump/${req.jump.x2data.id}/kml`)); return res.json({ file: file.toJSONForJump() }); /* - utils.fetchSkydiverIdApi(`jump/${req.jump.x2data.id}/kml`, {}).then(result => { + DateUtilities.fetchSkydiverIdApi(`jump/${req.jump.x2data.id}/kml`, {}).then(result => { if (result.errors === undefined) { - console.log(`${utils.getDateTimeISOString()}`, chalk.green(`Réception de l'url du fichier kml SkydiverId`)); + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.green(`Réception de l'url du fichier kml SkydiverId`)); } else { - console.log(`${utils.getDateTimeISOString()}`, chalk.red(`Une erreur ${result.errors.code} '${result.errors.type}' est survenue lors de la réception de l'url du fichier kml SkydiverId : ${result.errors.name} ${result.errors.message}`)); + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.red(`Une erreur ${result.errors.code} '${result.errors.type}' est survenue lors de la réception de l'url du fichier kml SkydiverId : ${result.errors.name} ${result.errors.message}`)); } if (result.url !== undefined) { console.log(chalk.green(result.url)); @@ -509,11 +510,11 @@ router.post('/kml/:jump', auth.required, function (req, res, next) { return file.save().then(function () { Jump.updateOne({_id: req.jump._id}, {files: files}).then(function () { - utils.fetchSkydiverIdApi(`jump/${req.jump.x2data.id}/kml`, {}).then(result => { + DateUtilities.fetchSkydiverIdApi(`jump/${req.jump.x2data.id}/kml`, {}).then(result => { if (result.errors === undefined) { - console.log(`${utils.getDateTimeISOString()}`, chalk.green(`Réception de l'url du fichier kml SkydiverId`)); + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.green(`Réception de l'url du fichier kml SkydiverId`)); } else { - console.log(`${utils.getDateTimeISOString()}`, chalk.red(`Une erreur ${result.errors.code} '${result.errors.type}' est survenue lors de la réception de l'url du fichier kml SkydiverId : ${result.errors.name} ${result.errors.message}`)); + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.red(`Une erreur ${result.errors.code} '${result.errors.type}' est survenue lors de la réception de l'url du fichier kml SkydiverId : ${result.errors.name} ${result.errors.message}`)); } if (result.url !== undefined) { console.log(result.url); diff --git a/src/routes/api/v2/articles.routes.js b/src/routes/api/v2/articles.routes.js index 52cdc6f..e25f5f1 100644 --- a/src/routes/api/v2/articles.routes.js +++ b/src/routes/api/v2/articles.routes.js @@ -1,10 +1,10 @@ const express = require('express'); const { + articlesFeed, + createArticle, + deleteArticle, getArticle, getArticles, - createArticle, - articlesFeed, - deleteArticle, updateArticle, addFavoriteArticle, deleteFavoriteArticle diff --git a/src/routes/api/v2/comment.routes.js b/src/routes/api/v2/comments.routes.js similarity index 100% rename from src/routes/api/v2/comment.routes.js rename to src/routes/api/v2/comments.routes.js diff --git a/src/routes/api/v2/tag.routes.js b/src/routes/api/v2/tags.routes.js similarity index 62% rename from src/routes/api/v2/tag.routes.js rename to src/routes/api/v2/tags.routes.js index 0936721..ee7e07b 100644 --- a/src/routes/api/v2/tag.routes.js +++ b/src/routes/api/v2/tags.routes.js @@ -1,10 +1,11 @@ const express = require('express'); -const { createTag, getAllTags } = require('../../../controllers/tag.controller'); +const { createTag, deleteTag, getAllTags } = require('../../../controllers/tag.controller'); const auth = require('../../../middlewares/auth'); const tagRouter = express.Router(); tagRouter.post('/', auth.required, createTag); tagRouter.get('/', auth.optional, getAllTags); +tagRouter.delete('/:name', auth.required, deleteTag); module.exports = tagRouter; diff --git a/src/routes/api/v2/user.routes.js b/src/routes/api/v2/user.routes.js index 791bfb6..ecf7d24 100644 --- a/src/routes/api/v2/user.routes.js +++ b/src/routes/api/v2/user.routes.js @@ -1,5 +1,5 @@ const express = require('express'); -const { authenticate, createUser, getUser, loginAsUser, updateUser } = require('../../../controllers/user.controller'); +const { authenticate, createUser, getUser, loginAsUser, updateUser, addFollowUser, deleteFollowUser } = require('../../../controllers/user.controller'); const auth = require('../../../middlewares/auth'); const userRouter = express.Router(); @@ -8,8 +8,11 @@ userRouter.get('/', auth.required, getUser); 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.get('/users', auth.required, getAllUsers); + +userRouter.post('/:username/follow', auth.required, addFollowUser); +userRouter.delete('/:username/follow', auth.required, deleteFollowUser); module.exports = userRouter; diff --git a/src/routes/utils.js b/src/routes/utils.js deleted file mode 100644 index c4145a7..0000000 --- a/src/routes/utils.js +++ /dev/null @@ -1,83 +0,0 @@ -const chalk = require('chalk'); -const utils = { - getDateString: () => { - let today = new Date(); - let dateNow = today.toLocaleDateString(); - return `${dateNow}`; - }, - getTimeString: () => { - let today = new Date(); - let timeNow = today.toLocaleTimeString(); - return `${timeNow}`; - }, - getDateTimeString: (delimiter = true) => { - let today = new Date(); - let dateString = `${today.toLocaleDateString()} ${today.toLocaleTimeString()}`; - if (delimiter) { - dateString = `[${dateString}]`; - } - return dateString; - }, - getDateTimeISOString: (delimiter = true) => { - let today = new Date(); - let dateString = today.toISOString(); - if (delimiter) { - dateString = `[${dateString}]`; - } - return dateString; - }, - async fetchSkydiverIdApi(path, query) { - let limit = 50; - let offset = 0; - let args = ''; - if (typeof query.limit !== 'undefined') { - limit = query.limit; - } - if (typeof query.offset !== 'undefined') { - offset = ((query.offset/limit)+1); - } - if (offset > 1) { - args += `&page=${offset}`; - } - if (typeof query.dateRangeStart !== 'undefined' && typeof query.dateRangeEnd !== 'undefined') { - args += `&filter[and][0][date][gte]=${query.dateRangeStart}`; - args += `&filter[and][0][date][lte]=${query.dateRangeEnd}`; - } - try { - let url = `${process.env.SKYDIVERID_API_URL}/${path}`; - if (typeof query.limit !== 'undefined') { - url += `?per-page=${limit}${args}`; - } - /* url pour provoquer une erreur en dev : url = `https://www.goldapi.ioppppp/api/${price.metal}/${price.currency}`; */ - let options = { - method: 'GET', - headers: { - 'Authorization': `Bearer ${process.env.SKYDIVERID_API_TOKEN}`, - 'Content-Type': 'application/json' - }, - redirect: 'follow' - }; - /* Pour tester le retour d'erreur : return { errors: { name: 'error name', message: 'error message', type: 'TYPE', code: 'CODE' } }; */ - //const response = await fetch(url, options); - return fetch(url, options).then(function (response) { - if (response.status >= 200 && response.status < 400) { - console.log(`${utils.getDateTimeISOString()}`, chalk.green(`Réception des sauts via SkydiverId API`), `${response.status} ${response.statusText}`); - const data = response.json(); - return data; - } else { - console.log(`${utils.getDateTimeISOString()}`, chalk.red(`Erreur ${response.status} ${response.statusText}`)); - return { errors: { name: response.statusText, message: `Une erreur ${response.status} '${response.statusText}' est survenue lors de la récupération des sauts via SkydiverId API`, type: 'API Error', code: response.status } }; - } - }) - .catch(error => { - console.log(`${utils.getDateTimeISOString()}`, chalk.red(`A fetchSkydiverIdApi error occured ${error.code} ${error.type}`)); - return { errors: { name: error.name, message: `A fetchSkydiverIdApi error occured : ${error.message}`, type: error.type, code: error.code } }; - }); - } catch (error) { - console.log(`${utils.getDateTimeISOString()}`, chalk.red(`fetchSkydiverIdApi error`)); - return { errors: { name: error.name, message: error.message, type: error.type, code: error.code } }; - } - } -}; - -module.exports = utils; diff --git a/src/services/article.service.js b/src/services/article.service.js deleted file mode 100644 index 27897e3..0000000 --- a/src/services/article.service.js +++ /dev/null @@ -1,94 +0,0 @@ -const DB = require('../database/mysql'); - -const { Article } = DB; - -class ArticleService { - static async createArticle(data) { - return Article.create(data); - } - - static async getAllArticles() { - return Article.findAll({ - order: [['createdAt', 'DESC']], - }); - } - - static async getArticlesByUserId(authorId) { - return Article.findAll({ - where: { authorId }, - include: [ - { - model: DB.User, - as: 'author', - attributes: ['username', 'email'], - }, - ], - order: [['createdAt', 'DESC']], - }); - } - - static async getArticleBySlug(slug) { - const articles = await Article.findAll({ - where: { slug }, - include: [ - { model: DB.Tag, as: "articleTags", 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]; - } - - static appendTagList(articleTags, article) { - const tagList = articleTags.map((tag) => tag.tagName); - - if (!article) return tagList; - delete article.dataValues.tagLists; //articleTags - article.dataValues.tagList = tagList; - } - - static async appendFavorites(loggedUser, article) { - console.log("article: ", article); - //const favorited = await article.hasUser(loggedUser ? loggedUser : null); - const favorited = await article.hasUserIdUsers(loggedUser ? loggedUser : null); - article.dataValues.favorited = loggedUser ? favorited : false; - - //const favoritesCount = await article.countUsers(); - const favoritesCount = await article.countFavorites(); - article.dataValues.favoritesCount = favoritesCount; - } - - static async appendFollowers(loggedUser, toAppend) { - if (toAppend?.author) { - const author = await toAppend.getAuthor(); - //const following = await author.hasFollower(loggedUser ? loggedUser : null); - const following = await author.hasFollowerIdUsers(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.hasFollowerIdUsers( - loggedUser ? loggedUser : null - ); - toAppend.dataValues.following = loggedUser ? following : false; - - const followersCount = await toAppend.countFollowers(); - toAppend.dataValues.followersCount = followersCount; - } - } -} - -module.exports = ArticleService; \ No newline at end of file diff --git a/src/services/index.js b/src/services/index.js index a2e2ea4..0d98b04 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -1,9 +1,15 @@ +/** + * Sequelize models for MySQL + */ +exports.ArticleService = require('./mysql/article.service'); +exports.CommentService = require('./mysql/comment.service'); +exports.ProductService = require('./mysql/product.service'); +exports.TagService = require('./mysql/tag.service'); +exports.UserService = require('./mysql/user.service'); +//exports.HeroWarService = require('./mysql/herowars.service'); -exports.ArticleService = require('./article.service'); -exports.CommentService = require('./comment.service'); -exports.ProductService = require('./product.service'); -exports.TagService = require('./tag.service'); -exports.UserService = require('./user.service'); -exports.HeroWarService = require('./herowars.service'); -exports.HWClanService = require('./hwclan.service'); -exports.HWMemberService = require('./hwmember.service'); \ No newline at end of file +/** + * Mongoose models for MongoDB + */ +exports.ClanService = require('./mongo/clan.service'); +exports.MemberService = require('./mongo/member.service'); \ No newline at end of file diff --git a/src/services/hwclan.service.js b/src/services/mongo/clan.service.js similarity index 90% rename from src/services/hwclan.service.js rename to src/services/mongo/clan.service.js index 7781eee..a84af21 100644 --- a/src/services/hwclan.service.js +++ b/src/services/mongo/clan.service.js @@ -1,12 +1,12 @@ var mongoose = require('mongoose'), HWClan = mongoose.model('HWClan'); -class HWClanService { +class ClanService { static async createClan(data) { let clan = new HWClan(); Object.assign(clan, data); return clan.save().then(function () { - return HWClanService.getClanById(data.id); + return ClanService.getClanById(data.id); }); } @@ -53,7 +53,7 @@ class HWClanService { static async updateClan(data) { return data.save().then(function () { - return HWClanService.getClanById(data.id); + return ClanService.getClanById(data.id); }); } @@ -63,4 +63,4 @@ class HWClanService { } -module.exports = HWClanService; \ No newline at end of file +module.exports = ClanService; \ No newline at end of file diff --git a/src/services/mongo/index.js b/src/services/mongo/index.js new file mode 100644 index 0000000..40c62e7 --- /dev/null +++ b/src/services/mongo/index.js @@ -0,0 +1,5 @@ +/** + * Mongoose models for MongoDB + */ +exports.ClanService = require('./clan.service'); +exports.MemberService = require('./member.service'); \ No newline at end of file diff --git a/src/services/hwmember.service.js b/src/services/mongo/member.service.js similarity index 90% rename from src/services/hwmember.service.js rename to src/services/mongo/member.service.js index 99f3bb8..6a798c2 100644 --- a/src/services/hwmember.service.js +++ b/src/services/mongo/member.service.js @@ -1,12 +1,12 @@ var mongoose = require('mongoose'), HWMember = mongoose.model('HWMember'); -class HWMemberService { +class MemberService { static async createMember(data) { let member = new HWMember(); Object.assign(member, data); return member.save().then(function () { - return HWMemberService.getMemberById(data.id); + return MemberService.getMemberById(data.id); }); } @@ -53,7 +53,7 @@ class HWMemberService { static async updateMember(data) { return data.save().then(function () { - return HWMemberService.getMemberById(data.id); + return MemberService.getMemberById(data.id); }); } @@ -63,4 +63,4 @@ class HWMemberService { } -module.exports = HWMemberService; \ No newline at end of file +module.exports = MemberService; \ No newline at end of file diff --git a/src/services/mysql/article.service.js b/src/services/mysql/article.service.js new file mode 100644 index 0000000..427a59f --- /dev/null +++ b/src/services/mysql/article.service.js @@ -0,0 +1,201 @@ +const slug = require("slug"); +const DB = require('../../database/mysql'); + +const { Article, Tag, User } = DB; + +class ArticleService { + static async createArticle(data) { + return Article.create(data); + } + + static async deleteArticle(article) { + return article.destroy(); + } + + static async getAllArticles() { + return Article.findAll({ + order: [['createdAt', 'DESC']], + }); + } + + static async getAllArticlesAndCount(searchOptions) { + return Article.findAndCountAll(searchOptions); + } + + static async getIncludeOptionsArticles() { + return ArticleService.getArticleIncludeAssoc(); + } + + static async getSearchOptionsArticles(tag, author, limit, offset) { + const searchOptions = { + include: [ + { + model: Tag, + as: "articleTags", + 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", "hash", "salt"] }, + // where: author ? { username: author } : {}, + ...(author && { where: { username: author } }), + }, + ], + limit: parseInt(limit), + offset: parseInt(offset), + order: [["createdAt", "DESC"]], + distinct: true, + }; + return searchOptions; + } + + static async getArticlesByUserId(authorId) { + return Article.findAll({ + where: { authorId }, + include: [ + { + model: DB.User, + as: 'author', + attributes: ['username', 'email'], + }, + ], + order: [['createdAt', 'DESC']], + }); + } + + static async getArticleBySlug(slug) { + const articles = await Article.findAll({ + where: { slug }, + include: ArticleService.getArticleIncludeAssoc(), + 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]; + } + + static async getArticleBySlugWithTagList(slug) { + const articles = await Article.findAll({ + where: { slug }, + include: ArticleService.getArticleIncludeAssoc(), + order: [['createdAt', 'DESC']], + }); + if (articles[0]) { + const articleTags = await articles[0].getTagLists(); + const tagList = articleTags.map((tag) => tag.tagName); + delete articles[0].dataValues.tagLists; + articles[0].dataValues.tagList = tagList; + } + + return articles[0]; + } + + static async getArticleById(id) { + const articles = await Article.findAll({ + where: { id }, + include: ArticleService.getArticleIncludeAssoc(), + order: [['createdAt', 'DESC']], + }); + + return articles[0]; + } + + static async getArticleByIdWithTagList(id) { + const articles = await Article.findAll({ + where: { id }, + include: ArticleService.getArticleIncludeAssoc(), + order: [['createdAt', 'DESC']], + }); + if (articles[0]) { + const articleTags = await articles[0].getTagLists(); + const tagList = articleTags.map((tag) => tag.tagName); + delete articles[0].dataValues.tagLists; + articles[0].dataValues.tagList = tagList; + } + + return articles[0]; + } + + static getArticleIncludeAssoc() { + const assocs = [ + { model: DB.Tag, as: "articleTags", 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"] } }, + ] + return assocs; + } + + static async isSlugUsed(slug) { + const article = await Article.findOne({ where: { slug: slug } }); + return !!article; + } + + static async appendTagList(article) { + const articleTags = await article.getTagLists(); + const tagList = articleTags.map((tag) => tag.tagName); + + if (!article) return tagList; + delete article.dataValues.tagLists; + article.dataValues.tagList = tagList; + } + + static async appendFavorites(loggedUser, article) { + const favorited = await article.hasArticleFavoriteUsers(loggedUser ? loggedUser : null); + article.dataValues.favorited = loggedUser ? favorited : false; + + const favoritesCount = await article.countFavorites(); + article.dataValues.favoritesCount = favoritesCount; + } + + static async appendFollowers(loggedUser, toAppend) { + // If toAppend has an author (it's an Article) + if (toAppend?.author) { + await this.appendArticleFollowers(loggedUser, toAppend); + } else if (toAppend?.id && toAppend?.hasUserIdUserFollower) { + // toAppend is a User model + await this.appendUserFollowers(loggedUser, toAppend); + } + } + + static async appendArticleFollowers(loggedUser, article) { + // Append follower info to an article's author + const author = await article.getAuthor(); + const following = loggedUser ? await author.hasUserIdUserFollower(loggedUser) : false; + article.author.dataValues.following = following; + + const followersCount = await author.countFollowers(); + article.author.dataValues.followersCount = followersCount; + } + + static async appendUserFollowers(loggedUser, user) { + // Append follower info to a user + const following = loggedUser ? await user.hasUserIdUserFollower(loggedUser) : false; + user.dataValues.following = following; + + const followersCount = await user.countFollowers(); + user.dataValues.followersCount = followersCount; + } + + static slugify(title) { + return slug(`${title}`); + } + + static async updateArticleById(data, id) { + return Article.update(data, { where: { id: id } }); + } +} + +module.exports = ArticleService; \ No newline at end of file diff --git a/src/services/comment.service.js b/src/services/mysql/comment.service.js similarity index 77% rename from src/services/comment.service.js rename to src/services/mysql/comment.service.js index b21eab9..824d0f8 100644 --- a/src/services/comment.service.js +++ b/src/services/mysql/comment.service.js @@ -1,4 +1,4 @@ -const DB = require('../database/mysql'); +const DB = require('../../database/mysql'); const { Comment } = DB; diff --git a/src/services/mysql/index.js b/src/services/mysql/index.js new file mode 100644 index 0000000..e914f59 --- /dev/null +++ b/src/services/mysql/index.js @@ -0,0 +1,8 @@ +/** + * Sequelize models for MySQL + */ +exports.ArticleService = require('./article.service'); +exports.CommentService = require('./comment.service'); +exports.ProductService = require('./product.service'); +exports.TagService = require('./tag.service'); +exports.UserService = require('./user.service'); diff --git a/src/services/product.service.js b/src/services/mysql/product.service.js similarity index 97% rename from src/services/product.service.js rename to src/services/mysql/product.service.js index 547b945..79f24d7 100644 --- a/src/services/product.service.js +++ b/src/services/mysql/product.service.js @@ -1,4 +1,4 @@ -const DB = require('../database/mysql'); +const DB = require('../../database/mysql'); const { Product } = DB; diff --git a/src/services/mysql/tag.service.js b/src/services/mysql/tag.service.js new file mode 100644 index 0000000..7d84ad2 --- /dev/null +++ b/src/services/mysql/tag.service.js @@ -0,0 +1,36 @@ +const DB = require('../../database/mysql'); + +const { Tag } = DB; + +class TagService { + static async createTag(name) { + return Tag.create({ name: name }); + } + + static async deleteTag(tag) { + return tag.destroy(); + } + + static async getAllTags() { + return Tag.findAll({ + attributes: ["name"], + order: [['createdAt', 'DESC']], + }); + } + + static async getTagByPk(tag) { + return Tag.findByPk(tag.trim()); + } + + static async getTagByName(name) { + return Tag.findOne({ where: { name: name } }); + } + + static async tagIsInDB(name) { + const tag = await Tag.findOne({ where: { name: name } }); + return !!tag; + } + +} + +module.exports = TagService; \ No newline at end of file diff --git a/src/services/mysql/user.service.js b/src/services/mysql/user.service.js new file mode 100644 index 0000000..34c80f7 --- /dev/null +++ b/src/services/mysql/user.service.js @@ -0,0 +1,62 @@ +const DB = require('../../database/mysql'); + +const { User } = DB; + +class UserService { + static async createUser(data) { + return User.create(data); + } + + static async getAllUsers() { + return User.findAll({ + order: [['createdAt', 'DESC']], + }); + } + + static async getUserById(id) { + return User.findByPk(id); + } + + static async getLoggedUserById(id) { + return User.findOne({ + attributes: { exclude: ["password", "email", "hash", "salt"] }, + where: { id: id } + }); + } + + static async getUserByEmail(email) { + return User.findOne({ where: { email: email } }); + } + + static async getUserByUsername(username) { + return User.findOne({ where: { username: username } }); + } + + static async getFavorites(user, searchOptions) { + return user.getArticleIdArticles(searchOptions); + } + + static async getUserFollowed(user) { + // Sequelize generated method for "users this user follows" is named + // `getUserIdUserFollowers()` (see `src/database/relationships/index.js`). + return user.getUserIdUserFollowers(); + } + + static async getFavoritesCount(user) { + return user.countArticleIdArticles(); + } + + static async addFavoriteArticle(user, article) { + return user.addArticleIdArticles(article); + } + + static async removeFavoriteArticle(user, article) { + return user.removeArticleIdArticles(article); + } + + static async updateUser(data) { + return User.update(data); + } +} + +module.exports = UserService; \ No newline at end of file diff --git a/src/services/tag.service.js b/src/services/tag.service.js deleted file mode 100644 index 0f4602f..0000000 --- a/src/services/tag.service.js +++ /dev/null @@ -1,19 +0,0 @@ -const DB = require('../database/mysql'); - -const { Tag } = DB; - -class TagService { - static async createTag(data) { - return Tag.create(data); - } - - static async getAllTags() { - return Tag.findAll({ - attributes: ["name"], - order: [['createdAt', 'DESC']], - }); - } - -} - -module.exports = TagService; \ No newline at end of file diff --git a/src/services/user.service.js b/src/services/user.service.js deleted file mode 100644 index 58f024a..0000000 --- a/src/services/user.service.js +++ /dev/null @@ -1,29 +0,0 @@ -const DB = require('../database/mysql'); - -const { User } = DB; - -class UserService { - static async createUser(data) { - return User.create(data); - } - - static async getAllUsers() { - return User.findAll({ - order: [['createdAt', 'DESC']], - }); - } - - static async getUserById(id) { - return User.findByPk(id); - } - - static async getUserByEmail(email) { - return User.findOne({ where: { email: email } }); - } - - static async updateUser(data) { - return User.update(data); - } -} - -module.exports = UserService; \ No newline at end of file diff --git a/src/utils/dateUtilities.js b/src/utils/dateUtilities.js new file mode 100644 index 0000000..616be70 --- /dev/null +++ b/src/utils/dateUtilities.js @@ -0,0 +1,33 @@ +class DateUtilities { + static getDateString() { + let today = new Date(); + let dateNow = today.toLocaleDateString(); + return `${dateNow}`; + } + + static getTimeString() { + let today = new Date(); + let timeNow = today.toLocaleTimeString(); + return `${timeNow}`; + } + + static getDateTimeString(delimiter = true) { + let today = new Date(); + let dateString = `${today.toLocaleDateString()} ${today.toLocaleTimeString()}`; + if (delimiter) { + dateString = `[${dateString}]`; + } + return dateString; + } + + static getDateTimeISOString(delimiter = true) { + let today = new Date(); + let dateString = today.toISOString(); + if (delimiter) { + dateString = `[${dateString}]`; + } + return dateString; + } +} + +module.exports = DateUtilities; diff --git a/src/utils/errorResponse.js b/src/utils/errorResponse.js index 29d4ac9..e8bbac5 100644 --- a/src/utils/errorResponse.js +++ b/src/utils/errorResponse.js @@ -1,8 +1,9 @@ class ErrorResponse extends Error { - constructor(message, statusCode) { - super(message); - this.statusCode = statusCode; - } + constructor(errorType, statusCode, message = '') { + super(message); + this.statusCode = statusCode; + this.errorType = errorType; + } } module.exports = ErrorResponse; diff --git a/src/utils/helpers.js b/src/utils/helpers.js index 97d7914..52dd1ef 100644 --- a/src/utils/helpers.js +++ b/src/utils/helpers.js @@ -9,7 +9,7 @@ const appendTagList = (articleTags, article) => { const appendFavorites = async (loggedUser, article) => { console.log("article: ", article); //const favorited = await article.hasUser(loggedUser ? loggedUser : null); - const favorited = await article.hasUserIdUsers(loggedUser ? loggedUser : null); + const favorited = await article.hasArticleFavoriteUsers(loggedUser ? loggedUser : null); article.dataValues.favorited = loggedUser ? favorited : false; //const favoritesCount = await article.countUsers(); @@ -18,23 +18,32 @@ const appendFavorites = async (loggedUser, article) => { }; const appendFollowers = async (loggedUser, toAppend) => { + // If toAppend has an author (it's an Article) if (toAppend?.author) { - const author = await toAppend.getAuthor(); - //const following = await author.hasFollower(loggedUser ? loggedUser : null); - const following = await author.hasFollowerIdUsers(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.hasFollowerIdUsers( - loggedUser ? loggedUser : null - ); - toAppend.dataValues.following = loggedUser ? following : false; - - const followersCount = await toAppend.countFollowers(); - toAppend.dataValues.followersCount = followersCount; + await appendArticleFollowers(loggedUser, toAppend); + } else if (toAppend?.id && toAppend?.hasUserIdUserFollower) { + // toAppend is a User model + await appendUserFollowers(loggedUser, toAppend); } }; -module.exports = { appendTagList, appendFavorites, appendFollowers }; +const appendArticleFollowers = async (loggedUser, article) => { + // Append follower info to an article's author + const author = await article.getAuthor(); + const following = loggedUser ? await author.hasUserIdUserFollower(loggedUser) : false; + article.author.dataValues.following = following; + + const followersCount = await author.countFollowers(); + article.author.dataValues.followersCount = followersCount; +}; + +const appendUserFollowers = async (loggedUser, user) => { + // Append follower info to a user + const following = loggedUser ? await user.hasUserIdUserFollower(loggedUser) : false; + user.dataValues.following = following; + + const followersCount = await user.countFollowers(); + user.dataValues.followersCount = followersCount; +}; + +module.exports = { appendTagList, appendFavorites, appendFollowers, appendArticleFollowers, appendUserFollowers }; diff --git a/src/utils/index.js b/src/utils/index.js index c4145a7..19c2ac1 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,31 +1,7 @@ const chalk = require('chalk'); +const DateUtilities = require('./dateUtilities'); + const utils = { - getDateString: () => { - let today = new Date(); - let dateNow = today.toLocaleDateString(); - return `${dateNow}`; - }, - getTimeString: () => { - let today = new Date(); - let timeNow = today.toLocaleTimeString(); - return `${timeNow}`; - }, - getDateTimeString: (delimiter = true) => { - let today = new Date(); - let dateString = `${today.toLocaleDateString()} ${today.toLocaleTimeString()}`; - if (delimiter) { - dateString = `[${dateString}]`; - } - return dateString; - }, - getDateTimeISOString: (delimiter = true) => { - let today = new Date(); - let dateString = today.toISOString(); - if (delimiter) { - dateString = `[${dateString}]`; - } - return dateString; - }, async fetchSkydiverIdApi(path, query) { let limit = 50; let offset = 0; @@ -61,20 +37,20 @@ const utils = { //const response = await fetch(url, options); return fetch(url, options).then(function (response) { if (response.status >= 200 && response.status < 400) { - console.log(`${utils.getDateTimeISOString()}`, chalk.green(`Réception des sauts via SkydiverId API`), `${response.status} ${response.statusText}`); + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.green(`Réception des sauts via SkydiverId API`), `${response.status} ${response.statusText}`); const data = response.json(); return data; } else { - console.log(`${utils.getDateTimeISOString()}`, chalk.red(`Erreur ${response.status} ${response.statusText}`)); + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.red(`Erreur ${response.status} ${response.statusText}`)); return { errors: { name: response.statusText, message: `Une erreur ${response.status} '${response.statusText}' est survenue lors de la récupération des sauts via SkydiverId API`, type: 'API Error', code: response.status } }; } }) .catch(error => { - console.log(`${utils.getDateTimeISOString()}`, chalk.red(`A fetchSkydiverIdApi error occured ${error.code} ${error.type}`)); + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.red(`A fetchSkydiverIdApi error occured ${error.code} ${error.type}`)); return { errors: { name: error.name, message: `A fetchSkydiverIdApi error occured : ${error.message}`, type: error.type, code: error.code } }; }); } catch (error) { - console.log(`${utils.getDateTimeISOString()}`, chalk.red(`fetchSkydiverIdApi error`)); + console.log(`${DateUtilities.getDateTimeISOString()}`, chalk.red(`fetchSkydiverIdApi error`)); return { errors: { name: error.name, message: error.message, type: error.type, code: error.code } }; } }