backend: POST /admin/functions créé une fonction

This commit is contained in:
Divlo 2020-03-25 22:59:08 +01:00
parent 96af78f967
commit cec6881308
9 changed files with 154 additions and 30 deletions

1
api/.gitignore vendored
View File

@ -18,6 +18,7 @@
.env.development.local
.env.test.local
.env.production.local
/temp
npm-debug.log*
yarn-debug.log*

View File

@ -1,10 +1,10 @@
exports.signupEmail = (url) => `
exports.emailTemplate = (subtitle, buttonText, url, footerText) => `
<center>
<table border="0" cellpadding="20" cellspacing="0" height="100%" width="100%" style="background-color:#eeeeee">
<table border="0" cellpadding="20" cellspacing="0" height="100%" width="100%" style="background-color:#181818">
<tbody>
<tr>
<td align="center" valign="top">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width:600px;border-radius:6px;background-color:none">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width:600px;border-radius:6px">
<tbody>
<tr>
<td align="center" valign="top">
@ -12,7 +12,7 @@ exports.signupEmail = (url) => `
<tbody>
<tr>
<td>
<h1 style="font-family:Arial, Helvetica, sans-serif;font-size:28px;line-height:110%;margin-bottom:30px;margin-top:0;padding:0">FunctionProject</h1>
<h1 style="font-family:Arial, Helvetica, sans-serif;color:#ffd800;font-size:28px;line-height:110%;margin-bottom:30px;margin-top:0;padding:0">FunctionProject</h1>
</td>
</tr>
</tbody>
@ -21,21 +21,17 @@ exports.signupEmail = (url) => `
</tr>
<tr>
<td align="center" valign="top">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width:600px;border-radius:6px;background-color:#ffffff">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width:600px;border-radius:6px;">
<tbody>
<tr>
<td align="left" valign="top" style="line-height:150%;font-family:Helvetica;font-size:14px;color:#333333;padding:20px">
<h2 style="font-size:22px;line-height:28px;margin:0 0 12px 0">
Veuillez confirmer l'inscription
<td align="left" valign="top" style="line-height:150%;font-family:Helvetica;font-size:14px;color:rgb(222, 222, 222);padding:30px;box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25);border: 1px solid black;border-radius: 1rem;">
<h2 style="font-size:22px;line-height:28px;margin:0 0 12px 0;">
${subtitle}
</h2>
<a href="${url}" style="color:#ffffff!important;display:inline-block;font-weight:500;font-size:16px;line-height:42px;font-family:'Helvetica',Arial,sans-serif;width:auto;white-space:nowrap;height:42px;margin:12px 5px 12px 0;padding:0 22px;text-decoration:none;text-align:center;border:0;border-radius:3px;vertical-align:top;background-color:#5d5d5d!important" target="_blank" rel="noopener noreferrer"><span
style="display:inline;font-family:'Helvetica',Arial,sans-serif;text-decoration:none;font-weight:500;font-style:normal;font-size:16px;line-height:42px;border:none;background-color:#5d5d5d!important;color:#ffffff!important">Oui, je m'inscris.</span></a>
<a href="${url}" style="display:inline-block;font-weight:500;font-size:16px;line-height:42px;font-family:'Helvetica',Arial,sans-serif;width:auto;white-space:nowrap;height:42px;margin:12px 5px 12px 0;padding:0 22px;text-decoration:none;text-align:center;border:0;border-radius:3px;vertical-align:top;background-color: #343a40;border-color: #343a40;" target="_blank" rel="noopener noreferrer"><span style="display:inline;font-family:'Helvetica',Arial,sans-serif;text-decoration:none;font-weight:500;font-style:normal;font-size:16px;line-height:42px;border:none;color: #fff;">${buttonText}</span></a>
<br>
<div>
<p style="padding:0 0 10px 0">Si vous avez reçu ce message
par erreur, il suffit de le supprimer. Vous ne serez pas
inscrit si vous ne cliquez pas sur le lien de
confirmation ci-dessus.</p>
<p style="padding:0 0 10px 0">${footerText}</p>
</div>
</td>
</tr>

