🎨 Configure standardJS

This commit is contained in:
divlo 2020-08-03 12:04:07 +02:00
parent e22e62a749
commit 58f47c7480
120 changed files with 12271 additions and 10025 deletions

View File

@ -5,10 +5,9 @@
</p> </p>
<p align="center"> <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> <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"> <img src="https://img.shields.io/github/stars/Divlo/FunctionProject?style=social" alt="Stars">
<br/> <br/> <br/> <br/>
<a href="https://function.divlo.fr/"><img src="https://raw.githubusercontent.com/Divlo/FunctionProject/master/.github/FunctionProject.png" alt="FunctionProject" /></a> <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! 😃 Enjoy! 😃
[![JavaScript Style Guide](https://cdn.rawgit.com/standard/standard/master/badge.svg)](https://github.com/standard/standard)
## 📄 Licence ## 📄 Licence
Ce projet est sous licence MIT - voir le fichier [LICENSE](./LICENSE) pour plus de détails. Ce projet est sous licence MIT - voir le fichier [LICENSE](./LICENSE) pour plus de détails.

1
api/.gitignore vendored
View File

@ -18,6 +18,7 @@
.env.development.local .env.development.local
.env.test.local .env.test.local
.env.production.local .env.production.local
.env.production
/temp /temp
/assets/images/ /assets/images/

View File

@ -1,83 +1,83 @@
/* Modules */ /* Modules */
require('dotenv').config(); require('dotenv').config()
const path = require('path'); const path = require('path')
const express = require('express'); const express = require('express')
const helmet = require('helmet'); const helmet = require('helmet')
const cors = require('cors'); const cors = require('cors')
const morgan = require('morgan'); const morgan = require('morgan')
const redirectToHTTPS = require('express-http-to-https').redirectToHTTPS; const redirectToHTTPS = require('express-http-to-https').redirectToHTTPS
/* Files Imports & Variables */ /* Files Imports & Variables */
const sequelize = require('./assets/utils/database'); const sequelize = require('./assets/utils/database')
const { PORT } = require('./assets/config/config'); const { PORT } = require('./assets/config/config')
const errorHandling = require('./assets/utils/errorHandling'); const errorHandling = require('./assets/utils/errorHandling')
const isAuth = require('./middlewares/isAuth'); const isAuth = require('./middlewares/isAuth')
const isAdmin = require('./middlewares/isAdmin'); const isAdmin = require('./middlewares/isAdmin')
const app = express(); const app = express()
/* Middlewares */ /* Middlewares */
app.use(helmet()); app.use(helmet())
app.use(cors()); app.use(cors())
app.use(morgan('dev')); app.use(morgan('dev'))
app.use(express.json()); app.use(express.json())
app.use(redirectToHTTPS([/localhost:(\d{4})/])); app.use(redirectToHTTPS([/localhost:(\d{4})/]))
/* Routes */ /* Routes */
app.use('/images', express.static(path.join(__dirname, "assets", "images"))); app.use('/images', express.static(path.join(__dirname, 'assets', 'images')))
app.use('/functions', require('./routes/functions')); app.use('/functions', require('./routes/functions'))
app.use('/categories', require('./routes/categories')); app.use('/categories', require('./routes/categories'))
app.use('/users', require('./routes/users')); app.use('/users', require('./routes/users'))
app.use('/admin', isAuth, isAdmin, require('./routes/admin')); app.use('/admin', isAuth, isAdmin, require('./routes/admin'))
app.use('/favorites', require('./routes/favorites')); app.use('/favorites', require('./routes/favorites'))
app.use('/comments', require('./routes/comments')); app.use('/comments', require('./routes/comments'))
app.use('/quotes', require('./routes/quotes')); app.use('/quotes', require('./routes/quotes'))
app.use('/tasks', require('./routes/tasks')); app.use('/tasks', require('./routes/tasks'))
/* Errors Handling */ /* 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) => { app.use((error, _req, res, _next) => {
console.log(error); console.log(error)
const { statusCode, message } = error; const { statusCode, message } = error
return res.status(statusCode || 500).json({ message }); return res.status(statusCode || 500).json({ message })
}); })
/* Database Relations */ /* Database Relations */
const Functions = require('./models/functions'); const Functions = require('./models/functions')
const Categories = require('./models/categories'); const Categories = require('./models/categories')
const Users = require('./models/users'); const Users = require('./models/users')
const Favorites = require('./models/favorites'); const Favorites = require('./models/favorites')
const Comments = require('./models/comments'); const Comments = require('./models/comments')
const Quotes = require('./models/quotes'); const Quotes = require('./models/quotes')
const Tasks = require('./models/tasks'); const Tasks = require('./models/tasks')
// A function has a category // A function has a category
Categories.hasOne(Functions, { constraints: true, onDelete: 'CASCADE'}); Categories.hasOne(Functions, { constraints: true, onDelete: 'CASCADE' })
Functions.belongsTo(Categories); Functions.belongsTo(Categories)
// Users can have favorites functions // Users can have favorites functions
Users.hasMany(Favorites); Users.hasMany(Favorites)
Favorites.belongsTo(Users, { constraints: false }); Favorites.belongsTo(Users, { constraints: false })
Functions.hasMany(Favorites); Functions.hasMany(Favorites)
Favorites.belongsTo(Functions, { constraints: false }); Favorites.belongsTo(Functions, { constraints: false })
// Users can post comments on functions // Users can post comments on functions
Users.hasMany(Comments); Users.hasMany(Comments)
Comments.belongsTo(Users, { constraints: false }); Comments.belongsTo(Users, { constraints: false })
Functions.hasMany(Comments); Functions.hasMany(Comments)
Comments.belongsTo(Functions, { constraints: false }); Comments.belongsTo(Functions, { constraints: false })
// Users can suggest new quotes // Users can suggest new quotes
Users.hasMany(Quotes); Users.hasMany(Quotes)
Quotes.belongsTo(Users, { constraints: false }); Quotes.belongsTo(Users, { constraints: false })
// Users can have tasks // Users can have tasks
Users.hasMany(Tasks); Users.hasMany(Tasks)
Tasks.belongsTo(Users, { constraints: false }); Tasks.belongsTo(Users, { constraints: false })
/* Server */ /* Server */
// sequelize.sync({ force: true }) // sequelize.sync({ force: true })
sequelize.sync() sequelize.sync()
.then(() => { .then(() => {
app.listen(PORT, () => console.log('\x1b[36m%s\x1b[0m', `Started on port ${PORT}.`)); app.listen(PORT, () => console.log('\x1b[36m%s\x1b[0m', `Started on port ${PORT}.`))
}) })
.catch((error) => console.log(error)); .catch((error) => console.log(error))

View File

@ -21,6 +21,6 @@ const config = {
} }
}, },
TOKEN_LIFE: '1 week' TOKEN_LIFE: '1 week'
}; }
module.exports = config; module.exports = config

View File

@ -26,7 +26,7 @@ exports.emailQuoteTemplate = (isValid, quote, frontendLink) => `
<tr> <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;"> <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;"> <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> </h2>
<p style="margin: 0 0 12px 0;"> <p style="margin: 0 0 12px 0;">
<a style="color: #ffd800;" href="${frontendLink}/functions/randomQuote">Lien vers la fonction randomQuote de FunctionProject.</a> <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;"> <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>. 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> </p>
` : ""} ` : ''}
<div> <div>
<p style="padding:0 0 10px 0"> <p style="padding:0 0 10px 0">
La citation en question : <br/> La citation en question : <br/>
@ -57,7 +57,7 @@ exports.emailQuoteTemplate = (isValid, quote, frontendLink) => `
</tbody> </tbody>
</table> </table>
</center> </center>
`; `
exports.emailUserTemplate = (subtitle, buttonText, url, footerText) => ` exports.emailUserTemplate = (subtitle, buttonText, url, footerText) => `
<center> <center>
@ -107,4 +107,4 @@ exports.emailUserTemplate = (subtitle, buttonText, url, footerText) => `
</tbody> </tbody>
</table> </table>
</center> </center>
`; `

View File

@ -10,9 +10,9 @@ const errors = {
}, },
requiredFields: { requiredFields: {
message: "Vous devez remplir tous les champs...", message: 'Vous devez remplir tous les champs...',
statusCode: 400 statusCode: 400
} }
}; }
module.exports = errors; module.exports = errors

View File

@ -1,6 +1,6 @@
const nodemailer = require('nodemailer'); const nodemailer = require('nodemailer')
const { EMAIL_INFO } = require('./config'); const { EMAIL_INFO } = require('./config')
const transporter = nodemailer.createTransport(EMAIL_INFO); const transporter = nodemailer.createTransport(EMAIL_INFO)
module.exports = transporter; module.exports = transporter

View File

@ -1,20 +1,20 @@
const { randomNumberOutput } = require('./main/randomNumber'); const { randomNumberOutput } = require('./main/randomNumber')
const convertRomanArabicNumbersOutput = require('./main/convertRomanArabicNumbers'); const convertRomanArabicNumbersOutput = require('./main/convertRomanArabicNumbers')
const convertDistanceOutput = require('./main/convertDistance'); const convertDistanceOutput = require('./main/convertDistance')
const convertTemperatureOutput = require('./main/convertTemperature'); const convertTemperatureOutput = require('./main/convertTemperature')
const armstrongNumberOutput = require('./main/armstrongNumber'); const armstrongNumberOutput = require('./main/armstrongNumber')
const weatherRequestOutput = require('./main/weatherRequest'); const weatherRequestOutput = require('./main/weatherRequest')
const convertCurrencyOutput = require('./main/convertCurrency'); const convertCurrencyOutput = require('./main/convertCurrency')
const calculateAgeOutput = require('./main/calculateAge'); const calculateAgeOutput = require('./main/calculateAge')
const heapAlgorithmOutput = require('./main/heapAlgorithm'); const heapAlgorithmOutput = require('./main/heapAlgorithm')
const convertEncodingOutput = require('./main/convertEncoding'); const convertEncodingOutput = require('./main/convertEncoding')
const randomQuote = require('./main/randomQuote'); const randomQuote = require('./main/randomQuote')
const linkShortener = require('./main/linkShortener'); const linkShortener = require('./main/linkShortener')
const rightPriceOutput = require('./main/rightPrice'); const rightPriceOutput = require('./main/rightPrice')
const isPalindromeOutput = require('./main/isPalindrome'); const isPalindromeOutput = require('./main/isPalindrome')
const findLongestWordOutput = require('./main/findLongestWord'); const findLongestWordOutput = require('./main/findLongestWord')
const fibonacciOutput = require('./main/fibonacci'); const fibonacciOutput = require('./main/fibonacci')
const sortArrayOutput = require('./main/sortArray'); const sortArrayOutput = require('./main/sortArray')
const functionObject = { const functionObject = {
randomNumber: randomNumberOutput, randomNumber: randomNumberOutput,
@ -33,12 +33,12 @@ const functionObject = {
isPalindrome: isPalindromeOutput, isPalindrome: isPalindromeOutput,
findLongestWord: findLongestWordOutput, findLongestWord: findLongestWordOutput,
fibonacci: fibonacciOutput, fibonacci: fibonacciOutput,
sortArray : sortArrayOutput, sortArray: sortArrayOutput
}; }
// Choisi la fonction à exécuter // Choisi la fonction à exécuter
function functionToExecute (option) { function functionToExecute (option) {
return functionObject[option]; return functionObject[option]
} }
module.exports = functionToExecute; module.exports = functionToExecute

View File

@ -1,6 +1,6 @@
const errorHandling = require('../../utils/errorHandling'); const errorHandling = require('../../utils/errorHandling')
const { requiredFields } = require('../../config/errors'); const { requiredFields } = require('../../config/errors')
const formatNumberResult = require('../secondary/formatNumberResult'); const formatNumberResult = require('../secondary/formatNumberResult')
/** /**
* @description Vérifie si un nombre fait partie des nombres d'Armstrong. * @description Vérifie si un nombre fait partie des nombres d'Armstrong.
@ -9,38 +9,38 @@ const formatNumberResult = require('../secondary/formatNumberResult');
* @examples armstrongNumber(153) 153 est un nombre d'Armstrong, car 1<sup>3</sup> + 5<sup>3</sup> + 3<sup>3</sup> = 153. * @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) { function armstrongNumber (number) {
let numberString = number.toString(); const numberString = number.toString()
let numberStringLength = numberString.length; const numberStringLength = numberString.length
let result = 0; let result = 0
let resultString = ""; let resultString = ''
for (let index = 0; index < numberStringLength; index++) { for (let index = 0; index < numberStringLength; index++) {
result = result + parseInt(numberString[index]) ** numberStringLength; result = result + parseInt(numberString[index]) ** numberStringLength
resultString = resultString + " + " + numberString[index] + "<sup>" + numberStringLength + "</sup>"; resultString = resultString + ' + ' + numberString[index] + '<sup>' + numberStringLength + '</sup>'
} }
const formattedNumber = formatNumberResult(number); const formattedNumber = formatNumberResult(number)
const isArmstrongNumber = (result === number); const isArmstrongNumber = (result === number)
return { return {
isArmstrongNumber, isArmstrongNumber,
resultHTML: `<p>${formattedNumber} ${isArmstrongNumber ? "est" : "n'est pas"} un nombre d'Armstrong, car ${resultString.slice(2)} = ${formatNumberResult(result)}.</p>` resultHTML: `<p>${formattedNumber} ${isArmstrongNumber ? 'est' : "n'est pas"} un nombre d'Armstrong, car ${resultString.slice(2)} = ${formatNumberResult(result)}.</p>`
} }
} }
/* OUTPUTS */ /* OUTPUTS */
module.exports = armstrongNumberOutput = ({ res, next }, argsObject) => { module.exports = ({ res, next }, argsObject) => {
let { number } = argsObject; let { number } = argsObject
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(number)) { if (!(number)) {
return errorHandling(next, requiredFields); return errorHandling(next, requiredFields)
} }
// Si ce n'est pas un nombre // Si ce n'est pas un nombre
number = parseInt(number); number = parseInt(number)
if (isNaN(number) || number <= 0) { if (isNaN(number) || number <= 0) {
return errorHandling(next, { message: "Veuillez rentré un nombre valide.", statusCode: 400 }); return errorHandling(next, { message: 'Veuillez rentré un nombre valide.', statusCode: 400 })
} }
return res.status(200).json(armstrongNumber(number)); return res.status(200).json(armstrongNumber(number))
} }

View File

@ -1,50 +1,50 @@
const errorHandling = require('../../utils/errorHandling'); const errorHandling = require('../../utils/errorHandling')
const moment = require('moment'); const moment = require('moment')
const { requiredFields } = require('../../config/errors'); const { requiredFields } = require('../../config/errors')
function calculateAge (currentDate, { birthDateDay, birthDateMonth, birthDateYear }) { function calculateAge (currentDate, { birthDateDay, birthDateMonth, birthDateYear }) {
const day = currentDate.getDate(); const day = currentDate.getDate()
const month = currentDate.getMonth(); const month = currentDate.getMonth()
const currentDateMoment = moment([currentDate.getFullYear(), month, day]); const currentDateMoment = moment([currentDate.getFullYear(), month, day])
const birthDateMoment = moment([birthDateYear, birthDateMonth, birthDateDay]); const birthDateMoment = moment([birthDateYear, birthDateMonth, birthDateDay])
// Calcule l'âge - Moment.js // Calcule l'âge - Moment.js
const ageYears = currentDateMoment.diff(birthDateMoment, 'year'); const ageYears = currentDateMoment.diff(birthDateMoment, 'year')
birthDateMoment.add(ageYears, 'years'); birthDateMoment.add(ageYears, 'years')
const ageMonths = currentDateMoment.diff(birthDateMoment, 'months'); const ageMonths = currentDateMoment.diff(birthDateMoment, 'months')
birthDateMoment.add(ageMonths, 'months'); birthDateMoment.add(ageMonths, 'months')
const ageDays = currentDateMoment.diff(birthDateMoment, 'days'); const ageDays = currentDateMoment.diff(birthDateMoment, 'days')
const isBirthday = (birthDateDay === day && birthDateMonth === month); const isBirthday = (birthDateDay === day && birthDateMonth === month)
return { ageYears, ageMonths, ageDays, isBirthday }; return { ageYears, ageMonths, ageDays, isBirthday }
} }
/* OUTPUTS */ /* OUTPUTS */
module.exports = calculateAgeOutput = ({ res, next }, argsObject) => { module.exports = ({ res, next }, argsObject) => {
let { birthDate } = argsObject; const { birthDate } = argsObject
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(birthDate)) { if (!(birthDate)) {
return errorHandling(next, requiredFields); return errorHandling(next, requiredFields)
} }
const birthDateDay = parseInt(birthDate.substring(0, 2)); const birthDateDay = parseInt(birthDate.substring(0, 2))
const birthDateMonth = parseInt((birthDate.substring(3, 5)) - 1); const birthDateMonth = parseInt((birthDate.substring(3, 5)) - 1)
const birthDateYear = parseInt(birthDate.substring(6, 10)); const birthDateYear = parseInt(birthDate.substring(6, 10))
// Si ce n'est pas une date valide // Si ce n'est pas une date valide
const currentDate = new Date(); const currentDate = new Date()
const birthDateObject = new Date(birthDateYear, birthDateMonth, birthDateDay); const birthDateObject = new Date(birthDateYear, birthDateMonth, birthDateDay)
const result = calculateAge(currentDate, { birthDateYear, birthDateMonth, birthDateDay }); const result = calculateAge(currentDate, { birthDateYear, birthDateMonth, birthDateDay })
if ((currentDate < birthDateObject) || isNaN(result.ageYears)) { if ((currentDate < birthDateObject) || isNaN(result.ageYears)) {
return errorHandling(next, { message: "Veuillez rentré une date valide...", statusCode: 400 }); return errorHandling(next, { message: 'Veuillez rentré une date valide...', statusCode: 400 })
} }
let resultHTML; let resultHTML
if (result.isBirthday) { if (result.isBirthday) {
resultHTML = `<p>Vous avez ${result.ageYears} ans. Joyeux Anniversaire! 🥳</p>`; resultHTML = `<p>Vous avez ${result.ageYears} ans. Joyeux Anniversaire! 🥳</p>`
} else { } else {
resultHTML = `<p>Vous avez ${result.ageYears} ans, ${result.ageMonths} mois et ${result.ageDays} jour(s).</p>`; resultHTML = `<p>Vous avez ${result.ageYears} ans, ${result.ageMonths} mois et ${result.ageDays} jour(s).</p>`
} }
return res.status(200).json({ ...result, resultHTML }); return res.status(200).json({ ...result, resultHTML })
} }

View File

