Merge branch 'dev-bookshelf' into dev

This commit is contained in:
Alexis
2020-06-08 20:27:50 +02:00
33 changed files with 2910 additions and 807 deletions

View File

@@ -29,15 +29,16 @@ CREATE TABLE IF NOT EXISTS `school` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL DEFAULT "Nom de l'école", `name` VARCHAR(255) NOT NULL DEFAULT "Nom de l'école",
`description` VARCHAR(255) DEFAULT "Description de l'école", `description` VARCHAR(255) DEFAULT "Description de l'école",
`id_meta_school` INT UNSIGNED NOT NULL, `meta_school_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
FOREIGN KEY(`id_meta_school`) REFERENCES meta_school(`id`) FOREIGN KEY(`meta_school_id`) REFERENCES meta_school(`id`)
); );
/* COMMON INGREDIENTS */ /* COMMON INGREDIENTS */
CREATE TABLE IF NOT EXISTS `ingredient` ( CREATE TABLE IF NOT EXISTS `ingredient` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL DEFAULT "Langue de salamandre", `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`) PRIMARY KEY (`id`)
); );
@@ -63,30 +64,30 @@ CREATE TABLE IF NOT EXISTS `user` (
/* SPELLS' SCHOOLS */ /* SPELLS' SCHOOLS */
/* One spell can have multiple (up to 3) schools */ /* One spell can have multiple (up to 3) schools */
CREATE TABLE IF NOT EXISTS `spells_schools` ( CREATE TABLE IF NOT EXISTS `spell_school` (
`id_spell` INT UNSIGNED NOT NULL, `spell_id` INT UNSIGNED NOT NULL,
`id_school` INT UNSIGNED NOT NULL, `school_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id_spell`, `id_school`), PRIMARY KEY (`spell_id`, `school_id`),
FOREIGN KEY(`id_spell`) REFERENCES spell(`id`), FOREIGN KEY(`spell_id`) REFERENCES spell(`id`),
FOREIGN KEY(`id_school`) REFERENCES school(`id`) FOREIGN KEY(`school_id`) REFERENCES school(`id`)
); );
/* SPELLS' VARIABLES */ /* SPELLS' VARIABLES */
/* One spell can have multiple (up to 2) variables of cost */ /* One spell can have multiple (up to 2) variables of cost */
CREATE TABLE IF NOT EXISTS `spells_variables` ( CREATE TABLE IF NOT EXISTS `spell_variable` (
`id_spell` INT UNSIGNED NOT NULL, `spell_id` INT UNSIGNED NOT NULL,
`id_variable` INT UNSIGNED NOT NULL, `variable_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id_spell`, `id_variable`), PRIMARY KEY (`spell_id`, `variable_id`),
FOREIGN KEY(`id_spell`) REFERENCES spell(`id`), FOREIGN KEY(`spell_id`) REFERENCES spell(`id`),
FOREIGN KEY(`id_variable`) REFERENCES variable(`id`) FOREIGN KEY(`variable_id`) REFERENCES variable(`id`)
); );
/* SPELLS' VARIABLES */ /* SPELLS' VARIABLES */
/* One spell can have multiple ingredients */ /* One spell can have multiple ingredients */
CREATE TABLE IF NOT EXISTS `spells_ingredients` ( CREATE TABLE IF NOT EXISTS `spell_ingredient` (
`id_spell` INT UNSIGNED NOT NULL, `spell_id` INT UNSIGNED NOT NULL,
`id_ingredient` INT UNSIGNED NOT NULL, `ingredient_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id_spell`, `id_ingredient`), PRIMARY KEY (`spell_id`, `ingredient_id`),
FOREIGN KEY(`id_spell`) REFERENCES spell(`id`), FOREIGN KEY(`spell_id`) REFERENCES spell(`id`),
FOREIGN KEY(`id_ingredient`) REFERENCES ingredient(`id`) FOREIGN KEY(`ingredient_id`) REFERENCES ingredient(`id`)
); );

View File

@@ -12,7 +12,7 @@ INSERT INTO `meta_school` (name, description) VALUES
('Magies autres', 'Magies trop spécifiques et ne rentrant dans aucune autre grande école.'); ('Magies autres', 'Magies trop spécifiques et ne rentrant dans aucune autre grande école.');
-- SCHOOLS -- SCHOOLS
INSERT INTO `school` (name, description, id_meta_school) VALUES INSERT INTO `school` (name, description, meta_school_id) VALUES
('Lumomancie', 'Discipline arcanique de la lumière.', 1), ('Lumomancie', 'Discipline arcanique de la lumière.', 1),
('Vitamancie', 'Discipline arcanique de la guérison et de l\'énergie vitale.', 1), ('Vitamancie', 'Discipline arcanique de la guérison et de l\'énergie vitale.', 1),
('Obstrumancie', 'Discipline arcanique de la protection et des sceaux.', 1), ('Obstrumancie', 'Discipline arcanique de la protection et des sceaux.', 1),
@@ -207,9 +207,9 @@ INSERT INTO `user` (name, mail, password) VALUES
('Kevin', '35.alexis.mail@gmail.com', 'sept777'); ('Kevin', '35.alexis.mail@gmail.com', 'sept777');
-- INGREDIENTS -- INGREDIENTS
INSERT INTO `ingredient` (name) VALUES INSERT INTO `ingredient` (name, description) VALUES
('Volonté'), ('Volonté', 'La force de volonté du lanceur, concentrée sur un objectif'),
('Geste'); ('Geste', 'Un geste précis facilitant la canalisation magique');
-- VARIABLES -- VARIABLES
INSERT INTO `variable` (description) VALUES INSERT INTO `variable` (description) VALUES
@@ -223,11 +223,11 @@ SCHEMA
*/ */
DELIMITER $$ DELIMITER $$
CREATE PROCEDURE insertIntoSchoolRange(IN delimiter_start INT, IN delimiter_end INT, IN school_id INT) CREATE PROCEDURE insertIntoSchoolRange(IN delimiter_start INT, IN delimiter_end INT, IN id_school INT)
BEGIN BEGIN
SET @i = delimiter_start; SET @i = delimiter_start;
WHILE @i <= delimiter_end DO WHILE @i <= delimiter_end DO
INSERT INTO spells_schools (id_spell, id_school) VALUES (@i, school_id); INSERT INTO spell_school (spell_id, school_id) VALUES (@i, id_school);
SET @i = @i + 1; SET @i = @i + 1;
END WHILE; END WHILE;
END$$ END$$

23
database/bookshelf.js Normal file
View File

@@ -0,0 +1,23 @@
// MODULES
const fs = require('fs')
const mysql = require('mysql')
// Fetches and parses credentials file
let credentials_data = fs.readFileSync('./database/credentials.json')
let credentials = JSON.parse(credentials_data)
// Setting up the database connection
const knex = require('knex')({
client: credentials.client,
connection: {
host : credentials.host,
user : credentials.user,
password : credentials.password,
database : credentials.database,
charset : credentials.charset
},
debug: true
})
const bookshelf = require('bookshelf')(knex)
module.exports = { bookshelf }

View File

@@ -1,22 +0,0 @@
// MODULES
const fs = require('fs')
const mysql = require('mysql')
// Fetches and parses credentials file
let credentials_data = fs.readFileSync('./database/credentials.json')
let credentials = JSON.parse(credentials_data)
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 }

View File

@@ -8,7 +8,7 @@ const morgan = require('morgan')
const cors = require('cors') // module to format the json response const cors = require('cors') // module to format the json response
// Creates instances of database connections // Creates instances of database connections
const connection = require('./database/connection') const connection = require('./database/bookshelf')
const db = connection.db const db = connection.db
// CONSTANTS // CONSTANTS
@@ -30,4 +30,7 @@ const server = app.listen( port, () => {console.log(`App listening on port ${por
// Routing // Routing
app.use('/api/spells', routes.spells) app.use('/api/spells', routes.spells)
app.use('/api/schools', routes.schools) app.use('/api/schools', routes.schools)
app.use('/api/meta_schools', routes.meta_schools)
app.use('/api/variables', routes.variables)
app.use('/api/ingredients', routes.ingredients)
app.use('/api/users', routes.users) app.use('/api/users', routes.users)

View File

@@ -0,0 +1,13 @@
'use strict'
const bookshelf = require('../database/bookshelf').bookshelf
require('./spell-model')
let Ingredient = bookshelf.Model.extend({
tableName: 'ingredient',
spells() {
return this.belongsToMany( 'Spell', 'spell_ingredient')
}
})
module.exports = bookshelf.model('Ingredient', Ingredient)

View File

@@ -0,0 +1,13 @@
'use strict'
const bookshelf = require('../database/bookshelf').bookshelf
require('./school-model')
let MetaSchool = bookshelf.Model.extend({
tableName: 'meta_school',
schools() {
return this.hasMany( 'School' )
}
})
module.exports = bookshelf.model('MetaSchool', MetaSchool)

17
models/school-model.js Normal file
View File

@@ -0,0 +1,17 @@
'use strict'
const bookshelf = require('../database/bookshelf').bookshelf
require('./spell-model')
require('./meta-school-model')
let School = bookshelf.Model.extend({
tableName: 'school',
spells() {
return this.belongsToMany( 'Spell', 'spell_school')
},
meta_schools() {
return this.belongsTo( 'MetaSchool', 'meta_school_id')
}
})
module.exports = bookshelf.model('School', School)

21
models/spell-model.js Normal file
View File

@@ -0,0 +1,21 @@
'use strict'
const bookshelf = require('../database/bookshelf').bookshelf
require('./school-model')
require('./variable-model')
require('./ingredient-model')
let Spell = bookshelf.Model.extend({
tableName: 'spell',
schools() {
return this.belongsToMany( 'School', 'spell_school' )
},
variables() {
return this.belongsToMany( 'Variable', 'spell_variable' )
},
ingredients() {
return this.belongsToMany( 'Ingredient', 'spell_ingredient' )
}
})
module.exports = bookshelf.model('Spell', Spell)

8
models/user-model.js Normal file
View File

@@ -0,0 +1,8 @@
'use strict'
const bookshelf = require('../database/bookshelf').bookshelf
let User = bookshelf.Model.extend({
tableName: 'user',
})
module.exports = bookshelf.model('User', User)

13
models/variable-model.js Normal file
View File

@@ -0,0 +1,13 @@
'use strict'
const bookshelf = require('../database/bookshelf').bookshelf
require('./spell-model')
let Variable = bookshelf.Model.extend({
tableName: 'variable',
spells() {
return this.belongsToMany( 'Spell', 'spell_variable')
}
})
module.exports = bookshelf.model('Variable', Variable)

1292
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -28,11 +28,13 @@
"homepage": "https://github.com/AlexisNP/spellsaurus#readme", "homepage": "https://github.com/AlexisNP/spellsaurus#readme",
"dependencies": { "dependencies": {
"bcrypt": "^4.0.1", "bcrypt": "^4.0.1",
"bookshelf": "^1.1.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^4.17.1", "express": "^4.17.1",
"helmet": "^3.22.0", "helmet": "^3.22.0",
"jsonschema": "^1.2.6", "jsonschema": "^1.2.6",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"knex": "^0.21.1",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"mysql": "^2.18.1" "mysql": "^2.18.1"
} }

View File

@@ -0,0 +1,173 @@
'use strict'
// Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf
const model = require('../models/ingredient-model')
// Model validation
const Validator = require('jsonschema').Validator
const v = new Validator()
const IngredientValidation = require("../validations/IngredientValidation")
v.addSchema(IngredientValidation, "/IngredientValidation")
// Validations
const regexXSS = RegExp(/<[^>]*script/)
// Error handling
const { HttpError } = require('../validations/Errors')
class IngredientRepository {
constructor() {
}
getAll() {
return new Promise((resolve, reject) => {
model.forge()
.fetchAll({ withRelated: ['spells'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get ingredients"))
})
})
}
getOne(id) {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'id' : id })
.fetch({ withRelated: ['spells']})
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get ingredient"))
})
})
}
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