View File

@ -1,8 +1,33 @@
const errorHandling = require('../assets/utils/errorHandling');
const { serverError } = require('../assets/config/errors');
const Users = require('../models/users');
const path = require('path');
const { validationResult } = require('express-validator');
const errorHandling = require('../assets/utils/errorHandling');
const { serverError } = require('../assets/config/errors');
const Functions = require('../models/functions');
exports.postFunction = (req, res, next) => {
// TODO: Pouvoir créé une fonction
res.status(200).json({ message: "test"});
const { title, slug, description, type, categorieId } = req.body;
const image = req.files.image;
const errors = validationResult(req);
if (!errors.isEmpty()) {
console.log(errors.array())
return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 });
}
if (!image || image.truncated && (
image.mimetype !== 'image/png' ||
image.mimetype !== 'image/jpg' ||
image.mimetype !== 'image/jpeg'
)) {
return errorHandling(next, { message:"La fonction doit avoir une image valide.", statusCode: 400 });
}
const imageName = slug + image.name;
image.mv(path.join(__dirname, '..', 'assets', 'images', 'functions') + '/' + imageName, async (error) => {
if (error) return errorHandling(next, serverError);
try {
await Functions.create({ title, slug, description, type, categorieId, image: `/images/functions/${imageName}` });
return res.status(201).json({ message: "La fonction a été correctement ajouté!"});
} catch (error) {
console.log(error);
errorHandling(next, serverError);
}
});
}

View File

