import { Model as SequelizeModel, ModelCtor } from 'sequelize'; import { Collection } from './collection'; import { Database } from './database'; import lodash from 'lodash'; import { Field } from './fields'; interface IModel { [key: string]: any; } interface JSONTransformerOptions { model: ModelCtor; collection: Collection; db: Database; key?: string; field?: Field; } export class Model extends SequelizeModel implements IModel { public static database: Database; public static collection: Collection; public toJSON(): T { const handleObj = (obj, options: JSONTransformerOptions) => { const handles = [ (data) => { if (data instanceof Model) { return data.toJSON(); } return data; }, this.hiddenObjKey, ]; return handles.reduce((carry, fn) => fn.apply(this, [carry, options]), obj); }; const handleArray = (arrayOfObj, options: JSONTransformerOptions) => { const handles = [this.sortAssociations]; return handles.reduce((carry, fn) => fn.apply(this, [carry, options]), arrayOfObj || []); }; const opts = { model: this.constructor as ModelCtor, collection: (this.constructor as any).collection, db: (this.constructor as any).database as Database, }; const traverseJSON = (data: T, options: JSONTransformerOptions): T => { const { model, db, collection } = options; // handle Object data = handleObj(data, options); const result = {}; for (const key of Object.keys(data)) { // @ts-ignore if (model.hasAlias(key)) { const association = model.associations[key]; const opts = { model: association.target, collection: db.getCollection(association.target.name), db, key, field: collection.getField(key), }; if (['HasMany', 'BelongsToMany'].includes(association.associationType)) { result[key] = handleArray(data[key], opts).map((item) => traverseJSON(item, opts)); } else { result[key] = data[key] ? traverseJSON(data[key], opts) : null; } } else { result[key] = data[key]; } } return result as T; }; return traverseJSON(super.toJSON(), opts); } private hiddenObjKey(obj, options: JSONTransformerOptions) { const hiddenFields = Array.from(options.collection.fields.values()) .filter((field) => field.options.hidden) .map((field) => field.options.name); return lodash.omit(obj, hiddenFields); } private sortAssociations(data, { field }: JSONTransformerOptions): any { const sortBy = field.options.sortBy; return sortBy ? this.sortArray(data, sortBy) : data; } private sortArray(data, sortBy: string | string[]) { if (!lodash.isArray(sortBy)) { sortBy = [sortBy]; } const orderItems = []; const orderDirections = []; sortBy.forEach((sortItem) => { orderDirections.push(sortItem.startsWith('-') ? 'desc' : 'asc'); orderItems.push(sortItem.replace('-', '')); }); return lodash.orderBy(data, orderItems, orderDirections); } }