Fix missing fields by default and fix model construction flow
This commit is contained in:
parent
4b6829bfd6
commit
c0dd48d064
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "wms-core",
|
"name": "wms-core",
|
||||||
"version": "0.19.0",
|
"version": "0.19.1",
|
||||||
"description": "Node web framework",
|
"description": "Node web framework",
|
||||||
"repository": "git@gitlab.com:ArisuOngaku/wms-core.git",
|
"repository": "git@gitlab.com:ArisuOngaku/wms-core.git",
|
||||||
"author": "Alice Gaudon <alice@gaudon.pro>",
|
"author": "Alice Gaudon <alice@gaudon.pro>",
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import config from "config";
|
import config from "config";
|
||||||
import {v4 as uuid} from "uuid";
|
import {v4 as uuid} from "uuid";
|
||||||
import Log from "./models/Log";
|
import Log from "./models/Log";
|
||||||
|
import ModelFactory from "./db/ModelFactory";
|
||||||
|
import {bufferToUUID} from "./Utils";
|
||||||
|
|
||||||
export default class Logger {
|
export default class Logger {
|
||||||
private static logLevel: LogLevelKeys = <LogLevelKeys>config.get<string>('log_level');
|
private static logLevel: LogLevelKeys = <LogLevelKeys>config.get<string>('log_level');
|
||||||
@ -71,19 +73,15 @@ export default class Logger {
|
|||||||
}
|
}
|
||||||
}).join(' ');
|
}).join(' ');
|
||||||
|
|
||||||
const log = new Log({});
|
const shouldSaveToDB = levelIndex <= LogLevel[this.dbLogLevel];
|
||||||
log.setLevel(level);
|
|
||||||
log.message = computedMsg;
|
|
||||||
log.setError(error);
|
|
||||||
|
|
||||||
let logID = Buffer.alloc(16);
|
|
||||||
uuid({}, logID);
|
|
||||||
log.setLogID(logID);
|
|
||||||
|
|
||||||
|
|
||||||
let output = `[${level}] `;
|
let output = `[${level}] `;
|
||||||
let pad = output.length;
|
const pad = output.length;
|
||||||
if (levelIndex <= LogLevel[this.dbLogLevel]) output += `${log.getLogID()} - `;
|
|
||||||
|
const logID = Buffer.alloc(16);
|
||||||
|
uuid({}, logID);
|
||||||
|
if (shouldSaveToDB) output += `${logID} - `;
|
||||||
|
|
||||||
output += computedMsg.replace(/\n/g, '\n' + ' '.repeat(pad));
|
output += computedMsg.replace(/\n/g, '\n' + ' '.repeat(pad));
|
||||||
|
|
||||||
switch (level) {
|
switch (level) {
|
||||||
@ -106,14 +104,19 @@ export default class Logger {
|
|||||||
break;
|
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 => {
|
log.save().catch(err => {
|
||||||
if (!silent && err.message.indexOf('ECONNREFUSED') < 0) {
|
if (!silent && err.message.indexOf('ECONNREFUSED') < 0) {
|
||||||
console.error({save_err: err, error});
|
console.error({save_err: err, error});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return log.getLogID();
|
return bufferToUUID(logID);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
12
src/Utils.ts
12
src/Utils.ts
@ -35,3 +35,15 @@ export function cryptoRandomDictionary(size: number, dictionary: string): string
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type Type<T> = { new(...args: any[]): T };
|
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;
|
||||||
|
}
|
||||||
|
@ -24,13 +24,14 @@ export default class MagicLink extends Model implements AuthProof {
|
|||||||
return config.get<number>('magic_link.validity_period') * 1000;
|
return config.get<number>('magic_link.validity_period') * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
private session_id?: string;
|
public readonly id?: number = undefined;
|
||||||
private email?: string;
|
private session_id?: string = undefined;
|
||||||
private token?: string;
|
private email?: string = undefined;
|
||||||
private action_type?: string;
|
private token?: string = undefined;
|
||||||
private original_url?: string;
|
private action_type?: string = undefined;
|
||||||
private generated_at?: Date;
|
private original_url?: string = undefined;
|
||||||
private authorized?: boolean;
|
private generated_at?: Date = undefined;
|
||||||
|
private authorized?: boolean = undefined;
|
||||||
|
|
||||||
constructor(data: any) {
|
constructor(data: any) {
|
||||||
super(data);
|
super(data);
|
||||||
|
@ -12,10 +12,11 @@ export default class User extends Model {
|
|||||||
return config.get<boolean>('approval_mode') && MysqlConnectionManager.hasMigration(AddApprovedFieldToUsersTable);
|
return config.get<boolean>('approval_mode') && MysqlConnectionManager.hasMigration(AddApprovedFieldToUsersTable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public name?: string;
|
public readonly id?: number = undefined;
|
||||||
public is_admin!: boolean;
|
public name?: string = undefined;
|
||||||
public created_at?: Date;
|
public is_admin: boolean = false;
|
||||||
public updated_at?: Date;
|
public created_at?: Date = undefined;
|
||||||
|
public updated_at?: Date = undefined;
|
||||||
|
|
||||||
public readonly emails = new ManyModelRelation(this, ModelFactory.get(UserEmail), {
|
public readonly emails = new ManyModelRelation(this, ModelFactory.get(UserEmail), {
|
||||||
localKey: 'id',
|
localKey: 'id',
|
||||||
@ -26,8 +27,6 @@ export default class User extends Model {
|
|||||||
|
|
||||||
public constructor(data: any) {
|
public constructor(data: any) {
|
||||||
super(data);
|
super(data);
|
||||||
if (this.approved === undefined) this.approved = false;
|
|
||||||
if (this.is_admin === undefined) this.is_admin = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected init(): void {
|
protected init(): void {
|
||||||
|
@ -2,5 +2,5 @@ import ModelComponent from "../../db/ModelComponent";
|
|||||||
import User from "./User";
|
import User from "./User";
|
||||||
|
|
||||||
export default class UserApprovedComponent extends ModelComponent<User> {
|
export default class UserApprovedComponent extends ModelComponent<User> {
|
||||||
public approved!: boolean;
|
public approved: boolean = false;
|
||||||
}
|
}
|
@ -6,10 +6,11 @@ import {OneModelRelation} from "../../db/ModelRelation";
|
|||||||
import ModelFactory from "../../db/ModelFactory";
|
import ModelFactory from "../../db/ModelFactory";
|
||||||
|
|
||||||
export default class UserEmail extends Model {
|
export default class UserEmail extends Model {
|
||||||
public user_id?: number;
|
public readonly id?: number = undefined;
|
||||||
public readonly email!: string;
|
public user_id?: number = undefined;
|
||||||
private main!: boolean;
|
public readonly email?: string = undefined;
|
||||||
public created_at?: Date;
|
private main?: boolean = undefined;
|
||||||
|
public created_at?: Date = undefined;
|
||||||
|
|
||||||
public readonly user = new OneModelRelation<UserEmail, User>(this, ModelFactory.get(User), {
|
public readonly user = new OneModelRelation<UserEmail, User>(this, ModelFactory.get(User), {
|
||||||
localKey: 'user_id',
|
localKey: 'user_id',
|
||||||
@ -36,7 +37,7 @@ export default class UserEmail extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public isMain(): boolean {
|
public isMain(): boolean {
|
||||||
return this.main;
|
return Boolean(this.main);
|
||||||
}
|
}
|
||||||
|
|
||||||
public setMain() {
|
public setMain() {
|
||||||
|
@ -29,19 +29,16 @@ export default abstract class Model {
|
|||||||
return ModelFactory.get(this).paginate(request, perPage, query);
|
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 _components: ModelComponent<any>[] = [];
|
||||||
private readonly _validators: { [key: string]: Validator<any> } = {};
|
private readonly _validators: { [key: string]: Validator<any> } = {};
|
||||||
|
|
||||||
[key: string]: 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.init();
|
||||||
this.updateWithData(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public setFactory(factory: ModelFactory<any>) {
|
|
||||||
(this as any)._factory = factory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract init(): void;
|
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}.`);
|
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) {
|
for (const property of this._properties) {
|
||||||
if (data[property] !== undefined) {
|
if (data[property] !== undefined) {
|
||||||
this[property] = data[property];
|
this[property] = data[property];
|
||||||
@ -128,7 +125,7 @@ export default abstract class Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let query = this._factory.update(data);
|
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]);
|
query = query.where(indexField, this[indexField]);
|
||||||
}
|
}
|
||||||
await query.execute(connection);
|
await query.execute(connection);
|
||||||
@ -154,7 +151,7 @@ export default abstract class Model {
|
|||||||
|
|
||||||
public async exists(): Promise<boolean> {
|
public async exists(): Promise<boolean> {
|
||||||
let query = this._factory.select('1');
|
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]);
|
query = query.where(indexField, this[indexField]);
|
||||||
}
|
}
|
||||||
return (await query.limit(1).execute()).results.length > 0;
|
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.');
|
if (!(await this.exists())) throw new Error('This model instance doesn\'t exist in DB.');
|
||||||
|
|
||||||
let query = this._factory.delete();
|
let query = this._factory.delete();
|
||||||
for (const indexField of this.getPrimaryKeyFields()) {
|
for (const indexField of this._factory.getPrimaryKeyFields()) {
|
||||||
query = query.where(indexField, this[indexField]);
|
query = query.where(indexField, this[indexField]);
|
||||||
}
|
}
|
||||||
await query.execute();
|
await query.execute();
|
||||||
|
@ -25,22 +25,21 @@ export default class ModelFactory<T extends Model> {
|
|||||||
this.modelType = modelType;
|
this.modelType = modelType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public addComponent(modelComponentFactory: ModelComponentFactory<T>) {
|
public addComponent(modelComponentFactory: ModelComponentFactory<T>) {
|
||||||
this.components.push(modelComponentFactory);
|
this.components.push(modelComponentFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public make(data: any): T {
|
public make(data: any): T {
|
||||||
const model = new this.modelType(data);
|
const model = new this.modelType(this, data);
|
||||||
for (const component of this.components) {
|
for (const component of this.components) {
|
||||||
model.addComponent(new component(model));
|
model.addComponent(new component(model));
|
||||||
}
|
}
|
||||||
model.setFactory(this);
|
model.updateWithData(data);
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get table(): string {
|
public get table(): string {
|
||||||
return this.constructor.name
|
return this.modelType.name
|
||||||
.replace(/(?:^|\.?)([A-Z])/g, (x, y) => '_' + y.toLowerCase())
|
.replace(/(?:^|\.?)([A-Z])/g, (x, y) => '_' + y.toLowerCase())
|
||||||
.replace(/^_/, '')
|
.replace(/^_/, '')
|
||||||
+ 's';
|
+ 's';
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import Model from "../db/Model";
|
import Model from "../db/Model";
|
||||||
import {LogLevel, LogLevelKeys} from "../Logger";
|
import {LogLevel, LogLevelKeys} from "../Logger";
|
||||||
|
import {bufferToUUID} from "../Utils";
|
||||||
|
|
||||||
export default class Log extends Model {
|
export default class Log extends Model {
|
||||||
private level?: number;
|
public readonly id?: number = undefined;
|
||||||
public message?: string;
|
private level?: number = undefined;
|
||||||
private log_id?: Buffer;
|
public message?: string = undefined;
|
||||||
private error_name?: string;
|
private log_id?: Buffer = undefined;
|
||||||
private error_message?: string;
|
private error_name?: string = undefined;
|
||||||
private error_stack?: string;
|
private error_message?: string = undefined;
|
||||||
private created_at?: Date;
|
private error_stack?: string = undefined;
|
||||||
|
private created_at?: Date = undefined;
|
||||||
|
|
||||||
protected init(): void {
|
protected init(): void {
|
||||||
this.setValidation('level').defined();
|
this.setValidation('level').defined();
|
||||||
@ -29,16 +31,7 @@ export default class Log extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getLogID(): string | null {
|
public getLogID(): string | null {
|
||||||
if (!this.log_id) return null;
|
return this.log_id ? bufferToUUID(this.log_id!) : 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public setLogID(buffer: Buffer) {
|
public setLogID(buffer: Buffer) {
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
import MysqlConnectionManager from "../src/db/MysqlConnectionManager";
|
import MysqlConnectionManager from "../src/db/MysqlConnectionManager";
|
||||||
import Model from "../src/db/Model";
|
import Model from "../src/db/Model";
|
||||||
import Validator from "../src/db/Validator";
|
|
||||||
import {MIGRATIONS} from "./_migrations";
|
import {MIGRATIONS} from "./_migrations";
|
||||||
|
import ModelFactory from "../src/db/ModelFactory";
|
||||||
|
|
||||||
class FakeDummyModel extends Model {
|
class FakeDummyModel extends Model {
|
||||||
public name?: string;
|
public id?: number = undefined;
|
||||||
public date?: Date;
|
public name?: string = undefined;
|
||||||
public date_default?: Date;
|
public date?: Date = undefined;
|
||||||
|
public date_default?: Date = undefined;
|
||||||
|
|
||||||
protected init(): void {
|
protected init(): void {
|
||||||
this.addProperty<string>('name', new Validator().acceptUndefined().between(3, 256));
|
this.setValidation('name').acceptUndefined().between(3, 256);
|
||||||
this.addProperty<Date>('date', new Validator());
|
|
||||||
this.addProperty<Date>('date_default', new Validator());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeAll(async (done) => {
|
beforeAll(async (done) => {
|
||||||
MysqlConnectionManager.registerMigrations(MIGRATIONS);
|
MysqlConnectionManager.registerMigrations(MIGRATIONS);
|
||||||
|
ModelFactory.register(FakeDummyModel);
|
||||||
await MysqlConnectionManager.prepare();
|
await MysqlConnectionManager.prepare();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -28,13 +28,15 @@ afterAll(async (done) => {
|
|||||||
|
|
||||||
describe('Model', () => {
|
describe('Model', () => {
|
||||||
it('should have a proper table name', async () => {
|
it('should have a proper table name', async () => {
|
||||||
expect(FakeDummyModel.table).toBe('fake_dummy_models');
|
const factory = ModelFactory.get(FakeDummyModel);
|
||||||
expect(new FakeDummyModel({}).table).toBe('fake_dummy_models');
|
expect(factory.table).toBe('fake_dummy_models');
|
||||||
|
expect(factory.make({}).table).toBe('fake_dummy_models');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should insert and retrieve properly', async () => {
|
it('should insert and retrieve properly', async () => {
|
||||||
await MysqlConnectionManager.query(`DROP TABLE IF EXISTS ${FakeDummyModel.table}`);
|
const factory = ModelFactory.get(FakeDummyModel);
|
||||||
await MysqlConnectionManager.query(`CREATE TABLE ${FakeDummyModel.table}(
|
await MysqlConnectionManager.query(`DROP TABLE IF EXISTS ${(factory.table)}`);
|
||||||
|
await MysqlConnectionManager.query(`CREATE TABLE ${(factory.table)}(
|
||||||
id INT NOT NULL AUTO_INCREMENT,
|
id INT NOT NULL AUTO_INCREMENT,
|
||||||
name VARCHAR(256),
|
name VARCHAR(256),
|
||||||
date DATETIME,
|
date DATETIME,
|
||||||
@ -43,11 +45,12 @@ describe('Model', () => {
|
|||||||
)`);
|
)`);
|
||||||
|
|
||||||
const date = new Date(569985);
|
const date = new Date(569985);
|
||||||
let instance: FakeDummyModel | null = new FakeDummyModel({
|
let instance: FakeDummyModel | null = factory.make({
|
||||||
name: 'name1',
|
name: 'name1',
|
||||||
date: date,
|
date: date,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(instance)
|
||||||
await instance.save();
|
await instance.save();
|
||||||
expect(instance.id).toBe(1);
|
expect(instance.id).toBe(1);
|
||||||
expect(instance.name).toBe('name1');
|
expect(instance.name).toBe('name1');
|
||||||
|
Loading…
Reference in New Issue
Block a user