From 4b4b6a1f63401101ae2a0574708345b5b7b48f7f Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Sun, 8 Mar 2026 12:25:27 +0330 Subject: [PATCH] watcher --- src/modules/users/DTO/create-watcher.dto.ts | 45 +++++++++++++ src/modules/users/services/users.service.ts | 70 ++++++++++++++++++++- src/modules/users/users.controller.ts | 34 ++++++++-- src/modules/users/users.module.ts | 5 +- 4 files changed, 147 insertions(+), 7 deletions(-) create mode 100755 src/modules/users/DTO/create-watcher.dto.ts diff --git a/src/modules/users/DTO/create-watcher.dto.ts b/src/modules/users/DTO/create-watcher.dto.ts new file mode 100755 index 0000000..5544ec8 --- /dev/null +++ b/src/modules/users/DTO/create-watcher.dto.ts @@ -0,0 +1,45 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsNotEmpty, IsNumberString, IsString, Length, MinLength } from "class-validator"; +import { IsMobilePhone } from "class-validator"; + +import { IsNationalCode } from "../../../common/decorators/is-national-code.decorator"; +import { AuthMessage } from "../../../common/enums/message.enum"; + +export class CreateWatcherDto { + @IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY }) + @IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT }) + @Length(11, 11, { message: AuthMessage.PHONE_SHOULD_BE_11_DIGIT }) + @ApiProperty({ description: "phone number", default: "09922320740" }) + phone: string; + + + @IsNotEmpty({ message: AuthMessage.FIRST_NAME_NOT_EMPTY }) + @IsString({ message: AuthMessage.FIRST_NAME_NOT_EMPTY }) + @Length(2, 50, { message: AuthMessage.FIRST_NAME_SHOULD_BE_BETWEEN_2_AND_50 }) + @ApiProperty({ description: "First name", example: "mahyar" }) + firstName: string; + + @IsNotEmpty({ message: AuthMessage.LAST_NAME_NOT_EMPTY }) + @IsString({ message: AuthMessage.LAST_NAME_NOT_EMPTY }) + @Length(2, 50, { message: AuthMessage.LAST_NAME_SHOULD_BE_BETWEEN_2_AND_50 }) + @ApiProperty({ description: "Last name", example: "jamshidi" }) + lastName: string; + + @IsNotEmpty({ message: AuthMessage.BIRTH_DATE_NOT_EMPTY }) + @IsString({ message: AuthMessage.BIRTH_DATE_NOT_EMPTY }) + @ApiProperty({ description: "Birth date", example: "1403/01/01" }) + birthDate: string; + + @IsNotEmpty({ message: AuthMessage.NATIONAL_NOT_EMPTY }) + @IsNumberString({ no_symbols: true }, { message: AuthMessage.NATIONAL_CODE_INCORRECT }) + @Length(10, 10, { message: AuthMessage.NATIONAL_CODE_INCORRECT }) + @IsNationalCode({ message: AuthMessage.NATIONAL_CODE_INVALID }) + @ApiProperty({ description: "iranian format (10 char)", example: "4569852169" }) + nationalCode: string; + + @IsNotEmpty({ message: AuthMessage.PASSWORD_NOT_EMPTY }) + @IsString({ message: AuthMessage.PASSWORD_FORMAT_INVALID }) + @ApiProperty({ description: "password", example: "12S345SS678" }) + @MinLength(8, { message: AuthMessage.PASSWORD_LENGTH }) + password: string; +} diff --git a/src/modules/users/services/users.service.ts b/src/modules/users/services/users.service.ts index 6606018..c939fc7 100644 --- a/src/modules/users/services/users.service.ts +++ b/src/modules/users/services/users.service.ts @@ -10,12 +10,15 @@ import { Role } from "../entities/role.entity"; import { User } from "../entities/user.entity"; import { RoleEnum } from "../enums/role.enum"; import { UserRepository } from "../repositories/user.repository"; +import { CreateWatcherDto } from "../DTO/create-watcher.dto"; +import { PasswordService } from "../../utils/providers/password.service"; @Injectable() export class UsersService { constructor( private readonly userRepository: UserRepository, + private readonly passwordService: PasswordService, private readonly em: EntityManager, - ) {} + ) { } /*******************************/ @@ -135,4 +138,69 @@ export class UsersService { return user; } + + /*******************************/ + async createWatcher(dto: CreateWatcherDto, business: Business) { + const em = this.em.fork(); + try { + await em.begin(); + + const hashedPassword = await this.passwordService.hashPassword(dto.password); + + const [role, existPhone, existUser] = await Promise.all([ + em.findOne(Role, { name: RoleEnum.WATCHER }), + em.findOne(User, { phone: dto.phone, business: { id: business.id } }), + em.findOne(User, { nationalCode: dto.nationalCode, business: { id: business.id } }), + ]); + + if (!role) throw new BadRequestException(UserMessage.ROLE_NOT_FOUND); + if (existPhone) throw new BadRequestException(UserMessage.PHONE_EXIST); + if (existUser) throw new BadRequestException(UserMessage.NATIONAL_CODE_EXIST); + + + const userName = slugify(`${dto.firstName} ${Date.now().toString().slice(-5)}`, { lower: true, trim: true }); + + const watcher = em.create(User, { + ...dto, + userName, + password: hashedPassword, + role, + business, + }); + + await em.persistAndFlush(watcher); + + await em.commit(); + + return watcher; + + + } catch (error) { + await em.rollback(); + throw error; + } + } + + /*******************************/ + async getWatchers(business: Business) { + return this.userRepository.find({ business: { id: business.id } }) + } + + /*******************************/ + async getWatcherById(id: string, business: Business) { + return this.userRepository.find({ id, business: { id: business.id } }) + } + + /*******************************/ + async deleteWatcherById(id: string, business: Business) { + const watcher = await this.userRepository.findOne({ id, business: { id: business.id } }) + if (!watcher) { + throw new BadRequestException("Not found") + } + watcher.deletedAt = new Date() + + await this.em.flush() + + return watcher + } } diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts index 52865fc..8149724 100644 --- a/src/modules/users/users.controller.ts +++ b/src/modules/users/users.controller.ts @@ -1,21 +1,47 @@ -import { Controller, Get } from "@nestjs/common"; +import { Body, Controller, Delete, Get, Param, Post } from "@nestjs/common"; import { ApiOperation } from "@nestjs/swagger"; import { UsersService } from "./services/users.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; -// import { BusinessDec } from "../../common/decorators/business.decorator"; import { UserDec } from "../../common/decorators/user.decorator"; -// import { BusinessInterceptor } from "../../core/interceptors/business.interceptor"; +import { CreateWatcherDto } from "./DTO/create-watcher.dto"; +import { BusinessDec } from "../../common/decorators/business.decorator"; +import { Business } from "../businesses/entities/business.entity"; @Controller("users") @AuthGuards() // @UseInterceptors(BusinessInterceptor) export class UsersController { - constructor(private readonly usersService: UsersService) {} + constructor(private readonly usersService: UsersService) { } @ApiOperation({ summary: "Get user profile" }) @Get("me") getMe(@UserDec("id") userId: string) { return this.usersService.getMe(userId); } + + @ApiOperation({ summary: "Create Watcher" }) + @Post("watcher") + createWatcher(@Body() dto: CreateWatcherDto, @BusinessDec() business: Business) { + return this.usersService.createWatcher(dto, business); + } + + @ApiOperation({ summary: "Get Watchers" }) + @Get("watcher") + getWatcherList(@BusinessDec() business: Business) { + return this.usersService.getWatchers(business); + } + + @ApiOperation({ summary: "Get Watcher by id" }) + @Get("watcher/:id") + getWatcher(@Param("id") watcherId: string, @BusinessDec() business: Business) { + return this.usersService.getWatcherById(watcherId, business); + } + + @ApiOperation({ summary: "delete Watcher by id" }) + @Delete("watcher/:id") + deleteWatcher(@Param("id") watcherId: string, @BusinessDec() business: Business) { + return this.usersService.getWatcherById(watcherId, business); + } + } diff --git a/src/modules/users/users.module.ts b/src/modules/users/users.module.ts index 15b2beb..d9343dc 100644 --- a/src/modules/users/users.module.ts +++ b/src/modules/users/users.module.ts @@ -6,11 +6,12 @@ import { User } from "./entities/user.entity"; import { UsersService } from "./services/users.service"; import { UsersController } from "./users.controller"; import { BusinessesModule } from "../businesses/businesses.module"; +import { UtilsModule } from "../utils/utils.module"; @Module({ - imports: [MikroOrmModule.forFeature([User, Role]), BusinessesModule], + imports: [MikroOrmModule.forFeature([User, Role]), BusinessesModule, UtilsModule], controllers: [UsersController], providers: [UsersService], exports: [UsersService], }) -export class UsersModule {} +export class UsersModule { }