Ajout initial des fichiers de la version mysal

This commit is contained in:
Rampeur
2025-08-08 18:34:15 +02:00
parent 29635cdf83
commit 9a534d5a17
30 changed files with 1381 additions and 0 deletions
+15
View File
@@ -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
View File
@@ -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
View File
@@ -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"
+15
View File
@@ -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"
+265
View File
@@ -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));
}
};
+120
View File
@@ -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));
}
};
+74
View File
@@ -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 });
});
+12
View File
@@ -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 });
};
+93
View File
@@ -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));
}
};
+27
View File
@@ -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;
+11
View File
@@ -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;
+16
View File
@@ -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;
+58
View File
@@ -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;
+48
View File
@@ -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;
};
+35
View File
@@ -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;
};
+57
View File
@@ -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;
};
+34
View File
@@ -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;
};
+74
View File
@@ -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;
};
+50
View File
@@ -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;
+26
View File
@@ -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"
}
}
+30
View File
@@ -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;
+23
View File
@@ -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;
+16
View File
@@ -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;
+10
View File
@@ -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;
+16
View File
@@ -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;
+138
View File
@@ -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
)
);
+28
View File
@@ -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;
+8
View File
@@ -0,0 +1,8 @@
class ErrorResponse extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
}
}
module.exports = ErrorResponse;
+38
View File
@@ -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
View File
@@ -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;
};