backend: POST /admin/functions créé une fonction
This commit is contained in:
		
							
								
								
									
										1
									
								
								api/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								api/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -18,6 +18,7 @@ | ||||
| .env.development.local | ||||
| .env.test.local | ||||
| .env.production.local | ||||
| /temp | ||||
|  | ||||
| npm-debug.log* | ||||
| yarn-debug.log* | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
| @@ -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
									
									
									
								
							
							
						
						
									
										29
									
								
								api/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -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", | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
| @@ -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; | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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"> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user