🎨 standardJS all files

This commit is contained in:
divlo 2020-08-03 14:14:45 +02:00
parent dc962c9120
commit 4be7a46a10
85 changed files with 3595 additions and 2383 deletions

View File

@ -34,7 +34,9 @@ app.use('/quotes', require('./routes/quotes'))
app.use('/tasks', require('./routes/tasks')) app.use('/tasks', require('./routes/tasks'))
/* Errors Handling */ /* Errors Handling */
app.use((_req, _res, next) => errorHandling(next, { statusCode: 404, message: "La route n'existe pas!" })) // 404 app.use((_req, _res, next) =>
errorHandling(next, { statusCode: 404, message: "La route n'existe pas!" })
) // 404
app.use((error, _req, res, _next) => { app.use((error, _req, res, _next) => {
console.log(error) console.log(error)
const { statusCode, message } = error const { statusCode, message } = error
@ -76,8 +78,11 @@ Tasks.belongsTo(Users, { constraints: false })
/* Server */ /* Server */
// sequelize.sync({ force: true }) // sequelize.sync({ force: true })
sequelize.sync() sequelize
.sync()
.then(() => { .then(() => {
app.listen(PORT, () => console.log('\x1b[36m%s\x1b[0m', `Started on port ${PORT}.`)) app.listen(PORT, () =>
console.log('\x1b[36m%s\x1b[0m', `Started on port ${PORT}.`)
)
}) })
.catch((error) => console.log(error)) .catch(error => console.log(error))

View File

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

View File

@ -16,14 +16,24 @@ function armstrongNumber (number) {
let resultString = '' let resultString = ''
for (let index = 0; index < numberStringLength; index++) { for (let index = 0; index < numberStringLength; index++) {
result = result + parseInt(numberString[index]) ** numberStringLength result = result + parseInt(numberString[index]) ** numberStringLength
resultString = resultString + ' + ' + numberString[index] + '<sup>' + numberStringLength + '</sup>' resultString =
resultString +
' + ' +
numberString[index] +
'<sup>' +
numberStringLength +
'</sup>'
} }
const formattedNumber = formatNumberResult(number) const formattedNumber = formatNumberResult(number)
const isArmstrongNumber = (result === number) const isArmstrongNumber = result === number
return { return {
isArmstrongNumber, isArmstrongNumber,
resultHTML: `<p>${formattedNumber} ${isArmstrongNumber ? 'est' : "n'est pas"} un nombre d'Armstrong, car ${resultString.slice(2)} = ${formatNumberResult(result)}.</p>` resultHTML: `<p>${formattedNumber} ${
isArmstrongNumber ? 'est' : "n'est pas"
} un nombre d'Armstrong, car ${resultString.slice(
2
)} = ${formatNumberResult(result)}.</p>`
} }
} }
@ -32,14 +42,17 @@ module.exports = ({ res, next }, argsObject) => {
let { number } = argsObject let { number } = argsObject
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(number)) { if (!number) {
return errorHandling(next, requiredFields) return errorHandling(next, requiredFields)
} }
// Si ce n'est pas un nombre // Si ce n'est pas un nombre
number = parseInt(number) number = parseInt(number)
if (isNaN(number) || number <= 0) { if (isNaN(number) || number <= 0) {
return errorHandling(next, { message: 'Veuillez rentré un nombre valide.', statusCode: 400 }) return errorHandling(next, {
message: 'Veuillez rentré un nombre valide.',
statusCode: 400
})
} }
return res.status(200).json(armstrongNumber(number)) return res.status(200).json(armstrongNumber(number))

View File

@ -2,7 +2,10 @@ const errorHandling = require('../../utils/errorHandling')
const moment = require('moment') const moment = require('moment')
const { requiredFields } = require('../../config/errors') const { requiredFields } = require('../../config/errors')
function calculateAge (currentDate, { birthDateDay, birthDateMonth, birthDateYear }) { function calculateAge (
currentDate,
{ birthDateDay, birthDateMonth, birthDateYear }
) {
const day = currentDate.getDate() const day = currentDate.getDate()
const month = currentDate.getMonth() const month = currentDate.getMonth()
const currentDateMoment = moment([currentDate.getFullYear(), month, day]) const currentDateMoment = moment([currentDate.getFullYear(), month, day])
@ -15,7 +18,7 @@ function calculateAge (currentDate, { birthDateDay, birthDateMonth, birthDateYea
birthDateMoment.add(ageMonths, 'months') birthDateMoment.add(ageMonths, 'months')
const ageDays = currentDateMoment.diff(birthDateMoment, 'days') const ageDays = currentDateMoment.diff(birthDateMoment, 'days')
const isBirthday = (birthDateDay === day && birthDateMonth === month) const isBirthday = birthDateDay === day && birthDateMonth === month
return { ageYears, ageMonths, ageDays, isBirthday } return { ageYears, ageMonths, ageDays, isBirthday }
} }
@ -24,20 +27,27 @@ module.exports = ({ res, next }, argsObject) => {
const { birthDate } = argsObject const { birthDate } = argsObject
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(birthDate)) { if (!birthDate) {
return errorHandling(next, requiredFields) return errorHandling(next, requiredFields)
} }
const birthDateDay = parseInt(birthDate.substring(0, 2)) const birthDateDay = parseInt(birthDate.substring(0, 2))
const birthDateMonth = parseInt((birthDate.substring(3, 5)) - 1) const birthDateMonth = parseInt(birthDate.substring(3, 5) - 1)
const birthDateYear = parseInt(birthDate.substring(6, 10)) const birthDateYear = parseInt(birthDate.substring(6, 10))
// Si ce n'est pas une date valide // Si ce n'est pas une date valide
const currentDate = new Date() const currentDate = new Date()
const birthDateObject = new Date(birthDateYear, birthDateMonth, birthDateDay) const birthDateObject = new Date(birthDateYear, birthDateMonth, birthDateDay)
const result = calculateAge(currentDate, { birthDateYear, birthDateMonth, birthDateDay }) const result = calculateAge(currentDate, {
if ((currentDate < birthDateObject) || isNaN(result.ageYears)) { birthDateYear,
return errorHandling(next, { message: 'Veuillez rentré une date valide...', statusCode: 400 }) birthDateMonth,
birthDateDay
})
if (currentDate < birthDateObject || isNaN(result.ageYears)) {
return errorHandling(next, {
message: 'Veuillez rentré une date valide...',
statusCode: 400
})
} }
let resultHTML let resultHTML

View File

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

View File

@ -2,7 +2,33 @@ const errorHandling = require('../../utils/errorHandling')
const { requiredFields, generalError } = require('../../config/errors') const { requiredFields, generalError } = require('../../config/errors')
const formatNumberResult = require('../secondary/formatNumberResult') const formatNumberResult = require('../secondary/formatNumberResult')
const correspondancesDistance = ['pm', null, null, 'nm', null, null, 'µm', null, null, 'mm', 'cm', 'dm', 'm', 'dam', 'hm', 'km', null, null, 'Mm', null, null, 'Gm', null, null, 'Tm'] const correspondancesDistance = [
'pm',
null,
null,
'nm',
null,
null,
'µm',
null,
null,
'mm',
'cm',
'dm',
'm',
'dam',
'hm',
'km',
null,
null,
'Mm',
null,
null,
'Gm',
null,
null,
'Tm'
]
/** /**
* @description Convertis la longueur (distance) avec les unités allant de picomètre au Téramètre. * @description Convertis la longueur (distance) avec les unités allant de picomètre au Téramètre.
@ -21,7 +47,11 @@ function convertDistance (firstValue, unitFirstValue, unitFinalValue) {
const result = firstValue * Math.pow(10, difference) const result = firstValue * Math.pow(10, difference)
return { return {
result, result,
resultHTML: `<p>${formatNumberResult(firstValue)} ${unitFirstValue} = ${formatNumberResult(result)} ${unitFinalValue}</p>` resultHTML: `<p>${formatNumberResult(
firstValue
)} ${unitFirstValue} = ${formatNumberResult(
result
)} ${unitFinalValue}</p>`
} }
} }
return false return false
@ -39,7 +69,10 @@ module.exports = ({ res, next }, argsObject) => {
// Si ce n'est pas un nombre // Si ce n'est pas un nombre
number = parseFloat(number) number = parseFloat(number)
if (isNaN(number)) { if (isNaN(number)) {
return errorHandling(next, { message: 'Veuillez rentré un nombre valide.', statusCode: 400 }) return errorHandling(next, {
message: 'Veuillez rentré un nombre valide.',
statusCode: 400
})
} }
const result = convertDistance(number, numberUnit, finalUnit) const result = convertDistance(number, numberUnit, finalUnit)

View File

@ -73,7 +73,9 @@ function binaryToHexadecimal (value) {
if (isNaN(value)) { if (isNaN(value)) {
return false return false
} else { } else {
return parseInt(value).toString(16).toUpperCase() return parseInt(value)
.toString(16)
.toUpperCase()
} }
} }
@ -140,10 +142,15 @@ function numberUnicodeToText (string) {
function textToBinary (s) { function textToBinary (s) {
try { try {
s = unescape(encodeURIComponent(s)) s = unescape(encodeURIComponent(s))
let chr; let i = 0; const l = s.length; let out = '' let chr
let i = 0
const l = s.length
let out = ''
for (; i < l; i++) { for (; i < l; i++) {
chr = s.charCodeAt(i).toString(2) chr = s.charCodeAt(i).toString(2)
while (chr.length % 8 !== 0) { chr = '0' + chr } while (chr.length % 8 !== 0) {
chr = '0' + chr
}
out += chr out += chr
} }
return out.replace(/(\d{8})/g, '$1 ').replace(/(^\s+|\s+$)/, '') return out.replace(/(\d{8})/g, '$1 ').replace(/(^\s+|\s+$)/, '')
@ -161,10 +168,13 @@ function textToBinary (s) {
function binaryToText (s) { function binaryToText (s) {
try { try {
s = s.replace(/\s/g, '') s = s.replace(/\s/g, '')
let i = 0; const l = s.length; let chr; let out = '' let i = 0
const l = s.length
let chr
let out = ''
for (; i < l; i += 8) { for (; i < l; i += 8) {
chr = parseInt(s.substr(i, 8), 2).toString(16) chr = parseInt(s.substr(i, 8), 2).toString(16)
out += '%' + ((chr.length % 2 === 0) ? chr : '0' + chr) out += '%' + (chr.length % 2 === 0 ? chr : '0' + chr)
} }
return decodeURIComponent(out) return decodeURIComponent(out)
} catch (error) { } catch (error) {
@ -181,10 +191,13 @@ function binaryToText (s) {
function textToHexadecimal (s) { function textToHexadecimal (s) {
try { try {
s = unescape(encodeURIComponent(s)) s = unescape(encodeURIComponent(s))
let chr; let i = 0; const l = s.length; let out = '' let chr
let i = 0
const l = s.length
let out = ''
for (; i < l; i++) { for (; i < l; i++) {
chr = s.charCodeAt(i).toString(16) chr = s.charCodeAt(i).toString(16)
out += (chr.length % 2 === 0) ? chr : '0' + chr out += chr.length % 2 === 0 ? chr : '0' + chr
out += ' ' out += ' '
} }
return out.toUpperCase() return out.toUpperCase()
@ -209,7 +222,20 @@ function hexadecimalToText (s) {
} }
/* OUTPUTS */ /* OUTPUTS */
const convertEncoding = { decimalToBinary, binaryToDecimal, decimalToHexadecimal, hexadecimalToDecimal, binaryToHexadecimal, hexadecimalToBinary, textToNumberUnicode, numberUnicodeToText, textToBinary, binaryToText, textToHexadecimal, hexadecimalToText } const convertEncoding = {
decimalToBinary,
binaryToDecimal,
decimalToHexadecimal,
hexadecimalToDecimal,
binaryToHexadecimal,
hexadecimalToBinary,
textToNumberUnicode,
numberUnicodeToText,
textToBinary,
binaryToText,
textToHexadecimal,
hexadecimalToText
}
function executeFunction (option, value) { function executeFunction (option, value) {
return convertEncoding[option](value) return convertEncoding[option](value)
} }
@ -225,7 +251,10 @@ module.exports = ({ res, next }, argsObject) => {
// Si la fonction n'existe pas // Si la fonction n'existe pas
// eslint-disable-next-line // eslint-disable-next-line
if (!convertEncoding.hasOwnProperty(functionName)) { if (!convertEncoding.hasOwnProperty(functionName)) {
return errorHandling(next, { message: "Cette conversion n'existe pas.", statusCode: 400 }) return errorHandling(next, {
message: "Cette conversion n'existe pas.",
statusCode: 400
})
} }
const result = executeFunction(functionName, value) const result = executeFunction(functionName, value)

View File

@ -51,7 +51,7 @@ function convertArabicToRoman (nombre) {
*/ */
function convertRomanToArabic (string) { function convertRomanToArabic (string) {
let result = 0 let result = 0
correspondancesRomainArabe.forEach((correspondance) => { correspondancesRomainArabe.forEach(correspondance => {
while (string.indexOf(correspondance[1]) === 0) { while (string.indexOf(correspondance[1]) === 0) {
// Ajout de la valeur décimale au résultat // Ajout de la valeur décimale au résultat
result += correspondance[0] result += correspondance[0]
@ -68,7 +68,7 @@ function convertRomanToArabic (string) {
/* OUTPUTS */ /* OUTPUTS */
const convertRomanToArabicOutput = ({ res, next }, number) => { const convertRomanToArabicOutput = ({ res, next }, number) => {
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(number)) { if (!number) {
return errorHandling(next, requiredFields) return errorHandling(next, requiredFields)
} }
@ -80,26 +80,44 @@ const convertRomanToArabicOutput = ({ res, next }, number) => {
return errorHandling(next, generalError) return errorHandling(next, generalError)
} }
return res.status(200).json({ result, resultHTML: `<p><span class="important">${number}</span> s'écrit <span class="important">${result}</span> en chiffres arabes.</p>` }) return res
.status(200)
.json({
result,
resultHTML: `<p><span class="important">${number}</span> s'écrit <span class="important">${result}</span> en chiffres arabes.</p>`
})
} }
const convertArabicToRomanOutput = ({ res, next }, number) => { const convertArabicToRomanOutput = ({ res, next }, number) => {
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(number)) { if (!number) {
return errorHandling(next, requiredFields) return errorHandling(next, requiredFields)
} }
// Si ce n'est pas un nombre // Si ce n'est pas un nombre
number = parseInt(number) number = parseInt(number)
if (isNaN(number)) { if (isNaN(number)) {
return errorHandling(next, { message: 'Veuillez rentré un nombre valide.', statusCode: 400 }) return errorHandling(next, {
message: 'Veuillez rentré un nombre valide.',
statusCode: 400
})
} }
const result = convertArabicToRoman(number) const result = convertArabicToRoman(number)
return res.status(200).json({ result, resultHTML: `<p><span class="important">${formatNumberResult(number)}</span> s'écrit <span class="important">${result}</span> en chiffres romains.</p>` }) return res
.status(200)
.json({
result,
resultHTML: `<p><span class="important">${formatNumberResult(
number
)}</span> s'écrit <span class="important">${result}</span> en chiffres romains.</p>`
})
} }
const convertRomanArabicObject = { convertRomanToArabicOutput, convertArabicToRomanOutput } const convertRomanArabicObject = {
convertRomanToArabicOutput,
convertArabicToRomanOutput
}
function executeFunction (option, value, { res, next }) { function executeFunction (option, value, { res, next }) {
return convertRomanArabicObject[option]({ res, next }, value) return convertRomanArabicObject[option]({ res, next }, value)
} }
@ -115,7 +133,10 @@ module.exports = ({ res, next }, argsObject) => {
// Si la fonction n'existe pas // Si la fonction n'existe pas
// eslint-disable-next-line // eslint-disable-next-line
if (!convertRomanArabicObject.hasOwnProperty(functionName)) { if (!convertRomanArabicObject.hasOwnProperty(functionName)) {
return errorHandling(next, { message: "Cette conversion n'existe pas.", statusCode: 400 }) return errorHandling(next, {
message: "Cette conversion n'existe pas.",
statusCode: 400
})
} }
executeFunction(functionName, value, { res, next }) executeFunction(functionName, value, { res, next })

View File

@ -12,15 +12,17 @@ const formatNumberResult = require('../secondary/formatNumberResult')
function convertTemperature (degree, unit) { function convertTemperature (degree, unit) {
let temperatureValue = 0 let temperatureValue = 0
if (unit === '°C') { if (unit === '°C') {
temperatureValue = (degree - 32) * 5 / 9 temperatureValue = ((degree - 32) * 5) / 9
} else if (unit === '°F') { } else if (unit === '°F') {
temperatureValue = ((degree * 9 / 5) + 32) temperatureValue = (degree * 9) / 5 + 32
} else { } else {
return false return false
} }
return { return {
result: temperatureValue, result: temperatureValue,
resultHTML: `<p>${formatNumberResult(degree)} ${(unit === '°C') ? '°F' : '°C'} = ${formatNumberResult(temperatureValue)} ${unit}</p>` resultHTML: `<p>${formatNumberResult(degree)} ${
unit === '°C' ? '°F' : '°C'
} = ${formatNumberResult(temperatureValue)} ${unit}</p>`
} }
} }
@ -36,7 +38,10 @@ module.exports = ({ res, next }, argsObject) => {
// Si ce n'est pas un nombre // Si ce n'est pas un nombre
degree = parseFloat(degree) degree = parseFloat(degree)
if (isNaN(degree)) { if (isNaN(degree)) {
return errorHandling(next, { message: 'Veuillez rentré un nombre valide.', statusCode: 400 }) return errorHandling(next, {
message: 'Veuillez rentré un nombre valide.',
statusCode: 400
})
} }
const result = convertTemperature(degree, unitToConvert) const result = convertTemperature(degree, unitToConvert)

View File

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

View File

@ -12,7 +12,7 @@ function findLongestWord (string) {
let stringLength = 0 let stringLength = 0
let result = '' let result = ''
arrayString.forEach((element) => { arrayString.forEach(element => {
if (element.length > stringLength) { if (element.length > stringLength) {
result = element result = element
stringLength = element.length stringLength = element.length
@ -27,7 +27,7 @@ module.exports = ({ res, next }, argsObject) => {
const { string } = argsObject const { string } = argsObject
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(string)) { if (!string) {
return errorHandling(next, requiredFields) return errorHandling(next, requiredFields)
} }

View File

@ -18,9 +18,14 @@ function heapAlgorithm (string) {
for (let indexString = 0; indexString < string.length; indexString++) { for (let indexString = 0; indexString < string.length; indexString++) {
const firstChar = string[indexString] const firstChar = string[indexString]
const charsLeft = string.substring(0, indexString) + string.substring(indexString + 1) const charsLeft =
string.substring(0, indexString) + string.substring(indexString + 1)
const innerPermutations = heapAlgorithm(charsLeft) const innerPermutations = heapAlgorithm(charsLeft)
for (let indexPermutation = 0; indexPermutation < innerPermutations.length; indexPermutation++) { for (
let indexPermutation = 0;
indexPermutation < innerPermutations.length;
indexPermutation++
) {
results.push(firstChar + innerPermutations[indexPermutation]) results.push(firstChar + innerPermutations[indexPermutation])
} }
} }
@ -32,19 +37,26 @@ module.exports = ({ res, next }, argsObject) => {
const { string } = argsObject const { string } = argsObject
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(string)) { if (!string) {
return errorHandling(next, requiredFields) return errorHandling(next, requiredFields)
} }
// Si la chaîne de caractère dépasse LIMIT_CHARACTERS caractères // Si la chaîne de caractère dépasse LIMIT_CHARACTERS caractères
const LIMIT_CHARACTERS = 7 const LIMIT_CHARACTERS = 7
if (string.length > LIMIT_CHARACTERS) { if (string.length > LIMIT_CHARACTERS) {
return errorHandling(next, { message: `Par souci de performance, vous ne pouvez pas exécuter cette fonction avec un mot dépassant ${LIMIT_CHARACTERS} caractères.`, statusCode: 400 }) return errorHandling(next, {
message: `Par souci de performance, vous ne pouvez pas exécuter cette fonction avec un mot dépassant ${LIMIT_CHARACTERS} caractères.`,
statusCode: 400
})
} }
const result = heapAlgorithm(string) const result = heapAlgorithm(string)
let resultHTML = `<p>Il y a ${formatNumberResult(result.length)} possibilités d'anagramme pour le mot "${string}" qui contient ${string.length} caractères, la liste : <br/><br/>` let resultHTML = `<p>Il y a ${formatNumberResult(
result.forEach((string) => { 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 += string + '<br/>'
}) })
resultHTML += '</p>' resultHTML += '</p>'

View File

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

View File

@ -13,12 +13,19 @@ module.exports = async ({ res, next }, argsObject) => {
// Si ce n'est pas une url // Si ce n'est pas une url
if (!validator.isURL(url)) { if (!validator.isURL(url)) {
return errorHandling(next, { message: 'Veuillez entré une URL valide.', statusCode: 400 }) return errorHandling(next, {
message: 'Veuillez entré une URL valide.',
statusCode: 400
})
} }
// Si ce n'est pas de type slug // Si ce n'est pas de type slug
if (!validator.isSlug(shortcutName)) { if (!validator.isSlug(shortcutName)) {
return errorHandling(next, { message: "Le nom de votre raccourci doit être de type slug (ne pas contenir d'espaces, ni de caractères spéciaux).", statusCode: 400 }) return errorHandling(next, {
message:
"Le nom de votre raccourci doit être de type slug (ne pas contenir d'espaces, ni de caractères spéciaux).",
statusCode: 400
})
} }
// Sanitize shortcutName // Sanitize shortcutName
@ -31,20 +38,33 @@ module.exports = async ({ res, next }, argsObject) => {
const urlInDatabase = await shortLinks.findOne({ where: { url } }) const urlInDatabase = await shortLinks.findOne({ where: { url } })
if (urlInDatabase) { if (urlInDatabase) {
const urlShort = `https://short-links.divlo.fr/?q=${urlInDatabase.shortcut}` const urlShort = `https://short-links.divlo.fr/?q=${urlInDatabase.shortcut}`
return errorHandling(next, { message: `L'url a déjà été raccourcie... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`, statusCode: 400 }) return errorHandling(next, {
message: `L'url a déjà été raccourcie... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`,
statusCode: 400
})
} }
// Si le nom du raccourci existe déjà // Si le nom du raccourci existe déjà
const shortcutInDatabase = await shortLinks.findOne({ where: { shortcut: shortcutName } }) const shortcutInDatabase = await shortLinks.findOne({
where: { shortcut: shortcutName }
})
if (shortcutInDatabase) { if (shortcutInDatabase) {
const urlShort = `https://short-links.divlo.fr/?q=${shortcutInDatabase.shortcut}` const urlShort = `https://short-links.divlo.fr/?q=${shortcutInDatabase.shortcut}`
return errorHandling(next, { message: `Le nom du raccourci a déjà été utilisé... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`, statusCode: 400 }) return errorHandling(next, {
message: `Le nom du raccourci a déjà été utilisé... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`,
statusCode: 400
})
} }
// Ajout du lien raccourci // Ajout du lien raccourci
const result = await shortLinks.create({ url, shortcut: shortcutName }) const result = await shortLinks.create({ url, shortcut: shortcutName })
const shortcutLinkResult = `https://short-links.divlo.fr/?q=${result.shortcut}` const shortcutLinkResult = `https://short-links.divlo.fr/?q=${result.shortcut}`
return res.status(200).json({ resultHTML: `URL Raccourcie : <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${shortcutLinkResult}">${shortcutLinkResult}</a>`, result: shortcutLinkResult }) return res
.status(200)
.json({
resultHTML: `URL Raccourcie : <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${shortcutLinkResult}">${shortcutLinkResult}</a>`,
result: shortcutLinkResult
})
} catch (error) { } catch (error) {
console.log(error) console.log(error)
return errorHandling(next, serverError) return errorHandling(next, serverError)

View File

@ -26,11 +26,21 @@ const randomNumberOutput = ({ res, next }, argsObject) => {
min = parseInt(min) min = parseInt(min)
max = parseInt(max) max = parseInt(max)
if (isNaN(min) || isNaN(max)) { if (isNaN(min) || isNaN(max)) {
return errorHandling(next, { message: 'Les paramètres min et max doivent être des nombres...', statusCode: 400 }) return errorHandling(next, {
message: 'Les paramètres min et max doivent être des nombres...',
statusCode: 400
})
} }
const result = randomNumber(min, max) const result = randomNumber(min, max)
return res.status(200).json({ result, resultHTML: `<p>Nombre aléatoire compris entre ${min} inclus et ${max} inclus : <strong>${formatNumberResult(result)}</strong></p>` }) return res
.status(200)
.json({
result,
resultHTML: `<p>Nombre aléatoire compris entre ${min} inclus et ${max} inclus : <strong>${formatNumberResult(
result
)}</strong></p>`
})
} }
exports.randomNumber = randomNumber exports.randomNumber = randomNumber

View File

@ -8,9 +8,7 @@ module.exports = async ({ res, next }, _argsObject) => {
try { try {
const quote = await Quotes.findOne({ const quote = await Quotes.findOne({
order: sequelize.random(), order: sequelize.random(),
include: [ include: [{ model: Users, attributes: ['name', 'logo'] }],
{ model: Users, attributes: ['name', 'logo'] }
],
attributes: { attributes: {
exclude: ['isValidated'] exclude: ['isValidated']
}, },

View File

@ -23,15 +23,18 @@ function getRandomArrayElement (array) {
async function getAmazonProductList (subject) { async function getAmazonProductList (subject) {
const url = `https://www.amazon.fr/s?k=${subject}` const url = `https://www.amazon.fr/s?k=${subject}`
const { data } = await axios.get(`http://api.scraperapi.com/?api_key=${SCRAPER_API_KEY}&url=${url}`) const { data } = await axios.get(
const { document } = (new JSDOM(data)).window `http://api.scraperapi.com/?api_key=${SCRAPER_API_KEY}&url=${url}`
)
const { document } = new JSDOM(data).window
const amazonProductList = document.querySelectorAll('.s-result-item') const amazonProductList = document.querySelectorAll('.s-result-item')
const productsList = [] const productsList = []
for (const indexProduct in amazonProductList) { for (const indexProduct in amazonProductList) {
try { try {
const elementProduct = amazonProductList[indexProduct] const elementProduct = amazonProductList[indexProduct]
const productImage = elementProduct.querySelector('.s-image') const productImage = elementProduct.querySelector('.s-image')
const originalPrice = elementProduct.querySelector('.a-price-whole').innerHTML const originalPrice = elementProduct.querySelector('.a-price-whole')
.innerHTML
productsList.push({ productsList.push({
name: productImage.alt, name: productImage.alt,
image: productImage.src, image: productImage.src,

View File

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

View File

@ -26,21 +26,48 @@ module.exports = ({ res, next }, argsObject) => {
let { cityName } = argsObject let { cityName } = argsObject
// S'il n'y a pas les champs obligatoire // S'il n'y a pas les champs obligatoire
if (!(cityName)) { if (!cityName) {
return errorHandling(next, requiredFields) return errorHandling(next, requiredFields)
} }
cityName = cityName.split(' ').join('+') cityName = cityName.split(' ').join('+')
// Récupère les données météo grâce à l'API : openweathermap.org. (→ avec limite de 50 requêtes par minute) // Récupère les données météo grâce à l'API : openweathermap.org. (→ avec limite de 50 requêtes par minute)
queue.request(() => { queue.request(
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${cityName}&lang=fr&units=metric&appid=${WEATHER_API_KEY}`) () => {
.then((response) => { axios
.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 json = response.data
const showDateTimeValue = dateTimeUTC((json.timezone / 60 / 60).toString()).showDateTimeValue const showDateTimeValue = dateTimeUTC(
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>` (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 }) return res.status(200).json({ result: json, resultHTML })
}) })
.catch(() => errorHandling(next, { message: "La ville n'existe pas (dans l'API de openweathermap.org).", statusCode: 404 })) .catch(() =>
}, 'everyone', 'weatherRequest') errorHandling(next, {
message:
"La ville n'existe pas (dans l'API de openweathermap.org).",
statusCode: 404
})
)
},
'everyone',
'weatherRequest'
)
} }

View File

@ -27,7 +27,8 @@ function showDateTime (timeNow) {
const hour = ('0' + timeNow.getHours()).slice(-2) const hour = ('0' + timeNow.getHours()).slice(-2)
const minute = ('0' + timeNow.getMinutes()).slice(-2) const minute = ('0' + timeNow.getMinutes()).slice(-2)
const second = ('0' + timeNow.getSeconds()).slice(-2) const second = ('0' + timeNow.getSeconds()).slice(-2)
const showDateTimeValue = day + '/' + month + '/' + year + ' - ' + hour + ':' + minute + ':' + second const showDateTimeValue =
day + '/' + month + '/' + year + ' - ' + hour + ':' + minute + ':' + second
const objectDateTime = { const objectDateTime = {
year: year, year: year,
month: month, month: month,

View File

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

View File

@ -12,7 +12,11 @@ const DEFAULT_OPTIONS = {
* @param {*} Model Model Sequelize * @param {*} Model Model Sequelize
* @param {Object} options Options avec clause where etc. * @param {Object} options Options avec clause where etc.
*/ */
async function getPagesHelper ({ req, res, next }, Model, options = DEFAULT_OPTIONS) { async function getPagesHelper (
{ req, res, next },
Model,
options = DEFAULT_OPTIONS
) {
const page = helperQueryNumber(req.query.page, 1) const page = helperQueryNumber(req.query.page, 1)
const limit = helperQueryNumber(req.query.limit, 10) const limit = helperQueryNumber(req.query.limit, 10)
const offset = (page - 1) * limit const offset = (page - 1) * limit
@ -23,7 +27,7 @@ async function getPagesHelper ({ req, res, next }, Model, options = DEFAULT_OPTI
...options ...options
}) })
const { count, rows } = result const { count, rows } = result
const hasMore = (page * limit) < count const hasMore = page * limit < count
return res.status(200).json({ totalItems: count, hasMore, rows }) return res.status(200).json({ totalItems: count, hasMore, rows })
} catch (error) { } catch (error) {
console.log(error) console.log(error)

View File

@ -15,7 +15,12 @@ const { EMAIL_INFO, FRONT_END_HOST } = require('../assets/config/config')
const transporter = require('../assets/config/transporter') const transporter = require('../assets/config/transporter')
const { emailQuoteTemplate } = require('../assets/config/emails') const { emailQuoteTemplate } = require('../assets/config/emails')
const handleEditFunction = async (res, resultFunction, { title, slug, description, type, categorieId, isOnline }, imageName = false) => { const handleEditFunction = async (
res,
resultFunction,
{ title, slug, description, type, categorieId, isOnline },
imageName = false
) => {
resultFunction.title = title resultFunction.title = title
resultFunction.slug = slug resultFunction.slug = slug
resultFunction.description = description resultFunction.description = description
@ -32,23 +37,41 @@ const handleEditFunction = async (res, resultFunction, { title, slug, descriptio
exports.getFunctions = async (req, res, next) => { exports.getFunctions = async (req, res, next) => {
const categoryId = helperQueryNumber(req.query.categoryId, 0) const categoryId = helperQueryNumber(req.query.categoryId, 0)
let search = req.query.search let search = req.query.search
try { search = search.toLowerCase() } catch {}; try {
search = search.toLowerCase()
} catch {}
const options = { const options = {
where: { where: {
// Trie par catégorie // Trie par catégorie
...(categoryId !== 0) && { categorieId: categoryId }, ...(categoryId !== 0 && { categorieId: categoryId }),
// Recherche // Recherche
...(search != null) && { ...(search != null && {
[Sequelize.Op.or]: [ [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}%`) }, title: Sequelize.where(
{ description: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('description')), 'LIKE', `%${search}%`) } Sequelize.fn('LOWER', Sequelize.col('title')),
] 'LIKE',
} `%${search}%`
)
}, },
include: [ {
{ model: Categories, attributes: ['name', 'color'] } 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: { attributes: {
exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline'] exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline']
}, },
@ -61,18 +84,21 @@ exports.getFunctionBySlug = (req, res, next) => {
const { slug } = req.params const { slug } = req.params
Functions.findOne({ Functions.findOne({
where: { slug }, where: { slug },
include: [ include: [{ model: Categories, attributes: ['name', 'color'] }]
{ model: Categories, attributes: ['name', 'color'] }
]
}) })
.then((result) => { .then(result => {
if (!result) { if (!result) {
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }) return errorHandling(next, {
message: "La fonction n'existe pas.",
statusCode: 404
})
} }
try { result.utilizationForm = JSON.parse(result.utilizationForm) } catch {} try {
result.utilizationForm = JSON.parse(result.utilizationForm)
} catch {}
return res.status(200).json(result) return res.status(200).json(result)
}) })
.catch((error) => { .catch(error => {
console.log(error) console.log(error)
return errorHandling(next, serverError) return errorHandling(next, serverError)
}) })
@ -83,28 +109,49 @@ exports.postFunction = (req, res, next) => {
const image = req.files.image const image = req.files.image
const errors = validationResult(req) const errors = validationResult(req)
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }) return errorHandling(next, {
message: errors.array()[0].msg,
statusCode: 400
})
} }
if ((!image || image.truncated) && ( if (
image.mimetype !== 'image/png' || (!image || image.truncated) &&
(image.mimetype !== 'image/png' ||
image.mimetype !== 'image/jpg' || image.mimetype !== 'image/jpg' ||
image.mimetype !== 'image/jpeg' image.mimetype !== 'image/jpeg')
)) { ) {
return errorHandling(next, { message: 'La fonction doit avoir une image valide.', statusCode: 400 }) return errorHandling(next, {
message: 'La fonction doit avoir une image valide.',
statusCode: 400
})
} }
const splitedImageName = image.name.split('.') const splitedImageName = image.name.split('.')
if (splitedImageName.length !== 2) return errorHandling(next, serverError) if (splitedImageName.length !== 2) return errorHandling(next, serverError)
const imageName = slug + '.' + splitedImageName[1] const imageName = slug + '.' + splitedImageName[1]
image.mv(path.join(__dirname, '..', 'assets', 'images', 'functions') + '/' + imageName, async (error) => { image.mv(
path.join(__dirname, '..', 'assets', 'images', 'functions') +
'/' +
imageName,
async error => {
if (error) return errorHandling(next, serverError) if (error) return errorHandling(next, serverError)
try { try {
const result = await Functions.create({ title, slug, description, type, categorieId, image: `/images/functions/${imageName}` }) const result = await Functions.create({
return res.status(201).json({ message: 'La fonction a été correctement ajouté!', result }) title,
slug,
description,
type,
categorieId,
image: `/images/functions/${imageName}`
})
return res
.status(201)
.json({ message: 'La fonction a été correctement ajouté!', result })
} catch (error) { } catch (error) {
console.log(error) console.log(error)
return errorHandling(next, serverError) return errorHandling(next, serverError)
} }
}) }
)
} }
exports.putFunction = async (req, res, next) => { exports.putFunction = async (req, res, next) => {
@ -113,43 +160,74 @@ exports.putFunction = async (req, res, next) => {
const image = req.files.image const image = req.files.image
const errors = validationResult(req) const errors = validationResult(req)
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }) return errorHandling(next, {
message: errors.array()[0].msg,
statusCode: 400
})
} }
try { try {
// Vérifie si la fonction existe // Vérifie si la fonction existe
const resultFunction = await Functions.findOne({ where: { id } }) const resultFunction = await Functions.findOne({ where: { id } })
if (!resultFunction) { if (!resultFunction) {
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }) return errorHandling(next, {
message: "La fonction n'existe pas.",
statusCode: 404
})
} }
// Vérifie si le slug existe déjà // Vérifie si le slug existe déjà
const FunctionSlug = await Functions.findOne({ where: { slug } }) const FunctionSlug = await Functions.findOne({ where: { slug } })
if (!FunctionSlug && FunctionSlug.id !== resultFunction.id) { if (!FunctionSlug && FunctionSlug.id !== resultFunction.id) {
return errorHandling(next, { message: 'Le slug existe déjà...', statusCode: 404 }) return errorHandling(next, {
message: 'Le slug existe déjà...',
statusCode: 404
})
} }
// Sauvegarde de la fonction // Sauvegarde de la fonction
if (image != null) { if (image != null) {
if (image.truncated && ( if (
image.mimetype !== 'image/png' || image.truncated &&
(image.mimetype !== 'image/png' ||
image.mimetype !== 'image/jpg' || image.mimetype !== 'image/jpg' ||
image.mimetype !== 'image/jpeg' image.mimetype !== 'image/jpeg')
)) { ) {
return errorHandling(next, { message: 'La fonction doit avoir une image valide.', statusCode: 400 }) return errorHandling(next, {
message: 'La fonction doit avoir une image valide.',
statusCode: 400
})
} }
const splitedImageName = image.name.split('.') const splitedImageName = image.name.split('.')
if (splitedImageName.length !== 2) return errorHandling(next, serverError) if (splitedImageName.length !== 2) return errorHandling(next, serverError)
const imageName = slug + '.' + splitedImageName[1] const imageName = slug + '.' + splitedImageName[1]
// Supprime les anciennes images // Supprime les anciennes images
const functionPath = path.join(__dirname, '..', 'assets', 'images', 'functions') const functionPath = path.join(
__dirname,
'..',
'assets',
'images',
'functions'
)
deleteFilesNameStartWith(slug, functionPath, () => { deleteFilesNameStartWith(slug, functionPath, () => {
image.mv(path.join(functionPath, imageName), async (error) => { image.mv(path.join(functionPath, imageName), async error => {
if (error) return errorHandling(next, serverError) if (error) return errorHandling(next, serverError)
return await handleEditFunction(res, resultFunction, { title, slug, description, type, categorieId, isOnline }, imageName) return await handleEditFunction(
res,
resultFunction,
{ title, slug, description, type, categorieId, isOnline },
imageName
)
}) })
}) })
} else { } else {
return await handleEditFunction(res, resultFunction, { title, slug, description, type, categorieId, isOnline }) return await handleEditFunction(res, resultFunction, {
title,
slug,
description,
type,
categorieId,
isOnline
})
} }
} catch (error) { } catch (error) {
console.log(error) console.log(error)
@ -165,7 +243,10 @@ exports.putFunctionArticle = async (req, res, next) => {
// Vérifie si la fonction existe // Vérifie si la fonction existe
const resultFunction = await Functions.findOne({ where: { id } }) const resultFunction = await Functions.findOne({ where: { id } })
if (!resultFunction) { if (!resultFunction) {
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }) return errorHandling(next, {
message: "La fonction n'existe pas.",
statusCode: 404
})
} }
resultFunction.article = article resultFunction.article = article
const result = await resultFunction.save() const result = await resultFunction.save()
@ -184,7 +265,10 @@ exports.putFunctionForm = async (req, res, next) => {
// Vérifie si la fonction existe // Vérifie si la fonction existe
const resultFunction = await Functions.findOne({ where: { id } }) const resultFunction = await Functions.findOne({ where: { id } })
if (!resultFunction) { if (!resultFunction) {
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }) return errorHandling(next, {
message: "La fonction n'existe pas.",
statusCode: 404
})
} }
resultFunction.utilizationForm = JSON.stringify(form) resultFunction.utilizationForm = JSON.stringify(form)
const result = await resultFunction.save() const result = await resultFunction.save()
@ -200,14 +284,19 @@ exports.deleteFunction = async (req, res, next) => {
try { try {
const result = await Functions.findOne({ where: { id } }) const result = await Functions.findOne({ where: { id } })
if (!result) { if (!result) {
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }) return errorHandling(next, {
message: "La fonction n'existe pas.",
statusCode: 404
})
} }
if (result.image !== '/images/functions/default.png') { if (result.image !== '/images/functions/default.png') {
const filePath = path.join(__dirname, '..', 'assets', result.image) const filePath = path.join(__dirname, '..', 'assets', result.image)
fs.unlinkSync(filePath) // supprime le fichier fs.unlinkSync(filePath) // supprime le fichier
} }
await result.destroy() await result.destroy()
res.status(200).json({ message: 'La fonction a été correctement supprimé!' }) res
.status(200)
.json({ message: 'La fonction a été correctement supprimé!' })
} catch (error) { } catch (error) {
console.log(error) console.log(error)
return errorHandling(next, serverError) return errorHandling(next, serverError)
@ -217,11 +306,15 @@ exports.deleteFunction = async (req, res, next) => {
exports.postCategory = async (req, res, next) => { exports.postCategory = async (req, res, next) => {
const { name, color } = req.body const { name, color } = req.body
if (!(name && color)) { if (!(name && color)) {
return errorHandling(next, { message: 'La catégorie doit avoir un nom et une couleur.' }) return errorHandling(next, {
message: 'La catégorie doit avoir un nom et une couleur.'
})
} }
try { try {
const result = await Categories.create({ name, color }) const result = await Categories.create({ name, color })
return res.status(201).json({ message: 'La catégorie a bien été crée!', result }) return res
.status(201)
.json({ message: 'La catégorie a bien été crée!', result })
} catch (error) { } catch (error) {
console.log(error) console.log(error)
return errorHandling(next, serverError) return errorHandling(next, serverError)
@ -232,7 +325,9 @@ exports.putCategory = async (req, res, next) => {
const { name, color } = req.body const { name, color } = req.body
const { id } = req.params const { id } = req.params
if (!(name && color && id)) { if (!(name && color && id)) {
return errorHandling(next, { message: 'La catégorie doit avoir un nom, une couleur et un id.' }) return errorHandling(next, {
message: 'La catégorie doit avoir un nom, une couleur et un id.'
})
} }
try { try {
const category = await Categories.findOne({ where: { id } }) const category = await Categories.findOne({ where: { id } })
@ -242,7 +337,9 @@ exports.putCategory = async (req, res, next) => {
category.name = name category.name = name
category.color = color category.color = color
const result = await category.save() const result = await category.save()
return res.status(200).json({ message: 'La catégorie a bien été modifiée!', result }) return res
.status(200)
.json({ message: 'La catégorie a bien été modifiée!', result })
} catch (error) { } catch (error) {
console.log(error) console.log(error)
return errorHandling(next, serverError) return errorHandling(next, serverError)
@ -257,7 +354,9 @@ exports.deleteCategory = async (req, res, next) => {
return errorHandling(next, { message: "La catégorie n'existe pas." }) return errorHandling(next, { message: "La catégorie n'existe pas." })
} }
await category.destroy() await category.destroy()
return res.status(200).json({ message: 'La catégorie a bien été supprimée!' }) return res
.status(200)
.json({ message: 'La catégorie a bien été supprimée!' })
} catch (error) { } catch (error) {
console.log(error) console.log(error)
return errorHandling(next, serverError) return errorHandling(next, serverError)
@ -269,9 +368,7 @@ exports.getQuotes = async (req, res, next) => {
where: { where: {
isValidated: 0 isValidated: 0
}, },
include: [ include: [{ model: Users, attributes: ['name', 'logo'] }],
{ model: Users, attributes: ['name', 'logo'] }
],
order: [['createdAt', 'DESC']] order: [['createdAt', 'DESC']]
} }
return await getPagesHelper({ req, res, next }, Quotes, options) return await getPagesHelper({ req, res, next }, Quotes, options)
@ -282,19 +379,23 @@ exports.putQuote = async (req, res, next) => {
const { isValid } = req.body const { isValid } = req.body
try { try {
if (typeof isValid !== 'boolean') { if (typeof isValid !== 'boolean') {
return errorHandling(next, { message: 'isValid doit être un booléen.', statusCode: 400 }) return errorHandling(next, {
message: 'isValid doit être un booléen.',
statusCode: 400
})
} }
const quote = await Quotes.findOne({ const quote = await Quotes.findOne({
where: { where: {
id, id,
isValidated: 0 isValidated: 0
}, },
include: [ include: [{ model: Users, attributes: ['name', 'email'] }]
{ model: Users, attributes: ['name', 'email'] }
]
}) })
if (!quote) { if (!quote) {
return errorHandling(next, { message: "La citation n'existe pas (ou est déjà validé).", statusCode: 404 }) return errorHandling(next, {
message: "La citation n'existe pas (ou est déjà validé).",
statusCode: 404
})
} }
await transporter.sendMail({ await transporter.sendMail({
@ -307,10 +408,14 @@ exports.putQuote = async (req, res, next) => {
if (isValid) { if (isValid) {
quote.isValidated = true quote.isValidated = true
await quote.save() await quote.save()
return res.status(200).json({ message: 'La citation a bien été validée!' }) return res
.status(200)
.json({ message: 'La citation a bien été validée!' })
} else { } else {
await quote.destroy() await quote.destroy()
return res.status(200).json({ imessage: 'La citation a bien été supprimée!' }) return res
.status(200)
.json({ imessage: 'La citation a bien été supprimée!' })
} }
} catch (error) { } catch (error) {
console.log(error) console.log(error)

View File

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

View File

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

View File

@ -27,9 +27,14 @@ exports.postFavoriteByFunctionId = async (req, res, next) => {
const { functionId } = req.params const { functionId } = req.params
const { userId } = req const { userId } = req
try { try {
const resultFunction = await Functions.findOne({ where: { id: functionId } }) const resultFunction = await Functions.findOne({
where: { id: functionId }
})
if (!resultFunction) { if (!resultFunction) {
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }) return errorHandling(next, {
message: "La fonction n'existe pas.",
statusCode: 404
})
} }
const favorite = await Favorites.findOne({ const favorite = await Favorites.findOne({
where: { where: {
@ -41,7 +46,10 @@ exports.postFavoriteByFunctionId = async (req, res, next) => {
await Favorites.create({ userId, functionId }) await Favorites.create({ userId, functionId })
return res.status(201).json({ result: 'Le favoris a bien été ajouté!' }) return res.status(201).json({ result: 'Le favoris a bien été ajouté!' })
} }
return errorHandling(next, { message: 'La fonction est déjà en favoris.', statusCode: 400 }) return errorHandling(next, {
message: 'La fonction est déjà en favoris.',
statusCode: 400
})
} catch (error) { } catch (error) {
console.log(error) console.log(error)
return errorHandling(next, serverError) return errorHandling(next, serverError)
@ -52,9 +60,14 @@ exports.deleteFavoriteByFunctionId = async (req, res, next) => {
const { functionId } = req.params const { functionId } = req.params
const { userId } = req const { userId } = req
try { try {
const resultFunction = await Functions.findOne({ where: { id: functionId } }) const resultFunction = await Functions.findOne({
where: { id: functionId }
})
if (!resultFunction) { if (!resultFunction) {
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }) return errorHandling(next, {
message: "La fonction n'existe pas.",
statusCode: 404
})
} }
const favorite = await Favorites.findOne({ const favorite = await Favorites.findOne({
where: { where: {
@ -63,10 +76,15 @@ exports.deleteFavoriteByFunctionId = async (req, res, next) => {
} }
}) })
if (!favorite) { if (!favorite) {
return errorHandling(next, { message: "Le fonction n'est pas en favoris.", statusCode: 400 }) return errorHandling(next, {
message: "Le fonction n'est pas en favoris.",
statusCode: 400
})
} }
await favorite.destroy() await favorite.destroy()
return res.status(200).json({ message: 'Le fonction a bien été supprimé des favoris.' }) return res
.status(200)
.json({ message: 'Le fonction a bien été supprimé des favoris.' })
} catch (error) { } catch (error) {
console.log(error) console.log(error)
return errorHandling(next, serverError) return errorHandling(next, serverError)

View File

@ -10,24 +10,42 @@ const Sequelize = require('sequelize')
exports.getFunctions = async (req, res, next) => { exports.getFunctions = async (req, res, next) => {
const categoryId = helperQueryNumber(req.query.categoryId, 0) const categoryId = helperQueryNumber(req.query.categoryId, 0)
let { search } = req.query let { search } = req.query
try { search = search.toLowerCase() } catch {}; try {
search = search.toLowerCase()
} catch {}
const options = { const options = {
where: { where: {
isOnline: 1, isOnline: 1,
// Trie par catégorie // Trie par catégorie
...(categoryId !== 0) && { categorieId: categoryId }, ...(categoryId !== 0 && { categorieId: categoryId }),
// Recherche // Recherche
...(search != null) && { ...(search != null && {
[Sequelize.Op.or]: [ [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}%`) }, title: Sequelize.where(
{ description: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('description')), 'LIKE', `%${search}%`) } Sequelize.fn('LOWER', Sequelize.col('title')),
] 'LIKE',
} `%${search}%`
)
}, },
include: [ {
{ model: Categories, attributes: ['name', 'color'] } 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: { attributes: {
exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline'] exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline']
}, },
@ -43,18 +61,21 @@ exports.getFunctionBySlug = (req, res, next) => {
attributes: { attributes: {
exclude: ['updatedAt', 'isOnline'] exclude: ['updatedAt', 'isOnline']
}, },
include: [ include: [{ model: Categories, attributes: ['name', 'color'] }]
{ model: Categories, attributes: ['name', 'color'] }
]
}) })
.then((result) => { .then(result => {
if (!result) { if (!result) {
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }) return errorHandling(next, {
message: "La fonction n'existe pas.",
statusCode: 404
})
} }
try { result.utilizationForm = JSON.parse(result.utilizationForm) } catch {} try {
result.utilizationForm = JSON.parse(result.utilizationForm)
} catch {}
return res.status(200).json(result) return res.status(200).json(result)
}) })
.catch((error) => { .catch(error => {
console.log(error) console.log(error)
return errorHandling(next, serverError) return errorHandling(next, serverError)
}) })
@ -65,5 +86,8 @@ exports.executeFunctionBySlug = (req, res, next) => {
if (functionOutput !== undefined) { if (functionOutput !== undefined) {
return functionOutput({ res, next }, req.body) return functionOutput({ res, next }, req.body)
} }
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }) return errorHandling(next, {
message: "La fonction n'existe pas.",
statusCode: 404
})
} }

View File

@ -9,9 +9,7 @@ exports.getQuotes = async (req, res, next) => {
where: { where: {
isValidated: 1 isValidated: 1
}, },
include: [ include: [{ model: Users, attributes: ['name', 'logo'] }],
{ model: Users, attributes: ['name', 'logo'] }
],
attributes: { attributes: {
exclude: ['isValidated'] exclude: ['isValidated']
}, },
@ -27,10 +25,15 @@ exports.postQuote = (req, res, next) => {
return errorHandling(next, requiredFields) return errorHandling(next, requiredFields)
} }
Quotes.create({ quote, author, userId: req.userId }) Quotes.create({ quote, author, userId: req.userId })
.then((_result) => { .then(_result => {
return res.status(200).json({ message: "La citation a bien été ajoutée, elle est en attente de confirmation d'un administrateur." }) return res
.status(200)
.json({
message:
"La citation a bien été ajoutée, elle est en attente de confirmation d'un administrateur."
}) })
.catch((error) => { })
.catch(error => {
console.log(error) console.log(error)
return errorHandling(next, serverError) return errorHandling(next, serverError)
}) })

View File

@ -36,12 +36,20 @@ exports.putTask = async (req, res, next) => {
const { isCompleted } = req.body const { isCompleted } = req.body
try { try {
if (typeof isCompleted !== 'boolean') { if (typeof isCompleted !== 'boolean') {
return errorHandling(next, { message: 'isCompleted doit être un booléen.', statusCode: 400 }) return errorHandling(next, {
message: 'isCompleted doit être un booléen.',
statusCode: 400
})
} }
const taskResult = await Tasks.findOne({ where: { id, userId: req.userId } }) const taskResult = await Tasks.findOne({
where: { id, userId: req.userId }
})
if (!taskResult) { if (!taskResult) {
return errorHandling(next, { message: 'La "tâche à faire" n\'existe pas.', statusCode: 404 }) return errorHandling(next, {
message: 'La "tâche à faire" n\'existe pas.',
statusCode: 404
})
} }
taskResult.isCompleted = isCompleted taskResult.isCompleted = isCompleted
const taskSaved = await taskResult.save() const taskSaved = await taskResult.save()
@ -55,12 +63,19 @@ exports.putTask = async (req, res, next) => {
exports.deleteTask = async (req, res, next) => { exports.deleteTask = async (req, res, next) => {
const { id } = req.params const { id } = req.params
try { try {
const taskResult = await Tasks.findOne({ where: { id, userId: req.userId } }) const taskResult = await Tasks.findOne({
where: { id, userId: req.userId }
})
if (!taskResult) { if (!taskResult) {
return errorHandling(next, { message: 'La "tâche à faire" n\'existe pas.', statusCode: 404 }) return errorHandling(next, {
message: 'La "tâche à faire" n\'existe pas.',
statusCode: 404
})
} }
await taskResult.destroy() await taskResult.destroy()
return res.status(200).json({ message: 'La "tâche à faire" a bien été supprimée!' }) return res
.status(200)
.json({ message: 'La "tâche à faire" a bien été supprimée!' })
} catch (error) { } catch (error) {
console.log(error) console.log(error)
return errorHandling(next, serverError) return errorHandling(next, serverError)

View File

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

View File

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

View File

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

453
api/package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "api", "name": "api",
"version": "2.0.0", "version": "2.1.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -89,9 +89,9 @@
"dev": true "dev": true
}, },
"@types/node": { "@types/node": {
"version": "13.9.2", "version": "14.0.27",
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.9.2.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.27.tgz",
"integrity": "sha512-bnoqK579sAYrQbp73wwglccjJ4sfRdKU7WNEZ5FW4K2U6Kc0/eZ5kvXG0JKsEKFB50zrFmfFt52/cvBbZa7eXg==" "integrity": "sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g=="
}, },
"abab": { "abab": {
"version": "2.0.3", "version": "2.0.3",
@ -114,9 +114,9 @@
} }
}, },
"acorn": { "acorn": {
"version": "7.2.0", "version": "7.3.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz",
"integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==" "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA=="
}, },
"acorn-globals": { "acorn-globals": {
"version": "6.0.0", "version": "6.0.0",
@ -134,14 +134,14 @@
"dev": true "dev": true
}, },
"acorn-walk": { "acorn-walk": {
"version": "7.1.1", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
"integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==" "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA=="
}, },
"ajv": { "ajv": {
"version": "6.12.2", "version": "6.12.3",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz",
"integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==",
"requires": { "requires": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0", "fast-json-stable-stringify": "^2.0.0",
@ -279,9 +279,9 @@
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
}, },
"aws4": { "aws4": {
"version": "1.9.1", "version": "1.10.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz",
"integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA=="
}, },
"axios": { "axios": {
"version": "0.19.2", "version": "0.19.2",
@ -301,7 +301,6 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
"dev": true,
"requires": { "requires": {
"safe-buffer": "5.1.2" "safe-buffer": "5.1.2"
} }
@ -345,6 +344,21 @@
"qs": "6.7.0", "qs": "6.7.0",
"raw-body": "2.4.0", "raw-body": "2.4.0",
"type-is": "~1.6.17" "type-is": "~1.6.17"
},
"dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
} }
}, },
"bowser": { "bowser": {
@ -739,9 +753,9 @@
} }
}, },
"debug": { "debug": {
"version": "2.6.9", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": { "requires": {
"ms": "2.0.0" "ms": "2.0.0"
}, },
@ -849,11 +863,6 @@
"streamsearch": "0.1.2" "streamsearch": "0.1.2"
} }
}, },
"dns-prefetch-control": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.2.0.tgz",
"integrity": "sha512-hvSnros73+qyZXhHFjx2CMLwoj3Fe7eR9EJsFsqmcI1bB2OBWL/+0YzaEaKssCHnj/6crawNnUyw74Gm2EKe+Q=="
},
"doctrine": { "doctrine": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@ -895,8 +904,7 @@
"dotenv": { "dotenv": {
"version": "8.2.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
"dev": true
}, },
"dottie": { "dottie": {
"version": "2.0.2", "version": "2.0.2",
@ -1008,9 +1016,9 @@
"dev": true "dev": true
}, },
"escodegen": { "escodegen": {
"version": "1.14.1", "version": "1.14.3",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz",
"integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==",
"requires": { "requires": {
"esprima": "^4.0.1", "esprima": "^4.0.1",
"estraverse": "^4.2.0", "estraverse": "^4.2.0",
@ -1142,6 +1150,23 @@
"requires": { "requires": {
"debug": "^2.6.9", "debug": "^2.6.9",
"resolve": "^1.13.1" "resolve": "^1.13.1"
},
"dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
}
} }
}, },
"eslint-module-utils": { "eslint-module-utils": {
@ -1152,6 +1177,23 @@
"requires": { "requires": {
"debug": "^2.6.9", "debug": "^2.6.9",
"pkg-dir": "^2.0.0" "pkg-dir": "^2.0.0"
},
"dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
}
} }
}, },
"eslint-plugin-es": { "eslint-plugin-es": {
@ -1191,6 +1233,15 @@
"resolve": "^1.11.0" "resolve": "^1.11.0"
}, },
"dependencies": { "dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
},
"doctrine": { "doctrine": {
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
@ -1200,6 +1251,12 @@
"esutils": "^2.0.2", "esutils": "^2.0.2",
"isarray": "^1.0.0" "isarray": "^1.0.0"
} }
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
} }
} }
}, },
@ -1353,11 +1410,6 @@
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
}, },
"expect-ct": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/expect-ct/-/expect-ct-0.2.0.tgz",
"integrity": "sha512-6SK3MG/Bbhm8MsgyJAylg+ucIOU71/FzyFalcfu5nY19dH8y/z0tBJU0wrNBXD4B27EoQtqPF/9wqH0iYAd04g=="
},
"express": { "express": {
"version": "4.17.1", "version": "4.17.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
@ -1393,12 +1445,27 @@
"type-is": "~1.6.18", "type-is": "~1.6.18",
"utils-merge": "1.0.1", "utils-merge": "1.0.1",
"vary": "~1.1.2" "vary": "~1.1.2"
},
"dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
} }
}, },
"express-fileupload": { "express-fileupload": {
"version": "1.1.6", "version": "1.1.9",
"resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.1.6.tgz", "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.1.9.tgz",
"integrity": "sha512-w24zPWT8DkoIxSVkbxYPo9hkTiLpCQQzNsLRTCnecBhfbYv+IkIC5uLw2MIUAxBZ+7UMmXPjGxlhzUXo4RcbZw==", "integrity": "sha512-f2w0aoe7lj3NeD8a4MXmYQsqir3Z66I08l9AKq04QbFUAjeZNmPwTlR5Lx2NGwSu/PslsAjGC38MWzo5tTjoBg==",
"requires": { "requires": {
"busboy": "^0.3.1" "busboy": "^0.3.1"
} }
@ -1412,19 +1479,12 @@
} }
}, },
"express-validator": { "express-validator": {
"version": "6.4.0", "version": "6.6.0",
"resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.4.0.tgz", "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.6.0.tgz",
"integrity": "sha512-Fs+x0yDOSiUV+o5jIRloMyBxqpSzJiMM8KQW1IRVv2l49F6ATU0F9uPa+3K6vXNlLlhUjauv2FCGLFPMaNr24w==", "integrity": "sha512-xcephfzFbUssJph/nOSKIdx+I+8GRz5by/8rOIKL6gJikKKKJjnwYH5TG1nIDB6kEalUtZMbOFuSNOp/HHY84Q==",
"requires": { "requires": {
"lodash": "^4.17.15", "lodash": "^4.17.15",
"validator": "^12.1.0" "validator": "^13.1.1"
},
"dependencies": {
"validator": {
"version": "12.2.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-12.2.0.tgz",
"integrity": "sha512-jJfE/DW6tIK1Ek8nCfNFqt8Wb3nzMoAbocBF6/Icgg1ZFSBpObdnwVY2jQj6qUqzhx5jc71fpvBWyLGO7Xl+nQ=="
}
} }
}, },
"extend": { "extend": {
@ -1449,9 +1509,9 @@
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
}, },
"fast-deep-equal": { "fast-deep-equal": {
"version": "3.1.1", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
}, },
"fast-json-stable-stringify": { "fast-json-stable-stringify": {
"version": "2.1.0", "version": "2.1.0",
@ -1507,6 +1567,21 @@
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
"statuses": "~1.5.0", "statuses": "~1.5.0",
"unpipe": "~1.0.0" "unpipe": "~1.0.0"
},
"dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
} }
}, },
"find-root": { "find-root": {
@ -1547,23 +1622,6 @@
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
"requires": { "requires": {
"debug": "=3.1.0" "debug": "=3.1.0"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
},
"dependencies": {
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
}
}
} }
}, },
"forever-agent": { "forever-agent": {
@ -1586,11 +1644,6 @@
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
}, },
"frameguard": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/frameguard/-/frameguard-3.1.0.tgz",
"integrity": "sha512-TxgSKM+7LTA6sidjOiSZK9wxY0ffMPY3Wta//MqwmX0nZuEHc8QrkV8Fh3ZhMJeiH+Uyh/tcaarImRy8u77O7g=="
},
"fresh": { "fresh": {
"version": "0.5.2", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@ -1724,11 +1777,11 @@
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
}, },
"har-validator": { "har-validator": {
"version": "5.1.3", "version": "5.1.5",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
"requires": { "requires": {
"ajv": "^6.5.5", "ajv": "^6.12.3",
"har-schema": "^2.0.0" "har-schema": "^2.0.0"
} }
}, },
@ -1760,22 +1813,18 @@
"dev": true "dev": true
}, },
"helmet": { "helmet": {
"version": "3.21.3", "version": "3.23.3",
"resolved": "https://registry.npmjs.org/helmet/-/helmet-3.21.3.tgz", "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.23.3.tgz",
"integrity": "sha512-8OjGNdpG3WQhPO71fSy2fT4X3FSNutU1LDeAf+YS+Vil6r+fE7w8per5mNed6egGYbZl3QhKXgFzMYSwys+YQw==", "integrity": "sha512-U3MeYdzPJQhtvqAVBPntVgAvNSOJyagwZwyKsFdyRa8TV3pOKVFljalPOCxbw5Wwf2kncGhmP0qHjyazIdNdSA==",
"requires": { "requires": {
"depd": "2.0.0", "depd": "2.0.0",
"dns-prefetch-control": "0.2.0",
"dont-sniff-mimetype": "1.1.0", "dont-sniff-mimetype": "1.1.0",
"expect-ct": "0.2.0",
"feature-policy": "0.3.0", "feature-policy": "0.3.0",
"frameguard": "3.1.0",
"helmet-crossdomain": "0.4.0", "helmet-crossdomain": "0.4.0",
"helmet-csp": "2.9.5", "helmet-csp": "2.10.0",
"hide-powered-by": "1.1.0", "hide-powered-by": "1.1.0",
"hpkp": "2.0.0", "hpkp": "2.0.0",
"hsts": "2.2.0", "hsts": "2.2.0",
"ienoopen": "1.1.0",
"nocache": "2.1.0", "nocache": "2.1.0",
"referrer-policy": "1.2.0", "referrer-policy": "1.2.0",
"x-xss-protection": "1.3.0" "x-xss-protection": "1.3.0"
@ -1794,9 +1843,9 @@
"integrity": "sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA==" "integrity": "sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA=="
}, },
"helmet-csp": { "helmet-csp": {
"version": "2.9.5", "version": "2.10.0",
"resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.9.5.tgz", "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.10.0.tgz",
"integrity": "sha512-w9nps5adqFQwgktVPDbXkARmZot/nr8aegzQas9AXdBSwBFBBefPpDSTV0wtgHlAUdDwY6MZo7qAl9yts3ppJg==", "integrity": "sha512-Rz953ZNEFk8sT2XvewXkYN0Ho4GEZdjAZy4stjiEQV3eN7GDxg1QKmYggH7otDyIA7uGA6XnUMVSgeJwbR5X+w==",
"requires": { "requires": {
"bowser": "2.9.0", "bowser": "2.9.0",
"camelize": "1.0.0", "camelize": "1.0.0",
@ -1879,11 +1928,6 @@
"safer-buffer": ">= 2.1.2 < 3" "safer-buffer": ">= 2.1.2 < 3"
} }
}, },
"ienoopen": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/ienoopen/-/ienoopen-1.1.0.tgz",
"integrity": "sha512-MFs36e/ca6ohEKtinTJ5VvAJ6oDRAYFdYXweUnGY9L9vcoqFOU4n2ZhmJ0C4z/cwGZ3YIQRSB3XZ1+ghZkY5NQ=="
},
"ignore": { "ignore": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
@ -2197,9 +2241,9 @@
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
}, },
"jsdom": { "jsdom": {
"version": "16.2.2", "version": "16.3.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.2.2.tgz", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.3.0.tgz",
"integrity": "sha512-pDFQbcYtKBHxRaP55zGXCJWgFHkDAYbKcsXEK/3Icu9nKYZkutUXfLBwbD+09XDutkYSHcgfQLZ0qvpAAm9mvg==", "integrity": "sha512-zggeX5UuEknpdZzv15+MS1dPYG0J/TftiiNunOeNxSl3qr8Z6cIlQpN0IdJa44z9aFxZRIVqRncvEhQ7X5DtZg==",
"requires": { "requires": {
"abab": "^2.0.3", "abab": "^2.0.3",
"acorn": "^7.1.1", "acorn": "^7.1.1",
@ -2221,7 +2265,7 @@
"tough-cookie": "^3.0.1", "tough-cookie": "^3.0.1",
"w3c-hr-time": "^1.0.2", "w3c-hr-time": "^1.0.2",
"w3c-xmlserializer": "^2.0.0", "w3c-xmlserializer": "^2.0.0",
"webidl-conversions": "^6.0.0", "webidl-conversions": "^6.1.0",
"whatwg-encoding": "^1.0.5", "whatwg-encoding": "^1.0.5",
"whatwg-mimetype": "^2.3.0", "whatwg-mimetype": "^2.3.0",
"whatwg-url": "^8.0.0", "whatwg-url": "^8.0.0",
@ -2277,13 +2321,6 @@
"lodash.once": "^4.0.0", "lodash.once": "^4.0.0",
"ms": "^2.1.1", "ms": "^2.1.1",
"semver": "^5.6.0" "semver": "^5.6.0"
},
"dependencies": {
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
} }
}, },
"jsprim": { "jsprim": {
@ -2441,12 +2478,11 @@
"dev": true "dev": true
}, },
"lru-cache": { "lru-cache": {
"version": "4.1.5", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"requires": { "requires": {
"pseudomap": "^1.0.2", "yallist": "^3.0.2"
"yallist": "^2.1.2"
} }
}, },
"make-dir": { "make-dir": {
@ -2487,16 +2523,16 @@
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
}, },
"mime-db": { "mime-db": {
"version": "1.43.0", "version": "1.44.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
"integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg=="
}, },
"mime-types": { "mime-types": {
"version": "2.1.26", "version": "2.1.27",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
"integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==",
"requires": { "requires": {
"mime-db": "1.43.0" "mime-db": "1.44.0"
} }
}, },
"mimic-fn": { "mimic-fn": {
@ -2536,29 +2572,48 @@
} }
}, },
"moment": { "moment": {
"version": "2.24.0", "version": "2.27.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ=="
}, },
"moment-timezone": { "moment-timezone": {
"version": "0.5.28", "version": "0.5.31",
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.28.tgz", "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz",
"integrity": "sha512-TDJkZvAyKIVWg5EtVqRzU97w0Rb0YVbfpqyjgu6GwXCAohVRqwZjf4fOzDE6p1Ch98Sro/8hQQi65WDXW5STPw==", "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==",
"requires": { "requires": {
"moment": ">= 2.9.0" "moment": ">= 2.9.0"
} }
}, },
"morgan": { "morgan": {
"version": "1.9.1", "version": "1.10.0",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
"integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==", "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==",
"dev": true,
"requires": { "requires": {
"basic-auth": "~2.0.0", "basic-auth": "~2.0.1",
"debug": "2.6.9", "debug": "2.6.9",
"depd": "~1.1.2", "depd": "~2.0.0",
"on-finished": "~2.3.0", "on-finished": "~2.3.0",
"on-headers": "~1.0.1" "on-headers": "~1.0.2"
},
"dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
} }
}, },
"ms": { "ms": {
@ -2589,25 +2644,12 @@
}, },
"dependencies": { "dependencies": {
"iconv-lite": { "iconv-lite": {
"version": "0.5.1", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.1.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz",
"integrity": "sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==", "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==",
"requires": { "requires": {
"safer-buffer": ">= 2.1.2 < 3" "safer-buffer": ">= 2.1.2 < 3"
} }
},
"lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"requires": {
"yallist": "^3.0.2"
}
},
"yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
} }
} }
}, },
@ -2617,6 +2659,22 @@
"integrity": "sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA==", "integrity": "sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA==",
"requires": { "requires": {
"lru-cache": "^4.1.3" "lru-cache": "^4.1.3"
},
"dependencies": {
"lru-cache": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
"integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
"requires": {
"pseudomap": "^1.0.2",
"yallist": "^2.1.2"
}
},
"yallist": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
}
} }
}, },
"natural-compare": { "natural-compare": {
@ -2642,9 +2700,9 @@
"integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q=="
}, },
"nodemailer": { "nodemailer": {
"version": "6.4.6", "version": "6.4.11",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.6.tgz", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.11.tgz",
"integrity": "sha512-/kJ+FYVEm2HuUlw87hjSqTss+GU35D4giOpdSfGp7DO+5h6RlJj7R94YaYHOkoxu1CSaM0d3WRBtCzwXrY6MKA==" "integrity": "sha512-BVZBDi+aJV4O38rxsUh164Dk1NCqgh6Cm0rQSb9SK/DHGll/DrCMnycVDD7msJgZCnmVa8ASo8EZzR7jsgTukQ=="
}, },
"nodemon": { "nodemon": {
"version": "2.0.4", "version": "2.0.4",
@ -2793,8 +2851,7 @@
"on-headers": { "on-headers": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
"dev": true
}, },
"once": { "once": {
"version": "1.4.0", "version": "1.4.0",
@ -3319,19 +3376,19 @@
} }
}, },
"request-promise-core": { "request-promise-core": {
"version": "1.1.3", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz",
"integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==",
"requires": { "requires": {
"lodash": "^4.17.15" "lodash": "^4.17.19"
} }
}, },
"request-promise-native": { "request-promise-native": {
"version": "1.0.8", "version": "1.0.9",
"resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz",
"integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==",
"requires": { "requires": {
"request-promise-core": "1.1.3", "request-promise-core": "1.1.4",
"stealthy-require": "^1.1.1", "stealthy-require": "^1.1.1",
"tough-cookie": "^2.3.3" "tough-cookie": "^2.3.3"
}, },
@ -3479,6 +3536,21 @@
"statuses": "~1.5.0" "statuses": "~1.5.0"
}, },
"dependencies": { "dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
},
"dependencies": {
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
}
},
"ms": { "ms": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
@ -3492,9 +3564,9 @@
"integrity": "sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4=" "integrity": "sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4="
}, },
"sequelize": { "sequelize": {
"version": "5.21.5", "version": "5.22.3",
"resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.21.5.tgz", "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.22.3.tgz",
"integrity": "sha512-n9hR5K4uQGmBGK/Y/iqewCeSFmKVsd0TRnh0tfoLoAkmXbKC4tpeK96RhKs7d+TTMtrJlgt2TNLVBaAxEwC4iw==", "integrity": "sha512-+nxf4TzdrB+PRmoWhR05TP9ukLAurK7qtKcIFv5Vhxm5Z9v+d2PcTT6Ea3YAoIQVkZ47QlT9XWAIUevMT/3l8Q==",
"requires": { "requires": {
"bluebird": "^3.5.0", "bluebird": "^3.5.0",
"cls-bluebird": "^2.1.0", "cls-bluebird": "^2.1.0",
@ -3521,11 +3593,6 @@
"ms": "^2.1.1" "ms": "^2.1.1"
} }
}, },
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"semver": { "semver": {
"version": "6.3.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@ -3644,11 +3711,6 @@
"ms": "^2.1.1" "ms": "^2.1.1"
} }
}, },
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"uuid": { "uuid": {
"version": "3.4.0", "version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
@ -3768,9 +3830,9 @@
"dev": true "dev": true
}, },
"sqlstring": { "sqlstring": {
"version": "2.3.1", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz",
"integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" "integrity": "sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg=="
}, },
"sshpk": { "sshpk": {
"version": "1.16.1", "version": "1.16.1",
@ -4121,6 +4183,23 @@
"dev": true, "dev": true,
"requires": { "requires": {
"debug": "^2.2.0" "debug": "^2.2.0"
},
"dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
}
} }
}, },
"uniq": { "uniq": {
@ -4193,9 +4272,9 @@
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
}, },
"uuid": { "uuid": {
"version": "7.0.2", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.2.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz",
"integrity": "sha512-vy9V/+pKG+5ZTYKf+VcphF5Oc6EFiu3W8Nv3P3zIh0EqVI80ZxOzuPfe9EHjkFNvf8+xuTHVeei4Drydlx4zjw==" "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="
}, },
"v8-compile-cache": { "v8-compile-cache": {
"version": "2.1.1", "version": "2.1.1",
@ -4214,9 +4293,9 @@
} }
}, },
"validator": { "validator": {
"version": "13.0.0", "version": "13.1.1",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.0.0.tgz", "resolved": "https://registry.npmjs.org/validator/-/validator-13.1.1.tgz",
"integrity": "sha512-anYx5fURbgF04lQV18nEQWZ/3wHGnxiKdG4aL8J+jEDsm98n/sU/bey+tYk6tnGJzm7ioh5FoqrAiQ6m03IgaA==" "integrity": "sha512-8GfPiwzzRoWTg7OV1zva1KvrSemuMkv07MA9TTl91hfhe+wKrsrgVN4H2QSFd/U/FhiU3iWPYVgvbsOGwhyFWw=="
}, },
"vary": { "vary": {
"version": "1.1.2", "version": "1.1.2",
@ -4343,9 +4422,9 @@
} }
}, },
"ws": { "ws": {
"version": "7.3.0", "version": "7.3.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz",
"integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==" "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA=="
}, },
"x-xss-protection": { "x-xss-protection": {
"version": "1.3.0", "version": "1.3.0",
@ -4375,9 +4454,9 @@
"dev": true "dev": true
}, },
"yallist": { "yallist": {
"version": "2.1.2", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
} }
} }
} }