View File

@@ -0,0 +1,72 @@
'use strict'
// Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf
const model = require('../models/meta-school-model')
// Model validation
const Validator = require('jsonschema').Validator
const v = new Validator()
const MetaSchoolValidation = require("../validations/MetaSchoolValidation")
v.addSchema(MetaSchoolValidation, "/MetaSchoolValidation")
// Validations
const regexXSS = RegExp(/<[^>]*script/)
// Error handling
const { HttpError } = require('../validations/Errors')
class MetaSchoolRepository {
constructor() {
}
getAll() {
return new Promise((resolve, reject) => {
model.forge()
.fetchAll({ withRelated: ['schools'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get meta schools"))
})
})
}
getOne(id) {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'id' : id })
.fetch({ withRelated: ['schools']})
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get meta 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 = MetaSchoolRepository

View File

@@ -0,0 +1,174 @@
'use strict'
// Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf
const model = require('../models/school-model')
// Model validation
const Validator = require('jsonschema').Validator
const v = new Validator()
const SchoolValidation = require("../validations/SchoolValidation")
v.addSchema(SchoolValidation, "/SchoolValidation")
// Validations
const regexXSS = RegExp(/<[^>]*script/)
// Error handling
const { HttpError } = require('../validations/Errors')
class SchoolRepository {
constructor() {
}
getAll() {
return new Promise((resolve, reject) => {
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) => {
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, SchoolValidation).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, SchoolValidation).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 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, SchoolValidation).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, SchoolValidation).errors))
} else if (this.isXSSAttempt(s.name) || this.isXSSAttempt(s.description)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else {
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) => {
model.forge()
.where({ 'id' : id })
.fetch({require: true, withRelated: ['spells', 'meta_schools']})
.then(v => {
v.spells().detach()
v.destroy()
})
.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

View File

@@ -0,0 +1,245 @@
'use strict'
// Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf
const model = require('../models/spell-model')
// Model validation
const Validator = require('jsonschema').Validator
const v = new Validator()
const SpellValidation = require("../validations/SpellValidation")
v.addSchema(SpellValidation, "/SpellValidation")
// Validations
const regexXSS = RegExp(/<[^>]*script/)
// Error handling
const { HttpError } = require('../validations/Errors')
class SpellRepository {
constructor() {
}
getAll() {
return new Promise((resolve, reject) => {
model.forge()
.fetchAll({ withRelated: ['schools.meta_schools', 'variables', 'ingredients'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get spells"))
})
})
}
getOne(id) {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'id' : id })
.fetch({ withRelated: ['schools.meta_schools', 'variables', 'ingredients']})
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get spell"))
})
})
}
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: Spell cannot be nothing !"))
} else if (!v.validate(s, SpellValidation).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, SpellValidation).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 {
bookshelf.transaction(t => {
return model.forge({
'name': s.name,
'description': s.description,
'level': s.level,
'charge' : s.charge,
'cost' : s.cost,
'is_ritual' : s.is_ritual
}).save(null, {
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 => {
throw err
})
})
.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, "Insert Spell 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: Spell cannot be nothing !"))
} else if (!v.validate(s, SpellValidation).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, SpellValidation).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 {
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
})
})
.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 Spell error"))
})
})
.catch(err => {
console.log(err)
reject(new HttpError(404, "Couldn't get spell"))
})
}
})
}
deleteOne(id) {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'id' : id })
.fetch({require: true, withRelated: ['schools.meta_schools', 'variables', 'ingredients']})
.then(v => {
v.schools().detach()
v.variables().detach()
v.ingredients().detach()
v.destroy()
})
.then(() => {
resolve({
'message': 'Spell with ID ' + id + ' successfully deleted !'
})
})
.catch(err => {
console.log(err)
reject(new HttpError(404, "Couldn't get spell"))
})
})
}
// 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 = SpellRepository