@ -7,7 +7,7 @@ const { serverError, generalError } = require('../assets/config/errors');
const { JWT_SECRET } = require('../assets/config/config');
const transporter = require('../assets/config/transporter');
const { EMAIL_INFO, HOST } = require('../assets/config/config');
const { signupEmail } = require('../assets/config/emails');
const { emailTemplate } = require('../assets/config/emails');
const Users = require('../models/users');
exports.register = async (req, res, next) => {
@ -24,9 +24,9 @@ exports.register = async (req, res, next) => {
from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`,
to: email,
subject: "FunctionProject - Confirmer l'inscription",
html: signupEmail(`${HOST}/users/confirm-email/${tempToken}`)
html: emailTemplate("Veuillez confirmer l'inscription", "Oui, je m'inscris.", `${HOST}/users/confirm-email/${tempToken}`, "Si vous avez reçu ce message par erreur, il suffit de le supprimer. Vous ne serez pas inscrit si vous ne cliquez pas sur le lien de confirmation ci-dessus.")
});
return res.status(201).json({ result: "Vous y êtes presque, veuillez vérifier votre boite d'emails pour confirmer l'inscription." });
return res.status(201).json({ result: "Vous y êtes presque, veuillez vérifier vos emails pour confirmer l'inscription." });
} catch (error) {
console.log(error);
errorHandling(next, serverError);
@ -36,7 +36,7 @@ exports.register = async (req, res, next) => {
exports.login = async (req, res, next) => {
const { email, password } = req.body;
try {
const user = await Users.findOne({ where: { email, confirmed: true } });
const user = await Users.findOne({ where: { email, isConfirmed: true } });
if (!user) {
return errorHandling(next, { message: "Le mot de passe ou l'adresse email n'est pas valide.", statusCode: 400 });
}

29
api/package-lock.json generated
View File

@ -173,6 +173,14 @@
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
},
"busboy": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz",
"integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==",
"requires": {
"dicer": "0.3.0"
}
},
"bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
@ -384,6 +392,14 @@
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
},
"dicer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz",
"integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==",
"requires": {
"streamsearch": "0.1.2"
}
},
"dns-prefetch-control": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.2.0.tgz",
@ -516,6 +532,14 @@
"vary": "~1.1.2"
}
},
"express-fileupload": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.1.6.tgz",
"integrity": "sha512-w24zPWT8DkoIxSVkbxYPo9hkTiLpCQQzNsLRTCnecBhfbYv+IkIC5uLw2MIUAxBZ+7UMmXPjGxlhzUXo4RcbZw==",
"requires": {
"busboy": "^0.3.1"
}
},
"express-validator": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.4.0.tgz",
@ -1596,6 +1620,11 @@
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
},
"streamsearch": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
"integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",

View File

@ -14,6 +14,7 @@
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"express": "^4.17.1",
"express-fileupload": "^1.1.6",
"express-validator": "^6.4.0",
"helmet": "^3.21.3",
"jsonwebtoken": "^8.5.1",

View File

@ -1,11 +1,79 @@
const { Router } = require('express');
const fileUpload = require('express-fileupload');
const { body } = require('express-validator');
const adminController = require('../controllers/admin');
const isAuth = require('../middlewares/isAuth');
const isAdmin = require('../middlewares/isAdmin');
const isAuth = require('../middlewares/isAuth');
const isAdmin = require('../middlewares/isAdmin');
const Functions = require('../models/functions');
const Categories = require('../models/categories');
const AdminRouter = Router();
// Permet de créé une fonction
AdminRouter.post('/functions', isAuth, isAdmin, adminController.postFunction);
AdminRouter.post('/functions',
isAuth,
isAdmin,
fileUpload({
useTempFiles: true,
safeFileNames: true,
preserveExtension: Number,
limits: { fileSize: 5 * 1024 * 1024 }, // 5mb,
parseNested: true
}),
[
body('title')
.not()
.isEmpty()
.withMessage("La fonction doit avoir un titre.")
.isLength({ max: 100 })
.withMessage("Le titre est trop long."),
body('slug')
.not()
.isEmpty()
.withMessage("La fonction doit avoir un slug.")
.isLength({ max: 100 })
.withMessage("Le slug est trop long.")
.custom((async (slug) => {
try {
const FunctionSlug = await Functions.findOne({ where: { slug } });
if (FunctionSlug) {
return Promise.reject("Le slug existe déjà...");
}
} catch (error) {
console.log(error);
}
return true;
})),
body('description')
.not()
.isEmpty()
.withMessage("La fonction doit avoir une description.")
.isLength({ max: 255 })
.withMessage("La description est trop longue."),
body('categorieId')
.not()
.isEmpty()
.withMessage("La fonction doit avoir une catégorie.")
.custom(async (categorieId) => {
try {
const categorieFound = await Categories.findOne({ where: { id: categorieId } });
if (!categorieFound) {
return Promise.reject("La catégorie n'existe pas!");
}
} catch (error) {
console.log(error);
}
return true;
}),
body('type')
.custom((type) => {
if (!(type === 'article' || type === 'form' || type === 'page')) {
return Promise.reject('Le type de la fonction peut être : article, form ou page.');
}
return true;
})
],
adminController.postFunction
);
module.exports = AdminRouter;

View File

@ -22,6 +22,7 @@ UsersRouter.post('/register', [
} catch (error) {
return console.log(error);
}
return true;
}))
.normalizeEmail(),
body('password')
@ -34,16 +35,19 @@ UsersRouter.post('/register', [
.withMessage("Vous devez avoir un nom (ou pseudo).")
.isAlphanumeric()
.withMessage("Votre nom ne peut contenir que des lettres ou/et des nombres.")
.custom((async (name) => {
.isLength({ max: 30 })
.withMessage("Votre nom est trop long")
.custom(async (name) => {
try {
const user = await Users.findOne({ where: { name } });
if (user) {
return Promise.reject("Le nom existe déjà...");
}
} catch (error) {
return console.log(error);
console.log(error);
}
}))
return true;
})
], usersController.register);
// Confirme l'inscription

View File

@ -4,7 +4,7 @@ const FunctionComponentTop = (props) => (
<div className="container-fluid">
<div className="row justify-content-center text-center">
<div className="FunctionComponent__top col-24">
<img src={props.API_URL + props.image} alt={props.title} />
<img style={{ width: '150px' }} src={props.API_URL + props.image} alt={props.title} />
<h1 className="FunctionComponent__title title-important">{props.title}</h1>
<p className="FunctionComponent__description">{props.description}</p>
<div className="FunctionCard__info">