@ -1,37 +1,37 @@
const axios = require('axios'); const axios = require('axios')
const errorHandling = require('../../utils/errorHandling'); const errorHandling = require('../../utils/errorHandling')
const { requiredFields } = require('../../config/errors'); const { requiredFields } = require('../../config/errors')
const formatNumberResult = require('../secondary/formatNumberResult'); const formatNumberResult = require('../secondary/formatNumberResult')
/* OUTPUTS */ /* OUTPUTS */
module.exports = convertCurrencyOutput = ({ res, next }, argsObject) => { module.exports = ({ res, next }, argsObject) => {
let { number, baseCurrency, finalCurrency } = argsObject; let { number, baseCurrency, finalCurrency } = argsObject
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(number && baseCurrency && finalCurrency)) { if (!(number && baseCurrency && finalCurrency)) {
return errorHandling(next, requiredFields); return errorHandling(next, requiredFields)
} }
// Si ce n'est pas un nombre // Si ce n'est pas un nombre
number = parseFloat(number); number = parseFloat(number)
if (isNaN(number)) { if (isNaN(number)) {
return errorHandling(next, { message: "Veuillez rentré un nombre valide.", statusCode: 400 }); return errorHandling(next, { message: 'Veuillez rentré un nombre valide.', statusCode: 400 })
} }
axios.get(`https://api.exchangeratesapi.io/latest?base=${baseCurrency}`) axios.get(`https://api.exchangeratesapi.io/latest?base=${baseCurrency}`)
.then((response) => { .then((response) => {
const rate = response.data.rates[finalCurrency]; const rate = response.data.rates[finalCurrency]
if (!rate) { if (!rate) {
return errorHandling(next, { message: "La devise n'existe pas.", statusCode: 404 }); return errorHandling(next, { message: "La devise n'existe pas.", statusCode: 404 })
} }
const result = rate * number; const result = rate * number
const dateObject = new Date(response.data.date); const dateObject = new Date(response.data.date)
const year = dateObject.getFullYear(); const year = dateObject.getFullYear()
const day = ('0'+(dateObject.getDate())).slice(-2); const day = ('0' + (dateObject.getDate())).slice(-2)
const month = ('0'+(dateObject.getMonth()+1)).slice(-2); const month = ('0' + (dateObject.getMonth() + 1)).slice(-2)
const date = `${day}/${month}/${year}`; 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>`; 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 }); return res.status(200).json({ date, result, resultHTML })
}) })
.catch(() => errorHandling(next, { message: "La devise n'existe pas.", statusCode: 404 })); .catch(() => errorHandling(next, { message: "La devise n'existe pas.", statusCode: 404 }))
} }

View File

@ -1,8 +1,8 @@
const errorHandling = require('../../utils/errorHandling'); const errorHandling = require('../../utils/errorHandling')
const { requiredFields, generalError } = require('../../config/errors'); const { requiredFields, generalError } = require('../../config/errors')
const formatNumberResult = require('../secondary/formatNumberResult'); 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. * @description Convertis la longueur (distance) avec les unités allant de picomètre au Téramètre.
@ -14,38 +14,38 @@ const correspondancesDistance = ["pm", null, null, "nm", null, null, "µm", null
* @examples convertDistance(500, 'cm', 'm') { resultNumber: 5, resultString: "5 m" } * @examples convertDistance(500, 'cm', 'm') { resultNumber: 5, resultString: "5 m" }
*/ */
function convertDistance (firstValue, unitFirstValue, unitFinalValue) { function convertDistance (firstValue, unitFirstValue, unitFinalValue) {
const index1 = correspondancesDistance.indexOf(unitFirstValue); const index1 = correspondancesDistance.indexOf(unitFirstValue)
const index2 = correspondancesDistance.indexOf(unitFinalValue); const index2 = correspondancesDistance.indexOf(unitFinalValue)
if (index1 !== -1 && index2 !== -1) { if (index1 !== -1 && index2 !== -1) {
const difference = index1 - index2; const difference = index1 - index2
const result = firstValue * Math.pow(10, difference); const result = firstValue * Math.pow(10, difference)
return { return {
result, result,
resultHTML: `<p>${formatNumberResult(firstValue)} ${unitFirstValue} = ${formatNumberResult(result)} ${unitFinalValue}</p>` resultHTML: `<p>${formatNumberResult(firstValue)} ${unitFirstValue} = ${formatNumberResult(result)} ${unitFinalValue}</p>`
};
} }
return false; }
return false
} }
/* OUTPUTS */ /* OUTPUTS */
module.exports = convertDistanceOutput = ({ res, next }, argsObject) => { module.exports = ({ res, next }, argsObject) => {
let { number, numberUnit, finalUnit } = argsObject; let { number, numberUnit, finalUnit } = argsObject
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(number && numberUnit && finalUnit)) { if (!(number && numberUnit && finalUnit)) {
return errorHandling(next, requiredFields); return errorHandling(next, requiredFields)
} }
// Si ce n'est pas un nombre // Si ce n'est pas un nombre
number = parseFloat(number); number = parseFloat(number)
if (isNaN(number)) { if (isNaN(number)) {
return errorHandling(next, { message: "Veuillez rentré un nombre valide.", statusCode: 400 }); return errorHandling(next, { message: 'Veuillez rentré un nombre valide.', statusCode: 400 })
} }
const result = convertDistance(number, numberUnit, finalUnit); const result = convertDistance(number, numberUnit, finalUnit)
if (!result) { if (!result) {
return errorHandling(next, generalError); return errorHandling(next, generalError)
} }
return res.status(200).json(result); return res.status(200).json(result)
} }

View File

@ -1,5 +1,5 @@
const errorHandling = require('../../utils/errorHandling'); const errorHandling = require('../../utils/errorHandling')
const { requiredFields, generalError } = require('../../config/errors'); const { requiredFields, generalError } = require('../../config/errors')
/** /**
* @description Convertis un nombre décimal en binaire. * @description Convertis un nombre décimal en binaire.
@ -8,11 +8,11 @@ const { requiredFields, generalError } = require('../../config/errors');
* @examples decimalToBinary('2') '10' * @examples decimalToBinary('2') '10'
*/ */
function decimalToBinary (value) { function decimalToBinary (value) {
value = Number(value); value = Number(value)
if (isNaN(value)) { if (isNaN(value)) {
return false; return false
} else { } else {
return value.toString(2); return value.toString(2)
} }
} }
@ -23,11 +23,11 @@ function decimalToBinary(value) {
* @examples binaryToDecimal('10') 2 * @examples binaryToDecimal('10') 2
*/ */
function binaryToDecimal (value) { function binaryToDecimal (value) {
const result = parseInt(Number(value), 2); const result = parseInt(Number(value), 2)
if (isNaN(result)) { if (isNaN(result)) {
return false; return false
} else { } else {
return result; return result
} }
} }
@ -38,11 +38,11 @@ function binaryToDecimal(value) {
* @examples decimalToHexadecimal('15') 'F' * @examples decimalToHexadecimal('15') 'F'
*/ */
function decimalToHexadecimal (value) { function decimalToHexadecimal (value) {
value = Number(value); value = Number(value)
if (isNaN(value)) { if (isNaN(value)) {
return false; return false
} else { } else {
return value.toString(16).toUpperCase(); return value.toString(16).toUpperCase()
} }
} }
@ -53,11 +53,11 @@ function decimalToHexadecimal(value) {
* @examples hexadecimalToDecimal('F') 15 * @examples hexadecimalToDecimal('F') 15
*/ */
function hexadecimalToDecimal (value) { function hexadecimalToDecimal (value) {
const result = parseInt(value, 16); const result = parseInt(value, 16)
if (isNaN(result)) { if (isNaN(result)) {
return false; return false
} else { } else {
return result; return result
} }
} }
@ -68,12 +68,12 @@ function hexadecimalToDecimal(value) {
* @examples binaryToHexadecimal('1111') 'F' * @examples binaryToHexadecimal('1111') 'F'
*/ */
function binaryToHexadecimal (value) { function binaryToHexadecimal (value) {
value = Number(value); value = Number(value)
value = parseInt(value, 2); value = parseInt(value, 2)
if (isNaN(value)) { if (isNaN(value)) {
return false; return false
} else { } else {
return parseInt(value).toString(16).toUpperCase(); return parseInt(value).toString(16).toUpperCase()
} }
} }
@ -84,11 +84,11 @@ function binaryToHexadecimal(value) {
* @examples hexadecimalToBinary('F') '1111' * @examples hexadecimalToBinary('F') '1111'
*/ */
function hexadecimalToBinary (value) { function hexadecimalToBinary (value) {
value = parseInt(value, 16); value = parseInt(value, 16)
if (isNaN(value)) { if (isNaN(value)) {
return false; return false
} else { } else {
return parseInt(value).toString(2); return parseInt(value).toString(2)
} }
} }
@ -102,14 +102,13 @@ function hexadecimalToBinary(value) {
*/ */
function textToNumberUnicode (string) { function textToNumberUnicode (string) {
try { try {
let resultat = ""; let resultat = ''
for (let index in string) { for (const index in string) {
resultat = resultat + string.codePointAt(index) + " "; resultat = resultat + string.codePointAt(index) + ' '
} }
return resultat; return resultat
} } catch (error) {
catch(error) { return false
return false;
} }
} }
@ -121,15 +120,14 @@ function textToNumberUnicode(string) {
*/ */
function numberUnicodeToText (string) { function numberUnicodeToText (string) {
try { try {
const array = string.split(" "); const array = string.split(' ')
let resultat = ""; let resultat = ''
for (let index in array) { for (const index in array) {
resultat += String.fromCodePoint(parseInt(array[index]).toString()); resultat += String.fromCodePoint(parseInt(array[index]).toString())
} }
return resultat; return resultat
} } catch (error) {
catch(error) { return false
return false;
} }
} }
@ -141,16 +139,16 @@ function numberUnicodeToText(string) {
*/ */
function textToBinary (s) { function textToBinary (s) {
try { try {
s = unescape( encodeURIComponent(s)); s = unescape(encodeURIComponent(s))
let chr, i = 0, l = s.length, out = ''; let chr; let i = 0; const l = s.length; let out = ''
for (; i < l; i++) { for (; i < l; i++) {
chr = s.charCodeAt( i ).toString(2); chr = s.charCodeAt(i).toString(2)
while(chr.length % 8 != 0 ){ chr = '0' + chr; } while (chr.length % 8 !== 0) { chr = '0' + chr }
out += chr; out += chr
} }
return out.replace(/(\d{8})/g, '$1 ').replace(/(^\s+|\s+$)/,''); return out.replace(/(\d{8})/g, '$1 ').replace(/(^\s+|\s+$)/, '')
} catch (error) { } catch (error) {
return false; return false
} }
} }
@ -163,14 +161,14 @@ function textToBinary(s) {
function binaryToText (s) { function binaryToText (s) {
try { try {
s = s.replace(/\s/g, '') s = s.replace(/\s/g, '')
let i = 0, l = s.length, chr, out = ''; let i = 0; const l = s.length; let chr; let out = ''
for (; i < l; i += 8) { for (; i < l; i += 8) {
chr = parseInt( s.substr(i, 8 ), 2).toString(16); chr = parseInt(s.substr(i, 8), 2).toString(16)
out += '%' + ((chr.length % 2 == 0) ? chr : '0' + chr); out += '%' + ((chr.length % 2 === 0) ? chr : '0' + chr)
} }
return decodeURIComponent(out); return decodeURIComponent(out)
} catch (error) { } catch (error) {
return false; return false
} }
} }
@ -182,17 +180,16 @@ function binaryToText(s){
*/ */
function textToHexadecimal (s) { function textToHexadecimal (s) {
try { try {
s = unescape( encodeURIComponent( s ) ); s = unescape(encodeURIComponent(s))
let chr, i = 0, l = s.length, out = ''; let chr; let i = 0; const l = s.length; let out = ''
for (; i < l; i++) { for (; i < l; i++) {
chr = s.charCodeAt( i ).toString( 16 ); chr = s.charCodeAt(i).toString(16)
out += ( chr.length % 2 == 0 ) ? chr : '0' + chr; out += (chr.length % 2 === 0) ? chr : '0' + chr
out += " "; out += ' '
} }
return out.toUpperCase(); return out.toUpperCase()
} } catch (error) {
catch (error) { return false
return false;
} }
} }
@ -204,39 +201,39 @@ function textToHexadecimal (s) {
*/ */
function hexadecimalToText (s) { function hexadecimalToText (s) {
try { try {
s = s.replace(/\s/g,''); s = s.replace(/\s/g, '')
return decodeURIComponent( s.replace( /../g, '%$&' ) ); return decodeURIComponent(s.replace(/../g, '%$&'))
} } catch (error) {
catch (error) { return false
return false;
} }
} }
/* OUTPUTS */ /* OUTPUTS */
const convertEncoding = { decimalToBinary, binaryToDecimal, decimalToHexadecimal, hexadecimalToDecimal, binaryToHexadecimal, hexadecimalToBinary, textToNumberUnicode, numberUnicodeToText, textToBinary, binaryToText, textToHexadecimal, hexadecimalToText }; const convertEncoding = { decimalToBinary, binaryToDecimal, decimalToHexadecimal, hexadecimalToDecimal, binaryToHexadecimal, hexadecimalToBinary, textToNumberUnicode, numberUnicodeToText, textToBinary, binaryToText, textToHexadecimal, hexadecimalToText }
function executeFunction (option, value) { function executeFunction (option, value) {
return convertEncoding[option](value); return convertEncoding[option](value)
} }
module.exports = convertEncodingOutput = ({ res, next }, argsObject) => { module.exports = ({ res, next }, argsObject) => {
let { value, functionName } = argsObject; const { value, functionName } = argsObject
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(value && functionName)) { if (!(value && functionName)) {
return errorHandling(next, requiredFields); return errorHandling(next, requiredFields)
} }
// Si la fonction n'existe pas // Si la fonction n'existe pas
// eslint-disable-next-line
if (!convertEncoding.hasOwnProperty(functionName)) { if (!convertEncoding.hasOwnProperty(functionName)) {
return errorHandling(next, { message: "Cette conversion n'existe pas.", statusCode: 400 }); return errorHandling(next, { message: "Cette conversion n'existe pas.", statusCode: 400 })
} }
const result = executeFunction(functionName, value); const result = executeFunction(functionName, value)
// Mauvaise valeur entrée // Mauvaise valeur entrée
if (!result) { if (!result) {
return errorHandling(next, generalError); return errorHandling(next, generalError)
} }
return res.status(200).json({ result, resultHTML: `<p>${result}</p>` }); return res.status(200).json({ result, resultHTML: `<p>${result}</p>` })
} }

View File

@ -1,23 +1,23 @@
const errorHandling = require('../../utils/errorHandling'); const errorHandling = require('../../utils/errorHandling')
const { requiredFields, generalError } = require('../../config/errors'); const { requiredFields, generalError } = require('../../config/errors')
const formatNumberResult = require('../secondary/formatNumberResult'); const formatNumberResult = require('../secondary/formatNumberResult')
/* Variable pour convertRomanArabicNumbers */ /* Variable pour convertRomanArabicNumbers */
const correspondancesRomainArabe = [ const correspondancesRomainArabe = [
[1000, "M"], [1000, 'M'],
[900, "CM"], [900, 'CM'],
[500, "D"], [500, 'D'],
[400, "CD"], [400, 'CD'],
[100, "C"], [100, 'C'],
[90, "XC"], [90, 'XC'],
[50, "L"], [50, 'L'],
[40, "XL"], [40, 'XL'],
[10, "X"], [10, 'X'],
[9, "IX"], [9, 'IX'],
[5, "V"], [5, 'V'],
[4, "IV"], [4, 'IV'],
[1, "I"], [1, 'I']
]; ]
/** /**
* @description Convertis un nombre arabe en nombre romain. * @description Convertis un nombre arabe en nombre romain.
@ -27,20 +27,20 @@ const correspondancesRomainArabe = [
*/ */
function convertArabicToRoman (nombre) { function convertArabicToRoman (nombre) {
// Initialisation de la variable qui va contenir le résultat de la conversion // Initialisation de la variable qui va contenir le résultat de la conversion
let chiffresRomains = ""; let chiffresRomains = ''
function extraireChiffreRomain (valeurLettre, lettres) { function extraireChiffreRomain (valeurLettre, lettres) {
while (nombre >= valeurLettre) { while (nombre >= valeurLettre) {
chiffresRomains = chiffresRomains + lettres; chiffresRomains = chiffresRomains + lettres
nombre = nombre - valeurLettre; nombre = nombre - valeurLettre
} }
} }
correspondancesRomainArabe.forEach(correspondance => { correspondancesRomainArabe.forEach(correspondance => {
extraireChiffreRomain(correspondance[0], correspondance[1]); extraireChiffreRomain(correspondance[0], correspondance[1])
}); })
return chiffresRomains; return chiffresRomains
} }
/** /**
@ -50,74 +50,73 @@ function convertArabicToRoman(nombre) {
* @example convertRomanToArabic('XXIV') 24 * @example convertRomanToArabic('XXIV') 24
*/ */
function convertRomanToArabic (string) { function convertRomanToArabic (string) {
let result = 0; let result = 0
correspondancesRomainArabe.forEach((correspondance) => { correspondancesRomainArabe.forEach((correspondance) => {
while (string.indexOf(correspondance[1]) === 0) { while (string.indexOf(correspondance[1]) === 0) {
// Ajout de la valeur décimale au résultat // Ajout de la valeur décimale au résultat
result += correspondance[0]; result += correspondance[0]
// Supprimer la lettre romaine correspondante du début // Supprimer la lettre romaine correspondante du début
string = string.replace(correspondance[1], ''); string = string.replace(correspondance[1], '')
} }
}); })
if (string != '') { if (string !== '') {
result = 0; result = 0
} }
return result; return result
} }
/* OUTPUTS */ /* OUTPUTS */
const convertRomanToArabicOutput = ({ res, next }, number) => { const convertRomanToArabicOutput = ({ res, next }, number) => {
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(number)) { if (!(number)) {
return errorHandling(next, requiredFields); return errorHandling(next, requiredFields)
} }
// Formate le paramètre // Formate le paramètre
number = number.toUpperCase(); number = number.toUpperCase()
const result = convertRomanToArabic(number); const result = convertRomanToArabic(number)
if (result === 0) { if (result === 0) {
return errorHandling(next, generalError); 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>` }); 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) => { const convertArabicToRomanOutput = ({ res, next }, number) => {
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(number)) { if (!(number)) {
return errorHandling(next, requiredFields); return errorHandling(next, requiredFields)
} }
// Si ce n'est pas un nombre // Si ce n'est pas un nombre
number = parseInt(number); number = parseInt(number)
if (isNaN(number)) { if (isNaN(number)) {
return errorHandling(next, { message: "Veuillez rentré un nombre valide.", statusCode: 400 }); return errorHandling(next, { message: 'Veuillez rentré un nombre valide.', statusCode: 400 })
} }
const result = convertArabicToRoman(number); 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>` }); 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 }; const convertRomanArabicObject = { convertRomanToArabicOutput, convertArabicToRomanOutput }
function executeFunction (option, value, { res, next }) { function executeFunction (option, value, { res, next }) {
return convertRomanArabicObject[option]({ res, next}, value); return convertRomanArabicObject[option]({ res, next }, value)
} }
module.exports = convertRomanArabicNumbersOutput = ({ res, next }, argsObject) => { module.exports = ({ res, next }, argsObject) => {
let { value, functionName } = argsObject; const { value, functionName } = argsObject
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(value && functionName)) { if (!(value && functionName)) {
return errorHandling(next, requiredFields); return errorHandling(next, requiredFields)
} }
// Si la fonction n'existe pas // Si la fonction n'existe pas
// eslint-disable-next-line
if (!convertRomanArabicObject.hasOwnProperty(functionName)) { if (!convertRomanArabicObject.hasOwnProperty(functionName)) {
return errorHandling(next, { message: "Cette conversion n'existe pas.", statusCode: 400 }); return errorHandling(next, { message: "Cette conversion n'existe pas.", statusCode: 400 })
} }
executeFunction(functionName, value, { res, next }); executeFunction(functionName, value, { res, next })
} }

View File

@ -1,6 +1,6 @@
const errorHandling = require('../../utils/errorHandling'); const errorHandling = require('../../utils/errorHandling')
const { requiredFields, generalError } = require('../../config/errors'); const { requiredFields, generalError } = require('../../config/errors')
const formatNumberResult = require('../secondary/formatNumberResult'); const formatNumberResult = require('../secondary/formatNumberResult')
/** /**
* @description Convertis des °C en °F et l'inverse aussi. * @description Convertis des °C en °F et l'inverse aussi.
@ -10,41 +10,39 @@ const formatNumberResult = require('../secondary/formatNumberResul
* @examples convertTemperature(23, '°F') { result: 73.4, resultHTML: "73.4 °F" } * @examples convertTemperature(23, '°F') { result: 73.4, resultHTML: "73.4 °F" }
*/ */
function convertTemperature (degree, unit) { function convertTemperature (degree, unit) {
let temperatureValue = 0; let temperatureValue = 0
if (unit === "°C") { if (unit === '°C') {
temperatureValue = (degree - 32) * 5/9; temperatureValue = (degree - 32) * 5 / 9
} } else if (unit === '°F') {
else if (unit === "°F") { temperatureValue = ((degree * 9 / 5) + 32)
temperatureValue = ((degree * 9/5) + 32); } else {
} return false
else {
return false;
} }
return { return {
result: temperatureValue, result: temperatureValue,
resultHTML: `<p>${formatNumberResult(degree)} ${(unit === '°C') ? "°F" : "°C"} = ${formatNumberResult(temperatureValue)} ${unit}</p>` resultHTML: `<p>${formatNumberResult(degree)} ${(unit === '°C') ? '°F' : '°C'} = ${formatNumberResult(temperatureValue)} ${unit}</p>`
}; }
} }
/* OUTPUTS */ /* OUTPUTS */
module.exports = convertTemperatureOutput = ({ res, next }, argsObject) => { module.exports = ({ res, next }, argsObject) => {
let { degree, unitToConvert } = argsObject; let { degree, unitToConvert } = argsObject
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(degree && unitToConvert)) { if (!(degree && unitToConvert)) {
return errorHandling(next, requiredFields); return errorHandling(next, requiredFields)
} }
// Si ce n'est pas un nombre // Si ce n'est pas un nombre
degree = parseFloat(degree); degree = parseFloat(degree)
if (isNaN(degree)) { if (isNaN(degree)) {
return errorHandling(next, { message: "Veuillez rentré un nombre valide.", statusCode: 400 }); return errorHandling(next, { message: 'Veuillez rentré un nombre valide.', statusCode: 400 })
} }
const result = convertTemperature(degree, unitToConvert); const result = convertTemperature(degree, unitToConvert)
if (!result) { if (!result) {
return errorHandling(next, generalError); return errorHandling(next, generalError)
} }
return res.status(200).json(result); return res.status(200).json(result)
} }

View File

@ -1,6 +1,6 @@
const errorHandling = require('../../utils/errorHandling'); const errorHandling = require('../../utils/errorHandling')
const { requiredFields } = require('../../config/errors'); const { requiredFields } = require('../../config/errors')
const formatNumberResult = require('../secondary/formatNumberResult'); const formatNumberResult = require('../secondary/formatNumberResult')
/** /**
* @description Calcule les counter premiers nombres de la suite de fibonacci. * @description Calcule les counter premiers nombres de la suite de fibonacci.
@ -8,39 +8,39 @@ const formatNumberResult = require('../secondary/formatNumberResult');
*/ */
function fibonacci (counter, result = [], a = 0, b = 1) { function fibonacci (counter, result = [], a = 0, b = 1) {
if (counter === 0) { if (counter === 0) {
return result; return result
} }
counter--; counter--
result.push(a); result.push(a)
return fibonacci(counter, result, b, a + b); return fibonacci(counter, result, b, a + b)
} }
/* OUTPUTS */ /* OUTPUTS */
module.exports = fibonacciOutput = ({ res, next }, argsObject) => { module.exports = ({ res, next }, argsObject) => {
let { counter } = argsObject; let { counter } = argsObject
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(counter)) { if (!(counter)) {
return errorHandling(next, requiredFields); return errorHandling(next, requiredFields)
} }
// Si ce n'est pas un nombre // Si ce n'est pas un nombre
counter = parseInt(counter); counter = parseInt(counter)
if (isNaN(counter)) { if (isNaN(counter)) {
return errorHandling(next, { message: "Veuillez rentré un nombre valide.", statusCode: 400 }); return errorHandling(next, { message: 'Veuillez rentré un nombre valide.', statusCode: 400 })
} }
// Si le nombre dépasse LIMIT_COUNTER // Si le nombre dépasse LIMIT_COUNTER
const LIMIT_COUNTER = 51; const LIMIT_COUNTER = 51
if (counter >= LIMIT_COUNTER) { 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 }); 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 result = fibonacci(counter)
const resultFormatted = result.map((number) => formatNumberResult(number)); const resultFormatted = result.map((number) => formatNumberResult(number))
return res.status(200).json({ return res.status(200).json({
result, result,
resultFormatted, resultFormatted,
resultHTML: `<p>Les ${counter} premiers nombres de la suite de fibonacci :<br/> ${resultFormatted.join(', ')}</p>` resultHTML: `<p>Les ${counter} premiers nombres de la suite de fibonacci :<br/> ${resultFormatted.join(', ')}</p>`
}); })
} }

View File

@ -1,5 +1,5 @@
const errorHandling = require('../../utils/errorHandling'); const errorHandling = require('../../utils/errorHandling')
const { requiredFields } = require('../../config/errors'); const { requiredFields } = require('../../config/errors')
/** /**
* @description Renvoie le mot le plus long d'une chaîne de caractères * @description Renvoie le mot le plus long d'une chaîne de caractères
@ -8,32 +8,32 @@ const { requiredFields } = require('../../config/errors');
* @example findLongestWord('Chaîne de caractères') 'caractères' * @example findLongestWord('Chaîne de caractères') 'caractères'
*/ */
function findLongestWord (string) { function findLongestWord (string) {
const arrayString = string.split(" "); const arrayString = string.split(' ')
let stringLength = 0; let stringLength = 0
let result = ""; let result = ''
arrayString.forEach((element) => { arrayString.forEach((element) => {
if (element.length > stringLength) { if (element.length > stringLength) {
result = element; result = element
stringLength = element.length; stringLength = element.length
} }
}); })
return result; return result
} }
/* OUTPUTS */ /* OUTPUTS */
module.exports = findLongestWordOutput = ({ res, next }, argsObject) => { module.exports = ({ res, next }, argsObject) => {
const { string } = argsObject; const { string } = argsObject
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(string)) { if (!(string)) {
return errorHandling(next, requiredFields); return errorHandling(next, requiredFields)
} }
const result = findLongestWord(string); const result = findLongestWord(string)
return res.status(200).json({ return res.status(200).json({
result, result,
resultHTML: `<p>Le mot le plus long est : <br/>"${result}"</p>` resultHTML: `<p>Le mot le plus long est : <br/>"${result}"</p>`
}); })
} }

View File

@ -1,6 +1,6 @@
const errorHandling = require('../../utils/errorHandling'); const errorHandling = require('../../utils/errorHandling')
const { requiredFields } = require('../../config/errors'); const { requiredFields } = require('../../config/errors')
const formatNumberResult = require('../secondary/formatNumberResult'); const formatNumberResult = require('../secondary/formatNumberResult')
/** /**
* @description Retourne un tableau contenant toutes les possibilités d'anagramme d'un mot. * @description Retourne un tableau contenant toutes les possibilités d'anagramme d'un mot.
@ -9,44 +9,44 @@ const formatNumberResult = require('../secondary/formatNumberResult');
* @examples heapAlgorithm('abc') ["abc", "acb", "bac", "bca", "cab", "cba"] * @examples heapAlgorithm('abc') ["abc", "acb", "bac", "bca", "cab", "cba"]
*/ */
function heapAlgorithm (string) { function heapAlgorithm (string) {
let results = []; const results = []
if (string.length === 1) { if (string.length === 1) {
results.push(string); results.push(string)
return results; return results
} }
for (let indexString = 0; indexString < string.length; indexString++) { for (let indexString = 0; indexString < string.length; indexString++) {
const firstChar = string[indexString]; const firstChar = string[indexString]
const charsLeft = string.substring(0, indexString) + string.substring(indexString + 1); const charsLeft = string.substring(0, indexString) + string.substring(indexString + 1)
const innerPermutations = heapAlgorithm(charsLeft); const innerPermutations = heapAlgorithm(charsLeft)
for (let indexPermutation = 0; indexPermutation < innerPermutations.length; indexPermutation++) { for (let indexPermutation = 0; indexPermutation < innerPermutations.length; indexPermutation++) {
results.push(firstChar + innerPermutations[indexPermutation]); results.push(firstChar + innerPermutations[indexPermutation])
} }
} }
return results; return results
} }
/* OUTPUTS */ /* OUTPUTS */
module.exports = heapAlgorithmOutput = ({ res, next }, argsObject) => { module.exports = ({ res, next }, argsObject) => {
let { string } = argsObject; const { string } = argsObject
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(string)) { if (!(string)) {
return errorHandling(next, requiredFields); return errorHandling(next, requiredFields)
} }
// Si la chaîne de caractère dépasse LIMIT_CHARACTERS caractères // Si la chaîne de caractère dépasse LIMIT_CHARACTERS caractères
const LIMIT_CHARACTERS = 7; const LIMIT_CHARACTERS = 7
if (string.length > LIMIT_CHARACTERS) { 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 }); 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); 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/>`; 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) => { result.forEach((string) => {
resultHTML += string + "<br/>"; resultHTML += string + '<br/>'
}); })
resultHTML += "</p>"; resultHTML += '</p>'
return res.status(200).json({ result, resultHTML }); return res.status(200).json({ result, resultHTML })
} }

View File

@ -1,5 +1,5 @@
const errorHandling = require('../../utils/errorHandling'); const errorHandling = require('../../utils/errorHandling')
const { requiredFields } = require('../../config/errors'); const { requiredFields } = require('../../config/errors')
/** /**
* @description Inverse la chaîne de caractère * @description Inverse la chaîne de caractère
@ -8,7 +8,7 @@ const { requiredFields } = require('../../config/errors');
* @example reverseString('Hello') 'olleH' * @example reverseString('Hello') 'olleH'
*/ */
function reverseString (string) { function reverseString (string) {
return string.split("").reverse().join(''); return string.split('').reverse().join('')
} }
/** /**
@ -20,29 +20,29 @@ function reverseString(string) {
* @example isPalindrome('kayak') true * @example isPalindrome('kayak') true
*/ */
function isPalindrome (string, reverseStringResult) { function isPalindrome (string, reverseStringResult) {
return string === reverseStringResult; return string === reverseStringResult
} }
/* OUTPUTS */ /* OUTPUTS */
module.exports = isPalindromeOutput = ({ res, next }, argsObject) => { module.exports = ({ res, next }, argsObject) => {
let { string } = argsObject; let { string } = argsObject
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(string)) { if (!(string)) {
return errorHandling(next, requiredFields); return errorHandling(next, requiredFields)
} }
if (typeof string !== 'string') { if (typeof string !== 'string') {
return errorHandling(next, { message: "Vous devez rentré une chaîne de caractère valide.", statusCode: 400 }); return errorHandling(next, { message: 'Vous devez rentré une chaîne de caractère valide.', statusCode: 400 })
} }
string = string.toLowerCase(); string = string.toLowerCase()
const reverseStringResult = reverseString(string); const reverseStringResult = reverseString(string)
const isPalindromeResult = isPalindrome(string, reverseStringResult); const isPalindromeResult = isPalindrome(string, reverseStringResult)
return res.status(200).json({ return res.status(200).json({
isPalindrome: isPalindromeResult, isPalindrome: isPalindromeResult,
reverseString: reverseStringResult, reverseString: reverseStringResult,
resultHTML: `<p>"${string}" ${(isPalindromeResult) ? "est" : "n'est pas"} un palindrome car <br/> "${string}" ${(isPalindromeResult) ? "===" : "!=="} "${reverseStringResult}"</p>` resultHTML: `<p>"${string}" ${(isPalindromeResult) ? 'est' : "n'est pas"} un palindrome car <br/> "${string}" ${(isPalindromeResult) ? '===' : '!=='} "${reverseStringResult}"</p>`
}); })
} }

View File

@ -1,52 +1,52 @@
const validator = require('validator'); const validator = require('validator')
const errorHandling = require('../../utils/errorHandling'); const errorHandling = require('../../utils/errorHandling')
const { requiredFields, serverError } = require('../../config/errors'); const { requiredFields, serverError } = require('../../config/errors')
const Short_links = require('../../../models/short_links'); const shortLinks = require('../../../models/short_links')
module.exports = linkShortener = async ({ res, next }, argsObject) => { module.exports = async ({ res, next }, argsObject) => {
let { url, shortcutName } = argsObject; let { url, shortcutName } = argsObject
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(url && shortcutName)) { if (!(url && shortcutName)) {
return errorHandling(next, requiredFields); return errorHandling(next, requiredFields)
} }
// Si ce n'est pas une url // Si ce n'est pas une url
if (!validator.isURL(url)) { if (!validator.isURL(url)) {
return errorHandling(next, { message: "Veuillez entré une URL valide.", statusCode: 400 }); return errorHandling(next, { message: 'Veuillez entré une URL valide.', statusCode: 400 })
} }
// Si ce n'est pas de type slug // Si ce n'est pas de type slug
if (!validator.isSlug(shortcutName)) { 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 }); 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 // Sanitize shortcutName
shortcutName = validator.escape(shortcutName); shortcutName = validator.escape(shortcutName)
shortcutName = validator.trim(shortcutName); shortcutName = validator.trim(shortcutName)
shortcutName = validator.blacklist(shortcutName, ' '); shortcutName = validator.blacklist(shortcutName, ' ')
try { try {
// Si l'url a déjà été raccourcie // Si l'url a déjà été raccourcie
const urlInDatabase = await Short_links.findOne({ where: { url } }); const urlInDatabase = await shortLinks.findOne({ where: { url } })
if (urlInDatabase) { if (urlInDatabase) {
const urlShort = `https://short-links.divlo.fr/?q=${urlInDatabase.shortcut}`; 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 }); 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à // Si le nom du raccourci existe déjà
const shortcutInDatabase = await Short_links.findOne({ where: { shortcut: shortcutName } }); const shortcutInDatabase = await shortLinks.findOne({ where: { shortcut: shortcutName } })
if (shortcutInDatabase) { if (shortcutInDatabase) {
const urlShort = `https://short-links.divlo.fr/?q=${shortcutInDatabase.shortcut}`; 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 }); 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 // Ajout du lien raccourci
const result = await Short_links.create({ url, shortcut: shortcutName }); const result = await shortLinks.create({ url, shortcut: shortcutName })
const shortcutLinkResult = `https://short-links.divlo.fr/?q=${result.shortcut}`; 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 }); return res.status(200).json({ resultHTML: `URL Raccourcie : <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${shortcutLinkResult}">${shortcutLinkResult}</a>`, result: shortcutLinkResult })
} catch { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }

View File

