🎨 Configure standardJS

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

View File

@ -5,10 +5,9 @@
</p>
<p align="center">
<a href="https://gitmoji.carloscuesta.me/"><img src="https://camo.githubusercontent.com/2a4924a23bd9ef18afe793f4999b1b9ec474e48f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6769746d6f6a692d253230f09f989c253230f09f988d2d4646444436372e7376673f7374796c653d666c61742d737175617265" alt="Gitmoji"/></a>
<a href="https://standardjs.com"><img alt="JavaScript Style Guide" src="https://img.shields.io/badge/code_style-standard-brightgreen.svg"/></a>
<a href="./LICENSE"><img src="https://img.shields.io/badge/licence-MIT-blue.svg" alt="Licence MIT"/></a>
<img src="https://img.shields.io/github/repo-size/Divlo/FunctionProject" alt="Repo Size">
<a href="https://github.com/Divlo/FunctionProject/commits/master"><img src="https://img.shields.io/github/commit-activity/m/Divlo/FunctionProject" alt="Commit Activity"></a>
<a href="https://github.com/Divlo/FunctionProject/graphs/contributors"><img src="https://img.shields.io/github/contributors/Divlo/FunctionProject" alt="Contributors"></a>
<img src="https://img.shields.io/github/stars/Divlo/FunctionProject?style=social" alt="Stars">
<br/> <br/>
<a href="https://function.divlo.fr/"><img src="https://raw.githubusercontent.com/Divlo/FunctionProject/master/.github/FunctionProject.png" alt="FunctionProject" /></a>
@ -76,6 +75,8 @@ npm run dev # API lancé sur http://localhost:8080
Enjoy! 😃
[![JavaScript Style Guide](https://cdn.rawgit.com/standard/standard/master/badge.svg)](https://github.com/standard/standard)
## 📄 Licence
Ce projet est sous licence MIT - voir le fichier [LICENSE](./LICENSE) pour plus de détails.

1
api/.gitignore vendored
View File

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

View File

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

View File

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

View File

@ -26,7 +26,7 @@ exports.emailQuoteTemplate = (isValid, quote, frontendLink) => `
<tr>
<td align="left" valign="top" style="line-height:150%;font-family:Helvetica;font-size:14px;color:rgb(222, 222, 222);padding:30px;box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25);border: 1px solid black;border-radius: 1rem;">
<h2 style="font-size:22px;line-height:28px;margin:0 0 12px 0;">
La citation que vous avez proposée a été ${(isValid) ? "validée" : "supprimée"}.
La citation que vous avez proposée a été ${(isValid) ? 'validée' : 'supprimée'}.
</h2>
<p style="margin: 0 0 12px 0;">
<a style="color: #ffd800;" href="${frontendLink}/functions/randomQuote">Lien vers la fonction randomQuote de FunctionProject.</a>
@ -35,7 +35,7 @@ exports.emailQuoteTemplate = (isValid, quote, frontendLink) => `
<p style="margin: 0 0 12px 0;">
Si votre citation a été supprimée et vous pensez que c'est une erreur, contactez-moi à cette adresse email : <a style="color: #ffd800;" href="mailto:contact@divlo.fr">contact@divlo.fr</a>.
</p>
` : ""}
` : ''}
<div>
<p style="padding:0 0 10px 0">
La citation en question : <br/>
@ -57,7 +57,7 @@ exports.emailQuoteTemplate = (isValid, quote, frontendLink) => `
</tbody>
</table>
</center>
`;
`
exports.emailUserTemplate = (subtitle, buttonText, url, footerText) => `
<center>
@ -107,4 +107,4 @@ exports.emailUserTemplate = (subtitle, buttonText, url, footerText) => `
</tbody>
</table>
</center>
`;
`

View File

@ -1,18 +1,18 @@
const errors = {
generalError: {
message: "Vous n'avez pas rentré de valeur valide.",
statusCode: 400
},
generalError: {
message: "Vous n'avez pas rentré de valeur valide.",
statusCode: 400
},
serverError: {
message: "Le serveur n'a pas pu traiter votre requête.",
statusCode: 500
},
serverError: {
message: "Le serveur n'a pas pu traiter votre requête.",
statusCode: 500
},
requiredFields: {
message: "Vous devez remplir tous les champs...",
statusCode: 400
}
};
requiredFields: {
message: 'Vous devez remplir tous les champs...',
statusCode: 400
}
}
module.exports = errors;
module.exports = errors

View File

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

View File

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

View File

@ -1,46 +1,46 @@
const errorHandling = require('../../utils/errorHandling');
const { requiredFields } = require('../../config/errors');
const formatNumberResult = require('../secondary/formatNumberResult');
const errorHandling = require('../../utils/errorHandling')
const { requiredFields } = require('../../config/errors')
const formatNumberResult = require('../secondary/formatNumberResult')
/**
/**
* @description Vérifie si un nombre fait partie des nombres d'Armstrong.
* @param {Number} number - Le nombre à tester
* @returns {Object} Un objet contenant l'explication en html et le booléen si oui ou non c'est un nombre d'armstrong
* @examples armstrongNumber(153) 153 est un nombre d'Armstrong, car 1<sup>3</sup> + 5<sup>3</sup> + 3<sup>3</sup> = 153.
*/
function armstrongNumber(number) {
let numberString = number.toString();
let numberStringLength = numberString.length;
*/
function armstrongNumber (number) {
const numberString = number.toString()
const numberStringLength = numberString.length
let result = 0;
let resultString = "";
for (let index = 0; index < numberStringLength; index++) {
result = result + parseInt(numberString[index]) ** numberStringLength;
resultString = resultString + " + " + numberString[index] + "<sup>" + numberStringLength + "</sup>";
}
let result = 0
let resultString = ''
for (let index = 0; index < numberStringLength; index++) {
result = result + parseInt(numberString[index]) ** numberStringLength
resultString = resultString + ' + ' + numberString[index] + '<sup>' + numberStringLength + '</sup>'
}
const formattedNumber = formatNumberResult(number);
const isArmstrongNumber = (result === number);
return {
isArmstrongNumber,
resultHTML: `<p>${formattedNumber} ${isArmstrongNumber ? "est" : "n'est pas"} un nombre d'Armstrong, car ${resultString.slice(2)} = ${formatNumberResult(result)}.</p>`
}
const formattedNumber = formatNumberResult(number)
const isArmstrongNumber = (result === number)
return {
isArmstrongNumber,
resultHTML: `<p>${formattedNumber} ${isArmstrongNumber ? 'est' : "n'est pas"} un nombre d'Armstrong, car ${resultString.slice(2)} = ${formatNumberResult(result)}.</p>`
}
}
/* OUTPUTS */
module.exports = armstrongNumberOutput = ({ res, next }, argsObject) => {
let { number } = argsObject;
// S'il n'y a pas les champs obligatoire
if (!(number)) {
return errorHandling(next, requiredFields);
}
module.exports = ({ res, next }, argsObject) => {
let { number } = argsObject
// Si ce n'est pas un nombre
number = parseInt(number);
if (isNaN(number) || number <= 0) {
return errorHandling(next, { message: "Veuillez rentré un nombre valide.", statusCode: 400 });
}
// S'il n'y a pas les champs obligatoire
if (!(number)) {
return errorHandling(next, requiredFields)
}
return res.status(200).json(armstrongNumber(number));
}
// Si ce n'est pas un nombre
number = parseInt(number)
if (isNaN(number) || number <= 0) {
return errorHandling(next, { message: 'Veuillez rentré un nombre valide.', statusCode: 400 })
}
return res.status(200).json(armstrongNumber(number))
}

