mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-11-26 16:44:54 +00:00
feat: registration jounrey for parents
This commit is contained in:
1
src/common/modules/otp/constants/index.ts
Normal file
1
src/common/modules/otp/constants/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './otp-default.constant';
|
||||
2
src/common/modules/otp/constants/otp-default.constant.ts
Normal file
2
src/common/modules/otp/constants/otp-default.constant.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const DEFAULT_OTP_LENGTH = 6;
|
||||
export const DEFAULT_OTP_DIGIT = '1';
|
||||
@ -0,0 +1,20 @@
|
||||
import { IsNotEmpty } from 'class-validator';
|
||||
import { UserLocale } from '~/core/enums';
|
||||
import { OtpScope, OtpType } from '../../enums';
|
||||
|
||||
export class GenerateOtpRequestDto {
|
||||
@IsNotEmpty()
|
||||
userId!: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
phoneNumber!: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
scope!: OtpScope;
|
||||
|
||||
@IsNotEmpty()
|
||||
language?: UserLocale = UserLocale.ENGLISH;
|
||||
|
||||
@IsNotEmpty()
|
||||
otpType!: OtpType;
|
||||
}
|
||||
2
src/common/modules/otp/dtos/request/index.ts
Normal file
2
src/common/modules/otp/dtos/request/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './generate-otp-request.request.dto';
|
||||
export * from './verify-otp-request.dto';
|
||||
@ -0,0 +1,16 @@
|
||||
import { IsNotEmpty } from 'class-validator';
|
||||
import { OtpScope, OtpType } from '../../enums';
|
||||
|
||||
export class VerifyOtpRequestDto {
|
||||
@IsNotEmpty()
|
||||
userId!: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
scope!: OtpScope;
|
||||
|
||||
@IsNotEmpty()
|
||||
otpType!: OtpType;
|
||||
|
||||
@IsNotEmpty()
|
||||
value!: string;
|
||||
}
|
||||
1
src/common/modules/otp/entities/index.ts
Normal file
1
src/common/modules/otp/entities/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './otp.entity';
|
||||
33
src/common/modules/otp/entities/otp.entity.ts
Normal file
33
src/common/modules/otp/entities/otp.entity.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { Column, CreateDateColumn, Entity, Index, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { User } from '~/auth/entities';
|
||||
import { OtpScope, OtpType } from '../enums';
|
||||
|
||||
@Entity('otp')
|
||||
export class Otp {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@Column('varchar', { length: 255, name: 'value' })
|
||||
value!: string;
|
||||
|
||||
@Index()
|
||||
@Column('varchar', { length: 255, name: 'scope' })
|
||||
scope!: OtpScope;
|
||||
|
||||
@Column('varchar', { length: 255, name: 'otp_type' })
|
||||
otpType!: OtpType;
|
||||
|
||||
@Column('timestamp with time zone', { name: 'expires_at' })
|
||||
expiresAt!: Date;
|
||||
|
||||
@Index()
|
||||
@Column('varchar', { name: 'user_id' })
|
||||
userId!: string;
|
||||
|
||||
@ManyToOne(() => User, (user) => user.otp, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
user!: User;
|
||||
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamp with time zone' })
|
||||
createdAt!: Date;
|
||||
}
|
||||
2
src/common/modules/otp/enums/index.ts
Normal file
2
src/common/modules/otp/enums/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './otp-scope.enum';
|
||||
export * from './otp-type.enum';
|
||||
3
src/common/modules/otp/enums/otp-scope.enum.ts
Normal file
3
src/common/modules/otp/enums/otp-scope.enum.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export enum OtpScope {
|
||||
VERIFY_PHONE = 'VERIFY_PHONE',
|
||||
}
|
||||
4
src/common/modules/otp/enums/otp-type.enum.ts
Normal file
4
src/common/modules/otp/enums/otp-type.enum.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export enum OtpType {
|
||||
SMS = 'SMS',
|
||||
EMAIL = 'EMAIL',
|
||||
}
|
||||
13
src/common/modules/otp/otp.module.ts
Normal file
13
src/common/modules/otp/otp.module.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Otp } from './entities';
|
||||
import { OtpRepository } from './repositories';
|
||||
import { OtpService } from './services/otp.service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Otp])],
|
||||
providers: [OtpService, OtpRepository],
|
||||
exports: [OtpService],
|
||||
})
|
||||
export class OtpModule {}
|
||||
1
src/common/modules/otp/repositories/index.ts
Normal file
1
src/common/modules/otp/repositories/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './otp.repository';
|
||||
40
src/common/modules/otp/repositories/otp.repository.ts
Normal file
40
src/common/modules/otp/repositories/otp.repository.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { MoreThan, Repository } from 'typeorm';
|
||||
import { VerifyOtpRequestDto } from '../dtos/request';
|
||||
import { Otp } from '../entities';
|
||||
const FIVE = 5;
|
||||
const SIXTY = 60;
|
||||
const ONE_THOUSAND = 1000;
|
||||
const FIVE_MINUTES_IN_MILLISECONDS = FIVE * SIXTY * ONE_THOUSAND;
|
||||
@Injectable()
|
||||
export class OtpRepository {
|
||||
constructor(@InjectRepository(Otp) private readonly otpRepository: Repository<Otp>) {}
|
||||
|
||||
createOtp(otp: Partial<Otp>) {
|
||||
return this.otpRepository.save(
|
||||
this.otpRepository.create({
|
||||
userId: otp.userId,
|
||||
value: otp.value,
|
||||
scope: otp.scope,
|
||||
otpType: otp.otpType,
|
||||
expiresAt: new Date(Date.now() + FIVE_MINUTES_IN_MILLISECONDS),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
findOtp(otp: VerifyOtpRequestDto) {
|
||||
return this.otpRepository.findOne({
|
||||
where: {
|
||||
userId: otp.userId,
|
||||
scope: otp.scope,
|
||||
value: otp.value,
|
||||
otpType: otp.otpType,
|
||||
expiresAt: MoreThan(new Date()),
|
||||
},
|
||||
order: {
|
||||
createdAt: 'DESC',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
1
src/common/modules/otp/services/index.ts
Normal file
1
src/common/modules/otp/services/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './otp.service';
|
||||
32
src/common/modules/otp/services/otp.service.ts
Normal file
32
src/common/modules/otp/services/otp.service.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { DEFAULT_OTP_DIGIT, DEFAULT_OTP_LENGTH } from '../constants';
|
||||
import { GenerateOtpRequestDto, VerifyOtpRequestDto } from '../dtos/request';
|
||||
import { OtpRepository } from '../repositories';
|
||||
import { generateRandomOtp } from '../utils';
|
||||
|
||||
@Injectable()
|
||||
export class OtpService {
|
||||
constructor(private readonly configService: ConfigService, private readonly otpRepository: OtpRepository) {}
|
||||
private useMock = this.configService.get<boolean>('USE_MOCK', false);
|
||||
async generateAndSendOtp(sendotpRequest: GenerateOtpRequestDto) {
|
||||
const otp = this.useMock ? DEFAULT_OTP_DIGIT.repeat(DEFAULT_OTP_LENGTH) : generateRandomOtp(DEFAULT_OTP_LENGTH);
|
||||
|
||||
await this.otpRepository.createOtp({ ...sendotpRequest, value: otp });
|
||||
|
||||
this.sendOtp(sendotpRequest, otp);
|
||||
|
||||
return sendotpRequest.phoneNumber.replace(/.(?=.{4})/g, '*');
|
||||
}
|
||||
|
||||
async verifyOtp(verifyOtpRequest: VerifyOtpRequestDto) {
|
||||
const otp = await this.otpRepository.findOtp(verifyOtpRequest);
|
||||
|
||||
return !!otp;
|
||||
}
|
||||
|
||||
private sendOtp(sendotpRequest: GenerateOtpRequestDto, otp: string) {
|
||||
// TODO: send OTP to the user
|
||||
return;
|
||||
}
|
||||
}
|
||||
1
src/common/modules/otp/utils/index.ts
Normal file
1
src/common/modules/otp/utils/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './otp-generator.util';
|
||||
9
src/common/modules/otp/utils/otp-generator.util.ts
Normal file
9
src/common/modules/otp/utils/otp-generator.util.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { getRandomValues } from 'crypto';
|
||||
import { shuffle } from 'lodash';
|
||||
const ZERO = 0;
|
||||
const ONE = 1;
|
||||
export function generateRandomOtp(length: number): string {
|
||||
const u32 = getRandomValues(new Uint32Array(ONE))[ZERO];
|
||||
const randomOtpDigits = u32.toString().substring(ZERO, length).padEnd(length, '0');
|
||||
return shuffle(randomOtpDigits).join('');
|
||||
}
|
||||
Reference in New Issue
Block a user