Merge pull request #6 from Divlo/v2.1

v2.1
This commit is contained in:
Divlo 2020-08-03 21:25:50 +02:00 committed by GitHub
commit d8137a8c5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
200 changed files with 20438 additions and 12088 deletions

2
.github/backup.sql vendored

File diff suppressed because one or more lines are too long

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>
@ -24,9 +23,9 @@ Si vous aimez le projet, vous pouvez aider à **le faire connaître** en utilisa
Les dernières versions publiées : [https://github.com/Divlo/FunctionProject/releases](https://github.com/Divlo/FunctionProject/releases) Les dernières versions publiées : [https://github.com/Divlo/FunctionProject/releases](https://github.com/Divlo/FunctionProject/releases)
Le projet est disponible sur [function.divlo.fr](https://function.divlo.fr/) (actuellement en version 2.0). Le projet est disponible sur [function.divlo.fr](https://function.divlo.fr/) (actuellement en version 2.1).
## 🚀 Open Source ## 🚀 Open Source
Le partage est essentiel afin de progresser, l'**Open Source** permet l'entraide et le **partage de connaissance** entre développeurs. Le partage est essentiel afin de progresser, l'**Open Source** permet l'entraide et le **partage de connaissance** entre développeurs.
@ -61,21 +60,26 @@ npm install
Vous devrez ensuite configurer l'API en créant un fichier `.env` à la racine du dossier `/api` et prendre exemple du fichier `.env.example` avec votre configuration. Vous devrez ensuite configurer l'API en créant un fichier `.env` à la racine du dossier `/api` et prendre exemple du fichier `.env.example` avec votre configuration.
### Lancer l'environnement de développement : ### Lancer l'environnement de développement :
Dans deux terminals séparés : Dans deux terminals séparés :
- Lancer le front-end en allant dans `/website` - Lancer le front-end en allant dans `/website`
```sh ```sh
npm run dev # front-end lancé sur http://localhost:3000 npm run dev # front-end lancé sur http://localhost:3000
``` ```
- Lancer l'api en allant dans `/api` - Lancer l'api en allant dans `/api`
```sh ```sh
npm run dev # API lancé sur http://localhost:8080 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,94 @@
/* 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')
/* 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'))
app.use('/links', require('./routes/links_shortener'))
/* 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')
const ShortLinks = require('./models/short_links')
// 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 })
// Users can have links
Users.hasMany(ShortLinks)
ShortLinks.belongsTo(Users, { constraints: false })
/* Server */ /* Server */
// sequelize.sync({ force: true }) // sequelize.sync({ force: true })
sequelize.sync() sequelize
.then(() => { .sync()
app.listen(PORT, () => console.log('\x1b[36m%s\x1b[0m', `Started on port ${PORT}.`)); .then(() => {
}) app.listen(PORT, () =>
.catch((error) => console.log(error)); console.log('\x1b[36m%s\x1b[0m', `Started on port ${PORT}.`)
)
})
.catch(error => console.log(error))

View File

@ -1,26 +1,26 @@
const config = { const config = {
PORT: process.env.PORT || 8080, PORT: process.env.PORT || 8080,
HOST: process.env.HOST, HOST: process.env.HOST,
FRONT_END_HOST: process.env.FRONT_END_HOST, FRONT_END_HOST: process.env.FRONT_END_HOST,
WEATHER_API_KEY: process.env.OpenWeatherMap_API_KEY, WEATHER_API_KEY: process.env.OpenWeatherMap_API_KEY,
SCRAPER_API_KEY: process.env.Scraper_API_KEY, SCRAPER_API_KEY: process.env.Scraper_API_KEY,
DATABASE: { DATABASE: {
host: process.env.DB_HOST, host: process.env.DB_HOST,
name: process.env.DB_NAME, name: process.env.DB_NAME,
user: process.env.DB_USER, user: process.env.DB_USER,
password: process.env.DB_PASS password: process.env.DB_PASS
}, },
JWT_SECRET: process.env.JWT_SECRET, JWT_SECRET: process.env.JWT_SECRET,
EMAIL_INFO: { EMAIL_INFO: {
host: process.env.EMAIL_HOST, host: process.env.EMAIL_HOST,
port: 465, port: 465,
secure: true, // true for 465, false for other ports secure: true, // true for 465, false for other ports
auth: { auth: {
user: process.env.EMAIL_USER, user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASSWORD pass: process.env.EMAIL_PASSWORD
} }
}, },
TOKEN_LIFE: '1 week' TOKEN_LIFE: '1 week'
}; }
module.exports = config; module.exports = config

View File

@ -26,16 +26,24 @@ 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>
</p> </p>
${(!isValid) ? ` ${
!isValid
? `
<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 +65,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 +115,4 @@ exports.emailUserTemplate = (subtitle, buttonText, url, footerText) => `
</tbody> </tbody>
</table> </table>
</center> </center>
`; `

View File

@ -1,18 +1,18 @@
const errors = { const errors = {
generalError: { generalError: {
message: "Vous n'avez pas rentré de valeur valide.", message: "Vous n'avez pas rentré de valeur valide.",
statusCode: 400 statusCode: 400
}, },
serverError: { serverError: {
message: "Le serveur n'a pas pu traiter votre requête.", message: "Le serveur n'a pas pu traiter votre requête.",
statusCode: 500 statusCode: 500
}, },
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,44 +1,42 @@
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 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,
convertRomanArabicNumbers: convertRomanArabicNumbersOutput, convertRomanArabicNumbers: convertRomanArabicNumbersOutput,
convertDistance : convertDistanceOutput, convertDistance: convertDistanceOutput,
convertTemperature : convertTemperatureOutput, convertTemperature: convertTemperatureOutput,
armstrongNumber : armstrongNumberOutput, armstrongNumber: armstrongNumberOutput,
weatherRequest : weatherRequestOutput, weatherRequest: weatherRequestOutput,
convertCurrency : convertCurrencyOutput, convertCurrency: convertCurrencyOutput,
calculateAge : calculateAgeOutput, calculateAge: calculateAgeOutput,
heapAlgorithm : heapAlgorithmOutput, heapAlgorithm: heapAlgorithmOutput,
convertEncoding : convertEncodingOutput, convertEncoding: convertEncodingOutput,
randomQuote : randomQuote, randomQuote: randomQuote,
linkShortener : linkShortener, rightPrice: rightPriceOutput,
rightPrice : rightPriceOutput, isPalindrome: isPalindromeOutput,
isPalindrome : isPalindromeOutput, findLongestWord: findLongestWordOutput,
findLongestWord : findLongestWordOutput, fibonacci: fibonacciOutput,
fibonacci : fibonacciOutput, sortArray: sortArrayOutput
sortArray : sortArrayOutput,
};
// Choisi la fonction à exécuter
function functionToExecute(option) {
return functionObject[option];
} }
module.exports = functionToExecute; // Choisi la fonction à exécuter
function functionToExecute (option) {
return functionObject[option]
}
module.exports = functionToExecute

View File

@ -1,46 +1,59 @@
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.
* @param {Number} number - Le nombre à tester * @param {Number} number - Le nombre à tester
* @returns {Object} Un objet contenant l'explication en html et le booléen si oui ou non c'est un nombre d'armstrong * @returns {Object} Un objet contenant l'explication en html et le booléen si oui ou non c'est un nombre d'armstrong
* @examples armstrongNumber(153) 153 est un nombre d'Armstrong, car 1<sup>3</sup> + 5<sup>3</sup> + 3<sup>3</sup> = 153. * @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
if (!(number)) {
return errorHandling(next, requiredFields);
}
// Si ce n'est pas un nombre // S'il n'y a pas les champs obligatoire
number = parseInt(number); if (!number) {
if (isNaN(number) || number <= 0) { return errorHandling(next, requiredFields)
return errorHandling(next, { message: "Veuillez rentré un nombre valide.", statusCode: 400 }); }
}
return res.status(200).json(armstrongNumber(number)); // Si ce n'est pas un nombre
} number = parseInt(number)
if (isNaN(number) || number <= 0) {
return errorHandling(next, {
message: 'Veuillez rentré un nombre valide.',
statusCode: 400
})
}
return res.status(200).json(armstrongNumber(number))
}

View File

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

View File

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

View File

@ -1,10 +1,36 @@
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.
* @requires {@link correspondancesDistance} * @requires {@link correspondancesDistance}
* @param {Number} firstValue - Le nombre que vous voulez convertir * @param {Number} firstValue - Le nombre que vous voulez convertir
@ -13,39 +39,46 @@ const correspondancesDistance = ["pm", null, null, "nm", null, null, "µm", null
* @returns {Object|Boolean} false si arguments non valides et sinon un objet contenant la string et le nombre résultat * @returns {Object|Boolean} false si arguments non valides et sinon un objet contenant la string et le nombre résultat
* @examples convertDistance(500, 'cm', 'm') { resultNumber: 5, resultString: "5 m" } * @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
if (!(number && numberUnit && finalUnit)) {
return errorHandling(next, requiredFields);
}
// Si ce n'est pas un nombre // S'il n'y a pas les champs obligatoire
number = parseFloat(number); if (!(number && numberUnit && finalUnit)) {
if (isNaN(number)) { return errorHandling(next, requiredFields)
return errorHandling(next, { message: "Veuillez rentré un nombre valide.", statusCode: 400 }); }
}
const result = convertDistance(number, numberUnit, finalUnit); // Si ce n'est pas un nombre
if (!result) { number = parseFloat(number)
return errorHandling(next, generalError); if (isNaN(number)) {
} return errorHandling(next, {
message: 'Veuillez rentré un nombre valide.',
statusCode: 400
})
}
return res.status(200).json(result); const result = convertDistance(number, numberUnit, finalUnit)
} if (!result) {
return errorHandling(next, generalError)
}
return res.status(200).json(result)
}

View File

@ -1,242 +1,268 @@
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.
* @param {String} value - Le nombre à convertir en string * @param {String} value - Le nombre à convertir en string
* @returns {String} - Le nombre en binaire * @returns {String} - Le nombre en binaire
* @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)
} }
} }
/** /**
* @description Convertis un nombre binaire en décimal. * @description Convertis un nombre binaire en décimal.
* @param {String} value - Le nombre à convertir * @param {String} value - Le nombre à convertir
* @returns {(Number|String)} - Le nombre en décimal soit en nombre ou soit en string si supérieur à 1000 car pour 1000 par exemple formatNumberResult renvoie '1 000' * @returns {(Number|String)} - Le nombre en décimal soit en nombre ou soit en string si supérieur à 1000 car pour 1000 par exemple formatNumberResult renvoie '1 000'
* @examples binaryToDecimal('10') 2 * @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
} }
} }
/** /**
* @description Convertis un nombre décimal en hexadécimal. * @description Convertis un nombre décimal en hexadécimal.
* @param {String} value - Le nombre à convertir * @param {String} value - Le nombre à convertir
* @returns {String} - Le nombre en hexadécimal * @returns {String} - Le nombre en hexadécimal
* @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()
} }
} }
/** /**
* @description Convertis un nombre hexadécimal en décimal. * @description Convertis un nombre hexadécimal en décimal.
* @param {String} value - Le nombre à convertir * @param {String} value - Le nombre à convertir
* @returns {(Number|String)} - Le nombre en décimal soit en nombre ou soit en string si supérieur à 1000 car pour 1000 par exemple formatNumberResult renvoie '1 000' * @returns {(Number|String)} - Le nombre en décimal soit en nombre ou soit en string si supérieur à 1000 car pour 1000 par exemple formatNumberResult renvoie '1 000'
* @examples hexadecimalToDecimal('F') 15 * @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
} }
} }
/** /**
* @description Convertis un nombre binaire en hexadécimal. * @description Convertis un nombre binaire en hexadécimal.
* @param {String} value - Le nombre à convertir * @param {String} value - Le nombre à convertir
* @returns {String} - Le nombre en hexadécimal * @returns {String} - Le nombre en hexadécimal
* @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()
}
} }
/** /**
* @description Convertis un nombre hexadécimal en binaire. * @description Convertis un nombre hexadécimal en binaire.
* @param {String} value - Le nombre à convertir * @param {String} value - Le nombre à convertir
* @returns {String} - Le nombre en binaire * @returns {String} - Le nombre en binaire
* @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)
} }
} }
// Convertis des nombres de différentes bases et convertis en UTF-8. (source : http://jsfiddle.net/47zwb41o) // Convertis des nombres de différentes bases et convertis en UTF-8. (source : http://jsfiddle.net/47zwb41o)
/** /**
* @description Convertis chaque caractère d'une string en codePoint Unicode. * @description Convertis chaque caractère d'une string en codePoint Unicode.
* @param {String} value - La chaîne de caractère à convertir * @param {String} value - La chaîne de caractère à convertir
* @returns {String} * @returns {String}
* @examples textToNumberUnicode('abc') '97 98 99' * @examples textToNumberUnicode('abc') '97 98 99'
*/ */
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;
}
catch(error) {
return false;
} }
return resultat
} catch (error) {
return false
}
} }
/** /**
* @description Convertis chaque codePoint Unicode en caractère. * @description Convertis chaque codePoint Unicode en caractère.
* @param {String} string - Nombre Unicode à convertir espacé par un espace à chaque fois * @param {String} string - Nombre Unicode à convertir espacé par un espace à chaque fois
* @returns {String} * @returns {String}
* @examples numberUnicodeToText('97 98 99') 'abc' * @examples numberUnicodeToText('97 98 99') 'abc'
*/ */
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;
}
catch(error) {
return false;
} }
return resultat
} catch (error) {
return false
}
} }
/** /**
* @description Convertis un Texte en Binaire (UTF-8). * @description Convertis un Texte en Binaire (UTF-8).
* @param {String} s - La chaîne de caractère à convertir * @param {String} s - La chaîne de caractère à convertir
* @returns {String} * @returns {String}
* @examples textToBinary('abc') '01100001 01100010 01100011' * @examples textToBinary('abc') '01100001 01100010 01100011'
*/ */
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
for( ; i < l; i ++ ){ let i = 0
chr = s.charCodeAt( i ).toString(2); const l = s.length
while(chr.length % 8 != 0 ){ chr = '0' + chr; } let out = ''
out += chr; for (; i < l; i++) {
} chr = s.charCodeAt(i).toString(2)
return out.replace(/(\d{8})/g, '$1 ').replace(/(^\s+|\s+$)/,''); while (chr.length % 8 !== 0) {
} catch (error) { chr = '0' + chr
return false; }
out += chr
} }
return out.replace(/(\d{8})/g, '$1 ').replace(/(^\s+|\s+$)/, '')
} catch (error) {
return false
}
} }
/** /**
* @description Convertis du Binaire (UTF-8) en Texte. * @description Convertis du Binaire (UTF-8) en Texte.
* @param {String} s - La chaîne de caractère contenant tous les octets à convertir * @param {String} s - La chaîne de caractère contenant tous les octets à convertir
* @returns {String} * @returns {String}
* @examples binaryToText('01100001 01100010 01100011') 'abc' * @examples binaryToText('01100001 01100010 01100011') 'abc'
*/ */
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
for( ; i < l; i += 8){ const l = s.length
chr = parseInt( s.substr(i, 8 ), 2).toString(16); let chr
out += '%' + ((chr.length % 2 == 0) ? chr : '0' + chr); let out = ''
} for (; i < l; i += 8) {
return decodeURIComponent(out); chr = parseInt(s.substr(i, 8), 2).toString(16)
} catch (error) { out += '%' + (chr.length % 2 === 0 ? chr : '0' + chr)
return false;
} }
} return decodeURIComponent(out)
} catch (error) {
return false
}
}
/** /**
* @description Convertis un Texte en Hexadécimal (UTF-8). * @description Convertis un Texte en Hexadécimal (UTF-8).
* @param {String} s - La chaîne de caractère à convertir * @param {String} s - La chaîne de caractère à convertir
* @returns {String} * @returns {String}
* @examples textToHexadecimal('abc') '61 62 63' * @examples textToHexadecimal('abc') '61 62 63'
*/ */
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
for( ; i < l; i++ ){ let i = 0
chr = s.charCodeAt( i ).toString( 16 ); const l = s.length
out += ( chr.length % 2 == 0 ) ? chr : '0' + chr; let out = ''
out += " "; for (; i < l; i++) {
} chr = s.charCodeAt(i).toString(16)
return out.toUpperCase(); out += chr.length % 2 === 0 ? chr : '0' + chr
} out += ' '
catch (error) {
return false;
} }
return out.toUpperCase()
} catch (error) {
return false
}
} }
/** /**
* @description Convertis de l'Hexadécimal (UTF-8) en Texte. * @description Convertis de l'Hexadécimal (UTF-8) en Texte.
* @param {String} s - La chaîne de caractère contenant tous les nombres Hexadécimal à convertir * @param {String} s - La chaîne de caractère contenant tous les nombres Hexadécimal à convertir
* @returns {String} * @returns {String}
* @examples hexadecimalToText('61 62 63') 'abc' * @examples hexadecimalToText('61 62 63') 'abc'
*/ */
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 = {
function executeFunction(option, value) { decimalToBinary,
return convertEncoding[option](value); binaryToDecimal,
decimalToHexadecimal,
hexadecimalToDecimal,
binaryToHexadecimal,
hexadecimalToBinary,
textToNumberUnicode,
numberUnicodeToText,
textToBinary,
binaryToText,
textToHexadecimal,
hexadecimalToText
}
function executeFunction (option, value) {
return convertEncoding[option](value)
} }
module.exports = convertEncodingOutput = ({ res, next }, argsObject) => { module.exports = ({ res, next }, argsObject) => {
let { value, functionName } = argsObject; const { value, functionName } = argsObject
// S'il n'y a pas les champs obligatoire
if (!(value && functionName)) {
return errorHandling(next, requiredFields);
}
// Si la fonction n'existe pas // S'il n'y a pas les champs obligatoire
if (!convertEncoding.hasOwnProperty(functionName)) { if (!(value && functionName)) {
return errorHandling(next, { message: "Cette conversion n'existe pas.", statusCode: 400 }); return errorHandling(next, requiredFields)
} }
const result = executeFunction(functionName, value); // Si la fonction n'existe pas
// eslint-disable-next-line
if (!convertEncoding.hasOwnProperty(functionName)) {
return errorHandling(next, {
message: "Cette conversion n'existe pas.",
statusCode: 400
})
}
// Mauvaise valeur entrée const result = executeFunction(functionName, value)
if (!result) {
return errorHandling(next, generalError);
}
return res.status(200).json({ result, resultHTML: `<p>${result}</p>` }); // Mauvaise valeur entrée
} if (!result) {
return errorHandling(next, generalError)
}
return res.status(200).json({ result, resultHTML: `<p>${result}</p>` })
}

View File

@ -1,123 +1,143 @@
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.
* @param {number} nombre - Le nombre arabe à convertir * @param {number} nombre - Le nombre arabe à convertir
* @returns {string} * @returns {string}
* @examples convertArabicToRoman(24) 'XXIV' * @examples convertArabicToRoman(24) 'XXIV'
*/ */
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
} }
/** /**
* @description Convertis un nombre romain en nombre arabe. * @description Convertis un nombre romain en nombre arabe.
* @param {string} string - Le nombre romain à convertir * @param {string} string - Le nombre romain à convertir
* @return {number} * @return {number}
* @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 != '') {
result = 0;
} }
return result; })
} if (string !== '') {
result = 0
}
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
number = parseInt(number);
if (isNaN(number)) {
return errorHandling(next, { message: "Veuillez rentré un nombre valide.", statusCode: 400 });
}
const result = convertArabicToRoman(number); // Si ce n'est pas un nombre
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>` }); number = parseInt(number)
if (isNaN(number)) {
return errorHandling(next, {
message: 'Veuillez rentré un nombre valide.',
statusCode: 400
})
}
const result = convertArabicToRoman(number)
return res
.status(200)
.json({
result,
resultHTML: `<p><span class="important">${formatNumberResult(
number
)}</span> s'écrit <span class="important">${result}</span> en chiffres romains.</p>`
})
} }
const convertRomanArabicObject = { convertRomanToArabicOutput, convertArabicToRomanOutput }; const convertRomanArabicObject = {
function executeFunction(option, value, { res, next }) { convertRomanToArabicOutput,
return convertRomanArabicObject[option]({ res, next}, value); convertArabicToRomanOutput
}
function executeFunction (option, value, { res, next }) {
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
if (!convertRomanArabicObject.hasOwnProperty(functionName)) { // eslint-disable-next-line
return errorHandling(next, { message: "Cette conversion n'existe pas.", statusCode: 400 }); if (!convertRomanArabicObject.hasOwnProperty(functionName)) {
} return errorHandling(next, {
message: "Cette conversion n'existe pas.",
statusCode: 400
})
}
executeFunction(functionName, value, { res, next }); executeFunction(functionName, value, { res, next })
} }