View File

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

View File

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

View File

@ -1,10 +1,10 @@
const errorHandling = require('../../utils/errorHandling');
const { requiredFields, generalError } = require('../../config/errors');
const formatNumberResult = require('../secondary/formatNumberResult');
const errorHandling = require('../../utils/errorHandling')
const { requiredFields, generalError } = require('../../config/errors')
const formatNumberResult = require('../secondary/formatNumberResult')
const correspondancesDistance = ["pm", null, null, "nm", null, null, "µm", null, null, "mm", "cm", "dm", "m", "dam", "hm", "km", null, null, "Mm", null, null, "Gm", null, null, "Tm"];
const correspondancesDistance = ['pm', null, null, 'nm', null, null, 'µm', null, null, 'mm', 'cm', 'dm', 'm', 'dam', 'hm', 'km', null, null, 'Mm', null, null, 'Gm', null, null, 'Tm']
/**
/**
* @description Convertis la longueur (distance) avec les unités allant de picomètre au Téramètre.
* @requires {@link correspondancesDistance}
* @param {Number} firstValue - Le nombre que vous voulez convertir
@ -13,39 +13,39 @@ const correspondancesDistance = ["pm", null, null, "nm", null, null, "µm", null
* @returns {Object|Boolean} false si arguments non valides et sinon un objet contenant la string et le nombre résultat
* @examples convertDistance(500, 'cm', 'm') { resultNumber: 5, resultString: "5 m" }
*/
function convertDistance(firstValue, unitFirstValue, unitFinalValue) {
const index1 = correspondancesDistance.indexOf(unitFirstValue);
const index2 = correspondancesDistance.indexOf(unitFinalValue);
if (index1 !== -1 && index2 !== -1) {
const difference = index1 - index2;
const result = firstValue * Math.pow(10, difference);
return {
result,
resultHTML: `<p>${formatNumberResult(firstValue)} ${unitFirstValue} = ${formatNumberResult(result)} ${unitFinalValue}</p>`
};
function convertDistance (firstValue, unitFirstValue, unitFinalValue) {
const index1 = correspondancesDistance.indexOf(unitFirstValue)
const index2 = correspondancesDistance.indexOf(unitFinalValue)
if (index1 !== -1 && index2 !== -1) {
const difference = index1 - index2
const result = firstValue * Math.pow(10, difference)
return {
result,
resultHTML: `<p>${formatNumberResult(firstValue)} ${unitFirstValue} = ${formatNumberResult(result)} ${unitFinalValue}</p>`
}
return false;
}
return false
}
/* OUTPUTS */
module.exports = convertDistanceOutput = ({ res, next }, argsObject) => {
let { number, numberUnit, finalUnit } = argsObject;
// S'il n'y a pas les champs obligatoire
if (!(number && numberUnit && finalUnit)) {
return errorHandling(next, requiredFields);
}
module.exports = ({ res, next }, argsObject) => {
let { number, numberUnit, finalUnit } = argsObject
// Si ce n'est pas un nombre
number = parseFloat(number);
if (isNaN(number)) {
return errorHandling(next, { message: "Veuillez rentré un nombre valide.", statusCode: 400 });
}
// S'il n'y a pas les champs obligatoire
if (!(number && numberUnit && finalUnit)) {
return errorHandling(next, requiredFields)
}
const result = convertDistance(number, numberUnit, finalUnit);
if (!result) {
return errorHandling(next, generalError);
}
// Si ce n'est pas un nombre
number = parseFloat(number)
if (isNaN(number)) {
return errorHandling(next, { message: 'Veuillez rentré un nombre valide.', statusCode: 400 })
}
return res.status(200).json(result);
}
const result = convertDistance(number, numberUnit, finalUnit)
if (!result) {
return errorHandling(next, generalError)
}
return res.status(200).json(result)
}

View File

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

View File

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

View File

@ -1,50 +1,48 @@
const errorHandling = require('../../utils/errorHandling');
const { requiredFields, generalError } = require('../../config/errors');
const formatNumberResult = require('../secondary/formatNumberResult');
const errorHandling = require('../../utils/errorHandling')
const { requiredFields, generalError } = require('../../config/errors')
const formatNumberResult = require('../secondary/formatNumberResult')
/**
/**
* @description Convertis des °C en °F et l'inverse aussi.
* @param {Number} degree - Nombre de degrès
* @param {String} unit - Unité du nombre (°C ou °F) après conversion
* @returns {Object} false si arguments non valides et sinon un objet contenant la string et le nombre résultat
* @examples convertTemperature(23, '°F') { result: 73.4, resultHTML: "73.4 °F" }
*/
function convertTemperature(degree, unit) {
let temperatureValue = 0;
if (unit === "°C") {
temperatureValue = (degree - 32) * 5/9;
}
else if (unit === "°F") {
temperatureValue = ((degree * 9/5) + 32);
}
else {
return false;
}
return {
result: temperatureValue,
resultHTML: `<p>${formatNumberResult(degree)} ${(unit === '°C') ? "°F" : "°C"} = ${formatNumberResult(temperatureValue)} ${unit}</p>`
};
}
function convertTemperature (degree, unit) {
let temperatureValue = 0
if (unit === '°C') {
temperatureValue = (degree - 32) * 5 / 9
} else if (unit === '°F') {
temperatureValue = ((degree * 9 / 5) + 32)
} else {
return false
}
return {
result: temperatureValue,
resultHTML: `<p>${formatNumberResult(degree)} ${(unit === '°C') ? '°F' : '°C'} = ${formatNumberResult(temperatureValue)} ${unit}</p>`
}
}
/* OUTPUTS */
module.exports = convertTemperatureOutput = ({ res, next }, argsObject) => {
let { degree, unitToConvert } = argsObject;
// S'il n'y a pas les champs obligatoire
if (!(degree && unitToConvert)) {
return errorHandling(next, requiredFields);
}
// Si ce n'est pas un nombre
degree = parseFloat(degree);
if (isNaN(degree)) {
return errorHandling(next, { message: "Veuillez rentré un nombre valide.", statusCode: 400 });
}
module.exports = ({ res, next }, argsObject) => {
let { degree, unitToConvert } = argsObject
const result = convertTemperature(degree, unitToConvert);
if (!result) {
return errorHandling(next, generalError);
}
// S'il n'y a pas les champs obligatoire
if (!(degree && unitToConvert)) {
return errorHandling(next, requiredFields)
}
return res.status(200).json(result);
}
// Si ce n'est pas un nombre
degree = parseFloat(degree)
if (isNaN(degree)) {
return errorHandling(next, { message: 'Veuillez rentré un nombre valide.', statusCode: 400 })
}
const result = convertTemperature(degree, unitToConvert)
if (!result) {
return errorHandling(next, generalError)
}
return res.status(200).json(result)
}

View File

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

View File

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

View File

@ -1,52 +1,52 @@
const errorHandling = require('../../utils/errorHandling');
const { requiredFields } = require('../../config/errors');
const formatNumberResult = require('../secondary/formatNumberResult');
const errorHandling = require('../../utils/errorHandling')
const { requiredFields } = require('../../config/errors')
const formatNumberResult = require('../secondary/formatNumberResult')
/**
/**
* @description Retourne un tableau contenant toutes les possibilités d'anagramme d'un mot.
* @param {String} string - La chaîne de caractère à permuter
* @returns {Array}
* @examples heapAlgorithm('abc') ["abc", "acb", "bac", "bca", "cab", "cba"]
*/
function heapAlgorithm(string) {
let results = [];
function heapAlgorithm (string) {
const results = []
if (string.length === 1) {
results.push(string);
return results;
}
if (string.length === 1) {
results.push(string)
return results
}
for (let indexString = 0; indexString < string.length; indexString++) {
const firstChar = string[indexString];
const charsLeft = string.substring(0, indexString) + string.substring(indexString + 1);
const innerPermutations = heapAlgorithm(charsLeft);
for (let indexPermutation = 0; indexPermutation < innerPermutations.length; indexPermutation++) {
results.push(firstChar + innerPermutations[indexPermutation]);
}
for (let indexString = 0; indexString < string.length; indexString++) {
const firstChar = string[indexString]
const charsLeft = string.substring(0, indexString) + string.substring(indexString + 1)
const innerPermutations = heapAlgorithm(charsLeft)
for (let indexPermutation = 0; indexPermutation < innerPermutations.length; indexPermutation++) {
results.push(firstChar + innerPermutations[indexPermutation])
}
return results;
}
}
return results
}
/* OUTPUTS */
module.exports = heapAlgorithmOutput = ({ res, next }, argsObject) => {
let { string } = argsObject;
// S'il n'y a pas les champs obligatoire
if (!(string)) {
return errorHandling(next, requiredFields);
}
module.exports = ({ res, next }, argsObject) => {
const { string } = argsObject
// Si la chaîne de caractère dépasse LIMIT_CHARACTERS caractères
const LIMIT_CHARACTERS = 7;
if (string.length > LIMIT_CHARACTERS) {
return errorHandling(next, { message: `Par souci de performance, vous ne pouvez pas exécuter cette fonction avec un mot dépassant ${LIMIT_CHARACTERS} caractères.`, statusCode: 400 });
}
// S'il n'y a pas les champs obligatoire
if (!(string)) {
return errorHandling(next, requiredFields)
}
const result = heapAlgorithm(string);
let resultHTML = `<p>Il y a ${formatNumberResult(result.length)} possibilités d'anagramme pour le mot "${string}" qui contient ${string.length} caractères, la liste : <br/><br/>`;
result.forEach((string) => {
resultHTML += string + "<br/>";
});
resultHTML += "</p>";
return res.status(200).json({ result, resultHTML });
}
// Si la chaîne de caractère dépasse LIMIT_CHARACTERS caractères
const LIMIT_CHARACTERS = 7
if (string.length > LIMIT_CHARACTERS) {
return errorHandling(next, { message: `Par souci de performance, vous ne pouvez pas exécuter cette fonction avec un mot dépassant ${LIMIT_CHARACTERS} caractères.`, statusCode: 400 })
}
const result = heapAlgorithm(string)
let resultHTML = `<p>Il y a ${formatNumberResult(result.length)} possibilités d'anagramme pour le mot "${string}" qui contient ${string.length} caractères, la liste : <br/><br/>`
result.forEach((string) => {
resultHTML += string + '<br/>'
})
resultHTML += '</p>'
return res.status(200).json({ result, resultHTML })
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,46 +1,46 @@
const axios = require('axios');
const Queue = require('smart-request-balancer');
const errorHandling = require('../../utils/errorHandling');
const { requiredFields } = require('../../config/errors');
const { WEATHER_API_KEY } = require('../../config/config');
const dateTimeUTC = require('../secondary/dateTimeManagement');
const capitalize = require('../secondary/capitalize');
const axios = require('axios')
const Queue = require('smart-request-balancer')
const errorHandling = require('../../utils/errorHandling')
const { requiredFields } = require('../../config/errors')
const { WEATHER_API_KEY } = require('../../config/config')
const dateTimeUTC = require('../secondary/dateTimeManagement')
const capitalize = require('../secondary/capitalize')
const queue = new Queue({
/*
/*
rate: number of requests
per
limit: number of seconds
*/
rules: {
weatherRequest: {
rate: 50,
limit: 60,
priority: 1
},
rules: {
weatherRequest: {
rate: 50,
limit: 60,
priority: 1
}
});
}
})
/* OUTPUTS */
module.exports = weatherRequestOutput = ({ res, next }, argsObject) => {
let { cityName } = argsObject;
module.exports = ({ res, next }, argsObject) => {
let { cityName } = argsObject
// S'il n'y a pas les champs obligatoire
if (!(cityName)) {
return errorHandling(next, requiredFields);
}
// S'il n'y a pas les champs obligatoire
if (!(cityName)) {
return errorHandling(next, requiredFields)
}
cityName = cityName.split(' ').join('+');
cityName = cityName.split(' ').join('+')
// Récupère les données météo grâce à l'API : openweathermap.org. (→ avec limite de 50 requêtes par minute)
queue.request(() => {
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${cityName}&lang=fr&units=metric&appid=${WEATHER_API_KEY}`)
.then((response) =>{
const json = response.data;
const showDateTimeValue = dateTimeUTC((json.timezone / 60 / 60).toString()).showDateTimeValue;
const resultHTML = `<p>🌎 Position : <a href="https://www.google.com/maps/search/?api=1&query=${json.coord.lat},${json.coord.lon}" rel="noopener noreferrer" target="_blank">${json.name}, ${json.sys.country}</a><br/>⏰ Date et heure : ${showDateTimeValue} <br/>☁️ Météo : ${capitalize(json.weather[0].description)}<br/>🌡️ Température : ${json.main.temp} °C<br/> 💧 Humidité : ${json.main.humidity}% <br/> <img src="https://openweathermap.org/img/wn/${json.weather[0].icon}@2x.png"/></p>`;
return res.status(200).json({ result: json, resultHTML });
})
.catch(() => errorHandling(next, { message: "La ville n'existe pas (dans l'API de openweathermap.org).", statusCode: 404 }));
}, 'everyone', 'weatherRequest');
}
// Récupère les données météo grâce à l'API : openweathermap.org. (→ avec limite de 50 requêtes par minute)
queue.request(() => {
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${cityName}&lang=fr&units=metric&appid=${WEATHER_API_KEY}`)
.then((response) => {
const json = response.data
const showDateTimeValue = dateTimeUTC((json.timezone / 60 / 60).toString()).showDateTimeValue
const resultHTML = `<p>🌎 Position : <a href="https://www.google.com/maps/search/?api=1&query=${json.coord.lat},${json.coord.lon}" rel="noopener noreferrer" target="_blank">${json.name}, ${json.sys.country}</a><br/>⏰ Date et heure : ${showDateTimeValue} <br/>☁️ Météo : ${capitalize(json.weather[0].description)}<br/>🌡️ Température : ${json.main.temp} °C<br/> 💧 Humidité : ${json.main.humidity}% <br/> <img src="https://openweathermap.org/img/wn/${json.weather[0].icon}@2x.png"/></p>`
return res.status(200).json({ result: json, resultHTML })
})
.catch(() => errorHandling(next, { message: "La ville n'existe pas (dans l'API de openweathermap.org).", statusCode: 404 }))
}, 'everyone', 'weatherRequest')
}

View File

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

View File

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

View File

@ -1,14 +1,14 @@
/**
/**
* @description Formate un nombre avec des espaces.
* @param {Number} number
* @param {String} separator Le séparateur utilisé pour la virgule (exemple: "." ou ",")
* @returns {String} - Le nombre formaté
* @returns {String} - Le nombre formaté
* @examples formatNumberResult(76120) '76 120'
*/
function formatNumberResult(number, separator = ".") {
let parts = number.toString().split(separator);
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, " ");
return parts.join(separator);
*/
function formatNumberResult (number, separator = '.') {
const parts = number.toString().split(separator)
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ' ')
return parts.join(separator)
}
module.exports = formatNumberResult;
module.exports = formatNumberResult

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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('../assets/utils/database');
const Sequelize = require('sequelize')
const sequelize = require('../assets/utils/database')
module.exports = sequelize.define('function', {
title: {
type: Sequelize.STRING,
allowNull: false,
},
slug: {
type: Sequelize.STRING,
allowNull: false,
},
description: {
type: Sequelize.STRING,
allowNull: false
},
image: {
type: Sequelize.STRING,
allowNull: false,
defaultValue: "/images/functions/default.png"
},
type: {
type: Sequelize.STRING,
allowNull: false
},
article: {
type: Sequelize.TEXT,
allowNull: true
},
utilizationForm: {
type: Sequelize.TEXT,
allowNull: true
},
isOnline: {
type: Sequelize.BOOLEAN,
allowNull: false,
defaultValue: 0
}
});
title: {
type: Sequelize.STRING,
allowNull: false
},
slug: {
type: Sequelize.STRING,
allowNull: false
},
description: {
type: Sequelize.STRING,
allowNull: false
},
image: {
type: Sequelize.STRING,
allowNull: false,
defaultValue: '/images/functions/default.png'
},
type: {
type: Sequelize.STRING,
allowNull: false
},
article: {
type: Sequelize.TEXT,
allowNull: true
},
utilizationForm: {
type: Sequelize.TEXT,
allowNull: true
},
isOnline: {
type: Sequelize.BOOLEAN,
allowNull: false,
defaultValue: 0
}
})

