🎨 Configure standardJS
This commit is contained in:
		| @@ -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> | ||||
| @@ -76,6 +75,8 @@ 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. | ||||
							
								
								
									
										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/ | ||||
|  | ||||
|   | ||||
							
								
								
									
										116
									
								
								api/app.js
									
									
									
									
									
								
							
							
						
						
									
										116
									
								
								api/app.js
									
									
									
									
									
								
							| @@ -1,83 +1,83 @@ | ||||
| /* 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').redirectToHTTPS | ||||
|  | ||||
| /* 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')) | ||||
|  | ||||
| /* 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') | ||||
|  | ||||
| // 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 }) | ||||
|  | ||||
| /* 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)); | ||||
|   .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,7 +26,7 @@ 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> | ||||
| @@ -35,7 +35,7 @@ exports.emailQuoteTemplate = (isValid, quote, frontendLink) => ` | ||||
|                                                         <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 +57,7 @@ exports.emailQuoteTemplate = (isValid, quote, frontendLink) => ` | ||||
|         </tbody> | ||||
|     </table> | ||||
| </center> | ||||
| `; | ||||
| ` | ||||
|  | ||||
| exports.emailUserTemplate = (subtitle, buttonText, url, footerText) => ` | ||||
| <center> | ||||
| @@ -107,4 +107,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,44 @@ | ||||
| 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 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 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, | ||||
|   linkShortener: linkShortener, | ||||
|   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,46 @@ | ||||
| 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,50 @@ | ||||
| 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,37 @@ | ||||
| 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 }); | ||||
|         }) | ||||
|         .catch(() => errorHandling(next, { message: "La devise n'existe pas.", statusCode: 404 })); | ||||
| } | ||||
|   // 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 }) | ||||
|       } | ||||
|       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,10 @@ | ||||
| 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 +13,39 @@ 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,239 @@ | ||||
| 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,122 @@ | ||||
| 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,48 @@ | ||||
| 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,46 @@ | ||||
| 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,52 @@ | ||||
| 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,48 @@ | ||||
| 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 +1,52 @@ | ||||
| const validator                       = require('validator'); | ||||
| const errorHandling                   = require('../../utils/errorHandling'); | ||||
| const { requiredFields, serverError } = require('../../config/errors'); | ||||
| const Short_links                     = require('../../../models/short_links'); | ||||
| const validator = require('validator') | ||||
| const errorHandling = require('../../utils/errorHandling') | ||||
| const { requiredFields, serverError } = require('../../config/errors') | ||||
| const shortLinks = require('../../../models/short_links') | ||||
|  | ||||
| module.exports = linkShortener = async ({ res, next }, argsObject) => { | ||||
|     let { url, shortcutName } = argsObject; | ||||
| module.exports = async ({ res, next }, argsObject) => { | ||||
|   let { url, shortcutName } = argsObject | ||||
|  | ||||
|     // S'il n'y a pas les champs obligatoire | ||||
|     if (!(url && shortcutName)) { | ||||
|         return errorHandling(next, requiredFields); | ||||
|   // 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 = `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 ce n'est pas une url | ||||
|     if (!validator.isURL(url)) { | ||||
|         return errorHandling(next, { message: "Veuillez entré une URL valide.", statusCode: 400 }); | ||||
|     // Si le nom du raccourci existe déjà | ||||
|     const shortcutInDatabase = await shortLinks.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 }) | ||||
|     } | ||||
|  | ||||
|     // 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); | ||||
|     } | ||||
| } | ||||
|     // Ajout du lien raccourci | ||||
|     const result = await shortLinks.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 (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,37 +1,37 @@ | ||||
| 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,26 @@ | ||||
| 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,57 @@ | ||||
| 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,56 @@ | ||||
| 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,46 @@ | ||||
| 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,43 @@ | ||||
| /**  | ||||
| /** | ||||
|  * @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,9 @@ | ||||
| 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, { | ||||
|     dialect: 'mysql', | ||||
|     host: DATABASE.host | ||||
| }); | ||||
|   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,23 @@ 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,319 @@ | ||||
| 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 }); | ||||
|   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"] } | ||||
|         ] | ||||
|   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) | ||||
|     }) | ||||
|     .catch((error) => { | ||||
|       console.log(error) | ||||
|       return errorHandling(next, serverError) | ||||
|     }) | ||||
|         .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); | ||||
|         }) | ||||
|         .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 }); | ||||
|     } | ||||
|     if (!image || image.truncated && ( | ||||
|         image.mimetype !== 'image/png' ||  | ||||
|         image.mimetype !== 'image/jpg' ||  | ||||
|   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 }); | ||||
|   )) { | ||||
|     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) | ||||
|     } | ||||
|     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 }); | ||||
|         } | ||||
|     // 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' ||  | ||||
|     // 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); | ||||
|       )) { | ||||
|         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,72 @@ | ||||
| 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,74 @@ | ||||
| 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,69 @@ | ||||
| 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"] } | ||||
|         ] | ||||
|   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) | ||||
|     }) | ||||
|     .catch((error) => { | ||||
|       console.log(error) | ||||
|       return errorHandling(next, serverError) | ||||
|     }) | ||||
|         .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); | ||||
|         }) | ||||
|         .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 }) | ||||
| } | ||||
|   | ||||
| @@ -1,37 +1,37 @@ | ||||
| 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." }); | ||||
|         }) | ||||
|         .catch((error) => { | ||||
|             console.log(error); | ||||
|             return errorHandling(next, serverError); | ||||
|         }); | ||||
| } | ||||
|   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) | ||||
|     }) | ||||
| } | ||||
|   | ||||
| @@ -1,68 +1,68 @@ | ||||
| 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,292 @@ | ||||
| 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 }); | ||||
|     } | ||||
|     if (logo != undefined) { | ||||
|         if (!logo || logo.truncated && ( | ||||
|             logo.mimetype !== 'image/png' ||  | ||||
|             logo.mimetype !== 'image/jpg' ||  | ||||
|   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 }); | ||||
|         } | ||||
|         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); | ||||
|         } | ||||
|     )) { | ||||
|       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) | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| 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 }, | ||||
|       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,23 @@ | ||||
| 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(); | ||||
|         }) | ||||
|         .catch((error) => { | ||||
|             console.log(error); | ||||
|             return errorHandling(next, serverError); | ||||
|         }); | ||||
| } | ||||
|   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() | ||||
|     }) | ||||
|     .catch((error) => { | ||||
|       console.log(error) | ||||
|       return errorHandling(next, serverError) | ||||
|     }) | ||||
| } | ||||
|   | ||||
| @@ -1,24 +1,24 @@ | ||||
| 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 | ||||
|   } | ||||
| }) | ||||
|   | ||||
							
								
								
									
										2446
									
								
								api/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2446
									
								
								api/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -2,14 +2,11 @@ | ||||
|   "name": "api", | ||||
|   "version": "2.0.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", | ||||
| @@ -33,6 +30,8 @@ | ||||
|   "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,205 @@ | ||||
| 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 | ||||
|     }), | ||||
|     [ | ||||
|         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; | ||||
|             }) | ||||
|     ], adminController.postFunction); | ||||
| // 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(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) } }) | ||||
|           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 | ||||
|     }), | ||||
|     [ | ||||
|         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; | ||||
|             }) | ||||
|     ], adminController.putFunction) | ||||
| // 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(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) } }) | ||||
|           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 | ||||
|   | ||||
| @@ -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,145 @@ | ||||
| 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") | ||||
|         .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); | ||||
|           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 | ||||
|     }) | ||||
|     .normalizeEmail(), | ||||
|   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 | ||||
|   | ||||
							
								
								
									
										1
									
								
								website/.env.example
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								website/.env.example
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| NEXT_PUBLIC_API_URL = "http://localhost:8080" | ||||
							
								
								
									
										2
									
								
								website/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								website/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -17,7 +17,7 @@ | ||||
|  | ||||
| # misc | ||||
| .DS_Store | ||||
| .env* | ||||
| .env | ||||
|  | ||||
| # debug | ||||
| npm-debug.log* | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| import SyntaxHighlighter from "react-syntax-highlighter"; | ||||
| import { atomOneDark as styles} from "react-syntax-highlighter/dist/cjs/styles/hljs"; | ||||
| 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> | ||||
|     ); | ||||
| }; | ||||
|   return ( | ||||
|     <SyntaxHighlighter language={language} style={styles}> | ||||
|       {value} | ||||
|     </SyntaxHighlighter> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default CodeBlock; | ||||
| export default CodeBlock | ||||
|   | ||||
| @@ -1,16 +1,16 @@ | ||||
| import Link from 'next/link'; | ||||
| import './Footer.css'; | ||||
| 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> | ||||
|     ); | ||||
| } | ||||
| 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> | ||||
|   ) | ||||
| } | ||||
|   | ||||
| @@ -1,129 +1,126 @@ | ||||
| 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'; | ||||
| import { 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) | ||||
|  | ||||
|     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 | ||||
|                 } | ||||
|             }); | ||||
|   useEffect(() => { | ||||
|     if (categories.length > 0 && !props.isEditing) { | ||||
|       handleChange({ | ||||
|         target: { | ||||
|           name: 'categorieId', | ||||
|           value: categories[0].id | ||||
|         } | ||||
|     }, [categories]); | ||||
|       }) | ||||
|     } | ||||
|   }, [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 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) | ||||
|     } | ||||
|  | ||||
|     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); | ||||
|     } | ||||
|     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) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|     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); | ||||
|   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> | ||||
|  | ||||
|         if (props.isEditing) { | ||||
|             formData.append('isOnline', inputState.isOnline); | ||||
|         <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) | ||||
|         } | ||||
|  | ||||
|         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> | ||||
|     ); | ||||
|       </div> | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default AddEditFunction; | ||||
| export default AddEditFunction | ||||
|   | ||||
| @@ -1,46 +1,45 @@ | ||||
| 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'; | ||||
| 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 } | ||||
| ); | ||||
|   () => import('suneditor-react'), | ||||
|   { ssr: false } | ||||
| ) | ||||
|  | ||||
| const EditArticleFunction = (props) => { | ||||
|   const [htmlContent, setHtmlContent] = useState('') | ||||
|  | ||||
|     const [htmlContent, setHtmlContent] = useState(""); | ||||
|   const handleEditorChange = (content) => { | ||||
|     setHtmlContent(content) | ||||
|   } | ||||
|  | ||||
|     const handleEditorChange = (content) => { | ||||
|         setHtmlContent(content); | ||||
|   const handleSave = async (content) => { | ||||
|     let Notyf | ||||
|     if (typeof window !== 'undefined') { | ||||
|       Notyf = require('notyf') | ||||
|     } | ||||
|  | ||||
|     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!'); | ||||
|         } | ||||
|     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> | ||||
|     ); | ||||
|   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; | ||||
| export default EditArticleFunction | ||||
|   | ||||
| @@ -1,158 +1,154 @@ | ||||
| import { useState, Fragment } from 'react'; | ||||
| import api from '../../utils/api'; | ||||
| import 'notyf/notyf.min.css'; | ||||
| 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 [inputsArray, setInputsArray] = useState(props.functionInfo.utilizationForm || []); | ||||
|   const addInput = () => { | ||||
|     const newInputsArray = [...inputsArray] | ||||
|     newInputsArray.push({ name: '', label: '', placeholder: '', type: 'text' }) | ||||
|     setInputsArray(newInputsArray) | ||||
|   } | ||||
|  | ||||
|     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 addOption = (event) => { | ||||
|         const newInputsArray = [...inputsArray]; | ||||
|         const index          = event.target.id.split('-')[1]; | ||||
|         const inputObject    = newInputsArray[index]; | ||||
|         inputObject.options.push({ name: "", value: "" }); | ||||
|         setInputsArray(newInputsArray); | ||||
|   const handleSubmit = async (event) => { | ||||
|     event.preventDefault() | ||||
|     let Notyf | ||||
|     if (typeof window !== 'undefined') { | ||||
|       Notyf = require('notyf') | ||||
|     } | ||||
|  | ||||
|     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 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 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 handleRemoveInput = (event) => { | ||||
|     const newInputsArray = [...inputsArray] | ||||
|     const index = event.target.id.split('-')[1] | ||||
|     newInputsArray.splice(index, 1) | ||||
|     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 handleRemoveOption = (inputIndex, optionIndex) => { | ||||
|     const newInputsArray = [...inputsArray] | ||||
|     const inputObject = newInputsArray[inputIndex] | ||||
|     const optionsArray = inputObject.options | ||||
|     optionsArray.splice(optionIndex, 1) | ||||
|     setInputsArray(newInputsArray) | ||||
|   } | ||||
|  | ||||
|     const handleRemoveInput = (event) => { | ||||
|         const newInputsArray = [...inputsArray]; | ||||
|         const index          = event.target.id.split('-')[1]; | ||||
|         newInputsArray.splice(index, 1); | ||||
|         setInputsArray(newInputsArray); | ||||
|     } | ||||
|   return ( | ||||
|     <div className='container-fluid'> | ||||
|  | ||||
|     const handleRemoveOption = (inputIndex, optionIndex) => { | ||||
|         const newInputsArray = [...inputsArray]; | ||||
|         const inputObject    = newInputsArray[inputIndex]; | ||||
|         const optionsArray   = inputObject.options; | ||||
|         optionsArray.splice(optionIndex, 1); | ||||
|         setInputsArray(newInputsArray); | ||||
|     } | ||||
|       <form onSubmit={handleSubmit}> | ||||
|  | ||||
|     return ( | ||||
|         <div className="container-fluid"> | ||||
|         {(inputsArray.length > 0) && | ||||
|           <div className='form-group text-center'> | ||||
|             <button type='submit' className='btn btn-dark'>Sauvegarder</button> | ||||
|           </div>} | ||||
|  | ||||
|             <form onSubmit={handleSubmit}> | ||||
|         {inputsArray.map((input, index) => { | ||||
|           return ( | ||||
|             <div key={index} className='form-group Admin__Input-group'> | ||||
|  | ||||
|                 {(inputsArray.length > 0) &&                 | ||||
|                     <div className="form-group text-center"> | ||||
|                         <button type="submit" className="btn btn-dark">Sauvegarder</button> | ||||
|                     </div> | ||||
|                 } | ||||
|               <div className='text-center'> | ||||
|                 <button type='button' onClick={handleRemoveInput} id={`remove-${index}`} className='btn btn-dark'>Supprimer l'input</button> | ||||
|               </div> | ||||
|  | ||||
|                 {inputsArray.map((input, index) => { | ||||
|               <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={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 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> | ||||
|                     ); | ||||
|                 })} | ||||
|  | ||||
|             </form> | ||||
|                         <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" /> | ||||
|  | ||||
|             <div style={{ marginBottom: '30px' }} className="form-group text-center"> | ||||
|                 <button type="button" onClick={addInput} className="btn btn-dark">Ajouter un input</button> | ||||
|                         <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> | ||||
|         </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; | ||||
| export default EditFormFunction | ||||
|   | ||||
| @@ -1,58 +1,56 @@ | ||||
| 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'; | ||||
| 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 [isLoading, setIsLoading] = useState(true); | ||||
|   const handleLoad = () => { | ||||
|     setIsLoading(false) | ||||
|   } | ||||
|  | ||||
|     const handleLoad = () => { | ||||
|         setIsLoading(false); | ||||
|     } | ||||
|   const handleError = (event) => { | ||||
|     event.target.src = API_URL + '/images/functions/default.png' | ||||
|   } | ||||
|  | ||||
|     const handleError = (event) => { | ||||
|         event.target.src = API_URL + "/images/functions/default.png"; | ||||
|     } | ||||
|   const isFormOrArticle = (props.type === 'form' || props.type === 'article') | ||||
|  | ||||
|     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'> | ||||
|  | ||||
|     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' />} | ||||
|  | ||||
|                 {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> | ||||
|   ) | ||||
| }) | ||||
|  | ||||
|                 <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; | ||||
| export default FunctionCard | ||||
|   | ||||
| @@ -1,108 +1,109 @@ | ||||
| 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'; | ||||
| 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 { 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('') | ||||
|  | ||||
|     const { isAuth, user }          = useContext(UserContext); | ||||
|     const [isEditing, setEditing]   = useState(false); | ||||
|     const [editInput, setEditInput] = useState(""); | ||||
|     const [message, setMessage]     = useState(""); | ||||
|   useEffect(() => { | ||||
|     setEditInput(props.message) | ||||
|   }, []) | ||||
|  | ||||
|     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 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 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 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(""); | ||||
|     } | ||||
|   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> | ||||
|   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; | ||||
| export default CommentCard | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| import htmlParser from 'html-react-parser'; | ||||
| 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> | ||||
|     ); | ||||
|   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; | ||||
| export default FunctionArticle | ||||
|   | ||||
| @@ -1,115 +1,114 @@ | ||||
| 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'; | ||||
| 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 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) | ||||
|  | ||||
|     // 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]) | ||||
|  | ||||
|     // 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]) | ||||
|  | ||||
|     // 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); | ||||
|   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> | ||||
|                 ) | ||||
|             } | ||||
|         }, { 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> | ||||
|     ); | ||||
|           </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; | ||||
| export default FunctionComments | ||||
|   | ||||
| @@ -1,71 +1,69 @@ | ||||
| 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'; | ||||
| 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) | ||||
|  | ||||
|     const { isAuth, user }            = useContext(UserContext); | ||||
|     const [isFavorite, setIsFavorite] = useState(false); | ||||
|   useEffect(() => { | ||||
|     if (isAuth && user.token != null) { | ||||
|       fetchFavorite() | ||||
|     } | ||||
|   }, [isAuth]) | ||||
|  | ||||
|     useEffect(() => { | ||||
|         if (isAuth && user.token != undefined) { | ||||
|             fetchFavorite(); | ||||
|   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) | ||||
|         } | ||||
|     }, [isAuth]); | ||||
|  | ||||
|     const fetchFavorite = async () => { | ||||
|         try { | ||||
|             const favoriteResponse = await api.get(`/favorites/${props.id}`, { headers: { 'Authorization': user.token } }); | ||||
|             setIsFavorite(favoriteResponse.data.isFavorite); | ||||
|         } catch {} | ||||
|         const response = await api.post(`/favorites/${props.id}`, {}, { headers: { Authorization: user.token } }) | ||||
|         if (response.status === 201) return setIsFavorite(true) | ||||
|       } 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' | ||||
|   } | ||||
|  | ||||
|     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'> | ||||
|  | ||||
|     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' />} | ||||
|  | ||||
|                     {(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> | ||||
|           <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; | ||||
| export default FunctionComponentTop | ||||
|   | ||||
| @@ -1,161 +1,160 @@ | ||||
| import { Fragment, useState, useEffect } from 'react'; | ||||
| import Loader from '../Loader'; | ||||
| import htmlParser from 'html-react-parser'; | ||||
| import dynamic from 'next/dynamic'; | ||||
| import api from '../../utils/api'; | ||||
| import fr from 'date-fns/locale/fr'; | ||||
| import { registerLocale } from  "react-datepicker"; | ||||
| import date from 'date-and-time'; | ||||
| import "react-datepicker/dist/react-datepicker.css"; | ||||
| import { useState, useEffect } from 'react' | ||||
| import Loader from '../Loader' | ||||
| import htmlParser from 'html-react-parser' | ||||
| import dynamic from 'next/dynamic' | ||||
| import api from '../../utils/api' | ||||
| import fr from 'date-fns/locale/fr' | ||||
| import { registerLocale } from 'react-datepicker' | ||||
| import date from 'date-and-time' | ||||
| import 'react-datepicker/dist/react-datepicker.css' | ||||
|  | ||||
| registerLocale('fr', fr); | ||||
| registerLocale('fr', fr) | ||||
|  | ||||
| const FunctionForm = (props) => { | ||||
|   const [inputState, setInputState] = useState({}) | ||||
|   const [message, setMessage] = useState('') | ||||
|   const [isLoading, setIsLoading] = useState(false) | ||||
|  | ||||
|     const [inputState, setInputState]         = useState({}); | ||||
|     const [message, setMessage]               = useState(""); | ||||
|     const [isLoading, setIsLoading]           = useState(false); | ||||
|   // inputState par défaut | ||||
|   useEffect(() => { | ||||
|     const inputStateNew = { ...inputState } | ||||
|     props.inputsArray.forEach((input) => { | ||||
|       if (input.type === 'select' && input.options.length > 0) { | ||||
|         inputStateNew[input.name] = input.options[0].value | ||||
|       } | ||||
|     }) | ||||
|     setInputState(inputStateNew) | ||||
|   }, []) | ||||
|  | ||||
|     // inputState par défaut | ||||
|     useEffect(() => { | ||||
|         const inputStateNew = { ...inputState }; | ||||
|         props.inputsArray.forEach((input) => { | ||||
|             if (input.type === "select" && input.options.length > 0) { | ||||
|                 inputStateNew[input.name] = input.options[0].value; | ||||
|             } | ||||
|         }); | ||||
|         setInputState(inputStateNew); | ||||
|     }, []); | ||||
|   const handleSubmit = (event) => { | ||||
|     setIsLoading(true) | ||||
|     event.preventDefault() | ||||
|     api.post(`/functions/${props.slug}`, inputState) | ||||
|       .then((response) => { | ||||
|         setMessage(response.data.resultHTML) | ||||
|         setIsLoading(false) | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         setMessage(error.response.data.message) | ||||
|         setIsLoading(false) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|     const handleSubmit = (event) => { | ||||
|         setIsLoading(true); | ||||
|         event.preventDefault(); | ||||
|         api.post(`/functions/${props.slug}`, inputState) | ||||
|             .then((response) => { | ||||
|                 setMessage(response.data.resultHTML); | ||||
|                 setIsLoading(false); | ||||
|             }) | ||||
|             .catch((error) => { | ||||
|                 setMessage(error.response.data.message); | ||||
|                 setIsLoading(false); | ||||
|             }); | ||||
|     } | ||||
|   const handleChange = (event) => { | ||||
|     const inputStateNew = { ...inputState } | ||||
|     inputStateNew[event.target.name] = event.target.value | ||||
|     setInputState(inputStateNew) | ||||
|   } | ||||
|  | ||||
|     const handleChange = (event) => { | ||||
|         const inputStateNew = { ...inputState }; | ||||
|         inputStateNew[event.target.name] = event.target.value; | ||||
|         setInputState(inputStateNew); | ||||
|     } | ||||
|  | ||||
|     if (props.inputsArray.length <= 0) { | ||||
|         return ( | ||||
|             <div className="FunctionComponent__slide text-center"> | ||||
|                 <p>La fonction n'est pas encore disponible.</p> | ||||
|             </div> | ||||
|         ); | ||||
|     } | ||||
|   if (props.inputsArray.length <= 0) { | ||||
|     return ( | ||||
|         <Fragment> | ||||
|             <form onSubmit={handleSubmit}> | ||||
|                 {props.inputsArray.map((input, index) => { | ||||
|                     switch (input.type) { | ||||
|                         case "text": | ||||
|                             return ( | ||||
|                                 <div key={index} className="form-group"> | ||||
|                                     <label className="form-label" htmlFor={input.name}>{input.label}</label> | ||||
|                                     <input onChange={handleChange} type="text" name={input.name} id={input.name} placeholder={input.placeholder} className="form-control" /> | ||||
|                                 </div> | ||||
|                             ); | ||||
|                         case "integer": | ||||
|                         case "float": | ||||
|                             return ( | ||||
|                                 <div key={index} className="form-group"> | ||||
|                                     <label className="form-label" htmlFor={input.name}>{input.label}</label> | ||||
|                                     <input onChange={handleChange} type="number" step={(input.type === "integer") ? "1" : "0.01"} name={input.name} id={input.name} placeholder={input.placeholder} className="form-control" /> | ||||
|                                 </div> | ||||
|                             ); | ||||
|                         case "calendar": | ||||
|                             // Permet au datepicker de prendre la hauteur | ||||
|                             if (typeof window != 'undefined') { | ||||
|                                 const newScript = document.createElement("script"); | ||||
|                                 newScript.src = "/js/extraHeightCSS.js"; | ||||
|                                 document.body.appendChild(newScript); | ||||
|                             } | ||||
|                             const DatePicker = dynamic( | ||||
|                                 () => import('react-datepicker'), | ||||
|                                 { ssr: false } | ||||
|                             ); | ||||
|                             return ( | ||||
|                                 <div key={index} className="form-group"> | ||||
|                                     <label className="form-label"  htmlFor={input.name}>{input.label}</label> | ||||
|                                     <br/> | ||||
|                                     <DatePicker | ||||
|                                         selected={(() => { | ||||
|                                             try { | ||||
|                                                 if (inputState[input.name] != undefined) { | ||||
|                                                     const dateArray = inputState[input.name].split('/'); | ||||
|                                                     const year      = dateArray[2]; | ||||
|                                                     const month     = dateArray[1]; | ||||
|                                                     const day       = dateArray[0]; | ||||
|                                                     return new Date(year, parseInt(month) - 1, parseInt(day) + 1); | ||||
|                                                 } | ||||
|                                                 throw "Not a valid date"; | ||||
|                                             } catch {  | ||||
|                                                 return new Date(); | ||||
|                                             } | ||||
|                                         })()} | ||||
|                                         locale="fr" | ||||
|                                         dateFormat="dd/MM/yyyy" | ||||
|                                         fixedHeight | ||||
|                                         placeholderText={input.placeholder} | ||||
|                                         onChange={(dateObject) => { | ||||
|                                             try { | ||||
|                                                 const formattedDate = date.format(dateObject, 'DD/MM/YYYY', true); | ||||
|                                                 handleChange({ | ||||
|                                                     target: { | ||||
|                                                         name: input.name, | ||||
|                                                         value: formattedDate | ||||
|                                                     } | ||||
|                                                 }); | ||||
|                                             } catch {} | ||||
|                                         }} | ||||
|                                     /> | ||||
|                                 </div> | ||||
|                             ); | ||||
|                         case "select": | ||||
|                             return ( | ||||
|                                 <div key={index} className="form-group"> | ||||
|                                     <label className="form-label" htmlFor={input.name}>{input.label}</label> | ||||
|                                     <select onChange={handleChange} name={input.name} id={input.name} value={inputState[input.name] || input.options[0]} className="form-control"> | ||||
|                                         {input.options.map((option, optionIndex) => { | ||||
|                                             return ( | ||||
|                                                 <option key={optionIndex} value={option.value}>{option.name}</option> | ||||
|                                             ); | ||||
|                                         })} | ||||
|                                     </select> | ||||
|                                 </div> | ||||
|                             ); | ||||
|                         default: | ||||
|                             return ( | ||||
|                                 <p>Erreur, l'input n'est pas valide...</p> | ||||
|                             ); | ||||
|                     } | ||||
|                 })} | ||||
|                  | ||||
|                 <div className="form-group text-center"> | ||||
|                     <button type="submit" className="btn btn-dark">Envoyer</button> | ||||
|       <div className='FunctionComponent__slide text-center'> | ||||
|         <p>La fonction n'est pas encore disponible.</p> | ||||
|       </div> | ||||
|     ) | ||||
|   } | ||||
|   return ( | ||||
|     <> | ||||
|       <form onSubmit={handleSubmit}> | ||||
|         {props.inputsArray.map((input, index) => { | ||||
|           switch (input.type) { | ||||
|             case 'text': | ||||
|               return ( | ||||
|                 <div key={index} className='form-group'> | ||||
|                   <label className='form-label' htmlFor={input.name}>{input.label}</label> | ||||
|                   <input onChange={handleChange} type='text' name={input.name} id={input.name} placeholder={input.placeholder} className='form-control' /> | ||||
|                 </div> | ||||
|             </form> | ||||
|             <div className="form-result text-center"> | ||||
|                 { | ||||
|                     (isLoading) ?  | ||||
|                         <Loader /> | ||||
|                     : | ||||
|                         htmlParser(message) | ||||
|                 } | ||||
|             </div> | ||||
|         </Fragment> | ||||
|     ); | ||||
|               ) | ||||
|             case 'integer': | ||||
|             case 'float': | ||||
|               return ( | ||||
|                 <div key={index} className='form-group'> | ||||
|                   <label className='form-label' htmlFor={input.name}>{input.label}</label> | ||||
|                   <input onChange={handleChange} type='number' step={(input.type === 'integer') ? '1' : '0.01'} name={input.name} id={input.name} placeholder={input.placeholder} className='form-control' /> | ||||
|                 </div> | ||||
|               ) | ||||
|             case 'calendar': | ||||
|               // Permet au datepicker de prendre la hauteur | ||||
|               if (typeof window !== 'undefined') { | ||||
|                 const newScript = document.createElement('script') | ||||
|                 newScript.src = '/js/extraHeightCSS.js' | ||||
|                 document.body.appendChild(newScript) | ||||
|               } | ||||
|               // eslint-disable-next-line | ||||
|               const DatePicker = dynamic( | ||||
|                 () => import('react-datepicker'), | ||||
|                 { ssr: false } | ||||
|               ) | ||||
|               return ( | ||||
|                 <div key={index} className='form-group'> | ||||
|                   <label className='form-label' htmlFor={input.name}>{input.label}</label> | ||||
|                   <br /> | ||||
|                   <DatePicker | ||||
|                     selected={(() => { | ||||
|                       try { | ||||
|                         if (inputState[input.name] != null) { | ||||
|                           const dateArray = inputState[input.name].split('/') | ||||
|                           const year = dateArray[2] | ||||
|                           const month = dateArray[1] | ||||
|                           const day = dateArray[0] | ||||
|                           return new Date(year, parseInt(month) - 1, parseInt(day) + 1) | ||||
|                         } | ||||
|                         throw new Error('Not a valid date') | ||||
|                       } catch { | ||||
|                         return new Date() | ||||
|                       } | ||||
|                     })()} | ||||
|                     locale='fr' | ||||
|                     dateFormat='dd/MM/yyyy' | ||||
|                     fixedHeight | ||||
|                     placeholderText={input.placeholder} | ||||
|                     onChange={(dateObject) => { | ||||
|                       try { | ||||
|                         const formattedDate = date.format(dateObject, 'DD/MM/YYYY', true) | ||||
|                         handleChange({ | ||||
|                           target: { | ||||
|                             name: input.name, | ||||
|                             value: formattedDate | ||||
|                           } | ||||
|                         }) | ||||
|                       } catch {} | ||||
|                     }} | ||||
|                   /> | ||||
|                 </div> | ||||
|               ) | ||||
|             case 'select': | ||||
|               return ( | ||||
|                 <div key={index} className='form-group'> | ||||
|                   <label className='form-label' htmlFor={input.name}>{input.label}</label> | ||||
|                   <select onChange={handleChange} name={input.name} id={input.name} value={inputState[input.name] || input.options[0]} className='form-control'> | ||||
|                     {input.options.map((option, optionIndex) => { | ||||
|                       return ( | ||||
|                         <option key={optionIndex} value={option.value}>{option.name}</option> | ||||
|                       ) | ||||
|                     })} | ||||
|                   </select> | ||||
|                 </div> | ||||
|               ) | ||||
|             default: | ||||
|               return ( | ||||
|                 <p>Erreur, l'input n'est pas valide...</p> | ||||
|               ) | ||||
|           } | ||||
|         })} | ||||
|  | ||||
|         <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 FunctionForm; | ||||
| export default FunctionForm | ||||
|   | ||||
| @@ -1,24 +1,23 @@ | ||||
| import { Fragment, useState } from 'react'; | ||||
| import { API_URL } from '../../utils/config/config'; | ||||
| import HeadTag from '../HeadTag'; | ||||
| import FunctionTabsTop from './FunctionTabsTop'; | ||||
| import FunctionComponentTop from './FunctionComponentTop'; | ||||
| import { useState } from 'react' | ||||
| import { API_URL } from '../../utils/config/config' | ||||
| import HeadTag from '../HeadTag' | ||||
| import FunctionTabsTop from './FunctionTabsTop' | ||||
| import FunctionComponentTop from './FunctionComponentTop' | ||||
|  | ||||
| const FunctionPage = (props) => { | ||||
|   const [slideIndex, setSlideIndex] = useState(0) | ||||
|  | ||||
|     const [slideIndex, setSlideIndex] = useState(0); | ||||
|   return ( | ||||
|     <> | ||||
|       <HeadTag title={props.title} description={props.description} image={API_URL + props.image} /> | ||||
|  | ||||
|     return ( | ||||
|         <Fragment> | ||||
|             <HeadTag title={props.title} description={props.description} image={API_URL + props.image} /> | ||||
|  | ||||
|             <div className="container-fluid"> | ||||
|                 <FunctionTabsTop slideIndex={slideIndex} setSlideIndex={setSlideIndex} tabNames={props.tabNames} /> | ||||
|                 <FunctionComponentTop { ...props } /> | ||||
|                 <props.FunctionTabManager { ...props } slideIndex={slideIndex} setSlideIndex={setSlideIndex} /> | ||||
|             </div> | ||||
|         </Fragment> | ||||
|     ); | ||||
|       <div className='container-fluid'> | ||||
|         <FunctionTabsTop slideIndex={slideIndex} setSlideIndex={setSlideIndex} tabNames={props.tabNames} /> | ||||
|         <FunctionComponentTop {...props} /> | ||||
|         <props.FunctionTabManager {...props} slideIndex={slideIndex} setSlideIndex={setSlideIndex} /> | ||||
|       </div> | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default FunctionPage; | ||||
| export default FunctionPage | ||||
|   | ||||
| @@ -1,14 +1,14 @@ | ||||
| import SwipeableViews from 'react-swipeable-views'; | ||||
| import './FunctionTabs.css'; | ||||
| import SwipeableViews from 'react-swipeable-views' | ||||
| import './FunctionTabs.css' | ||||
|  | ||||
| const FunctionTabs = (props) => { | ||||
|     return ( | ||||
|         <div className="container-fluid"> | ||||
|             <SwipeableViews onChangeIndex={(index) => props.setSlideIndex(index)} index={props.slideIndex}> | ||||
|                 {props.children} | ||||
|             </SwipeableViews> | ||||
|         </div> | ||||
|     ); | ||||
|   return ( | ||||
|     <div className='container-fluid'> | ||||
|       <SwipeableViews onChangeIndex={(index) => props.setSlideIndex(index)} index={props.slideIndex}> | ||||
|         {props.children} | ||||
|       </SwipeableViews> | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default FunctionTabs; | ||||
| export default FunctionTabs | ||||
|   | ||||
| @@ -1,24 +1,24 @@ | ||||
| const FunctionTabsTop = (props) => { | ||||
|     return ( | ||||
|         <div className="container"> | ||||
|             <div className="row justify-content-center"> | ||||
|                 <ul className="FunctionTabs__nav"> | ||||
|                     {props.tabNames.map((tabName, index) => { | ||||
|                         return ( | ||||
|                             <li key={index} className="FunctionTabs__nav-item"> | ||||
|                                 <a  | ||||
|                                     className={`FunctionTabs__nav-link ${(props.slideIndex === index) ? "FunctionTabs__nav-link-active" : ""}`}  | ||||
|                                     onClick={() => props.setSlideIndex(index)} | ||||
|                                 > | ||||
|                                     {tabName} | ||||
|                                 </a> | ||||
|                             </li> | ||||
|                         ); | ||||
|                     })} | ||||
|                 </ul> | ||||
|             </div> | ||||
|         </div> | ||||
|     ); | ||||
|   return ( | ||||
|     <div className='container'> | ||||
|       <div className='row justify-content-center'> | ||||
|         <ul className='FunctionTabs__nav'> | ||||
|           {props.tabNames.map((tabName, index) => { | ||||
|             return ( | ||||
|               <li key={index} className='FunctionTabs__nav-item'> | ||||
|                 <a | ||||
|                   className={`FunctionTabs__nav-link ${(props.slideIndex === index) ? 'FunctionTabs__nav-link-active' : ''}`} | ||||
|                   onClick={() => props.setSlideIndex(index)} | ||||
|                 > | ||||
|                   {tabName} | ||||
|                 </a> | ||||
|               </li> | ||||
|             ) | ||||
|           })} | ||||
|         </ul> | ||||
|       </div> | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default FunctionTabsTop; | ||||
| export default FunctionTabsTop | ||||
|   | ||||
| @@ -1,104 +1,103 @@ | ||||
| import { useState, useEffect, useRef, useCallback } from 'react'; | ||||
| import { useRouter } from 'next/router'; | ||||
| import FunctionCard from '../FunctionCard/FunctionCard'; | ||||
| import Loader from '../Loader'; | ||||
| import api from '../../utils/api'; | ||||
| import useAPI from '../../hooks/useAPI'; | ||||
| import './FunctionsList.css'; | ||||
| import { useState, useEffect, useRef, useCallback } from 'react' | ||||
| import { useRouter } from 'next/router' | ||||
| import FunctionCard from '../FunctionCard/FunctionCard' | ||||
| import Loader from '../Loader' | ||||
| import api from '../../utils/api' | ||||
| import useAPI from '../../hooks/useAPI' | ||||
| import './FunctionsList.css' | ||||
|  | ||||
| let pageFunctions = 1; | ||||
| let pageFunctions = 1 | ||||
| const FunctionsList = (props) => { | ||||
|   const { categoryId } = useRouter().query | ||||
|  | ||||
|     const { categoryId } = useRouter().query; | ||||
|   // State de recherche et de catégories | ||||
|   const [, categories] = useAPI('/categories') | ||||
|   const [inputSearch, setInputSearch] = useState({ search: '', selectedCategory: categoryId || '0' }) | ||||
|  | ||||
|     // State de recherche et de catégories | ||||
|     const [, categories]                = useAPI('/categories'); | ||||
|     const [inputSearch, setInputSearch] = useState({ search: "", selectedCategory: categoryId || "0" }); | ||||
|   // State pour afficher les fonctions | ||||
|   const [functionsData, setFunctionsData] = useState({ hasMore: true, rows: [] }) | ||||
|   const [isLoadingFunctions, setLoadingFunctions] = useState(true) | ||||
|  | ||||
|     // State pour afficher les fonctions | ||||
|     const [functionsData, setFunctionsData]         = useState({ hasMore: true, rows: [] }); | ||||
|     const [isLoadingFunctions, setLoadingFunctions] = useState(true); | ||||
|  | ||||
|     // Récupère la catégorie avec la query categoryId | ||||
|     useEffect(() => { | ||||
|         if (categoryId) { | ||||
|             handleChange({ target: { name: "selectedCategory", value: categoryId } }); | ||||
|         } | ||||
|     }, [categoryId]); | ||||
|  | ||||
|     // Récupère les fonctions si la catégorie/recherche change | ||||
|     useEffect(() => { | ||||
|         pageFunctions = 1; | ||||
|         getFunctionsData().then((data) => setFunctionsData(data)); | ||||
|     }, [inputSearch]); | ||||
|  | ||||
|     // Permet la pagination au scroll | ||||
|     const observer = useRef(); | ||||
|     const lastFunctionCardRef = useCallback((node) => { | ||||
|         if (isLoadingFunctions) return; | ||||
|         if (observer.current) observer.current.disconnect(); | ||||
|         observer.current = new IntersectionObserver((entries) => { | ||||
|             if (entries[0].isIntersecting && functionsData.hasMore) { | ||||
|                 pageFunctions += 1; | ||||
|                 getFunctionsData().then((data) => { | ||||
|                     setFunctionsData((oldData) => { | ||||
|                         return {  | ||||
|                             hasMore: data.hasMore,  | ||||
|                             rows: [...oldData.rows, ...data.rows]  | ||||
|                         } | ||||
|                     }); | ||||
|                 });  | ||||
|             } | ||||
|         }, { threshold: 1 }); | ||||
|         if (node) observer.current.observe(node); | ||||
|     }, [isLoadingFunctions, functionsData.hasMore]); | ||||
|  | ||||
|     const getFunctionsData = async () => { | ||||
|         setLoadingFunctions(true); | ||||
|         const URL = `${(props.isAdmin) ? "/admin/functions" : "/functions"}?page=${pageFunctions}&limit=10&categoryId=${inputSearch.selectedCategory}&search=${inputSearch.search}`; | ||||
|         const { data } = await api.get(URL, { | ||||
|             headers: { | ||||
|                 ...(props.isAdmin && props.token != undefined) && { 'Authorization': props.token } | ||||
|             } | ||||
|         }); | ||||
|         setLoadingFunctions(false); | ||||
|         return data; | ||||
|   // Récupère la catégorie avec la query categoryId | ||||
|   useEffect(() => { | ||||
|     if (categoryId) { | ||||
|       handleChange({ target: { name: 'selectedCategory', value: categoryId } }) | ||||
|     } | ||||
|   }, [categoryId]) | ||||
|  | ||||
|     const handleChange = (event) => { | ||||
|         const inputSearchNew = { ...inputSearch }; | ||||
|         inputSearchNew[event.target.name] = event.target.value; | ||||
|         setInputSearch(inputSearchNew); | ||||
|     } | ||||
|   // Récupère les fonctions si la catégorie/recherche change | ||||
|   useEffect(() => { | ||||
|     pageFunctions = 1 | ||||
|     getFunctionsData().then((data) => setFunctionsData(data)) | ||||
|   }, [inputSearch]) | ||||
|  | ||||
|     return ( | ||||
|         <div className="container text-center"> | ||||
|             <div className="row justify-content-center"> | ||||
|                 {props.children} | ||||
|             </div> | ||||
|   // Permet la pagination au scroll | ||||
|   const observer = useRef() | ||||
|   const lastFunctionCardRef = useCallback((node) => { | ||||
|     if (isLoadingFunctions) return | ||||
|     if (observer.current) observer.current.disconnect() | ||||
|     observer.current = new window.IntersectionObserver((entries) => { | ||||
|       if (entries[0].isIntersecting && functionsData.hasMore) { | ||||
|         pageFunctions += 1 | ||||
|         getFunctionsData().then((data) => { | ||||
|           setFunctionsData((oldData) => { | ||||
|             return { | ||||
|               hasMore: data.hasMore, | ||||
|               rows: [...oldData.rows, ...data.rows] | ||||
|             } | ||||
|           }) | ||||
|         }) | ||||
|       } | ||||
|     }, { threshold: 1 }) | ||||
|     if (node) observer.current.observe(node) | ||||
|   }, [isLoadingFunctions, functionsData.hasMore]) | ||||
|  | ||||
|             <div className="Functions__search-container row justify-content-center"> | ||||
|                 <select name="selectedCategory" value={inputSearch.selectedCategory} onChange={handleChange} className="Functions__select Functions__form-control"> | ||||
|                     <option value="0">Toutes catégories</option> | ||||
|                     {categories.map((category) => ( | ||||
|                         <option key={category.id} value={category.id} className="Functions__select-option" style={{ backgroundColor: category.color }}>{category.name}</option> | ||||
|                     ))} | ||||
|                 </select> | ||||
|                 <input value={inputSearch.search} onChange={handleChange} type="search" className="Functions__form-control Functions__search-input" name="search" id="search" placeholder="🔎 Rechercher..." /> | ||||
|             </div> | ||||
|   const getFunctionsData = async () => { | ||||
|     setLoadingFunctions(true) | ||||
|     const URL = `${(props.isAdmin) ? '/admin/functions' : '/functions'}?page=${pageFunctions}&limit=10&categoryId=${inputSearch.selectedCategory}&search=${inputSearch.search}` | ||||
|     const { data } = await api.get(URL, { | ||||
|       headers: { | ||||
|         ...(props.isAdmin && props.token != null) && { Authorization: props.token } | ||||
|       } | ||||
|     }) | ||||
|     setLoadingFunctions(false) | ||||
|     return data | ||||
|   } | ||||
|  | ||||
|             <div className="row justify-content-center"> | ||||
|                 {functionsData.rows.map((currentFunction, index) => { | ||||
|                     // Si c'est le dernier élément | ||||
|                     if (functionsData.rows.length === index + 1) { | ||||
|                         return <FunctionCard isAdmin={props.isAdmin} key={currentFunction.id} ref={lastFunctionCardRef} { ...currentFunction } />; | ||||
|                     } | ||||
|                     return <FunctionCard isAdmin={props.isAdmin} key={currentFunction.id} { ...currentFunction } />; | ||||
|                 })} | ||||
|             </div> | ||||
|             {isLoadingFunctions && <Loader />} | ||||
|         </div> | ||||
|     ); | ||||
|   const handleChange = (event) => { | ||||
|     const inputSearchNew = { ...inputSearch } | ||||
|     inputSearchNew[event.target.name] = event.target.value | ||||
|     setInputSearch(inputSearchNew) | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <div className='container text-center'> | ||||
|       <div className='row justify-content-center'> | ||||
|         {props.children} | ||||
|       </div> | ||||
|  | ||||
|       <div className='Functions__search-container row justify-content-center'> | ||||
|         <select name='selectedCategory' value={inputSearch.selectedCategory} onChange={handleChange} className='Functions__select Functions__form-control'> | ||||
|           <option value='0'>Toutes catégories</option> | ||||
|           {categories.map((category) => ( | ||||
|             <option key={category.id} value={category.id} className='Functions__select-option' style={{ backgroundColor: category.color }}>{category.name}</option> | ||||
|           ))} | ||||
|         </select> | ||||
|         <input value={inputSearch.search} onChange={handleChange} type='search' className='Functions__form-control Functions__search-input' name='search' id='search' placeholder='🔎 Rechercher...' /> | ||||
|       </div> | ||||
|  | ||||
|       <div className='row justify-content-center'> | ||||
|         {functionsData.rows.map((currentFunction, index) => { | ||||
|           // Si c'est le dernier élément | ||||
|           if (functionsData.rows.length === index + 1) { | ||||
|             return <FunctionCard isAdmin={props.isAdmin} key={currentFunction.id} ref={lastFunctionCardRef} {...currentFunction} /> | ||||
|           } | ||||
|           return <FunctionCard isAdmin={props.isAdmin} key={currentFunction.id} {...currentFunction} /> | ||||
|         })} | ||||
|       </div> | ||||
|       {isLoadingFunctions && <Loader />} | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default FunctionsList; | ||||
| export default FunctionsList | ||||
|   | ||||
| @@ -1,43 +1,43 @@ | ||||
| import Head from 'next/head'; | ||||
| import Head from 'next/head' | ||||
|  | ||||
| const HeadTag = ({ title, image, description }) => ( | ||||
|     <Head> | ||||
|         <title>{title || ""}</title> | ||||
|         <link rel="icon" type="image/png" href={image} /> | ||||
|   <Head> | ||||
|     <title>{title || ''}</title> | ||||
|     <link rel='icon' type='image/png' href={image} /> | ||||
|  | ||||
|         {/* Meta Tag */} | ||||
|         <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|         <meta name="description" content={description} />  | ||||
|         <link rel="canonical" href="function.divlo.fr"/>  | ||||
|         <meta name="Language" content="fr" />  | ||||
|         <meta name="theme-color" content="#ffd800" /> | ||||
|     {/* Meta Tag */} | ||||
|     <meta name='viewport' content='width=device-width, initial-scale=1' /> | ||||
|     <meta name='description' content={description} /> | ||||
|     <link rel='canonical' href='function.divlo.fr' /> | ||||
|     <meta name='Language' content='fr' /> | ||||
|     <meta name='theme-color' content='#ffd800' /> | ||||
|  | ||||
|         {/* Open Graph Metadata */} | ||||
|         <meta property="og:title" content={title} /> | ||||
|         <meta property="og:type" content="website" />  | ||||
|         <meta property="og:url" content="https://function.divlo.fr/" />  | ||||
|         <meta property="og:image" content={image} />  | ||||
|         <meta property="og:description" content={description} />  | ||||
|         <meta property="og:locale" content="fr_FR" /> | ||||
|         <meta property="og:site_name" content="FunctionProject" />  | ||||
|     {/* Open Graph Metadata */} | ||||
|     <meta property='og:title' content={title} /> | ||||
|     <meta property='og:type' content='website' /> | ||||
|     <meta property='og:url' content='https://function.divlo.fr/' /> | ||||
|     <meta property='og:image' content={image} /> | ||||
|     <meta property='og:description' content={description} /> | ||||
|     <meta property='og:locale' content='fr_FR' /> | ||||
|     <meta property='og:site_name' content='FunctionProject' /> | ||||
|  | ||||
|         {/* Twitter card Metadata */} | ||||
|         <meta name="twitter:card" content="summary" /> | ||||
|         <meta name="twitter:description" content={description} /> | ||||
|         <meta name="twitter:title" content={title} /> | ||||
|         <meta name="twitter:site" content="@Divlo_FR" /> | ||||
|         <meta name="twitter:image:src" content={image} /> | ||||
|         <meta name="twitter:creator" content="@Divlo_FR" /> | ||||
|     {/* Twitter card Metadata */} | ||||
|     <meta name='twitter:card' content='summary' /> | ||||
|     <meta name='twitter:description' content={description} /> | ||||
|     <meta name='twitter:title' content={title} /> | ||||
|     <meta name='twitter:site' content='@Divlo_FR' /> | ||||
|     <meta name='twitter:image:src' content={image} /> | ||||
|     <meta name='twitter:creator' content='@Divlo_FR' /> | ||||
|  | ||||
|         {/* Preloader script */} | ||||
|         <script src="/js/preloader.js"></script> | ||||
|     </Head> | ||||
| ); | ||||
|     {/* Preloader script */} | ||||
|     <script src='/js/preloader.js' /> | ||||
|   </Head> | ||||
| ) | ||||
|  | ||||
| HeadTag.defaultProps = { | ||||
|     title: "FunctionProject",  | ||||
|     description: "Apprenez la programmation grâce à l'apprentissage par projet alias fonction.", | ||||
|     image: "/images/FunctionProject_icon_small.png" | ||||
|   title: 'FunctionProject', | ||||
|   description: "Apprenez la programmation grâce à l'apprentissage par projet alias fonction.", | ||||
|   image: '/images/FunctionProject_icon_small.png' | ||||
| } | ||||
|  | ||||
| export default HeadTag; | ||||
| export default HeadTag | ||||
|   | ||||
| @@ -1,71 +1,73 @@ | ||||
| import { Fragment, useState, useContext } from 'react'; | ||||
| import { UserContext } from '../../contexts/UserContext'; | ||||
| import Link from 'next/link'; | ||||
| import { useRouter } from 'next/router'; | ||||
| import NavigationLink from './NavigationLink'; | ||||
| import './Header.css'; | ||||
| import { useState, useContext } from 'react' | ||||
| import { UserContext } from '../../contexts/UserContext' | ||||
| import Link from 'next/link' | ||||
| import { useRouter } from 'next/router' | ||||
| import NavigationLink from './NavigationLink' | ||||
| import './Header.css' | ||||
|  | ||||
| export default function Header() { | ||||
| export default function Header () { | ||||
|   const { isAuth, logoutUser, user } = useContext(UserContext) | ||||
|   const [isActive, setIsActive] = useState(false) | ||||
|   const { pathname } = useRouter() | ||||
|  | ||||
|     const { isAuth, logoutUser, user } = useContext(UserContext); | ||||
|     const [isActive, setIsActive]      = useState(false); | ||||
|     const { pathname }                 = useRouter(); | ||||
|   const toggleNavbar = () => { | ||||
|     setIsActive(!isActive) | ||||
|   } | ||||
|  | ||||
|     const toggleNavbar = () => { | ||||
|         setIsActive(!isActive); | ||||
|     } | ||||
|   return ( | ||||
|     <header className='Header'> | ||||
|       <div className='container'> | ||||
|  | ||||
|     return ( | ||||
|         <header className="Header"> | ||||
|             <div className="container"> | ||||
|         {/* Brand */} | ||||
|         <Link href='/'> | ||||
|           <a className='Header__brand-link'> | ||||
|             <img id='brand-link__logo' src='/images/FunctionProject_brand-logo.png' alt='FunctionProject' /> | ||||
|             <img id='brand-link__logo-small-screen' src='/images/FunctionProject_icon_small.png' alt='FunctionProject' /> | ||||
|           </a> | ||||
|         </Link> | ||||
|  | ||||
|                 {/* Brand */} | ||||
|                 <Link href={"/"}> | ||||
|                     <a className="Header__brand-link"> | ||||
|                         <img id="brand-link__logo" src="/images/FunctionProject_brand-logo.png" alt="FunctionProject" /> | ||||
|                         <img id="brand-link__logo-small-screen" src="/images/FunctionProject_icon_small.png" alt="FunctionProject" /> | ||||
|                     </a> | ||||
|                 </Link> | ||||
|         {/* Hamburger icon on Mobile */} | ||||
|         <div onClick={toggleNavbar} className={`Header__hamburger ${(isActive) ? 'Header__hamburger-active' : ''}`}> | ||||
|           <span /> | ||||
|         </div> | ||||
|  | ||||
|                 {/* Hamburger icon on Mobile */} | ||||
|                 <div onClick={toggleNavbar} className={`Header__hamburger ${(isActive) ? "Header__hamburger-active" : ""}`}> | ||||
|                     <span></span> | ||||
|                 </div> | ||||
|         {/* Navigation */} | ||||
|         <nav className='Header__navbar'> | ||||
|           <ul className={`navbar__list ${(isActive) ? 'navbar__list-active' : ''}`}> | ||||
|             <NavigationLink name='Accueil' path='/' /> | ||||
|             <NavigationLink name='Fonctions' path='/functions' /> | ||||
|             <NavigationLink name='Utilisateurs' path='/users' /> | ||||
|             { | ||||
|               (!isAuth) | ||||
|                 ? ( | ||||
|                   <> | ||||
|                     <NavigationLink name="S'inscrire" path='/users/register' /> | ||||
|                     <NavigationLink name='Connexion' path='/users/login' /> | ||||
|                   </> | ||||
|                 ) | ||||
|                 : ( | ||||
|                   <> | ||||
|                     <li className='navbar-item'> | ||||
|                       <Link href='/users/[name]' as={`/users/${user.name}`}> | ||||
|                         <a className={`navbar-link ${pathname === '/users/[name]' ? 'navbar-link-active' : null}`}>Mon Profil</a> | ||||
|                       </Link> | ||||
|                     </li> | ||||
|                     <li className='navbar-item'> | ||||
|                       <Link href='/'> | ||||
|                         <a onClick={logoutUser} className='navbar-link'>Se déconnecter</a> | ||||
|                       </Link> | ||||
|                     </li> | ||||
|                   </> | ||||
|                 ) | ||||
|             } | ||||
|             { | ||||
|               (isAuth && user.isAdmin) && | ||||
|                 <NavigationLink name='Admin' path='/admin' /> | ||||
|             } | ||||
|           </ul> | ||||
|         </nav> | ||||
|  | ||||
|                 {/* Navigation */} | ||||
|                 <nav className="Header__navbar"> | ||||
|                     <ul className={`navbar__list ${(isActive) ? "navbar__list-active" : ""}`}> | ||||
|                         <NavigationLink name="Accueil" path="/" /> | ||||
|                         <NavigationLink name="Fonctions" path="/functions" /> | ||||
|                         <NavigationLink name="Utilisateurs" path="/users" /> | ||||
|                         { | ||||
|                             (!isAuth) ?  | ||||
|                                 <Fragment> | ||||
|                                     <NavigationLink name="S'inscrire" path="/users/register" /> | ||||
|                                     <NavigationLink name="Connexion" path="/users/login" /> | ||||
|                                 </Fragment> | ||||
|                             : | ||||
|                                 <Fragment> | ||||
|                                     <li className="navbar-item"> | ||||
|                                         <Link href={"/users/[name]"} as={`/users/${user.name}`}> | ||||
|                                             <a className={`navbar-link ${pathname === "/users/[name]" ? "navbar-link-active" : null}`}>Mon Profil</a> | ||||
|                                         </Link> | ||||
|                                     </li> | ||||
|                                     <li className="navbar-item"> | ||||
|                                         <Link href={"/"}> | ||||
|                                             <a onClick={logoutUser} className="navbar-link">Se déconnecter</a> | ||||
|                                         </Link> | ||||
|                                     </li> | ||||
|                                 </Fragment> | ||||
|                         } | ||||
|                         { | ||||
|                             (isAuth && user.isAdmin) && | ||||
|                                 <NavigationLink name="Admin" path="/admin" /> | ||||
|                         } | ||||
|                     </ul> | ||||
|                 </nav> | ||||
|  | ||||
|             </div> | ||||
|         </header> | ||||
|     ); | ||||
| } | ||||
|       </div> | ||||
|     </header> | ||||
|   ) | ||||
| } | ||||
|   | ||||
| @@ -1,18 +1,17 @@ | ||||
| import Link from 'next/link'; | ||||
| import { useRouter } from 'next/router'; | ||||
| import './Header.css'; | ||||
| import Link from 'next/link' | ||||
| import { useRouter } from 'next/router' | ||||
| import './Header.css' | ||||
|  | ||||
| export default function NavigationLink(props) { | ||||
| export default function NavigationLink (props) { | ||||
|   const { pathname } = useRouter() | ||||
|  | ||||
|     const { pathname } = useRouter(); | ||||
|  | ||||
|     return ( | ||||
|         <li className="navbar-item"> | ||||
|             <Link href={props.path}> | ||||
|                 <a className={`navbar-link ${pathname === props.path ? "navbar-link-active" : null}`}> | ||||
|                     {props.name} | ||||
|                 </a> | ||||
|             </Link> | ||||
|         </li> | ||||
|     ); | ||||
| } | ||||
|   return ( | ||||
|     <li className='navbar-item'> | ||||
|       <Link href={props.path}> | ||||
|         <a className={`navbar-link ${pathname === props.path ? 'navbar-link-active' : null}`}> | ||||
|           {props.name} | ||||
|         </a> | ||||
|       </Link> | ||||
|     </li> | ||||
|   ) | ||||
| } | ||||
|   | ||||
| @@ -1,15 +1,15 @@ | ||||
| const Loader = ({ width, height, speed }) => ( | ||||
|     <svg width={width} height={height} viewBox="0 0 100 100"> | ||||
|         <g transform="translate(50 50) rotate(0) scale(1 1) translate(-50 -50)"> | ||||
|             <image style={{transformOrigin: "50% 50%", animation: `${speed} linear 0s infinite normal forwards running Loader__spin`}} x="0" y="0" width="100" height="100" href="/images/FunctionProject_icon.png"></image> | ||||
|         </g> | ||||
|     </svg> | ||||
| ); | ||||
|   <svg width={width} height={height} viewBox='0 0 100 100'> | ||||
|     <g transform='translate(50 50) rotate(0) scale(1 1) translate(-50 -50)'> | ||||
|       <image style={{ transformOrigin: '50% 50%', animation: `${speed} linear 0s infinite normal forwards running Loader__spin` }} x='0' y='0' width='100' height='100' href='/images/FunctionProject_icon.png' /> | ||||
|     </g> | ||||
|   </svg> | ||||
| ) | ||||
|  | ||||
| Loader.defaultProps = { | ||||
|     width: "100px", | ||||
|     height: "100px", | ||||
|     speed: ".9s" | ||||
|   width: '100px', | ||||
|   height: '100px', | ||||
|   speed: '.9s' | ||||
| } | ||||
|  | ||||
| export default Loader; | ||||
| export default Loader | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| const Modal = (props) => ( | ||||
|     <div className="Modal container-fluid"> | ||||
|         <div className="Modal__content"> | ||||
|             {props.children} | ||||
|         </div> | ||||
|   <div className='Modal container-fluid'> | ||||
|     <div className='Modal__content'> | ||||
|       {props.children} | ||||
|     </div> | ||||
| ); | ||||
|   </div> | ||||
| ) | ||||
|  | ||||
| export default Modal; | ||||
| export default Modal | ||||
|   | ||||
| @@ -1,68 +1,63 @@ | ||||
| import { createContext, useState, useEffect } from 'react'; | ||||
| import Cookies from "universal-cookie"; | ||||
| import api from '../utils/api'; | ||||
| import { createContext, useState, useEffect } from 'react' | ||||
| import Cookies from 'universal-cookie' | ||||
| import api from '../utils/api' | ||||
|  | ||||
| const cookies = new Cookies(); | ||||
| const cookies = new Cookies() | ||||
|  | ||||
| export const UserContext = createContext(); | ||||
| export const UserContext = createContext() | ||||
|  | ||||
| function UserContextProvider(props) { | ||||
| function UserContextProvider (props) { | ||||
|   const [user, setUser] = useState(null) | ||||
|   const [isAuth, setIsAuth] = useState(false) | ||||
|   const [loginLoading, setLoginLoading] = useState(false) | ||||
|   const [messageLogin, setMessageLogin] = useState('') | ||||
|  | ||||
|     const [user, setUser]                 = useState(null); | ||||
|     const [isAuth, setIsAuth]             = useState(false); | ||||
|     const [loginLoading, setLoginLoading] = useState(false); | ||||
|     const [messageLogin, setMessageLogin] = useState(""); | ||||
|  | ||||
|     useEffect(() => { | ||||
|         const newUser = cookies.get('user'); | ||||
|         if (newUser != undefined) { | ||||
|             setIsAuth(true); | ||||
|             setUser(newUser); | ||||
|         } | ||||
|     }, []); | ||||
|  | ||||
|     useEffect(() => { | ||||
|         if (isAuth) { | ||||
|             setMessageLogin('<p class="form-error"><b>Erreur:</b> Vous devez être déconnecter avant de vous connecter.</p>'); | ||||
|         } else { | ||||
|             setMessageLogin(""); | ||||
|         } | ||||
|     }, [isAuth]); | ||||
|  | ||||
|     const logoutUser = () => { | ||||
|         cookies.remove('user', { path: '/' }); | ||||
|         setUser(null); | ||||
|         setIsAuth(false); | ||||
|     }  | ||||
|   | ||||
|     const loginUser = ({ email, password }) => { | ||||
|         setLoginLoading(true); | ||||
|         return new Promise(async (next) => { | ||||
|             try { | ||||
|                 const response = await api.post('/users/login', { email, password }); | ||||
|                 const newUser = response.data; | ||||
|                 cookies.remove('user', { path: '/' }); | ||||
|                 cookies.set('user', newUser, { path: '/', maxAge: newUser.expiresIn }); | ||||
|                 setUser(newUser); | ||||
|                 setIsAuth(true); | ||||
|                 setMessageLogin('<p class="form-success"><b>Succès:</b> Connexion réussi!</p>'); | ||||
|                 setLoginLoading(false); | ||||
|                 return next(); | ||||
|             } catch (error) { | ||||
|                 setMessageLogin(`<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>`); | ||||
|                 setLoginLoading(false); | ||||
|                 setIsAuth(false); | ||||
|                 setUser(null); | ||||
|                 return next(); | ||||
|             } | ||||
|         }); | ||||
|   useEffect(() => { | ||||
|     const newUser = cookies.get('user') | ||||
|     if (newUser != null) { | ||||
|       setIsAuth(true) | ||||
|       setUser(newUser) | ||||
|     } | ||||
|   }, []) | ||||
|  | ||||
|     return ( | ||||
|         <UserContext.Provider value={{ user, loginUser, logoutUser, loginLoading, messageLogin, isAuth, setMessageLogin }}> | ||||
|             {props.children} | ||||
|         </UserContext.Provider> | ||||
|     ); | ||||
|   useEffect(() => { | ||||
|     if (isAuth) { | ||||
|       setMessageLogin('<p class="form-error"><b>Erreur:</b> Vous devez être déconnecter avant de vous connecter.</p>') | ||||
|     } else { | ||||
|       setMessageLogin('') | ||||
|     } | ||||
|   }, [isAuth]) | ||||
|  | ||||
|   const logoutUser = () => { | ||||
|     cookies.remove('user', { path: '/' }) | ||||
|     setUser(null) | ||||
|     setIsAuth(false) | ||||
|   } | ||||
|  | ||||
|   const loginUser = async ({ email, password }) => { | ||||
|     setLoginLoading(true) | ||||
|     try { | ||||
|       const response = await api.post('/users/login', { email, password }) | ||||
|       const newUser = response.data | ||||
|       cookies.remove('user', { path: '/' }) | ||||
|       cookies.set('user', newUser, { path: '/', maxAge: newUser.expiresIn }) | ||||
|       setUser(newUser) | ||||
|       setIsAuth(true) | ||||
|       setMessageLogin('<p class="form-success"><b>Succès:</b> Connexion réussi!</p>') | ||||
|       setLoginLoading(false) | ||||
|     } catch (error) { | ||||
|       setMessageLogin(`<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>`) | ||||
|       setLoginLoading(false) | ||||
|       setIsAuth(false) | ||||
|       setUser(null) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <UserContext.Provider value={{ user, loginUser, logoutUser, loginLoading, messageLogin, isAuth, setMessageLogin }}> | ||||
|       {props.children} | ||||
|     </UserContext.Provider> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default UserContextProvider; | ||||
| export default UserContextProvider | ||||
|   | ||||
| @@ -1,19 +1,17 @@ | ||||
| import { useContext } from 'react'; | ||||
| import { UserContext } from '../contexts/UserContext'; | ||||
| import redirect from '../utils/redirect'; | ||||
| import { useContext } from 'react' | ||||
| import { UserContext } from '../contexts/UserContext' | ||||
| import redirect from '../utils/redirect' | ||||
|  | ||||
| const withoutAuth = (WrappedComponent) => { | ||||
|      | ||||
|     const Component = (props) => { | ||||
|          | ||||
|         const { isAuth, user } = useContext(UserContext); | ||||
|          | ||||
|         if (isAuth) return redirect({}, `/users/${user.name}`); | ||||
|   const Component = (props) => { | ||||
|     const { isAuth, user } = useContext(UserContext) | ||||
|  | ||||
|         return <WrappedComponent { ...props } />; | ||||
|     } | ||||
|     if (isAuth) return redirect({}, `/users/${user.name}`) | ||||
|  | ||||
|     return Component; | ||||
| }        | ||||
|     return <WrappedComponent {...props} /> | ||||
|   } | ||||
|  | ||||
| export default withoutAuth; | ||||
|   return Component | ||||
| } | ||||
|  | ||||
| export default withoutAuth | ||||
|   | ||||
| @@ -1,31 +1,30 @@ | ||||
| import { useEffect, useState } from 'react'; | ||||
| import api from '../utils/api'; | ||||
| import { useEffect, useState } from 'react' | ||||
| import api from '../utils/api' | ||||
|  | ||||
| /** | ||||
|  * @param {String} url  | ||||
|  * @param {*} defaultData  | ||||
|  * @param {String} method  | ||||
|  * @param {Object} options  | ||||
|  * @param {String} url | ||||
|  * @param {*} defaultData | ||||
|  * @param {String} method | ||||
|  * @param {Object} options | ||||
|  */ | ||||
| function useAPI(url, defaultData = [], method = "get", options = {}) { | ||||
| function useAPI (url, defaultData = [], method = 'get', options = {}) { | ||||
|   const [isLoading, setIsLoading] = useState(true) | ||||
|   const [data, setData] = useState(defaultData) | ||||
|   const [hasError, setHasError] = useState(false) | ||||
|  | ||||
|     const [isLoading, setIsLoading] = useState(true); | ||||
|     const [data, setData]           = useState(defaultData); | ||||
|     const [hasError, setHasError]   = useState(false); | ||||
|   useEffect(() => { | ||||
|     api[method](url, options) | ||||
|       .then((result) => { | ||||
|         setData(result.data) | ||||
|         setIsLoading(false) | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         setHasError(true) | ||||
|         console.error(error) | ||||
|       }) | ||||
|   }, []) | ||||
|  | ||||
|     useEffect(() => { | ||||
|         api[method](url, options) | ||||
|             .then((result) => { | ||||
|                 setData(result.data); | ||||
|                 setIsLoading(false); | ||||
|             }) | ||||
|             .catch((error) => { | ||||
|                 setHasError(true); | ||||
|                 console.error(error); | ||||
|             }); | ||||
|     }, []); | ||||
|  | ||||
|     return [isLoading, data, hasError]; | ||||
|   return [isLoading, data, hasError] | ||||
| } | ||||
|  | ||||
| export default useAPI; | ||||
| export default useAPI | ||||
|   | ||||
| @@ -1,3 +1,3 @@ | ||||
| const withCSS   = require('@zeit/next-css'); | ||||
| const withFonts = require('next-fonts'); | ||||
| module.exports = withFonts(withCSS()); | ||||
| const withCSS = require('@zeit/next-css') | ||||
| const withFonts = require('next-fonts') | ||||
| module.exports = withFonts(withCSS()) | ||||
|   | ||||
							
								
								
									
										9005
									
								
								website/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										9005
									
								
								website/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -4,10 +4,11 @@ | ||||
|   "description": "Website frontend for FunctionProject", | ||||
|   "main": "server.js", | ||||
|   "scripts": { | ||||
|     "dev": "cross-env NODE_ENV=dev node server", | ||||
|     "dev": "cross-env NODE_ENV=development node server", | ||||
|     "build": "next build", | ||||
|     "export": "next export", | ||||
|     "start": "cross-env NODE_ENV=production node server" | ||||
|     "start": "cross-env NODE_ENV=production node server", | ||||
|     "format": "standard \"./**/*.{js,jsx}\" --fix | snazzy || exit 0" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@fortawesome/fontawesome-svg-core": "^1.2.28", | ||||
| @@ -22,7 +23,7 @@ | ||||
|     "express": "^4.17.1", | ||||
|     "express-http-to-https": "^1.1.4", | ||||
|     "html-react-parser": "^0.10.2", | ||||
|     "next": "9.3.2", | ||||
|     "next": "^9.5.1", | ||||
|     "next-fonts": "^1.0.3", | ||||
|     "notyf": "^3.6.0", | ||||
|     "nprogress": "^0.2.0", | ||||
| @@ -39,6 +40,8 @@ | ||||
|     "universal-cookie": "^4.0.3" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "cross-env": "^7.0.2" | ||||
|     "cross-env": "^7.0.2", | ||||
|     "snazzy": "^8.0.0", | ||||
|     "standard": "^14.3.4" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,22 +1,21 @@ | ||||
| import { Fragment } from 'react'; | ||||
| import Link from 'next/link'; | ||||
| import HeadTag from '../components/HeadTag'; | ||||
| import '../public/css/pages/404.css'; | ||||
| import Link from 'next/link' | ||||
| import HeadTag from '../components/HeadTag' | ||||
| import '../public/css/pages/404.css' | ||||
|  | ||||
| const Error404 = () => ( | ||||
|     <Fragment> | ||||
|         <HeadTag  | ||||
|             title="Erreur 404"  | ||||
|             description="Cette page n'existe pas!"  | ||||
|             image="/images/error404.png"  | ||||
|         /> | ||||
|         <div className="Error404__container"> | ||||
|             <h1>Erreur <span className="important">404</span></h1> | ||||
|             <p className="text-center"> | ||||
|                 Cette page n'existe pas! <Link href={"/"}><a>Revenir à la page d'accueil ?</a></Link> | ||||
|             </p> | ||||
|         </div> | ||||
|     </Fragment> | ||||
| ); | ||||
|   <> | ||||
|     <HeadTag | ||||
|       title='Erreur 404' | ||||
|       description="Cette page n'existe pas!" | ||||
|       image='/images/error404.png' | ||||
|     /> | ||||
|     <div className='Error404__container'> | ||||
|       <h1>Erreur <span className='important'>404</span></h1> | ||||
|       <p className='text-center'> | ||||
|                 Cette page n'existe pas! <Link href='/'><a>Revenir à la page d'accueil ?</a></Link> | ||||
|       </p> | ||||
|     </div> | ||||
|   </> | ||||
| ) | ||||
|  | ||||
| export default Error404; | ||||
| export default Error404 | ||||
|   | ||||
| @@ -1,33 +1,33 @@ | ||||
| /* Libraries Imports */ | ||||
| import Router from 'next/router' | ||||
| import NProgress from 'nprogress'; | ||||
| import NProgress from 'nprogress' | ||||
|  | ||||
| /* Components Imports */ | ||||
| import Header from '../components/Header/Header'; | ||||
| import Footer from '../components/Footer/Footer'; | ||||
| import Header from '../components/Header/Header' | ||||
| import Footer from '../components/Footer/Footer' | ||||
|  | ||||
| /* Contexts Imports */ | ||||
| import UserContextProvider from '../contexts/UserContext'; | ||||
| import UserContextProvider from '../contexts/UserContext' | ||||
|  | ||||
| /* CSS Imports */ | ||||
| import '../public/fonts/Montserrat/Montserrat.css'; | ||||
| import '../public/css/normalize.css'; | ||||
| import '../public/css/grid.css'; | ||||
| import '../public/css/general.css'; | ||||
| import '../public/css/nprogress.css'; | ||||
| import '../public/fonts/Montserrat/Montserrat.css' | ||||
| import '../public/css/normalize.css' | ||||
| import '../public/css/grid.css' | ||||
| import '../public/css/general.css' | ||||
| import '../public/css/nprogress.css' | ||||
|  | ||||
| Router.events.on('routeChangeStart', () => NProgress.start()) | ||||
| Router.events.on('routeChangeComplete', () => NProgress.done()) | ||||
| Router.events.on('routeChangeError', () => NProgress.done()) | ||||
|  | ||||
| Router.events.on('routeChangeStart', () => NProgress.start()); | ||||
| Router.events.on('routeChangeComplete', () => NProgress.done()); | ||||
| Router.events.on('routeChangeError', () => NProgress.done()); | ||||
|    | ||||
| const App = ({ Component, pageProps }) => ( | ||||
|     <UserContextProvider> | ||||
|         <Header /> | ||||
|             <div className="content container-fluid"> | ||||
|                 <Component {...pageProps} /> | ||||
|             </div> | ||||
|         <Footer /> | ||||
|     </UserContextProvider> | ||||
| ); | ||||
|   <UserContextProvider> | ||||
|     <Header /> | ||||
|     <div className='content container-fluid'> | ||||
|       <Component {...pageProps} /> | ||||
|     </div> | ||||
|     <Footer /> | ||||
|   </UserContextProvider> | ||||
| ) | ||||
|  | ||||
| export default App; | ||||
| export default App | ||||
|   | ||||
| @@ -1,28 +1,28 @@ | ||||
| import Document, { Html, Head, Main, NextScript } from "next/document"; | ||||
| import Loader from '../components/Loader'; | ||||
| import Document, { Html, Head, Main, NextScript } from 'next/document' | ||||
| import Loader from '../components/Loader' | ||||
|  | ||||
| class MyDocument extends Document { | ||||
|     static async getInitialProps(ctx) { | ||||
|         const initialProps = await Document.getInitialProps(ctx); | ||||
|         return { ...initialProps }; | ||||
|     } | ||||
|   static async getInitialProps (ctx) { | ||||
|     const initialProps = await Document.getInitialProps(ctx) | ||||
|     return { ...initialProps } | ||||
|   } | ||||
|  | ||||
|     render() { | ||||
|         return ( | ||||
|             <Html lang="fr"> | ||||
|                 <Head /> | ||||
|                 <body> | ||||
|                     <div id="preloader"> | ||||
|                         <Loader /> | ||||
|                     </div> | ||||
|                     <div className="isLoading"> | ||||
|                         <Main /> | ||||
|                     </div> | ||||
|                     <NextScript /> | ||||
|                 </body> | ||||
|             </Html> | ||||
|         ); | ||||
|     } | ||||
|   render () { | ||||
|     return ( | ||||
|       <Html lang='fr'> | ||||
|         <Head /> | ||||
|         <body> | ||||
|           <div id='preloader'> | ||||
|             <Loader /> | ||||
|           </div> | ||||
|           <div className='isLoading'> | ||||
|             <Main /> | ||||
|           </div> | ||||
|           <NextScript /> | ||||
|         </body> | ||||
|       </Html> | ||||
|     ) | ||||
|   } | ||||
| } | ||||
|  | ||||
| export default MyDocument; | ||||
| export default MyDocument | ||||
|   | ||||
| @@ -1,49 +1,48 @@ | ||||
| import { Fragment } from 'react'; | ||||
| import axios from 'axios' | ||||
| import ReactMarkdown  from 'react-markdown/with-html'; | ||||
| import HeadTag from '../components/HeadTag'; | ||||
| import ReactMarkdown from 'react-markdown/with-html' | ||||
| import HeadTag from '../components/HeadTag' | ||||
|  | ||||
| const About = (props) => { | ||||
|     return ( | ||||
|         <Fragment> | ||||
|             <HeadTag  | ||||
|                 title="À-propos - FunctionProject" | ||||
|                 description="À-propos de FunctionProject." | ||||
|   return ( | ||||
|     <> | ||||
|       <HeadTag | ||||
|         title='À-propos - FunctionProject' | ||||
|         description='À-propos de FunctionProject.' | ||||
|       /> | ||||
|  | ||||
|       <div className='container'> | ||||
|         <div className='row justify-content-center'> | ||||
|           <div className='col-24 text-center'> | ||||
|             <h1 style={{ marginBottom: 0, paddingTop: '20px' }}>À-propos</h1> | ||||
|             <p style={{ marginTop: '5px', borderBottom: '1px solid var(--important)', paddingBottom: '30px' }}>(README.md du <a target='_blank' rel='noopener noreferrer' href='https://github.com/Divlo/FunctionProject'>GitHub</a>)</p> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <div style={{ marginBottom: '30px' }} className='row'> | ||||
|           <div className='col-24'> | ||||
|             <ReactMarkdown | ||||
|               source={props.data} | ||||
|               escapeHtml={false} | ||||
|               linkTarget='_blank' | ||||
|               transformLinkUri={(uri) => { | ||||
|                 if (uri.startsWith('./')) { | ||||
|                   return `https://github.com/Divlo/FunctionProject/blob/master/${uri.slice(2)}` | ||||
|                 } | ||||
|                 return uri | ||||
|               }} | ||||
|             /> | ||||
|  | ||||
|             <div className="container"> | ||||
|                 <div className="row justify-content-center"> | ||||
|                     <div className="col-24 text-center"> | ||||
|                         <h1 style={{ marginBottom: 0, paddingTop: "20px" }}>À-propos</h1> | ||||
|                         <p style={{ marginTop: '5px', borderBottom: '1px solid var(--important)', paddingBottom: '30px' }}>(README.md du <a target="_blank" rel="noopener noreferrer" href="https://github.com/Divlo/FunctionProject">GitHub</a>)</p> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <div style={{ marginBottom: '30px' }} className="row"> | ||||
|                     <div className="col-24"> | ||||
|                         <ReactMarkdown | ||||
|                             source={props.data} | ||||
|                             escapeHtml={false} | ||||
|                             linkTarget="_blank" | ||||
|                             transformLinkUri={(uri) => { | ||||
|                                 if (uri.startsWith('./')) { | ||||
|                                     return `https://github.com/Divlo/FunctionProject/blob/master/${uri.slice(2)}`; | ||||
|                                 } | ||||
|                                 return uri; | ||||
|                             }} | ||||
|                         /> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </Fragment> | ||||
|     ); | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export async function getServerSideProps(_context) { | ||||
|     const { data } = await axios.get('https://raw.githubusercontent.com/Divlo/FunctionProject/master/README.md'); | ||||
|     return { | ||||
|         props: { data } | ||||
|     }; | ||||
| export async function getServerSideProps (_context) { | ||||
|   const { data } = await axios.get('https://raw.githubusercontent.com/Divlo/FunctionProject/master/README.md') | ||||
|   return { | ||||
|     props: { data } | ||||
|   } | ||||
| } | ||||
|  | ||||
| export default About; | ||||
| export default About | ||||
|   | ||||
| @@ -1,88 +1,87 @@ | ||||
| import { Fragment, useState } from 'react'; | ||||
| import Cookies from "universal-cookie"; | ||||
| import SwipeableViews from 'react-swipeable-views'; | ||||
| import HeadTag from '../../components/HeadTag'; | ||||
| import AddEditFunction from '../../components/FunctionAdmin/AddEditFunction'; | ||||
| import EditArticleFunction from '../../components/FunctionAdmin/EditArticleFunction'; | ||||
| import EditFormFunction from '../../components/FunctionAdmin/EditFormFunction'; | ||||
| import redirect from '../../utils/redirect'; | ||||
| import api from '../../utils/api'; | ||||
| import { API_URL } from '../../utils/config/config'; | ||||
| import '../../components/FunctionPage/FunctionTabs.css'; | ||||
| import '../../public/css/pages/admin.css'; | ||||
| import { useState } from 'react' | ||||
| import Cookies from 'universal-cookie' | ||||
| import SwipeableViews from 'react-swipeable-views' | ||||
| import HeadTag from '../../components/HeadTag' | ||||
| import AddEditFunction from '../../components/FunctionAdmin/AddEditFunction' | ||||
| import EditArticleFunction from '../../components/FunctionAdmin/EditArticleFunction' | ||||
| import EditFormFunction from '../../components/FunctionAdmin/EditFormFunction' | ||||
| import redirect from '../../utils/redirect' | ||||
| import api from '../../utils/api' | ||||
| import { API_URL } from '../../utils/config/config' | ||||
| import '../../components/FunctionPage/FunctionTabs.css' | ||||
| import '../../public/css/pages/admin.css' | ||||
|  | ||||
| const AdminFunctionComponent = (props) => { | ||||
|   const [slideIndex, setSlideIndex] = useState(0) | ||||
|  | ||||
|     const [slideIndex, setSlideIndex] = useState(0); | ||||
|   const handleDeleteFunction = async () => { | ||||
|     await api.delete(`/admin/functions/${props.functionInfo.id}`, { headers: { Authorization: props.user.token } }) | ||||
|     redirect({}, '/admin') | ||||
|   } | ||||
|  | ||||
|     const handleDeleteFunction = async () => { | ||||
|         await api.delete(`/admin/functions/${props.functionInfo.id}`, { headers: { 'Authorization': props.user.token } }); | ||||
|         redirect({}, '/admin'); | ||||
|     } | ||||
|   return ( | ||||
|     <> | ||||
|       <HeadTag title={props.functionInfo.title} description={props.functionInfo.description} image={API_URL + props.functionInfo.image} /> | ||||
|  | ||||
|     return ( | ||||
|         <Fragment> | ||||
|             <HeadTag title={props.functionInfo.title} description={props.functionInfo.description} image={API_URL + props.functionInfo.image} /> | ||||
|       <div className='container-fluid'> | ||||
|         <div className='container'> | ||||
|           <div className='row justify-content-center'> | ||||
|             <ul className='FunctionTabs__nav'> | ||||
|               <li className='FunctionTabs__nav-item'> | ||||
|                 <a onClick={() => setSlideIndex(0)} className={`FunctionTabs__nav-link ${(slideIndex === 0) && 'FunctionTabs__nav-link-active'}`}>✒️ Modifier</a> | ||||
|               </li> | ||||
|               <li className='FunctionTabs__nav-item'> | ||||
|                 <a onClick={() => setSlideIndex(1)} className={`FunctionTabs__nav-link ${(slideIndex === 1) && 'FunctionTabs__nav-link-active'}`}>📝 Article</a> | ||||
|               </li> | ||||
|               <li className='FunctionTabs__nav-item'> | ||||
|                 <a onClick={() => setSlideIndex(2)} className={`FunctionTabs__nav-link ${(slideIndex === 2) && 'FunctionTabs__nav-link-active'}`}>⚙️ Utilisation</a> | ||||
|               </li> | ||||
|             </ul> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|             <div className="container-fluid"> | ||||
|                 <div className="container"> | ||||
|                     <div className="row justify-content-center"> | ||||
|                         <ul className="FunctionTabs__nav"> | ||||
|                             <li className="FunctionTabs__nav-item"> | ||||
|                                 <a onClick={() => setSlideIndex(0)} className={`FunctionTabs__nav-link ${(slideIndex === 0) && "FunctionTabs__nav-link-active"}`}>✒️ Modifier</a> | ||||
|                             </li> | ||||
|                             <li className="FunctionTabs__nav-item"> | ||||
|                                 <a onClick={() => setSlideIndex(1)} className={`FunctionTabs__nav-link ${(slideIndex === 1) && "FunctionTabs__nav-link-active"}`}>📝 Article</a> | ||||
|                             </li> | ||||
|                             <li className="FunctionTabs__nav-item"> | ||||
|                                 <a onClick={() => setSlideIndex(2)} className={`FunctionTabs__nav-link ${(slideIndex === 2) && "FunctionTabs__nav-link-active"}`}>⚙️ Utilisation</a> | ||||
|                             </li> | ||||
|                         </ul> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <div className="container-fluid"> | ||||
|                     <SwipeableViews onChangeIndex={(index) => setSlideIndex(index)} index={slideIndex}> | ||||
|                         <div className="Admin__Function-slide"> | ||||
|                             <AddEditFunction  | ||||
|                                 defaultInputState={{ ...props.functionInfo }}  | ||||
|                                 user={props.user}  | ||||
|                                 isEditing | ||||
|                             /> | ||||
|                             <div style={{ marginBottom: '30px' }} className="text-center"> | ||||
|                                 <button onClick={handleDeleteFunction} className="btn btn-dark">Supprimer la fonction</button> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div className="Admin__Function-slide"> | ||||
|                             <EditArticleFunction { ...props } /> | ||||
|                         </div> | ||||
|                         <div className="Admin__Function-slide"> | ||||
|                             <EditFormFunction { ...props } /> | ||||
|                         </div> | ||||
|                     </SwipeableViews> | ||||
|                 </div> | ||||
|         <div className='container-fluid'> | ||||
|           <SwipeableViews onChangeIndex={(index) => setSlideIndex(index)} index={slideIndex}> | ||||
|             <div className='Admin__Function-slide'> | ||||
|               <AddEditFunction | ||||
|                 defaultInputState={{ ...props.functionInfo }} | ||||
|                 user={props.user} | ||||
|                 isEditing | ||||
|               /> | ||||
|               <div style={{ marginBottom: '30px' }} className='text-center'> | ||||
|                 <button onClick={handleDeleteFunction} className='btn btn-dark'>Supprimer la fonction</button> | ||||
|               </div> | ||||
|             </div> | ||||
|         </Fragment> | ||||
|     ); | ||||
|             <div className='Admin__Function-slide'> | ||||
|               <EditArticleFunction {...props} /> | ||||
|             </div> | ||||
|             <div className='Admin__Function-slide'> | ||||
|               <EditFormFunction {...props} /> | ||||
|             </div> | ||||
|           </SwipeableViews> | ||||
|         </div> | ||||
|       </div> | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export async function getServerSideProps(context) { | ||||
|     const cookies  = new Cookies(context.req.headers.cookie); | ||||
|     const user     = { ...cookies.get('user') }; | ||||
|     const { slug } = context.params; | ||||
|     if (!user.isAdmin) { | ||||
|         return redirect(context, '/404'); | ||||
|     } | ||||
|     return api.get(`/admin/functions/${slug}`, { headers: { 'Authorization': user.token } }) | ||||
|         .then((response) => { | ||||
|             return { | ||||
|                 props: { | ||||
|                     user, | ||||
|                     functionInfo: response.data | ||||
|                 } | ||||
|             }; | ||||
|         }) | ||||
|         .catch(() => redirect(context, '/404')); | ||||
| export async function getServerSideProps (context) { | ||||
|   const cookies = new Cookies(context.req.headers.cookie) | ||||
|   const user = { ...cookies.get('user') } | ||||
|   const { slug } = context.params | ||||
|   if (!user.isAdmin) { | ||||
|     return redirect(context, '/404') | ||||
|   } | ||||
|   return api.get(`/admin/functions/${slug}`, { headers: { Authorization: user.token } }) | ||||
|     .then((response) => { | ||||
|       return { | ||||
|         props: { | ||||
|           user, | ||||
|           functionInfo: response.data | ||||
|         } | ||||
|       } | ||||
|     }) | ||||
|     .catch(() => redirect(context, '/404')) | ||||
| } | ||||
|  | ||||
| export default AdminFunctionComponent; | ||||
| export default AdminFunctionComponent | ||||
|   | ||||
| @@ -1,76 +1,76 @@ | ||||
| import Link from 'next/link'; | ||||
| import { Fragment, useState } from 'react'; | ||||
| import Cookies from "universal-cookie"; | ||||
| import HeadTag from '../../components/HeadTag'; | ||||
| import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; | ||||
| import { faTimes } from '@fortawesome/free-solid-svg-icons'; | ||||
| import Modal from '../../components/Modal'; | ||||
| import FunctionsList from '../../components/FunctionsList/FunctionsList'; | ||||
| import AddEditFunction from '../../components/FunctionAdmin/AddEditFunction'; | ||||
| import redirect from '../../utils/redirect'; | ||||
| import '../../public/css/pages/admin.css'; | ||||
| import Link from 'next/link' | ||||
| import { useState } from 'react' | ||||
| import Cookies from 'universal-cookie' | ||||
| import HeadTag from '../../components/HeadTag' | ||||
| import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' | ||||
| import { faTimes } from '@fortawesome/free-solid-svg-icons' | ||||
| import Modal from '../../components/Modal' | ||||
| import FunctionsList from '../../components/FunctionsList/FunctionsList' | ||||
| import AddEditFunction from '../../components/FunctionAdmin/AddEditFunction' | ||||
| import redirect from '../../utils/redirect' | ||||
| import '../../public/css/pages/admin.css' | ||||
|  | ||||
| const Admin = (props) => { | ||||
|   const [isOpen, setIsOpen] = useState(false) | ||||
|  | ||||
|     const [isOpen, setIsOpen]         = useState(false); | ||||
|   const toggleModal = () => setIsOpen(!isOpen) | ||||
|  | ||||
|     const toggleModal = () => setIsOpen(!isOpen); | ||||
|   return ( | ||||
|     <> | ||||
|       <HeadTag title='Admin - FunctionProject' description="Page d'administration de FunctionProject." /> | ||||
|  | ||||
|     return ( | ||||
|         <Fragment> | ||||
|             <HeadTag title="Admin - FunctionProject" description="Page d'administration de FunctionProject." /> | ||||
|       {/* Création d'une fonction */} | ||||
|       {(isOpen) | ||||
|         ? ( | ||||
|           <Modal toggleModal={toggleModal}> | ||||
|             <div className='Admin__Modal__container container-fluid'> | ||||
|               <div className='Admin__Modal__row row'> | ||||
|                 <div className='col-24'> | ||||
|                   <div className='Admin__Modal-top-container row'> | ||||
|                     <div className='col-24'> | ||||
|                       <span onClick={toggleModal} style={{ cursor: 'pointer', position: 'absolute', left: 0 }}> | ||||
|                         <FontAwesomeIcon icon={faTimes} style={{ width: '1.5rem', color: 'red' }} /> | ||||
|                       </span> | ||||
|                       <h2 className='text-center'>Crée une nouvelle fonction</h2> | ||||
|                     </div> | ||||
|                   </div> | ||||
|                 </div> | ||||
|  | ||||
|             {/* Création d'une fonction */} | ||||
|             {(isOpen) ? | ||||
|                     <Modal toggleModal={toggleModal}> | ||||
|                         <div className="Admin__Modal__container container-fluid"> | ||||
|                             <div className="Admin__Modal__row row"> | ||||
|                                 <div className="col-24"> | ||||
|                                     <div className="Admin__Modal-top-container row"> | ||||
|                                         <div className="col-24"> | ||||
|                                             <span onClick={toggleModal} style={{ cursor: 'pointer', position: 'absolute', left: 0 }}> | ||||
|                                                 <FontAwesomeIcon icon={faTimes} style={{ width: '1.5rem', color: 'red' }} /> | ||||
|                                             </span> | ||||
|                                             <h2 className="text-center">Crée une nouvelle fonction</h2> | ||||
|                                         </div> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                 <div className='col-24'> | ||||
|                   <AddEditFunction defaultInputState={{ type: 'form' }} {...props} /> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </Modal> | ||||
|         ) | ||||
|  | ||||
|                                 <div className="col-24"> | ||||
|                                     <AddEditFunction defaultInputState={{ type: 'form' }} { ...props } /> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </Modal> | ||||
|  | ||||
|                 : | ||||
|  | ||||
|                     <FunctionsList isAdmin token={props.user.token}> | ||||
|                         <div className="col-24"> | ||||
|                             <h1 className="Functions__title">Administration</h1> | ||||
|                             <button onClick={toggleModal} style={{ margin: '0 0 40px 0' }} className="btn btn-dark">Crée une nouvelle fonction</button> | ||||
|                             <Link href={"/admin/manageCategories"}> | ||||
|                                 <button style={{ margin: '0 0 0 20px' }} className="btn btn-dark">Gérer les catégories</button> | ||||
|                             </Link> | ||||
|                             <Link href={"/admin/manageQuotes"}> | ||||
|                                 <button style={{ margin: '0 0 0 20px' }} className="btn btn-dark">Gérer les citations</button> | ||||
|                             </Link> | ||||
|                         </div> | ||||
|                     </FunctionsList> | ||||
|             } | ||||
|         </Fragment> | ||||
|     ); | ||||
|         : ( | ||||
|           <FunctionsList isAdmin token={props.user.token}> | ||||
|             <div className='col-24'> | ||||
|               <h1 className='Functions__title'>Administration</h1> | ||||
|               <button onClick={toggleModal} style={{ margin: '0 0 40px 0' }} className='btn btn-dark'>Crée une nouvelle fonction</button> | ||||
|               <Link href='/admin/manageCategories'> | ||||
|                 <button style={{ margin: '0 0 0 20px' }} className='btn btn-dark'>Gérer les catégories</button> | ||||
|               </Link> | ||||
|               <Link href='/admin/manageQuotes'> | ||||
|                 <button style={{ margin: '0 0 0 20px' }} className='btn btn-dark'>Gérer les citations</button> | ||||
|               </Link> | ||||
|             </div> | ||||
|           </FunctionsList> | ||||
|         )} | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export async function getServerSideProps(context) { | ||||
|     const cookies = new Cookies(context.req.headers.cookie); | ||||
|     const user    = { ...cookies.get('user') }; | ||||
|     if (!user.isAdmin) { | ||||
|         return redirect(context, '/404'); | ||||
|     } | ||||
|     return { | ||||
|         props: { user } | ||||
|     }; | ||||
| export async function getServerSideProps (context) { | ||||
|   const cookies = new Cookies(context.req.headers.cookie) | ||||
|   const user = { ...cookies.get('user') } | ||||
|   if (!user.isAdmin) { | ||||
|     return redirect(context, '/404') | ||||
|   } | ||||
|   return { | ||||
|     props: { user } | ||||
|   } | ||||
| } | ||||
|  | ||||
| export default Admin; | ||||
| export default Admin | ||||
|   | ||||
| @@ -1,187 +1,187 @@ | ||||
| import { Fragment, useState } from 'react'; | ||||
| import Cookies from "universal-cookie"; | ||||
| import date from 'date-and-time'; | ||||
| import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; | ||||
| import { faPen, faTrash, faTimes } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { PhotoshopPicker } from 'react-color'; | ||||
| import HeadTag from '../../components/HeadTag'; | ||||
| import Modal from '../../components/Modal'; | ||||
| import redirect from '../../utils/redirect'; | ||||
| 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'; | ||||
| import { useState } from 'react' | ||||
| import Cookies from 'universal-cookie' | ||||
| import date from 'date-and-time' | ||||
| import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' | ||||
| import { faPen, faTrash, faTimes } from '@fortawesome/free-solid-svg-icons' | ||||
| import { PhotoshopPicker } from 'react-color' | ||||
| import HeadTag from '../../components/HeadTag' | ||||
| import Modal from '../../components/Modal' | ||||
| import redirect from '../../utils/redirect' | ||||
| 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 defaultCategoryState = { name: "", color: "#ffffff" }; | ||||
| const defaultCategoryState = { name: '', color: '#ffffff' } | ||||
|  | ||||
| const AddEditCategory = (props) => { | ||||
|   const [inputState, setInputState] = useState(props.defaultInputState) | ||||
|   const [message, setMessage] = useState('') | ||||
|   const [isLoading, setIsLoading] = useState(false) | ||||
|  | ||||
|     const [inputState, setInputState] = useState(props.defaultInputState); | ||||
|     const [message, setMessage]       = useState(""); | ||||
|     const [isLoading, setIsLoading]   = useState(false); | ||||
|   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 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 apiCallCategory = () => { | ||||
|     if (props.isEditing) return api.put(`/admin/categories/${inputState.id}`, { name: inputState.name, color: inputState.color }, { headers: { Authorization: props.user.token } }) | ||||
|     return api.post('/admin/categories', inputState, { headers: { Authorization: props.user.token } }) | ||||
|   } | ||||
|  | ||||
|     const apiCallCategory = () => { | ||||
|         if (props.isEditing) return api.put(`/admin/categories/${inputState.id}`, { name: inputState.name, color: inputState.color }, { headers: { 'Authorization': props.user.token } }); | ||||
|         return api.post('/admin/categories', inputState, { headers: { 'Authorization': props.user.token } }); | ||||
|     } | ||||
|   const handleSubmit = (event) => { | ||||
|     event.preventDefault() | ||||
|     setIsLoading(true) | ||||
|     apiCallCategory() | ||||
|       .then(() => { | ||||
|         setIsLoading(false) | ||||
|         window.location.reload(true) | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         setMessage(`<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>`) | ||||
|         setIsLoading(false) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|     const handleSubmit = (event) => { | ||||
|         event.preventDefault(); | ||||
|         setIsLoading(true); | ||||
|         apiCallCategory() | ||||
|             .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 ( | ||||
|         <div className="Admin__Modal__container container-fluid"> | ||||
|             <div className="Admin__Modal__row row"> | ||||
|                 <div className="col-24"> | ||||
|                     <div className="Admin__Modal-top-container row"> | ||||
|                         <div className="col-24"> | ||||
|                             <span onClick={props.toggleModal} style={{ cursor: 'pointer', position: 'absolute', left: 0 }}> | ||||
|                                 <FontAwesomeIcon icon={faTimes} style={{ width: '1.5rem', color: 'red' }} /> | ||||
|                             </span> | ||||
|                             <h2 className="text-center">{(props.isEditing) ? "Modifier la catégorie" : "Crée une nouvelle catégorie"}</h2> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <div className="col-24"> | ||||
|                     <form onSubmit={handleSubmit}> | ||||
|                         <div className="form-group"> | ||||
|                             <label className="form-label" htmlFor="name">Nom :</label> | ||||
|                             <input value={inputState.name} onChange={handleChange} type="text" name="name" id="name" className="form-control" placeholder="(e.g : ✨ Utilitaires)" /> | ||||
|                         </div> | ||||
|  | ||||
|                         <div className="form-group"> | ||||
|                             <label className="form-label" htmlFor="title">Couleur :</label> | ||||
|                             <PhotoshopPicker color={inputState.color} onChange={(color) => handleChange({ target: { name: "color", value: color.hex } })} /> | ||||
|                         </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> | ||||
|                 </div> | ||||
|   return ( | ||||
|     <div className='Admin__Modal__container container-fluid'> | ||||
|       <div className='Admin__Modal__row row'> | ||||
|         <div className='col-24'> | ||||
|           <div className='Admin__Modal-top-container row'> | ||||
|             <div className='col-24'> | ||||
|               <span onClick={props.handleToggleModal} style={{ cursor: 'pointer', position: 'absolute', left: 0 }}> | ||||
|                 <FontAwesomeIcon icon={faTimes} style={{ width: '1.5rem', color: 'red' }} /> | ||||
|               </span> | ||||
|               <h2 className='text-center'>{(props.isEditing) ? 'Modifier la catégorie' : 'Crée une nouvelle catégorie'}</h2> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|     ); | ||||
|  | ||||
|         <div className='col-24'> | ||||
|           <form onSubmit={handleSubmit}> | ||||
|             <div className='form-group'> | ||||
|               <label className='form-label' htmlFor='name'>Nom :</label> | ||||
|               <input value={inputState.name} onChange={handleChange} type='text' name='name' id='name' className='form-control' placeholder='(e.g : ✨ Utilitaires)' /> | ||||
|             </div> | ||||
|  | ||||
|             <div className='form-group'> | ||||
|               <label className='form-label' htmlFor='title'>Couleur :</label> | ||||
|               <PhotoshopPicker color={inputState.color} onChange={(color) => handleChange({ target: { name: 'color', value: color.hex } })} /> | ||||
|             </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> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| const manageCategories = (props) => { | ||||
|   const [, categories] = useAPI('/categories') | ||||
|   const [isOpen, setIsOpen] = useState(false) | ||||
|   const [isEditing, setIsEditing] = useState(false) | ||||
|   const [defaultInputState, setDefaultInputState] = useState(defaultCategoryState) | ||||
|  | ||||
|     const [, categories]                            = useAPI('/categories'); | ||||
|     const [isOpen, setIsOpen]                       = useState(false); | ||||
|     const [isEditing, setIsEditing]                 = useState(false); | ||||
|     const [defaultInputState, setDefaultInputState] = useState(defaultCategoryState); | ||||
|   const toggleModal = () => setIsOpen(!isOpen) | ||||
|  | ||||
|     const toggleModal = () =>  setIsOpen(!isOpen); | ||||
|   const handleRemoveCategory = async (categoryId) => { | ||||
|     try { | ||||
|       await api.delete(`/admin/categories/${categoryId}`, { headers: { Authorization: props.user.token } }) | ||||
|       window.location.reload(true) | ||||
|     } catch {} | ||||
|   } | ||||
|  | ||||
|     const handleRemoveCategory = async (categoryId) => { | ||||
|         try { | ||||
|             await api.delete(`/admin/categories/${categoryId}`, { headers: { 'Authorization': props.user.token } }); | ||||
|             window.location.reload(true); | ||||
|         } catch {} | ||||
|     } | ||||
|   const handleEditCategory = (categoryInfo) => { | ||||
|     setDefaultInputState(categoryInfo) | ||||
|     setIsEditing(true) | ||||
|     toggleModal() | ||||
|   } | ||||
|  | ||||
|     const handleEditCategory = (categoryInfo) => { | ||||
|         setDefaultInputState(categoryInfo); | ||||
|         setIsEditing(true); | ||||
|         toggleModal(); | ||||
|     } | ||||
|   return ( | ||||
|     <> | ||||
|       <HeadTag title='Admin - FunctionProject' description="Page d'administration de FunctionProject. Gérer les catégories." /> | ||||
|  | ||||
|     return ( | ||||
|         <Fragment> | ||||
|             <HeadTag title="Admin - FunctionProject" description="Page d'administration de FunctionProject. Gérer les catégories." /> | ||||
|  | ||||
|             { | ||||
|                 (isOpen) ? | ||||
|                     <Modal> | ||||
|                         <AddEditCategory toggleModal={toggleModal} defaultInputState={defaultInputState} { ...props } isEditing={isEditing} /> | ||||
|                     </Modal> | ||||
|                 : | ||||
|                     <div className="container-fluid text-center"> | ||||
|                         <div className="row justify-content-center"> | ||||
|                             <div className="col-24"> | ||||
|                                 <h1>Gérer les catégories</h1> | ||||
|                                 <button onClick={() => { setDefaultInputState(defaultCategoryState); toggleModal(); setIsEditing(false); }} style={{ margin: '0 0 40px 0' }} className="btn btn-dark">Ajouter une catégorie</button> | ||||
|                             </div> | ||||
|                         </div>     | ||||
|                         <div className="row justify-content-center"> | ||||
|                             <div className="container-fluid"> | ||||
|                                 <div className="col-24 table-column"> | ||||
|                                     <table className="table"> | ||||
|                                         <thead> | ||||
|                                             <tr> | ||||
|                                                 <th className="table-row" scope="col">id</th> | ||||
|                                                 <th className="table-row" scope="col">name</th> | ||||
|                                                 <th className="table-row" scope="col">color</th> | ||||
|                                                 <th className="table-row" scope="col">createdAt</th> | ||||
|                                                 <th className="table-row" scope="col">updatedAt</th> | ||||
|                                                 <th className="table-row" scope="col">Modifier</th> | ||||
|                                                 <th className="table-row" scope="col">Supprimer</th> | ||||
|                                             </tr> | ||||
|                                         </thead> | ||||
|                                         <tbody> | ||||
|                                             {categories.map((category) => { | ||||
|                                                 return ( | ||||
|                                                     <tr key={category.id} style={{ backgroundColor: category.color }}> | ||||
|                                                         <td className="table-row">{category.id}</td> | ||||
|                                                         <td className="table-row">{category.name}</td> | ||||
|                                                         <td className="table-row">{category.color}</td> | ||||
|                                                         <td className="table-row">{date.format(new Date(category.createdAt), 'DD/MM/YYYY à HH:mm', true)}</td> | ||||
|                                                         <td className="table-row">{date.format(new Date(category.updatedAt), 'DD/MM/YYYY à HH:mm', true)}</td> | ||||
|                                                         <td style={{ cursor: 'pointer' }} onClick={() => handleEditCategory({ name: category.name, color: category.color, id: category.id })}> | ||||
|                                                             <FontAwesomeIcon icon={faPen} style={{ width: '1.5rem' }} /> | ||||
|                                                         </td> | ||||
|                                                         <td style={{ cursor: 'pointer' }} onClick={() => handleRemoveCategory(category.id)}> | ||||
|                                                             <FontAwesomeIcon icon={faTrash} style={{ width: '1.5rem' }} /> | ||||
|                                                         </td> | ||||
|                                                     </tr> | ||||
|                                                 ); | ||||
|                                             })} | ||||
|                                         </tbody> | ||||
|                                     </table> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|             } | ||||
|         </Fragment> | ||||
|     ); | ||||
|       { | ||||
|         (isOpen) | ||||
|           ? ( | ||||
|             <Modal> | ||||
|               <AddEditCategory handleToggleModal={toggleModal} defaultInputState={defaultInputState} {...props} isEditing={isEditing} /> | ||||
|             </Modal> | ||||
|           ) | ||||
|           : ( | ||||
|             <div className='container-fluid text-center'> | ||||
|               <div className='row justify-content-center'> | ||||
|                 <div className='col-24'> | ||||
|                   <h1>Gérer les catégories</h1> | ||||
|                   <button onClick={() => { setDefaultInputState(defaultCategoryState); toggleModal(); setIsEditing(false) }} style={{ margin: '0 0 40px 0' }} className='btn btn-dark'>Ajouter une catégorie</button> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div className='row justify-content-center'> | ||||
|                 <div className='container-fluid'> | ||||
|                   <div className='col-24 table-column'> | ||||
|                     <table className='table'> | ||||
|                       <thead> | ||||
|                         <tr> | ||||
|                           <th className='table-row' scope='col'>id</th> | ||||
|                           <th className='table-row' scope='col'>name</th> | ||||
|                           <th className='table-row' scope='col'>color</th> | ||||
|                           <th className='table-row' scope='col'>createdAt</th> | ||||
|                           <th className='table-row' scope='col'>updatedAt</th> | ||||
|                           <th className='table-row' scope='col'>Modifier</th> | ||||
|                           <th className='table-row' scope='col'>Supprimer</th> | ||||
|                         </tr> | ||||
|                       </thead> | ||||
|                       <tbody> | ||||
|                         {categories.map((category) => { | ||||
|                           return ( | ||||
|                             <tr key={category.id} style={{ backgroundColor: category.color }}> | ||||
|                               <td className='table-row'>{category.id}</td> | ||||
|                               <td className='table-row'>{category.name}</td> | ||||
|                               <td className='table-row'>{category.color}</td> | ||||
|                               <td className='table-row'>{date.format(new Date(category.createdAt), 'DD/MM/YYYY à HH:mm', true)}</td> | ||||
|                               <td className='table-row'>{date.format(new Date(category.updatedAt), 'DD/MM/YYYY à HH:mm', true)}</td> | ||||
|                               <td style={{ cursor: 'pointer' }} onClick={() => handleEditCategory({ name: category.name, color: category.color, id: category.id })}> | ||||
|                                 <FontAwesomeIcon icon={faPen} style={{ width: '1.5rem' }} /> | ||||
|                               </td> | ||||
|                               <td style={{ cursor: 'pointer' }} onClick={() => handleRemoveCategory(category.id)}> | ||||
|                                 <FontAwesomeIcon icon={faTrash} style={{ width: '1.5rem' }} /> | ||||
|                               </td> | ||||
|                             </tr> | ||||
|                           ) | ||||
|                         })} | ||||
|                       </tbody> | ||||
|                     </table> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           ) | ||||
|       } | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export async function getServerSideProps(context) { | ||||
|     const cookies = new Cookies(context.req.headers.cookie); | ||||
|     const user    = { ...cookies.get('user') }; | ||||
|     if (!user.isAdmin) { | ||||
|         return redirect(context, '/404'); | ||||
|     } | ||||
|     return { | ||||
|         props: { user } | ||||
|     }; | ||||
| export async function getServerSideProps (context) { | ||||
|   const cookies = new Cookies(context.req.headers.cookie) | ||||
|   const user = { ...cookies.get('user') } | ||||
|   if (!user.isAdmin) { | ||||
|     return redirect(context, '/404') | ||||
|   } | ||||
|   return { | ||||
|     props: { user } | ||||
|   } | ||||
| } | ||||
|  | ||||
| export default manageCategories; | ||||
| export default manageCategories | ||||
|   | ||||
| @@ -1,122 +1,121 @@ | ||||
| import { Fragment, useState, useEffect, useRef, useCallback } from 'react'; | ||||
| import Cookies from "universal-cookie"; | ||||
| import Link from 'next/link'; | ||||
| import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; | ||||
| import { faCheck, faTrash } from '@fortawesome/free-solid-svg-icons'; | ||||
| import redirect from '../../utils/redirect'; | ||||
| import HeadTag from '../../components/HeadTag'; | ||||
| import api from '../../utils/api'; | ||||
| import '../../public/css/pages/admin.css'; | ||||
| import { useState, useEffect, useRef, useCallback } from 'react' | ||||
| import Cookies from 'universal-cookie' | ||||
| import Link from 'next/link' | ||||
| import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' | ||||
| import { faCheck, faTrash } from '@fortawesome/free-solid-svg-icons' | ||||
| import redirect from '../../utils/redirect' | ||||
| import HeadTag from '../../components/HeadTag' | ||||
| import api from '../../utils/api' | ||||
| import '../../public/css/pages/admin.css' | ||||
|  | ||||
| const manageQuotes = (props) => { | ||||
|   const [quotesData, setQuotesData] = useState({ hasMore: true, rows: [], totalItems: 0 }) | ||||
|   const [isLoadingQuotes, setLoadingQuotes] = useState(true) | ||||
|   const [pageQuotes, setPageQuotes] = useState(1) | ||||
|  | ||||
|     const [quotesData, setQuotesData]         = useState({ hasMore: true, rows: [], totalItems: 0 }); | ||||
|     const [isLoadingQuotes, setLoadingQuotes] = useState(true); | ||||
|     const [pageQuotes, setPageQuotes]         = useState(1); | ||||
|   // Récupère les citations si la page change | ||||
|   useEffect(() => { | ||||
|     getQuotesData() | ||||
|   }, [pageQuotes]) | ||||
|  | ||||
|     // Récupère les citations si la page change | ||||
|     useEffect(() => { | ||||
|         getQuotesData(); | ||||
|     }, [pageQuotes]); | ||||
|   const getQuotesData = async () => { | ||||
|     setLoadingQuotes(true) | ||||
|     const { data } = await api.get(`/admin/quotes?limit=20page=${pageQuotes}`, { headers: { Authorization: props.user.token } }) | ||||
|     setQuotesData({ | ||||
|       hasMore: data.hasMore, | ||||
|       rows: [...quotesData.rows, ...data.rows], | ||||
|       totalItems: data.totalItems | ||||
|     }) | ||||
|     setLoadingQuotes(false) | ||||
|   } | ||||
|  | ||||
|     const getQuotesData = async () => { | ||||
|         setLoadingQuotes(true); | ||||
|         const { data } = await api.get(`/admin/quotes?limit=20page=${pageQuotes}`, { headers: { 'Authorization': props.user.token } }); | ||||
|         setQuotesData({ | ||||
|             hasMore: data.hasMore, | ||||
|             rows: [...quotesData.rows, ...data.rows], | ||||
|             totalItems: data.totalItems | ||||
|         }); | ||||
|         setLoadingQuotes(false); | ||||
|     } | ||||
|   // Permet la pagination au scroll | ||||
|   const observer = useRef() | ||||
|   const lastQuoteRef = useCallback((node) => { | ||||
|     if (isLoadingQuotes) return | ||||
|     if (observer.current) observer.current.disconnect() | ||||
|     observer.current = new window.IntersectionObserver((entries) => { | ||||
|       if (entries[0].isIntersecting && quotesData.hasMore) { | ||||
|         setPageQuotes(pageQuotes + 1) | ||||
|       } | ||||
|     }, { threshold: 1 }) | ||||
|     if (node) observer.current.observe(node) | ||||
|   }, [isLoadingQuotes, quotesData.hasMore]) | ||||
|  | ||||
|     // Permet la pagination au scroll | ||||
|     const observer = useRef(); | ||||
|     const lastQuoteRef = useCallback((node) => { | ||||
|         if (isLoadingQuotes) return; | ||||
|         if (observer.current) observer.current.disconnect(); | ||||
|         observer.current = new IntersectionObserver((entries) => { | ||||
|             if (entries[0].isIntersecting && quotesData.hasMore) { | ||||
|                 setPageQuotes(pageQuotes + 1); | ||||
|             } | ||||
|         }, { threshold: 1 }); | ||||
|         if (node) observer.current.observe(node); | ||||
|     }, [isLoadingQuotes, quotesData.hasMore]); | ||||
|   const handleValidationQuote = async (id, isValid) => { | ||||
|     try { | ||||
|       await api.put(`/admin/quotes/${id}`, { isValid }, { headers: { Authorization: props.user.token } }) | ||||
|       window.location.reload(true) | ||||
|     } catch {} | ||||
|   } | ||||
|  | ||||
|     const handleValidationQuote = async (id, isValid) => { | ||||
|         try { | ||||
|             await api.put(`/admin/quotes/${id}`, { isValid }, { headers: { 'Authorization': props.user.token } }); | ||||
|             window.location.reload(true); | ||||
|         } catch {} | ||||
|     } | ||||
|   return ( | ||||
|     <> | ||||
|       <HeadTag title='Admin - FunctionProject' description="Page d'administration de FunctionProject. Gérer les citations." /> | ||||
|  | ||||
|     return ( | ||||
|         <Fragment> | ||||
|             <HeadTag title="Admin - FunctionProject" description="Page d'administration de FunctionProject. Gérer les citations." /> | ||||
|       <div className='container-fluid'> | ||||
|         <div className='row justify-content-center'> | ||||
|           <div className='col-24 text-center'> | ||||
|             <h2>Liste des citations (non validées) : </h2> | ||||
|             <p style={{ marginTop: '5px' }}>Total de {quotesData.totalItems} citations.</p> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|             <div className="container-fluid"> | ||||
|                 <div className="row justify-content-center"> | ||||
|                     <div className="col-24 text-center"> | ||||
|                         <h2>Liste des citations (non validées) : </h2> | ||||
|                         <p style={{ marginTop: '5px' }}>Total de {quotesData.totalItems} citations.</p> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <div className="row" style={{ marginBottom: '30px' }}> | ||||
|                     <div className="col-24 table-column"> | ||||
|                         <table className="table"> | ||||
|                             <thead> | ||||
|                                 <tr> | ||||
|                                     <th className="table-row" scope="col">Citation/Proverbe</th> | ||||
|                                     <th className="table-row" scope="col">Auteur</th> | ||||
|                                     <th className="table-row" scope="col">Proposée par</th> | ||||
|                                     <th className="table-row" scope="col">Valider</th> | ||||
|                                     <th className="table-row" scope="col">Supprimer</th> | ||||
|                                 </tr> | ||||
|                             </thead> | ||||
|                             <tbody> | ||||
|                                 {quotesData.rows.map((currentQuote, index) => { | ||||
|                                     const quoteJSX = ( | ||||
|                                         <Fragment> | ||||
|                                                 <td className="table-row text-center">{currentQuote.quote}</td> | ||||
|                                                 <td className="table-row text-center">{currentQuote.author}</td> | ||||
|                                                 <td className="table-row text-center"> | ||||
|                                                     <Link href={"/users/[name]"} as={`/users/${currentQuote.user.name}`}> | ||||
|                                                         <a>{currentQuote.user.name}</a> | ||||
|                                                     </Link> | ||||
|                                                 </td> | ||||
|                                                 <td onClick={() => handleValidationQuote(currentQuote.id, true)} className="table-row text-center" style={{ cursor: 'pointer' }}> | ||||
|                                                     <FontAwesomeIcon icon={faCheck} style={{ width: '1.5rem' }} /> | ||||
|                                                 </td> | ||||
|                                                 <td onClick={() => handleValidationQuote(currentQuote.id, false)} className="table-row text-center" style={{ cursor: 'pointer' }}> | ||||
|                                                     <FontAwesomeIcon icon={faTrash} style={{ width: '1.5rem' }} /> | ||||
|                                                 </td> | ||||
|                                         </Fragment> | ||||
|                                     ); | ||||
|                                     // Si c'est le dernier élément | ||||
|                                     if (quotesData.rows.length === index + 1) { | ||||
|                                         return <tr key={index} ref={lastQuoteRef}>{quoteJSX}</tr> | ||||
|                                     } | ||||
|                                     return <tr key={index}>{quoteJSX}</tr> | ||||
|                                 })} | ||||
|                             </tbody> | ||||
|                         </table> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </Fragment> | ||||
|     ); | ||||
|         <div className='row' style={{ marginBottom: '30px' }}> | ||||
|           <div className='col-24 table-column'> | ||||
|             <table className='table'> | ||||
|               <thead> | ||||
|                 <tr> | ||||
|                   <th className='table-row' scope='col'>Citation/Proverbe</th> | ||||
|                   <th className='table-row' scope='col'>Auteur</th> | ||||
|                   <th className='table-row' scope='col'>Proposée par</th> | ||||
|                   <th className='table-row' scope='col'>Valider</th> | ||||
|                   <th className='table-row' scope='col'>Supprimer</th> | ||||
|                 </tr> | ||||
|               </thead> | ||||
|               <tbody> | ||||
|                 {quotesData.rows.map((currentQuote, index) => { | ||||
|                   const quoteJSX = ( | ||||
|                     <> | ||||
|                       <td className='table-row text-center'>{currentQuote.quote}</td> | ||||
|                       <td className='table-row text-center'>{currentQuote.author}</td> | ||||
|                       <td className='table-row text-center'> | ||||
|                         <Link href='/users/[name]' as={`/users/${currentQuote.user.name}`}> | ||||
|                           <a>{currentQuote.user.name}</a> | ||||
|                         </Link> | ||||
|                       </td> | ||||
|                       <td onClick={() => handleValidationQuote(currentQuote.id, true)} className='table-row text-center' style={{ cursor: 'pointer' }}> | ||||
|                         <FontAwesomeIcon icon={faCheck} style={{ width: '1.5rem' }} /> | ||||
|                       </td> | ||||
|                       <td onClick={() => handleValidationQuote(currentQuote.id, false)} className='table-row text-center' style={{ cursor: 'pointer' }}> | ||||
|                         <FontAwesomeIcon icon={faTrash} style={{ width: '1.5rem' }} /> | ||||
|                       </td> | ||||
|                     </> | ||||
|                   ) | ||||
|                   // Si c'est le dernier élément | ||||
|                   if (quotesData.rows.length === index + 1) { | ||||
|                     return <tr key={index} ref={lastQuoteRef}>{quoteJSX}</tr> | ||||
|                   } | ||||
|                   return <tr key={index}>{quoteJSX}</tr> | ||||
|                 })} | ||||
|               </tbody> | ||||
|             </table> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export async function getServerSideProps(context) { | ||||
|     const cookies = new Cookies(context.req.headers.cookie); | ||||
|     const user    = { ...cookies.get('user') }; | ||||
|     if (!user.isAdmin) { | ||||
|         return redirect(context, '/404'); | ||||
|     } | ||||
|     return { | ||||
|         props: { user } | ||||
|     }; | ||||
| export async function getServerSideProps (context) { | ||||
|   const cookies = new Cookies(context.req.headers.cookie) | ||||
|   const user = { ...cookies.get('user') } | ||||
|   if (!user.isAdmin) { | ||||
|     return redirect(context, '/404') | ||||
|   } | ||||
|   return { | ||||
|     props: { user } | ||||
|   } | ||||
| } | ||||
|  | ||||
| export default manageQuotes; | ||||
| export default manageQuotes | ||||
|   | ||||
| @@ -1,54 +1,54 @@ | ||||
| import FunctionTabs from '../../components/FunctionPage/FunctionTabs'; | ||||
| import FunctionForm from '../../components/FunctionPage/FunctionForm'; | ||||
| import FunctionArticle from '../../components/FunctionPage/FunctionArticle'; | ||||
| import FunctionComments from '../../components/FunctionPage/FunctionComments/FunctionComments'; | ||||
| import FunctionPage from '../../components/FunctionPage/FunctionPage'; | ||||
| import redirect from '../../utils/redirect'; | ||||
| import api from '../../utils/api'; | ||||
| import '../../public/css/pages/FunctionComponent.css'; | ||||
| import FunctionTabs from '../../components/FunctionPage/FunctionTabs' | ||||
| import FunctionForm from '../../components/FunctionPage/FunctionForm' | ||||
| import FunctionArticle from '../../components/FunctionPage/FunctionArticle' | ||||
| import FunctionComments from '../../components/FunctionPage/FunctionComments/FunctionComments' | ||||
| import FunctionPage from '../../components/FunctionPage/FunctionPage' | ||||
| import redirect from '../../utils/redirect' | ||||
| import api from '../../utils/api' | ||||
| import '../../public/css/pages/FunctionComponent.css' | ||||
|  | ||||
| const FunctionTabManager = (props) => { | ||||
|     if (props.type === "form") { | ||||
|         return ( | ||||
|             <FunctionTabs setSlideIndex={props.setSlideIndex} slideIndex={props.slideIndex}> | ||||
|                 <div className="FunctionComponent__slide"> | ||||
|                     <FunctionForm inputsArray={ [...props.utilizationForm || []] } slug={props.slug} /> | ||||
|                 </div> | ||||
|                 <div className="FunctionComponent__slide"> | ||||
|                     <FunctionArticle article={props.article} /> | ||||
|                 </div> | ||||
|                 <div className="FunctionComponent__slide"> | ||||
|                     <FunctionComments functionId={props.id} /> | ||||
|                 </div> | ||||
|             </FunctionTabs> | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|   if (props.type === 'form') { | ||||
|     return ( | ||||
|         <FunctionTabs setSlideIndex={props.setSlideIndex} slideIndex={props.slideIndex}> | ||||
|             <div className="FunctionComponent__slide"> | ||||
|                 <FunctionArticle article={props.article} /> | ||||
|             </div> | ||||
|             <div className="FunctionComponent__slide"> | ||||
|                 <FunctionComments functionId={props.id} /> | ||||
|             </div> | ||||
|         </FunctionTabs> | ||||
|     ); | ||||
|       <FunctionTabs setSlideIndex={props.setSlideIndex} slideIndex={props.slideIndex}> | ||||
|         <div className='FunctionComponent__slide'> | ||||
|           <FunctionForm inputsArray={[...props.utilizationForm || []]} slug={props.slug} /> | ||||
|         </div> | ||||
|         <div className='FunctionComponent__slide'> | ||||
|           <FunctionArticle article={props.article} /> | ||||
|         </div> | ||||
|         <div className='FunctionComponent__slide'> | ||||
|           <FunctionComments functionId={props.id} /> | ||||
|         </div> | ||||
|       </FunctionTabs> | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <FunctionTabs setSlideIndex={props.setSlideIndex} slideIndex={props.slideIndex}> | ||||
|       <div className='FunctionComponent__slide'> | ||||
|         <FunctionArticle article={props.article} /> | ||||
|       </div> | ||||
|       <div className='FunctionComponent__slide'> | ||||
|         <FunctionComments functionId={props.id} /> | ||||
|       </div> | ||||
|     </FunctionTabs> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| const FunctionComponent = (props) => ( | ||||
|     <FunctionPage  | ||||
|         FunctionTabManager={FunctionTabManager} | ||||
|         { ...props } | ||||
|         tabNames={(props.type === "form") ? ["⚙️ Utilisation", "📝 Article", "📬 Commentaires"] : ["📝 Article", "📬 Commentaires"]}  | ||||
|     /> | ||||
| ); | ||||
|   <FunctionPage | ||||
|     FunctionTabManager={FunctionTabManager} | ||||
|     {...props} | ||||
|     tabNames={(props.type === 'form') ? ['⚙️ Utilisation', '📝 Article', '📬 Commentaires'] : ['📝 Article', '📬 Commentaires']} | ||||
|   /> | ||||
| ) | ||||
|  | ||||
| export async function getServerSideProps(context) { | ||||
|     const { slug } = context.params; | ||||
|     return api.get(`/functions/${slug}`) | ||||
|         .then((response) => ({ props: response.data })) | ||||
|         .catch(() => redirect(context, '/404')); | ||||
| export async function getServerSideProps (context) { | ||||
|   const { slug } = context.params | ||||
|   return api.get(`/functions/${slug}`) | ||||
|     .then((response) => ({ props: response.data })) | ||||
|     .catch(() => redirect(context, '/404')) | ||||
| } | ||||
|  | ||||
| export default FunctionComponent; | ||||
| export default FunctionComponent | ||||
|   | ||||
| @@ -1,124 +1,123 @@ | ||||
| import { useState } from 'react'; | ||||
| import Codepen from "react-codepen-embed"; | ||||
| import redirect from '../../utils/redirect'; | ||||
| import FunctionPage from '../../components/FunctionPage/FunctionPage'; | ||||
| import FunctionTabs from '../../components/FunctionPage/FunctionTabs'; | ||||
| import FunctionArticle from '../../components/FunctionPage/FunctionArticle'; | ||||
| import FunctionComments from '../../components/FunctionPage/FunctionComments/FunctionComments'; | ||||
| import Loader from '../../components/Loader'; | ||||
| import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; | ||||
| import { faPlay, faPause, faSync } from '@fortawesome/free-solid-svg-icons'; | ||||
| import api from '../../utils/api'; | ||||
| import '../../public/css/pages/FunctionComponent.css'; | ||||
| import '../../public/css/pages/functions/chronometerTimer.css'; | ||||
| import { useState } from 'react' | ||||
| import Codepen from 'react-codepen-embed' | ||||
| import redirect from '../../utils/redirect' | ||||
| import FunctionPage from '../../components/FunctionPage/FunctionPage' | ||||
| import FunctionTabs from '../../components/FunctionPage/FunctionTabs' | ||||
| import FunctionArticle from '../../components/FunctionPage/FunctionArticle' | ||||
| import FunctionComments from '../../components/FunctionPage/FunctionComments/FunctionComments' | ||||
| import Loader from '../../components/Loader' | ||||
| import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' | ||||
| import { faPlay, faPause, faSync } from '@fortawesome/free-solid-svg-icons' | ||||
| import api from '../../utils/api' | ||||
| import '../../public/css/pages/FunctionComponent.css' | ||||
| import '../../public/css/pages/functions/chronometerTimer.css' | ||||
|  | ||||
| let interval; | ||||
| function convertSeconds(seconds) { | ||||
|     return { | ||||
|         minutes: Math.floor(seconds / 60), | ||||
|         seconds: seconds % 60  | ||||
|     }; | ||||
| let interval | ||||
| function convertSeconds (seconds) { | ||||
|   return { | ||||
|     minutes: Math.floor(seconds / 60), | ||||
|     seconds: seconds % 60 | ||||
|   } | ||||
| } | ||||
|  | ||||
| const Chronometer = () => { | ||||
|   const [timeLength, setTimeLength] = useState(0) // seconds | ||||
|   const [isPlaying, setIsPlaying] = useState(false) | ||||
|  | ||||
|     const [timeLength, setTimeLength] = useState(0); // seconds | ||||
|     const [isPlaying, setIsPlaying]   = useState(false); | ||||
|  | ||||
|     const handlePlayPause = () => { | ||||
|         if (isPlaying) { | ||||
|             clearInterval(interval); | ||||
|         } else { | ||||
|             if (interval) clearInterval(interval); | ||||
|             interval = setInterval(() => { | ||||
|                 setTimeLength((time) => time + 1); | ||||
|             }, 1000); | ||||
|         } | ||||
|         setIsPlaying(!isPlaying); | ||||
|   const handlePlayPause = () => { | ||||
|     if (isPlaying) { | ||||
|       clearInterval(interval) | ||||
|     } else { | ||||
|       if (interval) clearInterval(interval) | ||||
|       interval = setInterval(() => { | ||||
|         setTimeLength((time) => time + 1) | ||||
|       }, 1000) | ||||
|     } | ||||
|     setIsPlaying(!isPlaying) | ||||
|   } | ||||
|  | ||||
|     const handleReset = () => { | ||||
|         if (interval) clearInterval(interval); | ||||
|         setIsPlaying(false); | ||||
|         setTimeLength(0); | ||||
|     } | ||||
|   const handleReset = () => { | ||||
|     if (interval) clearInterval(interval) | ||||
|     setIsPlaying(false) | ||||
|     setTimeLength(0) | ||||
|   } | ||||
|  | ||||
|     const getFormattedValue = () => { | ||||
|         const minutesAndSeconds = convertSeconds(timeLength); | ||||
|         const minutes = (minutesAndSeconds.minutes < 100) ? (('0'+minutesAndSeconds.minutes).slice(-2)) : minutesAndSeconds.minutes; | ||||
|         const seconds = ('0'+minutesAndSeconds.seconds).slice(-2); | ||||
|         return `${minutes}:${seconds}`; | ||||
|     } | ||||
|   const getFormattedValue = () => { | ||||
|     const minutesAndSeconds = convertSeconds(timeLength) | ||||
|     const minutes = (minutesAndSeconds.minutes < 100) ? (('0' + minutesAndSeconds.minutes).slice(-2)) : minutesAndSeconds.minutes | ||||
|     const seconds = ('0' + minutesAndSeconds.seconds).slice(-2) | ||||
|     return `${minutes}:${seconds}` | ||||
|   } | ||||
|  | ||||
|     return( | ||||
|         <div className="container-fluid"> | ||||
|             <div className="row justify-content-center"> | ||||
|                 <div className="col-sm-24 col-md-12"> | ||||
|                     <div className="Chronometer__container"> | ||||
|                         <div className="Chronometer__item"> | ||||
|                             <div className="Chronomter__row"> | ||||
|                                 <div className="Chronometer__time-left"> | ||||
|                                     {getFormattedValue()} | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|  | ||||
|                         <div className="Chronometer__item Chronometer__buttons"> | ||||
|                             <div className="Chronomter__row Chronometer__row-button"> | ||||
|                                 <button onClick={handlePlayPause} className="Chronometer-btn"> | ||||
|                                     <FontAwesomeIcon { ...(isPlaying) ? { icon: faPause } : { icon: faPlay } } /> | ||||
|                                 </button> | ||||
|                                 <button onClick={handleReset} className="Chronometer-btn" title="Remettre à zéro ?"> | ||||
|                                     <FontAwesomeIcon icon={faSync} /> | ||||
|                                 </button> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|   return ( | ||||
|     <div className='container-fluid'> | ||||
|       <div className='row justify-content-center'> | ||||
|         <div className='col-sm-24 col-md-12'> | ||||
|           <div className='Chronometer__container'> | ||||
|             <div className='Chronometer__item'> | ||||
|               <div className='Chronomter__row'> | ||||
|                 <div className='Chronometer__time-left'> | ||||
|                   {getFormattedValue()} | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|  | ||||
|             <div className='Chronometer__item Chronometer__buttons'> | ||||
|               <div className='Chronomter__row Chronometer__row-button'> | ||||
|                 <button onClick={handlePlayPause} className='Chronometer-btn'> | ||||
|                   <FontAwesomeIcon {...(isPlaying) ? { icon: faPause } : { icon: faPlay }} /> | ||||
|                 </button> | ||||
|                 <button onClick={handleReset} className='Chronometer-btn' title='Remettre à zéro ?'> | ||||
|                   <FontAwesomeIcon icon={faSync} /> | ||||
|                 </button> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|     ); | ||||
|       </div> | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| const Pomodoro = () => { | ||||
|     return ( | ||||
|         <div style={{ marginBottom: '50px' }} className="container-fluid"> | ||||
|             <Codepen hash="vYEbPoB" user="Divlo" height={800} defaultTab="result" preview={false} loader={() => <Loader />} /> | ||||
|         </div> | ||||
|     ); | ||||
|   return ( | ||||
|     <div style={{ marginBottom: '50px' }} className='container-fluid'> | ||||
|       <Codepen hash='vYEbPoB' user='Divlo' height={800} defaultTab='result' preview={false} loader={() => <Loader />} /> | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| const FunctionTabManager = (props) => { | ||||
|     return ( | ||||
|         <FunctionTabs setSlideIndex={props.setSlideIndex} slideIndex={props.slideIndex}> | ||||
|             <div className="FunctionComponent__slide"> | ||||
|                 <Chronometer /> | ||||
|             </div> | ||||
|             <div className="FunctionComponent__slide"> | ||||
|                 <Pomodoro /> | ||||
|             </div> | ||||
|             <div className="FunctionComponent__slide"> | ||||
|                 <FunctionArticle article={props.article} /> | ||||
|             </div> | ||||
|             <div className="FunctionComponent__slide"> | ||||
|                 <FunctionComments functionId={props.id} /> | ||||
|             </div> | ||||
|         </FunctionTabs> | ||||
|     ); | ||||
|   return ( | ||||
|     <FunctionTabs setSlideIndex={props.setSlideIndex} slideIndex={props.slideIndex}> | ||||
|       <div className='FunctionComponent__slide'> | ||||
|         <Chronometer /> | ||||
|       </div> | ||||
|       <div className='FunctionComponent__slide'> | ||||
|         <Pomodoro /> | ||||
|       </div> | ||||
|       <div className='FunctionComponent__slide'> | ||||
|         <FunctionArticle article={props.article} /> | ||||
|       </div> | ||||
|       <div className='FunctionComponent__slide'> | ||||
|         <FunctionComments functionId={props.id} /> | ||||
|       </div> | ||||
|     </FunctionTabs> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| const chronometerTimer = (props) => ( | ||||
|     <FunctionPage  | ||||
|         FunctionTabManager={FunctionTabManager} | ||||
|         { ...props } | ||||
|         tabNames={["⏰ Chronomètre", "⌛ Pomodoro", "📝 Article", "📬 Commentaires"]}  | ||||
|     /> | ||||
| ); | ||||
|   <FunctionPage | ||||
|     FunctionTabManager={FunctionTabManager} | ||||
|     {...props} | ||||
|     tabNames={['⏰ Chronomètre', '⌛ Pomodoro', '📝 Article', '📬 Commentaires']} | ||||
|   /> | ||||
| ) | ||||
|  | ||||
| export async function getServerSideProps(context) { | ||||
|     return api.get(`/functions/chronometerTimer`) | ||||
|         .then((response) => ({ props: response.data })) | ||||
|         .catch(() => redirect(context, '/404')); | ||||
| export async function getServerSideProps (context) { | ||||
|   return api.get('/functions/chronometerTimer') | ||||
|     .then((response) => ({ props: response.data })) | ||||
|     .catch(() => redirect(context, '/404')) | ||||
| } | ||||
|  | ||||
| export default chronometerTimer; | ||||
| export default chronometerTimer | ||||
|   | ||||
| @@ -1,22 +1,20 @@ | ||||
| import { Fragment } from 'react'; | ||||
| import HeadTag from '../../components/HeadTag'; | ||||
| import FunctionsList from '../../components/FunctionsList/FunctionsList'; | ||||
| import HeadTag from '../../components/HeadTag' | ||||
| import FunctionsList from '../../components/FunctionsList/FunctionsList' | ||||
|  | ||||
| const Functions = () => { | ||||
|   return ( | ||||
|     <> | ||||
|       <HeadTag | ||||
|         title='Fonctions' | ||||
|         description='Liste des fonctions.' | ||||
|         image='/images/FunctionProject_icon_small.png' | ||||
|       /> | ||||
|  | ||||
|     return ( | ||||
|         <Fragment> | ||||
|             <HeadTag  | ||||
|                 title="Fonctions"  | ||||
|                 description="Liste des fonctions."  | ||||
|                 image="/images/FunctionProject_icon_small.png"  | ||||
|             /> | ||||
|  | ||||
|             <FunctionsList> | ||||
|                 <h1 className="Functions__title">Fonctions</h1> | ||||
|             </FunctionsList> | ||||
|         </Fragment> | ||||
|     ); | ||||
|       <FunctionsList> | ||||
|         <h1 className='Functions__title'>Fonctions</h1> | ||||
|       </FunctionsList> | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default Functions; | ||||
| export default Functions | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user