swaf/src/db/Model.ts

191 lines
6.6 KiB
TypeScript
Raw Normal View History

2020-04-22 15:52:17 +02:00
import MysqlConnectionManager, {query} from "./MysqlConnectionManager";
import Validator from "./Validator";
import {Connection} from "mysql";
2020-07-24 12:13:28 +02:00
import ModelComponent from "./ModelComponent";
import {Type} from "../Utils";
import ModelFactory from "./ModelFactory";
import ModelRelation from "./ModelRelation";
import ModelQuery, {ModelQueryResult} from "./ModelQuery";
2020-04-22 15:52:17 +02:00
import {Request} from "express";
export default abstract class Model {
2020-07-24 12:13:28 +02:00
public static select<T extends Model>(this: Type<T>, ...fields: string[]): ModelQuery<T> {
return ModelFactory.get(this).select(...fields);
2020-04-22 15:52:17 +02:00
}
2020-07-24 12:13:28 +02:00
public static update<T extends Model>(this: Type<T>, data: { [key: string]: any }): ModelQuery<T> {
return ModelFactory.get(this).update(data);
2020-04-22 15:52:17 +02:00
}
2020-07-24 12:13:28 +02:00
public static delete<T extends Model>(this: Type<T>): ModelQuery<T> {
return ModelFactory.get(this).delete();
2020-04-22 15:52:17 +02:00
}
2020-07-24 12:13:28 +02:00
public static async getById<T extends Model>(this: Type<T>, ...id: any): Promise<T | null> {
return ModelFactory.get(this).getById(id);
}
2020-07-24 12:13:28 +02:00
public static async paginate<T extends Model>(this: Type<T>, request: Request, perPage: number = 20, query?: ModelQuery<T>): Promise<ModelQueryResult<T>> {
return ModelFactory.get(this).paginate(request, perPage, query);
2020-04-22 15:52:17 +02:00
}
2020-07-24 12:13:28 +02:00
protected readonly _factory!: ModelFactory<any>;
private readonly _components: ModelComponent<any>[] = [];
private readonly _validators: { [key: string]: Validator<any> } = {};
2020-04-22 15:52:17 +02:00
[key: string]: any;
2020-07-24 12:13:28 +02:00
public constructor(data: any) {
this.init();
2020-04-22 15:52:17 +02:00
this.updateWithData(data);
}
2020-07-24 12:13:28 +02:00
public setFactory(factory: ModelFactory<any>) {
(this as any)._factory = factory;
}
2020-07-24 12:13:28 +02:00
protected abstract init(): void;
protected setValidation<T>(propertyName: keyof this): Validator<T> {
const validator = new Validator<T>();
this._validators[propertyName as string] = validator;
return validator;
}
2020-07-24 12:13:28 +02:00
public addComponent(modelComponent: ModelComponent<this>): void {
modelComponent.init();
this._components.push(modelComponent);
}
2020-04-22 15:52:17 +02:00
2020-07-24 12:13:28 +02:00
public as<T extends ModelComponent<any>>(type: Type<T>): T {
for (const component of this._components) {
if (component instanceof type) {
return <any>this;
}
2020-04-22 15:52:17 +02:00
}
2020-07-24 12:13:28 +02:00
throw new Error(`Component ${type.name} was not initialized for this ${this.constructor.name}.`);
2020-04-22 15:52:17 +02:00
}
private updateWithData(data: any) {
2020-07-24 12:13:28 +02:00
for (const property of this._properties) {
if (data[property] !== undefined) {
this[property] = data[property];
2020-04-22 15:52:17 +02:00
}
}
}
protected async beforeSave(exists: boolean, connection: Connection): Promise<void> {
}
protected async afterSave(): Promise<void> {
}
public async save(connection?: Connection, postHook?: (callback: () => Promise<void>) => void): Promise<void> {
await this.validate(false, connection);
const exists = await this.exists();
let needs_full_update = false;
if (connection) {
needs_full_update = await this.saveTransaction(connection, exists, needs_full_update);
} else {
needs_full_update = await MysqlConnectionManager.wrapTransaction(async connection => this.saveTransaction(connection, exists, needs_full_update));
}
const callback = async () => {
if (needs_full_update) {
2020-07-24 12:13:28 +02:00
this.updateWithData((await this._factory.select().where('id', this.id!).limit(1).execute()).results[0]);
2020-04-22 15:52:17 +02:00
}
await this.afterSave();
};
if (connection) {
postHook!(callback);
} else {
await callback();
}
}
private async saveTransaction(connection: Connection, exists: boolean, needs_full_update: boolean): Promise<boolean> {
// Before save
await this.beforeSave(exists, connection);
if (exists && this.hasOwnProperty('updated_at')) {
this.updated_at = new Date();
}
2020-07-24 12:13:28 +02:00
const properties = [];
2020-04-22 15:52:17 +02:00
const values = [];
if (exists) {
const data: any = {};
2020-07-24 12:13:28 +02:00
for (const property of this._properties) {
const value = this[property];
if (value !== undefined) {
data[property] = value;
2020-04-22 15:52:17 +02:00
} else {
needs_full_update = true;
}
}
2020-07-24 12:13:28 +02:00
let query = this._factory.update(data);
for (const indexField of this.getPrimaryKeyFields()) {
query = query.where(indexField, this[indexField]);
}
await query.execute(connection);
2020-04-22 15:52:17 +02:00
} else {
const props_holders = [];
2020-07-24 12:13:28 +02:00
for (const property of this._properties) {
const value = this[property];
if (value !== undefined) {
properties.push(property);
2020-04-22 15:52:17 +02:00
props_holders.push('?');
2020-07-24 12:13:28 +02:00
values.push(value);
2020-04-22 15:52:17 +02:00
} else {
needs_full_update = true;
}
}
2020-07-24 12:13:28 +02:00
const result = await query(`INSERT INTO ${this.table} (${properties.join(', ')}) VALUES(${props_holders.join(', ')})`, values, connection);
2020-04-22 15:52:17 +02:00
2020-07-24 12:13:28 +02:00
if (this.hasOwnProperty('id')) this.id = result.other.insertId;
2020-04-22 15:52:17 +02:00
}
return needs_full_update;
}
public async exists(): Promise<boolean> {
2020-07-24 12:13:28 +02:00
let query = this._factory.select('1');
for (const indexField of this.getPrimaryKeyFields()) {
query = query.where(indexField, this[indexField]);
}
2020-06-27 17:11:31 +02:00
return (await query.limit(1).execute()).results.length > 0;
2020-04-22 15:52:17 +02:00
}
public async delete(): Promise<void> {
if (!(await this.exists())) throw new Error('This model instance doesn\'t exist in DB.');
2020-07-24 12:13:28 +02:00
let query = this._factory.delete();
for (const indexField of this.getPrimaryKeyFields()) {
query = query.where(indexField, this[indexField]);
}
await query.execute();
2020-04-22 15:52:17 +02:00
}
public async validate(onlyFormat: boolean = false, connection?: Connection): Promise<void[]> {
2020-07-24 12:13:28 +02:00
return await Promise.all(this._properties.map(
prop => this._validators[prop]?.execute(prop, this[prop], onlyFormat, connection)
));
2020-04-22 15:52:17 +02:00
}
2020-07-24 12:13:28 +02:00
public get table(): string {
return this._factory.table;
2020-04-22 15:52:17 +02:00
}
2020-07-24 12:13:28 +02:00
private get _properties(): string[] {
return Object.getOwnPropertyNames(this).filter(p => {
return !p.startsWith('_') && !(this[p] instanceof ModelRelation);
});
2020-04-22 15:52:17 +02:00
}
}
2020-07-24 12:13:28 +02:00
export const EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/;