fennec-be/src/commons/services/base-db.service.ts

194 lines
5.6 KiB
TypeScript

import { AppBaseEntity } from '../entities/app-base-entity';
import { Brackets, Repository, SelectQueryBuilder } from 'typeorm';
import { TypeormHelper } from './typeorm-helper';
import { UnprocessableEntityException } from '@nestjs/common';
import {
DuplicateEntityException,
DuplicateFieldInfo,
} from '../exceptions/duplicate-entity.exception';
export class BaseDbService<Entity extends AppBaseEntity> extends TypeormHelper {
constructor(protected readonly repository: Repository<Entity>) {
super();
}
readonly uniqueFields: Array<keyof Entity | Array<keyof Entity>> = [];
async isDuplicateEntity<Dto extends Entity & Record<string, any>>(
dto: Partial<Dto>,
extendsFields: Array<keyof Dto> = [],
): Promise<false | never> {
const qb = this.repository.createQueryBuilder('entity');
const compareFields = this.getCompareFields(dto, [
...this.uniqueFields,
...extendsFields,
]);
if (compareFields.length > 0) {
qb.andWhere(
new Brackets((bqb) => {
for (const key of compareFields) {
if (Array.isArray(key)) {
bqb.orWhere(
new Brackets((bbqb) => {
for (const k of key) {
bbqb.andWhere(`entity.${k} = :${k}`);
}
}),
);
} else {
bqb.orWhere(`entity.${key} = :${key}`);
}
}
}),
);
} else {
return false;
}
qb.setParameters(Object.assign({}, dto));
const uniqueFields = Array.from(new Set(compareFields.flat()));
qb.addSelect(uniqueFields.map((f) => `entity.${f} AS entity_${f}`));
return await this.checkDuplicateFields(qb, dto, uniqueFields);
}
async isDuplicateEntityForUpdate<Dto extends Entity>(
id: string,
dto: Partial<Dto>,
extendsFields?: Array<keyof Dto & string>,
): Promise<false | never>;
async isDuplicateEntityForUpdate<Dto extends Entity>(
old: Entity,
dto: Partial<Dto>,
extendsFields?: Array<keyof Dto & string>,
): Promise<false | never>;
async isDuplicateEntityForUpdate<Dto extends Entity>(
id: string | Entity,
dto: Partial<Dto>,
extendsFields: Array<keyof Dto & string> = [],
): Promise<false | never> {
if (typeof id !== 'string') {
dto = Object.assign({}, id, dto);
id = id.id;
}
const qb = this.repository.createQueryBuilder('entity');
const compareFields = this.getCompareFields(dto, [
...this.uniqueFields,
...extendsFields,
]);
const flatCompareFields = compareFields.flat();
if (compareFields.length > 0) {
qb.andWhere(
new Brackets((bqb) => {
for (const key of compareFields) {
if (Array.isArray(key)) {
if (key.length > 0) {
bqb.orWhere(
new Brackets((bbqb) =>
key.forEach((k) => {
bbqb.andWhere(`entity.${k} = :${k}`);
}),
),
);
}
} else {
bqb.orWhere(`entity.${key} = :${key}`);
}
}
}),
);
} else {
return false;
}
qb.andWhere(`entity.id <> :id`);
qb.setParameters(Object.assign({}, dto, { id }));
qb.addSelect(flatCompareFields.map((f) => `entity.${f} AS entity_${f}`));
return await this.checkDuplicateFields(qb, dto, compareFields);
}
async findOne(entity: Entity): Promise<Entity>;
async findOne(id: string): Promise<Entity>;
async findOne(idOrEntity: string | Entity): Promise<Entity> {
if (idOrEntity instanceof Object) {
return idOrEntity;
}
return await this.repository.findOneOrFail({
where: { id: idOrEntity },
});
}
checkProperty<T>(
obj: T,
field: keyof T,
whitelist: Array<typeof obj[keyof T]>,
errMsg: string,
) {
if (!whitelist.some((item) => obj[field] === item)) {
throw new UnprocessableEntityException(errMsg);
}
}
async canYouRemoveWithIds(ids: string[]): Promise<void> {
return;
}
private getCompareFields<Dto>(
dto: Partial<Dto>,
fields: Array<keyof Dto | Array<keyof Dto>>,
) {
if (!Array.isArray(fields)) {
return [];
}
const compareFields = [];
for (const field of fields as Array<keyof Dto | Array<keyof Dto>>) {
if (Array.isArray(field)) {
const tmpFields = [];
for (const f of field) {
if (dto[f] !== undefined) {
tmpFields.push(f);
}
}
compareFields.push(tmpFields);
} else if (dto[field] !== undefined) {
compareFields.push(field);
}
}
return compareFields;
}
private async checkDuplicateFields<Dto = { [p: string]: any }>(
qb: SelectQueryBuilder<Entity>,
dto: Dto,
compareFields: Array<keyof Dto & string>,
): Promise<false | never> {
const existingEntity = await qb.getOne();
if (!existingEntity) {
return false;
}
const duplicateEntityInfo: DuplicateFieldInfo[] = [];
for (const key of compareFields) {
if (existingEntity[key as string] === dto[key]) {
duplicateEntityInfo.push({
property: key,
value: dto[key],
});
}
}
throw new DuplicateEntityException(duplicateEntityInfo);
}
protected async getTotalQueryBuilder(
qb: SelectQueryBuilder<Entity>,
): Promise<SelectQueryBuilder<Entity>> {
const totalQb = qb.clone();
totalQb.offset(0).skip(0).take(undefined).limit(undefined);
return totalQb;
}
getUpdateRows(returned: any): number {
return returned?.affected;
}
isFindOne(queryArg): boolean {
return queryArg.id !== undefined;
}
}