View File

@ -25,11 +25,11 @@
"sequelize": "^5.21.5", "sequelize": "^5.21.5",
"smart-request-balancer": "^2.1.1", "smart-request-balancer": "^2.1.1",
"uuid": "^7.0.2", "uuid": "^7.0.2",
"validator": "^13.0.0" "validator": "^13.0.0",
"dotenv": "^8.2.0",
"morgan": "^1.9.1"
}, },
"devDependencies": { "devDependencies": {
"dotenv": "^8.2.0",
"morgan": "^1.9.1",
"nodemon": "^2.0.4", "nodemon": "^2.0.4",
"snazzy": "^8.0.0", "snazzy": "^8.0.0",
"standard": "^14.3.4" "standard": "^14.3.4"

View File

@ -13,7 +13,8 @@ AdminRouter.route('/functions')
.get(adminController.getFunctions) .get(adminController.getFunctions)
// Permet de créé une fonction // Permet de créé une fonction
.post(fileUpload({ .post(
fileUpload({
useTempFiles: true, useTempFiles: true,
safeFileNames: true, safeFileNames: true,
preserveExtension: Number, preserveExtension: Number,
@ -27,7 +28,7 @@ AdminRouter.route('/functions')
.withMessage('La fonction doit avoir un titre.') .withMessage('La fonction doit avoir un titre.')
.isLength({ max: 100 }) .isLength({ max: 100 })
.withMessage('Le titre est trop long.') .withMessage('Le titre est trop long.')
.custom((title) => { .custom(title => {
if (title === 'undefined') { if (title === 'undefined') {
return Promise.reject(new Error('La fonction doit avoir un titre.')) return Promise.reject(new Error('La fonction doit avoir un titre.'))
} }
@ -39,13 +40,13 @@ AdminRouter.route('/functions')
.withMessage('La fonction doit avoir un slug.') .withMessage('La fonction doit avoir un slug.')
.isLength({ max: 100 }) .isLength({ max: 100 })
.withMessage('Le slug est trop long.') .withMessage('Le slug est trop long.')
.custom((slug) => { .custom(slug => {
if (slug === 'undefined') { if (slug === 'undefined') {
return Promise.reject(new Error('La fonction doit avoir un slug.')) return Promise.reject(new Error('La fonction doit avoir un slug.'))
} }
return true return true
}) })
.custom(async (slug) => { .custom(async slug => {
try { try {
const FunctionSlug = await Functions.findOne({ where: { slug } }) const FunctionSlug = await Functions.findOne({ where: { slug } })
if (FunctionSlug) { if (FunctionSlug) {
@ -62,9 +63,11 @@ AdminRouter.route('/functions')
.withMessage('La fonction doit avoir une description.') .withMessage('La fonction doit avoir une description.')
.isLength({ max: 255, min: 1 }) .isLength({ max: 255, min: 1 })
.withMessage('La description est trop longue.') .withMessage('La description est trop longue.')
.custom((description) => { .custom(description => {
if (description === 'undefined') { if (description === 'undefined') {
return Promise.reject(new Error('La fonction doit avoir une description.')) return Promise.reject(
new Error('La fonction doit avoir une description.')
)
} }
return true return true
}), }),
@ -72,9 +75,11 @@ AdminRouter.route('/functions')
.not() .not()
.isEmpty() .isEmpty()
.withMessage('La fonction doit avoir une catégorie.') .withMessage('La fonction doit avoir une catégorie.')
.custom(async (categorieId) => { .custom(async categorieId => {
try { try {
const categorieFound = await Categories.findOne({ where: { id: parseInt(categorieId) } }) const categorieFound = await Categories.findOne({
where: { id: parseInt(categorieId) }
})
if (!categorieFound) { if (!categorieFound) {
return Promise.reject(new Error("La catégorie n'existe pas!")) return Promise.reject(new Error("La catégorie n'existe pas!"))
} }
@ -83,14 +88,19 @@ AdminRouter.route('/functions')
} }
return true return true
}), }),
body('type') body('type').custom(type => {
.custom((type) => {
if (!(type === 'article' || type === 'form' || type === 'page')) { if (!(type === 'article' || type === 'form' || type === 'page')) {
return Promise.reject(new Error('Le type de la fonction peut être : article, form ou page.')) return Promise.reject(
new Error(
'Le type de la fonction peut être : article, form ou page.'
)
)
} }
return true return true
}) })
], adminController.postFunction) ],
adminController.postFunction
)
AdminRouter.route('/functions/:slug') AdminRouter.route('/functions/:slug')
@ -100,7 +110,8 @@ AdminRouter.route('/functions/:slug')
AdminRouter.route('/functions/:id') AdminRouter.route('/functions/:id')
// Modifie information basique d'une fonction // Modifie information basique d'une fonction
.put(fileUpload({ .put(
fileUpload({
useTempFiles: true, useTempFiles: true,
safeFileNames: true, safeFileNames: true,
preserveExtension: Number, preserveExtension: Number,
@ -114,7 +125,7 @@ AdminRouter.route('/functions/:id')
.withMessage('La fonction doit avoir un titre.') .withMessage('La fonction doit avoir un titre.')
.isLength({ max: 100 }) .isLength({ max: 100 })
.withMessage('Le titre est trop long.') .withMessage('Le titre est trop long.')
.custom((title) => { .custom(title => {
if (title === 'undefined') { if (title === 'undefined') {
return Promise.reject(new Error('La fonction doit avoir un titre.')) return Promise.reject(new Error('La fonction doit avoir un titre.'))
} }
@ -126,7 +137,7 @@ AdminRouter.route('/functions/:id')
.withMessage('La fonction doit avoir un slug.') .withMessage('La fonction doit avoir un slug.')
.isLength({ max: 100 }) .isLength({ max: 100 })
.withMessage('Le slug est trop long.') .withMessage('Le slug est trop long.')
.custom((slug) => { .custom(slug => {
if (slug === 'undefined') { if (slug === 'undefined') {
return Promise.reject(new Error('La fonction doit avoir un slug.')) return Promise.reject(new Error('La fonction doit avoir un slug.'))
} }
@ -138,9 +149,11 @@ AdminRouter.route('/functions/:id')
.withMessage('La fonction doit avoir une description.') .withMessage('La fonction doit avoir une description.')
.isLength({ max: 255, min: 1 }) .isLength({ max: 255, min: 1 })
.withMessage('La description est trop longue.') .withMessage('La description est trop longue.')
.custom((description) => { .custom(description => {
if (description === 'undefined') { if (description === 'undefined') {
return Promise.reject(new Error('La fonction doit avoir une description.')) return Promise.reject(
new Error('La fonction doit avoir une description.')
)
} }
return true return true
}), }),
@ -148,9 +161,11 @@ AdminRouter.route('/functions/:id')
.not() .not()
.isEmpty() .isEmpty()
.withMessage('La fonction doit avoir une catégorie.') .withMessage('La fonction doit avoir une catégorie.')
.custom(async (categorieId) => { .custom(async categorieId => {
try { try {
const categorieFound = await Categories.findOne({ where: { id: parseInt(categorieId) } }) const categorieFound = await Categories.findOne({
where: { id: parseInt(categorieId) }
})
if (!categorieFound) { if (!categorieFound) {
return Promise.reject(new Error("La catégorie n'existe pas!")) return Promise.reject(new Error("La catégorie n'existe pas!"))
} }
@ -159,24 +174,27 @@ AdminRouter.route('/functions/:id')
} }
return true return true
}), }),
body('type') body('type').custom(type => {
.custom((type) => {
if (!(type === 'article' || type === 'form' || type === 'page')) { if (!(type === 'article' || type === 'form' || type === 'page')) {
return Promise.reject(new Error('Le type de la fonction peut être : article, form ou page.')) return Promise.reject(
new Error(
'Le type de la fonction peut être : article, form ou page.'
)
)
} }
return true return true
}) })
], adminController.putFunction) ],
adminController.putFunction
)
// Supprime une fonction avec son id // Supprime une fonction avec son id
.delete(adminController.deleteFunction) .delete(adminController.deleteFunction)
AdminRouter.route('/functions/article/:id') AdminRouter.route('/functions/article/:id')
.put(adminController.putFunctionArticle) .put(adminController.putFunctionArticle)
AdminRouter.route('/functions/form/:id') AdminRouter.route('/functions/form/:id')
.put(adminController.putFunctionForm) .put(adminController.putFunctionForm)
AdminRouter.route('/categories') AdminRouter.route('/categories')

View File

@ -9,7 +9,10 @@ export default function Footer () {
<a>FunctionProject</a> <a>FunctionProject</a>
</Link> </Link>
&nbsp;- Version 2.1 <br /> &nbsp;- Version 2.1 <br />
<a href='https://divlo.fr/' target='_blank' rel='noopener noreferrer'>Divlo</a> | Tous droits réservés <a href='https://divlo.fr/' target='_blank' rel='noopener noreferrer'>
Divlo
</a>{' '}
| Tous droits réservés
</p> </p>
</footer> </footer>
) )

View File

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

View File

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

View File

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

View File

@ -4,12 +4,12 @@
position: relative; position: relative;
flex-direction: column; flex-direction: column;
word-wrap: break-word; word-wrap: break-word;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black; border: 1px solid black;
border-radius: 1rem; border-radius: 1rem;
margin: 0 0 50px 0; margin: 0 0 50px 0;
cursor: pointer; cursor: pointer;
transition: all .3s; transition: all 0.3s;
color: var(--text-color); color: var(--text-color);
text-decoration: none !important; text-decoration: none !important;
} }

View File

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

View File

@ -2,9 +2,9 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
word-wrap: break-word; word-wrap: break-word;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black; border: 1px solid black;
border-radius: .7em; border-radius: 0.7em;
margin: 15px 0 15px 0; margin: 15px 0 15px 0;
} }
.CommentCard__container { .CommentCard__container {

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ import api from '../../utils/api'
import { API_URL } from '../../utils/config/config' import { API_URL } from '../../utils/config/config'
import '../FunctionCard/FunctionCard.css' import '../FunctionCard/FunctionCard.css'
const FunctionComponentTop = (props) => { const FunctionComponentTop = props => {
const { isAuth, user } = useContext(UserContext) const { isAuth, user } = useContext(UserContext)
const [isFavorite, setIsFavorite] = useState(false) const [isFavorite, setIsFavorite] = useState(false)
@ -21,7 +21,9 @@ const FunctionComponentTop = (props) => {
const fetchFavorite = async () => { const fetchFavorite = async () => {
try { try {
const favoriteResponse = await api.get(`/favorites/${props.id}`, { headers: { Authorization: user.token } }) const favoriteResponse = await api.get(`/favorites/${props.id}`, {
headers: { Authorization: user.token }
})
setIsFavorite(favoriteResponse.data.isFavorite) setIsFavorite(favoriteResponse.data.isFavorite)
} catch {} } catch {}
} }
@ -30,16 +32,22 @@ const FunctionComponentTop = (props) => {
if (isAuth && user.token != null) { if (isAuth && user.token != null) {
try { try {
if (isFavorite) { if (isFavorite) {
const response = await api.delete(`/favorites/${props.id}`, { headers: { Authorization: user.token } }) const response = await api.delete(`/favorites/${props.id}`, {
headers: { Authorization: user.token }
})
if (response.status === 200) return setIsFavorite(false) if (response.status === 200) return setIsFavorite(false)
} }
const response = await api.post(`/favorites/${props.id}`, {}, { headers: { Authorization: user.token } }) const response = await api.post(
`/favorites/${props.id}`,
{},
{ headers: { Authorization: user.token } }
)
if (response.status === 201) return setIsFavorite(true) if (response.status === 201) return setIsFavorite(true)
} catch {} } catch {}
} }
} }
const handleError = (event) => { const handleError = event => {
event.target.src = API_URL + '/images/functions/default.png' event.target.src = API_URL + '/images/functions/default.png'
} }
@ -47,18 +55,44 @@ const FunctionComponentTop = (props) => {
<div className='container-fluid'> <div className='container-fluid'>
<div className='row justify-content-center text-center'> <div className='row justify-content-center text-center'>
<div className='FunctionComponent__top col-24'> <div className='FunctionComponent__top col-24'>
{isAuth && (
<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) && <img
<FontAwesomeIcon onClick={toggleFavorite} {...(isFavorite) ? { icon: faStar } : { icon: farStar }} title={(isFavorite) ? 'Retirer la fonction des favoris' : 'Ajouter la fonction aux favoris'} className='FunctionComponent__star-favorite' />} onError={handleError}
className='FunctionComponent__image'
<img onError={handleError} className='FunctionComponent__image' src={API_URL + props.image} alt={props.title} /> src={API_URL + props.image}
<h1 className='FunctionComponent__title title-important'>{props.title}</h1> alt={props.title}
/>
<h1 className='FunctionComponent__title title-important'>
{props.title}
</h1>
<p className='FunctionComponent__description'>{props.description}</p> <p className='FunctionComponent__description'>{props.description}</p>
<div className='FunctionCard__info'> <div className='FunctionCard__info'>
<Link href={`/functions?categoryId=${props.categorieId}`}> <Link href={`/functions?categoryId=${props.categorieId}`}>
<a className='FunctionCard__category' style={{ backgroundColor: props.categorie.color, color: 'inherit' }}>{props.categorie.name}</a> <a
className='FunctionCard__category'
style={{
backgroundColor: props.categorie.color,
color: 'inherit'
}}
>
{props.categorie.name}
</a>
</Link> </Link>
<p className='FunctionCard__publication-date'>{date.format(new Date(props.createdAt), 'DD/MM/YYYY', true)}</p> <p className='FunctionCard__publication-date'>
{date.format(new Date(props.createdAt), 'DD/MM/YYYY', true)}
</p>
</div> </div>
</div> </div>
</div> </div>

View File

@ -10,7 +10,7 @@ import 'react-datepicker/dist/react-datepicker.css'
registerLocale('fr', fr) registerLocale('fr', fr)
const FunctionForm = (props) => { const FunctionForm = props => {
const [inputState, setInputState] = useState({}) const [inputState, setInputState] = useState({})
const [message, setMessage] = useState('') const [message, setMessage] = useState('')
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
@ -18,7 +18,7 @@ const FunctionForm = (props) => {
// inputState par défaut // inputState par défaut
useEffect(() => { useEffect(() => {
const inputStateNew = { ...inputState } const inputStateNew = { ...inputState }
props.inputsArray.forEach((input) => { props.inputsArray.forEach(input => {
if (input.type === 'select' && input.options.length > 0) { if (input.type === 'select' && input.options.length > 0) {
inputStateNew[input.name] = input.options[0].value inputStateNew[input.name] = input.options[0].value
} }
@ -26,21 +26,22 @@ const FunctionForm = (props) => {
setInputState(inputStateNew) setInputState(inputStateNew)
}, []) }, [])
const handleSubmit = (event) => { const handleSubmit = event => {
setIsLoading(true) setIsLoading(true)
event.preventDefault() event.preventDefault()
api.post(`/functions/${props.slug}`, inputState) api
.then((response) => { .post(`/functions/${props.slug}`, inputState)
.then(response => {
setMessage(response.data.resultHTML) setMessage(response.data.resultHTML)
setIsLoading(false) setIsLoading(false)
}) })
.catch((error) => { .catch(error => {
setMessage(error.response.data.message) setMessage(error.response.data.message)
setIsLoading(false) setIsLoading(false)
}) })
} }
const handleChange = (event) => { const handleChange = event => {
const inputStateNew = { ...inputState } const inputStateNew = { ...inputState }
inputStateNew[event.target.name] = event.target.value inputStateNew[event.target.name] = event.target.value
setInputState(inputStateNew) setInputState(inputStateNew)
@ -61,16 +62,35 @@ const FunctionForm = (props) => {
case 'text': case 'text':
return ( return (
<div key={index} className='form-group'> <div key={index} className='form-group'>
<label className='form-label' htmlFor={input.name}>{input.label}</label> <label className='form-label' htmlFor={input.name}>
<input onChange={handleChange} type='text' name={input.name} id={input.name} placeholder={input.placeholder} className='form-control' /> {input.label}
</label>
<input
onChange={handleChange}
type='text'
name={input.name}
id={input.name}
placeholder={input.placeholder}
className='form-control'
/>
</div> </div>
) )
case 'integer': case 'integer':
case 'float': case 'float':
return ( return (
<div key={index} className='form-group'> <div key={index} className='form-group'>
<label className='form-label' htmlFor={input.name}>{input.label}</label> <label className='form-label' htmlFor={input.name}>
<input onChange={handleChange} type='number' step={(input.type === 'integer') ? '1' : '0.01'} name={input.name} id={input.name} placeholder={input.placeholder} className='form-control' /> {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> </div>
) )
case 'calendar': case 'calendar':
@ -81,13 +101,14 @@ const FunctionForm = (props) => {
document.body.appendChild(newScript) document.body.appendChild(newScript)
} }
// eslint-disable-next-line // eslint-disable-next-line
const DatePicker = dynamic( const DatePicker = dynamic(() => import('react-datepicker'), {
() => import('react-datepicker'), ssr: false
{ ssr: false } })
)
return ( return (
<div key={index} className='form-group'> <div key={index} className='form-group'>
<label className='form-label' htmlFor={input.name}>{input.label}</label> <label className='form-label' htmlFor={input.name}>
{input.label}
</label>
<br /> <br />
<DatePicker <DatePicker
selected={(() => { selected={(() => {
@ -97,7 +118,11 @@ const FunctionForm = (props) => {
const year = dateArray[2] const year = dateArray[2]
const month = dateArray[1] const month = dateArray[1]
const day = dateArray[0] const day = dateArray[0]
return new Date(year, parseInt(month) - 1, parseInt(day) + 1) return new Date(
year,
parseInt(month) - 1,
parseInt(day) + 1
)
} }
throw new Error('Not a valid date') throw new Error('Not a valid date')
} catch { } catch {
@ -108,9 +133,13 @@ const FunctionForm = (props) => {
dateFormat='dd/MM/yyyy' dateFormat='dd/MM/yyyy'
fixedHeight fixedHeight
placeholderText={input.placeholder} placeholderText={input.placeholder}
onChange={(dateObject) => { onChange={dateObject => {
try { try {
const formattedDate = date.format(dateObject, 'DD/MM/YYYY', true) const formattedDate = date.format(
dateObject,
'DD/MM/YYYY',
true
)
handleChange({ handleChange({
target: { target: {
name: input.name, name: input.name,
@ -125,33 +154,39 @@ const FunctionForm = (props) => {
case 'select': case 'select':
return ( return (
<div key={index} className='form-group'> <div key={index} className='form-group'>
<label className='form-label' htmlFor={input.name}>{input.label}</label> <label className='form-label' htmlFor={input.name}>
<select onChange={handleChange} name={input.name} id={input.name} value={inputState[input.name] || input.options[0]} className='form-control'> {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) => { {input.options.map((option, optionIndex) => {
return ( return (
<option key={optionIndex} value={option.value}>{option.name}</option> <option key={optionIndex} value={option.value}>
{option.name}
</option>
) )
})} })}
</select> </select>
</div> </div>
) )
default: default:
return ( return <p>Erreur, l'input n'est pas valide...</p>
<p>Erreur, l'input n'est pas valide...</p>
)
} }
})} })}
<div className='form-group text-center'> <div className='form-group text-center'>
<button type='submit' className='btn btn-dark'>Envoyer</button> <button type='submit' className='btn btn-dark'>
Envoyer
</button>
</div> </div>
</form> </form>
<div className='form-result text-center'> <div className='form-result text-center'>
{ {isLoading ? <Loader /> : htmlParser(message)}
(isLoading)
? <Loader />
: htmlParser(message)
}
</div> </div>
</> </>
) )

View File

@ -4,17 +4,29 @@ import HeadTag from '../HeadTag'
import FunctionTabsTop from './FunctionTabsTop' import FunctionTabsTop from './FunctionTabsTop'
import FunctionComponentTop from './FunctionComponentTop' import FunctionComponentTop from './FunctionComponentTop'
const FunctionPage = (props) => { const FunctionPage = props => {
const [slideIndex, setSlideIndex] = useState(0) const [slideIndex, setSlideIndex] = useState(0)
return ( return (
<> <>
<HeadTag title={props.title} description={props.description} image={API_URL + props.image} /> <HeadTag
title={props.title}
description={props.description}
image={API_URL + props.image}
/>
<div className='container-fluid'> <div className='container-fluid'>
<FunctionTabsTop slideIndex={slideIndex} setSlideIndex={setSlideIndex} tabNames={props.tabNames} /> <FunctionTabsTop
slideIndex={slideIndex}
setSlideIndex={setSlideIndex}
tabNames={props.tabNames}
/>
<FunctionComponentTop {...props} /> <FunctionComponentTop {...props} />
<props.FunctionTabManager {...props} slideIndex={slideIndex} setSlideIndex={setSlideIndex} /> <props.FunctionTabManager
{...props}
slideIndex={slideIndex}
setSlideIndex={setSlideIndex}
/>
</div> </div>
</> </>
) )

View File

@ -16,11 +16,11 @@
color: var(--text-color); color: var(--text-color);
border: 1px solid #0c0b0b38; border: 1px solid #0c0b0b38;
border-bottom: 0px; border-bottom: 0px;
border-top-left-radius: .375rem; border-top-left-radius: 0.375rem;
border-top-right-radius: .375rem; border-top-right-radius: 0.375rem;
display: block; display: block;
padding: .5rem 1rem; padding: 0.5rem 1rem;
transition: .2s; transition: 0.2s;
} }
.FunctionTabs__nav-link-active { .FunctionTabs__nav-link-active {
border-color: #d9e2ef #d9e2ef #fff; border-color: #d9e2ef #d9e2ef #fff;

View File

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

View File

@ -1,4 +1,4 @@
const FunctionTabsTop = (props) => { const FunctionTabsTop = props => {
return ( return (
<div className='container'> <div className='container'>
<div className='row justify-content-center'> <div className='row justify-content-center'>
@ -7,7 +7,11 @@ const FunctionTabsTop = (props) => {
return ( return (
<li key={index} className='FunctionTabs__nav-item'> <li key={index} className='FunctionTabs__nav-item'>
<a <a
className={`FunctionTabs__nav-link ${(props.slideIndex === index) ? 'FunctionTabs__nav-link-active' : ''}`} className={`FunctionTabs__nav-link ${
props.slideIndex === index
? 'FunctionTabs__nav-link-active'
: ''
}`}
onClick={() => props.setSlideIndex(index)} onClick={() => props.setSlideIndex(index)}
> >
{tabName} {tabName}

View File

@ -4,8 +4,8 @@
} }
.Functions__form-control { .Functions__form-control {
display: block; display: block;
height: calc(1.5em + .75rem + 2px); height: calc(1.5em + 0.75rem + 2px);
padding: .375rem .75rem; padding: 0.375rem 0.75rem;
font-size: 1rem; font-size: 1rem;
font-weight: 400; font-weight: 400;
line-height: 1.5; line-height: 1.5;
@ -13,7 +13,7 @@
background-color: #fff; background-color: #fff;
background-clip: padding-box; background-clip: padding-box;
border: 1px solid #ced4da; border: 1px solid #ced4da;
border-radius: .5em; border-radius: 0.5em;
} }
.Functions__search-container { .Functions__search-container {
margin-bottom: 50px; margin-bottom: 50px;

View File

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

View File

@ -36,7 +36,8 @@ const HeadTag = ({ title, image, description }) => (
HeadTag.defaultProps = { HeadTag.defaultProps = {
title: 'FunctionProject', title: 'FunctionProject',
description: "Apprenez la programmation grâce à l'apprentissage par projet alias fonction.", description:
"Apprenez la programmation grâce à l'apprentissage par projet alias fonction.",
image: '/images/FunctionProject_icon_small.png' image: '/images/FunctionProject_icon_small.png'
} }

View File

@ -11,7 +11,7 @@
flex-flow: row wrap; flex-flow: row wrap;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: .5rem 1rem; padding: 0.5rem 1rem;
border-bottom: var(--border-header-footer); border-bottom: var(--border-header-footer);
background-color: var(--background-color); background-color: var(--background-color);
@ -36,8 +36,8 @@
/* Brand */ /* Brand */
.Header__brand-link { .Header__brand-link {
display: inline-block; display: inline-block;
padding-top: .3125rem; padding-top: 0.3125rem;
padding-bottom: .3125rem; padding-bottom: 0.3125rem;
margin-right: 1rem; margin-right: 1rem;
font-size: 1.25rem; font-size: 1.25rem;
line-height: inherit; line-height: inherit;
@ -89,18 +89,21 @@
} }
.navbar-link { .navbar-link {
display: block; display: block;
padding: .5rem 1rem; padding: 0.5rem 1rem;
} }
/* Details Styling */ /* Details Styling */
.navbar-link:hover { .navbar-link:hover {
text-decoration: none; text-decoration: none;
color: rgba(255, 255, 255, .75); color: rgba(255, 255, 255, 0.75);
} }
.navbar-link, .navbar-link-active { .navbar-link,
color: rgba(255, 255, 255, .5); .navbar-link-active {
color: rgba(255, 255, 255, 0.5);
} }
.navbar-link-active, .navbar-link-active:hover, .Header__brand-link { .navbar-link-active,
.navbar-link-active:hover,
.Header__brand-link {
color: var(--text-color); color: var(--text-color);
} }
.navbar-item { .navbar-item {
@ -108,7 +111,7 @@
} }
.navbar-link { .navbar-link {
font-size: 16px; font-size: 16px;
padding: .5rem; padding: 0.5rem;
} }
/* Hamburger Icon */ /* Hamburger Icon */
@ -118,11 +121,13 @@
height: 40px; height: 40px;
cursor: pointer; cursor: pointer;
background-color: transparent; background-color: transparent;
border: 1px solid rgba(255, 255, 255, .1); border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: .25rem; border-radius: 0.25rem;
position: relative; position: relative;
} }
.Header__hamburger > span, .Header__hamburger > span::before, .Header__hamburger > span::after { .Header__hamburger > span,
.Header__hamburger > span::before,
.Header__hamburger > span::after {
position: absolute; position: absolute;
width: 22px; width: 22px;
height: 1.3px; height: 1.3px;
@ -132,11 +137,12 @@
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
transition: background-color .3s ease-in-out; transition: background-color 0.3s ease-in-out;
} }
.Header__hamburger > span::before, .Header__hamburger > span::after { .Header__hamburger > span::before,
.Header__hamburger > span::after {
content: ''; content: '';
transition: transform .3s ease-in-out; transition: transform 0.3s ease-in-out;
} }
.Header__hamburger > span::before { .Header__hamburger > span::before {
transform: translateY(-8px); transform: translateY(-8px);

View File

@ -17,56 +17,74 @@ export default function Header () {
return ( return (
<header className='Header'> <header className='Header'>
<div className='container'> <div className='container'>
{/* Brand */} {/* Brand */}
<Link href='/'> <Link href='/'>
<a className='Header__brand-link'> <a className='Header__brand-link'>
<img id='brand-link__logo' src='/images/FunctionProject_brand-logo.png' alt='FunctionProject' /> <img
<img id='brand-link__logo-small-screen' src='/images/FunctionProject_icon_small.png' alt='FunctionProject' /> 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> </a>
</Link> </Link>
{/* Hamburger icon on Mobile */} {/* Hamburger icon on Mobile */}
<div onClick={toggleNavbar} className={`Header__hamburger ${(isActive) ? 'Header__hamburger-active' : ''}`}> <div
onClick={toggleNavbar}
className={`Header__hamburger ${
isActive ? 'Header__hamburger-active' : ''
}`}
>
<span /> <span />
</div> </div>
{/* Navigation */} {/* Navigation */}
<nav className='Header__navbar'> <nav className='Header__navbar'>
<ul className={`navbar__list ${(isActive) ? 'navbar__list-active' : ''}`}> <ul
className={`navbar__list ${isActive ? 'navbar__list-active' : ''}`}
>
<NavigationLink name='Accueil' path='/' /> <NavigationLink name='Accueil' path='/' />
<NavigationLink name='Fonctions' path='/functions' /> <NavigationLink name='Fonctions' path='/functions' />
<NavigationLink name='Utilisateurs' path='/users' /> <NavigationLink name='Utilisateurs' path='/users' />
{ {!isAuth ? (
(!isAuth)
? (
<> <>
<NavigationLink name="S'inscrire" path='/users/register' /> <NavigationLink name="S'inscrire" path='/users/register' />
<NavigationLink name='Connexion' path='/users/login' /> <NavigationLink name='Connexion' path='/users/login' />
</> </>
) ) : (
: (
<> <>
<li className='navbar-item'> <li className='navbar-item'>
<Link href='/users/[name]' as={`/users/${user.name}`}> <Link href='/users/[name]' as={`/users/${user.name}`}>
<a className={`navbar-link ${pathname === '/users/[name]' ? 'navbar-link-active' : null}`}>Mon Profil</a> <a
className={`navbar-link ${
pathname === '/users/[name]'
? 'navbar-link-active'
: null
}`}
>
Mon Profil
</a>
</Link> </Link>
</li> </li>
<li className='navbar-item'> <li className='navbar-item'>
<Link href='/'> <Link href='/'>
<a onClick={logoutUser} className='navbar-link'>Se déconnecter</a> <a onClick={logoutUser} className='navbar-link'>
Se déconnecter
</a>
</Link> </Link>
</li> </li>
</> </>
) )}
} {isAuth && user.isAdmin && (
{
(isAuth && user.isAdmin) &&
<NavigationLink name='Admin' path='/admin' /> <NavigationLink name='Admin' path='/admin' />
} )}
</ul> </ul>
</nav> </nav>
</div> </div>
</header> </header>
) )

View File

@ -8,7 +8,11 @@ export default function NavigationLink (props) {
return ( return (
<li className='navbar-item'> <li className='navbar-item'>
<Link href={props.path}> <Link href={props.path}>
<a className={`navbar-link ${pathname === props.path ? 'navbar-link-active' : null}`}> <a
className={`navbar-link ${
pathname === props.path ? 'navbar-link-active' : null
}`}
>
{props.name} {props.name}
</a> </a>
</Link> </Link>

View File

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

View File

@ -1,8 +1,6 @@
const Modal = (props) => ( const Modal = props => (
<div className='Modal container-fluid'> <div className='Modal container-fluid'>
<div className='Modal__content'> <div className='Modal__content'>{props.children}</div>
{props.children}
</div>
</div> </div>
) )

View File

@ -22,7 +22,9 @@ function UserContextProvider (props) {
useEffect(() => { useEffect(() => {
if (isAuth) { if (isAuth) {
setMessageLogin('<p class="form-error"><b>Erreur:</b> Vous devez être déconnecter avant de vous connecter.</p>') setMessageLogin(
'<p class="form-error"><b>Erreur:</b> Vous devez être déconnecter avant de vous connecter.</p>'
)
} else { } else {
setMessageLogin('') setMessageLogin('')
} }
@ -43,10 +45,14 @@ function UserContextProvider (props) {
cookies.set('user', newUser, { path: '/', maxAge: newUser.expiresIn }) cookies.set('user', newUser, { path: '/', maxAge: newUser.expiresIn })
setUser(newUser) setUser(newUser)
setIsAuth(true) setIsAuth(true)
setMessageLogin('<p class="form-success"><b>Succès:</b> Connexion réussi!</p>') setMessageLogin(
'<p class="form-success"><b>Succès:</b> Connexion réussi!</p>'
)
setLoginLoading(false) setLoginLoading(false)
} catch (error) { } catch (error) {
setMessageLogin(`<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>`) setMessageLogin(
`<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>`
)
setLoginLoading(false) setLoginLoading(false)
setIsAuth(false) setIsAuth(false)
setUser(null) setUser(null)
@ -54,7 +60,17 @@ function UserContextProvider (props) {
} }
return ( return (
<UserContext.Provider value={{ user, loginUser, logoutUser, loginLoading, messageLogin, isAuth, setMessageLogin }}> <UserContext.Provider
value={{
user,
loginUser,
logoutUser,
loginLoading,
messageLogin,
isAuth,
setMessageLogin
}}
>
{props.children} {props.children}
</UserContext.Provider> </UserContext.Provider>
) )

View File

@ -2,8 +2,8 @@ import { useContext } from 'react'
import { UserContext } from '../contexts/UserContext' import { UserContext } from '../contexts/UserContext'
import redirect from '../utils/redirect' import redirect from '../utils/redirect'
const withoutAuth = (WrappedComponent) => { const withoutAuth = WrappedComponent => {
const Component = (props) => { const Component = props => {
const { isAuth, user } = useContext(UserContext) const { isAuth, user } = useContext(UserContext)
if (isAuth) return redirect({}, `/users/${user.name}`) if (isAuth) return redirect({}, `/users/${user.name}`)

View File

@ -14,11 +14,11 @@ function useAPI (url, defaultData = [], method = 'get', options = {}) {
useEffect(() => { useEffect(() => {
api[method](url, options) api[method](url, options)
.then((result) => { .then(result => {
setData(result.data) setData(result.data)
setIsLoading(false) setIsLoading(false)
}) })
.catch((error) => { .catch(error => {
setHasError(true) setHasError(true)
console.error(error) console.error(error)
}) })

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/* GENERAL */ /* GENERAL */
:root { :root {
--border-header-footer: 3px rgba(255, 255, 255, .7) solid; --border-header-footer: 3px rgba(255, 255, 255, 0.7) solid;
--background-color: #181818; --background-color: #181818;
--text-color: rgb(222, 222, 222); --text-color: rgb(222, 222, 222);
--important: #ffd800; --important: #ffd800;
@ -41,7 +41,7 @@ a:hover {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
div[aria-hidden="true"] > * { div[aria-hidden='true'] > * {
display: none; display: none;
} }
.notyf__message { .notyf__message {
@ -83,7 +83,8 @@ div[aria-hidden="true"] > * {
color: var(--important); color: var(--important);
font-weight: 500; font-weight: 500;
} }
a, .important { a,
.important {
color: var(--important); color: var(--important);
text-decoration: none; text-decoration: none;
} }
@ -96,8 +97,8 @@ a, .important {
.form-control { .form-control {
display: block; display: block;
width: 100%; width: 100%;
height: calc(1.5em + .75rem + 2px); height: calc(1.5em + 0.75rem + 2px);
padding: .375rem .75rem; padding: 0.375rem 0.75rem;
font-size: 1rem; font-size: 1rem;
font-weight: 400; font-weight: 400;
line-height: 1.5; line-height: 1.5;
@ -105,17 +106,17 @@ a, .important {
background-color: #fff; background-color: #fff;
background-clip: padding-box; background-clip: padding-box;
border: 1px solid #ced4da; border: 1px solid #ced4da;
border-radius: .25rem; border-radius: 0.25rem;
} }
.form-label { .form-label {
display: inline-block; display: inline-block;
margin-bottom: .5em; margin-bottom: 0.5em;
} }
.form-result { .form-result {
margin: 30px; margin: 30px;
} }
.form-success { .form-success {
color: #90EE90; color: #90ee90;
} }
.form-error { .form-error {
color: #ff7f7f; color: #ff7f7f;
@ -123,17 +124,18 @@ a, .important {
.btn { .btn {
cursor: pointer; cursor: pointer;
border: 1px solid transparent; border: 1px solid transparent;
padding: .375rem .75rem; padding: 0.375rem 0.75rem;
font-size: 1rem; font-size: 1rem;
line-height: 1.5; line-height: 1.5;
border-radius: .25rem; border-radius: 0.25rem;
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out; transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out,
border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
} }
.btn-lg { .btn-lg {
padding: .5rem 1rem; padding: 0.5rem 1rem;
font-size: 1.25rem; font-size: 1.25rem;
line-height: 1.5; line-height: 1.5;
border-radius: .3rem; border-radius: 0.3rem;
} }
.btn-dark:hover { .btn-dark:hover {
color: #fff; color: #fff;
@ -178,50 +180,56 @@ a, .important {
left: -2.25rem; left: -2.25rem;
width: 1.75rem; width: 1.75rem;
pointer-events: all; pointer-events: all;
border-radius: .5rem; border-radius: 0.5rem;
} }
.custom-control-label::before { .custom-control-label::before {
position: absolute; position: absolute;
top: .25rem; top: 0.25rem;
left: -1.5rem; left: -1.5rem;
display: block; display: block;
width: 1rem; width: 1rem;
height: 1rem; height: 1rem;
pointer-events: none; pointer-events: none;
content: ""; content: '';
background-color: #fff; background-color: #fff;
border: #adb5bd solid 1px; border: #adb5bd solid 1px;
} }
.custom-switch .custom-control-input:checked ~ .custom-control-label::after { .custom-switch .custom-control-input:checked ~ .custom-control-label::after {
background-color: #fff; background-color: #fff;
-webkit-transform: translateX(.75rem); -webkit-transform: translateX(0.75rem);
transform: translateX(.75rem); transform: translateX(0.75rem);
} }
.custom-switch .custom-control-label::after { .custom-switch .custom-control-label::after {
top: calc(.25rem + 2px); top: calc(0.25rem + 2px);
left: calc(-2.25rem + 2px); left: calc(-2.25rem + 2px);
width: calc(1rem - 4px); width: calc(1rem - 4px);
height: calc(1rem - 4px); height: calc(1rem - 4px);
background-color: #adb5bd; background-color: #adb5bd;
border-radius: .5rem; border-radius: 0.5rem;
transition: background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out; transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out,
transition: transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out; box-shadow 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out;
transition: transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out; transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out,
border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out,
border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out,
-webkit-transform 0.15s ease-in-out;
} }
.custom-control-label::after { .custom-control-label::after {
position: absolute; position: absolute;
top: .25rem; top: 0.25rem;
left: -1.5rem; left: -1.5rem;
display: block; display: block;
width: 1rem; width: 1rem;
height: 1rem; height: 1rem;
content: ""; content: '';
background: no-repeat 50%/50% 50%; background: no-repeat 50%/50% 50%;
} }
.table-column { .table-column {
display: grid; display: grid;
} }
.table, th, td { .table,
th,
td {
border: 1px solid var(--text-color); border: 1px solid var(--text-color);
border-collapse: collapse; border-collapse: collapse;
} }

View File

@ -62,11 +62,126 @@
margin-left: -15px; margin-left: -15px;
} }
.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col-13, .col-14, .col-15, .col-16, .col-17, .col-18, .col-19, .col-20, .col-21, .col-22, .col-23, .col-24, .col-1,
.col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-13, .col-sm-14, .col-sm-15, .col-sm-16, .col-sm-17, .col-sm-18, .col-sm-19, .col-sm-20, .col-sm-21, .col-sm-22, .col-sm-23, .col-sm-24, .col-2,
.col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md-13, .col-md-14, .col-md-15, .col-md-16, .col-md-17, .col-md-18, .col-md-19, .col-md-20, .col-md-21, .col-md-22, .col-md-23, .col-md-24, .col-3,
.col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg-13, .col-lg-14, .col-lg-15, .col-lg-16, .col-lg-17, .col-lg-18, .col-lg-19, .col-lg-20, .col-lg-21, .col-lg-22, .col-lg-23, .col-lg-24, .col-4,
.col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl-13, .col-xl-14, .col-xl-15, .col-xl-16, .col-xl-17, .col-xl-18, .col-xl-19, .col-xl-20, .col-xl-21, .col-xl-22, .col-xl-23, .col-xl-24 { .col-5,
.col-6,
.col-7,
.col-8,
.col-9,
.col-10,
.col-11,
.col-12,
.col-13,
.col-14,
.col-15,
.col-16,
.col-17,
.col-18,
.col-19,
.col-20,
.col-21,
.col-22,
.col-23,
.col-24,
.col-sm-1,
.col-sm-2,
.col-sm-3,
.col-sm-4,
.col-sm-5,
.col-sm-6,
.col-sm-7,
.col-sm-8,
.col-sm-9,
.col-sm-10,
.col-sm-11,
.col-sm-12,
.col-sm-13,
.col-sm-14,
.col-sm-15,
.col-sm-16,
.col-sm-17,
.col-sm-18,
.col-sm-19,
.col-sm-20,
.col-sm-21,
.col-sm-22,
.col-sm-23,
.col-sm-24,
.col-md-1,
.col-md-2,
.col-md-3,
.col-md-4,
.col-md-5,
.col-md-6,
.col-md-7,
.col-md-8,
.col-md-9,
.col-md-10,
.col-md-11,
.col-md-12,
.col-md-13,
.col-md-14,
.col-md-15,
.col-md-16,
.col-md-17,
.col-md-18,
.col-md-19,
.col-md-20,
.col-md-21,
.col-md-22,
.col-md-23,
.col-md-24,
.col-lg-1,
.col-lg-2,
.col-lg-3,
.col-lg-4,
.col-lg-5,
.col-lg-6,
.col-lg-7,
.col-lg-8,
.col-lg-9,
.col-lg-10,
.col-lg-11,
.col-lg-12,
.col-lg-13,
.col-lg-14,
.col-lg-15,
.col-lg-16,
.col-lg-17,
.col-lg-18,
.col-lg-19,
.col-lg-20,
.col-lg-21,
.col-lg-22,
.col-lg-23,
.col-lg-24,
.col-xl-1,
.col-xl-2,
.col-xl-3,
.col-xl-4,
.col-xl-5,
.col-xl-6,
.col-xl-7,
.col-xl-8,
.col-xl-9,
.col-xl-10,
.col-xl-11,
.col-xl-12,
.col-xl-13,
.col-xl-14,
.col-xl-15,
.col-xl-16,
.col-xl-17,
.col-xl-18,
.col-xl-19,
.col-xl-20,
.col-xl-21,
.col-xl-22,
.col-xl-23,
.col-xl-24 {
position: relative; position: relative;
width: 100%; width: 100%;
padding-right: 15px; padding-right: 15px;

View File

@ -174,7 +174,8 @@ textarea {
*/ */
button, button,
input { /* 1 */ input {
/* 1 */
overflow: visible; overflow: visible;
} }
@ -184,7 +185,8 @@ input { /* 1 */
*/ */
button, button,
select { /* 1 */ select {
/* 1 */
text-transform: none; text-transform: none;
} }
@ -193,9 +195,9 @@ select { /* 1 */
*/ */
button, button,
[type="button"], [type='button'],
[type="reset"], [type='reset'],
[type="submit"] { [type='submit'] {
-webkit-appearance: button; -webkit-appearance: button;
} }
@ -204,9 +206,9 @@ button,
*/ */
button::-moz-focus-inner, button::-moz-focus-inner,
[type="button"]::-moz-focus-inner, [type='button']::-moz-focus-inner,
[type="reset"]::-moz-focus-inner, [type='reset']::-moz-focus-inner,
[type="submit"]::-moz-focus-inner { [type='submit']::-moz-focus-inner {
border-style: none; border-style: none;
padding: 0; padding: 0;
} }
@ -216,9 +218,9 @@ button::-moz-focus-inner,
*/ */
button:-moz-focusring, button:-moz-focusring,
[type="button"]:-moz-focusring, [type='button']:-moz-focusring,
[type="reset"]:-moz-focusring, [type='reset']:-moz-focusring,
[type="submit"]:-moz-focusring { [type='submit']:-moz-focusring {
outline: 1px dotted ButtonText; outline: 1px dotted ButtonText;
} }
@ -267,8 +269,8 @@ textarea {
* 2. Remove the padding in IE 10. * 2. Remove the padding in IE 10.
*/ */
[type="checkbox"], [type='checkbox'],
[type="radio"] { [type='radio'] {
box-sizing: border-box; /* 1 */ box-sizing: border-box; /* 1 */
padding: 0; /* 2 */ padding: 0; /* 2 */
} }
@ -277,8 +279,8 @@ textarea {
* Correct the cursor style of increment and decrement buttons in Chrome. * Correct the cursor style of increment and decrement buttons in Chrome.
*/ */
[type="number"]::-webkit-inner-spin-button, [type='number']::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button { [type='number']::-webkit-outer-spin-button {
height: auto; height: auto;
} }
@ -287,7 +289,7 @@ textarea {
* 2. Correct the outline style in Safari. * 2. Correct the outline style in Safari.
*/ */
[type="search"] { [type='search'] {
-webkit-appearance: textfield; /* 1 */ -webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */ outline-offset: -2px; /* 2 */
} }
@ -296,7 +298,7 @@ textarea {
* Remove the inner padding in Chrome and Safari on macOS. * Remove the inner padding in Chrome and Safari on macOS.
*/ */
[type="search"]::-webkit-search-decoration { [type='search']::-webkit-search-decoration {
-webkit-appearance: none; -webkit-appearance: none;
} }

View File

@ -4,7 +4,7 @@
position: relative; position: relative;
flex-direction: column; flex-direction: column;
word-wrap: break-word; word-wrap: break-word;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black; border: 1px solid black;
border-radius: 1rem; border-radius: 1rem;
margin-top: 40px; margin-top: 40px;

View File

@ -9,7 +9,7 @@
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
word-wrap: break-word; word-wrap: break-word;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black; border: 1px solid black;
border-radius: 1rem; border-radius: 1rem;
margin-bottom: 50px; margin-bottom: 50px;
@ -27,7 +27,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
word-wrap: break-word; word-wrap: break-word;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black; border: 1px solid black;
border-radius: 1rem; border-radius: 1rem;
margin-top: 40px; margin-top: 40px;

View File

@ -1,7 +1,7 @@
.Chronometer__container { .Chronometer__container {
display: flex; display: flex;
flex-flow: row wrap; flex-flow: row wrap;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black; border: 1px solid black;
border-radius: 1rem; border-radius: 1rem;
padding: 20px; padding: 20px;

View File

@ -1,7 +1,7 @@
.ManageToDo__container { .ManageToDo__container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black; border: 1px solid black;
border-radius: 1rem; border-radius: 1rem;
margin: 40px 40px; margin: 40px 40px;

View File

@ -10,6 +10,6 @@
.Home__image-width { .Home__image-width {
width: 13em; width: 13em;
} }
div[aria-hidden="false"] { div[aria-hidden='false'] {
overflow: hidden !important; overflow: hidden !important;
} }

View File

@ -9,7 +9,7 @@
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
word-wrap: break-word; word-wrap: break-word;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black; border: 1px solid black;
border-radius: 1rem; border-radius: 1rem;
margin-bottom: 50px; margin-bottom: 50px;

View File

@ -5,7 +5,7 @@
} }
.Register-Login__row { .Register-Login__row {
padding: 30px; padding: 30px;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black; border: 1px solid black;
border-radius: 1rem; border-radius: 1rem;
} }

View File

@ -21,7 +21,7 @@ app.prepare().then(() => {
}) })
/* Server */ /* Server */
server.listen(PORT, (error) => { server.listen(PORT, error => {
if (error) throw error if (error) throw error
console.log(`> Ready on http://localhost:${PORT}`) console.log(`> Ready on http://localhost:${PORT}`)
}) })