Ajout initial des fichiers de la version mysal
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
APP_ENV="development"
|
||||||
|
NODE_ENV="development"
|
||||||
|
DEBUG=false
|
||||||
|
SERVER_PORT="3200"
|
||||||
|
SECRET="$2b$10$2.JkQCRUa3fXESy/WyKVWO"
|
||||||
|
SESSION_LIFETIME="3600"
|
||||||
|
SENDGRID_API_KEY="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"
|
||||||
|
SENDGRID_FROM_MAIL="contact@rampeur.fr"
|
||||||
|
SENDGRID_TO_MAIL="rampeur@gmail.com"
|
||||||
|
MYSQL_DBNAME="c4adastracomdb1"
|
||||||
|
MYSQL_DBUSER="c4adastracomu1"
|
||||||
|
MYSQL_DBPASS="kbrnrt5jPK12"
|
||||||
|
MYSQL_DBHOST="ns388640.ip-176-31-255.eu"
|
||||||
|
MYSQL_DBPORT="3306"
|
||||||
|
MYSQL_DBTYPE="mysql"
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
APP_ENV="development"
|
||||||
|
NODE_ENV="development"
|
||||||
|
DEBUG=false
|
||||||
|
SERVER_PORT="3200"
|
||||||
|
SECRET="$2b$10$2.JkQCRUa3fXESy/WyKVWO"
|
||||||
|
SESSION_LIFETIME=3600
|
||||||
|
SENDGRID_API_KEY="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"
|
||||||
|
SENDGRID_FROM_MAIL="contact@rampeur.fr"
|
||||||
|
SENDGRID_TO_MAIL="rampeur@gmail.com"
|
||||||
|
MYSQL_DBNAME="c4adastracomdb1"
|
||||||
|
MYSQL_DBUSER="c4adastracomu1"
|
||||||
|
MYSQL_DBPASS="kbrnrt5jPK12"
|
||||||
|
MYSQL_DBHOST="ns388640.ip-176-31-255.eu"
|
||||||
|
MYSQL_DBPORT="3306"
|
||||||
|
MYSQL_DBTYPE="mysql"
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
APP_ENV="local"
|
||||||
|
NODE_ENV="development"
|
||||||
|
DEBUG=false
|
||||||
|
SERVER_PORT="3200"
|
||||||
|
SECRET="$2b$10$2.JkQCRUa3fXESy/WyKVWO"
|
||||||
|
SESSION_LIFETIME=3600
|
||||||
|
SENDGRID_API_KEY="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"
|
||||||
|
SENDGRID_FROM_MAIL="contact@rampeur.fr"
|
||||||
|
SENDGRID_TO_MAIL="rampeur@gmail.com"
|
||||||
|
MYSQL_DBNAME="c4adastracomdb1"
|
||||||
|
MYSQL_DBUSER="c4adastracomu1"
|
||||||
|
MYSQL_DBPASS="kbrnrt5jPK12"
|
||||||
|
MYSQL_DBHOST="ns388640.ip-176-31-255.eu"
|
||||||
|
MYSQL_DBPORT="3306"
|
||||||
|
MYSQL_DBTYPE="mysql"
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
APP_ENV="production"
|
||||||
|
NODE_ENV="production"
|
||||||
|
DEBUG=false
|
||||||
|
SERVER_PORT="3200"
|
||||||
|
SECRET="$2b$10$2.JkQCRUa3fXESy/WyKVWO"
|
||||||
|
SESSION_LIFETIME=3600
|
||||||
|
SENDGRID_API_KEY="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"
|
||||||
|
SENDGRID_FROM_MAIL="contact@rampeur.fr"
|
||||||
|
SENDGRID_TO_MAIL="rampeur@gmail.com"
|
||||||
|
MYSQL_DBNAME="c4adastracomdb1"
|
||||||
|
MYSQL_DBUSER="c4adastracomu1"
|
||||||
|
MYSQL_DBPASS="kbrnrt5jPK12"
|
||||||
|
MYSQL_DBHOST="ns388640.ip-176-31-255.eu"
|
||||||
|
MYSQL_DBPORT="3306"
|
||||||
|
MYSQL_DBTYPE="mysql"
|
||||||
@@ -0,0 +1,265 @@
|
|||||||
|
const { where } = require("sequelize");
|
||||||
|
const asyncHandler = require("../middlewares/asyncHandler");
|
||||||
|
const Article = require("../models/Article");
|
||||||
|
const Tag = require("../models/Tag");
|
||||||
|
const User = require("../models/User");
|
||||||
|
const ErrorResponse = require("../util/errorResponse");
|
||||||
|
const slugify = require("slugify");
|
||||||
|
|
||||||
|
const {
|
||||||
|
appendFollowers,
|
||||||
|
appendFavorites,
|
||||||
|
appendTagList,
|
||||||
|
} = require("../util/helpers");
|
||||||
|
|
||||||
|
const includeOptions = [
|
||||||
|
{
|
||||||
|
model: Tag,
|
||||||
|
as: "tagLists",
|
||||||
|
attributes: ["name"],
|
||||||
|
through: { attributes: [] },
|
||||||
|
},
|
||||||
|
{ model: User, as: "author", attributes: { exclude: ["email", "password"] } },
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports.getArticles = asyncHandler(async (req, res, next) => {
|
||||||
|
const { tag, author, favorited, limit = 20, offset = 0 } = req.query;
|
||||||
|
const { loggedUser } = req;
|
||||||
|
|
||||||
|
const searchOptions = {
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: Tag,
|
||||||
|
as: "tagLists",
|
||||||
|
attributes: ["name"],
|
||||||
|
through: { attributes: [] }, // ? this will remove the rows from the join table
|
||||||
|
// where: tag ? { name: tag } : {},
|
||||||
|
...(tag && { where: { name: tag } }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: User,
|
||||||
|
as: "author",
|
||||||
|
attributes: { exclude: ["password", "email"] },
|
||||||
|
// where: author ? { username: author } : {},
|
||||||
|
...(author && { where: { username: author } }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
limit: parseInt(limit),
|
||||||
|
offset: parseInt(offset),
|
||||||
|
order: [["createdAt", "DESC"]],
|
||||||
|
distinct: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let articles = { rows: [], count: 0 };
|
||||||
|
|
||||||
|
if (favorited) {
|
||||||
|
const user = await User.findOne({ where: { username: favorited } });
|
||||||
|
|
||||||
|
articles.rows = await user.getFavorites(searchOptions);
|
||||||
|
articles.count = await user.countFavorites();
|
||||||
|
} else {
|
||||||
|
articles = await Article.findAndCountAll(searchOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let article of articles.rows) {
|
||||||
|
const articleTags = await article.getTagLists();
|
||||||
|
// const articleTags = article.tagLists;
|
||||||
|
appendTagList(articleTags, article);
|
||||||
|
await appendFollowers(loggedUser, article);
|
||||||
|
await appendFavorites(loggedUser, article);
|
||||||
|
|
||||||
|
delete article.dataValues.Favorites;
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
.status(200)
|
||||||
|
.json({ articles: articles.rows, articlesCount: articles.count });
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.createArticle = asyncHandler(async (req, res, next) => {
|
||||||
|
const { loggedUser } = req;
|
||||||
|
|
||||||
|
fieldValidation(req.body.article.title, next);
|
||||||
|
fieldValidation(req.body.article.description, next);
|
||||||
|
fieldValidation(req.body.article.body, next);
|
||||||
|
|
||||||
|
const { title, description, body, tagList } = req.body.article;
|
||||||
|
const slug = slugify(title, { lower: true });
|
||||||
|
const slugInDB = await Article.findOne({ where: { slug: slug } });
|
||||||
|
if (slugInDB) next(new ErrorResponse("Title already exists", 400));
|
||||||
|
|
||||||
|
const article = await Article.create({
|
||||||
|
title: title,
|
||||||
|
description: description,
|
||||||
|
body: body,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const tag of tagList) {
|
||||||
|
const tagInDB = await Tag.findByPk(tag.trim());
|
||||||
|
|
||||||
|
if (tagInDB) {
|
||||||
|
await article.addTagList(tagInDB);
|
||||||
|
} else if (tag.length > 2) {
|
||||||
|
const newTag = await Tag.create({ name: tag.trim() });
|
||||||
|
await article.addTagList(newTag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete loggedUser.dataValues.token;
|
||||||
|
|
||||||
|
article.dataValues.tagList = tagList;
|
||||||
|
article.setAuthor(loggedUser);
|
||||||
|
article.dataValues.author = loggedUser;
|
||||||
|
await appendFollowers(loggedUser, loggedUser);
|
||||||
|
await appendFavorites(loggedUser, article);
|
||||||
|
|
||||||
|
res.status(201).json({ article });
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.deleteArticle = asyncHandler(async (req, res, next) => {
|
||||||
|
const { slug } = req.params;
|
||||||
|
const { loggedUser } = req;
|
||||||
|
|
||||||
|
const article = await Article.findOne({
|
||||||
|
where: { slug: slug },
|
||||||
|
include: includeOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!article) 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) 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: slugify(title ? title : article.title, { lower: true }) },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (slugInDB && slugInDB.slug !== slug)
|
||||||
|
return next(new ErrorResponse("Title already exists", 400));
|
||||||
|
|
||||||
|
await article.update({
|
||||||
|
title: title ? title : article.title,
|
||||||
|
description: description ? description : article.description,
|
||||||
|
body: body ? body : article.body,
|
||||||
|
});
|
||||||
|
|
||||||
|
const articleTags = await article.getTagLists();
|
||||||
|
appendTagList(articleTags, article);
|
||||||
|
await appendFollowers(loggedUser, article);
|
||||||
|
await appendFavorites(loggedUser, article);
|
||||||
|
|
||||||
|
res.status(200).json({ article });
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.articlesFeed = asyncHandler(async (req, res, next) => {
|
||||||
|
const { loggedUser } = req;
|
||||||
|
|
||||||
|
const { limit = 3, offset = 0 } = req.query;
|
||||||
|
const authors = await loggedUser.getFollowing();
|
||||||
|
|
||||||
|
const articles = await Article.findAndCountAll({
|
||||||
|
include: includeOptions,
|
||||||
|
limit: parseInt(limit),
|
||||||
|
offset: offset * limit,
|
||||||
|
order: [["createdAt", "DESC"]],
|
||||||
|
where: { authorId: authors.map((author) => author.id) },
|
||||||
|
distinct: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const article of articles.rows) {
|
||||||
|
const articleTags = await article.getTagLists();
|
||||||
|
|
||||||
|
appendTagList(articleTags, article);
|
||||||
|
await appendFollowers(loggedUser, article);
|
||||||
|
await appendFavorites(loggedUser, article);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ articles: articles.rows, articlesCount: articles.count });
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.getArticle = asyncHandler(async (req, res, next) => {
|
||||||
|
const { loggedUser } = req;
|
||||||
|
const { slug } = req.params;
|
||||||
|
|
||||||
|
const article = await Article.findOne({
|
||||||
|
where: { slug: slug },
|
||||||
|
include: includeOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!article) return next(new ErrorResponse("Article not found", 404));
|
||||||
|
|
||||||
|
const articleTags = await article.getTagLists();
|
||||||
|
appendTagList(articleTags, article);
|
||||||
|
await appendFollowers(loggedUser, article);
|
||||||
|
await appendFavorites(loggedUser, article);
|
||||||
|
|
||||||
|
res.status(200).json({ article });
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.addFavoriteArticle = asyncHandler(async (req, res, next) => {
|
||||||
|
const { loggedUser } = req;
|
||||||
|
const { slug } = req.params;
|
||||||
|
|
||||||
|
const article = await Article.findOne({
|
||||||
|
where: { slug: slug },
|
||||||
|
include: includeOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!article) return next(new ErrorResponse("Article not found", 404));
|
||||||
|
|
||||||
|
await loggedUser.addFavorite(article);
|
||||||
|
|
||||||
|
const articleTags = await article.getTagLists();
|
||||||
|
appendTagList(articleTags, article);
|
||||||
|
await appendFollowers(loggedUser, article);
|
||||||
|
await appendFavorites(loggedUser, article);
|
||||||
|
|
||||||
|
res.status(200).json({ article });
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.deleteFavoriteArticle = asyncHandler(async (req, res, next) => {
|
||||||
|
const { loggedUser } = req;
|
||||||
|
const { slug } = req.params;
|
||||||
|
|
||||||
|
const article = await Article.findOne({
|
||||||
|
where: { slug: slug },
|
||||||
|
include: includeOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!article) return next(new ErrorResponse("Article not found", 404));
|
||||||
|
|
||||||
|
await loggedUser.removeFavorite(article);
|
||||||
|
|
||||||
|
const articleTags = await article.getTagLists();
|
||||||
|
appendTagList(articleTags, article);
|
||||||
|
await appendFollowers(loggedUser, article);
|
||||||
|
await appendFavorites(loggedUser, article);
|
||||||
|
|
||||||
|
res.status(200).json({ article });
|
||||||
|
});
|
||||||
|
|
||||||
|
const fieldValidation = (field, next) => {
|
||||||
|
if (!field) {
|
||||||
|
return next(new ErrorResponse(`Missing fields`, 400));
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
const Comment = require("../models/Comment");
|
||||||
|
const Article = require("../models/Article");
|
||||||
|
const User = require("../models/User");
|
||||||
|
const asyncHandler = require("../middlewares/asyncHandler");
|
||||||
|
const { appendFollowers } = require("../util/helpers");
|
||||||
|
const ErrorResponse = require("../util/errorResponse");
|
||||||
|
|
||||||
|
module.exports.getComments = asyncHandler(async (req, res, next) => {
|
||||||
|
const { slug } = req.params;
|
||||||
|
const { loggedUser } = req;
|
||||||
|
|
||||||
|
const article = await Article.findOne({
|
||||||
|
where: { slug: slug },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!article) return next(new ErrorResponse("Article not found", 404));
|
||||||
|
|
||||||
|
const comments = await article.getComments({
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: User,
|
||||||
|
as: "author",
|
||||||
|
attributes: ["username", "bio", "image"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let comment of comments) {
|
||||||
|
await appendFollowers(loggedUser, comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ comments });
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.getComment = asyncHandler(async (req, res, next) => {
|
||||||
|
const { slug, id } = req.params;
|
||||||
|
const { loggedUser } = req;
|
||||||
|
|
||||||
|
const article = await Article.findOne({
|
||||||
|
where: { slug: slug },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!article) return next(new ErrorResponse("Article not found", 404));
|
||||||
|
|
||||||
|
const comment = await Comment.findOne({
|
||||||
|
where: { id: id },
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: User,
|
||||||
|
as: "author",
|
||||||
|
attributes: ["username", "bio", "image"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
if (!comment) return next(new ErrorResponse("Comment not found", 404));
|
||||||
|
|
||||||
|
await appendFollowers(loggedUser, comment);
|
||||||
|
|
||||||
|
res.status(200).json({ comment });
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.createComment = asyncHandler(async (req, res, next) => {
|
||||||
|
const { body } = req.body.comment;
|
||||||
|
const { slug } = req.params;
|
||||||
|
const { loggedUser } = req;
|
||||||
|
|
||||||
|
fieldValidation(body, next);
|
||||||
|
|
||||||
|
const article = await Article.findOne({
|
||||||
|
where: { slug: slug },
|
||||||
|
include: [
|
||||||
|
{ model: User, as: "author", attributes: ["username", "bio", "image"] },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!article) return next(new ErrorResponse("Article not found", 404));
|
||||||
|
|
||||||
|
const comment = await Comment.create({
|
||||||
|
body: body,
|
||||||
|
articleId: article.id,
|
||||||
|
authorId: loggedUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
delete loggedUser.dataValues.token;
|
||||||
|
await appendFollowers(loggedUser, loggedUser);
|
||||||
|
|
||||||
|
comment.dataValues.author = loggedUser;
|
||||||
|
|
||||||
|
res.status(201).json({ comment });
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.deleteComment = asyncHandler(async (req, res, next) => {
|
||||||
|
const { slug, id } = req.params;
|
||||||
|
const { loggedUser } = req;
|
||||||
|
|
||||||
|
const article = await Article.findOne({
|
||||||
|
where: { slug: slug },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!article) return next(new ErrorResponse("Article not found", 404));
|
||||||
|
|
||||||
|
const comment = await Comment.findOne({
|
||||||
|
where: { id: id },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!comment) return next(new ErrorResponse("Comment not found", 404));
|
||||||
|
|
||||||
|
if (comment.authorId !== loggedUser.id)
|
||||||
|
return next(new ErrorResponse("Unauthorized", 401));
|
||||||
|
|
||||||
|
await comment.destroy();
|
||||||
|
|
||||||
|
res.status(200).json({ comment });
|
||||||
|
});
|
||||||
|
|
||||||
|
const fieldValidation = (field, next) => {
|
||||||
|
if (!field) {
|
||||||
|
return next(new ErrorResponse(`Missing fields`, 400));
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
const User = require("../models/User");
|
||||||
|
const asyncHandler = require("../middlewares/asyncHandler");
|
||||||
|
const ErrorResponse = require("../util/errorResponse");
|
||||||
|
|
||||||
|
module.exports.getProfile = asyncHandler(async (req, res, next) => {
|
||||||
|
const username = req.params.username;
|
||||||
|
const { loggedUser } = req;
|
||||||
|
const user = await User.findOne({ where: { username: username } });
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return next(new ErrorResponse(`User not found`, 404));
|
||||||
|
}
|
||||||
|
|
||||||
|
let isFollowing = false;
|
||||||
|
|
||||||
|
if (loggedUser) {
|
||||||
|
const followers = await user.getFollowers();
|
||||||
|
isFollowing = followers.some((user) => user.id === loggedUser.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const profile = {
|
||||||
|
username,
|
||||||
|
bio: user.bio,
|
||||||
|
image: user.image,
|
||||||
|
following: isFollowing,
|
||||||
|
};
|
||||||
|
res.status(200).json({ profile });
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.followUser = asyncHandler(async (req, res, next) => {
|
||||||
|
const username = req.params.username;
|
||||||
|
const { loggedUser } = req;
|
||||||
|
const userToFollow = await User.findOne({ where: { username: username } });
|
||||||
|
|
||||||
|
if (!userToFollow) {
|
||||||
|
return next(new ErrorResponse(`User not found`, 404));
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentUser = await User.findByPk(loggedUser.id);
|
||||||
|
|
||||||
|
await userToFollow.addFollower(currentUser);
|
||||||
|
|
||||||
|
const profile = {
|
||||||
|
username: username,
|
||||||
|
bio: userToFollow.dataValues.bio,
|
||||||
|
image: userToFollow.dataValues.image,
|
||||||
|
following: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
res.status(200).json({ profile });
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.unfollowUser = asyncHandler(async (req, res, next) => {
|
||||||
|
const username = req.params.username;
|
||||||
|
const { loggedUser } = req;
|
||||||
|
const userToUnfollow = await User.findOne({ where: { username: username } });
|
||||||
|
|
||||||
|
if (!userToUnfollow) {
|
||||||
|
return next(new ErrorResponse(`User not found`, 404));
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentUser = await User.findByPk(loggedUser.id);
|
||||||
|
|
||||||
|
await userToUnfollow.removeFollower(currentUser);
|
||||||
|
|
||||||
|
const profile = {
|
||||||
|
username: username,
|
||||||
|
bio: userToUnfollow.dataValues.bio,
|
||||||
|
image: userToUnfollow.dataValues.image,
|
||||||
|
following: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
res.status(200).json({ profile });
|
||||||
|
});
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
const Tag = require("../models/Tag");
|
||||||
|
const { appendTagList } = require("../util/helpers");
|
||||||
|
|
||||||
|
module.exports.getTags = async (req, res, next) => {
|
||||||
|
let tags = await Tag.findAll({
|
||||||
|
attributes: ["name"],
|
||||||
|
});
|
||||||
|
|
||||||
|
tags = tags.map((tag) => tag.name);
|
||||||
|
|
||||||
|
res.status(200).json({ tags });
|
||||||
|
};
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
const asyncHandler = require("../middlewares/asyncHandler");
|
||||||
|
const User = require("../models/User");
|
||||||
|
const ErrorResponse = require("../util/errorResponse");
|
||||||
|
const { sign } = require("../util/jwt");
|
||||||
|
|
||||||
|
module.exports.createUser = asyncHandler(async (req, res, next) => {
|
||||||
|
const { email, password, username } = req.body.user;
|
||||||
|
|
||||||
|
fieldValidation(email, next);
|
||||||
|
fieldValidation(password, next);
|
||||||
|
fieldValidation(username, next);
|
||||||
|
|
||||||
|
const user = await User.create({
|
||||||
|
email: email,
|
||||||
|
password: password,
|
||||||
|
username: username,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user.dataValues.password) {
|
||||||
|
delete user.dataValues.password;
|
||||||
|
}
|
||||||
|
|
||||||
|
user.dataValues.token = await sign(user);
|
||||||
|
|
||||||
|
user.dataValues.bio = null;
|
||||||
|
user.dataValues.image = null;
|
||||||
|
|
||||||
|
res.status(201).json({ user });
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.loginUser = asyncHandler(async (req, res, next) => {
|
||||||
|
const { email, password } = req.body.user;
|
||||||
|
|
||||||
|
fieldValidation(email, next);
|
||||||
|
fieldValidation(password, next);
|
||||||
|
|
||||||
|
const user = await User.findOne({
|
||||||
|
where: {
|
||||||
|
email: email,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return next(new ErrorResponse(`User not found`, 404));
|
||||||
|
}
|
||||||
|
|
||||||
|
const isMatch = user.matchPassword(password);
|
||||||
|
|
||||||
|
if (!isMatch) {
|
||||||
|
return next(new ErrorResponse("Wrong password", 401));
|
||||||
|
}
|
||||||
|
|
||||||
|
delete user.dataValues.password;
|
||||||
|
|
||||||
|
user.dataValues.token = await sign(user);
|
||||||
|
|
||||||
|
user.dataValues.bio = null;
|
||||||
|
user.dataValues.image = null;
|
||||||
|
|
||||||
|
res.status(200).json({ user });
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.getCurrentUser = asyncHandler(async (req, res, next) => {
|
||||||
|
const { loggedUser } = req;
|
||||||
|
const user = await User.findByPk(loggedUser.id);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return next(new ErrorResponse(`User not found`, 404));
|
||||||
|
}
|
||||||
|
|
||||||
|
user.dataValues.token = req.headers.authorization.split(" ")[1];
|
||||||
|
|
||||||
|
res.status(200).json({ user });
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.updateUser = asyncHandler(async (req, res, next) => {
|
||||||
|
await User.update(req.body.user, {
|
||||||
|
where: {
|
||||||
|
id: req.user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = await User.findByPk(req.user.id);
|
||||||
|
user.dataValues.token = req.headers.authorization.split(" ")[1];
|
||||||
|
|
||||||
|
res.status(200).json({ user });
|
||||||
|
});
|
||||||
|
|
||||||
|
const fieldValidation = (field, next) => {
|
||||||
|
if (!field) {
|
||||||
|
return next(new ErrorResponse(`Missing fields`, 400));
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
const { DataTypes, Model } = require("sequelize");
|
||||||
|
const sequelize = require("../util/database");
|
||||||
|
const slugify = require("slugify");
|
||||||
|
|
||||||
|
const Article = sequelize.define("Article", {
|
||||||
|
slug: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Article.beforeValidate((article) => {
|
||||||
|
article.slug = slugify(article.title, { lower: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Article;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
const { DataTypes, Model } = require("sequelize");
|
||||||
|
const sequelize = require("../util/database");
|
||||||
|
|
||||||
|
const Comment = sequelize.define("Comment", {
|
||||||
|
body: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Comment;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
const { DataTypes, Model } = require("sequelize");
|
||||||
|
const sequelize = require("../util/database");
|
||||||
|
|
||||||
|
const Tag = sequelize.define(
|
||||||
|
"Tag",
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ timestamps: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
module.exports = Tag;
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
const { DataTypes, Model } = require("sequelize");
|
||||||
|
const sequelize = require("../util/database");
|
||||||
|
const bcrypt = require("bcryptjs");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "email": "jake@jake.jake",
|
||||||
|
"token": "jwt.token.here",
|
||||||
|
"username": "jake",
|
||||||
|
"bio": "I work at statefarm",
|
||||||
|
"image": null
|
||||||
|
*/
|
||||||
|
|
||||||
|
const User = sequelize.define(
|
||||||
|
"User",
|
||||||
|
{
|
||||||
|
email: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
unique: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
username: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
unique: true,
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
bio: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ timestamps: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
Model.prototype.matchPassword = async function (enteredPassword) {
|
||||||
|
return await bcrypt.compare(enteredPassword, this.password);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_SALT_ROUNDS = 10;
|
||||||
|
|
||||||
|
User.addHook("beforeCreate", async (user) => {
|
||||||
|
const encryptedPassword = await bcrypt.hash(
|
||||||
|
user.password,
|
||||||
|
DEFAULT_SALT_ROUNDS
|
||||||
|
);
|
||||||
|
user.password = encryptedPassword;
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = User;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
|
||||||
|
const slug = require("slug");
|
||||||
|
|
||||||
|
const {
|
||||||
|
Model
|
||||||
|
} = require('sequelize');
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
class Article extends Model {
|
||||||
|
/**
|
||||||
|
* Helper method for defining associations.
|
||||||
|
* This method is not a part of Sequelize lifecycle.
|
||||||
|
* The `models/index` file will call this method automatically.
|
||||||
|
*/
|
||||||
|
static associate(models) {
|
||||||
|
/* define association here */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Article.init({
|
||||||
|
article_id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false },
|
||||||
|
article_slug: { type: DataTypes.STRING, allowNull: false },
|
||||||
|
article_title: { type: DataTypes.STRING, allowNull: false },
|
||||||
|
article_description: { type: DataTypes.TEXT },
|
||||||
|
article_body: { type: DataTypes.TEXT, allowNull: false }
|
||||||
|
}, {
|
||||||
|
sequelize,
|
||||||
|
timestamps: false,
|
||||||
|
modelName: 'Article',
|
||||||
|
tableName: 'article',
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true
|
||||||
|
});
|
||||||
|
|
||||||
|
Article.beforeValidate((article) => {
|
||||||
|
article.slug = slug(article.title) + '-' + (Math.random() * Math.pow(36, 6) | 0).toString(36);
|
||||||
|
});
|
||||||
|
|
||||||
|
Article.prototype.toJSONFor = function () {
|
||||||
|
return {
|
||||||
|
id: this.article_id,
|
||||||
|
slug: this.article_slug,
|
||||||
|
title: this.article_title,
|
||||||
|
description: this.article_description,
|
||||||
|
body: this.article_body
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return Article;
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
const {
|
||||||
|
Model
|
||||||
|
} = require('sequelize');
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
class Comment extends Model {
|
||||||
|
/**
|
||||||
|
* Helper method for defining associations.
|
||||||
|
* This method is not a part of Sequelize lifecycle.
|
||||||
|
* The `models/index` file will call this method automatically.
|
||||||
|
*/
|
||||||
|
static associate(models) {
|
||||||
|
/* define association here */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Comment.init({
|
||||||
|
comment_id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false },
|
||||||
|
comment_body: { type: DataTypes.TEXT, allowNull: false }
|
||||||
|
}, {
|
||||||
|
sequelize,
|
||||||
|
timestamps: false,
|
||||||
|
modelName: 'Comment',
|
||||||
|
tableName: 'comment',
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true
|
||||||
|
});
|
||||||
|
|
||||||
|
Comment.prototype.toJSONFor = function () {
|
||||||
|
return {
|
||||||
|
id: this.comment_id,
|
||||||
|
body: this.comment_body
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return Comment;
|
||||||
|
};
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
'use strict';
|
||||||
|
const {
|
||||||
|
Model
|
||||||
|
} = require('sequelize');
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
class Membre extends Model {
|
||||||
|
/**
|
||||||
|
* Helper method for defining associations.
|
||||||
|
* This method is not a part of Sequelize lifecycle.
|
||||||
|
* The `models/index` file will call this method automatically.
|
||||||
|
*/
|
||||||
|
static associate(models) {
|
||||||
|
/* define association here */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Membre.init({
|
||||||
|
mbr_id: {type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false},
|
||||||
|
mbr_pseudo: {type: DataTypes.STRING, allowNull: false},
|
||||||
|
mbr_nom: {type: DataTypes.STRING},
|
||||||
|
mbr_prenom: {type: DataTypes.STRING},
|
||||||
|
mbr_email: {type: DataTypes.STRING},
|
||||||
|
mbr_adresse: {type: DataTypes.STRING},
|
||||||
|
mbr_ville: {type: DataTypes.STRING},
|
||||||
|
mbr_region: {type: DataTypes.STRING},
|
||||||
|
mbr_cp: {type: DataTypes.STRING},
|
||||||
|
mbr_pays: {type: DataTypes.STRING},
|
||||||
|
mbr_tel: {type: DataTypes.STRING},
|
||||||
|
mbr_mobile: {type: DataTypes.STRING},
|
||||||
|
mbr_statut: {type: DataTypes.INTEGER}
|
||||||
|
}, {
|
||||||
|
sequelize,
|
||||||
|
timestamps: false,
|
||||||
|
modelName: 'Membre',
|
||||||
|
tableName: 'membre',
|
||||||
|
createdAt: false,
|
||||||
|
updatedAt: false
|
||||||
|
});
|
||||||
|
|
||||||
|
Membre.prototype.toJSONFor = function () {
|
||||||
|
return {
|
||||||
|
id: this.mbr_id,
|
||||||
|
pseudo: this.mbr_pseudo,
|
||||||
|
nom: this.mbr_nom,
|
||||||
|
prenom: this.mbr_prenom,
|
||||||
|
email: this.mbr_email,
|
||||||
|
adresse: this.mbr_adresse,
|
||||||
|
ville: this.mbr_ville,
|
||||||
|
region: this.mbr_region,
|
||||||
|
cp: this.mbr_cp,
|
||||||
|
pays: this.mbr_pays,
|
||||||
|
tel: this.mbr_tel,
|
||||||
|
mobile: this.mbr_mobile,
|
||||||
|
statut: this.mbr_statut
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return Membre;
|
||||||
|
};
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
const {
|
||||||
|
Model
|
||||||
|
} = require('sequelize');
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
class Tag extends Model {
|
||||||
|
/**
|
||||||
|
* Helper method for defining associations.
|
||||||
|
* This method is not a part of Sequelize lifecycle.
|
||||||
|
* The `models/index` file will call this method automatically.
|
||||||
|
*/
|
||||||
|
static associate(models) {
|
||||||
|
/* define association here */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Tag.init({
|
||||||
|
tag_name: { type: DataTypes.STRING, allowNull: false, primaryKey: true }
|
||||||
|
}, {
|
||||||
|
sequelize,
|
||||||
|
timestamps: false,
|
||||||
|
modelName: 'Tag',
|
||||||
|
tableName: 'tag',
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true
|
||||||
|
});
|
||||||
|
|
||||||
|
Tag.prototype.toJSONFor = function () {
|
||||||
|
return {
|
||||||
|
id: this.tag_id,
|
||||||
|
name: this.tag_name
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return Tag;
|
||||||
|
};
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const bcrypt = require('bcrypt');
|
||||||
|
|
||||||
|
const {
|
||||||
|
Model
|
||||||
|
} = require('sequelize');
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
class User extends Model {
|
||||||
|
/**
|
||||||
|
* Helper method for defining associations.
|
||||||
|
* This method is not a part of Sequelize lifecycle.
|
||||||
|
* The `models/index` file will call this method automatically.
|
||||||
|
*/
|
||||||
|
static associate(models) {
|
||||||
|
/* define association here */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
User.init({
|
||||||
|
user_id: {type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false},
|
||||||
|
user_username: {type: DataTypes.STRING, allowNull: false},
|
||||||
|
user_lastname: {type: DataTypes.STRING},
|
||||||
|
user_firstname: {type: DataTypes.STRING},
|
||||||
|
user_email: {type: DataTypes.STRING},
|
||||||
|
user_adresse: {type: DataTypes.STRING},
|
||||||
|
user_ville: {type: DataTypes.STRING},
|
||||||
|
user_region: {type: DataTypes.STRING},
|
||||||
|
user_cp: {type: DataTypes.STRING},
|
||||||
|
user_pays: {type: DataTypes.STRING},
|
||||||
|
user_tel: {type: DataTypes.STRING},
|
||||||
|
user_mobile: {type: DataTypes.STRING},
|
||||||
|
user_role: {type: DataTypes.INTEGER}
|
||||||
|
}, {
|
||||||
|
sequelize,
|
||||||
|
timestamps: false,
|
||||||
|
modelName: 'User',
|
||||||
|
tableName: 'user',
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true
|
||||||
|
});
|
||||||
|
|
||||||
|
User.prototype.matchPassword = async function (enteredPassword) {
|
||||||
|
return await bcrypt.compare(enteredPassword, this.password);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_SALT_ROUNDS = 10;
|
||||||
|
|
||||||
|
User.addHook("beforeCreate", async (user) => {
|
||||||
|
const encryptedPassword = await bcrypt.hash(
|
||||||
|
user.password,
|
||||||
|
DEFAULT_SALT_ROUNDS
|
||||||
|
);
|
||||||
|
user.password = encryptedPassword;
|
||||||
|
});
|
||||||
|
|
||||||
|
User.prototype.toJSONFor = function () {
|
||||||
|
return {
|
||||||
|
id: this.mbr_id,
|
||||||
|
username: this.mbr_pseudo,
|
||||||
|
lastname: this.mbr_nom,
|
||||||
|
firstname: this.mbr_prenom,
|
||||||
|
email: this.mbr_email,
|
||||||
|
adresse: this.mbr_adresse,
|
||||||
|
ville: this.mbr_ville,
|
||||||
|
region: this.mbr_region,
|
||||||
|
cp: this.mbr_cp,
|
||||||
|
pays: this.mbr_pays,
|
||||||
|
tel: this.mbr_tel,
|
||||||
|
mobile: this.mbr_mobile,
|
||||||
|
statut: this.mbr_statut
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return User;
|
||||||
|
};
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
const fs = require('fs'),
|
||||||
|
path = require('path'),
|
||||||
|
basename = path.basename(__filename),
|
||||||
|
chalk = require('chalk'),
|
||||||
|
utils = require('../../routes/utils');
|
||||||
|
|
||||||
|
const { Sequelize } = require('sequelize');
|
||||||
|
const db = {};
|
||||||
|
const Op = Sequelize.Op;
|
||||||
|
const sequelize = new Sequelize(
|
||||||
|
process.env.MYSQL_DBNAME,
|
||||||
|
process.env.MYSQL_DBUSER,
|
||||||
|
process.env.MYSQL_DBPASS,
|
||||||
|
{
|
||||||
|
host: process.env.MYSQL_DBHOST,
|
||||||
|
port: process.env.MYSQL_DBPORT,
|
||||||
|
dialect: process.env.MYSQL_DBTYPE,
|
||||||
|
$like: Op.like,
|
||||||
|
$not: Op.not
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
sequelize.authenticate().then(() => {
|
||||||
|
if (process.env.DEBUG) {
|
||||||
|
console.log(`${utils.getDateTimeISOString()}`, chalk.green('MySQL connection has been established successfully'));
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
console.log(`${utils.getDateTimeISOString()}`, chalk.red('Unable to connect to the MySQL database'));
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
fs
|
||||||
|
.readdirSync(__dirname)
|
||||||
|
.filter(file => {
|
||||||
|
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
|
||||||
|
})
|
||||||
|
.forEach(file => {
|
||||||
|
const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
|
||||||
|
db[model.name] = model;
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(db).forEach(modelName => {
|
||||||
|
if (db[modelName].associate) {
|
||||||
|
db[modelName].associate(db);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
db.sequelize = sequelize;
|
||||||
|
|
||||||
|
module.exports = db;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "adastra-api",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"author": "adastra",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "nodemon server.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bcryptjs": "^2.4.3",
|
||||||
|
"colors": "^1.4.0",
|
||||||
|
"dotenv": "^16.0.3",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
"morgan": "^1.10.0",
|
||||||
|
"mysql2": "^2.3.3",
|
||||||
|
"nodemon": "^2.0.20",
|
||||||
|
"sequelize": "^6.26.0",
|
||||||
|
"slugify": "^1.6.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "^8.29.0",
|
||||||
|
"prettier": "^2.8.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const router = express.Router();
|
||||||
|
const {
|
||||||
|
getArticle,
|
||||||
|
getArticles,
|
||||||
|
createArticle,
|
||||||
|
articlesFeed,
|
||||||
|
addFavoriteArticle,
|
||||||
|
deleteFavoriteArticle,
|
||||||
|
deleteArticle,
|
||||||
|
updateArticle,
|
||||||
|
} = require("../controllers/articles");
|
||||||
|
const { protect } = require("../middlewares/auth");
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/articles")
|
||||||
|
.get(protect, getArticles)
|
||||||
|
.post(protect, createArticle);
|
||||||
|
router.route("/articles/feed").get(protect, articlesFeed);
|
||||||
|
router
|
||||||
|
.route("/articles/:slug")
|
||||||
|
.get(protect, getArticle)
|
||||||
|
.put(protect, updateArticle)
|
||||||
|
.delete(protect, deleteArticle);
|
||||||
|
router
|
||||||
|
.route("/articles/:slug/favorite")
|
||||||
|
.post(protect, addFavoriteArticle)
|
||||||
|
.delete(protect, deleteFavoriteArticle);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
const {
|
||||||
|
getComment,
|
||||||
|
getComments,
|
||||||
|
createComment,
|
||||||
|
deleteComment,
|
||||||
|
} = require("../controllers/comments");
|
||||||
|
|
||||||
|
const { protect } = require("../middlewares/auth");
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/articles/:slug/comments")
|
||||||
|
.get(protect, getComments)
|
||||||
|
.post(protect, createComment);
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/articles/:slug/comments/:id")
|
||||||
|
.get(protect, getComment)
|
||||||
|
.delete(protect, deleteComment);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const router = express.Router();
|
||||||
|
const {
|
||||||
|
getProfile,
|
||||||
|
followUser,
|
||||||
|
unfollowUser,
|
||||||
|
} = require("../controllers/profiles");
|
||||||
|
const { protect } = require("../middlewares/auth");
|
||||||
|
|
||||||
|
router.route("/profiles/:username").get(protect, getProfile);
|
||||||
|
router
|
||||||
|
.route("/profiles/:username/follow")
|
||||||
|
.post(protect, followUser)
|
||||||
|
.delete(protect, unfollowUser);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
const { getTags } = require("../controllers/tags");
|
||||||
|
|
||||||
|
const { protect } = require("../middlewares/auth");
|
||||||
|
|
||||||
|
router.route("/tags/").get(getTags);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const router = express.Router();
|
||||||
|
const {
|
||||||
|
createUser,
|
||||||
|
loginUser,
|
||||||
|
getCurrentUser,
|
||||||
|
updateUser,
|
||||||
|
} = require("../controllers/users");
|
||||||
|
const { protect } = require("../middlewares/auth");
|
||||||
|
|
||||||
|
router.post("/users", createUser);
|
||||||
|
router.post("/users/login", loginUser);
|
||||||
|
|
||||||
|
router.route("/user").get(protect, getCurrentUser).put(protect, updateUser);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
var appEnv = process.env.APP_ENV;
|
||||||
|
if (appEnv == undefined) {
|
||||||
|
appEnv = 'development';
|
||||||
|
}
|
||||||
|
const express = require("express");
|
||||||
|
const sequelize = require("./util/database");
|
||||||
|
const dotenv = require("dotenv");
|
||||||
|
const morgan = require("morgan");
|
||||||
|
const colors = require("colors");
|
||||||
|
const { errorHandler } = require("./middlewares/errorHandler");
|
||||||
|
|
||||||
|
// Import Models
|
||||||
|
const User = require("./models/User");
|
||||||
|
const Article = require("./models/Article");
|
||||||
|
const Tag = require("./models/Tag");
|
||||||
|
const Comment = require("./models/Comment");
|
||||||
|
|
||||||
|
dotenv.config({ path: `.env.${appEnv}` });
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
// Body parser
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === "development") {
|
||||||
|
app.use(morgan("dev"));
|
||||||
|
} else {
|
||||||
|
app.use(morgan('[:date[iso]] ":method :url HTTP/:http-version" :statusColor :res[content-length] ":referrer" ":user-agent" :remote-addr'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORS
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
res.header("Access-Control-Allow-Origin", "*");
|
||||||
|
res.header(
|
||||||
|
"Access-Control-Allow-Headers",
|
||||||
|
"Origin, X-Requested-With, Content-Type, Accept, Authorization"
|
||||||
|
);
|
||||||
|
if (req.method === "OPTIONS") {
|
||||||
|
res.header("Access-Control-Allow-Methods", "PUT, POST, PATCH, DELETE, GET");
|
||||||
|
return res.status(200).json({});
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Route files
|
||||||
|
const users = require("./routes/users");
|
||||||
|
const profiles = require("./routes/profiles");
|
||||||
|
const articles = require("./routes/articles");
|
||||||
|
const comments = require("./routes/comments");
|
||||||
|
const tags = require("./routes/tags");
|
||||||
|
|
||||||
|
// Mount routers
|
||||||
|
app.use(users);
|
||||||
|
app.use(profiles);
|
||||||
|
app.use(articles);
|
||||||
|
app.use(comments);
|
||||||
|
app.use(tags);
|
||||||
|
|
||||||
|
const PORT = process.env.PORT || process.env.SERVER_PORT;
|
||||||
|
|
||||||
|
app.use(errorHandler);
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
User.belongsToMany(User, {
|
||||||
|
as: "followers",
|
||||||
|
through: "Followers",
|
||||||
|
foreignKey: "userId",
|
||||||
|
timestamps: false,
|
||||||
|
});
|
||||||
|
User.belongsToMany(User, {
|
||||||
|
as: "following",
|
||||||
|
through: "Followers",
|
||||||
|
foreignKey: "followerId",
|
||||||
|
timestamps: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
User.hasMany(Article, {
|
||||||
|
foreignKey: "authorId",
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
});
|
||||||
|
Article.belongsTo(User, { as: "author", foreignKey: "authorId" });
|
||||||
|
|
||||||
|
User.hasMany(Comment, {
|
||||||
|
foreignKey: "authorId",
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
});
|
||||||
|
Comment.belongsTo(User, { as: "author", foreignKey: "authorId" });
|
||||||
|
|
||||||
|
Article.hasMany(Comment, {
|
||||||
|
foreignKey: "articleId",
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
});
|
||||||
|
Comment.belongsTo(Article, { foreignKey: "articleId" });
|
||||||
|
|
||||||
|
User.belongsToMany(Article, {
|
||||||
|
as: "favorites",
|
||||||
|
through: "Favorites",
|
||||||
|
timestamps: false,
|
||||||
|
});
|
||||||
|
Article.belongsToMany(User, {
|
||||||
|
through: "Favorites",
|
||||||
|
foreignKey: "articleId",
|
||||||
|
timestamps: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
Article.belongsToMany(Tag, {
|
||||||
|
through: "TagLists",
|
||||||
|
as: "tagLists",
|
||||||
|
foreignKey: "articleId",
|
||||||
|
timestamps: false,
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
});
|
||||||
|
Tag.belongsToMany(Article, {
|
||||||
|
through: "ArticleTags",
|
||||||
|
uniqueKey: false,
|
||||||
|
timestamps: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sync = async () => await sequelize.sync({ force: true });
|
||||||
|
sync().then(() => {
|
||||||
|
User.create({
|
||||||
|
email: "test@test.com",
|
||||||
|
password: "123456",
|
||||||
|
username: "neo",
|
||||||
|
});
|
||||||
|
User.create({
|
||||||
|
email: "test2@test.com",
|
||||||
|
password: "123456",
|
||||||
|
username: "celeb_neo",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const server = app.listen(
|
||||||
|
PORT,
|
||||||
|
console.log(
|
||||||
|
`Server running in ${process.env.NODE_ENV} mode on port ${PORT}`.yellow.bold
|
||||||
|
)
|
||||||
|
);
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
const { Sequelize } = require('sequelize');
|
||||||
|
|
||||||
|
const Op = Sequelize.Op;
|
||||||
|
const sequelize = new Sequelize(
|
||||||
|
process.env.MYSQL_DBNAME,
|
||||||
|
process.env.MYSQL_DBUSER,
|
||||||
|
process.env.MYSQL_DBPASS,
|
||||||
|
{
|
||||||
|
host: process.env.MYSQL_DBHOST,
|
||||||
|
port: process.env.MYSQL_DBPORT,
|
||||||
|
dialect: process.env.MYSQL_DBTYPE,
|
||||||
|
$like: Op.like,
|
||||||
|
$not: Op.not
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const checkConnection = async () => {
|
||||||
|
try {
|
||||||
|
await sequelize.authenticate();
|
||||||
|
console.log(`DB Connected`.cyan.underline.bold);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Unable to connect to the database:".red.bold, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkConnection();
|
||||||
|
|
||||||
|
module.exports = sequelize;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
class ErrorResponse extends Error {
|
||||||
|
constructor(message, statusCode) {
|
||||||
|
super(message);
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ErrorResponse;
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
const appendTagList = (articleTags, article) => {
|
||||||
|
const tagList = articleTags.map((tag) => tag.name);
|
||||||
|
|
||||||
|
if (!article) return tagList;
|
||||||
|
delete article.dataValues.tagLists;
|
||||||
|
article.dataValues.tagList = tagList;
|
||||||
|
};
|
||||||
|
|
||||||
|
const appendFavorites = async (loggedUser, article) => {
|
||||||
|
const favorited = await article.hasUser(loggedUser ? loggedUser : null);
|
||||||
|
article.dataValues.favorited = loggedUser ? favorited : false;
|
||||||
|
|
||||||
|
const favoritesCount = await article.countUsers();
|
||||||
|
article.dataValues.favoritesCount = favoritesCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
const appendFollowers = async (loggedUser, toAppend) => {
|
||||||
|
//
|
||||||
|
if (toAppend?.author) {
|
||||||
|
const author = await toAppend.getAuthor();
|
||||||
|
const following = await author.hasFollower(loggedUser ? loggedUser : null);
|
||||||
|
toAppend.author.dataValues.following = loggedUser ? following : false;
|
||||||
|
|
||||||
|
const followersCount = await author.countFollowers();
|
||||||
|
toAppend.author.dataValues.followersCount = followersCount;
|
||||||
|
//
|
||||||
|
} else {
|
||||||
|
const following = await toAppend.hasFollower(
|
||||||
|
loggedUser ? loggedUser : null
|
||||||
|
);
|
||||||
|
toAppend.dataValues.following = loggedUser ? following : false;
|
||||||
|
|
||||||
|
const followersCount = await toAppend.countFollowers();
|
||||||
|
toAppend.dataValues.followersCount = followersCount;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { appendTagList, appendFavorites, appendFollowers };
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
const jwt = require("jsonwebtoken");
|
||||||
|
|
||||||
|
module.exports.sign = async (user) => {
|
||||||
|
const token = await jwt.sign(
|
||||||
|
{ id: user.id, username: user.username, email: user.email },
|
||||||
|
process.env.JWT_SECRET
|
||||||
|
);
|
||||||
|
return token;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.verify = async (token) => {
|
||||||
|
const decoded = await jwt.verify(token, process.env.JWT_SECRET);
|
||||||
|
return decoded;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user