Make models extendable
This commit is contained in:
parent
f03d74412c
commit
7f398c1d4e
@ -1,5 +1,8 @@
|
|||||||
import Migration from "../../db/Migration";
|
import Migration from "../../db/Migration";
|
||||||
import {Connection} from "mysql";
|
import {Connection} from "mysql";
|
||||||
|
import ModelFactory from "../../db/ModelFactory";
|
||||||
|
import User from "../models/User";
|
||||||
|
import UserApprovedComponent from "../models/UserApprovedComponent";
|
||||||
|
|
||||||
export default class AddApprovedFieldToUsersTable extends Migration {
|
export default class AddApprovedFieldToUsersTable extends Migration {
|
||||||
public async install(connection: Connection): Promise<void> {
|
public async install(connection: Connection): Promise<void> {
|
||||||
@ -9,4 +12,8 @@ export default class AddApprovedFieldToUsersTable extends Migration {
|
|||||||
public async rollback(connection: Connection): Promise<void> {
|
public async rollback(connection: Connection): Promise<void> {
|
||||||
await this.query('ALTER TABLE users DROP COLUMN approved', connection);
|
await this.query('ALTER TABLE users DROP COLUMN approved', connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public registerModels(): void {
|
||||||
|
ModelFactory.get(User).addComponent(UserApprovedComponent);
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,23 +1,30 @@
|
|||||||
import Migration from "../../db/Migration";
|
import Migration from "../../db/Migration";
|
||||||
import {Connection} from "mysql";
|
import {Connection} from "mysql";
|
||||||
|
import ModelFactory from "../../db/ModelFactory";
|
||||||
|
import MagicLink from "../models/MagicLink";
|
||||||
|
|
||||||
export default class CreateMagicLinksTable extends Migration {
|
export default class CreateMagicLinksTable extends Migration {
|
||||||
async install(connection: Connection): Promise<void> {
|
public async install(connection: Connection): Promise<void> {
|
||||||
await this.query('CREATE TABLE magic_links(' +
|
await this.query(`CREATE TABLE magic_links
|
||||||
'id INT NOT NULL AUTO_INCREMENT,' +
|
(
|
||||||
'session_id CHAR(32) UNIQUE NOT NULL,' +
|
id INT NOT NULL AUTO_INCREMENT,
|
||||||
'email VARCHAR(254) NOT NULL,' +
|
session_id CHAR(32) UNIQUE NOT NULL,
|
||||||
'token CHAR(96) NOT NULL,' +
|
email VARCHAR(254) NOT NULL,
|
||||||
'action_type VARCHAR(64) NOT NULL,' +
|
token CHAR(96) NOT NULL,
|
||||||
'original_url VARCHAR(1745) NOT NULL,' +
|
action_type VARCHAR(64) NOT NULL,
|
||||||
'generated_at DATETIME NOT NULL,' +
|
original_url VARCHAR(1745) NOT NULL,
|
||||||
'authorized BOOLEAN NOT NULL,' +
|
generated_at DATETIME NOT NULL,
|
||||||
'PRIMARY KEY(id)' +
|
authorized BOOLEAN NOT NULL,
|
||||||
')', connection);
|
PRIMARY KEY (id)
|
||||||
|
)`, connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
async rollback(connection: Connection): Promise<void> {
|
public async rollback(connection: Connection): Promise<void> {
|
||||||
await this.query('DROP TABLE magic_links', connection);
|
await this.query('DROP TABLE magic_links', connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public registerModels(): void {
|
||||||
|
ModelFactory.register(MagicLink);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,29 +1,39 @@
|
|||||||
import Migration from "../../db/Migration";
|
import Migration from "../../db/Migration";
|
||||||
import {Connection} from "mysql";
|
import {Connection} from "mysql";
|
||||||
|
import ModelFactory from "../../db/ModelFactory";
|
||||||
|
import User from "../models/User";
|
||||||
|
import UserEmail from "../models/UserEmail";
|
||||||
|
|
||||||
export default class CreateUsersAndUserEmailsTable extends Migration {
|
export default class CreateUsersAndUserEmailsTable extends Migration {
|
||||||
async install(connection: Connection): Promise<void> {
|
public async install(connection: Connection): Promise<void> {
|
||||||
await this.query('CREATE TABLE users(' +
|
await this.query(`CREATE TABLE users
|
||||||
'id INT NOT NULL AUTO_INCREMENT,' +
|
(
|
||||||
'name VARCHAR(64),' +
|
id INT NOT NULL AUTO_INCREMENT,
|
||||||
'is_admin BOOLEAN NOT NULL DEFAULT false,' +
|
name VARCHAR(64),
|
||||||
'created_at DATETIME NOT NULL DEFAULT NOW(),' +
|
is_admin BOOLEAN NOT NULL DEFAULT false,
|
||||||
'updated_at DATETIME NOT NULL DEFAULT NOW(),' +
|
created_at DATETIME NOT NULL DEFAULT NOW(),
|
||||||
'PRIMARY KEY(id)' +
|
updated_at DATETIME NOT NULL DEFAULT NOW(),
|
||||||
')', connection);
|
PRIMARY KEY (id)
|
||||||
await this.query('CREATE TABLE user_emails(' +
|
)`, connection);
|
||||||
'id INT NOT NULL AUTO_INCREMENT,' +
|
await this.query(`CREATE TABLE user_emails
|
||||||
'user_id INT,' +
|
(
|
||||||
'email VARCHAR(254) UNIQUE NOT NULL,' +
|
id INT NOT NULL AUTO_INCREMENT,
|
||||||
'main BOOLEAN NOT NULL,' +
|
user_id INT NOT NULL,
|
||||||
'created_at DATETIME NOT NULL DEFAULT NOW(),' +
|
email VARCHAR(254) UNIQUE NOT NULL,
|
||||||
'PRIMARY KEY(id),' +
|
main BOOLEAN NOT NULL,
|
||||||
'FOREIGN KEY user_fk (user_id) REFERENCES users (id) ON DELETE CASCADE' +
|
created_at DATETIME NOT NULL DEFAULT NOW(),
|
||||||
')', connection);
|
PRIMARY KEY (id),
|
||||||
|
FOREIGN KEY user_fk (user_id) REFERENCES users (id) ON DELETE CASCADE
|
||||||
|
)`, connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
async rollback(connection: Connection): Promise<void> {
|
public async rollback(connection: Connection): Promise<void> {
|
||||||
await this.query('DROP TABLE user_emails', connection);
|
await this.query('DROP TABLE user_emails', connection);
|
||||||
await this.query('DROP TABLE users', connection);
|
await this.query('DROP TABLE users', connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public registerModels(): void {
|
||||||
|
ModelFactory.register(User);
|
||||||
|
ModelFactory.register(UserEmail);
|
||||||
|
}
|
||||||
}
|
}
|
@ -2,7 +2,6 @@ import crypto from "crypto";
|
|||||||
import config from "config";
|
import config from "config";
|
||||||
import Model, {EMAIL_REGEX} from "../../db/Model";
|
import Model, {EMAIL_REGEX} from "../../db/Model";
|
||||||
import AuthProof from "../AuthProof";
|
import AuthProof from "../AuthProof";
|
||||||
import Validator from "../../db/Validator";
|
|
||||||
import User from "./User";
|
import User from "./User";
|
||||||
import argon2 from "argon2";
|
import argon2 from "argon2";
|
||||||
import {WhereTest} from "../../db/ModelQuery";
|
import {WhereTest} from "../../db/ModelQuery";
|
||||||
@ -43,13 +42,12 @@ export default class MagicLink extends Model implements AuthProof {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected init(): void {
|
protected init(): void {
|
||||||
this.addProperty<string>('session_id', new Validator().defined().length(32).unique(this));
|
this.setValidation('session_id').defined().length(32).unique(this);
|
||||||
this.addProperty<string>('email', new Validator().defined().regexp(EMAIL_REGEX));
|
this.setValidation('email').defined().regexp(EMAIL_REGEX);
|
||||||
this.addProperty<string>('token', new Validator().defined().length(96));
|
this.setValidation('token').defined().length(96);
|
||||||
this.addProperty<string>('action_type', new Validator().defined().maxLength(64));
|
this.setValidation('action_type').defined().maxLength(64);
|
||||||
this.addProperty<string>('original_url', new Validator().defined().maxLength(1745));
|
this.setValidation('original_url').defined().maxLength(1745);
|
||||||
this.addProperty<Date>('generated_at', new Validator());
|
this.setValidation('authorized').defined();
|
||||||
this.addProperty<boolean>('authorized', new Validator().defined());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async isOwnedBy(userId: number): Promise<boolean> {
|
public async isOwnedBy(userId: number): Promise<boolean> {
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import Model from "../../db/Model";
|
import Model from "../../db/Model";
|
||||||
import Validator from "../../db/Validator";
|
|
||||||
import MysqlConnectionManager from "../../db/MysqlConnectionManager";
|
import MysqlConnectionManager from "../../db/MysqlConnectionManager";
|
||||||
import AddApprovedFieldToUsersTable from "../migrations/AddApprovedFieldToUsersTable";
|
import AddApprovedFieldToUsersTable from "../migrations/AddApprovedFieldToUsersTable";
|
||||||
import config from "config";
|
import config from "config";
|
||||||
import {ManyModelRelation} from "../../db/ModelRelation";
|
import {ManyModelRelation} from "../../db/ModelRelation";
|
||||||
import UserEmail from "./UserEmail";
|
import UserEmail from "./UserEmail";
|
||||||
|
import ModelFactory from "../../db/ModelFactory";
|
||||||
|
import UserApprovedComponent from "./UserApprovedComponent";
|
||||||
|
|
||||||
export default class User extends Model {
|
export default class User extends Model {
|
||||||
public static isApprovalMode(): boolean {
|
public static isApprovalMode(): boolean {
|
||||||
@ -12,34 +13,32 @@ export default class User extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public name?: string;
|
public name?: string;
|
||||||
public approved!: boolean;
|
|
||||||
public is_admin!: boolean;
|
public is_admin!: boolean;
|
||||||
public created_at?: Date;
|
public created_at?: Date;
|
||||||
public updated_at?: Date;
|
public updated_at?: Date;
|
||||||
|
|
||||||
public readonly emails = new ManyModelRelation<this, UserEmail>(this, UserEmail, {
|
public readonly emails = new ManyModelRelation(this, ModelFactory.get(UserEmail), {
|
||||||
localKey: 'id',
|
localKey: 'id',
|
||||||
foreignKey: 'user_id'
|
foreignKey: 'user_id'
|
||||||
});
|
});
|
||||||
|
|
||||||
public readonly mainEmail = this.emails.cloneReduceToOne().constraint(q => q.where('main', true));
|
public readonly mainEmail = this.emails.cloneReduceToOne().constraint(q => q.where('main', true));
|
||||||
|
|
||||||
constructor(data: any) {
|
public constructor(data: any) {
|
||||||
super(data);
|
super(data);
|
||||||
if (this.approved === undefined) this.approved = false;
|
if (this.approved === undefined) this.approved = false;
|
||||||
if (this.is_admin === undefined) this.is_admin = false;
|
if (this.is_admin === undefined) this.is_admin = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected init(): void {
|
protected init(): void {
|
||||||
this.addProperty<string>('name', new Validator().acceptUndefined().between(3, 64));
|
this.setValidation('name').acceptUndefined().between(3, 64);
|
||||||
if (User.isApprovalMode()) this.addProperty<boolean>('approved', new Validator().defined());
|
if (User.isApprovalMode()) {
|
||||||
this.addProperty<boolean>('is_admin', new Validator().defined());
|
this.setValidation('approved').defined();
|
||||||
|
}
|
||||||
this.addProperty<Date>('created_at');
|
this.setValidation('is_admin').defined();
|
||||||
this.addProperty<Date>('updated_at');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public isApproved(): boolean {
|
public isApproved(): boolean {
|
||||||
return !User.isApprovalMode() || this.approved!;
|
return !User.isApprovalMode() || this.as(UserApprovedComponent).approved;
|
||||||
}
|
}
|
||||||
}
|
}
|
6
src/auth/models/UserApprovedComponent.ts
Normal file
6
src/auth/models/UserApprovedComponent.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import ModelComponent from "../../db/ModelComponent";
|
||||||
|
import User from "./User";
|
||||||
|
|
||||||
|
export default class UserApprovedComponent extends ModelComponent<User> {
|
||||||
|
public approved!: boolean;
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
import User from "./User";
|
import User from "./User";
|
||||||
import {Connection} from "mysql";
|
import {Connection} from "mysql";
|
||||||
import Model, {EMAIL_REGEX} from "../../db/Model";
|
import Model, {EMAIL_REGEX} from "../../db/Model";
|
||||||
import Validator from "../../db/Validator";
|
|
||||||
import {query} from "../../db/MysqlConnectionManager";
|
import {query} from "../../db/MysqlConnectionManager";
|
||||||
import {OneModelRelation} from "../../db/ModelRelation";
|
import {OneModelRelation} from "../../db/ModelRelation";
|
||||||
|
import ModelFactory from "../../db/ModelFactory";
|
||||||
|
|
||||||
export default class UserEmail extends Model {
|
export default class UserEmail extends Model {
|
||||||
public user_id?: number;
|
public user_id?: number;
|
||||||
@ -11,28 +11,27 @@ export default class UserEmail extends Model {
|
|||||||
private main!: boolean;
|
private main!: boolean;
|
||||||
public created_at?: Date;
|
public created_at?: Date;
|
||||||
|
|
||||||
public readonly user = new OneModelRelation<this, User>(this, User, {
|
public readonly user = new OneModelRelation<UserEmail, User>(this, ModelFactory.get(User), {
|
||||||
localKey: 'user_id',
|
localKey: 'user_id',
|
||||||
foreignKey: 'id'
|
foreignKey: 'id'
|
||||||
});
|
});
|
||||||
|
|
||||||
private wasSetToMain: boolean = false;
|
private _wasSetToMain: boolean = false;
|
||||||
|
|
||||||
constructor(data: any) {
|
constructor(data: any) {
|
||||||
super(data);
|
super(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected init(): void {
|
protected init(): void {
|
||||||
this.addProperty<number>('user_id', new Validator().acceptUndefined().exists(User, 'id'));
|
this.setValidation('user_id').acceptUndefined().exists(User, 'id');
|
||||||
this.addProperty<string>('email', new Validator().defined().regexp(EMAIL_REGEX).unique(this));
|
this.setValidation('email').defined().regexp(EMAIL_REGEX).unique(this);
|
||||||
this.addProperty<boolean>('main', new Validator().defined());
|
this.setValidation('main').defined();
|
||||||
this.addProperty<Date>('created_at', new Validator());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async beforeSave(exists: boolean, connection: Connection) {
|
protected async beforeSave(exists: boolean, connection: Connection) {
|
||||||
if (this.wasSetToMain) {
|
if (this._wasSetToMain) {
|
||||||
await query(`UPDATE ${this.table} SET main=false WHERE user_id=${this.user_id}`, null, connection);
|
await query(`UPDATE ${this.table} SET main=false WHERE user_id=${this.user_id}`, null, connection);
|
||||||
this.wasSetToMain = false;
|
this._wasSetToMain = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +42,7 @@ export default class UserEmail extends Model {
|
|||||||
public setMain() {
|
public setMain() {
|
||||||
if (!this.isMain()) {
|
if (!this.isMain()) {
|
||||||
this.main = true;
|
this.main = true;
|
||||||
this.wasSetToMain = true;
|
this._wasSetToMain = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,17 +4,19 @@ import MysqlConnectionManager from "./MysqlConnectionManager";
|
|||||||
export default abstract class Migration {
|
export default abstract class Migration {
|
||||||
public readonly version: number;
|
public readonly version: number;
|
||||||
|
|
||||||
constructor(version: number) {
|
public constructor(version: number) {
|
||||||
this.version = version;
|
this.version = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
async shouldRun(currentVersion: number): Promise<boolean> {
|
public async shouldRun(currentVersion: number): Promise<boolean> {
|
||||||
return this.version > currentVersion;
|
return this.version > currentVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract async install(connection: Connection): Promise<void>;
|
public abstract async install(connection: Connection): Promise<void>;
|
||||||
|
|
||||||
abstract async rollback(connection: Connection): Promise<void>;
|
public abstract async rollback(connection: Connection): Promise<void>;
|
||||||
|
|
||||||
|
public abstract registerModels(): void;
|
||||||
|
|
||||||
protected async query(queryString: string, connection: Connection): Promise<void> {
|
protected async query(queryString: string, connection: Connection): Promise<void> {
|
||||||
await MysqlConnectionManager.query(queryString, undefined, connection);
|
await MysqlConnectionManager.query(queryString, undefined, connection);
|
||||||
|
213
src/db/Model.ts
213
src/db/Model.ts
@ -1,138 +1,75 @@
|
|||||||
import MysqlConnectionManager, {query} from "./MysqlConnectionManager";
|
import MysqlConnectionManager, {query} from "./MysqlConnectionManager";
|
||||||
import Validator from "./Validator";
|
import Validator from "./Validator";
|
||||||
import {Connection} from "mysql";
|
import {Connection} from "mysql";
|
||||||
|
import ModelComponent from "./ModelComponent";
|
||||||
|
import {Type} from "../Utils";
|
||||||
|
import ModelFactory from "./ModelFactory";
|
||||||
|
import ModelRelation from "./ModelRelation";
|
||||||
import ModelQuery, {ModelQueryResult} from "./ModelQuery";
|
import ModelQuery, {ModelQueryResult} from "./ModelQuery";
|
||||||
import {Request} from "express";
|
import {Request} from "express";
|
||||||
import {Type} from "../Utils";
|
|
||||||
|
|
||||||
export interface ModelClass<M extends Model> extends Type<M> {
|
|
||||||
getFactory<M extends Model>(factory?: ModelFactory<M>): ModelFactory<M>;
|
|
||||||
|
|
||||||
table: string;
|
|
||||||
|
|
||||||
getPrimaryKey(modelData: any): string;
|
|
||||||
|
|
||||||
getPrimaryKeyFields(): string[];
|
|
||||||
|
|
||||||
select<M extends Model>(...fields: string[]): ModelQuery<M>;
|
|
||||||
|
|
||||||
update<M extends Model>(data: { [key: string]: any }): ModelQuery<M>;
|
|
||||||
|
|
||||||
delete<M extends Model>(): ModelQuery<M>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default abstract class Model {
|
export default abstract class Model {
|
||||||
public static get table(): string {
|
public static select<T extends Model>(this: Type<T>, ...fields: string[]): ModelQuery<T> {
|
||||||
return this.name
|
return ModelFactory.get(this).select(...fields);
|
||||||
.replace(/(?:^|\.?)([A-Z])/g, (x, y) => '_' + y.toLowerCase())
|
|
||||||
.replace(/^_/, '')
|
|
||||||
+ 's';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getById<T extends Model>(this: ModelClass<T>, id: number): Promise<T | null> {
|
public static update<T extends Model>(this: Type<T>, data: { [key: string]: any }): ModelQuery<T> {
|
||||||
return this.select<T>().where('id', id).first();
|
return ModelFactory.get(this).update(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async paginate<T extends Model>(this: ModelClass<T>, request: Request, perPage: number = 20, query?: ModelQuery<T>): Promise<ModelQueryResult<T>> {
|
public static delete<T extends Model>(this: Type<T>): ModelQuery<T> {
|
||||||
let page = request.params.page ? parseInt(request.params.page) : 1;
|
return ModelFactory.get(this).delete();
|
||||||
if (!query) query = this.select();
|
|
||||||
if (request.params.sortBy) {
|
|
||||||
const dir = request.params.sortDirection;
|
|
||||||
query = query.sortBy(request.params.sortBy, dir === 'ASC' || dir === 'DESC' ? dir : undefined);
|
|
||||||
} else {
|
|
||||||
query = query.sortBy('id');
|
|
||||||
}
|
|
||||||
return await query.paginate(page, perPage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static select<T extends Model>(this: ModelClass<T>, ...fields: string[]): ModelQuery<T> {
|
public static async getById<T extends Model>(this: Type<T>, ...id: any): Promise<T | null> {
|
||||||
return ModelQuery.select(this, ...fields);
|
return ModelFactory.get(this).getById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static update<T extends Model>(this: ModelClass<T>, data: { [key: string]: any }): ModelQuery<T> {
|
public static async paginate<T extends Model>(this: Type<T>, request: Request, perPage: number = 20, query?: ModelQuery<T>): Promise<ModelQueryResult<T>> {
|
||||||
return ModelQuery.update(this, data);
|
return ModelFactory.get(this).paginate(request, perPage, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static delete<T extends Model>(this: ModelClass<T>): ModelQuery<T> {
|
protected readonly _factory!: ModelFactory<any>;
|
||||||
return ModelQuery.delete(this);
|
private readonly _components: ModelComponent<any>[] = [];
|
||||||
}
|
private readonly _validators: { [key: string]: Validator<any> } = {};
|
||||||
|
|
||||||
public static getPrimaryKey(modelData: any): string {
|
|
||||||
return this.getPrimaryKeyFields().map(f => `${modelData[f]}`).join(',');
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getPrimaryKeyFields(): string[] {
|
|
||||||
return ['id'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async loadRelation<T extends Model>(models: T[], relation: string, model: Function, localField: string) {
|
|
||||||
const loadMap: { [p: number]: (model: T) => void } = {};
|
|
||||||
const ids = models.map(m => {
|
|
||||||
m.relations[relation] = null;
|
|
||||||
if (m[localField]) loadMap[m[localField]] = v => m.relations[relation] = v;
|
|
||||||
return m[localField];
|
|
||||||
}).filter(id => id);
|
|
||||||
for (const v of await (<any>model).models((<any>model).select().whereIn('id', ids))) {
|
|
||||||
loadMap[v.id!](v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getFactory<T extends Model>(this: ModelClass<T>, factory?: ModelFactory<T>): ModelFactory<T> {
|
|
||||||
if (factory === undefined) {
|
|
||||||
factory = (<any>this).FACTORY;
|
|
||||||
if (factory === undefined) factory = data => new (<any>this)(data);
|
|
||||||
}
|
|
||||||
return factory;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected readonly modelClass: ModelClass<this> = <ModelClass<this>>this.constructor;
|
|
||||||
protected readonly properties: ModelProperty<any>[] = [];
|
|
||||||
public id?: number;
|
|
||||||
private readonly automaticIdProperty: boolean;
|
|
||||||
|
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
|
|
||||||
public constructor(data: any, automaticIdProperty: boolean = true) {
|
public constructor(data: any) {
|
||||||
this.automaticIdProperty = automaticIdProperty;
|
|
||||||
if (automaticIdProperty) {
|
|
||||||
this.addProperty<number>('id', new Validator());
|
|
||||||
}
|
|
||||||
this.init();
|
this.init();
|
||||||
this.updateWithData(data);
|
this.updateWithData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPrimaryKey(): string {
|
public setFactory(factory: ModelFactory<any>) {
|
||||||
return this.modelClass.getPrimaryKey(this);
|
(this as any)._factory = factory;
|
||||||
}
|
|
||||||
|
|
||||||
public getPrimaryKeyFields(): string[] {
|
|
||||||
return this.modelClass.getPrimaryKeyFields();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract init(): void;
|
protected abstract init(): void;
|
||||||
|
|
||||||
protected addProperty<T>(name: string, validator?: Validator<T> | RegExp) {
|
protected setValidation<T>(propertyName: keyof this): Validator<T> {
|
||||||
if (validator === undefined) validator = new Validator();
|
const validator = new Validator<T>();
|
||||||
if (validator instanceof RegExp) {
|
this._validators[propertyName as string] = validator;
|
||||||
const regexp = validator;
|
return validator;
|
||||||
validator = new Validator().regexp(regexp);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const prop = new ModelProperty<T>(name, validator);
|
public addComponent(modelComponent: ModelComponent<this>): void {
|
||||||
this.properties.push(prop);
|
modelComponent.init();
|
||||||
Object.defineProperty(this, name, {
|
this._components.push(modelComponent);
|
||||||
get: () => prop.value,
|
}
|
||||||
set: (value: T) => prop.value = value,
|
|
||||||
});
|
public as<T extends ModelComponent<any>>(type: Type<T>): T {
|
||||||
|
for (const component of this._components) {
|
||||||
|
if (component instanceof type) {
|
||||||
|
return <any>this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(`Component ${type.name} was not initialized for this ${this.constructor.name}.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateWithData(data: any) {
|
private updateWithData(data: any) {
|
||||||
if (this.automaticIdProperty) this.id = data['id'];
|
for (const property of this._properties) {
|
||||||
|
if (data[property] !== undefined) {
|
||||||
for (const prop of this.properties) {
|
this[property] = data[property];
|
||||||
if (data[prop.name] !== undefined) {
|
|
||||||
this[prop.name] = data[prop.name];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,7 +94,7 @@ export default abstract class Model {
|
|||||||
|
|
||||||
const callback = async () => {
|
const callback = async () => {
|
||||||
if (needs_full_update) {
|
if (needs_full_update) {
|
||||||
this.updateWithData((await this.modelClass.select().where('id', this.id!).limit(1).execute()).results[0]);
|
this.updateWithData((await this._factory.select().where('id', this.id!).limit(1).execute()).results[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.afterSave();
|
await this.afterSave();
|
||||||
@ -177,50 +114,46 @@ export default abstract class Model {
|
|||||||
this.updated_at = new Date();
|
this.updated_at = new Date();
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = [];
|
const properties = [];
|
||||||
const values = [];
|
const values = [];
|
||||||
|
|
||||||
if (exists) {
|
if (exists) {
|
||||||
const data: any = {};
|
const data: any = {};
|
||||||
for (const prop of this.properties) {
|
for (const property of this._properties) {
|
||||||
if (prop.value !== undefined) {
|
const value = this[property];
|
||||||
data[prop.name] = prop.value;
|
if (value !== undefined) {
|
||||||
|
data[property] = value;
|
||||||
} else {
|
} else {
|
||||||
needs_full_update = true;
|
needs_full_update = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let query = this.modelClass.update(data);
|
let query = this._factory.update(data);
|
||||||
for (const indexField of this.getPrimaryKeyFields()) {
|
for (const indexField of this.getPrimaryKeyFields()) {
|
||||||
query = query.where(indexField, this[indexField]);
|
query = query.where(indexField, this[indexField]);
|
||||||
}
|
}
|
||||||
await query.execute(connection);
|
await query.execute(connection);
|
||||||
} else {
|
} else {
|
||||||
const props_holders = [];
|
const props_holders = [];
|
||||||
for (const prop of this.properties) {
|
for (const property of this._properties) {
|
||||||
if (prop.value !== undefined) {
|
const value = this[property];
|
||||||
props.push(prop.name);
|
if (value !== undefined) {
|
||||||
|
properties.push(property);
|
||||||
props_holders.push('?');
|
props_holders.push('?');
|
||||||
values.push(prop.value);
|
values.push(value);
|
||||||
} else {
|
} else {
|
||||||
needs_full_update = true;
|
needs_full_update = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const result = await query(`INSERT INTO ${this.table} (${props.join(', ')}) VALUES(${props_holders.join(', ')})`, values, connection);
|
const result = await query(`INSERT INTO ${this.table} (${properties.join(', ')}) VALUES(${props_holders.join(', ')})`, values, connection);
|
||||||
|
|
||||||
if (this.automaticIdProperty) this.id = result.other.insertId;
|
if (this.hasOwnProperty('id')) this.id = result.other.insertId;
|
||||||
}
|
}
|
||||||
|
|
||||||
return needs_full_update;
|
return needs_full_update;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get table(): string {
|
|
||||||
return this.modelClass.table;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async exists(): Promise<boolean> {
|
public async exists(): Promise<boolean> {
|
||||||
if (!this.id) return false;
|
let query = this._factory.select('1');
|
||||||
|
|
||||||
let query = this.modelClass.select('1');
|
|
||||||
for (const indexField of this.getPrimaryKeyFields()) {
|
for (const indexField of this.getPrimaryKeyFields()) {
|
||||||
query = query.where(indexField, this[indexField]);
|
query = query.where(indexField, this[indexField]);
|
||||||
}
|
}
|
||||||
@ -230,43 +163,27 @@ export default abstract class Model {
|
|||||||
public async delete(): Promise<void> {
|
public async delete(): Promise<void> {
|
||||||
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.modelClass.delete();
|
let query = this._factory.delete();
|
||||||
for (const indexField of this.getPrimaryKeyFields()) {
|
for (const indexField of this.getPrimaryKeyFields()) {
|
||||||
query = query.where(indexField, this[indexField]);
|
query = query.where(indexField, this[indexField]);
|
||||||
}
|
}
|
||||||
await query.execute();
|
await query.execute();
|
||||||
if (this.automaticIdProperty) this.id = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async validate(onlyFormat: boolean = false, connection?: Connection): Promise<void[]> {
|
public async validate(onlyFormat: boolean = false, connection?: Connection): Promise<void[]> {
|
||||||
return await Promise.all(this.properties.map(prop => prop.validate(onlyFormat, connection)));
|
return await Promise.all(this._properties.map(
|
||||||
}
|
prop => this._validators[prop]?.execute(prop, this[prop], onlyFormat, connection)
|
||||||
}
|
));
|
||||||
|
|
||||||
export interface ModelFactory<T extends Model> {
|
|
||||||
(data: any): T;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ModelProperty<T> {
|
|
||||||
public readonly name: string;
|
|
||||||
private readonly validator: Validator<T>;
|
|
||||||
private val?: T;
|
|
||||||
|
|
||||||
constructor(name: string, validator: Validator<T>) {
|
|
||||||
this.name = name;
|
|
||||||
this.validator = validator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async validate(onlyFormat: boolean, connection?: Connection): Promise<void> {
|
public get table(): string {
|
||||||
return await this.validator.execute(this.name, this.value, onlyFormat, connection);
|
return this._factory.table;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get value(): T | undefined {
|
private get _properties(): string[] {
|
||||||
return this.val;
|
return Object.getOwnPropertyNames(this).filter(p => {
|
||||||
}
|
return !p.startsWith('_') && !(this[p] instanceof ModelRelation);
|
||||||
|
});
|
||||||
public set value(val: T | undefined) {
|
|
||||||
this.val = val;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
31
src/db/ModelComponent.ts
Normal file
31
src/db/ModelComponent.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import Model from "./Model";
|
||||||
|
import Validator from "./Validator";
|
||||||
|
|
||||||
|
export default abstract class ModelComponent<T extends Model> {
|
||||||
|
protected readonly _model: T;
|
||||||
|
private readonly _validators: { [key: string]: Validator<any> } = {};
|
||||||
|
|
||||||
|
[key: string]: any;
|
||||||
|
|
||||||
|
public constructor(model: T) {
|
||||||
|
this._model = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(): void {
|
||||||
|
for (const property of this._properties) {
|
||||||
|
if (!property.startsWith('_')) {
|
||||||
|
(this._model as Model)[property] = this[property];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected setValidation<T>(propertyName: keyof this): Validator<T> {
|
||||||
|
const validator = new Validator<T>();
|
||||||
|
this._validators[propertyName as string] = validator;
|
||||||
|
return validator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _properties(): string[] {
|
||||||
|
return Object.getOwnPropertyNames(this).filter(p => !p.startsWith('_'));
|
||||||
|
}
|
||||||
|
}
|
107
src/db/ModelFactory.ts
Normal file
107
src/db/ModelFactory.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import ModelComponent from "./ModelComponent";
|
||||||
|
import Model from "./Model";
|
||||||
|
import ModelQuery, {ModelQueryResult} from "./ModelQuery";
|
||||||
|
import {Request} from "express";
|
||||||
|
import {Type} from "../Utils";
|
||||||
|
|
||||||
|
export default class ModelFactory<T extends Model> {
|
||||||
|
private static readonly factories: { [modelType: string]: ModelFactory<any> } = {};
|
||||||
|
|
||||||
|
public static register<M extends Model>(modelType: Type<M>) {
|
||||||
|
if (this.factories[modelType.name]) throw new Error(`Factory for type ${modelType.name} already defined.`);
|
||||||
|
this.factories[modelType.name] = new ModelFactory<M>(modelType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get<M extends Model>(modelType: Type<M>): ModelFactory<M> {
|
||||||
|
const factory = this.factories[modelType.name];
|
||||||
|
if (!factory) throw new Error(`No factory registered for ${modelType.name}.`);
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly modelType: Type<T>;
|
||||||
|
private readonly components: ModelComponentFactory<T>[] = [];
|
||||||
|
|
||||||
|
protected constructor(modelType: Type<T>) {
|
||||||
|
this.modelType = modelType;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public addComponent(modelComponentFactory: ModelComponentFactory<T>) {
|
||||||
|
this.components.push(modelComponentFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public make(data: any): T {
|
||||||
|
const model = new this.modelType(data);
|
||||||
|
for (const component of this.components) {
|
||||||
|
model.addComponent(new component(model));
|
||||||
|
}
|
||||||
|
model.setFactory(this);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get table(): string {
|
||||||
|
return this.constructor.name
|
||||||
|
.replace(/(?:^|\.?)([A-Z])/g, (x, y) => '_' + y.toLowerCase())
|
||||||
|
.replace(/^_/, '')
|
||||||
|
+ 's';
|
||||||
|
}
|
||||||
|
|
||||||
|
public select(...fields: string[]): ModelQuery<T> {
|
||||||
|
return ModelQuery.select(this, ...fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(data: { [key: string]: any }): ModelQuery<T> {
|
||||||
|
return ModelQuery.update(this, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public delete(): ModelQuery<T> {
|
||||||
|
return ModelQuery.delete(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPrimaryKeyFields(): string[] {
|
||||||
|
return ['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPrimaryKey(modelData: any): any[] {
|
||||||
|
return this.getPrimaryKeyFields().map(f => modelData[f]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPrimaryKeyString(modelData: any): string {
|
||||||
|
return this.getPrimaryKey(modelData).join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getById(...id: any): Promise<T | null> {
|
||||||
|
let query = this.select();
|
||||||
|
const primaryKeyFields = this.getPrimaryKeyFields();
|
||||||
|
for (let i = 0; i < primaryKeyFields.length; i++) {
|
||||||
|
query = query.where(primaryKeyFields[i], id[i]);
|
||||||
|
}
|
||||||
|
return query.first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async paginate(request: Request, perPage: number = 20, query?: ModelQuery<T>): Promise<ModelQueryResult<T>> {
|
||||||
|
let page = request.params.page ? parseInt(request.params.page) : 1;
|
||||||
|
if (!query) query = this.select();
|
||||||
|
if (request.params.sortBy) {
|
||||||
|
const dir = request.params.sortDirection;
|
||||||
|
query = query.sortBy(request.params.sortBy, dir === 'ASC' || dir === 'DESC' ? dir : undefined);
|
||||||
|
} else {
|
||||||
|
query = query.sortBy('id');
|
||||||
|
}
|
||||||
|
return await query.paginate(page, perPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async loadRelation(models: T[], relation: string, model: Function, localField: string) {
|
||||||
|
const loadMap: { [p: number]: (model: T) => void } = {};
|
||||||
|
const ids = models.map(m => {
|
||||||
|
m.relations[relation] = null;
|
||||||
|
if (m[localField]) loadMap[m[localField]] = v => m.relations[relation] = v;
|
||||||
|
return m[localField];
|
||||||
|
}).filter(id => id);
|
||||||
|
for (const v of await (<any>model).models((<any>model).select().whereIn('id', ids))) {
|
||||||
|
loadMap[v.id!](v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ModelComponentFactory<T extends Model> = new (model: T) => ModelComponent<T>;
|
@ -1,15 +1,16 @@
|
|||||||
import {query, QueryResult} from "./MysqlConnectionManager";
|
import {query, QueryResult} from "./MysqlConnectionManager";
|
||||||
import {Connection} from "mysql";
|
import {Connection} from "mysql";
|
||||||
import Model, {ModelClass} from "./Model";
|
import Model from "./Model";
|
||||||
import Pagination from "../Pagination";
|
import Pagination from "../Pagination";
|
||||||
import ModelRelation from "./ModelRelation";
|
import ModelRelation from "./ModelRelation";
|
||||||
|
import ModelFactory from "./ModelFactory";
|
||||||
|
|
||||||
export default class ModelQuery<M extends Model> {
|
export default class ModelQuery<M extends Model> {
|
||||||
public static select<M extends Model>(modelClass: ModelClass<M>, ...fields: string[]): ModelQuery<M> {
|
public static select<M extends Model>(factory: ModelFactory<M>, ...fields: string[]): ModelQuery<M> {
|
||||||
return new ModelQuery(QueryType.SELECT, modelClass, fields.length > 0 ? fields : ['*']);
|
return new ModelQuery(QueryType.SELECT, factory, fields.length > 0 ? fields : ['*']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static update<M extends Model>(modelClass: ModelClass<M>, data: {
|
public static update<M extends Model>(factory: ModelFactory<M>, data: {
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}): ModelQuery<M> {
|
}): ModelQuery<M> {
|
||||||
const fields = [];
|
const fields = [];
|
||||||
@ -18,15 +19,15 @@ export default class ModelQuery<M extends Model> {
|
|||||||
fields.push(new UpdateFieldValue(key, data[key], false));
|
fields.push(new UpdateFieldValue(key, data[key], false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new ModelQuery(QueryType.UPDATE, modelClass, fields);
|
return new ModelQuery(QueryType.UPDATE, factory, fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static delete<M extends Model>(modelClass: ModelClass<M>): ModelQuery<M> {
|
public static delete<M extends Model>(factory: ModelFactory<M>): ModelQuery<M> {
|
||||||
return new ModelQuery(QueryType.DELETE, modelClass);
|
return new ModelQuery(QueryType.DELETE, factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly type: QueryType;
|
private readonly type: QueryType;
|
||||||
private readonly modelClass: ModelClass<M>;
|
private readonly factory: ModelFactory<M>;
|
||||||
private readonly table: string;
|
private readonly table: string;
|
||||||
private readonly fields: (string | SelectFieldValue | UpdateFieldValue)[];
|
private readonly fields: (string | SelectFieldValue | UpdateFieldValue)[];
|
||||||
private _leftJoin?: string;
|
private _leftJoin?: string;
|
||||||
@ -39,10 +40,10 @@ export default class ModelQuery<M extends Model> {
|
|||||||
private readonly relations: string[] = [];
|
private readonly relations: string[] = [];
|
||||||
private _pivot?: string[];
|
private _pivot?: string[];
|
||||||
|
|
||||||
private constructor(type: QueryType, modelClass: ModelClass<M>, fields?: (string | SelectFieldValue | UpdateFieldValue)[]) {
|
private constructor(type: QueryType, factory: ModelFactory<M>, fields?: (string | SelectFieldValue | UpdateFieldValue)[]) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.modelClass = modelClass;
|
this.factory = factory;
|
||||||
this.table = modelClass.table;
|
this.table = factory.table;
|
||||||
this.fields = fields || [];
|
this.fields = fields || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,9 +169,8 @@ export default class ModelQuery<M extends Model> {
|
|||||||
relationMap[relation] = [];
|
relationMap[relation] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const factory = this.modelClass.getFactory<M>();
|
|
||||||
for (const result of queryResult.results) {
|
for (const result of queryResult.results) {
|
||||||
const model = factory(result);
|
const model = this.factory.make(result);
|
||||||
models.push(model);
|
models.push(model);
|
||||||
|
|
||||||
if (this._pivot) {
|
if (this._pivot) {
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import ModelQuery, {ModelQueryResult, WhereTest} from "./ModelQuery";
|
import ModelQuery, {ModelQueryResult, WhereTest} from "./ModelQuery";
|
||||||
import Model, {ModelClass} from "./Model";
|
import Model from "./Model";
|
||||||
|
import ModelFactory from "./ModelFactory";
|
||||||
|
|
||||||
export default abstract class ModelRelation<S extends Model, O extends Model, R extends O | O[] | null> {
|
export default abstract class ModelRelation<S extends Model, O extends Model, R extends O | O[] | null> {
|
||||||
protected readonly model: S;
|
protected readonly model: S;
|
||||||
protected readonly foreignModelClass: ModelClass<O>;
|
protected readonly foreignFactory: ModelFactory<O>;
|
||||||
protected readonly query: ModelQuery<O>;
|
protected readonly query: ModelQuery<O>;
|
||||||
protected cachedModels?: R;
|
protected cachedModels?: R;
|
||||||
|
|
||||||
protected constructor(model: S, foreignModelClass: ModelClass<O>) {
|
protected constructor(model: S, foreignFactory: ModelFactory<O>) {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.foreignModelClass = foreignModelClass;
|
this.foreignFactory = foreignFactory;
|
||||||
this.query = this.foreignModelClass.select();
|
this.query = this.foreignFactory.select();
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract clone(): ModelRelation<S, O, R>;
|
public abstract clone(): ModelRelation<S, O, R>;
|
||||||
@ -46,13 +47,13 @@ export type QueryModifier<M extends Model> = (query: ModelQuery<M>) => ModelQuer
|
|||||||
export class OneModelRelation<S extends Model, O extends Model> extends ModelRelation<S, O, O | null> {
|
export class OneModelRelation<S extends Model, O extends Model> extends ModelRelation<S, O, O | null> {
|
||||||
protected readonly dbProperties: RelationDatabaseProperties;
|
protected readonly dbProperties: RelationDatabaseProperties;
|
||||||
|
|
||||||
constructor(model: S, foreignModelClass: ModelClass<O>, dbProperties: RelationDatabaseProperties) {
|
constructor(model: S, foreignFactory: ModelFactory<O>, dbProperties: RelationDatabaseProperties) {
|
||||||
super(model, foreignModelClass);
|
super(model, foreignFactory);
|
||||||
this.dbProperties = dbProperties;
|
this.dbProperties = dbProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
public clone(): OneModelRelation<S, O> {
|
public clone(): OneModelRelation<S, O> {
|
||||||
return new OneModelRelation(this.model, this.foreignModelClass, this.dbProperties);
|
return new OneModelRelation(this.model, this.foreignFactory, this.dbProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getModelID() {
|
public getModelID() {
|
||||||
@ -82,17 +83,17 @@ export class OneModelRelation<S extends Model, O extends Model> extends ModelRel
|
|||||||
export class ManyModelRelation<S extends Model, O extends Model> extends ModelRelation<S, O, O[]> {
|
export class ManyModelRelation<S extends Model, O extends Model> extends ModelRelation<S, O, O[]> {
|
||||||
protected readonly dbProperties: RelationDatabaseProperties;
|
protected readonly dbProperties: RelationDatabaseProperties;
|
||||||
|
|
||||||
constructor(model: S, foreignModelClass: ModelClass<O>, dbProperties: RelationDatabaseProperties) {
|
constructor(model: S, foreignFactory: ModelFactory<O>, dbProperties: RelationDatabaseProperties) {
|
||||||
super(model, foreignModelClass);
|
super(model, foreignFactory);
|
||||||
this.dbProperties = dbProperties;
|
this.dbProperties = dbProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
public clone(): ManyModelRelation<S, O> {
|
public clone(): ManyModelRelation<S, O> {
|
||||||
return new ManyModelRelation<S, O>(this.model, this.foreignModelClass, this.dbProperties);
|
return new ManyModelRelation<S, O>(this.model, this.foreignFactory, this.dbProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
public cloneReduceToOne(): OneModelRelation<S, O> {
|
public cloneReduceToOne(): OneModelRelation<S, O> {
|
||||||
return new OneModelRelation<S, O>(this.model, this.foreignModelClass, this.dbProperties);
|
return new OneModelRelation<S, O>(this.model, this.foreignFactory, this.dbProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getModelID(): any {
|
public getModelID(): any {
|
||||||
@ -123,16 +124,16 @@ export class ManyModelRelation<S extends Model, O extends Model> extends ModelRe
|
|||||||
export class ManyThroughModelRelation<S extends Model, O extends Model> extends ModelRelation<S, O, O[]> {
|
export class ManyThroughModelRelation<S extends Model, O extends Model> extends ModelRelation<S, O, O[]> {
|
||||||
protected readonly dbProperties: PivotRelationDatabaseProperties;
|
protected readonly dbProperties: PivotRelationDatabaseProperties;
|
||||||
|
|
||||||
constructor(model: S, foreignModelClass: ModelClass<O>, dbProperties: PivotRelationDatabaseProperties) {
|
constructor(model: S, foreignFactory: ModelFactory<O>, dbProperties: PivotRelationDatabaseProperties) {
|
||||||
super(model, foreignModelClass);
|
super(model, foreignFactory);
|
||||||
this.dbProperties = dbProperties;
|
this.dbProperties = dbProperties;
|
||||||
this.query
|
this.query
|
||||||
.leftJoin(`${this.dbProperties.pivotTable} as pivot`)
|
.leftJoin(`${this.dbProperties.pivotTable} as pivot`)
|
||||||
.on(`pivot.${this.dbProperties.foreignPivotKey}`, `${this.foreignModelClass.table}.${this.dbProperties.foreignKey}`);
|
.on(`pivot.${this.dbProperties.foreignPivotKey}`, `${this.foreignFactory.table}.${this.dbProperties.foreignKey}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public clone(): ManyThroughModelRelation<S, O> {
|
public clone(): ManyThroughModelRelation<S, O> {
|
||||||
return new ManyThroughModelRelation<S, O>(this.model, this.foreignModelClass, this.dbProperties);
|
return new ManyThroughModelRelation<S, O>(this.model, this.foreignFactory, this.dbProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getModelID(): any {
|
public getModelID(): any {
|
||||||
|
@ -192,6 +192,8 @@ export default class MysqlConnectionManager {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
migration.registerModels();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,25 +1,32 @@
|
|||||||
import Migration from "../db/Migration";
|
import Migration from "../db/Migration";
|
||||||
import {Connection} from "mysql";
|
import {Connection} from "mysql";
|
||||||
|
import ModelFactory from "../db/ModelFactory";
|
||||||
|
import Log from "../models/Log";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Must be the first migration
|
* Must be the first migration
|
||||||
*/
|
*/
|
||||||
export default class CreateLogsTable extends Migration {
|
export default class CreateLogsTable extends Migration {
|
||||||
async install(connection: Connection): Promise<void> {
|
public async install(connection: Connection): Promise<void> {
|
||||||
await this.query('CREATE TABLE logs(' +
|
await this.query(`CREATE TABLE logs
|
||||||
'id INT NOT NULL AUTO_INCREMENT,' +
|
(
|
||||||
'level TINYINT UNSIGNED NOT NULL,' +
|
id INT NOT NULL AUTO_INCREMENT,
|
||||||
'message TEXT NOT NULL,' +
|
level TINYINT UNSIGNED NOT NULL,
|
||||||
'log_id BINARY(16),' +
|
message TEXT NOT NULL,
|
||||||
'error_name VARCHAR(128),' +
|
log_id BINARY(16),
|
||||||
'error_message VARCHAR(512),' +
|
error_name VARCHAR(128),
|
||||||
'error_stack TEXT,' +
|
error_message VARCHAR(512),
|
||||||
'created_at DATETIME NOT NULL DEFAULT NOW(),' +
|
error_stack TEXT,
|
||||||
'PRIMARY KEY (id)' +
|
created_at DATETIME NOT NULL DEFAULT NOW(),
|
||||||
')', connection);
|
PRIMARY KEY (id)
|
||||||
|
)`, connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
async rollback(connection: Connection): Promise<void> {
|
public async rollback(connection: Connection): Promise<void> {
|
||||||
await this.query('DROP TABLE logs', connection);
|
await this.query('DROP TABLE logs', connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public registerModels(): void {
|
||||||
|
ModelFactory.register(Log);
|
||||||
|
}
|
||||||
}
|
}
|
@ -6,7 +6,7 @@ import {query} from "../db/MysqlConnectionManager";
|
|||||||
* Must be the first migration
|
* Must be the first migration
|
||||||
*/
|
*/
|
||||||
export default class CreateMigrationsTable extends Migration {
|
export default class CreateMigrationsTable extends Migration {
|
||||||
async shouldRun(currentVersion: number): Promise<boolean> {
|
public async shouldRun(currentVersion: number): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await query('SELECT 1 FROM migrations LIMIT 1');
|
await query('SELECT 1 FROM migrations LIMIT 1');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -18,16 +18,20 @@ export default class CreateMigrationsTable extends Migration {
|
|||||||
return await super.shouldRun(currentVersion);
|
return await super.shouldRun(currentVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
async install(connection: Connection): Promise<void> {
|
public async install(connection: Connection): Promise<void> {
|
||||||
await this.query('CREATE TABLE migrations(' +
|
await this.query(`CREATE TABLE migrations
|
||||||
'id INT NOT NULL,' +
|
(
|
||||||
'name VARCHAR(64) NOT NULL,' +
|
id INT NOT NULL,
|
||||||
'migration_date DATE,' +
|
name VARCHAR(64) NOT NULL,
|
||||||
'PRIMARY KEY (id)' +
|
migration_date DATE,
|
||||||
')', connection);
|
PRIMARY KEY (id)
|
||||||
|
)`, connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
async rollback(connection: Connection): Promise<void> {
|
public async rollback(connection: Connection): Promise<void> {
|
||||||
await this.query('DROP TABLE migrations', connection);
|
await this.query('DROP TABLE migrations', connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public registerModels(): void {
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,6 +1,5 @@
|
|||||||
import Model from "../db/Model";
|
import Model from "../db/Model";
|
||||||
import {LogLevel, LogLevelKeys} from "../Logger";
|
import {LogLevel, LogLevelKeys} from "../Logger";
|
||||||
import Validator from "../db/Validator";
|
|
||||||
|
|
||||||
export default class Log extends Model {
|
export default class Log extends Model {
|
||||||
private level?: number;
|
private level?: number;
|
||||||
@ -12,13 +11,12 @@ export default class Log extends Model {
|
|||||||
private created_at?: Date;
|
private created_at?: Date;
|
||||||
|
|
||||||
protected init(): void {
|
protected init(): void {
|
||||||
this.addProperty<number>('level', new Validator<number>().defined());
|
this.setValidation('level').defined();
|
||||||
this.addProperty<string>('message', new Validator<string>().defined().between(0, 65535));
|
this.setValidation('message').defined().between(0, 65535);
|
||||||
this.addProperty<Buffer>('log_id', new Validator<Buffer>().acceptUndefined().length(16));
|
this.setValidation('log_id').acceptUndefined().length(16);
|
||||||
this.addProperty<string>('error_name', new Validator<string>().acceptUndefined().between(0, 128));
|
this.setValidation('error_name').acceptUndefined().between(0, 128);
|
||||||
this.addProperty<string>('error_message', new Validator<string>().acceptUndefined().between(0, 512));
|
this.setValidation('error_message').acceptUndefined().between(0, 512);
|
||||||
this.addProperty<string>('error_stack', new Validator<string>().acceptUndefined().between(0, 65535));
|
this.setValidation('error_stack').acceptUndefined().between(0, 65535);
|
||||||
this.addProperty<Date>('created_at', new Validator<Date>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLevel(): LogLevelKeys {
|
public getLevel(): LogLevelKeys {
|
||||||
|
Loading…
Reference in New Issue
Block a user