From 85b4e5bb6a3164d7db177678bdce37e0a2ca4b78 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Sat, 13 Dec 2025 10:04:13 +0330 Subject: [PATCH] update --- src/modules/admin/admin.module.ts | 4 +- .../notifications/notifications.gateway.ts | 17 ++++++- .../users/controllers/users.controller.ts | 16 ++++++ .../users/dto/convert-score-to-wallet.dto.ts | 17 +++++++ src/modules/users/providers/user.service.ts | 49 ++++++++++++++++++- src/modules/users/user.module.ts | 3 +- 6 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 src/modules/users/dto/convert-score-to-wallet.dto.ts diff --git a/src/modules/admin/admin.module.ts b/src/modules/admin/admin.module.ts index 669d4ff..c474d15 100644 --- a/src/modules/admin/admin.module.ts +++ b/src/modules/admin/admin.module.ts @@ -1,4 +1,4 @@ -import { Module } from '@nestjs/common'; +import { Module, forwardRef } from '@nestjs/common'; import { AdminService } from './providers/admin.service'; import { MikroOrmModule } from '@mikro-orm/nestjs'; import { Admin } from './entities/admin.entity'; @@ -19,7 +19,7 @@ import { AdminRole } from './entities/adminRole.entity'; MikroOrmModule.forFeature([Admin, Role, Permission, RolePermission, AdminRole]), JwtModule, UtilsModule, - RestaurantsModule, + forwardRef(() => RestaurantsModule), ], exports: [AdminService, AdminRepository], }) diff --git a/src/modules/notifications/notifications.gateway.ts b/src/modules/notifications/notifications.gateway.ts index 7b9dd1a..7401e06 100644 --- a/src/modules/notifications/notifications.gateway.ts +++ b/src/modules/notifications/notifications.gateway.ts @@ -28,6 +28,7 @@ export class NotificationsGateway implements OnGatewayConnection, OnGatewayDisco server!: Server; private readonly logger = new Logger(NotificationsGateway.name); + private readonly pingIntervals = new Map(); constructor( @Inject(forwardRef(() => NotificationService)) @@ -41,6 +42,13 @@ export class NotificationsGateway implements OnGatewayConnection, OnGatewayDisco const authenticatedClient = client as AuthenticatedSocket; this.logger.log(`Admin connected: ${authenticatedClient.adminId}`); this.handleJoinRoom(authenticatedClient); + + // Start ping interval for this client + // const intervalId = setInterval(() => { + // void authenticatedClient.emit('ping', { message: 'ping', timestamp: new Date().toISOString() }); + // }, 2000); + + // this.pingIntervals.set(client.id, intervalId); } catch (error) { this.logger.error( `Connection authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`, @@ -53,6 +61,13 @@ export class NotificationsGateway implements OnGatewayConnection, OnGatewayDisco handleDisconnect(client: Socket) { this.logger.log(`Client disconnected: ${client.id}`); this.handleLeaveRoom(client); + + // Clear ping interval for this client + // const intervalId = this.pingIntervals.get(client.id); + // if (intervalId) { + // clearInterval(intervalId); + // this.pingIntervals.delete(client.id); + // } } sendInAppNotification(repipient: { adminId: string; restaurantId: string }, payload: IInAppNotificationPayload) { @@ -65,7 +80,7 @@ export class NotificationsGateway implements OnGatewayConnection, OnGatewayDisco private getRoom(adminId: string, restaurantId: string) { return `restaurant:${restaurantId}-admin:${adminId}`; } - + handleLeaveRoom(client: AuthenticatedSocket) { const room = this.getRoom(client.adminId!, client.restId!); void client.leave(room); diff --git a/src/modules/users/controllers/users.controller.ts b/src/modules/users/controllers/users.controller.ts index c689dee..b9a23ef 100644 --- a/src/modules/users/controllers/users.controller.ts +++ b/src/modules/users/controllers/users.controller.ts @@ -9,6 +9,7 @@ import { UserId } from 'src/common/decorators/user-id.decorator'; import { RestId } from 'src/common/decorators'; import { FindUsersDto } from '../dto/find-user.dto'; import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard'; +import { ConvertScoreToWalletDto } from '../dto/convert-score-to-wallet.dto'; @ApiTags('User') @Controller() @@ -117,4 +118,19 @@ export class UsersController { ) { return this.userService.findAll(restId, query); } + + @UseGuards(AuthGuard) + @ApiBearerAuth() + @ApiOperation({ summary: 'Convert user score (points) to wallet balance' }) + @ApiBody({ type: ConvertScoreToWalletDto }) + @Post('public/user/convert-score-to-wallet') + async convertScoreToWallet(@UserId() userId: string, @Body() dto: ConvertScoreToWalletDto, @RestId() restId: string) { + const user = await this.userService.convertScoreToWallet(userId, restId, dto); + return { + id: user.id, + wallet: user.wallet, + points: user.points, + message: 'Score converted to wallet successfully', + }; + } } diff --git a/src/modules/users/dto/convert-score-to-wallet.dto.ts b/src/modules/users/dto/convert-score-to-wallet.dto.ts new file mode 100644 index 0000000..2414bc0 --- /dev/null +++ b/src/modules/users/dto/convert-score-to-wallet.dto.ts @@ -0,0 +1,17 @@ +import { IsNumber, IsOptional, Min } from 'class-validator'; +import { ApiPropertyOptional } from '@nestjs/swagger'; + +/** + * DTO for converting user score (points) to wallet balance. + */ +export class ConvertScoreToWalletDto { + @ApiPropertyOptional({ + description: 'Amount of points to convert. If not provided, converts all available points', + example: 100, + minimum: 1, + }) + @IsOptional() + @IsNumber() + @Min(1) + points?: number; +} diff --git a/src/modules/users/providers/user.service.ts b/src/modules/users/providers/user.service.ts index cf205e8..5dce838 100644 --- a/src/modules/users/providers/user.service.ts +++ b/src/modules/users/providers/user.service.ts @@ -1,4 +1,4 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; +import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; import { FilterQuery, RequiredEntityData } from '@mikro-orm/core'; import { User } from '../entities/user.entity'; import { UserAddress } from '../entities/user-address.entity'; @@ -10,11 +10,14 @@ import { UpdateUserAddressDto } from '../dto/update-user-address.dto'; import { PaginatedResult } from 'src/common/interfaces/pagination.interface'; import { UserRepository } from '../repositories/user.repository'; import { normalizePhone } from '../../utils/phone.util'; +import { ConvertScoreToWalletDto } from '../dto/convert-score-to-wallet.dto'; +import { RestRepository } from 'src/modules/restaurants/repositories/rest.repository'; @Injectable() export class UserService { constructor( private readonly userRepository: UserRepository, + private readonly restaurantRepository: RestRepository, private readonly em: EntityManager, ) {} @@ -283,4 +286,48 @@ export class UserService { return address; } + + async convertScoreToWallet(userId: string, restId: string, dto: ConvertScoreToWalletDto): Promise { + const user = await this.userRepository.findOne({ id: userId }); + if (!user) { + throw new NotFoundException(`User with ID ${userId} not found.`); + } + + // Determine how many points to convert + const pointsToConvert = dto.points ?? user.points; + + // Validate points to convert + if (pointsToConvert <= 0) { + throw new BadRequestException('Points to convert must be greater than 0.'); + } + + if (user.points < pointsToConvert) { + throw new BadRequestException(`Insufficient points. Available: ${user.points}, Requested: ${pointsToConvert}.`); + } + const restaurant = await this.restaurantRepository.findOne({ id: restId }); + if (!restaurant) { + throw new NotFoundException(`Restaurant with ID ${restId} not found.`); + } + if (!restaurant.score) { + throw new BadRequestException('Restaurant score not found.'); + } + if (!restaurant.score.purchaseScore) { + throw new BadRequestException('Restaurant purchase score not found.'); + } + if (!restaurant.score.purchaseAmount) { + throw new BadRequestException('Restaurant purchase amount not found.'); + } + const walletIncreaseAmount = + (Number(pointsToConvert) * Number(restaurant.score.purchaseAmount)) / Number(restaurant.score.purchaseScore); + // Convert points to wallet (1:1 ratio - can be made configurable) + + + // Update user's wallet and points + user.wallet = (user.wallet || 0) + walletIncreaseAmount; + user.points = user.points - pointsToConvert; + + await this.em.flush(); + + return user; + } } diff --git a/src/modules/users/user.module.ts b/src/modules/users/user.module.ts index 4c9fda0..18a2721 100644 --- a/src/modules/users/user.module.ts +++ b/src/modules/users/user.module.ts @@ -6,11 +6,12 @@ import { User } from './entities/user.entity'; import { UserAddress } from './entities/user-address.entity'; import { JwtModule } from '@nestjs/jwt'; import { UserRepository } from './repositories/user.repository'; +import { RestaurantsModule } from '../restaurants/restaurants.module'; @Module({ providers: [UserService, UserRepository], controllers: [UsersController], - imports: [MikroOrmModule.forFeature([User, UserAddress]), JwtModule], + imports: [MikroOrmModule.forFeature([User, UserAddress]), JwtModule, RestaurantsModule], exports: [UserService, UserRepository], }) export class UserModule {}