View File

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

View File

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

View File

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

View File

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

2446
api/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,14 +2,11 @@
"name": "api",
"version": "2.0.0",
"description": "Backend REST API for FunctionProject",
"main": "app.js",
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js"
"dev": "nodemon app.js",
"format": "standard \"./**/*.js\" --fix | snazzy || exit 0"
},
"keywords": [],
"author": "Divlo",
"license": "MIT",
"dependencies": {
"axios": "^0.19.2",
"bcryptjs": "^2.4.3",
@ -33,6 +30,8 @@
"devDependencies": {
"dotenv": "^8.2.0",
"morgan": "^1.9.1",
"nodemon": "^2.0.2"
"nodemon": "^2.0.4",
"snazzy": "^8.0.0",
"standard": "^14.3.4"
}
}

View File

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

View File

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

View File

@ -1,23 +1,23 @@
const { Router } = require('express');
const commentsController = require('../controllers/comments');
const isAuth = require('../middlewares/isAuth');
const { Router } = require('express')
const commentsController = require('../controllers/comments')
const isAuth = require('../middlewares/isAuth')
const CommentsRouter = Router();
const CommentsRouter = Router()
CommentsRouter.route('/:commentId')
// Modifier un commentaire
.put(isAuth, commentsController.putCommentsById)
// Modifier un commentaire
.put(isAuth, commentsController.putCommentsById)
// Supprime un commentaire
.delete(isAuth, commentsController.deleteCommentById);
// Supprime un commentaire
.delete(isAuth, commentsController.deleteCommentById)
CommentsRouter.route('/:functionId')
// Récupère les commentaires
.get(commentsController.getCommentsByFunctionId)
// Récupère les commentaires
.get(commentsController.getCommentsByFunctionId)
// Permet à un utilisateur de poster un commentaire sur une fonction
.post(isAuth, commentsController.postCommentsByFunctionId);
// Permet à un utilisateur de poster un commentaire sur une fonction
.post(isAuth, commentsController.postCommentsByFunctionId)
module.exports = CommentsRouter;
module.exports = CommentsRouter