@ -1,6 +1,6 @@
const errorHandling = require('../../utils/errorHandling'); const errorHandling = require('../../utils/errorHandling')
const { requiredFields } = require('../../config/errors'); const { requiredFields } = require('../../config/errors')
const formatNumberResult = require('../secondary/formatNumberResult'); const formatNumberResult = require('../secondary/formatNumberResult')
/** /**
* @description Génère un nombre aléatoire entre un minimum inclus et un maximum inclus. * @description Génère un nombre aléatoire entre un minimum inclus et un maximum inclus.
@ -10,28 +10,28 @@ const formatNumberResult = require('../secondary/formatNumberResult');
* @examples randomNumber(1, 2) retourne soit 1 ou 2 * @examples randomNumber(1, 2) retourne soit 1 ou 2
*/ */
function randomNumber (min, max) { function randomNumber (min, max) {
return Math.floor(Math.random() * (max - min +1)) + min; return Math.floor(Math.random() * (max - min + 1)) + min
} }
/* OUTPUTS */ /* OUTPUTS */
const randomNumberOutput = ({ res, next }, argsObject) => { const randomNumberOutput = ({ res, next }, argsObject) => {
let { min, max } = argsObject; let { min, max } = argsObject
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(min && max)) { if (!(min && max)) {
return errorHandling(next, requiredFields); return errorHandling(next, requiredFields)
} }
// Si ce ne sont pas des nombres // Si ce ne sont pas des nombres
min = parseInt(min); min = parseInt(min)
max = parseInt(max); max = parseInt(max)
if (isNaN(min) || isNaN(max)) { if (isNaN(min) || isNaN(max)) {
return errorHandling(next, { message: "Les paramètres min et max doivent être des nombres...", statusCode: 400 }); return errorHandling(next, { message: 'Les paramètres min et max doivent être des nombres...', statusCode: 400 })
} }
const result = randomNumber(min, max); 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>` }); 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.randomNumber = randomNumber
exports.randomNumberOutput = randomNumberOutput; exports.randomNumberOutput = randomNumberOutput

View File

@ -1,26 +1,26 @@
const errorHandling = require('../../utils/errorHandling'); const errorHandling = require('../../utils/errorHandling')
const { serverError } = require('../../config/errors'); const { serverError } = require('../../config/errors')
const Quotes = require('../../../models/quotes'); const Quotes = require('../../../models/quotes')
const Users = require('../../../models/users'); const Users = require('../../../models/users')
const sequelize = require('../../utils/database'); const sequelize = require('../../utils/database')
module.exports = randomQuote = async ({ res, next }, _argsObject) => { module.exports = async ({ res, next }, _argsObject) => {
try { try {
const quote = await Quotes.findOne({ const quote = await Quotes.findOne({
order: sequelize.random(), order: sequelize.random(),
include: [ include: [
{ model: Users, attributes: ["name", "logo"] } { model: Users, attributes: ['name', 'logo'] }
], ],
attributes: { attributes: {
exclude: ["isValidated"] exclude: ['isValidated']
}, },
where: { where: {
isValidated: 1, isValidated: 1
} }
}); })
return res.status(200).json(quote); return res.status(200).json(quote)
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }

View File

@ -1,57 +1,57 @@
const { randomNumber } = require('./randomNumber'); const { randomNumber } = require('./randomNumber')
const errorHandling = require('../../utils/errorHandling'); const errorHandling = require('../../utils/errorHandling')
const { serverError } = require('../../config/errors'); const { serverError } = require('../../config/errors')
const { SCRAPER_API_KEY } = require('../../config/config'); const { SCRAPER_API_KEY } = require('../../config/config')
const axios = require('axios'); const axios = require('axios')
const { JSDOM } = require("jsdom"); const { JSDOM } = require('jsdom')
const subjectList = [ const subjectList = [
"smartphone", 'smartphone',
"pc+gamer", 'pc+gamer',
"pc+portable", 'pc+portable',
"TV", 'TV',
"casque", 'casque',
"clavier", 'clavier',
"souris", 'souris',
"ecran", 'ecran',
"jeux+vidéos" 'jeux+vidéos'
]; ]
function getRandomArrayElement (array) { function getRandomArrayElement (array) {
return array[randomNumber(0, array.length - 1)]; return array[randomNumber(0, array.length - 1)]
} }
async function getAmazonProductList (subject) { async function getAmazonProductList (subject) {
const url = `https://www.amazon.fr/s?k=${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 { data } = await axios.get(`http://api.scraperapi.com/?api_key=${SCRAPER_API_KEY}&url=${url}`)
const { document } = (new JSDOM(data)).window; const { document } = (new JSDOM(data)).window
const amazonProductList = document.querySelectorAll('.s-result-item'); const amazonProductList = document.querySelectorAll('.s-result-item')
const productsList = []; const productsList = []
for (let indexProduct in amazonProductList) { for (const indexProduct in amazonProductList) {
try { try {
const elementProduct = amazonProductList[indexProduct]; const elementProduct = amazonProductList[indexProduct]
const productImage = elementProduct.querySelector('.s-image'); const productImage = elementProduct.querySelector('.s-image')
const originalPrice = elementProduct.querySelector(".a-price-whole").innerHTML; const originalPrice = elementProduct.querySelector('.a-price-whole').innerHTML
productsList.push({ productsList.push({
name: productImage["alt"], name: productImage.alt,
image: productImage["src"], image: productImage.src,
price: Number(originalPrice.replace(",", ".").replace(" ", "")) price: Number(originalPrice.replace(',', '.').replace(' ', ''))
}); })
} catch (_error) { } catch (_error) {
continue; continue
} }
} }
return productsList; return productsList
} }
module.exports = rightPriceOutput = async ({ res, next }, _argsObject) => { module.exports = async ({ res, next }, _argsObject) => {
const subject = getRandomArrayElement(subjectList); const subject = getRandomArrayElement(subjectList)
try { try {
const productsList = await getAmazonProductList(subject); const productsList = await getAmazonProductList(subject)
const randomProduct = getRandomArrayElement(productsList); const randomProduct = getRandomArrayElement(productsList)
return res.status(200).json({ subject, ...randomProduct }); return res.status(200).json({ subject, ...randomProduct })
} catch (error) { } catch (error) {
console.error(error); console.error(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }

View File

@ -1,56 +1,56 @@
const errorHandling = require('../../utils/errorHandling'); const errorHandling = require('../../utils/errorHandling')
const { requiredFields } = require('../../config/errors'); const { requiredFields } = require('../../config/errors')
const formatNumberResult = require('../secondary/formatNumberResult'); const formatNumberResult = require('../secondary/formatNumberResult')
function minNumber (array) { function minNumber (array) {
let minNumber = { index: 0, value: array[0] } let minNumber = { index: 0, value: array[0] }
for (let index = 1; index < array.length; index++) { for (let index = 1; index < array.length; index++) {
const number = array[index]; const number = array[index]
if (number < minNumber.value) { if (number < minNumber.value) {
minNumber = { index: index, value: array[index] } minNumber = { index: index, value: array[index] }
} }
} }
return minNumber; return minNumber
} }
function sortArray (array) { function sortArray (array) {
const arrayDuplicated = [...array]; const arrayDuplicated = [...array]
const resultArray = []; const resultArray = []
while (array.length !== resultArray.length) { while (array.length !== resultArray.length) {
const min = minNumber(arrayDuplicated); const min = minNumber(arrayDuplicated)
resultArray.push(min.value); resultArray.push(min.value)
arrayDuplicated.splice(min.index, 1); arrayDuplicated.splice(min.index, 1)
} }
return resultArray; return resultArray
} }
/* OUTPUTS */ /* OUTPUTS */
module.exports = sortArrayOutput = ({ res, next }, argsObject) => { module.exports = ({ res, next }, argsObject) => {
let { numbersList } = argsObject; const { numbersList } = argsObject
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(numbersList)) { if (!(numbersList)) {
return errorHandling(next, requiredFields); return errorHandling(next, requiredFields)
} }
const numbersListArray = numbersList.split(',').map((number) => number.trim().replace(' ', '')).map(Number); const numbersListArray = numbersList.split(',').map((number) => number.trim().replace(' ', '')).map(Number)
// Si ce n'est pas une liste de nombres // Si ce n'est pas une liste de nombres
if (numbersListArray.includes(NaN)) { if (numbersListArray.includes(NaN)) {
return errorHandling(next, { message: "Vous devez rentrer une liste de nombres séparée par des virgules valide.", statusCode: 400 }); return errorHandling(next, { message: 'Vous devez rentrer une liste de nombres séparée par des virgules valide.', statusCode: 400 })
} }
// Si la taille du tableau dépasse LIMIT_ARRAY_LENGTH // Si la taille du tableau dépasse LIMIT_ARRAY_LENGTH
const LIMIT_ARRAY_LENGTH = 31; const LIMIT_ARRAY_LENGTH = 31
if (numbersListArray.length >= LIMIT_ARRAY_LENGTH) { 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 }); 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 result = sortArray(numbersListArray)
const resultFormatted = result.map((number) => formatNumberResult(number)); const resultFormatted = result.map((number) => formatNumberResult(number))
return res.status(200).json({ return res.status(200).json({
result, result,
resultFormatted, resultFormatted,
resultHTML: `<p>La liste de nombres dans l'ordre croissant :<br/> ${resultFormatted.join(', ')}</p>` resultHTML: `<p>La liste de nombres dans l'ordre croissant :<br/> ${resultFormatted.join(', ')}</p>`
}); })
} }

View File

@ -1,10 +1,10 @@
const axios = require('axios'); const axios = require('axios')
const Queue = require('smart-request-balancer'); const Queue = require('smart-request-balancer')
const errorHandling = require('../../utils/errorHandling'); const errorHandling = require('../../utils/errorHandling')
const { requiredFields } = require('../../config/errors'); const { requiredFields } = require('../../config/errors')
const { WEATHER_API_KEY } = require('../../config/config'); const { WEATHER_API_KEY } = require('../../config/config')
const dateTimeUTC = require('../secondary/dateTimeManagement'); const dateTimeUTC = require('../secondary/dateTimeManagement')
const capitalize = require('../secondary/capitalize'); const capitalize = require('../secondary/capitalize')
const queue = new Queue({ const queue = new Queue({
/* /*
@ -17,30 +17,30 @@ const queue = new Queue({
rate: 50, rate: 50,
limit: 60, limit: 60,
priority: 1 priority: 1
},
} }
}); }
})
/* OUTPUTS */ /* OUTPUTS */
module.exports = weatherRequestOutput = ({ res, next }, argsObject) => { module.exports = ({ res, next }, argsObject) => {
let { cityName } = argsObject; let { cityName } = argsObject
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(cityName)) { if (!(cityName)) {
return errorHandling(next, requiredFields); 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) // 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(() => { queue.request(() => {
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${cityName}&lang=fr&units=metric&appid=${WEATHER_API_KEY}`) axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${cityName}&lang=fr&units=metric&appid=${WEATHER_API_KEY}`)
.then((response) => { .then((response) => {
const json = response.data; const json = response.data
const showDateTimeValue = dateTimeUTC((json.timezone / 60 / 60).toString()).showDateTimeValue; 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>`; 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 }); 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 })); .catch(() => errorHandling(next, { message: "La ville n'existe pas (dans l'API de openweathermap.org).", statusCode: 404 }))
}, 'everyone', 'weatherRequest'); }, 'everyone', 'weatherRequest')
} }

View File

@ -9,4 +9,4 @@ function capitalize(s) {
return s.charAt(0).toUpperCase() + s.slice(1) return s.charAt(0).toUpperCase() + s.slice(1)
} }
module.exports = capitalize; module.exports = capitalize

View File

@ -5,12 +5,12 @@
* @examples dateTimeUTC('0') * @examples dateTimeUTC('0')
*/ */
function dateTimeUTC (utc) { function dateTimeUTC (utc) {
const timeNow = new Date(); const timeNow = new Date()
const utcOffset = timeNow.getTimezoneOffset(); const utcOffset = timeNow.getTimezoneOffset()
timeNow.setMinutes(timeNow.getMinutes() + utcOffset); timeNow.setMinutes(timeNow.getMinutes() + utcOffset)
const enteredOffset = parseFloat(utc)*60; const enteredOffset = parseFloat(utc) * 60
timeNow.setMinutes(timeNow.getMinutes() + enteredOffset); timeNow.setMinutes(timeNow.getMinutes() + enteredOffset)
return showDateTime(timeNow); return showDateTime(timeNow)
} }
/** /**
@ -21,13 +21,13 @@ function dateTimeUTC(utc) {
* @examples dateTimeUTC('0') dateTimeUTC vous renvoie l'exécution de showDateTime * @examples dateTimeUTC('0') dateTimeUTC vous renvoie l'exécution de showDateTime
*/ */
function showDateTime (timeNow) { function showDateTime (timeNow) {
const year = timeNow.getFullYear(); const year = timeNow.getFullYear()
const month = ('0'+(timeNow.getMonth()+1)).slice(-2); const month = ('0' + (timeNow.getMonth() + 1)).slice(-2)
const day = ('0'+timeNow.getDate()).slice(-2); const day = ('0' + timeNow.getDate()).slice(-2)
const hour = ('0'+timeNow.getHours()).slice(-2); const hour = ('0' + timeNow.getHours()).slice(-2)
const minute = ('0'+timeNow.getMinutes()).slice(-2); const minute = ('0' + timeNow.getMinutes()).slice(-2)
const second = ('0'+timeNow.getSeconds()).slice(-2); const second = ('0' + timeNow.getSeconds()).slice(-2)
const showDateTimeValue = day + "/" + month + "/" + year + " - " + hour + ":" + minute + ":" + second; const showDateTimeValue = day + '/' + month + '/' + year + ' - ' + hour + ':' + minute + ':' + second
const objectDateTime = { const objectDateTime = {
year: year, year: year,
month: month, month: month,
@ -36,8 +36,8 @@ function showDateTime(timeNow) {
minute: minute, minute: minute,
second: second, second: second,
showDateTimeValue: showDateTimeValue showDateTimeValue: showDateTimeValue
}; }
return objectDateTime; return objectDateTime
} }
module.exports = dateTimeUTC; module.exports = dateTimeUTC

View File

@ -5,10 +5,10 @@
* @returns {String} - Le nombre formaté * @returns {String} - Le nombre formaté
* @examples formatNumberResult(76120) '76 120' * @examples formatNumberResult(76120) '76 120'
*/ */
function formatNumberResult(number, separator = ".") { function formatNumberResult (number, separator = '.') {
let parts = number.toString().split(separator); const parts = number.toString().split(separator)
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, " "); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ' ')
return parts.join(separator); return parts.join(separator)
} }
module.exports = formatNumberResult; module.exports = formatNumberResult

View File

@ -1,9 +1,9 @@
const Sequelize = require('sequelize'); const Sequelize = require('sequelize')
const { DATABASE } = require('../config/config'); const { DATABASE } = require('../config/config')
const sequelize = new Sequelize(DATABASE.name, DATABASE.user, DATABASE.password, { const sequelize = new Sequelize(DATABASE.name, DATABASE.user, DATABASE.password, {
dialect: 'mysql', dialect: 'mysql',
host: DATABASE.host host: DATABASE.host
}); })
module.exports = sequelize; module.exports = sequelize

View File

@ -1,19 +1,19 @@
const fs = require("fs"); const fs = require('fs')
const path = require("path"); const path = require('path')
function deleteFilesNameStartWith (pattern, dirPath, callback) { function deleteFilesNameStartWith (pattern, dirPath, callback) {
fs.readdir(path.resolve(dirPath), (_error, fileNames) => { fs.readdir(path.resolve(dirPath), (_error, fileNames) => {
for (const name of fileNames) { for (const name of fileNames) {
const splitedName = name.split('.'); const splitedName = name.split('.')
if (splitedName.length === 2) { if (splitedName.length === 2) {
const fileName = splitedName[0]; const fileName = splitedName[0]
if (fileName === pattern && name !== 'default.png') { if (fileName === pattern && name !== 'default.png') {
return fs.unlink(path.join(dirPath, name), callback); return fs.unlink(path.join(dirPath, name), callback)
} }
} }
} }
return callback(); return callback()
}); })
} }
module.exports = deleteFilesNameStartWith; module.exports = deleteFilesNameStartWith

View File

@ -1,7 +1,7 @@
function errorHandling (next, { statusCode, message }) { function errorHandling (next, { statusCode, message }) {
const error = new Error(message); const error = new Error(message)
error.statusCode = statusCode; error.statusCode = statusCode
next(error); next(error)
} }
module.exports = errorHandling; module.exports = errorHandling

View File

@ -1,10 +1,10 @@
const errorHandling = require('../utils/errorHandling'); const errorHandling = require('../utils/errorHandling')
const { serverError } = require('../config/errors'); const { serverError } = require('../config/errors')
const helperQueryNumber = require('../utils/helperQueryNumber'); const helperQueryNumber = require('../utils/helperQueryNumber')
const DEFAULT_OPTIONS = { const DEFAULT_OPTIONS = {
order: [['createdAt', 'DESC']] order: [['createdAt', 'DESC']]
}; }
/** /**
* @description Permet de faire un système de pagination sur un model Sequelize * @description Permet de faire un système de pagination sur un model Sequelize
@ -13,22 +13,22 @@ const DEFAULT_OPTIONS = {
* @param {Object} options Options avec clause where etc. * @param {Object} options Options avec clause where etc.
*/ */
async function getPagesHelper ({ req, res, next }, Model, options = DEFAULT_OPTIONS) { async function getPagesHelper ({ req, res, next }, Model, options = DEFAULT_OPTIONS) {
const page = helperQueryNumber(req.query.page, 1); const page = helperQueryNumber(req.query.page, 1)
const limit = helperQueryNumber(req.query.limit, 10); const limit = helperQueryNumber(req.query.limit, 10)
const offset = (page - 1) * limit; const offset = (page - 1) * limit
try { try {
const result = await Model.findAndCountAll({ const result = await Model.findAndCountAll({
limit, limit,
offset, offset,
...options ...options
}); })
const { count, rows } = result; const { count, rows } = result
const hasMore = (page * limit) < count; const hasMore = (page * limit) < count
return res.status(200).json({ totalItems: count, hasMore, rows }); return res.status(200).json({ totalItems: count, hasMore, rows })
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }
module.exports = getPagesHelper; module.exports = getPagesHelper

View File

@ -1,6 +1,6 @@
function helperQueryNumber (value, defaultValue) { function helperQueryNumber (value, defaultValue) {
if (value && !isNaN(value)) return parseInt(value); if (value && !isNaN(value)) return parseInt(value)
return defaultValue; return defaultValue
} }
module.exports = helperQueryNumber; module.exports = helperQueryNumber

View File

@ -1,44 +1,44 @@
const path = require('path'); const path = require('path')
const fs = require('fs'); const fs = require('fs')
const { validationResult } = require('express-validator'); const { validationResult } = require('express-validator')
const errorHandling = require('../assets/utils/errorHandling'); const errorHandling = require('../assets/utils/errorHandling')
const { serverError } = require('../assets/config/errors'); const { serverError } = require('../assets/config/errors')
const Functions = require('../models/functions'); const Functions = require('../models/functions')
const Categories = require('../models/categories'); const Categories = require('../models/categories')
const Quotes = require('../models/quotes'); const Quotes = require('../models/quotes')
const Users = require('../models/users'); const Users = require('../models/users')
const helperQueryNumber = require('../assets/utils/helperQueryNumber'); const helperQueryNumber = require('../assets/utils/helperQueryNumber')
const getPagesHelper = require('../assets/utils/getPagesHelper'); const getPagesHelper = require('../assets/utils/getPagesHelper')
const Sequelize = require('sequelize'); const Sequelize = require('sequelize')
const deleteFilesNameStartWith = require('../assets/utils/deleteFilesNameStartWith'); const deleteFilesNameStartWith = require('../assets/utils/deleteFilesNameStartWith')
const { EMAIL_INFO, FRONT_END_HOST } = require('../assets/config/config'); const { EMAIL_INFO, FRONT_END_HOST } = require('../assets/config/config')
const transporter = require('../assets/config/transporter'); const transporter = require('../assets/config/transporter')
const { emailQuoteTemplate } = require('../assets/config/emails'); const { emailQuoteTemplate } = require('../assets/config/emails')
const handleEditFunction = async (res, resultFunction, { title, slug, description, type, categorieId, isOnline }, imageName = false) => { const handleEditFunction = async (res, resultFunction, { title, slug, description, type, categorieId, isOnline }, imageName = false) => {
resultFunction.title = title; resultFunction.title = title
resultFunction.slug = slug; resultFunction.slug = slug
resultFunction.description = description; resultFunction.description = description
resultFunction.type = type; resultFunction.type = type
resultFunction.categorieId = categorieId; resultFunction.categorieId = categorieId
resultFunction.isOnline = isOnline; resultFunction.isOnline = isOnline
if (imageName) { if (imageName) {
resultFunction.image = `/images/functions/${imageName}`; resultFunction.image = `/images/functions/${imageName}`
} }
const result = await resultFunction.save(); const result = await resultFunction.save()
res.status(200).json({ message: "La fonction a bien été modifié!", result }); res.status(200).json({ message: 'La fonction a bien été modifié!', result })
} }
exports.getFunctions = async (req, res, next) => { exports.getFunctions = async (req, res, next) => {
const categoryId = helperQueryNumber(req.query.categoryId, 0); const categoryId = helperQueryNumber(req.query.categoryId, 0)
let search = req.query.search; let search = req.query.search
try { search = search.toLowerCase(); } catch {}; try { search = search.toLowerCase() } catch {};
const options = { const options = {
where: { where: {
// Trie par catégorie // Trie par catégorie
...(categoryId !== 0) && { categorieId: categoryId }, ...(categoryId !== 0) && { categorieId: categoryId },
// Recherche // Recherche
... (search != undefined) && { ...(search != null) && {
[Sequelize.Op.or]: [ [Sequelize.Op.or]: [
{ title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', `%${search}%`) }, { title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', `%${search}%`) },
{ slug: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('slug')), 'LIKE', `%${search}%`) }, { slug: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('slug')), 'LIKE', `%${search}%`) },
@ -47,242 +47,242 @@ exports.getFunctions = async (req, res, next) => {
} }
}, },
include: [ include: [
{ model: Categories, attributes: ["name", "color"] } { model: Categories, attributes: ['name', 'color'] }
], ],
attributes: { attributes: {
exclude: ["updatedAt", "utilizationForm", "article", "isOnline"] exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline']
}, },
order: [['createdAt', 'DESC']] order: [['createdAt', 'DESC']]
}; }
return await getPagesHelper({ req, res, next }, Functions, options); return await getPagesHelper({ req, res, next }, Functions, options)
} }
exports.getFunctionBySlug = (req, res, next) => { exports.getFunctionBySlug = (req, res, next) => {
const { slug } = req.params; const { slug } = req.params
Functions.findOne({ Functions.findOne({
where: { slug }, where: { slug },
include: [ include: [
{ model: Categories, attributes: ["name", "color"] } { model: Categories, attributes: ['name', 'color'] }
] ]
}) })
.then((result) => { .then((result) => {
if (!result) { if (!result) {
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 })
} }
try { result.utilizationForm = JSON.parse(result.utilizationForm); } catch {} try { result.utilizationForm = JSON.parse(result.utilizationForm) } catch {}
return res.status(200).json(result); return res.status(200).json(result)
}) })
.catch((error) => { .catch((error) => {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
}); })
} }
exports.postFunction = (req, res, next) => { exports.postFunction = (req, res, next) => {
const { title, slug, description, type, categorieId } = req.body; const { title, slug, description, type, categorieId } = req.body
const image = req.files.image; const image = req.files.image
const errors = validationResult(req); const errors = validationResult(req)
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }); return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 })
} }
if (!image || image.truncated && ( if ((!image || image.truncated) && (
image.mimetype !== 'image/png' || image.mimetype !== 'image/png' ||
image.mimetype !== 'image/jpg' || image.mimetype !== 'image/jpg' ||
image.mimetype !== 'image/jpeg' 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('.'); const splitedImageName = image.name.split('.')
if (splitedImageName.length !== 2) return errorHandling(next, serverError); if (splitedImageName.length !== 2) return errorHandling(next, serverError)
const imageName = slug + '.' + splitedImageName[1]; const imageName = slug + '.' + splitedImageName[1]
image.mv(path.join(__dirname, '..', 'assets', 'images', 'functions') + '/' + imageName, async (error) => { image.mv(path.join(__dirname, '..', 'assets', 'images', 'functions') + '/' + imageName, async (error) => {
if (error) return errorHandling(next, serverError); if (error) return errorHandling(next, serverError)
try { try {
const result = await Functions.create({ title, slug, description, type, categorieId, image: `/images/functions/${imageName}` }); 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 }); return res.status(201).json({ message: 'La fonction a été correctement ajouté!', result })
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
}); })
} }
exports.putFunction = async (req, res, next) => { exports.putFunction = async (req, res, next) => {
const { id } = req.params; const { id } = req.params
const { title, slug, description, type, categorieId, isOnline } = req.body; const { title, slug, description, type, categorieId, isOnline } = req.body
const image = req.files.image; const image = req.files.image
const errors = validationResult(req); const errors = validationResult(req)
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }); return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 })
} }
try { try {
// Vérifie si la fonction existe // Vérifie si la fonction existe
const resultFunction = await Functions.findOne({ where: { id } }); const resultFunction = await Functions.findOne({ where: { id } })
if (!resultFunction) { if (!resultFunction) {
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 })
} }
// Vérifie si le slug existe déjà // Vérifie si le slug existe déjà
const FunctionSlug = await Functions.findOne({ where: { slug } }); const FunctionSlug = await Functions.findOne({ where: { slug } })
if (!FunctionSlug && FunctionSlug.id != resultFunction.id) { if (!FunctionSlug && FunctionSlug.id !== resultFunction.id) {
return errorHandling(next, { message: "Le slug existe déjà...", statusCode: 404 }); return errorHandling(next, { message: 'Le slug existe déjà...', statusCode: 404 })
} }
// Sauvegarde de la fonction // Sauvegarde de la fonction
if (image != undefined) { if (image != null) {
if (image.truncated && ( if (image.truncated && (
image.mimetype !== 'image/png' || image.mimetype !== 'image/png' ||
image.mimetype !== 'image/jpg' || image.mimetype !== 'image/jpg' ||
image.mimetype !== 'image/jpeg' 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('.'); const splitedImageName = image.name.split('.')
if (splitedImageName.length !== 2) return errorHandling(next, serverError); if (splitedImageName.length !== 2) return errorHandling(next, serverError)
const imageName = slug + '.' + splitedImageName[1]; const imageName = slug + '.' + splitedImageName[1]
// Supprime les anciennes images // Supprime les anciennes images
const functionPath = path.join(__dirname, '..', 'assets', 'images', 'functions'); const functionPath = path.join(__dirname, '..', 'assets', 'images', 'functions')
deleteFilesNameStartWith(slug, functionPath, () => { deleteFilesNameStartWith(slug, functionPath, () => {
image.mv(path.join(functionPath, imageName), async (error) => { image.mv(path.join(functionPath, imageName), async (error) => {
if (error) return errorHandling(next, serverError); if (error) return errorHandling(next, serverError)
return await handleEditFunction(res, resultFunction, { title, slug, description, type, categorieId, isOnline }, imageName); return await handleEditFunction(res, resultFunction, { title, slug, description, type, categorieId, isOnline }, imageName)
}); })
}); })
} else { } else {
return await handleEditFunction(res, resultFunction, { title, slug, description, type, categorieId, isOnline }); return await handleEditFunction(res, resultFunction, { title, slug, description, type, categorieId, isOnline })
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }
exports.putFunctionArticle = async (req, res, next) => { exports.putFunctionArticle = async (req, res, next) => {
const { id } = req.params; const { id } = req.params
const { article } = req.body; const { article } = req.body
try { try {
// Vérifie si la fonction existe // Vérifie si la fonction existe
const resultFunction = await Functions.findOne({ where: { id } }); const resultFunction = await Functions.findOne({ where: { id } })
if (!resultFunction) { if (!resultFunction) {
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 })
} }
resultFunction.article = article; resultFunction.article = article
const result = await resultFunction.save(); const result = await resultFunction.save()
return res.status(200).json(result); return res.status(200).json(result)
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }
exports.putFunctionForm = async (req, res, next) => { exports.putFunctionForm = async (req, res, next) => {
const { id } = req.params; const { id } = req.params
const { form } = req.body; const { form } = req.body
try { try {
// Vérifie si la fonction existe // Vérifie si la fonction existe
const resultFunction = await Functions.findOne({ where: { id } }); const resultFunction = await Functions.findOne({ where: { id } })
if (!resultFunction) { if (!resultFunction) {
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 })
} }
resultFunction.utilizationForm = JSON.stringify(form); resultFunction.utilizationForm = JSON.stringify(form)
const result = await resultFunction.save(); const result = await resultFunction.save()
return res.status(200).json(result); return res.status(200).json(result)
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }
exports.deleteFunction = async (req, res, next) => { exports.deleteFunction = async (req, res, next) => {
const { id } = req.params; const { id } = req.params
try { try {
const result = await Functions.findOne({ where: { id } }); const result = await Functions.findOne({ where: { id } })
if (!result) { if (!result) {
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 })
} }
if (result.image !== "/images/functions/default.png") { if (result.image !== '/images/functions/default.png') {
const filePath = path.join(__dirname, '..', 'assets', result.image); const filePath = path.join(__dirname, '..', 'assets', result.image)
fs.unlinkSync(filePath); // supprime le fichier fs.unlinkSync(filePath) // supprime le fichier
} }
await result.destroy(); await result.destroy()
res.status(200).json({ message: "La fonction a été correctement supprimé!"}); res.status(200).json({ message: 'La fonction a été correctement supprimé!' })
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }
exports.postCategory = async (req, res, next) => { exports.postCategory = async (req, res, next) => {
const { name, color } = req.body; const { name, color } = req.body
if (!(name && color)) { if (!(name && color)) {
return errorHandling(next, { message: "La catégorie doit avoir un nom et une couleur." }); return errorHandling(next, { message: 'La catégorie doit avoir un nom et une couleur.' })
} }
try { try {
const result = await Categories.create({ name, color }); const result = await Categories.create({ name, color })
return res.status(201).json({ message: "La catégorie a bien été crée!", result }); return res.status(201).json({ message: 'La catégorie a bien été crée!', result })
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }
exports.putCategory = async (req, res, next) => { exports.putCategory = async (req, res, next) => {
const { name, color } = req.body; const { name, color } = req.body
const { id } = req.params; const { id } = req.params
if (!(name && color && id)) { if (!(name && color && id)) {
return errorHandling(next, { message: "La catégorie doit avoir un nom, une couleur et un id." }); return errorHandling(next, { message: 'La catégorie doit avoir un nom, une couleur et un id.' })
} }
try { try {
const category = await Categories.findOne({ where: { id } }); const category = await Categories.findOne({ where: { id } })
if (!category) { if (!category) {
return errorHandling(next, { message: "La catégorie n'existe pas." }); return errorHandling(next, { message: "La catégorie n'existe pas." })
} }
category.name = name; category.name = name
category.color = color; category.color = color
const result = await category.save(); const result = await category.save()
return res.status(200).json({ message: "La catégorie a bien été modifiée!", result }); return res.status(200).json({ message: 'La catégorie a bien été modifiée!', result })
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }
exports.deleteCategory = async (req, res, next) => { exports.deleteCategory = async (req, res, next) => {
const { id } = req.params; const { id } = req.params
try { try {
const category = await Categories.findOne({ where: { id } }); const category = await Categories.findOne({ where: { id } })
if (!category) { if (!category) {
return errorHandling(next, { message: "La catégorie n'existe pas." }); return errorHandling(next, { message: "La catégorie n'existe pas." })
} }
await category.destroy(); await category.destroy()
return res.status(200).json({ message: "La catégorie a bien été supprimée!" }); return res.status(200).json({ message: 'La catégorie a bien été supprimée!' })
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }
exports.getQuotes = async (req, res, next) => { exports.getQuotes = async (req, res, next) => {
const options = { const options = {
where: { where: {
isValidated: 0, isValidated: 0
}, },
include: [ include: [
{ model: Users, attributes: ["name", "logo"] } { model: Users, attributes: ['name', 'logo'] }
], ],
order: [['createdAt', 'DESC']] order: [['createdAt', 'DESC']]
}; }
return await getPagesHelper({ req, res, next }, Quotes, options); return await getPagesHelper({ req, res, next }, Quotes, options)
} }
exports.putQuote = async (req, res, next) => { exports.putQuote = async (req, res, next) => {
const { id } = req.params; const { id } = req.params
const { isValid } = req.body; const { isValid } = req.body
try { try {
if (typeof isValid !== 'boolean') { if (typeof isValid !== 'boolean') {
return errorHandling(next, { message: "isValid doit être un booléen.", statusCode: 400 }); return errorHandling(next, { message: 'isValid doit être un booléen.', statusCode: 400 })
} }
const quote = await Quotes.findOne({ const quote = await Quotes.findOne({
where: { where: {
@ -290,31 +290,30 @@ exports.putQuote = async (req, res, next) => {
isValidated: 0 isValidated: 0
}, },
include: [ include: [
{ model: Users, attributes: ["name", "email"] } { model: Users, attributes: ['name', 'email'] }
] ]
}); })
if (!quote) { if (!quote) {
return errorHandling(next, { message: "La citation n'existe pas (ou est déjà validé).", statusCode: 404 }); return errorHandling(next, { message: "La citation n'existe pas (ou est déjà validé).", statusCode: 404 })
} }
await transporter.sendMail({ await transporter.sendMail({
from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`, from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`,
to: quote.user.email, to: quote.user.email,
subject: "FunctionProject - Citation proposée", subject: 'FunctionProject - Citation proposée',
html: emailQuoteTemplate(isValid, quote, FRONT_END_HOST) html: emailQuoteTemplate(isValid, quote, FRONT_END_HOST)
}); })
if (isValid) { if (isValid) {
quote.isValidated = true; quote.isValidated = true
await quote.save(); await quote.save()
return res.status(200).json({ message: "La citation a bien été validée!" }); return res.status(200).json({ message: 'La citation a bien été validée!' })
} else { } else {
await quote.destroy(); await quote.destroy()
return res.status(200).json({ imessage: "La citation a bien été supprimée!" }); return res.status(200).json({ imessage: 'La citation a bien été supprimée!' })
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }

View File

@ -1,14 +1,14 @@
const errorHandling = require('../assets/utils/errorHandling'); const errorHandling = require('../assets/utils/errorHandling')
const Categories = require('../models/categories'); const Categories = require('../models/categories')
const { serverError } = require('../assets/config/errors'); const { serverError } = require('../assets/config/errors')
exports.getCategories = (_req, res, next) => { exports.getCategories = (_req, res, next) => {
Categories.findAll() Categories.findAll()
.then((result) => { .then((result) => {
res.status(200).json(result); res.status(200).json(result)
}) })
.catch((error) => { .catch((error) => {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
}); })
} }

View File

@ -1,72 +1,72 @@
const errorHandling = require('../assets/utils/errorHandling'); const errorHandling = require('../assets/utils/errorHandling')
const Comments = require('../models/comments'); const Comments = require('../models/comments')
const Users = require('../models/users'); const Users = require('../models/users')
const Functions = require('../models/functions'); const Functions = require('../models/functions')
const getPagesHelper = require('../assets/utils/getPagesHelper'); const getPagesHelper = require('../assets/utils/getPagesHelper')
const { serverError } = require('../assets/config/errors'); const { serverError } = require('../assets/config/errors')
exports.getCommentsByFunctionId = async (req, res, next) => { exports.getCommentsByFunctionId = async (req, res, next) => {
const { functionId } = req.params; const { functionId } = req.params
const options = { const options = {
where: { functionId }, where: { functionId },
include: [ include: [
{ model: Users, attributes: ["name", "logo"] } { model: Users, attributes: ['name', 'logo'] }
], ],
order: [['createdAt', 'DESC']] order: [['createdAt', 'DESC']]
}; }
return await getPagesHelper({ req, res, next }, Comments, options); return await getPagesHelper({ req, res, next }, Comments, options)
} }
exports.postCommentsByFunctionId = async (req, res, next) => { exports.postCommentsByFunctionId = async (req, res, next) => {
const { functionId } = req.params; const { functionId } = req.params
const { message } = req.body; const { message } = req.body
try { try {
const resultFunction = await Functions.findOne({ where: { id: functionId } }); const resultFunction = await Functions.findOne({ where: { id: functionId } })
if (!resultFunction) { if (!resultFunction) {
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 })
} }
if (!message) { if (!message) {
return errorHandling(next, { message: "Vous ne pouvez pas poster de commentaire vide.", statusCode: 400 }); return errorHandling(next, { message: 'Vous ne pouvez pas poster de commentaire vide.', statusCode: 400 })
} }
const comment = await Comments.create({ message, userId: req.userId, functionId }); const comment = await Comments.create({ message, userId: req.userId, functionId })
return res.status(201).json(comment); return res.status(201).json(comment)
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }
exports.deleteCommentById = async (req, res, next) => { exports.deleteCommentById = async (req, res, next) => {
const { commentId } = req.params; const { commentId } = req.params
try { try {
const comment = await Comments.findOne({ where: { userId: req.userId, id: parseInt(commentId) } }); const comment = await Comments.findOne({ where: { userId: req.userId, id: parseInt(commentId) } })
if (!comment) { if (!comment) {
return errorHandling(next, { message: "Le commentaire n'existe pas.", statusCode: 404 }); return errorHandling(next, { message: "Le commentaire n'existe pas.", statusCode: 404 })
} }
await comment.destroy(); await comment.destroy()
return res.status(200).json({ message: "Le commentaire a bien été supprimé." }); return res.status(200).json({ message: 'Le commentaire a bien été supprimé.' })
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }
exports.putCommentsById = async (req, res, next) => { exports.putCommentsById = async (req, res, next) => {
const { commentId } = req.params; const { commentId } = req.params
const { message } = req.body; const { message } = req.body
if (!message) { if (!message) {
return errorHandling(next, { message: "Vous ne pouvez pas poster de commentaire vide.", statusCode: 400 }); return errorHandling(next, { message: 'Vous ne pouvez pas poster de commentaire vide.', statusCode: 400 })
} }
try { try {
const comment = await Comments.findOne({ where: { userId: req.userId, id: parseInt(commentId) } }); const comment = await Comments.findOne({ where: { userId: req.userId, id: parseInt(commentId) } })
if (!comment) { if (!comment) {
return errorHandling(next, { message: "Le commentaire n'existe pas.", statusCode: 404 }); return errorHandling(next, { message: "Le commentaire n'existe pas.", statusCode: 404 })
} }
comment.message = message; comment.message = message
await comment.save(); await comment.save()
return res.status(200).json({ message: "Le commentaire a bien été modifié." }); return res.status(200).json({ message: 'Le commentaire a bien été modifié.' })
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }

View File

@ -1,74 +1,74 @@
const errorHandling = require('../assets/utils/errorHandling'); const errorHandling = require('../assets/utils/errorHandling')
const { serverError } = require('../assets/config/errors'); const { serverError } = require('../assets/config/errors')
const Favorites = require('../models/favorites'); const Favorites = require('../models/favorites')
const Functions = require('../models/functions'); const Functions = require('../models/functions')
exports.getFavoriteByFunctionId = async (req, res, next) => { exports.getFavoriteByFunctionId = async (req, res, next) => {
const { functionId } = req.params; const { functionId } = req.params
const { userId } = req; const { userId } = req
try { try {
const favorite = await Favorites.findOne({ const favorite = await Favorites.findOne({
where: { where: {
userId, userId,
functionId functionId
} }
}); })
if (!favorite) { if (!favorite) {
return res.status(200).json({ isFavorite: false }); return res.status(200).json({ isFavorite: false })
} }
return res.status(200).json({ isFavorite: true }); return res.status(200).json({ isFavorite: true })
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }
exports.postFavoriteByFunctionId = async (req, res, next) => { exports.postFavoriteByFunctionId = async (req, res, next) => {
const { functionId } = req.params; const { functionId } = req.params
const { userId } = req; const { userId } = req
try { try {
const resultFunction = await Functions.findOne({ where: { id: functionId } }); const resultFunction = await Functions.findOne({ where: { id: functionId } })
if (!resultFunction) { if (!resultFunction) {
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 })
} }
const favorite = await Favorites.findOne({ const favorite = await Favorites.findOne({
where: { where: {
userId, userId,
functionId functionId
} }
}); })
if (!favorite) { if (!favorite) {
await Favorites.create({ userId, functionId }); await Favorites.create({ userId, functionId })
return res.status(201).json({ result: "Le favoris a bien été ajouté!" }); 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 }); return errorHandling(next, { message: 'La fonction est déjà en favoris.', statusCode: 400 })
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }
exports.deleteFavoriteByFunctionId = async (req, res, next) => { exports.deleteFavoriteByFunctionId = async (req, res, next) => {
const { functionId } = req.params; const { functionId } = req.params
const { userId } = req; const { userId } = req
try { try {
const resultFunction = await Functions.findOne({ where: { id: functionId } }); const resultFunction = await Functions.findOne({ where: { id: functionId } })
if (!resultFunction) { if (!resultFunction) {
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 })
} }
const favorite = await Favorites.findOne({ const favorite = await Favorites.findOne({
where: { where: {
userId, userId,
functionId functionId
} }
}); })
if (!favorite) { if (!favorite) {
return errorHandling(next, { message: "Le fonction n'est pas en favoris.", statusCode: 400 }); return errorHandling(next, { message: "Le fonction n'est pas en favoris.", statusCode: 400 })
} }
await favorite.destroy(); await favorite.destroy()
return res.status(200).json({ message: "Le fonction a bien été supprimé des favoris." }); return res.status(200).json({ message: 'Le fonction a bien été supprimé des favoris.' })
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }

View File

@ -1,23 +1,23 @@
const errorHandling = require('../assets/utils/errorHandling'); const errorHandling = require('../assets/utils/errorHandling')
const { serverError } = require('../assets/config/errors'); const { serverError } = require('../assets/config/errors')
const Functions = require('../models/functions'); const Functions = require('../models/functions')
const Categories = require('../models/categories'); const Categories = require('../models/categories')
const functionToExecute = require('../assets/functions/functionObject'); const functionToExecute = require('../assets/functions/functionObject')
const helperQueryNumber = require('../assets/utils/helperQueryNumber'); const helperQueryNumber = require('../assets/utils/helperQueryNumber')
const getPagesHelper = require('../assets/utils/getPagesHelper'); const getPagesHelper = require('../assets/utils/getPagesHelper')
const Sequelize = require('sequelize'); const Sequelize = require('sequelize')
exports.getFunctions = async (req, res, next) => { exports.getFunctions = async (req, res, next) => {
const categoryId = helperQueryNumber(req.query.categoryId, 0); const categoryId = helperQueryNumber(req.query.categoryId, 0)
let { search } = req.query; let { search } = req.query
try { search = search.toLowerCase(); } catch {}; try { search = search.toLowerCase() } catch {};
const options = { const options = {
where: { where: {
isOnline: 1, isOnline: 1,
// Trie par catégorie // Trie par catégorie
...(categoryId !== 0) && { categorieId: categoryId }, ...(categoryId !== 0) && { categorieId: categoryId },
// Recherche // Recherche
... (search != undefined) && { ...(search != null) && {
[Sequelize.Op.or]: [ [Sequelize.Op.or]: [
{ title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', `%${search}%`) }, { title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', `%${search}%`) },
{ slug: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('slug')), 'LIKE', `%${search}%`) }, { slug: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('slug')), 'LIKE', `%${search}%`) },
@ -26,44 +26,44 @@ exports.getFunctions = async (req, res, next) => {
} }
}, },
include: [ include: [
{ model: Categories, attributes: ["name", "color"] } { model: Categories, attributes: ['name', 'color'] }
], ],
attributes: { attributes: {
exclude: ["updatedAt", "utilizationForm", "article", "isOnline"] exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline']
}, },
order: [['createdAt', 'DESC']] order: [['createdAt', 'DESC']]
}; }
return await getPagesHelper({ req, res, next }, Functions, options); return await getPagesHelper({ req, res, next }, Functions, options)
} }
exports.getFunctionBySlug = (req, res, next) => { exports.getFunctionBySlug = (req, res, next) => {
const { slug } = req.params; const { slug } = req.params
Functions.findOne({ Functions.findOne({
where: { slug, isOnline: 1 }, where: { slug, isOnline: 1 },
attributes: { attributes: {
exclude: ["updatedAt", "isOnline"] exclude: ['updatedAt', 'isOnline']
}, },
include: [ include: [
{ model: Categories, attributes: ["name", "color"] } { model: Categories, attributes: ['name', 'color'] }
] ]
}) })
.then((result) => { .then((result) => {
if (!result) { if (!result) {
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 })
} }
try { result.utilizationForm = JSON.parse(result.utilizationForm); } catch {} try { result.utilizationForm = JSON.parse(result.utilizationForm) } catch {}
return res.status(200).json(result); return res.status(200).json(result)
}) })
.catch((error) => { .catch((error) => {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
}); })
} }
exports.executeFunctionBySlug = (req, res, next) => { exports.executeFunctionBySlug = (req, res, next) => {
const functionOutput = functionToExecute(req.params.slug); const functionOutput = functionToExecute(req.params.slug)
if (functionOutput !== undefined) { if (functionOutput !== undefined) {
return functionOutput({ res, next }, req.body); return functionOutput({ res, next }, req.body)
} }
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 })
} }

View File

@ -1,37 +1,37 @@
const errorHandling = require('../assets/utils/errorHandling'); const errorHandling = require('../assets/utils/errorHandling')
const { serverError, requiredFields } = require('../assets/config/errors'); const { serverError, requiredFields } = require('../assets/config/errors')
const Quotes = require('../models/quotes'); const Quotes = require('../models/quotes')
const Users = require('../models/users'); const Users = require('../models/users')
const getPagesHelper = require('../assets/utils/getPagesHelper'); const getPagesHelper = require('../assets/utils/getPagesHelper')
exports.getQuotes = async (req, res, next) => { exports.getQuotes = async (req, res, next) => {
const options = { const options = {
where: { where: {
isValidated: 1, isValidated: 1
}, },
include: [ include: [
{ model: Users, attributes: ["name", "logo"] } { model: Users, attributes: ['name', 'logo'] }
], ],
attributes: { attributes: {
exclude: ["isValidated"] exclude: ['isValidated']
}, },
order: [['createdAt', 'DESC']] order: [['createdAt', 'DESC']]
}; }
return await getPagesHelper({ req, res, next }, Quotes, options); return await getPagesHelper({ req, res, next }, Quotes, options)
} }
exports.postQuote = (req, res, next) => { exports.postQuote = (req, res, next) => {
const { quote, author } = req.body; const { quote, author } = req.body
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(quote && author)) { if (!(quote && author)) {
return errorHandling(next, requiredFields); return errorHandling(next, requiredFields)
} }
Quotes.create({ quote, author, userId: req.userId }) Quotes.create({ quote, author, userId: req.userId })
.then((_result) => { .then((_result) => {
return res.status(200).json({ message: "La citation a bien été ajoutée, elle est en attente de confirmation d'un administrateur." }); return res.status(200).json({ message: "La citation a bien été ajoutée, elle est en attente de confirmation d'un administrateur." })
}) })
.catch((error) => { .catch((error) => {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
}); })
} }

View File

@ -1,6 +1,6 @@
const errorHandling = require('../assets/utils/errorHandling'); const errorHandling = require('../assets/utils/errorHandling')
const { serverError, requiredFields } = require('../assets/config/errors'); const { serverError, requiredFields } = require('../assets/config/errors')
const Tasks = require('../models/tasks'); const Tasks = require('../models/tasks')
exports.getTasks = async (req, res, next) => { exports.getTasks = async (req, res, next) => {
try { try {
@ -9,60 +9,60 @@ exports.getTasks = async (req, res, next) => {
userId: req.userId userId: req.userId
}, },
order: [['createdAt', 'DESC']] order: [['createdAt', 'DESC']]
}); })
return res.status(200).json(tasks); return res.status(200).json(tasks)
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }
exports.postTask = async (req, res, next) => { exports.postTask = async (req, res, next) => {
const { task } = req.body; const { task } = req.body
try { try {
if (!task) { if (!task) {
return errorHandling(next, requiredFields); return errorHandling(next, requiredFields)
} }
const taskResult = await Tasks.create({ task, userId: req.userId }); const taskResult = await Tasks.create({ task, userId: req.userId })
return res.status(201).json(taskResult); return res.status(201).json(taskResult)
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }
exports.putTask = async (req, res, next) => { exports.putTask = async (req, res, next) => {
const { id } = req.params; const { id } = req.params
const { isCompleted } = req.body; const { isCompleted } = req.body
try { try {
if (typeof isCompleted !== 'boolean') { if (typeof isCompleted !== 'boolean') {
return errorHandling(next, { message: "isCompleted doit être un booléen.", statusCode: 400 }); return errorHandling(next, { message: 'isCompleted doit être un booléen.', statusCode: 400 })
} }
const taskResult = await Tasks.findOne({ where: { id, userId: req.userId } }); const taskResult = await Tasks.findOne({ where: { id, userId: req.userId } })
if (!taskResult) { if (!taskResult) {
return errorHandling(next, { message: `La "tâche à faire" n'existe pas.`, statusCode: 404 }); return errorHandling(next, { message: 'La "tâche à faire" n\'existe pas.', statusCode: 404 })
} }
taskResult.isCompleted = isCompleted; taskResult.isCompleted = isCompleted
const taskSaved = await taskResult.save(); const taskSaved = await taskResult.save()
return res.status(200).json(taskSaved); return res.status(200).json(taskSaved)
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }
exports.deleteTask = async (req, res, next) => { exports.deleteTask = async (req, res, next) => {
const { id } = req.params; const { id } = req.params
try { try {
const taskResult = await Tasks.findOne({ where: { id, userId: req.userId } }); const taskResult = await Tasks.findOne({ where: { id, userId: req.userId } })
if (!taskResult) { if (!taskResult) {
return errorHandling(next, { message: `La "tâche à faire" n'existe pas.`, statusCode: 404 }); return errorHandling(next, { message: 'La "tâche à faire" n\'existe pas.', statusCode: 404 })
} }
await taskResult.destroy(); await taskResult.destroy()
return res.status(200).json({ message: `La "tâche à faire" a bien été supprimée!` }); return res.status(200).json({ message: 'La "tâche à faire" a bien été supprimée!' })
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }

View File

@ -1,277 +1,277 @@
const path = require('path'); const path = require('path')
const { validationResult } = require('express-validator'); const { validationResult } = require('express-validator')
const bcrypt = require('bcryptjs'); const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken')
const ms = require('ms'); const ms = require('ms')
const uuid = require('uuid'); const uuid = require('uuid')
const Sequelize = require('sequelize'); const Sequelize = require('sequelize')
const errorHandling = require('../assets/utils/errorHandling'); const errorHandling = require('../assets/utils/errorHandling')
const { serverError, generalError } = require('../assets/config/errors'); const { serverError, generalError } = require('../assets/config/errors')
const { JWT_SECRET, FRONT_END_HOST, EMAIL_INFO, HOST, TOKEN_LIFE } = require('../assets/config/config'); const { JWT_SECRET, FRONT_END_HOST, EMAIL_INFO, HOST, TOKEN_LIFE } = require('../assets/config/config')
const transporter = require('../assets/config/transporter'); const transporter = require('../assets/config/transporter')
const { emailUserTemplate } = require('../assets/config/emails'); const { emailUserTemplate } = require('../assets/config/emails')
const Users = require('../models/users'); const Users = require('../models/users')
const Favorites = require('../models/favorites'); const Favorites = require('../models/favorites')
const Functions = require('../models/functions'); const Functions = require('../models/functions')
const Categories = require('../models/categories'); const Categories = require('../models/categories')
const Comments = require('../models/comments'); const Comments = require('../models/comments')
const Quotes = require('../models/quotes'); const Quotes = require('../models/quotes')
const deleteFilesNameStartWith = require('../assets/utils/deleteFilesNameStartWith'); const deleteFilesNameStartWith = require('../assets/utils/deleteFilesNameStartWith')
const getPagesHelper = require('../assets/utils/getPagesHelper'); const getPagesHelper = require('../assets/utils/getPagesHelper')
async function handleEditUser (res, { name, email, biography, isPublicEmail }, userId, logoName) { async function handleEditUser (res, { name, email, biography, isPublicEmail }, userId, logoName) {
const user = await Users.findOne({ where: { id: userId } }); const user = await Users.findOne({ where: { id: userId } })
user.name = name; user.name = name
if (user.email !== email) { if (user.email !== email) {
const tempToken = uuid.v4(); const tempToken = uuid.v4()
user.email = email; user.email = email
user.isConfirmed = false; user.isConfirmed = false
user.tempToken = tempToken; user.tempToken = tempToken
await transporter.sendMail({ await transporter.sendMail({
from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`, from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`,
to: email, to: email,
subject: "FunctionProject - Confirmer l'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.") 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) { if (biography != null) {
user.biography = biography; user.biography = biography
} }
user.isPublicEmail = isPublicEmail; user.isPublicEmail = isPublicEmail
if (logoName != undefined && `/images/users/${logoName}` !== user.logo) { if (logoName != null && `/images/users/${logoName}` !== user.logo) {
user.logo = `/images/users/${logoName}`; user.logo = `/images/users/${logoName}`
} }
await user.save(); 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 }); 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) => { exports.getUsers = async (req, res, next) => {
let { search } = req.query; let { search } = req.query
try { search = search.toLowerCase(); } catch {}; try { search = search.toLowerCase() } catch {};
const options = { const options = {
where: { where: {
isConfirmed: true, isConfirmed: true,
// Recherche // Recherche
...(search != undefined) && { ...(search != null) && {
name: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', `%${search}%`) name: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', `%${search}%`)
} }
}, },
attributes: { attributes: {
exclude: ["updatedAt", "isAdmin", "isConfirmed", "password", "tempToken", "tempExpirationToken", "isPublicEmail", "email"] exclude: ['updatedAt', 'isAdmin', 'isConfirmed', 'password', 'tempToken', 'tempExpirationToken', 'isPublicEmail', 'email']
}, },
order: [['createdAt', 'DESC']] order: [['createdAt', 'DESC']]
}; }
return await getPagesHelper({ req, res, next }, Users, options); return await getPagesHelper({ req, res, next }, Users, options)
} }
exports.putUser = async (req, res, next) => { exports.putUser = async (req, res, next) => {
const { name, email, biography, isPublicEmail } = req.body; const { name, email, biography, isPublicEmail } = req.body
const logo = req.files.logo; const logo = req.files.logo
const errors = validationResult(req); const errors = validationResult(req)
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }); return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 })
} }
if (logo != undefined) { if (logo != null) {
if (!logo || logo.truncated && ( if ((!logo || logo.truncated) && (
logo.mimetype !== 'image/png' || logo.mimetype !== 'image/png' ||
logo.mimetype !== 'image/jpg' || logo.mimetype !== 'image/jpg' ||
logo.mimetype !== 'image/jpeg' || logo.mimetype !== 'image/jpeg' ||
logo.mimetype !== 'image/gif' logo.mimetype !== 'image/gif'
)) { )) {
return errorHandling(next, { message:"Le profil doit avoir une image valide (PNG, JPG, GIF) et moins de 5mo.", statusCode: 400 }); 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('.'); const splitedLogoName = logo.name.split('.')
if (splitedLogoName.length !== 2) return errorHandling(next, serverError); if (splitedLogoName.length !== 2) return errorHandling(next, serverError)
const logoName = name + req.userId + '.' + splitedLogoName[1]; const logoName = name + req.userId + '.' + splitedLogoName[1]
// Supprime les anciens logo // Supprime les anciens logo
try { try {
deleteFilesNameStartWith(`${name + req.userId}`, path.join(__dirname, '..', 'assets', 'images', 'users'), async () => { deleteFilesNameStartWith(`${name + req.userId}`, path.join(__dirname, '..', 'assets', 'images', 'users'), async () => {
logo.mv(path.join(__dirname, '..', 'assets', 'images', 'users', logoName), async (error) => { logo.mv(path.join(__dirname, '..', 'assets', 'images', 'users', logoName), async (error) => {
if (error) return errorHandling(next, serverError); if (error) return errorHandling(next, serverError)
return await handleEditUser(res, { name, email, biography, isPublicEmail }, req.userId, logoName); return await handleEditUser(res, { name, email, biography, isPublicEmail }, req.userId, logoName)
}); })
}); })
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} else { } else {
try { try {
return await handleEditUser(res, { name, email, biography, isPublicEmail }, req.userId, null); return await handleEditUser(res, { name, email, biography, isPublicEmail }, req.userId, null)
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }
} }
exports.register = async (req, res, next) => { exports.register = async (req, res, next) => {
const { name, email, password } = req.body; const { name, email, password } = req.body
const errors = validationResult(req); const errors = validationResult(req)
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }); return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 })
} }
try { try {
const hashedPassword = await bcrypt.hash(password, 12); const hashedPassword = await bcrypt.hash(password, 12)
const tempToken = uuid.v4(); const tempToken = uuid.v4()
await Users.create({ email, name, password: hashedPassword, tempToken }); await Users.create({ email, name, password: hashedPassword, tempToken })
await transporter.sendMail({ await transporter.sendMail({
from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`, from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`,
to: email, to: email,
subject: "FunctionProject - Confirmer l'inscription", 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.") 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." }); return res.status(201).json({ result: "Vous y êtes presque, veuillez vérifier vos emails pour confirmer l'inscription." })
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }
exports.login = async (req, res, next) => { exports.login = async (req, res, next) => {
const { email, password } = req.body; const { email, password } = req.body
const errors = validationResult(req); const errors = validationResult(req)
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }); return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 })
} }
try { try {
const user = await Users.findOne({ where: { email } }); const user = await Users.findOne({ where: { email } })
if (!user) { if (!user) {
return errorHandling(next, { message: "Le mot de passe ou l'adresse email n'est pas valide.", statusCode: 400 }); 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); const isEqual = await bcrypt.compare(password, user.password)
if (!isEqual) { if (!isEqual) {
return errorHandling(next, { message: "Le mot de passe ou l'adresse email n'est pas valide.", statusCode: 400 }); return errorHandling(next, { message: "Le mot de passe ou l'adresse email n'est pas valide.", statusCode: 400 })
} }
if (!user.isConfirmed) { if (!user.isConfirmed) {
return errorHandling(next, { message: "Vous devez valider votre adresse email pour votre première connexion.", statusCode: 400 }); return errorHandling(next, { message: 'Vous devez valider votre adresse email pour votre première connexion.', statusCode: 400 })
} }
const token = jwt.sign({ const token = jwt.sign({
email: user.email, userId: user.id email: user.email, userId: user.id
}, JWT_SECRET, { expiresIn: TOKEN_LIFE }); }, 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) }); 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) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }
exports.confirmEmail = async (req, res, next) => { exports.confirmEmail = async (req, res, next) => {
const { tempToken } = req.params; const { tempToken } = req.params
if (!tempToken) { if (!tempToken) {
return errorHandling(next, generalError); return errorHandling(next, generalError)
} }
try { try {
const user = await Users.findOne({ where: { tempToken, isConfirmed: false } }); const user = await Users.findOne({ where: { tempToken, isConfirmed: false } })
if (!user) { if (!user) {
return errorHandling(next, { message: "Le token n'est pas valide.", statusCode: 400 }); return errorHandling(next, { message: "Le token n'est pas valide.", statusCode: 400 })
} }
user.tempToken = null; user.tempToken = null
user.isConfirmed = true; user.isConfirmed = true
await user.save(); await user.save()
return res.redirect(`${FRONT_END_HOST}/users/login?isConfirmed=true`); return res.redirect(`${FRONT_END_HOST}/users/login?isConfirmed=true`)
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }
exports.resetPassword = async (req, res, next) => { exports.resetPassword = async (req, res, next) => {
const { email } = req.body; const { email } = req.body
const errors = validationResult(req); const errors = validationResult(req)
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }); return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 })
} }
try { try {
const user = await Users.findOne({ where: { email, tempToken: null } }); const user = await Users.findOne({ where: { email, tempToken: null } })
if (!user) { if (!user) {
return errorHandling(next, { message: "L'adresse email n'existe pas ou une demande est déjà en cours.", statusCode: 400 }); return errorHandling(next, { message: "L'adresse email n'existe pas ou une demande est déjà en cours.", statusCode: 400 })
} }
const tempToken = uuid.v4(); const tempToken = uuid.v4()
user.tempExpirationToken = Date.now() + 3600000; // 1 heure user.tempExpirationToken = Date.now() + 3600000 // 1 heure
user.tempToken = tempToken; user.tempToken = tempToken
await user.save(); await user.save()
await transporter.sendMail({ await transporter.sendMail({
from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`, from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`,
to: email, to: email,
subject: "FunctionProject - Réinitialisation du mot de passe", 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.") 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!" }); return res.status(200).json({ result: 'Demande de réinitialisation du mot de passe réussi, veuillez vérifier vos emails!' })
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }
exports.newPassword = async (req, res, next) => { exports.newPassword = async (req, res, next) => {
const { tempToken, password } = req.body; const { tempToken, password } = req.body
const errors = validationResult(req); const errors = validationResult(req)
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }); return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 })
} }
try { try {
const user = await Users.findOne({ where: { tempToken } }); const user = await Users.findOne({ where: { tempToken } })
if (!user && parseInt(tempExpirationToken) < Date.now()) { if (!user && parseInt(user.tempExpirationToken) < Date.now()) {
return errorHandling(next, { message: "Le token n'est pas valide.", statusCode: 400 }); return errorHandling(next, { message: "Le token n'est pas valide.", statusCode: 400 })
} }
const hashedPassword = await bcrypt.hash(password, 12); const hashedPassword = await bcrypt.hash(password, 12)
user.password = hashedPassword; user.password = hashedPassword
user.tempToken = null; user.tempToken = null
user.tempExpirationToken = null; user.tempExpirationToken = null
await user.save(); await user.save()
return res.status(200).json({ result: "Le mot de passe a bien été modifié!" }); return res.status(200).json({ result: 'Le mot de passe a bien été modifié!' })
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }
exports.getUserInfo = async (req, res, next) => { exports.getUserInfo = async (req, res, next) => {
const { name } = req.params; const { name } = req.params
try { try {
const user = await Users.findOne({ const user = await Users.findOne({
where: { name, isConfirmed: true }, where: { name, isConfirmed: true },
attributes: { attributes: {
exclude: ["updatedAt", "isAdmin", "isConfirmed", "password", "tempToken", "tempExpirationToken"] exclude: ['updatedAt', 'isAdmin', 'isConfirmed', 'password', 'tempToken', 'tempExpirationToken']
}, }
}); })
if (!user) { if (!user) {
return errorHandling(next, { message: "L'utilisateur n'existe pas.", statusCode: 404 }); return errorHandling(next, { message: "L'utilisateur n'existe pas.", statusCode: 404 })
} }
const favorites = await Favorites.findAll({ const favorites = await Favorites.findAll({
where: { userId: user.id }, where: { userId: user.id },
include: [ include: [
{ model: Functions, attributes: { exclude: ["updatedAt", "utilizationForm", "article", "isOnline"] }, include: { model: Categories, attributes: ["name", "color"] } } { model: Functions, attributes: { exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline'] }, include: { model: Categories, attributes: ['name', 'color'] } }
], ],
order: [['createdAt', 'DESC']], order: [['createdAt', 'DESC']],
limit: 5 limit: 5
}); })
const favoritesArray = favorites.map((favorite) => favorite.function); const favoritesArray = favorites.map((favorite) => favorite.function)
const comments = await Comments.findAll({ const comments = await Comments.findAll({
where: { userId: user.id }, where: { userId: user.id },
include: [ include: [
{ model: Functions, attributes: { exclude: ["updatedAt", "utilizationForm", "article", "isOnline"] } } { model: Functions, attributes: { exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline'] } }
], ],
order: [['createdAt', 'DESC']], order: [['createdAt', 'DESC']],
limit: 5 limit: 5
}); })
const commentsArray = comments.map((commentObject) => { const commentsArray = comments.map((commentObject) => {
return { return {
id: commentObject.id, id: commentObject.id,
message: commentObject.message, message: commentObject.message,
createdAt: commentObject.createdAt, createdAt: commentObject.createdAt,
function: commentObject.function.dataValues function: commentObject.function.dataValues
}; }
}); })
const quotesArray = await Quotes.findAll({ const quotesArray = await Quotes.findAll({
where: { userId: user.id }, where: { userId: user.id },
attributes: { attributes: {
exclude: ["updatedAt", "createdAt", "isValidated", "userId", "id"] exclude: ['updatedAt', 'createdAt', 'isValidated', 'userId', 'id']
}, },
order: [['createdAt', 'DESC']], order: [['createdAt', 'DESC']],
limit: 5, limit: 5
}); })
const userObject = { const userObject = {
// Si Public Email // Si Public Email
...(user.isPublicEmail) && { email: user.email }, ...(user.isPublicEmail) && { email: user.email },
@ -283,10 +283,10 @@ exports.getUserInfo = async (req, res, next) => {
favoritesArray, favoritesArray,
commentsArray, commentsArray,
quotesArray quotesArray
}; }
return res.status(200).json(userObject); return res.status(200).json(userObject)
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
} }
} }

View File

@ -1,23 +1,23 @@
const errorHandling = require('../assets/utils/errorHandling'); const errorHandling = require('../assets/utils/errorHandling')
const { serverError } = require('../assets/config/errors'); const { serverError } = require('../assets/config/errors')
const Users = require('../models/users'); const Users = require('../models/users')
module.exports = (req, _res, next) => { module.exports = (req, _res, next) => {
if (!req.userId) { if (!req.userId) {
return errorHandling(next, { message: "Vous n'êtes pas connecté.", statusCode: 403 }); return errorHandling(next, { message: "Vous n'êtes pas connecté.", statusCode: 403 })
} }
Users.findOne({ where: { id: req.userId } }) Users.findOne({ where: { id: req.userId } })
.then((user) => { .then((user) => {
if (!user) { if (!user) {
return errorHandling(next, { message: "Le mot de passe ou l'adresse email n'est pas valide.", statusCode: 403 }); return errorHandling(next, { message: "Le mot de passe ou l'adresse email n'est pas valide.", statusCode: 403 })
} }
if (!user.isAdmin) { if (!user.isAdmin) {
return errorHandling(next, { message: "Vous n'êtes pas administrateur.", statusCode: 403 }); return errorHandling(next, { message: "Vous n'êtes pas administrateur.", statusCode: 403 })
} }
next(); next()
}) })
.catch((error) => { .catch((error) => {
console.log(error); console.log(error)
return errorHandling(next, serverError); return errorHandling(next, serverError)
}); })
} }

View File

@ -1,24 +1,24 @@
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken')
const errorHandling = require('../assets/utils/errorHandling'); const errorHandling = require('../assets/utils/errorHandling')
const { JWT_SECRET } = require('../assets/config/config'); const { JWT_SECRET } = require('../assets/config/config')
module.exports = (req, _res, next) => { module.exports = (req, _res, next) => {
const token = req.get('Authorization'); const token = req.get('Authorization')
if (!token) { if (!token) {
return errorHandling(next, { message: "Vous devez être connecter pour effectuer cette opération.", statusCode: 403 }); return errorHandling(next, { message: 'Vous devez être connecter pour effectuer cette opération.', statusCode: 403 })
} }
let decodedToken; let decodedToken
try { try {
decodedToken = jwt.verify(token, JWT_SECRET); decodedToken = jwt.verify(token, JWT_SECRET)
} catch (error) { } catch (error) {
return errorHandling(next, { message: "Vous devez être connecter pour effectuer cette opération.", statusCode: 403 }); return errorHandling(next, { message: 'Vous devez être connecter pour effectuer cette opération.', statusCode: 403 })
} }
if (!decodedToken) { if (!decodedToken) {
return errorHandling(next, { message: "Vous devez être connecter pour effectuer cette opération.", statusCode: 403 }); return errorHandling(next, { message: 'Vous devez être connecter pour effectuer cette opération.', statusCode: 403 })
} }
req.userId = decodedToken.userId; req.userId = decodedToken.userId
next(); next()
} }

View File

@ -1,13 +1,13 @@
const Sequelize = require('sequelize'); const Sequelize = require('sequelize')
const sequelize = require('../assets/utils/database'); const sequelize = require('../assets/utils/database')
module.exports = sequelize.define('categorie', { module.exports = sequelize.define('categorie', {
name: { name: {
type: Sequelize.STRING, type: Sequelize.STRING,
allowNull: false, allowNull: false
}, },
color: { color: {
type: Sequelize.STRING, type: Sequelize.STRING,
allowNull: false, allowNull: false
} }
}); })

View File

@ -1,9 +1,9 @@
const Sequelize = require('sequelize'); const Sequelize = require('sequelize')
const sequelize = require('../assets/utils/database'); const sequelize = require('../assets/utils/database')
module.exports = sequelize.define('comment', { module.exports = sequelize.define('comment', {
message: { message: {
type: Sequelize.TEXT, type: Sequelize.TEXT,
allowNull: false allowNull: false
} }
}); })

View File

@ -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', {})

View File

@ -1,14 +1,14 @@
const Sequelize = require('sequelize'); const Sequelize = require('sequelize')
const sequelize = require('../assets/utils/database'); const sequelize = require('../assets/utils/database')
module.exports = sequelize.define('function', { module.exports = sequelize.define('function', {
title: { title: {
type: Sequelize.STRING, type: Sequelize.STRING,
allowNull: false, allowNull: false
}, },
slug: { slug: {
type: Sequelize.STRING, type: Sequelize.STRING,
allowNull: false, allowNull: false
}, },
description: { description: {
type: Sequelize.STRING, type: Sequelize.STRING,
@ -17,7 +17,7 @@ module.exports = sequelize.define('function', {
image: { image: {
type: Sequelize.STRING, type: Sequelize.STRING,
allowNull: false, allowNull: false,
defaultValue: "/images/functions/default.png" defaultValue: '/images/functions/default.png'
}, },
type: { type: {
type: Sequelize.STRING, type: Sequelize.STRING,
@ -36,4 +36,4 @@ module.exports = sequelize.define('function', {
allowNull: false, allowNull: false,
defaultValue: 0 defaultValue: 0
} }
}); })

View File

@ -1,18 +1,18 @@
const Sequelize = require('sequelize'); const Sequelize = require('sequelize')
const sequelize = require('../assets/utils/database'); const sequelize = require('../assets/utils/database')
module.exports = sequelize.define('quote', { module.exports = sequelize.define('quote', {
quote: { quote: {
type: Sequelize.STRING, type: Sequelize.STRING,
allowNull: false, allowNull: false
}, },
author: { author: {
type: Sequelize.STRING, type: Sequelize.STRING,
allowNull: false, allowNull: false
}, },
isValidated: { isValidated: {
type: Sequelize.BOOLEAN, type: Sequelize.BOOLEAN,
allowNull: false, allowNull: false,
defaultValue: 0 defaultValue: 0
} }
}); })

View File

@ -1,13 +1,13 @@
const Sequelize = require('sequelize'); const Sequelize = require('sequelize')
const sequelize = require('../assets/utils/database'); const sequelize = require('../assets/utils/database')
module.exports = sequelize.define('short_link', { module.exports = sequelize.define('short_link', {
url: { url: {
type: Sequelize.TEXT, type: Sequelize.TEXT,
allowNull: false, allowNull: false
}, },
shortcut: { shortcut: {
type: Sequelize.TEXT, type: Sequelize.TEXT,
allowNull: false, allowNull: false
} }
}); })

View File

@ -1,14 +1,14 @@
const Sequelize = require('sequelize'); const Sequelize = require('sequelize')
const sequelize = require('../assets/utils/database'); const sequelize = require('../assets/utils/database')
module.exports = sequelize.define('task', { module.exports = sequelize.define('task', {
task: { task: {
type: Sequelize.STRING, type: Sequelize.STRING,
allowNull: false, allowNull: false
}, },
isCompleted: { isCompleted: {
type: Sequelize.BOOLEAN, type: Sequelize.BOOLEAN,
allowNull: false, allowNull: false,
defaultValue: 0 defaultValue: 0
} }
}); })

View File

@ -1,26 +1,26 @@
const Sequelize = require('sequelize'); const Sequelize = require('sequelize')
const sequelize = require('../assets/utils/database'); const sequelize = require('../assets/utils/database')
module.exports = sequelize.define('user', { module.exports = sequelize.define('user', {
name: { name: {
type: Sequelize.STRING, type: Sequelize.STRING,
allowNull: false, allowNull: false
}, },
email: { email: {
type: Sequelize.STRING, type: Sequelize.STRING,
allowNull: false, allowNull: false
}, },
password: { password: {
type: Sequelize.STRING, type: Sequelize.STRING,
allowNull: false, allowNull: false
}, },
biography: { biography: {
type: Sequelize.TEXT, type: Sequelize.TEXT,
defaultValue: "" defaultValue: ''
}, },
logo: { logo: {
type: Sequelize.STRING, type: Sequelize.STRING,
defaultValue: "/images/users/default.png" defaultValue: '/images/users/default.png'
}, },
isConfirmed: { isConfirmed: {
type: Sequelize.BOOLEAN, type: Sequelize.BOOLEAN,
@ -42,4 +42,4 @@ module.exports = sequelize.define('user', {
type: Sequelize.DATE, type: Sequelize.DATE,
allowNull: true allowNull: true
} }
}); })

2446
api/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,14 +2,11 @@
"name": "api", "name": "api",
"version": "2.0.0", "version": "2.0.0",
"description": "Backend REST API for FunctionProject", "description": "Backend REST API for FunctionProject",
"main": "app.js",
"scripts": { "scripts": {
"start": "node app.js", "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": { "dependencies": {
"axios": "^0.19.2", "axios": "^0.19.2",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
@ -33,6 +30,8 @@
"devDependencies": { "devDependencies": {
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"morgan": "^1.9.1", "morgan": "^1.9.1",
"nodemon": "^2.0.2" "nodemon": "^2.0.4",
"snazzy": "^8.0.0",
"standard": "^14.3.4"
} }
} }

View File

@ -1,11 +1,11 @@
const { Router } = require('express'); const { Router } = require('express')
const fileUpload = require('express-fileupload'); const fileUpload = require('express-fileupload')
const { body } = require('express-validator'); const { body } = require('express-validator')
const adminController = require('../controllers/admin'); const adminController = require('../controllers/admin')
const Functions = require('../models/functions'); const Functions = require('../models/functions')
const Categories = require('../models/categories'); const Categories = require('../models/categories')
const AdminRouter = Router(); const AdminRouter = Router()
AdminRouter.route('/functions') AdminRouter.route('/functions')
@ -24,78 +24,78 @@ AdminRouter.route('/functions')
body('title') body('title')
.not() .not()
.isEmpty() .isEmpty()
.withMessage("La fonction doit avoir un titre.") .withMessage('La fonction doit avoir un titre.')
.isLength({ max: 100 }) .isLength({ max: 100 })
.withMessage("Le titre est trop long.") .withMessage('Le titre est trop long.')
.custom(((title) => { .custom((title) => {
if (title === 'undefined') { if (title === 'undefined') {
return Promise.reject("La fonction doit avoir un titre."); return Promise.reject(new Error('La fonction doit avoir un titre.'))
} }
return true; return true
})), }),
body('slug') body('slug')
.not() .not()
.isEmpty() .isEmpty()
.withMessage("La fonction doit avoir un slug.") .withMessage('La fonction doit avoir un slug.')
.isLength({ max: 100 }) .isLength({ max: 100 })
.withMessage("Le slug est trop long.") .withMessage('Le slug est trop long.')
.custom(((slug) => { .custom((slug) => {
if (slug === 'undefined') { if (slug === 'undefined') {
return Promise.reject("La fonction doit avoir un slug."); return Promise.reject(new Error('La fonction doit avoir un slug.'))
} }
return true; return true
})) })
.custom((async (slug) => { .custom(async (slug) => {
try { try {
const FunctionSlug = await Functions.findOne({ where: { slug } }); const FunctionSlug = await Functions.findOne({ where: { slug } })
if (FunctionSlug) { if (FunctionSlug) {
return Promise.reject("Le slug existe déjà..."); return Promise.reject(new Error('Le slug existe déjà...'))
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error)
} }
return true; return true
})), }),
body('description') body('description')
.not() .not()
.isEmpty() .isEmpty()
.withMessage("La fonction doit avoir une description.") .withMessage('La fonction doit avoir une description.')
.isLength({ max: 255, min: 1 }) .isLength({ max: 255, min: 1 })
.withMessage("La description est trop longue.") .withMessage('La description est trop longue.')
.custom(((description) => { .custom((description) => {
if (description === 'undefined') { if (description === 'undefined') {
return Promise.reject("La fonction doit avoir une description."); return Promise.reject(new Error('La fonction doit avoir une description.'))
} }
return true; return true
})), }),
body('categorieId') body('categorieId')
.not() .not()
.isEmpty() .isEmpty()
.withMessage("La fonction doit avoir une catégorie.") .withMessage('La fonction doit avoir une catégorie.')
.custom(async (categorieId) => { .custom(async (categorieId) => {
try { try {
const categorieFound = await Categories.findOne({ where: { id: parseInt(categorieId) } }); const categorieFound = await Categories.findOne({ where: { id: parseInt(categorieId) } })
if (!categorieFound) { if (!categorieFound) {
return Promise.reject("La catégorie n'existe pas!"); return Promise.reject(new Error("La catégorie n'existe pas!"))
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error)
} }
return true; return true
}), }),
body('type') body('type')
.custom((type) => { .custom((type) => {
if (!(type === 'article' || type === 'form' || type === 'page')) { if (!(type === 'article' || type === 'form' || type === 'page')) {
return Promise.reject('Le type de la fonction peut être : article, form ou page.'); return Promise.reject(new Error('Le type de la fonction peut être : article, form ou page.'))
} }
return true; return true
}) })
], adminController.postFunction); ], adminController.postFunction)
AdminRouter.route('/functions/:slug') AdminRouter.route('/functions/:slug')
// Récupère les informations d'une fonction // Récupère les informations d'une fonction
.get(adminController.getFunctionBySlug); .get(adminController.getFunctionBySlug)
AdminRouter.route('/functions/:id') AdminRouter.route('/functions/:id')
@ -111,78 +111,78 @@ AdminRouter.route('/functions/:id')
body('title') body('title')
.not() .not()
.isEmpty() .isEmpty()
.withMessage("La fonction doit avoir un titre.") .withMessage('La fonction doit avoir un titre.')
.isLength({ max: 100 }) .isLength({ max: 100 })
.withMessage("Le titre est trop long.") .withMessage('Le titre est trop long.')
.custom(((title) => { .custom((title) => {
if (title === 'undefined') { if (title === 'undefined') {
return Promise.reject("La fonction doit avoir un titre."); return Promise.reject(new Error('La fonction doit avoir un titre.'))
} }
return true; return true
})), }),
body('slug') body('slug')
.not() .not()
.isEmpty() .isEmpty()
.withMessage("La fonction doit avoir un slug.") .withMessage('La fonction doit avoir un slug.')
.isLength({ max: 100 }) .isLength({ max: 100 })
.withMessage("Le slug est trop long.") .withMessage('Le slug est trop long.')
.custom(((slug) => { .custom((slug) => {
if (slug === 'undefined') { if (slug === 'undefined') {
return Promise.reject("La fonction doit avoir un slug."); return Promise.reject(new Error('La fonction doit avoir un slug.'))
} }
return true; return true
})), }),
body('description') body('description')
.not() .not()
.isEmpty() .isEmpty()
.withMessage("La fonction doit avoir une description.") .withMessage('La fonction doit avoir une description.')
.isLength({ max: 255, min: 1 }) .isLength({ max: 255, min: 1 })
.withMessage("La description est trop longue.") .withMessage('La description est trop longue.')
.custom(((description) => { .custom((description) => {
if (description === 'undefined') { if (description === 'undefined') {
return Promise.reject("La fonction doit avoir une description."); return Promise.reject(new Error('La fonction doit avoir une description.'))
} }
return true; return true
})), }),
body('categorieId') body('categorieId')
.not() .not()
.isEmpty() .isEmpty()
.withMessage("La fonction doit avoir une catégorie.") .withMessage('La fonction doit avoir une catégorie.')
.custom(async (categorieId) => { .custom(async (categorieId) => {
try { try {
const categorieFound = await Categories.findOne({ where: { id: parseInt(categorieId) } }); const categorieFound = await Categories.findOne({ where: { id: parseInt(categorieId) } })
if (!categorieFound) { if (!categorieFound) {
return Promise.reject("La catégorie n'existe pas!"); return Promise.reject(new Error("La catégorie n'existe pas!"))
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error)
} }
return true; return true
}), }),
body('type') body('type')
.custom((type) => { .custom((type) => {
if (!(type === 'article' || type === 'form' || type === 'page')) { if (!(type === 'article' || type === 'form' || type === 'page')) {
return Promise.reject('Le type de la fonction peut être : article, form ou page.'); return Promise.reject(new Error('Le type de la fonction peut être : article, form ou page.'))
} }
return true; return true
}) })
], adminController.putFunction) ], adminController.putFunction)
// Supprime une fonction avec son id // Supprime une fonction avec son id
.delete(adminController.deleteFunction); .delete(adminController.deleteFunction)
AdminRouter.route('/functions/article/:id') AdminRouter.route('/functions/article/:id')
.put(adminController.putFunctionArticle); .put(adminController.putFunctionArticle)
AdminRouter.route('/functions/form/:id') AdminRouter.route('/functions/form/:id')
.put(adminController.putFunctionForm); .put(adminController.putFunctionForm)
AdminRouter.route('/categories') AdminRouter.route('/categories')
// Crée une catégorie // Crée une catégorie
.post(adminController.postCategory); .post(adminController.postCategory)
AdminRouter.route('/categories/:id') AdminRouter.route('/categories/:id')
@ -190,16 +190,16 @@ AdminRouter.route('/categories/:id')
.put(adminController.putCategory) .put(adminController.putCategory)
// Supprime une catégorie avec son id // Supprime une catégorie avec son id
.delete(adminController.deleteCategory); .delete(adminController.deleteCategory)
AdminRouter.route('/quotes') AdminRouter.route('/quotes')
// Récupère les citations pas encore validées // Récupère les citations pas encore validées
.get(adminController.getQuotes); .get(adminController.getQuotes)
AdminRouter.route('/quotes/:id') AdminRouter.route('/quotes/:id')
// Valide ou supprime une citation // Valide ou supprime une citation
.put(adminController.putQuote); .put(adminController.putQuote)
module.exports = AdminRouter; module.exports = AdminRouter

View File

@ -1,11 +1,11 @@
const { Router } = require('express'); const { Router } = require('express')
const categoriesController = require('../controllers/categories'); const categoriesController = require('../controllers/categories')
const CategoriesRouter = Router(); const CategoriesRouter = Router()
CategoriesRouter.route('/') CategoriesRouter.route('/')
// Récupère les catégories // Récupère les catégories
.get(categoriesController.getCategories); .get(categoriesController.getCategories)
module.exports = CategoriesRouter; module.exports = CategoriesRouter

View File

@ -1,8 +1,8 @@
const { Router } = require('express'); const { Router } = require('express')
const commentsController = require('../controllers/comments'); const commentsController = require('../controllers/comments')
const isAuth = require('../middlewares/isAuth'); const isAuth = require('../middlewares/isAuth')
const CommentsRouter = Router(); const CommentsRouter = Router()
CommentsRouter.route('/:commentId') CommentsRouter.route('/:commentId')
@ -10,7 +10,7 @@ CommentsRouter.route('/:commentId')
.put(isAuth, commentsController.putCommentsById) .put(isAuth, commentsController.putCommentsById)
// Supprime un commentaire // Supprime un commentaire
.delete(isAuth, commentsController.deleteCommentById); .delete(isAuth, commentsController.deleteCommentById)
CommentsRouter.route('/:functionId') CommentsRouter.route('/:functionId')
@ -18,6 +18,6 @@ CommentsRouter.route('/:functionId')
.get(commentsController.getCommentsByFunctionId) .get(commentsController.getCommentsByFunctionId)
// Permet à un utilisateur de poster un commentaire sur une fonction // Permet à un utilisateur de poster un commentaire sur une fonction
.post(isAuth, commentsController.postCommentsByFunctionId); .post(isAuth, commentsController.postCommentsByFunctionId)
module.exports = CommentsRouter; module.exports = CommentsRouter

View File

@ -1,8 +1,8 @@
const { Router } = require('express'); const { Router } = require('express')
const favoritesController = require('../controllers/favorites'); const favoritesController = require('../controllers/favorites')
const isAuth = require('../middlewares/isAuth'); const isAuth = require('../middlewares/isAuth')
const FavoritesRouter = Router(); const FavoritesRouter = Router()
FavoritesRouter.route('/:functionId') FavoritesRouter.route('/:functionId')
@ -13,6 +13,6 @@ FavoritesRouter.route('/:functionId')
.post(isAuth, favoritesController.postFavoriteByFunctionId) .post(isAuth, favoritesController.postFavoriteByFunctionId)
// Supprime une fonction des favoris d'un utilisateur // Supprime une fonction des favoris d'un utilisateur
.delete(isAuth, favoritesController.deleteFavoriteByFunctionId); .delete(isAuth, favoritesController.deleteFavoriteByFunctionId)
module.exports = FavoritesRouter; module.exports = FavoritesRouter

View File

@ -1,12 +1,12 @@
const { Router } = require('express'); const { Router } = require('express')
const functionsController = require('../controllers/functions'); const functionsController = require('../controllers/functions')
const FunctionsRouter = Router(); const FunctionsRouter = Router()
FunctionsRouter.route('/') FunctionsRouter.route('/')
// Récupère les fonctions // Récupère les fonctions
.get(functionsController.getFunctions); .get(functionsController.getFunctions)
FunctionsRouter.route('/:slug') FunctionsRouter.route('/:slug')
@ -14,6 +14,6 @@ FunctionsRouter.route('/:slug')
.get(functionsController.getFunctionBySlug) .get(functionsController.getFunctionBySlug)
// Exécute la fonction demandée en paramètre // Exécute la fonction demandée en paramètre
.post(functionsController.executeFunctionBySlug); .post(functionsController.executeFunctionBySlug)
module.exports = FunctionsRouter; module.exports = FunctionsRouter

View File

@ -1,8 +1,8 @@
const { Router } = require('express'); const { Router } = require('express')
const quotesController = require('../controllers/quotes'); const quotesController = require('../controllers/quotes')
const isAuth = require('../middlewares/isAuth'); const isAuth = require('../middlewares/isAuth')
const QuotesRouter = Router(); const QuotesRouter = Router()
QuotesRouter.route('/') QuotesRouter.route('/')
@ -10,6 +10,6 @@ QuotesRouter.route('/')
.get(quotesController.getQuotes) .get(quotesController.getQuotes)
// Proposer une citation // Proposer une citation
.post(isAuth, quotesController.postQuote); .post(isAuth, quotesController.postQuote)
module.exports = QuotesRouter; module.exports = QuotesRouter

View File

@ -1,8 +1,8 @@
const { Router } = require('express'); const { Router } = require('express')
const tasksController = require('../controllers/tasks'); const tasksController = require('../controllers/tasks')
const isAuth = require('../middlewares/isAuth'); const isAuth = require('../middlewares/isAuth')
const TasksRouter = Router(); const TasksRouter = Router()
TasksRouter.route('/') TasksRouter.route('/')
@ -10,7 +10,7 @@ TasksRouter.route('/')
.get(isAuth, tasksController.getTasks) .get(isAuth, tasksController.getTasks)
// Poster une nouvelle tâche à faire // Poster une nouvelle tâche à faire
.post(isAuth, tasksController.postTask); .post(isAuth, tasksController.postTask)
TasksRouter.route('/:id') TasksRouter.route('/:id')
@ -18,6 +18,6 @@ TasksRouter.route('/:id')
.put(isAuth, tasksController.putTask) .put(isAuth, tasksController.putTask)
// Supprimer une tâche à faire // Supprimer une tâche à faire
.delete(isAuth, tasksController.deleteTask); .delete(isAuth, tasksController.deleteTask)
module.exports = TasksRouter; module.exports = TasksRouter

View File

@ -1,12 +1,12 @@
const { Router } = require('express'); const { Router } = require('express')
const { body } = require('express-validator'); const { body } = require('express-validator')
const fileUpload = require('express-fileupload'); const fileUpload = require('express-fileupload')
const usersController = require('../controllers/users'); const usersController = require('../controllers/users')
const { requiredFields } = require('../assets/config/errors'); const { requiredFields } = require('../assets/config/errors')
const Users = require('../models/users'); const Users = require('../models/users')
const isAuth = require('../middlewares/isAuth'); const isAuth = require('../middlewares/isAuth')
const UsersRouter = Router(); const UsersRouter = Router()
UsersRouter.route('/') UsersRouter.route('/')
@ -25,38 +25,38 @@ UsersRouter.route('/')
[ [
body('email') body('email')
.isEmail() .isEmail()
.withMessage("Veuillez rentré une adresse mail valide.") .withMessage('Veuillez rentré une adresse mail valide.')
.custom((async (email) => { .custom(async (email) => {
try { try {
const user = await Users.findOne({ where: { email } }); const user = await Users.findOne({ where: { email } })
if (user && user.email !== email) { if (user && user.email !== email) {
return Promise.reject("L'adresse email existe déjà..."); return Promise.reject(new Error("L'adresse email existe déjà..."))
} }
} catch (error) { } catch (error) {
return console.log(error); return console.log(error)
} }
return true; return true
})) })
.normalizeEmail(), .normalizeEmail(),
body('name') body('name')
.trim() .trim()
.not() .not()
.isEmpty() .isEmpty()
.withMessage("Vous devez avoir un nom (ou pseudo).") .withMessage('Vous devez avoir un nom (ou pseudo).')
.isAlphanumeric() .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 }) .isLength({ max: 30 })
.withMessage("Votre nom est trop long") .withMessage('Votre nom est trop long')
.custom(async (name) => { .custom(async (name) => {
try { try {
const user = await Users.findOne({ where: { name } }); const user = await Users.findOne({ where: { name } })
if (user && user.name !== name) { if (user && user.name !== name) {
return Promise.reject("Le nom existe déjà..."); return Promise.reject(new Error('Le nom existe déjà...'))
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error)
} }
return true; return true
}), }),
body('isPublicEmail') body('isPublicEmail')
.isBoolean() .isBoolean()
@ -64,7 +64,7 @@ UsersRouter.route('/')
body('biography') body('biography')
.trim() .trim()
.escape() .escape()
], usersController.putUser); ], usersController.putUser)
// Permet de se connecter // Permet de se connecter
UsersRouter.post('/login', [ UsersRouter.post('/login', [
@ -76,55 +76,55 @@ UsersRouter.post('/login', [
.not() .not()
.isEmpty() .isEmpty()
.withMessage(requiredFields.message) .withMessage(requiredFields.message)
], usersController.login); ], usersController.login)
// Récupère les informations public d'un profil // Récupère les informations public d'un profil
UsersRouter.get('/:name', usersController.getUserInfo); UsersRouter.get('/:name', usersController.getUserInfo)
// Permet de s'inscrire // Permet de s'inscrire
UsersRouter.post('/register', [ UsersRouter.post('/register', [
body('email') body('email')
.isEmail() .isEmail()
.withMessage("Veuillez rentré une adresse mail valide.") .withMessage('Veuillez rentré une adresse mail valide.')
.custom((async (email) => { .custom(async (email) => {
try { try {
const user = await Users.findOne({ where: { email } }); const user = await Users.findOne({ where: { email } })
if (user) { if (user) {
return Promise.reject("L'adresse email existe déjà..."); return Promise.reject(new Error("L'adresse email existe déjà..."))
} }
} catch (error) { } catch (error) {
return console.log(error); return console.log(error)
} }
return true; return true
})) })
.normalizeEmail(), .normalizeEmail(),
body('password') body('password')
.isLength({ min: 4 }) .isLength({ min: 4 })
.withMessage("Votre mot de passe est trop court!"), .withMessage('Votre mot de passe est trop court!'),
body('name') body('name')
.trim() .trim()
.not() .not()
.isEmpty() .isEmpty()
.withMessage("Vous devez avoir un nom (ou pseudo).") .withMessage('Vous devez avoir un nom (ou pseudo).')
.isAlphanumeric() .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 }) .isLength({ max: 30 })
.withMessage("Votre nom est trop long") .withMessage('Votre nom est trop long')
.custom(async (name) => { .custom(async (name) => {
try { try {
const user = await Users.findOne({ where: { name } }); const user = await Users.findOne({ where: { name } })
if (user) { if (user) {
return Promise.reject("Le nom existe déjà..."); return Promise.reject(new Error('Le nom existe déjà...'))
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error)
} }
return true; return true
}) })
], usersController.register); ], usersController.register)
// Confirme l'inscription // Confirme l'inscription
UsersRouter.get('/confirm-email/:tempToken', usersController.confirmEmail); UsersRouter.get('/confirm-email/:tempToken', usersController.confirmEmail)
UsersRouter.route('/reset-password') UsersRouter.route('/reset-password')
@ -132,14 +132,14 @@ UsersRouter.route('/reset-password')
.post([ .post([
body('email') body('email')
.isEmail() .isEmail()
.withMessage("Veuillez rentré une adresse mail valide.") .withMessage('Veuillez rentré une adresse mail valide.')
], usersController.resetPassword) ], usersController.resetPassword)
// Nouveau mot de passe // Nouveau mot de passe
.put([ .put([
body('password') body('password')
.isLength({ min: 4 }) .isLength({ min: 4 })
.withMessage("Votre mot de passe est trop court!") .withMessage('Votre mot de passe est trop court!')
], usersController.newPassword); ], usersController.newPassword)
module.exports = UsersRouter; module.exports = UsersRouter

1
website/.env.example Normal file
View File

@ -0,0 +1 @@
NEXT_PUBLIC_API_URL = "http://localhost:8080"

2
website/.gitignore vendored
View File

@ -17,7 +17,7 @@
# misc # misc
.DS_Store .DS_Store
.env* .env
# debug # debug
npm-debug.log* npm-debug.log*

View File

@ -1,12 +1,12 @@
import SyntaxHighlighter from "react-syntax-highlighter"; import SyntaxHighlighter from 'react-syntax-highlighter'
import { atomOneDark as styles} from "react-syntax-highlighter/dist/cjs/styles/hljs"; import { atomOneDark as styles } from 'react-syntax-highlighter/dist/cjs/styles/hljs'
const CodeBlock = ({ language, value }) => { const CodeBlock = ({ language, value }) => {
return ( return (
<SyntaxHighlighter language={language} style={styles}> <SyntaxHighlighter language={language} style={styles}>
{value} {value}
</SyntaxHighlighter> </SyntaxHighlighter>
); )
}; }
export default CodeBlock; export default CodeBlock

View File

@ -1,16 +1,16 @@
import Link from 'next/link'; import Link from 'next/link'
import './Footer.css'; import './Footer.css'
export default function Footer () { export default function Footer () {
return ( return (
<footer className="footer"> <footer className='footer'>
<p className="footer-text text-center"> <p className='footer-text text-center'>
<Link href={"/about"}> <Link href='/about'>
<a>FunctionProject</a> <a>FunctionProject</a>
</Link> </Link>
&nbsp;- Version 2.0 <br /> &nbsp;- Version 2.0 <br />
<a href="https://divlo.fr/" target="_blank" rel="noopener noreferrer">Divlo</a> | Tous droits réservés <a href='https://divlo.fr/' target='_blank' rel='noopener noreferrer'>Divlo</a> | Tous droits réservés
</p> </p>
</footer> </footer>
); )
} }

View File

@ -1,129 +1,126 @@
import { Fragment, useState, useEffect } from 'react'; import { useState, useEffect } from 'react'
import htmlParser from 'html-react-parser'; import htmlParser from 'html-react-parser'
import Loader from '../../components/Loader'; import Loader from '../../components/Loader'
import useAPI from '../../hooks/useAPI'; import useAPI from '../../hooks/useAPI'
import api from '../../utils/api'; import api from '../../utils/api'
import '../../public/css/pages/admin.css'; import '../../public/css/pages/admin.css'
const AddEditFunction = (props) => { const AddEditFunction = (props) => {
const [, categories] = useAPI('/categories')
const [, categories] = useAPI('/categories'); const [inputState, setInputState] = useState(props.defaultInputState)
const [inputState, setInputState] = useState(props.defaultInputState); const [message, setMessage] = useState('')
const [message, setMessage] = useState(""); const [isLoading, setIsLoading] = useState(false)
const [isLoading, setIsLoading] = useState(false);
useEffect(() => { useEffect(() => {
if (categories.length > 0 && !props.isEditing) { if (categories.length > 0 && !props.isEditing) {
handleChange({ handleChange({
target: { target: {
name: "categorieId", name: 'categorieId',
value: categories[0].id value: categories[0].id
} }
}); })
} }
}, [categories]); }, [categories])
const apiCallFunction = (formData) => { const apiCallFunction = (formData) => {
if (props.isEditing) return api.put(`/admin/functions/${inputState.id}`, formData, { headers: { 'Authorization': props.user.token } }); 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 } }); return api.post('/admin/functions', formData, { headers: { Authorization: props.user.token } })
} }
const handleChange = (event, isTypeCheck = false) => { const handleChange = (event, isTypeCheck = false) => {
const inputStateNew = { ...inputState }; const inputStateNew = { ...inputState }
inputStateNew[event.target.name] = (event.target.files != undefined) ? event.target.files[0] : (isTypeCheck) ? event.target.checked : event.target.value; inputStateNew[event.target.name] = (event.target.files != null) ? event.target.files[0] : (isTypeCheck) ? event.target.checked : event.target.value
setInputState(inputStateNew); setInputState(inputStateNew)
} }
const handleSubmit = (event) => { const handleSubmit = (event) => {
event.preventDefault(); event.preventDefault()
setIsLoading(true); setIsLoading(true)
const formData = new FormData(); const formData = new window.FormData()
formData.append('type', inputState.type); formData.append('type', inputState.type)
formData.append('categorieId', inputState.categorieId); formData.append('categorieId', inputState.categorieId)
formData.append('title', inputState.title); formData.append('title', inputState.title)
formData.append('slug', inputState.slug); formData.append('slug', inputState.slug)
formData.append('description', inputState.description); formData.append('description', inputState.description)
formData.append('image', inputState.image); formData.append('image', inputState.image)
if (props.isEditing) { if (props.isEditing) {
formData.append('isOnline', inputState.isOnline); formData.append('isOnline', inputState.isOnline)
} }
apiCallFunction(formData) apiCallFunction(formData)
.then(() => { .then(() => {
setIsLoading(false); setIsLoading(false)
window.location.reload(true); window.location.reload(true)
}) })
.catch((error) => { .catch((error) => {
setMessage(`<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>`); setMessage(`<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>`)
setIsLoading(false); setIsLoading(false)
}); })
} }
return ( return (
<Fragment> <>
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="form-group"> <div className='form-group'>
<label className="form-label" htmlFor="title">Titre :</label> <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)" /> <input value={inputState.title} onChange={handleChange} type='text' name='title' id='title' className='form-control' placeholder='(e.g : Nombre aléatoire)' />
</div> </div>
<div className="form-group"> <div className='form-group'>
<label className="form-label" htmlFor="slug">Slug :</label> <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)" /> <input value={inputState.slug} onChange={handleChange} type='text' name='slug' id='slug' className='form-control' placeholder='(e.g : randomNumber)' />
</div> </div>
<div className="form-group"> <div className='form-group'>
<label className="form-label" htmlFor="description">Description :</label> <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> <textarea style={{ height: 'auto' }} value={inputState.description} onChange={handleChange} name='description' id='description' className='form-control' rows='5' />
</div> </div>
<div className="form-group"> <div className='form-group'>
<label className="form-label" htmlFor="type">Type :</label> <label className='form-label' htmlFor='type'>Type :</label>
<select onChange={handleChange} name="type" id="type" className="form-control" { ...(props.isEditing) && { value: inputState.type } }> <select onChange={handleChange} name='type' id='type' className='form-control' {...(props.isEditing) && { value: inputState.type }}>
<option value="form">Formulaire</option> <option value='form'>Formulaire</option>
<option value="article">Article</option> <option value='article'>Article</option>
<option value="page">Page</option> <option value='page'>Page</option>
</select> </select>
</div> </div>
<div className="form-group"> <div className='form-group'>
<label className="form-label" htmlFor="categorieId">Catégorie :</label> <label className='form-label' htmlFor='categorieId'>Catégorie :</label>
<select onChange={handleChange} name="categorieId" id="categorieId" className="form-control" { ...(props.isEditing) && { value: inputState.categorieId } }> <select onChange={handleChange} name='categorieId' id='categorieId' className='form-control' {...(props.isEditing) && { value: inputState.categorieId }}>
{categories.map((category) => ( {categories.map((category) => (
<option key={category.id} value={category.id} className="Admin__Modal-select-option" style={{ backgroundColor: category.color }}>{category.name}</option> <option key={category.id} value={category.id} className='Admin__Modal-select-option' style={{ backgroundColor: category.color }}>{category.name}</option>
))} ))}
</select> </select>
</div> </div>
<div className="form-group"> <div className='form-group'>
<label className="form-label" htmlFor="image">Image <em>(150x150 recommandé)</em> :</label> <label className='form-label' htmlFor='image'>Image <em>(150x150 recommandé)</em> :</label>
<br /> <br />
<input onChange={handleChange} accept="image/jpeg,image/jpg,image/png" type="file" name="image" id="image" /> <input onChange={handleChange} accept='image/jpeg,image/jpg,image/png' type='file' name='image' id='image' />
</div> </div>
{(props.isEditing) && {(props.isEditing) &&
<div className="form-group custom-control custom-switch"> <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" /> <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> <label className='custom-control-label' htmlFor='isOnline'>isOnline</label>
</div> </div>}
}
<div className="form-group text-center"> <div className='form-group text-center'>
<button type="submit" className="btn btn-dark">Envoyer</button> <button type='submit' className='btn btn-dark'>Envoyer</button>
</div> </div>
</form> </form>
<div className="form-result text-center"> <div className='form-result text-center'>
{ {
(isLoading) ? (isLoading)
<Loader /> ? <Loader />
: : htmlParser(message)
htmlParser(message)
} }
</div> </div>
</Fragment> </>
); )
} }
export default AddEditFunction; export default AddEditFunction

View File

@ -1,46 +1,45 @@
import { useState } from 'react'; import { useState } from 'react'
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic'
import { complex } from '../../utils/sunEditorConfig'; import { complex } from '../../utils/sunEditorConfig'
import api from '../../utils/api'; import api from '../../utils/api'
import FunctionArticle from '../FunctionPage/FunctionArticle'; import FunctionArticle from '../FunctionPage/FunctionArticle'
import 'notyf/notyf.min.css'; import 'notyf/notyf.min.css'
import '../../public/css/suneditor.min.css'; import '../../public/css/suneditor.min.css'
const SunEditor = dynamic( const SunEditor = dynamic(
() => import('suneditor-react'), () => import('suneditor-react'),
{ ssr: false } { ssr: false }
); )
const EditArticleFunction = (props) => { const EditArticleFunction = (props) => {
const [htmlContent, setHtmlContent] = useState('')
const [htmlContent, setHtmlContent] = useState("");
const handleEditorChange = (content) => { const handleEditorChange = (content) => {
setHtmlContent(content); setHtmlContent(content)
} }
const handleSave = async (content) => { const handleSave = async (content) => {
let Notyf; let Notyf
if (typeof window != 'undefined') { if (typeof window !== 'undefined') {
Notyf = require('notyf'); Notyf = require('notyf')
} }
const notyf = new Notyf.Notyf({ const notyf = new Notyf.Notyf({
duration: 5000 duration: 5000
}); })
try { try {
await api.put(`/admin/functions/article/${props.functionInfo.id}`, { article: content }, { headers: { 'Authorization': props.user.token } }); await api.put(`/admin/functions/article/${props.functionInfo.id}`, { article: content }, { headers: { Authorization: props.user.token } })
notyf.success('Sauvegardé!'); notyf.success('Sauvegardé!')
} catch { } catch {
notyf.error('Erreur!'); notyf.error('Erreur!')
} }
} }
return ( return (
<div className="container-fluid"> <div className='container-fluid'>
<SunEditor setContents={props.functionInfo.article} lang="fr" onChange={handleEditorChange} setOptions={{ buttonList: complex, callBackSave: handleSave }} /> <SunEditor setContents={props.functionInfo.article} lang='fr' onChange={handleEditorChange} setOptions={{ buttonList: complex, callBackSave: handleSave }} />
<FunctionArticle article={htmlContent} /> <FunctionArticle article={htmlContent} />
</div> </div>
); )
} }
export default EditArticleFunction; export default EditArticleFunction

View File

@ -1,158 +1,154 @@
import { useState, Fragment } from 'react'; import { useState } from 'react'
import api from '../../utils/api'; import api from '../../utils/api'
import 'notyf/notyf.min.css'; import 'notyf/notyf.min.css'
const EditFormFunction = (props) => { const EditFormFunction = (props) => {
const [inputsArray, setInputsArray] = useState(props.functionInfo.utilizationForm || [])
const [inputsArray, setInputsArray] = useState(props.functionInfo.utilizationForm || []);
const addInput = () => { const addInput = () => {
const newInputsArray = [...inputsArray]; const newInputsArray = [...inputsArray]
newInputsArray.push({ name: "", label: "", placeholder: "", type: "text" }); newInputsArray.push({ name: '', label: '', placeholder: '', type: 'text' })
setInputsArray(newInputsArray); setInputsArray(newInputsArray)
} }
const addOption = (event) => { const addOption = (event) => {
const newInputsArray = [...inputsArray]; const newInputsArray = [...inputsArray]
const index = event.target.id.split('-')[1]; const index = event.target.id.split('-')[1]
const inputObject = newInputsArray[index]; const inputObject = newInputsArray[index]
inputObject.options.push({ name: "", value: "" }); inputObject.options.push({ name: '', value: '' })
setInputsArray(newInputsArray); setInputsArray(newInputsArray)
} }
const handleChangeOption = (inputIndex, optionIndex, event) => { const handleChangeOption = (inputIndex, optionIndex, event) => {
const newInputsArray = [...inputsArray]; const newInputsArray = [...inputsArray]
const inputObject = newInputsArray[inputIndex]; const inputObject = newInputsArray[inputIndex]
const optionObject = inputObject.options[optionIndex]; const optionObject = inputObject.options[optionIndex]
optionObject[event.target.name] = event.target.value; optionObject[event.target.name] = event.target.value
setInputsArray(newInputsArray); setInputsArray(newInputsArray)
} }
const handleChangeInput = (event) => { const handleChangeInput = (event) => {
const newInputsArray = [...inputsArray]; const newInputsArray = [...inputsArray]
const index = event.target.id.split('-')[1]; const index = event.target.id.split('-')[1]
const inputObject = newInputsArray[index]; const inputObject = newInputsArray[index]
inputObject[event.target.name] = event.target.value; inputObject[event.target.name] = event.target.value
if (event.target.value === "select") { if (event.target.value === 'select') {
inputObject.options = []; inputObject.options = []
} }
setInputsArray(newInputsArray); setInputsArray(newInputsArray)
} }
const handleSubmit = async (event) => { const handleSubmit = async (event) => {
event.preventDefault(); event.preventDefault()
let Notyf; let Notyf
if (typeof window != 'undefined') { if (typeof window !== 'undefined') {
Notyf = require('notyf'); Notyf = require('notyf')
} }
const notyf = new Notyf.Notyf({ const notyf = new Notyf.Notyf({
duration: 5000 duration: 5000
}); })
try { try {
await api.put(`/admin/functions/form/${props.functionInfo.id}`, { form: inputsArray }, { headers: { 'Authorization': props.user.token } }); await api.put(`/admin/functions/form/${props.functionInfo.id}`, { form: inputsArray }, { headers: { Authorization: props.user.token } })
notyf.success('Sauvegardé!'); notyf.success('Sauvegardé!')
} catch (error) { } catch (error) {
notyf.error('Erreur!'); notyf.error('Erreur!')
} }
} }
const handleRemoveInput = (event) => { const handleRemoveInput = (event) => {
const newInputsArray = [...inputsArray]; const newInputsArray = [...inputsArray]
const index = event.target.id.split('-')[1]; const index = event.target.id.split('-')[1]
newInputsArray.splice(index, 1); newInputsArray.splice(index, 1)
setInputsArray(newInputsArray); setInputsArray(newInputsArray)
} }
const handleRemoveOption = (inputIndex, optionIndex) => { const handleRemoveOption = (inputIndex, optionIndex) => {
const newInputsArray = [...inputsArray]; const newInputsArray = [...inputsArray]
const inputObject = newInputsArray[inputIndex]; const inputObject = newInputsArray[inputIndex]
const optionsArray = inputObject.options; const optionsArray = inputObject.options
optionsArray.splice(optionIndex, 1); optionsArray.splice(optionIndex, 1)
setInputsArray(newInputsArray); setInputsArray(newInputsArray)
} }
return ( return (
<div className="container-fluid"> <div className='container-fluid'>
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
{(inputsArray.length > 0) && {(inputsArray.length > 0) &&
<div className="form-group text-center"> <div className='form-group text-center'>
<button type="submit" className="btn btn-dark">Sauvegarder</button> <button type='submit' className='btn btn-dark'>Sauvegarder</button>
</div> </div>}
}
{inputsArray.map((input, index) => { {inputsArray.map((input, index) => {
return ( return (
<div key={index} className="form-group Admin__Input-group"> <div key={index} className='form-group Admin__Input-group'>
<div className="text-center"> <div className='text-center'>
<button type="button" onClick={handleRemoveInput} id={`remove-${index}`} className="btn btn-dark">Supprimer l'input</button> <button type='button' onClick={handleRemoveInput} id={`remove-${index}`} className='btn btn-dark'>Supprimer l'input</button>
</div> </div>
<label className="form-label" htmlFor={`name-${index}`}>Nom de l'input :</label> <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)" /> <input value={input.name} onChange={handleChangeInput} type='text' name='name' id={`name-${index}`} className='form-control' placeholder='(e.g : cityName)' />
<br /> <br />
<label className="form-label" htmlFor={`label-${index}`}>Label :</label> <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 :)" /> <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 /> <br />
{(input.type !== "select") && {(input.type !== 'select') &&
<Fragment> <>
<label className="form-label" htmlFor={`placeholder-${index}`}>Placeholder :</label> <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)" /> <input value={input.placeholder} onChange={handleChangeInput} type='text' name='placeholder' id={`placeholder-${index}`} className='form-control' placeholder='(e.g : Paris, FR)' />
<br /> <br />
</Fragment> </>}
}
<label className="form-label" htmlFor={`type-${index}`}>Type :</label> <label className='form-label' htmlFor={`type-${index}`}>Type :</label>
<select value={input.type} onChange={handleChangeInput} name="type" id={`type-${index}`} className="form-control"> <select value={input.type} onChange={handleChangeInput} name='type' id={`type-${index}`} className='form-control'>
<option value="text">text</option> <option value='text'>text</option>
<option value="integer">Number integer</option> <option value='integer'>Number integer</option>
<option value="float">Number float</option> <option value='float'>Number float</option>
<option value="calendar">calendar</option> <option value='calendar'>calendar</option>
<option value="select">select</option> <option value='select'>select</option>
</select> </select>
{(input.type === "select") && {(input.type === 'select') &&
<div style={{ marginTop: '50px' }}> <div style={{ marginTop: '50px' }}>
<label className="form-label">Options :</label> <label className='form-label'>Options :</label>
{input.options.map((option, optionIndex) => { {input.options.map((option, optionIndex) => {
return ( return (
<div key={optionIndex} style={{ margin: "0 0 30px 0" }} className="form-group Admin__Input-group"> <div key={optionIndex} style={{ margin: '0 0 30px 0' }} className='form-group Admin__Input-group'>
<div className="text-center"> <div className='text-center'>
<button type="button" onClick={() => handleRemoveOption(index, optionIndex)} className="btn btn-dark">Supprimer l'option</button> <button type='button' onClick={() => handleRemoveOption(index, optionIndex)} className='btn btn-dark'>Supprimer l'option</button>
</div> </div>
<label className="form-label" htmlFor={`optionName-${optionIndex}-${index}`}>Nom de l'option</label> <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" /> <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 /> <br />
<label className="form-label" htmlFor={`optionValue-${optionIndex}-${index}`}>Valeur de l'option</label> <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" /> <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>
); )
})} })}
<div className="form-group text-center"> <div className='form-group text-center'>
<button id={`optionAdd-${index}`} onClick={addOption} type="button" className="btn btn-dark">Ajouter une option</button> <button id={`optionAdd-${index}`} onClick={addOption} type='button' className='btn btn-dark'>Ajouter une option</button>
</div> </div>
</div>}
</div> </div>
} )
</div>
);
})} })}
</form> </form>
<div style={{ marginBottom: '30px' }} className="form-group text-center"> <div style={{ marginBottom: '30px' }} className='form-group text-center'>
<button type="button" onClick={addInput} className="btn btn-dark">Ajouter un input</button> <button type='button' onClick={addInput} className='btn btn-dark'>Ajouter un input</button>
</div> </div>
</div> </div>
); )
} }
export default EditFormFunction; export default EditFormFunction

View File

@ -1,58 +1,56 @@
import Link from 'next/link'; import Link from 'next/link'
import { useState, forwardRef } from 'react'; import { useState, forwardRef } from 'react'
import date from 'date-and-time'; import date from 'date-and-time'
import Loader from '../Loader'; import Loader from '../Loader'
import { API_URL } from '../../utils/config/config'; import { API_URL } from '../../utils/config/config'
import './FunctionCard.css'; import './FunctionCard.css'
const FunctionCard = forwardRef((props, ref) => { const FunctionCard = forwardRef((props, ref) => {
const [isLoading, setIsLoading] = useState(true)
const [isLoading, setIsLoading] = useState(true);
const handleLoad = () => { const handleLoad = () => {
setIsLoading(false); setIsLoading(false)
} }
const handleError = (event) => { const handleError = (event) => {
event.target.src = API_URL + "/images/functions/default.png"; 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 ( return (
<Link <Link
{ {
...(props.isAdmin) ? ...(props.isAdmin)
{ ? {
href: "/admin/[slug]", href: '/admin/[slug]',
as: `/admin/${props.slug}` as: `/admin/${props.slug}`
} }
: : {
{ href: (isFormOrArticle) ? '/functions/[slug]' : `/functions/${props.slug}`,
href: (isFormOrArticle) ? "/functions/[slug]" : `/functions/${props.slug}`,
as: `/functions/${props.slug}` as: `/functions/${props.slug}`
} }
} }
> >
{/* FunctionCard a une hauteur pendant chargement */} {/* 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"}> <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__container ${isLoading ? 'd-none' : ''}`}>
<div className="FunctionCard__top"> <div className='FunctionCard__top'>
<img onLoad={handleLoad} onError={handleError} className="FunctionCard__image" alt={props.title} src={API_URL + props.image} /> <img onLoad={handleLoad} onError={handleError} className='FunctionCard__image' alt={props.title} src={API_URL + props.image} />
<h2 className="FunctionCard__title">{props.title}</h2> <h2 className='FunctionCard__title'>{props.title}</h2>
<p className="FunctionCard__description text-center">{props.description}</p> <p className='FunctionCard__description text-center'>{props.description}</p>
</div> </div>
<div className="FunctionCard__info"> <div className='FunctionCard__info'>
<p className="FunctionCard__category" style={{ backgroundColor: props.categorie.color }}>{props.categorie.name}</p> <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> <p className='FunctionCard__publication-date'>{date.format(new Date(props.createdAt), 'DD/MM/YYYY', true)}</p>
</div> </div>
</div> </div>
</div> </div>
</Link> </Link>
); )
}); })
export default FunctionCard; export default FunctionCard

View File

@ -1,108 +1,109 @@
import Link from 'next/link'; import Link from 'next/link'
import { useEffect, useState, forwardRef, useContext, Fragment } from 'react'; import { useEffect, useState, forwardRef, useContext } from 'react'
import date from 'date-and-time'; import date from 'date-and-time'
import htmlParser from 'html-react-parser'; import htmlParser from 'html-react-parser'
import { UserContext } from '../../../contexts/UserContext'; import { UserContext } from '../../../contexts/UserContext'
import { API_URL } from '../../../utils/config/config'; import { API_URL } from '../../../utils/config/config'
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown'
import CodeBlock from "../../CodeBlock"; import CodeBlock from '../../CodeBlock'
import api from '../../../utils/api'; import api from '../../../utils/api'
import './CommentCard.css'; import './CommentCard.css'
const CommentCard = forwardRef((props, ref) => { const CommentCard = forwardRef((props, ref) => {
const { isAuth, user } = useContext(UserContext)
const { isAuth, user } = useContext(UserContext); const [isEditing, setEditing] = useState(false)
const [isEditing, setEditing] = useState(false); const [editInput, setEditInput] = useState('')
const [editInput, setEditInput] = useState(""); const [message, setMessage] = useState('')
const [message, setMessage] = useState("");
useEffect(() => { useEffect(() => {
setEditInput(props.message); setEditInput(props.message)
}, []); }, [])
const deleteCommentById = async () => { const deleteCommentById = async () => {
props.manageComment.setLoadingComments(true); props.manageComment.setLoadingComments(true)
if (isAuth && user.token != undefined) { if (isAuth && user.token != null) {
try { try {
await api.delete(`/comments/${props.id}`, { headers: { 'Authorization': user.token } }); await api.delete(`/comments/${props.id}`, { headers: { Authorization: user.token } })
const newCommentsData = { ...props.manageComment.commentsData }; const newCommentsData = { ...props.manageComment.commentsData }
const commentIndex = newCommentsData.rows.findIndex((value) => value.id === props.id); const commentIndex = newCommentsData.rows.findIndex((value) => value.id === props.id)
newCommentsData.rows.splice(commentIndex, 1); newCommentsData.rows.splice(commentIndex, 1)
props.manageComment.setCommentsData({ hasMore: props.manageComment.commentsData.hasMore, rows: newCommentsData.rows }); props.manageComment.setCommentsData({ hasMore: props.manageComment.commentsData.hasMore, rows: newCommentsData.rows })
} catch {} } catch {}
} }
props.manageComment.setLoadingComments(false); props.manageComment.setLoadingComments(false)
} }
const handleChange = (event) => { const handleChange = (event) => {
setEditInput(event.target.value); setEditInput(event.target.value)
} }
const handleSubmit = (event) => { const handleSubmit = (event) => {
event.preventDefault(); event.preventDefault()
api.put(`/comments/${props.id}`, { message: editInput }, { headers: { 'Authorization': user.token } }) api.put(`/comments/${props.id}`, { message: editInput }, { headers: { Authorization: user.token } })
.then((_response) => { .then((_response) => {
setEditing(false); setEditing(false)
}) })
.catch((error) => { .catch((error) => {
setMessage(`<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>`); setMessage(`<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>`)
}); })
} }
const editComment = () => { const editComment = () => {
setEditing(true); setEditing(true)
setMessage(""); setMessage('')
} }
return ( return (
<div ref={ref} className="CommentCard col-24"> <div ref={ref} className='CommentCard col-24'>
<div className="CommentCard__container"> <div className='CommentCard__container'>
<div className="row"> <div className='row'>
<Link href={"/users/[name]"} as={`/users/${props.user.name}`}> <Link href='/users/[name]' as={`/users/${props.user.name}`}>
<img className="CommentCard__user-logo" src={API_URL + props.user.logo} alt={props.user.name} /> <img className='CommentCard__user-logo' src={API_URL + props.user.logo} alt={props.user.name} />
</Link> </Link>
<span className="CommentCard__message-info"> <span className='CommentCard__message-info'>
<Link href={"/users/[name]"} as={`/users/${props.user.name}`}> <Link href='/users/[name]' as={`/users/${props.user.name}`}>
<a>{props.user.name}</a> <a>{props.user.name}</a>
</Link> </Link>
&nbsp;- {date.format(new Date(props.createdAt), 'DD/MM/YYYY à HH:mm', true)} &nbsp;- {date.format(new Date(props.createdAt), 'DD/MM/YYYY à HH:mm', true)}
</span> </span>
</div> </div>
<div className="row"> <div className='row'>
<div className="col-24"> <div className='col-24'>
{ {
(!isEditing) ? (!isEditing)
<Fragment> ? (
<div className="CommentCard__message"> <>
<div className='CommentCard__message'>
<ReactMarkdown source={editInput} renderers={{ code: CodeBlock }} /> <ReactMarkdown source={editInput} renderers={{ code: CodeBlock }} />
</div> </div>
{(isAuth && user.name === props.user.name) && {(isAuth && user.name === props.user.name) &&
<p style={{ fontSize: '15px', margin: '15px 0 0 0', fontStyle: 'italic' }}> <p style={{ fontSize: '15px', margin: '15px 0 0 0', fontStyle: 'italic' }}>
<a onClick={deleteCommentById} href="#">supprimer</a> <a onClick={deleteCommentById} href='#'>supprimer</a>
&nbsp;-&nbsp; &nbsp;-&nbsp;
<a style={{ cursor: 'pointer' }} onClick={editComment}>modifier</a> <a style={{ cursor: 'pointer' }} onClick={editComment}>modifier</a>
</p> </p>}
} </>
</Fragment> )
: : (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="form-group FunctionComments__post-group"> <div className='form-group FunctionComments__post-group'>
<label className="form-label" htmlFor="commentEdit">Modifier le commentaire :</label> <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> <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>
<div className="form-group" style={{ marginTop: '0.7em' }}> <div className='form-group' style={{ marginTop: '0.7em' }}>
<button type="submit" className="btn btn-dark">Envoyer</button> <button type='submit' className='btn btn-dark'>Envoyer</button>
</div> </div>
<div className="text-center"> <div className='text-center'>
{htmlParser(message)} {htmlParser(message)}
</div> </div>
</form> </form>
)
} }
</div> </div>
</div> </div>
</div> </div>
</div> </div>
); )
}); })
export default CommentCard; export default CommentCard

View File

@ -1,11 +1,11 @@
import htmlParser from 'html-react-parser'; import htmlParser from 'html-react-parser'
const FunctionArticle = ({ article }) => { const FunctionArticle = ({ article }) => {
return ( return (
<div style={{ marginBottom: '50px' }} className="container-fluid"> <div style={{ marginBottom: '50px' }} className='container-fluid'>
{(article != undefined) ? htmlParser(article) : <p className="text-center">L'article n'est pas encore disponible.</p>} {(article != null) ? htmlParser(article) : <p className='text-center'>L'article n'est pas encore disponible.</p>}
</div> </div>
); )
} }
export default FunctionArticle; export default FunctionArticle

View File

@ -1,115 +1,114 @@
import { Fragment, useState, useEffect, useContext, useRef, useCallback } from 'react'; import { useState, useEffect, useContext, useRef, useCallback } from 'react'
import Link from 'next/link'; import Link from 'next/link'
import { UserContext } from '../../../contexts/UserContext'; import { UserContext } from '../../../contexts/UserContext'
import CommentCard from '../CommentCard/CommentCard'; import CommentCard from '../CommentCard/CommentCard'
import Loader from '../../Loader'; import Loader from '../../Loader'
import api from '../../../utils/api'; import api from '../../../utils/api'
import './FunctionComments.css'; import './FunctionComments.css'
const FunctionComments = ({ functionId }) => { const FunctionComments = ({ functionId }) => {
// State pour poster un commentaire // State pour poster un commentaire
const { isAuth, user } = useContext(UserContext); const { isAuth, user } = useContext(UserContext)
const [inputState, setInputState] = useState({}); const [inputState, setInputState] = useState({})
// State pour afficher les commentaires // State pour afficher les commentaires
const [commentsData, setCommentsData] = useState({ hasMore: true, rows: [] }); const [commentsData, setCommentsData] = useState({ hasMore: true, rows: [] })
const [isLoadingComments, setLoadingComments] = useState(true); const [isLoadingComments, setLoadingComments] = useState(true)
const [pageComments, setPageComments] = useState(1); const [pageComments, setPageComments] = useState(1)
// Récupère les commentaires si la page change // Récupère les commentaires si la page change
useEffect(() => { useEffect(() => {
getCommentsData().then((data) => setCommentsData({ getCommentsData().then((data) => setCommentsData({
hasMore: data.hasMore, hasMore: data.hasMore,
rows: [...commentsData.rows, ...data.rows] rows: [...commentsData.rows, ...data.rows]
})); }))
}, [pageComments]); }, [pageComments])
// Permet la pagination au scroll // Permet la pagination au scroll
const observer = useRef(); const observer = useRef()
const lastCommentCardRef = useCallback((node) => { const lastCommentCardRef = useCallback((node) => {
if (isLoadingComments) return; if (isLoadingComments) return
if (observer.current) observer.current.disconnect(); if (observer.current) observer.current.disconnect()
observer.current = new IntersectionObserver((entries) => { observer.current = new window.IntersectionObserver((entries) => {
if (entries[0].isIntersecting && commentsData.hasMore) { if (entries[0].isIntersecting && commentsData.hasMore) {
setPageComments(pageComments + 1); setPageComments(pageComments + 1)
} }
}, { threshold: 1 }); }, { threshold: 1 })
if (node) observer.current.observe(node); if (node) observer.current.observe(node)
}, [isLoadingComments, commentsData.hasMore]); }, [isLoadingComments, commentsData.hasMore])
const getCommentsData = () => { const getCommentsData = async () => {
setLoadingComments(true); setLoadingComments(true)
return new Promise(async (next) => { const { data } = await api.get(`/comments/${functionId}/?page=${pageComments}&limit=10`)
const result = await api.get(`/comments/${functionId}/?page=${pageComments}&limit=10`); setLoadingComments(false)
setLoadingComments(false); return data
next(result.data);
});
} }
const handleChange = (event) => { const handleChange = (event) => {
const inputStateNew = { ...inputState }; const inputStateNew = { ...inputState }
inputStateNew[event.target.name] = event.target.value; inputStateNew[event.target.name] = event.target.value
setInputState(inputStateNew); setInputState(inputStateNew)
} }
const handleSubmit = async (event) => { const handleSubmit = async (event) => {
setLoadingComments(true); setLoadingComments(true)
event.preventDefault(); event.preventDefault()
const token = user.token; const token = user.token
if (isAuth && token != undefined && inputState.commentPost != undefined) { if (isAuth && token != null && inputState.commentPost != null) {
try { try {
const response = await api.post(`/comments/${functionId}`, { message: inputState.commentPost }, { headers: { 'Authorization': token } }); const response = await api.post(`/comments/${functionId}`, { message: inputState.commentPost }, { headers: { Authorization: token } })
const comment = { ...response.data, user: { name: user.name, logo: user.logo } }; const comment = { ...response.data, user: { name: user.name, logo: user.logo } }
setCommentsData({ hasMore: commentsData.hasMore, rows: [comment, ...commentsData.rows] }); setCommentsData({ hasMore: commentsData.hasMore, rows: [comment, ...commentsData.rows] })
setInputState({ commentPost: "" }); setInputState({ commentPost: '' })
} catch { } } catch { }
} }
setLoadingComments(false); setLoadingComments(false)
} }
return ( return (
<Fragment> <>
<div className="FunctionComments__post container-fluid"> <div className='FunctionComments__post container-fluid'>
<div className="row FunctionComments__row"> <div className='row FunctionComments__row'>
<div className="col-24"> <div className='col-24'>
{ {
(isAuth) ? (isAuth)
? (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="form-group FunctionComments__post-group"> <div className='form-group FunctionComments__post-group'>
<label className="form-label" htmlFor="commentPost">Ajouter un commentaire :</label> <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> <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>
<div className="form-group" style={{ marginTop: '0.7em' }}> <div className='form-group' style={{ marginTop: '0.7em' }}>
<button type="submit" className="btn btn-dark">Envoyer</button> <button type='submit' className='btn btn-dark'>Envoyer</button>
</div> </div>
</form> </form>
: )
<p className="text-center"> : (
Vous devez être <Link href={'/users/login'}><a>connecté</a></Link> pour poster un commentaire. <p className='text-center'>
Vous devez être <Link href='/users/login'><a>connecté</a></Link> pour poster un commentaire.
</p> </p>
)
} }
</div> </div>
</div> </div>
</div> </div>
<div className="container-fluid"> <div className='container-fluid'>
{isLoadingComments && {isLoadingComments &&
<div className="row justify-content-center"> <div className='row justify-content-center'>
<Loader /> <Loader />
</div> </div>}
} <div className='row justify-content-center'>
<div className="row justify-content-center">
{commentsData.rows.map((comment, index) => { {commentsData.rows.map((comment, index) => {
// Si c'est le dernier élément // Si c'est le dernier élément
if (commentsData.rows.length === index + 1) { if (commentsData.rows.length === index + 1) {
return <CommentCard key={comment.id} ref={lastCommentCardRef} { ...comment } manageComment={{ setCommentsData, commentsData, setLoadingComments }} />; return <CommentCard key={comment.id} ref={lastCommentCardRef} {...comment} manageComment={{ setCommentsData, commentsData, setLoadingComments }} />
} }
return <CommentCard key={comment.id} { ...comment } manageComment={{ setCommentsData, commentsData, setLoadingComments }} />; return <CommentCard key={comment.id} {...comment} manageComment={{ setCommentsData, commentsData, setLoadingComments }} />
})} })}
</div> </div>
</div> </div>
</Fragment> </>
); )
} }
export default FunctionComments; export default FunctionComments

View File

@ -1,71 +1,69 @@
import { useState, useEffect, useContext } from 'react'; import { useState, useEffect, useContext } from 'react'
import Link from 'next/link'; import Link from 'next/link'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faStar } from '@fortawesome/free-solid-svg-icons'; import { faStar } from '@fortawesome/free-solid-svg-icons'
import { faStar as farStar } from '@fortawesome/free-regular-svg-icons'; import { faStar as farStar } from '@fortawesome/free-regular-svg-icons'
import date from 'date-and-time'; import date from 'date-and-time'
import { UserContext } from '../../contexts/UserContext'; import { UserContext } from '../../contexts/UserContext'
import api from '../../utils/api'; import api from '../../utils/api'
import { API_URL } from '../../utils/config/config'; import { API_URL } from '../../utils/config/config'
import '../FunctionCard/FunctionCard.css'; import '../FunctionCard/FunctionCard.css'
const FunctionComponentTop = (props) => { const FunctionComponentTop = (props) => {
const { isAuth, user } = useContext(UserContext)
const { isAuth, user } = useContext(UserContext); const [isFavorite, setIsFavorite] = useState(false)
const [isFavorite, setIsFavorite] = useState(false);
useEffect(() => { useEffect(() => {
if (isAuth && user.token != undefined) { if (isAuth && user.token != null) {
fetchFavorite(); fetchFavorite()
} }
}, [isAuth]); }, [isAuth])
const fetchFavorite = async () => { const fetchFavorite = async () => {
try { try {
const favoriteResponse = await api.get(`/favorites/${props.id}`, { headers: { 'Authorization': user.token } }); const favoriteResponse = await api.get(`/favorites/${props.id}`, { headers: { Authorization: user.token } })
setIsFavorite(favoriteResponse.data.isFavorite); setIsFavorite(favoriteResponse.data.isFavorite)
} catch {} } catch {}
} }
const toggleFavorite = async () => { const toggleFavorite = async () => {
if (isAuth && user.token != undefined) { if (isAuth && user.token != null) {
try { try {
if (isFavorite) { if (isFavorite) {
const response = await api.delete(`/favorites/${props.id}`, { headers: { 'Authorization': user.token } }); const response = await api.delete(`/favorites/${props.id}`, { headers: { Authorization: user.token } })
if (response.status === 200) return setIsFavorite(false); if (response.status === 200) return setIsFavorite(false)
} }
const response = await api.post(`/favorites/${props.id}`, {}, { headers: { 'Authorization': user.token } }); const response = await api.post(`/favorites/${props.id}`, {}, { headers: { Authorization: user.token } })
if (response.status === 201) return setIsFavorite(true); if (response.status === 201) return setIsFavorite(true)
} catch {} } catch {}
} }
} }
const handleError = (event) => { const handleError = (event) => {
event.target.src = API_URL + "/images/functions/default.png"; event.target.src = API_URL + '/images/functions/default.png'
} }
return ( return (
<div className="container-fluid"> <div className='container-fluid'>
<div className="row justify-content-center text-center"> <div className='row justify-content-center text-center'>
<div className="FunctionComponent__top col-24"> <div className='FunctionComponent__top col-24'>
{(isAuth) && {(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" /> <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} /> <img onError={handleError} className='FunctionComponent__image' src={API_URL + props.image} alt={props.title} />
<h1 className="FunctionComponent__title title-important">{props.title}</h1> <h1 className='FunctionComponent__title title-important'>{props.title}</h1>
<p className="FunctionComponent__description">{props.description}</p> <p className='FunctionComponent__description'>{props.description}</p>
<div className="FunctionCard__info"> <div className='FunctionCard__info'>
<Link href={`/functions?categoryId=${props.categorieId}`}> <Link href={`/functions?categoryId=${props.categorieId}`}>
<a className="FunctionCard__category" style={{ backgroundColor: props.categorie.color, color: 'inherit' }}>{props.categorie.name}</a> <a className='FunctionCard__category' style={{ backgroundColor: props.categorie.color, color: 'inherit' }}>{props.categorie.name}</a>
</Link> </Link>
<p className="FunctionCard__publication-date">{date.format(new Date(props.createdAt), 'DD/MM/YYYY', true)}</p> <p className='FunctionCard__publication-date'>{date.format(new Date(props.createdAt), 'DD/MM/YYYY', true)}</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
); )
} }
export default FunctionComponentTop; export default FunctionComponentTop

View File

@ -1,161 +1,160 @@
import { Fragment, useState, useEffect } from 'react'; import { useState, useEffect } from 'react'
import Loader from '../Loader'; import Loader from '../Loader'
import htmlParser from 'html-react-parser'; import htmlParser from 'html-react-parser'
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic'
import api from '../../utils/api'; import api from '../../utils/api'
import fr from 'date-fns/locale/fr'; import fr from 'date-fns/locale/fr'
import { registerLocale } from "react-datepicker"; import { registerLocale } from 'react-datepicker'
import date from 'date-and-time'; import date from 'date-and-time'
import "react-datepicker/dist/react-datepicker.css"; import 'react-datepicker/dist/react-datepicker.css'
registerLocale('fr', fr); registerLocale('fr', fr)
const FunctionForm = (props) => { const FunctionForm = (props) => {
const [inputState, setInputState] = useState({})
const [inputState, setInputState] = useState({}); const [message, setMessage] = useState('')
const [message, setMessage] = useState(""); const [isLoading, setIsLoading] = useState(false)
const [isLoading, setIsLoading] = useState(false);
// inputState par défaut // inputState par défaut
useEffect(() => { useEffect(() => {
const inputStateNew = { ...inputState }; const inputStateNew = { ...inputState }
props.inputsArray.forEach((input) => { props.inputsArray.forEach((input) => {
if (input.type === "select" && input.options.length > 0) { if (input.type === 'select' && input.options.length > 0) {
inputStateNew[input.name] = input.options[0].value; inputStateNew[input.name] = input.options[0].value
} }
}); })
setInputState(inputStateNew); setInputState(inputStateNew)
}, []); }, [])
const handleSubmit = (event) => { const handleSubmit = (event) => {
setIsLoading(true); setIsLoading(true)
event.preventDefault(); event.preventDefault()
api.post(`/functions/${props.slug}`, inputState) api.post(`/functions/${props.slug}`, inputState)
.then((response) => { .then((response) => {
setMessage(response.data.resultHTML); setMessage(response.data.resultHTML)
setIsLoading(false); setIsLoading(false)
}) })
.catch((error) => { .catch((error) => {
setMessage(error.response.data.message); setMessage(error.response.data.message)
setIsLoading(false); setIsLoading(false)
}); })
} }
const handleChange = (event) => { const handleChange = (event) => {
const inputStateNew = { ...inputState }; const inputStateNew = { ...inputState }
inputStateNew[event.target.name] = event.target.value; inputStateNew[event.target.name] = event.target.value
setInputState(inputStateNew); setInputState(inputStateNew)
} }
if (props.inputsArray.length <= 0) { if (props.inputsArray.length <= 0) {
return ( return (
<div className="FunctionComponent__slide text-center"> <div className='FunctionComponent__slide text-center'>
<p>La fonction n'est pas encore disponible.</p> <p>La fonction n'est pas encore disponible.</p>
</div> </div>
); )
} }
return ( return (
<Fragment> <>
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
{props.inputsArray.map((input, index) => { {props.inputsArray.map((input, index) => {
switch (input.type) { switch (input.type) {
case "text": case 'text':
return ( return (
<div key={index} className="form-group"> <div key={index} className='form-group'>
<label className="form-label" htmlFor={input.name}>{input.label}</label> <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" /> <input onChange={handleChange} type='text' name={input.name} id={input.name} placeholder={input.placeholder} className='form-control' />
</div> </div>
); )
case "integer": case 'integer':
case "float": case 'float':
return ( return (
<div key={index} className="form-group"> <div key={index} className='form-group'>
<label className="form-label" htmlFor={input.name}>{input.label}</label> <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" /> <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> </div>
); )
case "calendar": case 'calendar':
// Permet au datepicker de prendre la hauteur // Permet au datepicker de prendre la hauteur
if (typeof window != 'undefined') { if (typeof window !== 'undefined') {
const newScript = document.createElement("script"); const newScript = document.createElement('script')
newScript.src = "/js/extraHeightCSS.js"; newScript.src = '/js/extraHeightCSS.js'
document.body.appendChild(newScript); document.body.appendChild(newScript)
} }
// eslint-disable-next-line
const DatePicker = dynamic( const DatePicker = dynamic(
() => import('react-datepicker'), () => import('react-datepicker'),
{ ssr: false } { ssr: false }
); )
return ( return (
<div key={index} className="form-group"> <div key={index} className='form-group'>
<label className="form-label" htmlFor={input.name}>{input.label}</label> <label className='form-label' htmlFor={input.name}>{input.label}</label>
<br /> <br />
<DatePicker <DatePicker
selected={(() => { selected={(() => {
try { try {
if (inputState[input.name] != undefined) { if (inputState[input.name] != null) {
const dateArray = inputState[input.name].split('/'); const dateArray = inputState[input.name].split('/')
const year = dateArray[2]; const year = dateArray[2]
const month = dateArray[1]; const month = dateArray[1]
const day = dateArray[0]; const day = dateArray[0]
return new Date(year, parseInt(month) - 1, parseInt(day) + 1); return new Date(year, parseInt(month) - 1, parseInt(day) + 1)
} }
throw "Not a valid date"; throw new Error('Not a valid date')
} catch { } catch {
return new Date(); return new Date()
} }
})()} })()}
locale="fr" locale='fr'
dateFormat="dd/MM/yyyy" dateFormat='dd/MM/yyyy'
fixedHeight fixedHeight
placeholderText={input.placeholder} placeholderText={input.placeholder}
onChange={(dateObject) => { onChange={(dateObject) => {
try { try {
const formattedDate = date.format(dateObject, 'DD/MM/YYYY', true); const formattedDate = date.format(dateObject, 'DD/MM/YYYY', true)
handleChange({ handleChange({
target: { target: {
name: input.name, name: input.name,
value: formattedDate value: formattedDate
} }
}); })
} catch {} } catch {}
}} }}
/> />
</div> </div>
); )
case "select": case 'select':
return ( return (
<div key={index} className="form-group"> <div key={index} className='form-group'>
<label className="form-label" htmlFor={input.name}>{input.label}</label> <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"> <select onChange={handleChange} name={input.name} id={input.name} value={inputState[input.name] || input.options[0]} className='form-control'>
{input.options.map((option, optionIndex) => { {input.options.map((option, optionIndex) => {
return ( return (
<option key={optionIndex} value={option.value}>{option.name}</option> <option key={optionIndex} value={option.value}>{option.name}</option>
); )
})} })}
</select> </select>
</div> </div>
); )
default: default:
return ( return (
<p>Erreur, l'input n'est pas valide...</p> <p>Erreur, l'input n'est pas valide...</p>
); )
} }
})} })}
<div className="form-group text-center"> <div className='form-group text-center'>
<button type="submit" className="btn btn-dark">Envoyer</button> <button type='submit' className='btn btn-dark'>Envoyer</button>
</div> </div>
</form> </form>
<div className="form-result text-center"> <div className='form-result text-center'>
{ {
(isLoading) ? (isLoading)
<Loader /> ? <Loader />
: : htmlParser(message)
htmlParser(message)
} }
</div> </div>
</Fragment> </>
); )
} }
export default FunctionForm; export default FunctionForm

View File

@ -1,24 +1,23 @@
import { Fragment, useState } from 'react'; import { useState } from 'react'
import { API_URL } from '../../utils/config/config'; import { API_URL } from '../../utils/config/config'
import HeadTag from '../HeadTag'; import HeadTag from '../HeadTag'
import FunctionTabsTop from './FunctionTabsTop'; import FunctionTabsTop from './FunctionTabsTop'
import FunctionComponentTop from './FunctionComponentTop'; import FunctionComponentTop from './FunctionComponentTop'
const FunctionPage = (props) => { const FunctionPage = (props) => {
const [slideIndex, setSlideIndex] = useState(0)
const [slideIndex, setSlideIndex] = useState(0);
return ( return (
<Fragment> <>
<HeadTag title={props.title} description={props.description} image={API_URL + props.image} /> <HeadTag title={props.title} description={props.description} image={API_URL + props.image} />
<div className="container-fluid"> <div className='container-fluid'>
<FunctionTabsTop slideIndex={slideIndex} setSlideIndex={setSlideIndex} tabNames={props.tabNames} /> <FunctionTabsTop slideIndex={slideIndex} setSlideIndex={setSlideIndex} tabNames={props.tabNames} />
<FunctionComponentTop {...props} /> <FunctionComponentTop {...props} />
<props.FunctionTabManager {...props} slideIndex={slideIndex} setSlideIndex={setSlideIndex} /> <props.FunctionTabManager {...props} slideIndex={slideIndex} setSlideIndex={setSlideIndex} />
</div> </div>
</Fragment> </>
); )
} }
export default FunctionPage; export default FunctionPage

View File

@ -1,14 +1,14 @@
import SwipeableViews from 'react-swipeable-views'; import SwipeableViews from 'react-swipeable-views'
import './FunctionTabs.css'; import './FunctionTabs.css'
const FunctionTabs = (props) => { const FunctionTabs = (props) => {
return ( return (
<div className="container-fluid"> <div className='container-fluid'>
<SwipeableViews onChangeIndex={(index) => props.setSlideIndex(index)} index={props.slideIndex}> <SwipeableViews onChangeIndex={(index) => props.setSlideIndex(index)} index={props.slideIndex}>
{props.children} {props.children}
</SwipeableViews> </SwipeableViews>
</div> </div>
); )
} }
export default FunctionTabs; export default FunctionTabs

View File

@ -1,24 +1,24 @@
const FunctionTabsTop = (props) => { const FunctionTabsTop = (props) => {
return ( return (
<div className="container"> <div className='container'>
<div className="row justify-content-center"> <div className='row justify-content-center'>
<ul className="FunctionTabs__nav"> <ul className='FunctionTabs__nav'>
{props.tabNames.map((tabName, index) => { {props.tabNames.map((tabName, index) => {
return ( return (
<li key={index} className="FunctionTabs__nav-item"> <li key={index} className='FunctionTabs__nav-item'>
<a <a
className={`FunctionTabs__nav-link ${(props.slideIndex === index) ? "FunctionTabs__nav-link-active" : ""}`} className={`FunctionTabs__nav-link ${(props.slideIndex === index) ? 'FunctionTabs__nav-link-active' : ''}`}
onClick={() => props.setSlideIndex(index)} onClick={() => props.setSlideIndex(index)}
> >
{tabName} {tabName}
</a> </a>
</li> </li>
); )
})} })}
</ul> </ul>
</div> </div>
</div> </div>
); )
} }
export default FunctionTabsTop; export default FunctionTabsTop

View File

@ -1,104 +1,103 @@
import { useState, useEffect, useRef, useCallback } from 'react'; import { useState, useEffect, useRef, useCallback } from 'react'
import { useRouter } from 'next/router'; import { useRouter } from 'next/router'
import FunctionCard from '../FunctionCard/FunctionCard'; import FunctionCard from '../FunctionCard/FunctionCard'
import Loader from '../Loader'; import Loader from '../Loader'
import api from '../../utils/api'; import api from '../../utils/api'
import useAPI from '../../hooks/useAPI'; import useAPI from '../../hooks/useAPI'
import './FunctionsList.css'; import './FunctionsList.css'
let pageFunctions = 1; let pageFunctions = 1
const FunctionsList = (props) => { const FunctionsList = (props) => {
const { categoryId } = useRouter().query
const { categoryId } = useRouter().query;
// State de recherche et de catégories // State de recherche et de catégories
const [, categories] = useAPI('/categories'); const [, categories] = useAPI('/categories')
const [inputSearch, setInputSearch] = useState({ search: "", selectedCategory: categoryId || "0" }); const [inputSearch, setInputSearch] = useState({ search: '', selectedCategory: categoryId || '0' })
// State pour afficher les fonctions // State pour afficher les fonctions
const [functionsData, setFunctionsData] = useState({ hasMore: true, rows: [] }); const [functionsData, setFunctionsData] = useState({ hasMore: true, rows: [] })
const [isLoadingFunctions, setLoadingFunctions] = useState(true); const [isLoadingFunctions, setLoadingFunctions] = useState(true)
// Récupère la catégorie avec la query categoryId // Récupère la catégorie avec la query categoryId
useEffect(() => { useEffect(() => {
if (categoryId) { if (categoryId) {
handleChange({ target: { name: "selectedCategory", value: categoryId } }); handleChange({ target: { name: 'selectedCategory', value: categoryId } })
} }
}, [categoryId]); }, [categoryId])
// Récupère les fonctions si la catégorie/recherche change // Récupère les fonctions si la catégorie/recherche change
useEffect(() => { useEffect(() => {
pageFunctions = 1; pageFunctions = 1
getFunctionsData().then((data) => setFunctionsData(data)); getFunctionsData().then((data) => setFunctionsData(data))
}, [inputSearch]); }, [inputSearch])
// Permet la pagination au scroll // Permet la pagination au scroll
const observer = useRef(); const observer = useRef()
const lastFunctionCardRef = useCallback((node) => { const lastFunctionCardRef = useCallback((node) => {
if (isLoadingFunctions) return; if (isLoadingFunctions) return
if (observer.current) observer.current.disconnect(); if (observer.current) observer.current.disconnect()
observer.current = new IntersectionObserver((entries) => { observer.current = new window.IntersectionObserver((entries) => {
if (entries[0].isIntersecting && functionsData.hasMore) { if (entries[0].isIntersecting && functionsData.hasMore) {
pageFunctions += 1; pageFunctions += 1
getFunctionsData().then((data) => { getFunctionsData().then((data) => {
setFunctionsData((oldData) => { setFunctionsData((oldData) => {
return { return {
hasMore: data.hasMore, hasMore: data.hasMore,
rows: [...oldData.rows, ...data.rows] rows: [...oldData.rows, ...data.rows]
} }
}); })
}); })
} }
}, { threshold: 1 }); }, { threshold: 1 })
if (node) observer.current.observe(node); if (node) observer.current.observe(node)
}, [isLoadingFunctions, functionsData.hasMore]); }, [isLoadingFunctions, functionsData.hasMore])
const getFunctionsData = async () => { const getFunctionsData = async () => {
setLoadingFunctions(true); setLoadingFunctions(true)
const URL = `${(props.isAdmin) ? "/admin/functions" : "/functions"}?page=${pageFunctions}&limit=10&categoryId=${inputSearch.selectedCategory}&search=${inputSearch.search}`; const URL = `${(props.isAdmin) ? '/admin/functions' : '/functions'}?page=${pageFunctions}&limit=10&categoryId=${inputSearch.selectedCategory}&search=${inputSearch.search}`
const { data } = await api.get(URL, { const { data } = await api.get(URL, {
headers: { headers: {
...(props.isAdmin && props.token != undefined) && { 'Authorization': props.token } ...(props.isAdmin && props.token != null) && { Authorization: props.token }
} }
}); })
setLoadingFunctions(false); setLoadingFunctions(false)
return data; return data
} }
const handleChange = (event) => { const handleChange = (event) => {
const inputSearchNew = { ...inputSearch }; const inputSearchNew = { ...inputSearch }
inputSearchNew[event.target.name] = event.target.value; inputSearchNew[event.target.name] = event.target.value
setInputSearch(inputSearchNew); setInputSearch(inputSearchNew)
} }
return ( return (
<div className="container text-center"> <div className='container text-center'>
<div className="row justify-content-center"> <div className='row justify-content-center'>
{props.children} {props.children}
</div> </div>
<div className="Functions__search-container row justify-content-center"> <div className='Functions__search-container row justify-content-center'>
<select name="selectedCategory" value={inputSearch.selectedCategory} onChange={handleChange} className="Functions__select Functions__form-control"> <select name='selectedCategory' value={inputSearch.selectedCategory} onChange={handleChange} className='Functions__select Functions__form-control'>
<option value="0">Toutes catégories</option> <option value='0'>Toutes catégories</option>
{categories.map((category) => ( {categories.map((category) => (
<option key={category.id} value={category.id} className="Functions__select-option" style={{ backgroundColor: category.color }}>{category.name}</option> <option key={category.id} value={category.id} className='Functions__select-option' style={{ backgroundColor: category.color }}>{category.name}</option>
))} ))}
</select> </select>
<input value={inputSearch.search} onChange={handleChange} type="search" className="Functions__form-control Functions__search-input" name="search" id="search" placeholder="🔎 Rechercher..." /> <input value={inputSearch.search} onChange={handleChange} type='search' className='Functions__form-control Functions__search-input' name='search' id='search' placeholder='🔎 Rechercher...' />
</div> </div>
<div className="row justify-content-center"> <div className='row justify-content-center'>
{functionsData.rows.map((currentFunction, index) => { {functionsData.rows.map((currentFunction, index) => {
// Si c'est le dernier élément // Si c'est le dernier élément
if (functionsData.rows.length === index + 1) { 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} ref={lastFunctionCardRef} {...currentFunction} />
} }
return <FunctionCard isAdmin={props.isAdmin} key={currentFunction.id} { ...currentFunction } />; return <FunctionCard isAdmin={props.isAdmin} key={currentFunction.id} {...currentFunction} />
})} })}
</div> </div>
{isLoadingFunctions && <Loader />} {isLoadingFunctions && <Loader />}
</div> </div>
); )
} }
export default FunctionsList; export default FunctionsList

View File

@ -1,43 +1,43 @@
import Head from 'next/head'; import Head from 'next/head'
const HeadTag = ({ title, image, description }) => ( const HeadTag = ({ title, image, description }) => (
<Head> <Head>
<title>{title || ""}</title> <title>{title || ''}</title>
<link rel="icon" type="image/png" href={image} /> <link rel='icon' type='image/png' href={image} />
{/* Meta Tag */} {/* Meta Tag */}
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name='viewport' content='width=device-width, initial-scale=1' />
<meta name="description" content={description} /> <meta name='description' content={description} />
<link rel="canonical" href="function.divlo.fr"/> <link rel='canonical' href='function.divlo.fr' />
<meta name="Language" content="fr" /> <meta name='Language' content='fr' />
<meta name="theme-color" content="#ffd800" /> <meta name='theme-color' content='#ffd800' />
{/* Open Graph Metadata */} {/* Open Graph Metadata */}
<meta property="og:title" content={title} /> <meta property='og:title' content={title} />
<meta property="og:type" content="website" /> <meta property='og:type' content='website' />
<meta property="og:url" content="https://function.divlo.fr/" /> <meta property='og:url' content='https://function.divlo.fr/' />
<meta property="og:image" content={image} /> <meta property='og:image' content={image} />
<meta property="og:description" content={description} /> <meta property='og:description' content={description} />
<meta property="og:locale" content="fr_FR" /> <meta property='og:locale' content='fr_FR' />
<meta property="og:site_name" content="FunctionProject" /> <meta property='og:site_name' content='FunctionProject' />
{/* Twitter card Metadata */} {/* Twitter card Metadata */}
<meta name="twitter:card" content="summary" /> <meta name='twitter:card' content='summary' />
<meta name="twitter:description" content={description} /> <meta name='twitter:description' content={description} />
<meta name="twitter:title" content={title} /> <meta name='twitter:title' content={title} />
<meta name="twitter:site" content="@Divlo_FR" /> <meta name='twitter:site' content='@Divlo_FR' />
<meta name="twitter:image:src" content={image} /> <meta name='twitter:image:src' content={image} />
<meta name="twitter:creator" content="@Divlo_FR" /> <meta name='twitter:creator' content='@Divlo_FR' />
{/* Preloader script */} {/* Preloader script */}
<script src="/js/preloader.js"></script> <script src='/js/preloader.js' />
</Head> </Head>
); )
HeadTag.defaultProps = { HeadTag.defaultProps = {
title: "FunctionProject", title: 'FunctionProject',
description: "Apprenez la programmation grâce à l'apprentissage par projet alias fonction.", description: "Apprenez la programmation grâce à l'apprentissage par projet alias fonction.",
image: "/images/FunctionProject_icon_small.png" image: '/images/FunctionProject_icon_small.png'
} }
export default HeadTag; export default HeadTag

View File

@ -1,71 +1,73 @@
import { Fragment, useState, useContext } from 'react'; import { useState, useContext } from 'react'
import { UserContext } from '../../contexts/UserContext'; import { UserContext } from '../../contexts/UserContext'
import Link from 'next/link'; import Link from 'next/link'
import { useRouter } from 'next/router'; import { useRouter } from 'next/router'
import NavigationLink from './NavigationLink'; import NavigationLink from './NavigationLink'
import './Header.css'; import './Header.css'
export default function Header () { export default function Header () {
const { isAuth, logoutUser, user } = useContext(UserContext)
const { isAuth, logoutUser, user } = useContext(UserContext); const [isActive, setIsActive] = useState(false)
const [isActive, setIsActive] = useState(false); const { pathname } = useRouter()
const { pathname } = useRouter();
const toggleNavbar = () => { const toggleNavbar = () => {
setIsActive(!isActive); setIsActive(!isActive)
} }
return ( return (
<header className="Header"> <header className='Header'>
<div className="container"> <div className='container'>
{/* Brand */} {/* Brand */}
<Link href={"/"}> <Link href='/'>
<a className="Header__brand-link"> <a className='Header__brand-link'>
<img id="brand-link__logo" src="/images/FunctionProject_brand-logo.png" alt="FunctionProject" /> <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" /> <img id='brand-link__logo-small-screen' src='/images/FunctionProject_icon_small.png' alt='FunctionProject' />
</a> </a>
</Link> </Link>
{/* Hamburger icon on Mobile */} {/* Hamburger icon on Mobile */}
<div onClick={toggleNavbar} className={`Header__hamburger ${(isActive) ? "Header__hamburger-active" : ""}`}> <div onClick={toggleNavbar} className={`Header__hamburger ${(isActive) ? 'Header__hamburger-active' : ''}`}>
<span></span> <span />
</div> </div>
{/* Navigation */} {/* Navigation */}
<nav className="Header__navbar"> <nav className='Header__navbar'>
<ul className={`navbar__list ${(isActive) ? "navbar__list-active" : ""}`}> <ul className={`navbar__list ${(isActive) ? 'navbar__list-active' : ''}`}>
<NavigationLink name="Accueil" path="/" /> <NavigationLink name='Accueil' path='/' />
<NavigationLink name="Fonctions" path="/functions" /> <NavigationLink name='Fonctions' path='/functions' />
<NavigationLink name="Utilisateurs" path="/users" /> <NavigationLink name='Utilisateurs' path='/users' />
{ {
(!isAuth) ? (!isAuth)
<Fragment> ? (
<NavigationLink name="S'inscrire" path="/users/register" /> <>
<NavigationLink name="Connexion" path="/users/login" /> <NavigationLink name="S'inscrire" path='/users/register' />
</Fragment> <NavigationLink name='Connexion' path='/users/login' />
: </>
<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> <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> </Link>
</li> </li>
<li className="navbar-item"> <li className='navbar-item'>
<Link href={"/"}> <Link href='/'>
<a onClick={logoutUser} className="navbar-link">Se déconnecter</a> <a onClick={logoutUser} className='navbar-link'>Se déconnecter</a>
</Link> </Link>
</li> </li>
</Fragment> </>
)
} }
{ {
(isAuth && user.isAdmin) && (isAuth && user.isAdmin) &&
<NavigationLink name="Admin" path="/admin" /> <NavigationLink name='Admin' path='/admin' />
} }
</ul> </ul>
</nav> </nav>
</div> </div>
</header> </header>
); )
} }

View File

@ -1,18 +1,17 @@
import Link from 'next/link'; import Link from 'next/link'
import { useRouter } from 'next/router'; import { useRouter } from 'next/router'
import './Header.css'; import './Header.css'
export default function NavigationLink (props) { export default function NavigationLink (props) {
const { pathname } = useRouter()
const { pathname } = useRouter();
return ( return (
<li className="navbar-item"> <li className='navbar-item'>
<Link href={props.path}> <Link href={props.path}>
<a className={`navbar-link ${pathname === props.path ? "navbar-link-active" : null}`}> <a className={`navbar-link ${pathname === props.path ? 'navbar-link-active' : null}`}>
{props.name} {props.name}
</a> </a>
</Link> </Link>
</li> </li>
); )
} }

View File

@ -1,15 +1,15 @@
const Loader = ({ width, height, speed }) => ( const Loader = ({ width, height, speed }) => (
<svg width={width} height={height} viewBox="0 0 100 100"> <svg width={width} height={height} viewBox='0 0 100 100'>
<g transform="translate(50 50) rotate(0) scale(1 1) translate(-50 -50)"> <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> <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> </g>
</svg> </svg>
); )
Loader.defaultProps = { Loader.defaultProps = {
width: "100px", width: '100px',
height: "100px", height: '100px',
speed: ".9s" speed: '.9s'
} }
export default Loader; export default Loader

View File

@ -1,9 +1,9 @@
const Modal = (props) => ( const Modal = (props) => (
<div className="Modal container-fluid"> <div className='Modal container-fluid'>
<div className="Modal__content"> <div className='Modal__content'>
{props.children} {props.children}
</div> </div>
</div> </div>
); )
export default Modal; export default Modal

View File

@ -1,68 +1,63 @@
import { createContext, useState, useEffect } from 'react'; import { createContext, useState, useEffect } from 'react'
import Cookies from "universal-cookie"; import Cookies from 'universal-cookie'
import api from '../utils/api'; 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 [user, setUser] = useState(null); const [isAuth, setIsAuth] = useState(false)
const [isAuth, setIsAuth] = useState(false); const [loginLoading, setLoginLoading] = useState(false)
const [loginLoading, setLoginLoading] = useState(false); const [messageLogin, setMessageLogin] = useState('')
const [messageLogin, setMessageLogin] = useState("");
useEffect(() => { useEffect(() => {
const newUser = cookies.get('user'); const newUser = cookies.get('user')
if (newUser != undefined) { if (newUser != null) {
setIsAuth(true); setIsAuth(true)
setUser(newUser); setUser(newUser)
} }
}, []); }, [])
useEffect(() => { useEffect(() => {
if (isAuth) { if (isAuth) {
setMessageLogin('<p class="form-error"><b>Erreur:</b> Vous devez être déconnecter avant de vous connecter.</p>'); setMessageLogin('<p class="form-error"><b>Erreur:</b> Vous devez être déconnecter avant de vous connecter.</p>')
} else { } else {
setMessageLogin(""); setMessageLogin('')
} }
}, [isAuth]); }, [isAuth])
const logoutUser = () => { const logoutUser = () => {
cookies.remove('user', { path: '/' }); cookies.remove('user', { path: '/' })
setUser(null); setUser(null)
setIsAuth(false); setIsAuth(false)
} }
const loginUser = ({ email, password }) => { const loginUser = async ({ email, password }) => {
setLoginLoading(true); setLoginLoading(true)
return new Promise(async (next) => {
try { try {
const response = await api.post('/users/login', { email, password }); const response = await api.post('/users/login', { email, password })
const newUser = response.data; const newUser = response.data
cookies.remove('user', { path: '/' }); cookies.remove('user', { path: '/' })
cookies.set('user', newUser, { path: '/', maxAge: newUser.expiresIn }); cookies.set('user', newUser, { path: '/', maxAge: newUser.expiresIn })
setUser(newUser); setUser(newUser)
setIsAuth(true); setIsAuth(true)
setMessageLogin('<p class="form-success"><b>Succès:</b> Connexion réussi!</p>'); setMessageLogin('<p class="form-success"><b>Succès:</b> Connexion réussi!</p>')
setLoginLoading(false); setLoginLoading(false)
return next();
} catch (error) { } catch (error) {
setMessageLogin(`<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>`); setMessageLogin(`<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>`)
setLoginLoading(false); setLoginLoading(false)
setIsAuth(false); setIsAuth(false)
setUser(null); setUser(null)
return next();
} }
});
} }
return ( return (
<UserContext.Provider value={{ user, loginUser, logoutUser, loginLoading, messageLogin, isAuth, setMessageLogin }}> <UserContext.Provider value={{ user, loginUser, logoutUser, loginLoading, messageLogin, isAuth, setMessageLogin }}>
{props.children} {props.children}
</UserContext.Provider> </UserContext.Provider>
); )
} }
export default UserContextProvider; export default UserContextProvider

View File

@ -1,19 +1,17 @@
import { useContext } from 'react'; import { useContext } from 'react'
import { UserContext } from '../contexts/UserContext'; import { UserContext } from '../contexts/UserContext'
import redirect from '../utils/redirect'; import redirect from '../utils/redirect'
const withoutAuth = (WrappedComponent) => { const withoutAuth = (WrappedComponent) => {
const Component = (props) => { const Component = (props) => {
const { isAuth, user } = useContext(UserContext)
const { isAuth, user } = useContext(UserContext); if (isAuth) return redirect({}, `/users/${user.name}`)
if (isAuth) return redirect({}, `/users/${user.name}`); return <WrappedComponent {...props} />
return <WrappedComponent { ...props } />;
} }
return Component; return Component
} }
export default withoutAuth; export default withoutAuth

View File

@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react'
import api from '../utils/api'; import api from '../utils/api'
/** /**
* @param {String} url * @param {String} url
@ -7,25 +7,24 @@ import api from '../utils/api';
* @param {String} method * @param {String} method
* @param {Object} options * @param {Object} options
*/ */
function useAPI(url, defaultData = [], method = "get", options = {}) { function useAPI (url, defaultData = [], method = 'get', options = {}) {
const [isLoading, setIsLoading] = useState(true)
const [isLoading, setIsLoading] = useState(true); const [data, setData] = useState(defaultData)
const [data, setData] = useState(defaultData); const [hasError, setHasError] = useState(false)
const [hasError, setHasError] = useState(false);
useEffect(() => { useEffect(() => {
api[method](url, options) api[method](url, options)
.then((result) => { .then((result) => {
setData(result.data); setData(result.data)
setIsLoading(false); setIsLoading(false)
}) })
.catch((error) => { .catch((error) => {
setHasError(true); setHasError(true)
console.error(error); console.error(error)
}); })
}, []); }, [])
return [isLoading, data, hasError]; return [isLoading, data, hasError]
} }
export default useAPI; export default useAPI

View File

@ -1,3 +1,3 @@
const withCSS = require('@zeit/next-css'); const withCSS = require('@zeit/next-css')
const withFonts = require('next-fonts'); const withFonts = require('next-fonts')
module.exports = withFonts(withCSS()); module.exports = withFonts(withCSS())

8983
website/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,10 +4,11 @@
"description": "Website frontend for FunctionProject", "description": "Website frontend for FunctionProject",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
"dev": "cross-env NODE_ENV=dev node server", "dev": "cross-env NODE_ENV=development node server",
"build": "next build", "build": "next build",
"export": "next export", "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": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.28", "@fortawesome/fontawesome-svg-core": "^1.2.28",
@ -22,7 +23,7 @@
"express": "^4.17.1", "express": "^4.17.1",
"express-http-to-https": "^1.1.4", "express-http-to-https": "^1.1.4",
"html-react-parser": "^0.10.2", "html-react-parser": "^0.10.2",
"next": "9.3.2", "next": "^9.5.1",
"next-fonts": "^1.0.3", "next-fonts": "^1.0.3",
"notyf": "^3.6.0", "notyf": "^3.6.0",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
@ -39,6 +40,8 @@
"universal-cookie": "^4.0.3" "universal-cookie": "^4.0.3"
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^7.0.2" "cross-env": "^7.0.2",
"snazzy": "^8.0.0",
"standard": "^14.3.4"
} }
} }

View File

@ -1,22 +1,21 @@
import { Fragment } from 'react'; import Link from 'next/link'
import Link from 'next/link'; import HeadTag from '../components/HeadTag'
import HeadTag from '../components/HeadTag'; import '../public/css/pages/404.css'
import '../public/css/pages/404.css';
const Error404 = () => ( const Error404 = () => (
<Fragment> <>
<HeadTag <HeadTag
title="Erreur 404" title='Erreur 404'
description="Cette page n'existe pas!" description="Cette page n'existe pas!"
image="/images/error404.png" image='/images/error404.png'
/> />
<div className="Error404__container"> <div className='Error404__container'>
<h1>Erreur <span className="important">404</span></h1> <h1>Erreur <span className='important'>404</span></h1>
<p className="text-center"> <p className='text-center'>
Cette page n'existe pas! <Link href={"/"}><a>Revenir à la page d'accueil ?</a></Link> Cette page n'existe pas! <Link href='/'><a>Revenir à la page d'accueil ?</a></Link>
</p> </p>
</div> </div>
</Fragment> </>
); )
export default Error404; export default Error404

View File

@ -1,33 +1,33 @@
/* Libraries Imports */ /* Libraries Imports */
import Router from 'next/router' import Router from 'next/router'
import NProgress from 'nprogress'; import NProgress from 'nprogress'
/* Components Imports */ /* Components Imports */
import Header from '../components/Header/Header'; import Header from '../components/Header/Header'
import Footer from '../components/Footer/Footer'; import Footer from '../components/Footer/Footer'
/* Contexts Imports */ /* Contexts Imports */
import UserContextProvider from '../contexts/UserContext'; import UserContextProvider from '../contexts/UserContext'
/* CSS Imports */ /* CSS Imports */
import '../public/fonts/Montserrat/Montserrat.css'; import '../public/fonts/Montserrat/Montserrat.css'
import '../public/css/normalize.css'; import '../public/css/normalize.css'
import '../public/css/grid.css'; import '../public/css/grid.css'
import '../public/css/general.css'; import '../public/css/general.css'
import '../public/css/nprogress.css'; import '../public/css/nprogress.css'
Router.events.on('routeChangeStart', () => NProgress.start()); Router.events.on('routeChangeStart', () => NProgress.start())
Router.events.on('routeChangeComplete', () => NProgress.done()); Router.events.on('routeChangeComplete', () => NProgress.done())
Router.events.on('routeChangeError', () => NProgress.done()); Router.events.on('routeChangeError', () => NProgress.done())
const App = ({ Component, pageProps }) => ( const App = ({ Component, pageProps }) => (
<UserContextProvider> <UserContextProvider>
<Header /> <Header />
<div className="content container-fluid"> <div className='content container-fluid'>
<Component {...pageProps} /> <Component {...pageProps} />
</div> </div>
<Footer /> <Footer />
</UserContextProvider> </UserContextProvider>
); )
export default App; export default App

View File

@ -1,28 +1,28 @@
import Document, { Html, Head, Main, NextScript } from "next/document"; import Document, { Html, Head, Main, NextScript } from 'next/document'
import Loader from '../components/Loader'; import Loader from '../components/Loader'
class MyDocument extends Document { class MyDocument extends Document {
static async getInitialProps (ctx) { static async getInitialProps (ctx) {
const initialProps = await Document.getInitialProps(ctx); const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }; return { ...initialProps }
} }
render () { render () {
return ( return (
<Html lang="fr"> <Html lang='fr'>
<Head /> <Head />
<body> <body>
<div id="preloader"> <div id='preloader'>
<Loader /> <Loader />
</div> </div>
<div className="isLoading"> <div className='isLoading'>
<Main /> <Main />
</div> </div>
<NextScript /> <NextScript />
</body> </body>
</Html> </Html>
); )
} }
} }
export default MyDocument; export default MyDocument

View File

@ -1,49 +1,48 @@
import { Fragment } from 'react';
import axios from 'axios' import axios from 'axios'
import ReactMarkdown from 'react-markdown/with-html'; import ReactMarkdown from 'react-markdown/with-html'
import HeadTag from '../components/HeadTag'; import HeadTag from '../components/HeadTag'
const About = (props) => { const About = (props) => {
return ( return (
<Fragment> <>
<HeadTag <HeadTag
title="À-propos - FunctionProject" title='À-propos - FunctionProject'
description="À-propos de FunctionProject." description='À-propos de FunctionProject.'
/> />
<div className="container"> <div className='container'>
<div className="row justify-content-center"> <div className='row justify-content-center'>
<div className="col-24 text-center"> <div className='col-24 text-center'>
<h1 style={{ marginBottom: 0, paddingTop: "20px" }}>À-propos</h1> <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> <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> </div>
<div style={{ marginBottom: '30px' }} className="row"> <div style={{ marginBottom: '30px' }} className='row'>
<div className="col-24"> <div className='col-24'>
<ReactMarkdown <ReactMarkdown
source={props.data} source={props.data}
escapeHtml={false} escapeHtml={false}
linkTarget="_blank" linkTarget='_blank'
transformLinkUri={(uri) => { transformLinkUri={(uri) => {
if (uri.startsWith('./')) { if (uri.startsWith('./')) {
return `https://github.com/Divlo/FunctionProject/blob/master/${uri.slice(2)}`; return `https://github.com/Divlo/FunctionProject/blob/master/${uri.slice(2)}`
} }
return uri; return uri
}} }}
/> />
</div> </div>
</div> </div>
</div> </div>
</Fragment> </>
); )
} }
export async function getServerSideProps (_context) { export async function getServerSideProps (_context) {
const { data } = await axios.get('https://raw.githubusercontent.com/Divlo/FunctionProject/master/README.md'); const { data } = await axios.get('https://raw.githubusercontent.com/Divlo/FunctionProject/master/README.md')
return { return {
props: { data } props: { data }
}; }
} }
export default About; export default About

View File

@ -1,88 +1,87 @@
import { Fragment, useState } from 'react'; import { useState } from 'react'
import Cookies from "universal-cookie"; import Cookies from 'universal-cookie'
import SwipeableViews from 'react-swipeable-views'; import SwipeableViews from 'react-swipeable-views'
import HeadTag from '../../components/HeadTag'; import HeadTag from '../../components/HeadTag'
import AddEditFunction from '../../components/FunctionAdmin/AddEditFunction'; import AddEditFunction from '../../components/FunctionAdmin/AddEditFunction'
import EditArticleFunction from '../../components/FunctionAdmin/EditArticleFunction'; import EditArticleFunction from '../../components/FunctionAdmin/EditArticleFunction'
import EditFormFunction from '../../components/FunctionAdmin/EditFormFunction'; import EditFormFunction from '../../components/FunctionAdmin/EditFormFunction'
import redirect from '../../utils/redirect'; import redirect from '../../utils/redirect'
import api from '../../utils/api'; import api from '../../utils/api'
import { API_URL } from '../../utils/config/config'; import { API_URL } from '../../utils/config/config'
import '../../components/FunctionPage/FunctionTabs.css'; import '../../components/FunctionPage/FunctionTabs.css'
import '../../public/css/pages/admin.css'; import '../../public/css/pages/admin.css'
const AdminFunctionComponent = (props) => { const AdminFunctionComponent = (props) => {
const [slideIndex, setSlideIndex] = useState(0)
const [slideIndex, setSlideIndex] = useState(0);
const handleDeleteFunction = async () => { const handleDeleteFunction = async () => {
await api.delete(`/admin/functions/${props.functionInfo.id}`, { headers: { 'Authorization': props.user.token } }); await api.delete(`/admin/functions/${props.functionInfo.id}`, { headers: { Authorization: props.user.token } })
redirect({}, '/admin'); redirect({}, '/admin')
} }
return ( return (
<Fragment> <>
<HeadTag title={props.functionInfo.title} description={props.functionInfo.description} image={API_URL + props.functionInfo.image} /> <HeadTag title={props.functionInfo.title} description={props.functionInfo.description} image={API_URL + props.functionInfo.image} />
<div className="container-fluid"> <div className='container-fluid'>
<div className="container"> <div className='container'>
<div className="row justify-content-center"> <div className='row justify-content-center'>
<ul className="FunctionTabs__nav"> <ul className='FunctionTabs__nav'>
<li className="FunctionTabs__nav-item"> <li className='FunctionTabs__nav-item'>
<a onClick={() => setSlideIndex(0)} className={`FunctionTabs__nav-link ${(slideIndex === 0) && "FunctionTabs__nav-link-active"}`}> Modifier</a> <a onClick={() => setSlideIndex(0)} className={`FunctionTabs__nav-link ${(slideIndex === 0) && 'FunctionTabs__nav-link-active'}`}> Modifier</a>
</li> </li>
<li className="FunctionTabs__nav-item"> <li className='FunctionTabs__nav-item'>
<a onClick={() => setSlideIndex(1)} className={`FunctionTabs__nav-link ${(slideIndex === 1) && "FunctionTabs__nav-link-active"}`}>📝 Article</a> <a onClick={() => setSlideIndex(1)} className={`FunctionTabs__nav-link ${(slideIndex === 1) && 'FunctionTabs__nav-link-active'}`}>📝 Article</a>
</li> </li>
<li className="FunctionTabs__nav-item"> <li className='FunctionTabs__nav-item'>
<a onClick={() => setSlideIndex(2)} className={`FunctionTabs__nav-link ${(slideIndex === 2) && "FunctionTabs__nav-link-active"}`}> Utilisation</a> <a onClick={() => setSlideIndex(2)} className={`FunctionTabs__nav-link ${(slideIndex === 2) && 'FunctionTabs__nav-link-active'}`}> Utilisation</a>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
<div className="container-fluid"> <div className='container-fluid'>
<SwipeableViews onChangeIndex={(index) => setSlideIndex(index)} index={slideIndex}> <SwipeableViews onChangeIndex={(index) => setSlideIndex(index)} index={slideIndex}>
<div className="Admin__Function-slide"> <div className='Admin__Function-slide'>
<AddEditFunction <AddEditFunction
defaultInputState={{ ...props.functionInfo }} defaultInputState={{ ...props.functionInfo }}
user={props.user} user={props.user}
isEditing isEditing
/> />
<div style={{ marginBottom: '30px' }} className="text-center"> <div style={{ marginBottom: '30px' }} className='text-center'>
<button onClick={handleDeleteFunction} className="btn btn-dark">Supprimer la fonction</button> <button onClick={handleDeleteFunction} className='btn btn-dark'>Supprimer la fonction</button>
</div> </div>
</div> </div>
<div className="Admin__Function-slide"> <div className='Admin__Function-slide'>
<EditArticleFunction {...props} /> <EditArticleFunction {...props} />
</div> </div>
<div className="Admin__Function-slide"> <div className='Admin__Function-slide'>
<EditFormFunction {...props} /> <EditFormFunction {...props} />
</div> </div>
</SwipeableViews> </SwipeableViews>
</div> </div>
</div> </div>
</Fragment> </>
); )
} }
export async function getServerSideProps (context) { export async function getServerSideProps (context) {
const cookies = new Cookies(context.req.headers.cookie); const cookies = new Cookies(context.req.headers.cookie)
const user = { ...cookies.get('user') }; const user = { ...cookies.get('user') }
const { slug } = context.params; const { slug } = context.params
if (!user.isAdmin) { if (!user.isAdmin) {
return redirect(context, '/404'); return redirect(context, '/404')
} }
return api.get(`/admin/functions/${slug}`, { headers: { 'Authorization': user.token } }) return api.get(`/admin/functions/${slug}`, { headers: { Authorization: user.token } })
.then((response) => { .then((response) => {
return { return {
props: { props: {
user, user,
functionInfo: response.data functionInfo: response.data
} }
}; }
}) })
.catch(() => redirect(context, '/404')); .catch(() => redirect(context, '/404'))
} }
export default AdminFunctionComponent; export default AdminFunctionComponent

View File

@ -1,76 +1,76 @@
import Link from 'next/link'; import Link from 'next/link'
import { Fragment, useState } from 'react'; import { useState } from 'react'
import Cookies from "universal-cookie"; import Cookies from 'universal-cookie'
import HeadTag from '../../components/HeadTag'; import HeadTag from '../../components/HeadTag'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { faTimes } from '@fortawesome/free-solid-svg-icons'
import Modal from '../../components/Modal'; import Modal from '../../components/Modal'
import FunctionsList from '../../components/FunctionsList/FunctionsList'; import FunctionsList from '../../components/FunctionsList/FunctionsList'
import AddEditFunction from '../../components/FunctionAdmin/AddEditFunction'; import AddEditFunction from '../../components/FunctionAdmin/AddEditFunction'
import redirect from '../../utils/redirect'; import redirect from '../../utils/redirect'
import '../../public/css/pages/admin.css'; import '../../public/css/pages/admin.css'
const Admin = (props) => { const Admin = (props) => {
const [isOpen, setIsOpen] = useState(false)
const [isOpen, setIsOpen] = useState(false); const toggleModal = () => setIsOpen(!isOpen)
const toggleModal = () => setIsOpen(!isOpen);
return ( return (
<Fragment> <>
<HeadTag title="Admin - FunctionProject" description="Page d'administration de FunctionProject." /> <HeadTag title='Admin - FunctionProject' description="Page d'administration de FunctionProject." />
{/* Création d'une fonction */} {/* Création d'une fonction */}
{(isOpen) ? {(isOpen)
? (
<Modal toggleModal={toggleModal}> <Modal toggleModal={toggleModal}>
<div className="Admin__Modal__container container-fluid"> <div className='Admin__Modal__container container-fluid'>
<div className="Admin__Modal__row row"> <div className='Admin__Modal__row row'>
<div className="col-24"> <div className='col-24'>
<div className="Admin__Modal-top-container row"> <div className='Admin__Modal-top-container row'>
<div className="col-24"> <div className='col-24'>
<span onClick={toggleModal} style={{ cursor: 'pointer', position: 'absolute', left: 0 }}> <span onClick={toggleModal} style={{ cursor: 'pointer', position: 'absolute', left: 0 }}>
<FontAwesomeIcon icon={faTimes} style={{ width: '1.5rem', color: 'red' }} /> <FontAwesomeIcon icon={faTimes} style={{ width: '1.5rem', color: 'red' }} />
</span> </span>
<h2 className="text-center">Crée une nouvelle fonction</h2> <h2 className='text-center'>Crée une nouvelle fonction</h2>
</div> </div>
</div> </div>
</div> </div>
<div className="col-24"> <div className='col-24'>
<AddEditFunction defaultInputState={{ type: 'form' }} {...props} /> <AddEditFunction defaultInputState={{ type: 'form' }} {...props} />
</div> </div>
</div> </div>
</div> </div>
</Modal> </Modal>
)
: : (
<FunctionsList isAdmin token={props.user.token}> <FunctionsList isAdmin token={props.user.token}>
<div className="col-24"> <div className='col-24'>
<h1 className="Functions__title">Administration</h1> <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> <button onClick={toggleModal} style={{ margin: '0 0 40px 0' }} className='btn btn-dark'>Crée une nouvelle fonction</button>
<Link href={"/admin/manageCategories"}> <Link href='/admin/manageCategories'>
<button style={{ margin: '0 0 0 20px' }} className="btn btn-dark">Gérer les catégories</button> <button style={{ margin: '0 0 0 20px' }} className='btn btn-dark'>Gérer les catégories</button>
</Link> </Link>
<Link href={"/admin/manageQuotes"}> <Link href='/admin/manageQuotes'>
<button style={{ margin: '0 0 0 20px' }} className="btn btn-dark">Gérer les citations</button> <button style={{ margin: '0 0 0 20px' }} className='btn btn-dark'>Gérer les citations</button>
</Link> </Link>
</div> </div>
</FunctionsList> </FunctionsList>
} )}
</Fragment> </>
); )
} }
export async function getServerSideProps (context) { export async function getServerSideProps (context) {
const cookies = new Cookies(context.req.headers.cookie); const cookies = new Cookies(context.req.headers.cookie)
const user = { ...cookies.get('user') }; const user = { ...cookies.get('user') }
if (!user.isAdmin) { if (!user.isAdmin) {
return redirect(context, '/404'); return redirect(context, '/404')
} }
return { return {
props: { user } props: { user }
}; }
} }
export default Admin; export default Admin

View File

@ -1,158 +1,157 @@
import { Fragment, useState } from 'react'; import { useState } from 'react'
import Cookies from "universal-cookie"; import Cookies from 'universal-cookie'
import date from 'date-and-time'; import date from 'date-and-time'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faPen, faTrash, faTimes } from '@fortawesome/free-solid-svg-icons'; import { faPen, faTrash, faTimes } from '@fortawesome/free-solid-svg-icons'
import { PhotoshopPicker } from 'react-color'; import { PhotoshopPicker } from 'react-color'
import HeadTag from '../../components/HeadTag'; import HeadTag from '../../components/HeadTag'
import Modal from '../../components/Modal'; import Modal from '../../components/Modal'
import redirect from '../../utils/redirect'; import redirect from '../../utils/redirect'
import htmlParser from 'html-react-parser'; import htmlParser from 'html-react-parser'
import Loader from '../../components/Loader'; import Loader from '../../components/Loader'
import useAPI from '../../hooks/useAPI'; import useAPI from '../../hooks/useAPI'
import api from '../../utils/api'; import api from '../../utils/api'
import '../../public/css/pages/admin.css'; import '../../public/css/pages/admin.css'
const defaultCategoryState = { name: "", color: "#ffffff" }; const defaultCategoryState = { name: '', color: '#ffffff' }
const AddEditCategory = (props) => { const AddEditCategory = (props) => {
const [inputState, setInputState] = useState(props.defaultInputState)
const [inputState, setInputState] = useState(props.defaultInputState); const [message, setMessage] = useState('')
const [message, setMessage] = useState(""); const [isLoading, setIsLoading] = useState(false)
const [isLoading, setIsLoading] = useState(false);
const handleChange = (event, isTypeCheck = false) => { const handleChange = (event, isTypeCheck = false) => {
const inputStateNew = { ...inputState }; const inputStateNew = { ...inputState }
inputStateNew[event.target.name] = (event.target.files != undefined) ? event.target.files[0] : (isTypeCheck) ? event.target.checked : event.target.value; inputStateNew[event.target.name] = (event.target.files != null) ? event.target.files[0] : (isTypeCheck) ? event.target.checked : event.target.value
setInputState(inputStateNew); setInputState(inputStateNew)
} }
const apiCallCategory = () => { const apiCallCategory = () => {
if (props.isEditing) return api.put(`/admin/categories/${inputState.id}`, { name: inputState.name, color: inputState.color }, { headers: { 'Authorization': props.user.token } }); 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 } }); return api.post('/admin/categories', inputState, { headers: { Authorization: props.user.token } })
} }
const handleSubmit = (event) => { const handleSubmit = (event) => {
event.preventDefault(); event.preventDefault()
setIsLoading(true); setIsLoading(true)
apiCallCategory() apiCallCategory()
.then(() => { .then(() => {
setIsLoading(false); setIsLoading(false)
window.location.reload(true); window.location.reload(true)
}) })
.catch((error) => { .catch((error) => {
setMessage(`<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>`); setMessage(`<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>`)
setIsLoading(false); setIsLoading(false)
}); })
} }
return ( return (
<div className="Admin__Modal__container container-fluid"> <div className='Admin__Modal__container container-fluid'>
<div className="Admin__Modal__row row"> <div className='Admin__Modal__row row'>
<div className="col-24"> <div className='col-24'>
<div className="Admin__Modal-top-container row"> <div className='Admin__Modal-top-container row'>
<div className="col-24"> <div className='col-24'>
<span onClick={props.toggleModal} style={{ cursor: 'pointer', position: 'absolute', left: 0 }}> <span onClick={props.handleToggleModal} style={{ cursor: 'pointer', position: 'absolute', left: 0 }}>
<FontAwesomeIcon icon={faTimes} style={{ width: '1.5rem', color: 'red' }} /> <FontAwesomeIcon icon={faTimes} style={{ width: '1.5rem', color: 'red' }} />
</span> </span>
<h2 className="text-center">{(props.isEditing) ? "Modifier la catégorie" : "Crée une nouvelle catégorie"}</h2> <h2 className='text-center'>{(props.isEditing) ? 'Modifier la catégorie' : 'Crée une nouvelle catégorie'}</h2>
</div> </div>
</div> </div>
</div> </div>
<div className="col-24"> <div className='col-24'>
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="form-group"> <div className='form-group'>
<label className="form-label" htmlFor="name">Nom :</label> <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)" /> <input value={inputState.name} onChange={handleChange} type='text' name='name' id='name' className='form-control' placeholder='(e.g : ✨ Utilitaires)' />
</div> </div>
<div className="form-group"> <div className='form-group'>
<label className="form-label" htmlFor="title">Couleur :</label> <label className='form-label' htmlFor='title'>Couleur :</label>
<PhotoshopPicker color={inputState.color} onChange={(color) => handleChange({ target: { name: "color", value: color.hex } })} /> <PhotoshopPicker color={inputState.color} onChange={(color) => handleChange({ target: { name: 'color', value: color.hex } })} />
</div> </div>
<div className="form-group text-center"> <div className='form-group text-center'>
<button type="submit" className="btn btn-dark">Envoyer</button> <button type='submit' className='btn btn-dark'>Envoyer</button>
</div> </div>
</form> </form>
<div className="form-result text-center"> <div className='form-result text-center'>
{ {
(isLoading) ? (isLoading)
<Loader /> ? <Loader />
: : htmlParser(message)
htmlParser(message)
} }
</div> </div>
</div> </div>
</div> </div>
</div> </div>
); )
} }
const manageCategories = (props) => { 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 toggleModal = () => setIsOpen(!isOpen)
const [isOpen, setIsOpen] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const [defaultInputState, setDefaultInputState] = useState(defaultCategoryState);
const toggleModal = () => setIsOpen(!isOpen);
const handleRemoveCategory = async (categoryId) => { const handleRemoveCategory = async (categoryId) => {
try { try {
await api.delete(`/admin/categories/${categoryId}`, { headers: { 'Authorization': props.user.token } }); await api.delete(`/admin/categories/${categoryId}`, { headers: { Authorization: props.user.token } })
window.location.reload(true); window.location.reload(true)
} catch {} } catch {}
} }
const handleEditCategory = (categoryInfo) => { const handleEditCategory = (categoryInfo) => {
setDefaultInputState(categoryInfo); setDefaultInputState(categoryInfo)
setIsEditing(true); setIsEditing(true)
toggleModal(); toggleModal()
} }
return ( return (
<Fragment> <>
<HeadTag title="Admin - FunctionProject" description="Page d'administration de FunctionProject. Gérer les catégories." /> <HeadTag title='Admin - FunctionProject' description="Page d'administration de FunctionProject. Gérer les catégories." />
{ {
(isOpen) ? (isOpen)
? (
<Modal> <Modal>
<AddEditCategory toggleModal={toggleModal} defaultInputState={defaultInputState} { ...props } isEditing={isEditing} /> <AddEditCategory handleToggleModal={toggleModal} defaultInputState={defaultInputState} {...props} isEditing={isEditing} />
</Modal> </Modal>
: )
<div className="container-fluid text-center"> : (
<div className="row justify-content-center"> <div className='container-fluid text-center'>
<div className="col-24"> <div className='row justify-content-center'>
<div className='col-24'>
<h1>Gérer les catégories</h1> <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> <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> </div>
<div className="row justify-content-center"> <div className='row justify-content-center'>
<div className="container-fluid"> <div className='container-fluid'>
<div className="col-24 table-column"> <div className='col-24 table-column'>
<table className="table"> <table className='table'>
<thead> <thead>
<tr> <tr>
<th className="table-row" scope="col">id</th> <th className='table-row' scope='col'>id</th>
<th className="table-row" scope="col">name</th> <th className='table-row' scope='col'>name</th>
<th className="table-row" scope="col">color</th> <th className='table-row' scope='col'>color</th>
<th className="table-row" scope="col">createdAt</th> <th className='table-row' scope='col'>createdAt</th>
<th className="table-row" scope="col">updatedAt</th> <th className='table-row' scope='col'>updatedAt</th>
<th className="table-row" scope="col">Modifier</th> <th className='table-row' scope='col'>Modifier</th>
<th className="table-row" scope="col">Supprimer</th> <th className='table-row' scope='col'>Supprimer</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{categories.map((category) => { {categories.map((category) => {
return ( return (
<tr key={category.id} style={{ backgroundColor: category.color }}> <tr key={category.id} style={{ backgroundColor: category.color }}>
<td className="table-row">{category.id}</td> <td className='table-row'>{category.id}</td>
<td className="table-row">{category.name}</td> <td className='table-row'>{category.name}</td>
<td className="table-row">{category.color}</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.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 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 })}> <td style={{ cursor: 'pointer' }} onClick={() => handleEditCategory({ name: category.name, color: category.color, id: category.id })}>
<FontAwesomeIcon icon={faPen} style={{ width: '1.5rem' }} /> <FontAwesomeIcon icon={faPen} style={{ width: '1.5rem' }} />
</td> </td>
@ -160,7 +159,7 @@ const manageCategories = (props) => {
<FontAwesomeIcon icon={faTrash} style={{ width: '1.5rem' }} /> <FontAwesomeIcon icon={faTrash} style={{ width: '1.5rem' }} />
</td> </td>
</tr> </tr>
); )
})} })}
</tbody> </tbody>
</table> </table>
@ -168,20 +167,21 @@ const manageCategories = (props) => {
</div> </div>
</div> </div>
</div> </div>
)
} }
</Fragment> </>
); )
} }
export async function getServerSideProps (context) { export async function getServerSideProps (context) {
const cookies = new Cookies(context.req.headers.cookie); const cookies = new Cookies(context.req.headers.cookie)
const user = { ...cookies.get('user') }; const user = { ...cookies.get('user') }
if (!user.isAdmin) { if (!user.isAdmin) {
return redirect(context, '/404'); return redirect(context, '/404')
} }
return { return {
props: { user } props: { user }
}; }
} }
export default manageCategories; export default manageCategories

