42 Commits

Author SHA1 Message Date
Alexis
db2a0cf9de Fixed dead class 2021-10-27 14:21:36 +02:00
Alexis
a1c6b31f9f Removed useless imports 2021-10-27 14:10:24 +02:00
Alexis
5d54730f6d Added parent class to models 2021-10-27 14:10:09 +02:00
Alexis
8631eac69d Updated API request messages 2021-08-18 17:49:07 +02:00
Alexis
e84ff967a7 Updated API launch messages 2021-08-18 17:48:24 +02:00
Alexis
bf0a19fa45 Added env specimen files and start scripts 2021-08-18 17:47:48 +02:00
Alexis
fabe285fb2 Rearranged and refactored main Api Class 2021-08-03 17:33:20 +02:00
Alexis
288432244b Updated models with unique columns constraints 2021-08-03 17:15:31 +02:00
Alexis
2a59436625 Added terminal colours for logging 2021-08-03 17:09:39 +02:00
Alexis
5913d3914d Removed useless imports 2021-08-02 16:45:19 +02:00
Alexis
884edba504 Updated Repository
Repositories and controllers are merged, since the functions would just be really redundant thanks to TypeORM
2021-08-02 16:44:40 +02:00
Alexis
f12190c95a Added relationships to entities 2021-08-01 22:30:53 +02:00
Alexis
3b0c79c57b Added timestamp columns 2021-08-01 21:43:24 +02:00
Alexis
f19b48f801 Fixed a typo 2021-07-31 12:48:32 +02:00
Alexis
2cef0d6f8c Fixed vulnerabilities 2021-07-31 12:48:08 +02:00
Alexis
481b52bb30 Added JWT for the future 2021-07-31 12:46:10 +02:00
Alexis
dce8bf95d6 Converted models to TypeORM models 2021-07-31 12:38:04 +02:00
Alexis
477dc14e63 TypeORM tsconfig 2021-07-31 12:37:47 +02:00
Alexis
f6b95b9be1 Replacer Sequelize for TypeORM
This decision was made because it's become apparent that the Sequelize TS support is more of an afterthought than an actual feature.
TypeORM, on the other hand, seems to achieve what Sequelize does in far less lines of code.
2021-07-31 12:20:41 +02:00
Alexis
4cc7c5533e Massive cleanup
Functionnal basic API calls for spells
Cleaned up some functions
Moved some files around
2021-07-30 12:24:11 +02:00
Alexis
a15a6614e7 Fixed tablenames 2021-07-24 22:51:21 +02:00
Alexis
f1d620dacd Fixed sequelize crash, added variables 2021-07-24 22:47:45 +02:00
Alexis
f3bbe696ed Updated db models 2021-07-24 22:20:48 +02:00
Alexis
ca9dbbd7db Moved old scripts to docs folder 2021-07-23 21:57:01 +02:00
Alexis
681ff2b086 Added nodemon scripts 2021-07-23 21:56:44 +02:00
Alexis
a43b5a95c4 Removed migration files and modules
This is too time consuming for now, need to focus on something more useful
2021-07-21 22:35:14 +02:00
Alexis
4be8a25370 Automated model registration 2021-07-20 22:03:20 +02:00
Alexis
c03a5d62ec Cleaned up old files and moved things around 2021-07-19 22:02:04 +02:00
Alexis
c61293c1a9 Moved data (again) to the root folder 2021-07-19 21:52:47 +02:00
Alexis
693f481cf5 Added csv-parser and sequelize-cli 2021-07-19 21:52:34 +02:00
Alexis
e84143c114 Added cast to apiPort and cleaned more code 2021-07-19 21:34:00 +02:00
Alexis
b80ff9a982 Moved old files down a peg 2021-07-19 21:26:58 +02:00
Alexis
546e8a1eed Implemented models and cleaned some code 2021-07-19 21:26:37 +02:00
Alexis
0048c59d56 Fixed wrong import of sequelize 2021-07-19 21:25:58 +02:00
Alexis
382e8633ad Added mariadb package 2021-07-19 21:25:11 +02:00
Alexis
30a5454934 Created main class instance 2021-07-18 20:05:46 +02:00
Alexis
206a1e0737 Updated dependencies and removed esconfig rules from package json 2021-07-18 20:05:31 +02:00
Alexis
392af38965 Updated tsconfig 2021-07-18 20:04:49 +02:00
Alexis
21329863d3 Added eslint rules 2021-07-18 20:04:37 +02:00
Alexis
061bb52dac Added ts scripts and config 2021-07-18 16:08:15 +02:00
Alexis
d977ce6551 Moved code to /src folder 2021-07-18 16:08:03 +02:00
Alexis
fbc38207f7 Updated dependencies before refacto 2021-07-18 16:00:00 +02:00
69 changed files with 3068 additions and 5203 deletions

14
.env.dev Normal file
View File

@@ -0,0 +1,14 @@
NODE_ENV = dev
API_PORT =
DB_NAME =
DB_USER =
DB_PASSWORD =
DB_HOST =
API_KEY_PUBLIC =
API_KEY_PRIVATE =
SMTP_USER =
SMTP_PASS =

14
.env.prod Normal file
View File

@@ -0,0 +1,14 @@
NODE_ENV = prod
API_PORT =
DB_NAME =
DB_USER =
DB_PASSWORD =
DB_HOST =
API_KEY_PUBLIC =
API_KEY_PRIVATE =
SMTP_USER =
SMTP_PASS =

28
.eslintrc.js Normal file
View File

@@ -0,0 +1,28 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'prettier/@typescript-eslint',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
"prettier/prettier": ["error", {
"endOfLine":"auto"
}],
},
}

BIN
_docs/old_scripts.zip Normal file

Binary file not shown.

View File