View File

@@ -0,0 +1,74 @@
'use strict'
// Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf
const model = require('../models/user-model')
// Hashing and passwords
const bcrypt = require('bcrypt')
// Model validation
const Validator = require('jsonschema').Validator
const v = new Validator()
const UserValidation = require("../validations/UserValidation")
v.addSchema(UserValidation, "/UserValidation")
// Validations
const regexXSS = RegExp(/<[^>]*script/)
// Error handling
const { HttpError } = require('../validations/Errors')
class UserRepository {
constructor() {
}
getAll() {
return new Promise((resolve, reject) => {
this._model.forge()
.fetchAll()
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get users"))
})
})
}
getOne(id) {
return new Promise((resolve, reject) => {
this._model.forge()
.where({ 'id' : id })
.fetch()
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get user"))
})
})
}
// 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 = UserRepository

View File

@@ -0,0 +1,170 @@
'use strict'
// Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf
const model = require('../models/variable-model')
// Model validation
const Validator = require('jsonschema').Validator
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() {
}
getAll() {
return new Promise((resolve, reject) => {
model.forge()
.fetchAll({ withRelated: ['spells'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get variables"))
})
})
}
getOne(id) {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'id' : id })
.fetch({ withRelated: ['spells']})
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get variable"))
})
})
}
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

