import { Service, Inject } from 'typedi'; import { getManager, SelectQueryBuilder, ObjectLiteral } from 'typeorm'; import { BaseModel, WhereInput } from '@joystream/warthog'; import { snakeCase, camelCase, uniq, orderBy } from 'lodash'; import { GraphQLResolveInfo } from 'graphql'; {{#subclasses}} import { {{className}}Service } from '../{{kebabName}}/{{kebabName}}.service' import { {{className}} } from '../{{kebabName}}/{{kebabName}}.model'; {{/subclasses}} import { {{className}} } from './{{kebabName}}.model'; import { {{className}}TypeOptions } from '../enums/enums'; import { WarthogBaseService, addOrderBy, orderByFields, parseOrderBy, } from '../../WarthogBaseService'; @Service('{{className}}Service') export class {{className}}Service { public readonly typeToService: { [key: string]: WarthogBaseService } = {}; constructor( {{#subclasses}} @Inject('{{className}}Service') public readonly {{camelName}}Service: {{className}}Service, {{/subclasses}} ) { {{#subclasses}} this.typeToService['{{className}}'] = {{camelName}}Service; {{/subclasses}} } async find( where?: any, ob?: string | string[], _limit?: number, _offset?: number, _fields?: string[], info?: GraphQLResolveInfo | string ): Promise { const limit = _limit ?? 50; const offset = _offset ?? 0; const fields = uniq([...(_fields || []), ...orderByFields(ob)]); if (limit > 10000) { throw new Error('Cannot fetch more than 10000 at a time'); } const { type_in, type_eq } = where; const types: string[] = (type_eq ? [type_eq] : type_in || [ {{#subclasses}} {{className}}, {{/subclasses}} ] ).map((t: EventTypeOptions) => t.toString()); delete where.type_in; delete where.type_eq; // take fields that are present in all implemetations const commonFields = fields.filter((f) => types.reduce( (hasField: boolean, t) => this.typeToService[t].columnMap[f] !== undefined && hasField, true ) ); const queries: SelectQueryBuilder[] = types.map( (t) => (this.typeToService[t] .buildFindQueryWithParams( where, undefined, undefined, commonFields, (field) => snakeCase(field) ) .addSelect(`'${t}'`, 'type') .take(undefined) as unknown) as SelectQueryBuilder ); const parameters = queries.reduce((params: ObjectLiteral, q: SelectQueryBuilder) => { return { ...params, ...q.getParameters() }; }, {} as ObjectLiteral); let rawQuery = queries.map((q) => `( ${q.getQuery()} )`).join(' UNION ALL '); let qb = getManager() .createQueryBuilder() .select('u.id', 'u_id') .addSelect('u.type', 'u_type') .from(`( ${rawQuery} )`, 'u') .setParameters(parameters) .take(limit) .skip(offset); qb = (addOrderBy( ob, qb, (attr) => `u.${snakeCase(attr)}` ) as unknown) as SelectQueryBuilder; const results = await qb.getRawMany<{ u_id: string; u_type: string }>(); const entityPromises: Promise[] = types.map((t) => { const service = this.typeToService[t]; return service.find( { id_in: results.filter((r) => r.u_type === t).map((r) => r.u_id) }, undefined, limit, undefined, fields.filter((f) => service.columnMap[f] !== undefined) ).then(tmpResults => tmpResults.map(item => (item.type = t, item))); }); const result = (await Promise.all(entityPromises)).reduce( (acc, curr) => [...acc, ...curr], [] as Event[] ); const [attrs, dirs] = parseOrderBy(ob); return orderBy(result, attrs, dirs); } }