View File

@ -1,50 +1,53 @@
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.
* @param {Number} degree - Nombre de degrès * @param {Number} degree - Nombre de degrès
* @param {String} unit - Unité du nombre (°C ou °F) après conversion * @param {String} unit - Unité du nombre (°C ou °F) après conversion
* @returns {Object} false si arguments non valides et sinon un objet contenant la string et le nombre résultat * @returns {Object} false si arguments non valides et sinon un objet contenant la string et le nombre résultat
* @examples convertTemperature(23, '°F') { result: 73.4, resultHTML: "73.4 °F" } * @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 {
} result: temperatureValue,
return { resultHTML: `<p>${formatNumberResult(degree)} ${
result: temperatureValue, unit === '°C' ? '°F' : '°C'
resultHTML: `<p>${formatNumberResult(degree)} ${(unit === '°C') ? "°F" : "°C"} = ${formatNumberResult(temperatureValue)} ${unit}</p>` } = ${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
if (!(degree && unitToConvert)) {
return errorHandling(next, requiredFields);
}
// Si ce n'est pas un nombre
degree = parseFloat(degree);
if (isNaN(degree)) {
return errorHandling(next, { message: "Veuillez rentré un nombre valide.", statusCode: 400 });
}
const result = convertTemperature(degree, unitToConvert); // S'il n'y a pas les champs obligatoire
if (!result) { if (!(degree && unitToConvert)) {
return errorHandling(next, generalError); return errorHandling(next, requiredFields)
} }
return res.status(200).json(result); // Si ce n'est pas un nombre
} degree = parseFloat(degree)
if (isNaN(degree)) {
return errorHandling(next, {
message: 'Veuillez rentré un nombre valide.',
statusCode: 400
})
}
const result = convertTemperature(degree, unitToConvert)
if (!result) {
return errorHandling(next, generalError)
}
return res.status(200).json(result)
}

View File

@ -1,46 +1,55 @@
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.
* @param {number} counter * @param {number} counter
*/ */
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
if (!(counter)) {
return errorHandling(next, requiredFields);
}
// Si ce n'est pas un nombre
counter = parseInt(counter);
if (isNaN(counter)) {
return errorHandling(next, { message: "Veuillez rentré un nombre valide.", statusCode: 400 });
}
// Si le nombre dépasse LIMIT_COUNTER // S'il n'y a pas les champs obligatoire
const LIMIT_COUNTER = 51; if (!counter) {
if (counter >= LIMIT_COUNTER) { return errorHandling(next, requiredFields)
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); // Si ce n'est pas un nombre
const resultFormatted = result.map((number) => formatNumberResult(number)); counter = parseInt(counter)
return res.status(200).json({ if (isNaN(counter)) {
result, return errorHandling(next, {
resultFormatted, message: 'Veuillez rentré un nombre valide.',
resultHTML: `<p>Les ${counter} premiers nombres de la suite de fibonacci :<br/> ${resultFormatted.join(', ')}</p>` statusCode: 400
}); })
} }
// Si le nombre dépasse LIMIT_COUNTER
const LIMIT_COUNTER = 51
if (counter >= LIMIT_COUNTER) {
return errorHandling(next, {
message: `Par souci de performance, vous ne pouvez pas exécuter cette fonction avec un compteur dépassant ${LIMIT_COUNTER -
1}.`,
statusCode: 400
})
}
const result = fibonacci(counter)
const resultFormatted = result.map(number => formatNumberResult(number))
return res.status(200).json({
result,
resultFormatted,
resultHTML: `<p>Les ${counter} premiers nombres de la suite de fibonacci :<br/> ${resultFormatted.join(
', '
)}</p>`
})
}

View File

@ -1,39 +1,39 @@
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
* @param {string} string * @param {string} string
* @returns {string} * @returns {string}
* @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
if (!(string)) {
return errorHandling(next, requiredFields);
}
const result = findLongestWord(string); // S'il n'y a pas les champs obligatoire
return res.status(200).json({ if (!string) {
result, return errorHandling(next, requiredFields)
resultHTML: `<p>Le mot le plus long est : <br/>"${result}"</p>` }
});
} const result = findLongestWord(string)
return res.status(200).json({
result,
resultHTML: `<p>Le mot le plus long est : <br/>"${result}"</p>`
})
}

View File

@ -1,52 +1,64 @@
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.
* @param {String} string - La chaîne de caractère à permuter * @param {String} string - La chaîne de caractère à permuter
* @returns {Array} * @returns {Array}
* @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 =
const innerPermutations = heapAlgorithm(charsLeft); string.substring(0, indexString) + string.substring(indexString + 1)
for (let indexPermutation = 0; indexPermutation < innerPermutations.length; indexPermutation++) { const innerPermutations = heapAlgorithm(charsLeft)
results.push(firstChar + innerPermutations[indexPermutation]); for (
} let indexPermutation = 0;
indexPermutation < innerPermutations.length;
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
if (!(string)) {
return errorHandling(next, requiredFields);
}
// Si la chaîne de caractère dépasse LIMIT_CHARACTERS caractères // S'il n'y a pas les champs obligatoire
const LIMIT_CHARACTERS = 7; if (!string) {
if (string.length > LIMIT_CHARACTERS) { return errorHandling(next, requiredFields)
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); // Si la chaîne de caractère dépasse LIMIT_CHARACTERS caractères
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/>`; const LIMIT_CHARACTERS = 7
result.forEach((string) => { if (string.length > LIMIT_CHARACTERS) {
resultHTML += string + "<br/>"; 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.`,
resultHTML += "</p>"; statusCode: 400
return res.status(200).json({ result, resultHTML }); })
} }
const result = heapAlgorithm(string)
let resultHTML = `<p>Il y a ${formatNumberResult(
result.length
)} possibilités d'anagramme pour le mot "${string}" qui contient ${
string.length
} caractères, la liste : <br/><br/>`
result.forEach(string => {
resultHTML += string + '<br/>'
})
resultHTML += '</p>'
return res.status(200).json({ result, resultHTML })
}

View File

@ -1,48 +1,58 @@
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
* @param {string} string * @param {string} string
* @returns {string} * @returns {string}
* @example reverseString('Hello') 'olleH' * @example reverseString('Hello') 'olleH'
*/ */
function reverseString(string) { function reverseString (string) {
return string.split("").reverse().join(''); return string
.split('')
.reverse()
.join('')
} }
/** /**
* @description Vérifie si un mot est un palindrome (un mot qui peut s'écrire dans les deux sens) * @description Vérifie si un mot est un palindrome (un mot qui peut s'écrire dans les deux sens)
* @requires reverseString * @requires reverseString
* @param {string} string * @param {string} string
* @param {string} reverseStringResult La chaîne de caractères inversée * @param {string} reverseStringResult La chaîne de caractères inversée
* @returns {boolean} * @returns {boolean}
* @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
if (!(string)) {
return errorHandling(next, requiredFields);
}
if (typeof string !== 'string') { // S'il n'y a pas les champs obligatoire
return errorHandling(next, { message: "Vous devez rentré une chaîne de caractère valide.", statusCode: 400 }); if (!string) {
} return errorHandling(next, requiredFields)
}
string = string.toLowerCase(); if (typeof string !== 'string') {
return errorHandling(next, {
message: 'Vous devez rentré une chaîne de caractère valide.',
statusCode: 400
})
}
const reverseStringResult = reverseString(string); string = string.toLowerCase()
const isPalindromeResult = isPalindrome(string, reverseStringResult);
return res.status(200).json({ const reverseStringResult = reverseString(string)
isPalindrome: isPalindromeResult, const isPalindromeResult = isPalindrome(string, reverseStringResult)
reverseString: reverseStringResult, return res.status(200).json({
resultHTML: `<p>"${string}" ${(isPalindromeResult) ? "est" : "n'est pas"} un palindrome car <br/> "${string}" ${(isPalindromeResult) ? "===" : "!=="} "${reverseStringResult}"</p>` isPalindrome: isPalindromeResult,
}); reverseString: reverseStringResult,
} resultHTML: `<p>"${string}" ${
isPalindromeResult ? 'est' : "n'est pas"
} un palindrome car <br/> "${string}" ${
isPalindromeResult ? '===' : '!=='
} "${reverseStringResult}"</p>`
})
}

View File

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

View File

@ -1,37 +1,47 @@
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.
* @param {Number} min Nombre Minimum * @param {Number} min Nombre Minimum
* @param {Number} max Nombre Maximum * @param {Number} max Nombre Maximum
* @returns {Number} Nombre aléatoire * @returns {Number} Nombre aléatoire
* @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
if (!(min && max)) {
return errorHandling(next, requiredFields);
}
// Si ce ne sont pas des nombres
min = parseInt(min);
max = parseInt(max);
if (isNaN(min) || isNaN(max)) {
return errorHandling(next, { message: "Les paramètres min et max doivent être des nombres...", statusCode: 400 });
}
const result = randomNumber(min, max); // S'il n'y a pas les champs obligatoire
return res.status(200).json({ result, resultHTML: `<p>Nombre aléatoire compris entre ${min} inclus et ${max} inclus : <strong>${formatNumberResult(result)}</strong></p>` }); if (!(min && max)) {
return errorHandling(next, requiredFields)
}
// Si ce ne sont pas des nombres
min = parseInt(min)
max = parseInt(max)
if (isNaN(min) || isNaN(max)) {
return errorHandling(next, {
message: 'Les paramètres min et max doivent être des nombres...',
statusCode: 400
})
}
const result = randomNumber(min, max)
return res
.status(200)
.json({
result,
resultHTML: `<p>Nombre aléatoire compris entre ${min} inclus et ${max} inclus : <strong>${formatNumberResult(
result
)}</strong></p>`
})
} }
exports.randomNumber = randomNumber; exports.randomNumber = randomNumber
exports.randomNumberOutput = randomNumberOutput; exports.randomNumberOutput = randomNumberOutput

View File

@ -1,26 +1,24 @@
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: {
], exclude: ['isValidated']
attributes: { },
exclude: ["isValidated"] where: {
}, isValidated: 1
where: { }
isValidated: 1, })
} return res.status(200).json(quote)
}); } catch (error) {
return res.status(200).json(quote); console.log(error)
} catch (error) { return errorHandling(next, serverError)
console.log(error); }
return errorHandling(next, serverError); }
}
}

View File

@ -1,57 +1,60 @@
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(
const { document } = (new JSDOM(data)).window; `http://api.scraperapi.com/?api_key=${SCRAPER_API_KEY}&url=${url}`
const amazonProductList = document.querySelectorAll('.s-result-item'); )
const productsList = []; const { document } = new JSDOM(data).window
for (let indexProduct in amazonProductList) { const amazonProductList = document.querySelectorAll('.s-result-item')
try { const productsList = []
const elementProduct = amazonProductList[indexProduct]; for (const indexProduct in amazonProductList) {
const productImage = elementProduct.querySelector('.s-image');
const originalPrice = elementProduct.querySelector(".a-price-whole").innerHTML;
productsList.push({
name: productImage["alt"],
image: productImage["src"],
price: Number(originalPrice.replace(",", ".").replace(" ", ""))
});
} catch (_error) {
continue;
}
}
return productsList;
}
module.exports = rightPriceOutput = async ({ res, next }, _argsObject) => {
const subject = getRandomArrayElement(subjectList);
try { try {
const productsList = await getAmazonProductList(subject); const elementProduct = amazonProductList[indexProduct]
const randomProduct = getRandomArrayElement(productsList); const productImage = elementProduct.querySelector('.s-image')
return res.status(200).json({ subject, ...randomProduct }); const originalPrice = elementProduct.querySelector('.a-price-whole')
} catch (error) { .innerHTML
console.error(error); productsList.push({
return errorHandling(next, serverError); name: productImage.alt,
image: productImage.src,
price: Number(originalPrice.replace(',', '.').replace(' ', ''))
})
} catch (_error) {
continue
} }
} }
return productsList
}
module.exports = async ({ res, next }, _argsObject) => {
const subject = getRandomArrayElement(subjectList)
try {
const productsList = await getAmazonProductList(subject)
const randomProduct = getRandomArrayElement(productsList)
return res.status(200).json({ subject, ...randomProduct })
} catch (error) {
console.error(error)
return errorHandling(next, serverError)
}
}

