chore: change the login and regitster flow to single route
This commit is contained in:
@@ -391,6 +391,8 @@ export const enum NotificationMessage {
|
|||||||
USERID_IS_STRING = "شناسه کاربر باید یک رشته متنی باشد",
|
USERID_IS_STRING = "شناسه کاربر باید یک رشته متنی باشد",
|
||||||
LOGIN = "لاگین",
|
LOGIN = "لاگین",
|
||||||
LOGIN_MESSAGE = "یک لاگین در تاریخ [loginDate] به حساب کاربری شما صورت گرفت",
|
LOGIN_MESSAGE = "یک لاگین در تاریخ [loginDate] به حساب کاربری شما صورت گرفت",
|
||||||
|
USER_PASSWORD = "رمز عبور پیشفرض",
|
||||||
|
USER_PASSWORD_MESSAGE = "رمز عبور پیشفرض شما: [password]",
|
||||||
NOTIFICATION_NOT_FOUND_OR_ACCESS_DENIED = "اعلان یافت نشد یا دسترسی غیرمجاز است",
|
NOTIFICATION_NOT_FOUND_OR_ACCESS_DENIED = "اعلان یافت نشد یا دسترسی غیرمجاز است",
|
||||||
INVOICE_CREATION = "صدور صورت حساب",
|
INVOICE_CREATION = "صدور صورت حساب",
|
||||||
INVOICE_CREATION_MESSAGE = "یک صورت حساب به شماره [id] جدید برای شما صادر شده است",
|
INVOICE_CREATION_MESSAGE = "یک صورت حساب به شماره [id] جدید برای شما صادر شده است",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export function smsConfigs() {
|
|||||||
SMS_PATTERN_NEW_SUBSCRIPTION: configService.getOrThrow<string>("SMS_PATTERN_NEW_SUBSCRIPTION"),
|
SMS_PATTERN_NEW_SUBSCRIPTION: configService.getOrThrow<string>("SMS_PATTERN_NEW_SUBSCRIPTION"),
|
||||||
SMS_PATTERN_NEW_TICKET_GLOBAL: configService.getOrThrow<string>("SMS_PATTERN_NEW_TICKET_GLOBAL"),
|
SMS_PATTERN_NEW_TICKET_GLOBAL: configService.getOrThrow<string>("SMS_PATTERN_NEW_TICKET_GLOBAL"),
|
||||||
SMS_PATTERN_NEW_CRITICISM: configService.getOrThrow<string>("SMS_PATTERN_NEW_CRITICISM"),
|
SMS_PATTERN_NEW_CRITICISM: configService.getOrThrow<string>("SMS_PATTERN_NEW_CRITICISM"),
|
||||||
|
SMS_PATTERN_USER_PASSWORD: configService.getOrThrow<string>("SMS_PATTERN_USER_PASSWORD"),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -65,4 +66,5 @@ export interface ISmsConfigs {
|
|||||||
SMS_PATTERN_NEW_SUBSCRIPTION: string;
|
SMS_PATTERN_NEW_SUBSCRIPTION: string;
|
||||||
SMS_PATTERN_NEW_TICKET_GLOBAL: string;
|
SMS_PATTERN_NEW_TICKET_GLOBAL: string;
|
||||||
SMS_PATTERN_NEW_CRITICISM: string;
|
SMS_PATTERN_NEW_CRITICISM: string;
|
||||||
|
SMS_PATTERN_USER_PASSWORD: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export function databaseConfigs(): TypeOrmModuleAsyncOptions {
|
|||||||
username: configService.getOrThrow<string>("DB_USER"),
|
username: configService.getOrThrow<string>("DB_USER"),
|
||||||
password: configService.getOrThrow<string>("DB_PASS"),
|
password: configService.getOrThrow<string>("DB_PASS"),
|
||||||
autoLoadEntities: true,
|
autoLoadEntities: true,
|
||||||
synchronize: configService.getOrThrow<string>("NODE_ENV") == "production" ? false : true,
|
synchronize: configService.getOrThrow<string>("NODE_ENV") == "production" ? false : false,
|
||||||
logging: configService.getOrThrow<string>("NODE_ENV") == "production" ? false : true,
|
logging: configService.getOrThrow<string>("NODE_ENV") == "production" ? false : true,
|
||||||
migrationsTableName: "typeorm_migrations",
|
migrationsTableName: "typeorm_migrations",
|
||||||
migrationsRun: false,
|
migrationsRun: false,
|
||||||
|
|||||||
@@ -1,56 +1,56 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
// import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
import { IsMobilePhone, IsNotEmpty, IsNumberString, IsOptional, IsString, Length, MinLength } from "class-validator";
|
// import { IsMobilePhone, IsNotEmpty, IsNumberString, IsOptional, IsString, Length } from "class-validator";
|
||||||
|
|
||||||
// import { IsNationalCode } from "../../../common/decorators/is-national-code.decorator";
|
// // import { IsNationalCode } from "../../../common/decorators/is-national-code.decorator";
|
||||||
import { AuthMessage, ReferralMessage } from "../../../common/enums/message.enum";
|
// import { AuthMessage, ReferralMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
export class CompleteRegistrationDto {
|
// export class CompleteRegistrationDto {
|
||||||
@IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY })
|
// @IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY })
|
||||||
@IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT })
|
// @IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT })
|
||||||
@Length(11, 11, { message: AuthMessage.PHONE_SHOULD_BE_11_DIGIT })
|
// @Length(11, 11, { message: AuthMessage.PHONE_SHOULD_BE_11_DIGIT })
|
||||||
@ApiProperty({ description: "phone number", default: "09922320740" })
|
// @ApiProperty({ description: "phone number", default: "09922320740" })
|
||||||
phone: string;
|
// phone: string;
|
||||||
|
|
||||||
@ApiProperty({ description: "OTP code received via SMS", example: "56893" })
|
// @ApiProperty({ description: "OTP code received via SMS", example: "56893" })
|
||||||
@IsNotEmpty({ message: AuthMessage.OTP_NOT_EMPTY })
|
// @IsNotEmpty({ message: AuthMessage.OTP_NOT_EMPTY })
|
||||||
@IsNumberString(undefined, { message: AuthMessage.OTP_FORMAT_INVALID })
|
// @IsNumberString(undefined, { message: AuthMessage.OTP_FORMAT_INVALID })
|
||||||
@Length(5, 5, { message: AuthMessage.OTP_FORMAT_INVALID })
|
// @Length(5, 5, { message: AuthMessage.OTP_FORMAT_INVALID })
|
||||||
code: string;
|
// code: string;
|
||||||
|
|
||||||
@IsNotEmpty({ message: AuthMessage.FIRST_NAME_NOT_EMPTY })
|
// // @IsNotEmpty({ message: AuthMessage.FIRST_NAME_NOT_EMPTY })
|
||||||
@IsString({ 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 })
|
// // @Length(2, 50, { message: AuthMessage.FIRST_NAME_SHOULD_BE_BETWEEN_2_AND_50 })
|
||||||
@ApiProperty({ description: "First name", example: "mahyar" })
|
// // @ApiProperty({ description: "First name", example: "mahyar" })
|
||||||
firstName: string;
|
// // firstName: string;
|
||||||
|
|
||||||
@IsNotEmpty({ message: AuthMessage.LAST_NAME_NOT_EMPTY })
|
// // @IsNotEmpty({ message: AuthMessage.LAST_NAME_NOT_EMPTY })
|
||||||
@IsString({ 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 })
|
// // @Length(2, 50, { message: AuthMessage.LAST_NAME_SHOULD_BE_BETWEEN_2_AND_50 })
|
||||||
@ApiProperty({ description: "Last name", example: "jamshidi" })
|
// // @ApiProperty({ description: "Last name", example: "jamshidi" })
|
||||||
lastName: string;
|
// // lastName: string;
|
||||||
|
|
||||||
|
// // @IsOptional()
|
||||||
|
// // @IsNotEmpty({ message: AuthMessage.BIRTH_DATE_NOT_EMPTY })
|
||||||
|
// // @IsString({ message: AuthMessage.BIRTH_DATE_NOT_EMPTY })
|
||||||
|
// // @ApiProperty({ description: "Birth date", example: "1403/01/01" })
|
||||||
|
// // birthDate: string;
|
||||||
|
|
||||||
|
// // @IsOptional()
|
||||||
|
// // @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;
|
||||||
|
|
||||||
// @IsOptional()
|
// @IsOptional()
|
||||||
// @IsNotEmpty({ message: AuthMessage.BIRTH_DATE_NOT_EMPTY })
|
// @IsString({ message: ReferralMessage.INVALID_REFERRAL_CODE })
|
||||||
// @IsString({ message: AuthMessage.BIRTH_DATE_NOT_EMPTY })
|
// @ApiPropertyOptional({ description: "Referral code", example: "abc123" })
|
||||||
// @ApiProperty({ description: "Birth date", example: "1403/01/01" })
|
// referralCode?: string;
|
||||||
// birthDate: string;
|
// }
|
||||||
|
|
||||||
// @IsOptional()
|
|
||||||
// @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;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString({ message: ReferralMessage.INVALID_REFERRAL_CODE })
|
|
||||||
@ApiPropertyOptional({ description: "Referral code", example: "abc123" })
|
|
||||||
referralCode?: string;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ApiProperty, PickType } from "@nestjs/swagger";
|
import { ApiProperty, ApiPropertyOptional, PickType } from "@nestjs/swagger";
|
||||||
import { IsMobilePhone, IsNotEmpty, IsNumberString, Length } from "class-validator";
|
import { IsMobilePhone, IsNotEmpty, IsNumberString, IsOptional, IsString, Length } from "class-validator";
|
||||||
|
|
||||||
import { AuthMessage } from "../../../common/enums/message.enum";
|
import { AuthMessage, ReferralMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
export class VerifyOtpDto {
|
export class VerifyOtpDto {
|
||||||
@IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY })
|
@IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY })
|
||||||
@@ -15,6 +15,11 @@ export class VerifyOtpDto {
|
|||||||
@IsNumberString(undefined, { message: AuthMessage.OTP_FORMAT_INVALID })
|
@IsNumberString(undefined, { message: AuthMessage.OTP_FORMAT_INVALID })
|
||||||
@Length(5, 5, { message: AuthMessage.OTP_FORMAT_INVALID })
|
@Length(5, 5, { message: AuthMessage.OTP_FORMAT_INVALID })
|
||||||
code: string;
|
code: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString({ message: ReferralMessage.INVALID_REFERRAL_CODE })
|
||||||
|
@ApiPropertyOptional({ description: "Referral code", example: "abc123" })
|
||||||
|
referralCode?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VerifyOtpWithUserId extends PickType(VerifyOtpDto, ["code"]) {}
|
export class VerifyOtpWithUserId extends PickType(VerifyOtpDto, ["code"]) {}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { ApiOperation, ApiTags } from "@nestjs/swagger";
|
|||||||
import { Throttle, ThrottlerGuard } from "@nestjs/throttler";
|
import { Throttle, ThrottlerGuard } from "@nestjs/throttler";
|
||||||
|
|
||||||
import { ChangePasswordDto } from "./DTO/change-password.dto";
|
import { ChangePasswordDto } from "./DTO/change-password.dto";
|
||||||
import { CompleteRegistrationDto } from "./DTO/complete-register.dto";
|
|
||||||
import { CheckUserExistDto, LoginPasswordDTO } from "./DTO/loginPassword.dto";
|
import { CheckUserExistDto, LoginPasswordDTO } from "./DTO/loginPassword.dto";
|
||||||
import { RefreshTokenDto } from "./DTO/refresh-token.dto";
|
import { RefreshTokenDto } from "./DTO/refresh-token.dto";
|
||||||
import { RequestOtpDto } from "./DTO/request-otp.dto";
|
import { RequestOtpDto } from "./DTO/request-otp.dto";
|
||||||
@@ -27,19 +26,6 @@ export class AuthController {
|
|||||||
private readonly ssoService: SSOService,
|
private readonly ssoService: SSOService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ApiOperation({ summary: "Initiate registration" })
|
|
||||||
@HttpCode(HttpStatus.OK)
|
|
||||||
@Post("register/initiate")
|
|
||||||
register(@Body() registerDto: RequestOtpDto) {
|
|
||||||
return this.authService.initiateRegistration(registerDto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOperation({ summary: "complete registration ==> step 2" })
|
|
||||||
@Post("register/complete")
|
|
||||||
completeRegistration(@Body() completeRegistrationDto: CompleteRegistrationDto) {
|
|
||||||
return this.authService.completeRegistration(completeRegistrationDto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOperation({ summary: "request to login with otp" })
|
@ApiOperation({ summary: "request to login with otp" })
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post("otp/send")
|
@Post("otp/send")
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
import { randomBytes } from "node:crypto";
|
||||||
|
|
||||||
|
import { BadRequestException, Injectable, Logger } from "@nestjs/common";
|
||||||
import { DataSource } from "typeorm";
|
import { DataSource } from "typeorm";
|
||||||
|
|
||||||
import { TokensService } from "./tokens.service";
|
import { TokensService } from "./tokens.service";
|
||||||
@@ -10,13 +12,14 @@ import { AdminsService } from "../../users/providers/admins.service";
|
|||||||
import { UsersService } from "../../users/providers/users.service";
|
import { UsersService } from "../../users/providers/users.service";
|
||||||
import { OTPService } from "../../utils/providers/otp.service";
|
import { OTPService } from "../../utils/providers/otp.service";
|
||||||
import { PasswordService } from "../../utils/providers/password.service";
|
import { PasswordService } from "../../utils/providers/password.service";
|
||||||
|
import { randomizeCase } from "../../utils/providers/string.utils";
|
||||||
import { ChangePasswordDto } from "../DTO/change-password.dto";
|
import { ChangePasswordDto } from "../DTO/change-password.dto";
|
||||||
import { CompleteRegistrationDto } from "../DTO/complete-register.dto";
|
|
||||||
import { CheckUserExistDto, LoginPasswordDTO } from "../DTO/loginPassword.dto";
|
import { CheckUserExistDto, LoginPasswordDTO } from "../DTO/loginPassword.dto";
|
||||||
import { RequestOtpDto } from "../DTO/request-otp.dto";
|
import { RequestOtpDto } from "../DTO/request-otp.dto";
|
||||||
import { VerifyOtpDto } from "../DTO/verify-otp.dto";
|
import { VerifyOtpDto } from "../DTO/verify-otp.dto";
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
|
private readonly logger = new Logger(AuthService.name);
|
||||||
constructor(
|
constructor(
|
||||||
private readonly usersService: UsersService,
|
private readonly usersService: UsersService,
|
||||||
private readonly adminsService: AdminsService,
|
private readonly adminsService: AdminsService,
|
||||||
@@ -27,78 +30,7 @@ export class AuthService {
|
|||||||
private readonly notificationQueue: NotificationQueue,
|
private readonly notificationQueue: NotificationQueue,
|
||||||
private readonly referralsService: ReferralsService,
|
private readonly referralsService: ReferralsService,
|
||||||
) {}
|
) {}
|
||||||
//****************** */
|
|
||||||
//****************** */
|
|
||||||
async initiateRegistration(requestOtpDto: RequestOtpDto) {
|
|
||||||
const { phone } = requestOtpDto;
|
|
||||||
const existUser = await this.usersService.findOneWithPhone(phone);
|
|
||||||
if (existUser) throw new BadRequestException(AuthMessage.PHONE_EXISTS);
|
|
||||||
|
|
||||||
const existCode = await this.otpService.checkExistOtp(phone, "REGISTER");
|
|
||||||
if (existCode) {
|
|
||||||
return {
|
|
||||||
message: AuthMessage.OTP_ALREADY_SENT,
|
|
||||||
ttlSecond: existCode,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const otpCode = await this.otpService.generateAndSetInCache(phone, "REGISTER");
|
|
||||||
//
|
|
||||||
// await this.smsService.sendSmsVerifyCode(phone, otpCode);
|
|
||||||
await this.notificationQueue.addLoginOtpNotification({ phone, code: otpCode });
|
|
||||||
|
|
||||||
return {
|
|
||||||
message: AuthMessage.OTP_SENT,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
//****************** */
|
|
||||||
//****************** */
|
|
||||||
async completeRegistration(completeRegistrationDto: CompleteRegistrationDto) {
|
|
||||||
const { phone, code, referralCode } = completeRegistrationDto;
|
|
||||||
const queryRunner = this.dataSource.createQueryRunner();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await queryRunner.connect();
|
|
||||||
await queryRunner.startTransaction();
|
|
||||||
//
|
|
||||||
const isValid = await this.otpService.verifyOtp(phone, code, "REGISTER");
|
|
||||||
if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP);
|
|
||||||
|
|
||||||
const existUser = await this.usersService.findOneWithPhone(phone);
|
|
||||||
if (existUser) throw new BadRequestException(AuthMessage.PHONE_EXISTS);
|
|
||||||
|
|
||||||
const hashedPassword = await this.passwordService.hashPassword(completeRegistrationDto.password);
|
|
||||||
const user = await this.usersService.createUser(completeRegistrationDto, hashedPassword, queryRunner);
|
|
||||||
|
|
||||||
if (referralCode) await this.referralsService.useReferralCode({ referralCode, userId: user.id }, queryRunner);
|
|
||||||
|
|
||||||
const tokens = await this.tokensService.generateTokens(user, queryRunner);
|
|
||||||
|
|
||||||
const superAdmins = await this.adminsService.getSuperAdmins(queryRunner);
|
|
||||||
|
|
||||||
for (const admin of superAdmins) {
|
|
||||||
await this.notificationQueue.addNewCustomerNotification(admin.id, {
|
|
||||||
userPhone: admin.phone,
|
|
||||||
userEmail: admin.email,
|
|
||||||
fullName: `${user.firstName} ${user.lastName}`,
|
|
||||||
mobile: user.phone,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.otpService.delOtpFormCache(phone, "REGISTER");
|
|
||||||
|
|
||||||
await queryRunner.commitTransaction();
|
|
||||||
|
|
||||||
return {
|
|
||||||
message: AuthMessage.USER_REGISTER_SUCCESS,
|
|
||||||
...tokens,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
await queryRunner.rollbackTransaction();
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
await queryRunner.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//****************** */
|
//****************** */
|
||||||
//****************** */
|
//****************** */
|
||||||
async loginWithPassword(loginDto: LoginPasswordDTO) {
|
async loginWithPassword(loginDto: LoginPasswordDTO) {
|
||||||
@@ -150,18 +82,10 @@ export class AuthService {
|
|||||||
//****************** */
|
//****************** */
|
||||||
//****************** */
|
//****************** */
|
||||||
|
|
||||||
async requestLoginOtp(requestOtpDto: RequestOtpDto, isAdmin: boolean = false) {
|
async requestLoginOtp(requestOtpDto: RequestOtpDto) {
|
||||||
const { phone } = requestOtpDto;
|
const { phone } = requestOtpDto;
|
||||||
const user = await this.usersService.findOneWithPhone(phone);
|
|
||||||
if (!user) throw new BadRequestException(AuthMessage.PHONE_NOT_FOUND);
|
|
||||||
|
|
||||||
//check the if the method call is from admin or not
|
const existCode = await this.otpService.checkExistOtp(phone, "LOGIN/REGISTER");
|
||||||
|
|
||||||
const isUserAdmin = this.checkUserIsAdmin(user.roles);
|
|
||||||
|
|
||||||
if (isAdmin && !isUserAdmin) throw new BadRequestException(AuthMessage.NOT_ADMIN);
|
|
||||||
|
|
||||||
const existCode = await this.otpService.checkExistOtp(phone, "LOGIN");
|
|
||||||
if (existCode) {
|
if (existCode) {
|
||||||
return {
|
return {
|
||||||
message: AuthMessage.OTP_ALREADY_SENT,
|
message: AuthMessage.OTP_ALREADY_SENT,
|
||||||
@@ -169,9 +93,9 @@ export class AuthService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const otpCode = await this.otpService.generateAndSetInCache(phone, "LOGIN");
|
const otpCode = await this.otpService.generateAndSetInCache(phone, "LOGIN/REGISTER");
|
||||||
//
|
//
|
||||||
// await this.smsService.sendSmsVerifyCode(phone, otpCode);
|
this.logger.log(`otp code: ${otpCode} for phone: ${phone}`);
|
||||||
await this.notificationQueue.addLoginOtpNotification({ phone, code: otpCode });
|
await this.notificationQueue.addLoginOtpNotification({ phone, code: otpCode });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -183,20 +107,67 @@ export class AuthService {
|
|||||||
//****************** */
|
//****************** */
|
||||||
|
|
||||||
async verifyLoginOtp(verifyOtpDto: VerifyOtpDto) {
|
async verifyLoginOtp(verifyOtpDto: VerifyOtpDto) {
|
||||||
const { code, phone } = verifyOtpDto;
|
const { code, phone, referralCode } = verifyOtpDto;
|
||||||
|
|
||||||
const user = await this.checkUserLoginCredentialWithPhone(phone, code);
|
const queryRunner = this.dataSource.createQueryRunner();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await queryRunner.connect();
|
||||||
|
await queryRunner.startTransaction();
|
||||||
|
|
||||||
|
const isValid = await this.otpService.verifyOtp(phone, code, "LOGIN/REGISTER");
|
||||||
|
if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP);
|
||||||
|
|
||||||
|
let user = await this.usersService.findOneWithPhone(phone);
|
||||||
|
let isNewUser = false;
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
const defaultUserPassword = randomizeCase(randomBytes(6).toString("hex"));
|
||||||
|
isNewUser = true;
|
||||||
|
|
||||||
|
const hashedPassword = await this.passwordService.hashPassword(defaultUserPassword);
|
||||||
|
user = await this.usersService.createUser(verifyOtpDto, hashedPassword, queryRunner);
|
||||||
|
|
||||||
|
if (referralCode) await this.referralsService.useReferralCode({ referralCode, userId: user.id }, queryRunner);
|
||||||
|
|
||||||
|
const superAdmins = await this.adminsService.getSuperAdmins(queryRunner);
|
||||||
|
|
||||||
|
for (const admin of superAdmins) {
|
||||||
|
await this.notificationQueue.addNewCustomerNotification(admin.id, {
|
||||||
|
userPhone: admin.phone,
|
||||||
|
userEmail: admin.email,
|
||||||
|
fullName: `${user.firstName} ${user.lastName}`,
|
||||||
|
mobile: user.phone,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.notificationQueue.addUserPasswordNotification(user.id, {
|
||||||
|
userPhone: user.phone,
|
||||||
|
userEmail: user.email,
|
||||||
|
password: defaultUserPassword,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!user.roles.some((role) => !role.isAdmin)) throw new BadRequestException(AuthMessage.ADMIN_CAN_NOT_LOGIN);
|
if (!user.roles.some((role) => !role.isAdmin)) throw new BadRequestException(AuthMessage.ADMIN_CAN_NOT_LOGIN);
|
||||||
// if (user.roles.some((role) => role.isAdmin)) throw new BadRequestException(AuthMessage.ADMIN_CAN_NOT_LOGIN);
|
|
||||||
|
|
||||||
const tokens = await this.tokensService.generateTokens(user);
|
|
||||||
|
|
||||||
await this.notificationQueue.addLoginNotification(user.id, { userPhone: user.phone, userEmail: user.email });
|
await this.notificationQueue.addLoginNotification(user.id, { userPhone: user.phone, userEmail: user.email });
|
||||||
|
|
||||||
|
const tokens = await this.tokensService.generateTokens(user, queryRunner);
|
||||||
|
|
||||||
|
await this.otpService.delOtpFormCache(phone, "LOGIN/REGISTER");
|
||||||
|
|
||||||
|
await queryRunner.commitTransaction();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: AuthMessage.LOGIN_SUCCESS,
|
message: isNewUser ? AuthMessage.USER_REGISTER_SUCCESS : AuthMessage.LOGIN_SUCCESS,
|
||||||
...tokens,
|
...tokens,
|
||||||
};
|
};
|
||||||
|
} catch (error) {
|
||||||
|
await queryRunner.rollbackTransaction();
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
await queryRunner.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//****************** */
|
//****************** */
|
||||||
@@ -204,7 +175,9 @@ export class AuthService {
|
|||||||
async adminVerifyLoginOtp(verifyOtpDto: VerifyOtpDto) {
|
async adminVerifyLoginOtp(verifyOtpDto: VerifyOtpDto) {
|
||||||
const { code, phone } = verifyOtpDto;
|
const { code, phone } = verifyOtpDto;
|
||||||
|
|
||||||
const user = await this.checkUserLoginCredentialWithPhone(phone, code);
|
const user = await this.checkUserAuthCredentialWithPhone(phone, code);
|
||||||
|
|
||||||
|
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
||||||
|
|
||||||
const isUserAdmin = this.checkUserIsAdmin(user.roles);
|
const isUserAdmin = this.checkUserIsAdmin(user.roles);
|
||||||
|
|
||||||
@@ -260,23 +233,18 @@ export class AuthService {
|
|||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
//****************** */
|
|
||||||
//****************** */
|
|
||||||
|
|
||||||
private async checkUserLoginCredentialWithPhone(phone: string, otpCode: string) {
|
private async checkUserAuthCredentialWithPhone(phone: string, otpCode: string) {
|
||||||
const isValid = await this.otpService.verifyOtp(phone, otpCode, "LOGIN");
|
const isValid = await this.otpService.verifyOtp(phone, otpCode, "LOGIN/REGISTER");
|
||||||
if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP);
|
if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP);
|
||||||
|
|
||||||
await this.otpService.delOtpFormCache(phone, "LOGIN");
|
await this.otpService.delOtpFormCache(phone, "LOGIN/REGISTER");
|
||||||
|
|
||||||
const user = await this.usersService.findOneWithPhone(phone);
|
const user = await this.usersService.findOneWithPhone(phone);
|
||||||
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
// private checkUserPerm(user: User, perm: PermissionEnum) {
|
|
||||||
// return user.roles.some((role) => role?.permissions?.some((p) => p.name === perm));
|
|
||||||
// }
|
|
||||||
//****************** */
|
//****************** */
|
||||||
//****************** */
|
//****************** */
|
||||||
private checkUserIsAdmin(roles: Role[]) {
|
private checkUserIsAdmin(roles: Role[]) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export { NotificationHandlerFactory } from "./notification-handler.factory";
|
|||||||
// Special handlers (non-transactional)
|
// Special handlers (non-transactional)
|
||||||
export { OtpHandler } from "./otp.handler";
|
export { OtpHandler } from "./otp.handler";
|
||||||
export { UserLoginHandler } from "./user-login.handler";
|
export { UserLoginHandler } from "./user-login.handler";
|
||||||
|
export { UserPasswordHandler } from "./user-password.handler";
|
||||||
|
|
||||||
// Wallet handlers
|
// Wallet handlers
|
||||||
export { WalletChargeHandler, WalletDeductionHandler } from "./wallet.handler";
|
export { WalletChargeHandler, WalletDeductionHandler } from "./wallet.handler";
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
import { PaymentCancellationHandler, PaymentReminderHandler } from "./payment.handler";
|
import { PaymentCancellationHandler, PaymentReminderHandler } from "./payment.handler";
|
||||||
import { BlockServiceHandler } from "./service.handler";
|
import { BlockServiceHandler } from "./service.handler";
|
||||||
import { AnswerTicketHandler, AssignTicketHandler, CreateTicketHandler, NewTicketHandler } from "./ticket.handler";
|
import { AnswerTicketHandler, AssignTicketHandler, CreateTicketHandler, NewTicketHandler } from "./ticket.handler";
|
||||||
|
import { UserPasswordHandler } from "./user-password.handler";
|
||||||
import { WalletChargeHandler, WalletDeductionHandler } from "./wallet.handler";
|
import { WalletChargeHandler, WalletDeductionHandler } from "./wallet.handler";
|
||||||
import { NotifType } from "../../settings/enums/notif-settings.enum";
|
import { NotifType } from "../../settings/enums/notif-settings.enum";
|
||||||
|
|
||||||
@@ -25,11 +26,9 @@ export class NotificationHandlerFactory {
|
|||||||
private readonly handlers: Map<NotifType, BaseNotificationHandler> = new Map();
|
private readonly handlers: Map<NotifType, BaseNotificationHandler> = new Map();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
// Wallet handlers
|
|
||||||
private readonly walletChargeHandler: WalletChargeHandler,
|
private readonly walletChargeHandler: WalletChargeHandler,
|
||||||
private readonly walletDeductionHandler: WalletDeductionHandler,
|
private readonly walletDeductionHandler: WalletDeductionHandler,
|
||||||
|
|
||||||
// Invoice handlers
|
|
||||||
private readonly createInvoiceHandler: CreateInvoiceHandler,
|
private readonly createInvoiceHandler: CreateInvoiceHandler,
|
||||||
private readonly billInvoiceReminderHandler: BillInvoiceReminderHandler,
|
private readonly billInvoiceReminderHandler: BillInvoiceReminderHandler,
|
||||||
private readonly billInvoiceHandler: BillInvoiceHandler,
|
private readonly billInvoiceHandler: BillInvoiceHandler,
|
||||||
@@ -37,7 +36,6 @@ export class NotificationHandlerFactory {
|
|||||||
private readonly invoiceOverdueHandler: InvoiceOverdueHandler,
|
private readonly invoiceOverdueHandler: InvoiceOverdueHandler,
|
||||||
private readonly recurringInvoiceHandler: RecurringInvoiceHandler,
|
private readonly recurringInvoiceHandler: RecurringInvoiceHandler,
|
||||||
|
|
||||||
// Admin handlers
|
|
||||||
private readonly blogCommentHandler: BlogCommentHandler,
|
private readonly blogCommentHandler: BlogCommentHandler,
|
||||||
private readonly serviceReviewHandler: ServiceReviewHandler,
|
private readonly serviceReviewHandler: ServiceReviewHandler,
|
||||||
private readonly newCustomerHandler: NewCustomerHandler,
|
private readonly newCustomerHandler: NewCustomerHandler,
|
||||||
@@ -45,30 +43,26 @@ export class NotificationHandlerFactory {
|
|||||||
private readonly newCriticismHandler: NewCriticismHandler,
|
private readonly newCriticismHandler: NewCriticismHandler,
|
||||||
private readonly newTicketHandler: NewTicketHandler,
|
private readonly newTicketHandler: NewTicketHandler,
|
||||||
|
|
||||||
// User handlers
|
|
||||||
private readonly announcementHandler: AnnouncementHandler,
|
private readonly announcementHandler: AnnouncementHandler,
|
||||||
|
|
||||||
// Ticket handlers
|
|
||||||
private readonly answerTicketHandler: AnswerTicketHandler,
|
private readonly answerTicketHandler: AnswerTicketHandler,
|
||||||
private readonly createTicketHandler: CreateTicketHandler,
|
private readonly createTicketHandler: CreateTicketHandler,
|
||||||
private readonly assignTicketHandler: AssignTicketHandler,
|
private readonly assignTicketHandler: AssignTicketHandler,
|
||||||
|
|
||||||
// Service handlers
|
|
||||||
private readonly blockServiceHandler: BlockServiceHandler,
|
private readonly blockServiceHandler: BlockServiceHandler,
|
||||||
|
|
||||||
// Payment handlers
|
|
||||||
private readonly paymentReminderHandler: PaymentReminderHandler,
|
private readonly paymentReminderHandler: PaymentReminderHandler,
|
||||||
private readonly paymentCancellationHandler: PaymentCancellationHandler,
|
private readonly paymentCancellationHandler: PaymentCancellationHandler,
|
||||||
|
|
||||||
|
private readonly userPasswordHandler: UserPasswordHandler,
|
||||||
) {
|
) {
|
||||||
this.registerHandlers();
|
this.registerHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerHandlers(): void {
|
private registerHandlers(): void {
|
||||||
// Wallet notifications
|
|
||||||
this.handlers.set(NotifType.WALLET_CHARGE, this.walletChargeHandler);
|
this.handlers.set(NotifType.WALLET_CHARGE, this.walletChargeHandler);
|
||||||
this.handlers.set(NotifType.WALLET_DEDUCTION, this.walletDeductionHandler);
|
this.handlers.set(NotifType.WALLET_DEDUCTION, this.walletDeductionHandler);
|
||||||
|
|
||||||
// Invoice notifications
|
|
||||||
this.handlers.set(NotifType.CREATE_INVOICE, this.createInvoiceHandler);
|
this.handlers.set(NotifType.CREATE_INVOICE, this.createInvoiceHandler);
|
||||||
this.handlers.set(NotifType.BILL_INVOICE_REMINDER, this.billInvoiceReminderHandler);
|
this.handlers.set(NotifType.BILL_INVOICE_REMINDER, this.billInvoiceReminderHandler);
|
||||||
this.handlers.set(NotifType.BILL_INVOICE, this.billInvoiceHandler);
|
this.handlers.set(NotifType.BILL_INVOICE, this.billInvoiceHandler);
|
||||||
@@ -76,7 +70,6 @@ export class NotificationHandlerFactory {
|
|||||||
this.handlers.set(NotifType.INVOICE_OVERDUE, this.invoiceOverdueHandler);
|
this.handlers.set(NotifType.INVOICE_OVERDUE, this.invoiceOverdueHandler);
|
||||||
this.handlers.set(NotifType.RECURRING_INVOICE, this.recurringInvoiceHandler);
|
this.handlers.set(NotifType.RECURRING_INVOICE, this.recurringInvoiceHandler);
|
||||||
|
|
||||||
// Admin notifications
|
|
||||||
this.handlers.set(NotifType.NEW_BLOG_COMMENT, this.blogCommentHandler);
|
this.handlers.set(NotifType.NEW_BLOG_COMMENT, this.blogCommentHandler);
|
||||||
this.handlers.set(NotifType.NEW_SERVICE_REVIEW, this.serviceReviewHandler);
|
this.handlers.set(NotifType.NEW_SERVICE_REVIEW, this.serviceReviewHandler);
|
||||||
this.handlers.set(NotifType.NEW_CUSTOMER, this.newCustomerHandler);
|
this.handlers.set(NotifType.NEW_CUSTOMER, this.newCustomerHandler);
|
||||||
@@ -84,21 +77,19 @@ export class NotificationHandlerFactory {
|
|||||||
this.handlers.set(NotifType.NEW_CRITICISM, this.newCriticismHandler);
|
this.handlers.set(NotifType.NEW_CRITICISM, this.newCriticismHandler);
|
||||||
this.handlers.set(NotifType.NEW_TICKET, this.newTicketHandler);
|
this.handlers.set(NotifType.NEW_TICKET, this.newTicketHandler);
|
||||||
|
|
||||||
// User notifications
|
|
||||||
this.handlers.set(NotifType.ANNOUNCEMENT, this.announcementHandler);
|
this.handlers.set(NotifType.ANNOUNCEMENT, this.announcementHandler);
|
||||||
|
|
||||||
// Ticket notifications
|
|
||||||
this.handlers.set(NotifType.ANSWER_TICKET, this.answerTicketHandler);
|
this.handlers.set(NotifType.ANSWER_TICKET, this.answerTicketHandler);
|
||||||
this.handlers.set(NotifType.CREATE_TICKET, this.createTicketHandler);
|
this.handlers.set(NotifType.CREATE_TICKET, this.createTicketHandler);
|
||||||
this.handlers.set(NotifType.ASSIGN_TICKET, this.assignTicketHandler);
|
this.handlers.set(NotifType.ASSIGN_TICKET, this.assignTicketHandler);
|
||||||
|
|
||||||
// Service notifications
|
|
||||||
this.handlers.set(NotifType.BLOCK_SERVICE, this.blockServiceHandler);
|
this.handlers.set(NotifType.BLOCK_SERVICE, this.blockServiceHandler);
|
||||||
|
|
||||||
// Payment notifications
|
|
||||||
this.handlers.set(NotifType.PAYMENT_REMINDER, this.paymentReminderHandler);
|
this.handlers.set(NotifType.PAYMENT_REMINDER, this.paymentReminderHandler);
|
||||||
this.handlers.set(NotifType.PAYMENT_CANCELLATION, this.paymentCancellationHandler);
|
this.handlers.set(NotifType.PAYMENT_CANCELLATION, this.paymentCancellationHandler);
|
||||||
|
|
||||||
|
this.handlers.set(NotifType.USER_PASSWORD, this.userPasswordHandler);
|
||||||
|
|
||||||
this.logger.log(`Registered ${this.handlers.size} notification handlers`);
|
this.logger.log(`Registered ${this.handlers.size} notification handlers`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Injectable, Logger } from "@nestjs/common";
|
||||||
|
import { QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
import { BaseNotificationHandler } from "./base-notification.handler";
|
||||||
|
import { IUserPasswordNotificationData } from "../interfaces/ISendNotificationData";
|
||||||
|
import { NotificationsService } from "../providers/notifications.service";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserPasswordHandler extends BaseNotificationHandler<IUserPasswordNotificationData> {
|
||||||
|
protected readonly logger = new Logger(UserPasswordHandler.name);
|
||||||
|
|
||||||
|
constructor(private readonly notificationsService: NotificationsService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async handle(recipientId: string, data: IUserPasswordNotificationData, queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await this.notificationsService.createUserPasswordNotification(recipientId, data, queryRunner);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getHandlerName(): string {
|
||||||
|
return "UserPassword";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -82,3 +82,7 @@ export interface INewCriticismNotificationData extends IBaseNotificationData {
|
|||||||
title: string;
|
title: string;
|
||||||
fullName: string;
|
fullName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IUserPasswordNotificationData extends IBaseNotificationData {
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,34 +5,28 @@ import { TypeOrmModule } from "@nestjs/typeorm";
|
|||||||
|
|
||||||
import { NOTIFICATION } from "./constants";
|
import { NOTIFICATION } from "./constants";
|
||||||
import { Notification } from "./entities/notification.entity";
|
import { Notification } from "./entities/notification.entity";
|
||||||
|
import { BlogCommentHandler } from "./handlers/blog-comment.handler";
|
||||||
|
import { NotificationController } from "./notifications.controller";
|
||||||
|
import { LoggerModule } from "../logger/logger.module";
|
||||||
import {
|
import {
|
||||||
AnnouncementHandler,
|
|
||||||
AnswerTicketHandler,
|
|
||||||
ApproveInvoiceHandler,
|
|
||||||
AssignTicketHandler,
|
|
||||||
BillInvoiceHandler,
|
|
||||||
BillInvoiceReminderHandler,
|
|
||||||
BlockServiceHandler,
|
|
||||||
BlogCommentHandler,
|
|
||||||
CreateInvoiceHandler,
|
|
||||||
CreateTicketHandler,
|
|
||||||
InvoiceOverdueHandler,
|
|
||||||
NewCriticismHandler,
|
NewCriticismHandler,
|
||||||
NewCustomerHandler,
|
NewCustomerHandler,
|
||||||
NewSubscriptionHandler,
|
NewSubscriptionHandler,
|
||||||
NewTicketHandler,
|
|
||||||
NotificationHandlerFactory,
|
|
||||||
OtpHandler,
|
|
||||||
PaymentCancellationHandler,
|
|
||||||
PaymentReminderHandler,
|
|
||||||
RecurringInvoiceHandler,
|
|
||||||
ServiceReviewHandler,
|
ServiceReviewHandler,
|
||||||
UserLoginHandler,
|
} from "./handlers/admin-notifications.handler";
|
||||||
WalletChargeHandler,
|
import { AnnouncementHandler } from "./handlers/announcement.handler";
|
||||||
WalletDeductionHandler,
|
import {
|
||||||
} from "./handlers";
|
ApproveInvoiceHandler,
|
||||||
import { NotificationController } from "./notifications.controller";
|
BillInvoiceHandler,
|
||||||
import { LoggerModule } from "../logger/logger.module";
|
BillInvoiceReminderHandler,
|
||||||
|
CreateInvoiceHandler,
|
||||||
|
InvoiceOverdueHandler,
|
||||||
|
RecurringInvoiceHandler,
|
||||||
|
} from "./handlers/invoice.handler";
|
||||||
|
import { PaymentCancellationHandler, PaymentReminderHandler } from "./handlers/payment.handler";
|
||||||
|
import { BlockServiceHandler } from "./handlers/service.handler";
|
||||||
|
import { AnswerTicketHandler, AssignTicketHandler, CreateTicketHandler, NewTicketHandler } from "./handlers/ticket.handler";
|
||||||
|
import { UserPasswordHandler } from "./handlers/user-password.handler";
|
||||||
import { NotificationsService } from "./providers/notifications.service";
|
import { NotificationsService } from "./providers/notifications.service";
|
||||||
import { NotificationProcessor } from "./queue/notification.processor";
|
import { NotificationProcessor } from "./queue/notification.processor";
|
||||||
import { NotificationQueue } from "./queue/notification.queue";
|
import { NotificationQueue } from "./queue/notification.queue";
|
||||||
@@ -40,6 +34,10 @@ import { NotificationRepository } from "./repositories/notifications.repository"
|
|||||||
import { NotificationSetting } from "../settings/entities/notification-setting.entity";
|
import { NotificationSetting } from "../settings/entities/notification-setting.entity";
|
||||||
import { SettingModule } from "../settings/settings.module";
|
import { SettingModule } from "../settings/settings.module";
|
||||||
import { UtilsModule } from "../utils/utils.module";
|
import { UtilsModule } from "../utils/utils.module";
|
||||||
|
import { NotificationHandlerFactory } from "./handlers/notification-handler.factory";
|
||||||
|
import { OtpHandler } from "./handlers/otp.handler";
|
||||||
|
import { UserLoginHandler } from "./handlers/user-login.handler";
|
||||||
|
import { WalletChargeHandler, WalletDeductionHandler } from "./handlers/wallet.handler";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -92,6 +90,7 @@ import { UtilsModule } from "../utils/utils.module";
|
|||||||
BlockServiceHandler,
|
BlockServiceHandler,
|
||||||
PaymentReminderHandler,
|
PaymentReminderHandler,
|
||||||
PaymentCancellationHandler,
|
PaymentCancellationHandler,
|
||||||
|
UserPasswordHandler,
|
||||||
],
|
],
|
||||||
controllers: [NotificationController],
|
controllers: [NotificationController],
|
||||||
exports: [NotificationRepository, NotificationsService, NotificationQueue],
|
exports: [NotificationRepository, NotificationsService, NotificationQueue],
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
IServiceReviewNotificationData,
|
IServiceReviewNotificationData,
|
||||||
ISubscriptionNotificationData,
|
ISubscriptionNotificationData,
|
||||||
ITicketNotificationData,
|
ITicketNotificationData,
|
||||||
|
IUserPasswordNotificationData,
|
||||||
IWalletNotificationData,
|
IWalletNotificationData,
|
||||||
} from "../interfaces/ISendNotificationData";
|
} from "../interfaces/ISendNotificationData";
|
||||||
import { NotificationRepository } from "../repositories/notifications.repository";
|
import { NotificationRepository } from "../repositories/notifications.repository";
|
||||||
@@ -551,4 +552,15 @@ export class NotificationsService {
|
|||||||
queryRunner,
|
queryRunner,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
//************************************************* */
|
||||||
|
|
||||||
|
async createUserPasswordNotification(recipientId: string, data: IUserPasswordNotificationData, queryRunner: QueryRunner) {
|
||||||
|
const message = NotificationMessage.USER_PASSWORD_MESSAGE.replace("[password]", data.password);
|
||||||
|
|
||||||
|
await this.smsService.sendUserPasswordSms(data.userPhone, data.password);
|
||||||
|
return this.createNotification(
|
||||||
|
{ title: NotificationMessage.USER_PASSWORD, type: NotifType.USER_PASSWORD, message, recipientId },
|
||||||
|
queryRunner,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
IServiceReviewNotificationData,
|
IServiceReviewNotificationData,
|
||||||
ISubscriptionNotificationData,
|
ISubscriptionNotificationData,
|
||||||
ITicketNotificationData,
|
ITicketNotificationData,
|
||||||
|
IUserPasswordNotificationData,
|
||||||
IWalletNotificationData,
|
IWalletNotificationData,
|
||||||
} from "../interfaces/ISendNotificationData";
|
} from "../interfaces/ISendNotificationData";
|
||||||
|
|
||||||
@@ -145,4 +146,8 @@ export class NotificationQueue {
|
|||||||
async addPaymentCancellationNotification(recipientId: string, data: IPaymentNotificationData) {
|
async addPaymentCancellationNotification(recipientId: string, data: IPaymentNotificationData) {
|
||||||
await this.addNotificationJob(NotifType.PAYMENT_CANCELLATION, recipientId, data);
|
await this.addNotificationJob(NotifType.PAYMENT_CANCELLATION, recipientId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addUserPasswordNotification(recipientId: string, data: IUserPasswordNotificationData) {
|
||||||
|
await this.addNotificationJob(NotifType.USER_PASSWORD, recipientId, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export enum NotifCategory {
|
|||||||
export enum NotifType {
|
export enum NotifType {
|
||||||
// Account category
|
// Account category
|
||||||
USER_LOGIN = "USER_LOGIN",
|
USER_LOGIN = "USER_LOGIN",
|
||||||
|
USER_PASSWORD = "USER_PASSWORD",
|
||||||
ANNOUNCEMENT = "ANNOUNCEMENT",
|
ANNOUNCEMENT = "ANNOUNCEMENT",
|
||||||
|
|
||||||
// Finance category
|
// Finance category
|
||||||
@@ -50,6 +51,8 @@ export const NotifDescriptions: Record<NotifType, { fa: string; category: NotifC
|
|||||||
// Account category
|
// Account category
|
||||||
[NotifType.USER_LOGIN]: { fa: "ورود به ناحیه کاربری", category: NotifCategory.ACCOUNT },
|
[NotifType.USER_LOGIN]: { fa: "ورود به ناحیه کاربری", category: NotifCategory.ACCOUNT },
|
||||||
[NotifType.ANNOUNCEMENT]: { fa: "اعلانات و اطلاعیه ها", category: NotifCategory.ACCOUNT },
|
[NotifType.ANNOUNCEMENT]: { fa: "اعلانات و اطلاعیه ها", category: NotifCategory.ACCOUNT },
|
||||||
|
//DO_NOT_CREATE_THIS_NOTIFICATION_IN_DATABASE
|
||||||
|
[NotifType.USER_PASSWORD]: { fa: "ارسال رمز عبور پیشفرض", category: NotifCategory.ACCOUNT },
|
||||||
|
|
||||||
// Finance category
|
// Finance category
|
||||||
[NotifType.PAYMENT_REMINDER]: { fa: "یادآوری پرداخت", category: NotifCategory.FINANCE },
|
[NotifType.PAYMENT_REMINDER]: { fa: "یادآوری پرداخت", category: NotifCategory.FINANCE },
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional, PickType } from "@nestjs/swagger";
|
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
import { IsEmail, IsNotEmpty, IsOptional, IsString, IsUUID, IsUrl, MinLength } from "class-validator";
|
import { IsEmail, IsMobilePhone, IsNotEmpty, IsOptional, IsString, IsUUID, IsUrl, Length, MinLength } from "class-validator";
|
||||||
|
|
||||||
import { AdminMessage, AuthMessage } from "../../../common/enums/message.enum";
|
import { AdminMessage, AuthMessage } from "../../../common/enums/message.enum";
|
||||||
import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto";
|
|
||||||
|
|
||||||
export class CreateAdminDto extends PickType(CompleteRegistrationDto, ["firstName", "lastName", "phone", "password"] as const) {
|
export class CreateAdminDto {
|
||||||
@IsNotEmpty({ message: AuthMessage.PASSWORD_NOT_EMPTY })
|
@IsNotEmpty({ message: AuthMessage.PASSWORD_NOT_EMPTY })
|
||||||
@IsString({ message: AuthMessage.PASSWORD_FORMAT_INVALID })
|
@IsString({ message: AuthMessage.PASSWORD_FORMAT_INVALID })
|
||||||
@ApiProperty({ description: "repeat password", example: "12345678" })
|
@ApiProperty({ description: "repeat password", example: "12345678" })
|
||||||
@@ -16,14 +15,38 @@ export class CreateAdminDto extends PickType(CompleteRegistrationDto, ["firstNam
|
|||||||
@ApiProperty({ description: "Email", example: "ma@gmail.com" })
|
@ApiProperty({ description: "Email", example: "ma@gmail.com" })
|
||||||
email: string;
|
email: string;
|
||||||
|
|
||||||
|
@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: AdminMessage.ROLE_REQUIRED })
|
@IsNotEmpty({ message: AdminMessage.ROLE_REQUIRED })
|
||||||
@IsUUID("4", { message: AdminMessage.ROLE_ID_SHOULD_BE_UUID })
|
@IsUUID("4", { message: AdminMessage.ROLE_ID_SHOULD_BE_UUID })
|
||||||
@ApiProperty({ description: "Role id", example: "123e4567-e89b-12d3-a456-426614174000" })
|
@ApiProperty({ description: "Role id", example: "123e4567-e89b-12d3-a456-426614174000" })
|
||||||
roleId: string;
|
roleId: 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.PASSWORD_NOT_EMPTY })
|
||||||
|
@IsString({ message: AuthMessage.PASSWORD_FORMAT_INVALID })
|
||||||
|
@ApiProperty({ description: "password", example: "12S345SS678" })
|
||||||
|
@MinLength(8, { message: AuthMessage.PASSWORD_LENGTH })
|
||||||
|
password: string;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsNotEmpty({ message: AdminMessage.PROFILE_PIC_REQUIRED })
|
@IsNotEmpty({ message: AdminMessage.PROFILE_PIC_REQUIRED })
|
||||||
@IsUrl({ protocols: ["http", "https"], require_protocol: true }, { message: AdminMessage.PROFILE_PIC_URL })
|
@IsUrl({ protocols: ["http", "https"], require_protocol: true }, { message: AdminMessage.PROFILE_PIC_URL })
|
||||||
@ApiPropertyOptional({ description: "Profile picture", example: "https://www.google.com" })
|
@ApiPropertyOptional({ description: "Profile picture", example: "https://www.google.com" })
|
||||||
profilePic: string;
|
profilePic?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,35 @@
|
|||||||
import { ApiProperty, PickType } from "@nestjs/swagger";
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
import { IsEnum, IsInt, IsNotEmpty, IsNumberString, IsString, Length } from "class-validator";
|
import { IsEnum, IsInt, IsMobilePhone, IsNotEmpty, IsNumberString, IsString, Length } from "class-validator";
|
||||||
|
|
||||||
import { IsNationalCode } from "../../../common/decorators/is-national-code.decorator";
|
import { IsNationalCode } from "../../../common/decorators/is-national-code.decorator";
|
||||||
import { AuthMessage, FinancialMessage } from "../../../common/enums/message.enum";
|
import { AuthMessage, FinancialMessage } from "../../../common/enums/message.enum";
|
||||||
import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto";
|
|
||||||
import { GenderEnum } from "../enums/gender-type.enum";
|
import { GenderEnum } from "../enums/gender-type.enum";
|
||||||
import { NationalityEnum } from "../enums/nationality.enum";
|
import { NationalityEnum } from "../enums/nationality.enum";
|
||||||
|
|
||||||
export class CreateRealUserDto extends PickType(CompleteRegistrationDto, ["firstName", "lastName", "phone"]) {
|
export class CreateRealUserDto {
|
||||||
@IsNotEmpty({ message: FinancialMessage.GENDER_TYPE_REQUIRED })
|
@IsNotEmpty({ message: FinancialMessage.GENDER_TYPE_REQUIRED })
|
||||||
@IsEnum(GenderEnum)
|
@IsEnum(GenderEnum)
|
||||||
@ApiProperty({ enum: GenderEnum, description: "Gender type", example: GenderEnum.MALE })
|
@ApiProperty({ enum: GenderEnum, description: "Gender type", example: GenderEnum.MALE })
|
||||||
gender: GenderEnum;
|
gender: GenderEnum;
|
||||||
|
|
||||||
|
@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: FinancialMessage.FATHER_NAME_REQUIRED })
|
@IsNotEmpty({ message: FinancialMessage.FATHER_NAME_REQUIRED })
|
||||||
@IsString({ message: FinancialMessage.FATHER_NAME_INVALID })
|
@IsString({ message: FinancialMessage.FATHER_NAME_INVALID })
|
||||||
@ApiProperty({ description: "Father name" })
|
@ApiProperty({ description: "Father name" })
|
||||||
|
|||||||
@@ -1,11 +1,24 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional, PartialType, PickType } from "@nestjs/swagger";
|
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
import { IsNotEmpty, IsNumberString, IsOptional, IsString, IsUrl, Length } from "class-validator";
|
import { IsNotEmpty, IsNumberString, IsOptional, IsString, IsUrl, Length } from "class-validator";
|
||||||
|
|
||||||
import { IsNationalCode } from "../../../common/decorators/is-national-code.decorator";
|
import { IsNationalCode } from "../../../common/decorators/is-national-code.decorator";
|
||||||
import { AuthMessage, UserMessage } from "../../../common/enums/message.enum";
|
import { AuthMessage, UserMessage } from "../../../common/enums/message.enum";
|
||||||
import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto";
|
|
||||||
|
|
||||||
export class UpdateProfileDto extends PartialType(PickType(CompleteRegistrationDto, ["firstName", "lastName"] as const)) {
|
export class UpdateProfileDto {
|
||||||
|
@IsOptional()
|
||||||
|
@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;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@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;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsNotEmpty({ message: UserMessage.USERNAME_NOT_EMPTY })
|
@IsNotEmpty({ message: UserMessage.USERNAME_NOT_EMPTY })
|
||||||
@Length(3, 50, { message: UserMessage.USERNAME_SHOULD_BE_BETWEEN_3_AND_50 })
|
@Length(3, 50, { message: UserMessage.USERNAME_SHOULD_BE_BETWEEN_3_AND_50 })
|
||||||
@@ -17,7 +30,7 @@ export class UpdateProfileDto extends PartialType(PickType(CompleteRegistrationD
|
|||||||
@IsNotEmpty({ message: AuthMessage.BIRTH_DATE_NOT_EMPTY })
|
@IsNotEmpty({ message: AuthMessage.BIRTH_DATE_NOT_EMPTY })
|
||||||
@IsString({ message: AuthMessage.BIRTH_DATE_NOT_EMPTY })
|
@IsString({ message: AuthMessage.BIRTH_DATE_NOT_EMPTY })
|
||||||
@ApiProperty({ description: "Birth date", example: "1403/01/01" })
|
@ApiProperty({ description: "Birth date", example: "1403/01/01" })
|
||||||
birthDate: string;
|
birthDate?: string;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsNotEmpty({ message: AuthMessage.NATIONAL_NOT_EMPTY })
|
@IsNotEmpty({ message: AuthMessage.NATIONAL_NOT_EMPTY })
|
||||||
@@ -25,7 +38,7 @@ export class UpdateProfileDto extends PartialType(PickType(CompleteRegistrationD
|
|||||||
@Length(10, 10, { message: AuthMessage.NATIONAL_CODE_INCORRECT })
|
@Length(10, 10, { message: AuthMessage.NATIONAL_CODE_INCORRECT })
|
||||||
@IsNationalCode({ message: AuthMessage.NATIONAL_CODE_INVALID })
|
@IsNationalCode({ message: AuthMessage.NATIONAL_CODE_INVALID })
|
||||||
@ApiProperty({ description: "iranian format (10 char)", example: "4569852169" })
|
@ApiProperty({ description: "iranian format (10 char)", example: "4569852169" })
|
||||||
nationalCode: string;
|
nationalCode?: string;
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: "Profile picture", example: "https://www.google.com" })
|
@ApiPropertyOptional({ description: "Profile picture", example: "https://www.google.com" })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@@ -47,8 +60,8 @@ export class UpdateProfileDto extends PartialType(PickType(CompleteRegistrationD
|
|||||||
@Length(10, 10, { message: UserMessage.POSTAL_CODE_LENGTH })
|
@Length(10, 10, { message: UserMessage.POSTAL_CODE_LENGTH })
|
||||||
postalCode?: string;
|
postalCode?: string;
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: "City id" })
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
@ApiPropertyOptional({ description: "City id" })
|
||||||
@IsNotEmpty({ message: UserMessage.CITY_REQUIRED })
|
@IsNotEmpty({ message: UserMessage.CITY_REQUIRED })
|
||||||
cityId: string;
|
cityId?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,13 +50,13 @@ export class User extends BaseEntity {
|
|||||||
lastName: string;
|
lastName: string;
|
||||||
|
|
||||||
@Column({ type: "varchar", length: 12, nullable: true })
|
@Column({ type: "varchar", length: 12, nullable: true })
|
||||||
birthDate: string;
|
birthDate?: string;
|
||||||
|
|
||||||
@Column({ type: "varchar", length: 100, unique: true, nullable: true })
|
@Column({ type: "varchar", length: 100, unique: true, nullable: true })
|
||||||
nationalCode: string | null;
|
nationalCode?: string;
|
||||||
|
|
||||||
@Column({ type: "varchar", length: 100, nullable: true })
|
@Column({ type: "varchar", length: 100, nullable: true })
|
||||||
profilePic: string;
|
profilePic?: string;
|
||||||
|
|
||||||
@Column({ type: "boolean", default: false })
|
@Column({ type: "boolean", default: false })
|
||||||
emailVerified: boolean;
|
emailVerified: boolean;
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { In, Not, QueryRunner } from "typeorm";
|
|||||||
|
|
||||||
import { AuthMessage, CommonMessage, UserMessage } from "../../../common/enums/message.enum";
|
import { AuthMessage, CommonMessage, UserMessage } from "../../../common/enums/message.enum";
|
||||||
import { AddressService } from "../../address/providers/address.service";
|
import { AddressService } from "../../address/providers/address.service";
|
||||||
import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto";
|
|
||||||
import { RequestOtpDto } from "../../auth/DTO/request-otp.dto";
|
import { RequestOtpDto } from "../../auth/DTO/request-otp.dto";
|
||||||
import { VerifyOtpDto } from "../../auth/DTO/verify-otp.dto";
|
import { VerifyOtpDto } from "../../auth/DTO/verify-otp.dto";
|
||||||
import { ReferralsService } from "../../referrals/providers/referrals.service";
|
import { ReferralsService } from "../../referrals/providers/referrals.service";
|
||||||
@@ -201,7 +200,7 @@ export class UsersService {
|
|||||||
const existPhone = await this.userRepository.findOneBy({ phone });
|
const existPhone = await this.userRepository.findOneBy({ phone });
|
||||||
if (existPhone) throw new BadRequestException(UserMessage.PHONE_EXIST);
|
if (existPhone) throw new BadRequestException(UserMessage.PHONE_EXIST);
|
||||||
|
|
||||||
const existCode = await this.otpService.checkExistOtp(phone, "LOGIN");
|
const existCode = await this.otpService.checkExistOtp(phone, "LOGIN/REGISTER");
|
||||||
if (existCode) {
|
if (existCode) {
|
||||||
return {
|
return {
|
||||||
message: AuthMessage.OTP_ALREADY_SENT,
|
message: AuthMessage.OTP_ALREADY_SENT,
|
||||||
@@ -209,7 +208,7 @@ export class UsersService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const otpCode = await this.otpService.generateAndSetInCache(phone, "LOGIN");
|
const otpCode = await this.otpService.generateAndSetInCache(phone, "LOGIN/REGISTER");
|
||||||
//
|
//
|
||||||
await this.smsService.sendSmsVerifyCode(phone, otpCode);
|
await this.smsService.sendSmsVerifyCode(phone, otpCode);
|
||||||
this.logger.debug(`OTP sent to ${phone}: ${otpCode}`);
|
this.logger.debug(`OTP sent to ${phone}: ${otpCode}`);
|
||||||
@@ -237,7 +236,7 @@ export class UsersService {
|
|||||||
|
|
||||||
/************************************************************ */
|
/************************************************************ */
|
||||||
|
|
||||||
async createUser(registerDto: CompleteRegistrationDto, hashedPassword: string, queryRunner: QueryRunner): Promise<User> {
|
async createUser(registerDto: VerifyOtpDto, hashedPassword: string, queryRunner: QueryRunner): Promise<User> {
|
||||||
const role = await queryRunner.manager.findOneBy(Role, { name: RoleEnum.USER });
|
const role = await queryRunner.manager.findOneBy(Role, { name: RoleEnum.USER });
|
||||||
if (!role) throw new BadRequestException(UserMessage.ROLE_NOT_FOUND);
|
if (!role) throw new BadRequestException(UserMessage.ROLE_NOT_FOUND);
|
||||||
|
|
||||||
@@ -247,7 +246,10 @@ export class UsersService {
|
|||||||
// const existUser = await queryRunner.manager.findOneBy(User, { nationalCode: registerDto.nationalCode });
|
// const existUser = await queryRunner.manager.findOneBy(User, { nationalCode: registerDto.nationalCode });
|
||||||
// if (existUser) throw new BadRequestException(UserMessage.NATIONAL_CODE_EXIST);
|
// if (existUser) throw new BadRequestException(UserMessage.NATIONAL_CODE_EXIST);
|
||||||
|
|
||||||
const userName = slugify(`${registerDto.firstName} ${Date.now().toString().slice(-5)}`, { lower: true, trim: true });
|
const userName = slugify(`u-${registerDto.phone}`, { lower: true, trim: true });
|
||||||
|
|
||||||
|
const defaultFirstName = "کاربر";
|
||||||
|
const defaultLastName = userName.slice(0, 5);
|
||||||
|
|
||||||
const { referralCode, ...userData } = registerDto;
|
const { referralCode, ...userData } = registerDto;
|
||||||
const user = queryRunner.manager.create(User, {
|
const user = queryRunner.manager.create(User, {
|
||||||
@@ -255,6 +257,8 @@ export class UsersService {
|
|||||||
userName,
|
userName,
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
roles: [role],
|
roles: [role],
|
||||||
|
firstName: defaultFirstName,
|
||||||
|
lastName: defaultLastName,
|
||||||
});
|
});
|
||||||
await queryRunner.manager.save(user);
|
await queryRunner.manager.save(user);
|
||||||
|
|
||||||
@@ -383,10 +387,10 @@ export class UsersService {
|
|||||||
/************************************************************ */
|
/************************************************************ */
|
||||||
|
|
||||||
private async checkUserLoginCredentialWithPhone(phone: string, otpCode: string, userId: string) {
|
private async checkUserLoginCredentialWithPhone(phone: string, otpCode: string, userId: string) {
|
||||||
const isValid = await this.otpService.verifyOtp(phone, otpCode, "LOGIN");
|
const isValid = await this.otpService.verifyOtp(phone, otpCode, "LOGIN/REGISTER");
|
||||||
if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP);
|
if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP);
|
||||||
|
|
||||||
await this.otpService.delOtpFormCache(phone, "LOGIN");
|
await this.otpService.delOtpFormCache(phone, "LOGIN/REGISTER");
|
||||||
|
|
||||||
const user = await this.userRepository.findOneBy({ id: userId });
|
const user = await this.userRepository.findOneBy({ id: userId });
|
||||||
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export type OtpCacheKeyType = "LOGIN" | "REGISTER" | "FORGOT_PASSWORD" | "INVOICE_VERIFY";
|
export type OtpCacheKeyType = "LOGIN/REGISTER" | "FORGOT_PASSWORD" | "INVOICE_VERIFY";
|
||||||
|
|||||||
@@ -48,4 +48,5 @@ export type TemplateParams =
|
|||||||
| "message"
|
| "message"
|
||||||
| "blogTitle"
|
| "blogTitle"
|
||||||
| "fullName"
|
| "fullName"
|
||||||
| "mobile";
|
| "mobile"
|
||||||
|
| "password";
|
||||||
|
|||||||
@@ -789,5 +789,35 @@ export class SmsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sendUserPasswordSms(mobile: string, password: string) {
|
||||||
|
const smsData: ISmsVerifyBody = {
|
||||||
|
Parameters: [{ name: "password", value: password }],
|
||||||
|
Mobile: mobile,
|
||||||
|
TemplateId: this.smsConfigs.SMS_PATTERN_USER_PASSWORD,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await firstValueFrom(
|
||||||
|
this.httpService
|
||||||
|
.post<ISmsVerifyResponse>(
|
||||||
|
`${this.smsConfigs.API_URL}/send/verify`,
|
||||||
|
{ ...smsData },
|
||||||
|
{
|
||||||
|
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
catchError((err: AxiosError) => {
|
||||||
|
this.logger.error("error in sending user password sms", err);
|
||||||
|
throw new InternalServerErrorException("error in sending user password sms");
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error("error in sending sms", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//************************************************* */
|
//************************************************* */
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,3 +2,19 @@ export function truncateIfLong(str: string, maxLength = 30): string {
|
|||||||
if (typeof str !== "string") return str;
|
if (typeof str !== "string") return str;
|
||||||
return str.length > maxLength ? str.slice(0, maxLength) + "..." : str;
|
return str.length > maxLength ? str.slice(0, maxLength) + "..." : str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function randomizeCase(str: string): string {
|
||||||
|
if (typeof str !== "string") return str;
|
||||||
|
|
||||||
|
return str
|
||||||
|
.split("")
|
||||||
|
.map((char) => {
|
||||||
|
// Only process alphabetic characters
|
||||||
|
if (/[a-zA-Z]/.test(char)) {
|
||||||
|
// 50% chance to make it uppercase, 50% lowercase
|
||||||
|
return Math.random() < 0.5 ? char.toLowerCase() : char.toUpperCase();
|
||||||
|
}
|
||||||
|
return char; // Keep non-alphabetic characters as they are
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user