@@ -1,230 +0,0 @@
DROP DATABASE IF EXISTS auracle;
CREATE DATABASE auracle CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE auracle;
/* =========== PRIMARY TABLES =========== */
-- ROLES
CREATE TABLE IF NOT EXISTS `role` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`name` VARCHAR(255) NOT NULL,
`description` VARCHAR(255) NOT NULL,
PRIMARY KEY(`id`)
);
-- PERMISSIONS
CREATE TABLE IF NOT EXISTS `permission` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`slug` VARCHAR(255) NOT NULL,
PRIMARY KEY(`id`)
);
-- USERS
CREATE TABLE IF NOT EXISTS `user` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`uuid` VARCHAR(36) NOT NULL UNIQUE,
`name` VARCHAR(255) NOT NULL DEFAULT "Disciple",
`mail` VARCHAR(255) NOT NULL,
`avatar` VARCHAR(255),
`gender` VARCHAR(255),
`register_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`password` VARCHAR(255) NOT NULL,
`role_id` INT UNSIGNED NOT NULL DEFAULT 1,
`verified` BOOLEAN DEFAULT false,
`verification_token` VARCHAR(255),
`banned` BOOLEAN DEFAULT false,
PRIMARY KEY(`id`),
FOREIGN KEY(`role_id`) REFERENCES role(`id`)
);
-- API_TOKENS
CREATE TABLE IF NOT EXISTS `api_token` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`value` VARCHAR(255) NOT NULL,
`user_uuid` VARCHAR(36) NOT NULL UNIQUE,
PRIMARY KEY(`id`),
FOREIGN KEY(`user_uuid`) REFERENCES user(`uuid`)
);
-- SPELLS
CREATE TABLE IF NOT EXISTS `spell` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`name` VARCHAR(255) NOT NULL DEFAULT "Nom du sort",
`description` VARCHAR(1000) NOT NULL DEFAULT "Description du sort",
`level` INT UNSIGNED DEFAULT 0,
`charge` INT UNSIGNED DEFAULT 0,
`cost` VARCHAR(255) DEFAULT 0,
`is_ritual` BOOLEAN DEFAULT false,
`published` BOOLEAN DEFAULT false,
`public` BOOLEAN DEFAULT true,
`author_id` INT UNSIGNED NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
FOREIGN KEY(`author_id`) REFERENCES user(`id`)
);
-- META SCHOOLS
CREATE TABLE IF NOT EXISTS `meta_school` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`name` VARCHAR(255) NOT NULL DEFAULT "Nom de l'école mère",
`description` VARCHAR(255) DEFAULT "Description de l'école mère",
PRIMARY KEY (`id`)
);
-- SCHOOLS
CREATE TABLE IF NOT EXISTS `school` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`name` VARCHAR(255) NOT NULL DEFAULT "Nom de l'école",
`description` VARCHAR(255) DEFAULT "Description de l'école",
`published` BOOLEAN DEFAULT false,
`meta_school_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`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 UNIQUE,
`name` VARCHAR(255) NOT NULL DEFAULT "Langue de salamandre",
`description` VARCHAR(255) NOT NULL DEFAULT "Une langue de salamandre de feu encore chaude.",
`published` BOOLEAN DEFAULT false,
PRIMARY KEY (`id`)
);
-- COMMON VARIABLES
CREATE TABLE IF NOT EXISTS `variable` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`description` VARCHAR(255) NOT NULL DEFAULT "Nombre de créatures affectées",
`published` BOOLEAN DEFAULT false,
PRIMARY KEY (`id`)
);
/* ==== ASSOCIATION TABLES ==== */
-- SPELLS' SCHOOLS
-- One spell can have multiple (up to 3) schools
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 `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 `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`)
);
-- ROLES' PERMISSIONS
-- One role can have any number of permissions, or none at all
CREATE TABLE IF NOT EXISTS `role_permission` (
`role_id` INT UNSIGNED NOT NULL,
`permission_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`role_id`, `permission_id`),
FOREIGN KEY(`role_id`) REFERENCES role(`id`),
FOREIGN KEY(`permission_id`) REFERENCES permission(`id`)
);
-- Ajout d`une nouvelle ligne avant l`insert de description
DELIMITER $$
CREATE TRIGGER `multiLine` BEFORE INSERT ON `spell` FOR EACH ROW
BEGIN
SET NEW.description = replace(NEW.description, "<l>", "\n");
END$$
DELIMITER ;
/* =========== PRIMARY INSERTS =========== */
SET NAMES utf8;
-- CSV DATA
LOAD DATA INFILE 'C:/temp/auracle_data/permission.csv'
INTO TABLE `permission`
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
LOAD DATA INFILE 'C:/temp/auracle_data/role.csv'
INTO TABLE `role`
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
LOAD DATA INFILE 'C:/temp/auracle_data/role_permission.csv'
INTO TABLE `role_permission`
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
LOAD DATA INFILE 'C:/temp/auracle_data/user.csv'
INTO TABLE `user`
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
LOAD DATA INFILE 'C:/temp/auracle_data/api_token.csv'
INTO TABLE `api_token`
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
LOAD DATA INFILE 'C:/temp/auracle_data/spell.csv'
INTO TABLE `spell`
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
LOAD DATA INFILE 'C:/temp/auracle_data/meta_school.csv'
INTO TABLE `meta_school`
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
LOAD DATA INFILE 'C:/temp/auracle_data/school.csv'
INTO TABLE `school`
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
LOAD DATA INFILE 'C:/temp/auracle_data/ingredient.csv'
INTO TABLE `ingredient`
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
LOAD DATA INFILE 'C:/temp/auracle_data/variable.csv'
INTO TABLE `variable`
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
LOAD DATA INFILE 'C:/temp/auracle_data/spell_school.csv'
INTO TABLE `spell_school`
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;

View File

@@ -1,14 +0,0 @@
// Setting up the database connection
const knex = require('knex')({
client: "mysql",
connection: {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
charset: "utf8"
},
});
const bookshelf = require('bookshelf')(knex);
module.exports = { bookshelf };

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,41 +0,0 @@
'use strict';
// MODULES
const express = require('express');
const bodyParser = require('body-parser');
const helmet = require('helmet');
const morgan = require('morgan');
const cors = require('cors'); // module to format the json response
require('dotenv').config();
// CONSTANTS
const port = 2814;
// Import routes
const routes = require('./routes');
// Builds app w/ express
let app = express();
app.use(bodyParser.json({ limit: '10kb' }));
app.use(cors({
origin: [
"http://localhost:8080",
],
credentials: true,
}));
app.use(morgan('dev'));
app.use(helmet());
// Server
app.listen(port, () => console.log(`App listening on port ${port}`));
// Entry route
app.use('/api/v1/', routes.auth);
// Routing
app.use('/api/v1/spells', routes.spells);
app.use('/api/v1/schools', routes.schools);
app.use('/api/v1/meta_schools', routes.meta_schools);
app.use('/api/v1/variables', routes.variables);
app.use('/api/v1/ingredients', routes.ingredients);
app.use('/api/v1/users', routes.users);

View File

@@ -1,13 +0,0 @@
const bookshelf = require('../database/bookshelf').bookshelf;
require('./user-model');
let APIToken = bookshelf.Model.extend({
tableName: 'api_token',
hidden: ['id'],
user() {
return this.belongsTo('User', 'user_uuid', 'uuid');
}
});
module.exports = bookshelf.model('APIToken', APIToken);

View File

@@ -1,12 +0,0 @@
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

@@ -1,12 +0,0 @@
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);

View File

@@ -1,13 +0,0 @@
const bookshelf = require('../database/bookshelf').bookshelf;
require('./role-model');
let Permission = bookshelf.Model.extend({
tableName: 'permission',
hidden: ['id'],
role() {
return this.belongsToMany('Role', 'role_permission');
}
});
module.exports = bookshelf.model('Permission', Permission);

View File

@@ -1,12 +0,0 @@
const bookshelf = require('../database/bookshelf').bookshelf;
require('./permission-model');
let Role = bookshelf.Model.extend({
tableName: 'role',
permissions() {
return this.belongsToMany('Permission', 'role_permission');
}
});
module.exports = bookshelf.model('Role', Role);

View File

@@ -1,16 +0,0 @@
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);

View File

@@ -1,24 +0,0 @@
const bookshelf = require('../database/bookshelf').bookshelf;
require('./school-model');
require('./variable-model');
require('./ingredient-model');
let Spell = bookshelf.Model.extend({
tableName: 'spell',
hidden: [ 'author_id' ],
author() {
return this.belongsTo( 'User', 'author_id' );
},
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);

View File

@@ -1,17 +0,0 @@
const bookshelf = require('../database/bookshelf').bookshelf;
require('./role-model');
require('./spell-model');
let User = bookshelf.Model.extend({
tableName: 'user',
hidden: ['password', 'role_id'],
role() {
return this.belongsTo('Role');
},
spells() {
return this.hasMany('Spell', 'author_id');
}
});
module.exports = bookshelf.model('User', User);

View File

@@ -1,12 +0,0 @@
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);

13
nodemon.json Normal file
View File

@@ -0,0 +1,13 @@
{
"ignore": [
"src/**/*.test.ts",
"src/**/*.spec.ts",
".git",
"node_modules"
],
"watch": [
"src"
],
"exec": "npm start",
"ext": "ts"
}

3978
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,13 +2,16 @@
"name": "auracle-api",
"version": "1.0.0",
"description": "API for Auracle database",
"main": "index.js",
"main": "./src/main.ts",
"private": true,
"scripts": {
"start": "node index.js"
"build": "tsc -p ./",
"start:dev": "set NODE_ENV=dev && ts-node ./src/main.ts",
"start:prod": "set NODE_ENV=prod && npm run build && node ./dist/main.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/AlexisNP/spellsaurus.git"
"url": "git+https://github.com/AlexisNP/auracle_api"
},
"keywords": [
"api",
@@ -18,7 +21,10 @@
"database",
"ttrpg",
"dices",
"worldbuilding"
"worldbuilding",
"express",
"node",
"typescript"
],
"author": "AlexisNP",
"license": "ISC",
@@ -27,47 +33,29 @@
},
"homepage": "https://github.com/AlexisNP/spellsaurus#readme",
"dependencies": {
"bcrypt": "^5.0.0",
"bookshelf": "^1.1.1",
"bcrypt": "^5.0.1",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"handlebars": "^4.7.6",
"helmet": "^3.22.0",
"jsonschema": "^1.2.6",
"knex": "^0.21.1",
"helmet": "^4.4.1",
"jsonwebtoken": "^8.5.1",
"mariadb": "^2.5.4",
"morgan": "^1.10.0",
"mysql": "^2.18.1",
"nodemailer": "^6.4.17",
"uuid": "^8.2.0"
"nodemailer": "^6.6.3",
"typeorm": "^0.2.35"
},
"devDependencies": {
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"@types/helmet": "^4.0.0",
"@types/morgan": "^1.9.3",
"@types/validator": "^13.6.3",
"babel-eslint": "^10.1.0",
"eslint": "^7.18.0",
"eslint-plugin-import": "^2.22.1"
},
"eslintConfig": {
"env": {
"es6": true,
"browser": true
},
"parser": "babel-eslint",
"rules": {
"accessor-pairs": "warn",
"array-callback-return": "error",
"brace-style": "warn",
"consistent-return": "error",
"default-case": "error",
"eqeqeq": "warn",
"no-case-declarations": "error",
"no-empty-pattern": "warn",
"no-fallthrough": "error",
"no-unused-vars": "error",
"no-useless-catch": "error",
"no-var": "error",
"semi": "warn",
"no-extra-semi": "error",
"yoda": "warn"
}
"dotenv": "^8.6.0",
"eslint": "^7.31.0",
"eslint-plugin-import": "^2.23.4",
"nodemon": "^2.0.12",
"ts-node": "^10.1.0",
"typescript": "^4.3.5"
}
}
}

View File

@@ -1,206 +0,0 @@
// Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf;
const model = require('../models/ingredient-model');
// Model validation
const Validator = require('jsonschema').Validator;
const validator = new Validator();
const IngredientValidation = require("../validations/IngredientValidation");
validator.addSchema(IngredientValidation, "/IngredientValidation");
// Validations
const isXSSAttempt = require('../functions').isXSSAttempt;
const isEmptyObject = require('../functions').isEmptyObject;
class IngredientRepository {
constructor() {
}
getAll() {
return new Promise((resolve, reject) => {
new model()
.fetchAll({ withRelated: ['spells'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }));
})
.catch(err => {
console.log(err);
reject({
"message": "Il n'existe aucun ingrédient disponible.",
"code": 404,
});
});
});
}
getOne(id) {
return new Promise((resolve, reject) => {
new model()
.where({ 'id': id })
.fetch({ withRelated: ['spells'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }));
})
.catch(err => {
console.log(err);
reject({
"message": "L'ingrédient en question n'a pas pu être trouvé.",
"code": 404,
});
});
});
}
getSpellsFromOne(id) {
return new Promise((resolve, reject) => {
new model()
.where({ 'id': id })
.fetch({ withRelated: ['spells', 'spells.schools', 'spells.variables', 'spells.ingredients', 'spells.schools.meta_schools'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }));
})
.catch(err => {
console.log(err);
reject({
"message": "Les sortilèges liés à cet ingrédient n'ont pas pu être récupérés.",
"code": 404,
});
});
});
}
addOne(igr) {
return new Promise((resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't
if (isEmptyObject(igr)) {
reject({
"message": "Le corps de la requête ne peut pas être vide.",
"code": 403,
});
} else if (!validator.validate(igr, IngredientValidation).valid) {
reject({
"message": `Le modèle d'ingrédient n'est pas respecté : ${validator.validate(s, IngredientValidation).errors}`,
"code": 403,
});
} else if (isXSSAttempt(igr.description)) {
reject({
"message": "Tentative d'injection XSS détectée, requête refusée.",
"code": 403,
});
} else {
bookshelf.transaction(t => {
return new model({
'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({
"message": "Une erreur d'insertion s'est produite.",
"code": 500,
});
});
}
});
}
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 (isEmptyObject(igr)) {
reject({
"message": "Le corps de la requête ne peut pas être vide.",
"code": 403,
});
} else if (!validator.validate(igr, IngredientValidation).valid) {
reject({
"message": `Le modèle d'ingrédient n'est pas respecté : ${validator.validate(s, IngredientValidation).errors}`,
"code": 403,
});
} else if (isXSSAttempt(igr.description)) {
reject({
"message": "Tentative d'injection XSS détectée, requête refusée.",
"code": 403,
});
} else {
new model({ 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({
"message": "Une erreur d'insertion s'est produite.",
"code": 500,
});
});
})
.catch(err => {
console.log(err);
reject({
"message": "L'ingrédient en question n'a pas été trouvé.",
"code": 404,
});
});
}
});
}
deleteOne(id) {
return new Promise((resolve, reject) => {
new model()
.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({
"message": "L'ingrédient en question n'a pas été trouvé.",
"code": 404,
});
});
});
}
}
module.exports = IngredientRepository;

View File

@@ -1,51 +0,0 @@
// Bookshelf
const model = require('../models/meta-school-model')
// Model validation
const Validator = require('jsonschema').Validator
const validator = new Validator()
const MetaSchoolValidation = require("../validations/MetaSchoolValidation")
validator.addSchema(MetaSchoolValidation, "/MetaSchoolValidation")
class MetaSchoolRepository {
constructor() {
}
getAll() {
return new Promise((resolve, reject) => {
new model()
.fetchAll({ withRelated: ['schools'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject({
"message": "Il n'existe aucune grande école disponible.",
"code": 404
});
})
})
}
getOne(id) {
return new Promise((resolve, reject) => {
new model()
.where({ 'id': id })
.fetch({ withRelated: ['schools'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject({
"message": "La grande école en question n'a pas pu être trouvée.",
"code": 404
});
})
})
}
}
module.exports = MetaSchoolRepository

View File

@@ -1,207 +0,0 @@
// Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf;
const model = require('../models/school-model');
// Model validation
const Validator = require('jsonschema').Validator;
const validator = new Validator();
const SchoolValidation = require("../validations/SchoolValidation");
validator.addSchema(SchoolValidation, "/SchoolValidation");
// Validations
const isXSSAttempt = require('../functions').isXSSAttempt;
const isEmptyObject = require('../functions').isEmptyObject;
class SchoolRepository {
constructor() {
}
getAll() {
return new Promise((resolve, reject) => {
new model()
.fetchAll({ withRelated: ['meta_schools'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }));
})
.catch(err => {
console.log(err);
reject({
"message": "Il n'existe aucune école disponible.",
"code": 404,
});
});
});
}
getOne(id) {
return new Promise((resolve, reject) => {
new model()
.where({ 'id': id })
.fetch({ withRelated: ['meta_schools'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }));
})
.catch(err => {
console.log(err);
reject({
"message": "L'école en question n'a pas pu être trouvée.",
"code": 404,
});
});
});
}
getSpellsFromOne(id) {
return new Promise((resolve, reject) => {
new model()
.where({ 'id': id })
.fetch({ withRelated: ['spells', 'spells.schools', 'spells.variables', 'spells.ingredients', 'spells.schools.meta_schools'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }));
})
.catch(err => {
console.log(err);
reject({
"message": "Les sortilèges de cette école n'ont pas pu être récupérés.",
"code": 404,
});
});
});
}
addOne(s) {
return new Promise((resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't
if (isEmptyObject(s)) {
reject({
"message": "Le corps de la requête ne peut pas être vide.",
"code": 403,
});
} else if (!validator.validate(s, SchoolValidation).valid) {
reject({
"message": `Le modèle d'école n'est pas respecté : ${validator.validate(s, SchoolValidation).errors}`,
"code": 403,
});
} else if (isXSSAttempt(s.name) || isXSSAttempt(s.description)) {
reject({
"message": "Tentative d'injection XSS détectée, requête refusée.",
"code": 403,
});
} else {
bookshelf.transaction(t => {
return new model({
'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({
"message": "Une erreur d'insertion s'est produite.",
"code": 500,
});
});
}
});
}
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 (isEmptyObject(s)) {
reject({
"message": "Le corps de la requête ne peut pas être vide.",
"code": 403,
});
} else if (!validator.validate(s, SchoolValidation).valid) {
reject({
"message": `Le modèle d'école n'est pas respecté : ${validator.validate(s, SchoolValidation).errors}`,
"code": 403,
});
} else if (isXSSAttempt(s.name) || isXSSAttempt(s.description)) {
reject({
"message": "Tentative d'injection XSS détectée, requête refusée.",
"code": 403,
});
} else {
new model({ 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({
"message": "Une erreur d'insertion s'est produite.",
"code": 500,
});
});
})
.catch(err => {
console.log(err);
reject({
"message": "L'école en question n'a pas été trouvée.",
"code": 404,
});
});
}
});
}
deleteOne(id) {
return new Promise((resolve, reject) => {
new model()
.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({
"message": "L'école en question n'a pas été trouvée.",
"code": 404,
});
});
});
}
}
module.exports = SchoolRepository;

View File

@@ -1,351 +0,0 @@
'use strict'
// Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf
const model = require('../models/spell-model')
// Model validation
const Validator = require('jsonschema').Validator
const validator = new Validator()
const SpellValidation = require("../validations/SpellValidation")
validator.addSchema(SpellValidation, "/SpellValidation")
// Validations
const isXSSAttempt = require('../functions').isXSSAttempt
const isEmptyObject = require('../functions').isEmptyObject
class SpellRepository {
constructor() {
}
getAll(name, description, level, charge, cost, ritual) {
return new Promise((resolve, reject) => {
let query = new model();
if (name) {
query.where('name', 'like', `%${name}%`)
}
if (description) {
query.where('description', 'like', `%${description}%`)
}
if (level) {
query.where({ 'level': level })
}
if (charge) {
query.where({ 'charge': charge })
}
if (cost) {
query.where({ 'cost': cost })
}
if (ritual) {
query.where({ 'is_ritual': ritual })
}
query.fetchAll({ withRelated: ['schools.meta_schools', 'variables', 'ingredients', 'author'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err);
reject({
"message": "Il n'existe aucun sortilège disponible.",
"code": 404,
});
})
})
}
getAllPublic(name, description, level, charge, cost, ritual) {
return new Promise((resolve, reject) => {
let query = new model().where({ 'public': 1 })
if (name) {
query.where('name', 'like', `%${name}%`)
}
if (description) {
query.where('description', 'like', `%${description}%`)
}
if (level) {
query.where({ 'level': level })
}
if (charge) {
query.where({ 'charge': charge })
}
if (cost) {
query.where({ 'cost': cost })
}
if (ritual) {
query.where({ 'is_ritual': ritual })
}
query.fetchAll({ withRelated: ['schools.meta_schools', 'variables', 'ingredients', 'author'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }));
})
.catch(err => {
console.log(err);
reject({
"message": "Il n'existe aucun sortilège disponible.",
"code": 404,
});
})
})
}
getPage(page) {
return new Promise((resolve, reject) => {
new model()
.where({ 'public': 1 })
.fetchPage({
pageSize: 20,
page: page,
withRelated: ['schools.meta_schools', 'variables', 'ingredients', 'author'],
})
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err);
reject({
"message": "La page de sortilèges n'a pas pu être chargée",
"code": 404,
});
})
})
}
getOne(id) {
return new Promise((resolve, reject) => {
new model()
.where({ 'id': id })
.fetch({ withRelated: ['schools.meta_schools', 'variables', 'ingredients', 'author'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err);
reject({
"message": "Le sortilège en question n'a pas été trouvé.",
"code": 404,
});
})
})
}
addOne(s) {
return new Promise((resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't
if (isEmptyObject(s)) {
reject({
"message": "Le corps de la requête ne peut pas être vide.",
"code": 403,
});
} else if (!validator.validate(s, SpellValidation).valid) {
reject({
"message": `Le modèle de sortilège n'est pas respecté : ${validator.validate(s, SpellValidation).errors}`,
"code": 403,
});
} else if (isXSSAttempt(s.name) || isXSSAttempt(s.description) || isXSSAttempt(s.cost)) {
reject({
"message": "Tentative d'injection XSS détectée, requête refusée.",
"code": 403,
});
} else {
bookshelf.transaction(t => {
return new model({
'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 => {
console.log(err);
reject({
"message": "Un attributs du sortilège a provoqué une erreur d'insertion.",
"code": 500,
});
})
})
.then(v => {
return v.load(['schools.meta_schools', 'variables', 'ingredients', 'author'])
})
.then(v => {
resolve(this.getOne(v.id))
})
.catch(err => {
console.log(err);
reject({
"message": "Le sortilège n'a pas pu être ajouté.",
"code": 500,
});
})
}
})
}
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 (isEmptyObject(s)) {
reject({
"message": "Le corps de la requête ne peut pas être vide.",
"code": 403,
});
} else if (!validator.validate(s, SpellValidation).valid) {
reject({
"message": `Le modèle de sortilège n'est pas respecté : ${validator.validate(s, SpellValidation).errors}`,
"code": 403,
});
} else if (isXSSAttempt(s.name) || isXSSAttempt(s.description) || isXSSAttempt(s.cost)) {
reject({
"message": "Tentative d'injection XSS détectée, requête refusée.",
"code": 403,
});
} else {
new model({ id: id })
.fetch({ require: true, withRelated: ['schools.meta_schools', 'variables', 'ingredients', 'author'] })
.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 spell
})
.tap(spell => {
if (s.variables) {
let variables = spell.related('variable');
return spell.variables().detach(variables, { transacting: t });
}
return spell;
})
.tap(spell => {
if (s.ingredients) {
let ingredients = spell.related('ingredient');
return spell.ingredients().detach(ingredients, { transacting: t });
}
return spell;
})
.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);
reject({
"message": "Un attributs du sortilège a provoqué une erreur d'insertion.",
"code": 500,
})
})
})
.then(v => {
return v.load(['schools.meta_schools', 'variables', 'ingredients', 'author']);
})
.then(v => {
resolve(this.getOne(v.id));
})
.catch(err => {
console.log(err);
reject({
"message": "Une erreur de chargement est survenue.",
"code": 500,
});
})
})
.catch(err => {
console.log(err);
reject({
"message": "Le sortilège en question n'a pas été trouvé.",
"code": 404,
});
})
}
})
}
deleteOne(id) {
return new Promise((resolve, reject) => {
new model()
.where({ 'id': id })
.fetch({ require: true, withRelated: ['schools.meta_schools', 'variables', 'ingredients', 'author'] })
.then(v => {
v.schools().detach();
v.variables().detach();
v.ingredients().detach();
v.destroy();
})
.then(() => {
resolve({
"message": `Le sortilège #${id} a été supprimé avec succès.`,
"code": 200,
});
})
.catch(err => {
console.log(err);
reject({
"message": "Le sortilège en question n'a pas été trouvé.",
"code": 404,
});
})
})
}
}
module.exports = SpellRepository