View File

@ -1,56 +1,69 @@
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
if (!(numbersList)) {
return errorHandling(next, requiredFields);
}
const numbersListArray = numbersList.split(',').map((number) => number.trim().replace(' ', '')).map(Number); // S'il n'y a pas les champs obligatoire
if (!numbersList) {
return errorHandling(next, requiredFields)
}
// Si ce n'est pas une liste de nombres const numbersListArray = numbersList
if (numbersListArray.includes(NaN)) { .split(',')
return errorHandling(next, { message: "Vous devez rentrer une liste de nombres séparée par des virgules valide.", statusCode: 400 }); .map(number => number.trim().replace(' ', ''))
} .map(Number)
// Si la taille du tableau dépasse LIMIT_ARRAY_LENGTH // Si ce n'est pas une liste de nombres
const LIMIT_ARRAY_LENGTH = 31; if (numbersListArray.includes(NaN)) {
if (numbersListArray.length >= LIMIT_ARRAY_LENGTH) { return errorHandling(next, {
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 }); message:
} 'Vous devez rentrer une liste de nombres séparée par des virgules valide.',
statusCode: 400
})
}
const result = sortArray(numbersListArray); // Si la taille du tableau dépasse LIMIT_ARRAY_LENGTH
const resultFormatted = result.map((number) => formatNumberResult(number)); const LIMIT_ARRAY_LENGTH = 31
return res.status(200).json({ if (numbersListArray.length >= LIMIT_ARRAY_LENGTH) {
result, return errorHandling(next, {
resultFormatted, message: `Par souci de performance, vous ne pouvez pas exécuter cette fonction avec une liste de nombres dépassant ${LIMIT_ARRAY_LENGTH -
resultHTML: `<p>La liste de nombres dans l'ordre croissant :<br/> ${resultFormatted.join(', ')}</p>` 1} nombres.`,
}); statusCode: 400
} })
}
const result = sortArray(numbersListArray)
const resultFormatted = result.map(number => formatNumberResult(number))
return res.status(200).json({
result,
resultFormatted,
resultHTML: `<p>La liste de nombres dans l'ordre croissant :<br/> ${resultFormatted.join(
', '
)}</p>`
})
}

View File