View File

@@ -1,9 +1,15 @@
const users = require('./users')
const spells = require('./spells') const spells = require('./spells')
const schools = require('./schools') const schools = require('./schools')
const meta_schools = require('./meta_schools')
const variables = require('./variables')
const ingredients = require('./ingredients')
const users = require('./users')
module.exports = { module.exports = {
spells, spells,
schools, schools,
users meta_schools,
ingredients,
variables,
users,
} }

165
routes/ingredients.js Normal file
View File

@@ -0,0 +1,165 @@
'use strict'
// Router
const express = require('express')
let router = express.Router()
// Connection
const connection = require('../database/bookshelf')
const db = connection.db
// Repository
const IngredientRepository = require('../repositories/ingredient-repository');
const Ingredients = new IngredientRepository();
const regexInt = RegExp(/^[1-9]\d*$/)
// Error handling
const { HttpError } = require('../validations/Errors')
// ROUTES
// GET ALL ------------------
const getIngredients = () => {
return Ingredients.getAll()
.catch(err => {
console.log(err)
throw err
})
}
router.get('/', async (req, res) => {
getIngredients()
.then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.end(JSON.stringify(v))
})
.catch(err => {
res.status(err.code).send(JSON.stringify(
{
"error": err.message,
"code": err.code
})
)
})
})
// GET ONE ------------------
const getIngredient = (id) => {
return Ingredients.getOne(id)
.catch(err => {
console.log(err)
throw err
})
}
router.get('/:id/', async (req, res) => {
getIngredient(req.params.id)
.then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.end(JSON.stringify(v))
})
.catch(err => {
res.status(err.code).send(JSON.stringify(
{
"error": err.message,
"code": err.code
})
)
})
})
// 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) => {
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

91
routes/meta_schools.js Normal file
View File

