update
This commit is contained in:
parent
d4dfdfc5dd
commit
ca73c44dfa
@ -2,6 +2,7 @@ CREATE TABLE "users" (
|
|||||||
"id" VARCHAR PRIMARY KEY,
|
"id" VARCHAR PRIMARY KEY,
|
||||||
"phone_number" TEXT NOT NULL UNIQUE,
|
"phone_number" TEXT NOT NULL UNIQUE,
|
||||||
"email" TEXT NOT NULL UNIQUE,
|
"email" TEXT NOT NULL UNIQUE,
|
||||||
|
"password" TEXT NOT NULL,
|
||||||
"fullname" TEXT,
|
"fullname" TEXT,
|
||||||
"avatar" TEXT,
|
"avatar" TEXT,
|
||||||
"latitude" TEXT,
|
"latitude" TEXT,
|
||||||
@ -11,6 +12,7 @@ CREATE TABLE "users" (
|
|||||||
"fcm_token" TEXT,
|
"fcm_token" TEXT,
|
||||||
"is_verified" TEXT,
|
"is_verified" TEXT,
|
||||||
"is_merchant" BOOLEAN,
|
"is_merchant" BOOLEAN,
|
||||||
|
"role_id" INT,
|
||||||
|
|
||||||
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||||
"updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
"updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||||
|
@ -32,7 +32,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/common": "^9.0.0",
|
"@nestjs/common": "^9.0.0",
|
||||||
"@nestjs/core": "^9.0.0",
|
"@nestjs/core": "^9.0.0",
|
||||||
|
"@nestjs/cqrs": "^9.0.3",
|
||||||
"@nestjs/event-emitter": "^1.4.1",
|
"@nestjs/event-emitter": "^1.4.1",
|
||||||
|
"@nestjs/microservices": "^9.3.9",
|
||||||
"@nestjs/platform-express": "^9.0.0",
|
"@nestjs/platform-express": "^9.0.0",
|
||||||
"@slonik/migrator": "^0.11.3",
|
"@slonik/migrator": "^0.11.3",
|
||||||
"cache-manager": "^5.1.6",
|
"cache-manager": "^5.1.6",
|
||||||
@ -66,13 +68,16 @@
|
|||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"jest": "29.3.1",
|
"jest": "29.3.1",
|
||||||
"prettier": "^2.3.2",
|
"prettier": "^2.3.2",
|
||||||
|
"run-script-webpack-plugin": "^0.1.1",
|
||||||
"source-map-support": "^0.5.20",
|
"source-map-support": "^0.5.20",
|
||||||
"supertest": "^6.1.3",
|
"supertest": "^6.1.3",
|
||||||
"ts-jest": "29.0.3",
|
"ts-jest": "29.0.3",
|
||||||
"ts-loader": "^9.2.3",
|
"ts-loader": "^9.2.3",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"tsconfig-paths": "4.1.1",
|
"tsconfig-paths": "4.1.1",
|
||||||
"typescript": "^4.7.4"
|
"typescript": "^4.7.4",
|
||||||
|
"webpack": "^5.75.0",
|
||||||
|
"webpack-node-externals": "^3.0.0"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"moduleFileExtensions": [
|
"moduleFileExtensions": [
|
||||||
|
@ -3,18 +3,37 @@ import { SlonikModule } from 'nestjs-slonik';
|
|||||||
import { RedisClientOptions } from 'redis';
|
import { RedisClientOptions } from 'redis';
|
||||||
import { postgresConnectionUrl } from './config/database.config';
|
import { postgresConnectionUrl } from './config/database.config';
|
||||||
import { redisConfig } from './config/redis.config';
|
import { redisConfig } from './config/redis.config';
|
||||||
import { UserModule } from './user/user.module';
|
|
||||||
import { UserModule } from './modules/user/user.module';
|
import { UserModule } from './modules/user/user.module';
|
||||||
|
import { APP_INTERCEPTOR } from '@nestjs/core';
|
||||||
|
import { ContextInterceptor } from './libs/application/context/ContextInterceptor';
|
||||||
|
import { ExceptionInterceptor } from './libs/application/interceptors/exception.interceptors';
|
||||||
|
import { RequestContextModule } from 'nestjs-request-context';
|
||||||
|
import { CqrsModule } from '@nestjs/cqrs';
|
||||||
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
|
|
||||||
|
const interceptors = [
|
||||||
|
{
|
||||||
|
provide: APP_INTERCEPTOR,
|
||||||
|
useClass: ContextInterceptor
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: APP_INTERCEPTOR,
|
||||||
|
useClass: ExceptionInterceptor
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
EventEmitterModule.forRoot(),
|
||||||
|
RequestContextModule,
|
||||||
SlonikModule.forRoot({
|
SlonikModule.forRoot({
|
||||||
connectionUri: postgresConnectionUrl,
|
connectionUri: postgresConnectionUrl,
|
||||||
}),
|
}),
|
||||||
|
CqrsModule,
|
||||||
CacheModule.register<RedisClientOptions>(redisConfig),
|
CacheModule.register<RedisClientOptions>(redisConfig),
|
||||||
UserModule
|
UserModule
|
||||||
],
|
],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
providers: [],
|
providers: [...interceptors],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
@ -22,7 +22,7 @@ export class ExceptionInterceptor implements NestInterceptor {
|
|||||||
Array.isArray(err?.response?.message) &&
|
Array.isArray(err?.response?.message) &&
|
||||||
typeof err?.response?.error === 'string' &&
|
typeof err?.response?.error === 'string' &&
|
||||||
err.status === 400;
|
err.status === 400;
|
||||||
|
|
||||||
if(isClassValidatorError) {
|
if(isClassValidatorError) {
|
||||||
err = new BadRequestException(
|
err = new BadRequestException(
|
||||||
new ApiErrorResponse({
|
new ApiErrorResponse({
|
||||||
|
@ -170,7 +170,7 @@ export abstract class SqlRepositoryBase<
|
|||||||
*/
|
*/
|
||||||
protected generateInsertQuery(
|
protected generateInsertQuery(
|
||||||
models: DbModel[],
|
models: DbModel[],
|
||||||
): SqlSqlToken<QueryResultRow> {
|
): SqlSqlToken<QueryResultRow > {
|
||||||
// TODO: generate query from an entire array to insert multiple records at once
|
// TODO: generate query from an entire array to insert multiple records at once
|
||||||
const entries = Object.entries(models[0]);
|
const entries = Object.entries(models[0]);
|
||||||
const values: any = [];
|
const values: any = [];
|
||||||
@ -186,7 +186,7 @@ export abstract class SqlRepositoryBase<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const query = sql`INSERT INTO ${sql.identifier([
|
const query = sql`INSERT INTO ${sql.identifier([
|
||||||
this.tableName,
|
this.tableName,
|
||||||
])} (${sql.join(propertyNames, sql`, `)}) VALUES (${sql.join(
|
])} (${sql.join(propertyNames, sql`, `)}) VALUES (${sql.join(
|
||||||
@ -194,6 +194,7 @@ export abstract class SqlRepositoryBase<
|
|||||||
sql`, `,
|
sql`, `,
|
||||||
)})`;
|
)})`;
|
||||||
|
|
||||||
|
|
||||||
const parsedQuery = query;
|
const parsedQuery = query;
|
||||||
return parsedQuery;
|
return parsedQuery;
|
||||||
}
|
}
|
||||||
|
13
src/main.ts
13
src/main.ts
@ -1,8 +1,21 @@
|
|||||||
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
|
declare const module: any;
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
|
|
||||||
|
app.enableShutdownHooks();
|
||||||
|
|
||||||
|
app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));
|
||||||
|
|
||||||
await app.listen(3000);
|
await app.listen(3000);
|
||||||
|
|
||||||
|
if (module.hot) {
|
||||||
|
module.hot.accept();
|
||||||
|
module.hot.dispose(() => app.close());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
18
src/modules/user/commands/create-user.command.ts
Normal file
18
src/modules/user/commands/create-user.command.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Command, CommandProps } from "@src/libs/ddd/base/command.base";
|
||||||
|
|
||||||
|
export class CreateUserCommand extends Command {
|
||||||
|
readonly email: string;
|
||||||
|
readonly password: string;
|
||||||
|
readonly fullname: string;
|
||||||
|
readonly phone_number: string;
|
||||||
|
readonly role_id: number;
|
||||||
|
|
||||||
|
constructor(props: CommandProps<CreateUserCommand>) {
|
||||||
|
super(props);
|
||||||
|
this.email = props.email;
|
||||||
|
this.password = props.password;
|
||||||
|
this.phone_number = props.phone_number;
|
||||||
|
this.fullname = props.fullname;
|
||||||
|
this.role_id = props.role_id;
|
||||||
|
}
|
||||||
|
}
|
32
src/modules/user/commands/create-user.http.controller.ts
Normal file
32
src/modules/user/commands/create-user.http.controller.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { Post, ConflictException as ConflictHttpException, Body, Controller, InternalServerErrorException } from "@nestjs/common";
|
||||||
|
import { match, Result} from 'oxide.ts';
|
||||||
|
import { routesV1 } from "@src/config/app.routes";
|
||||||
|
import { UserAlreadyExistsError } from "../domain/user.error";
|
||||||
|
import { UserResponseDto } from "../dtos/user.response.dto";
|
||||||
|
import { CreateUserCommand } from "./create-user.command";
|
||||||
|
import { CommandBus } from "@nestjs/cqrs";
|
||||||
|
import { CreateUserRequestDto } from "./create-user.request.dto";
|
||||||
|
|
||||||
|
@Controller(routesV1.version)
|
||||||
|
export class CreateUserHttpController {
|
||||||
|
constructor(private readonly commandBus: CommandBus) {}
|
||||||
|
|
||||||
|
@Post(routesV1.user.root)
|
||||||
|
async create(@Body() body: CreateUserRequestDto): Promise<UserResponseDto> {
|
||||||
|
const command = new CreateUserCommand(body);
|
||||||
|
|
||||||
|
const result: Result<UserResponseDto, UserAlreadyExistsError> =
|
||||||
|
await this.commandBus.execute(command);
|
||||||
|
|
||||||
|
console.log(result);
|
||||||
|
|
||||||
|
return match(result, {
|
||||||
|
Ok: (res: any) => res,
|
||||||
|
Err: (error: Error) => {
|
||||||
|
if(error instanceof UserAlreadyExistsError)
|
||||||
|
throw new ConflictHttpException(error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
20
src/modules/user/commands/create-user.message.controller.ts
Normal file
20
src/modules/user/commands/create-user.message.controller.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Controller } from "@nestjs/common";
|
||||||
|
import { CommandBus } from "@nestjs/cqrs";
|
||||||
|
import { MessagePattern } from "@nestjs/microservices";
|
||||||
|
import { UserResponseDto } from "../dtos/user.response.dto";
|
||||||
|
import { CreateUserCommand } from "./create-user.command";
|
||||||
|
import { CreateUserRequestDto } from "./create-user.request.dto";
|
||||||
|
|
||||||
|
@Controller()
|
||||||
|
export class CreateUserMessageController {
|
||||||
|
constructor(private readonly commandBus: CommandBus) {}
|
||||||
|
|
||||||
|
@MessagePattern('user.create')
|
||||||
|
async create(message: CreateUserRequestDto): Promise<string> {
|
||||||
|
const command = new CreateUserCommand(message);
|
||||||
|
|
||||||
|
const id: string = await this.commandBus.execute(command);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
21
src/modules/user/commands/create-user.request.dto.ts
Normal file
21
src/modules/user/commands/create-user.request.dto.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { IsEmail, IsNotEmpty, IsNumber, IsString, MaxLength, MinLength, } from "class-validator";
|
||||||
|
|
||||||
|
export class CreateUserRequestDto {
|
||||||
|
@IsEmail()
|
||||||
|
readonly email: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@MaxLength(14)
|
||||||
|
@MinLength(10)
|
||||||
|
readonly phone_number: string
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
readonly fullname: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
readonly password: string;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@IsNotEmpty()
|
||||||
|
readonly role_id: number;
|
||||||
|
}
|
46
src/modules/user/commands/create-user.service.ts
Normal file
46
src/modules/user/commands/create-user.service.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { ConflictException, Inject } from "@nestjs/common";
|
||||||
|
import { CommandHandler, ICommandHandler } from "@nestjs/cqrs";
|
||||||
|
import { AggregateID } from "@src/libs/ddd";
|
||||||
|
import { Err, Ok, Result } from "oxide.ts";
|
||||||
|
import { UserRepositoryPort } from "../database/user.repository.port";
|
||||||
|
import { UserEntity } from "../domain/user.entity";
|
||||||
|
import { UserAlreadyExistsError } from "../domain/user.error";
|
||||||
|
import { UserResponseDto } from "../dtos/user.response.dto";
|
||||||
|
import { USER_REPOSITORY } from "../user.di-tokens";
|
||||||
|
import { CreateUserCommand } from "./create-user.command";
|
||||||
|
|
||||||
|
@CommandHandler(CreateUserCommand)
|
||||||
|
export class CreateUserService implements ICommandHandler {
|
||||||
|
constructor(
|
||||||
|
@Inject(USER_REPOSITORY)
|
||||||
|
protected readonly userRepo: UserRepositoryPort
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(
|
||||||
|
command: CreateUserCommand
|
||||||
|
): Promise<Result<UserResponseDto, UserAlreadyExistsError>> {
|
||||||
|
const user = UserEntity.create({
|
||||||
|
email: command.email,
|
||||||
|
password: command.password,
|
||||||
|
fullname: command.fullname,
|
||||||
|
phone_number: command.phone_number,
|
||||||
|
role_id: command.role_id
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.userRepo.transaction(async () => this.userRepo.insert(user));
|
||||||
|
|
||||||
|
const userResponse = new UserResponseDto(user);
|
||||||
|
userResponse.email = command.email;
|
||||||
|
userResponse.fullname = command.fullname;
|
||||||
|
userResponse.phone_number = command.phone_number;
|
||||||
|
|
||||||
|
return Ok(userResponse)
|
||||||
|
} catch (error: any) {
|
||||||
|
if(error instanceof ConflictException) {
|
||||||
|
return Err(new UserAlreadyExistsError(error));
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
|
|||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { SqlRepositoryBase } from '@src/libs/db/sql-repository.base';
|
import { SqlRepositoryBase } from '@src/libs/db/sql-repository.base';
|
||||||
import { InjectPool } from 'nestjs-slonik';
|
import { InjectPool } from 'nestjs-slonik';
|
||||||
import { DatabasePool } from 'slonik';
|
import { DatabasePool, sql } from 'slonik';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { UserEntity } from '../domain/user.entity';
|
import { UserEntity } from '../domain/user.entity';
|
||||||
import { UserMapper } from '../user.mapper';
|
import { UserMapper } from '../user.mapper';
|
||||||
@ -12,15 +12,17 @@ export const userSchema = z.object({
|
|||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
phone_number: z.string().min(10).max(15),
|
phone_number: z.string().min(10).max(15),
|
||||||
|
password: z.string(),
|
||||||
fullname: z.string(),
|
fullname: z.string(),
|
||||||
avatar: z.string(),
|
avatar: z.string().optional(),
|
||||||
latitude: z.string(),
|
latitude: z.string().optional(),
|
||||||
longitude: z.string(),
|
longitude: z.string().optional(),
|
||||||
auth_token: z.string(),
|
auth_token: z.string().optional(),
|
||||||
verify_token: z.string(),
|
verify_token: z.string().optional(),
|
||||||
fcm_token: z.string(),
|
fcm_token: z.string().optional(),
|
||||||
is_verified: z.string(),
|
is_verified: z.string().optional(),
|
||||||
is_merchant: z.string(),
|
is_merchant: z.string().optional(),
|
||||||
|
role_id: z.number(),
|
||||||
created_at: z.preprocess((val: any) => new Date(val), z.date()),
|
created_at: z.preprocess((val: any) => new Date(val), z.date()),
|
||||||
updated_at: z.preprocess((val: any) => new Date(val), z.date()),
|
updated_at: z.preprocess((val: any) => new Date(val), z.date()),
|
||||||
});
|
});
|
||||||
@ -36,10 +38,8 @@ export class UserRepository
|
|||||||
extends SqlRepositoryBase<UserEntity, UserModel>
|
extends SqlRepositoryBase<UserEntity, UserModel>
|
||||||
implements UserRepositoryPort
|
implements UserRepositoryPort
|
||||||
{
|
{
|
||||||
findOneByEmail(email: string): Promise<UserEntity> {
|
protected tableName = 'users';
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
protected tableName: 'users';
|
|
||||||
protected schema = userSchema;
|
protected schema = userSchema;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -49,10 +49,14 @@ export class UserRepository
|
|||||||
eventEmitter: EventEmitter2
|
eventEmitter: EventEmitter2
|
||||||
) {
|
) {
|
||||||
super(pool, mapper, eventEmitter, new Logger(UserRepository.name));
|
super(pool, mapper, eventEmitter, new Logger(UserRepository.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateAddress(user: UserEntity): Promise<void> {
|
async findOneByEmail(email: string): Promise<UserEntity> {
|
||||||
const address = user.getPropsCopy().address;
|
const user = await this.pool.one(
|
||||||
|
sql.type(userSchema)`SELECT * FROM "users" WHERE email = ${email}`
|
||||||
|
)
|
||||||
|
|
||||||
|
return this.mapper.toDomain(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -5,6 +5,7 @@ export class UserCreatedDomainEvent extends DomainEvent {
|
|||||||
readonly phone_number: string;
|
readonly phone_number: string;
|
||||||
readonly password: string;
|
readonly password: string;
|
||||||
readonly fullname: string;
|
readonly fullname: string;
|
||||||
|
readonly role_id: number;
|
||||||
|
|
||||||
constructor(props: DomainEventProps<UserCreatedDomainEvent>) {
|
constructor(props: DomainEventProps<UserCreatedDomainEvent>) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -12,5 +13,6 @@ export class UserCreatedDomainEvent extends DomainEvent {
|
|||||||
this.phone_number = props.phone_number;
|
this.phone_number = props.phone_number;
|
||||||
this.password = props.password;
|
this.password = props.password;
|
||||||
this.fullname = props.fullname;
|
this.fullname = props.fullname;
|
||||||
|
this.role_id = props.role_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,7 +9,7 @@ export class UserEntity extends AggregateRoot<UserProps> {
|
|||||||
|
|
||||||
static create(create: CreateUserProps): UserEntity {
|
static create(create: CreateUserProps): UserEntity {
|
||||||
const id = v4();
|
const id = v4();
|
||||||
const props: UserProps = { ...create, role: UserRoles.user };
|
const props: UserProps = { ...create };
|
||||||
const user = new UserEntity({ id, props });
|
const user = new UserEntity({ id, props });
|
||||||
user.addEvent(
|
user.addEvent(
|
||||||
new UserCreatedDomainEvent({
|
new UserCreatedDomainEvent({
|
||||||
@ -17,14 +17,15 @@ export class UserEntity extends AggregateRoot<UserProps> {
|
|||||||
email: props.email,
|
email: props.email,
|
||||||
password: props.password,
|
password: props.password,
|
||||||
phone_number: props.password,
|
phone_number: props.password,
|
||||||
fullname: props.fullname
|
fullname: props.fullname,
|
||||||
|
role_id: props.role_id
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
get role(): UserRoles {
|
get role(): UserRoles {
|
||||||
return this.props.role;
|
return this.props.role_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(): void {
|
delete(): void {
|
||||||
@ -36,6 +37,6 @@ export class UserEntity extends AggregateRoot<UserProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public validate(): void {
|
public validate(): void {
|
||||||
throw new Error("Method not implemented.");
|
// throw new Error("Method not implemented.");
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
import { ExceptionBase } from "@src/libs/exceptions";
|
||||||
|
|
||||||
|
export class UserAlreadyExistsError extends ExceptionBase {
|
||||||
|
static readonly message = 'User already exists';
|
||||||
|
|
||||||
|
public readonly code = 'USER.ALREADY_EXISTS';
|
||||||
|
|
||||||
|
constructor(cause?: Error, metadata?: unknown) {
|
||||||
|
super(UserAlreadyExistsError.message, cause, metadata);
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
export interface UserProps {
|
export interface UserProps {
|
||||||
password: string;
|
password: string;
|
||||||
phone_number: string;
|
phone_number: string;
|
||||||
role: UserRoles;
|
role_id: UserRoles;
|
||||||
email: string;
|
email: string;
|
||||||
fullname: string;
|
fullname: string;
|
||||||
}
|
}
|
||||||
@ -11,6 +11,7 @@ export interface CreateUserProps {
|
|||||||
phone_number: string;
|
phone_number: string;
|
||||||
password: string;
|
password: string;
|
||||||
fullname: string;
|
fullname: string;
|
||||||
|
role_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateUserProps {
|
export interface UpdateUserProps {
|
||||||
@ -20,6 +21,6 @@ export interface UpdateUserProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum UserRoles {
|
export enum UserRoles {
|
||||||
admin = 'admin',
|
admin = 1,
|
||||||
user = 'user'
|
user = 2
|
||||||
}
|
}
|
6
src/modules/user/dtos/user.paginated.response.dto.ts
Normal file
6
src/modules/user/dtos/user.paginated.response.dto.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { PaginatedResponseDto } from "@src/libs/api/paginated.response.base";
|
||||||
|
import { UserResponseDto } from "./user.response.dto";
|
||||||
|
|
||||||
|
export class UserPaginatedResponseDto extends PaginatedResponseDto<UserResponseDto> {
|
||||||
|
readonly data: readonly UserResponseDto[];
|
||||||
|
}
|
7
src/modules/user/dtos/user.response.dto.ts
Normal file
7
src/modules/user/dtos/user.response.dto.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { ResponseBase } from "@src/libs/api/response.base";
|
||||||
|
|
||||||
|
export class UserResponseDto extends ResponseBase {
|
||||||
|
email: string;
|
||||||
|
phone_number: string;
|
||||||
|
fullname: string;
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export const USER_REPOSITORY = Symbol('USER_REPOSITORY');
|
@ -1,5 +1,50 @@
|
|||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
import { Mapper } from "@src/libs/ddd";
|
import { Mapper } from "@src/libs/ddd";
|
||||||
|
import { UserModel, userSchema } from "./database/user.repository";
|
||||||
|
import { UserEntity } from "./domain/user.entity";
|
||||||
|
import { UserResponseDto } from "./dtos/user.response.dto";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserMapper implements Mapper<
|
export class UserMapper implements Mapper<UserEntity, UserModel, UserResponseDto> {
|
||||||
|
toPersistence(entity: UserEntity): UserModel {
|
||||||
|
const copy = entity.getPropsCopy();
|
||||||
|
|
||||||
|
const record: UserModel = {
|
||||||
|
id: copy.id,
|
||||||
|
email: copy.email,
|
||||||
|
phone_number: copy.phone_number,
|
||||||
|
password: copy.password,
|
||||||
|
role_id: copy.role_id,
|
||||||
|
fullname: copy.fullname,
|
||||||
|
created_at: copy.createdAt,
|
||||||
|
updated_at: copy.updatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
return userSchema.parse(record)
|
||||||
|
}
|
||||||
|
|
||||||
|
toDomain(record: UserModel): UserEntity {
|
||||||
|
const entity = new UserEntity({
|
||||||
|
id: record.id,
|
||||||
|
props: {
|
||||||
|
email: record.email,
|
||||||
|
fullname: record.fullname,
|
||||||
|
password: record.password,
|
||||||
|
phone_number: record.phone_number,
|
||||||
|
role_id: record.role_id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
toResponse(entity: UserEntity): UserResponseDto {
|
||||||
|
const props = entity.getPropsCopy();
|
||||||
|
const response = new UserResponseDto(entity);
|
||||||
|
response.email = props.email;
|
||||||
|
response.fullname = props.fullname;
|
||||||
|
response.phone_number = props.phone_number;
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,32 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Logger, Module, Provider } from '@nestjs/common';
|
||||||
|
import { CqrsModule } from '@nestjs/cqrs';
|
||||||
|
import { CreateUserHttpController } from './commands/create-user.http.controller';
|
||||||
|
import { CreateUserMessageController } from './commands/create-user.message.controller';
|
||||||
|
import { CreateUserService } from './commands/create-user.service';
|
||||||
|
import { UserRepository } from './database/user.repository';
|
||||||
|
import { USER_REPOSITORY } from './user.di-tokens';
|
||||||
|
import { UserMapper } from './user.mapper';
|
||||||
|
|
||||||
@Module({})
|
|
||||||
|
const httpControllers = [
|
||||||
|
CreateUserHttpController
|
||||||
|
];
|
||||||
|
|
||||||
|
const messageControllers = [CreateUserMessageController];
|
||||||
|
const commandHandlers: Provider[] = [CreateUserService];
|
||||||
|
const mappers: Provider[] = [UserMapper];
|
||||||
|
const repositories: Provider[] = [
|
||||||
|
{ provide: USER_REPOSITORY, useClass: UserRepository}
|
||||||
|
];
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [CqrsModule],
|
||||||
|
controllers: [...httpControllers, ...messageControllers],
|
||||||
|
providers: [
|
||||||
|
Logger,
|
||||||
|
...repositories,
|
||||||
|
...mappers,
|
||||||
|
...commandHandlers
|
||||||
|
],
|
||||||
|
})
|
||||||
export class UserModule {}
|
export class UserModule {}
|
||||||
|
29
yarn.lock
29
yarn.lock
@ -724,6 +724,13 @@
|
|||||||
path-to-regexp "3.2.0"
|
path-to-regexp "3.2.0"
|
||||||
tslib "2.5.0"
|
tslib "2.5.0"
|
||||||
|
|
||||||
|
"@nestjs/cqrs@^9.0.3":
|
||||||
|
version "9.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@nestjs/cqrs/-/cqrs-9.0.3.tgz#1ccd87feffebf33b2f3b0170f98eda8c6c74796f"
|
||||||
|
integrity sha512-hmbrqf51BVdgmnnxErnLVXfPNTEqr4Hz8DyLa9dKLIW3BuOyI5RDwJ/9sKbJ47UDBhumC5nQlNK9qk27mhqHfw==
|
||||||
|
dependencies:
|
||||||
|
uuid "9.0.0"
|
||||||
|
|
||||||
"@nestjs/event-emitter@^1.4.1":
|
"@nestjs/event-emitter@^1.4.1":
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/@nestjs/event-emitter/-/event-emitter-1.4.1.tgz#a96fae678b0257b9cd8f6c82c9f3f82a0690e221"
|
resolved "https://registry.yarnpkg.com/@nestjs/event-emitter/-/event-emitter-1.4.1.tgz#a96fae678b0257b9cd8f6c82c9f3f82a0690e221"
|
||||||
@ -731,6 +738,14 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
eventemitter2 "6.4.9"
|
eventemitter2 "6.4.9"
|
||||||
|
|
||||||
|
"@nestjs/microservices@^9.3.9":
|
||||||
|
version "9.3.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/@nestjs/microservices/-/microservices-9.3.9.tgz#dbcecd5c3903ee6433be303eec0415ff9e60fb81"
|
||||||
|
integrity sha512-G4EsQpOS3l2dWjJID+z/YyDPTx+SEq/5YQ/cC8XV9Hap1S0rEmo+Z1R2OdlUt+ZnkcJp7H0GIHiB0EjaYanmjA==
|
||||||
|
dependencies:
|
||||||
|
iterare "1.2.1"
|
||||||
|
tslib "2.5.0"
|
||||||
|
|
||||||
"@nestjs/platform-express@^9.0.0":
|
"@nestjs/platform-express@^9.0.0":
|
||||||
version "9.3.9"
|
version "9.3.9"
|
||||||
resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-9.3.9.tgz#557ace8589b54d4ee7bad87a1247a521058395d7"
|
resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-9.3.9.tgz#557ace8589b54d4ee7bad87a1247a521058395d7"
|
||||||
@ -4571,6 +4586,11 @@ run-parallel@^1.1.9:
|
|||||||
dependencies:
|
dependencies:
|
||||||
queue-microtask "^1.2.2"
|
queue-microtask "^1.2.2"
|
||||||
|
|
||||||
|
run-script-webpack-plugin@^0.1.1:
|
||||||
|
version "0.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/run-script-webpack-plugin/-/run-script-webpack-plugin-0.1.1.tgz#dad3114be32eb864d2160306e4d9c52a2c1cfd59"
|
||||||
|
integrity sha512-PrxBRLv1K9itDKMlootSCyGhdTU+KbKGJ2wF6/k0eyo6M0YGPC58HYbS/J/QsDiwM0t7G99WcuCqto0J7omOXA==
|
||||||
|
|
||||||
rxjs@6.6.7, rxjs@^6.6.0:
|
rxjs@6.6.7, rxjs@^6.6.0:
|
||||||
version "6.6.7"
|
version "6.6.7"
|
||||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
|
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
|
||||||
@ -5245,6 +5265,11 @@ uuid@8.3.2, uuid@^8.3.2:
|
|||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||||
|
|
||||||
|
uuid@9.0.0:
|
||||||
|
version "9.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
|
||||||
|
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
|
||||||
|
|
||||||
v8-compile-cache-lib@^3.0.1:
|
v8-compile-cache-lib@^3.0.1:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
|
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
|
||||||
@ -5301,7 +5326,7 @@ webidl-conversions@^4.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
|
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
|
||||||
integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
|
integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
|
||||||
|
|
||||||
webpack-node-externals@3.0.0:
|
webpack-node-externals@3.0.0, webpack-node-externals@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz#1a3407c158d547a9feb4229a9e3385b7b60c9917"
|
resolved "https://registry.yarnpkg.com/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz#1a3407c158d547a9feb4229a9e3385b7b60c9917"
|
||||||
integrity sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==
|
integrity sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==
|
||||||
@ -5311,7 +5336,7 @@ webpack-sources@^3.2.3:
|
|||||||
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
|
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
|
||||||
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
|
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
|
||||||
|
|
||||||
webpack@5.75.0:
|
webpack@5.75.0, webpack@^5.75.0:
|
||||||
version "5.75.0"
|
version "5.75.0"
|
||||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.75.0.tgz#1e440468647b2505860e94c9ff3e44d5b582c152"
|
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.75.0.tgz#1e440468647b2505860e94c9ff3e44d5b582c152"
|
||||||
integrity sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==
|
integrity sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==
|
||||||
|
Loading…
Reference in New Issue
Block a user