@ -1,46 +1,73 @@
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({
/* /*
rate: number of requests rate: number of requests
per per
limit: number of seconds limit: number of seconds
*/ */
rules: { rules: {
weatherRequest: { weatherRequest: {
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}`) () => {
.then((response) =>{ axios
const json = response.data; .get(
const showDateTimeValue = dateTimeUTC((json.timezone / 60 / 60).toString()).showDateTimeValue; `https://api.openweathermap.org/data/2.5/weather?q=${cityName}&lang=fr&units=metric&appid=${WEATHER_API_KEY}`
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 }); .then(response => {
}) const json = response.data
.catch(() => errorHandling(next, { message: "La ville n'existe pas (dans l'API de openweathermap.org).", statusCode: 404 })); const showDateTimeValue = dateTimeUTC(
}, 'everyone', 'weatherRequest'); (json.timezone / 60 / 60).toString()
} ).showDateTimeValue
const resultHTML = `<p>🌎 Position : <a href="https://www.google.com/maps/search/?api=1&query=${
json.coord.lat
},${json.coord.lon}" rel="noopener noreferrer" target="_blank">${
json.name
}, ${
json.sys.country
}</a><br/> Date et heure : ${showDateTimeValue} <br/> Météo : ${capitalize(
json.weather[0].description
)}<br/>🌡 Température : ${json.main.temp} °C<br/> 💧 Humidité : ${
json.main.humidity
}% <br/> <img src="https://openweathermap.org/img/wn/${
json.weather[0].icon
}@2x.png"/></p>`
return res.status(200).json({ result: json, resultHTML })
})
.catch(() =>
errorHandling(next, {
message:
"La ville n'existe pas (dans l'API de openweathermap.org).",
statusCode: 404
})
)
},
'everyone',
'weatherRequest'
)
}

View File

@ -1,12 +1,12 @@
/** /**
* @description Majuscule à la 1ère lettre d'une string. * @description Majuscule à la 1ère lettre d'une string.
* @param {String} s * @param {String} s
* @returns {String} * @returns {String}
* @examples capitalize('hello world!') 'Hello world!' * @examples capitalize('hello world!') 'Hello world!'
*/ */
function capitalize(s) { function capitalize (s) {
if (typeof s !== 'string') return '' if (typeof s !== 'string') return ''
return s.charAt(0).toUpperCase() + s.slice(1) return s.charAt(0).toUpperCase() + s.slice(1)
} }
module.exports = capitalize; module.exports = capitalize

View File

@ -1,43 +1,44 @@
/** /**
* @description Donne la date et l'heure selon l'UTC (Universal Time Coordinated). * @description Donne la date et l'heure selon l'UTC (Universal Time Coordinated).
* @param {String} utc Heure de décalage par rapport à l'UTC * @param {String} utc Heure de décalage par rapport à l'UTC
* @returns {Function} showDateTime(enteredOffset) Retourne l'exécution de la fonction showDateTime * @returns {Function} showDateTime(enteredOffset) Retourne l'exécution de la fonction showDateTime
* @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)
}
/**
* @description Affiche la date et l'heure (format : dd/mm/yyyy - 00:00:00).
* @requires {@link fonctions_annexes.js: showDateTime}
* @param {String} utc Heure de décalage par rapport à l'UTC
* @returns {Object} Retourne un objet contenant l'année, le mois, le jour, l'heure, les minutes, les secondes et la date formaté
* @examples dateTimeUTC('0') dateTimeUTC vous renvoie l'exécution de showDateTime
*/
function showDateTime(timeNow) {
const year = timeNow.getFullYear();
const month = ('0'+(timeNow.getMonth()+1)).slice(-2);
const day = ('0'+timeNow.getDate()).slice(-2);
const hour = ('0'+timeNow.getHours()).slice(-2);
const minute = ('0'+timeNow.getMinutes()).slice(-2);
const second = ('0'+timeNow.getSeconds()).slice(-2);
const showDateTimeValue = day + "/" + month + "/" + year + " - " + hour + ":" + minute + ":" + second;
const objectDateTime = {
year: year,
month: month,
day: day,
hour: hour,
minute: minute,
second: second,
showDateTimeValue: showDateTimeValue
};
return objectDateTime;
} }
module.exports = dateTimeUTC; /**
* @description Affiche la date et l'heure (format : dd/mm/yyyy - 00:00:00).
* @requires {@link fonctions_annexes.js: showDateTime}
* @param {String} utc Heure de décalage par rapport à l'UTC
* @returns {Object} Retourne un objet contenant l'année, le mois, le jour, l'heure, les minutes, les secondes et la date formaté
* @examples dateTimeUTC('0') dateTimeUTC vous renvoie l'exécution de showDateTime
*/
function showDateTime (timeNow) {
const year = timeNow.getFullYear()
const month = ('0' + (timeNow.getMonth() + 1)).slice(-2)
const day = ('0' + timeNow.getDate()).slice(-2)
const hour = ('0' + timeNow.getHours()).slice(-2)
const minute = ('0' + timeNow.getMinutes()).slice(-2)
const second = ('0' + timeNow.getSeconds()).slice(-2)
const showDateTimeValue =
day + '/' + month + '/' + year + ' - ' + hour + ':' + minute + ':' + second
const objectDateTime = {
year: year,
month: month,
day: day,
hour: hour,
minute: minute,
second: second,
showDateTimeValue: showDateTimeValue
}
return objectDateTime
}
module.exports = dateTimeUTC

View File