@@ -0,0 +1,91 @@
'use strict'
// Router
const express = require('express')
let router = express.Router()
// Connection
const connection = require('../database/bookshelf')
const db = connection.db
// Repository
const MetaSchoolRepository = require('../repositories/meta-school-repository');
const MetaSchools = new MetaSchoolRepository();
const regexInt = RegExp(/^[1-9]\d*$/)
// Error handling
const { HttpError } = require('../validations/Errors')
// ROUTES
// GET ALL ------------------
const getMetaSchools = () => {
return MetaSchools.getAll()
.catch(err => {
console.log(err)
throw err
})
}
router.get('/', async (req, res) => {
getMetaSchools()
.then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.end(JSON.stringify(v))
})
.catch(err => {
res.status(err.code).send(JSON.stringify(
{
"error": err.message,
"code": err.code
})
)
})
})
// GET ONE ------------------
const getMetaSchool = (id) => {
return MetaSchools.getOne(id)
.catch(err => {
console.log(err)
throw err
})
}
router.get('/:id/', async (req, res) => {
getMetaSchool(req.params.id)
.then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.end(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) => {
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

View File

@@ -5,49 +5,24 @@ const express = require('express')
let router = express.Router() let router = express.Router()
// Connection // Connection
const connection = require('../database/connection') const connection = require('../database/bookshelf')
const db = connection.db const db = connection.db
// Validations // Repository
const regexInt = RegExp(/^[1-9]\d*$/) const SchoolRepository = require('../repositories/school-repository');
const regexXSS = RegExp(/<[^>]*script/) const Schools = new SchoolRepository();
// Model validation const regexInt = RegExp(/^[1-9]\d*$/)
const Validator = require('jsonschema').Validator
const v = new Validator()
const School = require("../models/School")
v.addSchema(School, "/SchoolModel")
// Error handling // Error handling
const { HttpError } = require('../models/Errors') const { HttpError } = require('../validations/Errors')
// ROUTES // ROUTES
// GET ALL ------------------ // GET ALL ------------------
const getSchools = () => { const getSchools = () => {
return new Promise((resolve, reject) => { return Schools.getAll()
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)
})
})
.catch(err => { .catch(err => {
console.log(err)
throw err throw err
}) })
} }
@@ -70,23 +45,9 @@ router.get('/', async (req, res) => {
// GET ONE ------------------ // GET ONE ------------------
const getSchool = (id) => { const getSchool = (id) => {
return new Promise((resolve, reject) => { return Schools.getOne(id)
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)
}
})
})
.catch(err => { .catch(err => {
console.log(err)
throw err throw err
}) })
} }
@@ -109,41 +70,9 @@ router.get('/:id/', async (req, res) => {
// CREATE ONE ------------------ // CREATE ONE ------------------
const addSchool = (s) => { const addSchool = (s) => {
return new Promise(async (resolve, reject) => { return Schools.addOne(s)
// 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 => { .catch(err => {
reject(err) console.log(err)
})
}
})
}
}).catch(err => {
throw err throw err
}) })
} }
@@ -163,17 +92,66 @@ router.post('/', async (req, res) => {
}) })
}) })
// Param validation for single school // 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
})
)
})
})
// DELETE ONE ------------------
const deleteSchool = (id) => {
return Schools.deleteOne(id)
.catch(err => {
console.log(err)
throw err
})
}
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
})
)
})
})
// Param validation for ID
// (check if id is int) (could be refactored) // (check if id is int) (could be refactored)
router.param('id', (req, res, next, id) => { router.param('id', (req, res, next, id) => {
const regex = RegExp(/^[1-9]\d*$/);
try { try {
if (regex.test(id)) { if (regexInt.test(id)) {
next() next()
} else { } else {
throw new HttpError(403, 'Provided ID must be an integer') throw new Error;
} }
} catch (err) { } catch (err) {
err = new HttpError(403, 'Provided ID must be an integer and not zero')
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
{ {
"error": err.message, "error": err.message,
@@ -183,59 +161,4 @@ router.param('id', (req, res, next, id) => {
} }
}) })
// 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)
.catch(err => {
throw err
})
return s
}
// 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 module.exports = router

View File

@@ -5,48 +5,24 @@ const express = require('express')
let router = express.Router() let router = express.Router()
// Connection // Connection
const connection = require('../database/connection') const connection = require('../database/bookshelf')
const db = connection.db const db = connection.db
// Validations // Repository
const regexInt = RegExp(/^[1-9]\d*$/) const SpellReposity = require('../repositories/spell-repository');
const regexXSS = RegExp(/<[^>]*script/) const Spells = new SpellReposity();
// Model validation const regexInt = RegExp(/^[1-9]\d*$/)
const Validator = require('jsonschema').Validator
const v = new Validator()
const Spell = require("../models/Spell")
v.addSchema(Spell, "/SpellModel")
// Error handling // Error handling
const { HttpError } = require('../models/Errors') const { HttpError } = require('../validations/Errors')
// ROUTES // ROUTES
// GET ALL ------------------ // GET ALL ------------------
const getSpells = () => { const getSpells = () => {
return new Promise((resolve, reject) => { return Spells.getAll()
let query = "SELECT DISTINCT * FROM spell"
db.query(query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error'))
} else if (result.length == 0) {
reject(new HttpError(404, 'No spells were found'))
}
// Loops over the results to fetch the associated tables
for (let i = 0; i < result.length; i++) {
try {
result[i] = await buildSpell(result[i])
} catch (err) {
reject(err)
}
}
resolve(result)
})
})
.catch(err => { .catch(err => {
console.log(err)
throw err throw err
}) })
} }
@@ -69,23 +45,9 @@ router.get('/', async (req, res) => {
// GET ONE ------------------ // GET ONE ------------------
const getSpell = (id) => { const getSpell = (id) => {
return new Promise((resolve, reject) => { return Spells.getOne(id)
let query = "SELECT * FROM spell WHERE id = " + db.escape(id)
db.query(query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error'))
}
try {
result = buildSpell(result[0])
resolve(result)
} catch (err) {
reject(err)
}
})
})
.catch(err => { .catch(err => {
console.log(err)
throw err throw err
}) })
} }
@@ -108,136 +70,9 @@ router.get('/:id/', async (req, res) => {
// CREATE ONE ------------------ // CREATE ONE ------------------
const addSpell = (s) => { const addSpell = (s) => {
return new Promise(async (resolve, reject) => { return Spells.addOne(s)
// Checks if body exists and if the model fits, and throws errors if it doesn't
if (isEmptyObject(s)) {
reject(new HttpError(403, "Error: Spell cannot be nothing !"))
} else if (!v.validate(s, Spell).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, Spell).errors))
} else if (isXSSAttempt(s.name) || isXSSAttempt(s.description) || isXSSAttempt(s.cost)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else {
let query =
'INSERT INTO spell (name, description'
if (s.level !== null) { query += ', level' }
if (s.charge !== null) { query += ', charge' }
if (s.cost !== null) { query += ', cost' }
if (s.is_ritual !== null) { query += ', is_ritual' }
query += `) VALUES (${db.escape(s.name)}, ${db.escape(s.description)}`
if (s.level !== null) { query += `, ${s.level}` }
if (s.charge !== null) { query += `, ${s.charge}` }
if (s.cost !== null) { query += `, ${db.escape(s.cost)}` }
if (s.is_ritual !== null) { query += `, ${s.is_ritual}` }
query += ')'
db.query(query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error'))
}
console.log(`Inserted "${s.name}" with ID ${result.insertId}, affecting ${result.affectedRows} row(s)`)
const new_spell_id = result.insertId
let addSchoolsData = () => {
return new Promise((resolve, reject) => {
if (s.schools !== null) {
if (s.schools.length > 0) {
for (let i = 0; i < s.schools.length; i++) {
if (!regexInt.test(s.schools[i].id)) {
reject(new HttpError(403, 'Query error - School ID should be an integer !'))
}
let insert_schools_query = `INSERT INTO spells_schools (id_spell, id_school) VALUES (${new_spell_id}, ${s.schools[i].id})`
db.query(insert_schools_query, async (err, result) => {
if (err) {
reject(new HttpError(404, 'Database error - No schools matching this ID'))
} else {
console.log(`Associated school ID ${s.schools[i].id} to spell ID ${new_spell_id}, affecting ${result.affectedRows} row(s)`)
resolve()
}
})
}
} else {
resolve()
}
} else {
resolve()
}
})
}
let addVariablesData = () => {
return new Promise((resolve, reject) => {
if (s.variables !== null) {
if (s.variables.length > 0) {
for (let i = 0; i < s.variables.length; i++) {
if (!regexInt.test(s.variables[i].id)) {
reject(new HttpError(403, 'Query error - Variable ID should be an integer !'))
}
let insert_variables_query = `INSERT INTO spells_variables (id_spell, id_variable) VALUES (${new_spell_id}, ${s.variables[i].id})`
db.query(insert_variables_query, async (err, result) => {
if (err) {
reject(new HttpError(404, 'Database error - No variables matching this ID'))
} else {
console.log(`Associated variable ID ${s.variables[i].id} to spell ID ${new_spell_id}, affecting ${result.affectedRows} row(s)`)
resolve()
}
})
}
} else {
resolve()
}
} else {
resolve()
}
})
}
let addIngredientsData = () => {
return new Promise((resolve, reject) => {
if (s.ingredients !== null) {
if (s.ingredients.length > 0) {
for (let i = 0; i < s.ingredients.length; i++) {
if (!regexInt.test(s.ingredients[i].id)) {
reject(new HttpError(403, 'Query error - Ingredient ID should be an integer !'))
}
let insert_ingredients_query = `INSERT INTO spells_ingredients (id_spell, id_ingredient) VALUES (${new_spell_id}, ${s.ingredients[i].id})`
db.query(insert_ingredients_query, async (err, result) => {
if (err) {
reject(new HttpError(404, 'Database error - No ingredients matching this ID'))
} else {
console.log(`Associated ingredient ID ${s.ingredients[i].id} to spell ID ${new_spell_id}, affecting ${result.affectedRows} row(s)`)
resolve()
}
})
}
} else {
resolve()
}
} else {
resolve()
}
})
}
Promise.all([addSchoolsData(), addVariablesData(), addIngredientsData()])
.then(() => {
resolve(getSpell(new_spell_id))
})
.catch(err => { .catch(err => {
reject(err) console.log(err)
})
})
}
}).catch(err => {
throw err throw err
}) })
} }
@@ -259,186 +94,15 @@ router.post('/', async (req, res) => {
// UPDATE ONE ------------------ // UPDATE ONE ------------------
const updateSpell = (s, id) => { const updateSpell = (id, s) => {
return new Promise( async (resolve, reject) => { return Spells.updateOne(id, s)
// Check if spell exists
let old_spell = await getSpell(id)
.catch(err => {
reject(err)
})
// Checks if body exists and if the model fits, and throws errors if it doesn't
if (isEmptyObject(s)) {
reject(new HttpError(403, "Error: Spell cannot be nothing !"))
} else if (!v.validate(s, Spell).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, Spell).errors))
} else if (isXSSAttempt(s.name) || isXSSAttempt(s.description) || isXSSAttempt(s.cost)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else {
let updateSchoolsData = () => {
return new Promise((resolve, reject) => {
if (s.schools !== null) {
if (s.schools.length > 0) {
let delete_schools_query =
`DELETE FROM spells_schools WHERE id_spell = ${old_spell.id}`
db.query(delete_schools_query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error - Spell school deletion failed.'))
}
})
for (let i = 0; i < s.schools.length; i++) {
if (!regexInt.test(s.schools[i].id)) {
reject(new HttpError(403, 'Query error - School ID should be an integer !'))
} else {}
let update_schools_query = `INSERT INTO spells_schools (id_spell, id_school) VALUES (${old_spell.id}, ${s.schools[i].id})`
db.query(update_schools_query, async (err, result) => {
if (err) {
reject(new HttpError(404, 'Database error - No schools matching this ID'))
} else {
console.log(`Updated association school ID ${s.schools[i].id} to spell ID ${old_spell.id}`)
resolve()
}
})
}
} else {
resolve()
}
} else {
resolve()
}
})
}
let updateVariablesData = () => {
return new Promise((resolve, reject) => {
if (s.variables !== null) {
if (s.variables.length > 0) {
let delete_variables_query =
`DELETE FROM spells_variables WHERE id_spell = ${old_spell.id}`
db.query(delete_variables_query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error - Spell variable deletion failed.'))
}
})
for (let i = 0; i < s.variables.length; i++) {
if (!regexInt.test(s.variables[i].id)) {
reject(new HttpError(403, 'Query error - Variable ID should be an integer !'))
}
let update_variables_query = `INSERT INTO spells_variables (id_spell, id_variable) VALUES (${old_spell.id}, ${s.variables[i].id})`
db.query(update_variables_query, async (err, result) => {
if (err) {
reject(new HttpError(404, 'Database error - No variables matching this ID'))
} else {
console.log(`Updated variable ID "${s.variables[i].id}" to spell ID ${old_spell.id}`)
resolve()
}
})
}
} else {
resolve()
}
} else {
resolve()
}
})
}
let updateIngredientsData = () => {
return new Promise((resolve, reject) => {
if (s.ingredients !== null) {
if (s.ingredients.length > 0) {
let delete_ingredients_query =
`DELETE FROM spells_ingredients WHERE id_spell = ${old_spell.id}`
db.query(delete_ingredients_query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error - Spell ingredients deletion failed.'))
}
console.log(result)
})
// Loops over ingredients query
for (let i = 0; i < s.ingredients.length; i++) {
if (!regexInt.test(s.ingredients[i].id)) {
reject(new HttpError(403, 'Query error - Ingredient ID should be an integer !'))
}
let update_ingredients_query = `INSERT INTO spells_ingredients (id_spell, id_ingredient) VALUES (${old_spell.id}, ${s.ingredients[i].id})`
db.query(update_ingredients_query, async (err, result) => {
if (err) {
reject(new HttpError(404, 'Database error - No ingredients matching this ID'))
} else {
console.log(`Updated ingredient ID "${s.ingredients[i].id}" to spell ID ${old_spell.id}`)
resolve()
}
})
}
} else {
resolve()
}
} else {
resolve()
}
})
}
let updateSpellData = () => {
return new Promise((resolve, reject) => {
let update_spell_query =
'UPDATE spell SET '
if (s.name !== null) { update_spell_query += `name = "${s.name}" ` }
if (s.description !== null) { update_spell_query += `, description = "${s.description}" ` }
if (s.level !== null) { update_spell_query += `, level = ${s.level} ` }
if (s.charge !== null) { update_spell_query += `, charge = ${s.charge} ` }
if (s.cost !== null) { update_spell_query += `, cost = "${s.cost}" ` }
if (s.is_ritual !== null) { update_spell_query += `, is_ritual = ${s.is_ritual} ` }
update_spell_query += ` WHERE id = ${db.escape(id)}`
db.query(update_spell_query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error - Spell update failed'))
} else {
console.log(`Updated "${s.name}" on ID ${old_spell.id}, affecting ${result.affectedRows} row(s)`)
resolve()
}
})
})
}
const sub_promises = [
updateSchoolsData(),
updateVariablesData(),
updateIngredientsData()
]
Promise.all(sub_promises)
.then(() => {
updateSpellData()
})
.then(() => {
resolve(getSpell(old_spell.id))
})
.catch(err => {
reject(err)
})
}
})
.catch(err => { .catch(err => {
console.log(err)
throw err throw err
}) })
} }
router.put('/:id/', async (req, res) => { router.put('/:id/', async (req, res) => {
updateSpell(req.body, req.params.id) updateSpell(req.params.id, req.body)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.send(JSON.stringify(v)) res.send(JSON.stringify(v))
@@ -456,92 +120,9 @@ router.put('/:id/', async (req, res) => {
// DELETE ONE ------------------ // DELETE ONE ------------------
const deleteSpell = (id) => { const deleteSpell = (id) => {
return new Promise(async (resolve, reject) => { return Spells.deleteOne(id)
// Check if spell exists
let old_spell = await getSpell(id)
.catch(() => {
reject((new HttpError(404, 'No spell matching this ID')))
})
let deleteSchoolsData = () => {
return new Promise((resolve, reject) => {
let delete_schools_query = `DELETE FROM spells_schools WHERE id_spell = ${db.escape(id)}`
db.query(delete_schools_query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Spell schools deletion failed'))
} else {
console.log(`Deleted schools associated to spell ID ${db.escape(id)}`)
resolve()
}
})
})
}
let deleteVariablesData = () => {
return new Promise((resolve, reject) => {
let delete_variables_query = `DELETE FROM spells_variables WHERE id_spell = ${db.escape(id)}`
db.query(delete_variables_query, async (err, result) => {
if (err) {
console.log(err)
reject(new HttpError(500, 'Spell variables deletion failed'))
} else {
console.log(`Deleted variables associated to spell ID ${db.escape(id)}`)
resolve()
}
})
})
}
let deleteIngredientsData = () => {
return new Promise((resolve, reject) => {
let delete_ingredients_query = `DELETE FROM spells_ingredients WHERE id_spell = ${db.escape(id)}`
db.query(delete_ingredients_query, async (err, result) => {
if (err) {
console.log(err)
reject(new HttpError(500, 'Spell ingredients deletion failed'))
} else {
console.log(`Deleted ingredients associated to spell ID ${db.escape(id)}`)
resolve()
}
})
})
}
let deleteSpellData = () => {
return new Promise((resolve, reject) => {
let delete_spell_query = `DELETE FROM spell WHERE id = ${db.escape(id)}`
db.query(delete_spell_query, async (err, result) => {
if (err) {
console.log(err)
reject(new HttpError(500, 'Spell deletion failed'))
} else {
console.log(`Deleted spell ID ${db.escape(id)}, affecting ${result.affectedRows} rows`)
resolve()
}
})
})
}
const promises = [
deleteSchoolsData(),
deleteVariablesData(),
deleteIngredientsData()
]
Promise.all(promises)
.then(() => {
deleteSpellData()
let response = {
message: `Spell ID ${id} was successfully deleted.`
}
resolve(response)
})
.catch(err => {
reject(err)
})
})
.catch(err => { .catch(err => {
console.log(err)
throw err throw err
}) })
} }
@@ -562,16 +143,17 @@ router.delete('/:id/', async (req, res) => {
}) })
// Param validation for single spell // Param validation for ID
// (check if id is int) (could be refactored) // (check if id is int) (could be refactored)
router.param('id', (req, res, next, id) => { router.param('id', (req, res, next, id) => {
try { try {
if (regexInt.test(id)) { if (regexInt.test(id)) {
next() next()
} else { } else {
throw new HttpError(403, 'Provided ID must be an integer and not zero') throw new Error;
} }
} catch (err) { } catch (err) {
err = new HttpError(403, 'Provided ID must be an integer and not zero')
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
{ {
"error": err.message, "error": err.message,
@@ -581,107 +163,4 @@ router.param('id', (req, res, next, id) => {
} }
}) })
// SHARED FUNCTIONS ------------------
// Builds the associated infos for a given spell object
const buildSpell = async (spell) => {
// Fetches the spell's schools
let fetchSpellSchoolData = (s) => {
return new Promise((resolve, reject) => {
if (s == null) { reject(new HttpError(404, "Error: No spell matching this ID"))}
let query =
"SELECT school.id, school.name " +
"FROM spells_schools AS sc " +
"INNER JOIN school AS school ON sc.id_school = school.id " +
"WHERE sc.id_spell = " + s.id
db.query(query, (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error'))
} else {
s.schools = result
resolve(s)
}
})
})
}
// Fetches the spell's variables
let fetchSpellVariablesData = (s) => {
return new Promise((resolve, reject) => {
if (s == null) { reject(new HttpError(404, "Error: No spell matching this ID"))}
let query =
"SELECT variable.id, variable.description " +
"FROM spells_variables AS sv " +
"INNER JOIN variable AS variable ON sv.id_variable = variable.id " +
"WHERE sv.id_spell = " + s.id
db.query(query, (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error'))
} else {
s.variables = result
resolve(s)
}
})
})
}
// Fetches the spell's ingredients
let fetchSpellIngredientsData = (s) => {
return new Promise((resolve, reject) => {
if (s == null) { reject(new HttpError(404, "Error: No spell matching this ID"))}
let query =
"SELECT ingredient.id, ingredient.name " +
"FROM spells_ingredients AS si " +
"INNER JOIN ingredient AS ingredient ON si.id_ingredient = ingredient.id " +
"WHERE si.id_spell = " + s.id
db.query(query, (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error'))
} else {
s.ingredients = result
resolve(s)
}
})
})
}
// Builds the spell and returns it
let s = await fetchSpellSchoolData(spell)
.then(s => { return fetchSpellVariablesData(s) })
.then(s => { return fetchSpellIngredientsData(s) })
.catch(err => {
throw err
})
return s
}
// 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 module.exports = router

View File

@@ -1,45 +1,28 @@
'use strict'
// Router // Router
const express = require('express') const express = require('express')
let router = express.Router() let router = express.Router()
// Connection // Connection
const connection = require('../database/connection') const connection = require('../database/bookshelf')
const db = connection.db const db = connection.db
// Hashing and passwords // Repository
const bcrypt = require('bcrypt') const UserRepository = require('../repositories/user-repository');
const Users = new UserRepository();
// Validations
const regexInt = RegExp(/^[1-9]\d*$/) const regexInt = RegExp(/^[1-9]\d*$/)
const regexXSS = RegExp(/<[^>]*script/)
// Model validation
const Validator = require('jsonschema').Validator
const v = new Validator()
const User = require("../models/User")
v.addSchema(User, "/UserModel")
// Error handling // Error handling
const { HttpError } = require('../models/Errors') const { HttpError } = require('../validations/Errors')
// ROUTES // ROUTES
// GET ALL ------------------ // GET ALL ------------------
const getUsers = () => { const getUsers = () => {
return new Promise((resolve, reject) => { return Users.getAll()
let query = "SELECT DISTINCT * FROM user"
db.query(query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error'))
} else if (result.length == 0) {
reject(new HttpError(404, 'No users were found'))
} else {
resolve(result)
}
})
})
.catch(err => { .catch(err => {
console.log(err)
throw err throw err
}) })
} }
@@ -62,21 +45,9 @@ router.get('/', async (req, res) => {
// GET ONE ------------------ // GET ONE ------------------
const getUser = (id) => { const getUser = (id) => {
return new Promise((resolve, reject) => { return Users.getOne(id)
let query = "SELECT * FROM user WHERE id = " + db.escape(id)
db.query(query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error'))
} else if (result.length == 0) {
reject(new HttpError(404, 'No User matching this ID'))
} else {
resolve(result)
}
})
})
.catch(err => { .catch(err => {
console.log(err)
throw err throw err
}) })
} }
@@ -211,33 +182,11 @@ router.param('id', (req, res, next, id) => {
if (regexInt.test(id)) { if (regexInt.test(id)) {
next() next()
} else { } else {
throw new HttpError(403, 'Provided ID must be an integer and not zero') new Error
} }
} catch (err) { } catch (err) {
res.status(err.code).send(JSON.stringify( throw new HttpError(403, 'Provided ID must be an integer and not zero')
{
"error": err.message,
})
)
} }
}) })
// 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 module.exports = router

