import {Request} from "express"; import {PageNotFoundError} from "../common/Pagination.js"; import {NotFoundHttpError} from "../HttpError.js"; import Model, {ModelType} from "./Model.js"; import ModelComponent from "./ModelComponent.js"; import ModelQuery, {ModelQueryResult, QueryFields} from "./ModelQuery.js"; export default class ModelFactory { private static readonly factories: { [modelType: string]: ModelFactory | undefined } = {}; public static register(modelType: ModelType): void { if (this.factories[modelType.name]) throw new Error(`Factory for type ${modelType.name} already defined.`); this.factories[modelType.name] = new ModelFactory(modelType) as unknown as ModelFactory; } public static get(modelType: ModelType): ModelFactory { const factory = this.factories[modelType.name]; if (!factory) throw new Error(`No factory registered for ${modelType.name}.`); return factory as unknown as ModelFactory; } public static has(modelType: ModelType): boolean { return !!this.factories[modelType.name]; } private readonly modelType: ModelType; private readonly components: ModelComponentFactory[] = []; protected constructor(modelType: ModelType) { this.modelType = modelType; } public addComponent(modelComponentFactory: ModelComponentFactory): void { this.components.push(modelComponentFactory); } public hasComponent(modelComponentFactory: ModelComponentFactory): boolean { return !!this.components.find(c => c === modelComponentFactory); } public create(data: Pick, isNewModel: boolean): M { const model = new this.modelType(this as unknown as ModelFactory, isNewModel); for (const component of this.components) { model.addComponent(new component(model)); } model.updateWithData(data); return model; } public get table(): string { return this.modelType.table; } public select(...fields: QueryFields): ModelQuery { return ModelQuery.select(this, ...fields); } public insert(data: Pick): ModelQuery { return ModelQuery.insert(this, data); } public update(data: Pick): ModelQuery { return ModelQuery.update(this, data); } public delete(): ModelQuery { return ModelQuery.delete(this); } public getPrimaryKeyFields(): (keyof M & string)[] { return this.modelType.getPrimaryKeyFields(); } public getPrimaryKey(modelData: Pick): Pick[keyof M & string][] { return this.getPrimaryKeyFields().map(f => modelData[f]); } public getPrimaryKeyString(modelData: Pick): string { return this.getPrimaryKey(modelData).join(','); } public async getById(...id: PrimaryKeyValue[]): Promise { let query = this.select(); const primaryKeyFields = this.getPrimaryKeyFields(); for (let i = 0; i < primaryKeyFields.length; i++) { query = query.where(primaryKeyFields[i], id[i]); } return await query.first(); } public async paginate(req: Request, perPage: number = 20, query?: ModelQuery): Promise> { const page = req.params.page ? parseInt(req.params.page) : 1; if (!query) query = this.select(); if (req.params.sortBy) { const dir = req.params.sortDirection; query = query.sortBy(req.params.sortBy, dir === 'ASC' || dir === 'DESC' ? dir : undefined); } else { query = query.sortBy('id'); } try { return await query.paginate(page, perPage); } catch (e) { if (e instanceof PageNotFoundError) { throw new NotFoundHttpError(`page ${e.page}`, req.url, e); } else { throw e; } } } } export type ModelComponentFactory = new (model: M) => ModelComponent; export type PrimaryKeyValue = string | number | boolean | null | undefined;