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,
`name` VARCHAR(255) NOT NULL DEFAULT "Nom 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`),
FOREIGN KEY(`id_meta_school`) REFERENCES meta_school(`id`)
FOREIGN KEY(`meta_school_id`) REFERENCES meta_school(`id`)
);
/* COMMON INGREDIENTS */
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`)
);
@@ -63,30 +64,30 @@ CREATE TABLE IF NOT EXISTS `user` (
/* SPELLS' SCHOOLS */
/* One spell can have multiple (up to 3) schools */
CREATE TABLE IF NOT EXISTS `spells_schools` (
`id_spell` INT UNSIGNED NOT NULL,
`id_school` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id_spell`, `id_school`),
FOREIGN KEY(`id_spell`) REFERENCES spell(`id`),
FOREIGN KEY(`id_school`) REFERENCES school(`id`)
CREATE TABLE IF NOT EXISTS `spell_school` (
`spell_id` INT UNSIGNED NOT NULL,
`school_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`spell_id`, `school_id`),
FOREIGN KEY(`spell_id`) REFERENCES spell(`id`),
FOREIGN KEY(`school_id`) REFERENCES school(`id`)
);
/* SPELLS' VARIABLES */
/* One spell can have multiple (up to 2) variables of cost */
CREATE TABLE IF NOT EXISTS `spells_variables` (
`id_spell` INT UNSIGNED NOT NULL,
`id_variable` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id_spell`, `id_variable`),
FOREIGN KEY(`id_spell`) REFERENCES spell(`id`),
FOREIGN KEY(`id_variable`) REFERENCES variable(`id`)
CREATE TABLE IF NOT EXISTS `spell_variable` (
`spell_id` INT UNSIGNED NOT NULL,
`variable_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`spell_id`, `variable_id`),
FOREIGN KEY(`spell_id`) REFERENCES spell(`id`),
FOREIGN KEY(`variable_id`) REFERENCES variable(`id`)
);
/* SPELLS' VARIABLES */
/* One spell can have multiple ingredients */
CREATE TABLE IF NOT EXISTS `spells_ingredients` (
`id_spell` INT UNSIGNED NOT NULL,
`id_ingredient` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id_spell`, `id_ingredient`),
FOREIGN KEY(`id_spell`) REFERENCES spell(`id`),
FOREIGN KEY(`id_ingredient`) REFERENCES ingredient(`id`)
CREATE TABLE IF NOT EXISTS `spell_ingredient` (
`spell_id` INT UNSIGNED NOT NULL,
`ingredient_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`spell_id`, `ingredient_id`),
FOREIGN KEY(`spell_id`) REFERENCES spell(`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.');
-- 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),
('Vitamancie', 'Discipline arcanique de la guérison et de l\'énergie vitale.', 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');
-- 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
@@ -223,11 +223,11 @@ SCHEMA
*/
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
SET @i = delimiter_start;
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;
END WHILE;
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
// Creates instances of database connections
const connection = require('./database/connection')
const connection = require('./database/bookshelf')
const db = connection.db
// CONSTANTS
@@ -30,4 +30,7 @@ const server = app.listen( port, () => {console.log(`App listening on port ${por
// Routing
app.use('/api/spells', routes.spells)
app.use('/api/schools', routes.schools)
app.use('/api/users', routes.users)
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)

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",
"dependencies": {
"bcrypt": "^4.0.1",
"bookshelf": "^1.1.1",
"cors": "^2.8.5",
"express": "^4.17.1",
"helmet": "^3.22.0",
"jsonschema": "^1.2.6",
"jsonwebtoken": "^8.5.1",
"knex": "^0.21.1",
"morgan": "^1.10.0",
"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 schools = require('./schools')
const meta_schools = require('./meta_schools')
const variables = require('./variables')
const ingredients = require('./ingredients')
const users = require('./users')
module.exports = {
spells,
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()
// Connection
const connection = require('../database/connection')
const connection = require('../database/bookshelf')
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/School")
v.addSchema(School, "/SchoolModel")
const regexInt = RegExp(/^[1-9]\d*$/)
// Error handling
const { HttpError } = require('../models/Errors')
const { HttpError } = require('../validations/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,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)
router.param('id', (req, res, next, id) => {
const regex = RegExp(/^[1-9]\d*$/);
try {
if (regex.test(id)) {
if (regexInt.test(id)) {
next()
} else {
throw new HttpError(403, 'Provided ID must be an integer')
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,
@@ -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

View File

@@ -5,48 +5,24 @@ const express = require('express')
let router = express.Router()
// Connection
const connection = require('../database/connection')
const connection = require('../database/bookshelf')
const db = connection.db
// Validations
const regexInt = RegExp(/^[1-9]\d*$/)
const regexXSS = RegExp(/<[^>]*script/)
// Repository
const SpellReposity = require('../repositories/spell-repository');
const Spells = new SpellReposity();
// Model validation
const Validator = require('jsonschema').Validator
const v = new Validator()
const Spell = require("../models/Spell")
v.addSchema(Spell, "/SpellModel")
const regexInt = RegExp(/^[1-9]\d*$/)
// Error handling
const { HttpError } = require('../models/Errors')
const { HttpError } = require('../validations/Errors')
// ROUTES
// GET ALL ------------------
const getSpells = () => {
return new Promise((resolve, reject) => {
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)
})
})
return Spells.getAll()
.catch(err => {
console.log(err)
throw err
})
}
@@ -69,23 +45,9 @@ router.get('/', async (req, res) => {
// GET ONE ------------------
const getSpell = (id) => {
return new Promise((resolve, reject) => {
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)
}
})
})
return Spells.getOne(id)
.catch(err => {
console.log(err)
throw err
})
}
@@ -108,136 +70,9 @@ router.get('/:id/', async (req, res) => {
// CREATE ONE ------------------
const addSpell = (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: 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 => {
reject(err)
})
})
}
}).catch(err => {
return Spells.addOne(s)
.catch(err => {
console.log(err)
throw err
})
}
@@ -259,186 +94,15 @@ router.post('/', async (req, res) => {
// UPDATE ONE ------------------
const updateSpell = (s, id) => {
return new Promise( async (resolve, reject) => {
// 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)
})
}
})
const updateSpell = (id, s) => {
return Spells.updateOne(id, s)
.catch(err => {
console.log(err)
throw err
})
}
router.put('/:id/', async (req, res) => {
updateSpell(req.body, req.params.id)
updateSpell(req.params.id, req.body)
.then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.send(JSON.stringify(v))
@@ -456,92 +120,9 @@ router.put('/:id/', async (req, res) => {
// DELETE ONE ------------------
const deleteSpell = (id) => {
return new Promise(async (resolve, reject) => {
// 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)
})
})
return Spells.deleteOne(id)
.catch(err => {
console.log(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)
router.param('id', (req, res, next, id) => {
try {
if (regexInt.test(id)) {
next()
} else {
throw new HttpError(403, 'Provided ID must be an integer and not zero')
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,
@@ -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

View File

@@ -1,45 +1,28 @@
'use strict'
// Router
const express = require('express')
let router = express.Router()
// Connection
const connection = require('../database/connection')
const connection = require('../database/bookshelf')
const db = connection.db
// Hashing and passwords
const bcrypt = require('bcrypt')
// Repository
const UserRepository = require('../repositories/user-repository');
const Users = new UserRepository();
// Validations
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
const { HttpError } = require('../models/Errors')
const { HttpError } = require('../validations/Errors')
// ROUTES
// GET ALL ------------------
const getUsers = () => {
return new Promise((resolve, reject) => {
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)
}
})
})
return Users.getAll()
.catch(err => {
console.log(err)
throw err
})
}
@@ -62,21 +45,9 @@ router.get('/', async (req, res) => {
// GET ONE ------------------
const getUser = (id) => {
return new Promise((resolve, reject) => {
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)
}
})
})
return Users.getOne(id)
.catch(err => {
console.log(err)
throw err
})
}
@@ -211,33 +182,11 @@ router.param('id', (req, res, next, id) => {
if (regexInt.test(id)) {
next()
} else {
throw new HttpError(403, 'Provided ID must be an integer and not zero')
new Error
}
} catch (err) {
res.status(err.code).send(JSON.stringify(
{
"error": err.message,
})
)
throw new HttpError(403, 'Provided ID must be an integer and not zero')
}
})
// 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

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 = {
"id": "/SchoolModel",
"id": "/SchoolValidation",
"type": Object,
"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

View File

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

View File

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

View File

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