165
routes/variables.js Normal file
View File

@@ -0,0 +1,165 @@
'use strict'
// Router
const express = require('express')
let router = express.Router()
// Connection
const connection = require('../database/bookshelf')
const db = connection.db
// Repository
const VariableRepository = require('../repositories/variable-repository');
const Variables = new VariableRepository();
const regexInt = RegExp(/^[1-9]\d*$/)
// Error handling
const { HttpError } = require('../validations/Errors')
// ROUTES
// GET ALL ------------------
const getvariables = () => {
return Variables.getAll()
.catch(err => {
console.log(err)
throw err
})
}
router.get('/', async (req, res) => {
getvariables()
.then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.end(JSON.stringify(v))
})
.catch(err => {
res.status(err.code).send(JSON.stringify(
{
"error": err.message,
"code": err.code
})
)
})
})
// GET ONE ------------------
const getVariable = (id) => {
return Variables.getOne(id)
.catch(err => {
console.log(err)
throw err
})
}
router.get('/:id/', async (req, res) => {
getVariable(req.params.id)
.then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.end(JSON.stringify(v))
})
.catch(err => {
res.status(err.code).send(JSON.stringify(
{
"error": err.message,
"code": err.code
})
)
})
})
// 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) => {
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

View File

@@ -0,0 +1,11 @@
const Ingredient = {
"id": "/IngredientValidation",
"type": Object,
"properties": {
"name": { "type": "string" },
"description": { "type": "string" }
},
"required": ["name", "description"]
}
module.exports = Ingredient

