From ce9018f65ef39513582fd560946f9ab00a428e5b Mon Sep 17 00:00:00 2001 From: Alexis <35.alexis.pele@gmail.com> Date: Sat, 6 Jun 2020 23:35:11 +0200 Subject: [PATCH] - Added some school methods but will need to refactor the model calls --- database/connection.js | 15 +-- models/SchoolValidation.js | 4 +- repositories/school-repository.js | 161 +++++++++++++++++++++++ repositories/spell-repository.js | 143 ++++++++++---------- routes/schools.js | 212 ++++++++++-------------------- routes/spells.js | 2 +- 6 files changed, 309 insertions(+), 228 deletions(-) diff --git a/database/connection.js b/database/connection.js index d890da9..1ffb0ac 100644 --- a/database/connection.js +++ b/database/connection.js @@ -20,17 +20,4 @@ const knex = require('knex')({ }) const bookshelf = require('bookshelf')(knex) -const db = mysql.createConnection({ - host: credentials.host, - user: credentials.user, - password: credentials.password, - database: credentials.database, -}) - -db.on('error', err => { - if (err.errno == 'ECONNRESET') { - console.log("The connection was reset during your request. Please try again later."); - } -}) - -module.exports = { db, bookshelf } \ No newline at end of file +module.exports = { bookshelf } \ No newline at end of file diff --git a/models/SchoolValidation.js b/models/SchoolValidation.js index fe1067c..8b48344 100644 --- a/models/SchoolValidation.js +++ b/models/SchoolValidation.js @@ -4,9 +4,9 @@ const School = { "properties": { "name": { "type": "string" }, "description": { "type": "string" }, - "id_meta_school": { "type": "number" }, + "meta_school_id": { "type": "number" }, }, - "required": ["name", "description", "id_meta_school"] + "required": ["name", "description", "meta_school_id"] } module.exports = School \ No newline at end of file diff --git a/repositories/school-repository.js b/repositories/school-repository.js index f625679..477d45b 100644 --- a/repositories/school-repository.js +++ b/repositories/school-repository.js @@ -7,6 +7,18 @@ const Spells = require('./spell-repository') const MetaSchoolRepository = require('./meta-school-repository') const MetaSchools = new MetaSchoolRepository() +// Model validation +const Validator = require('jsonschema').Validator +const v = new Validator() +const SchoolModel = require("../models/SchoolValidation") +v.addSchema(SchoolModel, "/SchoolModel") + +// Validations +const regexXSS = RegExp(/<[^>]*script/) + +// Error handling +const { HttpError } = require('../models/Errors') + class SchoolRepository { constructor() { @@ -28,6 +40,155 @@ class SchoolRepository { get model() { return this._model } + + getAll() { + return new Promise((resolve, reject) => { + this._model.forge() + .fetchAll({ withRelated: ['meta_schools'] }) + .then(v => { + resolve(v.toJSON({ omitPivot: true })) + }) + .catch(err => { + console.log(err) + reject(new HttpError(500, "Couldn't get schools")) + }) + }) + } + + getOne(id) { + return new Promise((resolve, reject) => { + this._model.forge() + .where({ 'id' : id }) + .fetch({ withRelated: ['meta_schools']}) + .then(v => { + resolve(v.toJSON({ omitPivot: true })) + }) + .catch(err => { + console.log(err) + reject(new HttpError(500, "Couldn't get school")) + }) + }) + } + + addOne(s) { + return new Promise((resolve, reject) => { + // Checks if body exists and if the model fits, and throws errors if it doesn't + if (this.isEmptyObject(s)) { + reject(new HttpError(403, "Error: School cannot be nothing !")) + } else if (!v.validate(s, SchoolModel).valid) { + reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, SchoolModel).errors)) + } else if (this.isXSSAttempt(s.name) || this.isXSSAttempt(s.description)) { + reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) + } else { + bookshelf.transaction(t => { + return this._model.forge({ + 'name': s.name, + 'description': s.description, + 'meta_school_id': s.meta_school_id, + }).save(null, { + transacting: t + }) + .catch(err => { + throw err + }) + }) + .then(v => { + return v.load(['meta_schools']) + }) + .then(v => { + resolve(this.getOne(v.id)) + }) + .catch(err => { + console.log(err) + reject(new HttpError(500, "Insert School error")) + }) + } + }) + } + + updateOne(id, s) { + return new Promise((resolve, reject) => { + // Checks if body exists and if the model fits, and throws errors if it doesn't + if (this.isEmptyObject(s)) { + reject(new HttpError(403, "Error: School cannot be nothing !")) + } else if (!v.validate(s, SchoolModel).valid) { + reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, SchoolModel).errors)) + } else if (this.isXSSAttempt(s.name) || this.isXSSAttempt(s.description)) { + reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) + } else { + this._model.forge({id: id}) + .fetch({require: true, withRelated: ['meta_schools']}) + .then(v => { + bookshelf.transaction(t => { + return v.save({ + 'name': s.name, + 'description': s.description, + 'meta_school_id': s.meta_school_id + }, { + method: 'update', + transacting: t + }) + .catch(err => { + console.log(err) + throw err + }) + }) + .then(v => { + return v.load(['meta_schools']) + }) + .then(v => { + resolve(this.getOne(v.id)) + }) + .catch(err => { + console.log(err) + reject(new HttpError(500, "Update School error")) + }) + }) + .catch(err => { + console.log(err) + reject(new HttpError(404, "Couldn't get school")) + }) + } + }) + } + + deleteOne(id) { + return new Promise((resolve, reject) => { + this._model.forge() + .where({ 'id' : id }) + .fetch({require: true, withRelated: ['spells', 'meta_schools']}) + .then(v => { + + }) + .then(() => { + resolve({ + 'message': 'School with ID ' + id + ' successfully deleted !' + }) + }) + .catch(err => { + console.log(err) + reject(new HttpError(404, "Couldn't get school")) + }) + }) + } + + // 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 = SchoolRepository \ No newline at end of file diff --git a/repositories/spell-repository.js b/repositories/spell-repository.js index eb720d3..b49ac45 100644 --- a/repositories/spell-repository.js +++ b/repositories/spell-repository.js @@ -132,7 +132,7 @@ class SpellRepository { }) .catch(err => { console.log(err) - reject(new HttpError(500, "Insert error")) + reject(new HttpError(500, "Insert Spell error")) }) } }) @@ -140,83 +140,92 @@ class SpellRepository { updateOne(id, s) { return new Promise((resolve, reject) => { - this._model.forge({id: id}) - .fetch({require: true, withRelated: ['schools.meta_schools', 'variables', 'ingredients']}) - .then(v => { - bookshelf.transaction(t => { - return v.save({ - 'name': s.name, - 'description': s.description, - 'level': s.level, - 'charge' : s.charge, - 'cost' : s.cost, - 'is_ritual' : s.is_ritual - }, { - method: 'update', - transacting: t - }) - // Detaches AND attaches pivot tables, dw about it - .tap(spell => { - if (s.schools) { - let schools = spell.related('school'); - return spell.schools().detach(schools, { transacting: t}); - } - return - }) - .tap(spell => { - if (s.variables) { - let variables = spell.related('variable'); - return spell.variables().detach(variables, { transacting: t}); - } - return - }) - .tap(spell => { - if (s.ingredients) { - let ingredients = spell.related('ingredient'); - return spell.ingredients().detach(ingredients, { transacting: t}); - } - }) - .tap(spell => { - return spell - .schools() - .attach(s.schools, { + // Checks if body exists and if the model fits, and throws errors if it doesn't + if (this.isEmptyObject(s)) { + reject(new HttpError(403, "Error: Spell cannot be nothing !")) + } else if (!v.validate(s, SpellModel).valid) { + reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, SpellModel).errors)) + } else if (this.isXSSAttempt(s.name) || this.isXSSAttempt(s.description) || this.isXSSAttempt(s.cost)) { + reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) + } else { + this._model.forge({id: id}) + .fetch({require: true, withRelated: ['schools.meta_schools', 'variables', 'ingredients']}) + .then(v => { + bookshelf.transaction(t => { + return v.save({ + 'name': s.name, + 'description': s.description, + 'level': s.level, + 'charge' : s.charge, + 'cost' : s.cost, + 'is_ritual' : s.is_ritual + }, { + method: 'update', transacting: t - }); + }) + // Detaches AND attaches pivot tables, dw about it + .tap(spell => { + if (s.schools) { + let schools = spell.related('school'); + return spell.schools().detach(schools, { transacting: t}); + } + return + }) + .tap(spell => { + if (s.variables) { + let variables = spell.related('variable'); + return spell.variables().detach(variables, { transacting: t}); + } + return + }) + .tap(spell => { + if (s.ingredients) { + let ingredients = spell.related('ingredient'); + return spell.ingredients().detach(ingredients, { transacting: t}); + } + }) + .tap(spell => { + return spell + .schools() + .attach(s.schools, { + transacting: t + }); + }) + .tap(spell => { + return spell + .variables() + .attach(s.variables, { + transacting: t + }); + }) + .tap(spell => { + return spell + .ingredients() + .attach(s.ingredients, { + transacting: t + }); + }) + .catch(err => { + console.log(err) + throw err + }) }) - .tap(spell => { - return spell - .variables() - .attach(s.variables, { - transacting: t - }); + .then(v => { + return v.load(['schools.meta_schools', 'variables', 'ingredients']) }) - .tap(spell => { - return spell - .ingredients() - .attach(s.ingredients, { - transacting: t - }); + .then(v => { + resolve(this.getOne(v.id)) }) .catch(err => { console.log(err) - throw err + reject(new HttpError(500, "Update Spell error")) }) }) - .then(v => { - return v.load(['schools.meta_schools', 'variables', 'ingredients']) - }) - .then(v => { - resolve(this.getOne(v.id)) - }) .catch(err => { console.log(err) - reject(new HttpError(500, "Update error")) + reject(new HttpError(404, "Couldn't get spell")) }) - }) - .catch(err => { - console.log(err) - reject(new HttpError(404, "Couldn't get spell")) - }) + } }) } diff --git a/routes/schools.js b/routes/schools.js index daceed1..502e303 100644 --- a/routes/schools.js +++ b/routes/schools.js @@ -8,46 +8,21 @@ let router = express.Router() const connection = require('../database/connection') const db = connection.db -// Validations -const regexInt = RegExp(/^[1-9]\d*$/) -const regexXSS = RegExp(/<[^>]*script/) +// Repository +const SchoolRepository = require('../repositories/school-repository'); +const Schools = new SchoolRepository(); -// Model validation -const Validator = require('jsonschema').Validator -const v = new Validator() -const School = require("../models/SchoolValidation") -v.addSchema(School, "/SchoolModel") +const regexInt = RegExp(/^[1-9]\d*$/) // Error handling const { HttpError } = require('../models/Errors') - // ROUTES // GET ALL ------------------ const getSchools = () => { - return new Promise((resolve, reject) => { - - let query = "SELECT DISTINCT * FROM school" - - db.query(query, async (err, result) => { - if (err) { - reject(new HttpError(500, 'Database error')) - } else if (result.length == 0) { - reject(new HttpError(404, 'No schools were found')) - } - - // Loops over the results to fetch the associated tables - for (let i = 0; i < result.length; i++) { - try { - result[i] = await buildSchool(result[i]) - } catch (err) { - reject(err) - } - } - resolve(result) - }) - }) + return Schools.getAll() .catch(err => { + console.log(err) throw err }) } @@ -70,23 +45,9 @@ router.get('/', async (req, res) => { // GET ONE ------------------ const getSchool = (id) => { - return new Promise((resolve, reject) => { - - let query = "SELECT * FROM school WHERE id = " + db.escape(id) - - db.query(query, async (err, result) => { - if (err) { - reject(new HttpError(500, 'Database error')) - } - try { - result = buildSchool(result[0]) - resolve(result) - } catch (err) { - reject(err) - } - }) - }) + return Schools.getOne(id) .catch(err => { + console.log(err) throw err }) } @@ -109,41 +70,9 @@ router.get('/:id/', async (req, res) => { // CREATE ONE ------------------ const addSchool = (s) => { - return new Promise(async (resolve, reject) => { - - // Checks if body exists and if the model fits, and throws errors if it doesn't - if (isEmptyObject(s)) { - reject(new HttpError(403, "Error: School cannot be nothing !")) - } else if (!v.validate(s, School).valid) { - reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, School).errors)) - } else if (isXSSAttempt(s.name) || isXSSAttempt(s.description)) { - reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) - } else { - let query = `INSERT INTO school (name, description, id_meta_school) VALUES (${db.escape(s.name)}, ${db.escape(s.description)}, ${db.escape(s.id_meta_school)})` - - db.query(query, (err, result) => { - if (err) { - if (err.errno == 1452) { - reject(new HttpError(404, "Error: No meta school matching this ID")) - } else { - reject(new HttpError(500, 'Database error')) - } - } else { - console.log(`Inserted "${s.name}" with ID ${result.insertId}, affecting ${result.affectedRows} row(s)`) - - const new_school_id = result.insertId - - getSchool(new_school_id) - .then(v => { - resolve(v) - }) - .catch(err => { - reject(err) - }) - } - }) - } - }).catch(err => { + return Schools.addOne(s) + .catch(err => { + console.log(err) throw err }) } @@ -163,79 +92,74 @@ router.post('/', async (req, res) => { }) }) -// Param validation for single school -// (check if id is int) (could be refactored) -router.param('id', (req, res, next, id) => { - const regex = RegExp(/^[1-9]\d*$/); - try { - if (regex.test(id)) { - next() - } else { - throw new HttpError(403, 'Provided ID must be an integer') - } - } catch (err) { +// UPDATE ONE ------------------ +const updateSchool = (id, s) => { + return Schools.updateOne(id, s) + .catch(err => { + console.log(err) + throw err + }) +} +router.put('/:id/', async (req, res) => { + updateSchool(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 }) ) - } + }) }) -// SHARED FUNCTIONS ------------------ -// Builds the associated infos for a given school object -const buildSchool = async (school) => { - - // Fetches the school's parent school - let fetchMetaSchoolData = (s) => { - return new Promise((resolve, reject) => { - - if (s == null) { reject(new HttpError(404, "Error: No school matching this ID"))} - - let query = - "SELECT ms.id, ms.name " + - "FROM meta_school AS ms " + - "WHERE ms.id = " + s.id_meta_school - - - db.query(query, (err, result) => { - if (err) { - reject(new HttpError(500, 'Database error')) - } else { - delete s.id_meta_school - s.meta_school = result - resolve(s) - } - }) - }) - } - - let s = fetchMetaSchoolData(school) +// DELETE ONE ------------------ +const deleteSchool = (id) => { + return Schools.deleteOne(id) .catch(err => { + console.log(err) throw err }) - - return s } +router.delete('/:id/', async (req, res) => { + deleteSchool(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 + }) + ) + }) +}) -// Check if object is null -const isEmptyObject = (obj) => { - if (Object.keys(obj).length === 0 && obj.constructor === Object) { - return true - } else { - return false - } -} - -// Check if script injection attempt -const isXSSAttempt = (string) => { - if (regexXSS.test(string)) { - return true - } else { - return false - } -} - -module.exports = router \ No newline at end of file +// Param validation for ID + // (check if id is int) (could be refactored) + router.param('id', (req, res, next, id) => { + try { + if (regexInt.test(id)) { + next() + } else { + throw new Error; + } + } catch (err) { + err = new HttpError(403, 'Provided ID must be an integer and not zero') + res.status(err.code).send(JSON.stringify( + { + "error": err.message, + "code": err.code + }) + ) + } + }) + + module.exports = router + \ No newline at end of file diff --git a/routes/spells.js b/routes/spells.js index d7ed52c..f032da8 100644 --- a/routes/spells.js +++ b/routes/spells.js @@ -143,7 +143,7 @@ router.delete('/:id/', async (req, res) => { }) -// Param validation for single spell +// Param validation for ID // (check if id is int) (could be refactored) router.param('id', (req, res, next, id) => { try {