diff --git a/apps/api/src/auth/repository/auth.repository.ts b/apps/api/src/auth/repository/auth.repository.ts index 518dd52b..44828159 100644 --- a/apps/api/src/auth/repository/auth.repository.ts +++ b/apps/api/src/auth/repository/auth.repository.ts @@ -55,4 +55,15 @@ export class AuthRepository implements IAuthRepository { } }) } + + async deleteExpiredOtps(): Promise { + const timeNow = new Date() + await this.prisma.otp.deleteMany({ + where: { + expiresAt: { + lte: new Date(timeNow.getTime()) + } + } + }) + } } diff --git a/apps/api/src/auth/repository/interface.repository.ts b/apps/api/src/auth/repository/interface.repository.ts index 32dd3a0e..4d5d5f0c 100644 --- a/apps/api/src/auth/repository/interface.repository.ts +++ b/apps/api/src/auth/repository/interface.repository.ts @@ -34,4 +34,10 @@ export interface IAuthRepository { * @returns {Promise} - A promise that resolves when the OTP is successfully deleted. */ deleteOtp(email: User['email'], otp: string): Promise + + /** + * Cron Job that runs every hours to delete expired otp + * @returns {Promise} - A promise that resolves when the Expired OTPs are successfully deleted. + */ + deleteExpiredOtps(): Promise } diff --git a/apps/api/src/auth/repository/mock.repository.ts b/apps/api/src/auth/repository/mock.repository.ts index e770fdcc..38559ceb 100644 --- a/apps/api/src/auth/repository/mock.repository.ts +++ b/apps/api/src/auth/repository/mock.repository.ts @@ -20,4 +20,8 @@ export class MockAuthRepository implements IAuthRepository { deleteOtp(email: string, otp: string): Promise { throw new Error('Method not implemented.') } + + deleteExpiredOtps(): Promise { + throw new Error('Method not implemented.') + } } diff --git a/apps/api/src/auth/service/auth.service.ts b/apps/api/src/auth/service/auth.service.ts index ef6770a3..327e53a8 100644 --- a/apps/api/src/auth/service/auth.service.ts +++ b/apps/api/src/auth/service/auth.service.ts @@ -8,6 +8,7 @@ import { } from '@nestjs/common' import { randomUUID } from 'crypto' import { JwtService } from '@nestjs/jwt' +import { Cron, CronExpression } from '@nestjs/schedule' import { UserAuthenticatedResponse } from '../auth.types' import { IMailService, @@ -84,4 +85,14 @@ export class AuthService { token: await this.jwt.signAsync({ id: user.id }) } } + + @Cron(CronExpression.EVERY_HOUR) + async cleanUpExpiredOtps() { + try { + await this.authRepository.deleteExpiredOtps() + this.logger.log('Expired OTPs cleaned up successfully.') + } catch (error) { + this.logger.error(`Error cleaning up expired OTPs: ${error.message}`) + } + } } diff --git a/package.json b/package.json index 7a85a656..ec57ff45 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "@nestjs/jwt": "^10.2.0", "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.3.0", + "@nestjs/schedule": "^4.0.0", "@nestjs/swagger": "^7.1.17", "@prisma/client": "^5.7.1", "@supabase/supabase-js": "^2.39.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7d95a482..abc55520 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@nestjs/platform-express': specifier: ^10.3.0 version: 10.3.0(@nestjs/common@10.3.0)(@nestjs/core@10.3.0) + '@nestjs/schedule': + specifier: ^4.0.0 + version: 4.0.0(@nestjs/common@10.3.0)(@nestjs/core@10.3.0)(reflect-metadata@0.1.14) '@nestjs/swagger': specifier: ^7.1.17 version: 7.1.17(@nestjs/common@10.3.0)(@nestjs/core@10.3.0)(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.14) @@ -2129,6 +2132,20 @@ packages: transitivePeerDependencies: - supports-color + /@nestjs/schedule@4.0.0(@nestjs/common@10.3.0)(@nestjs/core@10.3.0)(reflect-metadata@0.1.14): + resolution: {integrity: sha512-zz4h54m/F/1qyQKvMJCRphmuwGqJltDAkFxUXCVqJBXEs5kbPt93Pza3heCQOcMH22MZNhGlc9DmDMLXVHmgVQ==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 + reflect-metadata: ^0.1.12 + dependencies: + '@nestjs/common': 10.3.0(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) + '@nestjs/core': 10.3.0(@nestjs/common@10.3.0)(@nestjs/platform-express@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) + cron: 3.1.3 + reflect-metadata: 0.1.14 + uuid: 9.0.1 + dev: false + /@nestjs/schematics@10.0.3(typescript@5.2.2): resolution: {integrity: sha512-2BRujK0GqGQ7j1Zpz+obVfskDnnOeVKt5aXoSaVngKo8Oczy8uYCY+R547TQB+Kf35epdfFER2pVnQrX3/It5A==} peerDependencies: @@ -4434,6 +4451,10 @@ packages: dependencies: '@types/node': 18.16.9 + /@types/luxon@3.3.8: + resolution: {integrity: sha512-jYvz8UMLDgy3a5SkGJne8H7VA7zPV2Lwohjx0V8V31+SqAjNmurWMkk9cQhfvlcnXWudBpK9xPM1n4rljOcHYQ==} + dev: false + /@types/mime@1.3.5: resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} dev: true @@ -5907,6 +5928,13 @@ packages: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} dev: true + /cron@3.1.3: + resolution: {integrity: sha512-KVxeKTKYj2eNzN4ElnT6nRSbjbfhyxR92O/Jdp6SH3pc05CDJws59jBrZWEMQlxevCiE6QUTrXy+Im3vC3oD3A==} + dependencies: + '@types/luxon': 3.3.8 + luxon: 3.4.4 + dev: false + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -8976,6 +9004,11 @@ packages: dependencies: yallist: 4.0.0 + /luxon@3.4.4: + resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==} + engines: {node: '>=12'} + dev: false + /lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -11828,6 +11861,11 @@ packages: hasBin: true dev: false + /uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + dev: false + /v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} dev: true