View File

@@ -1,562 +0,0 @@
'use strict'
// Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf;
const model = require('../models/user-model');
const token_model = require('../models/api-token-model');
// Hashing and passwords
const bcrypt = require('bcrypt');
const { v4: uuidv4 } = require('uuid');
// Mailing methods
const mails = require('../smtp/mails');
// Model validation
const Validator = require('jsonschema').Validator;
const validator = new Validator();
const UserValidation = require("../validations/UserValidation");
validator.addSchema(UserValidation, "/UserValidation");
// Validations
const isXSSAttempt = require('../functions').isXSSAttempt;
const isEmptyObject = require('../functions').isEmptyObject;
class UserRepository {
constructor() {
}
/**
* Fetches all users in the dabatase.
*
* @returns { Promise }
* Fulfilled data: Array of user objects.
*/
getAll() {
return new Promise((resolve, reject) => {
new model()
.fetchAll({ withRelated: ['role.permissions'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }));
})
.catch(err => {
console.log(err)
reject({
"message": "Erreur de base de données, les utilisateurs n'ont pas pu être récupérés.",
"code": 500,
});
})
})
}
/**
* Fetches a user object associated with the uuid.
*
* @param { string } uuid
* @param { boolean } full
* Whether the password should also be fetched. (should never be true unless you want to log the user)
*
* @returns { Promise }
* Fulfilled data: Queried user object.
*/
getOneByUUID(uuid, full) {
return new Promise((resolve, reject) => {
if (!(uuid)) {
reject({
"message": "La requête doit renseigner un uuid.",
"code": 400,
})
}
new model()
.where({ 'uuid': uuid })
.fetch({ withRelated: ['role.permissions'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true, visibility: !full }));
})
.catch(() => {
reject({
"message": "L'utilisateur avec cet UUID n'a pas été trouvé.",
"code": 404,
});
})
})
}
/**
* Fetches a user object associated with the mail address.
*
* @param { string } mail
* @param { boolean } full
* Whether the password should also be fetched. (should never be true unless you want to log the user)
*
* @returns { Promise }
* Fulfilled data: Queried user object.
*/
getOneByEmail(mail, full) {
return new Promise((resolve, reject) => {
if (!(mail)) {
reject({
"message": "La requête doit renseigner un email.",
"code": 400,
})
}
new model()
.where({ 'mail': mail })
.fetch({ withRelated: ['role'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true, visibility: !full }));
})
.catch(() => {
reject({
"message": "L'utilisateur avec cet email n'a pas été trouvé.",
"code": 404,
});
});
})
}
/**
* Fetches all spells linked to a user's uuid
*
* @param { string } uuid
*
* @returns
*/
getSpellsFromOne(uuid) {
return new Promise((resolve, reject) => {
if (!(uuid)) {
reject({
"message": "La requête doit renseigner un uuid.",
"code": 400,
})
}
new model()
.where({ 'uuid': uuid })
.fetch({ withRelated: ['role', 'spells.schools.meta_schools', 'spells.variables', 'spells.ingredients'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }));
})
.catch(() => {
reject({
"message": "Les sorts liés à cet utilisateur n'ont pas été trouvés.",
"code": 404,
});
});
})
}
/**
* Registers a user based on the model at ./models/user-model.js.
*
* @param { object} u
* User object
*
* @returns { Promise }
* Fulfilled data: Queried user object.
*/
addOne(u) {
return new Promise(async (resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't
if (isEmptyObject(u)) {
reject({
"message": "Le corps de requête ne peut être vide.",
"code": 403,
})
} else if (!validator.validate(u, UserValidation).valid) {
reject({
"message": "Structure de la requête invalide - " + validator.validate(u, UserValidation).errors,
"code": 403,
})
} else if (isXSSAttempt(u.name) || isXSSAttempt(u.password) || isXSSAttempt(u.mail)) {
reject({
"message": "Essai d'injection détecté, avortement de la requête.",
"code": 403,
})
} else {
let hash = await bcrypt.hash(u.password, 10);
let uuid = uuidv4();
let verification_token = uuidv4();
this.checkIfEmailAvailable(u.mail)
.then(() => {
bookshelf.transaction(t => {
return new model({
'uuid': uuid,
'name': u.name,
'mail': u.mail,
'password': hash,
'verification_token': verification_token,
})
.save(null, {
transacting: t
})
.catch(err => {
console.log(err);
reject({
"message": "Un attribut de l'utilisateur a provoqué une erreur d'insertion.",
"code": 500,
});
})
})
.then(() => {
return this.getOneByUUID(uuid, false)
})
.then(newUser => {
// Send a mail to the new user's email with a link to verification url
mails.sendRegistrationMail({
user: {
name: newUser.name,
mail: newUser.mail,
token: verification_token,
}
});
// Then resolves the api call
resolve({
"message": `Compte utilisateur #${newUser.id} créé avec succès.`,
"code": 201,
"user": newUser,
});
})
.catch(err => {
console.log(err);
resolve({
"message": "Une erreur s'est produite en créant votre compte. Veuillez réessayer ultérieurement ou contactez l'administrateur.",
"code": 500,
})
})
})
.catch(err => {
reject(err)
})
}
})
}
/**
* Verifies an account based on a private UUID token
*
* @param { string } token
* A UUID v4 identification token provided at registration and on special demands
*
* @returns Redirects to login page
*/
verifyUser(token) {
return new Promise((resolve, reject) => {
new model()
.where({ 'verification_token': token })
.fetch()
.then(v => {
bookshelf.transaction(t => {
return v.save({
'verification_token': null,
'verified': 1,
}, {
method: 'update',
transacting: t
})
})
.then(() => {
resolve({
"message": "Insérez ici une future redirection vers le client.",
"code": 202,
})
})
})
.catch(() => {
reject({
"message": "Le lien de vérification ne semble pas correct.",
"code": 404,
})
})
});
}
/**
* Logs a user by comparing the dual mail/password inputs
*
* @param { string } mail
* @param { string } password
*
* @return { Promise }
* Fulfilled data: Queried user object.
*/
logUser(mail, password) {
return new Promise((resolve, reject) => {
this.getOneByEmail(mail, true)
.then(async fetchedUser => {
let match = await bcrypt.compare(password, fetchedUser.password);
// Makes sure no hash gets out
delete fetchedUser.password;
// If you found a user...
if (match) {
// If they're banned...
if (fetchedUser.banned) {
reject({
"message": `L'utilisateur #${fetchedUser.name} a été banni, la connexion est impossible.`,
"code": 403,
});
// If they're not verified...
} else if (!fetchedUser.verified) {
reject({
"message": `L'utilisateur #${fetchedUser.name} n'as pas été vérifié, le compte doit être activé avant la connexion.`,
"code": 401,
});
} else {
resolve({
"message": `L'utilisateur #${fetchedUser.name} s'est connecté.`,
"code": 200,
"user": fetchedUser,
});
}
} else {
reject({
"message": "Les informations de connexions sont erronées.",
"code": 400,
});
}
})
.catch(err => {
reject(err);
})
})
}
/**
* Generate a token for the user to use the API
* Requires mail and password for verifying the user
*
* @param { string } mail
* @param { string } password
*
* @returns { Promise }
* Fulfilled data: A unique UUID token string.
*/
genAPIToken(mail, password) {
return new Promise((resolve, reject) => {
this.logUser(mail, password)
.then(v => {
let user = v.user;
let new_token = uuidv4();
bookshelf.transaction(t => {
return new token_model({
'value': new_token,
'user_uuid': user.uuid,
})
.save(null, {
transacting: t
})
.catch(err => {
// If the account already has an API key linked...
if (err.errno === 1062) {
this.fetchAPIKey(user.uuid)
.then(old_api_key => {
reject({
"message": "Votre compte a déjà généré une clé d'API.",
"code": 409,
"API_key": old_api_key.value,
});
});
// Default errors
} else {
throw err
}
});
})
.then(api_key => {
resolve({
"message": "La clé d'API a été généré.",
"code": 201,
"API_key": api_key,
})
})
.catch(err => {
console.log(err);
reject({
"message": "La génération de jeton d'API n'a pas pu être conclue.",
"code": 500,
});
})
})
.catch(err => {
reject(err);
})
})
}
checkAPITokenPerms(token, permissions) {
return new Promise(async (resolve, reject) => {
// If the request doesn't have a token, reject it.
if (!token) {
reject({
"message": "Vous devez utiliser un jeton d'API dans l'en-tête de votre requête.",
"code": 401,
})
}
// Fetches user from token
new token_model()
.where({ 'value': token })
.fetch({ withRelated: 'user.role.permissions' })
// Catches not found errors
.catch(err => {
if (err instanceof token_model.NotFoundError) {
reject({
"message": "Ce jeton n'est affilié à aucun compte.",
"code": 404,
});
} else {
reject({
"message": "Le jeton ou l'utilisateur n'a pas pu être récupéré.",
"code": 500,
});
}
})
.then(fullToken => {
token = fullToken.toJSON({ omitPivot: true });
// If the token is associated with a banned user...
if (token.user.banned) {
reject({
"message": "Le jeton est lié à un utilisateur banni, il n'est donc plus utilisable.",
"code": 401,
});
}
// If the token is associated with a non-verified user...
if (!token.user.verified) {
reject({
"message": "Le jeton est lié à un utilisateur non-vérifié, il n'est donc pas utilisable.",
"code": 401,
});
}
let user_permissions = token.user.role.permissions;
// Convert user_perm to array
user_permissions.forEach((user_perm, i) => {
user_permissions[i] = user_perm.slug;
});
// Loops to check if the person has all the permissions required...
permissions.forEach(perm => {
// If not, reject...
if (!user_permissions.includes(perm)) {
reject({
"message": "Permissions insuffisantes.",
"code": 401
})
}
});
// If everything went well, resolve the promise.
resolve({
"message": "Credentials accepted.",
"code": 200
});
})
// Unhandled errors
.catch(() => {
reject({
"message": "Une erreur inconnue est survenue.",
"code": 500,
});
});
})
}
/**
* Check if the email that was input is available for account creation.
*
* @param {string} mail
*
* @returns { Promise }
* Fulfilled: HTTP 200 if email is available.
* Rejected: HTTP 400-409 if email is already used.
*/
checkIfEmailAvailable(mail) {
return new Promise((resolve, reject) => {
if (!(mail)) {
reject({
"message": "La requête doit renseigner un email.",
"code": 400,
})
}
if (!this.validateMail(mail)) {
reject({
"message": "La requête n'est pas un email valide.",
"code": 400,
})
}
this.getOneByEmail(mail, false)
.then(() => {
reject({
"message": "Cet email est déjà lié à un compte.",
"code": 409,
})
})
.catch(() => {
resolve({
"message": "Cet email est disponible.",
"code": 200,
})
})
})
}
/**
* Fetches the associated api_token from a user uuid.
*
* @param { string } uuid
*
* @returns { Promise }
* Fulfilled data: Queried API token object
*/
fetchAPIKey(uuid) {
return new Promise((resolve, reject) => {
new token_model()
.where({ 'user_uuid': uuid })
.fetch()
.then(v => {
resolve(v.toJSON({ omitPivot: true }));
})
.catch(err => {
reject(err);
})
})
}
/**
* Whether a mail is correctly formed and ripe for receiving, ie: xxx@yyy.zzz.
*
* @param { string } mail
*
* @returns { boolean }
*/
validateMail(mail) {
const regex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return regex.test(String(mail).toLowerCase());
}
}
module.exports = UserRepository

