This commit is contained in:
2025-11-11 11:29:39 +03:30
parent 5dd7a6588e
commit d4db672672
9 changed files with 76 additions and 20 deletions
+1 -1
View File
@@ -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 = 'اسلاگ مورد نیاز است',
@@ -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;
}
+1 -2
View File
@@ -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();
@@ -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 {}
+13 -6
View File
@@ -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 };
}
+8 -2
View File
@@ -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<number>('ACCESS_TOKEN_EXPIRE');
const refreshExpire = this.configService.getOrThrow<number>('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);
@@ -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<Restaurant> {}
@@ -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<User> {
async getUserListForAdmin(businessId: string, queryDto: UserListQueryDto) {
const { limit, skip } = PaginationUtils(queryDto);
const whereClause: FilterQuery<User> = {
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" },
});
}
}
+1 -1
View File
@@ -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);