View File

@ -1,98 +1,97 @@
import { Fragment, useState, useEffect, useRef, useCallback } from 'react'; import { useState, useEffect, useRef, useCallback } from 'react'
import Cookies from "universal-cookie"; import Cookies from 'universal-cookie'
import Link from 'next/link'; import Link from 'next/link'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faCheck, faTrash } from '@fortawesome/free-solid-svg-icons'; import { faCheck, faTrash } from '@fortawesome/free-solid-svg-icons'
import redirect from '../../utils/redirect'; import redirect from '../../utils/redirect'
import HeadTag from '../../components/HeadTag'; import HeadTag from '../../components/HeadTag'
import api from '../../utils/api'; import api from '../../utils/api'
import '../../public/css/pages/admin.css'; import '../../public/css/pages/admin.css'
const manageQuotes = (props) => { const manageQuotes = (props) => {
const [quotesData, setQuotesData] = useState({ hasMore: true, rows: [], totalItems: 0 })
const [quotesData, setQuotesData] = useState({ hasMore: true, rows: [], totalItems: 0 }); const [isLoadingQuotes, setLoadingQuotes] = useState(true)
const [isLoadingQuotes, setLoadingQuotes] = useState(true); const [pageQuotes, setPageQuotes] = useState(1)
const [pageQuotes, setPageQuotes] = useState(1);
// Récupère les citations si la page change // Récupère les citations si la page change
useEffect(() => { useEffect(() => {
getQuotesData(); getQuotesData()
}, [pageQuotes]); }, [pageQuotes])
const getQuotesData = async () => { const getQuotesData = async () => {
setLoadingQuotes(true); setLoadingQuotes(true)
const { data } = await api.get(`/admin/quotes?limit=20page=${pageQuotes}`, { headers: { 'Authorization': props.user.token } }); const { data } = await api.get(`/admin/quotes?limit=20page=${pageQuotes}`, { headers: { Authorization: props.user.token } })
setQuotesData({ setQuotesData({
hasMore: data.hasMore, hasMore: data.hasMore,
rows: [...quotesData.rows, ...data.rows], rows: [...quotesData.rows, ...data.rows],
totalItems: data.totalItems totalItems: data.totalItems
}); })
setLoadingQuotes(false); setLoadingQuotes(false)
} }
// Permet la pagination au scroll // Permet la pagination au scroll
const observer = useRef(); const observer = useRef()
const lastQuoteRef = useCallback((node) => { const lastQuoteRef = useCallback((node) => {
if (isLoadingQuotes) return; if (isLoadingQuotes) return
if (observer.current) observer.current.disconnect(); if (observer.current) observer.current.disconnect()
observer.current = new IntersectionObserver((entries) => { observer.current = new window.IntersectionObserver((entries) => {
if (entries[0].isIntersecting && quotesData.hasMore) { if (entries[0].isIntersecting && quotesData.hasMore) {
setPageQuotes(pageQuotes + 1); setPageQuotes(pageQuotes + 1)
} }
}, { threshold: 1 }); }, { threshold: 1 })
if (node) observer.current.observe(node); if (node) observer.current.observe(node)
}, [isLoadingQuotes, quotesData.hasMore]); }, [isLoadingQuotes, quotesData.hasMore])
const handleValidationQuote = async (id, isValid) => { const handleValidationQuote = async (id, isValid) => {
try { try {
await api.put(`/admin/quotes/${id}`, { isValid }, { headers: { 'Authorization': props.user.token } }); await api.put(`/admin/quotes/${id}`, { isValid }, { headers: { Authorization: props.user.token } })
window.location.reload(true); window.location.reload(true)
} catch {} } catch {}
} }
return ( return (
<Fragment> <>
<HeadTag title="Admin - FunctionProject" description="Page d'administration de FunctionProject. Gérer les citations." /> <HeadTag title='Admin - FunctionProject' description="Page d'administration de FunctionProject. Gérer les citations." />
<div className="container-fluid"> <div className='container-fluid'>
<div className="row justify-content-center"> <div className='row justify-content-center'>
<div className="col-24 text-center"> <div className='col-24 text-center'>
<h2>Liste des citations (non validées) : </h2> <h2>Liste des citations (non validées) : </h2>
<p style={{ marginTop: '5px' }}>Total de {quotesData.totalItems} citations.</p> <p style={{ marginTop: '5px' }}>Total de {quotesData.totalItems} citations.</p>
</div> </div>
</div> </div>
<div className="row" style={{ marginBottom: '30px' }}> <div className='row' style={{ marginBottom: '30px' }}>
<div className="col-24 table-column"> <div className='col-24 table-column'>
<table className="table"> <table className='table'>
<thead> <thead>
<tr> <tr>
<th className="table-row" scope="col">Citation/Proverbe</th> <th className='table-row' scope='col'>Citation/Proverbe</th>
<th className="table-row" scope="col">Auteur</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'>Proposée par</th>
<th className="table-row" scope="col">Valider</th> <th className='table-row' scope='col'>Valider</th>
<th className="table-row" scope="col">Supprimer</th> <th className='table-row' scope='col'>Supprimer</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{quotesData.rows.map((currentQuote, index) => { {quotesData.rows.map((currentQuote, index) => {
const quoteJSX = ( const quoteJSX = (
<Fragment> <>
<td className="table-row text-center">{currentQuote.quote}</td> <td className='table-row text-center'>{currentQuote.quote}</td>
<td className="table-row text-center">{currentQuote.author}</td> <td className='table-row text-center'>{currentQuote.author}</td>
<td className="table-row text-center"> <td className='table-row text-center'>
<Link href={"/users/[name]"} as={`/users/${currentQuote.user.name}`}> <Link href='/users/[name]' as={`/users/${currentQuote.user.name}`}>
<a>{currentQuote.user.name}</a> <a>{currentQuote.user.name}</a>
</Link> </Link>
</td> </td>
<td onClick={() => handleValidationQuote(currentQuote.id, true)} className="table-row text-center" style={{ cursor: 'pointer' }}> <td onClick={() => handleValidationQuote(currentQuote.id, true)} className='table-row text-center' style={{ cursor: 'pointer' }}>
<FontAwesomeIcon icon={faCheck} style={{ width: '1.5rem' }} /> <FontAwesomeIcon icon={faCheck} style={{ width: '1.5rem' }} />
</td> </td>
<td onClick={() => handleValidationQuote(currentQuote.id, false)} className="table-row text-center" style={{ cursor: 'pointer' }}> <td onClick={() => handleValidationQuote(currentQuote.id, false)} className='table-row text-center' style={{ cursor: 'pointer' }}>
<FontAwesomeIcon icon={faTrash} style={{ width: '1.5rem' }} /> <FontAwesomeIcon icon={faTrash} style={{ width: '1.5rem' }} />
</td> </td>
</Fragment> </>
); )
// Si c'est le dernier élément // Si c'est le dernier élément
if (quotesData.rows.length === index + 1) { if (quotesData.rows.length === index + 1) {
return <tr key={index} ref={lastQuoteRef}>{quoteJSX}</tr> return <tr key={index} ref={lastQuoteRef}>{quoteJSX}</tr>
@ -104,19 +103,19 @@ const manageQuotes = (props) => {
</div> </div>
</div> </div>
</div> </div>
</Fragment> </>
); )
} }
export async function getServerSideProps (context) { export async function getServerSideProps (context) {
const cookies = new Cookies(context.req.headers.cookie); const cookies = new Cookies(context.req.headers.cookie)
const user = { ...cookies.get('user') }; const user = { ...cookies.get('user') }
if (!user.isAdmin) { if (!user.isAdmin) {
return redirect(context, '/404'); return redirect(context, '/404')
} }
return { return {
props: { user } props: { user }
}; }
} }
export default manageQuotes; export default manageQuotes

View File

@ -1,54 +1,54 @@
import FunctionTabs from '../../components/FunctionPage/FunctionTabs'; import FunctionTabs from '../../components/FunctionPage/FunctionTabs'
import FunctionForm from '../../components/FunctionPage/FunctionForm'; import FunctionForm from '../../components/FunctionPage/FunctionForm'
import FunctionArticle from '../../components/FunctionPage/FunctionArticle'; import FunctionArticle from '../../components/FunctionPage/FunctionArticle'
import FunctionComments from '../../components/FunctionPage/FunctionComments/FunctionComments'; import FunctionComments from '../../components/FunctionPage/FunctionComments/FunctionComments'
import FunctionPage from '../../components/FunctionPage/FunctionPage'; import FunctionPage from '../../components/FunctionPage/FunctionPage'
import redirect from '../../utils/redirect'; import redirect from '../../utils/redirect'
import api from '../../utils/api'; import api from '../../utils/api'
import '../../public/css/pages/FunctionComponent.css'; import '../../public/css/pages/FunctionComponent.css'
const FunctionTabManager = (props) => { const FunctionTabManager = (props) => {
if (props.type === "form") { if (props.type === 'form') {
return ( return (
<FunctionTabs setSlideIndex={props.setSlideIndex} slideIndex={props.slideIndex}> <FunctionTabs setSlideIndex={props.setSlideIndex} slideIndex={props.slideIndex}>
<div className="FunctionComponent__slide"> <div className='FunctionComponent__slide'>
<FunctionForm inputsArray={[...props.utilizationForm || []]} slug={props.slug} /> <FunctionForm inputsArray={[...props.utilizationForm || []]} slug={props.slug} />
</div> </div>
<div className="FunctionComponent__slide"> <div className='FunctionComponent__slide'>
<FunctionArticle article={props.article} /> <FunctionArticle article={props.article} />
</div> </div>
<div className="FunctionComponent__slide"> <div className='FunctionComponent__slide'>
<FunctionComments functionId={props.id} /> <FunctionComments functionId={props.id} />
</div> </div>
</FunctionTabs> </FunctionTabs>
); )
} }
return ( return (
<FunctionTabs setSlideIndex={props.setSlideIndex} slideIndex={props.slideIndex}> <FunctionTabs setSlideIndex={props.setSlideIndex} slideIndex={props.slideIndex}>
<div className="FunctionComponent__slide"> <div className='FunctionComponent__slide'>
<FunctionArticle article={props.article} /> <FunctionArticle article={props.article} />
</div> </div>
<div className="FunctionComponent__slide"> <div className='FunctionComponent__slide'>
<FunctionComments functionId={props.id} /> <FunctionComments functionId={props.id} />
</div> </div>
</FunctionTabs> </FunctionTabs>
); )
} }
const FunctionComponent = (props) => ( const FunctionComponent = (props) => (
<FunctionPage <FunctionPage
FunctionTabManager={FunctionTabManager} FunctionTabManager={FunctionTabManager}
{...props} {...props}
tabNames={(props.type === "form") ? ["⚙️ Utilisation", "📝 Article", "📬 Commentaires"] : ["📝 Article", "📬 Commentaires"]} tabNames={(props.type === 'form') ? ['⚙️ Utilisation', '📝 Article', '📬 Commentaires'] : ['📝 Article', '📬 Commentaires']}
/> />
); )
export async function getServerSideProps (context) { export async function getServerSideProps (context) {
const { slug } = context.params; const { slug } = context.params
return api.get(`/functions/${slug}`) return api.get(`/functions/${slug}`)
.then((response) => ({ props: response.data })) .then((response) => ({ props: response.data }))
.catch(() => redirect(context, '/404')); .catch(() => redirect(context, '/404'))
} }
export default FunctionComponent; export default FunctionComponent

View File

@ -1,74 +1,73 @@
import { useState } from 'react'; import { useState } from 'react'
import Codepen from "react-codepen-embed"; import Codepen from 'react-codepen-embed'
import redirect from '../../utils/redirect'; import redirect from '../../utils/redirect'
import FunctionPage from '../../components/FunctionPage/FunctionPage'; import FunctionPage from '../../components/FunctionPage/FunctionPage'
import FunctionTabs from '../../components/FunctionPage/FunctionTabs'; import FunctionTabs from '../../components/FunctionPage/FunctionTabs'
import FunctionArticle from '../../components/FunctionPage/FunctionArticle'; import FunctionArticle from '../../components/FunctionPage/FunctionArticle'
import FunctionComments from '../../components/FunctionPage/FunctionComments/FunctionComments'; import FunctionComments from '../../components/FunctionPage/FunctionComments/FunctionComments'
import Loader from '../../components/Loader'; import Loader from '../../components/Loader'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faPlay, faPause, faSync } from '@fortawesome/free-solid-svg-icons'; import { faPlay, faPause, faSync } from '@fortawesome/free-solid-svg-icons'
import api from '../../utils/api'; import api from '../../utils/api'
import '../../public/css/pages/FunctionComponent.css'; import '../../public/css/pages/FunctionComponent.css'
import '../../public/css/pages/functions/chronometerTimer.css'; import '../../public/css/pages/functions/chronometerTimer.css'
let interval; let interval
function convertSeconds (seconds) { function convertSeconds (seconds) {
return { return {
minutes: Math.floor(seconds / 60), minutes: Math.floor(seconds / 60),
seconds: seconds % 60 seconds: seconds % 60
}; }
} }
const Chronometer = () => { const Chronometer = () => {
const [timeLength, setTimeLength] = useState(0) // seconds
const [timeLength, setTimeLength] = useState(0); // seconds const [isPlaying, setIsPlaying] = useState(false)
const [isPlaying, setIsPlaying] = useState(false);
const handlePlayPause = () => { const handlePlayPause = () => {
if (isPlaying) { if (isPlaying) {
clearInterval(interval); clearInterval(interval)
} else { } else {
if (interval) clearInterval(interval); if (interval) clearInterval(interval)
interval = setInterval(() => { interval = setInterval(() => {
setTimeLength((time) => time + 1); setTimeLength((time) => time + 1)
}, 1000); }, 1000)
} }
setIsPlaying(!isPlaying); setIsPlaying(!isPlaying)
} }
const handleReset = () => { const handleReset = () => {
if (interval) clearInterval(interval); if (interval) clearInterval(interval)
setIsPlaying(false); setIsPlaying(false)
setTimeLength(0); setTimeLength(0)
} }
const getFormattedValue = () => { const getFormattedValue = () => {
const minutesAndSeconds = convertSeconds(timeLength); const minutesAndSeconds = convertSeconds(timeLength)
const minutes = (minutesAndSeconds.minutes < 100) ? (('0'+minutesAndSeconds.minutes).slice(-2)) : minutesAndSeconds.minutes; const minutes = (minutesAndSeconds.minutes < 100) ? (('0' + minutesAndSeconds.minutes).slice(-2)) : minutesAndSeconds.minutes
const seconds = ('0'+minutesAndSeconds.seconds).slice(-2); const seconds = ('0' + minutesAndSeconds.seconds).slice(-2)
return `${minutes}:${seconds}`; return `${minutes}:${seconds}`
} }
return ( return (
<div className="container-fluid"> <div className='container-fluid'>
<div className="row justify-content-center"> <div className='row justify-content-center'>
<div className="col-sm-24 col-md-12"> <div className='col-sm-24 col-md-12'>
<div className="Chronometer__container"> <div className='Chronometer__container'>
<div className="Chronometer__item"> <div className='Chronometer__item'>
<div className="Chronomter__row"> <div className='Chronomter__row'>
<div className="Chronometer__time-left"> <div className='Chronometer__time-left'>
{getFormattedValue()} {getFormattedValue()}
</div> </div>
</div> </div>
</div> </div>
<div className="Chronometer__item Chronometer__buttons"> <div className='Chronometer__item Chronometer__buttons'>
<div className="Chronomter__row Chronometer__row-button"> <div className='Chronomter__row Chronometer__row-button'>
<button onClick={handlePlayPause} className="Chronometer-btn"> <button onClick={handlePlayPause} className='Chronometer-btn'>
<FontAwesomeIcon {...(isPlaying) ? { icon: faPause } : { icon: faPlay }} /> <FontAwesomeIcon {...(isPlaying) ? { icon: faPause } : { icon: faPlay }} />
</button> </button>
<button onClick={handleReset} className="Chronometer-btn" title="Remettre à zéro ?"> <button onClick={handleReset} className='Chronometer-btn' title='Remettre à zéro ?'>
<FontAwesomeIcon icon={faSync} /> <FontAwesomeIcon icon={faSync} />
</button> </button>
</div> </div>
@ -77,48 +76,48 @@ const Chronometer = () => {
</div> </div>
</div> </div>
</div> </div>
); )
} }
const Pomodoro = () => { const Pomodoro = () => {
return ( return (
<div style={{ marginBottom: '50px' }} className="container-fluid"> <div style={{ marginBottom: '50px' }} className='container-fluid'>
<Codepen hash="vYEbPoB" user="Divlo" height={800} defaultTab="result" preview={false} loader={() => <Loader />} /> <Codepen hash='vYEbPoB' user='Divlo' height={800} defaultTab='result' preview={false} loader={() => <Loader />} />
</div> </div>
); )
} }
const FunctionTabManager = (props) => { const FunctionTabManager = (props) => {
return ( return (
<FunctionTabs setSlideIndex={props.setSlideIndex} slideIndex={props.slideIndex}> <FunctionTabs setSlideIndex={props.setSlideIndex} slideIndex={props.slideIndex}>
<div className="FunctionComponent__slide"> <div className='FunctionComponent__slide'>
<Chronometer /> <Chronometer />
</div> </div>
<div className="FunctionComponent__slide"> <div className='FunctionComponent__slide'>
<Pomodoro /> <Pomodoro />
</div> </div>
<div className="FunctionComponent__slide"> <div className='FunctionComponent__slide'>
<FunctionArticle article={props.article} /> <FunctionArticle article={props.article} />
</div> </div>
<div className="FunctionComponent__slide"> <div className='FunctionComponent__slide'>
<FunctionComments functionId={props.id} /> <FunctionComments functionId={props.id} />
</div> </div>
</FunctionTabs> </FunctionTabs>
); )
} }
const chronometerTimer = (props) => ( const chronometerTimer = (props) => (
<FunctionPage <FunctionPage
FunctionTabManager={FunctionTabManager} FunctionTabManager={FunctionTabManager}
{...props} {...props}
tabNames={["⏰ Chronomètre", "⌛ Pomodoro", "📝 Article", "📬 Commentaires"]} tabNames={['⏰ Chronomètre', '⌛ Pomodoro', '📝 Article', '📬 Commentaires']}
/> />
); )
export async function getServerSideProps (context) { export async function getServerSideProps (context) {
return api.get(`/functions/chronometerTimer`) return api.get('/functions/chronometerTimer')
.then((response) => ({ props: response.data })) .then((response) => ({ props: response.data }))
.catch(() => redirect(context, '/404')); .catch(() => redirect(context, '/404'))
} }
export default chronometerTimer; export default chronometerTimer

View File

@ -1,22 +1,20 @@
import { Fragment } from 'react'; import HeadTag from '../../components/HeadTag'
import HeadTag from '../../components/HeadTag'; import FunctionsList from '../../components/FunctionsList/FunctionsList'
import FunctionsList from '../../components/FunctionsList/FunctionsList';
const Functions = () => { const Functions = () => {
return ( return (
<Fragment> <>
<HeadTag <HeadTag
title="Fonctions" title='Fonctions'
description="Liste des fonctions." description='Liste des fonctions.'
image="/images/FunctionProject_icon_small.png" image='/images/FunctionProject_icon_small.png'
/> />
<FunctionsList> <FunctionsList>
<h1 className="Functions__title">Fonctions</h1> <h1 className='Functions__title'>Fonctions</h1>
</FunctionsList> </FunctionsList>
</Fragment> </>
); )
} }
export default Functions; export default Functions

Some files were not shown because too many files have changed in this diff Show More