diff --git a/package.json b/package.json index 918605c..6129dda 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wms-core", - "version": "0.19.0", + "version": "0.19.1", "description": "Node web framework", "repository": "git@gitlab.com:ArisuOngaku/wms-core.git", "author": "Alice Gaudon ", diff --git a/src/Logger.ts b/src/Logger.ts index 9c36575..325be1b 100644 --- a/src/Logger.ts +++ b/src/Logger.ts @@ -1,6 +1,8 @@ import config from "config"; import {v4 as uuid} from "uuid"; import Log from "./models/Log"; +import ModelFactory from "./db/ModelFactory"; +import {bufferToUUID} from "./Utils"; export default class Logger { private static logLevel: LogLevelKeys = config.get('log_level'); @@ -71,19 +73,15 @@ export default class Logger { } }).join(' '); - const log = new Log({}); - log.setLevel(level); - log.message = computedMsg; - log.setError(error); - - let logID = Buffer.alloc(16); - uuid({}, logID); - log.setLogID(logID); - + const shouldSaveToDB = levelIndex <= LogLevel[this.dbLogLevel]; let output = `[${level}] `; - let pad = output.length; - if (levelIndex <= LogLevel[this.dbLogLevel]) output += `${log.getLogID()} - `; + const pad = output.length; + + const logID = Buffer.alloc(16); + uuid({}, logID); + if (shouldSaveToDB) output += `${logID} - `; + output += computedMsg.replace(/\n/g, '\n' + ' '.repeat(pad)); switch (level) { @@ -106,14 +104,19 @@ export default class Logger { break; } - if (levelIndex <= LogLevel[this.dbLogLevel]) { + if (shouldSaveToDB) { + const log = ModelFactory.get(Log).make({}); + log.setLevel(level); + log.message = computedMsg; + log.setError(error); + log.setLogID(logID); log.save().catch(err => { if (!silent && err.message.indexOf('ECONNREFUSED') < 0) { console.error({save_err: err, error}); } }); } - return log.getLogID(); + return bufferToUUID(logID); } return null; } diff --git a/src/Utils.ts b/src/Utils.ts index 98ec04c..e91a5b8 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -35,3 +35,15 @@ export function cryptoRandomDictionary(size: number, dictionary: string): string } export type Type = { new(...args: any[]): T }; + +export function bufferToUUID(buffer: Buffer): string { + const chars = buffer.toString('hex'); + let out = ''; + let i = 0; + for (const l of [8, 4, 4, 4, 12]) { + if (i > 0) out += '-'; + out += chars.substr(i, l); + i += l; + } + return out; +} diff --git a/src/auth/models/MagicLink.ts b/src/auth/models/MagicLink.ts index 36c16b2..255cb1e 100644 --- a/src/auth/models/MagicLink.ts +++ b/src/auth/models/MagicLink.ts @@ -24,13 +24,14 @@ export default class MagicLink extends Model implements AuthProof { return config.get('magic_link.validity_period') * 1000; } - private session_id?: string; - private email?: string; - private token?: string; - private action_type?: string; - private original_url?: string; - private generated_at?: Date; - private authorized?: boolean; + public readonly id?: number = undefined; + private session_id?: string = undefined; + private email?: string = undefined; + private token?: string = undefined; + private action_type?: string = undefined; + private original_url?: string = undefined; + private generated_at?: Date = undefined; + private authorized?: boolean = undefined; constructor(data: any) { super(data); diff --git a/src/auth/models/User.ts b/src/auth/models/User.ts index 01aef5b..dd67de5 100644 --- a/src/auth/models/User.ts +++ b/src/auth/models/User.ts @@ -12,10 +12,11 @@ export default class User extends Model { return config.get('approval_mode') && MysqlConnectionManager.hasMigration(AddApprovedFieldToUsersTable); } - public name?: string; - public is_admin!: boolean; - public created_at?: Date; - public updated_at?: Date; + public readonly id?: number = undefined; + public name?: string = undefined; + public is_admin: boolean = false; + public created_at?: Date = undefined; + public updated_at?: Date = undefined; public readonly emails = new ManyModelRelation(this, ModelFactory.get(UserEmail), { localKey: 'id', @@ -26,8 +27,6 @@ export default class User extends Model { public constructor(data: any) { super(data); - if (this.approved === undefined) this.approved = false; - if (this.is_admin === undefined) this.is_admin = false; } protected init(): void { diff --git a/src/auth/models/UserApprovedComponent.ts b/src/auth/models/UserApprovedComponent.ts index 93d2448..ab65006 100644 --- a/src/auth/models/UserApprovedComponent.ts +++ b/src/auth/models/UserApprovedComponent.ts @@ -2,5 +2,5 @@ import ModelComponent from "../../db/ModelComponent"; import User from "./User"; export default class UserApprovedComponent extends ModelComponent { - public approved!: boolean; + public approved: boolean = false; } \ No newline at end of file diff --git a/src/auth/models/UserEmail.ts b/src/auth/models/UserEmail.ts index 4295b4b..fe99ed9 100644 --- a/src/auth/models/UserEmail.ts +++ b/src/auth/models/UserEmail.ts @@ -6,10 +6,11 @@ import {OneModelRelation} from "../../db/ModelRelation"; import ModelFactory from "../../db/ModelFactory"; export default class UserEmail extends Model { - public user_id?: number; - public readonly email!: string; - private main!: boolean; - public created_at?: Date; + public readonly id?: number = undefined; + public user_id?: number = undefined; + public readonly email?: string = undefined; + private main?: boolean = undefined; + public created_at?: Date = undefined; public readonly user = new OneModelRelation(this, ModelFactory.get(User), { localKey: 'user_id', @@ -36,7 +37,7 @@ export default class UserEmail extends Model { } public isMain(): boolean { - return this.main; + return Boolean(this.main); } public setMain() { diff --git a/src/db/Model.ts b/src/db/Model.ts index 7d51481..23bc7b6 100644 --- a/src/db/Model.ts +++ b/src/db/Model.ts @@ -29,19 +29,16 @@ export default abstract class Model { return ModelFactory.get(this).paginate(request, perPage, query); } - protected readonly _factory!: ModelFactory; + protected readonly _factory: ModelFactory; private readonly _components: ModelComponent[] = []; private readonly _validators: { [key: string]: Validator } = {}; [key: string]: any; - public constructor(data: any) { + public constructor(factory: ModelFactory) { + if (!factory || !(factory instanceof ModelFactory)) throw new Error('Cannot instantiate model directly.'); + this._factory = factory; this.init(); - this.updateWithData(data); - } - - public setFactory(factory: ModelFactory) { - (this as any)._factory = factory; } protected abstract init(): void; @@ -66,7 +63,7 @@ export default abstract class Model { throw new Error(`Component ${type.name} was not initialized for this ${this.constructor.name}.`); } - private updateWithData(data: any) { + public updateWithData(data: any) { for (const property of this._properties) { if (data[property] !== undefined) { this[property] = data[property]; @@ -128,7 +125,7 @@ export default abstract class Model { } } let query = this._factory.update(data); - for (const indexField of this.getPrimaryKeyFields()) { + for (const indexField of this._factory.getPrimaryKeyFields()) { query = query.where(indexField, this[indexField]); } await query.execute(connection); @@ -154,7 +151,7 @@ export default abstract class Model { public async exists(): Promise { let query = this._factory.select('1'); - for (const indexField of this.getPrimaryKeyFields()) { + for (const indexField of this._factory.getPrimaryKeyFields()) { query = query.where(indexField, this[indexField]); } return (await query.limit(1).execute()).results.length > 0; @@ -164,7 +161,7 @@ export default abstract class Model { if (!(await this.exists())) throw new Error('This model instance doesn\'t exist in DB.'); let query = this._factory.delete(); - for (const indexField of this.getPrimaryKeyFields()) { + for (const indexField of this._factory.getPrimaryKeyFields()) { query = query.where(indexField, this[indexField]); } await query.execute(); diff --git a/src/db/ModelFactory.ts b/src/db/ModelFactory.ts index 1078931..a47f9c9 100644 --- a/src/db/ModelFactory.ts +++ b/src/db/ModelFactory.ts @@ -25,22 +25,21 @@ export default class ModelFactory { this.modelType = modelType; } - public addComponent(modelComponentFactory: ModelComponentFactory) { this.components.push(modelComponentFactory); } public make(data: any): T { - const model = new this.modelType(data); + const model = new this.modelType(this, data); for (const component of this.components) { model.addComponent(new component(model)); } - model.setFactory(this); + model.updateWithData(data); return model; } public get table(): string { - return this.constructor.name + return this.modelType.name .replace(/(?:^|\.?)([A-Z])/g, (x, y) => '_' + y.toLowerCase()) .replace(/^_/, '') + 's'; diff --git a/src/models/Log.ts b/src/models/Log.ts index e4a38d4..b922cbe 100644 --- a/src/models/Log.ts +++ b/src/models/Log.ts @@ -1,14 +1,16 @@ import Model from "../db/Model"; import {LogLevel, LogLevelKeys} from "../Logger"; +import {bufferToUUID} from "../Utils"; export default class Log extends Model { - private level?: number; - public message?: string; - private log_id?: Buffer; - private error_name?: string; - private error_message?: string; - private error_stack?: string; - private created_at?: Date; + public readonly id?: number = undefined; + private level?: number = undefined; + public message?: string = undefined; + private log_id?: Buffer = undefined; + private error_name?: string = undefined; + private error_message?: string = undefined; + private error_stack?: string = undefined; + private created_at?: Date = undefined; protected init(): void { this.setValidation('level').defined(); @@ -29,16 +31,7 @@ export default class Log extends Model { } public getLogID(): string | null { - if (!this.log_id) return null; - const chars = this.log_id!.toString('hex'); - let out = ''; - let i = 0; - for (const l of [8, 4, 4, 4, 12]) { - if (i > 0) out += '-'; - out += chars.substr(i, l); - i += l; - } - return out; + return this.log_id ? bufferToUUID(this.log_id!) : null; } public setLogID(buffer: Buffer) { diff --git a/test/Model.test.ts b/test/Model.test.ts index 7ba3222..e4896c3 100644 --- a/test/Model.test.ts +++ b/test/Model.test.ts @@ -1,22 +1,22 @@ import MysqlConnectionManager from "../src/db/MysqlConnectionManager"; import Model from "../src/db/Model"; -import Validator from "../src/db/Validator"; import {MIGRATIONS} from "./_migrations"; +import ModelFactory from "../src/db/ModelFactory"; class FakeDummyModel extends Model { - public name?: string; - public date?: Date; - public date_default?: Date; + public id?: number = undefined; + public name?: string = undefined; + public date?: Date = undefined; + public date_default?: Date = undefined; protected init(): void { - this.addProperty('name', new Validator().acceptUndefined().between(3, 256)); - this.addProperty('date', new Validator()); - this.addProperty('date_default', new Validator()); + this.setValidation('name').acceptUndefined().between(3, 256); } } beforeAll(async (done) => { MysqlConnectionManager.registerMigrations(MIGRATIONS); + ModelFactory.register(FakeDummyModel); await MysqlConnectionManager.prepare(); done(); }); @@ -28,13 +28,15 @@ afterAll(async (done) => { describe('Model', () => { it('should have a proper table name', async () => { - expect(FakeDummyModel.table).toBe('fake_dummy_models'); - expect(new FakeDummyModel({}).table).toBe('fake_dummy_models'); + const factory = ModelFactory.get(FakeDummyModel); + expect(factory.table).toBe('fake_dummy_models'); + expect(factory.make({}).table).toBe('fake_dummy_models'); }); it('should insert and retrieve properly', async () => { - await MysqlConnectionManager.query(`DROP TABLE IF EXISTS ${FakeDummyModel.table}`); - await MysqlConnectionManager.query(`CREATE TABLE ${FakeDummyModel.table}( + const factory = ModelFactory.get(FakeDummyModel); + await MysqlConnectionManager.query(`DROP TABLE IF EXISTS ${(factory.table)}`); + await MysqlConnectionManager.query(`CREATE TABLE ${(factory.table)}( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(256), date DATETIME, @@ -43,11 +45,12 @@ describe('Model', () => { )`); const date = new Date(569985); - let instance: FakeDummyModel | null = new FakeDummyModel({ + let instance: FakeDummyModel | null = factory.make({ name: 'name1', date: date, }); + console.log(instance) await instance.save(); expect(instance.id).toBe(1); expect(instance.name).toBe('name1');