From 4cc7c5533e02a24139813158ad0558a549eba6fd Mon Sep 17 00:00:00 2001 From: Alexis <35.alexis.pele@gmail.com> Date: Fri, 30 Jul 2021 12:24:11 +0200 Subject: [PATCH] Massive cleanup Functionnal basic API calls for spells Cleaned up some functions Moved some files around --- src/common/classes/AuracleApi.ts | 135 +++++++++++++-------- src/common/classes/AuracleApiController.ts | 9 ++ src/common/classes/AuracleApiRepository.ts | 9 ++ src/common/classes/AuracleApiResponse.ts | 52 ++++++++ src/common/classes/AuracleApiRouter.ts | 17 +++ src/{ => common/functions}/functions.ts | 0 src/controllers/SpellController.ts | 54 +++++++++ src/database/AuracleDatabaseDriver.ts | 18 +-- src/database/config/index.ts | 6 +- src/database/models/spells/Ingredient.ts | 16 +-- src/database/models/spells/MetaSchool.ts | 16 +-- src/database/models/spells/School.ts | 16 +-- src/database/models/spells/Spell.ts | 48 ++++---- src/database/models/spells/Variable.ts | 16 +-- src/database/models/users/Permission.ts | 16 +-- src/database/models/users/Role.ts | 2 +- src/database/models/users/User.ts | 2 +- src/main.ts | 5 + src/repositories/SpellRepository.ts | 20 +++ src/routes/SpellRouter.ts | 23 ++++ tsconfig.json | 2 +- 21 files changed, 334 insertions(+), 148 deletions(-) create mode 100644 src/common/classes/AuracleApiController.ts create mode 100644 src/common/classes/AuracleApiRepository.ts create mode 100644 src/common/classes/AuracleApiResponse.ts create mode 100644 src/common/classes/AuracleApiRouter.ts rename src/{ => common/functions}/functions.ts (100%) create mode 100644 src/controllers/SpellController.ts create mode 100644 src/repositories/SpellRepository.ts create mode 100644 src/routes/SpellRouter.ts diff --git a/src/common/classes/AuracleApi.ts b/src/common/classes/AuracleApi.ts index 040540f..d031d9d 100644 --- a/src/common/classes/AuracleApi.ts +++ b/src/common/classes/AuracleApi.ts @@ -1,74 +1,111 @@ import express, { Application } from 'express' import { Sequelize } from 'sequelize' - -// import ControllerBase from './class/controller.base' +import { AuracleApiRouter } from './AuracleApiRouter' export class AuracleApi { public app: Application public port: number - public base_url = '/api/v1/' + public version = 'v1' + public baseUrl = `/api/${this.version}/` - private database?: Sequelize; - - constructor(init: { - port: any - middlewares: any - // controllers: Array - modules?: Array - database?: Sequelize - }) { + constructor( + init: { + port: any + database: Sequelize + routers?: Array + modules?: Array + middlewares?: Array + } + ) { this.app = express() this.port = Number(init.port) - this.database = init.database - - if (init.modules) { - this.modules(init.modules) - } - - if (init.middlewares) { - this.middlewares(init.middlewares) - } - - // if (init.controllers) { - // this.controller(init.controllers) - // } + // Making sure the DB is up before init routers, modules, etc... if (init.database) { this.db_connect(init.database) + .then(() => { + + // Sets up tasks to execute... + const tasks = [ + this.modules(init.modules), + this.routers(init.routers), + this.middlewares(init.middlewares) + ] + + Promise.all(tasks) + .catch((err) => { + throw err + }) + }) + .catch((err) => { + console.error("App couldn't boot up with the following errors : ") + console.error(err) + }) } } - private modules = (modules: Array) => { - modules.forEach((module) => { - this.app.use(module) - }) - } - - // private controller = (controllers: Array) => { - // controllers.forEach((controller) => { - // this.app.use(this.base_url + controller.path, controller.router) - // console.log(`${controller.name} chargé`) - // }) - // } - - private middlewares = (middleWares: { - forEach: (arg0: (middleWare: any) => void) => void; - }) => { - middleWares.forEach((middleWare) => { - this.app.use(middleWare) - }) - } - + /** + * Links the API with a Sequelize Database Object + * @param db_driver A Sequelize instance + */ private db_connect = async (db_driver: Sequelize) => { try { - await db_driver.authenticate(); + const connection = await db_driver.authenticate(); console.info('Connection has been established successfully.') + + return connection } catch (err) { - console.error('Unable to connect to the database') - console.error(err) + console.error('Unable to connect to the database.') + throw err } } + /** + * Initializes the App with a bunch of modules like helmet, cors, morgan... + * @param modules An array of modules and extensions + */ + private modules = async (modules: Array) => { + try { + modules.forEach((module) => { + this.app.use(module) + }) + } catch (err) { + console.error('Unable to initialize modules and extensions.') + throw err + } + } + + /** + * Initializes routers + * @param routers An array of AuracleApiRouter children objects + */ + private routers = async (routers: Array) => { + try { + routers.forEach((router) => { + this.app.use(`${this.baseUrl}`, router.instance) + }) + } catch (err) { + console.error('Unable to initialize routers.') + throw err + } + } + + private middlewares = async (middleWares: { + forEach: (arg0: (middleWare: any) => void) => void; + }) => { + try { + middleWares.forEach((middleWare) => { + this.app.use(middleWare) + }) + } catch (err) { + console.error('Unable to initialize middlewares.') + throw err + } + } + + /** + * Allows the App to run on its given port + */ public listen = () => { this.app.listen(this.port, () => { console.log(`App listening on port ${this.port}`) diff --git a/src/common/classes/AuracleApiController.ts b/src/common/classes/AuracleApiController.ts new file mode 100644 index 0000000..8105064 --- /dev/null +++ b/src/common/classes/AuracleApiController.ts @@ -0,0 +1,9 @@ +import { AuracleApiRepository } from "./AuracleApiRepository"; + +export class AuracleApiController { + public repository: AuracleApiRepository + + constructor(repository: AuracleApiRepository) { + this.repository = repository + } +} diff --git a/src/common/classes/AuracleApiRepository.ts b/src/common/classes/AuracleApiRepository.ts new file mode 100644 index 0000000..480dca7 --- /dev/null +++ b/src/common/classes/AuracleApiRepository.ts @@ -0,0 +1,9 @@ +import { Model } from "sequelize/types"; + +export class AuracleApiRepository { + public model: Model + + constructor(model: Model) { + this.model = model + } +} diff --git a/src/common/classes/AuracleApiResponse.ts b/src/common/classes/AuracleApiResponse.ts new file mode 100644 index 0000000..e6fb4ec --- /dev/null +++ b/src/common/classes/AuracleApiResponse.ts @@ -0,0 +1,52 @@ +import { Response } from "express" + +/** + * An enum that contains all the default value for the ApiResponse class + */ +enum AuracleApiMessages { + defaultValid = "La requête a été effectuée avec succès.", + defaultRedirect = "La requête redirige vers une autre URL.", + defaultClientError = "Une erreur inconnue dûe à une erreur client s'est produite", + 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 + + constructor(status: number, data?: Array | 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 = AuracleApiMessages.defaultValid + } + + // HTTP Redirect ranges + else if ((this.status >= 300) && (this.status <= 399)) { + this.message = AuracleApiMessages.defaultRedirect + } + + // HTTP Client Error ranges + else if ((this.status >= 400) && (this.status <= 499)) { + this.message = AuracleApiMessages.defaultClientError + } + + // HTTP Server Error ranges + else if ((this.status >= 500) && (this.status <= 599)) { + this.message = AuracleApiMessages.defaultServerError + } + } + } + + public send(res: Response): Response { + return res.status(this.status).json(this) + } +} diff --git a/src/common/classes/AuracleApiRouter.ts b/src/common/classes/AuracleApiRouter.ts new file mode 100644 index 0000000..da567ed --- /dev/null +++ b/src/common/classes/AuracleApiRouter.ts @@ -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 + } +} diff --git a/src/functions.ts b/src/common/functions/functions.ts similarity index 100% rename from src/functions.ts rename to src/common/functions/functions.ts diff --git a/src/controllers/SpellController.ts b/src/controllers/SpellController.ts new file mode 100644 index 0000000..4ad206a --- /dev/null +++ b/src/controllers/SpellController.ts @@ -0,0 +1,54 @@ +import express from 'express'; +import { AuracleApiController } from "../common/classes/AuracleApiController"; +import { AuracleApiResponse } from '../common/classes/AuracleApiResponse'; +import { Spell, SpellCreationAttributes } from '../database/models/spells/Spell'; +import { SpellRepository } from "../repositories/SpellRepository"; + +export class SpellController extends AuracleApiController { + public repository: SpellRepository + + constructor() { + super(new SpellRepository) + } + + public async getAll(req: express.Request, res: express.Response) { + let spells: Spell[] + + try { + spells = await this.repository.fetchAll() + 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 this.repository.fetchOne(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 + } as SpellCreationAttributes + + try { + const newSpell = await this.repository.createOne(spell) + res = new AuracleApiResponse(201, newSpell).send(res) + } catch (err) { + console.log(err) + } + } +} diff --git a/src/database/AuracleDatabaseDriver.ts b/src/database/AuracleDatabaseDriver.ts index d43e72a..534d3fc 100644 --- a/src/database/AuracleDatabaseDriver.ts +++ b/src/database/AuracleDatabaseDriver.ts @@ -7,15 +7,6 @@ export let AuracleDatabaseDriver: Sequelize; // Creates the sequelize instance AuracleDatabaseDriver = new Sequelize(dbConfig) -/** - * If the environement is production... - */ -if (process.env.NODE_ENV != 'production') { - AuracleDatabaseDriver.sync({ - force: true - }) -} - /** * Fetches all Typescript files from a given directory * @@ -49,3 +40,12 @@ models.forEach(modelPath => { const registerModel = require(modelPath).default as Function registerModel() }) + +/** + * If the environement is production, sync the DB + */ +if (process.env.NODE_ENV != 'production') { + AuracleDatabaseDriver.sync({ + force: true, + }) +} diff --git a/src/database/config/index.ts b/src/database/config/index.ts index c4ecb7c..83aa66e 100644 --- a/src/database/config/index.ts +++ b/src/database/config/index.ts @@ -20,9 +20,5 @@ export const dbConfig: Options = { * Sequelize Hooks * Docs : https://sequelize.org/master/manual/hooks.html */ - hooks: { - beforeDefine: (columns: any, model: ModelOptions>) => { - model.tableName = tablePrefix + model.tableName - } - } + hooks: {} } diff --git a/src/database/models/spells/Ingredient.ts b/src/database/models/spells/Ingredient.ts index 6229c39..91ebffa 100644 --- a/src/database/models/spells/Ingredient.ts +++ b/src/database/models/spells/Ingredient.ts @@ -3,7 +3,6 @@ import { DataTypes, Model, Optional } from "sequelize"; import { AuracleDatabaseDriver } from "../../AuracleDatabaseDriver"; interface IngredientAttributes { - id: number uuid: string name: string description: string @@ -11,10 +10,9 @@ interface IngredientAttributes { published: boolean } -interface IngredientCreationAttributes extends Optional { } +interface IngredientCreationAttributes extends Optional { } export class Ingredient extends Model implements IngredientAttributes { - public readonly id!: number public readonly uuid!: string public name!: string public description!: string @@ -28,16 +26,12 @@ export class Ingredient extends Model { Ingredient.init( { - id: { - type: DataTypes.INTEGER.UNSIGNED, - autoIncrement: true, - primaryKey: true - }, uuid: { type: DataTypes.UUID, - allowNull: false, unique: true, - defaultValue: DataTypes.UUIDV4 + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true }, name: { type: DataTypes.STRING, @@ -55,7 +49,7 @@ export default () => { } }, { - tableName: 'ingredients', + tableName: 'au_ingredients', sequelize: AuracleDatabaseDriver, } ) diff --git a/src/database/models/spells/MetaSchool.ts b/src/database/models/spells/MetaSchool.ts index 2095b24..3d28014 100644 --- a/src/database/models/spells/MetaSchool.ts +++ b/src/database/models/spells/MetaSchool.ts @@ -3,7 +3,6 @@ import { DataTypes, Model, Optional } from "sequelize"; import { AuracleDatabaseDriver } from "../../AuracleDatabaseDriver"; interface MetaSchoolAttributes { - id: number uuid: string name: string description: string @@ -11,10 +10,9 @@ interface MetaSchoolAttributes { published: boolean } -interface MetaSchoolCreationAttributes extends Optional { } +interface MetaSchoolCreationAttributes extends Optional { } export class MetaSchool extends Model implements MetaSchoolAttributes { - public readonly id!: number public readonly uuid!: string public name!: string public description!: string @@ -28,16 +26,12 @@ export class MetaSchool extends Model { MetaSchool.init( { - id: { - type: DataTypes.INTEGER.UNSIGNED, - autoIncrement: true, - primaryKey: true - }, uuid: { type: DataTypes.UUID, - allowNull: false, unique: true, - defaultValue: DataTypes.UUIDV4 + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true }, name: { type: DataTypes.STRING, @@ -55,7 +49,7 @@ export default () => { } }, { - tableName: 'meta_schools', + tableName: 'au_meta_schools', sequelize: AuracleDatabaseDriver, } ) diff --git a/src/database/models/spells/School.ts b/src/database/models/spells/School.ts index 3ea36bd..2757988 100644 --- a/src/database/models/spells/School.ts +++ b/src/database/models/spells/School.ts @@ -3,7 +3,6 @@ import { DataTypes, Model, Optional } from "sequelize"; import { AuracleDatabaseDriver } from "../../AuracleDatabaseDriver"; interface SchoolAttributes { - id: number uuid: string name: string description: string @@ -11,10 +10,9 @@ interface SchoolAttributes { published: boolean } -interface SchoolCreationAttributes extends Optional { } +interface SchoolCreationAttributes extends Optional { } export class School extends Model implements SchoolAttributes { - public readonly id!: number public readonly uuid!: string public name!: string public description!: string @@ -28,16 +26,12 @@ export class School extends Model im export default () => { School.init( { - id: { - type: DataTypes.INTEGER.UNSIGNED, - autoIncrement: true, - primaryKey: true - }, uuid: { type: DataTypes.UUID, - allowNull: false, unique: true, - defaultValue: DataTypes.UUIDV4 + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true }, name: { type: DataTypes.STRING, @@ -55,7 +49,7 @@ export default () => { } }, { - tableName: 'schools', + tableName: 'au_schools', sequelize: AuracleDatabaseDriver, } ) diff --git a/src/database/models/spells/Spell.ts b/src/database/models/spells/Spell.ts index 9497575..a1fd441 100644 --- a/src/database/models/spells/Spell.ts +++ b/src/database/models/spells/Spell.ts @@ -1,55 +1,49 @@ -import { DataTypes, Model, Optional } from "sequelize"; +import { DataTypes, Model, Optional, TableHints } from "sequelize"; import { AuracleDatabaseDriver } from "../../AuracleDatabaseDriver"; interface SpellAttributes { - id: number uuid: string name: string description: string - level: number - charge: number - cost: string - isRitual: boolean + level?: number + charge?: number + cost?: string + isRitual?: boolean - published: boolean - public: boolean + published?: boolean + public?: boolean } -interface SpellCreationAttributes extends Optional { } +export interface SpellCreationAttributes extends Optional { } -export class SpellModel extends Model implements SpellAttributes { - public readonly id!: number +export class Spell extends Model implements SpellAttributes { public readonly uuid!: string public name!: string public description!: string - public level: number - public charge: number - public cost: string - public isRitual: boolean + public level?: number + public charge?: number + public cost?: string + public isRitual?: boolean - public published: boolean - public public: boolean + public published?: boolean + public public?: boolean public readonly createdAt!: Date; public readonly updatedAt!: Date; } -export default () => { - SpellModel.init( +export default async () => { + Spell.init( { - id: { - type: DataTypes.INTEGER.UNSIGNED, - autoIncrement: true, - primaryKey: true - }, uuid: { type: DataTypes.UUID, - allowNull: false, unique: true, - defaultValue: DataTypes.UUIDV4 + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true }, name: { type: DataTypes.STRING, @@ -88,7 +82,7 @@ export default () => { }, }, { - tableName: 'spells', + tableName: 'au_spells', sequelize: AuracleDatabaseDriver, } ) diff --git a/src/database/models/spells/Variable.ts b/src/database/models/spells/Variable.ts index 6819c9f..f2ade71 100644 --- a/src/database/models/spells/Variable.ts +++ b/src/database/models/spells/Variable.ts @@ -3,17 +3,15 @@ import { DataTypes, Model, Optional } from "sequelize"; import { AuracleDatabaseDriver } from "../../AuracleDatabaseDriver"; interface VariableAttributes { - id: number uuid: string description: string published: boolean } -interface VariableCreationAttributes extends Optional { } +interface VariableCreationAttributes extends Optional { } export class Variable extends Model implements VariableAttributes { - public readonly id!: number public readonly uuid!: string public description!: string @@ -26,16 +24,12 @@ export class Variable extends Model { Variable.init( { - id: { - type: DataTypes.INTEGER.UNSIGNED, - autoIncrement: true, - primaryKey: true - }, uuid: { type: DataTypes.UUID, - allowNull: false, unique: true, - defaultValue: DataTypes.UUIDV4 + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true }, description: { type: DataTypes.STRING, @@ -48,7 +42,7 @@ export default () => { } }, { - tableName: 'variables', + tableName: 'au_variables', sequelize: AuracleDatabaseDriver, } ) diff --git a/src/database/models/users/Permission.ts b/src/database/models/users/Permission.ts index 0852778..f19f949 100644 --- a/src/database/models/users/Permission.ts +++ b/src/database/models/users/Permission.ts @@ -3,15 +3,13 @@ import { DataTypes, Model, Optional } from "sequelize"; import { AuracleDatabaseDriver } from "../../AuracleDatabaseDriver"; interface PermissionAttributes { - id: number uuid: string slug: string } -interface PermissionCreationAttributes extends Optional { } +interface PermissionCreationAttributes extends Optional { } export class Permission extends Model implements PermissionAttributes { - public readonly id!: number public readonly uuid!: string public slug!: string @@ -22,16 +20,12 @@ export class Permission extends Model { Permission.init( { - id: { - type: DataTypes.INTEGER.UNSIGNED, - autoIncrement: true, - primaryKey: true - }, uuid: { type: DataTypes.UUID, - allowNull: false, unique: true, - defaultValue: DataTypes.UUIDV4 + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true }, slug: { type: DataTypes.STRING, @@ -40,7 +34,7 @@ export default () => { }, }, { - tableName: 'permissions', + tableName: 'au_permissions', sequelize: AuracleDatabaseDriver, } ) diff --git a/src/database/models/users/Role.ts b/src/database/models/users/Role.ts index 7525101..6200cb7 100644 --- a/src/database/models/users/Role.ts +++ b/src/database/models/users/Role.ts @@ -47,7 +47,7 @@ export default () => { }, }, { - tableName: 'roles', + tableName: 'au_roles', sequelize: AuracleDatabaseDriver, } ) diff --git a/src/database/models/users/User.ts b/src/database/models/users/User.ts index ce959e7..ccae0bb 100644 --- a/src/database/models/users/User.ts +++ b/src/database/models/users/User.ts @@ -70,7 +70,7 @@ export default () => { }, }, { - tableName: 'users', + tableName: 'au_users', sequelize: AuracleDatabaseDriver, } ) diff --git a/src/main.ts b/src/main.ts index ac9e6ab..fab3ab1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -14,11 +14,16 @@ 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 app = new AuracleApi({ port: apiPort, middlewares: [], + routers: [ + new SpellRouter() + ], modules: [ express.json({ limit: '10kb' }), morgan('dev'), diff --git a/src/repositories/SpellRepository.ts b/src/repositories/SpellRepository.ts new file mode 100644 index 0000000..8220736 --- /dev/null +++ b/src/repositories/SpellRepository.ts @@ -0,0 +1,20 @@ +import { AuracleApiRepository } from "../common/classes/AuracleApiRepository" +import { Spell, SpellCreationAttributes } from "../database/models/spells/Spell" + +export class SpellRepository extends AuracleApiRepository { + constructor() { + super(new Spell) + } + + public async fetchAll(): Promise { + return Spell.findAll() + } + + public async fetchOne(uuid: string): Promise { + return Spell.findByPk(uuid) + } + + public async createOne(spell: SpellCreationAttributes): Promise { + return Spell.create(spell) + } +} diff --git a/src/routes/SpellRouter.ts b/src/routes/SpellRouter.ts new file mode 100644 index 0000000..bf1d4e2 --- /dev/null +++ b/src/routes/SpellRouter.ts @@ -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)) + } +} diff --git a/tsconfig.json b/tsconfig.json index 618133a..bbd74a2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,7 @@ "sourceMap": true, "outDir": "./dist", "esModuleInterop": true, - "baseUrl": "./src", + "baseUrl": "/src", "incremental": true, "types": [ "node",