mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-08-25 21:59:40 +00:00
add login flow for waiting list demo app
This commit is contained in:
@ -4,7 +4,7 @@ import { Request } from 'express';
|
|||||||
import { DEVICE_ID_HEADER } from '~/common/constants';
|
import { DEVICE_ID_HEADER } from '~/common/constants';
|
||||||
import { AuthenticatedUser, Public } from '~/common/decorators';
|
import { AuthenticatedUser, Public } from '~/common/decorators';
|
||||||
import { AccessTokenGuard } from '~/common/guards';
|
import { AccessTokenGuard } from '~/common/guards';
|
||||||
import { ApiLangRequestHeader } from '~/core/decorators';
|
import { ApiDataResponse, ApiLangRequestHeader } from '~/core/decorators';
|
||||||
import { ResponseFactory } from '~/core/utils';
|
import { ResponseFactory } from '~/core/utils';
|
||||||
import {
|
import {
|
||||||
CreateUnverifiedUserRequestDto,
|
CreateUnverifiedUserRequestDto,
|
||||||
@ -14,9 +14,11 @@ import {
|
|||||||
LoginRequestDto,
|
LoginRequestDto,
|
||||||
RefreshTokenRequestDto,
|
RefreshTokenRequestDto,
|
||||||
SendForgetPasswordOtpRequestDto,
|
SendForgetPasswordOtpRequestDto,
|
||||||
|
SendLoginOtpRequestDto,
|
||||||
SetEmailRequestDto,
|
SetEmailRequestDto,
|
||||||
setJuniorPasswordRequestDto,
|
setJuniorPasswordRequestDto,
|
||||||
SetPasscodeRequestDto,
|
SetPasscodeRequestDto,
|
||||||
|
VerifyLoginOtpRequestDto,
|
||||||
VerifyOtpRequestDto,
|
VerifyOtpRequestDto,
|
||||||
VerifyUserRequestDto,
|
VerifyUserRequestDto,
|
||||||
} from '../dtos/request';
|
} from '../dtos/request';
|
||||||
@ -43,6 +45,20 @@ export class AuthController {
|
|||||||
return ResponseFactory.data(new LoginResponseDto(res, user));
|
return ResponseFactory.data(new LoginResponseDto(res, user));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('login/otp')
|
||||||
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
|
async sendLoginOtp(@Body() data: SendLoginOtpRequestDto) {
|
||||||
|
return this.authService.sendLoginOtp(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('login/verify')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@ApiDataResponse(LoginResponseDto)
|
||||||
|
async verifyLoginOtp(@Body() data: VerifyLoginOtpRequestDto) {
|
||||||
|
const [token, user] = await this.authService.verifyLoginOtp(data);
|
||||||
|
return ResponseFactory.data(new LoginResponseDto(token, user));
|
||||||
|
}
|
||||||
|
|
||||||
@Post('register/set-email')
|
@Post('register/set-email')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
@UseGuards(AccessTokenGuard)
|
@UseGuards(AccessTokenGuard)
|
||||||
|
@ -5,8 +5,10 @@ export * from './forget-password.request.dto';
|
|||||||
export * from './login.request.dto';
|
export * from './login.request.dto';
|
||||||
export * from './refresh-token.request.dto';
|
export * from './refresh-token.request.dto';
|
||||||
export * from './send-forget-password-otp.request.dto';
|
export * from './send-forget-password-otp.request.dto';
|
||||||
|
export * from './send-login-otp.request.dto';
|
||||||
export * from './set-email.request.dto';
|
export * from './set-email.request.dto';
|
||||||
export * from './set-junior-password.request.dto';
|
export * from './set-junior-password.request.dto';
|
||||||
export * from './set-passcode.request.dto';
|
export * from './set-passcode.request.dto';
|
||||||
|
export * from './verify-login-otp.request.dto';
|
||||||
export * from './verify-otp.request.dto';
|
export * from './verify-otp.request.dto';
|
||||||
export * from './verify-user.request.dto';
|
export * from './verify-user.request.dto';
|
||||||
|
9
src/auth/dtos/request/send-login-otp.request.dto.ts
Normal file
9
src/auth/dtos/request/send-login-otp.request.dto.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsEmail } from 'class-validator';
|
||||||
|
import { i18nValidationMessage as i18n } from 'nestjs-i18n';
|
||||||
|
|
||||||
|
export class SendLoginOtpRequestDto {
|
||||||
|
@ApiProperty({ example: 'test@test.com' })
|
||||||
|
@IsEmail({}, { message: i18n('validation.IsEmail', { path: 'general', property: 'auth.email' }) })
|
||||||
|
email!: string;
|
||||||
|
}
|
20
src/auth/dtos/request/verify-login-otp.request.dto.ts
Normal file
20
src/auth/dtos/request/verify-login-otp.request.dto.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsNumberString, MaxLength, MinLength } from 'class-validator';
|
||||||
|
import { i18nValidationMessage as i18n } from 'nestjs-i18n';
|
||||||
|
import { DEFAULT_OTP_LENGTH } from '~/common/modules/otp/constants';
|
||||||
|
import { SendLoginOtpRequestDto } from './send-login-otp.request.dto';
|
||||||
|
|
||||||
|
export class VerifyLoginOtpRequestDto extends SendLoginOtpRequestDto {
|
||||||
|
@ApiProperty({ example: '111111' })
|
||||||
|
@IsNumberString(
|
||||||
|
{ no_symbols: true },
|
||||||
|
{ message: i18n('validation.IsNumberString', { path: 'general', property: 'auth.otp' }) },
|
||||||
|
)
|
||||||
|
@MaxLength(DEFAULT_OTP_LENGTH, {
|
||||||
|
message: i18n('validation.MaxLength', { path: 'general', property: 'auth.otp', length: DEFAULT_OTP_LENGTH }),
|
||||||
|
})
|
||||||
|
@MinLength(DEFAULT_OTP_LENGTH, {
|
||||||
|
message: i18n('validation.MinLength', { path: 'general', property: 'auth.otp', length: DEFAULT_OTP_LENGTH }),
|
||||||
|
})
|
||||||
|
otp!: string;
|
||||||
|
}
|
@ -18,8 +18,10 @@ import {
|
|||||||
ForgetPasswordRequestDto,
|
ForgetPasswordRequestDto,
|
||||||
LoginRequestDto,
|
LoginRequestDto,
|
||||||
SendForgetPasswordOtpRequestDto,
|
SendForgetPasswordOtpRequestDto,
|
||||||
|
SendLoginOtpRequestDto,
|
||||||
SetEmailRequestDto,
|
SetEmailRequestDto,
|
||||||
setJuniorPasswordRequestDto,
|
setJuniorPasswordRequestDto,
|
||||||
|
VerifyLoginOtpRequestDto,
|
||||||
VerifyUserRequestDto,
|
VerifyUserRequestDto,
|
||||||
} from '../dtos/request';
|
} from '../dtos/request';
|
||||||
import { GrantType, Roles } from '../enums';
|
import { GrantType, Roles } from '../enums';
|
||||||
@ -325,6 +327,39 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sendLoginOtp({ email }: SendLoginOtpRequestDto) {
|
||||||
|
const user = await this.userService.findOrCreateByEmail(email);
|
||||||
|
this.logger.log(`Sending login OTP to ${email}`);
|
||||||
|
return this.otpService.generateAndSendOtp({
|
||||||
|
recipient: email,
|
||||||
|
scope: OtpScope.LOGIN,
|
||||||
|
otpType: OtpType.EMAIL,
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyLoginOtp({ email, otp }: VerifyLoginOtpRequestDto): Promise<[ILoginResponse, User]> {
|
||||||
|
const user = await this.userService.findUserOrThrow({ email });
|
||||||
|
|
||||||
|
this.logger.log(`Verifying login OTP for ${email}`);
|
||||||
|
const isOtpValid = await this.otpService.verifyOtp({
|
||||||
|
otpType: OtpType.EMAIL,
|
||||||
|
scope: OtpScope.LOGIN,
|
||||||
|
userId: user.id,
|
||||||
|
value: otp,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isOtpValid) {
|
||||||
|
this.logger.error(`Invalid OTP for user with email ${email}`);
|
||||||
|
throw new BadRequestException('OTP.INVALID_OTP');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.log(`Login OTP verified successfully for ${email}`);
|
||||||
|
|
||||||
|
const token = await this.generateAuthToken(user);
|
||||||
|
return [token, user];
|
||||||
|
}
|
||||||
|
|
||||||
logout(req: Request) {
|
logout(req: Request) {
|
||||||
this.logger.log('Logging out');
|
this.logger.log('Logging out');
|
||||||
const accessToken = req.headers.authorization?.split(' ')[1] as string;
|
const accessToken = req.headers.authorization?.split(' ')[1] as string;
|
||||||
|
@ -56,6 +56,8 @@ export class NotificationsService {
|
|||||||
scope: NotificationScope.USER_INVITED,
|
scope: NotificationScope.USER_INVITED,
|
||||||
channel: NotificationChannel.EMAIL,
|
channel: NotificationChannel.EMAIL,
|
||||||
});
|
});
|
||||||
|
console.log('++++++++++++++++++++++++=');
|
||||||
|
console.log(data);
|
||||||
return this.eventEmitter.emit(EventType.NOTIFICATION_CREATED, notification, data.data);
|
return this.eventEmitter.emit(EventType.NOTIFICATION_CREATED, notification, data.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +73,7 @@ export class NotificationsService {
|
|||||||
|
|
||||||
this.logger.log(`emitting ${EventType.NOTIFICATION_CREATED} event`);
|
this.logger.log(`emitting ${EventType.NOTIFICATION_CREATED} event`);
|
||||||
|
|
||||||
return this.eventEmitter.emit(EventType.NOTIFICATION_CREATED, notification);
|
return this.eventEmitter.emit(EventType.NOTIFICATION_CREATED, notification, { otp });
|
||||||
}
|
}
|
||||||
|
|
||||||
private async sendPushNotification(userId: string, title: string, body: string) {
|
private async sendPushNotification(userId: string, title: string, body: string) {
|
||||||
|
@ -24,6 +24,9 @@ export class Otp {
|
|||||||
@Column('varchar', { name: 'user_id' })
|
@Column('varchar', { name: 'user_id' })
|
||||||
userId!: string;
|
userId!: string;
|
||||||
|
|
||||||
|
@Column('boolean', { default: false, name: 'is_used' })
|
||||||
|
isUsed!: boolean;
|
||||||
|
|
||||||
@ManyToOne(() => User, (user) => user.otp, { onDelete: 'CASCADE' })
|
@ManyToOne(() => User, (user) => user.otp, { onDelete: 'CASCADE' })
|
||||||
@JoinColumn({ name: 'user_id' })
|
@JoinColumn({ name: 'user_id' })
|
||||||
user!: User;
|
user!: User;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
export enum OtpScope {
|
export enum OtpScope {
|
||||||
VERIFY_PHONE = 'VERIFY_PHONE',
|
VERIFY_PHONE = 'VERIFY_PHONE',
|
||||||
FORGET_PASSWORD = 'FORGET_PASSWORD',
|
FORGET_PASSWORD = 'FORGET_PASSWORD',
|
||||||
|
LOGIN = 'LOGIN',
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ export class OtpRepository {
|
|||||||
createOtp(otp: Partial<Otp>) {
|
createOtp(otp: Partial<Otp>) {
|
||||||
return this.otpRepository.save(
|
return this.otpRepository.save(
|
||||||
this.otpRepository.create({
|
this.otpRepository.create({
|
||||||
userId: otp.userId,
|
userId: otp.userId ?? undefined,
|
||||||
value: otp.value,
|
value: otp.value,
|
||||||
scope: otp.scope,
|
scope: otp.scope,
|
||||||
otpType: otp.otpType,
|
otpType: otp.otpType,
|
||||||
@ -31,10 +31,15 @@ export class OtpRepository {
|
|||||||
value: otp.value,
|
value: otp.value,
|
||||||
otpType: otp.otpType,
|
otpType: otp.otpType,
|
||||||
expiresAt: MoreThan(new Date()),
|
expiresAt: MoreThan(new Date()),
|
||||||
|
isUsed: false,
|
||||||
},
|
},
|
||||||
order: {
|
order: {
|
||||||
createdAt: 'DESC',
|
createdAt: 'DESC',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateOtp(id: string, data: Partial<Otp>) {
|
||||||
|
return this.otpRepository.update(id, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,11 +35,16 @@ export class OtpService {
|
|||||||
|
|
||||||
if (!otp) {
|
if (!otp) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`OTP value ${verifyOtpRequest.value} not found for ${verifyOtpRequest.userId} and ${verifyOtpRequest.otpType}`,
|
`OTP value ${verifyOtpRequest.value} not found for ${verifyOtpRequest.userId} and ${verifyOtpRequest.otpType} or used`,
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { affected } = await this.otpRepository.updateOtp(otp.id, { isUsed: true });
|
||||||
|
console.log('+++++++++++++++++++++++++++');
|
||||||
|
console.log(affected);
|
||||||
|
this.logger.log(`OTP verified successfully for ${verifyOtpRequest.userId}`);
|
||||||
|
|
||||||
return !!otp;
|
return !!otp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IsBoolean, IsDateString, IsEnum, IsNotEmpty, IsString, IsUUID } from 'class-validator';
|
import { IsBoolean, IsDateString, IsEnum, IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
|
||||||
import { i18nValidationMessage as i18n } from 'nestjs-i18n';
|
import { i18nValidationMessage as i18n } from 'nestjs-i18n';
|
||||||
import { IsAbove18 } from '~/core/decorators/validations';
|
import { IsAbove18 } from '~/core/decorators/validations';
|
||||||
import { Gender } from '~/customer/enums';
|
import { Gender } from '~/customer/enums';
|
||||||
@ -16,7 +16,8 @@ export class CreateCustomerRequestDto {
|
|||||||
|
|
||||||
@ApiProperty({ example: 'MALE' })
|
@ApiProperty({ example: 'MALE' })
|
||||||
@IsEnum(Gender, { message: i18n('validation.IsEnum', { path: 'general', property: 'customer.gender' }) })
|
@IsEnum(Gender, { message: i18n('validation.IsEnum', { path: 'general', property: 'customer.gender' }) })
|
||||||
gender!: Gender;
|
@IsOptional()
|
||||||
|
gender?: Gender;
|
||||||
|
|
||||||
@ApiProperty({ example: 'JO' })
|
@ApiProperty({ example: 'JO' })
|
||||||
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.countryOfResidence' }) })
|
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.countryOfResidence' }) })
|
||||||
@ -31,39 +32,47 @@ export class CreateCustomerRequestDto {
|
|||||||
@ApiProperty({ example: '999300024' })
|
@ApiProperty({ example: '999300024' })
|
||||||
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.nationalId' }) })
|
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.nationalId' }) })
|
||||||
@IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'customer.nationalId' }) })
|
@IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'customer.nationalId' }) })
|
||||||
nationalId!: string;
|
@IsOptional()
|
||||||
|
nationalId?: string;
|
||||||
|
|
||||||
@ApiProperty({ example: '2021-01-01' })
|
@ApiProperty({ example: '2021-01-01' })
|
||||||
@IsDateString(
|
@IsDateString(
|
||||||
{},
|
{},
|
||||||
{ message: i18n('validation.IsDateString', { path: 'general', property: 'junior.nationalIdExpiry' }) },
|
{ message: i18n('validation.IsDateString', { path: 'general', property: 'junior.nationalIdExpiry' }) },
|
||||||
)
|
)
|
||||||
nationalIdExpiry!: Date;
|
@IsOptional()
|
||||||
|
nationalIdExpiry?: Date;
|
||||||
|
|
||||||
@ApiProperty({ example: 'Employee' })
|
@ApiProperty({ example: 'Employee' })
|
||||||
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.sourceOfIncome' }) })
|
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.sourceOfIncome' }) })
|
||||||
@IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'customer.sourceOfIncome' }) })
|
@IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'customer.sourceOfIncome' }) })
|
||||||
sourceOfIncome!: string;
|
@IsOptional()
|
||||||
|
sourceOfIncome?: string;
|
||||||
|
|
||||||
@ApiProperty({ example: 'Accountant' })
|
@ApiProperty({ example: 'Accountant' })
|
||||||
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.profession' }) })
|
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.profession' }) })
|
||||||
@IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'customer.profession' }) })
|
@IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'customer.profession' }) })
|
||||||
profession!: string;
|
@IsOptional()
|
||||||
|
profession?: string;
|
||||||
|
|
||||||
@ApiProperty({ example: 'Finance' })
|
@ApiProperty({ example: 'Finance' })
|
||||||
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.professionType' }) })
|
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.professionType' }) })
|
||||||
@IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'customer.professionType' }) })
|
@IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'customer.professionType' }) })
|
||||||
professionType!: string;
|
@IsOptional()
|
||||||
|
professionType?: string;
|
||||||
|
|
||||||
@ApiProperty({ example: false })
|
@ApiProperty({ example: false })
|
||||||
@IsBoolean({ message: i18n('validation.IsBoolean', { path: 'general', property: 'junior.isPep' }) })
|
@IsBoolean({ message: i18n('validation.IsBoolean', { path: 'general', property: 'junior.isPep' }) })
|
||||||
isPep!: boolean;
|
@IsOptional()
|
||||||
|
isPep?: boolean;
|
||||||
|
|
||||||
@ApiProperty({ example: 'bf342-3f3f-3f3f-3f3f' })
|
@ApiProperty({ example: 'bf342-3f3f-3f3f-3f3f' })
|
||||||
@IsUUID('4', { message: i18n('validation.IsUUID', { path: 'general', property: 'junior.civilIdFrontId' }) })
|
@IsUUID('4', { message: i18n('validation.IsUUID', { path: 'general', property: 'junior.civilIdFrontId' }) })
|
||||||
civilIdFrontId!: string;
|
@IsOptional()
|
||||||
|
civilIdFrontId?: string;
|
||||||
|
|
||||||
@ApiProperty({ example: 'bf342-3f3f-3f3f-3f3f' })
|
@ApiProperty({ example: 'bf342-3f3f-3f3f-3f3f' })
|
||||||
@IsUUID('4', { message: i18n('validation.IsUUID', { path: 'general', property: 'junior.civilIdBackId' }) })
|
@IsUUID('4', { message: i18n('validation.IsUUID', { path: 'general', property: 'junior.civilIdBackId' }) })
|
||||||
civilIdBackId!: string;
|
@IsOptional()
|
||||||
|
civilIdBackId?: string;
|
||||||
}
|
}
|
||||||
|
@ -87,17 +87,17 @@ export class Customer extends BaseEntity {
|
|||||||
@OneToOne(() => Guardian, (guardian) => guardian.customer, { cascade: true })
|
@OneToOne(() => Guardian, (guardian) => guardian.customer, { cascade: true })
|
||||||
guardian!: Guardian;
|
guardian!: Guardian;
|
||||||
|
|
||||||
@Column('uuid', { name: 'civil_id_front_id' })
|
@Column('uuid', { name: 'civil_id_front_id', nullable: true })
|
||||||
civilIdFrontId!: string;
|
civilIdFrontId!: string;
|
||||||
|
|
||||||
@Column('uuid', { name: 'civil_id_back_id' })
|
@Column('uuid', { name: 'civil_id_back_id', nullable: true })
|
||||||
civilIdBackId!: string;
|
civilIdBackId!: string;
|
||||||
|
|
||||||
@OneToOne(() => Document, (document) => document.customerCivilIdFront)
|
@OneToOne(() => Document, (document) => document.customerCivilIdFront, { nullable: true })
|
||||||
@JoinColumn({ name: 'civil_id_front_id' })
|
@JoinColumn({ name: 'civil_id_front_id' })
|
||||||
civilIdFront!: Document;
|
civilIdFront!: Document;
|
||||||
|
|
||||||
@OneToOne(() => Document, (document) => document.customerCivilIdBack)
|
@OneToOne(() => Document, (document) => document.customerCivilIdBack, { nullable: true })
|
||||||
@JoinColumn({ name: 'civil_id_back_id' })
|
@JoinColumn({ name: 'civil_id_back_id' })
|
||||||
civilIdBack!: Document;
|
civilIdBack!: Document;
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ export class CustomerService {
|
|||||||
throw new BadRequestException('CUSTOMER.ALRADY_EXISTS');
|
throw new BadRequestException('CUSTOMER.ALRADY_EXISTS');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.validateCivilIdForCustomer(userId, body.civilIdFrontId, body.civilIdBackId);
|
// await this.validateCivilIdForCustomer(userId, body.civilIdFrontId, body.civilIdBackId);
|
||||||
|
|
||||||
const customer = await this.customerRepository.createCustomer(userId, body, true);
|
const customer = await this.customerRepository.createCustomer(userId, body, true);
|
||||||
this.logger.log(`customer created for user ${userId}`);
|
this.logger.log(`customer created for user ${userId}`);
|
||||||
@ -166,9 +166,12 @@ export class CustomerService {
|
|||||||
throw new BadRequestException('CUSTOMER.CIVIL_ID_NOT_CREATED_BY_USER');
|
throw new BadRequestException('CUSTOMER.CIVIL_ID_NOT_CREATED_BY_USER');
|
||||||
}
|
}
|
||||||
|
|
||||||
const customerWithTheSameId = await this.customerRepository.findCustomerByCivilId(civilIdFrontId, civilIdBackId);
|
const customerWithTheSameCivilId = await this.customerRepository.findCustomerByCivilId(
|
||||||
|
civilIdFrontId,
|
||||||
|
civilIdBackId,
|
||||||
|
);
|
||||||
|
|
||||||
if (customerWithTheSameId) {
|
if (customerWithTheSameCivilId) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Customer with civil id front ${civilIdFrontId} and civil id back ${civilIdBackId} already exists`,
|
`Customer with civil id front ${civilIdFrontId} and civil id back ${civilIdBackId} already exists`,
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class AddUsedFlagToOtpAndRemoveConstraintsFromCustomers1741087742821 implements MigrationInterface {
|
||||||
|
name = 'AddUsedFlagToOtpAndRemoveConstraintsFromCustomers1741087742821';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "otp" ADD "is_used" boolean NOT NULL DEFAULT false`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "customers" DROP CONSTRAINT "FK_d5f99c497892ce31598ba19a72c"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "customers" DROP CONSTRAINT "FK_2191662d124c56dd968ba01bf18"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "customers" ALTER COLUMN "civil_id_front_id" DROP NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "customers" ALTER COLUMN "civil_id_back_id" DROP NOT NULL`);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "customers" ADD CONSTRAINT "FK_d5f99c497892ce31598ba19a72c" FOREIGN KEY ("civil_id_front_id") REFERENCES "documents"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "customers" ADD CONSTRAINT "FK_2191662d124c56dd968ba01bf18" FOREIGN KEY ("civil_id_back_id") REFERENCES "documents"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "customers" DROP CONSTRAINT "FK_2191662d124c56dd968ba01bf18"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "customers" DROP CONSTRAINT "FK_d5f99c497892ce31598ba19a72c"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "customers" ALTER COLUMN "civil_id_back_id" SET NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "customers" ALTER COLUMN "civil_id_front_id" SET NOT NULL`);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "customers" ADD CONSTRAINT "FK_2191662d124c56dd968ba01bf18" FOREIGN KEY ("civil_id_back_id") REFERENCES "documents"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "customers" ADD CONSTRAINT "FK_d5f99c497892ce31598ba19a72c" FOREIGN KEY ("civil_id_front_id") REFERENCES "documents"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(`ALTER TABLE "otp" DROP COLUMN "is_used"`);
|
||||||
|
}
|
||||||
|
}
|
@ -22,3 +22,4 @@ export * from './1736753223884-add_created_by_to_document_table';
|
|||||||
export * from './1739868002943-add-kyc-status-to-customer';
|
export * from './1739868002943-add-kyc-status-to-customer';
|
||||||
export * from './1739954239949-add-civilid-to-customers-and-update-notifications-settings';
|
export * from './1739954239949-add-civilid-to-customers-and-update-notifications-settings';
|
||||||
export * from './1740045960580-create-user-registration-table';
|
export * from './1740045960580-create-user-registration-table';
|
||||||
|
export * from './1741087742821-add-used-flag-to-otp-and-remove-constraints-from-customers';
|
||||||
|
@ -97,6 +97,25 @@ export class UserService {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findOrCreateByEmail(email: string) {
|
||||||
|
this.logger.log(`Finding or creating user with email ${email} `);
|
||||||
|
const user = await this.userRepository.findOne({ email });
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
this.logger.log(`User with email ${email} not found, creating new user`);
|
||||||
|
return this.userRepository.createUser({ email, roles: [Roles.GUARDIAN] });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user && user.roles.includes(Roles.JUNIOR)) {
|
||||||
|
this.logger.error(`User with email ${email} is an already registered junior`);
|
||||||
|
throw new BadRequestException('USER.JUNIOR_UPGRADE_NOT_SUPPORTED_YET');
|
||||||
|
//TODO add role Guardian to the existing user and send OTP
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.log(`User with email ${email} found successfully`);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
async createUser(data: Partial<User>) {
|
async createUser(data: Partial<User>) {
|
||||||
this.logger.log(`Creating user with data ${JSON.stringify(data)}`);
|
this.logger.log(`Creating user with data ${JSON.stringify(data)}`);
|
||||||
const user = await this.userRepository.createUser(data);
|
const user = await this.userRepository.createUser(data);
|
||||||
|
Reference in New Issue
Block a user