@ -1,14 +1,14 @@
/** /**
* @description Formate un nombre avec des espaces. * @description Formate un nombre avec des espaces.
* @param {Number} number * @param {Number} number
* @param {String} separator Le séparateur utilisé pour la virgule (exemple: "." ou ",") * @param {String} separator Le séparateur utilisé pour la virgule (exemple: "." ou ",")
* @returns {String} - Le nombre formaté * @returns {String} - Le nombre formaté
* @examples formatNumberResult(76120) '76 120' * @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,14 @@
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
@ -12,23 +12,27 @@ const DEFAULT_OPTIONS = {
* @param {*} Model Model Sequelize * @param {*} Model Model Sequelize
* @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 (
const page = helperQueryNumber(req.query.page, 1); { req, res, next },
const limit = helperQueryNumber(req.query.limit, 10); Model,
const offset = (page - 1) * limit; options = DEFAULT_OPTIONS
try { ) {
const result = await Model.findAndCountAll({ const page = helperQueryNumber(req.query.page, 1)
limit, const limit = helperQueryNumber(req.query.limit, 10)
offset, const offset = (page - 1) * limit
...options try {
}); const result = await Model.findAndCountAll({
const { count, rows } = result; limit,
const hasMore = (page * limit) < count; offset,
return res.status(200).json({ totalItems: count, hasMore, rows }); ...options
} catch (error) { })
console.log(error); const { count, rows } = result
return errorHandling(next, serverError); const hasMore = page * limit < count
} return res.status(200).json({ totalItems: count, hasMore, rows })
} catch (error) {
console.log(error)
return errorHandling(next, serverError)
}
} }
module.exports = getPagesHelper; module.exports = getPagesHelper

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

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,99 @@
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({
if (!resultFunction) { where: { id: functionId }
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); })
} if (!resultFunction) {
if (!message) { return errorHandling(next, {
return errorHandling(next, { message: "Vous ne pouvez pas poster de commentaire vide.", statusCode: 400 }); message: "La fonction n'existe pas.",
} statusCode: 404
const comment = await Comments.create({ message, userId: req.userId, functionId }); })
return res.status(201).json(comment);
} catch (error) {
console.log(error);
return errorHandling(next, serverError);
} }
if (!message) {
return errorHandling(next, {
message: 'Vous ne pouvez pas poster de commentaire vide.',
statusCode: 400
})
}
const comment = await Comments.create({
message,
userId: req.userId,
functionId
})
return res.status(201).json(comment)
} catch (error) {
console.log(error)
return errorHandling(next, serverError)
}
} }
exports.deleteCommentById = async (req, res, next) => { 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({
if (!comment) { where: { userId: req.userId, id: parseInt(commentId) }
return errorHandling(next, { message: "Le commentaire n'existe pas.", statusCode: 404 }); })
} if (!comment) {
await comment.destroy(); return errorHandling(next, {
return res.status(200).json({ message: "Le commentaire a bien été supprimé." }); message: "Le commentaire n'existe pas.",
} catch (error) { statusCode: 404
console.log(error); })
return errorHandling(next, serverError);
} }
await comment.destroy()
return res
.status(200)
.json({ message: 'Le commentaire a bien été supprimé.' })
} catch (error) {
console.log(error)
return errorHandling(next, serverError)
}
} }
exports.putCommentsById = async (req, res, next) => { 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 {
const comment = await Comments.findOne({
where: { userId: req.userId, id: parseInt(commentId) }
})
if (!comment) {
return errorHandling(next, {
message: "Le commentaire n'existe pas.",
statusCode: 404
})
} }
try { comment.message = message
const comment = await Comments.findOne({ where: { userId: req.userId, id: parseInt(commentId) } }); await comment.save()
if (!comment) { return res
return errorHandling(next, { message: "Le commentaire n'existe pas.", statusCode: 404 }); .status(200)
} .json({ message: 'Le commentaire a bien été modifié.' })
comment.message = message; } catch (error) {
await comment.save(); console.log(error)
return res.status(200).json({ message: "Le commentaire a bien été modifié." }); return errorHandling(next, serverError)
} catch (error) { }
console.log(error); }
return errorHandling(next, serverError);
}
}

View File

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

View File

@ -1,69 +1,93 @@
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 {
const options = { search = search.toLowerCase()
where: { } catch {}
isOnline: 1, const options = {
// Trie par catégorie where: {
... (categoryId !== 0) && { categorieId: categoryId }, isOnline: 1,
// Recherche // Trie par catégorie
... (search != undefined) && { ...(categoryId !== 0 && { categorieId: categoryId }),
[Sequelize.Op.or]: [ // Recherche
{ title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', `%${search}%`) }, ...(search != null && {
{ slug: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('slug')), 'LIKE', `%${search}%`) }, [Sequelize.Op.or]: [
{ description: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('description')), 'LIKE', `%${search}%`) } {
] title: Sequelize.where(
} Sequelize.fn('LOWER', Sequelize.col('title')),
}, 'LIKE',
include: [ `%${search}%`
{ model: Categories, attributes: ["name", "color"] } )
], },
attributes: { {
exclude: ["updatedAt", "utilizationForm", "article", "isOnline"] slug: Sequelize.where(
}, Sequelize.fn('LOWER', Sequelize.col('slug')),
order: [['createdAt', 'DESC']] 'LIKE',
}; `%${search}%`
return await getPagesHelper({ req, res, next }, Functions, options); )
},
{
description: Sequelize.where(
Sequelize.fn('LOWER', Sequelize.col('description')),
'LIKE',
`%${search}%`
)
}
]
})
},
include: [{ model: Categories, attributes: ['name', 'color'] }],
attributes: {
exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline']
},
order: [['createdAt', 'DESC']]
}
return await getPagesHelper({ req, res, next }, Functions, options)
} }
exports.getFunctionBySlug = (req, res, next) => { 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 => {
}) if (!result) {
.then((result) => { return errorHandling(next, {
if (!result) { message: "La fonction n'existe pas.",
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); statusCode: 404
}
try { result.utilizationForm = JSON.parse(result.utilizationForm); } catch {}
return res.status(200).json(result);
}) })
.catch((error) => { }
console.log(error); try {
return errorHandling(next, serverError); result.utilizationForm = JSON.parse(result.utilizationForm)
}); } catch {}
return res.status(200).json(result)
})
.catch(error => {
console.log(error)
return errorHandling(next, serverError)
})
} }
exports.executeFunctionBySlug = (req, res, next) => { 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

@ -0,0 +1,191 @@
const validator = require('validator')
const errorHandling = require('../assets/utils/errorHandling')
const { requiredFields, serverError } = require('../assets/config/errors')
const ShortLinks = require('../models/short_links')
const getPagesHelper = require('../assets/utils/getPagesHelper')
const Sequelize = require('sequelize')
const shortLinkBaseURL = 'https://s.divlo.fr'
exports.getLinks = async (req, res, next) => {
const { userId } = req
const options = {
where: { userId },
order: [['createdAt', 'DESC']]
}
return await getPagesHelper({ req, res, next }, ShortLinks, options)
}
exports.postLink = async (req, res, next) => {
const { userId } = req
let { url, shortcutName } = req.body
// S'il n'y a pas les champs obligatoire
if (!(url && shortcutName)) {
return errorHandling(next, requiredFields)
}
// Si ce n'est pas une url
if (!validator.isURL(url)) {
return errorHandling(next, {
message: 'Veuillez entré une URL valide.',
statusCode: 400
})
}
// Si ce n'est pas de type slug
if (!validator.isSlug(shortcutName)) {
return errorHandling(next, {
message:
"Le nom de votre raccourci doit être de type slug (ne pas contenir d'espaces, ni de caractères spéciaux).",
statusCode: 400
})
}
// Sanitize shortcutName
shortcutName = validator.escape(shortcutName)
shortcutName = validator.trim(shortcutName)
shortcutName = validator.blacklist(shortcutName, ' ')
try {
// Si l'url a déjà été raccourcie
const urlInDatabase = await ShortLinks.findOne({ where: { url } })
if (urlInDatabase) {
const urlShort = `${shortLinkBaseURL}/${urlInDatabase.shortcut}`
return errorHandling(next, {
message: `L'url a déjà été raccourcie... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`,
statusCode: 400
})
}
// Si le nom du raccourci existe déjà
const shortcutInDatabase = await ShortLinks.findOne({
where: { shortcut: shortcutName }
})
if (shortcutInDatabase) {
const urlShort = `${shortLinkBaseURL}/${shortcutInDatabase.shortcut}`
return errorHandling(next, {
message: `Le nom du raccourci a déjà été utilisé... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`,
statusCode: 400
})
}
// Ajout du lien raccourci
const result = await ShortLinks.create({
url,
shortcut: shortcutName,
userId
})
const shortcutLinkResult = `${shortLinkBaseURL}/${result.shortcut}`
return res.status(200).json({
resultHTML: `URL Raccourcie : <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${shortcutLinkResult}">${shortcutLinkResult}</a>`,
result: shortcutLinkResult
})
} catch (error) {
console.log(error)
return errorHandling(next, serverError)
}
}
exports.putLink = async (req, res, next) => {
const { id } = req.params
const { userId } = req
let { url, shortcutName } = req.body
// S'il n'y a pas les champs obligatoire
if (!(url && shortcutName)) {
return errorHandling(next, requiredFields)
}
// Si ce n'est pas une url
if (!validator.isURL(url)) {
return errorHandling(next, {
message: 'Veuillez entré une URL valide.',
statusCode: 400
})
}
// Si ce n'est pas de type slug
if (!validator.isSlug(shortcutName)) {
return errorHandling(next, {
message:
"Le nom de votre raccourci doit être de type slug (ne pas contenir d'espaces, ni de caractères spéciaux).",
statusCode: 400
})
}
// Sanitize shortcutName
shortcutName = validator.escape(shortcutName)
shortcutName = validator.trim(shortcutName)
shortcutName = validator.blacklist(shortcutName, ' ')
try {
// Si l'url a déjà été raccourcie par quelqu'un d'autre
const urlInDatabase = await ShortLinks.findOne({
where: { url, [Sequelize.Op.not]: { userId } }
})
if (urlInDatabase) {
const urlShort = `${shortLinkBaseURL}/${urlInDatabase.shortcut}`
return errorHandling(next, {
message: `L'url a déjà été raccourcie... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`,
statusCode: 400
})
}
// Si le nom du raccourci existe déjà par quelqu'un d'autre
const shortcutInDatabase = await ShortLinks.findOne({
where: { shortcut: shortcutName, [Sequelize.Op.not]: { userId } }
})
if (shortcutInDatabase) {
const urlShort = `${shortLinkBaseURL}/${shortcutInDatabase.shortcut}`
return errorHandling(next, {
message: `Le nom du raccourci a déjà été utilisé... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`,
statusCode: 400
})
}
// Modification du lien raccourci
const result = await ShortLinks.findOne({
where: { id, userId }
})
console.log(result)
if (!result) {
return errorHandling(next, {
statusCode: 404,
message: "Le raccourci n'existe pas..."
})
}
result.url = url
result.shortcut = shortcutName
const { shortcut } = await result.save()
const shortcutLinkResult = `${shortLinkBaseURL}/${shortcut}`
return res.status(200).json({
resultHTML: `URL Raccourcie : <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${shortcutLinkResult}">${shortcutLinkResult}</a>`,
result: shortcutLinkResult
})
} catch (error) {
console.log(error)
return errorHandling(next, serverError)
}
}
exports.deleteLink = async (req, res, next) => {
const { id } = req.params
const { userId } = req
try {
const linkResult = await ShortLinks.findOne({
where: { id, userId }
})
if (!linkResult) {
return errorHandling(next, {
message: "Le lien raccourci n'existe pas.",
statusCode: 404
})
}
await linkResult.destroy()
return res.status(200).json({ message: 'La lien a bien été supprimé!' })
} catch (error) {
console.log(error)
return errorHandling(next, serverError)
}
}

View File

@ -1,37 +1,40 @@
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: {
], exclude: ['isValidated']
attributes: { },
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) => { })
console.log(error); .catch(error => {
return errorHandling(next, serverError); console.log(error)
}); return errorHandling(next, serverError)
} })
}

View File

@ -1,68 +1,83 @@
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 {
const tasks = await Tasks.findAll({ const tasks = await Tasks.findAll({
where: { where: {
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 });
return res.status(201).json(taskResult);
} catch (error) {
console.log(error);
return errorHandling(next, serverError);
} }
const taskResult = await Tasks.create({ task, userId: req.userId })
return res.status(201).json(taskResult)
} catch (error) {
console.log(error)
return errorHandling(next, serverError)
}
} }
exports.putTask = async (req, res, next) => { 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 } }); })
if (!taskResult) {
return errorHandling(next, { message: `La "tâche à faire" n'existe pas.`, statusCode: 404 });
}
taskResult.isCompleted = isCompleted;
const taskSaved = await taskResult.save();
return res.status(200).json(taskSaved);
} catch (error) {
console.log(error);
return errorHandling(next, serverError);
} }
const taskResult = await Tasks.findOne({
where: { id, userId: req.userId }
})
if (!taskResult) {
return errorHandling(next, {
message: 'La "tâche à faire" n\'existe pas.',
statusCode: 404
})
}
taskResult.isCompleted = isCompleted
const taskSaved = await taskResult.save()
return res.status(200).json(taskSaved)
} catch (error) {
console.log(error)
return errorHandling(next, serverError)
}
} }
exports.deleteTask = async (req, res, next) => { 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({
if (!taskResult) { where: { id, userId: req.userId }
return errorHandling(next, { message: `La "tâche à faire" n'existe pas.`, statusCode: 404 }); })
} if (!taskResult) {
await taskResult.destroy(); return errorHandling(next, {
return res.status(200).json({ message: `La "tâche à faire" a bien été supprimée!` }); message: 'La "tâche à faire" n\'existe pas.',
} catch (error) { statusCode: 404
console.log(error); })
return errorHandling(next, serverError);
} }
} await taskResult.destroy()
return res
.status(200)
.json({ message: 'La "tâche à faire" a bien été supprimée!' })
} catch (error) {
console.log(error)
return errorHandling(next, serverError)
}
}

View File

@ -1,292 +1,454 @@
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 {
const transporter = require('../assets/config/transporter'); JWT_SECRET,
const { emailUserTemplate } = require('../assets/config/emails'); FRONT_END_HOST,
const Users = require('../models/users'); EMAIL_INFO,
const Favorites = require('../models/favorites'); HOST,
const Functions = require('../models/functions'); TOKEN_LIFE
const Categories = require('../models/categories'); } = require('../assets/config/config')
const Comments = require('../models/comments'); const transporter = require('../assets/config/transporter')
const Quotes = require('../models/quotes'); const { emailUserTemplate } = require('../assets/config/emails')
const deleteFilesNameStartWith = require('../assets/utils/deleteFilesNameStartWith'); const Users = require('../models/users')
const getPagesHelper = require('../assets/utils/getPagesHelper'); const Favorites = require('../models/favorites')
const Functions = require('../models/functions')
const Categories = require('../models/categories')
const Comments = require('../models/comments')
const Quotes = require('../models/quotes')
const deleteFilesNameStartWith = require('../assets/utils/deleteFilesNameStartWith')
const getPagesHelper = require('../assets/utils/getPagesHelper')
async function handleEditUser(res, { name, email, biography, isPublicEmail }, userId, logoName) { async function handleEditUser (
const user = await Users.findOne({ where: { id: userId } }); res,
user.name = name; { name, email, biography, isPublicEmail },
if (user.email !== email) { userId,
const tempToken = uuid.v4(); logoName
user.email = email; ) {
user.isConfirmed = false; const user = await Users.findOne({ where: { id: userId } })
user.tempToken = tempToken; user.name = name
await transporter.sendMail({ if (user.email !== email) {
from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`, const tempToken = uuid.v4()
to: email, user.email = email
subject: "FunctionProject - Confirmer l'email", user.isConfirmed = false
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.") user.tempToken = tempToken
}); await transporter.sendMail({
} from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`,
if (biography != undefined) { to: email,
user.biography = biography; subject: "FunctionProject - Confirmer l'email",
} html: emailUserTemplate(
user.isPublicEmail = isPublicEmail; "Veuillez confirmer l'email",
if (logoName != undefined && `/images/users/${logoName}` !== user.logo) { 'Oui, je confirme.',
user.logo = `/images/users/${logoName}`; `${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.'
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 }); })
}
if (biography != null) {
user.biography = biography
}
user.isPublicEmail = isPublicEmail
if (logoName != null && `/images/users/${logoName}` !== user.logo) {
user.logo = `/images/users/${logoName}`
}
await user.save()
return res
.status(200)
.json({
id: user.id,
name: user.name,
email: user.email,
biography: user.biography,
logo: user.logo,
isPublicEmail: user.isPublicEmail,
isAdmin: user.isAdmin,
createdAt: user.createdAt
})
} }
exports.getUsers = async (req, res, next) => { exports.getUsers = async (req, res, next) => {
let { search } = req.query; let { search } = req.query
try { search = search.toLowerCase(); } catch {}; try {
const options = { search = search.toLowerCase()
where: { } catch {}
isConfirmed: true, const options = {
// Recherche where: {
...(search != undefined) && { isConfirmed: true,
name: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', `%${search}%`) // Recherche
} ...(search != null && {
}, name: Sequelize.where(
attributes: { Sequelize.fn('LOWER', Sequelize.col('name')),
exclude: ["updatedAt", "isAdmin", "isConfirmed", "password", "tempToken", "tempExpirationToken", "isPublicEmail", "email"] 'LIKE',
}, `%${search}%`
order: [['createdAt', 'DESC']] )
}; })
return await getPagesHelper({ req, res, next }, Users, options); },
attributes: {
exclude: [
'updatedAt',
'isAdmin',
'isConfirmed',
'password',
'tempToken',
'tempExpirationToken',
'isPublicEmail',
'email'
]
},
order: [['createdAt', 'DESC']]
}
return await getPagesHelper({ req, res, next }, Users, options)
} }
exports.putUser = async (req, res, next) => { 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 != null) {
if (
(!logo || logo.truncated) &&
(logo.mimetype !== 'image/png' ||
logo.mimetype !== 'image/jpg' ||
logo.mimetype !== 'image/jpeg' ||
logo.mimetype !== 'image/gif')
) {
return errorHandling(next, {
message:
'Le profil doit avoir une image valide (PNG, JPG, GIF) et moins de 5mo.',
statusCode: 400
})
} }
if (logo != undefined) { const splitedLogoName = logo.name.split('.')
if (!logo || logo.truncated && ( if (splitedLogoName.length !== 2) return errorHandling(next, serverError)
logo.mimetype !== 'image/png' || const logoName = name + req.userId + '.' + splitedLogoName[1]
logo.mimetype !== 'image/jpg' || // Supprime les anciens logo
logo.mimetype !== 'image/jpeg' || try {
logo.mimetype !== 'image/gif' deleteFilesNameStartWith(
)) { `${name + req.userId}`,
return errorHandling(next, { message:"Le profil doit avoir une image valide (PNG, JPG, GIF) et moins de 5mo.", statusCode: 400 }); path.join(__dirname, '..', 'assets', 'images', 'users'),
} async () => {
const splitedLogoName = logo.name.split('.'); logo.mv(
if (splitedLogoName.length !== 2) return errorHandling(next, serverError); path.join(__dirname, '..', 'assets', 'images', 'users', logoName),
const logoName = name + req.userId + '.' + splitedLogoName[1]; async error => {
// Supprime les anciens logo if (error) return errorHandling(next, serverError)
try { return await handleEditUser(
deleteFilesNameStartWith(`${name + req.userId}`, path.join(__dirname, '..', 'assets', 'images', 'users'), async () => { res,
logo.mv(path.join(__dirname, '..', 'assets', 'images', 'users', logoName), async (error) => { { name, email, biography, isPublicEmail },
if (error) return errorHandling(next, serverError); req.userId,
return await handleEditUser(res, { name, email, biography, isPublicEmail }, req.userId, logoName); logoName
}); )
}); }
} catch (error) { )
console.log(error);
return errorHandling(next, serverError);
}
} else {
try {
return await handleEditUser(res, { name, email, biography, isPublicEmail }, req.userId, null);
} catch (error) {
console.log(error);
return errorHandling(next, serverError);
} }
)
} catch (error) {
console.log(error)
return errorHandling(next, serverError)
} }
} else {
try {
return await handleEditUser(
res,
{ name, email, biography, isPublicEmail },
req.userId,
null
)
} catch (error) {
console.log(error)
return errorHandling(next, serverError)
}
}
} }
exports.register = async (req, res, next) => { 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,
try { statusCode: 400
const hashedPassword = await bcrypt.hash(password, 12); })
const tempToken = uuid.v4(); }
await Users.create({ email, name, password: hashedPassword, tempToken }); try {
await transporter.sendMail({ const hashedPassword = await bcrypt.hash(password, 12)
from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`, const tempToken = uuid.v4()
to: email, await Users.create({ email, name, password: hashedPassword, tempToken })
subject: "FunctionProject - Confirmer l'inscription", await transporter.sendMail({
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.") from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`,
}); to: email,
return res.status(201).json({ result: "Vous y êtes presque, veuillez vérifier vos emails pour confirmer l'inscription." }); subject: "FunctionProject - Confirmer l'inscription",
} catch (error) { html: emailUserTemplate(
console.log(error); "Veuillez confirmer l'inscription",
return errorHandling(next, serverError); "Oui, je m'inscris.",
} `${HOST}/users/confirm-email/${tempToken}`,
'Si vous avez reçu ce message par erreur, il suffit de le supprimer. Vous ne serez pas inscrit si vous ne cliquez pas sur le lien de confirmation ci-dessus.'
)
})
return res
.status(201)
.json({
result:
"Vous y êtes presque, veuillez vérifier vos emails pour confirmer l'inscription."
})
} catch (error) {
console.log(error)
return errorHandling(next, serverError)
}
} }
exports.login = async (req, res, next) => { 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 {
const user = await Users.findOne({ where: { email } })
if (!user) {
return errorHandling(next, {
message: "Le mot de passe ou l'adresse email n'est pas valide.",
statusCode: 400
})
} }
try { const isEqual = await bcrypt.compare(password, user.password)
const user = await Users.findOne({ where: { email } }); if (!isEqual) {
if (!user) { return errorHandling(next, {
return errorHandling(next, { message: "Le mot de passe ou l'adresse email n'est pas valide.", statusCode: 400 }); message: "Le mot de passe ou l'adresse email n'est pas valide.",
} statusCode: 400
const isEqual = await bcrypt.compare(password, user.password); })
if (!isEqual) {
return errorHandling(next, { message: "Le mot de passe ou l'adresse email n'est pas valide.", statusCode: 400 });
}
if (!user.isConfirmed) {
return errorHandling(next, { message: "Vous devez valider votre adresse email pour votre première connexion.", statusCode: 400 });
}
const token = jwt.sign({
email: user.email, userId: user.id
}, JWT_SECRET, { expiresIn: TOKEN_LIFE });
return res.status(200).json({ token, id: user.id, name: user.name, email: user.email, biography: user.biography, logo: user.logo, isPublicEmail: user.isPublicEmail, isAdmin: user.isAdmin, createdAt: user.createdAt, expiresIn: Math.round(ms(TOKEN_LIFE) / 1000) });
} catch (error) {
console.log(error);
return errorHandling(next, serverError);
} }
if (!user.isConfirmed) {
return errorHandling(next, {
message:
'Vous devez valider votre adresse email pour votre première connexion.',
statusCode: 400
})
}
const token = jwt.sign(
{
email: user.email,
userId: user.id
},
JWT_SECRET,
{ expiresIn: TOKEN_LIFE }
)
return res
.status(200)
.json({
token,
id: user.id,
name: user.name,
email: user.email,
biography: user.biography,
logo: user.logo,
isPublicEmail: user.isPublicEmail,
isAdmin: user.isAdmin,
createdAt: user.createdAt,
expiresIn: Math.round(ms(TOKEN_LIFE) / 1000)
})
} catch (error) {
console.log(error)
return errorHandling(next, serverError)
}
} }
exports.confirmEmail = async (req, res, next) => { 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({
if (!user) { where: { tempToken, isConfirmed: false }
return errorHandling(next, { message: "Le token n'est pas valide.", statusCode: 400 }); })
} if (!user) {
user.tempToken = null; return errorHandling(next, {
user.isConfirmed = true; message: "Le token n'est pas valide.",
await user.save(); statusCode: 400
return res.redirect(`${FRONT_END_HOST}/users/login?isConfirmed=true`); })
} catch (error) {
console.log(error);
return errorHandling(next, serverError);
} }
user.tempToken = null
user.isConfirmed = true
await user.save()
return res.redirect(`${FRONT_END_HOST}/users/login?isConfirmed=true`)
} catch (error) {
console.log(error)
return errorHandling(next, serverError)
}
} }
exports.resetPassword = async (req, res, next) => { 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,
try { statusCode: 400
const user = await Users.findOne({ where: { email, tempToken: null } }); })
if (!user) { }
return errorHandling(next, { message: "L'adresse email n'existe pas ou une demande est déjà en cours.", statusCode: 400 }); try {
} const user = await Users.findOne({ where: { email, tempToken: null } })
const tempToken = uuid.v4(); if (!user) {
user.tempExpirationToken = Date.now() + 3600000; // 1 heure return errorHandling(next, {
user.tempToken = tempToken; message:
await user.save(); "L'adresse email n'existe pas ou une demande est déjà en cours.",
await transporter.sendMail({ statusCode: 400
from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`, })
to: email,
subject: "FunctionProject - Réinitialisation du mot de passe",
html: emailUserTemplate("Veuillez confirmer la réinitialisation du mot de passe", "Oui, je change mon mot de passe.", `${FRONT_END_HOST}/users/newPassword?token=${tempToken}`, "Si vous avez reçu ce message par erreur, il suffit de le supprimer. Votre mot de passe ne sera pas réinitialiser si vous ne cliquez pas sur le lien ci-dessus. Par ailleurs, pour la sécurité de votre compte, la réinitialisation du mot de passe est disponible pendant un délai de 1 heure, passez ce temps, la réinitialisation ne sera plus valide.")
});
return res.status(200).json({ result: "Demande de réinitialisation du mot de passe réussi, veuillez vérifier vos emails!" });
} catch (error) {
console.log(error);
return errorHandling(next, serverError);
} }
const tempToken = uuid.v4()
user.tempExpirationToken = Date.now() + 3600000 // 1 heure
user.tempToken = tempToken
await user.save()
await transporter.sendMail({
from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`,
to: email,
subject: 'FunctionProject - Réinitialisation du mot de passe',
html: emailUserTemplate(
'Veuillez confirmer la réinitialisation du mot de passe',
'Oui, je change mon mot de passe.',
`${FRONT_END_HOST}/users/newPassword?token=${tempToken}`,
'Si vous avez reçu ce message par erreur, il suffit de le supprimer. Votre mot de passe ne sera pas réinitialiser si vous ne cliquez pas sur le lien ci-dessus. Par ailleurs, pour la sécurité de votre compte, la réinitialisation du mot de passe est disponible pendant un délai de 1 heure, passez ce temps, la réinitialisation ne sera plus valide.'
)
})
return res
.status(200)
.json({
result:
'Demande de réinitialisation du mot de passe réussi, veuillez vérifier vos emails!'
})
} catch (error) {
console.log(error)
return errorHandling(next, serverError)
}
} }
exports.newPassword = async (req, res, next) => { 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,
try { statusCode: 400
const user = await Users.findOne({ where: { tempToken } }); })
if (!user && parseInt(tempExpirationToken) < Date.now()) { }
return errorHandling(next, { message: "Le token n'est pas valide.", statusCode: 400 }); try {
} const user = await Users.findOne({ where: { tempToken } })
const hashedPassword = await bcrypt.hash(password, 12); if (!user && parseInt(user.tempExpirationToken) < Date.now()) {
user.password = hashedPassword; return errorHandling(next, {
user.tempToken = null; message: "Le token n'est pas valide.",
user.tempExpirationToken = null; statusCode: 400
await user.save(); })
return res.status(200).json({ result: "Le mot de passe a bien été modifié!" });
} catch (error) {
console.log(error);
return errorHandling(next, serverError);
} }
const hashedPassword = await bcrypt.hash(password, 12)
user.password = hashedPassword
user.tempToken = null
user.tempExpirationToken = null
await user.save()
return res
.status(200)
.json({ result: 'Le mot de passe a bien été modifié!' })
} catch (error) {
console.log(error)
return errorHandling(next, serverError)
}
} }
exports.getUserInfo = async (req, res, next) => { 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',
if (!user) { 'isConfirmed',
return errorHandling(next, { message: "L'utilisateur n'existe pas.", statusCode: 404 }); 'password',
} 'tempToken',
const favorites = await Favorites.findAll({ 'tempExpirationToken'
where: { userId: user.id }, ]
include: [ }
{ model: Functions, attributes: { exclude: ["updatedAt", "utilizationForm", "article", "isOnline"] }, include: { model: Categories, attributes: ["name", "color"] } } })
], if (!user) {
order: [['createdAt', 'DESC']], return errorHandling(next, {
limit: 5 message: "L'utilisateur n'existe pas.",
}); statusCode: 404
const favoritesArray = favorites.map((favorite) => favorite.function); })
const comments = await Comments.findAll({
where: { userId: user.id },
include: [
{ model: Functions, attributes: { exclude: ["updatedAt", "utilizationForm", "article", "isOnline"] } }
],
order: [['createdAt', 'DESC']],
limit: 5
});
const commentsArray = comments.map((commentObject) => {
return {
id: commentObject.id,
message: commentObject.message,
createdAt: commentObject.createdAt,
function: commentObject.function.dataValues
};
});
const quotesArray = await Quotes.findAll({
where: { userId: user.id },
attributes: {
exclude: ["updatedAt", "createdAt", "isValidated", "userId", "id"]
},
order: [['createdAt', 'DESC']],
limit: 5,
});
const userObject = {
// Si Public Email
... (user.isPublicEmail) && { email: user.email },
isPublicEmail: user.isPublicEmail,
name: user.name,
biography: user.biography,
logo: user.logo,
createdAt: user.createdAt,
favoritesArray,
commentsArray,
quotesArray
};
return res.status(200).json(userObject);
} catch (error) {
console.log(error);
return errorHandling(next, serverError);
} }
} const favorites = await Favorites.findAll({
where: { userId: user.id },
include: [
{
model: Functions,
attributes: {
exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline']
},
include: { model: Categories, attributes: ['name', 'color'] }
}
],
order: [['createdAt', 'DESC']],
limit: 5
})
const favoritesArray = favorites.map(favorite => favorite.function)
const comments = await Comments.findAll({
where: { userId: user.id },
include: [
{
model: Functions,
attributes: {
exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline']
}
}
],
order: [['createdAt', 'DESC']],
limit: 5
})
const commentsArray = comments.map(commentObject => {
return {
id: commentObject.id,
message: commentObject.message,
createdAt: commentObject.createdAt,
function: commentObject.function.dataValues
}
})
const quotesArray = await Quotes.findAll({
where: { userId: user.id, isValidated: 1 },
attributes: {
exclude: ['updatedAt', 'createdAt', 'isValidated', 'userId', 'id']
},
order: [['createdAt', 'DESC']],
limit: 5
})
const userObject = {
// Si Public Email
...(user.isPublicEmail && { email: user.email }),
isPublicEmail: user.isPublicEmail,
name: user.name,
biography: user.biography,
logo: user.logo,
createdAt: user.createdAt,
favoritesArray,
commentsArray,
quotesArray
}
return res.status(200).json(userObject)
} catch (error) {
console.log(error)
return errorHandling(next, serverError)
}
}

View File

@ -1,23 +1,32 @@
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é.",
Users.findOne({ where: { id: req.userId } }) statusCode: 403
.then((user) => { })
if (!user) { }
return errorHandling(next, { message: "Le mot de passe ou l'adresse email n'est pas valide.", statusCode: 403 }); Users.findOne({ where: { id: req.userId } })
} .then(user => {
if (!user.isAdmin) { if (!user) {
return errorHandling(next, { message: "Vous n'êtes pas administrateur.", statusCode: 403 }); return errorHandling(next, {
} message: "Le mot de passe ou l'adresse email n'est pas valide.",
next(); statusCode: 403
}) })
.catch((error) => { }
console.log(error); if (!user.isAdmin) {
return errorHandling(next, serverError); return errorHandling(next, {
}); message: "Vous n'êtes pas administrateur.",
} statusCode: 403
})
}
next()
})
.catch(error => {
console.log(error)
return errorHandling(next, serverError)
})
}

View File

@ -1,24 +1,33 @@
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,39 +1,39 @@
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,
allowNull: false allowNull: false
}, },
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,
allowNull: false allowNull: false
}, },
article: { article: {
type: Sequelize.TEXT, type: Sequelize.TEXT,
allowNull: true allowNull: true
}, },
utilizationForm: { utilizationForm: {
type: Sequelize.TEXT, type: Sequelize.TEXT,
allowNull: true allowNull: true
}, },
isOnline: { isOnline: {
type: Sequelize.BOOLEAN, type: Sequelize.BOOLEAN,
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,45 +1,45 @@
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,
defaultValue: false defaultValue: false
}, },
isPublicEmail: { isPublicEmail: {
type: Sequelize.BOOLEAN, type: Sequelize.BOOLEAN,
defaultValue: false defaultValue: false
}, },
isAdmin: { isAdmin: {
type: Sequelize.BOOLEAN, type: Sequelize.BOOLEAN,
defaultValue: false defaultValue: false
}, },
tempToken: { tempToken: {
type: Sequelize.TEXT, type: Sequelize.TEXT,
allowNull: true allowNull: true
}, },
tempExpirationToken: { tempExpirationToken: {
type: Sequelize.DATE, type: Sequelize.DATE,
allowNull: true allowNull: true
} }
}); })

2895
api/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,12 @@
{ {
"name": "api", "name": "api",
"version": "2.0.0", "version": "2.1.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",
@ -28,11 +25,13 @@
"sequelize": "^5.21.5", "sequelize": "^5.21.5",
"smart-request-balancer": "^2.1.1", "smart-request-balancer": "^2.1.1",
"uuid": "^7.0.2", "uuid": "^7.0.2",
"validator": "^13.0.0" "validator": "^13.0.0",
"dotenv": "^8.2.0",
"morgan": "^1.9.1"
}, },
"devDependencies": { "devDependencies": {
"dotenv": "^8.2.0", "nodemon": "^2.0.4",
"morgan": "^1.9.1", "snazzy": "^8.0.0",
"nodemon": "^2.0.2" "standard": "^14.3.4"
} }
} }

View File

@ -1,205 +1,223 @@
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')
// Récupère les fonctions // Récupère les fonctions
.get(adminController.getFunctions) .get(adminController.getFunctions)
// Permet de créé une fonction // Permet de créé une fonction
.post(fileUpload({ .post(
useTempFiles: true, fileUpload({
safeFileNames: true, useTempFiles: true,
preserveExtension: Number, safeFileNames: true,
limits: { fileSize: 5 * 1024 * 1024 }, // 5mb, preserveExtension: Number,
parseNested: true limits: { fileSize: 5 * 1024 * 1024 }, // 5mb,
parseNested: true
}), }),
[ [
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; )
})), }
body('categorieId') return true
.not() }),
.isEmpty() body('categorieId')
.withMessage("La fonction doit avoir une catégorie.") .not()
.custom(async (categorieId) => { .isEmpty()
try { .withMessage('La fonction doit avoir une catégorie.')
const categorieFound = await Categories.findOne({ where: { id: parseInt(categorieId) } }); .custom(async categorieId => {
if (!categorieFound) { try {
return Promise.reject("La catégorie n'existe pas!"); const categorieFound = await Categories.findOne({
} where: { id: parseInt(categorieId) }
} catch (error) {
console.log(error);
}
return true;
}),
body('type')
.custom((type) => {
if (!(type === 'article' || type === 'form' || type === 'page')) {
return Promise.reject('Le type de la fonction peut être : article, form ou page.');
}
return true;
}) })
], adminController.postFunction); if (!categorieFound) {
return Promise.reject(new Error("La catégorie n'existe pas!"))
}
} catch (error) {
console.log(error)
}
return true
}),
body('type').custom(type => {
if (!(type === 'article' || type === 'form' || type === 'page')) {
return Promise.reject(
new Error(
'Le type de la fonction peut être : article, form ou page.'
)
)
}
return true
})
],
adminController.postFunction
)
AdminRouter.route('/functions/:slug') 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')
// Modifie information basique d'une fonction // Modifie information basique d'une fonction
.put(fileUpload({ .put(
useTempFiles: true, fileUpload({
safeFileNames: true, useTempFiles: true,
preserveExtension: Number, safeFileNames: true,
limits: { fileSize: 5 * 1024 * 1024 }, // 5mb, preserveExtension: Number,
parseNested: true limits: { fileSize: 5 * 1024 * 1024 }, // 5mb,
parseNested: true
}), }),
[ [
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; )
})), }
body('categorieId') return true
.not() }),
.isEmpty() body('categorieId')
.withMessage("La fonction doit avoir une catégorie.") .not()
.custom(async (categorieId) => { .isEmpty()
try { .withMessage('La fonction doit avoir une catégorie.')
const categorieFound = await Categories.findOne({ where: { id: parseInt(categorieId) } }); .custom(async categorieId => {
if (!categorieFound) { try {
return Promise.reject("La catégorie n'existe pas!"); const categorieFound = await Categories.findOne({
} where: { id: parseInt(categorieId) }
} catch (error) {
console.log(error);
}
return true;
}),
body('type')
.custom((type) => {
if (!(type === 'article' || type === 'form' || type === 'page')) {
return Promise.reject('Le type de la fonction peut être : article, form ou page.');
}
return true;
}) })
], adminController.putFunction) if (!categorieFound) {
return Promise.reject(new Error("La catégorie n'existe pas!"))
}
} catch (error) {
console.log(error)
}
return true
}),
body('type').custom(type => {
if (!(type === 'article' || type === 'form' || type === 'page')) {
return Promise.reject(
new Error(
'Le type de la fonction peut être : article, form ou page.'
)
)
}
return true
})
],
adminController.putFunction
)
// Supprime une fonction avec son id // 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')
// Modifier une catégorie avec son id // Modifier une catégorie avec son 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,23 +1,23 @@
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')
// Modifier un commentaire // Modifier un commentaire
.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')
// Récupère les commentaires // Récupère les commentaires
.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,18 +1,18 @@
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')
// Récupère si une fonction est en favoris (d'un utilisateur) // Récupère si une fonction est en favoris (d'un utilisateur)
.get(isAuth, favoritesController.getFavoriteByFunctionId) .get(isAuth, favoritesController.getFavoriteByFunctionId)
// Permet à un utilisateur d'ajouter une fonction aux favoris // Permet à un utilisateur d'ajouter une fonction aux favoris
.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,19 +1,19 @@
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')
// Récupère les informations de la fonction par son slug // Récupère les informations de la fonction par son 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

@ -0,0 +1,23 @@
const { Router } = require('express')
const linksShortenerController = require('../controllers/links_shortener')
const isAuth = require('../middlewares/isAuth')
const LinksShortenerRouter = Router()
LinksShortenerRouter.route('/')
// Récupère les liens d'un utilisateur
.get(isAuth, linksShortenerController.getLinks)
// Ajouter un lien à raccourcir d'un utilisateur
.post(isAuth, linksShortenerController.postLink)
LinksShortenerRouter.route('/:id')
// Permet de modifier le lien raccourci d'un utilisateur
.put(isAuth, linksShortenerController.putLink)
// Supprimer un lien d'un utilisateur
.delete(isAuth, linksShortenerController.deleteLink)
module.exports = LinksShortenerRouter

View File

@ -1,15 +1,15 @@
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('/')
// Récupère les citations // Récupère les citations
.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,23 +1,23 @@
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('/')
// Récupère les tâches à faire d'un user // Récupère les tâches à faire d'un user
.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')
// Permet de mettre une tâche à faire en isCompleted ou !isCompleted // Permet de mettre une tâche à faire en isCompleted ou !isCompleted
.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,145 +1,167 @@
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('/')
// Récupère les utilisateurs // Récupère les utilisateurs
.get(usersController.getUsers) .get(usersController.getUsers)
// Permet de modifier son profil // Permet de modifier son profil
.put(isAuth, .put(
fileUpload({ isAuth,
useTempFiles: true, fileUpload({
safeFileNames: true, useTempFiles: true,
preserveExtension: Number, safeFileNames: true,
limits: { fileSize: 5 * 1024 * 1024 }, // 5mb, preserveExtension: Number,
parseNested: true limits: { fileSize: 5 * 1024 * 1024 }, // 5mb,
parseNested: true
}), }),
[ [
body('email') body('email')
.isEmail()
.withMessage("Veuillez rentré une adresse mail valide.")
.custom((async (email) => {
try {
const user = await Users.findOne({ where: { email } });
if (user && user.email !== email) {
return Promise.reject("L'adresse email existe déjà...");
}
} catch (error) {
return console.log(error);
}
return true;
}))
.normalizeEmail(),
body('name')
.trim()
.not()
.isEmpty()
.withMessage("Vous devez avoir un nom (ou pseudo).")
.isAlphanumeric()
.withMessage("Votre nom ne peut contenir que des lettres ou/et des nombres.")
.isLength({ max: 30 })
.withMessage("Votre nom est trop long")
.custom(async (name) => {
try {
const user = await Users.findOne({ where: { name } });
if (user && user.name !== name) {
return Promise.reject("Le nom existe déjà...");
}
} catch (error) {
console.log(error);
}
return true;
}),
body('isPublicEmail')
.isBoolean()
.withMessage("L'adresse email peut être public ou privé, rien d'autre."),
body('biography')
.trim()
.escape()
], usersController.putUser);
// Permet de se connecter
UsersRouter.post('/login', [
body('email')
.not()
.isEmpty()
.withMessage(requiredFields.message),
body('password')
.not()
.isEmpty()
.withMessage(requiredFields.message)
], usersController.login);
// Récupère les informations public d'un profil
UsersRouter.get('/:name', usersController.getUserInfo);
// Permet de s'inscrire
UsersRouter.post('/register', [
body('email')
.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 && 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) {
return console.log(error);
} }
return true; } catch (error) {
})) return console.log(error)
}
return true
})
.normalizeEmail(), .normalizeEmail(),
body('password') body('name')
.isLength({ min: 4 })
.withMessage("Votre mot de passe est trop court!"),
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 && user.name !== name) {
return Promise.reject("Le nom existe déjà..."); return Promise.reject(new Error('Le nom existe déjà...'))
}
} catch (error) {
console.log(error);
} }
return true; } catch (error) {
}) console.log(error)
], usersController.register); }
return true
}),
body('isPublicEmail')
.isBoolean()
.withMessage(
"L'adresse email peut être public ou privé, rien d'autre."
),
body('biography')
.trim()
.escape()
],
usersController.putUser
)
// Permet de se connecter
UsersRouter.post(
'/login',
[
body('email')
.not()
.isEmpty()
.withMessage(requiredFields.message),
body('password')
.not()
.isEmpty()
.withMessage(requiredFields.message)
],
usersController.login
)
// Récupère les informations public d'un profil
UsersRouter.get('/:name', usersController.getUserInfo)
// Permet de s'inscrire
UsersRouter.post(
'/register',
[
body('email')
.isEmail()
.withMessage('Veuillez rentré une adresse mail valide.')
.custom(async email => {
try {
const user = await Users.findOne({ where: { email } })
if (user) {
return Promise.reject(new Error("L'adresse email existe déjà..."))
}
} catch (error) {
return console.log(error)
}
return true
}),
body('password')
.isLength({ min: 4 })
.withMessage('Votre mot de passe est trop court!'),
body('name')
.trim()
.not()
.isEmpty()
.withMessage('Vous devez avoir un nom (ou pseudo).')
.isAlphanumeric()
.withMessage(
'Votre nom ne peut contenir que des lettres ou/et des nombres.'
)
.isLength({ max: 30 })
.withMessage('Votre nom est trop long')
.custom(async name => {
try {
const user = await Users.findOne({ where: { name } })
if (user) {
return Promise.reject(new Error('Le nom existe déjà...'))
}
} catch (error) {
console.log(error)
}
return true
})
],
usersController.register
)
// Confirme l'inscription // 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')
// Demande une réinitialisation du mot de passe // Demande une réinitialisation du mot de passe
.post([ .post(
body('email') [
.isEmail() body('email')
.withMessage("Veuillez rentré une adresse mail valide.") .isEmail()
], usersController.resetPassword) .withMessage('Veuillez rentré une adresse mail valide.')
],
usersController.resetPassword
)
// Nouveau mot de passe // Nouveau mot de passe
.put([ .put(
body('password') [
.isLength({ min: 4 }) body('password')
.withMessage("Votre mot de passe est trop court!") .isLength({ min: 4 })
], usersController.newPassword); .withMessage('Votre mot de passe est trop court!')
],
usersController.newPassword
)
module.exports = UsersRouter; module.exports = UsersRouter

4
s.divlo.fr/.env.example Normal file
View File

@ -0,0 +1,4 @@
DB_HOST = ""
DB_NAME = ""
DB_USER = ""
DB_PASS = ""

2
s.divlo.fr/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules
.env

3
s.divlo.fr/README.md Normal file
View File

@ -0,0 +1,3 @@
# s.divlo.fr
Site web qui permet de rediriger les utilisateurs vers leurs liens raccourcis sur [function.divlo.fr](https://function.divlo.fr/).

72
s.divlo.fr/app.js Normal file
View File

@ -0,0 +1,72 @@
/* Modules */
require('dotenv').config()
const path = require('path')
const express = require('express')
const helmet = require('helmet')
const morgan = require('morgan')
const { redirectToHTTPS } = require('express-http-to-https')
const mysql = require('mysql')
/* Files Imports & Variables */
const app = express()
const database = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
port: process.env.DB_PORT
})
/* Middlewares */
app.use(helmet())
app.use(morgan('dev'))
app.use(express.json())
app.use(redirectToHTTPS([/localhost:(\d{4})/]))
/* EJS Template Engines */
app.set('view engine', 'ejs')
app.set('views', path.join(__dirname, 'views'))
/* Routes */
app.use(express.static(path.join(__dirname, 'public')))
app.get('/', (_req, res) => {
return res.render('index')
})
app.get('/:shortcut', (req, res, next) => {
const { shortcut } = req.params
if (shortcut == null) {
return res.redirect('/errors/404')
}
database.query(
'SELECT * FROM short_links WHERE shortcut = ?',
[shortcut],
(error, [result]) => {
if (error != null) {
return next(error)
}
if (result == null) {
return res.redirect('/error/404')
}
return res.redirect(result.url)
}
)
})
/* Errors */
app.use((_req, res) => {
return res.status(404).render('errors')
})
app.use((error, _req, res) => {
console.log(error)
return res.status(500).render('errors')
})
/* Server */
const PORT = process.env.PORT || 8000
app.listen(PORT, () => {
console.log('\x1b[36m%s\x1b[0m', `Started on port ${PORT}.`)
})