View File

@@ -1,204 +0,0 @@
// Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf;
const model = require('../models/variable-model');
// Model validation
const Validator = require('jsonschema').Validator;
const validator = new Validator();
const VariableValidation = require("../validations/VariableValidation");
validator.addSchema(VariableValidation, "/VariableValidation");
// Validations
const isXSSAttempt = require('../functions').isXSSAttempt;
const isEmptyObject = require('../functions').isEmptyObject;
class VariableRepository {
constructor() {
}
getAll() {
return new Promise((resolve, reject) => {
new model()
.fetchAll({ withRelated: ['spells'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }));
})
.catch(err => {
console.log(err);
reject({
"message": "Il n'existe aucune variable disponible.",
"code": 404,
});
});
});
}
getOne(id) {
return new Promise((resolve, reject) => {
new model()
.where({ 'id': id })
.fetch({ withRelated: ['spells'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }));
})
.catch(err => {
console.log(err);
reject({
"message": "La variable en question n'a pas pu être trouvée.",
"code": 404,
});
});
});
}
getSpellsFromOne(id) {
return new Promise((resolve, reject) => {
new model()
.where({ 'id': id })
.fetch({ withRelated: ['spells', 'spells.schools', 'spells.variables', 'spells.ingredients', 'spells.schools.meta_schools'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }));
})
.catch(err => {
console.log(err);
reject({
"message": "Les sortilèges liés à cette variable n'ont pas pu être récupérés.",
"code": 404,
});
});
});
}
addOne(vr) {
return new Promise((resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't
if (isEmptyObject(vr)) {
reject({
"message": "Le corps de la requête ne peut pas être vide.",
"code": 403,
});
} else if (!validator.validate(vr, VariableValidation).valid) {
reject({
"message": `Le modèle de variable n'est pas respecté : ${validator.validate(s, VariableValidation).errors}`,
"code": 403,
});
} else if (isXSSAttempt(vr.description)) {
reject({
"message": "Tentative d'injection XSS détectée, requête refusée.",
"code": 403,
});
} else {
bookshelf.transaction(t => {
return new model({
'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({
"message": "Une erreur d'insertion s'est produite.",
"code": 500,
});
});
}
});
}
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 (isEmptyObject(vr)) {
reject({
"message": "Le corps de la requête ne peut pas être vide.",
"code": 403,
});
} else if (!validator.validate(vr, VariableValidation).valid) {
reject({
"message": `Le modèle de variable n'est pas respecté : ${validator.validate(s, VariableValidation).errors}`,
"code": 403,
});
} else if (isXSSAttempt(vr.description)) {
reject({
"message": "Tentative d'injection XSS détectée, requête refusée.",
"code": 403,
});
} else {
new model({ 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({
"message": "Une erreur d'insertion s'est produite.",
"code": 500,
});
});
})
.catch(err => {
console.log(err);
reject({
"message": "La variable en question n'a pas été trouvée.",
"code": 404,
});
});
}
});
}
deleteOne(id) {
return new Promise((resolve, reject) => {
new model()
.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({
"message": "La variable en question n'a pas été trouvée.",
"code": 404,
});
});
});
}
}
module.exports = VariableRepository;