View File

@ -1,18 +1,18 @@
const { Router } = require('express');
const favoritesController = require('../controllers/favorites');
const isAuth = require('../middlewares/isAuth');
const { Router } = require('express')
const favoritesController = require('../controllers/favorites')
const isAuth = require('../middlewares/isAuth')
const FavoritesRouter = Router();
const FavoritesRouter = Router()
FavoritesRouter.route('/:functionId')
// Récupère si une fonction est en favoris (d'un utilisateur)
.get(isAuth, favoritesController.getFavoriteByFunctionId)
// Récupère si une fonction est en favoris (d'un utilisateur)
.get(isAuth, favoritesController.getFavoriteByFunctionId)
// Permet à un utilisateur d'ajouter une fonction aux favoris
.post(isAuth, favoritesController.postFavoriteByFunctionId)
// Permet à un utilisateur d'ajouter une fonction aux favoris
.post(isAuth, favoritesController.postFavoriteByFunctionId)
// Supprime une fonction des favoris d'un utilisateur
.delete(isAuth, favoritesController.deleteFavoriteByFunctionId);
// Supprime une fonction des favoris d'un utilisateur
.delete(isAuth, favoritesController.deleteFavoriteByFunctionId)
module.exports = FavoritesRouter;
module.exports = FavoritesRouter

