2
									
								
								.github/backup.sql
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/backup.sql
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										20
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								README.md
									
									
									
									
									
								
							| @@ -5,10 +5,9 @@ | ||||
| </p> | ||||
|  | ||||
| <p align="center"> | ||||
|   <a href="https://gitmoji.carloscuesta.me/"><img src="https://camo.githubusercontent.com/2a4924a23bd9ef18afe793f4999b1b9ec474e48f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6769746d6f6a692d253230f09f989c253230f09f988d2d4646444436372e7376673f7374796c653d666c61742d737175617265" alt="Gitmoji"/></a> | ||||
|   <a href="https://standardjs.com"><img alt="JavaScript Style Guide" src="https://img.shields.io/badge/code_style-standard-brightgreen.svg"/></a> | ||||
|   <a href="./LICENSE"><img src="https://img.shields.io/badge/licence-MIT-blue.svg" alt="Licence MIT"/></a> | ||||
|   <img src="https://img.shields.io/github/repo-size/Divlo/FunctionProject" alt="Repo Size"> | ||||
|   <a href="https://github.com/Divlo/FunctionProject/commits/master"><img src="https://img.shields.io/github/commit-activity/m/Divlo/FunctionProject" alt="Commit Activity"></a> | ||||
|   <a href="https://github.com/Divlo/FunctionProject/graphs/contributors"><img src="https://img.shields.io/github/contributors/Divlo/FunctionProject" alt="Contributors"></a> | ||||
|   <img src="https://img.shields.io/github/stars/Divlo/FunctionProject?style=social" alt="Stars"> | ||||
|   <br/> <br/> | ||||
|   <a href="https://function.divlo.fr/"><img src="https://raw.githubusercontent.com/Divlo/FunctionProject/master/.github/FunctionProject.png" alt="FunctionProject" /></a> | ||||
| @@ -24,9 +23,9 @@ Si vous aimez le projet, vous pouvez aider à **le faire connaître** en utilisa | ||||
|  | ||||
| Les dernières versions publiées : [https://github.com/Divlo/FunctionProject/releases](https://github.com/Divlo/FunctionProject/releases) | ||||
|  | ||||
| Le projet est disponible sur [function.divlo.fr](https://function.divlo.fr/) (actuellement en version 2.0). | ||||
| Le projet est disponible sur [function.divlo.fr](https://function.divlo.fr/) (actuellement en version 2.1). | ||||
|  | ||||
| ## 🚀 Open Source  | ||||
| ## 🚀 Open Source | ||||
|  | ||||
| Le partage est essentiel afin de progresser, l'**Open Source** permet l'entraide et le **partage de connaissance** entre développeurs. | ||||
|  | ||||
| @@ -61,21 +60,26 @@ npm install | ||||
|  | ||||
| Vous devrez ensuite configurer l'API en créant un fichier `.env` à la racine du dossier `/api` et prendre exemple du fichier `.env.example` avec votre configuration. | ||||
|  | ||||
| ### Lancer l'environnement de développement :  | ||||
| ### Lancer l'environnement de développement : | ||||
|  | ||||
| Dans deux terminals séparés :  | ||||
| Dans deux terminals séparés : | ||||
|  | ||||
| - Lancer le front-end en allant dans `/website` | ||||
|  | ||||
| ```sh | ||||
| npm run dev # front-end lancé sur http://localhost:3000 | ||||
| ``` | ||||
|  | ||||
| - Lancer l'api en allant dans `/api` | ||||
|  | ||||
| ```sh | ||||
| npm run dev # API lancé sur http://localhost:8080 | ||||
| ``` | ||||
|  | ||||
| Enjoy! 😃 | ||||
|  | ||||
| [](https://github.com/standard/standard) | ||||
|  | ||||
| ## 📄 Licence | ||||
|  | ||||
| Ce projet est sous licence MIT - voir le fichier [LICENSE](./LICENSE) pour plus de détails. | ||||
| Ce projet est sous licence MIT - voir le fichier [LICENSE](./LICENSE) pour plus de détails. | ||||
|   | ||||
							
								
								
									
										1
									
								
								api/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								api/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -18,6 +18,7 @@ | ||||
| .env.development.local | ||||
| .env.test.local | ||||
| .env.production.local | ||||
| .env.production | ||||
| /temp | ||||
| /assets/images/ | ||||
|  | ||||
|   | ||||
							
								
								
									
										129
									
								
								api/app.js
									
									
									
									
									
								
							
							
						
						
									
										129
									
								
								api/app.js
									
									
									
									
									
								
							| @@ -1,83 +1,94 @@ | ||||
| /* Modules */ | ||||
| require('dotenv').config(); | ||||
| const path            = require('path'); | ||||
| const express         = require('express'); | ||||
| const helmet          = require('helmet'); | ||||
| const cors            = require('cors'); | ||||
| const morgan          = require('morgan'); | ||||
| const redirectToHTTPS = require('express-http-to-https').redirectToHTTPS; | ||||
| require('dotenv').config() | ||||
| const path = require('path') | ||||
| const express = require('express') | ||||
| const helmet = require('helmet') | ||||
| const cors = require('cors') | ||||
| const morgan = require('morgan') | ||||
| const { redirectToHTTPS } = require('express-http-to-https') | ||||
|  | ||||
| /* Files Imports & Variables */ | ||||
| const sequelize     = require('./assets/utils/database'); | ||||
| const { PORT }      = require('./assets/config/config'); | ||||
| const errorHandling = require('./assets/utils/errorHandling'); | ||||
| const isAuth        = require('./middlewares/isAuth'); | ||||
| const isAdmin       = require('./middlewares/isAdmin'); | ||||
| const app           = express(); | ||||
| const sequelize = require('./assets/utils/database') | ||||
| const { PORT } = require('./assets/config/config') | ||||
| const errorHandling = require('./assets/utils/errorHandling') | ||||
| const isAuth = require('./middlewares/isAuth') | ||||
| const isAdmin = require('./middlewares/isAdmin') | ||||
| const app = express() | ||||
|  | ||||
| /* Middlewares */ | ||||
| app.use(helmet()); | ||||
| app.use(cors()); | ||||
| app.use(morgan('dev')); | ||||
| app.use(express.json()); | ||||
| app.use(redirectToHTTPS([/localhost:(\d{4})/])); | ||||
| app.use(helmet()) | ||||
| app.use(cors()) | ||||
| app.use(morgan('dev')) | ||||
| app.use(express.json()) | ||||
| app.use(redirectToHTTPS([/localhost:(\d{4})/])) | ||||
|  | ||||
| /* Routes */  | ||||
| app.use('/images', express.static(path.join(__dirname, "assets", "images"))); | ||||
| app.use('/functions', require('./routes/functions')); | ||||
| app.use('/categories', require('./routes/categories')); | ||||
| app.use('/users', require('./routes/users')); | ||||
| app.use('/admin', isAuth, isAdmin, require('./routes/admin')); | ||||
| app.use('/favorites', require('./routes/favorites')); | ||||
| app.use('/comments', require('./routes/comments')); | ||||
| app.use('/quotes', require('./routes/quotes')); | ||||
| app.use('/tasks', require('./routes/tasks')); | ||||
| /* Routes */ | ||||
| app.use('/images', express.static(path.join(__dirname, 'assets', 'images'))) | ||||
| app.use('/functions', require('./routes/functions')) | ||||
| app.use('/categories', require('./routes/categories')) | ||||
| app.use('/users', require('./routes/users')) | ||||
| app.use('/admin', isAuth, isAdmin, require('./routes/admin')) | ||||
| app.use('/favorites', require('./routes/favorites')) | ||||
| app.use('/comments', require('./routes/comments')) | ||||
| app.use('/quotes', require('./routes/quotes')) | ||||
| app.use('/tasks', require('./routes/tasks')) | ||||
| app.use('/links', require('./routes/links_shortener')) | ||||
|  | ||||
| /* Errors Handling */ | ||||
| app.use((_req, _res, next) => errorHandling(next, { statusCode: 404, message: "La route n'existe pas!" })); // 404 | ||||
| app.use((_req, _res, next) => | ||||
|   errorHandling(next, { statusCode: 404, message: "La route n'existe pas!" }) | ||||
| ) // 404 | ||||
| app.use((error, _req, res, _next) => { | ||||
|     console.log(error); | ||||
|     const { statusCode, message } = error; | ||||
|     return res.status(statusCode || 500).json({ message }); | ||||
| }); | ||||
|   console.log(error) | ||||
|   const { statusCode, message } = error | ||||
|   return res.status(statusCode || 500).json({ message }) | ||||
| }) | ||||
|  | ||||
| /* Database Relations */ | ||||
| const Functions  = require('./models/functions'); | ||||
| const Categories = require('./models/categories'); | ||||
| const Users      = require('./models/users'); | ||||
| const Favorites  = require('./models/favorites'); | ||||
| const Comments   = require('./models/comments'); | ||||
| const Quotes     = require('./models/quotes'); | ||||
| const Tasks      = require('./models/tasks'); | ||||
| const Functions = require('./models/functions') | ||||
| const Categories = require('./models/categories') | ||||
| const Users = require('./models/users') | ||||
| const Favorites = require('./models/favorites') | ||||
| const Comments = require('./models/comments') | ||||
| const Quotes = require('./models/quotes') | ||||
| const Tasks = require('./models/tasks') | ||||
| const ShortLinks = require('./models/short_links') | ||||
|  | ||||
| // A function has a category | ||||
| Categories.hasOne(Functions, { constraints: true, onDelete: 'CASCADE'}); | ||||
| Functions.belongsTo(Categories); | ||||
| Categories.hasOne(Functions, { constraints: true, onDelete: 'CASCADE' }) | ||||
| Functions.belongsTo(Categories) | ||||
|  | ||||
| // Users can have favorites functions | ||||
| Users.hasMany(Favorites); | ||||
| Favorites.belongsTo(Users, { constraints: false }); | ||||
| Functions.hasMany(Favorites); | ||||
| Favorites.belongsTo(Functions, { constraints: false }); | ||||
| Users.hasMany(Favorites) | ||||
| Favorites.belongsTo(Users, { constraints: false }) | ||||
| Functions.hasMany(Favorites) | ||||
| Favorites.belongsTo(Functions, { constraints: false }) | ||||
|  | ||||
| // Users can post comments on functions | ||||
| Users.hasMany(Comments); | ||||
| Comments.belongsTo(Users, { constraints: false }); | ||||
| Functions.hasMany(Comments); | ||||
| Comments.belongsTo(Functions, { constraints: false }); | ||||
| Users.hasMany(Comments) | ||||
| Comments.belongsTo(Users, { constraints: false }) | ||||
| Functions.hasMany(Comments) | ||||
| Comments.belongsTo(Functions, { constraints: false }) | ||||
|  | ||||
| // Users can suggest new quotes | ||||
| Users.hasMany(Quotes); | ||||
| Quotes.belongsTo(Users, { constraints: false }); | ||||
| Users.hasMany(Quotes) | ||||
| Quotes.belongsTo(Users, { constraints: false }) | ||||
|  | ||||
| // Users can have tasks | ||||
| Users.hasMany(Tasks); | ||||
| Tasks.belongsTo(Users, { constraints: false }); | ||||
| Users.hasMany(Tasks) | ||||
| Tasks.belongsTo(Users, { constraints: false }) | ||||
|  | ||||
| // Users can have links | ||||
| Users.hasMany(ShortLinks) | ||||
| ShortLinks.belongsTo(Users, { constraints: false }) | ||||
|  | ||||
| /* Server */ | ||||
| // sequelize.sync({ force: true }) | ||||
| sequelize.sync() | ||||
|     .then(() => { | ||||
|         app.listen(PORT, () =>  console.log('\x1b[36m%s\x1b[0m', `Started on port ${PORT}.`)); | ||||
|     }) | ||||
|     .catch((error) => console.log(error)); | ||||
| sequelize | ||||
|   .sync() | ||||
|   .then(() => { | ||||
|     app.listen(PORT, () => | ||||
|       console.log('\x1b[36m%s\x1b[0m', `Started on port ${PORT}.`) | ||||
|     ) | ||||
|   }) | ||||
|   .catch(error => console.log(error)) | ||||
|   | ||||
| @@ -1,26 +1,26 @@ | ||||
| const config = { | ||||
|     PORT: process.env.PORT || 8080, | ||||
|     HOST: process.env.HOST, | ||||
|     FRONT_END_HOST: process.env.FRONT_END_HOST, | ||||
|     WEATHER_API_KEY: process.env.OpenWeatherMap_API_KEY, | ||||
|     SCRAPER_API_KEY: process.env.Scraper_API_KEY, | ||||
|     DATABASE: { | ||||
|         host: process.env.DB_HOST, | ||||
|         name: process.env.DB_NAME, | ||||
|         user: process.env.DB_USER, | ||||
|         password: process.env.DB_PASS | ||||
|     }, | ||||
|     JWT_SECRET: process.env.JWT_SECRET, | ||||
|     EMAIL_INFO: { | ||||
|         host: process.env.EMAIL_HOST, | ||||
|         port: 465, | ||||
|         secure: true, // true for 465, false for other ports | ||||
|         auth: { | ||||
|           user: process.env.EMAIL_USER,  | ||||
|           pass: process.env.EMAIL_PASSWORD | ||||
|         } | ||||
|     }, | ||||
|     TOKEN_LIFE: '1 week' | ||||
| }; | ||||
|   PORT: process.env.PORT || 8080, | ||||
|   HOST: process.env.HOST, | ||||
|   FRONT_END_HOST: process.env.FRONT_END_HOST, | ||||
|   WEATHER_API_KEY: process.env.OpenWeatherMap_API_KEY, | ||||
|   SCRAPER_API_KEY: process.env.Scraper_API_KEY, | ||||
|   DATABASE: { | ||||
|     host: process.env.DB_HOST, | ||||
|     name: process.env.DB_NAME, | ||||
|     user: process.env.DB_USER, | ||||
|     password: process.env.DB_PASS | ||||
|   }, | ||||
|   JWT_SECRET: process.env.JWT_SECRET, | ||||
|   EMAIL_INFO: { | ||||
|     host: process.env.EMAIL_HOST, | ||||
|     port: 465, | ||||
|     secure: true, // true for 465, false for other ports | ||||
|     auth: { | ||||
|       user: process.env.EMAIL_USER, | ||||
|       pass: process.env.EMAIL_PASSWORD | ||||
|     } | ||||
|   }, | ||||
|   TOKEN_LIFE: '1 week' | ||||
| } | ||||
|  | ||||
| module.exports = config; | ||||
| module.exports = config | ||||
|   | ||||
| @@ -26,16 +26,24 @@ exports.emailQuoteTemplate = (isValid, quote, frontendLink) => ` | ||||
|                                             <tr> | ||||
|                                                 <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;"> | ||||
|                                                         La citation que vous avez proposée a été ${(isValid) ? "validée" : "supprimée"}. | ||||
|                                                         La citation que vous avez proposée a été ${ | ||||
|                                                           isValid | ||||
|                                                             ? 'validée' | ||||
|                                                             : 'supprimée' | ||||
|                                                         }. | ||||
|                                                     </h2> | ||||
|                                                     <p style="margin: 0 0 12px 0;"> | ||||
|                                                         <a style="color: #ffd800;" href="${frontendLink}/functions/randomQuote">Lien vers la fonction randomQuote de FunctionProject.</a> | ||||
|                                                     </p> | ||||
|                                                     ${(!isValid) ? ` | ||||
|                                                     ${ | ||||
|                                                       !isValid | ||||
|                                                         ? ` | ||||
|                                                         <p style="margin: 0 0 12px 0;"> | ||||
|                                                             Si votre citation a été supprimée et vous pensez que c'est une erreur, contactez-moi à cette adresse email : <a style="color: #ffd800;" href="mailto:contact@divlo.fr">contact@divlo.fr</a>. | ||||
|                                                         </p> | ||||
|                                                     ` : ""} | ||||
|                                                     ` | ||||
|                                                         : '' | ||||
|                                                     } | ||||
|                                                     <div> | ||||
|                                                         <p style="padding:0 0 10px 0"> | ||||
|                                                             La citation en question : <br/> | ||||
| @@ -57,7 +65,7 @@ exports.emailQuoteTemplate = (isValid, quote, frontendLink) => ` | ||||
|         </tbody> | ||||
|     </table> | ||||
| </center> | ||||
| `; | ||||
| ` | ||||
|  | ||||
| exports.emailUserTemplate = (subtitle, buttonText, url, footerText) => ` | ||||
| <center> | ||||
| @@ -107,4 +115,4 @@ exports.emailUserTemplate = (subtitle, buttonText, url, footerText) => ` | ||||
|         </tbody> | ||||
|     </table> | ||||
| </center> | ||||
| `; | ||||
| ` | ||||
|   | ||||
| @@ -1,18 +1,18 @@ | ||||
| const errors = { | ||||
|     generalError: { | ||||
|         message: "Vous n'avez pas rentré de valeur valide.", | ||||
|         statusCode: 400 | ||||
|     }, | ||||
|   generalError: { | ||||
|     message: "Vous n'avez pas rentré de valeur valide.", | ||||
|     statusCode: 400 | ||||
|   }, | ||||
|  | ||||
|     serverError: { | ||||
|         message: "Le serveur n'a pas pu traiter votre requête.", | ||||
|         statusCode: 500 | ||||
|     }, | ||||
|   serverError: { | ||||
|     message: "Le serveur n'a pas pu traiter votre requête.", | ||||
|     statusCode: 500 | ||||
|   }, | ||||
|  | ||||
|     requiredFields: { | ||||
|         message: "Vous devez remplir tous les champs...", | ||||
|         statusCode: 400 | ||||
|     } | ||||
| }; | ||||
|   requiredFields: { | ||||
|     message: 'Vous devez remplir tous les champs...', | ||||
|     statusCode: 400 | ||||
|   } | ||||
| } | ||||
|  | ||||
| module.exports = errors; | ||||
| module.exports = errors | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| const nodemailer     = require('nodemailer'); | ||||
| const { EMAIL_INFO } = require('./config'); | ||||
| const nodemailer = require('nodemailer') | ||||
| const { EMAIL_INFO } = require('./config') | ||||
|  | ||||
| const transporter = nodemailer.createTransport(EMAIL_INFO); | ||||
| const transporter = nodemailer.createTransport(EMAIL_INFO) | ||||
|  | ||||
| module.exports = transporter; | ||||
| module.exports = transporter | ||||
|   | ||||
| @@ -1,44 +1,42 @@ | ||||
| const { randomNumberOutput }          = require('./main/randomNumber'); | ||||
| const convertRomanArabicNumbersOutput = require('./main/convertRomanArabicNumbers'); | ||||
| const convertDistanceOutput           = require('./main/convertDistance'); | ||||
| const convertTemperatureOutput        = require('./main/convertTemperature'); | ||||
| const armstrongNumberOutput           = require('./main/armstrongNumber'); | ||||
| const weatherRequestOutput            = require('./main/weatherRequest'); | ||||
| const convertCurrencyOutput           = require('./main/convertCurrency'); | ||||
| const calculateAgeOutput              = require('./main/calculateAge'); | ||||
| const heapAlgorithmOutput             = require('./main/heapAlgorithm'); | ||||
| const convertEncodingOutput           = require('./main/convertEncoding'); | ||||
| const randomQuote                     = require('./main/randomQuote'); | ||||
| const linkShortener                   = require('./main/linkShortener'); | ||||
| const rightPriceOutput                = require('./main/rightPrice'); | ||||
| const isPalindromeOutput              = require('./main/isPalindrome'); | ||||
| const findLongestWordOutput           = require('./main/findLongestWord'); | ||||
| const fibonacciOutput                 = require('./main/fibonacci'); | ||||
| const sortArrayOutput                 = require('./main/sortArray'); | ||||
| const { randomNumberOutput } = require('./main/randomNumber') | ||||
| const convertRomanArabicNumbersOutput = require('./main/convertRomanArabicNumbers') | ||||
| const convertDistanceOutput = require('./main/convertDistance') | ||||
| const convertTemperatureOutput = require('./main/convertTemperature') | ||||
| const armstrongNumberOutput = require('./main/armstrongNumber') | ||||
| const weatherRequestOutput = require('./main/weatherRequest') | ||||
| const convertCurrencyOutput = require('./main/convertCurrency') | ||||
| const calculateAgeOutput = require('./main/calculateAge') | ||||
| const heapAlgorithmOutput = require('./main/heapAlgorithm') | ||||
| const convertEncodingOutput = require('./main/convertEncoding') | ||||
| const randomQuote = require('./main/randomQuote') | ||||
| const rightPriceOutput = require('./main/rightPrice') | ||||
| const isPalindromeOutput = require('./main/isPalindrome') | ||||
| const findLongestWordOutput = require('./main/findLongestWord') | ||||
| const fibonacciOutput = require('./main/fibonacci') | ||||
| const sortArrayOutput = require('./main/sortArray') | ||||
|  | ||||
| const functionObject = { | ||||
|     randomNumber             : randomNumberOutput, | ||||
|     convertRomanArabicNumbers: convertRomanArabicNumbersOutput, | ||||
|     convertDistance          : convertDistanceOutput, | ||||
|     convertTemperature       : convertTemperatureOutput, | ||||
|     armstrongNumber          : armstrongNumberOutput, | ||||
|     weatherRequest           : weatherRequestOutput, | ||||
|     convertCurrency          : convertCurrencyOutput, | ||||
|     calculateAge             : calculateAgeOutput, | ||||
|     heapAlgorithm            : heapAlgorithmOutput, | ||||
|     convertEncoding          : convertEncodingOutput, | ||||
|     randomQuote              : randomQuote, | ||||
|     linkShortener            : linkShortener, | ||||
|     rightPrice               : rightPriceOutput, | ||||
|     isPalindrome             : isPalindromeOutput, | ||||
|     findLongestWord          : findLongestWordOutput, | ||||
|     fibonacci                : fibonacciOutput, | ||||
|     sortArray                : sortArrayOutput, | ||||
| }; | ||||
|  | ||||
| // Choisi la fonction à exécuter | ||||
| function functionToExecute(option) { | ||||
|     return functionObject[option]; | ||||
|   randomNumber: randomNumberOutput, | ||||
|   convertRomanArabicNumbers: convertRomanArabicNumbersOutput, | ||||
|   convertDistance: convertDistanceOutput, | ||||
|   convertTemperature: convertTemperatureOutput, | ||||
|   armstrongNumber: armstrongNumberOutput, | ||||
|   weatherRequest: weatherRequestOutput, | ||||
|   convertCurrency: convertCurrencyOutput, | ||||
|   calculateAge: calculateAgeOutput, | ||||
|   heapAlgorithm: heapAlgorithmOutput, | ||||
|   convertEncoding: convertEncodingOutput, | ||||
|   randomQuote: randomQuote, | ||||
|   rightPrice: rightPriceOutput, | ||||
|   isPalindrome: isPalindromeOutput, | ||||
|   findLongestWord: findLongestWordOutput, | ||||
|   fibonacci: fibonacciOutput, | ||||
|   sortArray: sortArrayOutput | ||||
| } | ||||
|  | ||||
| module.exports = functionToExecute; | ||||
| // Choisi la fonction à exécuter | ||||
| function functionToExecute (option) { | ||||
|   return functionObject[option] | ||||
| } | ||||
|  | ||||
| module.exports = functionToExecute | ||||
|   | ||||
| @@ -1,46 +1,59 @@ | ||||
| const errorHandling      = require('../../utils/errorHandling'); | ||||
| const { requiredFields } = require('../../config/errors'); | ||||
| const formatNumberResult = require('../secondary/formatNumberResult'); | ||||
| const errorHandling = require('../../utils/errorHandling') | ||||
| const { requiredFields } = require('../../config/errors') | ||||
| const formatNumberResult = require('../secondary/formatNumberResult') | ||||
|  | ||||
| /**  | ||||
| /** | ||||
|  * @description Vérifie si un nombre fait partie des nombres d'Armstrong. | ||||
|  * @param {Number} number - Le nombre à tester | ||||
|  * @returns {Object} Un objet contenant l'explication en html et le booléen si oui ou non c'est un nombre d'armstrong | ||||
|  * @examples armstrongNumber(153) → 153 est un nombre d'Armstrong, car  1<sup>3</sup> + 5<sup>3</sup> + 3<sup>3</sup> = 153. | ||||
|  */  | ||||
| function armstrongNumber(number) { | ||||
|     let numberString = number.toString(); | ||||
|     let numberStringLength = numberString.length; | ||||
|  */ | ||||
| function armstrongNumber (number) { | ||||
|   const numberString = number.toString() | ||||
|   const numberStringLength = numberString.length | ||||
|  | ||||
|     let result = 0; | ||||
|     let resultString = ""; | ||||
|     for (let index = 0; index < numberStringLength; index++) { | ||||
|         result = result + parseInt(numberString[index]) ** numberStringLength; | ||||
|         resultString = resultString + " + " + numberString[index] + "<sup>" + numberStringLength + "</sup>"; | ||||
|     } | ||||
|   let result = 0 | ||||
|   let resultString = '' | ||||
|   for (let index = 0; index < numberStringLength; index++) { | ||||
|     result = result + parseInt(numberString[index]) ** numberStringLength | ||||
|     resultString = | ||||
|       resultString + | ||||
|       ' + ' + | ||||
|       numberString[index] + | ||||
|       '<sup>' + | ||||
|       numberStringLength + | ||||
|       '</sup>' | ||||
|   } | ||||
|  | ||||
|     const formattedNumber = formatNumberResult(number); | ||||
|     const isArmstrongNumber = (result === number);  | ||||
|     return { | ||||
|         isArmstrongNumber, | ||||
|         resultHTML: `<p>${formattedNumber} ${isArmstrongNumber ? "est" : "n'est pas"} un nombre d'Armstrong, car ${resultString.slice(2)} = ${formatNumberResult(result)}.</p>` | ||||
|     }     | ||||
|   const formattedNumber = formatNumberResult(number) | ||||
|   const isArmstrongNumber = result === number | ||||
|   return { | ||||
|     isArmstrongNumber, | ||||
|     resultHTML: `<p>${formattedNumber} ${ | ||||
|       isArmstrongNumber ? 'est' : "n'est pas" | ||||
|     } un nombre d'Armstrong, car ${resultString.slice( | ||||
|       2 | ||||
|     )} = ${formatNumberResult(result)}.</p>` | ||||
|   } | ||||
| } | ||||
|  | ||||
| /* OUTPUTS */ | ||||
| module.exports = armstrongNumberOutput = ({ res, next }, argsObject) => { | ||||
|     let { number } = argsObject; | ||||
|      | ||||
|     // S'il n'y a pas les champs obligatoire | ||||
|     if (!(number)) { | ||||
|         return errorHandling(next, requiredFields); | ||||
|     } | ||||
| module.exports = ({ res, next }, argsObject) => { | ||||
|   let { number } = argsObject | ||||
|  | ||||
|     // Si ce n'est pas un nombre | ||||
|     number = parseInt(number); | ||||
|     if (isNaN(number) || number <= 0) { | ||||
|         return errorHandling(next, { message: "Veuillez rentré un nombre valide.", statusCode: 400 }); | ||||
|     } | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!number) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|     return res.status(200).json(armstrongNumber(number)); | ||||
| } | ||||
|   // Si ce n'est pas un nombre | ||||
|   number = parseInt(number) | ||||
|   if (isNaN(number) || number <= 0) { | ||||
|     return errorHandling(next, { | ||||
|       message: 'Veuillez rentré un nombre valide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   return res.status(200).json(armstrongNumber(number)) | ||||
| } | ||||
|   | ||||
| @@ -1,50 +1,60 @@ | ||||
| const errorHandling      = require('../../utils/errorHandling'); | ||||
| const moment             = require('moment'); | ||||
| const { requiredFields } = require('../../config/errors'); | ||||
| const errorHandling = require('../../utils/errorHandling') | ||||
| const moment = require('moment') | ||||
| const { requiredFields } = require('../../config/errors') | ||||
|  | ||||
| function calculateAge(currentDate, { birthDateDay, birthDateMonth, birthDateYear }) { | ||||
|     const day   = currentDate.getDate(); | ||||
|     const month = currentDate.getMonth(); | ||||
|     const currentDateMoment = moment([currentDate.getFullYear(), month, day]); | ||||
|     const birthDateMoment   = moment([birthDateYear, birthDateMonth, birthDateDay]); | ||||
| function calculateAge ( | ||||
|   currentDate, | ||||
|   { birthDateDay, birthDateMonth, birthDateYear } | ||||
| ) { | ||||
|   const day = currentDate.getDate() | ||||
|   const month = currentDate.getMonth() | ||||
|   const currentDateMoment = moment([currentDate.getFullYear(), month, day]) | ||||
|   const birthDateMoment = moment([birthDateYear, birthDateMonth, birthDateDay]) | ||||
|  | ||||
|     // Calcule l'âge - Moment.js | ||||
|     const ageYears = currentDateMoment.diff(birthDateMoment, 'year'); | ||||
|     birthDateMoment.add(ageYears, 'years'); | ||||
|     const ageMonths = currentDateMoment.diff(birthDateMoment, 'months'); | ||||
|     birthDateMoment.add(ageMonths, 'months'); | ||||
|     const ageDays = currentDateMoment.diff(birthDateMoment, 'days'); | ||||
|   // Calcule l'âge - Moment.js | ||||
|   const ageYears = currentDateMoment.diff(birthDateMoment, 'year') | ||||
|   birthDateMoment.add(ageYears, 'years') | ||||
|   const ageMonths = currentDateMoment.diff(birthDateMoment, 'months') | ||||
|   birthDateMoment.add(ageMonths, 'months') | ||||
|   const ageDays = currentDateMoment.diff(birthDateMoment, 'days') | ||||
|  | ||||
|     const isBirthday = (birthDateDay === day && birthDateMonth === month);  | ||||
|     return { ageYears, ageMonths, ageDays, isBirthday }; | ||||
|   const isBirthday = birthDateDay === day && birthDateMonth === month | ||||
|   return { ageYears, ageMonths, ageDays, isBirthday } | ||||
| } | ||||
|  | ||||
| /* OUTPUTS */ | ||||
| module.exports = calculateAgeOutput = ({ res, next }, argsObject) => { | ||||
|     let { birthDate } = argsObject; | ||||
|      | ||||
|     // S'il n'y a pas les champs obligatoire | ||||
|     if (!(birthDate)) { | ||||
|         return errorHandling(next, requiredFields); | ||||
|     } | ||||
| module.exports = ({ res, next }, argsObject) => { | ||||
|   const { birthDate } = argsObject | ||||
|  | ||||
|     const birthDateDay   = parseInt(birthDate.substring(0, 2)); | ||||
|     const birthDateMonth = parseInt((birthDate.substring(3, 5)) - 1); | ||||
|     const birthDateYear  = parseInt(birthDate.substring(6, 10)); | ||||
|      | ||||
|     // Si ce n'est pas une date valide | ||||
|     const currentDate     = new Date(); | ||||
|     const birthDateObject = new Date(birthDateYear, birthDateMonth, birthDateDay); | ||||
|     const result          = calculateAge(currentDate, { birthDateYear, birthDateMonth, birthDateDay }); | ||||
|     if ((currentDate < birthDateObject) || isNaN(result.ageYears)) { | ||||
|         return errorHandling(next, { message: "Veuillez rentré une date valide...", statusCode: 400 }); | ||||
|     } | ||||
|      | ||||
|     let resultHTML; | ||||
|     if (result.isBirthday) { | ||||
|         resultHTML = `<p>Vous avez ${result.ageYears} ans. Joyeux Anniversaire! 🥳</p>`; | ||||
|     } else { | ||||
|         resultHTML = `<p>Vous avez ${result.ageYears} ans, ${result.ageMonths} mois et ${result.ageDays} jour(s).</p>`; | ||||
|     } | ||||
|     return res.status(200).json({ ...result, resultHTML }); | ||||
| } | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!birthDate) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|   const birthDateDay = parseInt(birthDate.substring(0, 2)) | ||||
|   const birthDateMonth = parseInt(birthDate.substring(3, 5) - 1) | ||||
|   const birthDateYear = parseInt(birthDate.substring(6, 10)) | ||||
|  | ||||
|   // Si ce n'est pas une date valide | ||||
|   const currentDate = new Date() | ||||
|   const birthDateObject = new Date(birthDateYear, birthDateMonth, birthDateDay) | ||||
|   const result = calculateAge(currentDate, { | ||||
|     birthDateYear, | ||||
|     birthDateMonth, | ||||
|     birthDateDay | ||||
|   }) | ||||
|   if (currentDate < birthDateObject || isNaN(result.ageYears)) { | ||||
|     return errorHandling(next, { | ||||
|       message: 'Veuillez rentré une date valide...', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   let resultHTML | ||||
|   if (result.isBirthday) { | ||||
|     resultHTML = `<p>Vous avez ${result.ageYears} ans. Joyeux Anniversaire! 🥳</p>` | ||||
|   } else { | ||||
|     resultHTML = `<p>Vous avez ${result.ageYears} ans, ${result.ageMonths} mois et ${result.ageDays} jour(s).</p>` | ||||
|   } | ||||
|   return res.status(200).json({ ...result, resultHTML }) | ||||
| } | ||||
|   | ||||
| @@ -1,37 +1,53 @@ | ||||
| const axios              = require('axios'); | ||||
| const errorHandling      = require('../../utils/errorHandling'); | ||||
| const { requiredFields } = require('../../config/errors'); | ||||
| const formatNumberResult = require('../secondary/formatNumberResult'); | ||||
| const axios = require('axios') | ||||
| const errorHandling = require('../../utils/errorHandling') | ||||
| const { requiredFields } = require('../../config/errors') | ||||
| const formatNumberResult = require('../secondary/formatNumberResult') | ||||
|  | ||||
| /* OUTPUTS */ | ||||
| module.exports = convertCurrencyOutput = ({ res, next }, argsObject) => { | ||||
|     let { number, baseCurrency, finalCurrency } = argsObject; | ||||
|      | ||||
|     // S'il n'y a pas les champs obligatoire | ||||
|     if (!(number && baseCurrency && finalCurrency)) { | ||||
|         return errorHandling(next, requiredFields); | ||||
|     } | ||||
| module.exports = ({ res, next }, argsObject) => { | ||||
|   let { number, baseCurrency, finalCurrency } = argsObject | ||||
|  | ||||
|     // Si ce n'est pas un nombre | ||||
|     number = parseFloat(number); | ||||
|     if (isNaN(number)) { | ||||
|         return  errorHandling(next, { message: "Veuillez rentré un nombre valide.", statusCode: 400 }); | ||||
|     } | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!(number && baseCurrency && finalCurrency)) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|     axios.get(`https://api.exchangeratesapi.io/latest?base=${baseCurrency}`) | ||||
|         .then((response) => { | ||||
|             const rate = response.data.rates[finalCurrency]; | ||||
|             if (!rate) { | ||||
|                 return errorHandling(next, { message: "La devise n'existe pas.", statusCode: 404 }); | ||||
|             } | ||||
|             const result     = rate * number; | ||||
|             const dateObject = new Date(response.data.date); | ||||
|             const year       = dateObject.getFullYear(); | ||||
|             const day        = ('0'+(dateObject.getDate())).slice(-2); | ||||
|             const month      = ('0'+(dateObject.getMonth()+1)).slice(-2); | ||||
|             const date       = `${day}/${month}/${year}`; | ||||
|             const resultHTML = `<p>${formatNumberResult(number)} ${response.data.base} = ${formatNumberResult(result.toFixed(2))} ${finalCurrency}</p><p>Dernier rafraîchissement du taux d'échange : ${date}</p>`; | ||||
|             return res.status(200).json({ date, result, resultHTML }); | ||||
|   // Si ce n'est pas un nombre | ||||
|   number = parseFloat(number) | ||||
|   if (isNaN(number)) { | ||||
|     return errorHandling(next, { | ||||
|       message: 'Veuillez rentré un nombre valide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   axios | ||||
|     .get(`https://api.exchangeratesapi.io/latest?base=${baseCurrency}`) | ||||
|     .then(response => { | ||||
|       const rate = response.data.rates[finalCurrency] | ||||
|       if (!rate) { | ||||
|         return errorHandling(next, { | ||||
|           message: "La devise n'existe pas.", | ||||
|           statusCode: 404 | ||||
|         }) | ||||
|         .catch(() => errorHandling(next, { message: "La devise n'existe pas.", statusCode: 404 })); | ||||
| } | ||||
|       } | ||||
|       const result = rate * number | ||||
|       const dateObject = new Date(response.data.date) | ||||
|       const year = dateObject.getFullYear() | ||||
|       const day = ('0' + dateObject.getDate()).slice(-2) | ||||
|       const month = ('0' + (dateObject.getMonth() + 1)).slice(-2) | ||||
|       const date = `${day}/${month}/${year}` | ||||
|       const resultHTML = `<p>${formatNumberResult(number)} ${ | ||||
|         response.data.base | ||||
|       } = ${formatNumberResult( | ||||
|         result.toFixed(2) | ||||
|       )} ${finalCurrency}</p><p>Dernier rafraîchissement du taux d'échange : ${date}</p>` | ||||
|       return res.status(200).json({ date, result, resultHTML }) | ||||
|     }) | ||||
|     .catch(() => | ||||
|       errorHandling(next, { | ||||
|         message: "La devise n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     ) | ||||
| } | ||||
|   | ||||
| @@ -1,10 +1,36 @@ | ||||
| const errorHandling                    = require('../../utils/errorHandling'); | ||||
| const { requiredFields, generalError } = require('../../config/errors'); | ||||
| const formatNumberResult               = require('../secondary/formatNumberResult'); | ||||
| const errorHandling = require('../../utils/errorHandling') | ||||
| const { requiredFields, generalError } = require('../../config/errors') | ||||
| const formatNumberResult = require('../secondary/formatNumberResult') | ||||
|  | ||||
| const correspondancesDistance = ["pm", null, null, "nm", null, null, "µm", null, null, "mm", "cm", "dm", "m", "dam", "hm", "km", null, null, "Mm", null, null, "Gm", null, null, "Tm"]; | ||||
| const correspondancesDistance = [ | ||||
|   'pm', | ||||
|   null, | ||||
|   null, | ||||
|   'nm', | ||||
|   null, | ||||
|   null, | ||||
|   'µm', | ||||
|   null, | ||||
|   null, | ||||
|   'mm', | ||||
|   'cm', | ||||
|   'dm', | ||||
|   'm', | ||||
|   'dam', | ||||
|   'hm', | ||||
|   'km', | ||||
|   null, | ||||
|   null, | ||||
|   'Mm', | ||||
|   null, | ||||
|   null, | ||||
|   'Gm', | ||||
|   null, | ||||
|   null, | ||||
|   'Tm' | ||||
| ] | ||||
|  | ||||
| /**  | ||||
| /** | ||||
|  * @description Convertis la longueur (distance) avec les unités allant de picomètre au Téramètre. | ||||
|  * @requires {@link correspondancesDistance} | ||||
|  * @param {Number} firstValue - Le nombre que vous voulez convertir | ||||
| @@ -13,39 +39,46 @@ const correspondancesDistance = ["pm", null, null, "nm", null, null, "µm", null | ||||
|  * @returns {Object|Boolean} → false si arguments non valides et sinon un objet contenant la string et le nombre résultat | ||||
|  * @examples convertDistance(500, 'cm', 'm') → { resultNumber: 5, resultString: "5 m" } | ||||
|  */ | ||||
| function convertDistance(firstValue, unitFirstValue, unitFinalValue) { | ||||
|     const index1 = correspondancesDistance.indexOf(unitFirstValue);  | ||||
|     const index2 = correspondancesDistance.indexOf(unitFinalValue); | ||||
|     if (index1 !== -1 && index2 !== -1) { | ||||
|         const difference = index1 - index2;  | ||||
|         const result = firstValue * Math.pow(10, difference); | ||||
|         return { | ||||
|             result, | ||||
|             resultHTML: `<p>${formatNumberResult(firstValue)} ${unitFirstValue} = ${formatNumberResult(result)} ${unitFinalValue}</p>` | ||||
|         }; | ||||
| function convertDistance (firstValue, unitFirstValue, unitFinalValue) { | ||||
|   const index1 = correspondancesDistance.indexOf(unitFirstValue) | ||||
|   const index2 = correspondancesDistance.indexOf(unitFinalValue) | ||||
|   if (index1 !== -1 && index2 !== -1) { | ||||
|     const difference = index1 - index2 | ||||
|     const result = firstValue * Math.pow(10, difference) | ||||
|     return { | ||||
|       result, | ||||
|       resultHTML: `<p>${formatNumberResult( | ||||
|         firstValue | ||||
|       )} ${unitFirstValue} = ${formatNumberResult( | ||||
|         result | ||||
|       )} ${unitFinalValue}</p>` | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
|   return false | ||||
| } | ||||
|  | ||||
| /* OUTPUTS */ | ||||
| module.exports = convertDistanceOutput = ({ res, next }, argsObject) => { | ||||
|     let { number, numberUnit, finalUnit } = argsObject; | ||||
|      | ||||
|     // S'il n'y a pas les champs obligatoire | ||||
|     if (!(number && numberUnit && finalUnit)) { | ||||
|         return errorHandling(next, requiredFields); | ||||
|     } | ||||
| module.exports = ({ res, next }, argsObject) => { | ||||
|   let { number, numberUnit, finalUnit } = argsObject | ||||
|  | ||||
|     // Si ce n'est pas un nombre | ||||
|     number = parseFloat(number); | ||||
|     if (isNaN(number)) { | ||||
|         return errorHandling(next, { message: "Veuillez rentré un nombre valide.", statusCode: 400 }); | ||||
|     } | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!(number && numberUnit && finalUnit)) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|     const result = convertDistance(number, numberUnit, finalUnit); | ||||
|     if (!result) { | ||||
|         return errorHandling(next, generalError); | ||||
|     } | ||||
|   // Si ce n'est pas un nombre | ||||
|   number = parseFloat(number) | ||||
|   if (isNaN(number)) { | ||||
|     return errorHandling(next, { | ||||
|       message: 'Veuillez rentré un nombre valide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|     return res.status(200).json(result); | ||||
| } | ||||
|   const result = convertDistance(number, numberUnit, finalUnit) | ||||
|   if (!result) { | ||||
|     return errorHandling(next, generalError) | ||||
|   } | ||||
|  | ||||
|   return res.status(200).json(result) | ||||
| } | ||||
|   | ||||
| @@ -1,242 +1,268 @@ | ||||
| const errorHandling                    = require('../../utils/errorHandling'); | ||||
| const { requiredFields, generalError } = require('../../config/errors'); | ||||
| const errorHandling = require('../../utils/errorHandling') | ||||
| const { requiredFields, generalError } = require('../../config/errors') | ||||
|  | ||||
| /**  | ||||
| /** | ||||
|  * @description Convertis un nombre décimal en binaire. | ||||
|  * @param {String} value - Le nombre à convertir en string | ||||
|  * @returns {String} - Le nombre en binaire | ||||
|  * @examples decimalToBinary('2') → '10' | ||||
|  */ | ||||
| function decimalToBinary(value) { | ||||
|     value = Number(value); | ||||
|     if (isNaN(value)) { | ||||
|         return false; | ||||
|     } else { | ||||
|         return value.toString(2); | ||||
|     } | ||||
| }  | ||||
| function decimalToBinary (value) { | ||||
|   value = Number(value) | ||||
|   if (isNaN(value)) { | ||||
|     return false | ||||
|   } else { | ||||
|     return value.toString(2) | ||||
|   } | ||||
| } | ||||
|  | ||||
| /**  | ||||
| /** | ||||
|  * @description Convertis un nombre binaire en décimal. | ||||
|  * @param {String} value - Le nombre à convertir | ||||
|  * @returns {(Number|String)} - Le nombre en décimal soit en nombre ou soit en string si supérieur à 1000 car pour 1000 par exemple formatNumberResult renvoie '1 000' | ||||
|  * @examples binaryToDecimal('10') → 2 | ||||
|  */ | ||||
| function binaryToDecimal(value) { | ||||
|     const result = parseInt(Number(value), 2); | ||||
|     if (isNaN(result)) { | ||||
|         return false; | ||||
|     } else { | ||||
|         return result; | ||||
|     } | ||||
| }  | ||||
| function binaryToDecimal (value) { | ||||
|   const result = parseInt(Number(value), 2) | ||||
|   if (isNaN(result)) { | ||||
|     return false | ||||
|   } else { | ||||
|     return result | ||||
|   } | ||||
| } | ||||
|  | ||||
| /**  | ||||
| /** | ||||
|  * @description Convertis un nombre décimal en hexadécimal. | ||||
|  * @param {String} value - Le nombre à convertir  | ||||
|  * @param {String} value - Le nombre à convertir | ||||
|  * @returns {String} - Le nombre en hexadécimal | ||||
|  * @examples decimalToHexadecimal('15') → 'F' | ||||
|  */ | ||||
| function decimalToHexadecimal(value) { | ||||
|     value = Number(value); | ||||
|     if (isNaN(value)) { | ||||
|         return false; | ||||
|     } else { | ||||
|         return value.toString(16).toUpperCase(); | ||||
|     } | ||||
| }  | ||||
| function decimalToHexadecimal (value) { | ||||
|   value = Number(value) | ||||
|   if (isNaN(value)) { | ||||
|     return false | ||||
|   } else { | ||||
|     return value.toString(16).toUpperCase() | ||||
|   } | ||||
| } | ||||
|  | ||||
| /**  | ||||
| /** | ||||
|  * @description Convertis un nombre hexadécimal en décimal. | ||||
|  * @param {String} value - Le nombre à convertir | ||||
|  * @returns {(Number|String)} - Le nombre en décimal soit en nombre ou soit en string si supérieur à 1000 car pour 1000 par exemple formatNumberResult renvoie '1 000' | ||||
|  * @examples hexadecimalToDecimal('F') → 15 | ||||
|  */ | ||||
| function hexadecimalToDecimal(value) { | ||||
|     const result = parseInt(value, 16); | ||||
|     if (isNaN(result)) { | ||||
|         return false; | ||||
|     } else { | ||||
|         return result; | ||||
|     } | ||||
| }  | ||||
| function hexadecimalToDecimal (value) { | ||||
|   const result = parseInt(value, 16) | ||||
|   if (isNaN(result)) { | ||||
|     return false | ||||
|   } else { | ||||
|     return result | ||||
|   } | ||||
| } | ||||
|  | ||||
| /**  | ||||
|  * @description Convertis un nombre binaire en hexadécimal.  | ||||
| /** | ||||
|  * @description Convertis un nombre binaire en hexadécimal. | ||||
|  * @param {String} value - Le nombre à convertir | ||||
|  * @returns {String} - Le nombre en hexadécimal | ||||
|  * @examples binaryToHexadecimal('1111') → 'F' | ||||
|  */  | ||||
| function binaryToHexadecimal(value) { | ||||
|     value = Number(value); | ||||
|     value = parseInt(value, 2); | ||||
|     if (isNaN(value)) { | ||||
|         return false; | ||||
|     } else { | ||||
|         return parseInt(value).toString(16).toUpperCase(); | ||||
|     }    | ||||
|  */ | ||||
| function binaryToHexadecimal (value) { | ||||
|   value = Number(value) | ||||
|   value = parseInt(value, 2) | ||||
|   if (isNaN(value)) { | ||||
|     return false | ||||
|   } else { | ||||
|     return parseInt(value) | ||||
|       .toString(16) | ||||
|       .toUpperCase() | ||||
|   } | ||||
| } | ||||
|  | ||||
| /**  | ||||
|  * @description Convertis un nombre hexadécimal en binaire.  | ||||
| /** | ||||
|  * @description Convertis un nombre hexadécimal en binaire. | ||||
|  * @param {String} value - Le nombre à convertir | ||||
|  * @returns {String} - Le nombre en binaire | ||||
|  * @examples hexadecimalToBinary('F') → '1111' | ||||
|  */  | ||||
| function hexadecimalToBinary(value) { | ||||
|     value = parseInt(value, 16); | ||||
|     if (isNaN(value)) { | ||||
|         return false; | ||||
|     } else { | ||||
|         return parseInt(value).toString(2); | ||||
|     } | ||||
| }  | ||||
|  */ | ||||
| function hexadecimalToBinary (value) { | ||||
|   value = parseInt(value, 16) | ||||
|   if (isNaN(value)) { | ||||
|     return false | ||||
|   } else { | ||||
|     return parseInt(value).toString(2) | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Convertis des nombres de différentes bases et convertis en UTF-8. (source : http://jsfiddle.net/47zwb41o) | ||||
|  | ||||
| /**  | ||||
|  * @description Convertis chaque caractère d'une string en codePoint Unicode.  | ||||
| /** | ||||
|  * @description Convertis chaque caractère d'une string en codePoint Unicode. | ||||
|  * @param {String} value - La chaîne de caractère à convertir | ||||
|  * @returns {String} | ||||
|  * @examples textToNumberUnicode('abc') → '97 98 99' | ||||
|  */  | ||||
| function textToNumberUnicode(string) { | ||||
|     try { | ||||
|         let resultat = ""; | ||||
|         for (let index in string) { | ||||
|           resultat = resultat + string.codePointAt(index) + " "; | ||||
|         } | ||||
|         return resultat; | ||||
|     } | ||||
|     catch(error) { | ||||
|         return false; | ||||
|  */ | ||||
| function textToNumberUnicode (string) { | ||||
|   try { | ||||
|     let resultat = '' | ||||
|     for (const index in string) { | ||||
|       resultat = resultat + string.codePointAt(index) + ' ' | ||||
|     } | ||||
|     return resultat | ||||
|   } catch (error) { | ||||
|     return false | ||||
|   } | ||||
| } | ||||
|  | ||||
| /**  | ||||
|  * @description Convertis chaque codePoint Unicode en caractère.  | ||||
| /** | ||||
|  * @description Convertis chaque codePoint Unicode en caractère. | ||||
|  * @param {String} string - Nombre Unicode à convertir espacé par un espace à chaque fois | ||||
|  * @returns {String} | ||||
|  * @examples numberUnicodeToText('97 98 99') → 'abc' | ||||
|  */   | ||||
| function numberUnicodeToText(string) { | ||||
|     try { | ||||
|         const array = string.split(" "); | ||||
|         let resultat = ""; | ||||
|         for (let index in array) { | ||||
|             resultat += String.fromCodePoint(parseInt(array[index]).toString()); | ||||
|         } | ||||
|         return resultat; | ||||
|     } | ||||
|     catch(error) { | ||||
|         return false; | ||||
|  */ | ||||
| function numberUnicodeToText (string) { | ||||
|   try { | ||||
|     const array = string.split(' ') | ||||
|     let resultat = '' | ||||
|     for (const index in array) { | ||||
|       resultat += String.fromCodePoint(parseInt(array[index]).toString()) | ||||
|     } | ||||
|     return resultat | ||||
|   } catch (error) { | ||||
|     return false | ||||
|   } | ||||
| } | ||||
|  | ||||
| /**  | ||||
| /** | ||||
|  * @description Convertis un Texte en Binaire (UTF-8). | ||||
|  * @param {String} s - La chaîne de caractère à convertir | ||||
|  * @returns {String} | ||||
|  * @examples textToBinary('abc') → '01100001 01100010 01100011' | ||||
|  */ | ||||
| function textToBinary(s) { | ||||
|     try { | ||||
|         s = unescape( encodeURIComponent(s)); | ||||
|         let chr, i = 0, l = s.length, out = ''; | ||||
|         for( ; i < l; i ++ ){ | ||||
|             chr = s.charCodeAt( i ).toString(2); | ||||
|             while(chr.length % 8 != 0 ){ chr = '0' + chr; } | ||||
|             out += chr; | ||||
|         } | ||||
|         return out.replace(/(\d{8})/g, '$1 ').replace(/(^\s+|\s+$)/,''); | ||||
|     } catch (error) { | ||||
|         return false; | ||||
| function textToBinary (s) { | ||||
|   try { | ||||
|     s = unescape(encodeURIComponent(s)) | ||||
|     let chr | ||||
|     let i = 0 | ||||
|     const l = s.length | ||||
|     let out = '' | ||||
|     for (; i < l; i++) { | ||||
|       chr = s.charCodeAt(i).toString(2) | ||||
|       while (chr.length % 8 !== 0) { | ||||
|         chr = '0' + chr | ||||
|       } | ||||
|       out += chr | ||||
|     } | ||||
|     return out.replace(/(\d{8})/g, '$1 ').replace(/(^\s+|\s+$)/, '') | ||||
|   } catch (error) { | ||||
|     return false | ||||
|   } | ||||
| } | ||||
|  | ||||
| /**  | ||||
| /** | ||||
|  * @description Convertis du Binaire (UTF-8) en Texte. | ||||
|  * @param {String} s - La chaîne de caractère contenant tous les octets à convertir | ||||
|  * @returns {String} | ||||
|  * @examples binaryToText('01100001 01100010 01100011') → 'abc' | ||||
|  */ | ||||
| function binaryToText(s){ | ||||
|     try { | ||||
|         s = s.replace(/\s/g,'') | ||||
|         let i = 0, l = s.length, chr, out = ''; | ||||
|         for( ; i < l; i += 8){ | ||||
|             chr = parseInt( s.substr(i, 8 ), 2).toString(16); | ||||
|             out += '%' + ((chr.length % 2 == 0) ? chr : '0' + chr); | ||||
|         } | ||||
|         return decodeURIComponent(out); | ||||
|     } catch (error) { | ||||
|         return false; | ||||
| function binaryToText (s) { | ||||
|   try { | ||||
|     s = s.replace(/\s/g, '') | ||||
|     let i = 0 | ||||
|     const l = s.length | ||||
|     let chr | ||||
|     let out = '' | ||||
|     for (; i < l; i += 8) { | ||||
|       chr = parseInt(s.substr(i, 8), 2).toString(16) | ||||
|       out += '%' + (chr.length % 2 === 0 ? chr : '0' + chr) | ||||
|     } | ||||
| }  | ||||
|     return decodeURIComponent(out) | ||||
|   } catch (error) { | ||||
|     return false | ||||
|   } | ||||
| } | ||||
|  | ||||
| /**  | ||||
| /** | ||||
|  * @description Convertis un Texte en Hexadécimal (UTF-8). | ||||
|  * @param {String} s - La chaîne de caractère à convertir | ||||
|  * @returns {String} | ||||
|  * @examples textToHexadecimal('abc') → '61 62 63' | ||||
|  */ | ||||
| function textToHexadecimal (s) { | ||||
|     try { | ||||
|         s = unescape( encodeURIComponent( s ) ); | ||||
|         let chr, i = 0, l = s.length, out = ''; | ||||
|         for( ; i < l; i++ ){ | ||||
|             chr = s.charCodeAt( i ).toString( 16 ); | ||||
|             out += ( chr.length % 2 == 0 ) ? chr : '0' + chr; | ||||
|             out += " "; | ||||
|         } | ||||
|         return out.toUpperCase(); | ||||
|     } | ||||
|     catch (error) { | ||||
|         return false; | ||||
|   try { | ||||
|     s = unescape(encodeURIComponent(s)) | ||||
|     let chr | ||||
|     let i = 0 | ||||
|     const l = s.length | ||||
|     let out = '' | ||||
|     for (; i < l; i++) { | ||||
|       chr = s.charCodeAt(i).toString(16) | ||||
|       out += chr.length % 2 === 0 ? chr : '0' + chr | ||||
|       out += ' ' | ||||
|     } | ||||
|     return out.toUpperCase() | ||||
|   } catch (error) { | ||||
|     return false | ||||
|   } | ||||
| } | ||||
|  | ||||
| /**  | ||||
| /** | ||||
|  * @description Convertis de l'Hexadécimal (UTF-8) en Texte. | ||||
|  * @param {String} s - La chaîne de caractère contenant tous les nombres Hexadécimal à convertir | ||||
|  * @returns {String} | ||||
|  * @examples hexadecimalToText('61 62 63') → 'abc' | ||||
|  */ | ||||
| function hexadecimalToText (s) { | ||||
|     try { | ||||
|         s = s.replace(/\s/g,''); | ||||
|         return decodeURIComponent( s.replace( /../g, '%$&' ) ); | ||||
|     } | ||||
| 	catch (error) { | ||||
|         return false; | ||||
|     } | ||||
|   try { | ||||
|     s = s.replace(/\s/g, '') | ||||
|     return decodeURIComponent(s.replace(/../g, '%$&')) | ||||
|   } catch (error) { | ||||
|     return false | ||||
|   } | ||||
| } | ||||
|  | ||||
| /* OUTPUTS */ | ||||
| const convertEncoding = { decimalToBinary, binaryToDecimal, decimalToHexadecimal, hexadecimalToDecimal, binaryToHexadecimal, hexadecimalToBinary, textToNumberUnicode, numberUnicodeToText, textToBinary, binaryToText, textToHexadecimal, hexadecimalToText }; | ||||
| function executeFunction(option, value) { | ||||
|     return convertEncoding[option](value); | ||||
| const convertEncoding = { | ||||
|   decimalToBinary, | ||||
|   binaryToDecimal, | ||||
|   decimalToHexadecimal, | ||||
|   hexadecimalToDecimal, | ||||
|   binaryToHexadecimal, | ||||
|   hexadecimalToBinary, | ||||
|   textToNumberUnicode, | ||||
|   numberUnicodeToText, | ||||
|   textToBinary, | ||||
|   binaryToText, | ||||
|   textToHexadecimal, | ||||
|   hexadecimalToText | ||||
| } | ||||
| function executeFunction (option, value) { | ||||
|   return convertEncoding[option](value) | ||||
| } | ||||
|  | ||||
| module.exports = convertEncodingOutput = ({ res, next }, argsObject) => { | ||||
|     let { value, functionName } = argsObject; | ||||
|      | ||||
|     // S'il n'y a pas les champs obligatoire | ||||
|     if (!(value && functionName)) { | ||||
|         return errorHandling(next, requiredFields); | ||||
|     } | ||||
| module.exports = ({ res, next }, argsObject) => { | ||||
|   const { value, functionName } = argsObject | ||||
|  | ||||
|     // Si la fonction n'existe pas | ||||
|     if (!convertEncoding.hasOwnProperty(functionName)) { | ||||
|         return errorHandling(next, { message: "Cette conversion n'existe pas.", statusCode: 400 }); | ||||
|     }  | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!(value && functionName)) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|     const result = executeFunction(functionName, value); | ||||
|   // Si la fonction n'existe pas | ||||
|   // eslint-disable-next-line | ||||
|   if (!convertEncoding.hasOwnProperty(functionName)) { | ||||
|     return errorHandling(next, { | ||||
|       message: "Cette conversion n'existe pas.", | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|     // Mauvaise valeur entrée | ||||
|     if (!result) { | ||||
|         return errorHandling(next, generalError); | ||||
|     } | ||||
|   const result = executeFunction(functionName, value) | ||||
|  | ||||
|     return res.status(200).json({ result, resultHTML: `<p>${result}</p>` }); | ||||
| } | ||||
|   // Mauvaise valeur entrée | ||||
|   if (!result) { | ||||
|     return errorHandling(next, generalError) | ||||
|   } | ||||
|  | ||||
|   return res.status(200).json({ result, resultHTML: `<p>${result}</p>` }) | ||||
| } | ||||
|   | ||||
| @@ -1,123 +1,143 @@ | ||||
| const errorHandling                    = require('../../utils/errorHandling'); | ||||
| const { requiredFields, generalError } = require('../../config/errors'); | ||||
| const formatNumberResult               = require('../secondary/formatNumberResult'); | ||||
| const errorHandling = require('../../utils/errorHandling') | ||||
| const { requiredFields, generalError } = require('../../config/errors') | ||||
| const formatNumberResult = require('../secondary/formatNumberResult') | ||||
|  | ||||
| /* Variable pour convertRomanArabicNumbers */ | ||||
| const correspondancesRomainArabe = [ | ||||
|     [1000, "M"], | ||||
|     [900, "CM"], | ||||
|     [500, "D"], | ||||
|     [400, "CD"], | ||||
|     [100, "C"], | ||||
|     [90, "XC"], | ||||
|     [50, "L"], | ||||
|     [40, "XL"], | ||||
|     [10, "X"], | ||||
|     [9, "IX"], | ||||
|     [5, "V"], | ||||
|     [4, "IV"], | ||||
|     [1, "I"], | ||||
| ]; | ||||
|   [1000, 'M'], | ||||
|   [900, 'CM'], | ||||
|   [500, 'D'], | ||||
|   [400, 'CD'], | ||||
|   [100, 'C'], | ||||
|   [90, 'XC'], | ||||
|   [50, 'L'], | ||||
|   [40, 'XL'], | ||||
|   [10, 'X'], | ||||
|   [9, 'IX'], | ||||
|   [5, 'V'], | ||||
|   [4, 'IV'], | ||||
|   [1, 'I'] | ||||
| ] | ||||
|  | ||||
| /**  | ||||
| /** | ||||
|  * @description Convertis un nombre arabe en nombre romain. | ||||
|  * @param {number} nombre - Le nombre arabe à convertir | ||||
|  * @returns {string} | ||||
|  * @examples convertArabicToRoman(24) → 'XXIV' | ||||
|  */ | ||||
| function convertArabicToRoman(nombre) { | ||||
|     // Initialisation de la variable qui va contenir le résultat de la conversion | ||||
|     let chiffresRomains = ""; | ||||
| function convertArabicToRoman (nombre) { | ||||
|   // Initialisation de la variable qui va contenir le résultat de la conversion | ||||
|   let chiffresRomains = '' | ||||
|  | ||||
|     function extraireChiffreRomain(valeurLettre, lettres) { | ||||
|         while (nombre >= valeurLettre) { | ||||
|             chiffresRomains = chiffresRomains + lettres; | ||||
|             nombre = nombre - valeurLettre; | ||||
|         } | ||||
|   function extraireChiffreRomain (valeurLettre, lettres) { | ||||
|     while (nombre >= valeurLettre) { | ||||
|       chiffresRomains = chiffresRomains + lettres | ||||
|       nombre = nombre - valeurLettre | ||||
|     } | ||||
|   } | ||||
|  | ||||
|     correspondancesRomainArabe.forEach(correspondance => { | ||||
|         extraireChiffreRomain(correspondance[0], correspondance[1]); | ||||
|     }); | ||||
|   correspondancesRomainArabe.forEach(correspondance => { | ||||
|     extraireChiffreRomain(correspondance[0], correspondance[1]) | ||||
|   }) | ||||
|  | ||||
|     return chiffresRomains; | ||||
| }  | ||||
|   return chiffresRomains | ||||
| } | ||||
|  | ||||
| /**  | ||||
| /** | ||||
|  * @description Convertis un nombre romain en nombre arabe. | ||||
|  * @param {string} string - Le nombre romain à convertir | ||||
|  * @return {number} | ||||
|  * @example convertRomanToArabic('XXIV') → 24 | ||||
|  */ | ||||
| function convertRomanToArabic(string) { | ||||
|     let result = 0; | ||||
|     correspondancesRomainArabe.forEach((correspondance) => {         | ||||
|         while (string.indexOf(correspondance[1]) === 0) { | ||||
|             // Ajout de la valeur décimale au résultat | ||||
|             result += correspondance[0]; | ||||
|             // Supprimer la lettre romaine correspondante du début | ||||
|             string = string.replace(correspondance[1], ''); | ||||
|         } | ||||
|     }); | ||||
|     if (string != '') { | ||||
|         result = 0; | ||||
| function convertRomanToArabic (string) { | ||||
|   let result = 0 | ||||
|   correspondancesRomainArabe.forEach(correspondance => { | ||||
|     while (string.indexOf(correspondance[1]) === 0) { | ||||
|       // Ajout de la valeur décimale au résultat | ||||
|       result += correspondance[0] | ||||
|       // Supprimer la lettre romaine correspondante du début | ||||
|       string = string.replace(correspondance[1], '') | ||||
|     } | ||||
|     return result; | ||||
| }  | ||||
|   }) | ||||
|   if (string !== '') { | ||||
|     result = 0 | ||||
|   } | ||||
|   return result | ||||
| } | ||||
|  | ||||
| /* OUTPUTS */ | ||||
| const convertRomanToArabicOutput = ({ res, next }, number) => { | ||||
|      | ||||
|     // S'il n'y a pas les champs obligatoire | ||||
|     if (!(number)) { | ||||
|         return errorHandling(next, requiredFields); | ||||
|     } | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!number) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|     // Formate le paramètre | ||||
|     number = number.toUpperCase();  | ||||
|   // Formate le paramètre | ||||
|   number = number.toUpperCase() | ||||
|  | ||||
|     const result = convertRomanToArabic(number); | ||||
|     if (result === 0) { | ||||
|         return errorHandling(next, generalError); | ||||
|     } | ||||
|          | ||||
|     return res.status(200).json({ result, resultHTML: `<p><span class="important">${number}</span> s'écrit <span class="important">${result}</span> en chiffres arabes.</p>` }); | ||||
|   const result = convertRomanToArabic(number) | ||||
|   if (result === 0) { | ||||
|     return errorHandling(next, generalError) | ||||
|   } | ||||
|  | ||||
|   return res | ||||
|     .status(200) | ||||
|     .json({ | ||||
|       result, | ||||
|       resultHTML: `<p><span class="important">${number}</span> s'écrit <span class="important">${result}</span> en chiffres arabes.</p>` | ||||
|     }) | ||||
| } | ||||
|  | ||||
| const convertArabicToRomanOutput = ({ res, next }, number) => { | ||||
|      | ||||
|     // S'il n'y a pas les champs obligatoire | ||||
|     if (!(number)) { | ||||
|         return errorHandling(next, requiredFields); | ||||
|     } | ||||
|      | ||||
|     // Si ce n'est pas un nombre | ||||
|     number = parseInt(number); | ||||
|     if (isNaN(number)) { | ||||
|         return errorHandling(next, { message: "Veuillez rentré un nombre valide.", statusCode: 400 }); | ||||
|     } | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!number) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|     const result = convertArabicToRoman(number); | ||||
|     return res.status(200).json({ result, resultHTML: `<p><span class="important">${formatNumberResult(number)}</span> s'écrit <span class="important">${result}</span> en chiffres romains.</p>` }); | ||||
|   // Si ce n'est pas un nombre | ||||
|   number = parseInt(number) | ||||
|   if (isNaN(number)) { | ||||
|     return errorHandling(next, { | ||||
|       message: 'Veuillez rentré un nombre valide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   const result = convertArabicToRoman(number) | ||||
|   return res | ||||
|     .status(200) | ||||
|     .json({ | ||||
|       result, | ||||
|       resultHTML: `<p><span class="important">${formatNumberResult( | ||||
|         number | ||||
|       )}</span> s'écrit <span class="important">${result}</span> en chiffres romains.</p>` | ||||
|     }) | ||||
| } | ||||
|  | ||||
| const convertRomanArabicObject = { convertRomanToArabicOutput, convertArabicToRomanOutput }; | ||||
| function executeFunction(option, value, { res, next }) { | ||||
|     return convertRomanArabicObject[option]({ res, next}, value); | ||||
| const convertRomanArabicObject = { | ||||
|   convertRomanToArabicOutput, | ||||
|   convertArabicToRomanOutput | ||||
| } | ||||
| function executeFunction (option, value, { res, next }) { | ||||
|   return convertRomanArabicObject[option]({ res, next }, value) | ||||
| } | ||||
|  | ||||
| module.exports = convertRomanArabicNumbersOutput = ({ res, next }, argsObject) => { | ||||
|     let { value, functionName } = argsObject; | ||||
| module.exports = ({ res, next }, argsObject) => { | ||||
|   const { value, functionName } = argsObject | ||||
|  | ||||
|     // S'il n'y a pas les champs obligatoire | ||||
|     if (!(value && functionName)) { | ||||
|         return errorHandling(next, requiredFields); | ||||
|     } | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!(value && functionName)) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|     // Si la fonction n'existe pas | ||||
|     if (!convertRomanArabicObject.hasOwnProperty(functionName)) { | ||||
|         return errorHandling(next, { message: "Cette conversion n'existe pas.", statusCode: 400 }); | ||||
|     }  | ||||
|   // Si la fonction n'existe pas | ||||
|   // eslint-disable-next-line | ||||
|   if (!convertRomanArabicObject.hasOwnProperty(functionName)) { | ||||
|     return errorHandling(next, { | ||||
|       message: "Cette conversion n'existe pas.", | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|     executeFunction(functionName, value, { res, next }); | ||||
| } | ||||
|   executeFunction(functionName, value, { res, next }) | ||||
| } | ||||
|   | ||||
| @@ -1,50 +1,53 @@ | ||||
| const errorHandling                    = require('../../utils/errorHandling'); | ||||
| const { requiredFields, generalError } = require('../../config/errors'); | ||||
| const formatNumberResult               = require('../secondary/formatNumberResult'); | ||||
| const errorHandling = require('../../utils/errorHandling') | ||||
| const { requiredFields, generalError } = require('../../config/errors') | ||||
| const formatNumberResult = require('../secondary/formatNumberResult') | ||||
|  | ||||
| /**  | ||||
| /** | ||||
|  * @description Convertis des °C en °F et l'inverse aussi. | ||||
|  * @param {Number} degree - Nombre de degrès | ||||
|  * @param {String} unit - Unité du nombre (°C ou °F) après conversion | ||||
|  * @returns {Object} false si arguments non valides et sinon un objet contenant la string et le nombre résultat | ||||
|  * @examples convertTemperature(23, '°F') → { result: 73.4, resultHTML: "73.4 °F" } | ||||
|  */ | ||||
| function convertTemperature(degree, unit) { | ||||
|     let temperatureValue = 0; | ||||
|     if (unit === "°C") { | ||||
|         temperatureValue = (degree - 32) * 5/9; | ||||
|     } | ||||
|     else if (unit === "°F") { | ||||
|         temperatureValue = ((degree * 9/5) + 32); | ||||
|     } | ||||
|     else { | ||||
|         return false; | ||||
|     } | ||||
|     return { | ||||
|         result: temperatureValue, | ||||
|         resultHTML: `<p>${formatNumberResult(degree)} ${(unit === '°C') ? "°F" : "°C"} = ${formatNumberResult(temperatureValue)} ${unit}</p>` | ||||
|     }; | ||||
| }  | ||||
| function convertTemperature (degree, unit) { | ||||
|   let temperatureValue = 0 | ||||
|   if (unit === '°C') { | ||||
|     temperatureValue = ((degree - 32) * 5) / 9 | ||||
|   } else if (unit === '°F') { | ||||
|     temperatureValue = (degree * 9) / 5 + 32 | ||||
|   } else { | ||||
|     return false | ||||
|   } | ||||
|   return { | ||||
|     result: temperatureValue, | ||||
|     resultHTML: `<p>${formatNumberResult(degree)} ${ | ||||
|       unit === '°C' ? '°F' : '°C' | ||||
|     } = ${formatNumberResult(temperatureValue)} ${unit}</p>` | ||||
|   } | ||||
| } | ||||
|  | ||||
| /* OUTPUTS */ | ||||
| module.exports = convertTemperatureOutput = ({ res, next }, argsObject) => { | ||||
|     let { degree, unitToConvert } = argsObject; | ||||
|      | ||||
|     // S'il n'y a pas les champs obligatoire | ||||
|     if (!(degree && unitToConvert)) { | ||||
|         return errorHandling(next, requiredFields); | ||||
|     } | ||||
|      | ||||
|     // Si ce n'est pas un nombre | ||||
|     degree = parseFloat(degree); | ||||
|     if (isNaN(degree)) { | ||||
|         return errorHandling(next, { message: "Veuillez rentré un nombre valide.", statusCode: 400 }); | ||||
|     } | ||||
| module.exports = ({ res, next }, argsObject) => { | ||||
|   let { degree, unitToConvert } = argsObject | ||||
|  | ||||
|     const result = convertTemperature(degree, unitToConvert); | ||||
|     if (!result) { | ||||
|         return errorHandling(next, generalError); | ||||
|     } | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!(degree && unitToConvert)) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|     return res.status(200).json(result); | ||||
| } | ||||
|   // Si ce n'est pas un nombre | ||||
|   degree = parseFloat(degree) | ||||
|   if (isNaN(degree)) { | ||||
|     return errorHandling(next, { | ||||
|       message: 'Veuillez rentré un nombre valide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   const result = convertTemperature(degree, unitToConvert) | ||||
|   if (!result) { | ||||
|     return errorHandling(next, generalError) | ||||
|   } | ||||
|  | ||||
|   return res.status(200).json(result) | ||||
| } | ||||
|   | ||||
| @@ -1,46 +1,55 @@ | ||||
| const errorHandling      = require('../../utils/errorHandling'); | ||||
| const { requiredFields } = require('../../config/errors'); | ||||
| const formatNumberResult = require('../secondary/formatNumberResult'); | ||||
| const errorHandling = require('../../utils/errorHandling') | ||||
| const { requiredFields } = require('../../config/errors') | ||||
| const formatNumberResult = require('../secondary/formatNumberResult') | ||||
|  | ||||
| /** | ||||
|  * @description Calcule les counter premiers nombres de la suite de fibonacci. | ||||
|  * @param {number} counter  | ||||
|  * @param {number} counter | ||||
|  */ | ||||
| function fibonacci(counter, result = [], a = 0, b = 1) { | ||||
|     if (counter === 0) { | ||||
|         return result;  | ||||
|     } | ||||
|     counter--; | ||||
|     result.push(a); | ||||
|     return fibonacci(counter, result, b, a + b); | ||||
| function fibonacci (counter, result = [], a = 0, b = 1) { | ||||
|   if (counter === 0) { | ||||
|     return result | ||||
|   } | ||||
|   counter-- | ||||
|   result.push(a) | ||||
|   return fibonacci(counter, result, b, a + b) | ||||
| } | ||||
|  | ||||
| /* OUTPUTS */ | ||||
| module.exports = fibonacciOutput = ({ res, next }, argsObject) => { | ||||
|     let { counter } = argsObject; | ||||
|      | ||||
|     // S'il n'y a pas les champs obligatoire | ||||
|     if (!(counter)) { | ||||
|         return errorHandling(next, requiredFields); | ||||
|     } | ||||
|      | ||||
|     // Si ce n'est pas un nombre | ||||
|     counter = parseInt(counter); | ||||
|     if (isNaN(counter)) { | ||||
|         return errorHandling(next, { message: "Veuillez rentré un nombre valide.", statusCode: 400 }); | ||||
|     } | ||||
| module.exports = ({ res, next }, argsObject) => { | ||||
|   let { counter } = argsObject | ||||
|  | ||||
|     // Si le nombre dépasse LIMIT_COUNTER | ||||
|     const LIMIT_COUNTER = 51; | ||||
|     if (counter >= LIMIT_COUNTER) { | ||||
|         return errorHandling(next, { message: `Par souci de performance, vous ne pouvez pas exécuter cette fonction avec un compteur dépassant ${LIMIT_COUNTER - 1}.`, statusCode: 400 }); | ||||
|     } | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!counter) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|     const result          = fibonacci(counter); | ||||
|     const resultFormatted = result.map((number) => formatNumberResult(number)); | ||||
|     return res.status(200).json({ | ||||
|         result, | ||||
|         resultFormatted, | ||||
|         resultHTML: `<p>Les ${counter} premiers nombres de la suite de fibonacci :<br/> ${resultFormatted.join(', ')}</p>` | ||||
|     }); | ||||
| } | ||||
|   // Si ce n'est pas un nombre | ||||
|   counter = parseInt(counter) | ||||
|   if (isNaN(counter)) { | ||||
|     return errorHandling(next, { | ||||
|       message: 'Veuillez rentré un nombre valide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   // Si le nombre dépasse LIMIT_COUNTER | ||||
|   const LIMIT_COUNTER = 51 | ||||
|   if (counter >= LIMIT_COUNTER) { | ||||
|     return errorHandling(next, { | ||||
|       message: `Par souci de performance, vous ne pouvez pas exécuter cette fonction avec un compteur dépassant ${LIMIT_COUNTER - | ||||
|         1}.`, | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   const result = fibonacci(counter) | ||||
|   const resultFormatted = result.map(number => formatNumberResult(number)) | ||||
|   return res.status(200).json({ | ||||
|     result, | ||||
|     resultFormatted, | ||||
|     resultHTML: `<p>Les ${counter} premiers nombres de la suite de fibonacci :<br/> ${resultFormatted.join( | ||||
|       ', ' | ||||
|     )}</p>` | ||||
|   }) | ||||
| } | ||||
|   | ||||
| @@ -1,39 +1,39 @@ | ||||
| const errorHandling      = require('../../utils/errorHandling'); | ||||
| const { requiredFields } = require('../../config/errors'); | ||||
| const errorHandling = require('../../utils/errorHandling') | ||||
| const { requiredFields } = require('../../config/errors') | ||||
|  | ||||
| /** | ||||
|  * @description Renvoie le mot le plus long d'une chaîne de caractères | ||||
|  * @param {string} string  | ||||
|  * @param {string} string | ||||
|  * @returns {string} | ||||
|  * @example findLongestWord('Chaîne de caractères') → 'caractères' | ||||
|  */ | ||||
| function findLongestWord(string) { | ||||
|     const arrayString = string.split(" "); | ||||
|     let stringLength = 0; | ||||
|     let result = ""; | ||||
| function findLongestWord (string) { | ||||
|   const arrayString = string.split(' ') | ||||
|   let stringLength = 0 | ||||
|   let result = '' | ||||
|  | ||||
|     arrayString.forEach((element) => { | ||||
|         if (element.length > stringLength) { | ||||
|             result = element; | ||||
|             stringLength = element.length; | ||||
|         } | ||||
|     }); | ||||
|   arrayString.forEach(element => { | ||||
|     if (element.length > stringLength) { | ||||
|       result = element | ||||
|       stringLength = element.length | ||||
|     } | ||||
|   }) | ||||
|  | ||||
|     return result; | ||||
|   return result | ||||
| } | ||||
|  | ||||
| /* OUTPUTS */ | ||||
| module.exports = findLongestWordOutput = ({ res, next }, argsObject) => { | ||||
|     const { string } = argsObject; | ||||
|      | ||||
|     // S'il n'y a pas les champs obligatoire | ||||
|     if (!(string)) { | ||||
|         return errorHandling(next, requiredFields); | ||||
|     } | ||||
| module.exports = ({ res, next }, argsObject) => { | ||||
|   const { string } = argsObject | ||||
|  | ||||
|     const result = findLongestWord(string); | ||||
|     return res.status(200).json({ | ||||
|         result, | ||||
|         resultHTML: `<p>Le mot le plus long est : <br/>"${result}"</p>` | ||||
|     }); | ||||
| } | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!string) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|   const result = findLongestWord(string) | ||||
|   return res.status(200).json({ | ||||
|     result, | ||||
|     resultHTML: `<p>Le mot le plus long est : <br/>"${result}"</p>` | ||||
|   }) | ||||
| } | ||||
|   | ||||
| @@ -1,52 +1,64 @@ | ||||
| const errorHandling      = require('../../utils/errorHandling'); | ||||
| const { requiredFields } = require('../../config/errors'); | ||||
| const formatNumberResult = require('../secondary/formatNumberResult'); | ||||
| const errorHandling = require('../../utils/errorHandling') | ||||
| const { requiredFields } = require('../../config/errors') | ||||
| const formatNumberResult = require('../secondary/formatNumberResult') | ||||
|  | ||||
| /**  | ||||
| /** | ||||
|  * @description Retourne un tableau contenant toutes les possibilités d'anagramme d'un mot. | ||||
|  * @param {String} string - La chaîne de caractère à permuter | ||||
|  * @returns {Array} | ||||
|  * @examples heapAlgorithm('abc') → ["abc", "acb", "bac", "bca", "cab", "cba"] | ||||
|  */ | ||||
| function heapAlgorithm(string) { | ||||
|     let results = []; | ||||
| function heapAlgorithm (string) { | ||||
|   const results = [] | ||||
|  | ||||
|     if (string.length === 1) { | ||||
|         results.push(string); | ||||
|         return results; | ||||
|     } | ||||
|   if (string.length === 1) { | ||||
|     results.push(string) | ||||
|     return results | ||||
|   } | ||||
|  | ||||
|     for (let indexString = 0; indexString < string.length; indexString++) { | ||||
|         const firstChar = string[indexString]; | ||||
|         const charsLeft = string.substring(0, indexString) + string.substring(indexString + 1); | ||||
|         const innerPermutations = heapAlgorithm(charsLeft); | ||||
|         for (let indexPermutation = 0; indexPermutation < innerPermutations.length; indexPermutation++) { | ||||
|             results.push(firstChar + innerPermutations[indexPermutation]); | ||||
|         } | ||||
|   for (let indexString = 0; indexString < string.length; indexString++) { | ||||
|     const firstChar = string[indexString] | ||||
|     const charsLeft = | ||||
|       string.substring(0, indexString) + string.substring(indexString + 1) | ||||
|     const innerPermutations = heapAlgorithm(charsLeft) | ||||
|     for ( | ||||
|       let indexPermutation = 0; | ||||
|       indexPermutation < innerPermutations.length; | ||||
|       indexPermutation++ | ||||
|     ) { | ||||
|       results.push(firstChar + innerPermutations[indexPermutation]) | ||||
|     } | ||||
|     return results; | ||||
| }  | ||||
|   } | ||||
|   return results | ||||
| } | ||||
|  | ||||
| /* OUTPUTS */ | ||||
| module.exports = heapAlgorithmOutput = ({ res, next }, argsObject) => { | ||||
|     let { string } = argsObject; | ||||
|      | ||||
|     // S'il n'y a pas les champs obligatoire | ||||
|     if (!(string)) { | ||||
|         return errorHandling(next, requiredFields); | ||||
|     } | ||||
| module.exports = ({ res, next }, argsObject) => { | ||||
|   const { string } = argsObject | ||||
|  | ||||
|     // Si la chaîne de caractère dépasse LIMIT_CHARACTERS caractères | ||||
|     const LIMIT_CHARACTERS = 7; | ||||
|     if (string.length > LIMIT_CHARACTERS) { | ||||
|         return errorHandling(next, { message: `Par souci de performance, vous ne pouvez pas exécuter cette fonction avec un mot dépassant ${LIMIT_CHARACTERS} caractères.`, statusCode: 400 }); | ||||
|     } | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!string) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|     const result = heapAlgorithm(string); | ||||
|     let resultHTML = `<p>Il y a ${formatNumberResult(result.length)} possibilités d'anagramme pour le mot "${string}" qui contient ${string.length} caractères, la liste : <br/><br/>`; | ||||
|     result.forEach((string) => { | ||||
|         resultHTML += string + "<br/>"; | ||||
|     }); | ||||
|     resultHTML += "</p>"; | ||||
|     return res.status(200).json({ result, resultHTML }); | ||||
| } | ||||
|   // Si la chaîne de caractère dépasse LIMIT_CHARACTERS caractères | ||||
|   const LIMIT_CHARACTERS = 7 | ||||
|   if (string.length > LIMIT_CHARACTERS) { | ||||
|     return errorHandling(next, { | ||||
|       message: `Par souci de performance, vous ne pouvez pas exécuter cette fonction avec un mot dépassant ${LIMIT_CHARACTERS} caractères.`, | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   const result = heapAlgorithm(string) | ||||
|   let resultHTML = `<p>Il y a ${formatNumberResult( | ||||
|     result.length | ||||
|   )} possibilités d'anagramme pour le mot "${string}" qui contient ${ | ||||
|     string.length | ||||
|   } caractères, la liste : <br/><br/>` | ||||
|   result.forEach(string => { | ||||
|     resultHTML += string + '<br/>' | ||||
|   }) | ||||
|   resultHTML += '</p>' | ||||
|   return res.status(200).json({ result, resultHTML }) | ||||
| } | ||||
|   | ||||
| @@ -1,48 +1,58 @@ | ||||
| const errorHandling      = require('../../utils/errorHandling'); | ||||
| const { requiredFields } = require('../../config/errors'); | ||||
| const errorHandling = require('../../utils/errorHandling') | ||||
| const { requiredFields } = require('../../config/errors') | ||||
|  | ||||
| /** | ||||
|  * @description Inverse la chaîne de caractère | ||||
|  * @param {string} string  | ||||
|  * @param {string} string | ||||
|  * @returns {string} | ||||
|  * @example reverseString('Hello') → 'olleH' | ||||
|  */ | ||||
| function reverseString(string) { | ||||
|     return string.split("").reverse().join('');    | ||||
| function reverseString (string) { | ||||
|   return string | ||||
|     .split('') | ||||
|     .reverse() | ||||
|     .join('') | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @description Vérifie si un mot est un palindrome (un mot qui peut s'écrire dans les deux sens) | ||||
|  * @requires reverseString | ||||
|  * @param {string} string  | ||||
|  * @param {string} string | ||||
|  * @param {string} reverseStringResult La chaîne de caractères inversée | ||||
|  * @returns {boolean} | ||||
|  * @example isPalindrome('kayak') → true | ||||
|  */ | ||||
| function isPalindrome(string, reverseStringResult) { | ||||
|     return string === reverseStringResult; | ||||
| function isPalindrome (string, reverseStringResult) { | ||||
|   return string === reverseStringResult | ||||
| } | ||||
|  | ||||
| /* OUTPUTS */ | ||||
| module.exports = isPalindromeOutput = ({ res, next }, argsObject) => { | ||||
|     let { string } = argsObject; | ||||
|      | ||||
|     // S'il n'y a pas les champs obligatoire | ||||
|     if (!(string)) { | ||||
|         return errorHandling(next, requiredFields); | ||||
|     } | ||||
| module.exports = ({ res, next }, argsObject) => { | ||||
|   let { string } = argsObject | ||||
|  | ||||
|     if (typeof string !== 'string') { | ||||
|         return errorHandling(next, { message: "Vous devez rentré une chaîne de caractère valide.", statusCode: 400 }); | ||||
|     } | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!string) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|     string = string.toLowerCase(); | ||||
|   if (typeof string !== 'string') { | ||||
|     return errorHandling(next, { | ||||
|       message: 'Vous devez rentré une chaîne de caractère valide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|     const reverseStringResult = reverseString(string); | ||||
|     const isPalindromeResult  = isPalindrome(string, reverseStringResult); | ||||
|     return res.status(200).json({ | ||||
|         isPalindrome: isPalindromeResult, | ||||
|         reverseString: reverseStringResult, | ||||
|         resultHTML: `<p>"${string}" ${(isPalindromeResult) ? "est" : "n'est pas"} un palindrome car <br/> "${string}" ${(isPalindromeResult) ? "===" : "!=="} "${reverseStringResult}"</p>` | ||||
|     }); | ||||
| } | ||||
|   string = string.toLowerCase() | ||||
|  | ||||
|   const reverseStringResult = reverseString(string) | ||||
|   const isPalindromeResult = isPalindrome(string, reverseStringResult) | ||||
|   return res.status(200).json({ | ||||
|     isPalindrome: isPalindromeResult, | ||||
|     reverseString: reverseStringResult, | ||||
|     resultHTML: `<p>"${string}" ${ | ||||
|       isPalindromeResult ? 'est' : "n'est pas" | ||||
|     } un palindrome car <br/> "${string}" ${ | ||||
|       isPalindromeResult ? '===' : '!==' | ||||
|     } "${reverseStringResult}"</p>` | ||||
|   }) | ||||
| } | ||||
|   | ||||
| @@ -1,52 +0,0 @@ | ||||
| const validator                       = require('validator'); | ||||
| const errorHandling                   = require('../../utils/errorHandling'); | ||||
| const { requiredFields, serverError } = require('../../config/errors'); | ||||
| const Short_links                     = require('../../../models/short_links'); | ||||
|  | ||||
| module.exports = linkShortener = async ({ res, next }, argsObject) => { | ||||
|     let { url, shortcutName } = argsObject; | ||||
|  | ||||
|     // S'il n'y a pas les champs obligatoire | ||||
|     if (!(url && shortcutName)) { | ||||
|         return errorHandling(next, requiredFields); | ||||
|     } | ||||
|  | ||||
|     // Si ce n'est pas une url | ||||
|     if (!validator.isURL(url)) { | ||||
|         return errorHandling(next, { message: "Veuillez entré une URL valide.", statusCode: 400 }); | ||||
|     } | ||||
|  | ||||
|     // Si ce n'est pas de type slug | ||||
|     if (!validator.isSlug(shortcutName)) { | ||||
|         return errorHandling(next, { message: "Le nom de votre raccourci doit être de type slug (ne pas contenir d'espaces, ni de caractères spéciaux).", statusCode: 400 }); | ||||
|     } | ||||
|  | ||||
|     // Sanitize shortcutName | ||||
|     shortcutName = validator.escape(shortcutName); | ||||
|     shortcutName = validator.trim(shortcutName); | ||||
|     shortcutName = validator.blacklist(shortcutName, ' '); | ||||
|  | ||||
|     try { | ||||
|         // Si l'url a déjà été raccourcie | ||||
|         const urlInDatabase = await Short_links.findOne({ where: { url } }); | ||||
|         if (urlInDatabase) { | ||||
|             const urlShort = `https://short-links.divlo.fr/?q=${urlInDatabase.shortcut}`; | ||||
|             return errorHandling(next, { message: `L'url a déjà été raccourcie... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`, statusCode: 400 }); | ||||
|         } | ||||
|  | ||||
|         // Si le nom du raccourci existe déjà | ||||
|         const shortcutInDatabase = await Short_links.findOne({ where: { shortcut: shortcutName } }); | ||||
|         if (shortcutInDatabase) { | ||||
|             const urlShort = `https://short-links.divlo.fr/?q=${shortcutInDatabase.shortcut}`; | ||||
|             return errorHandling(next, { message: `Le nom du raccourci a déjà été utilisé... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`, statusCode: 400 }); | ||||
|         } | ||||
|  | ||||
|         // Ajout du lien raccourci | ||||
|         const result             = await Short_links.create({ url, shortcut: shortcutName }); | ||||
|         const shortcutLinkResult = `https://short-links.divlo.fr/?q=${result.shortcut}`; | ||||
|         return res.status(200).json({ resultHTML: `URL Raccourcie : <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${shortcutLinkResult}">${shortcutLinkResult}</a>`, result: shortcutLinkResult }); | ||||
|     } catch { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|     } | ||||
| } | ||||
| @@ -1,37 +1,47 @@ | ||||
| const errorHandling      = require('../../utils/errorHandling'); | ||||
| const { requiredFields } = require('../../config/errors'); | ||||
| const formatNumberResult = require('../secondary/formatNumberResult'); | ||||
| const errorHandling = require('../../utils/errorHandling') | ||||
| const { requiredFields } = require('../../config/errors') | ||||
| const formatNumberResult = require('../secondary/formatNumberResult') | ||||
|  | ||||
| /**  | ||||
|  * @description Génère un nombre aléatoire entre un minimum inclus et un maximum inclus.  | ||||
|  * @param {Number} min Nombre Minimum  | ||||
|  * @param {Number} max Nombre Maximum  | ||||
|  * @returns {Number} Nombre aléatoire  | ||||
| /** | ||||
|  * @description Génère un nombre aléatoire entre un minimum inclus et un maximum inclus. | ||||
|  * @param {Number} min Nombre Minimum | ||||
|  * @param {Number} max Nombre Maximum | ||||
|  * @returns {Number} Nombre aléatoire | ||||
|  * @examples randomNumber(1, 2) → retourne soit 1 ou 2 | ||||
|  */  | ||||
| function randomNumber(min, max) { | ||||
|     return Math.floor(Math.random() * (max - min +1)) + min; | ||||
|  */ | ||||
| function randomNumber (min, max) { | ||||
|   return Math.floor(Math.random() * (max - min + 1)) + min | ||||
| } | ||||
|  | ||||
| /* OUTPUTS */ | ||||
| const randomNumberOutput = ({ res, next }, argsObject) => { | ||||
|     let { min, max } = argsObject; | ||||
|      | ||||
|     // S'il n'y a pas les champs obligatoire | ||||
|     if (!(min && max)) { | ||||
|         return errorHandling(next, requiredFields); | ||||
|     } | ||||
|      | ||||
|     // Si ce ne sont pas des nombres | ||||
|     min = parseInt(min); | ||||
|     max = parseInt(max); | ||||
|     if (isNaN(min) || isNaN(max)) { | ||||
|         return errorHandling(next, { message: "Les paramètres min et max doivent être des nombres...", statusCode: 400 }); | ||||
|     } | ||||
|   let { min, max } = argsObject | ||||
|  | ||||
|     const result = randomNumber(min, max); | ||||
|     return res.status(200).json({ result, resultHTML: `<p>Nombre aléatoire compris entre ${min} inclus et ${max} inclus : <strong>${formatNumberResult(result)}</strong></p>` }); | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!(min && max)) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|   // Si ce ne sont pas des nombres | ||||
|   min = parseInt(min) | ||||
|   max = parseInt(max) | ||||
|   if (isNaN(min) || isNaN(max)) { | ||||
|     return errorHandling(next, { | ||||
|       message: 'Les paramètres min et max doivent être des nombres...', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   const result = randomNumber(min, max) | ||||
|   return res | ||||
|     .status(200) | ||||
|     .json({ | ||||
|       result, | ||||
|       resultHTML: `<p>Nombre aléatoire compris entre ${min} inclus et ${max} inclus : <strong>${formatNumberResult( | ||||
|         result | ||||
|       )}</strong></p>` | ||||
|     }) | ||||
| } | ||||
|  | ||||
| exports.randomNumber       = randomNumber; | ||||
| exports.randomNumberOutput = randomNumberOutput; | ||||
| exports.randomNumber = randomNumber | ||||
| exports.randomNumberOutput = randomNumberOutput | ||||
|   | ||||
| @@ -1,26 +1,24 @@ | ||||
| const errorHandling   = require('../../utils/errorHandling'); | ||||
| const { serverError } = require('../../config/errors'); | ||||
| const Quotes          = require('../../../models/quotes'); | ||||
| const Users           = require('../../../models/users'); | ||||
| const sequelize       = require('../../utils/database'); | ||||
| const errorHandling = require('../../utils/errorHandling') | ||||
| const { serverError } = require('../../config/errors') | ||||
| const Quotes = require('../../../models/quotes') | ||||
| const Users = require('../../../models/users') | ||||
| const sequelize = require('../../utils/database') | ||||
|  | ||||
| module.exports = randomQuote = async ({ res, next }, _argsObject) => { | ||||
|     try { | ||||
|         const quote = await Quotes.findOne({ | ||||
|             order: sequelize.random(), | ||||
|             include: [ | ||||
|                 { model: Users, attributes: ["name", "logo"] } | ||||
|             ], | ||||
|             attributes: { | ||||
|                 exclude: ["isValidated"] | ||||
|             }, | ||||
|             where: {  | ||||
|                 isValidated: 1, | ||||
|             } | ||||
|         }); | ||||
|         return res.status(200).json(quote); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|     } | ||||
| } | ||||
| module.exports = async ({ res, next }, _argsObject) => { | ||||
|   try { | ||||
|     const quote = await Quotes.findOne({ | ||||
|       order: sequelize.random(), | ||||
|       include: [{ model: Users, attributes: ['name', 'logo'] }], | ||||
|       attributes: { | ||||
|         exclude: ['isValidated'] | ||||
|       }, | ||||
|       where: { | ||||
|         isValidated: 1 | ||||
|       } | ||||
|     }) | ||||
|     return res.status(200).json(quote) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,57 +1,60 @@ | ||||
| const { randomNumber }    = require('./randomNumber'); | ||||
| const errorHandling       = require('../../utils/errorHandling'); | ||||
| const { serverError }     = require('../../config/errors'); | ||||
| const { SCRAPER_API_KEY } = require('../../config/config'); | ||||
| const axios               = require('axios'); | ||||
| const { JSDOM }           = require("jsdom"); | ||||
| const { randomNumber } = require('./randomNumber') | ||||
| const errorHandling = require('../../utils/errorHandling') | ||||
| const { serverError } = require('../../config/errors') | ||||
| const { SCRAPER_API_KEY } = require('../../config/config') | ||||
| const axios = require('axios') | ||||
| const { JSDOM } = require('jsdom') | ||||
|  | ||||
| const subjectList = [ | ||||
|     "smartphone", | ||||
|     "pc+gamer", | ||||
|     "pc+portable", | ||||
|     "TV", | ||||
|     "casque", | ||||
|     "clavier", | ||||
|     "souris", | ||||
|     "ecran", | ||||
|     "jeux+vidéos" | ||||
| ]; | ||||
|   'smartphone', | ||||
|   'pc+gamer', | ||||
|   'pc+portable', | ||||
|   'TV', | ||||
|   'casque', | ||||
|   'clavier', | ||||
|   'souris', | ||||
|   'ecran', | ||||
|   'jeux+vidéos' | ||||
| ] | ||||
|  | ||||
| function getRandomArrayElement(array) { | ||||
|     return array[randomNumber(0, array.length - 1)]; | ||||
| function getRandomArrayElement (array) { | ||||
|   return array[randomNumber(0, array.length - 1)] | ||||
| } | ||||
|  | ||||
| async function getAmazonProductList(subject) { | ||||
|     const url               = `https://www.amazon.fr/s?k=${subject}`; | ||||
|     const { data }          = await axios.get(`http://api.scraperapi.com/?api_key=${SCRAPER_API_KEY}&url=${url}`); | ||||
|     const { document }      = (new JSDOM(data)).window; | ||||
|     const amazonProductList = document.querySelectorAll('.s-result-item'); | ||||
|     const productsList      = []; | ||||
|     for (let indexProduct in amazonProductList) { | ||||
|         try {    | ||||
|             const elementProduct = amazonProductList[indexProduct]; | ||||
|             const productImage   = elementProduct.querySelector('.s-image'); | ||||
|             const originalPrice  = elementProduct.querySelector(".a-price-whole").innerHTML; | ||||
|             productsList.push({ | ||||
|                 name: productImage["alt"], | ||||
|                 image: productImage["src"], | ||||
|                 price: Number(originalPrice.replace(",", ".").replace(" ", "")) | ||||
|             }); | ||||
|         } catch (_error) { | ||||
|             continue; | ||||
|         } | ||||
|     } | ||||
|     return productsList; | ||||
| } | ||||
|  | ||||
| module.exports = rightPriceOutput = async ({ res, next }, _argsObject) => { | ||||
|     const subject = getRandomArrayElement(subjectList); | ||||
| async function getAmazonProductList (subject) { | ||||
|   const url = `https://www.amazon.fr/s?k=${subject}` | ||||
|   const { data } = await axios.get( | ||||
|     `http://api.scraperapi.com/?api_key=${SCRAPER_API_KEY}&url=${url}` | ||||
|   ) | ||||
|   const { document } = new JSDOM(data).window | ||||
|   const amazonProductList = document.querySelectorAll('.s-result-item') | ||||
|   const productsList = [] | ||||
|   for (const indexProduct in amazonProductList) { | ||||
|     try { | ||||
|         const productsList  = await getAmazonProductList(subject); | ||||
|         const randomProduct = getRandomArrayElement(productsList); | ||||
|         return res.status(200).json({ subject, ...randomProduct }); | ||||
|     } catch (error) { | ||||
|         console.error(error); | ||||
|         return errorHandling(next, serverError); | ||||
|       const elementProduct = amazonProductList[indexProduct] | ||||
|       const productImage = elementProduct.querySelector('.s-image') | ||||
|       const originalPrice = elementProduct.querySelector('.a-price-whole') | ||||
|         .innerHTML | ||||
|       productsList.push({ | ||||
|         name: productImage.alt, | ||||
|         image: productImage.src, | ||||
|         price: Number(originalPrice.replace(',', '.').replace(' ', '')) | ||||
|       }) | ||||
|     } catch (_error) { | ||||
|       continue | ||||
|     } | ||||
| } | ||||
|   } | ||||
|   return productsList | ||||
| } | ||||
|  | ||||
| module.exports = async ({ res, next }, _argsObject) => { | ||||
|   const subject = getRandomArrayElement(subjectList) | ||||
|   try { | ||||
|     const productsList = await getAmazonProductList(subject) | ||||
|     const randomProduct = getRandomArrayElement(productsList) | ||||
|     return res.status(200).json({ subject, ...randomProduct }) | ||||
|   } catch (error) { | ||||
|     console.error(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,56 +1,69 @@ | ||||
| const errorHandling      = require('../../utils/errorHandling'); | ||||
| const { requiredFields } = require('../../config/errors'); | ||||
| const formatNumberResult = require('../secondary/formatNumberResult'); | ||||
| const errorHandling = require('../../utils/errorHandling') | ||||
| const { requiredFields } = require('../../config/errors') | ||||
| const formatNumberResult = require('../secondary/formatNumberResult') | ||||
|  | ||||
| function minNumber(array) { | ||||
|     let minNumber = { index: 0, value: array[0] } | ||||
|     for (let index = 1; index < array.length; index++) { | ||||
|         const number = array[index]; | ||||
|         if (number < minNumber.value) { | ||||
|             minNumber = { index: index, value:  array[index] } | ||||
|         } | ||||
| function minNumber (array) { | ||||
|   let minNumber = { index: 0, value: array[0] } | ||||
|   for (let index = 1; index < array.length; index++) { | ||||
|     const number = array[index] | ||||
|     if (number < minNumber.value) { | ||||
|       minNumber = { index: index, value: array[index] } | ||||
|     } | ||||
|     return minNumber; | ||||
|   } | ||||
|   return minNumber | ||||
| } | ||||
|  | ||||
| function sortArray(array) { | ||||
|     const arrayDuplicated = [...array]; | ||||
|     const resultArray = []; | ||||
|     while (array.length !== resultArray.length) { | ||||
|         const min = minNumber(arrayDuplicated); | ||||
|         resultArray.push(min.value); | ||||
|         arrayDuplicated.splice(min.index, 1); | ||||
|     } | ||||
|     return resultArray; | ||||
| function sortArray (array) { | ||||
|   const arrayDuplicated = [...array] | ||||
|   const resultArray = [] | ||||
|   while (array.length !== resultArray.length) { | ||||
|     const min = minNumber(arrayDuplicated) | ||||
|     resultArray.push(min.value) | ||||
|     arrayDuplicated.splice(min.index, 1) | ||||
|   } | ||||
|   return resultArray | ||||
| } | ||||
|  | ||||
| /* OUTPUTS */ | ||||
| module.exports = sortArrayOutput = ({ res, next }, argsObject) => { | ||||
|     let { numbersList } = argsObject; | ||||
|      | ||||
|     // S'il n'y a pas les champs obligatoire | ||||
|     if (!(numbersList)) { | ||||
|         return errorHandling(next, requiredFields); | ||||
|     } | ||||
| module.exports = ({ res, next }, argsObject) => { | ||||
|   const { numbersList } = argsObject | ||||
|  | ||||
|     const numbersListArray = numbersList.split(',').map((number) => number.trim().replace(' ', '')).map(Number); | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!numbersList) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|     // Si ce n'est pas une liste de nombres | ||||
|     if (numbersListArray.includes(NaN)) { | ||||
|         return errorHandling(next, { message: "Vous devez rentrer une liste de nombres séparée par des virgules valide.", statusCode: 400 }); | ||||
|     } | ||||
|   const numbersListArray = numbersList | ||||
|     .split(',') | ||||
|     .map(number => number.trim().replace(' ', '')) | ||||
|     .map(Number) | ||||
|  | ||||
|     // Si la taille du tableau dépasse LIMIT_ARRAY_LENGTH | ||||
|     const LIMIT_ARRAY_LENGTH = 31; | ||||
|     if (numbersListArray.length >= LIMIT_ARRAY_LENGTH) { | ||||
|         return errorHandling(next, { message: `Par souci de performance, vous ne pouvez pas exécuter cette fonction avec une liste de nombres dépassant ${LIMIT_ARRAY_LENGTH - 1} nombres.`, statusCode: 400 }); | ||||
|     } | ||||
|   // Si ce n'est pas une liste de nombres | ||||
|   if (numbersListArray.includes(NaN)) { | ||||
|     return errorHandling(next, { | ||||
|       message: | ||||
|         'Vous devez rentrer une liste de nombres séparée par des virgules valide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|     const result          = sortArray(numbersListArray); | ||||
|     const resultFormatted = result.map((number) => formatNumberResult(number)); | ||||
|     return res.status(200).json({ | ||||
|         result, | ||||
|         resultFormatted, | ||||
|         resultHTML: `<p>La liste de nombres dans l'ordre croissant :<br/> ${resultFormatted.join(', ')}</p>` | ||||
|     }); | ||||
| } | ||||
|   // Si la taille du tableau dépasse LIMIT_ARRAY_LENGTH | ||||
|   const LIMIT_ARRAY_LENGTH = 31 | ||||
|   if (numbersListArray.length >= LIMIT_ARRAY_LENGTH) { | ||||
|     return errorHandling(next, { | ||||
|       message: `Par souci de performance, vous ne pouvez pas exécuter cette fonction avec une liste de nombres dépassant ${LIMIT_ARRAY_LENGTH - | ||||
|         1} nombres.`, | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   const result = sortArray(numbersListArray) | ||||
|   const resultFormatted = result.map(number => formatNumberResult(number)) | ||||
|   return res.status(200).json({ | ||||
|     result, | ||||
|     resultFormatted, | ||||
|     resultHTML: `<p>La liste de nombres dans l'ordre croissant :<br/> ${resultFormatted.join( | ||||
|       ', ' | ||||
|     )}</p>` | ||||
|   }) | ||||
| } | ||||
|   | ||||
| @@ -1,46 +1,73 @@ | ||||
| const axios               = require('axios'); | ||||
| const Queue               = require('smart-request-balancer'); | ||||
| const errorHandling       = require('../../utils/errorHandling'); | ||||
| const { requiredFields }  = require('../../config/errors'); | ||||
| const { WEATHER_API_KEY } = require('../../config/config'); | ||||
| const dateTimeUTC         = require('../secondary/dateTimeManagement'); | ||||
| const capitalize          = require('../secondary/capitalize'); | ||||
| const axios = require('axios') | ||||
| const Queue = require('smart-request-balancer') | ||||
| const errorHandling = require('../../utils/errorHandling') | ||||
| const { requiredFields } = require('../../config/errors') | ||||
| const { WEATHER_API_KEY } = require('../../config/config') | ||||
| const dateTimeUTC = require('../secondary/dateTimeManagement') | ||||
| const capitalize = require('../secondary/capitalize') | ||||
|  | ||||
| const queue = new Queue({ | ||||
|     /* | ||||
|   /* | ||||
|         rate: number of requests | ||||
|             per | ||||
|         limit: number of seconds | ||||
|     */ | ||||
|     rules: { | ||||
|         weatherRequest: {  | ||||
|             rate: 50,             | ||||
|             limit: 60,            | ||||
|             priority: 1 | ||||
|         }, | ||||
|   rules: { | ||||
|     weatherRequest: { | ||||
|       rate: 50, | ||||
|       limit: 60, | ||||
|       priority: 1 | ||||
|     } | ||||
| }); | ||||
|   } | ||||
| }) | ||||
|  | ||||
| /* OUTPUTS */ | ||||
| module.exports = weatherRequestOutput = ({ res, next }, argsObject) => { | ||||
|     let { cityName } = argsObject; | ||||
| module.exports = ({ res, next }, argsObject) => { | ||||
|   let { cityName } = argsObject | ||||
|  | ||||
|     // S'il n'y a pas les champs obligatoire | ||||
|     if (!(cityName)) { | ||||
|         return errorHandling(next, requiredFields); | ||||
|     } | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!cityName) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|     cityName = cityName.split(' ').join('+'); | ||||
|   cityName = cityName.split(' ').join('+') | ||||
|  | ||||
|     // Récupère les données météo grâce à l'API : openweathermap.org. (→ avec limite de 50 requêtes par minute) | ||||
|     queue.request(() => { | ||||
|         axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${cityName}&lang=fr&units=metric&appid=${WEATHER_API_KEY}`) | ||||
|             .then((response) =>{ | ||||
|                 const json = response.data; | ||||
|                 const showDateTimeValue = dateTimeUTC((json.timezone / 60 / 60).toString()).showDateTimeValue; | ||||
|                 const resultHTML = `<p>🌎 Position : <a href="https://www.google.com/maps/search/?api=1&query=${json.coord.lat},${json.coord.lon}" rel="noopener noreferrer" target="_blank">${json.name}, ${json.sys.country}</a><br/>⏰ Date et heure : ${showDateTimeValue} <br/>☁️ Météo : ${capitalize(json.weather[0].description)}<br/>🌡️ Température : ${json.main.temp} °C<br/> 💧 Humidité : ${json.main.humidity}% <br/> <img src="https://openweathermap.org/img/wn/${json.weather[0].icon}@2x.png"/></p>`; | ||||
|                 return res.status(200).json({ result: json, resultHTML }); | ||||
|             }) | ||||
|             .catch(() => errorHandling(next, { message: "La ville n'existe pas (dans l'API de openweathermap.org).", statusCode: 404 })); | ||||
|     }, 'everyone', 'weatherRequest'); | ||||
| } | ||||
|   // Récupère les données météo grâce à l'API : openweathermap.org. (→ avec limite de 50 requêtes par minute) | ||||
|   queue.request( | ||||
|     () => { | ||||
|       axios | ||||
|         .get( | ||||
|           `https://api.openweathermap.org/data/2.5/weather?q=${cityName}&lang=fr&units=metric&appid=${WEATHER_API_KEY}` | ||||
|         ) | ||||
|         .then(response => { | ||||
|           const json = response.data | ||||
|           const showDateTimeValue = dateTimeUTC( | ||||
|             (json.timezone / 60 / 60).toString() | ||||
|           ).showDateTimeValue | ||||
|           const resultHTML = `<p>🌎 Position : <a href="https://www.google.com/maps/search/?api=1&query=${ | ||||
|             json.coord.lat | ||||
|           },${json.coord.lon}" rel="noopener noreferrer" target="_blank">${ | ||||
|             json.name | ||||
|           }, ${ | ||||
|             json.sys.country | ||||
|           }</a><br/>⏰ Date et heure : ${showDateTimeValue} <br/>☁️ Météo : ${capitalize( | ||||
|             json.weather[0].description | ||||
|           )}<br/>🌡️ Température : ${json.main.temp} °C<br/> 💧 Humidité : ${ | ||||
|             json.main.humidity | ||||
|           }% <br/> <img src="https://openweathermap.org/img/wn/${ | ||||
|             json.weather[0].icon | ||||
|           }@2x.png"/></p>` | ||||
|           return res.status(200).json({ result: json, resultHTML }) | ||||
|         }) | ||||
|         .catch(() => | ||||
|           errorHandling(next, { | ||||
|             message: | ||||
|               "La ville n'existe pas (dans l'API de openweathermap.org).", | ||||
|             statusCode: 404 | ||||
|           }) | ||||
|         ) | ||||
|     }, | ||||
|     'everyone', | ||||
|     'weatherRequest' | ||||
|   ) | ||||
| } | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| /**  | ||||
| /** | ||||
|  * @description Majuscule à la 1ère lettre d'une string. | ||||
|  * @param {String} s  | ||||
|  * @returns {String}  | ||||
|  * @param {String} s | ||||
|  * @returns {String} | ||||
|  * @examples capitalize('hello world!') → 'Hello world!' | ||||
|  */ | ||||
| function capitalize(s) {  | ||||
|     if (typeof s !== 'string') return '' | ||||
|     return s.charAt(0).toUpperCase() + s.slice(1) | ||||
| function capitalize (s) { | ||||
|   if (typeof s !== 'string') return '' | ||||
|   return s.charAt(0).toUpperCase() + s.slice(1) | ||||
| } | ||||
|  | ||||
| module.exports = capitalize; | ||||
| module.exports = capitalize | ||||
|   | ||||
| @@ -1,43 +1,44 @@ | ||||
| /**  | ||||
| /** | ||||
|  * @description Donne la date et l'heure selon l'UTC (Universal Time Coordinated). | ||||
|  * @param {String} utc Heure de décalage par rapport à l'UTC  | ||||
|  * @param {String} utc Heure de décalage par rapport à l'UTC | ||||
|  * @returns {Function} → showDateTime(enteredOffset) → Retourne l'exécution de la fonction showDateTime | ||||
|  * @examples dateTimeUTC('0')  | ||||
|  */  | ||||
| function dateTimeUTC(utc) { | ||||
|     const timeNow = new Date(); | ||||
|     const utcOffset = timeNow.getTimezoneOffset(); | ||||
|     timeNow.setMinutes(timeNow.getMinutes() + utcOffset); | ||||
|     const enteredOffset = parseFloat(utc)*60; | ||||
|     timeNow.setMinutes(timeNow.getMinutes() + enteredOffset); | ||||
|     return showDateTime(timeNow); | ||||
| }  | ||||
|  | ||||
| /**  | ||||
|  * @description Affiche la date et l'heure (format : dd/mm/yyyy - 00:00:00). | ||||
|  * @requires {@link fonctions_annexes.js: showDateTime} | ||||
|  * @param {String} utc Heure de décalage par rapport à l'UTC  | ||||
|  * @returns {Object} Retourne un objet contenant l'année, le mois, le jour, l'heure, les minutes, les secondes et la date formaté | ||||
|  * @examples dateTimeUTC('0') → dateTimeUTC vous renvoie l'exécution de showDateTime | ||||
|  */  | ||||
| function showDateTime(timeNow) { | ||||
|     const year    = timeNow.getFullYear(); | ||||
|     const month   = ('0'+(timeNow.getMonth()+1)).slice(-2); | ||||
|     const day     = ('0'+timeNow.getDate()).slice(-2); | ||||
|     const hour    = ('0'+timeNow.getHours()).slice(-2); | ||||
|     const minute  = ('0'+timeNow.getMinutes()).slice(-2); | ||||
|     const second  = ('0'+timeNow.getSeconds()).slice(-2); | ||||
|     const showDateTimeValue = day + "/" + month + "/" + year + " - " + hour + ":" + minute + ":" + second; | ||||
|     const objectDateTime = { | ||||
|         year: year, | ||||
|         month: month, | ||||
|         day: day, | ||||
|         hour: hour, | ||||
|         minute: minute, | ||||
|         second: second, | ||||
|         showDateTimeValue: showDateTimeValue | ||||
|     }; | ||||
|     return objectDateTime; | ||||
|  * @examples dateTimeUTC('0') | ||||
|  */ | ||||
| function dateTimeUTC (utc) { | ||||
|   const timeNow = new Date() | ||||
|   const utcOffset = timeNow.getTimezoneOffset() | ||||
|   timeNow.setMinutes(timeNow.getMinutes() + utcOffset) | ||||
|   const enteredOffset = parseFloat(utc) * 60 | ||||
|   timeNow.setMinutes(timeNow.getMinutes() + enteredOffset) | ||||
|   return showDateTime(timeNow) | ||||
| } | ||||
|  | ||||
| module.exports = dateTimeUTC; | ||||
| /** | ||||
|  * @description Affiche la date et l'heure (format : dd/mm/yyyy - 00:00:00). | ||||
|  * @requires {@link fonctions_annexes.js: showDateTime} | ||||
|  * @param {String} utc Heure de décalage par rapport à l'UTC | ||||
|  * @returns {Object} Retourne un objet contenant l'année, le mois, le jour, l'heure, les minutes, les secondes et la date formaté | ||||
|  * @examples dateTimeUTC('0') → dateTimeUTC vous renvoie l'exécution de showDateTime | ||||
|  */ | ||||
| function showDateTime (timeNow) { | ||||
|   const year = timeNow.getFullYear() | ||||
|   const month = ('0' + (timeNow.getMonth() + 1)).slice(-2) | ||||
|   const day = ('0' + timeNow.getDate()).slice(-2) | ||||
|   const hour = ('0' + timeNow.getHours()).slice(-2) | ||||
|   const minute = ('0' + timeNow.getMinutes()).slice(-2) | ||||
|   const second = ('0' + timeNow.getSeconds()).slice(-2) | ||||
|   const showDateTimeValue = | ||||
|     day + '/' + month + '/' + year + ' - ' + hour + ':' + minute + ':' + second | ||||
|   const objectDateTime = { | ||||
|     year: year, | ||||
|     month: month, | ||||
|     day: day, | ||||
|     hour: hour, | ||||
|     minute: minute, | ||||
|     second: second, | ||||
|     showDateTimeValue: showDateTimeValue | ||||
|   } | ||||
|   return objectDateTime | ||||
| } | ||||
|  | ||||
| module.exports = dateTimeUTC | ||||
|   | ||||
| @@ -1,14 +1,14 @@ | ||||
| /**  | ||||
| /** | ||||
|  * @description Formate un nombre avec des espaces. | ||||
|  * @param {Number} number | ||||
|  * @param {String} separator Le séparateur utilisé pour la virgule (exemple: "." ou ",") | ||||
|  * @returns {String} - Le nombre formaté  | ||||
|  * @returns {String} - Le nombre formaté | ||||
|  * @examples formatNumberResult(76120) → '76 120' | ||||
|  */  | ||||
| function formatNumberResult(number, separator = ".") { | ||||
|     let parts = number.toString().split(separator); | ||||
|     parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, " "); | ||||
|     return parts.join(separator); | ||||
|  */ | ||||
| function formatNumberResult (number, separator = '.') { | ||||
|   const parts = number.toString().split(separator) | ||||
|   parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ' ') | ||||
|   return parts.join(separator) | ||||
| } | ||||
|  | ||||
| module.exports = formatNumberResult; | ||||
| module.exports = formatNumberResult | ||||
|   | ||||
| @@ -1,9 +1,14 @@ | ||||
| const Sequelize    = require('sequelize'); | ||||
| const { DATABASE } = require('../config/config'); | ||||
| const Sequelize = require('sequelize') | ||||
| const { DATABASE } = require('../config/config') | ||||
|  | ||||
| const sequelize = new Sequelize(DATABASE.name, DATABASE.user, DATABASE.password, { | ||||
| const sequelize = new Sequelize( | ||||
|   DATABASE.name, | ||||
|   DATABASE.user, | ||||
|   DATABASE.password, | ||||
|   { | ||||
|     dialect: 'mysql', | ||||
|     host: DATABASE.host | ||||
| }); | ||||
|   } | ||||
| ) | ||||
|  | ||||
| module.exports = sequelize; | ||||
| module.exports = sequelize | ||||
|   | ||||
| @@ -1,19 +1,19 @@ | ||||
| const fs   = require("fs"); | ||||
| const path = require("path"); | ||||
| const fs = require('fs') | ||||
| const path = require('path') | ||||
|  | ||||
| function deleteFilesNameStartWith(pattern, dirPath, callback) { | ||||
|     fs.readdir(path.resolve(dirPath), (_error, fileNames) => { | ||||
|         for (const name of fileNames) { | ||||
|             const splitedName = name.split('.'); | ||||
|             if (splitedName.length === 2) { | ||||
|                 const fileName = splitedName[0]; | ||||
|                 if (fileName === pattern && name !== 'default.png') { | ||||
|                     return fs.unlink(path.join(dirPath, name), callback); | ||||
|                 } | ||||
|             } | ||||
| function deleteFilesNameStartWith (pattern, dirPath, callback) { | ||||
|   fs.readdir(path.resolve(dirPath), (_error, fileNames) => { | ||||
|     for (const name of fileNames) { | ||||
|       const splitedName = name.split('.') | ||||
|       if (splitedName.length === 2) { | ||||
|         const fileName = splitedName[0] | ||||
|         if (fileName === pattern && name !== 'default.png') { | ||||
|           return fs.unlink(path.join(dirPath, name), callback) | ||||
|         } | ||||
|         return callback(); | ||||
|     }); | ||||
|       } | ||||
|     } | ||||
|     return callback() | ||||
|   }) | ||||
| } | ||||
|  | ||||
| module.exports = deleteFilesNameStartWith; | ||||
| module.exports = deleteFilesNameStartWith | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| function errorHandling(next, { statusCode, message }) { | ||||
|     const error = new Error(message); | ||||
|     error.statusCode = statusCode; | ||||
|     next(error); | ||||
| function errorHandling (next, { statusCode, message }) { | ||||
|   const error = new Error(message) | ||||
|   error.statusCode = statusCode | ||||
|   next(error) | ||||
| } | ||||
|  | ||||
| module.exports = errorHandling; | ||||
| module.exports = errorHandling | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| const errorHandling     = require('../utils/errorHandling'); | ||||
| const { serverError }   = require('../config/errors'); | ||||
| const helperQueryNumber = require('../utils/helperQueryNumber'); | ||||
| const errorHandling = require('../utils/errorHandling') | ||||
| const { serverError } = require('../config/errors') | ||||
| const helperQueryNumber = require('../utils/helperQueryNumber') | ||||
|  | ||||
| const DEFAULT_OPTIONS = { | ||||
|     order: [['createdAt', 'DESC']] | ||||
| }; | ||||
|   order: [['createdAt', 'DESC']] | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @description Permet de faire un système de pagination sur un model Sequelize | ||||
| @@ -12,23 +12,27 @@ const DEFAULT_OPTIONS = { | ||||
|  * @param {*} Model Model Sequelize | ||||
|  * @param {Object} options Options avec clause where etc. | ||||
|  */ | ||||
| async function getPagesHelper({ req, res, next }, Model, options = DEFAULT_OPTIONS) { | ||||
|     const page   = helperQueryNumber(req.query.page, 1); | ||||
|     const limit  = helperQueryNumber(req.query.limit, 10); | ||||
|     const offset = (page - 1) * limit; | ||||
|     try { | ||||
|         const result = await Model.findAndCountAll({ | ||||
|             limit,  | ||||
|             offset,  | ||||
|             ...options | ||||
|         }); | ||||
|         const { count, rows } = result; | ||||
|         const hasMore         = (page * limit) < count; | ||||
|         return res.status(200).json({ totalItems: count, hasMore, rows }); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|     } | ||||
| async function getPagesHelper ( | ||||
|   { req, res, next }, | ||||
|   Model, | ||||
|   options = DEFAULT_OPTIONS | ||||
| ) { | ||||
|   const page = helperQueryNumber(req.query.page, 1) | ||||
|   const limit = helperQueryNumber(req.query.limit, 10) | ||||
|   const offset = (page - 1) * limit | ||||
|   try { | ||||
|     const result = await Model.findAndCountAll({ | ||||
|       limit, | ||||
|       offset, | ||||
|       ...options | ||||
|     }) | ||||
|     const { count, rows } = result | ||||
|     const hasMore = page * limit < count | ||||
|     return res.status(200).json({ totalItems: count, hasMore, rows }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| module.exports = getPagesHelper; | ||||
| module.exports = getPagesHelper | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| function helperQueryNumber(value, defaultValue) { | ||||
|     if (value && !isNaN(value)) return parseInt(value); | ||||
|     return defaultValue; | ||||
| function helperQueryNumber (value, defaultValue) { | ||||
|   if (value && !isNaN(value)) return parseInt(value) | ||||
|   return defaultValue | ||||
| } | ||||
|  | ||||
| module.exports = helperQueryNumber; | ||||
| module.exports = helperQueryNumber | ||||
|   | ||||
| @@ -1,320 +1,424 @@ | ||||
| const path                           = require('path'); | ||||
| const fs                             = require('fs'); | ||||
| const { validationResult }           = require('express-validator'); | ||||
| const errorHandling                  = require('../assets/utils/errorHandling'); | ||||
| const { serverError }                = require('../assets/config/errors'); | ||||
| const Functions                      = require('../models/functions'); | ||||
| const Categories                     = require('../models/categories'); | ||||
| const Quotes                         = require('../models/quotes'); | ||||
| const Users                          = require('../models/users'); | ||||
| const helperQueryNumber              = require('../assets/utils/helperQueryNumber'); | ||||
| const getPagesHelper                 = require('../assets/utils/getPagesHelper'); | ||||
| const Sequelize                      = require('sequelize'); | ||||
| const deleteFilesNameStartWith       = require('../assets/utils/deleteFilesNameStartWith'); | ||||
| const { EMAIL_INFO, FRONT_END_HOST } = require('../assets/config/config'); | ||||
| const transporter                    = require('../assets/config/transporter'); | ||||
| const { emailQuoteTemplate }         = require('../assets/config/emails'); | ||||
| const path = require('path') | ||||
| const fs = require('fs') | ||||
| const { validationResult } = require('express-validator') | ||||
| const errorHandling = require('../assets/utils/errorHandling') | ||||
| const { serverError } = require('../assets/config/errors') | ||||
| const Functions = require('../models/functions') | ||||
| const Categories = require('../models/categories') | ||||
| const Quotes = require('../models/quotes') | ||||
| const Users = require('../models/users') | ||||
| const helperQueryNumber = require('../assets/utils/helperQueryNumber') | ||||
| const getPagesHelper = require('../assets/utils/getPagesHelper') | ||||
| const Sequelize = require('sequelize') | ||||
| const deleteFilesNameStartWith = require('../assets/utils/deleteFilesNameStartWith') | ||||
| const { EMAIL_INFO, FRONT_END_HOST } = require('../assets/config/config') | ||||
| const transporter = require('../assets/config/transporter') | ||||
| const { emailQuoteTemplate } = require('../assets/config/emails') | ||||
|  | ||||
| const handleEditFunction = async (res, resultFunction, { title, slug, description, type, categorieId, isOnline }, imageName = false) => { | ||||
|     resultFunction.title       = title; | ||||
|     resultFunction.slug        = slug; | ||||
|     resultFunction.description = description; | ||||
|     resultFunction.type        = type; | ||||
|     resultFunction.categorieId = categorieId; | ||||
|     resultFunction.isOnline    = isOnline; | ||||
|     if (imageName) { | ||||
|         resultFunction.image = `/images/functions/${imageName}`; | ||||
|     } | ||||
|     const result = await resultFunction.save(); | ||||
|     res.status(200).json({ message: "La fonction a bien été modifié!", result }); | ||||
| const handleEditFunction = async ( | ||||
|   res, | ||||
|   resultFunction, | ||||
|   { title, slug, description, type, categorieId, isOnline }, | ||||
|   imageName = false | ||||
| ) => { | ||||
|   resultFunction.title = title | ||||
|   resultFunction.slug = slug | ||||
|   resultFunction.description = description | ||||
|   resultFunction.type = type | ||||
|   resultFunction.categorieId = categorieId | ||||
|   resultFunction.isOnline = isOnline | ||||
|   if (imageName) { | ||||
|     resultFunction.image = `/images/functions/${imageName}` | ||||
|   } | ||||
|   const result = await resultFunction.save() | ||||
|   res.status(200).json({ message: 'La fonction a bien été modifié!', result }) | ||||
| } | ||||
|  | ||||
| exports.getFunctions = async (req, res, next) => { | ||||
|     const categoryId = helperQueryNumber(req.query.categoryId, 0); | ||||
|     let   search     = req.query.search; | ||||
|     try { search = search.toLowerCase(); } catch {}; | ||||
|     const options = { | ||||
|         where: { | ||||
|             // Trie par catégorie | ||||
|             ... (categoryId !== 0) && { categorieId: categoryId }, | ||||
|             // Recherche | ||||
|             ... (search != undefined) && {  | ||||
|                 [Sequelize.Op.or]: [ | ||||
|                     { title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', `%${search}%`) }, | ||||
|                     { slug: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('slug')), 'LIKE', `%${search}%`) }, | ||||
|                     { description: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('description')), 'LIKE', `%${search}%`) } | ||||
|                 ] | ||||
|             } | ||||
|         }, | ||||
|         include: [ | ||||
|             { model: Categories, attributes: ["name", "color"] } | ||||
|         ], | ||||
|         attributes: { | ||||
|             exclude: ["updatedAt", "utilizationForm", "article", "isOnline"] | ||||
|         }, | ||||
|         order: [['createdAt', 'DESC']] | ||||
|     }; | ||||
|     return await getPagesHelper({ req, res, next }, Functions, options); | ||||
|   const categoryId = helperQueryNumber(req.query.categoryId, 0) | ||||
|   let search = req.query.search | ||||
|   try { | ||||
|     search = search.toLowerCase() | ||||
|   } catch {} | ||||
|   const options = { | ||||
|     where: { | ||||
|       // Trie par catégorie | ||||
|       ...(categoryId !== 0 && { categorieId: categoryId }), | ||||
|       // Recherche | ||||
|       ...(search != null && { | ||||
|         [Sequelize.Op.or]: [ | ||||
|           { | ||||
|             title: Sequelize.where( | ||||
|               Sequelize.fn('LOWER', Sequelize.col('title')), | ||||
|               'LIKE', | ||||
|               `%${search}%` | ||||
|             ) | ||||
|           }, | ||||
|           { | ||||
|             slug: Sequelize.where( | ||||
|               Sequelize.fn('LOWER', Sequelize.col('slug')), | ||||
|               'LIKE', | ||||
|               `%${search}%` | ||||
|             ) | ||||
|           }, | ||||
|           { | ||||
|             description: Sequelize.where( | ||||
|               Sequelize.fn('LOWER', Sequelize.col('description')), | ||||
|               'LIKE', | ||||
|               `%${search}%` | ||||
|             ) | ||||
|           } | ||||
|         ] | ||||
|       }) | ||||
|     }, | ||||
|     include: [{ model: Categories, attributes: ['name', 'color'] }], | ||||
|     attributes: { | ||||
|       exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline'] | ||||
|     }, | ||||
|     order: [['createdAt', 'DESC']] | ||||
|   } | ||||
|   return await getPagesHelper({ req, res, next }, Functions, options) | ||||
| } | ||||
|  | ||||
| exports.getFunctionBySlug = (req, res, next) => { | ||||
|     const { slug } = req.params; | ||||
|     Functions.findOne({  | ||||
|         where: { slug }, | ||||
|         include: [ | ||||
|             { model: Categories, attributes: ["name", "color"] } | ||||
|         ] | ||||
|     }) | ||||
|         .then((result) => { | ||||
|             if (!result) { | ||||
|                 return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); | ||||
|             } | ||||
|             try { result.utilizationForm = JSON.parse(result.utilizationForm); } catch {} | ||||
|             return res.status(200).json(result); | ||||
|   const { slug } = req.params | ||||
|   Functions.findOne({ | ||||
|     where: { slug }, | ||||
|     include: [{ model: Categories, attributes: ['name', 'color'] }] | ||||
|   }) | ||||
|     .then(result => { | ||||
|       if (!result) { | ||||
|         return errorHandling(next, { | ||||
|           message: "La fonction n'existe pas.", | ||||
|           statusCode: 404 | ||||
|         }) | ||||
|         .catch((error) => { | ||||
|             console.log(error); | ||||
|             return errorHandling(next, serverError); | ||||
|         }); | ||||
|       } | ||||
|       try { | ||||
|         result.utilizationForm = JSON.parse(result.utilizationForm) | ||||
|       } catch {} | ||||
|       return res.status(200).json(result) | ||||
|     }) | ||||
|     .catch(error => { | ||||
|       console.log(error) | ||||
|       return errorHandling(next, serverError) | ||||
|     }) | ||||
| } | ||||
|  | ||||
| exports.postFunction = (req, res, next) => { | ||||
|     const { title, slug, description, type, categorieId } = req.body; | ||||
|     const image = req.files.image; | ||||
|     const errors = validationResult(req); | ||||
|     if (!errors.isEmpty()) { | ||||
|         return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }); | ||||
|   const { title, slug, description, type, categorieId } = req.body | ||||
|   const image = req.files.image | ||||
|   const errors = validationResult(req) | ||||
|   if (!errors.isEmpty()) { | ||||
|     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 splitedImageName = image.name.split('.') | ||||
|   if (splitedImageName.length !== 2) return errorHandling(next, serverError) | ||||
|   const imageName = slug + '.' + splitedImageName[1] | ||||
|   image.mv( | ||||
|     path.join(__dirname, '..', 'assets', 'images', 'functions') + | ||||
|       '/' + | ||||
|       imageName, | ||||
|     async error => { | ||||
|       if (error) return errorHandling(next, serverError) | ||||
|       try { | ||||
|         const result = await Functions.create({ | ||||
|           title, | ||||
|           slug, | ||||
|           description, | ||||
|           type, | ||||
|           categorieId, | ||||
|           image: `/images/functions/${imageName}` | ||||
|         }) | ||||
|         return res | ||||
|           .status(201) | ||||
|           .json({ message: 'La fonction a été correctement ajouté!', result }) | ||||
|       } catch (error) { | ||||
|         console.log(error) | ||||
|         return errorHandling(next, serverError) | ||||
|       } | ||||
|     } | ||||
|     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 splitedImageName = image.name.split('.'); | ||||
|     if (splitedImageName.length !== 2) return errorHandling(next, serverError); | ||||
|     const imageName = slug + '.' + splitedImageName[1]; | ||||
|     image.mv(path.join(__dirname, '..', 'assets', 'images', 'functions') + '/' + imageName, async (error) => { | ||||
|         if (error) return errorHandling(next, serverError); | ||||
|         try { | ||||
|             const result = await Functions.create({ title, slug, description, type, categorieId, image: `/images/functions/${imageName}` }); | ||||
|             return res.status(201).json({ message: "La fonction a été correctement ajouté!", result }); | ||||
|         } catch (error) { | ||||
|             console.log(error); | ||||
|             return errorHandling(next, serverError); | ||||
|         } | ||||
|     }); | ||||
|   ) | ||||
| } | ||||
|  | ||||
| exports.putFunction = async (req, res, next) => { | ||||
|     const { id }                                                    = req.params; | ||||
|     const { title, slug, description, type, categorieId, isOnline } = req.body; | ||||
|     const image                                                     = req.files.image; | ||||
|     const errors = validationResult(req); | ||||
|     if (!errors.isEmpty()) { | ||||
|         return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }); | ||||
|   const { id } = req.params | ||||
|   const { title, slug, description, type, categorieId, isOnline } = req.body | ||||
|   const image = req.files.image | ||||
|   const errors = validationResult(req) | ||||
|   if (!errors.isEmpty()) { | ||||
|     return errorHandling(next, { | ||||
|       message: errors.array()[0].msg, | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|   try { | ||||
|     // Vérifie si la fonction existe | ||||
|     const resultFunction = await Functions.findOne({ where: { id } }) | ||||
|     if (!resultFunction) { | ||||
|       return errorHandling(next, { | ||||
|         message: "La fonction n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|     try { | ||||
|         // Vérifie si la fonction existe | ||||
|         const resultFunction = await Functions.findOne({ where: { id } }); | ||||
|         if (!resultFunction) { | ||||
|             return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); | ||||
|         } | ||||
|  | ||||
|         // Vérifie si le slug existe déjà | ||||
|         const FunctionSlug = await Functions.findOne({ where: { slug } }); | ||||
|         if (!FunctionSlug && FunctionSlug.id != resultFunction.id) { | ||||
|             return errorHandling(next, { message: "Le slug existe déjà...", statusCode: 404 }); | ||||
|         } | ||||
|  | ||||
|         // Sauvegarde de la fonction | ||||
|         if (image != undefined) { | ||||
|             if (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 splitedImageName = image.name.split('.'); | ||||
|             if (splitedImageName.length !== 2) return errorHandling(next, serverError); | ||||
|             const imageName = slug + '.' + splitedImageName[1]; | ||||
|             // Supprime les anciennes images | ||||
|             const functionPath = path.join(__dirname, '..', 'assets', 'images', 'functions'); | ||||
|             deleteFilesNameStartWith(slug, functionPath, () => { | ||||
|                 image.mv(path.join(functionPath, imageName), async (error) => { | ||||
|                     if (error) return errorHandling(next, serverError); | ||||
|                     return await handleEditFunction(res, resultFunction, { title, slug, description, type, categorieId, isOnline }, imageName); | ||||
|                 }); | ||||
|             }); | ||||
|         } else { | ||||
|             return await handleEditFunction(res, resultFunction, { title, slug, description, type, categorieId, isOnline }); | ||||
|         } | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|     // Vérifie si le slug existe déjà | ||||
|     const FunctionSlug = await Functions.findOne({ where: { slug } }) | ||||
|     if (!FunctionSlug && FunctionSlug.id !== resultFunction.id) { | ||||
|       return errorHandling(next, { | ||||
|         message: 'Le slug existe déjà...', | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|  | ||||
|     // Sauvegarde de la fonction | ||||
|     if (image != null) { | ||||
|       if ( | ||||
|         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 splitedImageName = image.name.split('.') | ||||
|       if (splitedImageName.length !== 2) return errorHandling(next, serverError) | ||||
|       const imageName = slug + '.' + splitedImageName[1] | ||||
|       // Supprime les anciennes images | ||||
|       const functionPath = path.join( | ||||
|         __dirname, | ||||
|         '..', | ||||
|         'assets', | ||||
|         'images', | ||||
|         'functions' | ||||
|       ) | ||||
|       deleteFilesNameStartWith(slug, functionPath, () => { | ||||
|         image.mv(path.join(functionPath, imageName), async error => { | ||||
|           if (error) return errorHandling(next, serverError) | ||||
|           return await handleEditFunction( | ||||
|             res, | ||||
|             resultFunction, | ||||
|             { title, slug, description, type, categorieId, isOnline }, | ||||
|             imageName | ||||
|           ) | ||||
|         }) | ||||
|       }) | ||||
|     } else { | ||||
|       return await handleEditFunction(res, resultFunction, { | ||||
|         title, | ||||
|         slug, | ||||
|         description, | ||||
|         type, | ||||
|         categorieId, | ||||
|         isOnline | ||||
|       }) | ||||
|     } | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.putFunctionArticle = async (req, res, next) => { | ||||
|     const { id }      = req.params; | ||||
|     const { article } = req.body; | ||||
|   const { id } = req.params | ||||
|   const { article } = req.body | ||||
|  | ||||
|     try { | ||||
|         // Vérifie si la fonction existe | ||||
|         const resultFunction = await Functions.findOne({ where: { id } }); | ||||
|         if (!resultFunction) { | ||||
|             return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); | ||||
|         } | ||||
|         resultFunction.article = article; | ||||
|         const result = await resultFunction.save(); | ||||
|         return res.status(200).json(result); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|   try { | ||||
|     // Vérifie si la fonction existe | ||||
|     const resultFunction = await Functions.findOne({ where: { id } }) | ||||
|     if (!resultFunction) { | ||||
|       return errorHandling(next, { | ||||
|         message: "La fonction n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|     resultFunction.article = article | ||||
|     const result = await resultFunction.save() | ||||
|     return res.status(200).json(result) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.putFunctionForm = async (req, res, next) => { | ||||
|     const { id }   = req.params; | ||||
|     const { form } = req.body; | ||||
|   const { id } = req.params | ||||
|   const { form } = req.body | ||||
|  | ||||
|     try { | ||||
|         // Vérifie si la fonction existe | ||||
|         const resultFunction = await Functions.findOne({ where: { id } }); | ||||
|         if (!resultFunction) { | ||||
|             return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); | ||||
|         } | ||||
|         resultFunction.utilizationForm = JSON.stringify(form); | ||||
|         const result = await resultFunction.save(); | ||||
|         return res.status(200).json(result); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|   try { | ||||
|     // Vérifie si la fonction existe | ||||
|     const resultFunction = await Functions.findOne({ where: { id } }) | ||||
|     if (!resultFunction) { | ||||
|       return errorHandling(next, { | ||||
|         message: "La fonction n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|     resultFunction.utilizationForm = JSON.stringify(form) | ||||
|     const result = await resultFunction.save() | ||||
|     return res.status(200).json(result) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.deleteFunction = async (req, res, next) => { | ||||
|     const { id } = req.params; | ||||
|     try { | ||||
|         const result = await Functions.findOne({ where: { id } }); | ||||
|         if (!result) { | ||||
|             return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); | ||||
|         } | ||||
|         if (result.image !== "/images/functions/default.png") { | ||||
|             const filePath = path.join(__dirname, '..', 'assets', result.image); | ||||
|             fs.unlinkSync(filePath); // supprime le fichier | ||||
|         } | ||||
|         await result.destroy(); | ||||
|         res.status(200).json({ message: "La fonction a été correctement supprimé!"}); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|   const { id } = req.params | ||||
|   try { | ||||
|     const result = await Functions.findOne({ where: { id } }) | ||||
|     if (!result) { | ||||
|       return errorHandling(next, { | ||||
|         message: "La fonction n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|     if (result.image !== '/images/functions/default.png') { | ||||
|       const filePath = path.join(__dirname, '..', 'assets', result.image) | ||||
|       fs.unlinkSync(filePath) // supprime le fichier | ||||
|     } | ||||
|     await result.destroy() | ||||
|     res | ||||
|       .status(200) | ||||
|       .json({ message: 'La fonction a été correctement supprimé!' }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.postCategory = async (req, res, next) => { | ||||
|     const { name, color } = req.body; | ||||
|     if (!(name && color)) { | ||||
|         return errorHandling(next, { message: "La catégorie doit avoir un nom et une couleur." }); | ||||
|     } | ||||
|     try { | ||||
|         const result = await Categories.create({ name, color }); | ||||
|         return res.status(201).json({ message: "La catégorie a bien été crée!", result }); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|     } | ||||
|   const { name, color } = req.body | ||||
|   if (!(name && color)) { | ||||
|     return errorHandling(next, { | ||||
|       message: 'La catégorie doit avoir un nom et une couleur.' | ||||
|     }) | ||||
|   } | ||||
|   try { | ||||
|     const result = await Categories.create({ name, color }) | ||||
|     return res | ||||
|       .status(201) | ||||
|       .json({ message: 'La catégorie a bien été crée!', result }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.putCategory = async (req, res, next) => { | ||||
|     const { name, color } = req.body; | ||||
|     const { id }          = req.params; | ||||
|     if (!(name && color && id)) { | ||||
|         return errorHandling(next, { message: "La catégorie doit avoir un nom, une couleur et un id." }); | ||||
|     } | ||||
|     try { | ||||
|         const category = await Categories.findOne({ where: { id } }); | ||||
|         if (!category) { | ||||
|             return errorHandling(next, { message: "La catégorie n'existe pas." }); | ||||
|         } | ||||
|         category.name = name; | ||||
|         category.color = color; | ||||
|         const result = await category.save(); | ||||
|         return res.status(200).json({ message: "La catégorie a bien été modifiée!", result }); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|   const { name, color } = req.body | ||||
|   const { id } = req.params | ||||
|   if (!(name && color && id)) { | ||||
|     return errorHandling(next, { | ||||
|       message: 'La catégorie doit avoir un nom, une couleur et un id.' | ||||
|     }) | ||||
|   } | ||||
|   try { | ||||
|     const category = await Categories.findOne({ where: { id } }) | ||||
|     if (!category) { | ||||
|       return errorHandling(next, { message: "La catégorie n'existe pas." }) | ||||
|     } | ||||
|     category.name = name | ||||
|     category.color = color | ||||
|     const result = await category.save() | ||||
|     return res | ||||
|       .status(200) | ||||
|       .json({ message: 'La catégorie a bien été modifiée!', result }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.deleteCategory = async (req, res, next) => { | ||||
|     const { id } = req.params; | ||||
|     try { | ||||
|         const category = await Categories.findOne({ where: { id } }); | ||||
|         if (!category) { | ||||
|             return errorHandling(next, { message: "La catégorie n'existe pas." }); | ||||
|         } | ||||
|         await category.destroy(); | ||||
|         return res.status(200).json({ message: "La catégorie a bien été supprimée!" }); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|   const { id } = req.params | ||||
|   try { | ||||
|     const category = await Categories.findOne({ where: { id } }) | ||||
|     if (!category) { | ||||
|       return errorHandling(next, { message: "La catégorie n'existe pas." }) | ||||
|     } | ||||
|     await category.destroy() | ||||
|     return res | ||||
|       .status(200) | ||||
|       .json({ message: 'La catégorie a bien été supprimée!' }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.getQuotes = async (req, res, next) => { | ||||
|     const options = { | ||||
|         where: {  | ||||
|             isValidated: 0, | ||||
|         }, | ||||
|         include: [ | ||||
|             { model: Users, attributes: ["name", "logo"] } | ||||
|         ], | ||||
|         order: [['createdAt', 'DESC']] | ||||
|     }; | ||||
|     return await getPagesHelper({ req, res, next }, Quotes, options); | ||||
|   const options = { | ||||
|     where: { | ||||
|       isValidated: 0 | ||||
|     }, | ||||
|     include: [{ model: Users, attributes: ['name', 'logo'] }], | ||||
|     order: [['createdAt', 'DESC']] | ||||
|   } | ||||
|   return await getPagesHelper({ req, res, next }, Quotes, options) | ||||
| } | ||||
|  | ||||
| exports.putQuote = async (req, res, next) => { | ||||
|     const { id }      = req.params; | ||||
|     const { isValid } = req.body; | ||||
|     try { | ||||
|         if (typeof isValid !== 'boolean') { | ||||
|             return errorHandling(next, { message: "isValid doit être un booléen.", statusCode: 400 }); | ||||
|         } | ||||
|         const quote = await Quotes.findOne({  | ||||
|             where: {  | ||||
|                 id,  | ||||
|                 isValidated: 0  | ||||
|             }, | ||||
|             include: [ | ||||
|                 { model: Users, attributes: ["name", "email"] } | ||||
|             ] | ||||
|         }); | ||||
|         if (!quote) { | ||||
|             return errorHandling(next, { message: "La citation n'existe pas (ou est déjà validé).", statusCode: 404 }); | ||||
|         } | ||||
|  | ||||
|         await transporter.sendMail({ | ||||
|             from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`, | ||||
|             to: quote.user.email, | ||||
|             subject: "FunctionProject - Citation proposée", | ||||
|             html: emailQuoteTemplate(isValid, quote, FRONT_END_HOST) | ||||
|         }); | ||||
|  | ||||
|         if (isValid) { | ||||
|             quote.isValidated = true; | ||||
|             await quote.save(); | ||||
|             return res.status(200).json({ message: "La citation a bien été validée!" }); | ||||
|         } else { | ||||
|             await quote.destroy(); | ||||
|             return res.status(200).json({ imessage: "La citation a bien été supprimée!" }); | ||||
|         } | ||||
|   | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError);    | ||||
|   const { id } = req.params | ||||
|   const { isValid } = req.body | ||||
|   try { | ||||
|     if (typeof isValid !== 'boolean') { | ||||
|       return errorHandling(next, { | ||||
|         message: 'isValid doit être un booléen.', | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
| } | ||||
|     const quote = await Quotes.findOne({ | ||||
|       where: { | ||||
|         id, | ||||
|         isValidated: 0 | ||||
|       }, | ||||
|       include: [{ model: Users, attributes: ['name', 'email'] }] | ||||
|     }) | ||||
|     if (!quote) { | ||||
|       return errorHandling(next, { | ||||
|         message: "La citation n'existe pas (ou est déjà validé).", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|  | ||||
|     await transporter.sendMail({ | ||||
|       from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`, | ||||
|       to: quote.user.email, | ||||
|       subject: 'FunctionProject - Citation proposée', | ||||
|       html: emailQuoteTemplate(isValid, quote, FRONT_END_HOST) | ||||
|     }) | ||||
|  | ||||
|     if (isValid) { | ||||
|       quote.isValidated = true | ||||
|       await quote.save() | ||||
|       return res | ||||
|         .status(200) | ||||
|         .json({ message: 'La citation a bien été validée!' }) | ||||
|     } else { | ||||
|       await quote.destroy() | ||||
|       return res | ||||
|         .status(200) | ||||
|         .json({ imessage: 'La citation a bien été supprimée!' }) | ||||
|     } | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,14 +1,14 @@ | ||||
| const errorHandling   = require('../assets/utils/errorHandling'); | ||||
| const Categories      = require('../models/categories'); | ||||
| const { serverError } = require('../assets/config/errors'); | ||||
| const errorHandling = require('../assets/utils/errorHandling') | ||||
| const Categories = require('../models/categories') | ||||
| const { serverError } = require('../assets/config/errors') | ||||
|  | ||||
| exports.getCategories = (_req, res, next) => { | ||||
|     Categories.findAll() | ||||
|         .then((result) => { | ||||
|             res.status(200).json(result); | ||||
|         }) | ||||
|         .catch((error) => { | ||||
|             console.log(error); | ||||
|             return errorHandling(next, serverError); | ||||
|         }); | ||||
| } | ||||
|   Categories.findAll() | ||||
|     .then(result => { | ||||
|       res.status(200).json(result) | ||||
|     }) | ||||
|     .catch(error => { | ||||
|       console.log(error) | ||||
|       return errorHandling(next, serverError) | ||||
|     }) | ||||
| } | ||||
|   | ||||
| @@ -1,72 +1,99 @@ | ||||
| const errorHandling   = require('../assets/utils/errorHandling'); | ||||
| const Comments        = require('../models/comments'); | ||||
| const Users           = require('../models/users'); | ||||
| const Functions       = require('../models/functions'); | ||||
| const getPagesHelper  = require('../assets/utils/getPagesHelper'); | ||||
| const { serverError } = require('../assets/config/errors'); | ||||
| const errorHandling = require('../assets/utils/errorHandling') | ||||
| const Comments = require('../models/comments') | ||||
| const Users = require('../models/users') | ||||
| const Functions = require('../models/functions') | ||||
| const getPagesHelper = require('../assets/utils/getPagesHelper') | ||||
| const { serverError } = require('../assets/config/errors') | ||||
|  | ||||
| exports.getCommentsByFunctionId = async (req, res, next) => { | ||||
|     const { functionId } = req.params; | ||||
|     const options = { | ||||
|         where: { functionId }, | ||||
|         include: [ | ||||
|             { model: Users, attributes: ["name", "logo"] } | ||||
|         ], | ||||
|         order: [['createdAt', 'DESC']] | ||||
|     }; | ||||
|     return await getPagesHelper({ req, res, next }, Comments, options); | ||||
|   const { functionId } = req.params | ||||
|   const options = { | ||||
|     where: { functionId }, | ||||
|     include: [{ model: Users, attributes: ['name', 'logo'] }], | ||||
|     order: [['createdAt', 'DESC']] | ||||
|   } | ||||
|   return await getPagesHelper({ req, res, next }, Comments, options) | ||||
| } | ||||
|  | ||||
| exports.postCommentsByFunctionId = async (req, res, next) => { | ||||
|     const { functionId } = req.params; | ||||
|     const { message }    = req.body; | ||||
|     try { | ||||
|         const resultFunction = await Functions.findOne({ where: { id: functionId } }); | ||||
|         if (!resultFunction) { | ||||
|             return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); | ||||
|         } | ||||
|         if (!message) { | ||||
|             return errorHandling(next, { message: "Vous ne pouvez pas poster de commentaire vide.", statusCode: 400 }); | ||||
|         } | ||||
|         const comment = await Comments.create({ message, userId: req.userId, functionId }); | ||||
|         return res.status(201).json(comment); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError);    | ||||
|   const { functionId } = req.params | ||||
|   const { message } = req.body | ||||
|   try { | ||||
|     const resultFunction = await Functions.findOne({ | ||||
|       where: { id: functionId } | ||||
|     }) | ||||
|     if (!resultFunction) { | ||||
|       return errorHandling(next, { | ||||
|         message: "La fonction n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|     if (!message) { | ||||
|       return errorHandling(next, { | ||||
|         message: 'Vous ne pouvez pas poster de commentaire vide.', | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|     const comment = await Comments.create({ | ||||
|       message, | ||||
|       userId: req.userId, | ||||
|       functionId | ||||
|     }) | ||||
|     return res.status(201).json(comment) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.deleteCommentById = async (req, res, next) => { | ||||
|     const { commentId } = req.params; | ||||
|     try { | ||||
|         const comment = await Comments.findOne({ where: { userId: req.userId, id: parseInt(commentId) } }); | ||||
|         if (!comment) { | ||||
|             return errorHandling(next, { message: "Le commentaire n'existe pas.", statusCode: 404 }); | ||||
|         } | ||||
|         await comment.destroy(); | ||||
|         return res.status(200).json({ message: "Le commentaire a bien été supprimé." }); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError);    | ||||
|   const { commentId } = req.params | ||||
|   try { | ||||
|     const comment = await Comments.findOne({ | ||||
|       where: { userId: req.userId, id: parseInt(commentId) } | ||||
|     }) | ||||
|     if (!comment) { | ||||
|       return errorHandling(next, { | ||||
|         message: "Le commentaire n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|     await comment.destroy() | ||||
|     return res | ||||
|       .status(200) | ||||
|       .json({ message: 'Le commentaire a bien été supprimé.' }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.putCommentsById = async (req, res, next) => { | ||||
|     const { commentId } = req.params; | ||||
|     const { message }   = req.body; | ||||
|     if (!message) { | ||||
|         return errorHandling(next, { message: "Vous ne pouvez pas poster de commentaire vide.", statusCode: 400 }); | ||||
|   const { commentId } = req.params | ||||
|   const { message } = req.body | ||||
|   if (!message) { | ||||
|     return errorHandling(next, { | ||||
|       message: 'Vous ne pouvez pas poster de commentaire vide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|   try { | ||||
|     const comment = await Comments.findOne({ | ||||
|       where: { userId: req.userId, id: parseInt(commentId) } | ||||
|     }) | ||||
|     if (!comment) { | ||||
|       return errorHandling(next, { | ||||
|         message: "Le commentaire n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|     try { | ||||
|         const comment = await Comments.findOne({ where: { userId: req.userId, id: parseInt(commentId) } }); | ||||
|         if (!comment) { | ||||
|             return errorHandling(next, { message: "Le commentaire n'existe pas.", statusCode: 404 }); | ||||
|         } | ||||
|         comment.message = message; | ||||
|         await comment.save(); | ||||
|         return res.status(200).json({ message: "Le commentaire a bien été modifié." }); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError);    | ||||
|     } | ||||
| } | ||||
|     comment.message = message | ||||
|     await comment.save() | ||||
|     return res | ||||
|       .status(200) | ||||
|       .json({ message: 'Le commentaire a bien été modifié.' }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,74 +1,92 @@ | ||||
| const errorHandling   = require('../assets/utils/errorHandling'); | ||||
| const { serverError } = require('../assets/config/errors'); | ||||
| const Favorites       = require('../models/favorites'); | ||||
| const Functions       = require('../models/functions'); | ||||
| const errorHandling = require('../assets/utils/errorHandling') | ||||
| const { serverError } = require('../assets/config/errors') | ||||
| const Favorites = require('../models/favorites') | ||||
| const Functions = require('../models/functions') | ||||
|  | ||||
| exports.getFavoriteByFunctionId = async (req, res, next) => { | ||||
|     const { functionId } = req.params; | ||||
|     const { userId }     = req; | ||||
|     try { | ||||
|         const favorite = await Favorites.findOne({  | ||||
|             where: { | ||||
|                 userId,  | ||||
|                 functionId | ||||
|             }  | ||||
|         }); | ||||
|         if (!favorite) { | ||||
|             return res.status(200).json({ isFavorite: false }); | ||||
|         } | ||||
|         return res.status(200).json({ isFavorite: true }); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|   const { functionId } = req.params | ||||
|   const { userId } = req | ||||
|   try { | ||||
|     const favorite = await Favorites.findOne({ | ||||
|       where: { | ||||
|         userId, | ||||
|         functionId | ||||
|       } | ||||
|     }) | ||||
|     if (!favorite) { | ||||
|       return res.status(200).json({ isFavorite: false }) | ||||
|     } | ||||
|     return res.status(200).json({ isFavorite: true }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.postFavoriteByFunctionId = async (req, res, next) => { | ||||
|     const { functionId } = req.params; | ||||
|     const { userId }     = req; | ||||
|     try { | ||||
|         const resultFunction = await Functions.findOne({ where: { id: functionId } }); | ||||
|         if (!resultFunction) { | ||||
|             return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); | ||||
|         } | ||||
|         const favorite = await Favorites.findOne({  | ||||
|             where: { | ||||
|                 userId,  | ||||
|                 functionId | ||||
|             }  | ||||
|         }); | ||||
|         if (!favorite) { | ||||
|             await Favorites.create({ userId, functionId }); | ||||
|             return res.status(201).json({ result: "Le favoris a bien été ajouté!" }); | ||||
|         } | ||||
|         return errorHandling(next, { message: "La fonction est déjà en favoris.", statusCode: 400 }); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|   const { functionId } = req.params | ||||
|   const { userId } = req | ||||
|   try { | ||||
|     const resultFunction = await Functions.findOne({ | ||||
|       where: { id: functionId } | ||||
|     }) | ||||
|     if (!resultFunction) { | ||||
|       return errorHandling(next, { | ||||
|         message: "La fonction n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|     const favorite = await Favorites.findOne({ | ||||
|       where: { | ||||
|         userId, | ||||
|         functionId | ||||
|       } | ||||
|     }) | ||||
|     if (!favorite) { | ||||
|       await Favorites.create({ userId, functionId }) | ||||
|       return res.status(201).json({ result: 'Le favoris a bien été ajouté!' }) | ||||
|     } | ||||
|     return errorHandling(next, { | ||||
|       message: 'La fonction est déjà en favoris.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.deleteFavoriteByFunctionId = async (req, res, next) => { | ||||
|     const { functionId } = req.params; | ||||
|     const { userId }     = req; | ||||
|     try { | ||||
|         const resultFunction = await Functions.findOne({ where: { id: functionId } }); | ||||
|         if (!resultFunction) { | ||||
|             return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); | ||||
|         } | ||||
|         const favorite = await Favorites.findOne({  | ||||
|             where: { | ||||
|                 userId,  | ||||
|                 functionId | ||||
|             }  | ||||
|         }); | ||||
|         if (!favorite) { | ||||
|             return errorHandling(next, { message: "Le fonction n'est pas en favoris.", statusCode: 400 }); | ||||
|         } | ||||
|         await favorite.destroy(); | ||||
|         return res.status(200).json({ message: "Le fonction a bien été supprimé des favoris." }); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|   const { functionId } = req.params | ||||
|   const { userId } = req | ||||
|   try { | ||||
|     const resultFunction = await Functions.findOne({ | ||||
|       where: { id: functionId } | ||||
|     }) | ||||
|     if (!resultFunction) { | ||||
|       return errorHandling(next, { | ||||
|         message: "La fonction n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
| } | ||||
|     const favorite = await Favorites.findOne({ | ||||
|       where: { | ||||
|         userId, | ||||
|         functionId | ||||
|       } | ||||
|     }) | ||||
|     if (!favorite) { | ||||
|       return errorHandling(next, { | ||||
|         message: "Le fonction n'est pas en favoris.", | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|     await favorite.destroy() | ||||
|     return res | ||||
|       .status(200) | ||||
|       .json({ message: 'Le fonction a bien été supprimé des favoris.' }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,69 +1,93 @@ | ||||
| const errorHandling     = require('../assets/utils/errorHandling'); | ||||
| const { serverError }   = require('../assets/config/errors'); | ||||
| const Functions         = require('../models/functions'); | ||||
| const Categories        = require('../models/categories'); | ||||
| const functionToExecute = require('../assets/functions/functionObject'); | ||||
| const helperQueryNumber = require('../assets/utils/helperQueryNumber'); | ||||
| const getPagesHelper    = require('../assets/utils/getPagesHelper'); | ||||
| const Sequelize         = require('sequelize'); | ||||
| const errorHandling = require('../assets/utils/errorHandling') | ||||
| const { serverError } = require('../assets/config/errors') | ||||
| const Functions = require('../models/functions') | ||||
| const Categories = require('../models/categories') | ||||
| const functionToExecute = require('../assets/functions/functionObject') | ||||
| const helperQueryNumber = require('../assets/utils/helperQueryNumber') | ||||
| const getPagesHelper = require('../assets/utils/getPagesHelper') | ||||
| const Sequelize = require('sequelize') | ||||
|  | ||||
| exports.getFunctions = async (req, res, next) => { | ||||
|     const categoryId = helperQueryNumber(req.query.categoryId, 0); | ||||
|     let   { search } = req.query; | ||||
|     try { search = search.toLowerCase(); } catch {}; | ||||
|     const options = { | ||||
|         where: {  | ||||
|             isOnline: 1, | ||||
|             // Trie par catégorie | ||||
|             ... (categoryId !== 0) && { categorieId: categoryId }, | ||||
|             // Recherche | ||||
|             ... (search != undefined) && {  | ||||
|                 [Sequelize.Op.or]: [ | ||||
|                     { title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', `%${search}%`) }, | ||||
|                     { slug: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('slug')), 'LIKE', `%${search}%`) }, | ||||
|                     { description: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('description')), 'LIKE', `%${search}%`) } | ||||
|                 ] | ||||
|             } | ||||
|         }, | ||||
|         include: [ | ||||
|             { model: Categories, attributes: ["name", "color"] } | ||||
|         ], | ||||
|         attributes: { | ||||
|             exclude: ["updatedAt", "utilizationForm", "article", "isOnline"] | ||||
|         }, | ||||
|         order: [['createdAt', 'DESC']] | ||||
|     }; | ||||
|     return await getPagesHelper({ req, res, next }, Functions, options); | ||||
|   const categoryId = helperQueryNumber(req.query.categoryId, 0) | ||||
|   let { search } = req.query | ||||
|   try { | ||||
|     search = search.toLowerCase() | ||||
|   } catch {} | ||||
|   const options = { | ||||
|     where: { | ||||
|       isOnline: 1, | ||||
|       // Trie par catégorie | ||||
|       ...(categoryId !== 0 && { categorieId: categoryId }), | ||||
|       // Recherche | ||||
|       ...(search != null && { | ||||
|         [Sequelize.Op.or]: [ | ||||
|           { | ||||
|             title: Sequelize.where( | ||||
|               Sequelize.fn('LOWER', Sequelize.col('title')), | ||||
|               'LIKE', | ||||
|               `%${search}%` | ||||
|             ) | ||||
|           }, | ||||
|           { | ||||
|             slug: Sequelize.where( | ||||
|               Sequelize.fn('LOWER', Sequelize.col('slug')), | ||||
|               'LIKE', | ||||
|               `%${search}%` | ||||
|             ) | ||||
|           }, | ||||
|           { | ||||
|             description: Sequelize.where( | ||||
|               Sequelize.fn('LOWER', Sequelize.col('description')), | ||||
|               'LIKE', | ||||
|               `%${search}%` | ||||
|             ) | ||||
|           } | ||||
|         ] | ||||
|       }) | ||||
|     }, | ||||
|     include: [{ model: Categories, attributes: ['name', 'color'] }], | ||||
|     attributes: { | ||||
|       exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline'] | ||||
|     }, | ||||
|     order: [['createdAt', 'DESC']] | ||||
|   } | ||||
|   return await getPagesHelper({ req, res, next }, Functions, options) | ||||
| } | ||||
|  | ||||
| exports.getFunctionBySlug = (req, res, next) => { | ||||
|     const { slug } = req.params; | ||||
|     Functions.findOne({  | ||||
|         where: { slug, isOnline: 1 }, | ||||
|         attributes: { | ||||
|             exclude: ["updatedAt", "isOnline"] | ||||
|         }, | ||||
|         include: [ | ||||
|             { model: Categories, attributes: ["name", "color"] } | ||||
|         ] | ||||
|     }) | ||||
|         .then((result) => { | ||||
|             if (!result) { | ||||
|                 return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); | ||||
|             } | ||||
|             try { result.utilizationForm = JSON.parse(result.utilizationForm); } catch {} | ||||
|             return res.status(200).json(result); | ||||
|   const { slug } = req.params | ||||
|   Functions.findOne({ | ||||
|     where: { slug, isOnline: 1 }, | ||||
|     attributes: { | ||||
|       exclude: ['updatedAt', 'isOnline'] | ||||
|     }, | ||||
|     include: [{ model: Categories, attributes: ['name', 'color'] }] | ||||
|   }) | ||||
|     .then(result => { | ||||
|       if (!result) { | ||||
|         return errorHandling(next, { | ||||
|           message: "La fonction n'existe pas.", | ||||
|           statusCode: 404 | ||||
|         }) | ||||
|         .catch((error) => { | ||||
|             console.log(error); | ||||
|             return errorHandling(next, serverError); | ||||
|         }); | ||||
|       } | ||||
|       try { | ||||
|         result.utilizationForm = JSON.parse(result.utilizationForm) | ||||
|       } catch {} | ||||
|       return res.status(200).json(result) | ||||
|     }) | ||||
|     .catch(error => { | ||||
|       console.log(error) | ||||
|       return errorHandling(next, serverError) | ||||
|     }) | ||||
| } | ||||
|  | ||||
| exports.executeFunctionBySlug = (req, res, next) => { | ||||
|     const functionOutput = functionToExecute(req.params.slug); | ||||
|     if (functionOutput !== undefined) { | ||||
|         return functionOutput({ res, next }, req.body); | ||||
|     }   | ||||
|     return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); | ||||
| } | ||||
|   const functionOutput = functionToExecute(req.params.slug) | ||||
|   if (functionOutput !== undefined) { | ||||
|     return functionOutput({ res, next }, req.body) | ||||
|   } | ||||
|   return errorHandling(next, { | ||||
|     message: "La fonction n'existe pas.", | ||||
|     statusCode: 404 | ||||
|   }) | ||||
| } | ||||
|   | ||||
							
								
								
									
										191
									
								
								api/controllers/links_shortener.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								api/controllers/links_shortener.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,191 @@ | ||||
| const validator = require('validator') | ||||
| const errorHandling = require('../assets/utils/errorHandling') | ||||
| const { requiredFields, serverError } = require('../assets/config/errors') | ||||
| const ShortLinks = require('../models/short_links') | ||||
| const getPagesHelper = require('../assets/utils/getPagesHelper') | ||||
| const Sequelize = require('sequelize') | ||||
|  | ||||
| const shortLinkBaseURL = 'https://s.divlo.fr' | ||||
|  | ||||
| exports.getLinks = async (req, res, next) => { | ||||
|   const { userId } = req | ||||
|   const options = { | ||||
|     where: { userId }, | ||||
|     order: [['createdAt', 'DESC']] | ||||
|   } | ||||
|   return await getPagesHelper({ req, res, next }, ShortLinks, options) | ||||
| } | ||||
|  | ||||
| exports.postLink = async (req, res, next) => { | ||||
|   const { userId } = req | ||||
|   let { url, shortcutName } = req.body | ||||
|  | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!(url && shortcutName)) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|   // Si ce n'est pas une url | ||||
|   if (!validator.isURL(url)) { | ||||
|     return errorHandling(next, { | ||||
|       message: 'Veuillez entré une URL valide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   // Si ce n'est pas de type slug | ||||
|   if (!validator.isSlug(shortcutName)) { | ||||
|     return errorHandling(next, { | ||||
|       message: | ||||
|         "Le nom de votre raccourci doit être de type slug (ne pas contenir d'espaces, ni de caractères spéciaux).", | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   // Sanitize shortcutName | ||||
|   shortcutName = validator.escape(shortcutName) | ||||
|   shortcutName = validator.trim(shortcutName) | ||||
|   shortcutName = validator.blacklist(shortcutName, ' ') | ||||
|  | ||||
|   try { | ||||
|     // Si l'url a déjà été raccourcie | ||||
|     const urlInDatabase = await ShortLinks.findOne({ where: { url } }) | ||||
|     if (urlInDatabase) { | ||||
|       const urlShort = `${shortLinkBaseURL}/${urlInDatabase.shortcut}` | ||||
|       return errorHandling(next, { | ||||
|         message: `L'url a déjà été raccourcie... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`, | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|  | ||||
|     // Si le nom du raccourci existe déjà | ||||
|     const shortcutInDatabase = await ShortLinks.findOne({ | ||||
|       where: { shortcut: shortcutName } | ||||
|     }) | ||||
|     if (shortcutInDatabase) { | ||||
|       const urlShort = `${shortLinkBaseURL}/${shortcutInDatabase.shortcut}` | ||||
|       return errorHandling(next, { | ||||
|         message: `Le nom du raccourci a déjà été utilisé... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`, | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|  | ||||
|     // Ajout du lien raccourci | ||||
|     const result = await ShortLinks.create({ | ||||
|       url, | ||||
|       shortcut: shortcutName, | ||||
|       userId | ||||
|     }) | ||||
|     const shortcutLinkResult = `${shortLinkBaseURL}/${result.shortcut}` | ||||
|     return res.status(200).json({ | ||||
|       resultHTML: `URL Raccourcie : <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${shortcutLinkResult}">${shortcutLinkResult}</a>`, | ||||
|       result: shortcutLinkResult | ||||
|     }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.putLink = async (req, res, next) => { | ||||
|   const { id } = req.params | ||||
|   const { userId } = req | ||||
|   let { url, shortcutName } = req.body | ||||
|  | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!(url && shortcutName)) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|   // Si ce n'est pas une url | ||||
|   if (!validator.isURL(url)) { | ||||
|     return errorHandling(next, { | ||||
|       message: 'Veuillez entré une URL valide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   // Si ce n'est pas de type slug | ||||
|   if (!validator.isSlug(shortcutName)) { | ||||
|     return errorHandling(next, { | ||||
|       message: | ||||
|         "Le nom de votre raccourci doit être de type slug (ne pas contenir d'espaces, ni de caractères spéciaux).", | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   // Sanitize shortcutName | ||||
|   shortcutName = validator.escape(shortcutName) | ||||
|   shortcutName = validator.trim(shortcutName) | ||||
|   shortcutName = validator.blacklist(shortcutName, ' ') | ||||
|  | ||||
|   try { | ||||
|     // Si l'url a déjà été raccourcie par quelqu'un d'autre | ||||
|     const urlInDatabase = await ShortLinks.findOne({ | ||||
|       where: { url, [Sequelize.Op.not]: { userId } } | ||||
|     }) | ||||
|     if (urlInDatabase) { | ||||
|       const urlShort = `${shortLinkBaseURL}/${urlInDatabase.shortcut}` | ||||
|       return errorHandling(next, { | ||||
|         message: `L'url a déjà été raccourcie... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`, | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|  | ||||
|     // Si le nom du raccourci existe déjà par quelqu'un d'autre | ||||
|     const shortcutInDatabase = await ShortLinks.findOne({ | ||||
|       where: { shortcut: shortcutName, [Sequelize.Op.not]: { userId } } | ||||
|     }) | ||||
|     if (shortcutInDatabase) { | ||||
|       const urlShort = `${shortLinkBaseURL}/${shortcutInDatabase.shortcut}` | ||||
|       return errorHandling(next, { | ||||
|         message: `Le nom du raccourci a déjà été utilisé... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`, | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|  | ||||
|     // Modification du lien raccourci | ||||
|     const result = await ShortLinks.findOne({ | ||||
|       where: { id, userId } | ||||
|     }) | ||||
|     console.log(result) | ||||
|     if (!result) { | ||||
|       return errorHandling(next, { | ||||
|         statusCode: 404, | ||||
|         message: "Le raccourci n'existe pas..." | ||||
|       }) | ||||
|     } | ||||
|     result.url = url | ||||
|     result.shortcut = shortcutName | ||||
|     const { shortcut } = await result.save() | ||||
|     const shortcutLinkResult = `${shortLinkBaseURL}/${shortcut}` | ||||
|     return res.status(200).json({ | ||||
|       resultHTML: `URL Raccourcie : <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${shortcutLinkResult}">${shortcutLinkResult}</a>`, | ||||
|       result: shortcutLinkResult | ||||
|     }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.deleteLink = async (req, res, next) => { | ||||
|   const { id } = req.params | ||||
|   const { userId } = req | ||||
|   try { | ||||
|     const linkResult = await ShortLinks.findOne({ | ||||
|       where: { id, userId } | ||||
|     }) | ||||
|     if (!linkResult) { | ||||
|       return errorHandling(next, { | ||||
|         message: "Le lien raccourci n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|     await linkResult.destroy() | ||||
|     return res.status(200).json({ message: 'La lien a bien été supprimé!' }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
| @@ -1,37 +1,40 @@ | ||||
| const errorHandling                   = require('../assets/utils/errorHandling'); | ||||
| const { serverError, requiredFields } = require('../assets/config/errors'); | ||||
| const Quotes                          = require('../models/quotes'); | ||||
| const Users                           = require('../models/users'); | ||||
| const getPagesHelper                  = require('../assets/utils/getPagesHelper'); | ||||
| const errorHandling = require('../assets/utils/errorHandling') | ||||
| const { serverError, requiredFields } = require('../assets/config/errors') | ||||
| const Quotes = require('../models/quotes') | ||||
| const Users = require('../models/users') | ||||
| const getPagesHelper = require('../assets/utils/getPagesHelper') | ||||
|  | ||||
| exports.getQuotes = async (req, res, next) => { | ||||
|     const options = { | ||||
|         where: {  | ||||
|             isValidated: 1, | ||||
|         }, | ||||
|         include: [ | ||||
|             { model: Users, attributes: ["name", "logo"] } | ||||
|         ], | ||||
|         attributes: { | ||||
|             exclude: ["isValidated"] | ||||
|         }, | ||||
|         order: [['createdAt', 'DESC']] | ||||
|     }; | ||||
|     return await getPagesHelper({ req, res, next }, Quotes, options); | ||||
|   const options = { | ||||
|     where: { | ||||
|       isValidated: 1 | ||||
|     }, | ||||
|     include: [{ model: Users, attributes: ['name', 'logo'] }], | ||||
|     attributes: { | ||||
|       exclude: ['isValidated'] | ||||
|     }, | ||||
|     order: [['createdAt', 'DESC']] | ||||
|   } | ||||
|   return await getPagesHelper({ req, res, next }, Quotes, options) | ||||
| } | ||||
|  | ||||
| exports.postQuote = (req, res, next) => { | ||||
|     const { quote, author } = req.body; | ||||
|     // S'il n'y a pas les champs obligatoire | ||||
|     if (!(quote && author)) { | ||||
|         return errorHandling(next, requiredFields); | ||||
|     } | ||||
|     Quotes.create({ quote, author, userId: req.userId }) | ||||
|         .then((_result) => { | ||||
|             return res.status(200).json({ message: "La citation a bien été ajoutée, elle est en attente de confirmation d'un administrateur." }); | ||||
|   const { quote, author } = req.body | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!(quote && author)) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|   Quotes.create({ quote, author, userId: req.userId }) | ||||
|     .then(_result => { | ||||
|       return res | ||||
|         .status(200) | ||||
|         .json({ | ||||
|           message: | ||||
|             "La citation a bien été ajoutée, elle est en attente de confirmation d'un administrateur." | ||||
|         }) | ||||
|         .catch((error) => { | ||||
|             console.log(error); | ||||
|             return errorHandling(next, serverError); | ||||
|         }); | ||||
| } | ||||
|     }) | ||||
|     .catch(error => { | ||||
|       console.log(error) | ||||
|       return errorHandling(next, serverError) | ||||
|     }) | ||||
| } | ||||
|   | ||||
| @@ -1,68 +1,83 @@ | ||||
| const errorHandling                   = require('../assets/utils/errorHandling'); | ||||
| const { serverError, requiredFields } = require('../assets/config/errors'); | ||||
| const Tasks                           = require('../models/tasks'); | ||||
| const errorHandling = require('../assets/utils/errorHandling') | ||||
| const { serverError, requiredFields } = require('../assets/config/errors') | ||||
| const Tasks = require('../models/tasks') | ||||
|  | ||||
| exports.getTasks = async (req, res, next) => { | ||||
|     try { | ||||
|         const tasks = await Tasks.findAll({ | ||||
|             where: { | ||||
|                 userId: req.userId | ||||
|             }, | ||||
|             order: [['createdAt', 'DESC']] | ||||
|         }); | ||||
|         return res.status(200).json(tasks); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|     } | ||||
|   try { | ||||
|     const tasks = await Tasks.findAll({ | ||||
|       where: { | ||||
|         userId: req.userId | ||||
|       }, | ||||
|       order: [['createdAt', 'DESC']] | ||||
|     }) | ||||
|     return res.status(200).json(tasks) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.postTask = async (req, res, next) => { | ||||
|     const { task } = req.body; | ||||
|     try { | ||||
|         if (!task) { | ||||
|             return errorHandling(next, requiredFields); | ||||
|         } | ||||
|         const taskResult = await Tasks.create({ task, userId: req.userId }); | ||||
|         return res.status(201).json(taskResult); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|   const { task } = req.body | ||||
|   try { | ||||
|     if (!task) { | ||||
|       return errorHandling(next, requiredFields) | ||||
|     } | ||||
|     const taskResult = await Tasks.create({ task, userId: req.userId }) | ||||
|     return res.status(201).json(taskResult) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.putTask = async (req, res, next) => { | ||||
|     const { id }          = req.params; | ||||
|     const { isCompleted } = req.body; | ||||
|     try { | ||||
|         if (typeof isCompleted !== 'boolean') { | ||||
|             return errorHandling(next, { message: "isCompleted doit être un booléen.", statusCode: 400 }); | ||||
|         } | ||||
|  | ||||
|         const taskResult = await Tasks.findOne({ where: { id, userId: req.userId } }); | ||||
|         if (!taskResult) { | ||||
|             return errorHandling(next, { message: `La "tâche à faire" n'existe pas.`, statusCode: 404 }); | ||||
|         } | ||||
|         taskResult.isCompleted = isCompleted; | ||||
|         const taskSaved = await taskResult.save(); | ||||
|         return res.status(200).json(taskSaved); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|   const { id } = req.params | ||||
|   const { isCompleted } = req.body | ||||
|   try { | ||||
|     if (typeof isCompleted !== 'boolean') { | ||||
|       return errorHandling(next, { | ||||
|         message: 'isCompleted doit être un booléen.', | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|  | ||||
|     const taskResult = await Tasks.findOne({ | ||||
|       where: { id, userId: req.userId } | ||||
|     }) | ||||
|     if (!taskResult) { | ||||
|       return errorHandling(next, { | ||||
|         message: 'La "tâche à faire" n\'existe pas.', | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|     taskResult.isCompleted = isCompleted | ||||
|     const taskSaved = await taskResult.save() | ||||
|     return res.status(200).json(taskSaved) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.deleteTask = async (req, res, next) => { | ||||
|     const { id } = req.params; | ||||
|     try { | ||||
|         const taskResult = await Tasks.findOne({ where: { id, userId: req.userId } }); | ||||
|         if (!taskResult) { | ||||
|             return errorHandling(next, { message: `La "tâche à faire" n'existe pas.`, statusCode: 404 }); | ||||
|         } | ||||
|         await taskResult.destroy(); | ||||
|         return res.status(200).json({ message: `La "tâche à faire" a bien été supprimée!` }); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|   const { id } = req.params | ||||
|   try { | ||||
|     const taskResult = await Tasks.findOne({ | ||||
|       where: { id, userId: req.userId } | ||||
|     }) | ||||
|     if (!taskResult) { | ||||
|       return errorHandling(next, { | ||||
|         message: 'La "tâche à faire" n\'existe pas.', | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
| } | ||||
|     await taskResult.destroy() | ||||
|     return res | ||||
|       .status(200) | ||||
|       .json({ message: 'La "tâche à faire" a bien été supprimée!' }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,292 +1,454 @@ | ||||
| const path                                                         = require('path'); | ||||
| const { validationResult }                                         = require('express-validator'); | ||||
| const bcrypt                                                       = require('bcryptjs'); | ||||
| const jwt                                                          = require('jsonwebtoken'); | ||||
| const ms                                                           = require('ms'); | ||||
| const uuid                                                         = require('uuid'); | ||||
| const Sequelize                                                    = require('sequelize'); | ||||
| const errorHandling                                                = require('../assets/utils/errorHandling'); | ||||
| const { serverError, generalError }                                = require('../assets/config/errors'); | ||||
| const { JWT_SECRET, FRONT_END_HOST, EMAIL_INFO, HOST, TOKEN_LIFE } = require('../assets/config/config'); | ||||
| const transporter                                                  = require('../assets/config/transporter'); | ||||
| const { emailUserTemplate }                                        = require('../assets/config/emails'); | ||||
| const Users                                                        = require('../models/users'); | ||||
| const Favorites                                                    = require('../models/favorites'); | ||||
| const Functions                                                    = require('../models/functions'); | ||||
| const Categories                                                   = require('../models/categories'); | ||||
| const Comments                                                     = require('../models/comments'); | ||||
| const Quotes                                                       = require('../models/quotes'); | ||||
| const deleteFilesNameStartWith                                     = require('../assets/utils/deleteFilesNameStartWith'); | ||||
| const getPagesHelper                                               = require('../assets/utils/getPagesHelper'); | ||||
| const path = require('path') | ||||
| const { validationResult } = require('express-validator') | ||||
| const bcrypt = require('bcryptjs') | ||||
| const jwt = require('jsonwebtoken') | ||||
| const ms = require('ms') | ||||
| const uuid = require('uuid') | ||||
| const Sequelize = require('sequelize') | ||||
| const errorHandling = require('../assets/utils/errorHandling') | ||||
| const { serverError, generalError } = require('../assets/config/errors') | ||||
| const { | ||||
|   JWT_SECRET, | ||||
|   FRONT_END_HOST, | ||||
|   EMAIL_INFO, | ||||
|   HOST, | ||||
|   TOKEN_LIFE | ||||
| } = require('../assets/config/config') | ||||
| const transporter = require('../assets/config/transporter') | ||||
| const { emailUserTemplate } = require('../assets/config/emails') | ||||
| const Users = require('../models/users') | ||||
| const Favorites = require('../models/favorites') | ||||
| const Functions = require('../models/functions') | ||||
| const Categories = require('../models/categories') | ||||
| const Comments = require('../models/comments') | ||||
| const Quotes = require('../models/quotes') | ||||
| const deleteFilesNameStartWith = require('../assets/utils/deleteFilesNameStartWith') | ||||
| const getPagesHelper = require('../assets/utils/getPagesHelper') | ||||
|  | ||||
| async function handleEditUser(res, { name, email, biography, isPublicEmail }, userId, logoName) { | ||||
|     const user = await Users.findOne({ where: { id: userId } }); | ||||
|     user.name = name; | ||||
|     if (user.email !== email) { | ||||
|         const tempToken = uuid.v4(); | ||||
|         user.email = email; | ||||
|         user.isConfirmed = false; | ||||
|         user.tempToken = tempToken; | ||||
|         await transporter.sendMail({ | ||||
|             from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`, | ||||
|             to: email, | ||||
|             subject: "FunctionProject - Confirmer l'email", | ||||
|             html: emailUserTemplate("Veuillez confirmer l'email", "Oui, je confirme.", `${HOST}/users/confirm-email/${tempToken}`, "Si vous avez reçu ce message par erreur, il suffit de le supprimer. Votre email ne serez pas confirmé si vous ne cliquez pas sur le lien de confirmation ci-dessus.") | ||||
|         }); | ||||
|     } | ||||
|     if (biography != undefined) { | ||||
|         user.biography = biography; | ||||
|     } | ||||
|     user.isPublicEmail = isPublicEmail; | ||||
|     if (logoName != undefined && `/images/users/${logoName}` !== user.logo) { | ||||
|         user.logo = `/images/users/${logoName}`; | ||||
|     } | ||||
|     await user.save(); | ||||
|     return res.status(200).json({ id: user.id, name: user.name, email: user.email, biography: user.biography, logo: user.logo, isPublicEmail: user.isPublicEmail, isAdmin: user.isAdmin, createdAt: user.createdAt }); | ||||
| async function handleEditUser ( | ||||
|   res, | ||||
|   { name, email, biography, isPublicEmail }, | ||||
|   userId, | ||||
|   logoName | ||||
| ) { | ||||
|   const user = await Users.findOne({ where: { id: userId } }) | ||||
|   user.name = name | ||||
|   if (user.email !== email) { | ||||
|     const tempToken = uuid.v4() | ||||
|     user.email = email | ||||
|     user.isConfirmed = false | ||||
|     user.tempToken = tempToken | ||||
|     await transporter.sendMail({ | ||||
|       from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`, | ||||
|       to: email, | ||||
|       subject: "FunctionProject - Confirmer l'email", | ||||
|       html: emailUserTemplate( | ||||
|         "Veuillez confirmer l'email", | ||||
|         'Oui, je confirme.', | ||||
|         `${HOST}/users/confirm-email/${tempToken}`, | ||||
|         'Si vous avez reçu ce message par erreur, il suffit de le supprimer. Votre email ne serez pas confirmé si vous ne cliquez pas sur le lien de confirmation ci-dessus.' | ||||
|       ) | ||||
|     }) | ||||
|   } | ||||
|   if (biography != null) { | ||||
|     user.biography = biography | ||||
|   } | ||||
|   user.isPublicEmail = isPublicEmail | ||||
|   if (logoName != null && `/images/users/${logoName}` !== user.logo) { | ||||
|     user.logo = `/images/users/${logoName}` | ||||
|   } | ||||
|   await user.save() | ||||
|   return res | ||||
|     .status(200) | ||||
|     .json({ | ||||
|       id: user.id, | ||||
|       name: user.name, | ||||
|       email: user.email, | ||||
|       biography: user.biography, | ||||
|       logo: user.logo, | ||||
|       isPublicEmail: user.isPublicEmail, | ||||
|       isAdmin: user.isAdmin, | ||||
|       createdAt: user.createdAt | ||||
|     }) | ||||
| } | ||||
|  | ||||
| exports.getUsers = async (req, res, next) => { | ||||
|     let { search } = req.query; | ||||
|     try { search = search.toLowerCase(); } catch {}; | ||||
|     const options = { | ||||
|         where: {  | ||||
|             isConfirmed: true, | ||||
|             // Recherche | ||||
|             ...(search != undefined) && { | ||||
|                 name: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', `%${search}%`) | ||||
|             } | ||||
|         }, | ||||
|         attributes: { | ||||
|             exclude: ["updatedAt", "isAdmin", "isConfirmed", "password", "tempToken", "tempExpirationToken", "isPublicEmail", "email"] | ||||
|         },  | ||||
|         order: [['createdAt', 'DESC']] | ||||
|     }; | ||||
|     return await getPagesHelper({ req, res, next }, Users, options); | ||||
|   let { search } = req.query | ||||
|   try { | ||||
|     search = search.toLowerCase() | ||||
|   } catch {} | ||||
|   const options = { | ||||
|     where: { | ||||
|       isConfirmed: true, | ||||
|       // Recherche | ||||
|       ...(search != null && { | ||||
|         name: Sequelize.where( | ||||
|           Sequelize.fn('LOWER', Sequelize.col('name')), | ||||
|           'LIKE', | ||||
|           `%${search}%` | ||||
|         ) | ||||
|       }) | ||||
|     }, | ||||
|     attributes: { | ||||
|       exclude: [ | ||||
|         'updatedAt', | ||||
|         'isAdmin', | ||||
|         'isConfirmed', | ||||
|         'password', | ||||
|         'tempToken', | ||||
|         'tempExpirationToken', | ||||
|         'isPublicEmail', | ||||
|         'email' | ||||
|       ] | ||||
|     }, | ||||
|     order: [['createdAt', 'DESC']] | ||||
|   } | ||||
|   return await getPagesHelper({ req, res, next }, Users, options) | ||||
| } | ||||
|  | ||||
| exports.putUser = async (req, res, next) => { | ||||
|     const { name, email, biography, isPublicEmail } = req.body; | ||||
|     const logo = req.files.logo; | ||||
|     const errors = validationResult(req); | ||||
|     if (!errors.isEmpty()) { | ||||
|         return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }); | ||||
|   const { name, email, biography, isPublicEmail } = req.body | ||||
|   const logo = req.files.logo | ||||
|   const errors = validationResult(req) | ||||
|   if (!errors.isEmpty()) { | ||||
|     return errorHandling(next, { | ||||
|       message: errors.array()[0].msg, | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|   if (logo != null) { | ||||
|     if ( | ||||
|       (!logo || logo.truncated) && | ||||
|       (logo.mimetype !== 'image/png' || | ||||
|         logo.mimetype !== 'image/jpg' || | ||||
|         logo.mimetype !== 'image/jpeg' || | ||||
|         logo.mimetype !== 'image/gif') | ||||
|     ) { | ||||
|       return errorHandling(next, { | ||||
|         message: | ||||
|           'Le profil doit avoir une image valide (PNG, JPG, GIF) et moins de 5mo.', | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|     if (logo != undefined) { | ||||
|         if (!logo || logo.truncated && ( | ||||
|             logo.mimetype !== 'image/png' ||  | ||||
|             logo.mimetype !== 'image/jpg' ||  | ||||
|             logo.mimetype !== 'image/jpeg' || | ||||
|             logo.mimetype !== 'image/gif' | ||||
|         )) { | ||||
|             return errorHandling(next, { message:"Le profil doit avoir une image valide (PNG, JPG, GIF) et moins de 5mo.", statusCode: 400 }); | ||||
|         } | ||||
|         const splitedLogoName = logo.name.split('.'); | ||||
|         if (splitedLogoName.length !== 2) return errorHandling(next, serverError); | ||||
|         const logoName = name + req.userId + '.' + splitedLogoName[1]; | ||||
|         // Supprime les anciens logo | ||||
|         try { | ||||
|             deleteFilesNameStartWith(`${name + req.userId}`, path.join(__dirname, '..', 'assets', 'images', 'users'), async () => { | ||||
|                 logo.mv(path.join(__dirname, '..', 'assets', 'images', 'users', logoName), async (error) => { | ||||
|                     if (error) return errorHandling(next, serverError); | ||||
|                     return await handleEditUser(res, { name, email, biography, isPublicEmail }, req.userId, logoName); | ||||
|                 }); | ||||
|             }); | ||||
|         } catch (error) { | ||||
|             console.log(error); | ||||
|             return errorHandling(next, serverError); | ||||
|         } | ||||
|     } else { | ||||
|         try { | ||||
|             return await handleEditUser(res, { name, email, biography, isPublicEmail }, req.userId, null); | ||||
|         } catch (error) { | ||||
|             console.log(error); | ||||
|             return errorHandling(next, serverError); | ||||
|     const splitedLogoName = logo.name.split('.') | ||||
|     if (splitedLogoName.length !== 2) return errorHandling(next, serverError) | ||||
|     const logoName = name + req.userId + '.' + splitedLogoName[1] | ||||
|     // Supprime les anciens logo | ||||
|     try { | ||||
|       deleteFilesNameStartWith( | ||||
|         `${name + req.userId}`, | ||||
|         path.join(__dirname, '..', 'assets', 'images', 'users'), | ||||
|         async () => { | ||||
|           logo.mv( | ||||
|             path.join(__dirname, '..', 'assets', 'images', 'users', logoName), | ||||
|             async error => { | ||||
|               if (error) return errorHandling(next, serverError) | ||||
|               return await handleEditUser( | ||||
|                 res, | ||||
|                 { name, email, biography, isPublicEmail }, | ||||
|                 req.userId, | ||||
|                 logoName | ||||
|               ) | ||||
|             } | ||||
|           ) | ||||
|         } | ||||
|       ) | ||||
|     } catch (error) { | ||||
|       console.log(error) | ||||
|       return errorHandling(next, serverError) | ||||
|     } | ||||
|   } else { | ||||
|     try { | ||||
|       return await handleEditUser( | ||||
|         res, | ||||
|         { name, email, biography, isPublicEmail }, | ||||
|         req.userId, | ||||
|         null | ||||
|       ) | ||||
|     } catch (error) { | ||||
|       console.log(error) | ||||
|       return errorHandling(next, serverError) | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.register = async (req, res, next) => { | ||||
|     const { name, email, password } = req.body; | ||||
|     const errors = validationResult(req); | ||||
|     if (!errors.isEmpty()) { | ||||
|         return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }); | ||||
|     } | ||||
|     try { | ||||
|         const hashedPassword = await bcrypt.hash(password, 12); | ||||
|         const tempToken = uuid.v4(); | ||||
|         await Users.create({ email, name, password: hashedPassword, tempToken }); | ||||
|         await transporter.sendMail({ | ||||
|             from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`, | ||||
|             to: email, | ||||
|             subject: "FunctionProject - Confirmer l'inscription", | ||||
|             html: emailUserTemplate("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 vos emails pour confirmer l'inscription." }); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|     } | ||||
|   const { name, email, password } = req.body | ||||
|   const errors = validationResult(req) | ||||
|   if (!errors.isEmpty()) { | ||||
|     return errorHandling(next, { | ||||
|       message: errors.array()[0].msg, | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|   try { | ||||
|     const hashedPassword = await bcrypt.hash(password, 12) | ||||
|     const tempToken = uuid.v4() | ||||
|     await Users.create({ email, name, password: hashedPassword, tempToken }) | ||||
|     await transporter.sendMail({ | ||||
|       from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`, | ||||
|       to: email, | ||||
|       subject: "FunctionProject - Confirmer l'inscription", | ||||
|       html: emailUserTemplate( | ||||
|         "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 vos emails pour confirmer l'inscription." | ||||
|       }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.login = async (req, res, next) => { | ||||
|     const { email, password } = req.body; | ||||
|     const errors = validationResult(req); | ||||
|     if (!errors.isEmpty()) { | ||||
|         return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }); | ||||
|   const { email, password } = req.body | ||||
|   const errors = validationResult(req) | ||||
|   if (!errors.isEmpty()) { | ||||
|     return errorHandling(next, { | ||||
|       message: errors.array()[0].msg, | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|   try { | ||||
|     const user = await Users.findOne({ where: { email } }) | ||||
|     if (!user) { | ||||
|       return errorHandling(next, { | ||||
|         message: "Le mot de passe ou l'adresse email n'est pas valide.", | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|     try { | ||||
|         const user = await Users.findOne({ where: { email } }); | ||||
|         if (!user) { | ||||
|             return errorHandling(next, { message: "Le mot de passe ou l'adresse email n'est pas valide.", statusCode: 400 }); | ||||
|         } | ||||
|         const isEqual = await bcrypt.compare(password, user.password); | ||||
|         if (!isEqual) { | ||||
|             return errorHandling(next, { message: "Le mot de passe ou l'adresse email n'est pas valide.", statusCode: 400 }); | ||||
|         } | ||||
|         if (!user.isConfirmed) { | ||||
|             return errorHandling(next, { message: "Vous devez valider votre adresse email pour votre première connexion.", statusCode: 400 }); | ||||
|         } | ||||
|         const token = jwt.sign({  | ||||
|             email: user.email, userId: user.id | ||||
|         }, JWT_SECRET, { expiresIn: TOKEN_LIFE }); | ||||
|         return res.status(200).json({ token, id: user.id, name: user.name, email: user.email, biography: user.biography, logo: user.logo, isPublicEmail: user.isPublicEmail, isAdmin: user.isAdmin, createdAt: user.createdAt, expiresIn: Math.round(ms(TOKEN_LIFE) / 1000) }); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|     const isEqual = await bcrypt.compare(password, user.password) | ||||
|     if (!isEqual) { | ||||
|       return errorHandling(next, { | ||||
|         message: "Le mot de passe ou l'adresse email n'est pas valide.", | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|     if (!user.isConfirmed) { | ||||
|       return errorHandling(next, { | ||||
|         message: | ||||
|           'Vous devez valider votre adresse email pour votre première connexion.', | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|     const token = jwt.sign( | ||||
|       { | ||||
|         email: user.email, | ||||
|         userId: user.id | ||||
|       }, | ||||
|       JWT_SECRET, | ||||
|       { expiresIn: TOKEN_LIFE } | ||||
|     ) | ||||
|     return res | ||||
|       .status(200) | ||||
|       .json({ | ||||
|         token, | ||||
|         id: user.id, | ||||
|         name: user.name, | ||||
|         email: user.email, | ||||
|         biography: user.biography, | ||||
|         logo: user.logo, | ||||
|         isPublicEmail: user.isPublicEmail, | ||||
|         isAdmin: user.isAdmin, | ||||
|         createdAt: user.createdAt, | ||||
|         expiresIn: Math.round(ms(TOKEN_LIFE) / 1000) | ||||
|       }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.confirmEmail = async (req, res, next) => { | ||||
|     const { tempToken } = req.params; | ||||
|     if (!tempToken) { | ||||
|         return errorHandling(next, generalError); | ||||
|     } | ||||
|     try { | ||||
|         const user = await Users.findOne({ where: { tempToken, isConfirmed: false } }); | ||||
|         if (!user) { | ||||
|             return errorHandling(next, { message: "Le token n'est pas valide.", statusCode: 400 }); | ||||
|         } | ||||
|         user.tempToken = null; | ||||
|         user.isConfirmed = true; | ||||
|         await user.save(); | ||||
|         return res.redirect(`${FRONT_END_HOST}/users/login?isConfirmed=true`); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|   const { tempToken } = req.params | ||||
|   if (!tempToken) { | ||||
|     return errorHandling(next, generalError) | ||||
|   } | ||||
|   try { | ||||
|     const user = await Users.findOne({ | ||||
|       where: { tempToken, isConfirmed: false } | ||||
|     }) | ||||
|     if (!user) { | ||||
|       return errorHandling(next, { | ||||
|         message: "Le token n'est pas valide.", | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|     user.tempToken = null | ||||
|     user.isConfirmed = true | ||||
|     await user.save() | ||||
|     return res.redirect(`${FRONT_END_HOST}/users/login?isConfirmed=true`) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.resetPassword = async (req, res, next) => { | ||||
|     const { email } = req.body; | ||||
|     const errors = validationResult(req); | ||||
|     if (!errors.isEmpty()) { | ||||
|         return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }); | ||||
|     } | ||||
|     try { | ||||
|         const user = await Users.findOne({ where: { email, tempToken: null } }); | ||||
|         if (!user) { | ||||
|             return errorHandling(next, { message: "L'adresse email n'existe pas ou une demande est déjà en cours.", statusCode: 400 }); | ||||
|         } | ||||
|         const tempToken = uuid.v4(); | ||||
|         user.tempExpirationToken = Date.now() + 3600000; // 1 heure | ||||
|         user.tempToken = tempToken; | ||||
|         await user.save(); | ||||
|         await transporter.sendMail({ | ||||
|             from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`, | ||||
|             to: email, | ||||
|             subject: "FunctionProject - Réinitialisation du mot de passe", | ||||
|             html: emailUserTemplate("Veuillez confirmer la réinitialisation du mot de passe", "Oui, je change mon mot de passe.", `${FRONT_END_HOST}/users/newPassword?token=${tempToken}`, "Si vous avez reçu ce message par erreur, il suffit de le supprimer. Votre mot de passe ne sera pas réinitialiser si vous ne cliquez pas sur le lien ci-dessus. Par ailleurs, pour la sécurité de votre compte, la réinitialisation du mot de passe est disponible pendant un délai de 1 heure, passez ce temps, la réinitialisation ne sera plus valide.") | ||||
|         }); | ||||
|         return res.status(200).json({ result: "Demande de réinitialisation du mot de passe réussi, veuillez vérifier vos emails!" }); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|   const { email } = req.body | ||||
|   const errors = validationResult(req) | ||||
|   if (!errors.isEmpty()) { | ||||
|     return errorHandling(next, { | ||||
|       message: errors.array()[0].msg, | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|   try { | ||||
|     const user = await Users.findOne({ where: { email, tempToken: null } }) | ||||
|     if (!user) { | ||||
|       return errorHandling(next, { | ||||
|         message: | ||||
|           "L'adresse email n'existe pas ou une demande est déjà en cours.", | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|     const tempToken = uuid.v4() | ||||
|     user.tempExpirationToken = Date.now() + 3600000 // 1 heure | ||||
|     user.tempToken = tempToken | ||||
|     await user.save() | ||||
|     await transporter.sendMail({ | ||||
|       from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`, | ||||
|       to: email, | ||||
|       subject: 'FunctionProject - Réinitialisation du mot de passe', | ||||
|       html: emailUserTemplate( | ||||
|         'Veuillez confirmer la réinitialisation du mot de passe', | ||||
|         'Oui, je change mon mot de passe.', | ||||
|         `${FRONT_END_HOST}/users/newPassword?token=${tempToken}`, | ||||
|         'Si vous avez reçu ce message par erreur, il suffit de le supprimer. Votre mot de passe ne sera pas réinitialiser si vous ne cliquez pas sur le lien ci-dessus. Par ailleurs, pour la sécurité de votre compte, la réinitialisation du mot de passe est disponible pendant un délai de 1 heure, passez ce temps, la réinitialisation ne sera plus valide.' | ||||
|       ) | ||||
|     }) | ||||
|     return res | ||||
|       .status(200) | ||||
|       .json({ | ||||
|         result: | ||||
|           'Demande de réinitialisation du mot de passe réussi, veuillez vérifier vos emails!' | ||||
|       }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.newPassword = async (req, res, next) => { | ||||
|     const { tempToken, password } = req.body; | ||||
|     const errors = validationResult(req); | ||||
|     if (!errors.isEmpty()) { | ||||
|         return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }); | ||||
|     } | ||||
|     try { | ||||
|         const user = await Users.findOne({ where: { tempToken } }); | ||||
|         if (!user && parseInt(tempExpirationToken) < Date.now()) { | ||||
|             return errorHandling(next, { message: "Le token n'est pas valide.", statusCode: 400 }); | ||||
|         } | ||||
|         const hashedPassword = await bcrypt.hash(password, 12); | ||||
|         user.password = hashedPassword; | ||||
|         user.tempToken = null; | ||||
|         user.tempExpirationToken = null; | ||||
|         await user.save(); | ||||
|         return res.status(200).json({ result: "Le mot de passe a bien été modifié!" }); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|   const { tempToken, password } = req.body | ||||
|   const errors = validationResult(req) | ||||
|   if (!errors.isEmpty()) { | ||||
|     return errorHandling(next, { | ||||
|       message: errors.array()[0].msg, | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|   try { | ||||
|     const user = await Users.findOne({ where: { tempToken } }) | ||||
|     if (!user && parseInt(user.tempExpirationToken) < Date.now()) { | ||||
|       return errorHandling(next, { | ||||
|         message: "Le token n'est pas valide.", | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|     const hashedPassword = await bcrypt.hash(password, 12) | ||||
|     user.password = hashedPassword | ||||
|     user.tempToken = null | ||||
|     user.tempExpirationToken = null | ||||
|     await user.save() | ||||
|     return res | ||||
|       .status(200) | ||||
|       .json({ result: 'Le mot de passe a bien été modifié!' }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|  | ||||
| exports.getUserInfo = async (req, res, next) => { | ||||
|     const { name } = req.params; | ||||
|     try { | ||||
|         const user = await Users.findOne({  | ||||
|             where: { name, isConfirmed: true }, | ||||
|             attributes: { | ||||
|                 exclude: ["updatedAt", "isAdmin", "isConfirmed", "password", "tempToken", "tempExpirationToken"] | ||||
|             },  | ||||
|         }); | ||||
|         if (!user) { | ||||
|             return errorHandling(next, { message: "L'utilisateur n'existe pas.", statusCode: 404 }); | ||||
|         } | ||||
|         const favorites = await Favorites.findAll({  | ||||
|             where: { userId: user.id }, | ||||
|             include: [ | ||||
|                 { model: Functions, attributes: { exclude: ["updatedAt", "utilizationForm", "article", "isOnline"] }, include: { model: Categories, attributes: ["name", "color"] } } | ||||
|             ], | ||||
|             order: [['createdAt', 'DESC']], | ||||
|             limit: 5 | ||||
|         }); | ||||
|         const favoritesArray = favorites.map((favorite) => favorite.function); | ||||
|         const comments = await Comments.findAll({ | ||||
|             where: { userId: user.id }, | ||||
|             include: [ | ||||
|                 { model: Functions, attributes: { exclude: ["updatedAt", "utilizationForm", "article", "isOnline"] } } | ||||
|             ], | ||||
|             order: [['createdAt', 'DESC']], | ||||
|             limit: 5 | ||||
|         }); | ||||
|         const commentsArray = comments.map((commentObject) => { | ||||
|             return { | ||||
|                 id: commentObject.id, | ||||
|                 message: commentObject.message, | ||||
|                 createdAt: commentObject.createdAt, | ||||
|                 function: commentObject.function.dataValues | ||||
|             }; | ||||
|         }); | ||||
|         const quotesArray = await Quotes.findAll({ | ||||
|             where: { userId: user.id }, | ||||
|             attributes: { | ||||
|                 exclude: ["updatedAt", "createdAt", "isValidated", "userId", "id"] | ||||
|             },  | ||||
|             order: [['createdAt', 'DESC']], | ||||
|             limit: 5, | ||||
|         }); | ||||
|         const userObject = { | ||||
|             // Si Public Email | ||||
|             ... (user.isPublicEmail) && { email: user.email }, | ||||
|             isPublicEmail: user.isPublicEmail, | ||||
|             name: user.name, | ||||
|             biography: user.biography, | ||||
|             logo: user.logo, | ||||
|             createdAt: user.createdAt, | ||||
|             favoritesArray, | ||||
|             commentsArray, | ||||
|             quotesArray | ||||
|         }; | ||||
|         return res.status(200).json(userObject); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return errorHandling(next, serverError); | ||||
|   const { name } = req.params | ||||
|   try { | ||||
|     const user = await Users.findOne({ | ||||
|       where: { name, isConfirmed: true }, | ||||
|       attributes: { | ||||
|         exclude: [ | ||||
|           'updatedAt', | ||||
|           'isAdmin', | ||||
|           'isConfirmed', | ||||
|           'password', | ||||
|           'tempToken', | ||||
|           'tempExpirationToken' | ||||
|         ] | ||||
|       } | ||||
|     }) | ||||
|     if (!user) { | ||||
|       return errorHandling(next, { | ||||
|         message: "L'utilisateur n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
| } | ||||
|     const favorites = await Favorites.findAll({ | ||||
|       where: { userId: user.id }, | ||||
|       include: [ | ||||
|         { | ||||
|           model: Functions, | ||||
|           attributes: { | ||||
|             exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline'] | ||||
|           }, | ||||
|           include: { model: Categories, attributes: ['name', 'color'] } | ||||
|         } | ||||
|       ], | ||||
|       order: [['createdAt', 'DESC']], | ||||
|       limit: 5 | ||||
|     }) | ||||
|     const favoritesArray = favorites.map(favorite => favorite.function) | ||||
|     const comments = await Comments.findAll({ | ||||
|       where: { userId: user.id }, | ||||
|       include: [ | ||||
|         { | ||||
|           model: Functions, | ||||
|           attributes: { | ||||
|             exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline'] | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       order: [['createdAt', 'DESC']], | ||||
|       limit: 5 | ||||
|     }) | ||||
|     const commentsArray = comments.map(commentObject => { | ||||
|       return { | ||||
|         id: commentObject.id, | ||||
|         message: commentObject.message, | ||||
|         createdAt: commentObject.createdAt, | ||||
|         function: commentObject.function.dataValues | ||||
|       } | ||||
|     }) | ||||
|     const quotesArray = await Quotes.findAll({ | ||||
|       where: { userId: user.id, isValidated: 1 }, | ||||
|       attributes: { | ||||
|         exclude: ['updatedAt', 'createdAt', 'isValidated', 'userId', 'id'] | ||||
|       }, | ||||
|       order: [['createdAt', 'DESC']], | ||||
|       limit: 5 | ||||
|     }) | ||||
|     const userObject = { | ||||
|       // Si Public Email | ||||
|       ...(user.isPublicEmail && { email: user.email }), | ||||
|       isPublicEmail: user.isPublicEmail, | ||||
|       name: user.name, | ||||
|       biography: user.biography, | ||||
|       logo: user.logo, | ||||
|       createdAt: user.createdAt, | ||||
|       favoritesArray, | ||||
|       commentsArray, | ||||
|       quotesArray | ||||
|     } | ||||
|     return res.status(200).json(userObject) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,23 +1,32 @@ | ||||
| const errorHandling   = require('../assets/utils/errorHandling'); | ||||
| const { serverError } = require('../assets/config/errors'); | ||||
| const Users           = require('../models/users'); | ||||
| const errorHandling = require('../assets/utils/errorHandling') | ||||
| const { serverError } = require('../assets/config/errors') | ||||
| const Users = require('../models/users') | ||||
|  | ||||
| module.exports = (req, _res, next) => { | ||||
|     if (!req.userId) { | ||||
|         return errorHandling(next, { message: "Vous n'êtes pas connecté.", statusCode: 403 }); | ||||
|     } | ||||
|     Users.findOne({ where: { id: req.userId } }) | ||||
|         .then((user) => { | ||||
|             if (!user) { | ||||
|                 return errorHandling(next, { message: "Le mot de passe ou l'adresse email n'est pas valide.", statusCode: 403 }); | ||||
|             } | ||||
|             if (!user.isAdmin) { | ||||
|                 return errorHandling(next, { message: "Vous n'êtes pas administrateur.", statusCode: 403 }); | ||||
|             } | ||||
|             next(); | ||||
|   if (!req.userId) { | ||||
|     return errorHandling(next, { | ||||
|       message: "Vous n'êtes pas connecté.", | ||||
|       statusCode: 403 | ||||
|     }) | ||||
|   } | ||||
|   Users.findOne({ where: { id: req.userId } }) | ||||
|     .then(user => { | ||||
|       if (!user) { | ||||
|         return errorHandling(next, { | ||||
|           message: "Le mot de passe ou l'adresse email n'est pas valide.", | ||||
|           statusCode: 403 | ||||
|         }) | ||||
|         .catch((error) => { | ||||
|             console.log(error); | ||||
|             return errorHandling(next, serverError); | ||||
|         }); | ||||
| } | ||||
|       } | ||||
|       if (!user.isAdmin) { | ||||
|         return errorHandling(next, { | ||||
|           message: "Vous n'êtes pas administrateur.", | ||||
|           statusCode: 403 | ||||
|         }) | ||||
|       } | ||||
|       next() | ||||
|     }) | ||||
|     .catch(error => { | ||||
|       console.log(error) | ||||
|       return errorHandling(next, serverError) | ||||
|     }) | ||||
| } | ||||
|   | ||||
| @@ -1,24 +1,33 @@ | ||||
| const jwt             = require('jsonwebtoken'); | ||||
| const errorHandling   = require('../assets/utils/errorHandling'); | ||||
| const { JWT_SECRET }  = require('../assets/config/config'); | ||||
| const jwt = require('jsonwebtoken') | ||||
| const errorHandling = require('../assets/utils/errorHandling') | ||||
| const { JWT_SECRET } = require('../assets/config/config') | ||||
|  | ||||
| module.exports = (req, _res, next) => { | ||||
|     const token = req.get('Authorization'); | ||||
|     if (!token) { | ||||
|         return errorHandling(next, { message: "Vous devez être connecter pour effectuer cette opération.", statusCode: 403 }); | ||||
|     } | ||||
|   const token = req.get('Authorization') | ||||
|   if (!token) { | ||||
|     return errorHandling(next, { | ||||
|       message: 'Vous devez être connecter pour effectuer cette opération.', | ||||
|       statusCode: 403 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|     let decodedToken; | ||||
|     try { | ||||
|         decodedToken = jwt.verify(token, JWT_SECRET); | ||||
|     } catch (error) { | ||||
|         return errorHandling(next, { message: "Vous devez être connecter pour effectuer cette opération.", statusCode: 403 }); | ||||
|     } | ||||
|   let decodedToken | ||||
|   try { | ||||
|     decodedToken = jwt.verify(token, JWT_SECRET) | ||||
|   } catch (error) { | ||||
|     return errorHandling(next, { | ||||
|       message: 'Vous devez être connecter pour effectuer cette opération.', | ||||
|       statusCode: 403 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|     if (!decodedToken) { | ||||
|         return errorHandling(next, { message: "Vous devez être connecter pour effectuer cette opération.", statusCode: 403 }); | ||||
|     } | ||||
|   if (!decodedToken) { | ||||
|     return errorHandling(next, { | ||||
|       message: 'Vous devez être connecter pour effectuer cette opération.', | ||||
|       statusCode: 403 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|     req.userId = decodedToken.userId; | ||||
|     next(); | ||||
| } | ||||
|   req.userId = decodedToken.userId | ||||
|   next() | ||||
| } | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| const Sequelize = require('sequelize'); | ||||
| const sequelize = require('../assets/utils/database'); | ||||
| const Sequelize = require('sequelize') | ||||
| const sequelize = require('../assets/utils/database') | ||||
|  | ||||
| module.exports = sequelize.define('categorie', { | ||||
|     name: { | ||||
|         type: Sequelize.STRING, | ||||
|         allowNull: false, | ||||
|     }, | ||||
|     color: { | ||||
|         type: Sequelize.STRING, | ||||
|         allowNull: false, | ||||
|     } | ||||
| }); | ||||
|   name: { | ||||
|     type: Sequelize.STRING, | ||||
|     allowNull: false | ||||
|   }, | ||||
|   color: { | ||||
|     type: Sequelize.STRING, | ||||
|     allowNull: false | ||||
|   } | ||||
| }) | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| const Sequelize = require('sequelize'); | ||||
| const sequelize = require('../assets/utils/database'); | ||||
| const Sequelize = require('sequelize') | ||||
| const sequelize = require('../assets/utils/database') | ||||
|  | ||||
| module.exports = sequelize.define('comment', { | ||||
|     message: { | ||||
|         type: Sequelize.TEXT, | ||||
|         allowNull: false | ||||
|     } | ||||
| }); | ||||
|   message: { | ||||
|     type: Sequelize.TEXT, | ||||
|     allowNull: false | ||||
|   } | ||||
| }) | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| const Sequelize = require('sequelize'); | ||||
| const sequelize = require('../assets/utils/database'); | ||||
| const sequelize = require('../assets/utils/database') | ||||
|  | ||||
| module.exports = sequelize.define('favorite', {}); | ||||
| module.exports = sequelize.define('favorite', {}) | ||||
|   | ||||
| @@ -1,39 +1,39 @@ | ||||
| const Sequelize = require('sequelize'); | ||||
| const sequelize = require('../assets/utils/database'); | ||||
| const Sequelize = require('sequelize') | ||||
| const sequelize = require('../assets/utils/database') | ||||
|  | ||||
| module.exports = sequelize.define('function', { | ||||
|     title: { | ||||
|         type: Sequelize.STRING, | ||||
|         allowNull: false, | ||||
|     }, | ||||
|     slug: { | ||||
|         type: Sequelize.STRING, | ||||
|         allowNull: false, | ||||
|     }, | ||||
|     description: { | ||||
|         type: Sequelize.STRING, | ||||
|         allowNull: false | ||||
|     }, | ||||
|     image: { | ||||
|         type: Sequelize.STRING, | ||||
|         allowNull: false, | ||||
|         defaultValue: "/images/functions/default.png" | ||||
|     }, | ||||
|     type: { | ||||
|         type: Sequelize.STRING, | ||||
|         allowNull: false | ||||
|     }, | ||||
|     article: { | ||||
|         type: Sequelize.TEXT, | ||||
|         allowNull: true | ||||
|     }, | ||||
|     utilizationForm: { | ||||
|         type: Sequelize.TEXT, | ||||
|         allowNull: true | ||||
|     }, | ||||
|     isOnline: { | ||||
|         type: Sequelize.BOOLEAN, | ||||
|         allowNull: false, | ||||
|         defaultValue: 0 | ||||
|     } | ||||
| }); | ||||
|   title: { | ||||
|     type: Sequelize.STRING, | ||||
|     allowNull: false | ||||
|   }, | ||||
|   slug: { | ||||
|     type: Sequelize.STRING, | ||||
|     allowNull: false | ||||
|   }, | ||||
|   description: { | ||||
|     type: Sequelize.STRING, | ||||
|     allowNull: false | ||||
|   }, | ||||
|   image: { | ||||
|     type: Sequelize.STRING, | ||||
|     allowNull: false, | ||||
|     defaultValue: '/images/functions/default.png' | ||||
|   }, | ||||
|   type: { | ||||
|     type: Sequelize.STRING, | ||||
|     allowNull: false | ||||
|   }, | ||||
|   article: { | ||||
|     type: Sequelize.TEXT, | ||||
|     allowNull: true | ||||
|   }, | ||||
|   utilizationForm: { | ||||
|     type: Sequelize.TEXT, | ||||
|     allowNull: true | ||||
|   }, | ||||
|   isOnline: { | ||||
|     type: Sequelize.BOOLEAN, | ||||
|     allowNull: false, | ||||
|     defaultValue: 0 | ||||
|   } | ||||
| }) | ||||
|   | ||||
| @@ -1,18 +1,18 @@ | ||||
| const Sequelize = require('sequelize'); | ||||
| const sequelize = require('../assets/utils/database'); | ||||
| const Sequelize = require('sequelize') | ||||
| const sequelize = require('../assets/utils/database') | ||||
|  | ||||
| module.exports = sequelize.define('quote', { | ||||
|     quote: { | ||||
|         type: Sequelize.STRING, | ||||
|         allowNull: false, | ||||
|     }, | ||||
|     author: { | ||||
|         type: Sequelize.STRING, | ||||
|         allowNull: false, | ||||
|     }, | ||||
|     isValidated: { | ||||
|         type: Sequelize.BOOLEAN, | ||||
|         allowNull: false, | ||||
|         defaultValue: 0 | ||||
|     } | ||||
| }); | ||||
|   quote: { | ||||
|     type: Sequelize.STRING, | ||||
|     allowNull: false | ||||
|   }, | ||||
|   author: { | ||||
|     type: Sequelize.STRING, | ||||
|     allowNull: false | ||||
|   }, | ||||
|   isValidated: { | ||||
|     type: Sequelize.BOOLEAN, | ||||
|     allowNull: false, | ||||
|     defaultValue: 0 | ||||
|   } | ||||
| }) | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| const Sequelize = require('sequelize'); | ||||
| const sequelize = require('../assets/utils/database'); | ||||
| const Sequelize = require('sequelize') | ||||
| const sequelize = require('../assets/utils/database') | ||||
|  | ||||
| module.exports = sequelize.define('short_link', { | ||||
|     url: { | ||||
|         type: Sequelize.TEXT, | ||||
|         allowNull: false, | ||||
|     }, | ||||
|     shortcut: { | ||||
|         type: Sequelize.TEXT, | ||||
|         allowNull: false, | ||||
|     } | ||||
| }); | ||||
|   url: { | ||||
|     type: Sequelize.TEXT, | ||||
|     allowNull: false | ||||
|   }, | ||||
|   shortcut: { | ||||
|     type: Sequelize.TEXT, | ||||
|     allowNull: false | ||||
|   } | ||||
| }) | ||||
|   | ||||
| @@ -1,14 +1,14 @@ | ||||
| const Sequelize = require('sequelize'); | ||||
| const sequelize = require('../assets/utils/database'); | ||||
| const Sequelize = require('sequelize') | ||||
| const sequelize = require('../assets/utils/database') | ||||
|  | ||||
| module.exports = sequelize.define('task', { | ||||
|     task: { | ||||
|         type: Sequelize.STRING, | ||||
|         allowNull: false, | ||||
|     }, | ||||
|     isCompleted: { | ||||
|         type: Sequelize.BOOLEAN, | ||||
|         allowNull: false, | ||||
|         defaultValue: 0 | ||||
|     } | ||||
| }); | ||||
|   task: { | ||||
|     type: Sequelize.STRING, | ||||
|     allowNull: false | ||||
|   }, | ||||
|   isCompleted: { | ||||
|     type: Sequelize.BOOLEAN, | ||||
|     allowNull: false, | ||||
|     defaultValue: 0 | ||||
|   } | ||||
| }) | ||||
|   | ||||
| @@ -1,45 +1,45 @@ | ||||
| const Sequelize = require('sequelize'); | ||||
| const sequelize = require('../assets/utils/database'); | ||||
| const Sequelize = require('sequelize') | ||||
| const sequelize = require('../assets/utils/database') | ||||
|  | ||||
| module.exports = sequelize.define('user', { | ||||
|     name: { | ||||
|         type: Sequelize.STRING, | ||||
|         allowNull: false, | ||||
|     }, | ||||
|     email: { | ||||
|         type: Sequelize.STRING, | ||||
|         allowNull: false, | ||||
|     }, | ||||
|     password: { | ||||
|         type: Sequelize.STRING, | ||||
|         allowNull: false, | ||||
|     }, | ||||
|     biography: { | ||||
|         type: Sequelize.TEXT, | ||||
|         defaultValue: "" | ||||
|     }, | ||||
|     logo: { | ||||
|         type: Sequelize.STRING, | ||||
|         defaultValue: "/images/users/default.png" | ||||
|     }, | ||||
|     isConfirmed: { | ||||
|         type: Sequelize.BOOLEAN, | ||||
|         defaultValue: false  | ||||
|     }, | ||||
|     isPublicEmail: { | ||||
|         type: Sequelize.BOOLEAN, | ||||
|         defaultValue: false  | ||||
|     }, | ||||
|     isAdmin: { | ||||
|         type: Sequelize.BOOLEAN, | ||||
|         defaultValue: false | ||||
|     }, | ||||
|     tempToken: { | ||||
|         type: Sequelize.TEXT, | ||||
|         allowNull: true | ||||
|     }, | ||||
|     tempExpirationToken: { | ||||
|         type: Sequelize.DATE, | ||||
|         allowNull: true | ||||
|     } | ||||
| }); | ||||
|   name: { | ||||
|     type: Sequelize.STRING, | ||||
|     allowNull: false | ||||
|   }, | ||||
|   email: { | ||||
|     type: Sequelize.STRING, | ||||
|     allowNull: false | ||||
|   }, | ||||
|   password: { | ||||
|     type: Sequelize.STRING, | ||||
|     allowNull: false | ||||
|   }, | ||||
|   biography: { | ||||
|     type: Sequelize.TEXT, | ||||
|     defaultValue: '' | ||||
|   }, | ||||
|   logo: { | ||||
|     type: Sequelize.STRING, | ||||
|     defaultValue: '/images/users/default.png' | ||||
|   }, | ||||
|   isConfirmed: { | ||||
|     type: Sequelize.BOOLEAN, | ||||
|     defaultValue: false | ||||
|   }, | ||||
|   isPublicEmail: { | ||||
|     type: Sequelize.BOOLEAN, | ||||
|     defaultValue: false | ||||
|   }, | ||||
|   isAdmin: { | ||||
|     type: Sequelize.BOOLEAN, | ||||
|     defaultValue: false | ||||
|   }, | ||||
|   tempToken: { | ||||
|     type: Sequelize.TEXT, | ||||
|     allowNull: true | ||||
|   }, | ||||
|   tempExpirationToken: { | ||||
|     type: Sequelize.DATE, | ||||
|     allowNull: true | ||||
|   } | ||||
| }) | ||||
|   | ||||
							
								
								
									
										2895
									
								
								api/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2895
									
								
								api/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,15 +1,12 @@ | ||||
| { | ||||
|   "name": "api", | ||||
|   "version": "2.0.0", | ||||
|   "version": "2.1.0", | ||||
|   "description": "Backend REST API for FunctionProject", | ||||
|   "main": "app.js", | ||||
|   "scripts": { | ||||
|     "start": "node app.js", | ||||
|     "dev": "nodemon app.js" | ||||
|     "dev": "nodemon app.js", | ||||
|     "format": "standard \"./**/*.js\" --fix | snazzy || exit 0" | ||||
|   }, | ||||
|   "keywords": [], | ||||
|   "author": "Divlo", | ||||
|   "license": "MIT", | ||||
|   "dependencies": { | ||||
|     "axios": "^0.19.2", | ||||
|     "bcryptjs": "^2.4.3", | ||||
| @@ -28,11 +25,13 @@ | ||||
|     "sequelize": "^5.21.5", | ||||
|     "smart-request-balancer": "^2.1.1", | ||||
|     "uuid": "^7.0.2", | ||||
|     "validator": "^13.0.0" | ||||
|     "validator": "^13.0.0", | ||||
|     "dotenv": "^8.2.0", | ||||
|     "morgan": "^1.9.1" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "dotenv": "^8.2.0", | ||||
|     "morgan": "^1.9.1", | ||||
|     "nodemon": "^2.0.2" | ||||
|     "nodemon": "^2.0.4", | ||||
|     "snazzy": "^8.0.0", | ||||
|     "standard": "^14.3.4" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,205 +1,223 @@ | ||||
| const { Router }      = require('express'); | ||||
| const fileUpload      = require('express-fileupload'); | ||||
| const { body }        = require('express-validator'); | ||||
| const adminController = require('../controllers/admin'); | ||||
| const Functions       = require('../models/functions'); | ||||
| const Categories      = require('../models/categories'); | ||||
| const { Router } = require('express') | ||||
| const fileUpload = require('express-fileupload') | ||||
| const { body } = require('express-validator') | ||||
| const adminController = require('../controllers/admin') | ||||
| const Functions = require('../models/functions') | ||||
| const Categories = require('../models/categories') | ||||
|  | ||||
| const AdminRouter = Router(); | ||||
| const AdminRouter = Router() | ||||
|  | ||||
| AdminRouter.route('/functions') | ||||
|  | ||||
|     // Récupère les fonctions | ||||
|     .get(adminController.getFunctions) | ||||
|   // Récupère les fonctions | ||||
|   .get(adminController.getFunctions) | ||||
|  | ||||
|     // Permet de créé une fonction | ||||
|     .post(fileUpload({  | ||||
|         useTempFiles: true,  | ||||
|         safeFileNames: true, | ||||
|         preserveExtension: Number, | ||||
|         limits: { fileSize: 5 * 1024 * 1024 }, // 5mb, | ||||
|         parseNested: true | ||||
|   // Permet de créé une fonction | ||||
|   .post( | ||||
|     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.") | ||||
|             .custom(((title) => { | ||||
|                 if (title === 'undefined') { | ||||
|                     return Promise.reject("La fonction doit avoir un titre."); | ||||
|                 } | ||||
|                 return true; | ||||
|             })), | ||||
|         body('slug') | ||||
|             .not() | ||||
|             .isEmpty() | ||||
|             .withMessage("La fonction doit avoir un slug.") | ||||
|             .isLength({ max: 100 }) | ||||
|             .withMessage("Le slug est trop long.") | ||||
|             .custom(((slug) => { | ||||
|                 if (slug === 'undefined') { | ||||
|                     return Promise.reject("La fonction doit avoir un slug."); | ||||
|                 } | ||||
|                 return true; | ||||
|             })) | ||||
|             .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, min: 1 }) | ||||
|             .withMessage("La description est trop longue.") | ||||
|             .custom(((description) => { | ||||
|                 if (description === 'undefined') { | ||||
|                     return Promise.reject("La fonction doit avoir une description."); | ||||
|                 } | ||||
|                 return true; | ||||
|             })), | ||||
|         body('categorieId') | ||||
|             .not() | ||||
|             .isEmpty() | ||||
|             .withMessage("La fonction doit avoir une catégorie.") | ||||
|             .custom(async (categorieId) => { | ||||
|                 try { | ||||
|                     const categorieFound = await Categories.findOne({ where: { id: parseInt(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; | ||||
|       body('title') | ||||
|         .not() | ||||
|         .isEmpty() | ||||
|         .withMessage('La fonction doit avoir un titre.') | ||||
|         .isLength({ max: 100 }) | ||||
|         .withMessage('Le titre est trop long.') | ||||
|         .custom(title => { | ||||
|           if (title === 'undefined') { | ||||
|             return Promise.reject(new Error('La fonction doit avoir un titre.')) | ||||
|           } | ||||
|           return true | ||||
|         }), | ||||
|       body('slug') | ||||
|         .not() | ||||
|         .isEmpty() | ||||
|         .withMessage('La fonction doit avoir un slug.') | ||||
|         .isLength({ max: 100 }) | ||||
|         .withMessage('Le slug est trop long.') | ||||
|         .custom(slug => { | ||||
|           if (slug === 'undefined') { | ||||
|             return Promise.reject(new Error('La fonction doit avoir un slug.')) | ||||
|           } | ||||
|           return true | ||||
|         }) | ||||
|         .custom(async slug => { | ||||
|           try { | ||||
|             const FunctionSlug = await Functions.findOne({ where: { slug } }) | ||||
|             if (FunctionSlug) { | ||||
|               return Promise.reject(new Error('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, min: 1 }) | ||||
|         .withMessage('La description est trop longue.') | ||||
|         .custom(description => { | ||||
|           if (description === 'undefined') { | ||||
|             return Promise.reject( | ||||
|               new Error('La fonction doit avoir une description.') | ||||
|             ) | ||||
|           } | ||||
|           return true | ||||
|         }), | ||||
|       body('categorieId') | ||||
|         .not() | ||||
|         .isEmpty() | ||||
|         .withMessage('La fonction doit avoir une catégorie.') | ||||
|         .custom(async categorieId => { | ||||
|           try { | ||||
|             const categorieFound = await Categories.findOne({ | ||||
|               where: { id: parseInt(categorieId) } | ||||
|             }) | ||||
|     ], adminController.postFunction); | ||||
|             if (!categorieFound) { | ||||
|               return Promise.reject(new Error("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( | ||||
|             new Error( | ||||
|               'Le type de la fonction peut être : article, form ou page.' | ||||
|             ) | ||||
|           ) | ||||
|         } | ||||
|         return true | ||||
|       }) | ||||
|     ], | ||||
|     adminController.postFunction | ||||
|   ) | ||||
|  | ||||
| AdminRouter.route('/functions/:slug') | ||||
|  | ||||
|     // Récupère les informations d'une fonction | ||||
|     .get(adminController.getFunctionBySlug); | ||||
|   // Récupère les informations d'une fonction | ||||
|   .get(adminController.getFunctionBySlug) | ||||
|  | ||||
| AdminRouter.route('/functions/:id') | ||||
|  | ||||
|     // Modifie information basique d'une fonction | ||||
|     .put(fileUpload({  | ||||
|             useTempFiles: true,  | ||||
|             safeFileNames: true, | ||||
|             preserveExtension: Number, | ||||
|             limits: { fileSize: 5 * 1024 * 1024 }, // 5mb, | ||||
|             parseNested: true | ||||
|   // Modifie information basique d'une fonction | ||||
|   .put( | ||||
|     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.") | ||||
|             .custom(((title) => { | ||||
|                 if (title === 'undefined') { | ||||
|                     return Promise.reject("La fonction doit avoir un titre."); | ||||
|                 } | ||||
|                 return true; | ||||
|             })), | ||||
|         body('slug') | ||||
|             .not() | ||||
|             .isEmpty() | ||||
|             .withMessage("La fonction doit avoir un slug.") | ||||
|             .isLength({ max: 100 }) | ||||
|             .withMessage("Le slug est trop long.") | ||||
|             .custom(((slug) => { | ||||
|                 if (slug === 'undefined') { | ||||
|                     return Promise.reject("La fonction doit avoir un slug."); | ||||
|                 } | ||||
|                 return true; | ||||
|             })), | ||||
|         body('description') | ||||
|             .not() | ||||
|             .isEmpty() | ||||
|             .withMessage("La fonction doit avoir une description.") | ||||
|             .isLength({ max: 255, min: 1 }) | ||||
|             .withMessage("La description est trop longue.") | ||||
|             .custom(((description) => { | ||||
|                 if (description === 'undefined') { | ||||
|                     return Promise.reject("La fonction doit avoir une description."); | ||||
|                 } | ||||
|                 return true; | ||||
|             })), | ||||
|         body('categorieId') | ||||
|             .not() | ||||
|             .isEmpty() | ||||
|             .withMessage("La fonction doit avoir une catégorie.") | ||||
|             .custom(async (categorieId) => { | ||||
|                 try { | ||||
|                     const categorieFound = await Categories.findOne({ where: { id: parseInt(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; | ||||
|       body('title') | ||||
|         .not() | ||||
|         .isEmpty() | ||||
|         .withMessage('La fonction doit avoir un titre.') | ||||
|         .isLength({ max: 100 }) | ||||
|         .withMessage('Le titre est trop long.') | ||||
|         .custom(title => { | ||||
|           if (title === 'undefined') { | ||||
|             return Promise.reject(new Error('La fonction doit avoir un titre.')) | ||||
|           } | ||||
|           return true | ||||
|         }), | ||||
|       body('slug') | ||||
|         .not() | ||||
|         .isEmpty() | ||||
|         .withMessage('La fonction doit avoir un slug.') | ||||
|         .isLength({ max: 100 }) | ||||
|         .withMessage('Le slug est trop long.') | ||||
|         .custom(slug => { | ||||
|           if (slug === 'undefined') { | ||||
|             return Promise.reject(new Error('La fonction doit avoir un slug.')) | ||||
|           } | ||||
|           return true | ||||
|         }), | ||||
|       body('description') | ||||
|         .not() | ||||
|         .isEmpty() | ||||
|         .withMessage('La fonction doit avoir une description.') | ||||
|         .isLength({ max: 255, min: 1 }) | ||||
|         .withMessage('La description est trop longue.') | ||||
|         .custom(description => { | ||||
|           if (description === 'undefined') { | ||||
|             return Promise.reject( | ||||
|               new Error('La fonction doit avoir une description.') | ||||
|             ) | ||||
|           } | ||||
|           return true | ||||
|         }), | ||||
|       body('categorieId') | ||||
|         .not() | ||||
|         .isEmpty() | ||||
|         .withMessage('La fonction doit avoir une catégorie.') | ||||
|         .custom(async categorieId => { | ||||
|           try { | ||||
|             const categorieFound = await Categories.findOne({ | ||||
|               where: { id: parseInt(categorieId) } | ||||
|             }) | ||||
|     ], adminController.putFunction) | ||||
|             if (!categorieFound) { | ||||
|               return Promise.reject(new Error("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( | ||||
|             new Error( | ||||
|               'Le type de la fonction peut être : article, form ou page.' | ||||
|             ) | ||||
|           ) | ||||
|         } | ||||
|         return true | ||||
|       }) | ||||
|     ], | ||||
|     adminController.putFunction | ||||
|   ) | ||||
|  | ||||
|     // Supprime une fonction avec son id | ||||
|     .delete(adminController.deleteFunction); | ||||
|   // Supprime une fonction avec son id | ||||
|   .delete(adminController.deleteFunction) | ||||
|  | ||||
| AdminRouter.route('/functions/article/:id') | ||||
|  | ||||
|     .put(adminController.putFunctionArticle); | ||||
|   .put(adminController.putFunctionArticle) | ||||
|  | ||||
| AdminRouter.route('/functions/form/:id') | ||||
|  | ||||
|     .put(adminController.putFunctionForm); | ||||
|   .put(adminController.putFunctionForm) | ||||
|  | ||||
| AdminRouter.route('/categories') | ||||
|  | ||||
|     // Crée une catégorie | ||||
|     .post(adminController.postCategory); | ||||
|   // Crée une catégorie | ||||
|   .post(adminController.postCategory) | ||||
|  | ||||
| AdminRouter.route('/categories/:id') | ||||
|  | ||||
|     // Modifier une catégorie avec son id | ||||
|     .put(adminController.putCategory) | ||||
|   // Modifier une catégorie avec son id | ||||
|   .put(adminController.putCategory) | ||||
|  | ||||
|     // Supprime une catégorie avec son id | ||||
|     .delete(adminController.deleteCategory); | ||||
|   // Supprime une catégorie avec son id | ||||
|   .delete(adminController.deleteCategory) | ||||
|  | ||||
| AdminRouter.route('/quotes') | ||||
|  | ||||
|     // Récupère les citations pas encore validées | ||||
|     .get(adminController.getQuotes); | ||||
|   // Récupère les citations pas encore validées | ||||
|   .get(adminController.getQuotes) | ||||
|  | ||||
| AdminRouter.route('/quotes/:id') | ||||
|  | ||||
|     // Valide ou supprime une citation | ||||
|     .put(adminController.putQuote); | ||||
|   // Valide ou supprime une citation | ||||
|   .put(adminController.putQuote) | ||||
|  | ||||
| module.exports = AdminRouter; | ||||
| module.exports = AdminRouter | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| const { Router }           = require('express'); | ||||
| const categoriesController = require('../controllers/categories'); | ||||
| const { Router } = require('express') | ||||
| const categoriesController = require('../controllers/categories') | ||||
|  | ||||
| const CategoriesRouter = Router(); | ||||
| const CategoriesRouter = Router() | ||||
|  | ||||
| CategoriesRouter.route('/') | ||||
|  | ||||
|     // Récupère les catégories | ||||
|     .get(categoriesController.getCategories); | ||||
|   // Récupère les catégories | ||||
|   .get(categoriesController.getCategories) | ||||
|  | ||||
| module.exports = CategoriesRouter; | ||||
| module.exports = CategoriesRouter | ||||
|   | ||||
| @@ -1,23 +1,23 @@ | ||||
| const { Router }         = require('express'); | ||||
| const commentsController = require('../controllers/comments'); | ||||
| const isAuth             = require('../middlewares/isAuth'); | ||||
| const { Router } = require('express') | ||||
| const commentsController = require('../controllers/comments') | ||||
| const isAuth = require('../middlewares/isAuth') | ||||
|  | ||||
| const CommentsRouter = Router(); | ||||
| const CommentsRouter = Router() | ||||
|  | ||||
| CommentsRouter.route('/:commentId') | ||||
|  | ||||
|     // Modifier un commentaire | ||||
|     .put(isAuth, commentsController.putCommentsById) | ||||
|   // Modifier un commentaire | ||||
|   .put(isAuth, commentsController.putCommentsById) | ||||
|  | ||||
|     // Supprime un commentaire | ||||
|     .delete(isAuth, commentsController.deleteCommentById); | ||||
|   // Supprime un commentaire | ||||
|   .delete(isAuth, commentsController.deleteCommentById) | ||||
|  | ||||
| CommentsRouter.route('/:functionId') | ||||
|  | ||||
|     // Récupère les commentaires | ||||
|     .get(commentsController.getCommentsByFunctionId) | ||||
|   // Récupère les commentaires | ||||
|   .get(commentsController.getCommentsByFunctionId) | ||||
|  | ||||
|     // Permet à un utilisateur de poster un commentaire sur une fonction | ||||
|     .post(isAuth, commentsController.postCommentsByFunctionId); | ||||
|   // Permet à un utilisateur de poster un commentaire sur une fonction | ||||
|   .post(isAuth, commentsController.postCommentsByFunctionId) | ||||
|  | ||||
| module.exports = CommentsRouter; | ||||
| module.exports = CommentsRouter | ||||
|   | ||||
| @@ -1,18 +1,18 @@ | ||||
| const { Router }          = require('express'); | ||||
| const favoritesController = require('../controllers/favorites'); | ||||
| const isAuth              = require('../middlewares/isAuth'); | ||||
| const { Router } = require('express') | ||||
| const favoritesController = require('../controllers/favorites') | ||||
| const isAuth = require('../middlewares/isAuth') | ||||
|  | ||||
| const FavoritesRouter = Router(); | ||||
| const FavoritesRouter = Router() | ||||
|  | ||||
| FavoritesRouter.route('/:functionId') | ||||
|  | ||||
|     // Récupère si une fonction est en favoris (d'un utilisateur) | ||||
|     .get(isAuth, favoritesController.getFavoriteByFunctionId) | ||||
|   // Récupère si une fonction est en favoris (d'un utilisateur) | ||||
|   .get(isAuth, favoritesController.getFavoriteByFunctionId) | ||||
|  | ||||
|     // Permet à un utilisateur d'ajouter une fonction aux favoris | ||||
|     .post(isAuth, favoritesController.postFavoriteByFunctionId) | ||||
|   // Permet à un utilisateur d'ajouter une fonction aux favoris | ||||
|   .post(isAuth, favoritesController.postFavoriteByFunctionId) | ||||
|  | ||||
|     // Supprime une fonction des favoris d'un utilisateur | ||||
|     .delete(isAuth, favoritesController.deleteFavoriteByFunctionId); | ||||
|   // Supprime une fonction des favoris d'un utilisateur | ||||
|   .delete(isAuth, favoritesController.deleteFavoriteByFunctionId) | ||||
|  | ||||
| module.exports = FavoritesRouter; | ||||
| module.exports = FavoritesRouter | ||||
|   | ||||
| @@ -1,19 +1,19 @@ | ||||
| const { Router }          = require('express'); | ||||
| const functionsController = require('../controllers/functions'); | ||||
| const { Router } = require('express') | ||||
| const functionsController = require('../controllers/functions') | ||||
|  | ||||
| const FunctionsRouter = Router(); | ||||
| const FunctionsRouter = Router() | ||||
|  | ||||
| FunctionsRouter.route('/') | ||||
|  | ||||
|     // Récupère les fonctions | ||||
|     .get(functionsController.getFunctions); | ||||
|   // Récupère les fonctions | ||||
|   .get(functionsController.getFunctions) | ||||
|  | ||||
| FunctionsRouter.route('/:slug') | ||||
|  | ||||
|     // Récupère les informations de la fonction par son slug | ||||
|     .get(functionsController.getFunctionBySlug) | ||||
|   // Récupère les informations de la fonction par son slug | ||||
|   .get(functionsController.getFunctionBySlug) | ||||
|  | ||||
|     // Exécute la fonction demandée en paramètre | ||||
|     .post(functionsController.executeFunctionBySlug); | ||||
|   // Exécute la fonction demandée en paramètre | ||||
|   .post(functionsController.executeFunctionBySlug) | ||||
|  | ||||
| module.exports = FunctionsRouter; | ||||
| module.exports = FunctionsRouter | ||||
|   | ||||
							
								
								
									
										23
									
								
								api/routes/links_shortener.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								api/routes/links_shortener.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| const { Router } = require('express') | ||||
| const linksShortenerController = require('../controllers/links_shortener') | ||||
| const isAuth = require('../middlewares/isAuth') | ||||
|  | ||||
| const LinksShortenerRouter = Router() | ||||
|  | ||||
| LinksShortenerRouter.route('/') | ||||
|  | ||||
|   // Récupère les liens d'un utilisateur | ||||
|   .get(isAuth, linksShortenerController.getLinks) | ||||
|  | ||||
|   // Ajouter un lien à raccourcir d'un utilisateur | ||||
|   .post(isAuth, linksShortenerController.postLink) | ||||
|  | ||||
| LinksShortenerRouter.route('/:id') | ||||
|  | ||||
|   // Permet de modifier le lien raccourci d'un utilisateur | ||||
|   .put(isAuth, linksShortenerController.putLink) | ||||
|  | ||||
|   // Supprimer un lien d'un utilisateur | ||||
|   .delete(isAuth, linksShortenerController.deleteLink) | ||||
|  | ||||
| module.exports = LinksShortenerRouter | ||||
| @@ -1,15 +1,15 @@ | ||||
| const { Router }       = require('express'); | ||||
| const quotesController = require('../controllers/quotes'); | ||||
| const isAuth           = require('../middlewares/isAuth'); | ||||
| const { Router } = require('express') | ||||
| const quotesController = require('../controllers/quotes') | ||||
| const isAuth = require('../middlewares/isAuth') | ||||
|  | ||||
| const QuotesRouter = Router(); | ||||
| const QuotesRouter = Router() | ||||
|  | ||||
| QuotesRouter.route('/') | ||||
|  | ||||
|     // Récupère les citations | ||||
|     .get(quotesController.getQuotes) | ||||
|   // Récupère les citations | ||||
|   .get(quotesController.getQuotes) | ||||
|  | ||||
|     // Proposer une citation | ||||
|     .post(isAuth, quotesController.postQuote); | ||||
|   // Proposer une citation | ||||
|   .post(isAuth, quotesController.postQuote) | ||||
|  | ||||
| module.exports = QuotesRouter; | ||||
| module.exports = QuotesRouter | ||||
|   | ||||
| @@ -1,23 +1,23 @@ | ||||
| const { Router }      = require('express'); | ||||
| const tasksController = require('../controllers/tasks'); | ||||
| const isAuth          = require('../middlewares/isAuth'); | ||||
| const { Router } = require('express') | ||||
| const tasksController = require('../controllers/tasks') | ||||
| const isAuth = require('../middlewares/isAuth') | ||||
|  | ||||
| const TasksRouter = Router(); | ||||
| const TasksRouter = Router() | ||||
|  | ||||
| TasksRouter.route('/') | ||||
|  | ||||
|     // Récupère les tâches à faire d'un user | ||||
|     .get(isAuth, tasksController.getTasks) | ||||
|   // Récupère les tâches à faire d'un user | ||||
|   .get(isAuth, tasksController.getTasks) | ||||
|  | ||||
|     // Poster une nouvelle tâche à faire | ||||
|     .post(isAuth, tasksController.postTask); | ||||
|   // Poster une nouvelle tâche à faire | ||||
|   .post(isAuth, tasksController.postTask) | ||||
|  | ||||
| TasksRouter.route('/:id') | ||||
|  | ||||
|     // Permet de mettre une tâche à faire en isCompleted ou !isCompleted | ||||
|     .put(isAuth, tasksController.putTask) | ||||
|   // Permet de mettre une tâche à faire en isCompleted ou !isCompleted | ||||
|   .put(isAuth, tasksController.putTask) | ||||
|  | ||||
|     // Supprimer une tâche à faire | ||||
|     .delete(isAuth, tasksController.deleteTask); | ||||
|   // Supprimer une tâche à faire | ||||
|   .delete(isAuth, tasksController.deleteTask) | ||||
|  | ||||
| module.exports = TasksRouter; | ||||
| module.exports = TasksRouter | ||||
|   | ||||
| @@ -1,145 +1,167 @@ | ||||
| const { Router }         = require('express'); | ||||
| const { body }           = require('express-validator'); | ||||
| const fileUpload         = require('express-fileupload'); | ||||
| const usersController    = require('../controllers/users'); | ||||
| const { requiredFields } = require('../assets/config/errors'); | ||||
| const Users              = require('../models/users'); | ||||
| const isAuth             = require('../middlewares/isAuth'); | ||||
| const { Router } = require('express') | ||||
| const { body } = require('express-validator') | ||||
| const fileUpload = require('express-fileupload') | ||||
| const usersController = require('../controllers/users') | ||||
| const { requiredFields } = require('../assets/config/errors') | ||||
| const Users = require('../models/users') | ||||
| const isAuth = require('../middlewares/isAuth') | ||||
|  | ||||
| const UsersRouter = Router(); | ||||
| const UsersRouter = Router() | ||||
|  | ||||
| UsersRouter.route('/') | ||||
|  | ||||
|     // Récupère les utilisateurs | ||||
|     .get(usersController.getUsers) | ||||
|   // Récupère les utilisateurs | ||||
|   .get(usersController.getUsers) | ||||
|  | ||||
|     // Permet de modifier son profil | ||||
|     .put(isAuth,  | ||||
|     fileUpload({  | ||||
|         useTempFiles: true,  | ||||
|         safeFileNames: true, | ||||
|         preserveExtension: Number, | ||||
|         limits: { fileSize: 5 * 1024 * 1024 }, // 5mb, | ||||
|         parseNested: true | ||||
|   // Permet de modifier son profil | ||||
|   .put( | ||||
|     isAuth, | ||||
|     fileUpload({ | ||||
|       useTempFiles: true, | ||||
|       safeFileNames: true, | ||||
|       preserveExtension: Number, | ||||
|       limits: { fileSize: 5 * 1024 * 1024 }, // 5mb, | ||||
|       parseNested: true | ||||
|     }), | ||||
|     [ | ||||
|         body('email') | ||||
|             .isEmail() | ||||
|             .withMessage("Veuillez rentré une adresse mail valide.") | ||||
|             .custom((async (email) => { | ||||
|                 try { | ||||
|                     const user = await Users.findOne({ where: { email } }); | ||||
|                     if (user && user.email !== email) { | ||||
|                         return Promise.reject("L'adresse email existe déjà..."); | ||||
|                     } | ||||
|                 } catch (error) { | ||||
|                     return console.log(error); | ||||
|                 } | ||||
|                 return true; | ||||
|             })) | ||||
|             .normalizeEmail(), | ||||
|         body('name') | ||||
|             .trim() | ||||
|             .not() | ||||
|             .isEmpty() | ||||
|             .withMessage("Vous devez avoir un nom (ou pseudo).") | ||||
|             .isAlphanumeric() | ||||
|             .withMessage("Votre nom ne peut contenir que des lettres ou/et des nombres.") | ||||
|             .isLength({ max: 30 }) | ||||
|             .withMessage("Votre nom est trop long") | ||||
|             .custom(async (name) => { | ||||
|                 try { | ||||
|                     const user = await Users.findOne({ where: { name } }); | ||||
|                     if (user && user.name !== name) { | ||||
|                         return Promise.reject("Le nom existe déjà..."); | ||||
|                     } | ||||
|                 } catch (error) { | ||||
|                     console.log(error); | ||||
|                 } | ||||
|                 return true; | ||||
|             }), | ||||
|         body('isPublicEmail') | ||||
|             .isBoolean() | ||||
|             .withMessage("L'adresse email peut être public ou privé, rien d'autre."), | ||||
|         body('biography') | ||||
|             .trim() | ||||
|             .escape() | ||||
|     ], usersController.putUser); | ||||
|  | ||||
| // Permet de se connecter | ||||
| UsersRouter.post('/login', [ | ||||
|     body('email') | ||||
|         .not() | ||||
|         .isEmpty() | ||||
|         .withMessage(requiredFields.message), | ||||
|     body('password') | ||||
|         .not() | ||||
|         .isEmpty() | ||||
|         .withMessage(requiredFields.message) | ||||
| ], usersController.login); | ||||
|  | ||||
| // Récupère les informations public d'un profil | ||||
| UsersRouter.get('/:name', usersController.getUserInfo); | ||||
|  | ||||
| // Permet de s'inscrire | ||||
| UsersRouter.post('/register', [ | ||||
|     body('email') | ||||
|       body('email') | ||||
|         .isEmail() | ||||
|         .withMessage("Veuillez rentré une adresse mail valide.") | ||||
|         .custom((async (email) => { | ||||
|             try { | ||||
|                 const user = await Users.findOne({ where: { email } }); | ||||
|                 if (user) { | ||||
|                     return Promise.reject("L'adresse email existe déjà..."); | ||||
|                 } | ||||
|             } catch (error) { | ||||
|                 return console.log(error); | ||||
|         .withMessage('Veuillez rentré une adresse mail valide.') | ||||
|         .custom(async email => { | ||||
|           try { | ||||
|             const user = await Users.findOne({ where: { email } }) | ||||
|             if (user && user.email !== email) { | ||||
|               return Promise.reject(new Error("L'adresse email existe déjà...")) | ||||
|             } | ||||
|             return true; | ||||
|         })) | ||||
|           } catch (error) { | ||||
|             return console.log(error) | ||||
|           } | ||||
|           return true | ||||
|         }) | ||||
|         .normalizeEmail(), | ||||
|     body('password') | ||||
|         .isLength({ min: 4 }) | ||||
|         .withMessage("Votre mot de passe est trop court!"), | ||||
|     body('name') | ||||
|       body('name') | ||||
|         .trim() | ||||
|         .not() | ||||
|         .isEmpty() | ||||
|         .withMessage("Vous devez avoir un nom (ou pseudo).") | ||||
|         .withMessage('Vous devez avoir un nom (ou pseudo).') | ||||
|         .isAlphanumeric() | ||||
|         .withMessage("Votre nom ne peut contenir que des lettres ou/et des nombres.") | ||||
|         .withMessage( | ||||
|           'Votre nom ne peut contenir que des lettres ou/et des nombres.' | ||||
|         ) | ||||
|         .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) { | ||||
|                 console.log(error); | ||||
|         .withMessage('Votre nom est trop long') | ||||
|         .custom(async name => { | ||||
|           try { | ||||
|             const user = await Users.findOne({ where: { name } }) | ||||
|             if (user && user.name !== name) { | ||||
|               return Promise.reject(new Error('Le nom existe déjà...')) | ||||
|             } | ||||
|             return true; | ||||
|         }) | ||||
| ], usersController.register); | ||||
|           } catch (error) { | ||||
|             console.log(error) | ||||
|           } | ||||
|           return true | ||||
|         }), | ||||
|       body('isPublicEmail') | ||||
|         .isBoolean() | ||||
|         .withMessage( | ||||
|           "L'adresse email peut être public ou privé, rien d'autre." | ||||
|         ), | ||||
|       body('biography') | ||||
|         .trim() | ||||
|         .escape() | ||||
|     ], | ||||
|     usersController.putUser | ||||
|   ) | ||||
|  | ||||
| // Permet de se connecter | ||||
| UsersRouter.post( | ||||
|   '/login', | ||||
|   [ | ||||
|     body('email') | ||||
|       .not() | ||||
|       .isEmpty() | ||||
|       .withMessage(requiredFields.message), | ||||
|     body('password') | ||||
|       .not() | ||||
|       .isEmpty() | ||||
|       .withMessage(requiredFields.message) | ||||
|   ], | ||||
|   usersController.login | ||||
| ) | ||||
|  | ||||
| // Récupère les informations public d'un profil | ||||
| UsersRouter.get('/:name', usersController.getUserInfo) | ||||
|  | ||||
| // Permet de s'inscrire | ||||
| UsersRouter.post( | ||||
|   '/register', | ||||
|   [ | ||||
|     body('email') | ||||
|       .isEmail() | ||||
|       .withMessage('Veuillez rentré une adresse mail valide.') | ||||
|       .custom(async email => { | ||||
|         try { | ||||
|           const user = await Users.findOne({ where: { email } }) | ||||
|           if (user) { | ||||
|             return Promise.reject(new Error("L'adresse email existe déjà...")) | ||||
|           } | ||||
|         } catch (error) { | ||||
|           return console.log(error) | ||||
|         } | ||||
|         return true | ||||
|       }), | ||||
|     body('password') | ||||
|       .isLength({ min: 4 }) | ||||
|       .withMessage('Votre mot de passe est trop court!'), | ||||
|     body('name') | ||||
|       .trim() | ||||
|       .not() | ||||
|       .isEmpty() | ||||
|       .withMessage('Vous devez avoir un nom (ou pseudo).') | ||||
|       .isAlphanumeric() | ||||
|       .withMessage( | ||||
|         'Votre nom ne peut contenir que des lettres ou/et des nombres.' | ||||
|       ) | ||||
|       .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(new Error('Le nom existe déjà...')) | ||||
|           } | ||||
|         } catch (error) { | ||||
|           console.log(error) | ||||
|         } | ||||
|         return true | ||||
|       }) | ||||
|   ], | ||||
|   usersController.register | ||||
| ) | ||||
|  | ||||
| // Confirme l'inscription | ||||
| UsersRouter.get('/confirm-email/:tempToken', usersController.confirmEmail); | ||||
| UsersRouter.get('/confirm-email/:tempToken', usersController.confirmEmail) | ||||
|  | ||||
| UsersRouter.route('/reset-password') | ||||
|  | ||||
|     // Demande une réinitialisation du mot de passe  | ||||
|     .post([ | ||||
|         body('email') | ||||
|             .isEmail() | ||||
|             .withMessage("Veuillez rentré une adresse mail valide.") | ||||
|     ], usersController.resetPassword) | ||||
|   // Demande une réinitialisation du mot de passe | ||||
|   .post( | ||||
|     [ | ||||
|       body('email') | ||||
|         .isEmail() | ||||
|         .withMessage('Veuillez rentré une adresse mail valide.') | ||||
|     ], | ||||
|     usersController.resetPassword | ||||
|   ) | ||||
|  | ||||
|     // Nouveau mot de passe | ||||
|     .put([ | ||||
|         body('password') | ||||
|             .isLength({ min: 4 }) | ||||
|             .withMessage("Votre mot de passe est trop court!") | ||||
|     ], usersController.newPassword); | ||||
|   // Nouveau mot de passe | ||||
|   .put( | ||||
|     [ | ||||
|       body('password') | ||||
|         .isLength({ min: 4 }) | ||||
|         .withMessage('Votre mot de passe est trop court!') | ||||
|     ], | ||||
|     usersController.newPassword | ||||
|   ) | ||||
|  | ||||
| module.exports = UsersRouter; | ||||
| module.exports = UsersRouter | ||||
|   | ||||
							
								
								
									
										4
									
								
								s.divlo.fr/.env.example
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								s.divlo.fr/.env.example
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| DB_HOST = "" | ||||
| DB_NAME = "" | ||||
| DB_USER = "" | ||||
| DB_PASS = "" | ||||
							
								
								
									
										2
									
								
								s.divlo.fr/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								s.divlo.fr/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| node_modules | ||||
| .env | ||||
							
								
								
									
										3
									
								
								s.divlo.fr/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								s.divlo.fr/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| # s.divlo.fr | ||||
|  | ||||
| Site web qui permet de rediriger les utilisateurs vers leurs liens raccourcis sur [function.divlo.fr](https://function.divlo.fr/). | ||||
							
								
								
									
										72
									
								
								s.divlo.fr/app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								s.divlo.fr/app.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| /* Modules */ | ||||
| require('dotenv').config() | ||||
| const path = require('path') | ||||
| const express = require('express') | ||||
| const helmet = require('helmet') | ||||
| const morgan = require('morgan') | ||||
| const { redirectToHTTPS } = require('express-http-to-https') | ||||
| const mysql = require('mysql') | ||||
|  | ||||
| /* Files Imports & Variables */ | ||||
| const app = express() | ||||
| const database = mysql.createPool({ | ||||
|   host: process.env.DB_HOST, | ||||
|   user: process.env.DB_USER, | ||||
|   password: process.env.DB_PASS, | ||||
|   database: process.env.DB_NAME, | ||||
|   port: process.env.DB_PORT | ||||
| }) | ||||
|  | ||||
| /* Middlewares */ | ||||
| app.use(helmet()) | ||||
| app.use(morgan('dev')) | ||||
| app.use(express.json()) | ||||
| app.use(redirectToHTTPS([/localhost:(\d{4})/])) | ||||
|  | ||||
| /* EJS Template Engines */ | ||||
| app.set('view engine', 'ejs') | ||||
| app.set('views', path.join(__dirname, 'views')) | ||||
|  | ||||
| /* Routes */ | ||||
| app.use(express.static(path.join(__dirname, 'public'))) | ||||
|  | ||||
| app.get('/', (_req, res) => { | ||||
|   return res.render('index') | ||||
| }) | ||||
|  | ||||
| app.get('/:shortcut', (req, res, next) => { | ||||
|   const { shortcut } = req.params | ||||
|   if (shortcut == null) { | ||||
|     return res.redirect('/errors/404') | ||||
|   } | ||||
|   database.query( | ||||
|     'SELECT * FROM short_links WHERE shortcut = ?', | ||||
|     [shortcut], | ||||
|     (error, [result]) => { | ||||
|       if (error != null) { | ||||
|         return next(error) | ||||
|       } | ||||
|  | ||||
|       if (result == null) { | ||||
|         return res.redirect('/error/404') | ||||
|       } | ||||
|  | ||||
|       return res.redirect(result.url) | ||||
|     } | ||||
|   ) | ||||
| }) | ||||
|  | ||||
| /* Errors */ | ||||
| app.use((_req, res) => { | ||||
|   return res.status(404).render('errors') | ||||
| }) | ||||
| app.use((error, _req, res) => { | ||||
|   console.log(error) | ||||
|   return res.status(500).render('errors') | ||||
| }) | ||||
|  | ||||
| /* Server */ | ||||
| const PORT = process.env.PORT || 8000 | ||||
| app.listen(PORT, () => { | ||||
|   console.log('\x1b[36m%s\x1b[0m', `Started on port ${PORT}.`) | ||||
| }) | ||||
							
								
								
									
										3497
									
								
								s.divlo.fr/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										3497
									
								
								s.divlo.fr/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										24
									
								
								s.divlo.fr/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								s.divlo.fr/package.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| { | ||||
|   "name": "short.divlo.fr", | ||||
|   "version": "1.0.0", | ||||
|   "description": "Link shortener for FunctionProject", | ||||
|   "scripts": { | ||||
|     "start": "node app.js", | ||||
|     "dev": "nodemon app.js", | ||||
|     "format": "standard \"./**/*.js\" --fix | snazzy || exit 0" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "dotenv": "^8.2.0", | ||||
|     "ejs": "^3.1.3", | ||||
|     "express": "^4.17.1", | ||||
|     "express-http-to-https": "^1.1.4", | ||||
|     "helmet": "^4.0.0", | ||||
|     "morgan": "^1.10.0", | ||||
|     "mysql": "^2.18.1" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "nodemon": "^2.0.4", | ||||
|     "snazzy": "^8.0.0", | ||||
|     "standard": "^14.3.4" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								s.divlo.fr/public/images/error404.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								s.divlo.fr/public/images/error404.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 168 KiB | 
							
								
								
									
										
											BIN
										
									
								
								s.divlo.fr/public/images/linkShortener.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								s.divlo.fr/public/images/linkShortener.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 94 KiB | 
							
								
								
									
										20
									
								
								s.divlo.fr/views/errors.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								s.divlo.fr/views/errors.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
|   <head> | ||||
|     <meta charset="UTF-8" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||
|     <meta http-equiv="X-UA-Compatible" content="ie=edge" /> | ||||
|     <title>Short-links</title> | ||||
|     <link rel="icon" type="image/png" href="/images/error404.png" /> | ||||
|   </head> | ||||
|   <body> | ||||
|     <style type="text/css"> | ||||
|       * { | ||||
|         font-family: Arial; | ||||
|         color: #fff; | ||||
|         background-color: black; | ||||
|       } | ||||
|     </style> | ||||
|     <p>Adresse url non connue</p> | ||||
|   </body> | ||||
| </html> | ||||
							
								
								
									
										19
									
								
								s.divlo.fr/views/index.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								s.divlo.fr/views/index.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
|   <head> | ||||
|     <meta charset="UTF-8" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||
|     <meta http-equiv="X-UA-Compatible" content="ie=edge" /> | ||||
|     <title>Short-links</title> | ||||
|     <link rel="icon" type="image/png" href="/images/linkShortener.png" /> | ||||
|   </head> | ||||
|   <body> | ||||
|     <style type="text/css"> | ||||
|       * { | ||||
|         font-family: Arial; | ||||
|         color: #fff; | ||||
|         background-color: black; | ||||
|       } | ||||
|     </style> | ||||
|   </body> | ||||
| </html> | ||||
							
								
								
									
										1
									
								
								website/.env.example
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								website/.env.example
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| NEXT_PUBLIC_API_URL = "http://localhost:8080" | ||||
							
								
								
									
										3
									
								
								website/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								website/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -17,7 +17,8 @@ | ||||
|  | ||||
| # misc | ||||
| .DS_Store | ||||
| .env* | ||||
| .env | ||||
| .env.production | ||||
|  | ||||
| # debug | ||||
| npm-debug.log* | ||||
|   | ||||
| @@ -1,12 +0,0 @@ | ||||
| import SyntaxHighlighter from "react-syntax-highlighter"; | ||||
| import { atomOneDark as styles} from "react-syntax-highlighter/dist/cjs/styles/hljs"; | ||||
|  | ||||
| const CodeBlock = ({ language, value }) => { | ||||
|     return ( | ||||
|         <SyntaxHighlighter language={language} style={styles}> | ||||
|             {value} | ||||
|         </SyntaxHighlighter> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| export default CodeBlock; | ||||
							
								
								
									
										12
									
								
								website/components/CodeBlock.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								website/components/CodeBlock.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| import SyntaxHighlighter from 'react-syntax-highlighter' | ||||
| import { atomOneDark as styles } from 'react-syntax-highlighter/dist/cjs/styles/hljs' | ||||
|  | ||||
| const CodeBlock = ({ language, value }) => { | ||||
|   return ( | ||||
|     <SyntaxHighlighter language={language} style={styles}> | ||||
|       {value} | ||||
|     </SyntaxHighlighter> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default CodeBlock | ||||
| @@ -1,10 +1,10 @@ | ||||
| .footer { | ||||
|     border-top: var(--border-header-footer); | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
|   border-top: var(--border-header-footer); | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
| } | ||||
| .footer-text { | ||||
|     line-height: 2.5; | ||||
| } | ||||
|   line-height: 2.5; | ||||
| } | ||||
|   | ||||
| @@ -1,16 +0,0 @@ | ||||
| import Link from 'next/link'; | ||||
| import './Footer.css'; | ||||
|  | ||||
| export default function Footer() { | ||||
|     return ( | ||||
|         <footer className="footer"> | ||||
|             <p className="footer-text text-center"> | ||||
|                 <Link href={"/about"}> | ||||
|                     <a>FunctionProject</a> | ||||
|                 </Link> | ||||
|                  - Version 2.0 <br/> | ||||
|                 <a href="https://divlo.fr/" target="_blank" rel="noopener noreferrer">Divlo</a> | Tous droits réservés | ||||
|             </p> | ||||
|         </footer> | ||||
|     ); | ||||
| } | ||||
							
								
								
									
										19
									
								
								website/components/Footer/Footer.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								website/components/Footer/Footer.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| import Link from 'next/link' | ||||
| import './Footer.css' | ||||
|  | ||||
| export default function Footer () { | ||||
|   return ( | ||||
|     <footer className='footer'> | ||||
|       <p className='footer-text text-center'> | ||||
|         <Link href='/about'> | ||||
|           <a>FunctionProject</a> | ||||
|         </Link> | ||||
|          - Version 2.1 <br /> | ||||
|         <a href='https://divlo.fr/' target='_blank' rel='noopener noreferrer'> | ||||
|           Divlo | ||||
|         </a>{' '} | ||||
|         | Tous droits réservés | ||||
|       </p> | ||||
|     </footer> | ||||
|   ) | ||||
| } | ||||
| @@ -1,129 +0,0 @@ | ||||
| import { Fragment, useState, useEffect } from 'react'; | ||||
| import htmlParser from 'html-react-parser'; | ||||
| import Loader from '../../components/Loader'; | ||||
| import useAPI from '../../hooks/useAPI'; | ||||
| import api from '../../utils/api'; | ||||
| import '../../public/css/pages/admin.css'; | ||||
|  | ||||
| const AddEditFunction = (props) => { | ||||
|  | ||||
|     const [, categories]              = useAPI('/categories'); | ||||
|     const [inputState, setInputState] = useState(props.defaultInputState); | ||||
|     const [message, setMessage]       = useState(""); | ||||
|     const [isLoading, setIsLoading]   = useState(false); | ||||
|  | ||||
|     useEffect(() => { | ||||
|         if (categories.length > 0 && !props.isEditing) { | ||||
|             handleChange({ | ||||
|                 target: { | ||||
|                     name: "categorieId", | ||||
|                     value: categories[0].id | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     }, [categories]); | ||||
|  | ||||
|     const apiCallFunction = (formData) => { | ||||
|         if (props.isEditing) return api.put(`/admin/functions/${inputState.id}`, formData, { headers: { 'Authorization': props.user.token } }); | ||||
|         return api.post('/admin/functions', formData, { headers: { 'Authorization': props.user.token } }); | ||||
|     } | ||||
|  | ||||
|     const handleChange = (event, isTypeCheck = false) => { | ||||
|         const inputStateNew = { ...inputState }; | ||||
|         inputStateNew[event.target.name] = (event.target.files != undefined) ? event.target.files[0] : (isTypeCheck) ? event.target.checked : event.target.value; | ||||
|         setInputState(inputStateNew); | ||||
|     } | ||||
|  | ||||
|     const handleSubmit = (event) => { | ||||
|         event.preventDefault(); | ||||
|         setIsLoading(true); | ||||
|         const formData = new FormData(); | ||||
|         formData.append('type', inputState.type); | ||||
|         formData.append('categorieId', inputState.categorieId); | ||||
|         formData.append('title', inputState.title); | ||||
|         formData.append('slug', inputState.slug); | ||||
|         formData.append('description', inputState.description); | ||||
|         formData.append('image', inputState.image); | ||||
|  | ||||
|         if (props.isEditing) { | ||||
|             formData.append('isOnline', inputState.isOnline); | ||||
|         } | ||||
|  | ||||
|         apiCallFunction(formData) | ||||
|             .then(() => { | ||||
|                 setIsLoading(false); | ||||
|                 window.location.reload(true); | ||||
|             }) | ||||
|             .catch((error) => { | ||||
|                 setMessage(`<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>`); | ||||
|                 setIsLoading(false); | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
|         <Fragment> | ||||
|             <form onSubmit={handleSubmit}> | ||||
|                 <div className="form-group"> | ||||
|                     <label className="form-label" htmlFor="title">Titre :</label> | ||||
|                     <input value={inputState.title} onChange={handleChange} type="text" name="title" id="title" className="form-control" placeholder="(e.g : Nombre aléatoire)" /> | ||||
|                 </div> | ||||
|  | ||||
|                 <div className="form-group"> | ||||
|                     <label className="form-label" htmlFor="slug">Slug :</label> | ||||
|                     <input value={inputState.slug} onChange={handleChange} type="text" name="slug" id="slug" className="form-control" placeholder="(e.g : randomNumber)" /> | ||||
|                 </div> | ||||
|  | ||||
|                 <div className="form-group"> | ||||
|                     <label className="form-label" htmlFor="description">Description :</label> | ||||
|                     <textarea style={{ height: 'auto' }} value={inputState.description} onChange={handleChange} name="description" id="description" className="form-control" rows="5"></textarea> | ||||
|                 </div> | ||||
|  | ||||
|                 <div className="form-group"> | ||||
|                     <label className="form-label" htmlFor="type">Type :</label> | ||||
|                     <select onChange={handleChange} name="type" id="type" className="form-control" { ...(props.isEditing) && { value: inputState.type } }> | ||||
|                         <option value="form">Formulaire</option> | ||||
|                         <option value="article">Article</option> | ||||
|                         <option value="page">Page</option> | ||||
|                     </select> | ||||
|                 </div> | ||||
|  | ||||
|                 <div className="form-group"> | ||||
|                     <label className="form-label" htmlFor="categorieId">Catégorie :</label> | ||||
|                     <select onChange={handleChange} name="categorieId" id="categorieId" className="form-control" { ...(props.isEditing) && { value: inputState.categorieId } }> | ||||
|                         {categories.map((category) => ( | ||||
|                             <option key={category.id} value={category.id} className="Admin__Modal-select-option" style={{ backgroundColor: category.color }}>{category.name}</option> | ||||
|                         ))} | ||||
|                     </select> | ||||
|                 </div> | ||||
|  | ||||
|                 <div className="form-group"> | ||||
|                     <label className="form-label" htmlFor="image">Image <em>(150x150 recommandé)</em> :</label> | ||||
|                     <br/> | ||||
|                     <input onChange={handleChange} accept="image/jpeg,image/jpg,image/png" type="file" name="image" id="image" /> | ||||
|                 </div> | ||||
|  | ||||
|                 {(props.isEditing) &&                  | ||||
|                     <div className="form-group custom-control custom-switch"> | ||||
|                         <input onChange={(event) => handleChange(event, true)} type="checkbox" name="isOnline" checked={inputState.isOnline} className="custom-control-input" id="isOnline" /> | ||||
|                         <label className="custom-control-label" htmlFor="isOnline">isOnline</label> | ||||
|                     </div> | ||||
|                 } | ||||
|  | ||||
|                 <div className="form-group text-center"> | ||||
|                     <button type="submit" className="btn btn-dark">Envoyer</button> | ||||
|                 </div> | ||||
|             </form> | ||||
|  | ||||
|             <div className="form-result text-center"> | ||||
|                 { | ||||
|                     (isLoading) ?  | ||||
|                         <Loader /> | ||||
|                     : | ||||
|                         htmlParser(message) | ||||
|                 } | ||||
|             </div> | ||||
|         </Fragment> | ||||
|     ); | ||||
| } | ||||
|  | ||||
| export default AddEditFunction; | ||||
							
								
								
									
										208
									
								
								website/components/FunctionAdmin/AddEditFunction.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								website/components/FunctionAdmin/AddEditFunction.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,208 @@ | ||||
| import { useState, useEffect } from 'react' | ||||
| import htmlParser from 'html-react-parser' | ||||
| import Loader from '../Loader' | ||||
| import useAPI from '../../hooks/useAPI' | ||||
| import api from '../../utils/api' | ||||
| import '../../public/css/pages/admin.css' | ||||
|  | ||||
| const AddEditFunction = props => { | ||||
|   const [, categories] = useAPI('/categories') | ||||
|   const [inputState, setInputState] = useState(props.defaultInputState) | ||||
|   const [message, setMessage] = useState('') | ||||
|   const [isLoading, setIsLoading] = useState(false) | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (categories.length > 0 && !props.isEditing) { | ||||
|       handleChange({ | ||||
|         target: { | ||||
|           name: 'categorieId', | ||||
|           value: categories[0].id | ||||
|         } | ||||
|       }) | ||||
|     } | ||||
|   }, [categories]) | ||||
|  | ||||
|   const apiCallFunction = formData => { | ||||
|     if (props.isEditing) { | ||||
|       return api.put(`/admin/functions/${inputState.id}`, formData, { | ||||
|         headers: { Authorization: props.user.token } | ||||
|       }) | ||||
|     } | ||||
|     return api.post('/admin/functions', formData, { | ||||
|       headers: { Authorization: props.user.token } | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   const handleChange = (event, isTypeCheck = false) => { | ||||
|     const inputStateNew = { ...inputState } | ||||
|     inputStateNew[event.target.name] = | ||||
|       event.target.files != null | ||||
|         ? event.target.files[0] | ||||
|         : isTypeCheck | ||||
|           ? event.target.checked | ||||
|           : event.target.value | ||||
|     setInputState(inputStateNew) | ||||
|   } | ||||
|  | ||||
|   const handleSubmit = event => { | ||||
|     event.preventDefault() | ||||
|     setIsLoading(true) | ||||
|     const formData = new window.FormData() | ||||
|     formData.append('type', inputState.type) | ||||
|     formData.append('categorieId', inputState.categorieId) | ||||
|     formData.append('title', inputState.title) | ||||
|     formData.append('slug', inputState.slug) | ||||
|     formData.append('description', inputState.description) | ||||
|     formData.append('image', inputState.image) | ||||
|  | ||||
|     if (props.isEditing) { | ||||
|       formData.append('isOnline', inputState.isOnline) | ||||
|     } | ||||
|  | ||||
|     apiCallFunction(formData) | ||||
|       .then(() => { | ||||
|         setIsLoading(false) | ||||
|         window.location.reload(true) | ||||
|       }) | ||||
|       .catch(error => { | ||||
|         setMessage( | ||||
|           `<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>` | ||||
|         ) | ||||
|         setIsLoading(false) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <form onSubmit={handleSubmit}> | ||||
|         <div className='form-group'> | ||||
|           <label className='form-label' htmlFor='title'> | ||||
|             Titre : | ||||
|           </label> | ||||
|           <input | ||||
|             value={inputState.title} | ||||
|             onChange={handleChange} | ||||
|             type='text' | ||||
|             name='title' | ||||
|             id='title' | ||||
|             className='form-control' | ||||
|             placeholder='(e.g : Nombre aléatoire)' | ||||
|           /> | ||||
|         </div> | ||||
|  | ||||
|         <div className='form-group'> | ||||
|           <label className='form-label' htmlFor='slug'> | ||||
|             Slug : | ||||
|           </label> | ||||
|           <input | ||||
|             value={inputState.slug} | ||||
|             onChange={handleChange} | ||||
|             type='text' | ||||
|             name='slug' | ||||
|             id='slug' | ||||
|             className='form-control' | ||||
|             placeholder='(e.g : randomNumber)' | ||||
|           /> | ||||
|         </div> | ||||
|  | ||||
|         <div className='form-group'> | ||||
|           <label className='form-label' htmlFor='description'> | ||||
|             Description : | ||||
|           </label> | ||||
|           <textarea | ||||
|             style={{ height: 'auto' }} | ||||
|             value={inputState.description} | ||||
|             onChange={handleChange} | ||||
|             name='description' | ||||
|             id='description' | ||||
|             className='form-control' | ||||
|             rows='5' | ||||
|           /> | ||||
|         </div> | ||||
|  | ||||
|         <div className='form-group'> | ||||
|           <label className='form-label' htmlFor='type'> | ||||
|             Type : | ||||
|           </label> | ||||
|           <select | ||||
|             onChange={handleChange} | ||||
|             name='type' | ||||
|             id='type' | ||||
|             className='form-control' | ||||
|             {...(props.isEditing && { value: inputState.type })} | ||||
|           > | ||||
|             <option value='form'>Formulaire</option> | ||||
|             <option value='article'>Article</option> | ||||
|             <option value='page'>Page</option> | ||||
|           </select> | ||||
|         </div> | ||||
|  | ||||
|         <div className='form-group'> | ||||
|           <label className='form-label' htmlFor='categorieId'> | ||||
|             Catégorie : | ||||
|           </label> | ||||
|           <select | ||||
|             onChange={handleChange} | ||||
|             name='categorieId' | ||||
|             id='categorieId' | ||||
|             className='form-control' | ||||
|             {...(props.isEditing && { value: inputState.categorieId })} | ||||
|           > | ||||
|             {categories.map(category => ( | ||||
|               <option | ||||
|                 key={category.id} | ||||
|                 value={category.id} | ||||
|                 className='Admin__Modal-select-option' | ||||
|                 style={{ backgroundColor: category.color }} | ||||
|               > | ||||
|                 {category.name} | ||||
|               </option> | ||||
|             ))} | ||||
|           </select> | ||||
|         </div> | ||||
|  | ||||
|         <div className='form-group'> | ||||
|           <label className='form-label' htmlFor='image'> | ||||
|             Image <em>(150x150 recommandé)</em> : | ||||
|           </label> | ||||
|           <br /> | ||||
|           <input | ||||
|             onChange={handleChange} | ||||
|             accept='image/jpeg,image/jpg,image/png' | ||||
|             type='file' | ||||
|             name='image' | ||||
|             id='image' | ||||
|           /> | ||||
|         </div> | ||||
|  | ||||
|         {props.isEditing && ( | ||||
|           <div className='form-group custom-control custom-switch'> | ||||
|             <input | ||||
|               onChange={event => handleChange(event, true)} | ||||
|               type='checkbox' | ||||
|               name='isOnline' | ||||
|               checked={inputState.isOnline} | ||||
|               className='custom-control-input' | ||||
|               id='isOnline' | ||||
|             /> | ||||
|             <label className='custom-control-label' htmlFor='isOnline'> | ||||
|               isOnline | ||||
|             </label> | ||||
|           </div> | ||||
|         )} | ||||
|  | ||||
|         <div className='form-group text-center'> | ||||
|           <button type='submit' className='btn btn-dark'> | ||||
|             Envoyer | ||||
|           </button> | ||||
|         </div> | ||||
|       </form> | ||||
|  | ||||
|       <div className='form-result text-center'> | ||||
|         {isLoading ? <Loader /> : htmlParser(message)} | ||||
|       </div> | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default AddEditFunction | ||||
| @@ -1,46 +0,0 @@ | ||||
| import { useState } from 'react'; | ||||
| import dynamic from 'next/dynamic'; | ||||
| import { complex } from '../../utils/sunEditorConfig'; | ||||
| import api from '../../utils/api'; | ||||
| import FunctionArticle from '../FunctionPage/FunctionArticle'; | ||||
| import 'notyf/notyf.min.css'; | ||||
| import '../../public/css/suneditor.min.css'; | ||||
|  | ||||
| const SunEditor = dynamic( | ||||
|     () => import('suneditor-react'), | ||||
|     { ssr: false } | ||||
| ); | ||||
|  | ||||
| const EditArticleFunction = (props) => { | ||||
|  | ||||
|     const [htmlContent, setHtmlContent] = useState(""); | ||||
|  | ||||
|     const handleEditorChange = (content) => { | ||||
|         setHtmlContent(content); | ||||
|     } | ||||
|  | ||||
|     const handleSave = async (content) => { | ||||
|         let Notyf; | ||||
|         if (typeof window != 'undefined') { | ||||
|             Notyf = require('notyf'); | ||||
|         } | ||||
|         const notyf = new Notyf.Notyf({ | ||||
|             duration: 5000 | ||||
|         }); | ||||
|         try { | ||||
|             await api.put(`/admin/functions/article/${props.functionInfo.id}`, { article: content }, { headers: { 'Authorization': props.user.token } }); | ||||
|             notyf.success('Sauvegardé!'); | ||||
|         } catch { | ||||
|             notyf.error('Erreur!'); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
|         <div className="container-fluid"> | ||||
|             <SunEditor setContents={props.functionInfo.article} lang="fr" onChange={handleEditorChange} setOptions={{ buttonList: complex, callBackSave: handleSave }} /> | ||||
|             <FunctionArticle article={htmlContent} /> | ||||
|         </div> | ||||
|     ); | ||||
| } | ||||
|  | ||||
| export default EditArticleFunction; | ||||
							
								
								
									
										51
									
								
								website/components/FunctionAdmin/EditArticleFunction.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								website/components/FunctionAdmin/EditArticleFunction.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| import { useState } from 'react' | ||||
| import dynamic from 'next/dynamic' | ||||
| import { complex } from '../../utils/sunEditorConfig' | ||||
| import api from '../../utils/api' | ||||
| import FunctionArticle from '../FunctionPage/FunctionArticle' | ||||
| import 'notyf/notyf.min.css' | ||||
| import '../../public/css/suneditor.min.css' | ||||
|  | ||||
| const SunEditor = dynamic(() => import('suneditor-react'), { ssr: false }) | ||||
|  | ||||
| const EditArticleFunction = props => { | ||||
|   const [htmlContent, setHtmlContent] = useState('') | ||||
|  | ||||
|   const handleEditorChange = content => { | ||||
|     setHtmlContent(content) | ||||
|   } | ||||
|  | ||||
|   const handleSave = async content => { | ||||
|     let Notyf | ||||
|     if (typeof window !== 'undefined') { | ||||
|       Notyf = require('notyf') | ||||
|     } | ||||
|     const notyf = new Notyf.Notyf({ | ||||
|       duration: 5000 | ||||
|     }) | ||||
|     try { | ||||
|       await api.put( | ||||
|         `/admin/functions/article/${props.functionInfo.id}`, | ||||
|         { article: content }, | ||||
|         { headers: { Authorization: props.user.token } } | ||||
|       ) | ||||
|       notyf.success('Sauvegardé!') | ||||
|     } catch { | ||||
|       notyf.error('Erreur!') | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <div className='container-fluid'> | ||||
|       <SunEditor | ||||
|         setContents={props.functionInfo.article} | ||||
|         lang='fr' | ||||
|         onChange={handleEditorChange} | ||||
|         setOptions={{ buttonList: complex, callBackSave: handleSave }} | ||||
|       /> | ||||
|       <FunctionArticle article={htmlContent} /> | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default EditArticleFunction | ||||
| @@ -1,158 +0,0 @@ | ||||
| import { useState, Fragment } from 'react'; | ||||
| import api from '../../utils/api'; | ||||
| import 'notyf/notyf.min.css'; | ||||
|  | ||||
| const EditFormFunction = (props) => { | ||||
|  | ||||
|     const [inputsArray, setInputsArray] = useState(props.functionInfo.utilizationForm || []); | ||||
|  | ||||
|     const addInput = () => { | ||||
|         const newInputsArray = [...inputsArray]; | ||||
|         newInputsArray.push({ name: "", label: "", placeholder: "", type: "text" }); | ||||
|         setInputsArray(newInputsArray); | ||||
|     } | ||||
|  | ||||
|     const addOption = (event) => { | ||||
|         const newInputsArray = [...inputsArray]; | ||||
|         const index          = event.target.id.split('-')[1]; | ||||
|         const inputObject    = newInputsArray[index]; | ||||
|         inputObject.options.push({ name: "", value: "" }); | ||||
|         setInputsArray(newInputsArray); | ||||
|     } | ||||
|  | ||||
|     const handleChangeOption = (inputIndex, optionIndex, event) => { | ||||
|         const newInputsArray = [...inputsArray]; | ||||
|         const inputObject    = newInputsArray[inputIndex]; | ||||
|         const optionObject   = inputObject.options[optionIndex]; | ||||
|         optionObject[event.target.name] = event.target.value; | ||||
|         setInputsArray(newInputsArray); | ||||
|     } | ||||
|  | ||||
|     const handleChangeInput = (event) => { | ||||
|         const       newInputsArray     = [...inputsArray]; | ||||
|         const       index              = event.target.id.split('-')[1]; | ||||
|         const       inputObject        = newInputsArray[index]; | ||||
|         inputObject[event.target.name] = event.target.value; | ||||
|         if (event.target.value === "select") { | ||||
|             inputObject.options = []; | ||||
|         } | ||||
|         setInputsArray(newInputsArray); | ||||
|     } | ||||
|  | ||||
|     const handleSubmit = async (event) => { | ||||
|         event.preventDefault(); | ||||
|         let Notyf; | ||||
|         if (typeof window != 'undefined') { | ||||
|             Notyf = require('notyf'); | ||||
|         } | ||||
|         const notyf = new Notyf.Notyf({ | ||||
|             duration: 5000 | ||||
|         }); | ||||
|         try { | ||||
|             await api.put(`/admin/functions/form/${props.functionInfo.id}`, { form: inputsArray }, { headers: { 'Authorization': props.user.token } }); | ||||
|             notyf.success('Sauvegardé!'); | ||||
|         } catch (error) { | ||||
|             notyf.error('Erreur!'); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     const handleRemoveInput = (event) => { | ||||
|         const newInputsArray = [...inputsArray]; | ||||
|         const index          = event.target.id.split('-')[1]; | ||||
|         newInputsArray.splice(index, 1); | ||||
|         setInputsArray(newInputsArray); | ||||
|     } | ||||
|  | ||||
|     const handleRemoveOption = (inputIndex, optionIndex) => { | ||||
|         const newInputsArray = [...inputsArray]; | ||||
|         const inputObject    = newInputsArray[inputIndex]; | ||||
|         const optionsArray   = inputObject.options; | ||||
|         optionsArray.splice(optionIndex, 1); | ||||
|         setInputsArray(newInputsArray); | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
|         <div className="container-fluid"> | ||||
|  | ||||
|             <form onSubmit={handleSubmit}> | ||||
|  | ||||
|                 {(inputsArray.length > 0) &&                 | ||||
|                     <div className="form-group text-center"> | ||||
|                         <button type="submit" className="btn btn-dark">Sauvegarder</button> | ||||
|                     </div> | ||||
|                 } | ||||
|  | ||||
|                 {inputsArray.map((input, index) => { | ||||
|                     return ( | ||||
|                         <div key={index} className="form-group Admin__Input-group"> | ||||
|  | ||||
|                             <div className="text-center"> | ||||
|                                 <button type="button" onClick={handleRemoveInput} id={`remove-${index}`} className="btn btn-dark">Supprimer l'input</button> | ||||
|                             </div> | ||||
|  | ||||
|                             <label className="form-label" htmlFor={`name-${index}`}>Nom de l'input :</label> | ||||
|                             <input value={input.name} onChange={handleChangeInput} type="text" name="name" id={`name-${index}`} className="form-control" placeholder="(e.g : cityName)" /> | ||||
|                             <br/> | ||||
|  | ||||
|                             <label className="form-label" htmlFor={`label-${index}`}>Label :</label> | ||||
|                             <input value={input.label} onChange={handleChangeInput} type="text" name="label" id={`label-${index}`} className="form-control" placeholder="(e.g : Entrez le nom d'une ville :)" /> | ||||
|                             <br/> | ||||
|                              | ||||
|                             {(input.type !== "select") &&  | ||||
|                                 <Fragment> | ||||
|                                     <label className="form-label" htmlFor={`placeholder-${index}`}>Placeholder :</label> | ||||
|                                     <input value={input.placeholder} onChange={handleChangeInput} type="text" name="placeholder" id={`placeholder-${index}`} className="form-control" placeholder="(e.g : Paris, FR)" /> | ||||
|                                     <br/> | ||||
|                                 </Fragment> | ||||
|                             } | ||||
|  | ||||
|                             <label className="form-label" htmlFor={`type-${index}`}>Type :</label> | ||||
|                             <select value={input.type} onChange={handleChangeInput} name="type" id={`type-${index}`} className="form-control"> | ||||
|                                 <option value="text">text</option> | ||||
|                                 <option value="integer">Number integer</option> | ||||
|                                 <option value="float">Number float</option> | ||||
|                                 <option value="calendar">calendar</option> | ||||
|                                 <option value="select">select</option> | ||||
|                             </select> | ||||
|  | ||||
|                             {(input.type === "select") && | ||||
|                                 <div style={{ marginTop: '50px' }}> | ||||
|  | ||||
|                                     <label className="form-label">Options :</label> | ||||
|  | ||||
|                                     {input.options.map((option, optionIndex) => { | ||||
|                                         return ( | ||||
|                                             <div key={optionIndex} style={{ margin: "0 0 30px 0" }} className="form-group Admin__Input-group"> | ||||
|                                                 <div className="text-center"> | ||||
|                                                     <button type="button" onClick={() => handleRemoveOption(index, optionIndex)} className="btn btn-dark">Supprimer l'option</button> | ||||
|                                                 </div> | ||||
|  | ||||
|                                                 <label className="form-label" htmlFor={`optionName-${optionIndex}-${index}`}>Nom de l'option</label> | ||||
|                                                 <input onChange={(event) => handleChangeOption(index, optionIndex, event)} value={option.name} id={`optionName-${optionIndex}-${index}`} name="name" type="text"className="form-control" placeholder="Nom de l'option" /> | ||||
|  | ||||
|                                                 <br /> | ||||
|                                                 <label className="form-label" htmlFor={`optionValue-${optionIndex}-${index}`}>Valeur de l'option</label> | ||||
|                                                 <input onChange={(event) => handleChangeOption(index, optionIndex, event)} value={option.value} id={`optionValue-${optionIndex}-${index}`} name="value" type="text"className="form-control" placeholder="Valeur de l'option" /> | ||||
|                                             </div> | ||||
|                                         ); | ||||
|                                     })} | ||||
|  | ||||
|                                     <div className="form-group text-center"> | ||||
|                                         <button id={`optionAdd-${index}`} onClick={addOption} type="button" className="btn btn-dark">Ajouter une option</button> | ||||
|                                     </div> | ||||
|                                 </div>  | ||||
|                             } | ||||
|                         </div> | ||||
|                     ); | ||||
|                 })} | ||||
|  | ||||
|             </form> | ||||
|  | ||||
|             <div style={{ marginBottom: '30px' }} className="form-group text-center"> | ||||
|                 <button type="button" onClick={addInput} className="btn btn-dark">Ajouter un input</button> | ||||
|             </div> | ||||
|         </div> | ||||
|     ); | ||||
| } | ||||
|  | ||||
| export default EditFormFunction; | ||||
							
								
								
									
										256
									
								
								website/components/FunctionAdmin/EditFormFunction.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								website/components/FunctionAdmin/EditFormFunction.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,256 @@ | ||||
| import { useState } from 'react' | ||||
| import api from '../../utils/api' | ||||
| import 'notyf/notyf.min.css' | ||||
|  | ||||
| const EditFormFunction = props => { | ||||
|   const [inputsArray, setInputsArray] = useState( | ||||
|     props.functionInfo.utilizationForm || [] | ||||
|   ) | ||||
|  | ||||
|   const addInput = () => { | ||||
|     const newInputsArray = [...inputsArray] | ||||
|     newInputsArray.push({ name: '', label: '', placeholder: '', type: 'text' }) | ||||
|     setInputsArray(newInputsArray) | ||||
|   } | ||||
|  | ||||
|   const addOption = event => { | ||||
|     const newInputsArray = [...inputsArray] | ||||
|     const index = event.target.id.split('-')[1] | ||||
|     const inputObject = newInputsArray[index] | ||||
|     inputObject.options.push({ name: '', value: '' }) | ||||
|     setInputsArray(newInputsArray) | ||||
|   } | ||||
|  | ||||
|   const handleChangeOption = (inputIndex, optionIndex, event) => { | ||||
|     const newInputsArray = [...inputsArray] | ||||
|     const inputObject = newInputsArray[inputIndex] | ||||
|     const optionObject = inputObject.options[optionIndex] | ||||
|     optionObject[event.target.name] = event.target.value | ||||
|     setInputsArray(newInputsArray) | ||||
|   } | ||||
|  | ||||
|   const handleChangeInput = event => { | ||||
|     const newInputsArray = [...inputsArray] | ||||
|     const index = event.target.id.split('-')[1] | ||||
|     const inputObject = newInputsArray[index] | ||||
|     inputObject[event.target.name] = event.target.value | ||||
|     if (event.target.value === 'select') { | ||||
|       inputObject.options = [] | ||||
|     } | ||||
|     setInputsArray(newInputsArray) | ||||
|   } | ||||
|  | ||||
|   const handleSubmit = async event => { | ||||
|     event.preventDefault() | ||||
|     let Notyf | ||||
|     if (typeof window !== 'undefined') { | ||||
|       Notyf = require('notyf') | ||||
|     } | ||||
|     const notyf = new Notyf.Notyf({ | ||||
|       duration: 5000 | ||||
|     }) | ||||
|     try { | ||||
|       await api.put( | ||||
|         `/admin/functions/form/${props.functionInfo.id}`, | ||||
|         { form: inputsArray }, | ||||
|         { headers: { Authorization: props.user.token } } | ||||
|       ) | ||||
|       notyf.success('Sauvegardé!') | ||||
|     } catch (error) { | ||||
|       notyf.error('Erreur!') | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   const handleRemoveInput = event => { | ||||
|     const newInputsArray = [...inputsArray] | ||||
|     const index = event.target.id.split('-')[1] | ||||
|     newInputsArray.splice(index, 1) | ||||
|     setInputsArray(newInputsArray) | ||||
|   } | ||||
|  | ||||
|   const handleRemoveOption = (inputIndex, optionIndex) => { | ||||
|     const newInputsArray = [...inputsArray] | ||||
|     const inputObject = newInputsArray[inputIndex] | ||||
|     const optionsArray = inputObject.options | ||||
|     optionsArray.splice(optionIndex, 1) | ||||
|     setInputsArray(newInputsArray) | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <div className='container-fluid'> | ||||
|       <form onSubmit={handleSubmit}> | ||||
|         {inputsArray.length > 0 && ( | ||||
|           <div className='form-group text-center'> | ||||
|             <button type='submit' className='btn btn-dark'> | ||||
|               Sauvegarder | ||||
|             </button> | ||||
|           </div> | ||||
|         )} | ||||
|  | ||||
|         {inputsArray.map((input, index) => { | ||||
|           return ( | ||||
|             <div key={index} className='form-group Admin__Input-group'> | ||||
|               <div className='text-center'> | ||||
|                 <button | ||||
|                   type='button' | ||||
|                   onClick={handleRemoveInput} | ||||
|                   id={`remove-${index}`} | ||||
|                   className='btn btn-dark' | ||||
|                 > | ||||
|                   Supprimer l'input | ||||
|                 </button> | ||||
|               </div> | ||||
|  | ||||
|               <label className='form-label' htmlFor={`name-${index}`}> | ||||
|                 Nom de l'input : | ||||
|               </label> | ||||
|               <input | ||||
|                 value={input.name} | ||||
|                 onChange={handleChangeInput} | ||||
|                 type='text' | ||||
|                 name='name' | ||||
|                 id={`name-${index}`} | ||||
|                 className='form-control' | ||||
|                 placeholder='(e.g : cityName)' | ||||
|               /> | ||||
|               <br /> | ||||
|  | ||||
|               <label className='form-label' htmlFor={`label-${index}`}> | ||||
|                 Label : | ||||
|               </label> | ||||
|               <input | ||||
|                 value={input.label} | ||||
|                 onChange={handleChangeInput} | ||||
|                 type='text' | ||||
|                 name='label' | ||||
|                 id={`label-${index}`} | ||||
|                 className='form-control' | ||||
|                 placeholder="(e.g : Entrez le nom d'une ville :)" | ||||
|               /> | ||||
|               <br /> | ||||
|  | ||||
|               {input.type !== 'select' && ( | ||||
|                 <> | ||||
|                   <label | ||||
|                     className='form-label' | ||||
|                     htmlFor={`placeholder-${index}`} | ||||
|                   > | ||||
|                     Placeholder : | ||||
|                   </label> | ||||
|                   <input | ||||
|                     value={input.placeholder} | ||||
|                     onChange={handleChangeInput} | ||||
|                     type='text' | ||||
|                     name='placeholder' | ||||
|                     id={`placeholder-${index}`} | ||||
|                     className='form-control' | ||||
|                     placeholder='(e.g : Paris, FR)' | ||||
|                   /> | ||||
|                   <br /> | ||||
|                 </> | ||||
|               )} | ||||
|  | ||||
|               <label className='form-label' htmlFor={`type-${index}`}> | ||||
|                 Type : | ||||
|               </label> | ||||
|               <select | ||||
|                 value={input.type} | ||||
|                 onChange={handleChangeInput} | ||||
|                 name='type' | ||||
|                 id={`type-${index}`} | ||||
|                 className='form-control' | ||||
|               > | ||||
|                 <option value='text'>text</option> | ||||
|                 <option value='integer'>Number integer</option> | ||||
|                 <option value='float'>Number float</option> | ||||
|                 <option value='calendar'>calendar</option> | ||||
|                 <option value='select'>select</option> | ||||
|               </select> | ||||
|  | ||||
|               {input.type === 'select' && ( | ||||
|                 <div style={{ marginTop: '50px' }}> | ||||
|                   <label className='form-label'>Options :</label> | ||||
|  | ||||
|                   {input.options.map((option, optionIndex) => { | ||||
|                     return ( | ||||
|                       <div | ||||
|                         key={optionIndex} | ||||
|                         style={{ margin: '0 0 30px 0' }} | ||||
|                         className='form-group Admin__Input-group' | ||||
|                       > | ||||
|                         <div className='text-center'> | ||||
|                           <button | ||||
|                             type='button' | ||||
|                             onClick={() => | ||||
|                               handleRemoveOption(index, optionIndex)} | ||||
|                             className='btn btn-dark' | ||||
|                           > | ||||
|                             Supprimer l'option | ||||
|                           </button> | ||||
|                         </div> | ||||
|  | ||||
|                         <label | ||||
|                           className='form-label' | ||||
|                           htmlFor={`optionName-${optionIndex}-${index}`} | ||||
|                         > | ||||
|                           Nom de l'option | ||||
|                         </label> | ||||
|                         <input | ||||
|                           onChange={event => | ||||
|                             handleChangeOption(index, optionIndex, event)} | ||||
|                           value={option.name} | ||||
|                           id={`optionName-${optionIndex}-${index}`} | ||||
|                           name='name' | ||||
|                           type='text' | ||||
|                           className='form-control' | ||||
|                           placeholder="Nom de l'option" | ||||
|                         /> | ||||
|  | ||||
|                         <br /> | ||||
|                         <label | ||||
|                           className='form-label' | ||||
|                           htmlFor={`optionValue-${optionIndex}-${index}`} | ||||
|                         > | ||||
|                           Valeur de l'option | ||||
|                         </label> | ||||
|                         <input | ||||
|                           onChange={event => | ||||
|                             handleChangeOption(index, optionIndex, event)} | ||||
|                           value={option.value} | ||||
|                           id={`optionValue-${optionIndex}-${index}`} | ||||
|                           name='value' | ||||
|                           type='text' | ||||
|                           className='form-control' | ||||
|                           placeholder="Valeur de l'option" | ||||
|                         /> | ||||
|                       </div> | ||||
|                     ) | ||||
|                   })} | ||||
|  | ||||
|                   <div className='form-group text-center'> | ||||
|                     <button | ||||
|                       id={`optionAdd-${index}`} | ||||
|                       onClick={addOption} | ||||
|                       type='button' | ||||
|                       className='btn btn-dark' | ||||
|                     > | ||||
|                       Ajouter une option | ||||
|                     </button> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               )} | ||||
|             </div> | ||||
|           ) | ||||
|         })} | ||||
|       </form> | ||||
|  | ||||
|       <div style={{ marginBottom: '30px' }} className='form-group text-center'> | ||||
|         <button type='button' onClick={addInput} className='btn btn-dark'> | ||||
|           Ajouter un input | ||||
|         </button> | ||||
|       </div> | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default EditFormFunction | ||||
| @@ -1,65 +1,67 @@ | ||||
| .FunctionCard { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     position: relative; | ||||
|     flex-direction: column; | ||||
|     word-wrap: break-word; | ||||
|     box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); | ||||
|     border: 1px solid black; | ||||
|     border-radius: 1rem; | ||||
|     margin: 0 0 50px 0; | ||||
|     cursor: pointer; | ||||
|     transition: all .3s; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   position: relative; | ||||
|   flex-direction: column; | ||||
|   word-wrap: break-word; | ||||
|   box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25); | ||||
|   border: 1px solid black; | ||||
|   border-radius: 1rem; | ||||
|   margin: 0 0 50px 0; | ||||
|   cursor: pointer; | ||||
|   transition: all 0.3s; | ||||
|   color: var(--text-color); | ||||
|   text-decoration: none !important; | ||||
| } | ||||
| .FunctionCard__container { | ||||
|     height: 100%; | ||||
|     width: 100%; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|   height: 100%; | ||||
|   width: 100%; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
| } | ||||
| .FunctionCard:hover { | ||||
|     transform: translateY(-7px); | ||||
|   transform: translateY(-7px); | ||||
| } | ||||
| /* col-md */ | ||||
| @media (min-width: 768px) { | ||||
|     .FunctionCard { | ||||
|         margin: 0 30px 50px 30px; | ||||
|     }  | ||||
|   .FunctionCard { | ||||
|     margin: 0 30px 50px 30px; | ||||
|   } | ||||
| } | ||||
| /* col-xl */ | ||||
| @media (min-width: 1200px) { | ||||
|     .FunctionCard { | ||||
|         margin: 0 20px 50px 20px; | ||||
|     }    | ||||
|   .FunctionCard { | ||||
|     margin: 0 20px 50px 20px; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .FunctionCard__top { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
|     flex-grow: 1; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   align-items: center; | ||||
|   flex-grow: 1; | ||||
| } | ||||
| .FunctionCard__image { | ||||
|     width: 150px; | ||||
|   width: 150px; | ||||
| } | ||||
| .FunctionCard__title { | ||||
|     font-size: 1.4em; | ||||
|     margin: 0; | ||||
|     color: var(--important); | ||||
|     font-weight: 300; | ||||
|   font-size: 1.4em; | ||||
|   margin: 0; | ||||
|   color: var(--important); | ||||
|   font-weight: 300; | ||||
| } | ||||
| .FunctionCard__description { | ||||
|     margin: 20px 0 10px 0; | ||||
|   margin: 20px 0 10px 0; | ||||
| } | ||||
| .FunctionCard__info { | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
|     width: 100%; | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
|   width: 100%; | ||||
| } | ||||
| .FunctionCard__category { | ||||
|     border-radius: 0.5em; | ||||
|     padding: 0.5em; | ||||
|     margin-right: 20px; | ||||
|     font-size: 16.4px; | ||||
| } | ||||
|   border-radius: 0.5em; | ||||
|   padding: 0.5em; | ||||
|   margin-right: 20px; | ||||
|   font-size: 16.4px; | ||||
| } | ||||
|   | ||||
| @@ -1,58 +0,0 @@ | ||||
| import Link from 'next/link'; | ||||
| import { useState, forwardRef } from 'react'; | ||||
| import date from 'date-and-time'; | ||||
| import Loader from '../Loader'; | ||||
| import { API_URL } from '../../utils/config/config'; | ||||
| import './FunctionCard.css'; | ||||
|  | ||||
| const FunctionCard = forwardRef((props, ref) => { | ||||
|  | ||||
|     const [isLoading, setIsLoading] = useState(true); | ||||
|  | ||||
|     const handleLoad = () => { | ||||
|         setIsLoading(false); | ||||
|     } | ||||
|  | ||||
|     const handleError = (event) => { | ||||
|         event.target.src = API_URL + "/images/functions/default.png"; | ||||
|     } | ||||
|  | ||||
|     const isFormOrArticle = (props.type === 'form' || props.type === 'article'); | ||||
|  | ||||
|     return ( | ||||
|         <Link  | ||||
|             {  | ||||
|                 ...(props.isAdmin) ?  | ||||
|                 { | ||||
|                     href: "/admin/[slug]", | ||||
|                     as: `/admin/${props.slug}` | ||||
|                 }  | ||||
|                     :  | ||||
|                 { | ||||
|                     href: (isFormOrArticle) ? "/functions/[slug]" : `/functions/${props.slug}`, | ||||
|                     as: `/functions/${props.slug}` | ||||
|                 } | ||||
|             } | ||||
|         > | ||||
|             {/* FunctionCard a une hauteur pendant chargement */} | ||||
|             <div ref={ref} style={isLoading ? { height: "360px", justifyContent: "center" } : null} className={"FunctionCard col-sm-24 col-md-10 col-xl-7"}> | ||||
|  | ||||
|                 {isLoading && <Loader width="125px" height="125px" />}  | ||||
|  | ||||
|                 <div className={`FunctionCard__container ${isLoading ? "d-none" : ""}`}> | ||||
|                     <div className="FunctionCard__top"> | ||||
|                         <img onLoad={handleLoad} onError={handleError} className="FunctionCard__image" alt={props.title} src={API_URL + props.image} /> | ||||
|                         <h2 className="FunctionCard__title">{props.title}</h2> | ||||
|                         <p className="FunctionCard__description text-center">{props.description}</p> | ||||
|                     </div> | ||||
|                     <div className="FunctionCard__info"> | ||||
|                         <p className="FunctionCard__category" style={{ backgroundColor: props.categorie.color }}>{props.categorie.name}</p> | ||||
|                         <p className="FunctionCard__publication-date">{date.format(new Date(props.createdAt), 'DD/MM/YYYY', true)}</p> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </Link> | ||||
|     ); | ||||
| });  | ||||
|  | ||||
| export default FunctionCard; | ||||
							
								
								
									
										80
									
								
								website/components/FunctionCard/FunctionCard.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								website/components/FunctionCard/FunctionCard.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| import Link from 'next/link' | ||||
| import { useState, forwardRef, memo } from 'react' | ||||
| import date from 'date-and-time' | ||||
| import Loader from '../Loader' | ||||
| import { API_URL } from '../../utils/api' | ||||
| import './FunctionCard.css' | ||||
|  | ||||
| const FunctionCard = memo( | ||||
|   forwardRef((props, ref) => { | ||||
|     const [isLoading, setIsLoading] = useState(true) | ||||
|  | ||||
|     const handleLoad = () => { | ||||
|       setIsLoading(false) | ||||
|     } | ||||
|  | ||||
|     const handleError = event => { | ||||
|       event.target.src = API_URL + '/images/functions/default.png' | ||||
|     } | ||||
|  | ||||
|     const isFormOrArticle = props.type === 'form' || props.type === 'article' | ||||
|  | ||||
|     return ( | ||||
|       <Link | ||||
|         {...(props.isAdmin | ||||
|           ? { | ||||
|             href: '/admin/[slug]', | ||||
|             as: `/admin/${props.slug}` | ||||
|           } | ||||
|           : { | ||||
|             href: isFormOrArticle | ||||
|               ? '/functions/[slug]' | ||||
|               : `/functions/${props.slug}`, | ||||
|             as: `/functions/${props.slug}` | ||||
|           })} | ||||
|       > | ||||
|         {/* FunctionCard a une hauteur pendant chargement */} | ||||
|         <a | ||||
|           ref={ref} | ||||
|           style={ | ||||
|             isLoading ? { height: '360px', justifyContent: 'center' } : null | ||||
|           } | ||||
|           className='FunctionCard col-sm-24 col-md-10 col-xl-7' | ||||
|         > | ||||
|           {isLoading && <Loader width='125px' height='125px' />} | ||||
|  | ||||
|           <div | ||||
|             className={`FunctionCard__container ${isLoading ? 'd-none' : ''}`} | ||||
|           > | ||||
|             <div className='FunctionCard__top'> | ||||
|               <img | ||||
|                 onLoad={handleLoad} | ||||
|                 onError={handleError} | ||||
|                 className='FunctionCard__image' | ||||
|                 alt={props.title} | ||||
|                 src={API_URL + props.image} | ||||
|               /> | ||||
|               <h2 className='FunctionCard__title'>{props.title}</h2> | ||||
|               <p className='FunctionCard__description text-center'> | ||||
|                 {props.description} | ||||
|               </p> | ||||
|             </div> | ||||
|             <div className='FunctionCard__info'> | ||||
|               <p | ||||
|                 className='FunctionCard__category' | ||||
|                 style={{ backgroundColor: props.categorie.color }} | ||||
|               > | ||||
|                 {props.categorie.name} | ||||
|               </p> | ||||
|               <p className='FunctionCard__publication-date'> | ||||
|                 {date.format(new Date(props.createdAt), 'DD/MM/YYYY', true)} | ||||
|               </p> | ||||
|             </div> | ||||
|           </div> | ||||
|         </a> | ||||
|       </Link> | ||||
|     ) | ||||
|   }) | ||||
| ) | ||||
|  | ||||
| export default FunctionCard | ||||
| @@ -1,33 +1,33 @@ | ||||
| .CommentCard { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     word-wrap: break-word; | ||||
|     box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); | ||||
|     border: 1px solid black; | ||||
|     border-radius: .7em; | ||||
|     margin: 15px 0 15px 0; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   word-wrap: break-word; | ||||
|   box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25); | ||||
|   border: 1px solid black; | ||||
|   border-radius: 0.7em; | ||||
|   margin: 15px 0 15px 0; | ||||
| } | ||||
| .CommentCard__container { | ||||
|     height: 100%; | ||||
|     width: 100%; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     padding: 20px; | ||||
|   height: 100%; | ||||
|   width: 100%; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   padding: 20px; | ||||
| } | ||||
| .CommentCard__user-logo { | ||||
|     border-radius: 50%; | ||||
|     object-fit: cover; | ||||
|     width: 50px; | ||||
|     height: 50px; | ||||
|     cursor: pointer; | ||||
|   border-radius: 50%; | ||||
|   object-fit: cover; | ||||
|   width: 50px; | ||||
|   height: 50px; | ||||
|   cursor: pointer; | ||||
| } | ||||
| .CommentCard__message-info { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     margin-left: 10px; | ||||
|     font-size: 16px; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   margin-left: 10px; | ||||
|   font-size: 16px; | ||||
| } | ||||
| .CommentCard__message { | ||||
|     line-height: 1.8; | ||||
|     margin: 15px 0 0 0; | ||||
| } | ||||
|   line-height: 1.8; | ||||
|   margin: 15px 0 0 0; | ||||
| } | ||||
|   | ||||
| @@ -1,108 +0,0 @@ | ||||
| import Link from 'next/link'; | ||||
| import { useEffect, useState, forwardRef, useContext, Fragment } from 'react'; | ||||
| import date from 'date-and-time'; | ||||
| import htmlParser from 'html-react-parser'; | ||||
| import { UserContext } from '../../../contexts/UserContext'; | ||||
| import { API_URL } from '../../../utils/config/config'; | ||||
| import ReactMarkdown  from 'react-markdown'; | ||||
| import CodeBlock from "../../CodeBlock"; | ||||
| import api from '../../../utils/api'; | ||||
| import './CommentCard.css'; | ||||
|  | ||||
| const CommentCard = forwardRef((props, ref) => { | ||||
|  | ||||
|     const { isAuth, user }          = useContext(UserContext); | ||||
|     const [isEditing, setEditing]   = useState(false); | ||||
|     const [editInput, setEditInput] = useState(""); | ||||
|     const [message, setMessage]     = useState(""); | ||||
|  | ||||
|     useEffect(() => { | ||||
|         setEditInput(props.message); | ||||
|     }, []); | ||||
|  | ||||
|     const deleteCommentById = async () => { | ||||
|         props.manageComment.setLoadingComments(true); | ||||
|         if (isAuth && user.token != undefined) { | ||||
|             try { | ||||
|                 await api.delete(`/comments/${props.id}`, { headers: { 'Authorization': user.token } }); | ||||
|                 const newCommentsData = { ...props.manageComment.commentsData }; | ||||
|                 const commentIndex    = newCommentsData.rows.findIndex((value) => value.id === props.id); | ||||
|                 newCommentsData.rows.splice(commentIndex, 1); | ||||
|                 props.manageComment.setCommentsData({ hasMore: props.manageComment.commentsData.hasMore, rows: newCommentsData.rows }); | ||||
|             } catch {} | ||||
|         } | ||||
|         props.manageComment.setLoadingComments(false); | ||||
|     } | ||||
|  | ||||
|     const handleChange = (event) => { | ||||
|         setEditInput(event.target.value); | ||||
|     } | ||||
|  | ||||
|     const handleSubmit = (event) => { | ||||
|         event.preventDefault(); | ||||
|         api.put(`/comments/${props.id}`, { message: editInput }, { headers: { 'Authorization': user.token } }) | ||||
|             .then((_response) => { | ||||
|                 setEditing(false); | ||||
|             }) | ||||
|             .catch((error) => { | ||||
|                 setMessage(`<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>`); | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     const editComment = () => { | ||||
|         setEditing(true); | ||||
|         setMessage(""); | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
|         <div ref={ref} className="CommentCard col-24"> | ||||
|             <div className="CommentCard__container"> | ||||
|                 <div className="row"> | ||||
|                     <Link href={"/users/[name]"} as={`/users/${props.user.name}`}> | ||||
|                         <img className="CommentCard__user-logo" src={API_URL + props.user.logo} alt={props.user.name} />     | ||||
|                     </Link> | ||||
|                     <span className="CommentCard__message-info"> | ||||
|                         <Link href={"/users/[name]"} as={`/users/${props.user.name}`}> | ||||
|                             <a>{props.user.name}</a> | ||||
|                         </Link>  | ||||
|                          - {date.format(new Date(props.createdAt), 'DD/MM/YYYY à HH:mm', true)}  | ||||
|                     </span> | ||||
|                 </div> | ||||
|                 <div className="row"> | ||||
|                     <div className="col-24"> | ||||
|                         { | ||||
|                             (!isEditing) ? | ||||
|                                 <Fragment> | ||||
|                                     <div className="CommentCard__message"> | ||||
|                                         <ReactMarkdown source={editInput} renderers={{ code: CodeBlock }} /> | ||||
|                                     </div> | ||||
|                                     {(isAuth && user.name === props.user.name) &&  | ||||
|                                         <p style={{ fontSize: '15px', margin: '15px 0 0 0', fontStyle: 'italic' }}> | ||||
|                                             <a onClick={deleteCommentById} href="#">supprimer</a> | ||||
|                                              -  | ||||
|                                             <a style={{ cursor: 'pointer' }} onClick={editComment}>modifier</a> | ||||
|                                         </p> | ||||
|                                     } | ||||
|                                 </Fragment> | ||||
|                             :  | ||||
|                                 <form onSubmit={handleSubmit}> | ||||
|                                     <div className="form-group FunctionComments__post-group"> | ||||
|                                         <label className="form-label" htmlFor="commentEdit">Modifier le commentaire :</label>    | ||||
|                                         <textarea style={{ height: 'auto' }} value={editInput} onChange={handleChange} name="commentEdit" id="commentEdit" className="form-control" rows="5" placeholder="Idée d'amélioration, avis, remarque, partage d'expérience personnel, ... (Markdown autorisé)"></textarea> | ||||
|                                     </div> | ||||
|                                     <div className="form-group" style={{ marginTop: '0.7em' }}> | ||||
|                                         <button type="submit" className="btn btn-dark">Envoyer</button> | ||||
|                                     </div> | ||||
|                                     <div className="text-center"> | ||||
|                                         {htmlParser(message)} | ||||
|                                     </div> | ||||
|                                 </form> | ||||
|                         } | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     ); | ||||
| }); | ||||
|  | ||||
| export default CommentCard; | ||||
							
								
								
									
										148
									
								
								website/components/FunctionPage/CommentCard/CommentCard.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								website/components/FunctionPage/CommentCard/CommentCard.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,148 @@ | ||||
| import Link from 'next/link' | ||||
| import { useEffect, useState, forwardRef, useContext } from 'react' | ||||
| import date from 'date-and-time' | ||||
| import htmlParser from 'html-react-parser' | ||||
| import { UserContext } from '../../../contexts/UserContext' | ||||
| import ReactMarkdown from 'react-markdown' | ||||
| import CodeBlock from '../../CodeBlock' | ||||
| import api, { API_URL } from '../../../utils/api' | ||||
| import './CommentCard.css' | ||||
|  | ||||
| const CommentCard = forwardRef((props, ref) => { | ||||
|   const { isAuth, user } = useContext(UserContext) | ||||
|   const [isEditing, setEditing] = useState(false) | ||||
|   const [editInput, setEditInput] = useState('') | ||||
|   const [message, setMessage] = useState('') | ||||
|  | ||||
|   useEffect(() => { | ||||
|     setEditInput(props.message) | ||||
|   }, []) | ||||
|  | ||||
|   const deleteCommentById = async () => { | ||||
|     props.manageComment.setLoadingComments(true) | ||||
|     if (isAuth && user.token != null) { | ||||
|       try { | ||||
|         await api.delete(`/comments/${props.id}`, { | ||||
|           headers: { Authorization: user.token } | ||||
|         }) | ||||
|         const newCommentsData = { ...props.manageComment.commentsData } | ||||
|         const commentIndex = newCommentsData.rows.findIndex( | ||||
|           value => value.id === props.id | ||||
|         ) | ||||
|         newCommentsData.rows.splice(commentIndex, 1) | ||||
|         props.manageComment.setCommentsData({ | ||||
|           hasMore: props.manageComment.commentsData.hasMore, | ||||
|           rows: newCommentsData.rows | ||||
|         }) | ||||
|       } catch {} | ||||
|     } | ||||
|     props.manageComment.setLoadingComments(false) | ||||
|   } | ||||
|  | ||||
|   const handleChange = event => { | ||||
|     setEditInput(event.target.value) | ||||
|   } | ||||
|  | ||||
|   const handleSubmit = event => { | ||||
|     event.preventDefault() | ||||
|     api | ||||
|       .put( | ||||
|         `/comments/${props.id}`, | ||||
|         { message: editInput }, | ||||
|         { headers: { Authorization: user.token } } | ||||
|       ) | ||||
|       .then(_response => { | ||||
|         setEditing(false) | ||||
|       }) | ||||
|       .catch(error => { | ||||
|         setMessage( | ||||
|           `<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>` | ||||
|         ) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   const editComment = () => { | ||||
|     setEditing(true) | ||||
|     setMessage('') | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <div ref={ref} className='CommentCard col-24'> | ||||
|       <div className='CommentCard__container'> | ||||
|         <div className='row'> | ||||
|           <Link href='/users/[name]' as={`/users/${props.user.name}`}> | ||||
|             <img | ||||
|               className='CommentCard__user-logo' | ||||
|               src={API_URL + props.user.logo} | ||||
|               alt={props.user.name} | ||||
|             /> | ||||
|           </Link> | ||||
|           <span className='CommentCard__message-info'> | ||||
|             <Link href='/users/[name]' as={`/users/${props.user.name}`}> | ||||
|               <a>{props.user.name}</a> | ||||
|             </Link> | ||||
|              -{' '} | ||||
|             {date.format(new Date(props.createdAt), 'DD/MM/YYYY à HH:mm', true)} | ||||
|           </span> | ||||
|         </div> | ||||
|         <div className='row'> | ||||
|           <div className='col-24'> | ||||
|             {!isEditing ? ( | ||||
|               <> | ||||
|                 <div className='CommentCard__message'> | ||||
|                   <ReactMarkdown | ||||
|                     source={editInput} | ||||
|                     renderers={{ code: CodeBlock }} | ||||
|                   /> | ||||
|                 </div> | ||||
|                 {isAuth && user.name === props.user.name && ( | ||||
|                   <p | ||||
|                     style={{ | ||||
|                       fontSize: '15px', | ||||
|                       margin: '15px 0 0 0', | ||||
|                       fontStyle: 'italic' | ||||
|                     }} | ||||
|                   > | ||||
|                     <a onClick={deleteCommentById} href='#'> | ||||
|                       supprimer | ||||
|                     </a> | ||||
|                      -  | ||||
|                     <a style={{ cursor: 'pointer' }} onClick={editComment}> | ||||
|                       modifier | ||||
|                     </a> | ||||
|                   </p> | ||||
|                 )} | ||||
|               </> | ||||
|             ) : ( | ||||
|               <form onSubmit={handleSubmit}> | ||||
|                 <div className='form-group FunctionComments__post-group'> | ||||
|                   <label className='form-label' htmlFor='commentEdit'> | ||||
|                     Modifier le commentaire : | ||||
|                   </label> | ||||
|                   <textarea | ||||
|                     style={{ height: 'auto' }} | ||||
|                     value={editInput} | ||||
|                     onChange={handleChange} | ||||
|                     name='commentEdit' | ||||
|                     id='commentEdit' | ||||
|                     className='form-control' | ||||
|                     rows='5' | ||||
|                     placeholder="Idée d'amélioration, avis, remarque, partage d'expérience personnel, ... (Markdown autorisé)" | ||||
|                   /> | ||||
|                 </div> | ||||
|                 <div className='form-group' style={{ marginTop: '0.7em' }}> | ||||
|                   <button type='submit' className='btn btn-dark'> | ||||
|                     Envoyer | ||||
|                   </button> | ||||
|                 </div> | ||||
|                 <div className='text-center'>{htmlParser(message)}</div> | ||||
|               </form> | ||||
|             )} | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   ) | ||||
| }) | ||||
|  | ||||
| export default CommentCard | ||||
| @@ -1,11 +0,0 @@ | ||||
| import htmlParser from 'html-react-parser'; | ||||
|  | ||||
| const FunctionArticle = ({ article }) => { | ||||
|     return ( | ||||
|         <div style={{ marginBottom: '50px' }} className="container-fluid"> | ||||
|             {(article != undefined) ? htmlParser(article) : <p className="text-center">L'article n'est pas encore disponible.</p>} | ||||
|         </div> | ||||
|     ); | ||||
| } | ||||
|  | ||||
| export default FunctionArticle; | ||||
							
								
								
									
										15
									
								
								website/components/FunctionPage/FunctionArticle.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								website/components/FunctionPage/FunctionArticle.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| import htmlParser from 'html-react-parser' | ||||
|  | ||||
| const FunctionArticle = ({ article }) => { | ||||
|   return ( | ||||
|     <div style={{ marginBottom: '50px' }} className='container-fluid'> | ||||
|       {article != null ? ( | ||||
|         htmlParser(article) | ||||
|       ) : ( | ||||
|         <p className='text-center'>L'article n'est pas encore disponible.</p> | ||||
|       )} | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default FunctionArticle | ||||
| @@ -1,7 +1,7 @@ | ||||
| .FunctionComments__row { | ||||
|     margin-bottom: 20px; | ||||
|   margin-bottom: 20px; | ||||
| } | ||||
| .FunctionComments__textarea { | ||||
|     height: auto; | ||||
|     resize: vertical; | ||||
| } | ||||
|   height: auto; | ||||
|   resize: vertical; | ||||
| } | ||||
|   | ||||
| @@ -1,115 +0,0 @@ | ||||
| import { Fragment, useState, useEffect, useContext, useRef, useCallback } from 'react'; | ||||
| import Link from 'next/link'; | ||||
| import { UserContext } from '../../../contexts/UserContext'; | ||||
| import CommentCard from '../CommentCard/CommentCard'; | ||||
| import Loader from '../../Loader'; | ||||
| import api from '../../../utils/api'; | ||||
| import './FunctionComments.css'; | ||||
|  | ||||
| const FunctionComments = ({ functionId }) => { | ||||
|  | ||||
|     // State pour poster un commentaire | ||||
|     const { isAuth, user }                = useContext(UserContext); | ||||
|     const [inputState, setInputState]     = useState({}); | ||||
|  | ||||
|     // State pour afficher les commentaires | ||||
|     const [commentsData, setCommentsData]         = useState({ hasMore: true, rows: [] }); | ||||
|     const [isLoadingComments, setLoadingComments] = useState(true); | ||||
|     const [pageComments, setPageComments]         = useState(1); | ||||
|  | ||||
|     // Récupère les commentaires si la page change | ||||
|     useEffect(() => { | ||||
|         getCommentsData().then((data) => setCommentsData({  | ||||
|             hasMore: data.hasMore,  | ||||
|             rows: [...commentsData.rows, ...data.rows]  | ||||
|         })); | ||||
|     }, [pageComments]); | ||||
|  | ||||
|     // Permet la pagination au scroll | ||||
|     const observer = useRef(); | ||||
|     const lastCommentCardRef = useCallback((node) => { | ||||
|         if (isLoadingComments) return; | ||||
|         if (observer.current) observer.current.disconnect(); | ||||
|         observer.current = new IntersectionObserver((entries) => { | ||||
|             if (entries[0].isIntersecting && commentsData.hasMore) { | ||||
|                 setPageComments(pageComments + 1); | ||||
|             } | ||||
|         }, { threshold: 1 }); | ||||
|         if (node) observer.current.observe(node); | ||||
|     }, [isLoadingComments, commentsData.hasMore]); | ||||
|  | ||||
|     const getCommentsData = () => { | ||||
|         setLoadingComments(true); | ||||
|         return new Promise(async (next) => { | ||||
|             const result = await api.get(`/comments/${functionId}/?page=${pageComments}&limit=10`); | ||||
|             setLoadingComments(false); | ||||
|             next(result.data); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     const handleChange = (event) => { | ||||
|         const inputStateNew = { ...inputState }; | ||||
|         inputStateNew[event.target.name] = event.target.value; | ||||
|         setInputState(inputStateNew); | ||||
|     } | ||||
|  | ||||
|     const handleSubmit = async (event) => { | ||||
|         setLoadingComments(true); | ||||
|         event.preventDefault(); | ||||
|         const token = user.token; | ||||
|         if (isAuth && token != undefined && inputState.commentPost != undefined) { | ||||
|             try { | ||||
|                 const response = await api.post(`/comments/${functionId}`, { message: inputState.commentPost }, { headers: { 'Authorization': token } }); | ||||
|                 const comment = { ...response.data, user: { name: user.name, logo: user.logo } }; | ||||
|                 setCommentsData({ hasMore: commentsData.hasMore, rows: [comment, ...commentsData.rows] }); | ||||
|                 setInputState({ commentPost: "" }); | ||||
|             } catch { } | ||||
|         } | ||||
|         setLoadingComments(false); | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
|         <Fragment> | ||||
|             <div className="FunctionComments__post container-fluid"> | ||||
|                 <div className="row FunctionComments__row"> | ||||
|                     <div className="col-24"> | ||||
|                         { | ||||
|                             (isAuth) ? | ||||
|                                 <form onSubmit={handleSubmit}> | ||||
|                                     <div className="form-group FunctionComments__post-group"> | ||||
|                                         <label className="form-label" htmlFor="commentPost">Ajouter un commentaire :</label>    | ||||
|                                         <textarea className="FunctionComments__textarea form-control" value={inputState.commentPost} onChange={handleChange} name="commentPost" id="commentPost" placeholder="Idée d'amélioration, avis, remarque, partage d'expérience personnel, ... (Markdown autorisé)"></textarea> | ||||
|                                     </div> | ||||
|                                     <div className="form-group" style={{ marginTop: '0.7em' }}> | ||||
|                                         <button type="submit" className="btn btn-dark">Envoyer</button> | ||||
|                                     </div> | ||||
|                                 </form> | ||||
|                             : | ||||
|                                 <p className="text-center"> | ||||
|                                     Vous devez être <Link href={'/users/login'}><a>connecté</a></Link> pour poster un commentaire. | ||||
|                                 </p> | ||||
|                         } | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div className="container-fluid"> | ||||
|                 {isLoadingComments &&  | ||||
|                     <div className="row justify-content-center"> | ||||
|                         <Loader /> | ||||
|                     </div> | ||||
|                 } | ||||
|                 <div className="row justify-content-center"> | ||||
|                     {commentsData.rows.map((comment, index) => { | ||||
|                         // Si c'est le dernier élément | ||||
|                         if (commentsData.rows.length === index + 1) { | ||||
|                             return <CommentCard key={comment.id} ref={lastCommentCardRef} { ...comment } manageComment={{ setCommentsData, commentsData, setLoadingComments }} />; | ||||
|                         } | ||||
|                         return <CommentCard key={comment.id} { ...comment } manageComment={{ setCommentsData, commentsData, setLoadingComments }} />; | ||||
|                     })} | ||||
|                 </div> | ||||
|             </div> | ||||
|         </Fragment> | ||||
|     ); | ||||
| } | ||||
|  | ||||
| export default FunctionComments; | ||||
| @@ -0,0 +1,167 @@ | ||||
| import { useState, useEffect, useContext, useRef, useCallback } from 'react' | ||||
| import Link from 'next/link' | ||||
| import { UserContext } from '../../../contexts/UserContext' | ||||
| import CommentCard from '../CommentCard/CommentCard' | ||||
| import Loader from '../../Loader' | ||||
| import api from '../../../utils/api' | ||||
| import './FunctionComments.css' | ||||
|  | ||||
| const FunctionComments = ({ functionId }) => { | ||||
|   // State pour poster un commentaire | ||||
|   const { isAuth, user } = useContext(UserContext) | ||||
|   const [inputState, setInputState] = useState({}) | ||||
|  | ||||
|   // State pour afficher les commentaires | ||||
|   const [commentsData, setCommentsData] = useState({ hasMore: true, rows: [] }) | ||||
|   const [isLoadingComments, setLoadingComments] = useState(true) | ||||
|   const [pageComments, setPageComments] = useState(1) | ||||
|  | ||||
|   // Récupère les commentaires si la page change | ||||
|   useEffect(() => { | ||||
|     getCommentsData().then(data => | ||||
|       setCommentsData({ | ||||
|         hasMore: data.hasMore, | ||||
|         rows: [...commentsData.rows, ...data.rows] | ||||
|       }) | ||||
|     ) | ||||
|   }, [pageComments]) | ||||
|  | ||||
|   // Permet la pagination au scroll | ||||
|   const observer = useRef() | ||||
|   const lastCommentCardRef = useCallback( | ||||
|     node => { | ||||
|       if (isLoadingComments) return | ||||
|       if (observer.current) observer.current.disconnect() | ||||
|       observer.current = new window.IntersectionObserver( | ||||
|         entries => { | ||||
|           if (entries[0].isIntersecting && commentsData.hasMore) { | ||||
|             setPageComments(pageComments + 1) | ||||
|           } | ||||
|         }, | ||||
|         { threshold: 1 } | ||||
|       ) | ||||
|       if (node) observer.current.observe(node) | ||||
|     }, | ||||
|     [isLoadingComments, commentsData.hasMore] | ||||
|   ) | ||||
|  | ||||
|   const getCommentsData = async () => { | ||||
|     setLoadingComments(true) | ||||
|     const { data } = await api.get( | ||||
|       `/comments/${functionId}/?page=${pageComments}&limit=10` | ||||
|     ) | ||||
|     setLoadingComments(false) | ||||
|     return data | ||||
|   } | ||||
|  | ||||
|   const handleChange = event => { | ||||
|     const inputStateNew = { ...inputState } | ||||
|     inputStateNew[event.target.name] = event.target.value | ||||
|     setInputState(inputStateNew) | ||||
|   } | ||||
|  | ||||
|   const handleSubmit = async event => { | ||||
|     setLoadingComments(true) | ||||
|     event.preventDefault() | ||||
|     const token = user.token | ||||
|     if (isAuth && token != null && inputState.commentPost != null) { | ||||
|       try { | ||||
|         const response = await api.post( | ||||
|           `/comments/${functionId}`, | ||||
|           { message: inputState.commentPost }, | ||||
|           { headers: { Authorization: token } } | ||||
|         ) | ||||
|         const comment = { | ||||
|           ...response.data, | ||||
|           user: { name: user.name, logo: user.logo } | ||||
|         } | ||||
|         setCommentsData({ | ||||
|           hasMore: commentsData.hasMore, | ||||
|           rows: [comment, ...commentsData.rows] | ||||
|         }) | ||||
|         setInputState({ commentPost: '' }) | ||||
|       } catch {} | ||||
|     } | ||||
|     setLoadingComments(false) | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <div className='FunctionComments__post container-fluid'> | ||||
|         <div className='row FunctionComments__row'> | ||||
|           <div className='col-24'> | ||||
|             {isAuth ? ( | ||||
|               <form onSubmit={handleSubmit}> | ||||
|                 <div className='form-group FunctionComments__post-group'> | ||||
|                   <label className='form-label' htmlFor='commentPost'> | ||||
|                     Ajouter un commentaire : | ||||
|                   </label> | ||||
|                   <textarea | ||||
|                     className='FunctionComments__textarea form-control' | ||||
|                     value={inputState.commentPost} | ||||
|                     onChange={handleChange} | ||||
|                     name='commentPost' | ||||
|                     id='commentPost' | ||||
|                     placeholder="Idée d'amélioration, avis, remarque, partage d'expérience personnel, ... (Markdown autorisé)" | ||||
|                   /> | ||||
|                 </div> | ||||
|                 <div className='form-group' style={{ marginTop: '0.7em' }}> | ||||
|                   <button type='submit' className='btn btn-dark'> | ||||
|                     Envoyer | ||||
|                   </button> | ||||
|                 </div> | ||||
|               </form> | ||||
|             ) : ( | ||||
|               <p className='text-center'> | ||||
|                 Vous devez être{' '} | ||||
|                 <Link href='/users/login'> | ||||
|                   <a>connecté</a> | ||||
|                 </Link>{' '} | ||||
|                 pour poster un commentaire. | ||||
|               </p> | ||||
|             )} | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div className='container-fluid'> | ||||
|         {isLoadingComments && ( | ||||
|           <div className='row justify-content-center'> | ||||
|             <Loader /> | ||||
|           </div> | ||||
|         )} | ||||
|         <div className='row justify-content-center'> | ||||
|           {commentsData.rows.map((comment, index) => { | ||||
|             // Si c'est le dernier élément | ||||
|             if (commentsData.rows.length === index + 1) { | ||||
|               return ( | ||||
|                 <CommentCard | ||||
|                   key={comment.id} | ||||
|                   ref={lastCommentCardRef} | ||||
|                   {...comment} | ||||
|                   manageComment={{ | ||||
|                     setCommentsData, | ||||
|                     commentsData, | ||||
|                     setLoadingComments | ||||
|                   }} | ||||
|                 /> | ||||
|               ) | ||||
|             } | ||||
|             return ( | ||||
|               <CommentCard | ||||
|                 key={comment.id} | ||||
|                 {...comment} | ||||
|                 manageComment={{ | ||||
|                   setCommentsData, | ||||
|                   commentsData, | ||||
|                   setLoadingComments | ||||
|                 }} | ||||
|               /> | ||||
|             ) | ||||
|           })} | ||||
|         </div> | ||||
|       </div> | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default FunctionComments | ||||
| @@ -1,71 +0,0 @@ | ||||
| import { useState, useEffect, useContext } from 'react'; | ||||
| import Link from 'next/link'; | ||||
| import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; | ||||
| import { faStar } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faStar as farStar } from '@fortawesome/free-regular-svg-icons'; | ||||
| import date from 'date-and-time'; | ||||
| import { UserContext } from '../../contexts/UserContext'; | ||||
| import api from '../../utils/api'; | ||||
| import { API_URL } from '../../utils/config/config'; | ||||
| import '../FunctionCard/FunctionCard.css'; | ||||
|  | ||||
| const FunctionComponentTop = (props) => { | ||||
|  | ||||
|     const { isAuth, user }            = useContext(UserContext); | ||||
|     const [isFavorite, setIsFavorite] = useState(false); | ||||
|  | ||||
|     useEffect(() => { | ||||
|         if (isAuth && user.token != undefined) { | ||||
|             fetchFavorite(); | ||||
|         } | ||||
|     }, [isAuth]); | ||||
|  | ||||
|     const fetchFavorite = async () => { | ||||
|         try { | ||||
|             const favoriteResponse = await api.get(`/favorites/${props.id}`, { headers: { 'Authorization': user.token } }); | ||||
|             setIsFavorite(favoriteResponse.data.isFavorite); | ||||
|         } catch {} | ||||
|     } | ||||
|  | ||||
|     const toggleFavorite = async () => { | ||||
|         if (isAuth && user.token != undefined) { | ||||
|             try { | ||||
|                 if (isFavorite) { | ||||
|                     const response = await api.delete(`/favorites/${props.id}`, { headers: { 'Authorization': user.token } }); | ||||
|                     if (response.status === 200) return setIsFavorite(false); | ||||
|                 } | ||||
|                 const response = await api.post(`/favorites/${props.id}`, {}, { headers: { 'Authorization': user.token } }); | ||||
|                 if (response.status === 201) return setIsFavorite(true); | ||||
|             } catch {} | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     const handleError = (event) => { | ||||
|         event.target.src = API_URL + "/images/functions/default.png"; | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
|         <div className="container-fluid"> | ||||
|             <div className="row justify-content-center text-center"> | ||||
|                 <div className="FunctionComponent__top col-24"> | ||||
|  | ||||
|                     {(isAuth) && | ||||
|                         <FontAwesomeIcon onClick={toggleFavorite} { ...(isFavorite) ? { icon: faStar } : { icon: farStar } } title={(isFavorite) ? "Retirer la fonction des favoris" : "Ajouter la fonction aux favoris"} className="FunctionComponent__star-favorite" /> | ||||
|                     } | ||||
|  | ||||
|                     <img onError={handleError} className="FunctionComponent__image" src={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"> | ||||
|                         <Link href={`/functions?categoryId=${props.categorieId}`}> | ||||
|                             <a className="FunctionCard__category" style={{ backgroundColor: props.categorie.color, color: 'inherit' }}>{props.categorie.name}</a> | ||||
|                         </Link> | ||||
|                         <p className="FunctionCard__publication-date">{date.format(new Date(props.createdAt), 'DD/MM/YYYY', true)}</p> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     ); | ||||
| } | ||||
|  | ||||
| export default FunctionComponentTop; | ||||
							
								
								
									
										102
									
								
								website/components/FunctionPage/FunctionComponentTop.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								website/components/FunctionPage/FunctionComponentTop.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | ||||
| import { useState, useEffect, useContext } from 'react' | ||||
| import Link from 'next/link' | ||||
| import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' | ||||
| import { faStar } from '@fortawesome/free-solid-svg-icons' | ||||
| import { faStar as farStar } from '@fortawesome/free-regular-svg-icons' | ||||
| import date from 'date-and-time' | ||||
| import { UserContext } from '../../contexts/UserContext' | ||||
| import api, { API_URL } from '../../utils/api' | ||||
| import '../FunctionCard/FunctionCard.css' | ||||
|  | ||||
| const FunctionComponentTop = props => { | ||||
|   const { isAuth, user } = useContext(UserContext) | ||||
|   const [isFavorite, setIsFavorite] = useState(false) | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (isAuth && user.token != null) { | ||||
|       fetchFavorite() | ||||
|     } | ||||
|   }, [isAuth]) | ||||
|  | ||||
|   const fetchFavorite = async () => { | ||||
|     try { | ||||
|       const favoriteResponse = await api.get(`/favorites/${props.id}`, { | ||||
|         headers: { Authorization: user.token } | ||||
|       }) | ||||
|       setIsFavorite(favoriteResponse.data.isFavorite) | ||||
|     } catch {} | ||||
|   } | ||||
|  | ||||
|   const toggleFavorite = async () => { | ||||
|     if (isAuth && user.token != null) { | ||||
|       try { | ||||
|         if (isFavorite) { | ||||
|           const response = await api.delete(`/favorites/${props.id}`, { | ||||
|             headers: { Authorization: user.token } | ||||
|           }) | ||||
|           if (response.status === 200) return setIsFavorite(false) | ||||
|         } | ||||
|         const response = await api.post( | ||||
|           `/favorites/${props.id}`, | ||||
|           {}, | ||||
|           { headers: { Authorization: user.token } } | ||||
|         ) | ||||
|         if (response.status === 201) return setIsFavorite(true) | ||||
|       } catch {} | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   const handleError = event => { | ||||
|     event.target.src = API_URL + '/images/functions/default.png' | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <div className='container-fluid'> | ||||
|       <div className='row justify-content-center text-center'> | ||||
|         <div className='FunctionComponent__top col-24'> | ||||
|           {isAuth && ( | ||||
|             <FontAwesomeIcon | ||||
|               onClick={toggleFavorite} | ||||
|               {...(isFavorite ? { icon: faStar } : { icon: farStar })} | ||||
|               title={ | ||||
|                 isFavorite | ||||
|                   ? 'Retirer la fonction des favoris' | ||||
|                   : 'Ajouter la fonction aux favoris' | ||||
|               } | ||||
|               className='FunctionComponent__star-favorite' | ||||
|             /> | ||||
|           )} | ||||
|  | ||||
|           <img | ||||
|             onError={handleError} | ||||
|             className='FunctionComponent__image' | ||||
|             src={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'> | ||||
|             <Link href={`/functions?categoryId=${props.categorieId}`}> | ||||
|               <a | ||||
|                 className='FunctionCard__category' | ||||
|                 style={{ | ||||
|                   backgroundColor: props.categorie.color, | ||||
|                   color: 'inherit' | ||||
|                 }} | ||||
|               > | ||||
|                 {props.categorie.name} | ||||
|               </a> | ||||
|             </Link> | ||||
|             <p className='FunctionCard__publication-date'> | ||||
|               {date.format(new Date(props.createdAt), 'DD/MM/YYYY', true)} | ||||
|             </p> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default FunctionComponentTop | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user