From 1b4a7a1c09863e063e946cc0641d257be36d55ac Mon Sep 17 00:00:00 2001 From: Divlo Date: Tue, 17 Mar 2020 12:02:12 +0100 Subject: [PATCH] =?UTF-8?q?backend:=20Premi=C3=A8re=20route=20/functions/:?= =?UTF-8?q?functionName=20+=20Gestion=20des=20erreurs=20404=20et=20500?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 - backend/app.js | 20 ++- backend/assets/config/config.js | 6 +- backend/assets/config/errors.js | 18 +++ backend/assets/functions/functionObject.js | 24 ++++ .../main/convertRomanArabicNumbers.js | 109 ++++++++++++++ backend/assets/functions/main/randomNumber.js | 32 +++++ backend/assets/utils/sendResponse.js | 2 +- backend/controllers/functions.js | 10 ++ backend/package-lock.json | 135 ++++++++++++++++++ backend/package.json | 3 +- backend/routes/functions.js | 11 ++ 12 files changed, 360 insertions(+), 12 deletions(-) create mode 100644 backend/assets/config/errors.js create mode 100644 backend/assets/functions/functionObject.js create mode 100644 backend/assets/functions/main/convertRomanArabicNumbers.js create mode 100644 backend/assets/functions/main/randomNumber.js create mode 100644 backend/controllers/functions.js create mode 100644 backend/routes/functions.js diff --git a/README.md b/README.md index bfe9fb2..42156ba 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,5 @@ Le projet est disponible sur [function.divlo.fr](https://function.divlo.fr/). -[![FunctionProject](./img/FunctionProject.png)](https://function.divlo.fr/) - ## Licence Ce projet est sous licence MIT - voir le fichier [LICENSE](./LICENSE) pour plus de détails. \ No newline at end of file diff --git a/backend/app.js b/backend/app.js index b37bf90..74dadf6 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1,19 +1,27 @@ /* Modules */ -const express = require('express'); -const cors = require('cors'); -const morgan = require('morgan'); +const express = require('express'); +const helmet = require('helmet'); +const cors = require('cors'); +const morgan = require('morgan'); /* Files Imports & Variables */ -const { PORT } = require('./assets/config/config'); -const app = express(); +const { PORT } = require('./assets/config/config'); +const { serverError } = require('./assets/config/errors'); +const sendResponse = require('./assets/utils/sendResponse'); +const app = express(); /* Middlewares */ +app.use(helmet()); app.use(cors()); app.use(morgan('dev')); app.use(express.json()); /* Routes */ -app.get('/', (_req, res) => res.send('Hello world!')); +app.use('/functions', require('./routes/functions')); + +/* Errors Handling */ +app.use((_req, res, _next) => sendResponse(res, { result: "La route n'existe pas!", httpStatus: 404 })); // 404 +app.use((error, _req, res, _next) => { console.log(error); return sendResponse(res, serverError); }); // 500 /* Server */ app.listen(PORT, () => console.log('\x1b[36m%s\x1b[0m', `Started on port ${PORT}.`)); \ No newline at end of file diff --git a/backend/assets/config/config.js b/backend/assets/config/config.js index e6d1f3a..48ec1d3 100644 --- a/backend/assets/config/config.js +++ b/backend/assets/config/config.js @@ -1,3 +1,5 @@ -module.exports = { +const config = { PORT: process.env.PORT || 8080 -}; \ No newline at end of file +}; + +module.exports = config; \ No newline at end of file diff --git a/backend/assets/config/errors.js b/backend/assets/config/errors.js new file mode 100644 index 0000000..f7f7500 --- /dev/null +++ b/backend/assets/config/errors.js @@ -0,0 +1,18 @@ +const errors = { + generalError: { + result: "Vous n'avez pas rentré de valeur valide.", + httpStatus: 400 + }, + + serverError: { + result: "Le serveur n'a pas pu traiter votre requête.", + httpStatus: 500 + }, + + requiredFields: { + result: "Vous devez remplir tous les champs...", + httpStatus: 400 + } +}; + +module.exports = errors; \ No newline at end of file diff --git a/backend/assets/functions/functionObject.js b/backend/assets/functions/functionObject.js new file mode 100644 index 0000000..daa683e --- /dev/null +++ b/backend/assets/functions/functionObject.js @@ -0,0 +1,24 @@ +const { randomNumberOutput } = require('./main/randomNumber'); +const { convertArabicToRomanOutput, convertRomanToArabicOutput } = require('./main/convertRomanArabicNumbers'); + +const functionObject = { + randomNumber: { + functionOutput: randomNumberOutput, + args: ["min", "max"] + }, + convertArabicToRoman: { + functionOutput: convertArabicToRomanOutput, + args: ["number"] + }, + convertRomanToArabic: { + functionOutput: convertRomanToArabicOutput, + args: ["romanNumber"] + } +}; + +// Choisi la fonction à exécuter +function functionToExecute(option) { + return functionObject[option]; +} + +module.exports = functionToExecute; \ No newline at end of file diff --git a/backend/assets/functions/main/convertRomanArabicNumbers.js b/backend/assets/functions/main/convertRomanArabicNumbers.js new file mode 100644 index 0000000..de21aa1 --- /dev/null +++ b/backend/assets/functions/main/convertRomanArabicNumbers.js @@ -0,0 +1,109 @@ +const sendResponse = require('../../utils/sendResponse'); +const { requiredFields, generalError } = require('../../config/errors'); + +/* Variable pour convertRomanArabicNumbers */ +const correspondancesRomainArabe = [ + [1000, "M"], + [900, "CM"], + [500, "D"], + [400, "CD"], + [100, "C"], + [90, "XC"], + [50, "L"], + [40, "XL"], + [10, "X"], + [9, "IX"], + [5, "V"], + [4, "IV"], + [1, "I"], +]; + +/** + * @description Convertis un nombre arabe en nombre romain. + * @requires {@link correspondancesRomainArabe} + * @param {Number} nombre - Le nombre arabe à convertir + * @returns {String} + * @examples convertArabicToRoman(24) → 'XXIV' + */ +function convertArabicToRoman(nombre) { + // Initialisation de la variable qui va contenir le résultat de la conversion + let chiffresRomains = ""; + + function extraireChiffreRomain(valeurLettre, lettres) { + while (nombre >= valeurLettre) { + chiffresRomains = chiffresRomains + lettres; + nombre = nombre - valeurLettre; + } + } + + correspondancesRomainArabe.forEach(correspondance => { + extraireChiffreRomain(correspondance[0], correspondance[1]); + }); + + return chiffresRomains; +} + +/** + * @description Convertis un nombre romain en nombre arabe. + * @requires {@link correspondancesRomainArabe} + * @param {String} str - Le nombre romain à convertir + * @returns {Number} + * @examples convertRomanToArabic('XXIV') → 24 + */ +function convertRomanToArabic(str) { + let result = 0; + for (let i = 0;i < correspondancesRomainArabe.length; i++) { + while (str.indexOf(correspondancesRomainArabe[i][1]) === 0) { + // Ajout de la valeur décimale au résultat + result += correspondancesRomainArabe[i][0]; + // Supprimer la lettre romaine correspondante du début + str = str.replace(correspondancesRomainArabe[i][1],''); + } + } + if (str != '') { + result = 0; + } + return result; +} + +/* OUTPUTS */ +exports.convertRomanToArabicOutput = (res, argsObject) => { + let { romanNumber } = argsObject; + + // S'il n'y a pas les champs obligatoire + if (!(romanNumber)) { + return sendResponse(res, requiredFields); + } + + // Formate le paramètre + try { + romanNumber = romanNumber.toUpperCase(); + } + catch { + return sendResponse(res, generalError); + } + + const result = convertRomanToArabic(romanNumber); + if (result === 0) { + return sendResponse(res, generalError); + } + + return sendResponse(res, { result, httpStatus: 200 }, true); +} + +exports.convertArabicToRomanOutput = (res, argsObject) => { + let { number } = argsObject; + number = parseInt(number); + + // S'il n'y a pas les champs obligatoire + if (!(number)) { + return sendResponse(res, requiredFields); + } + + // Si ce n'est pas un nombre + if (isNaN(number)) { + return sendResponse(res, { result: "Veuillez rentré un nombre valide.", httpStatus: 400 }); + } + + return sendResponse(res, { result: convertArabicToRoman(number) }, true); +} \ No newline at end of file diff --git a/backend/assets/functions/main/randomNumber.js b/backend/assets/functions/main/randomNumber.js new file mode 100644 index 0000000..4493aa2 --- /dev/null +++ b/backend/assets/functions/main/randomNumber.js @@ -0,0 +1,32 @@ +const sendResponse = require('../../utils/sendResponse'); +const { requiredFields } = require('../../config/errors'); + +/** + * @description Génère un nombre aléatoire entre un minimum inclus et un maximum inclus. + * @param {Number} min Nombre Minimum + * @param {Number} max Nombre Maximum + * @returns {Number} Nombre aléatoire + * @examples randomNumber(1, 2) → retourne soit 1 ou 2 + */ +function randomNumber(min, max) { + return Math.floor(Math.random() * (max - min +1)) + min; +} + +/* OUTPUTS */ +exports.randomNumberOutput = (res, argsObject) => { + let { min, max } = argsObject; + min = parseInt(min); + max = parseInt(max); + + // S'il n'y a pas les champs obligatoire + if (!(min && max)) { + return sendResponse(res, requiredFields); + } + + // Si ce n'est pas des nombres + if (isNaN(min) || isNaN(max)) { + return sendResponse(res, { result: "Les paramètres min et max doivent être des nombres...", httpStatus: 400 }); + } + + return sendResponse(res, { result: randomNumber(min, max) }, true); +} \ No newline at end of file diff --git a/backend/assets/utils/sendResponse.js b/backend/assets/utils/sendResponse.js index 3178f09..9adb20b 100644 --- a/backend/assets/utils/sendResponse.js +++ b/backend/assets/utils/sendResponse.js @@ -2,7 +2,7 @@ * @description Envoie la réponse au client * @param {Response} res Objet réponse d'une réponse http/express * @param {Object} object { httpStatus, customProperties{Object}, result } - * @param {Boolean} isSuccess + * @param {Boolean} isSuccess (false par defaut) */ function sendResponse (res, object, isSuccess = false) { res.status(object.httpStatus || 200).send({ isSuccess, ...object.customProperties, result: object.result }); diff --git a/backend/controllers/functions.js b/backend/controllers/functions.js new file mode 100644 index 0000000..3439f63 --- /dev/null +++ b/backend/controllers/functions.js @@ -0,0 +1,10 @@ +const functionToExecute = require('../assets/functions/functionObject'); +const sendResponse = require('../assets/utils/sendResponse'); + +exports.executeFunctionName = (req, res, _next) => { + const functionObject = functionToExecute(req.params.functionName); + if (functionObject !== undefined) { + return functionObject.functionOutput(res, req.body); + } + return sendResponse(res, { result: "La fonction n'existe pas.", httpStatus: 404 }); +} \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index 54cb687..636e402 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -96,6 +96,11 @@ "type-is": "~1.6.17" } }, + "bowser": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.9.0.tgz", + "integrity": "sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA==" + }, "boxen": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", @@ -141,6 +146,11 @@ "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", "dev": true }, + "camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" + }, "capture-stack-trace": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", @@ -229,6 +239,11 @@ "safe-buffer": "5.1.2" } }, + "content-security-policy-builder": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-2.1.0.tgz", + "integrity": "sha512-/MtLWhJVvJNkA9dVLAp6fg9LxD2gfI6R2Fi1hPmfjYXSahJJzcfvoeDOxSyp4NvxMuwWv3WMssE9o31DoULHrQ==" + }, "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -279,6 +294,11 @@ "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", "dev": true }, + "dasherize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz", + "integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg=" + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -303,6 +323,16 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "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==" + }, + "dont-sniff-mimetype": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/dont-sniff-mimetype/-/dont-sniff-mimetype-1.1.0.tgz", + "integrity": "sha512-ZjI4zqTaxveH2/tTlzS1wFp+7ncxNZaIEWYg3lzZRHkKf5zPT/MnEG6WL0BhHMJUabkh8GeU5NL5j+rEUCb7Ug==" + }, "dot-prop": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", @@ -359,6 +389,11 @@ "strip-eof": "^1.0.0" } }, + "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", @@ -396,6 +431,11 @@ "vary": "~1.1.2" } }, + "feature-policy": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/feature-policy/-/feature-policy-0.3.0.tgz", + "integrity": "sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ==" + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -424,6 +464,11 @@ "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", @@ -491,6 +536,76 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "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==", + "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", + "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" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } + } + }, + "helmet-crossdomain": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/helmet-crossdomain/-/helmet-crossdomain-0.4.0.tgz", + "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==", + "requires": { + "bowser": "2.9.0", + "camelize": "1.0.0", + "content-security-policy-builder": "2.1.0", + "dasherize": "2.0.0" + } + }, + "hide-powered-by": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.1.0.tgz", + "integrity": "sha512-Io1zA2yOA1YJslkr+AJlWSf2yWFkKjvkcL9Ni1XSUqnGLr/qRQe2UI3Cn/J9MsJht7yEVCe0SscY1HgVMujbgg==" + }, + "hpkp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hpkp/-/hpkp-2.0.0.tgz", + "integrity": "sha1-EOFCJk52IVpdMMROxD3mTe5tFnI=" + }, + "hsts": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hsts/-/hsts-2.2.0.tgz", + "integrity": "sha512-ToaTnQ2TbJkochoVcdXYm4HOCliNozlviNsg+X2XQLQvZNI/kCHR9rZxVYpJB3UPcHz80PgxRyWQ7PdU1r+VBQ==", + "requires": { + "depd": "2.0.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } + } + }, "http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", @@ -511,6 +626,11 @@ "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-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -750,6 +870,11 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "nocache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz", + "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" + }, "nodemon": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.2.tgz", @@ -949,6 +1074,11 @@ "picomatch": "^2.0.7" } }, + "referrer-policy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz", + "integrity": "sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA==" + }, "registry-auth-token": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", @@ -1244,6 +1374,11 @@ "signal-exit": "^3.0.2" } }, + "x-xss-protection": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.3.0.tgz", + "integrity": "sha512-kpyBI9TlVipZO4diReZMAHWtS0MMa/7Kgx8hwG/EuZLiA6sg4Ah/4TRdASHhRRN3boobzcYgFRUFSgHRge6Qhg==" + }, "xdg-basedir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", diff --git a/backend/package.json b/backend/package.json index 7277352..601c728 100644 --- a/backend/package.json +++ b/backend/package.json @@ -11,7 +11,8 @@ "license": "MIT", "dependencies": { "cors": "^2.8.5", - "express": "^4.17.1" + "express": "^4.17.1", + "helmet": "^3.21.3" }, "devDependencies": { "morgan": "^1.9.1", diff --git a/backend/routes/functions.js b/backend/routes/functions.js new file mode 100644 index 0000000..e34b4f0 --- /dev/null +++ b/backend/routes/functions.js @@ -0,0 +1,11 @@ +const { Router } = require('express'); +const { executeFunctionName } = require('../controllers/functions'); + +const FunctionsRouter = Router(); + +FunctionsRouter.route('/:functionName') + + // Exécute la fonction demandée en paramètre + .post(executeFunctionName); + +module.exports = FunctionsRouter; \ No newline at end of file