View File

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

View File

@ -1,15 +1,15 @@
const { Router } = require('express');
const quotesController = require('../controllers/quotes');
const isAuth = require('../middlewares/isAuth');
const { Router } = require('express')
const quotesController = require('../controllers/quotes')
const isAuth = require('../middlewares/isAuth')
const QuotesRouter = Router();
const QuotesRouter = Router()
QuotesRouter.route('/')
// Récupère les citations
.get(quotesController.getQuotes)
// Récupère les citations
.get(quotesController.getQuotes)
// Proposer une citation
.post(isAuth, quotesController.postQuote);
// Proposer une citation
.post(isAuth, quotesController.postQuote)
module.exports = QuotesRouter;
module.exports = QuotesRouter

View File

@ -1,23 +1,23 @@
const { Router } = require('express');
const tasksController = require('../controllers/tasks');
const isAuth = require('../middlewares/isAuth');
const { Router } = require('express')
const tasksController = require('../controllers/tasks')
const isAuth = require('../middlewares/isAuth')
const TasksRouter = Router();
const TasksRouter = Router()
TasksRouter.route('/')
// Récupère les tâches à faire d'un user
.get(isAuth, tasksController.getTasks)
// Récupère les tâches à faire d'un user
.get(isAuth, tasksController.getTasks)
// Poster une nouvelle tâche à faire
.post(isAuth, tasksController.postTask);
// Poster une nouvelle tâche à faire
.post(isAuth, tasksController.postTask)
TasksRouter.route('/:id')
// Permet de mettre une tâche à faire en isCompleted ou !isCompleted
.put(isAuth, tasksController.putTask)
// Permet de mettre une tâche à faire en isCompleted ou !isCompleted
.put(isAuth, tasksController.putTask)
// Supprimer une tâche à faire
.delete(isAuth, tasksController.deleteTask);
// Supprimer une tâche à faire
.delete(isAuth, tasksController.deleteTask)
module.exports = TasksRouter;
module.exports = TasksRouter

