mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-08-25 05:42:27 +00:00
Merge branch 'waiting-list' into feat/neoleap-integration
This commit is contained in:
@ -18,7 +18,6 @@ import {
|
|||||||
setJuniorPasswordRequestDto,
|
setJuniorPasswordRequestDto,
|
||||||
SetPasscodeRequestDto,
|
SetPasscodeRequestDto,
|
||||||
VerifyLoginOtpRequestDto,
|
VerifyLoginOtpRequestDto,
|
||||||
VerifyOtpRequestDto,
|
|
||||||
VerifyUserRequestDto,
|
VerifyUserRequestDto,
|
||||||
} from '../dtos/request';
|
} from '../dtos/request';
|
||||||
import { SendForgetPasswordOtpResponseDto, SendRegisterOtpResponseDto } from '../dtos/response';
|
import { SendForgetPasswordOtpResponseDto, SendRegisterOtpResponseDto } from '../dtos/response';
|
||||||
@ -72,22 +71,22 @@ export class AuthController {
|
|||||||
await this.authService.setPasscode(sub, passcode);
|
await this.authService.setPasscode(sub, passcode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('register/set-phone/otp')
|
// @Post('register/set-phone/otp')
|
||||||
@UseGuards(AccessTokenGuard)
|
// @UseGuards(AccessTokenGuard)
|
||||||
async setPhoneNumber(
|
// async setPhoneNumber(
|
||||||
@AuthenticatedUser() { sub }: IJwtPayload,
|
// @AuthenticatedUser() { sub }: IJwtPayload,
|
||||||
@Body() setPhoneNumberDto: CreateUnverifiedUserRequestDto,
|
// @Body() setPhoneNumberDto: CreateUnverifiedUserRequestDto,
|
||||||
) {
|
// ) {
|
||||||
const phoneNumber = await this.authService.setPhoneNumber(sub, setPhoneNumberDto);
|
// const phoneNumber = await this.authService.setPhoneNumber(sub, setPhoneNumberDto);
|
||||||
return ResponseFactory.data(new SendRegisterOtpResponseDto(phoneNumber));
|
// return ResponseFactory.data(new SendRegisterOtpResponseDto(phoneNumber));
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Post('register/set-phone/verify')
|
// @Post('register/set-phone/verify')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
// @HttpCode(HttpStatus.NO_CONTENT)
|
||||||
@UseGuards(AccessTokenGuard)
|
// @UseGuards(AccessTokenGuard)
|
||||||
async verifyPhoneNumber(@AuthenticatedUser() { sub }: IJwtPayload, @Body() { otp }: VerifyOtpRequestDto) {
|
// async verifyPhoneNumber(@AuthenticatedUser() { sub }: IJwtPayload, @Body() { otp }: VerifyOtpRequestDto) {
|
||||||
await this.authService.verifyPhoneNumber(sub, otp);
|
// await this.authService.verifyPhoneNumber(sub, otp);
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Post('biometric/enable')
|
@Post('biometric/enable')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
|
@ -1,19 +1,14 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { Matches } from 'class-validator';
|
import { IsEmail } from 'class-validator';
|
||||||
import { i18nValidationMessage as i18n } from 'nestjs-i18n';
|
import { i18nValidationMessage as i18n } from 'nestjs-i18n';
|
||||||
import { COUNTRY_CODE_REGEX } from '~/auth/constants';
|
|
||||||
import { IsValidPhoneNumber } from '~/core/decorators/validations';
|
|
||||||
|
|
||||||
export class CreateUnverifiedUserRequestDto {
|
export class CreateUnverifiedUserRequestDto {
|
||||||
@ApiProperty({ example: '+962' })
|
@ApiProperty({ example: 'test@test.com' })
|
||||||
@Matches(COUNTRY_CODE_REGEX, {
|
@IsEmail(
|
||||||
message: i18n('validation.Matches', { path: 'general', property: 'auth.countryCode' }),
|
{},
|
||||||
})
|
{
|
||||||
countryCode: string = '+966';
|
message: i18n('validation.IsEmail', { path: 'general', property: 'auth.email' }),
|
||||||
|
},
|
||||||
@ApiProperty({ example: '787259134' })
|
)
|
||||||
@IsValidPhoneNumber({
|
email!: string;
|
||||||
message: i18n('validation.IsValidPhoneNumber', { path: 'general', property: 'auth.phoneNumber' }),
|
|
||||||
})
|
|
||||||
phoneNumber!: string;
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,32 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty, PickType } from '@nestjs/swagger';
|
||||||
import { IsNumberString, MaxLength, MinLength } from 'class-validator';
|
import { IsDateString, IsNotEmpty, IsNumberString, IsOptional, IsString, MaxLength, MinLength } from 'class-validator';
|
||||||
import { i18nValidationMessage as i18n } from 'nestjs-i18n';
|
import { i18nValidationMessage as i18n } from 'nestjs-i18n';
|
||||||
import { DEFAULT_OTP_LENGTH } from '~/common/modules/otp/constants';
|
import { DEFAULT_OTP_LENGTH } from '~/common/modules/otp/constants';
|
||||||
|
import { IsAbove18 } from '~/core/decorators/validations';
|
||||||
import { CreateUnverifiedUserRequestDto } from './create-unverified-user.request.dto';
|
import { CreateUnverifiedUserRequestDto } from './create-unverified-user.request.dto';
|
||||||
|
|
||||||
export class VerifyUserRequestDto extends CreateUnverifiedUserRequestDto {
|
export class VerifyUserRequestDto extends PickType(CreateUnverifiedUserRequestDto, ['email']) {
|
||||||
|
@ApiProperty({ example: 'John' })
|
||||||
|
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.firstName' }) })
|
||||||
|
@IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'customer.firstName' }) })
|
||||||
|
firstName!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'Doe' })
|
||||||
|
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.lastName' }) })
|
||||||
|
@IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'customer.lastName' }) })
|
||||||
|
lastName!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: '2021-01-01' })
|
||||||
|
@IsDateString({}, { message: i18n('validation.IsDateString', { path: 'general', property: 'customer.dateOfBirth' }) })
|
||||||
|
@IsAbove18({ message: i18n('validation.IsAbove18', { path: 'general', property: 'customer.dateOfBirth' }) })
|
||||||
|
dateOfBirth!: Date;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'JO' })
|
||||||
|
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.countryOfResidence' }) })
|
||||||
|
@IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'customer.countryOfResidence' }) })
|
||||||
|
@IsOptional()
|
||||||
|
countryOfResidence: string = 'SA';
|
||||||
|
|
||||||
@ApiProperty({ example: '111111' })
|
@ApiProperty({ example: '111111' })
|
||||||
@IsNumberString(
|
@IsNumberString(
|
||||||
{ no_symbols: true },
|
{ no_symbols: true },
|
||||||
|
@ -2,9 +2,9 @@ import { ApiProperty } from '@nestjs/swagger';
|
|||||||
|
|
||||||
export class SendRegisterOtpResponseDto {
|
export class SendRegisterOtpResponseDto {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
phoneNumber!: string;
|
email!: string;
|
||||||
|
|
||||||
constructor(phoneNumber: string) {
|
constructor(email: string) {
|
||||||
this.phoneNumber = phoneNumber;
|
this.email = email;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,62 +45,45 @@ export class AuthService {
|
|||||||
private readonly cacheService: CacheService,
|
private readonly cacheService: CacheService,
|
||||||
private readonly oauth2Service: Oauth2Service,
|
private readonly oauth2Service: Oauth2Service,
|
||||||
) {}
|
) {}
|
||||||
async sendRegisterOtp({ phoneNumber, countryCode }: CreateUnverifiedUserRequestDto) {
|
async sendRegisterOtp(body: CreateUnverifiedUserRequestDto) {
|
||||||
this.logger.log(`Sending OTP to ${countryCode + phoneNumber}`);
|
this.logger.log(`Sending OTP to ${body.email}`);
|
||||||
const user = await this.userService.findOrCreateUser({ phoneNumber, countryCode });
|
const user = await this.userService.findOrCreateUser(body);
|
||||||
|
|
||||||
return this.otpService.generateAndSendOtp({
|
return this.otpService.generateAndSendOtp({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
recipient: user.countryCode + user.phoneNumber,
|
recipient: user.email,
|
||||||
scope: OtpScope.VERIFY_PHONE,
|
scope: OtpScope.VERIFY_EMAIL,
|
||||||
otpType: OtpType.SMS,
|
otpType: OtpType.EMAIL,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyUser(verifyUserDto: VerifyUserRequestDto): Promise<[ILoginResponse, User]> {
|
async verifyUser(verifyUserDto: VerifyUserRequestDto): Promise<[ILoginResponse, User]> {
|
||||||
this.logger.log(`Verifying user with phone number ${verifyUserDto.countryCode + verifyUserDto.phoneNumber}`);
|
this.logger.log(`Verifying user with email ${verifyUserDto.email}`);
|
||||||
const user = await this.userService.findUserOrThrow({ phoneNumber: verifyUserDto.phoneNumber });
|
const user = await this.userService.findUserOrThrow({ email: verifyUserDto.email });
|
||||||
|
|
||||||
if (user.isProfileCompleted) {
|
if (user.isEmailVerified) {
|
||||||
this.logger.error(
|
this.logger.error(`User with email ${verifyUserDto.email} already verified`);
|
||||||
`User with phone number ${verifyUserDto.countryCode + verifyUserDto.phoneNumber} already verified`,
|
throw new BadRequestException('USER.EMAIL_ALREADY_VERIFIED');
|
||||||
);
|
|
||||||
throw new BadRequestException('USER.PHONE_ALREADY_VERIFIED');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isOtpValid = await this.otpService.verifyOtp({
|
const isOtpValid = await this.otpService.verifyOtp({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
scope: OtpScope.VERIFY_PHONE,
|
scope: OtpScope.VERIFY_EMAIL,
|
||||||
otpType: OtpType.SMS,
|
otpType: OtpType.EMAIL,
|
||||||
value: verifyUserDto.otp,
|
value: verifyUserDto.otp,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isOtpValid) {
|
if (!isOtpValid) {
|
||||||
this.logger.error(
|
this.logger.error(`Invalid OTP for user with email ${verifyUserDto.email}`);
|
||||||
`Invalid OTP for user with phone number ${verifyUserDto.countryCode + verifyUserDto.phoneNumber}`,
|
|
||||||
);
|
|
||||||
throw new BadRequestException('OTP.INVALID_OTP');
|
throw new BadRequestException('OTP.INVALID_OTP');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.isPhoneVerified) {
|
await this.userService.verifyUser(user.id, verifyUserDto);
|
||||||
this.logger.log(
|
|
||||||
`User with phone number ${
|
|
||||||
verifyUserDto.countryCode + verifyUserDto.phoneNumber
|
|
||||||
} already verified but did not complete registration process`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const tokens = await this.generateAuthToken(user);
|
|
||||||
return [tokens, user];
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.userService.verifyPhoneNumber(user.id);
|
|
||||||
|
|
||||||
await user.reload();
|
await user.reload();
|
||||||
|
|
||||||
const tokens = await this.generateAuthToken(user);
|
const tokens = await this.generateAuthToken(user);
|
||||||
this.logger.log(
|
this.logger.log(`User with email ${verifyUserDto.email} verified successfully`);
|
||||||
`User with phone number ${verifyUserDto.countryCode + verifyUserDto.phoneNumber} verified successfully`,
|
|
||||||
);
|
|
||||||
return [tokens, user];
|
return [tokens, user];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,46 +121,46 @@ export class AuthService {
|
|||||||
this.logger.log(`Passcode set successfully for user with id ${userId}`);
|
this.logger.log(`Passcode set successfully for user with id ${userId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setPhoneNumber(userId: string, { phoneNumber, countryCode }: CreateUnverifiedUserRequestDto) {
|
// async setPhoneNumber(userId: string, { phoneNumber, countryCode }: CreateUnverifiedUserRequestDto) {
|
||||||
const user = await this.userService.findUserOrThrow({ id: userId });
|
// const user = await this.userService.findUserOrThrow({ id: userId });
|
||||||
|
|
||||||
if (user.phoneNumber || user.countryCode) {
|
// if (user.phoneNumber || user.countryCode) {
|
||||||
this.logger.error(`Phone number already set for user with id ${userId}`);
|
// this.logger.error(`Phone number already set for user with id ${userId}`);
|
||||||
throw new BadRequestException('USER.PHONE_NUMBER_ALREADY_SET');
|
// throw new BadRequestException('USER.PHONE_NUMBER_ALREADY_SET');
|
||||||
}
|
// }
|
||||||
|
|
||||||
const existingUser = await this.userService.findUser({ phoneNumber, countryCode });
|
// const existingUser = await this.userService.findUser({ phoneNumber, countryCode });
|
||||||
|
|
||||||
if (existingUser) {
|
// if (existingUser) {
|
||||||
this.logger.error(`Phone number ${countryCode + phoneNumber} already taken`);
|
// this.logger.error(`Phone number ${countryCode + phoneNumber} already taken`);
|
||||||
throw new BadRequestException('USER.PHONE_NUMBER_ALREADY_TAKEN');
|
// throw new BadRequestException('USER.PHONE_NUMBER_ALREADY_TAKEN');
|
||||||
}
|
// }
|
||||||
|
|
||||||
await this.userService.setPhoneNumber(userId, phoneNumber, countryCode);
|
// await this.userService.setPhoneNumber(userId, phoneNumber, countryCode);
|
||||||
|
|
||||||
return this.otpService.generateAndSendOtp({
|
// return this.otpService.generateAndSendOtp({
|
||||||
userId,
|
// userId,
|
||||||
recipient: countryCode + phoneNumber,
|
// recipient: countryCode + phoneNumber,
|
||||||
scope: OtpScope.VERIFY_PHONE,
|
// scope: OtpScope.VERIFY_PHONE,
|
||||||
otpType: OtpType.SMS,
|
// otpType: OtpType.SMS,
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
async verifyPhoneNumber(userId: string, otp: string) {
|
// async verifyPhoneNumber(userId: string, otp: string) {
|
||||||
const isOtpValid = await this.otpService.verifyOtp({
|
// const isOtpValid = await this.otpService.verifyOtp({
|
||||||
otpType: OtpType.SMS,
|
// otpType: OtpType.SMS,
|
||||||
scope: OtpScope.VERIFY_PHONE,
|
// scope: OtpScope.VERIFY_PHONE,
|
||||||
userId,
|
// userId,
|
||||||
value: otp,
|
// value: otp,
|
||||||
});
|
// });
|
||||||
|
|
||||||
if (!isOtpValid) {
|
// if (!isOtpValid) {
|
||||||
this.logger.error(`Invalid OTP for user with id ${userId}`);
|
// this.logger.error(`Invalid OTP for user with id ${userId}`);
|
||||||
throw new BadRequestException('OTP.INVALID_OTP');
|
// throw new BadRequestException('OTP.INVALID_OTP');
|
||||||
}
|
// }
|
||||||
|
|
||||||
return this.userService.verifyPhoneNumber(userId);
|
// return this.userService.verifyPhoneNumber(userId);
|
||||||
}
|
// }
|
||||||
|
|
||||||
async enableBiometric(userId: string, { deviceId, publicKey }: EnableBiometricRequestDto) {
|
async enableBiometric(userId: string, { deviceId, publicKey }: EnableBiometricRequestDto) {
|
||||||
this.logger.log(`Enabling biometric for user with id ${userId}`);
|
this.logger.log(`Enabling biometric for user with id ${userId}`);
|
||||||
@ -330,7 +313,8 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async sendLoginOtp({ email }: SendLoginOtpRequestDto) {
|
async sendLoginOtp({ email }: SendLoginOtpRequestDto) {
|
||||||
const user = await this.userService.findOrCreateByEmail(email);
|
const user = await this.userService.findUserOrThrow({ email });
|
||||||
|
|
||||||
this.logger.log(`Sending login OTP to ${email}`);
|
this.logger.log(`Sending login OTP to ${email}`);
|
||||||
return this.otpService.generateAndSendOtp({
|
return this.otpService.generateAndSendOtp({
|
||||||
recipient: email,
|
recipient: email,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
export enum OtpScope {
|
export enum OtpScope {
|
||||||
VERIFY_PHONE = 'VERIFY_PHONE',
|
VERIFY_PHONE = 'VERIFY_PHONE',
|
||||||
|
VERIFY_EMAIL = 'VERIFY_EMAIL',
|
||||||
FORGET_PASSWORD = 'FORGET_PASSWORD',
|
FORGET_PASSWORD = 'FORGET_PASSWORD',
|
||||||
LOGIN = 'LOGIN',
|
LOGIN = 'LOGIN',
|
||||||
}
|
}
|
||||||
|
@ -29,17 +29,6 @@ export class CustomerRepository {
|
|||||||
firstName: body.firstName,
|
firstName: body.firstName,
|
||||||
lastName: body.lastName,
|
lastName: body.lastName,
|
||||||
dateOfBirth: body.dateOfBirth,
|
dateOfBirth: body.dateOfBirth,
|
||||||
gender: body.gender,
|
|
||||||
countryOfResidence: body.countryOfResidence,
|
|
||||||
nationalId: body.nationalId,
|
|
||||||
nationalIdExpiry: body.nationalIdExpiry,
|
|
||||||
sourceOfIncome: body.sourceOfIncome,
|
|
||||||
profession: body.profession,
|
|
||||||
professionType: body.professionType,
|
|
||||||
isPep: body.isPep,
|
|
||||||
profilePictureId: body.profilePictureId,
|
|
||||||
civilIdFrontId: body.civilIdFrontId,
|
|
||||||
civilIdBackId: body.civilIdBackId,
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -89,13 +89,13 @@ export class CustomerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional()
|
@Transactional()
|
||||||
async createGuardianCustomer(userId: string, body: CreateCustomerRequestDto) {
|
async createGuardianCustomer(userId: string, body: Partial<CreateCustomerRequestDto>) {
|
||||||
this.logger.log(`Creating guardian customer for user ${userId}`);
|
this.logger.log(`Creating guardian customer for user ${userId}`);
|
||||||
const existingCustomer = await this.customerRepository.findOne({ id: userId });
|
const existingCustomer = await this.customerRepository.findOne({ id: userId });
|
||||||
|
|
||||||
if (existingCustomer) {
|
if (existingCustomer) {
|
||||||
this.logger.error(`Customer ${userId} already exists`);
|
this.logger.error(`Customer ${userId} already exists`);
|
||||||
throw new BadRequestException('CUSTOMER.ALRADY_EXISTS');
|
throw new BadRequestException('CUSTOMER.ALREADY_EXISTS');
|
||||||
}
|
}
|
||||||
|
|
||||||
// await this.validateCivilIdForCustomer(userId, body.civilIdFrontId, body.civilIdBackId);
|
// await this.validateCivilIdForCustomer(userId, body.civilIdFrontId, body.civilIdBackId);
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
"USER": {
|
"USER": {
|
||||||
"PHONE_ALREADY_VERIFIED": "تم التحقق من رقم الهاتف بالفعل.",
|
"PHONE_ALREADY_VERIFIED": "تم التحقق من رقم الهاتف بالفعل.",
|
||||||
|
"EMAIL_ALREADY_VERIFIED": "تم التحقق من عنوان البريد الإلكتروني بالفعل.",
|
||||||
"EMAIL_ALREADY_SET": "تم تعيين عنوان البريد الإلكتروني بالفعل.",
|
"EMAIL_ALREADY_SET": "تم تعيين عنوان البريد الإلكتروني بالفعل.",
|
||||||
"EMAIL_ALREADY_TAKEN": "عنوان البريد الإلكتروني مستخدم بالفعل. يرجى تجربة عنوان بريد إلكتروني آخر.",
|
"EMAIL_ALREADY_TAKEN": "عنوان البريد الإلكتروني مستخدم بالفعل. يرجى تجربة عنوان بريد إلكتروني آخر.",
|
||||||
"PHONE_NUMBER_ALREADY_SET": "تم تعيين رقم الهاتف بالفعل.",
|
"PHONE_NUMBER_ALREADY_SET": "تم تعيين رقم الهاتف بالفعل.",
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
"USER": {
|
"USER": {
|
||||||
"PHONE_ALREADY_VERIFIED": "The phone number has already been verified.",
|
"PHONE_ALREADY_VERIFIED": "The phone number has already been verified.",
|
||||||
|
"EMAIL_ALREADY_VERIFIED": "The email address has already been verified.",
|
||||||
"EMAIL_ALREADY_SET": "The email address has already been set.",
|
"EMAIL_ALREADY_SET": "The email address has already been set.",
|
||||||
"EMAIL_ALREADY_TAKEN": "The email address is already in use. Please try another email address.",
|
"EMAIL_ALREADY_TAKEN": "The email address is already in use. Please try another email address.",
|
||||||
"PHONE_NUMBER_ALREADY_SET": "The phone number has already been set.",
|
"PHONE_NUMBER_ALREADY_SET": "The phone number has already been set.",
|
||||||
|
@ -11,8 +11,7 @@ export class UserRepository {
|
|||||||
createUnverifiedUser(data: Partial<User>) {
|
createUnverifiedUser(data: Partial<User>) {
|
||||||
return this.userRepository.save(
|
return this.userRepository.save(
|
||||||
this.userRepository.create({
|
this.userRepository.create({
|
||||||
phoneNumber: data.phoneNumber,
|
email: data.email,
|
||||||
countryCode: data.countryCode,
|
|
||||||
roles: data.roles,
|
roles: data.roles,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
@ -5,7 +5,8 @@ import moment from 'moment';
|
|||||||
import { FindOptionsWhere } from 'typeorm';
|
import { FindOptionsWhere } from 'typeorm';
|
||||||
import { Transactional } from 'typeorm-transactional';
|
import { Transactional } from 'typeorm-transactional';
|
||||||
import { NotificationsService } from '~/common/modules/notification/services';
|
import { NotificationsService } from '~/common/modules/notification/services';
|
||||||
import { CreateUnverifiedUserRequestDto } from '../../auth/dtos/request';
|
import { CustomerService } from '~/customer/services';
|
||||||
|
import { CreateUnverifiedUserRequestDto, VerifyUserRequestDto } from '../../auth/dtos/request';
|
||||||
import { Roles } from '../../auth/enums';
|
import { Roles } from '../../auth/enums';
|
||||||
import {
|
import {
|
||||||
CreateCheckerRequestDto,
|
CreateCheckerRequestDto,
|
||||||
@ -29,6 +30,7 @@ export class UserService {
|
|||||||
private readonly deviceService: DeviceService,
|
private readonly deviceService: DeviceService,
|
||||||
private readonly userTokenService: UserTokenService,
|
private readonly userTokenService: UserTokenService,
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
|
private customerService: CustomerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
findUser(where: FindOptionsWhere<User> | FindOptionsWhere<User>[]) {
|
findUser(where: FindOptionsWhere<User> | FindOptionsWhere<User>[]) {
|
||||||
@ -51,9 +53,18 @@ export class UserService {
|
|||||||
return this.userRepository.update(userId, { phoneNumber, countryCode });
|
return this.userRepository.update(userId, { phoneNumber, countryCode });
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyPhoneNumber(userId: string) {
|
@Transactional()
|
||||||
this.logger.log(`Verifying phone number for user ${userId}`);
|
async verifyUser(userId: string, body: VerifyUserRequestDto) {
|
||||||
return this.userRepository.update(userId, { isPhoneVerified: true });
|
this.logger.log(`Verifying user email with id ${userId}`);
|
||||||
|
await Promise.all([
|
||||||
|
this.customerService.createGuardianCustomer(userId, {
|
||||||
|
firstName: body.firstName,
|
||||||
|
lastName: body.lastName,
|
||||||
|
dateOfBirth: body.dateOfBirth,
|
||||||
|
countryOfResidence: body.countryOfResidence,
|
||||||
|
}),
|
||||||
|
this.userRepository.update(userId, { isEmailVerified: true }),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
findUsers(filters: UserFiltersRequestDto) {
|
findUsers(filters: UserFiltersRequestDto) {
|
||||||
@ -74,26 +85,27 @@ export class UserService {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOrCreateUser({ phoneNumber, countryCode }: CreateUnverifiedUserRequestDto) {
|
@Transactional()
|
||||||
this.logger.log(`Finding or creating user with phone number ${phoneNumber} and country code ${countryCode}`);
|
async findOrCreateUser(body: CreateUnverifiedUserRequestDto) {
|
||||||
const user = await this.userRepository.findOne({ phoneNumber });
|
this.logger.log(`Finding or creating user with email ${body.email}`);
|
||||||
|
const user = await this.userRepository.findOne({ email: body.email });
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
this.logger.log(`User with phone number ${phoneNumber} not found, creating new user`);
|
this.logger.log(`User with email ${body.email} not found, creating new user`);
|
||||||
return this.userRepository.createUnverifiedUser({ phoneNumber, countryCode, roles: [Roles.GUARDIAN] });
|
return this.userRepository.createUnverifiedUser({ email: body.email, roles: [Roles.GUARDIAN] });
|
||||||
}
|
}
|
||||||
if (user && user.roles.includes(Roles.GUARDIAN) && user.isProfileCompleted) {
|
if (user && user.roles.includes(Roles.GUARDIAN) && user.isEmailVerified) {
|
||||||
this.logger.error(`User with phone number ${phoneNumber} already exists`);
|
this.logger.error(`User with email ${body.email} already exists`);
|
||||||
throw new BadRequestException('USER.PHONE_NUMBER_ALREADY_EXISTS');
|
throw new BadRequestException('USER.EMAIL_ALREADY_TAKEN');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user && user.roles.includes(Roles.JUNIOR)) {
|
if (user && user.roles.includes(Roles.JUNIOR)) {
|
||||||
this.logger.error(`User with phone number ${phoneNumber} is an already registered junior`);
|
this.logger.error(`User with email ${body.email} is an already registered junior`);
|
||||||
throw new BadRequestException('USER.JUNIOR_UPGRADE_NOT_SUPPORTED_YET');
|
throw new BadRequestException('USER.JUNIOR_UPGRADE_NOT_SUPPORTED_YET');
|
||||||
//TODO add role Guardian to the existing user and send OTP
|
//TODO add role Guardian to the existing user and send OTP
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.log(`User with phone number ${phoneNumber} and country code ${countryCode} found successfully`);
|
this.logger.log(`User with email ${body.email}`);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
import { forwardRef, Module } from '@nestjs/common';
|
import { forwardRef, Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { NotificationModule } from '~/common/modules/notification/notification.module';
|
import { NotificationModule } from '~/common/modules/notification/notification.module';
|
||||||
|
import { CustomerModule } from '~/customer/customer.module';
|
||||||
import { AdminUserController, UserController } from './controllers';
|
import { AdminUserController, UserController } from './controllers';
|
||||||
import { Device, User, UserRegistrationToken } from './entities';
|
import { Device, User, UserRegistrationToken } from './entities';
|
||||||
import { DeviceRepository, UserRepository, UserTokenRepository } from './repositories';
|
import { DeviceRepository, UserRepository, UserTokenRepository } from './repositories';
|
||||||
import { DeviceService, UserService, UserTokenService } from './services';
|
import { DeviceService, UserService, UserTokenService } from './services';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([User, Device, UserRegistrationToken]), forwardRef(() => NotificationModule)],
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([User, Device, UserRegistrationToken]),
|
||||||
|
forwardRef(() => NotificationModule),
|
||||||
|
forwardRef(() => CustomerModule),
|
||||||
|
],
|
||||||
providers: [UserService, DeviceService, UserRepository, DeviceRepository, UserTokenRepository, UserTokenService],
|
providers: [UserService, DeviceService, UserRepository, DeviceRepository, UserTokenRepository, UserTokenService],
|
||||||
exports: [UserService, DeviceService, UserTokenService],
|
exports: [UserService, DeviceService, UserTokenService],
|
||||||
controllers: [UserController, AdminUserController],
|
controllers: [UserController, AdminUserController],
|
||||||
|
Reference in New Issue
Block a user