View File

@@ -0,0 +1,12 @@
const MetaSchool = {
"id": "/MetaSchoolValidation",
"type": Object,
"properties": {
"name": { "type": "string" },
"description": { "type": "string" },
"schools": { "type": "array" }
},
"required": ["name", "description"]
}
module.exports = MetaSchool

View File

@@ -1,12 +1,12 @@
const School = { const School = {
"id": "/SchoolModel", "id": "/SchoolValidation",
"type": Object, "type": Object,
"properties": { "properties": {
"name": { "type": "string" }, "name": { "type": "string" },
"description": { "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 module.exports = School

View File

@@ -1,5 +1,5 @@
const Spell = { const Spell = {
"id": "/SpellModel", "id": "/SpellValidation",
"type": Object, "type": Object,
"properties": { "properties": {
"name": { "type": "string" }, "name": { "type": "string" },

View File

@@ -1,5 +1,5 @@
const User = { const User = {
"id": "/UserModel", "id": "/UserValidation",
"type": Object, "type": Object,
"properties": { "properties": {
"name": { "type": "string" }, "name": { "type": "string" },

View File

@@ -0,0 +1,10 @@
const Variable = {
"id": "/VariableValidation",
"type": Object,
"properties": {
"description": { "type": "string" },
},
"required": ["description"]
}
module.exports = Variable