Fix missing fields by default and fix model construction flow

This commit is contained in:
Alice Gaudon 2020-07-24 15:40:10 +02:00
parent 4b6829bfd6
commit c0dd48d064
11 changed files with 85 additions and 77 deletions

View File

@ -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 <alice@gaudon.pro>",

View File

@ -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 = <LogLevelKeys>config.get<string>('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;
}

View File

@ -35,3 +35,15 @@ export function cryptoRandomDictionary(size: number, dictionary: string): string
}
export type Type<T> = { 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;
}

View File

@ -24,13 +24,14 @@ export default class MagicLink extends Model implements AuthProof {
return config.get<number>('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);

View File

@ -12,10 +12,11 @@ export default class User extends Model {
return config.get<boolean>('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 {

View File

@ -2,5 +2,5 @@ import ModelComponent from "../../db/ModelComponent";
import User from "./User";
export default class UserApprovedComponent extends ModelComponent<User> {
public approved!: boolean;
public approved: boolean = false;
}

View File

@ -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<UserEmail, User>(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() {

View File

@ -29,19 +29,16 @@ export default abstract class Model {
return ModelFactory.get(this).paginate(request, perPage, query);
}
protected readonly _factory!: ModelFactory<any>;
protected readonly _factory: ModelFactory<any>;
private readonly _components: ModelComponent<any>[] = [];
private readonly _validators: { [key: string]: Validator<any> } = {};
[key: string]: any;
public constructor(data: any) {
public constructor(factory: ModelFactory<any>) {
if (!factory || !(factory instanceof ModelFactory)) throw new Error('Cannot instantiate model directly.');
this._factory = factory;
this.init();
this.updateWithData(data);
}
public setFactory(factory: ModelFactory<any>) {
(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<boolean> {
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();

View File

@ -25,22 +25,21 @@ export default class ModelFactory<T extends Model> {
this.modelType = modelType;
}
public addComponent(modelComponentFactory: ModelComponentFactory<T>) {
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';

View File

@ -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) {

View File

@ -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<string>('name', new Validator().acceptUndefined().between(3, 256));
this.addProperty<Date>('date', new Validator());
this.addProperty<Date>('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');