3497
s.divlo.fr/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

24
s.divlo.fr/package.json Normal file
View File

@ -0,0 +1,24 @@
{
"name": "short.divlo.fr",
"version": "1.0.0",
"description": "Link shortener for FunctionProject",
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js",
"format": "standard \"./**/*.js\" --fix | snazzy || exit 0"
},
"dependencies": {
"dotenv": "^8.2.0",
"ejs": "^3.1.3",
"express": "^4.17.1",
"express-http-to-https": "^1.1.4",
"helmet": "^4.0.0",
"morgan": "^1.10.0",
"mysql": "^2.18.1"
},
"devDependencies": {
"nodemon": "^2.0.4",
"snazzy": "^8.0.0",
"standard": "^14.3.4"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Short-links</title>
<link rel="icon" type="image/png" href="/images/error404.png" />
</head>
<body>
<style type="text/css">
* {
font-family: Arial;
color: #fff;
background-color: black;
}
</style>
<p>Adresse url non connue</p>
</body>
</html>

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Short-links</title>
<link rel="icon" type="image/png" href="/images/linkShortener.png" />
</head>
<body>
<style type="text/css">
* {
font-family: Arial;
color: #fff;
background-color: black;
}
</style>
</body>
</html>

1
website/.env.example Normal file
View File

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

3
website/.gitignore vendored
View File

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

View File

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

View File

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

View File

@ -1,10 +1,10 @@
.footer { .footer {
border-top: var(--border-header-footer); border-top: var(--border-header-footer);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.footer-text { .footer-text {
line-height: 2.5; line-height: 2.5;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,65 +1,67 @@
.FunctionCard { .FunctionCard {
display: flex; display: flex;
align-items: center; align-items: center;
position: relative; position: relative;
flex-direction: column; flex-direction: column;
word-wrap: break-word; word-wrap: break-word;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black; border: 1px solid black;
border-radius: 1rem; border-radius: 1rem;
margin: 0 0 50px 0; margin: 0 0 50px 0;
cursor: pointer; cursor: pointer;
transition: all .3s; transition: all 0.3s;
color: var(--text-color);
text-decoration: none !important;
} }
.FunctionCard__container { .FunctionCard__container {
height: 100%; height: 100%;
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.FunctionCard:hover { .FunctionCard:hover {
transform: translateY(-7px); transform: translateY(-7px);
} }
/* col-md */ /* col-md */
@media (min-width: 768px) { @media (min-width: 768px) {
.FunctionCard { .FunctionCard {
margin: 0 30px 50px 30px; margin: 0 30px 50px 30px;
} }
} }
/* col-xl */ /* col-xl */
@media (min-width: 1200px) { @media (min-width: 1200px) {
.FunctionCard { .FunctionCard {
margin: 0 20px 50px 20px; margin: 0 20px 50px 20px;
} }
} }
.FunctionCard__top { .FunctionCard__top {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
flex-grow: 1; flex-grow: 1;
} }
.FunctionCard__image { .FunctionCard__image {
width: 150px; width: 150px;
} }
.FunctionCard__title { .FunctionCard__title {
font-size: 1.4em; font-size: 1.4em;
margin: 0; margin: 0;
color: var(--important); color: var(--important);
font-weight: 300; font-weight: 300;
} }
.FunctionCard__description { .FunctionCard__description {
margin: 20px 0 10px 0; margin: 20px 0 10px 0;
} }
.FunctionCard__info { .FunctionCard__info {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 100%; width: 100%;
} }
.FunctionCard__category { .FunctionCard__category {
border-radius: 0.5em; border-radius: 0.5em;
padding: 0.5em; padding: 0.5em;
margin-right: 20px; margin-right: 20px;
font-size: 16.4px; font-size: 16.4px;
} }

View File

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

View File

@ -0,0 +1,80 @@
import Link from 'next/link'
import { useState, forwardRef, memo } from 'react'
import date from 'date-and-time'
import Loader from '../Loader'
import { API_URL } from '../../utils/api'
import './FunctionCard.css'
const FunctionCard = memo(
forwardRef((props, ref) => {
const [isLoading, setIsLoading] = useState(true)
const handleLoad = () => {
setIsLoading(false)
}
const handleError = event => {
event.target.src = API_URL + '/images/functions/default.png'
}
const isFormOrArticle = props.type === 'form' || props.type === 'article'
return (
<Link
{...(props.isAdmin
? {
href: '/admin/[slug]',
as: `/admin/${props.slug}`
}
: {
href: isFormOrArticle
? '/functions/[slug]'
: `/functions/${props.slug}`,
as: `/functions/${props.slug}`
})}
>
{/* FunctionCard a une hauteur pendant chargement */}
<a
ref={ref}
style={
isLoading ? { height: '360px', justifyContent: 'center' } : null
}
className='FunctionCard col-sm-24 col-md-10 col-xl-7'
>
{isLoading && <Loader width='125px' height='125px' />}
<div
className={`FunctionCard__container ${isLoading ? 'd-none' : ''}`}
>
<div className='FunctionCard__top'>
<img
onLoad={handleLoad}
onError={handleError}
className='FunctionCard__image'
alt={props.title}
src={API_URL + props.image}
/>
<h2 className='FunctionCard__title'>{props.title}</h2>
<p className='FunctionCard__description text-center'>
{props.description}
</p>
</div>
<div className='FunctionCard__info'>
<p
className='FunctionCard__category'
style={{ backgroundColor: props.categorie.color }}
>
{props.categorie.name}
</p>
<p className='FunctionCard__publication-date'>
{date.format(new Date(props.createdAt), 'DD/MM/YYYY', true)}
</p>
</div>
</div>
</a>
</Link>
)
})
)
export default FunctionCard

View File

@ -1,33 +1,33 @@
.CommentCard { .CommentCard {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
word-wrap: break-word; word-wrap: break-word;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black; border: 1px solid black;
border-radius: .7em; border-radius: 0.7em;
margin: 15px 0 15px 0; margin: 15px 0 15px 0;
} }
.CommentCard__container { .CommentCard__container {
height: 100%; height: 100%;
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 20px; padding: 20px;
} }
.CommentCard__user-logo { .CommentCard__user-logo {
border-radius: 50%; border-radius: 50%;
object-fit: cover; object-fit: cover;
width: 50px; width: 50px;
height: 50px; height: 50px;
cursor: pointer; cursor: pointer;
} }
.CommentCard__message-info { .CommentCard__message-info {
display: flex; display: flex;
align-items: center; align-items: center;
margin-left: 10px; margin-left: 10px;
font-size: 16px; font-size: 16px;
} }
.CommentCard__message { .CommentCard__message {
line-height: 1.8; line-height: 1.8;
margin: 15px 0 0 0; margin: 15px 0 0 0;
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
.FunctionComments__row { .FunctionComments__row {
margin-bottom: 20px; margin-bottom: 20px;
} }
.FunctionComments__textarea { .FunctionComments__textarea {
height: auto; height: auto;
resize: vertical; resize: vertical;
} }

View File

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

View File

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

View File

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

View File

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

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