View File

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

1
website/.env.example Normal file
View File

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

2
website/.gitignore vendored
View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@ -1,108 +1,109 @@
import Link from 'next/link';
import { useEffect, useState, forwardRef, useContext, Fragment } from 'react';
import date from 'date-and-time';
import htmlParser from 'html-react-parser';
import { UserContext } from '../../../contexts/UserContext';
import { API_URL } from '../../../utils/config/config';
import ReactMarkdown from 'react-markdown';
import CodeBlock from "../../CodeBlock";
import api from '../../../utils/api';
import './CommentCard.css';
import Link from 'next/link'
import { useEffect, useState, forwardRef, useContext } from 'react'
import date from 'date-and-time'
import htmlParser from 'html-react-parser'
import { UserContext } from '../../../contexts/UserContext'
import { API_URL } from '../../../utils/config/config'
import ReactMarkdown from 'react-markdown'
import CodeBlock from '../../CodeBlock'
import api from '../../../utils/api'
import './CommentCard.css'
const CommentCard = forwardRef((props, ref) => {
const { isAuth, user } = useContext(UserContext)
const [isEditing, setEditing] = useState(false)
const [editInput, setEditInput] = useState('')
const [message, setMessage] = useState('')
const { isAuth, user } = useContext(UserContext);
const [isEditing, setEditing] = useState(false);
const [editInput, setEditInput] = useState("");
const [message, setMessage] = useState("");
useEffect(() => {
setEditInput(props.message)
}, [])
useEffect(() => {
setEditInput(props.message);
}, []);
const deleteCommentById = async () => {
props.manageComment.setLoadingComments(true);
if (isAuth && user.token != undefined) {
try {
await api.delete(`/comments/${props.id}`, { headers: { 'Authorization': user.token } });
const newCommentsData = { ...props.manageComment.commentsData };
const commentIndex = newCommentsData.rows.findIndex((value) => value.id === props.id);
newCommentsData.rows.splice(commentIndex, 1);
props.manageComment.setCommentsData({ hasMore: props.manageComment.commentsData.hasMore, rows: newCommentsData.rows });
} catch {}
}
props.manageComment.setLoadingComments(false);
const deleteCommentById = async () => {
props.manageComment.setLoadingComments(true)
if (isAuth && user.token != null) {
try {
await api.delete(`/comments/${props.id}`, { headers: { Authorization: user.token } })
const newCommentsData = { ...props.manageComment.commentsData }
const commentIndex = newCommentsData.rows.findIndex((value) => value.id === props.id)
newCommentsData.rows.splice(commentIndex, 1)
props.manageComment.setCommentsData({ hasMore: props.manageComment.commentsData.hasMore, rows: newCommentsData.rows })
} catch {}
}
props.manageComment.setLoadingComments(false)
}
const handleChange = (event) => {
setEditInput(event.target.value);
}
const handleChange = (event) => {
setEditInput(event.target.value)
}
const handleSubmit = (event) => {
event.preventDefault();
api.put(`/comments/${props.id}`, { message: editInput }, { headers: { 'Authorization': user.token } })
.then((_response) => {
setEditing(false);
})
.catch((error) => {
setMessage(`<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>`);
});
}
const handleSubmit = (event) => {
event.preventDefault()
api.put(`/comments/${props.id}`, { message: editInput }, { headers: { Authorization: user.token } })
.then((_response) => {
setEditing(false)
})
.catch((error) => {
setMessage(`<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>`)
})
}
const editComment = () => {
setEditing(true);
setMessage("");
}
const editComment = () => {
setEditing(true)
setMessage('')
}
return (
<div ref={ref} className="CommentCard col-24">
<div className="CommentCard__container">
<div className="row">
<Link href={"/users/[name]"} as={`/users/${props.user.name}`}>
<img className="CommentCard__user-logo" src={API_URL + props.user.logo} alt={props.user.name} />
</Link>
<span className="CommentCard__message-info">
<Link href={"/users/[name]"} as={`/users/${props.user.name}`}>
<a>{props.user.name}</a>
</Link>
&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>
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;
export default CommentCard

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,15 +1,15 @@
const Loader = ({ width, height, speed }) => (
<svg width={width} height={height} viewBox="0 0 100 100">
<g transform="translate(50 50) rotate(0) scale(1 1) translate(-50 -50)">
<image style={{transformOrigin: "50% 50%", animation: `${speed} linear 0s infinite normal forwards running Loader__spin`}} x="0" y="0" width="100" height="100" href="/images/FunctionProject_icon.png"></image>
</g>
</svg>
);
<svg width={width} height={height} viewBox='0 0 100 100'>
<g transform='translate(50 50) rotate(0) scale(1 1) translate(-50 -50)'>
<image style={{ transformOrigin: '50% 50%', animation: `${speed} linear 0s infinite normal forwards running Loader__spin` }} x='0' y='0' width='100' height='100' href='/images/FunctionProject_icon.png' />
</g>
</svg>
)
Loader.defaultProps = {
width: "100px",
height: "100px",
speed: ".9s"
width: '100px',
height: '100px',
speed: '.9s'
}
export default Loader;
export default Loader

View File

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

View File

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

View File

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

View File

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

View File

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

9005
website/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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