From d4db6726727d4cdb5adb90793e6e47d7b6899fad Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Tue, 11 Nov 2025 11:29:39 +0330 Subject: [PATCH] update --- src/common/enums/message.enum.ts | 2 +- src/modules/auth/dto/verify-otp.dto copy.ts | 5 +++ src/modules/auth/guards/auth.guard.ts | 3 +- src/modules/auth/interfaces/IToken-payload.ts | 10 +---- src/modules/auth/services/auth.service.ts | 19 ++++++--- src/modules/auth/services/tokens.service.ts | 10 ++++- .../repositories/user.repository.ts | 6 +++ .../users/repositories/user.repository.ts | 39 +++++++++++++++++++ src/modules/users/user.controller.ts | 2 +- 9 files changed, 76 insertions(+), 20 deletions(-) create mode 100644 src/modules/restaurants/repositories/user.repository.ts create mode 100644 src/modules/users/repositories/user.repository.ts diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index ebde49d..75f0b39 100755 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -384,7 +384,7 @@ export const enum SmsMessage { SEND_SMS_ERROR = 'خطا در ارسال پیامک', } -export const enum BusinessMessage { +export const enum RestMessage { NOT_FOUND = 'کسب و کار یافت نشد', BUSINESS_ID_REQUIRED = 'شناسه کسب و کار مورد نیاز است', SLUG_REQUIRED = 'اسلاگ مورد نیاز است', diff --git a/src/modules/auth/dto/verify-otp.dto copy.ts b/src/modules/auth/dto/verify-otp.dto copy.ts index 6dd32a2..3cc7995 100644 --- a/src/modules/auth/dto/verify-otp.dto copy.ts +++ b/src/modules/auth/dto/verify-otp.dto copy.ts @@ -16,4 +16,9 @@ export class VerifyOtpDto { @ApiProperty({ example: '12345', description: 'Otp' }) @Length(5) otp: string; + + @IsNotEmpty() + @IsString() + @ApiProperty({ example: '', description: 'Restuarant slug' }) + slug: string; } diff --git a/src/modules/auth/guards/auth.guard.ts b/src/modules/auth/guards/auth.guard.ts index 3f6ebef..4397f42 100644 --- a/src/modules/auth/guards/auth.guard.ts +++ b/src/modules/auth/guards/auth.guard.ts @@ -11,6 +11,7 @@ export enum AuthEntityType { export interface AuthRequest extends Request { user: { sub: string; // userId + slug: string; }; } export interface JwtPayload { @@ -42,8 +43,6 @@ export class AuthGuard implements CanActivate { if (payload.type !== AuthEntityType.USER) { throw new UnauthorizedException('Access denied. User rights required.'); } - // 💡 We're assigning the payload to the request object here - // so that we can access it in our route handlers request['user'] = payload; } catch { throw new UnauthorizedException(); diff --git a/src/modules/auth/interfaces/IToken-payload.ts b/src/modules/auth/interfaces/IToken-payload.ts index 223bd5e..bbd7b1b 100755 --- a/src/modules/auth/interfaces/IToken-payload.ts +++ b/src/modules/auth/interfaces/IToken-payload.ts @@ -1,10 +1,4 @@ -export interface ILocalTokenPayload { +export interface ITokenPayload { id: string; + restId: string; } - -export interface IConsoleTokenPayload { - id: string; - slug: string; -} - -export interface ITokenPayload extends ILocalTokenPayload, IConsoleTokenPayload {} diff --git a/src/modules/auth/services/auth.service.ts b/src/modules/auth/services/auth.service.ts index 39cc11f..5472ea1 100644 --- a/src/modules/auth/services/auth.service.ts +++ b/src/modules/auth/services/auth.service.ts @@ -8,6 +8,9 @@ import { JwtService } from '@nestjs/jwt'; import { AdminService } from '../../admin/admin.service'; import { AuthEntityType } from 'src/modules/auth/guards/auth.guard'; import { TokensService } from './tokens.service'; +import { RestaurantsService } from 'src/modules/restaurants/restaurants.service'; +import { RestRepository } from 'src/modules/restaurants/repositories/user.repository'; +import { RestMessage } from 'src/common/enums/message.enum'; @Injectable() export class AuthService { @@ -18,13 +21,14 @@ export class AuthService { private readonly jwtService: JwtService, private readonly adminService: AdminService, private readonly tokensService: TokensService, + private readonly restRepository: RestRepository, ) {} async requestOtp(dto: RequestOtpDto) { const { phone, slug } = dto; const code = this.generateOtpCode(); - await this.cacheService.set(`otp:${phone}:${slug}`, code, 160); + await this.cacheService.set(`otp:${slug}:${phone}`, code, 160); await this.smsService.sendOtp(phone, code); @@ -48,17 +52,20 @@ export class AuthService { return { success: true, message: ' OTP sent successfully' }; } - async verifyOtp(phone: string, code: string) { - const cachedCode = await this.cacheService.get(`otp:${phone}`); + async verifyOtp(phone: string, slug: string, code: string) { + const cachedCode = await this.cacheService.get(`otp:${slug}:${phone}`); if (!cachedCode) throw new BadRequestException('OTP expired or not found'); if (cachedCode !== code) throw new BadRequestException('Invalid OTP'); - await this.cacheService.del(`otp:${phone}`); + await this.cacheService.del(`otp:${slug}:${phone}`); const user = await this.userService.findOrCreateByPhone(phone); - + const rest = await this.restRepository.findOne({ slug }); + if (!rest) { + throw new BadRequestException(RestMessage.NOT_FOUND); + } // const accessToken = await this.issueToken(user.id, AuthEntityType.USER); - const tokens = await this.tokensService.generateTokens(user); + const tokens = await this.tokensService.generateTokens(user, rest); return { success: true, message: 'User registered successfully!', tokens }; } diff --git a/src/modules/auth/services/tokens.service.ts b/src/modules/auth/services/tokens.service.ts index 5c980d6..11ed997 100755 --- a/src/modules/auth/services/tokens.service.ts +++ b/src/modules/auth/services/tokens.service.ts @@ -10,6 +10,7 @@ import { AuthMessage, UserMessage } from '../../../common/enums/message.enum'; import { RefreshToken } from '../../users/entities/refresh-token.entity'; import { User } from '../../users/entities/user.entity'; import { ITokenPayload } from '../interfaces/IToken-payload'; +import { Restaurant } from 'src/modules/restaurants/entities/restaurant.entity'; @Injectable() export class TokensService { @@ -20,10 +21,11 @@ export class TokensService { private readonly em: EntityManager, ) {} - async generateTokens(user: User, em?: EntityManager) { + async generateTokens(user: User, rest: Restaurant, em?: EntityManager) { return this.generateAccessAndRefreshToken( { id: user.id, + restId: rest.id, }, em, ); @@ -33,7 +35,11 @@ export class TokensService { const accessExpire = this.configService.getOrThrow('ACCESS_TOKEN_EXPIRE'); const refreshExpire = this.configService.getOrThrow('REFRESH_TOKEN_EXPIRE'); - const accessToken = await this.jwtService.signAsync(payload, { expiresIn: `${accessExpire}m` }); + const tokenPayload: ITokenPayload = { + id: payload.id, + restId: payload.restId, + }; + const accessToken = await this.jwtService.signAsync(tokenPayload, { expiresIn: `${accessExpire}m` }); const refreshToken = this.generateRefreshToken(); await this.storeRefreshToken(payload.id, refreshToken, em); diff --git a/src/modules/restaurants/repositories/user.repository.ts b/src/modules/restaurants/repositories/user.repository.ts new file mode 100644 index 0000000..dc7cb7c --- /dev/null +++ b/src/modules/restaurants/repositories/user.repository.ts @@ -0,0 +1,6 @@ +import { EntityRepository } from '@mikro-orm/postgresql'; +import type { Restaurant } from '../entities/restaurant.entity'; + +// import { PaginationUtils } from '../../utils/services/pagination.utils'; + +export class RestRepository extends EntityRepository {} diff --git a/src/modules/users/repositories/user.repository.ts b/src/modules/users/repositories/user.repository.ts new file mode 100644 index 0000000..2cedf3f --- /dev/null +++ b/src/modules/users/repositories/user.repository.ts @@ -0,0 +1,39 @@ +import { EntityRepository, FilterQuery } from "@mikro-orm/postgresql"; + +import { PaginationUtils } from "../../utils/services/pagination.utils"; +import { UserListQueryDto } from "../DTO/user-list-query.dto"; +import { User } from "../entities/user.entity"; + +export class UserRepository extends EntityRepository { + async getUserListForAdmin(businessId: string, queryDto: UserListQueryDto) { + const { limit, skip } = PaginationUtils(queryDto); + + const whereClause: FilterQuery = { + business: { id: businessId }, + emailEnabled: true, + deletedAt: null, + }; + + if (queryDto.q) { + whereClause.$or = [ + { userName: { $like: `%${queryDto.q}%` } }, + { emailAddress: { $like: `%${queryDto.q}%` } }, + { displayName: { $like: `%${queryDto.q}%` } }, + ]; + } + + if (queryDto.isActive) { + console.log(queryDto.isActive); + + whereClause.isActive = queryDto.isActive === 1; + } + + return this.findAndCount(whereClause, { + exclude: ["password"], + populate: ["domain", "business"], + limit, + offset: skip, + orderBy: { createdAt: "DESC" }, + }); + } +} diff --git a/src/modules/users/user.controller.ts b/src/modules/users/user.controller.ts index ca9e439..496f6f1 100644 --- a/src/modules/users/user.controller.ts +++ b/src/modules/users/user.controller.ts @@ -14,7 +14,7 @@ export class UserController { @UseGuards(AuthGuard) @ApiBearerAuth() @ApiOperation({ summary: 'Get the current authenticated user profile' }) - @Get('/') + @Get('/me') async getUser(@Req() req: AuthRequest) { const userId = req.user.sub; const user = await this.userService.findById(userId);