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 { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
|
||||
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({
|
||||
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: [],
|
||||
providers: [
|
||||
/**
|
||||
|
|
|
@ -19,9 +19,9 @@ export class AuthService {
|
|||
|
||||
const token = this.session.createAccessToken({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
// username: user.username,
|
||||
name: user.name,
|
||||
roles: user.roles,
|
||||
// roles: user.roles,
|
||||
});
|
||||
|
||||
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,
|
||||
ArgumentsHost,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { Response } from 'express';
|
||||
|
||||
@Catch(HttpException)
|
||||
@Catch()
|
||||
export class HttpExceptionFilter implements ExceptionFilter {
|
||||
catch(exception: HttpException, host: ArgumentsHost) {
|
||||
catch(exception: any, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
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 {
|
||||
id: number;
|
||||
username: 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 { AppModule } from './app.module';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { DocumentBuilder, SwaggerCustomOptions, SwaggerModule } from '@nestjs/swagger';
|
||||
|
||||
async function bootstrap() {
|
||||
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();
|
||||
|
|
Loading…
Reference in New Issue