feat(SPG-122) Abstraction Based Data
parent
fcfb30d4d1
commit
38e420856e
|
@ -4,9 +4,40 @@ import { AuthModule } from './auth/auth.module';
|
||||||
import { JWTGuard } from './core/guards';
|
import { JWTGuard } from './core/guards';
|
||||||
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
|
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
|
||||||
import { HttpExceptionFilter, TransformInterceptor } from './core/response';
|
import { HttpExceptionFilter, TransformInterceptor } from './core/response';
|
||||||
|
import { ApmModule } from './core/apm';
|
||||||
|
import { CONNECTION_NAME } from './core/strings/constants/base.constants';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { UserPrivilegeModule } from './modules/user-related/user-privilege/user-privilege.module';
|
||||||
|
import { UserPrivilegeModel } from './modules/user-related/user-privilege/data/model/user-privilege.model';
|
||||||
|
import { CqrsModule } from '@nestjs/cqrs';
|
||||||
|
import { CouchModule } from './modules/configuration/couch/couch.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [SessionModule, AuthModule],
|
imports: [
|
||||||
|
ConfigModule.forRoot({
|
||||||
|
isGlobal: true,
|
||||||
|
}),
|
||||||
|
TypeOrmModule.forRoot({
|
||||||
|
name: CONNECTION_NAME.DEFAULT,
|
||||||
|
type: 'postgres',
|
||||||
|
host: process.env.DEFAULT_DB_HOST,
|
||||||
|
port: parseInt(process.env.DEFAULT_DB_PORT),
|
||||||
|
username: process.env.DEFAULT_DB_USER,
|
||||||
|
password: process.env.DEFAULT_DB_PASS,
|
||||||
|
database: process.env.DEFAULT_DB_NAME,
|
||||||
|
entities: [
|
||||||
|
UserPrivilegeModel,
|
||||||
|
],
|
||||||
|
synchronize: true,
|
||||||
|
}),
|
||||||
|
CqrsModule,
|
||||||
|
SessionModule,
|
||||||
|
AuthModule,
|
||||||
|
CouchModule,
|
||||||
|
|
||||||
|
UserPrivilegeModule,
|
||||||
|
],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
providers: [
|
providers: [
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,9 +19,9 @@ export class AuthService {
|
||||||
|
|
||||||
const token = this.session.createAccessToken({
|
const token = this.session.createAccessToken({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
username: user.username,
|
// username: user.username,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
roles: user.roles,
|
// roles: user.roles,
|
||||||
});
|
});
|
||||||
|
|
||||||
return token;
|
return token;
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||||
|
import { BaseCoreEntity } from "../../domain/entities/base-core.entity";
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export abstract class BaseCoreModel<Entity> implements BaseCoreEntity {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { Column, Entity } from "typeorm";
|
||||||
|
import { BaseModel } from "./base.model";
|
||||||
|
import { STATUS } from "src/core/strings/constants/base.constants";
|
||||||
|
import { BaseStatusEntity } from "../../domain/entities/base-status.entity";
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export abstract class BaseStatusModel<Entity> extends BaseModel<Entity> implements BaseStatusEntity {
|
||||||
|
@Column('enum', { name: 'status', enum: STATUS, default: STATUS.DRAFT })
|
||||||
|
status: STATUS;
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { Column, Entity } from 'typeorm';
|
||||||
|
import { BaseCoreModel } from './base-core.model';
|
||||||
|
import { BaseEntity } from '../../domain/entities/base.entity';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export abstract class BaseModel<Entity> extends BaseCoreModel<Entity> implements BaseEntity {
|
||||||
|
@Column('varchar', { name: 'creator_id', length: 36, nullable: true })
|
||||||
|
creator_id: string;
|
||||||
|
|
||||||
|
@Column('varchar', { name: 'creator_name', length: 125, nullable: true })
|
||||||
|
creator_name: string;
|
||||||
|
|
||||||
|
@Column('varchar', { name: 'editor_id', length: 36, nullable: true })
|
||||||
|
editor_id: string;
|
||||||
|
|
||||||
|
@Column('varchar', { name: 'editor_name', length: 125, nullable: true })
|
||||||
|
editor_name: string;
|
||||||
|
|
||||||
|
@Column({ type: 'bigint', nullable: false })
|
||||||
|
created_at: number;
|
||||||
|
|
||||||
|
@Column({ type: 'bigint', nullable: false })
|
||||||
|
updated_at: number;
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { EntityTarget, FindManyOptions, QueryRunner, Repository } from "typeorm";
|
||||||
|
|
||||||
|
export abstract class BaseDataService<Entity> {
|
||||||
|
|
||||||
|
constructor(private repository: Repository<Entity>) {}
|
||||||
|
|
||||||
|
getRepository(): Repository<Entity> {
|
||||||
|
return this.repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(
|
||||||
|
queryRunner: QueryRunner,
|
||||||
|
entityTarget: EntityTarget<Entity>,
|
||||||
|
entity: Entity,
|
||||||
|
): Promise<Entity> {
|
||||||
|
const newEntity = queryRunner.manager.create(entityTarget, entity);
|
||||||
|
return await queryRunner.manager.save(newEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(
|
||||||
|
queryRunner: QueryRunner,
|
||||||
|
entityTarget: EntityTarget<Entity>,
|
||||||
|
filterUpdate: any,
|
||||||
|
entity: Entity,
|
||||||
|
): Promise<Entity> {
|
||||||
|
const newEntity = await queryRunner.manager.findOne(entityTarget, {
|
||||||
|
where: filterUpdate,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!newEntity) throw new Error('Data not found!');
|
||||||
|
Object.assign(newEntity, entity);
|
||||||
|
return await queryRunner.manager.save(newEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteById(
|
||||||
|
queryRunner: QueryRunner,
|
||||||
|
entityTarget: EntityTarget<Entity>,
|
||||||
|
id: string,
|
||||||
|
): Promise<void> {
|
||||||
|
await queryRunner.manager.delete(entityTarget, { id });
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteByOptions(
|
||||||
|
queryRunner: QueryRunner,
|
||||||
|
entityTarget: EntityTarget<Entity>,
|
||||||
|
findManyOptions: FindManyOptions<Entity>,
|
||||||
|
): Promise<void> {
|
||||||
|
await queryRunner.manager.delete(entityTarget, findManyOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOneByOptions(findOneOptions): Promise<Entity> {
|
||||||
|
return await this.repository.findOne(findOneOptions);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { FindOneOptions, Repository, SelectQueryBuilder } from "typeorm";
|
||||||
|
import { BaseFilterEntity } from "../../domain/entities/base-filter.entity";
|
||||||
|
import { PaginationResponse } from "src/core/response/domain/ok-response.interface";
|
||||||
|
|
||||||
|
export abstract class BaseReadService<Entity> {
|
||||||
|
|
||||||
|
constructor(private repository: Repository<Entity>) {}
|
||||||
|
|
||||||
|
getRepository(): Repository<Entity> {
|
||||||
|
return this.repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getIndex(
|
||||||
|
queryBuilder: SelectQueryBuilder<Entity>,
|
||||||
|
params: BaseFilterEntity,
|
||||||
|
): Promise<PaginationResponse<Entity>> {
|
||||||
|
const [data, total] = await queryBuilder
|
||||||
|
.take(+params.limit)
|
||||||
|
.skip(+params.limit * +params.page - +params.limit)
|
||||||
|
.getManyAndCount();
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
total,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOneByOptions(findOneOptions): Promise<Entity> {
|
||||||
|
return await this.repository.findOne(findOneOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOneOrFailByOptions(
|
||||||
|
findOneOptions: FindOneOptions<Entity>,
|
||||||
|
): Promise<Entity> {
|
||||||
|
return await this.repository.findOneOrFail(findOneOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getManyByOptions(findManyOptions): Promise<Entity[]> {
|
||||||
|
return await this.repository.find(findManyOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { Repository, TreeRepository } from "typeorm";
|
||||||
|
import { BaseReadService } from "./base-read.service";
|
||||||
|
|
||||||
|
export abstract class BaseTreeReadService<Entity> extends BaseReadService<Entity> {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private dataRepository: Repository<Entity>,
|
||||||
|
private treeRepository: TreeRepository<Entity>
|
||||||
|
) {
|
||||||
|
super(dataRepository);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findRoots() {
|
||||||
|
return this.treeRepository.findRoots()
|
||||||
|
}
|
||||||
|
|
||||||
|
async findDescendants(
|
||||||
|
parent,
|
||||||
|
relations = [],
|
||||||
|
): Promise<Entity[]> {
|
||||||
|
return this.treeRepository.findDescendants(parent, {
|
||||||
|
relations: relations,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findDescendantsTree(
|
||||||
|
parent,
|
||||||
|
relations = [],
|
||||||
|
): Promise<Entity> {
|
||||||
|
return this.treeRepository.findDescendantsTree(parent, {
|
||||||
|
relations: relations,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAncestors(
|
||||||
|
parent,
|
||||||
|
relations = [],
|
||||||
|
): Promise<Entity[]> {
|
||||||
|
return await this.treeRepository.findAncestors(parent, {
|
||||||
|
relations: relations,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAncestorsTree(
|
||||||
|
parent,
|
||||||
|
relations = [],
|
||||||
|
): Promise<Entity> {
|
||||||
|
return await this.treeRepository.findAncestorsTree(parent, {
|
||||||
|
relations: relations,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export interface BaseCoreEntity {
|
||||||
|
id: string;
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { BaseCoreEntity } from "../../domain/entities/base-core.entity";
|
||||||
|
|
||||||
|
export class BaseCoreDto implements BaseCoreEntity {
|
||||||
|
id: string;
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { STATUS } from "src/core/strings/constants/base.constants";
|
||||||
|
import { BaseStatusEntity } from "../../domain/entities/base-status.entity";
|
||||||
|
import { BaseDto } from "./base.dto";
|
||||||
|
|
||||||
|
export class BaseStatusDto extends BaseDto implements BaseStatusEntity {
|
||||||
|
status: STATUS;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { BaseEntity } from "../../domain/entities/base.entity";
|
||||||
|
import { BaseCoreDto } from "./base-core.dto";
|
||||||
|
|
||||||
|
export class BaseDto extends BaseCoreDto implements BaseEntity {
|
||||||
|
creator_id: string;
|
||||||
|
creator_name: string;
|
||||||
|
editor_id: string;
|
||||||
|
editor_name: string;
|
||||||
|
created_at: number;
|
||||||
|
updated_at: number;
|
||||||
|
}
|
|
@ -3,16 +3,44 @@ import {
|
||||||
Catch,
|
Catch,
|
||||||
ArgumentsHost,
|
ArgumentsHost,
|
||||||
HttpException,
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
|
|
||||||
@Catch(HttpException)
|
@Catch()
|
||||||
export class HttpExceptionFilter implements ExceptionFilter {
|
export class HttpExceptionFilter implements ExceptionFilter {
|
||||||
catch(exception: HttpException, host: ArgumentsHost) {
|
catch(exception: any, host: ArgumentsHost) {
|
||||||
const ctx = host.switchToHttp();
|
const ctx = host.switchToHttp();
|
||||||
const response = ctx.getResponse<Response>();
|
const response = ctx.getResponse<Response>();
|
||||||
const status = exception.getStatus();
|
let status: HttpStatus;
|
||||||
|
let body: any;
|
||||||
|
let exceptionResponse;
|
||||||
|
|
||||||
response.status(status).json(exception.getResponse());
|
try {
|
||||||
|
exceptionResponse = JSON.parse(exception.message);
|
||||||
|
} catch (error) {}
|
||||||
|
|
||||||
|
if (exception instanceof HttpException) {
|
||||||
|
if (Array.isArray(exception.getResponse()['message'])) {
|
||||||
|
exception.getResponse()['message'] = exception
|
||||||
|
.getResponse()
|
||||||
|
['message'].join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
status = exception.getStatus();
|
||||||
|
body = exception.getResponse();
|
||||||
|
} else if (typeof exceptionResponse == 'object') {
|
||||||
|
status = exceptionResponse.statusCode;
|
||||||
|
body = exceptionResponse;
|
||||||
|
} else {
|
||||||
|
status = HttpStatus.FORBIDDEN;
|
||||||
|
body = {
|
||||||
|
statusCode: HttpStatus.FORBIDDEN,
|
||||||
|
message: exception.message,
|
||||||
|
error: exception.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
response.status(status).json(body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
export interface UsersSession {
|
export interface UsersSession {
|
||||||
id: number;
|
id: number;
|
||||||
username: string;
|
|
||||||
name: string;
|
name: string;
|
||||||
roles: string[];
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
export enum STATUS {
|
||||||
|
ACTIVE = 'active',
|
||||||
|
CANCEL = 'cancel',
|
||||||
|
CONFIRMED = 'confirmed',
|
||||||
|
DRAFT = 'draft',
|
||||||
|
EXPIRED = 'expired',
|
||||||
|
INACTIVE = 'inactive',
|
||||||
|
PENDING = 'pending',
|
||||||
|
REFUNDED = 'refunded',
|
||||||
|
SETTLED = 'settled',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ORDER_TYPE {
|
||||||
|
ASC = 'ASC',
|
||||||
|
DESC = 'DESC',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CONNECTION_NAME {
|
||||||
|
DEFAULT = 'default',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum METHOD {
|
||||||
|
POST = 'POST',
|
||||||
|
GET = 'GET',
|
||||||
|
PUT = 'PUT',
|
||||||
|
PATCH = 'PATCH',
|
||||||
|
DELETE = 'DELETE',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum OPERATION {
|
||||||
|
CREATE = 'Create',
|
||||||
|
READ = 'Read',
|
||||||
|
UPDATE = 'Update',
|
||||||
|
UPDATE_BATCH = 'Batch Update',
|
||||||
|
DELETE = 'Delete',
|
||||||
|
DELETE_BATCH = 'Batch Delete',
|
||||||
|
SYNC = 'Sync',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BLANK_USER = {
|
||||||
|
id: null,
|
||||||
|
name: null,
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { UsersSession } from "src/core/sessions";
|
||||||
|
import { OPERATION } from "./base.constants";
|
||||||
|
|
||||||
|
export interface EventTopics {
|
||||||
|
topic: any,
|
||||||
|
data: IEvent,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IEvent<Entity = any> {
|
||||||
|
id: string;
|
||||||
|
old: null | Entity;
|
||||||
|
data: null | Entity;
|
||||||
|
user: UsersSession;
|
||||||
|
description: null | string;
|
||||||
|
module: string;
|
||||||
|
op: OPERATION;
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export enum MODULE_NAME {
|
||||||
|
USER_PRIVILEGE = 'user-privileges'
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export enum TABLE_NAME {
|
||||||
|
USER_PRIVILEGE = 'user_privileges'
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
## Error message for response
|
27
src/main.ts
27
src/main.ts
|
@ -1,8 +1,33 @@
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
|
import { DocumentBuilder, SwaggerCustomOptions, SwaggerModule } from '@nestjs/swagger';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
await app.listen(3000);
|
|
||||||
|
app.setGlobalPrefix('api/v1');
|
||||||
|
app.enableCors();
|
||||||
|
app.useGlobalPipes(
|
||||||
|
new ValidationPipe({ transform: true, forbidUnknownValues: false }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const config = new DocumentBuilder()
|
||||||
|
.setTitle('Skyworld POS')
|
||||||
|
.setDescription('POS API Documentation')
|
||||||
|
.setVersion('1.0')
|
||||||
|
.addBearerAuth(
|
||||||
|
{ type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
|
||||||
|
'JWT',
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
const options: SwaggerCustomOptions = {
|
||||||
|
swaggerOptions: { docExpansion: 'list' },
|
||||||
|
};
|
||||||
|
const document = SwaggerModule.createDocument(app, config);
|
||||||
|
SwaggerModule.setup('api/v1/pos/docs', app, document, options);
|
||||||
|
|
||||||
|
await app.listen(process.env.PORT || 3000);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|
Loading…
Reference in New Issue