Skip to content

Commit

Permalink
EW-561 base implementation part 1
Browse files Browse the repository at this point in the history
  • Loading branch information
MajedAlaitwniCap committed Oct 5, 2023
1 parent 5a26dd1 commit 3186680
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 13 deletions.
21 changes: 21 additions & 0 deletions src/modules/person/persistence/person-sorting.mapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* eslint-disable @typescript-eslint/explicit-member-accessibility */
/* eslint-disable import/extensions */
import { QueryOrderMap } from '@mikro-orm/core';
import { SortOrderMap } from '../../../shared/interface/find-options';
import { PersonDo } from '../domain/person.do';
import { PersonEntity } from './person.entity';

export class PersonSortingMapper {
static mapDOSortOrderToQueryOrder(sort: SortOrderMap<PersonDo<boolean>>): QueryOrderMap<PersonEntity> {
const queryOrderMap: SortOrderMap<PersonEntity | undefined> = {
id: sort.id,
firstName: sort.firstName,
lastName: sort.lastName,
birthDate: sort.birthDate,
};
Object.keys(queryOrderMap)
.filter((key) => queryOrderMap[key] === undefined)

Check warning on line 17 in src/modules/person/persistence/person-sorting.mapper.ts

View workflow job for this annotation

GitHub Actions / nest_lint / Nest Lint

Expected key to have a type annotation
.forEach((key) => delete queryOrderMap[key]);

Check warning on line 18 in src/modules/person/persistence/person-sorting.mapper.ts

View workflow job for this annotation

GitHub Actions / nest_lint / Nest Lint

Expected key to have a type annotation
return queryOrderMap;
}
}
62 changes: 49 additions & 13 deletions src/modules/person/persistence/person.repo.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { Mapper } from '@automapper/core';
import { getMapperToken } from '@automapper/nestjs';
import { EntityManager } from '@mikro-orm/postgresql';
import { Inject, Injectable } from '@nestjs/common';
import { PersonDo } from '../domain/person.do.js';
import { PersonEntity } from './person.entity.js';
import { Loaded } from '@mikro-orm/core';
import { Loaded, QueryOrderMap } from '@mikro-orm/core';
import { IFindOptions, IPagination, SortOrder } from '../../../shared/interface/find-options.js';
import { Page } from '../../../shared/interface/page.js';
import { Scope } from '../../../shared/repo/scope.js';
import { PersonScope } from './person.scope.js';
import { PersonSortingMapper } from './person-sorting.mapper.js';
@Injectable()
export class PersonRepo {
public constructor(private readonly em: EntityManager, @Inject(getMapperToken()) private readonly mapper: Mapper) {}
Expand Down Expand Up @@ -58,21 +65,50 @@ export class PersonRepo {
return this.mapper.map(person, PersonEntity, PersonDo);
}

public async findAll(personDo: PersonDo<false>): Promise<PersonDo<true>[]> {
const query: Record<string, unknown> = {};
public async findAll(
personDo: PersonDo<false>,
options?: IFindOptions<PersonDo<true>[]>,
): Promise<PersonDo<true>[]> {
const pagination: IPagination = options?.pagination || {};
const order: QueryOrderMap<PersonEntity> = PersonSortingMapper.mapDOSortOrderToQueryOrder(options?.order || {});
const scope: Scope<PersonEntity> = new PersonScope()
.byFirstName(personDo.firstName)
.byLastName(personDo.lastName)
.byBirthDate(personDo.birthDate)
.allowEmptyQuery(true);

if (personDo.firstName) {
query['firstName'] = { $ilike: personDo.firstName };
if (order.id == null) {
order.firstName = SortOrder.asc;
}

if (personDo.lastName) {
query['lastName'] = { $ilike: personDo.lastName };
}
// if (personDo.firstName) {
// query['firstName'] = { $ilike: personDo.firstName };
// }

if (personDo.referrer) {
query['referrer'] = personDo.referrer;
}
const result: PersonEntity[] = await this.em.find(PersonEntity, query);
return result.map((person: PersonEntity) => this.mapper.map(person, PersonEntity, PersonDo));
// if (personDo.lastName) {
// query['lastName'] = { $ilike: personDo.lastName };
// }

// if (personDo.referrer) {
// query['referrer'] = personDo.referrer;
// }

const [entities, total]: [PersonEntity[], number] = await this.em.findAndCount(PersonEntity, scope.personDo, {
offset: pagination?.skip,
limit: pagination?.limit,
});

const entityDos: PersonDo<true>[] = entities.map((person: PersonEntity) =>
this.mapper.map(person, PersonEntity, PersonDo),
);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const page: Page<PersonDo<true>[]> = new Page<PersonDo<true>>(entityDos, total);
// return result.map((person: PersonEntity) => this.mapper.map(person, PersonEntity, PersonDo));
return page;
}
// mapEntityToDO(entity: PersonEntity): PersonDo {
// const domainObject = PersonRepo.mapEntityToDO(entity);

// return domainObject;
// }
}
34 changes: 34 additions & 0 deletions src/modules/person/persistence/person.scope.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* eslint-disable @typescript-eslint/explicit-member-accessibility */

// TODO should Refactor with german
import { Scope } from '../../../shared/repo/scope.js';
import { PersonEntity } from './person.entity.js';
export class PersonScope extends Scope<PersonEntity> {
byFirstName(firstName: string | undefined): this {
if (firstName) {
this.addQuery({ firstName: { $re: firstName } });
}
return this;
}

byLastName(lastName: string | undefined): this {
if (lastName) {
this.addQuery({ lastName: { $re: lastName } });
}
return this;
}

byBirthDate(birthDate: Date | undefined): this {
if (birthDate) {
this.addQuery({ birthDate });
}
return this;
}

/* byBirthPlace(birthPlace: string | undefined): this {
if (birthPlace) {
this.addQuery({ birthPlace });
}
return this;
} */
}
16 changes: 16 additions & 0 deletions src/shared/interface/find-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export interface IPagination {
skip?: number;
limit?: number;
}

export enum SortOrder {
asc = 'asc',
desc = 'desc',
}

export type SortOrderMap<T> = Partial<Record<keyof T, SortOrder>>;

export interface IFindOptions<T> {
pagination?: IPagination;
order?: SortOrderMap<T>;
}
10 changes: 10 additions & 0 deletions src/shared/interface/page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export class Page<T> {
public data: T[];

public total: number;

public constructor(data: T[], total: number) {
this.data = data;
this.total = total;
}
}
5 changes: 5 additions & 0 deletions src/shared/query/empty-result.query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* When this query is added ($and) to an existing query,
* it should ensure an empty result
*/
export const EmptyResultQuery = { $and: [{ id: false }] };

Check warning on line 5 in src/shared/query/empty-result.query.ts

View workflow job for this annotation

GitHub Actions / nest_lint / Nest Lint

Expected EmptyResultQuery to have a type annotation
43 changes: 43 additions & 0 deletions src/shared/repo/scope.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* eslint-disable @typescript-eslint/explicit-member-accessibility */
/* eslint-disable no-underscore-dangle */

import { FilterQuery } from '@mikro-orm/core';
import { EmptyResultQuery } from '../query/empty-result.query.js';

type EmptyResultQueryType = typeof EmptyResultQuery;

type ScopeOperator = '$and' | '$or';

export class Scope<T> {
private _queries: FilterQuery<T | EmptyResultQueryType>[] = [];

private _operator: ScopeOperator;

private _allowEmptyQuery: boolean;

constructor(operator: ScopeOperator = '$and') {
this._operator = operator;
this._allowEmptyQuery = false;
}

get query(): FilterQuery<T> {
if (this._queries.length === 0) {
if (this._allowEmptyQuery) {
return {} as FilterQuery<T>;
}
return EmptyResultQuery as FilterQuery<T>;
}
// eslint-disable-next-line @typescript-eslint/typedef
const query = this._queries.length > 1 ? { [this._operator]: this._queries } : this._queries[0];
return query as FilterQuery<T>;
}

addQuery(query: FilterQuery<T | EmptyResultQueryType>): void {
this._queries.push(query);
}

allowEmptyQuery(isEmptyQueryAllowed: boolean): Scope<T> {
this._allowEmptyQuery = isEmptyQueryAllowed;
return this;
}
}

0 comments on commit 3186680

Please sign in to comment.