From c92d5a8068d165390c101c834647d964487c73ff Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Sat, 31 Jan 2026 16:54:36 +0330 Subject: [PATCH] user refactor --- src/modules/auth/services/auth.service.ts | 2 +- src/modules/order/providers/order.service.ts | 25 ++- .../payment/services/payments.service.ts | 2 +- .../user/controllers/users.controller.ts | 182 +++++------------- src/modules/user/dto/create-user.dto.ts | 7 +- .../user/dto/update-user-address.dto.ts | 56 ------ src/modules/user/dto/update-user.dto.ts | 3 +- .../entities/credit-transaction.entity.ts | 4 +- src/modules/user/interface/credit.ts | 6 - src/modules/user/interface/user.ts | 15 ++ src/modules/user/providers/user.service.ts | 58 ++++-- 11 files changed, 128 insertions(+), 232 deletions(-) delete mode 100644 src/modules/user/dto/update-user-address.dto.ts delete mode 100644 src/modules/user/interface/credit.ts create mode 100644 src/modules/user/interface/user.ts diff --git a/src/modules/auth/services/auth.service.ts b/src/modules/auth/services/auth.service.ts index 7a48eb3..59adae3 100644 --- a/src/modules/auth/services/auth.service.ts +++ b/src/modules/auth/services/auth.service.ts @@ -54,7 +54,7 @@ export class AuthService { const user = await this.userService.getOrCreate({ phone: normalizedPhone, gender: true, - maxCredit: 0 + // maxCredit: 0 }); diff --git a/src/modules/order/providers/order.service.ts b/src/modules/order/providers/order.service.ts index 3bfecca..36be30a 100644 --- a/src/modules/order/providers/order.service.ts +++ b/src/modules/order/providers/order.service.ts @@ -476,27 +476,27 @@ export class OrderService { orderItem.designer = designer } - if (attributes) { + if (attributes != undefined) { orderItem.attributes = attributes } - if (printAttachments) { + if (printAttachments != undefined) { orderItem.printAttachments = printAttachments } - if (description) { + if (description != undefined) { orderItem.description = description } - if (quantity) { + if (quantity != undefined) { orderItem.quantity = quantity } - if (attachments) { + if (attachments != undefined) { orderItem.attachments = attachments } - if (discount) { + if (discount != undefined) { orderItem.discount = discount } - if (unitPrice) { + if (unitPrice != undefined) { orderItem.unitPrice = unitPrice } @@ -504,10 +504,6 @@ export class OrderService { orderItem.printAttributes = printAttributes } - if (attributes != undefined) { - orderItem.attributes = attributes - } - await this.em.flush() return orderItem @@ -526,10 +522,13 @@ export class OrderService { if (order.payments.length > 0) { throw new BadRequestException("order with payments can not be deleted!") } + await this.em.transactional(async (em) => { + await this.ticketRepository.nativeDelete({ orderItem: { order: { id: orderId } } }) await this.orderRepository.nativeDelete(order) - await this.orderItemRepository.nativeDelete({ order: { id: orderId } }) - // await this.ticketRepository.nativeDelete({ orderItemorder: { id: orderId } }) + // order item will be deleted because cascade is true + + // TODO : images also must be deleted await em.flush() }) diff --git a/src/modules/payment/services/payments.service.ts b/src/modules/payment/services/payments.service.ts index fae9f40..4df5b2e 100644 --- a/src/modules/payment/services/payments.service.ts +++ b/src/modules/payment/services/payments.service.ts @@ -13,7 +13,7 @@ import { PayOrderDto } from '../dto/pay-order.dto'; import { PaymentRepository } from '../repositories/payment.repository'; import { CreditRepository } from 'src/modules/user/repositories/credit.repository'; import { CreditService } from 'src/modules/user/providers/credit.service'; -import { CreditTransactionType } from 'src/modules/user/interface/credit'; +import { CreditTransactionType } from 'src/modules/user/interface/user'; import { Payment } from '../entities/payment.entity'; import { FindPaymentsDto } from '../dto/find-payments.dto'; import { Admin } from 'src/modules/admin/entities/admin.entity'; diff --git a/src/modules/user/controllers/users.controller.ts b/src/modules/user/controllers/users.controller.ts index bf9ff61..d5007ba 100644 --- a/src/modules/user/controllers/users.controller.ts +++ b/src/modules/user/controllers/users.controller.ts @@ -1,22 +1,17 @@ -import { Controller, Get, UseGuards, Patch, Body } from '@nestjs/common'; -import { ApiTags, ApiBearerAuth, ApiOperation, ApiBody, ApiOkResponse } from '@nestjs/swagger'; +import { Controller, Get, UseGuards, Patch, Body, Query, Post, Param, Delete } from '@nestjs/common'; +import { ApiTags, ApiBearerAuth, ApiOperation, ApiBody } from '@nestjs/swagger'; import { AuthGuard } from 'src/modules/auth/guards/auth.guard'; import { UserService } from '../providers/user.service'; -import { CreateUserDto } from '../dto/create-user.dto'; -import { UpdateUserDto } from '../dto/update-user.dto'; -import { CreateUserAddressDto } from '../dto/create-user-address.dto'; -import { UpdateUserAddressDto } from '../dto/update-user-address.dto'; -import { UserId } from 'src/common/decorators/user-id.decorator'; +import { CreateUserAsAdminDto } from '../dto/create-user.dto'; +import { UpdateUserAsAdminDto, UpdateUserDto } from '../dto/update-user.dto'; +import { UserId } from 'src/common/decorators'; import { } from 'src/common/decorators'; import { FindUsersDto } from '../dto/find-user.dto'; -import { FindWalletTransactionsDto } from '../dto/find-wallet-transactions.dto'; import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard'; -import { UserSuccessMessage } from 'src/common/enums/message.enum'; -import { Permissions } from 'src/common/decorators/permissions.decorator'; -import { Permission } from 'src/common/enums/permission.enum'; import { CreditService } from '../providers/credit.service'; @ApiTags('User') +@ApiBearerAuth() @Controller() export class UsersController { constructor( @@ -24,152 +19,63 @@ export class UsersController { private readonly walletService: CreditService, ) { } + @Get('public/users/me') @UseGuards(AuthGuard) - @ApiBearerAuth() @ApiOperation({ summary: 'Get the current authenticated user profile' }) - - @Get('public/user/me') async getUser(@UserId() userId: string) { const user = await this.userService.findById(userId); return user; } - @UseGuards(AuthGuard) - @ApiBearerAuth() - @ApiOperation({ summary: 'Update the authenticated user profile' }) + + @Patch('public/users/update') + @UseGuards(AuthGuard) + @ApiOperation({ summary: 'Update the authenticated user profile' }) @ApiBody({ type: UpdateUserDto }) - @Patch('public/user/update') async updateUser(@UserId() userId: string, @Body() dto: UpdateUserDto) { const user = await this.userService.updateUser(userId, dto); return user; } - - + // =================== Admin ===================== - // @UseGuards(AuthGuard) - // @ApiBearerAuth() - // @ApiOperation({ summary: 'Get User Wallet' }) + @Post('admin/users') + @UseGuards(AuthGuard) + @ApiOperation({ summary: 'Create user ' }) + createUser(@Body() dto: CreateUserAsAdminDto) { + return this.userService.createUserAsAdmin(dto); + } - // @Get('public/user/wallet/balance') - // async getUserWalletBalance(@UserId() userId: string,) { - // const wallet = await this.walletService.getUserCurrentWalletBalance(userId,); - // return wallet; - // } + @Post('admin/users/:id') + @UseGuards(AuthGuard) + @ApiOperation({ summary: 'find user ' }) + findUser(@Param('id') id: string) { + return this.userService.findById(id); + } - // @UseGuards(AuthGuard) - // @ApiBearerAuth() - // @ApiOperation({ summary: 'Get User Wallet' }) - // @Get('public/user/points/balance') - // async getUserWallet(@UserId() userId: string,) { - // const wallet = await this.walletService.getUserCurrentPoinrBalance(userId,); - // return wallet; - // } + @Patch('admin/users/:id') + @UseGuards(AuthGuard) + @ApiOperation({ summary: 'Update user ' }) + upateUser(@Param('id') id: string, @Body() dto: UpdateUserAsAdminDto) { + return this.userService.updateUser(id, dto); + } - // @UseGuards(AuthGuard) - // @ApiBearerAuth() - // @ApiOperation({ summary: 'Get User Wallet Transactions' }) + @Delete('admin/users/:id') + @UseGuards(AuthGuard) + @ApiOperation({ summary: 'find user ' }) + deleteUser(@Param('id') id: string) { + return this.userService.softDeleteUser(id); + } - // @Get('public/user/wallet/transactions') - // async getUserWalletTransactions( - // @UserId() userId: string, - // , - // @Query(new ValidationPipe({ transform: true, whitelist: true })) - // query: FindWalletTransactionsDto, - // ) { - // const transactions = await this.userService.getUserWalletTransactions(userId, , query); - // return transactions; - // } - // @UseGuards(AuthGuard) - // @ApiBearerAuth() - // @ApiOperation({ summary: 'Create a new address for the authenticated user' }) - - // @ApiBody({ type: CreateUserAddressDto }) - // @ApiOkResponse({ description: 'Created user address' }) - // @Post('public/user/addresses') - // async createUserAddress( - // @UserId() userId: string, - // @Body(new ValidationPipe({ transform: true, whitelist: true })) dto: CreateUserAddressDto, - // ) { - // const address = await this.userService.createUserAddress(userId, dto); - // return address; - // } - - // @UseGuards(AuthGuard) - // @ApiBearerAuth() - // @ApiOperation({ summary: 'Get a single address by ID for the authenticated user' }) - - // @ApiOkResponse({ description: 'User address details' }) - // @Get('public/user/addresses/:addressId') - // async getUserAddress(@UserId() userId: string, @Param('addressId') addressId: string) { - // const address = await this.userService.getUserAddress(userId, addressId); - // return address; - // } - - // @UseGuards(AuthGuard) - // @ApiBearerAuth() - // @ApiOperation({ summary: 'Update an address for the authenticated user' }) - - // @ApiBody({ type: UpdateUserAddressDto }) - // @ApiOkResponse({ description: 'Updated user address' }) - // @Patch('public/user/addresses/:addressId') - // async updateUserAddress( - // @UserId() userId: string, - // @Param('addressId') addressId: string, - // @Body(new ValidationPipe({ transform: true, whitelist: true })) dto: UpdateUserAddressDto, - // ) { - // const address = await this.userService.updateUserAddress(userId, addressId, dto); - // return address; - // } - - // @UseGuards(AuthGuard) - // @ApiBearerAuth() - // @ApiOperation({ summary: 'Delete an address for the authenticated user' }) - - // @ApiOkResponse({ description: 'Address deleted successfully' }) - // @Delete('public/user/addresses/:addressId') - // async deleteUserAddress(@UserId() userId: string, @Param('addressId') addressId: string) { - // await this.userService.deleteUserAddress(userId, addressId); - // return { message: UserSuccessMessage.ADDRESS_DELETED_SUCCESS }; - // } - - // @UseGuards(AuthGuard) - // @ApiBearerAuth() - // @ApiOperation({ summary: 'Set an address as default for the authenticated user' }) - - // @ApiOkResponse({ description: 'Address set as default' }) - // @Patch('public/user/addresses/:addressId/default') - // async setDefaultAddress(@UserId() userId: string, @Param('addressId') addressId: string) { - // const address = await this.userService.setDefaultAddress(userId, addressId); - // return address; - // } - - // @UseGuards(AuthGuard) - // @ApiBearerAuth() - // @ApiOperation({ summary: 'Convert user score (points) to wallet balance' }) - - // @Post('public/user/convert-score-to-wallet') - // async convertScoreToWallet(@UserId() userId: string,) { - // await this.userService.convertScoreToWallet(userId,); - // return { - // message: UserSuccessMessage.SCORE_CONVERTED_SUCCESS, - // }; - // } - - // /******************** Admin Routes **********************/ - - // @UseGuards(AdminAuthGuard) - // @ApiBearerAuth() + @Get('admin/users') + @UseGuards(AdminAuthGuard) + @ApiBearerAuth() // @Permissions(Permission.MANAGE_USERS) - // @ApiOperation({ summary: 'Get paginated list of restuarant users with filters' }) - // @Get('admin/users') - // async findAllRestaurantUsers( - // @Query(new ValidationPipe({ transform: true, whitelist: true })) - // query: FindUsersDto, - // , - // ) { - // return this.userService.findAll(, query); - // } + @ApiOperation({ summary: 'Get paginated list of restuarant users with filters' }) + async findUsers( + @Query() query: FindUsersDto) { + return this.userService.findAll(query); + } } diff --git a/src/modules/user/dto/create-user.dto.ts b/src/modules/user/dto/create-user.dto.ts index 7482e25..842c51d 100644 --- a/src/modules/user/dto/create-user.dto.ts +++ b/src/modules/user/dto/create-user.dto.ts @@ -27,8 +27,11 @@ export class CreateUserDto { @IsBoolean() gender: boolean; +} + + +export class CreateUserAsAdminDto extends CreateUserDto { @ApiProperty({ description: "User's max credit in Toman", example: 100000 }) @IsInt() maxCredit: number; - -} +} \ No newline at end of file diff --git a/src/modules/user/dto/update-user-address.dto.ts b/src/modules/user/dto/update-user-address.dto.ts deleted file mode 100644 index e82330c..0000000 --- a/src/modules/user/dto/update-user-address.dto.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { IsString, IsOptional, IsNumber, IsBoolean, Min, Max } from 'class-validator'; -import { ApiPropertyOptional } from '@nestjs/swagger'; - -/** - * DTO for updating a user address. - */ -export class UpdateUserAddressDto { - @ApiPropertyOptional({ description: 'Address title/label (e.g., "Home", "Work")', example: 'Home' }) - @IsOptional() - @IsString() - title?: string; - - @ApiPropertyOptional({ description: 'Street address', example: '123 Main Street' }) - @IsOptional() - @IsString() - address?: string; - - @ApiPropertyOptional({ description: 'City name', example: 'Tehran' }) - @IsOptional() - @IsString() - city?: string; - - @ApiPropertyOptional({ description: 'Province', example: 'Tehran Province' }) - @IsOptional() - @IsString() - province?: string; - - @ApiPropertyOptional({ description: 'Postal/ZIP code', example: '12345-6789' }) - @IsOptional() - @IsString() - postalCode?: string; - - @ApiPropertyOptional({ description: 'Latitude coordinate', example: 35.6892, minimum: -90, maximum: 90 }) - @IsOptional() - @IsNumber() - @Min(-90) - @Max(90) - latitude?: number; - - @ApiPropertyOptional({ description: 'Longitude coordinate', example: 51.389, minimum: -180, maximum: 180 }) - @IsOptional() - @IsNumber() - @Min(-180) - @Max(180) - longitude?: number; - - @ApiPropertyOptional({ description: 'Contact phone for this address', example: '+989123456789' }) - @IsOptional() - @IsString() - phone?: string; - - @ApiPropertyOptional({ description: 'Set as default address', example: false }) - @IsOptional() - @IsBoolean() - isDefault?: boolean; -} diff --git a/src/modules/user/dto/update-user.dto.ts b/src/modules/user/dto/update-user.dto.ts index 78ab602..d4cb570 100644 --- a/src/modules/user/dto/update-user.dto.ts +++ b/src/modules/user/dto/update-user.dto.ts @@ -1,4 +1,5 @@ import { PartialType } from '@nestjs/swagger'; -import { CreateUserDto } from './create-user.dto'; +import { CreateUserAsAdminDto, CreateUserDto } from './create-user.dto'; export class UpdateUserDto extends PartialType(CreateUserDto) {} +export class UpdateUserAsAdminDto extends PartialType(CreateUserAsAdminDto) {} \ No newline at end of file diff --git a/src/modules/user/entities/credit-transaction.entity.ts b/src/modules/user/entities/credit-transaction.entity.ts index 3b51714..37b2342 100644 --- a/src/modules/user/entities/credit-transaction.entity.ts +++ b/src/modules/user/entities/credit-transaction.entity.ts @@ -1,14 +1,14 @@ import { Entity, Property, ManyToOne, Index, Enum, PrimaryKey } from '@mikro-orm/core'; import { BaseEntity } from '../../../common/entities/base.entity'; import { User } from './user.entity'; -import { CreditTransactionType } from '../interface/credit'; +import { CreditTransactionType } from '../interface/user'; @Entity({ tableName: 'credit_transactions' }) @Index({ properties: ['user'] }) export class CreditTransaction extends BaseEntity { @PrimaryKey({ type: 'bigint', autoincrement: true }) id: bigint - + @ManyToOne(() => User) user!: User; diff --git a/src/modules/user/interface/credit.ts b/src/modules/user/interface/credit.ts deleted file mode 100644 index 3f07fc8..0000000 --- a/src/modules/user/interface/credit.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum CreditTransactionType { - WITHDRAW = 'withdraw', - DEPOSIT = 'deposit', -} - - diff --git a/src/modules/user/interface/user.ts b/src/modules/user/interface/user.ts new file mode 100644 index 0000000..dde7f73 --- /dev/null +++ b/src/modules/user/interface/user.ts @@ -0,0 +1,15 @@ +export enum CreditTransactionType { + WITHDRAW = 'withdraw', + DEPOSIT = 'deposit', +} + + + +export interface CreateUser { + phone: string; + firstName?: string; + lastName?: string; + address?: string; + gender: boolean; + maxCredit: number; +} \ No newline at end of file diff --git a/src/modules/user/providers/user.service.ts b/src/modules/user/providers/user.service.ts index 41c243f..a747aaa 100644 --- a/src/modules/user/providers/user.service.ts +++ b/src/modules/user/providers/user.service.ts @@ -2,12 +2,13 @@ import { Injectable, NotFoundException, BadRequestException } from '@nestjs/comm import { RequiredEntityData } from '@mikro-orm/core'; import { User } from '../entities/user.entity'; import { EntityManager } from '@mikro-orm/postgresql'; -import { CreateUserDto } from '../dto/create-user.dto'; +import { CreateUserAsAdminDto, CreateUserDto } from '../dto/create-user.dto'; import { FindUsersDto } from '../dto/find-user.dto'; import { PaginatedResult } from 'src/common/interfaces/pagination.interface'; import { UserRepository } from '../repositories/user.repository'; import { normalizePhone } from '../../util/phone.util'; import { UpdateUserDto } from '../dto/update-user.dto'; +import { CreateUser } from '../interface/user'; @Injectable() export class UserService { @@ -16,6 +17,19 @@ export class UserService { private readonly em: EntityManager, ) { } + async createUserAsAdmin(dto: CreateUserAsAdminDto): Promise { + + const exist = await this.findByPhone(dto.phone) + + if (exist) { + throw new BadRequestException("User with this phone already exist") + } + + const user = await this.createUser({ ...dto, phone: normalizePhone(dto.phone) }) + + return user; + } + async getOrCreate(dto: CreateUserDto): Promise { const normalizedPhone = normalizePhone(dto.phone) @@ -25,17 +39,7 @@ export class UserService { return currentUser } - const createData: RequiredEntityData = { - phone: normalizedPhone, - firstName: dto.firstName, - lastName: dto.lastName, - gender: dto.gender, - maxCredit: dto.maxCredit - }; - - const user = this.userRepository.create(createData); - - await this.em.persistAndFlush([user]); + const user = await this.createUser({ ...dto, phone: normalizedPhone,maxCredit:0 }) return user; } @@ -76,4 +80,34 @@ export class UserService { return user } + // ================= Core Logic =================== + + async createUser(param: CreateUser) { + + const createData: RequiredEntityData = { + phone: param.phone, + firstName: param.firstName, + lastName: param.lastName, + gender: param.gender, + maxCredit: param.maxCredit + }; + + const user = this.userRepository.create(createData); + + await this.em.persistAndFlush([user]); + + return user + } + + async softDeleteUser(userId: string) { + + const user = await this.findOrFail(userId) + + user.deletedAt = new Date() + + await this.em.flush(); + + return { message: 'success' } + } + }