diff --git a/database/auracle_db_create.sql b/database/auracle_db_create.sql index c07b3c1..9e5d75e 100644 --- a/database/auracle_db_create.sql +++ b/database/auracle_db_create.sql @@ -38,6 +38,7 @@ CREATE TABLE IF NOT EXISTS `school` ( CREATE TABLE IF NOT EXISTS `ingredient` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `name` VARCHAR(255) NOT NULL DEFAULT "Langue de salamandre", + `description` VARCHAR(255) NOT NULL DEFAULT "Une langue de salamandre de feu qui bouge encore un peu.", PRIMARY KEY (`id`) ); diff --git a/database/auracle_db_data.sql b/database/auracle_db_data.sql index e0a2fb3..b58cb4d 100644 --- a/database/auracle_db_data.sql +++ b/database/auracle_db_data.sql @@ -207,9 +207,9 @@ INSERT INTO `user` (name, mail, password) VALUES ('Kevin', '35.alexis.mail@gmail.com', 'sept777'); -- INGREDIENTS -INSERT INTO `ingredient` (name) VALUES -('Volonté'), -('Geste'); +INSERT INTO `ingredient` (name, description) VALUES +('Volonté', 'La force de volonté du lanceur, concentrée sur un objectif'), +('Geste', 'Un geste précis facilitant la canalisation magique'); -- VARIABLES INSERT INTO `variable` (description) VALUES diff --git a/repositories/ingredient-repository.js b/repositories/ingredient-repository.js index a24a30f..d6cc682 100644 --- a/repositories/ingredient-repository.js +++ b/repositories/ingredient-repository.js @@ -50,6 +50,124 @@ class IngredientRepository { }) } + addOne(igr) { + return new Promise((resolve, reject) => { + // Checks if body exists and if the model fits, and throws errors if it doesn't + if (this.isEmptyObject(igr)) { + reject(new HttpError(403, "Error: Ingredient cannot be nothing !")) + } else if (!v.validate(igr, IngredientValidation).valid) { + reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(igr, IngredientValidation).errors)) + } else if (this.isXSSAttempt(igr.description)) { + reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) + } else { + bookshelf.transaction(t => { + return model.forge({ + 'name': igr.name, + 'description': igr.description, + }).save(null, { + transacting: t + }) + .catch(err => { + throw err + }) + }) + .then(v => { + return v.load(['spells']) + }) + .then(v => { + resolve(this.getOne(v.id)) + }) + .catch(err => { + console.log(err) + reject(new HttpError(500, "Insert Ingredient error")) + }) + } + }) + } + + updateOne(id, igr) { + return new Promise((resolve, reject) => { + // Checks if body exists and if the model fits, and throws errors if it doesn't + if (this.isEmptyObject(igr)) { + reject(new HttpError(403, "Error: Ingredient cannot be nothing !")) + } else if (!v.validate(igr, IngredientValidation).valid) { + reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(igr, IngredientValidation).errors)) + } else if (this.isXSSAttempt(igr.description)) { + reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) + } else { + model.forge({id: id}) + .fetch({require: true, withRelated: ['spells']}) + .then(v => { + bookshelf.transaction(t => { + return v.save({ + 'name': igr.name, + 'description': igr.description, + }, { + method: 'update', + transacting: t + }) + .catch(err => { + console.log(err) + throw err + }) + }) + .then(v => { + return v.load(['spells']) + }) + .then(v => { + resolve(this.getOne(v.id)) + }) + .catch(err => { + console.log(err) + reject(new HttpError(500, "Update Ingredient error")) + }) + }) + .catch(err => { + console.log(err) + reject(new HttpError(404, "Couldn't get ingredient")) + }) + } + }) + } + + deleteOne(id) { + return new Promise((resolve, reject) => { + model.forge() + .where({ 'id' : id }) + .fetch({require: true, withRelated: ['spells']}) + .then(v => { + v.spells().detach() + v.destroy() + }) + .then(() => { + resolve({ + 'message': 'Ingredient with ID ' + id + ' successfully deleted !' + }) + }) + .catch(err => { + console.log(err) + reject(new HttpError(404, "Couldn't get ingredient")) + }) + }) + } + + // Check if object is null + isEmptyObject = (obj) => { + if (Object.keys(obj).length === 0 && obj.constructor === Object) { + return true + } else { + return false + } + } + + // Check if script injection attempt + isXSSAttempt = (string) => { + if (regexXSS.test(string)) { + return true + } else { + return false + } + } } module.exports = IngredientRepository \ No newline at end of file diff --git a/repositories/variable-repository.js b/repositories/variable-repository.js index 549f1ca..517a672 100644 --- a/repositories/variable-repository.js +++ b/repositories/variable-repository.js @@ -9,6 +9,11 @@ const v = new Validator() const VariableValidation = require("../validations/VariableValidation") v.addSchema(VariableValidation, "/VariableValidation") +// Validations +const regexXSS = RegExp(/<[^>]*script/) + +// Error handling +const { HttpError } = require('../validations/Errors') class VariableRepository { constructor() { @@ -43,6 +48,123 @@ class VariableRepository { }) }) } + + addOne(vr) { + return new Promise((resolve, reject) => { + // Checks if body exists and if the model fits, and throws errors if it doesn't + if (this.isEmptyObject(vr)) { + reject(new HttpError(403, "Error: Variable cannot be nothing !")) + } else if (!v.validate(vr, VariableValidation).valid) { + reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(vr, VariableValidation).errors)) + } else if (this.isXSSAttempt(vr.description)) { + reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) + } else { + bookshelf.transaction(t => { + return model.forge({ + 'description': vr.description, + }).save(null, { + transacting: t + }) + .catch(err => { + throw err + }) + }) + .then(v => { + return v.load(['spells']) + }) + .then(v => { + resolve(this.getOne(v.id)) + }) + .catch(err => { + console.log(err) + reject(new HttpError(500, "Insert Variable error")) + }) + } + }) + } + + updateOne(id, vr) { + return new Promise((resolve, reject) => { + // Checks if body exists and if the model fits, and throws errors if it doesn't + if (this.isEmptyObject(vr)) { + reject(new HttpError(403, "Error: Variable cannot be nothing !")) + } else if (!v.validate(vr, VariableValidation).valid) { + reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(vr, VariableValidation).errors)) + } else if (this.isXSSAttempt(vr.description)) { + reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) + } else { + model.forge({id: id}) + .fetch({require: true, withRelated: ['spells']}) + .then(v => { + bookshelf.transaction(t => { + return v.save({ + 'description': vr.description, + }, { + method: 'update', + transacting: t + }) + .catch(err => { + console.log(err) + throw err + }) + }) + .then(v => { + return v.load(['spells']) + }) + .then(v => { + resolve(this.getOne(v.id)) + }) + .catch(err => { + console.log(err) + reject(new HttpError(500, "Update Variable error")) + }) + }) + .catch(err => { + console.log(err) + reject(new HttpError(404, "Couldn't get variable")) + }) + } + }) + } + + deleteOne(id) { + return new Promise((resolve, reject) => { + model.forge() + .where({ 'id' : id }) + .fetch({require: true, withRelated: ['spells']}) + .then(v => { + v.spells().detach() + v.destroy() + }) + .then(() => { + resolve({ + 'message': 'Variable with ID ' + id + ' successfully deleted !' + }) + }) + .catch(err => { + console.log(err) + reject(new HttpError(404, "Couldn't get variable")) + }) + }) + } + + // Check if object is null + isEmptyObject = (obj) => { + if (Object.keys(obj).length === 0 && obj.constructor === Object) { + return true + } else { + return false + } + } + + // Check if script injection attempt + isXSSAttempt = (string) => { + if (regexXSS.test(string)) { + return true + } else { + return false + } + } } module.exports = VariableRepository \ No newline at end of file diff --git a/routes/ingredients.js b/routes/ingredients.js index ea5c36d..057b2f3 100644 --- a/routes/ingredients.js +++ b/routes/ingredients.js @@ -67,6 +67,81 @@ router.get('/:id/', async (req, res) => { }) }) + +// CREATE ONE ------------------ +const addIngredient = (igr) => { + return Ingredients.addOne(igr) + .catch(err => { + console.log(err) + throw err + }) +} +router.post('/', async (req, res) => { + addIngredient(req.body) + .then(v => { + res.setHeader('Content-Type', 'application/json;charset=utf-8') + res.send(JSON.stringify(v)) + }) + .catch(err => { + res.status(err.code).send(JSON.stringify( + { + "error": err.message, + "code": err.code + }) + ) + }) +}) + + +// UPDATE ONE ------------------ +const updateIngredient = (id, igr) => { + return Ingredients.updateOne(id, igr) + .catch(err => { + console.log(err) + throw err + }) +} +router.put('/:id/', async (req, res) => { + updateIngredient(req.params.id, req.body) + .then(v => { + res.setHeader('Content-Type', 'application/json;charset=utf-8') + res.send(JSON.stringify(v)) + }) + .catch(err => { + res.status(err.code).send(JSON.stringify( + { + "error": err.message, + "code": err.code + }) + ) + }) +}) + + +// DELETE ONE ------------------ +const deleteIngredient = (id) => { + return Ingredients.deleteOne(id) + .catch(err => { + console.log(err) + throw err + }) +} +router.delete('/:id/', async (req, res) => { + deleteIngredient(req.params.id) + .then(v => { + res.setHeader('Content-Type', 'application/json;charset=utf-8') + res.send(JSON.stringify(v)) + }) + .catch(err => { + res.status(err.code).send(JSON.stringify( + { + "error": err.message, + "code": err.code + }) + ) + }) +}) + // Param validation for ID // (check if id is int) (could be refactored) router.param('id', (req, res, next, id) => { diff --git a/routes/variables.js b/routes/variables.js index 1814b18..a4e649a 100644 --- a/routes/variables.js +++ b/routes/variables.js @@ -68,6 +68,80 @@ router.get('/:id/', async (req, res) => { }) +// CREATE ONE ------------------ +const addVariable = (vr) => { + return Variables.addOne(vr) + .catch(err => { + console.log(err) + throw err + }) +} +router.post('/', async (req, res) => { + addVariable(req.body) + .then(v => { + res.setHeader('Content-Type', 'application/json;charset=utf-8') + res.send(JSON.stringify(v)) + }) + .catch(err => { + res.status(err.code).send(JSON.stringify( + { + "error": err.message, + "code": err.code + }) + ) + }) +}) + + +// UPDATE ONE ------------------ +const updateVariable = (id, vr) => { + return Variables.updateOne(id, vr) + .catch(err => { + console.log(err) + throw err + }) +} +router.put('/:id/', async (req, res) => { + updateVariable(req.params.id, req.body) + .then(v => { + res.setHeader('Content-Type', 'application/json;charset=utf-8') + res.send(JSON.stringify(v)) + }) + .catch(err => { + res.status(err.code).send(JSON.stringify( + { + "error": err.message, + "code": err.code + }) + ) + }) +}) + + +// DELETE ONE ------------------ +const deleteVariable = (id) => { + return Variables.deleteOne(id) + .catch(err => { + console.log(err) + throw err + }) +} +router.delete('/:id/', async (req, res) => { + deleteVariable(req.params.id) + .then(v => { + res.setHeader('Content-Type', 'application/json;charset=utf-8') + res.send(JSON.stringify(v)) + }) + .catch(err => { + res.status(err.code).send(JSON.stringify( + { + "error": err.message, + "code": err.code + }) + ) + }) +}) + // Param validation for ID // (check if id is int) (could be refactored) router.param('id', (req, res, next, id) => { diff --git a/validations/IngredientValidation.js b/validations/IngredientValidation.js index 7bd1cef..7e7ef68 100644 --- a/validations/IngredientValidation.js +++ b/validations/IngredientValidation.js @@ -3,8 +3,9 @@ const Ingredient = { "type": Object, "properties": { "name": { "type": "string" }, + "description": { "type": "string" } }, - "required": ["name"] + "required": ["name", "description"] } module.exports = Ingredient \ No newline at end of file