View File

@@ -1,28 +0,0 @@
// Router
const express = require('express');
let router = express.Router();
// Repository
const UserRepository = require('../repositories/user-repository');
const Users = new UserRepository();
// ROUTES
// GEN API TOKEN
const generateAPIToken = (mail, password) => {
return Users.genAPIToken(mail, password)
.catch(err => {
throw err;
});
};
router.get('/genToken', async (req, res) => {
generateAPIToken(req.body.mail, req.body.password)
.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(err));
});
});
module.exports = router;

View File

@@ -1,18 +0,0 @@
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');
const auth = require('./auth');
module.exports = {
auth,
spells,
schools,
meta_schools,
ingredients,
variables,
users,
};

View File

@@ -1,183 +0,0 @@
// Router
const express = require('express');
let router = express.Router();
// AuthGuard
const authGuard = require('./middleware/authGuard');
// Repository
const IngredientRepository = require('../repositories/ingredient-repository');
const Ingredients = new IngredientRepository();
// Functions
const functions = require('../functions');
// 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
})
);
});
});
// GET SPELLS FROM ONE ------------------
const getSpellsFromOne = (id) => {
return Ingredients.getSpellsFromOne(id)
.catch(err => {
console.log(err);
throw err;
});
};
router.get(
'/:id/spells',
async (req, res) => {
getSpellsFromOne(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(
'/',
authGuard(['SUBMIT_INGREDIENTS']),
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/',
authGuard(['SUBMIT_INGREDIENTS', 'MODIFY_INGREDIENTS']),
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/',
authGuard(['SUBMIT_INGREDIENTS', 'MODIFY_INGREDIENTS', 'DELETE_INGREDIENTS']),
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 validations
router.param('id', functions.paramIntCheck);
module.exports = router;

View File

@@ -1,65 +0,0 @@
// Router
const express = require('express');
let router = express.Router();
// Functions
const functions = require('../functions');
// Repository
const MetaSchoolRepository = require('../repositories/meta-school-repository');
const MetaSchools = new MetaSchoolRepository();
// 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 validations
router.param('id', functions.paramIntCheck);
module.exports = router;

View File

@@ -1,23 +0,0 @@
// Repository
const UserRepository = require('../../repositories/user-repository');
const Users = new UserRepository();
// AUTHGUARD
const authGuard = (permissions) => {
return async (req, res, next) => {
// Get token from headers
let api_token = req.headers['auracle_key'];
// Uses repo to validate the associated perms with the token
Users.checkAPITokenPerms(api_token, permissions)
.then(() => {
next();
})
.catch(err => {
res.status(err.code).send(JSON.stringify(err));
});
};
};
module.exports = authGuard;

View File

@@ -1,182 +0,0 @@
// Router
const express = require('express');
let router = express.Router();
// AuthGuard
const authGuard = require('./middleware/authGuard');
// Repository
const SchoolRepository = require('../repositories/school-repository');
const Schools = new SchoolRepository();
// Functions
const functions = require('../functions');
// ROUTES
// GET ALL ------------------
const getSchools = () => {
return Schools.getAll()
.catch(err => {
console.log(err);
throw err;
});
};
router.get(
'/',
async (req, res) => {
getSchools()
.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 getSchool = (id) => {
return Schools.getOne(id)
.catch(err => {
console.log(err);
throw err;
});
};
router.get(
'/:id/',
async (req, res) => {
getSchool(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
})
);
});
});
// GET SPELLS FROM ONE ------------------
const getSpellsFromOne = (id) => {
return Schools.getSpellsFromOne(id)
.catch(err => {
console.log(err);
throw err;
});
};
router.get(
'/:id/spells',
async (req, res) => {
getSpellsFromOne(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 addSchool = (s) => {
return Schools.addOne(s)
.catch(err => {
console.log(err);
throw err;
});
};
router.post(
'/',
authGuard(['SUBMIT_SCHOOL']),
async (req, res) => {
addSchool(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 updateSchool = (id, s) => {
return Schools.updateOne(id, s)
.catch(err => {
console.log(err);
throw err;
});
};
router.put(
'/:id/',
authGuard(['SUBMIT_SCHOOLS', 'MODIFY_SCHOOLS']),
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/',
authGuard(['SUBMIT_SCHOOLS', 'MODIFY_SCHOOLS', 'DELETE_SCHOOLS']),
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 validations
router.param('id', functions.paramIntCheck);
module.exports = router;

View File

@@ -1,209 +0,0 @@
// Router
const express = require('express');
let router = express.Router();
// AuthGuard
const authGuard = require('./middleware/authGuard');
// Repository
const SpellReposity = require('../repositories/spell-repository');
const Spells = new SpellReposity();
// Functions
const functions = require('../functions');
// ROUTES
// GET ALL PUBLIC ------------------
const getPublicSpells = (name, description, level, charge, cost, ritual) => {
return Spells.getAllPublic(name, description, level, charge, cost, ritual)
.catch(err => {
console.log(err);
throw err;
});
};
router.get(
'//:name?/:description?/:level?/:charge?/:cost?/:ritual?/',
async (req, res) => {
getPublicSpells(req.query.name, req.query.description, req.query.level, req.query.charge, req.query.cost, req.query.ritual)
.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 ALL ------------------
const getSpells = (name, description, level, charge, cost, ritual) => {
return Spells.getAll(name, description, level, charge, cost, ritual)
.catch(err => {
console.log(err);
throw err;
});
};
router.get(
'/private/:name?/:description?/:level?/:charge?/:cost?/:ritual?/',
authGuard(['SECRET_SPELLS']),
async (req, res) => {
getSpells(req.query.name, req.query.description, req.query.level, req.query.charge, req.query.cost, req.query.ritual)
.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 SOME ------------------
const getSomeSpells = (page) => {
return Spells.getPage(page)
.catch(err => {
console.log(err);
throw err;
});
};
router.get(
'/page/:page',
async (req, res) => {
getSomeSpells(req.params.page)
.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 getSpell = (id) => {
return Spells.getOne(id)
.catch(err => {
console.log(err);
throw err;
});
};
router.get(
'/:id/',
async (req, res) => {
getSpell(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 addSpell = (s) => {
return Spells.addOne(s)
.catch(err => {
console.log(err);
throw err;
});
};
router.post(
'/',
authGuard(['SUBMIT_SPELLS']),
async (req, res) => {
addSpell(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 updateSpell = (id, s) => {
return Spells.updateOne(id, s)
.catch(err => {
console.log(err);
throw err;
});
};
router.put(
'/:id/',
authGuard(['SUBMIT_SPELLS', 'MODIFY_SPELLS']),
async (req, res) => {
updateSpell(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 deleteSpell = (id) => {
return Spells.deleteOne(id)
.catch(err => {
console.log(err);
throw err;
});
};
router.delete(
'/:id/',
authGuard(['SUBMIT_SPELLS', 'MODIFY_SPELLS', 'DELETE_SPELLS']),
async (req, res) => {
deleteSpell(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 validations
router.param('id', functions.paramIntCheck);
router.param('page', functions.paramIntCheck);
module.exports = router;

View File

@@ -1,178 +0,0 @@
// Router
const express = require('express');
let router = express.Router();
// AuthGuard
// const authGuard = require('./middleware/authGuard');
// Repository
const UserRepository = require('../repositories/user-repository');
const Users = new UserRepository();
// ROUTES
// GET ALL ------------------
const getUsers = () => {
return Users.getAll()
.catch(err => {
throw err;
});
};
router.get('/', async (req, res) => {
getUsers()
.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 FROM UUID ------------------
const getUserByUUID = (uuid) => {
return Users.getOneByUUID(uuid)
.catch(err => {
throw err;
});
};
router.get('/:uuid/', async (req, res) => {
getUserByUUID(req.params.uuid)
.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 SPELLS FROM ONE ------------------
const getSpellsFromUser = (uuid) => {
return Users.getSpellsFromOne(uuid)
.catch(err => {
throw err;
});
};
router.get('/:uuid/spells', async (req, res) => {
getSpellsFromUser(req.params.uuid)
.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
})
);
});
});
// CHECK IF MAIL IS AVAILABLE ------------------
const checkIfEmailAvailable = (mail) => {
return Users.checkIfEmailAvailable(mail)
.catch(err => {
throw err;
});
};
router.get('/available/:mail/', async (req, res) => {
checkIfEmailAvailable(req.params.mail)
.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 addUser = (u) => {
return Users.addOne(u)
.catch(err => {
throw err;
});
};
router.post('/', async (req, res) => {
addUser(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
})
);
});
});
// VERIFY A USER
const verifyUser = (token) => {
return Users.verifyUser(token)
.catch(err => {
throw err;
});
};
router.get('/verification/:token', async (req, res) => {
verifyUser(req.params.token)
.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
})
);
});
});
// LOG A USER ------------------
const logUser = (mail, password) => {
return Users.logUser(mail, password)
.catch(err => {
throw err;
});
};
router.post('/login', async (req, res) => {
logUser(req.body.mail, req.body.password)
.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
})
);
});
});
module.exports = router;

View File

@@ -1,185 +0,0 @@
'use strict';
// Router
const express = require('express');
let router = express.Router();
// AuthGuard
const authGuard = require('./middleware/authGuard');
// Repository
const VariableRepository = require('../repositories/variable-repository');
const Variables = new VariableRepository();
// Functions
const functions = require('../functions');
// 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
})
);
});
});
// GET SPELLS FROM ONE ------------------
const getSpellsFromOne = (id) => {
return Variables.getSpellsFromOne(id)
.catch(err => {
console.log(err);
throw err;
});
};
router.get(
'/:id/spells',
async (req, res) => {
getSpellsFromOne(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(
'/',
authGuard(['SUBMIT_VARIABLES']),
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/',
authGuard(['SUBMIT_VARIABLES', 'MODIFY_VARIABLES']),
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/',
authGuard(['SUBMIT_VARIABLES', 'MODIFY_VARIABLES', 'DELETE_VARIABLES']),
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 validations
router.param('id', functions.paramIntCheck);
module.exports = router;

View File

@@ -1,14 +0,0 @@
const nodemailer = require('nodemailer');
const transport = nodemailer.createTransport({
host: "smtp.mailtrap.io",
port: 2525,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
}
});
module.exports = {
transport,
};

View File

@@ -1,61 +0,0 @@
const smtp = require('./config');
const fs = require('fs');
const handlebars = require('handlebars');
// Sender address for mail service
const sender = 'tymos@auracle.io';
// Fetches a HTML template file for parsing
const getTemplateFile = (path) => {
return new Promise((resolve, reject) => {
fs.readFile(path, { encoding: 'utf-8' }, (err, html) => {
if (err) {
reject(err);
} else {
resolve(html);
}
});
});
};
/**
* SEND REGISTRATION MAIL FUNCTION
* @param {Object} data
* - user
* - name
* - mail
* - token
*/
const sendRegistrationMail = (data) => {
getTemplateFile(__dirname + '/templates/template-sign-up.html')
.then(html => {
let template = handlebars.compile(html);
let template_parsed = template(data);
let message = {
from: sender,
to: data.user.mail,
subject: 'Inscription Auracle.io',
html: template_parsed,
};
smtp.transport.sendMail(message, (err, info) => {
if (err) {
throw err;
} else {
console.log(info);
}
});
})
.catch(err => {
console.log(err);
});
};
// const sendBanEmail = (date) => {
// return null;
// };
module.exports = {
sendRegistrationMail,
};

View File

@@ -1,114 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Template Email Auracle Inscription</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&family=Playfair+Display:wght@700;800;900&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--black: 52, 58, 64;
--white: #FFF;
--primary: 89, 158, 244;
}
body {
color: rgb(var(--black));
font-family: 'Lato', sans-serif;
max-width: 100%;
}
.display {
font-family: 'Playfair Display', serif;
}
h1 {
font-size: 32px;
}
p:not(:last-child) {
margin-bottom: 15px;
}
.italics {
font-style: italic;
}
.wrapper {
position: relative;
padding: 10vh 5vw;
background: url('https://i.imgur.com/IxEKKEY.png') center center fixed repeat;
}
.wrapper:before {
display: block;
content: '';
position: absolute;
top: 0;
height: 120px;
left: 0;
right: 0;
background-color: rgb(var(--primary));
z-index: 0;
}
.container {
position: relative;
background-color: var(--white);
z-index: 5;
}
header,
footer {
text-align: center;
color: var(--white);
background-color: rgb(var(--black));
}
header {
padding: 15px;
}
main {
padding: 30px;
}
</style>
</head>
<body>
<div class="wrapper">
<div class="container">
<header>
<h1 class="display">Bienvenue sur Auracle, {{ user.name }}!</h1>
</header>
<main>
<p>Votre compte Auracle a bien été enregistré et est rattaché à cette adresse mail.</p>
<p>Cependant, afin de garantir la sécurité de vos données, votre compte doit être vérifié avant votre
première connexion. Vous pouvez
<a href="http://localhost:2814/api/v1/users/verification/{{ user.token }}">
suivre ce lien afin de confirmer votre inscription.
</a>
</p>
<p>Si vous rencontrez des problèmes, n'hésitez pas à envoyer un mail à
<a href="mailto:support@auracle.io">
support@auracle.io
</a>
</p>
<p><strong>Merci de votre soutien !</strong></p>
<p class="italics">Bien sincèrement, Izàc Tymos.</p>
</main>
<footer>
<div class="icon">
</div>
</footer>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,161 @@
import express, { Application } from 'express'
import { Connection } from 'typeorm'
import { VSColors } from '../enums/VSColors'
import { AuracleApiRouter } from './AuracleApiRouter'
/**
* AuracleApi is the main class handling all the routing, modules and middlewares.
* The
* Once the constructor is called, it automatically listens on the given port.
*/
export class AuracleApi {
/**
* Express core App.
*/
public app: Application
/**
* The port where the Express app is running on.
* @default 3000
*/
public port = 3000
/**
* Current version of the API.
*
* The usual format follows after `v1`
*/
public version = 'v1'
/**
* Base URI to access the various endpoints of the Express App.
*
* The usual format follows
* ```javascript
* /api/${this.version}/
* ```
*/
public baseUri = `/api/${this.version}/`
constructor(
init: {
database: Promise<Connection>
port?: any
routers?: Array<AuracleApiRouter>
modules?: Array<any>
middlewares?: Array<any>
}
) {
this.app = express()
if (init.port) {
this.port = Number(init.port)
}
console.log(VSColors.cyan, `[INFO] Running in a ${process.env.NODE_ENV} environment.`)
// Making sure the DB is up before init routers, modules, etc...
if (init.database) {
this.connectDatabase(init.database)
.then(async () => {
// Loads other services
await this.registerServices(init.routers, init.modules, init.middlewares)
this.listen()
})
.catch((err) => {
console.error(VSColors.red, "[ERROR] App couldn't boot up with the following errors : ")
console.error(VSColors.red, err)
})
}
}
/**
* Links the API with a TypeORM connection
*/
private async connectDatabase(db_driver: Promise<Connection>) {
try {
// Tries to connect...
console.info(VSColors.yellow, '[TASK] Trying to connect to the database...')
const connection = await db_driver
console.info(VSColors.green, '[SUCCESS] Connection has been established successfully.')
// Should the DB be rebuilt ? Are we in production ?
const dropDatabase = (process.env.NODE_ENV === 'prod') ? false : true
// Then builds the data schema from registered entities...
console.info(VSColors.yellow, '[TASK] Syncing database and its schemas...')
await connection.synchronize(dropDatabase)
console.info(VSColors.green, '[SUCCESS] Syncing done.')
return connection
} catch (err) {
console.error(VSColors.red, '[ERROR] Unable to connect to the database !')
throw err
}
}
/**
* Registers other express services to add onto the core app
*
* @param routers A list of Express Routers extended from AuracleApiRouter
* @param modules A list of Express extensions
* @param middlewares A list of Express middlewares that handle other functions
*/
private async registerServices(routers: Array<AuracleApiRouter>, modules: Array<any>, middlewares: Array<any>) {
return Promise.all([
this.modules(modules),
this.routers(routers),
this.middlewares(middlewares)
])
.catch((err) => {
throw err
})
}
/**
* Initializes the App with a bunch of modules like helmet, cors, morgan...
* @param modules An array of modules and extensions
*/
private async modules(modules: Array<any>) {
try {
modules.forEach(module => this.app.use(module))
} catch (err) {
console.error(VSColors.red, '[ERROR] Unable to initialize modules and extensions.')
throw err
}
}
/**
* Initializes routers
* @param routers An array of AuracleApiRouter children objects
*/
private async routers(routers: Array<AuracleApiRouter>) {
try {
routers.forEach(router => this.app.use(`${this.baseUri}`, router.instance))
} catch (err) {
console.error(VSColors.red, '[ERROR] Unable to initialize routers.')
throw err
}
}
private async middlewares(middleWares: {
forEach: (arg0: (middleWare: any) => void) => void;
}) {
try {
middleWares.forEach(middleWare => this.app.use(middleWare))
} catch (err) {
console.error(VSColors.red, '[ERROR] Unable to initialize middlewares.')
throw err
}
}
/**
* Allows the App to run on its given port
*/
public listen() {
this.app.listen(this.port, () => console.info(VSColors.green, `\n[APP] App listening on http://localhost${this.baseUri}.`))
}
}

View File

@@ -0,0 +1,9 @@
import { Repository } from "typeorm";
export class AuracleApiController {
public repository: Repository<any>
constructor() {
// Todo
}
}

View File

@@ -0,0 +1 @@
export class AuracleApiModel {}

View File

@@ -0,0 +1,63 @@
import { Response } from "express"
/**
* An enum that contains all the default value for the ApiResponse class
*/
enum AuracleApiResponseMessages {
defaultValid = "La requête a été effectuée avec succès.",
default201 = "La ressource a été créée avec succès.",
defaultAdditionnalAction = "La requête redirige vers une autre URL.",
default301 = "La ressource n'est plus accessible à cet URI et a été déplacée de manière permanente.",
default302 = "La ressource n'est plus accessible à cet URI et a été déplacée de manière temporaire.",
defaultClientError = "Une erreur inconnue dûe à une erreur client s'est produite",
default401 = "Vous n'êtes pas autorisé à consulter cette ressource car vous n'êtes pas identifié.",
default403 = "Vous n'êtes pas autorisé à consulter cette ressource car vous n'avez pas le niveau d'authentification requis.",
default404 = "La ressource demandée n'est pas ou plus disponible.",
default405 = "Cette méthode n'est pas disponible pour cette ressource.",
default409 = "La requête génère un conflit de ressource et ne peut aboutir.",
defaultServerError = "Une erreur serveur inconnue s'est produite, veuillez contactez l'administrateur de l'application.",
}
export class AuracleApiResponse {
public status: number
public message?: string
public data?: Object | Array<any>
constructor(status: number, data?: Array<any> | Object, message?: string) {
this.status = status
this.data = data
if (message) {
this.message = message
}
// If a message was not provided, gets one by default
else {
// HTTP Valid ranges
if ((this.status >= 200) && (this.status <= 299)) {
this.message = AuracleApiResponseMessages.defaultValid
}
// HTTP Additionnal Action ranges
else if ((this.status >= 300) && (this.status <= 399)) {
this.message = AuracleApiResponseMessages.defaultAdditionnalAction
}
// HTTP Client Error ranges
else if ((this.status >= 400) && (this.status <= 499)) {
this.message = AuracleApiResponseMessages.defaultClientError
}
// HTTP Server Error ranges
else if ((this.status >= 500) && (this.status <= 599)) {
this.message = AuracleApiResponseMessages.defaultServerError
}
}
}
public send(res: Response): Response {
return res.status(this.status).json(this)
}
}

View File

@@ -0,0 +1,17 @@
import { Router, RouterOptions } from "express";
import { AuracleApiController } from "./AuracleApiController";
export abstract class AuracleApiRouter {
public instance: Router
public routerOptions: RouterOptions
public resource: string
public controller: AuracleApiController
constructor(resource: string, controller: AuracleApiController, options?: RouterOptions) {
this.resource = resource
this.routerOptions = options
this.controller = controller
this.instance = Router(this.routerOptions) as Router
}
}

View File

@@ -0,0 +1,7 @@
export enum VSColors {
red = '\x1b[31m%s\x1b[0m',
yellow = '\x1b[33m%s\x1b[0m',
green = '\x1b[32m%s\x1b[0m',
cyan = '\x1b[36m%s\x1b[0m',
blue = '\x1b[34m%s\x1b[0m'
}

View File

@@ -1,8 +1,10 @@
import { NextFunction, Response } from "express";
const regexInt = RegExp(/^[1-9]\d*$/);
const regexXSS = RegExp(/<[^>]*script/);
// Check if int for param validation
const paramIntCheck = (req, res, next, input) => {
const paramIntCheck = (req: Request, res: Response, next: NextFunction, input: any) => {
try {
if (regexInt.test(input)) {
next();
@@ -19,12 +21,12 @@ const paramIntCheck = (req, res, next, input) => {
};
// Check if script injection attempt
const isXSSAttempt = (string) => {
const isXSSAttempt = (string: string) => {
return regexXSS.test(string);
};
// Check if object is null
const isEmptyObject = (obj) => {
const isEmptyObject = (obj: Object) => {
return (Object.keys(obj).length === 0 && obj.constructor === Object);
};
@@ -32,4 +34,4 @@ module.exports = {
paramIntCheck,
isXSSAttempt,
isEmptyObject
};
};

View File

@@ -0,0 +1,52 @@
import express from 'express';
import { getRepository } from 'typeorm';
import { AuracleApiController } from "../common/classes/AuracleApiController";
import { AuracleApiResponse } from '../common/classes/AuracleApiResponse';
import { Spell } from '../database/models/spells/Spell';
export class SpellController extends AuracleApiController {
constructor() {
super()
}
public async getAll(req: express.Request, res: express.Response) {
let spells: Spell[]
try {
spells = await getRepository(Spell).find()
res = new AuracleApiResponse(200, spells).send(res)
} catch (err) {
res = new AuracleApiResponse(400).send(res)
}
}
public async getOne(req: express.Request, res: express.Response) {
const uuid = req.params.uuid
let spell: Spell
try {
spell = await getRepository(Spell).findOne(uuid)
res = new AuracleApiResponse(200, spell).send(res)
} catch (err) {
res = new AuracleApiResponse(400).send(res)
}
}
public async createOne(req: express.Request, res: express.Response) {
const spell = {
name: req.body.name,
description: req.body.description,
level: req.body.level,
charge: req.body.charge,
cost: req.body.cost,
isRitual: req.body.isRitual
}
try {
const newSpell = getRepository(Spell).insert(spell)
res = new AuracleApiResponse(201, newSpell).send(res)
} catch (err) {
console.log(err)
}
}
}

View File

@@ -0,0 +1,4 @@
import { createConnection } from 'typeorm';
import { dbConfig } from './config'
export const AuracleDatabaseDriver = createConnection(dbConfig);

View File

@@ -0,0 +1,52 @@
import { MysqlConnectionOptions } from "typeorm/driver/mysql/MysqlConnectionOptions"
import * as fs from 'fs'
const tablePrefix = 'au_'
/**
* Fetches all Typescript files from a given directory
*
* @param dir The folder to scan where the models are located
* @param acc (Optional) The array to start
* @returns All .ts files within the dir
*/
export const fetchAllTypescriptFiles = (dir: string, acc: string[] = []): string[] => {
fs.readdirSync(dir)
.forEach(file => {
const fileIsTypescript = (file.split('.').pop() === 'ts')
const fileIsFolder = (file.indexOf('.') === -1)
if (fileIsFolder) {
fetchAllTypescriptFiles(`${dir}/${file}`, acc)
}
if (fileIsTypescript) {
acc.push(`${dir}/${file}`)
}
})
return acc
}
const modelDir = './src/database/models/'
const models = fetchAllTypescriptFiles(modelDir)
/**
* Registers the options to create the sequelize instance
*/
export const dbConfig: MysqlConnectionOptions = {
/**
* Access to database
*/
type: 'mariadb',
database: process.env.DB_NAME,
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
host: process.env.DB_HOST,
logging: false,
entityPrefix: tablePrefix,
entities: models
}

View File

@@ -0,0 +1,40 @@
import { Column, CreateDateColumn, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm";
import { AuracleApiModel } from "../../../common/classes/AuracleApiModel";
import { User } from "../users/User";
import { Spell } from "./Spell";
@Entity()
export class Ingredient extends AuracleApiModel {
@PrimaryGeneratedColumn('uuid')
public readonly uuid!: string
@Column({
unique: true
})
public name: string
@Column({
unique: true
})
public description: string
@Column()
public published: boolean
@ManyToMany(() => Spell, spell => spell.ingredients)
@JoinTable({
name: 'ingredients_spells'
})
public spells?: Spell[]
@ManyToOne(() => User, user => user.ingredients)
public creator: User
// TIMESTAMPS
@Column()
@CreateDateColumn()
public createdAt?: Date
@Column()
@UpdateDateColumn()
public updatedAt?: Date
}

View File

@@ -0,0 +1,37 @@
import { Column, CreateDateColumn, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm"
import { AuracleApiModel } from "../../../common/classes/AuracleApiModel"
import { User } from "../users/User"
import { School } from "./School"
@Entity()
export class MetaSchool extends AuracleApiModel {
@PrimaryGeneratedColumn('uuid')
public readonly uuid!: string
@Column({
unique: true
})
public name: string
@Column({
unique: true
})
public description: string
@Column()
public published: boolean
@OneToMany(() => School, school => school.metaSchool)
public schools?: School[]
@ManyToOne(() => User, user => user.metaSchools)
public creator: User
// TIMESTAMPS
@Column()
@CreateDateColumn()
public createdAt?: Date
@Column()
@UpdateDateColumn()
public updatedAt?: Date
}

View File

@@ -0,0 +1,44 @@
import { BaseEntity, Column, CreateDateColumn, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, Unique, UpdateDateColumn } from "typeorm";
import { AuracleApiModel } from "../../../common/classes/AuracleApiModel";
import { User } from "../users/User";
import { MetaSchool } from "./MetaSchool";
import { Spell } from "./Spell";
@Entity()
export class School extends AuracleApiModel {
@PrimaryGeneratedColumn('uuid')
public readonly uuid!: string
@Column({
unique: true
})
public name: string
@Column({
unique: true
})
public description: string
@Column()
public published: boolean
@ManyToMany(() => Spell, spell => spell.schools)
@JoinTable({
name: 'schools_spells'
})
public spells?: Spell[]
@ManyToOne(() => MetaSchool, metaSchool => metaSchool.schools)
public metaSchool: MetaSchool
@ManyToOne(() => User, user => user.schools)
public creator: User
// TIMESTAMPS
@Column()
@CreateDateColumn()
public createdAt?: Date
@Column()
@UpdateDateColumn()
public updatedAt?: Date
}

View File

@@ -0,0 +1,58 @@
import { Column, CreateDateColumn, Entity, JoinTable, ManyToMany, ManyToOne, OneToMany, OneToOne, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm"
import { AuracleApiModel } from "../../../common/classes/AuracleApiModel"
import { User } from "../users/User"
import { Ingredient } from "./Ingredient"
import { School } from "./School"
import { Variable } from "./Variable"
@Entity()
export class Spell extends AuracleApiModel {
@PrimaryGeneratedColumn('uuid')
public readonly uuid!: string
@Column({
unique: true
})
public name: string
@Column({
unique: true
})
public description: string
@Column()
public level?: number
@Column()
public charge?: number
@Column()
public cost?: string
@Column()
public isRitual?: boolean
@Column()
public published?: boolean
@Column()
public public?: boolean
// LINKED ENTITIES
@ManyToMany(() => School, school => school.spells)
public schools?: School[]
@ManyToMany(() => Variable, variable => variable.spells)
public variables?: Variable[]
@ManyToMany(() => Ingredient, ingredient => ingredient.spells)
public ingredients?: Ingredient[]
@ManyToOne(() => User, user => user.spells)
public creator: User
// TIMESTAMPS
@Column()
@CreateDateColumn()
public createdAt?: Date
@Column()
@UpdateDateColumn()
public updatedAt?: Date
}

View File

@@ -0,0 +1,36 @@
import { Column, CreateDateColumn, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm";
import { AuracleApiModel } from "../../../common/classes/AuracleApiModel";
import { User } from "../users/User";
import { Spell } from "./Spell";
@Entity()
export class Variable extends AuracleApiModel {
@PrimaryGeneratedColumn('uuid')
public readonly uuid!: string
@Column({
unique: true
})
public description: string
@Column()
public published: boolean
@ManyToMany(() => Spell, spell => spell.variables)
@JoinTable({
name: 'variables_spells'
})
public spells?: Spell[]
@ManyToOne(() => User, user => user.variables)
public creator: User
// TIMESTAMPS
@Column()
@CreateDateColumn()
public createdAt?: Date
@Column()
@UpdateDateColumn()
public updatedAt?: Date
}

View File

@@ -0,0 +1,26 @@
import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm";
import { AuracleApiModel } from "../../../common/classes/AuracleApiModel";
import { Role } from "./Role";
@Entity()
export class Permission extends AuracleApiModel {
@PrimaryGeneratedColumn('uuid')
public readonly uuid!: string
@Column({
unique: true
})
public slug: string
@ManyToOne(() => Role, role => role.permissions)
public roles: Role[]
// TIMESTAMPS
@Column()
@CreateDateColumn()
public createdAt?: Date
@Column()
@UpdateDateColumn()
public updatedAt?: Date
}

View File

@@ -0,0 +1,34 @@
import { Column, CreateDateColumn, Entity, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm";
import { AuracleApiModel } from "../../../common/classes/AuracleApiModel";
import { Permission } from "./Permission";
import { User } from "./User";
@Entity()
export class Role extends AuracleApiModel {
@PrimaryGeneratedColumn('uuid')
public readonly uuid!: string
@Column({
unique: true
})
public name: string
@Column({
unique: true
})
public description: string
@OneToMany(() => Permission, permission => permission.roles)
public permissions: Permission[]
@OneToMany(() => User, user => user.role)
public users: User[]
// TIMESTAMPS
@Column()
@CreateDateColumn()
public createdAt?: Date
@Column()
@UpdateDateColumn()
public updatedAt?: Date
}

View File

@@ -0,0 +1,60 @@
import { Column, CreateDateColumn, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm"
import { AuracleApiModel } from "../../../common/classes/AuracleApiModel"
import { Ingredient } from "../spells/Ingredient"
import { MetaSchool } from "../spells/MetaSchool"
import { School } from "../spells/School"
import { Spell } from "../spells/Spell"
import { Variable } from "../spells/Variable"
import { Role } from "./Role"
@Entity()
export class User extends AuracleApiModel {
@PrimaryGeneratedColumn('uuid')
public readonly uuid!: string
@Column({
unique: true
})
public readonly mail: string
@Column()
public readonly password: string
@Column()
public name: string
@Column()
public avatar: string
@Column()
public gender: string
@Column()
public verified: boolean
// CREATIONS
@OneToMany(() => Spell, spell => spell.creator)
public spells?: Spell[]
@OneToMany(() => School, school => school.creator)
public schools?: School[]
@OneToMany(() => Variable, variable => variable.creator)
public variables?: Variable[]
@OneToMany(() => Ingredient, ingredients => ingredients.creator)
public ingredients?: Ingredient[]
@OneToMany(() => MetaSchool, metaSchools => metaSchools.creator)
public metaSchools?: Ingredient[]
// ROLE
@ManyToOne(() => Role, role => role.users)
public role: Role
// TIMESTAMPS
@Column()
@CreateDateColumn()
public createdAt?: Date
@Column()
@UpdateDateColumn()
public updatedAt?: Date
}

36
src/main.ts Normal file
View File

@@ -0,0 +1,36 @@
/**
* Dependencies
*/
// .env Data
import * as dotenv from 'dotenv'
dotenv.config();
// TypeORM
import "reflect-metadata";
import morgan from 'morgan'
import helmet from 'helmet'
import express from 'express'
import { AuracleApi } from './common/classes/AuracleApi'
import { AuracleDatabaseDriver } from './database/AuracleDatabaseDriver'
import { SpellRouter } from './routes/SpellRouter';
const apiPort = process.env.API_PORT
const api = new AuracleApi({
database: AuracleDatabaseDriver,
port: apiPort,
middlewares: [],
routers: [
new SpellRouter
],
modules: [
express.json({ limit: '10kb' }),
morgan('dev'),
helmet()
],
})

23
src/routes/SpellRouter.ts Normal file
View File

@@ -0,0 +1,23 @@
import { AuracleApiRouter } from "../common/classes/AuracleApiRouter";
import { SpellController } from "../controllers/SpellController";
export class SpellRouter extends AuracleApiRouter {
public controller: SpellController
constructor() {
super('/spells', new SpellController)
this.initRoutes()
}
public initRoutes() {
// GET: /spells
this.instance.get(this.resource, this.controller.getAll.bind(this.controller))
// GET: /spells/uuid
this.instance.get(`${this.resource}/:uuid`, this.controller.getOne.bind(this.controller))
// POST: /spells
this.instance.post(this.resource, this.controller.createOne.bind(this.controller))
}
}

9
tsconfig.build.json Normal file
View File

@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"exclude": [
"node_modules",
"test",
"dist",
"**/*spec.ts"
]
}

24
tsconfig.json Normal file
View File

@@ -0,0 +1,24 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"noImplicitAny": true,
"alwaysStrict": true,
"charset": "utf-8",
"allowSyntheticDefaultImports": true,
"target": "es2017",
"sourceMap": true,
"outDir": "./dist",
"esModuleInterop": true,
"baseUrl": "/src",
"incremental": true,
"types": [
"node",
"express"
],
// Required for TypeORM
"emitDecoratorMetadata": true,
"experimentalDecorators": true
}
}

View File

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

View File

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

View File

@@ -1,39 +0,0 @@
const Spell = {
"id": "/SpellValidation",
"type": Object,
"properties": {
"name": { "type": "string" },
"description": { "type": "string" },
"level": { "type": "number" },
"charge": { "type": "number" },
"cost": { "type": "string" },
"is_ritual": { "type": "boolean" },
"schools": {
"type": { "type": "array" },
"items": {
"properties": {
"id": { "type": "number" },
}
}
},
"variables": {
"type": { "type": "array" },
"items": {
"properties": {
"id": { "type": "number" },
}
}
},
"ingredients": {
"type": { "type": "array" },
"items": {
"properties": {
"id": { "type": "number" },
}
}
}
},
"required": ["name", "description"]
};
module.exports = Spell;

View File

@@ -1,12 +0,0 @@
const User = {
"id": "/UserValidation",
"type": Object,
"properties": {
"name": { "type": "string" },
"mail": { "type": "string" },
"password": { "type": "string" },
},
"required": ["name", "password", "mail"]
};
module.exports = User;

View File

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