Skip to content

Commit

Permalink
pulled latest changes and resolved merge conflicts.
Browse files Browse the repository at this point in the history
  • Loading branch information
neoandmatrix committed Dec 23, 2024
2 parents cafb16d + 2d71008 commit a96841d
Show file tree
Hide file tree
Showing 17 changed files with 164 additions and 97 deletions.
7 changes: 0 additions & 7 deletions src/constants/kafkajs-consumer-options.ts

This file was deleted.

10 changes: 10 additions & 0 deletions src/constants/sports-enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,14 @@ enum SportsEnum {
Basketball = 'Basketball',
Volleyball = 'Volleyball',
}

export const SportsEnumList = [
SportsEnum.Football,
SportsEnum.Badminton,
SportsEnum.Basketball,
SportsEnum.Cricket,
SportsEnum.Football,
SportsEnum.Volleyball,
];

export default SportsEnum;
23 changes: 23 additions & 0 deletions src/services/apis/otp/dto/generateOtp.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { date, z } from 'zod';

// DTO for incoming OTP payload. This ensures the payload structure and validates fields.
/**
* The email of the user to send OTP.
* @example "[email protected]"
* */

export const OtpValidation = z.object({
email: z.string().email(),
});

/**
* The email of the user to send OTP.
* @example "
* */
export const VerifyOtpValidation = z.object({
email: z.string().email(),
otp: z.string().trim(),
});

export type OtpDto = z.infer<typeof OtpValidation>;
export type VerifyOtpDto = z.infer<typeof VerifyOtpValidation>;
19 changes: 0 additions & 19 deletions src/services/apis/otp/dto/genrateOtp.dto.ts

This file was deleted.

19 changes: 13 additions & 6 deletions src/services/apis/otp/generateOtp.controller.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import { Body, Controller, Post } from '@nestjs/common';
import { GenrateOtpService } from './genrateOtp.service';
import { OtpDto, VerifyOtpDto } from './dto/genrateOtp.dto';
import { GenerateOtpService } from './generateOtp.service';
import {
OtpDto,
OtpValidation,
VerifyOtpDto,
VerifyOtpValidation,
} from './dto/generateOtp.dto';
import { Public } from '../auth/decorators/public.decorator';

