🎨 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'))
/* Errors Handling */
app.use((_req, _res, next) => errorHandling(next, { statusCode: 404, message: "La route n'existe pas!" })) // 404
app.use((_req, _res, next) =>
errorHandling(next, { statusCode: 404, message: "La route n'existe pas!" })
) // 404
app.use((error, _req, res, _next) => {
console.log(error)
const { statusCode, message } = error
@ -76,8 +78,11 @@ Tasks.belongsTo(Users, { constraints: false })
/* Server */
// sequelize.sync({ force: true })
sequelize.sync()
sequelize
.sync()
.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>
<td align="left" valign="top" style="line-height:150%;font-family:Helvetica;font-size:14px;color:rgb(222, 222, 222);padding:30px;box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25);border: 1px solid black;border-radius: 1rem;">
<h2 style="font-size:22px;line-height:28px;margin:0 0 12px 0;">
La citation que vous avez proposée a été ${(isValid) ? 'validée' : 'supprimée'}.
La citation que vous avez proposée a été ${
isValid
? 'validée'
: 'supprimée'
}.
</h2>
<p style="margin: 0 0 12px 0;">
<a style="color: #ffd800;" href="${frontendLink}/functions/randomQuote">Lien vers la fonction randomQuote de FunctionProject.</a>
</p>
${(!isValid) ? `
${
!isValid
? `
<p style="margin: 0 0 12px 0;">
Si votre citation a été supprimée et vous pensez que c'est une erreur, contactez-moi à cette adresse email : <a style="color: #ffd800;" href="mailto:contact@divlo.fr">contact@divlo.fr</a>.
</p>
` : ''}
`
: ''
}
<div>
<p style="padding:0 0 10px 0">
La citation en question : <br/>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,15 +12,17 @@ const formatNumberResult = require('../secondary/formatNumberResult')
function convertTemperature (degree, unit) {
let temperatureValue = 0
if (unit === '°C') {
temperatureValue = (degree - 32) * 5 / 9
temperatureValue = ((degree - 32) * 5) / 9
} else if (unit === '°F') {
temperatureValue = ((degree * 9 / 5) + 32)
temperatureValue = (degree * 9) / 5 + 32
} else {
return false
}
return {
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
degree = parseFloat(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)

View File

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

View File

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

View File

@ -8,7 +8,10 @@ const { requiredFields } = require('../../config/errors')
* @example reverseString('Hello') 'olleH'
*/
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
// S'il n'y a pas les champs obligatoire
if (!(string)) {
if (!string) {
return errorHandling(next, requiredFields)
}
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()
@ -43,6 +49,10 @@ module.exports = ({ res, next }, argsObject) => {
return res.status(200).json({
isPalindrome: isPalindromeResult,
reverseString: reverseStringResult,
resultHTML: `<p>"${string}" ${(isPalindromeResult) ? 'est' : "n'est pas"} un palindrome car <br/> "${string}" ${(isPalindromeResult) ? '===' : '!=='} "${reverseStringResult}"</p>`
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
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
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
@ -31,20 +38,33 @@ module.exports = async ({ res, next }, argsObject) => {
const urlInDatabase = await shortLinks.findOne({ where: { url } })
if (urlInDatabase) {
const urlShort = `https://short-links.divlo.fr/?q=${urlInDatabase.shortcut}`
return errorHandling(next, { message: `L'url a déjà été raccourcie... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`, statusCode: 400 })
return errorHandling(next, {
message: `L'url a déjà été raccourcie... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`,
statusCode: 400
})
}
// Si le nom du raccourci existe déjà
const shortcutInDatabase = await shortLinks.findOne({ where: { shortcut: shortcutName } })
const shortcutInDatabase = await shortLinks.findOne({
where: { shortcut: shortcutName }
})
if (shortcutInDatabase) {
const urlShort = `https://short-links.divlo.fr/?q=${shortcutInDatabase.shortcut}`
return errorHandling(next, { message: `Le nom du raccourci a déjà été utilisé... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`, statusCode: 400 })
return errorHandling(next, {
message: `Le nom du raccourci a déjà été utilisé... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`,
statusCode: 400
})
}
// Ajout du lien raccourci
const result = await shortLinks.create({ url, shortcut: shortcutName })
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) {
console.log(error)
return errorHandling(next, serverError)

View File

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

View File

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

View File

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

View File

@ -29,28 +29,41 @@ module.exports = ({ res, next }, argsObject) => {
const { numbersList } = argsObject
// S'il n'y a pas les champs obligatoire
if (!(numbersList)) {
if (!numbersList) {
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
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
const LIMIT_ARRAY_LENGTH = 31
if (numbersListArray.length >= LIMIT_ARRAY_LENGTH) {
return errorHandling(next, { message: `Par souci de performance, vous ne pouvez pas exécuter cette fonction avec une liste de nombres dépassant ${LIMIT_ARRAY_LENGTH - 1} nombres.`, statusCode: 400 })
return errorHandling(next, {
message: `Par souci de performance, vous ne pouvez pas exécuter cette fonction avec une liste de nombres dépassant ${LIMIT_ARRAY_LENGTH -
1} nombres.`,
statusCode: 400
})
}
const result = sortArray(numbersListArray)
const resultFormatted = result.map((number) => formatNumberResult(number))
const resultFormatted = result.map(number => formatNumberResult(number))
return res.status(200).json({
result,
resultFormatted,
resultHTML: `<p>La liste de nombres dans l'ordre croissant :<br/> ${resultFormatted.join(', ')}</p>`
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
// S'il n'y a pas les champs obligatoire
if (!(cityName)) {
if (!cityName) {
return errorHandling(next, requiredFields)
}
cityName = cityName.split(' ').join('+')
// Récupère les données météo grâce à l'API : openweathermap.org. (→ avec limite de 50 requêtes par minute)
queue.request(() => {
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${cityName}&lang=fr&units=metric&appid=${WEATHER_API_KEY}`)
.then((response) => {
const json = response.data
const showDateTimeValue = dateTimeUTC((json.timezone / 60 / 60).toString()).showDateTimeValue
const resultHTML = `<p>🌎 Position : <a href="https://www.google.com/maps/search/?api=1&query=${json.coord.lat},${json.coord.lon}" rel="noopener noreferrer" target="_blank">${json.name}, ${json.sys.country}</a><br/>⏰ Date et heure : ${showDateTimeValue} <br/>☁️ Météo : ${capitalize(json.weather[0].description)}<br/>🌡️ Température : ${json.main.temp} °C<br/> 💧 Humidité : ${json.main.humidity}% <br/> <img src="https://openweathermap.org/img/wn/${json.weather[0].icon}@2x.png"/></p>`
return res.status(200).json({ result: json, resultHTML })
})
.catch(() => errorHandling(next, { message: "La ville n'existe pas (dans l'API de openweathermap.org).", statusCode: 404 }))
}, 'everyone', 'weatherRequest')
queue.request(
() => {
axios
.get(
`https://api.openweathermap.org/data/2.5/weather?q=${cityName}&lang=fr&units=metric&appid=${WEATHER_API_KEY}`
)
.then(response => {
const json = response.data
const showDateTimeValue = dateTimeUTC(
(json.timezone / 60 / 60).toString()
).showDateTimeValue
const resultHTML = `<p>🌎 Position : <a href="https://www.google.com/maps/search/?api=1&query=${
json.coord.lat
},${json.coord.lon}" rel="noopener noreferrer" target="_blank">${
json.name
}, ${
json.sys.country
}</a><br/> Date et heure : ${showDateTimeValue} <br/> Météo : ${capitalize(
json.weather[0].description
)}<br/>🌡 Température : ${json.main.temp} °C<br/> 💧 Humidité : ${
json.main.humidity
}% <br/> <img src="https://openweathermap.org/img/wn/${
json.weather[0].icon
}@2x.png"/></p>`
return res.status(200).json({ result: json, resultHTML })
})
.catch(() =>
errorHandling(next, {
message:
"La ville n'existe pas (dans l'API de openweathermap.org).",
statusCode: 404
})
)
},
'everyone',
'weatherRequest'
)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,24 +10,42 @@ const Sequelize = require('sequelize')
exports.getFunctions = async (req, res, next) => {
const categoryId = helperQueryNumber(req.query.categoryId, 0)
let { search } = req.query
try { search = search.toLowerCase() } catch {};
try {
search = search.toLowerCase()
} catch {}
const options = {
where: {
isOnline: 1,
// Trie par catégorie
...(categoryId !== 0) && { categorieId: categoryId },
...(categoryId !== 0 && { categorieId: categoryId }),
// Recherche
...(search != null) && {
...(search != null && {
[Sequelize.Op.or]: [
{ title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', `%${search}%`) },
{ slug: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('slug')), 'LIKE', `%${search}%`) },
{ description: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('description')), 'LIKE', `%${search}%`) }
{
title: Sequelize.where(
Sequelize.fn('LOWER', Sequelize.col('title')),
'LIKE',
`%${search}%`
)
},
{
slug: Sequelize.where(
Sequelize.fn('LOWER', Sequelize.col('slug')),
'LIKE',
`%${search}%`
)
},
{
description: Sequelize.where(
Sequelize.fn('LOWER', Sequelize.col('description')),
'LIKE',
`%${search}%`
)
}
]
}
})
},
include: [
{ model: Categories, attributes: ['name', 'color'] }
],
include: [{ model: Categories, attributes: ['name', 'color'] }],
attributes: {
exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline']
},
@ -43,18 +61,21 @@ exports.getFunctionBySlug = (req, res, next) => {
attributes: {
exclude: ['updatedAt', 'isOnline']
},
include: [
{ model: Categories, attributes: ['name', 'color'] }
]
include: [{ model: Categories, attributes: ['name', 'color'] }]
})
.then((result) => {
.then(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)
})
.catch((error) => {
.catch(error => {
console.log(error)
return errorHandling(next, serverError)
})
@ -65,5 +86,8 @@ exports.executeFunctionBySlug = (req, res, next) => {
if (functionOutput !== undefined) {
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: {
isValidated: 1
},
include: [
{ model: Users, attributes: ['name', 'logo'] }
],
include: [{ model: Users, attributes: ['name', 'logo'] }],
attributes: {
exclude: ['isValidated']
},
@ -27,10 +25,15 @@ exports.postQuote = (req, res, next) => {
return errorHandling(next, requiredFields)
}
Quotes.create({ quote, author, userId: req.userId })
.then((_result) => {
return res.status(200).json({ message: "La citation a bien été ajoutée, elle est en attente de confirmation d'un administrateur." })
.then(_result => {
return res
.status(200)
.json({
message:
"La citation a bien été ajoutée, elle est en attente de confirmation d'un administrateur."
})
})
.catch((error) => {
.catch(error => {
console.log(error)
return errorHandling(next, serverError)
})

View File

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

View File

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

View File

@ -4,19 +4,28 @@ const Users = require('../models/users')
module.exports = (req, _res, next) => {
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 } })
.then((user) => {
.then(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) {
return errorHandling(next, { message: "Vous n'êtes pas administrateur.", statusCode: 403 })
return errorHandling(next, {
message: "Vous n'êtes pas administrateur.",
statusCode: 403
})
}
next()
})
.catch((error) => {
.catch(error => {
console.log(error)
return errorHandling(next, serverError)
})

View File

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

453
api/package-lock.json generated
View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@ const CategoriesRouter = Router()
CategoriesRouter.route('/')
// Récupère les catégories
// Récupère les catégories
.get(categoriesController.getCategories)
module.exports = CategoriesRouter

View File

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

View File

@ -6,13 +6,13 @@ const FavoritesRouter = Router()
FavoritesRouter.route('/:functionId')
// Récupère si une fonction est en favoris (d'un utilisateur)
// Récupère si une fonction est en favoris (d'un utilisateur)
.get(isAuth, favoritesController.getFavoriteByFunctionId)
// Permet à un utilisateur d'ajouter une fonction aux favoris
// Permet à un utilisateur d'ajouter une fonction aux favoris
.post(isAuth, favoritesController.postFavoriteByFunctionId)
// Supprime une fonction des favoris d'un utilisateur
// Supprime une fonction des favoris d'un utilisateur
.delete(isAuth, favoritesController.deleteFavoriteByFunctionId)
module.exports = FavoritesRouter

View File

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

View File

@ -6,10 +6,10 @@ const QuotesRouter = Router()
QuotesRouter.route('/')
// Récupère les citations
// Récupère les citations
.get(quotesController.getQuotes)
// Proposer une citation
// Proposer une citation
.post(isAuth, quotesController.postQuote)
module.exports = QuotesRouter

View File

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

View File

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

View File

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

View File

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

View File

@ -6,19 +6,16 @@ import FunctionArticle from '../FunctionPage/FunctionArticle'
import 'notyf/notyf.min.css'
import '../../public/css/suneditor.min.css'
const SunEditor = dynamic(
() => import('suneditor-react'),
{ ssr: false }
)
const SunEditor = dynamic(() => import('suneditor-react'), { ssr: false })
const EditArticleFunction = (props) => {
const EditArticleFunction = props => {
const [htmlContent, setHtmlContent] = useState('')
const handleEditorChange = (content) => {
const handleEditorChange = content => {
setHtmlContent(content)
}
const handleSave = async (content) => {
const handleSave = async content => {
let Notyf
if (typeof window !== 'undefined') {
Notyf = require('notyf')
@ -27,7 +24,11 @@ const EditArticleFunction = (props) => {
duration: 5000
})
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é!')
} catch {
notyf.error('Erreur!')
@ -36,7 +37,12 @@ const EditArticleFunction = (props) => {
return (
<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} />
</div>
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,17 +4,29 @@ import HeadTag from '../HeadTag'
import FunctionTabsTop from './FunctionTabsTop'
import FunctionComponentTop from './FunctionComponentTop'
const FunctionPage = (props) => {
const FunctionPage = props => {
const [slideIndex, setSlideIndex] = useState(0)
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'>
<FunctionTabsTop slideIndex={slideIndex} setSlideIndex={setSlideIndex} tabNames={props.tabNames} />
<FunctionTabsTop
slideIndex={slideIndex}
setSlideIndex={setSlideIndex}
tabNames={props.tabNames}
/>
<FunctionComponentTop {...props} />
<props.FunctionTabManager {...props} slideIndex={slideIndex} setSlideIndex={setSlideIndex} />
<props.FunctionTabManager
{...props}
slideIndex={slideIndex}
setSlideIndex={setSlideIndex}
/>
</div>
</>
)

View File

@ -1,41 +1,41 @@
.FunctionTabs__nav {
display: flex;
flex-wrap: wrap;
padding-left: 0;
margin-bottom: 0;
list-style: none;
border-bottom: 1px solid #d9e2ef;
margin-bottom: -1px;
margin-top: 30px;
display: flex;
flex-wrap: wrap;
padding-left: 0;
margin-bottom: 0;
list-style: none;
border-bottom: 1px solid #d9e2ef;
margin-bottom: -1px;
margin-top: 30px;
}
.FunctionTabs__nav-item {
margin-bottom: -1px;
cursor: pointer;
margin-bottom: -1px;
cursor: pointer;
}
.FunctionTabs__nav-link {
color: var(--text-color);
border: 1px solid #0c0b0b38;
border-bottom: 0px;
border-top-left-radius: .375rem;
border-top-right-radius: .375rem;
display: block;
padding: .5rem 1rem;
transition: .2s;
color: var(--text-color);
border: 1px solid #0c0b0b38;
border-bottom: 0px;
border-top-left-radius: 0.375rem;
border-top-right-radius: 0.375rem;
display: block;
padding: 0.5rem 1rem;
transition: 0.2s;
}
.FunctionTabs__nav-link-active {
border-color: #d9e2ef #d9e2ef #fff;
color: var(--important);
border-color: #d9e2ef #d9e2ef #fff;
color: var(--important);
}
.FunctionTabs__nav-link:hover {
border-color: #f1f4f8 #f1f4f8 #d9e2ef;
text-decoration: none;
border-color: #f1f4f8 #f1f4f8 #d9e2ef;
text-decoration: none;
}
@media (max-width: 490px) {
.FunctionTabs__nav {
flex-direction: column;
}
.FunctionTabs__nav-link {
border-color: #f1f4f8 #f1f4f8 #d9e2ef;
}
.FunctionTabs__nav {
flex-direction: column;
}
.FunctionTabs__nav-link {
border-color: #f1f4f8 #f1f4f8 #d9e2ef;
}
}

View File

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

View File

@ -1,4 +1,4 @@
const FunctionTabsTop = (props) => {
const FunctionTabsTop = props => {
return (
<div className='container'>
<div className='row justify-content-center'>
@ -7,7 +7,11 @@ const FunctionTabsTop = (props) => {
return (
<li key={index} className='FunctionTabs__nav-item'>
<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)}
>
{tabName}

View File

@ -1,40 +1,40 @@
.Functions__title {
padding: 20px 0 20px 0;
margin-bottom: 0;
padding: 20px 0 20px 0;
margin-bottom: 0;
}
.Functions__form-control {
display: block;
height: calc(1.5em + .75rem + 2px);
padding: .375rem .75rem;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: .5em;
display: block;
height: calc(1.5em + 0.75rem + 2px);
padding: 0.375rem 0.75rem;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: 0.5em;
}
.Functions__search-container {
margin-bottom: 50px;
margin-bottom: 50px;
}
.Functions__select-option {
color: rgb(221, 220, 220);
color: rgb(221, 220, 220);
}
.Functions__search-input {
width: 40%;
width: 40%;
}
/* col-sm */
@media (max-width: 576px) {
.Functions__search-container {
flex-direction: column;
align-items: center;
}
.Functions__select {
width: 90%;
margin-bottom: 5px;
}
.Functions__search-input {
width: 90%;
}
.Functions__search-container {
flex-direction: column;
align-items: center;
}
.Functions__select {
width: 90%;
margin-bottom: 5px;
}
.Functions__search-input {
width: 90%;
}
}

View File

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

View File

@ -36,7 +36,8 @@ const HeadTag = ({ title, image, description }) => (
HeadTag.defaultProps = {
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'
}

View File

@ -1,161 +1,167 @@
/* HEADER */
.Header {
position: fixed;
width: 100%;
top: 0;
left: 0;
right: 0;
z-index: 100;
position: fixed;
width: 100%;
top: 0;
left: 0;
right: 0;
z-index: 100;
display: flex;
flex-flow: row wrap;
align-items: center;
justify-content: space-between;
padding: .5rem 1rem;
display: flex;
flex-flow: row wrap;
align-items: center;
justify-content: space-between;
padding: 0.5rem 1rem;
border-bottom: var(--border-header-footer);
background-color: var(--background-color);
border-bottom: var(--border-header-footer);
background-color: var(--background-color);
}
@media (min-width: 992px) {
.Header {
flex-flow: row nowrap;
justify-content: flex-start;
}
.Header {
flex-flow: row nowrap;
justify-content: flex-start;
}
}
.Header > .container {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
}
@media (min-width: 992px) {
.Header > .container {
flex-wrap: nowrap;
}
.Header > .container {
flex-wrap: nowrap;
}
}
/* Brand */
.Header__brand-link {
display: inline-block;
padding-top: .3125rem;
padding-bottom: .3125rem;
margin-right: 1rem;
font-size: 1.25rem;
line-height: inherit;
white-space: nowrap;
display: inline-block;
padding-top: 0.3125rem;
padding-bottom: 0.3125rem;
margin-right: 1rem;
font-size: 1.25rem;
line-height: inherit;
white-space: nowrap;
}
#brand-link__logo-small-screen {
display: none;
display: none;
}
@media (max-width: 496px) {
#brand-link__logo {
display: none;
}
.Header__brand-link {
width: 30%;
}
#brand-link__logo-small-screen {
display: inline-block;
}
#brand-link__logo {
display: none;
}
.Header__brand-link {
width: 30%;
}
#brand-link__logo-small-screen {
display: inline-block;
}
}
@media (min-width: 992px) {
.Header .Header__navbar {
display: flex;
flex-basis: auto;
}
.Header .Header__navbar {
display: flex;
flex-basis: auto;
}
}
.Header__navbar {
flex-basis: 100%;
flex-grow: 1;
align-items: center;
flex-basis: 100%;
flex-grow: 1;
align-items: center;
}
.navbar__list {
display: flex;
flex-direction: row;
margin-left: auto;
display: flex;
flex-direction: row;
margin-left: auto;
}
.navbar__list.navbar__list-active {
margin: 0 !important;
display: flex;
margin: 0 !important;
display: flex;
}
@media (max-width: 992px) {
.navbar__list {
display: none;
flex-direction: column;
align-items: center;
padding-left: 0;
list-style: none;
}
.navbar__list {
display: none;
flex-direction: column;
align-items: center;
padding-left: 0;
list-style: none;
}
}
.navbar-link {
display: block;
padding: .5rem 1rem;
display: block;
padding: 0.5rem 1rem;
}
/* Details Styling */
.navbar-link:hover {
text-decoration: none;
color: rgba(255, 255, 255, .75);
text-decoration: none;
color: rgba(255, 255, 255, 0.75);
}
.navbar-link, .navbar-link-active {
color: rgba(255, 255, 255, .5);
.navbar-link,
.navbar-link-active {
color: rgba(255, 255, 255, 0.5);
}
.navbar-link-active, .navbar-link-active:hover, .Header__brand-link {
color: var(--text-color);
.navbar-link-active,
.navbar-link-active:hover,
.Header__brand-link {
color: var(--text-color);
}
.navbar-item {
list-style: none;
list-style: none;
}
.navbar-link {
font-size: 16px;
padding: .5rem;
font-size: 16px;
padding: 0.5rem;
}
/* Hamburger Icon */
.Header__hamburger {
display: none;
width: 56px;
height: 40px;
cursor: pointer;
background-color: transparent;
border: 1px solid rgba(255, 255, 255, .1);
border-radius: .25rem;
position: relative;
display: none;
width: 56px;
height: 40px;
cursor: pointer;
background-color: transparent;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 0.25rem;
position: relative;
}
.Header__hamburger > span, .Header__hamburger > span::before, .Header__hamburger > span::after {
position: absolute;
width: 22px;
height: 1.3px;
background-color: rgba(255, 255, 255);
.Header__hamburger > span,
.Header__hamburger > span::before,
.Header__hamburger > span::after {
position: absolute;
width: 22px;
height: 1.3px;
background-color: rgba(255, 255, 255);
}
.Header__hamburger > span {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
transition: background-color .3s ease-in-out;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
transition: background-color 0.3s ease-in-out;
}
.Header__hamburger > span::before, .Header__hamburger > span::after {
content: '';
transition: transform .3s ease-in-out;
.Header__hamburger > span::before,
.Header__hamburger > span::after {
content: '';
transition: transform 0.3s ease-in-out;
}
.Header__hamburger > span::before {
transform: translateY(-8px);
transform: translateY(-8px);
}
.Header__hamburger > span::after {
transform: translateY(8px);
transform: translateY(8px);
}
.Header__hamburger-active span {
background-color: transparent;
background-color: transparent;
}
.Header__hamburger-active > span::before {
transform: translateY(0px) rotateZ(45deg);
transform: translateY(0px) rotateZ(45deg);
}
.Header__hamburger-active > span::after {
transform: translateY(0px) rotateZ(-45deg);
transform: translateY(0px) rotateZ(-45deg);
}
/* Apparition du hamburger */
@media (max-width: 992px) {
.Header__hamburger {
display: flex;
}
.Header__hamburger {
display: flex;
}
}

View File

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

View File

@ -8,7 +8,11 @@ export default function NavigationLink (props) {
return (
<li className='navbar-item'>
<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}
</a>
</Link>

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,236 +1,244 @@
/* GENERAL */
:root {
--border-header-footer: 3px rgba(255, 255, 255, .7) solid;
--background-color: #181818;
--text-color: rgb(222, 222, 222);
--important: #ffd800;
--border-header-footer: 3px rgba(255, 255, 255, 0.7) solid;
--background-color: #181818;
--text-color: rgb(222, 222, 222);
--important: #ffd800;
}
html {
line-height: initial;
line-height: initial;
}
body {
background-color: var(--background-color);
background-color: var(--background-color);
}
#__next {
min-height: 100vh;
display: flex;
flex-flow: column wrap;
background-color: var(--background-color);
color: var(--text-color);
font-family: 'Montserrat', sans-serif;
font-weight: 400;
font-size: 18px;
padding-top: 99px; /* Height of the Header */
min-height: 100vh;
display: flex;
flex-flow: column wrap;
background-color: var(--background-color);
color: var(--text-color);
font-family: 'Montserrat', sans-serif;
font-weight: 400;
font-size: 18px;
padding-top: 99px; /* Height of the Header */
}
p {
font-size: 18px;
line-height: 1.9;
font-size: 18px;
line-height: 1.9;
}
a:hover {
text-decoration: underline;
text-decoration: underline;
}
/* CONTENT */
.content {
flex-grow: 1;
display: flex;
flex-grow: 1;
display: flex;
}
.centeredContainer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
div[aria-hidden="true"] > * {
display: none;
div[aria-hidden='true'] > * {
display: none;
}
.notyf__message {
font-family: sans-serif;
font-family: sans-serif;
}
/* LOADING */
.isLoading {
display: none;
display: none;
}
#preloader {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
@keyframes Loader__spin {
0% {
animation-timing-function: cubic-bezier(0.5856, 0.0703, 0.4143, 0.9297);
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
0% {
animation-timing-function: cubic-bezier(0.5856, 0.0703, 0.4143, 0.9297);
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* UTILITIES */
.text-center {
text-align: center;
word-break: break-word;
text-align: center;
word-break: break-word;
}
.justify-content-center {
justify-content: center;
justify-content: center;
}
.align-items-center {
align-items: center;
align-items: center;
}
.title-important {
color: var(--important);
font-weight: 500;
color: var(--important);
font-weight: 500;
}
a, .important {
color: var(--important);
text-decoration: none;
a,
.important {
color: var(--important);
text-decoration: none;
}
.d-none {
display: none !important;
display: none !important;
}
.form-group {
margin-top: 1.4rem;
margin-top: 1.4rem;
}
.form-control {
display: block;
width: 100%;
height: calc(1.5em + .75rem + 2px);
padding: .375rem .75rem;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: .25rem;
display: block;
width: 100%;
height: calc(1.5em + 0.75rem + 2px);
padding: 0.375rem 0.75rem;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: 0.25rem;
}
.form-label {
display: inline-block;
margin-bottom: .5em;
display: inline-block;
margin-bottom: 0.5em;
}
.form-result {
margin: 30px;
margin: 30px;
}
.form-success {
color: #90EE90;
color: #90ee90;
}
.form-error {
color: #ff7f7f;
color: #ff7f7f;
}
.btn {
cursor: pointer;
border: 1px solid transparent;
padding: .375rem .75rem;
font-size: 1rem;
line-height: 1.5;
border-radius: .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;
cursor: pointer;
border: 1px solid transparent;
padding: 0.375rem 0.75rem;
font-size: 1rem;
line-height: 1.5;
border-radius: 0.25rem;
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 {
padding: .5rem 1rem;
font-size: 1.25rem;
line-height: 1.5;
border-radius: .3rem;
padding: 0.5rem 1rem;
font-size: 1.25rem;
line-height: 1.5;
border-radius: 0.3rem;
}
.btn-dark:hover {
color: #fff;
background-color: #23272b;
border-color: #1d2124;
color: #fff;
background-color: #23272b;
border-color: #1d2124;
}
.btn-dark {
color: #fff;
background-color: #343a40;
border-color: #343a40;
color: #fff;
background-color: #343a40;
border-color: #343a40;
}
.btn-primary:hover {
color: #fff;
background-color: #0069d9;
border-color: #0062cc;
color: #fff;
background-color: #0069d9;
border-color: #0062cc;
}
.btn-primary {
color: #fff;
background-color: #007bff;
border-color: #007bff;
color: #fff;
background-color: #007bff;
border-color: #007bff;
}
.custom-control {
display: flex;
justify-content: center;
display: flex;
justify-content: center;
}
.custom-control-input {
position: absolute;
z-index: -1;
opacity: 0;
position: absolute;
z-index: -1;
opacity: 0;
}
.custom-control-label {
position: relative;
margin-bottom: 0;
vertical-align: top;
position: relative;
margin-bottom: 0;
vertical-align: top;
}
.custom-control-input:checked~.custom-control-label::before {
color: #fff;
border-color: #007bff;
background-color: #007bff;
.custom-control-input:checked ~ .custom-control-label::before {
color: #fff;
border-color: #007bff;
background-color: #007bff;
}
.custom-switch .custom-control-label::before {
left: -2.25rem;
width: 1.75rem;
pointer-events: all;
border-radius: .5rem;
left: -2.25rem;
width: 1.75rem;
pointer-events: all;
border-radius: 0.5rem;
}
.custom-control-label::before {
position: absolute;
top: .25rem;
left: -1.5rem;
display: block;
width: 1rem;
height: 1rem;
pointer-events: none;
content: "";
background-color: #fff;
border: #adb5bd solid 1px;
position: absolute;
top: 0.25rem;
left: -1.5rem;
display: block;
width: 1rem;
height: 1rem;
pointer-events: none;
content: '';
background-color: #fff;
border: #adb5bd solid 1px;
}
.custom-switch .custom-control-input:checked~.custom-control-label::after {
background-color: #fff;
-webkit-transform: translateX(.75rem);
transform: translateX(.75rem);
.custom-switch .custom-control-input:checked ~ .custom-control-label::after {
background-color: #fff;
-webkit-transform: translateX(0.75rem);
transform: translateX(0.75rem);
}
.custom-switch .custom-control-label::after {
top: calc(.25rem + 2px);
left: calc(-2.25rem + 2px);
width: calc(1rem - 4px);
height: calc(1rem - 4px);
background-color: #adb5bd;
border-radius: .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: transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .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;
top: calc(0.25rem + 2px);
left: calc(-2.25rem + 2px);
width: calc(1rem - 4px);
height: calc(1rem - 4px);
background-color: #adb5bd;
border-radius: 0.5rem;
transition: 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;
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 {
position: absolute;
top: .25rem;
left: -1.5rem;
display: block;
width: 1rem;
height: 1rem;
content: "";
background: no-repeat 50%/50% 50%;
position: absolute;
top: 0.25rem;
left: -1.5rem;
display: block;
width: 1rem;
height: 1rem;
content: '';
background: no-repeat 50%/50% 50%;
}
.table-column {
display: grid;
display: grid;
}
.table, th, td {
border: 1px solid var(--text-color);
border-collapse: collapse;
.table,
th,
td {
border: 1px solid var(--text-color);
border-collapse: collapse;
}
.table-row {
padding: 15px;
padding: 15px;
}
.vscode-editor {
padding: 15px;
border-radius: 10px;
overflow: auto;
white-space: normal !important;
padding: 15px;
border-radius: 10px;
overflow: auto;
white-space: normal !important;
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,81 +1,81 @@
/* Make clicks pass-through */
#nprogress {
pointer-events: none;
pointer-events: none;
}
#nprogress .bar {
background: var(--important);
background: var(--important);
position: fixed;
z-index: 1031;
top: 0;
left: 0;
position: fixed;
z-index: 1031;
top: 0;
left: 0;
width: 100%;
height: 2px;
width: 100%;
height: 2px;
}
/* Fancy blur effect */
#nprogress .peg {
display: block;
position: absolute;
right: 0px;
width: 100px;
height: 100%;
box-shadow: 0 0 10px var(--important), 0 0 5px var(--important);
opacity: 1;
display: block;
position: absolute;
right: 0px;
width: 100px;
height: 100%;
box-shadow: 0 0 10px var(--important), 0 0 5px var(--important);
opacity: 1;
-webkit-transform: rotate(3deg) translate(0px, -4px);
-ms-transform: rotate(3deg) translate(0px, -4px);
transform: rotate(3deg) translate(0px, -4px);
-webkit-transform: rotate(3deg) translate(0px, -4px);
-ms-transform: rotate(3deg) translate(0px, -4px);
transform: rotate(3deg) translate(0px, -4px);
}
/* Remove these to get rid of the spinner */
#nprogress .spinner {
display: block;
position: fixed;
z-index: 1031;
top: 15px;
right: 15px;
display: block;
position: fixed;
z-index: 1031;
top: 15px;
right: 15px;
}
#nprogress .spinner-icon {
width: 18px;
height: 18px;
box-sizing: border-box;
width: 18px;
height: 18px;
box-sizing: border-box;
border: solid 2px transparent;
border-top-color: var(--important);
border-left-color: var(--important);
border-radius: 50%;
border: solid 2px transparent;
border-top-color: var(--important);
border-left-color: var(--important);
border-radius: 50%;
-webkit-animation: nprogress-spinner 400ms linear infinite;
animation: nprogress-spinner 400ms linear infinite;
-webkit-animation: nprogress-spinner 400ms linear infinite;
animation: nprogress-spinner 400ms linear infinite;
}
.nprogress-custom-parent {
overflow: hidden;
position: relative;
overflow: hidden;
position: relative;
}
.nprogress-custom-parent #nprogress .spinner,
.nprogress-custom-parent #nprogress .bar {
position: absolute;
position: absolute;
}
@-webkit-keyframes nprogress-spinner {
0% {
-webkit-transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
}
0% {
-webkit-transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
}
}
@keyframes nprogress-spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View File

@ -1,7 +1,7 @@
.Error404__container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-width: 100%;
}

View File

@ -1,34 +1,34 @@
.FunctionComponent__top {
display: flex;
align-items: center;
position: relative;
flex-direction: column;
word-wrap: break-word;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25);
border: 1px solid black;
border-radius: 1rem;
margin-top: 40px;
display: flex;
align-items: center;
position: relative;
flex-direction: column;
word-wrap: break-word;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black;
border-radius: 1rem;
margin-top: 40px;
}
.FunctionComponent__title {
margin: 0;
margin: 0;
}
.FunctionComponent__description {
word-break: break-all;
margin-bottom: 0;
word-break: break-all;
margin-bottom: 0;
}
.FunctionComponent__slide {
margin-top: 30px;
margin-top: 30px;
}
.FunctionComponent__image {
width: 150px;
width: 150px;
}
.FunctionComponent__star-favorite {
color: var(--important);
width: 2em !important;
height: 2em !important;
position: absolute;
right: 0;
top: 15px;
margin-right: 15px;
cursor: pointer;
color: var(--important);
width: 2em !important;
height: 2em !important;
position: absolute;
right: 0;
top: 15px;
margin-right: 15px;
cursor: pointer;
}

View File

@ -1,35 +1,35 @@
.Admin__Modal__container {
display: flex;
flex-direction: column;
justify-content: center;
margin: 30px 0 0 0;
display: flex;
flex-direction: column;
justify-content: center;
margin: 30px 0 0 0;
}
.Admin__Modal__row {
display: flex;
align-items: center;
flex-direction: column;
word-wrap: break-word;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25);
border: 1px solid black;
border-radius: 1rem;
margin-bottom: 50px;
display: flex;
align-items: center;
flex-direction: column;
word-wrap: break-word;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black;
border-radius: 1rem;
margin-bottom: 50px;
}
.Admin__Modal-top-container {
margin: 20px 0;
margin: 20px 0;
}
.Admin__Modal-select-option {
color: rgb(221, 220, 220);
color: rgb(221, 220, 220);
}
.Admin__Function-slide {
margin-top: 40px;
margin-top: 40px;
}
.Admin__Input-group {
display: flex;
flex-direction: column;
word-wrap: break-word;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25);
border: 1px solid black;
border-radius: 1rem;
margin-top: 40px;
padding: 40px;
display: flex;
flex-direction: column;
word-wrap: break-word;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black;
border-radius: 1rem;
margin-top: 40px;
padding: 40px;
}

View File

@ -1,38 +1,38 @@
.Chronometer__container {
display: flex;
flex-flow: row wrap;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25);
border: 1px solid black;
border-radius: 1rem;
padding: 20px;
margin-bottom: 40px;
display: flex;
flex-flow: row wrap;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black;
border-radius: 1rem;
padding: 20px;
margin-bottom: 40px;
}
.Chronometer__item {
width: 100%;
width: 100%;
}
.Chronomter__row {
margin: 8px 0 16px 0;
text-align: center;
margin: 8px 0 16px 0;
text-align: center;
}
.Chronometer__time-left {
font-size: 3rem;
font-weight: 700;
font-size: 3rem;
font-weight: 700;
}
.Chronometer__buttons {
border-top: solid 3px var(--important);
padding-top: 12px;
border-top: solid 3px var(--important);
padding-top: 12px;
}
.Chronometer__row-button {
display: flex;
justify-content: center;
display: flex;
justify-content: center;
}
.Chronometer-btn {
color: var(--text-color);
cursor: pointer;
background-color: transparent;
border: none;
outline: none;
margin-left: 14px;
margin-right: 14px;
width: 2em;
color: var(--text-color);
cursor: pointer;
background-color: transparent;
border: none;
outline: none;
margin-left: 14px;
margin-right: 14px;
width: 2em;
}

View File

@ -1,20 +1,20 @@
.Product__image {
max-width: 150px;
max-width: 150px;
}
.Price__result {
height: 50px;
margin: 5px 0 10px 0;
display: flex;
align-items: center;
padding-left: 10px;
color: #fff;
height: 50px;
margin: 5px 0 10px 0;
display: flex;
align-items: center;
padding-left: 10px;
color: #fff;
}
.Price__result-success {
background-color: #28a745;
background-color: #28a745;
}
.Price__result-plus {
background-color: #dc3545;
background-color: #dc3545;
}
.Price__result-moins {
background-color: #343a40;
background-color: #343a40;
}

View File

@ -1,34 +1,34 @@
.ManageToDo__container {
display: flex;
flex-direction: column;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25);
border: 1px solid black;
border-radius: 1rem;
margin: 40px 40px;
display: flex;
flex-direction: column;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black;
border-radius: 1rem;
margin: 40px 40px;
}
.ManageToDo__list {
overflow: hidden;
overflow: hidden;
}
.ManageToDo__list-item {
display: flex;
justify-content: space-between;
margin: 25px 0;
display: flex;
justify-content: space-between;
margin: 25px 0;
}
.ManageToDo__list-item.isCompleted {
text-decoration: line-through;
text-decoration: line-through;
}
.ManageToDo__task-btn {
color: var(--text-color);
cursor: pointer;
background-color: transparent;
border: none;
outline: none;
margin-left: 7px;
margin-right: 7px;
width: 1.75em;
color: var(--text-color);
cursor: pointer;
background-color: transparent;
border: none;
outline: none;
margin-left: 7px;
margin-right: 7px;
width: 1.75em;
}
.ManageToDo__list-item-span {
width: calc(100% - 120px);
overflow: hidden;
text-overflow: ellipsis;
width: calc(100% - 120px);
overflow: hidden;
text-overflow: ellipsis;
}

View File

@ -1,15 +1,15 @@
.Home__container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.Home__logo-spin {
cursor: pointer;
cursor: pointer;
}
.Home__image-width {
width: 13em;
width: 13em;
}
div[aria-hidden="false"] {
overflow: hidden !important;
div[aria-hidden='false'] {
overflow: hidden !important;
}

View File

@ -1,28 +1,28 @@
.Profile__container {
display: flex;
flex-direction: column;
justify-content: center;
margin: 30px 0 0 0;
display: flex;
flex-direction: column;
justify-content: center;
margin: 30px 0 0 0;
}
.Profile__row {
display: flex;
align-items: center;
flex-direction: column;
word-wrap: break-word;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25);
border: 1px solid black;
border-radius: 1rem;
margin-bottom: 50px;
display: flex;
align-items: center;
flex-direction: column;
word-wrap: break-word;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black;
border-radius: 1rem;
margin-bottom: 50px;
}
.Profile__logo {
border-radius: 50%;
object-fit: cover;
width: 150px;
height: 150px;
border-radius: 50%;
object-fit: cover;
width: 150px;
height: 150px;
}
.Profile__comment {
margin: 0 0 50px 0;
margin: 0 0 50px 0;
}
.Profile__Modal-top-container {
margin: 20px 0;
margin: 20px 0;
}

View File

@ -1,22 +1,22 @@
.Register-Login__container {
display: flex;
flex-direction: column;
justify-content: center;
display: flex;
flex-direction: column;
justify-content: center;
}
.Register-Login__row {
padding: 30px;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25);
border: 1px solid black;
border-radius: 1rem;
padding: 30px;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black;
border-radius: 1rem;
}
.Register-Login__title {
text-align: center;
text-align: center;
}
.Register-Login__Forgot-password {
color: var(--text-color);
font-size: 16px;
color: var(--text-color);
font-size: 16px;
}
.Register-Login__Forgot-password:hover {
color: var(--important);
text-decoration: none;
color: var(--important);
text-decoration: none;
}

View File

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