@Controller('otp')
export class GenerateOtpController {
constructor(private readonly genrateOtpService: GenrateOtpService) {}
constructor(private readonly generateOtpService: GenerateOtpService) {}

@Public()
@Post('generate')
async create(@Body() genrateOtpDto: OtpDto) {
return await this.genrateOtpService.enqueueOtpJob(genrateOtpDto);
async create(@Body() generateOtpDto: OtpDto) {
generateOtpDto = OtpValidation.parse(generateOtpDto);
return await this.generateOtpService.enqueueOtpJob(generateOtpDto);
}

@Public()
@Post('verify')
async verify(@Body() verifyOtpDto: VerifyOtpDto) {
return (await this.genrateOtpService.compareOtp(verifyOtpDto))
verifyOtpDto = VerifyOtpValidation.parse(verifyOtpDto);
return (await this.generateOtpService.compareOtp(verifyOtpDto))
? 'OTP is correct'
: 'OTP is incorrect';
}
Expand Down
9 changes: 4 additions & 5 deletions src/services/apis/otp/generateOtp.module.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Module } from '@nestjs/common';
import { GenerateOtpController } from './generateOtp.controller';
import { QueueModule } from 'src/services/bullmq/queue.module';
import { GenrateOtpService } from './genrateOtp.service';
import { GenerateOtpService } from './generateOtp.service';

@Module({
imports: [QueueModule],
imports: [],
controllers: [GenerateOtpController],
providers: [GenrateOtpService],
exports: [GenrateOtpService], // not exporting services as no need in testing
providers: [GenerateOtpService],
exports: [GenerateOtpService], // not exporting services as no need in testing (??)
})
export class GenerateOtpModule {}
46 changes: 46 additions & 0 deletions src/services/apis/otp/generateOtp.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { OtpProducer } from 'src/services/bullmq/producers/otp.producer';
import { OtpDto, VerifyOtpDto } from './dto/generateOtp.dto';
// import { processEmail } from './helpers/generateOtp.helper';
import { OtpQueueProcessor } from 'src/services/bullmq/processors/otp.processor';
import { compareHashedString } from 'src/common/hashing';
import { RedisService } from 'src/services/redis/redis.service';
import { generateKey } from 'src/services/bullmq/constants/generate-keys';

@Injectable()
export class GenerateOtpService {
constructor(
private readonly otpProducer: OtpProducer,
private readonly redisService: RedisService,
) {}

async enqueueOtpJob(createOtpDto: OtpDto) {
const email = createOtpDto['email'].trim();
const resp = await this.otpProducer.pushForAsyncMailing(
`process-otp`,
{ email },
{
removeOnComplete: true,
},
);
return resp;
}

async compareOtp(
verifyOtpDto: VerifyOtpDto,
removeEntryAfterCheck = false,
): Promise<boolean | BadRequestException> {
const key = generateKey(verifyOtpDto.email);
const val = await this.redisService.get(key);

if (!val) {
throw new BadRequestException(
'The OTP has expired or no request for an OTP was found. Please try requesting a new OTP.',
);
}

const resp = await compareHashedString(verifyOtpDto.otp, val);
if (removeEntryAfterCheck) await this.redisService.del(key);
return resp;
}
}
15 changes: 0 additions & 15 deletions src/services/apis/otp/genrateOtp.helper.ts

This file was deleted.

27 changes: 0 additions & 27 deletions src/services/apis/otp/genrateOtp.service.ts

This file was deleted.

15 changes: 15 additions & 0 deletions src/services/apis/otp/helpers/generateOtp.helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// import { z } from 'zod';

// const emailSchema = z.string().email();

// export function processEmail(input: { email: string }): { email: string } {
// const parsedEmail = emailSchema.safeParse(input.email);

// if (!parsedEmail.success) {
// throw new Error('Invalid email: Must be a valid email format');
// }

// return {
// email: input.email.trim(),
// };
// }
4 changes: 2 additions & 2 deletions src/services/apis/profiles/schemas/profiles.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { HydratedDocument, Types } from 'mongoose';
import { SoftDeleteSchema } from 'src/common/soft-delete-schema';
import EnsureObjectId from 'src/common/EnsureObjectId';
import { Users } from '../../users/schemas/users.schema';
import SportsEnum from 'src/constants/sports-enum';
import SportsEnum, { SportsEnumList } from 'src/constants/sports-enum';

export type ProfilesDocument = HydratedDocument<Profiles>;

Expand All @@ -13,7 +13,7 @@ export type ProfilesDocument = HydratedDocument<Profiles>;
export class Profiles extends SoftDeleteSchema {
@Prop({
type: String,
enum: Object.values(SportsEnum),
enum: SportsEnumList,
required: true,
})
sport: SportsEnum;
Expand Down
28 changes: 18 additions & 10 deletions src/services/apis/users/users.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ import { Users } from './schemas/users.schema';
import { Public } from '../auth/decorators/public.decorator';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
import { CreateUsersDTO } from './dto/users.dto';
import { GenerateOtpService } from '../otp/generateOtp.service';
@Controller('users')
export class UsersController {
constructor(
private readonly usersService: UsersService,
private jwtService: JwtService,
private readonly jwtService: JwtService,
private readonly generateOtpService: GenerateOtpService,
) {}

@Get()
Expand All @@ -35,7 +38,20 @@ export class UsersController {
@Public()
@Post()
async create(@Body() createUsersDto: Users) {
await this.verifyOTP(createUsersDto);
/**
* @todo use zod for validations...
*/
if (!createUsersDto.email || !createUsersDto['otp']) {
throw new BadRequestException('Email or OTP not provided!');
}

await this.generateOtpService.compareOtp(
{
email: createUsersDto.email.trim(),
otp: String(createUsersDto['otp']).trim(),
},
true, // removeEntryAfterCheck
);

const saltOrRounds = 10;
const password = await bcrypt.hash(createUsersDto.password, saltOrRounds);
Expand All @@ -45,7 +61,6 @@ export class UsersController {
password,
})) as Users;

// await this.removeOTP(createUsersDto.email);
const sanitizedUser = this.usersService.sanitizeUser(user);
const payload = { sub: { id: user._id }, user };

Expand All @@ -68,11 +83,4 @@ export class UsersController {
async delete(@Param('id') id, @Query() query, @User() user) {
return await this.usersService._remove(id, query, user);
}

async verifyOTP(createUsersDto: Users) {
if (!Object.keys(createUsersDto).includes('otp')) {
throw new BadRequestException('OTP not provided!');
}
console.log({ createUsersDto });
}
}
2 changes: 2 additions & 0 deletions src/services/apis/users/users.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import { UsersService } from './users.service';
import { Users, UsersSchema } from './schemas/users.schema';
import { APP_GUARD } from '@nestjs/core';
import { RolesGuard } from './roles.guard';
import { GenerateOtpModule } from '../otp/generateOtp.module';

@Module({
imports: [
MongooseModule.forFeature([{ name: Users.name, schema: UsersSchema }]),
GenerateOtpModule,
],
controllers: [UsersController],
providers: [
Expand Down
3 changes: 3 additions & 0 deletions src/services/bullmq/constants/generate-keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const generateKey = (email: string): string =>
`verification::email:${email}`;
// more keys
17 changes: 12 additions & 5 deletions src/services/bullmq/processors/otp.processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { OTP_QUEUE } from '../constants/queues';
import { RedisService } from 'src/services/redis/redis.service';
import { Job } from 'bullmq';
import { OTP_TTL } from 'src/constants/otp.constants';
import { hashString } from 'src/common/hashing';
import { compareHashedString, hashString } from 'src/common/hashing';
import { OtpJob } from '../jobs/otp.jobs';
import generateRandomNumber from 'src/common/generate-random-number';
import { Injectable } from '@nestjs/common';
Expand All @@ -19,7 +19,7 @@ export class OtpQueueProcessor extends WorkerHost {
super();
}
otp: string = generateRandomNumber();
async process(job: Job): Promise<void> {
async process(job: Job) {
try {
const {
data: { email },
Expand All @@ -28,8 +28,15 @@ export class OtpQueueProcessor extends WorkerHost {
console.log(`OTP for ${email} is ${this.otp} and stored in Redis`);
await this.mailerService.sendMail(email, this.otp, 'partials/otp.hbs');
console.log(`OTP email sent to ${email}`);
return {
success: true,
};
} catch (error) {
console.error('Error in processing OTP job', error);
return {
success: false,
message: error,
};
}
}

Expand All @@ -40,14 +47,14 @@ export class OtpQueueProcessor extends WorkerHost {
await this.redisService.set(key, value, OTP_TTL);
}

async comapreOtp(email: string, otp: string): Promise<boolean> {
const key = await generateKey(email);
async compareOtp(email: string, otp: string): Promise<boolean> {
const key = generateKey(email);
const hashedOtp = await hashString(otp);
const storedOtp = await this.redisService.get(key);
return hashedOtp === storedOtp;
}
}

async function generateKey(email: string): Promise<string> {
function generateKey(email: string): string {
return `verification::email:${email}`;
}
7 changes: 6 additions & 1 deletion src/services/bullmq/producers/otp.producer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ import { InjectQueue } from '@nestjs/bullmq';
@Injectable()
export class OtpProducer {
constructor(@InjectQueue(OTP_QUEUE) private otpQueue: Queue) {}
async pushForAsyncStream(
async pushForAsyncMailing(
jobName: string,
data: Record<string, any>,
options?: JobsOptions,
) {
await this.otpQueue.add(jobName, data, options);
console.log(`Job "${jobName}" added to the queue successfully.`);
return {
success: true,
message: `Your OTP request is being processed. We'll send the OTP to your email shortly.`,
jobName,
};
}
}
Loading

0 comments on commit a96841d

Please sign in to comment.