change otp
This commit is contained in:
@@ -24,10 +24,12 @@ import { LearningModule } from './modules/learnings/learning.module';
|
|||||||
import { CriticismModule } from './modules/criticisms/criticisms.module';
|
import { CriticismModule } from './modules/criticisms/criticisms.module';
|
||||||
import { AnnouncementModule } from './modules/announcements/announcement.module';
|
import { AnnouncementModule } from './modules/announcements/announcement.module';
|
||||||
import { TicketModule } from './modules/ticket/ticket.module';
|
import { TicketModule } from './modules/ticket/ticket.module';
|
||||||
|
import { cacheConfig } from './config/cache.config';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({ isGlobal: true, cache: true }),
|
ConfigModule.forRoot({ isGlobal: true, cache: true }),
|
||||||
|
CacheModule.registerAsync(cacheConfig()),
|
||||||
MikroOrmModule.forRootAsync(dataBaseConfig),
|
MikroOrmModule.forRootAsync(dataBaseConfig),
|
||||||
UserModule,
|
UserModule,
|
||||||
UtilsModule,
|
UtilsModule,
|
||||||
|
|||||||
Executable
+27
@@ -0,0 +1,27 @@
|
|||||||
|
import { Keyv } from 'keyv';
|
||||||
|
import KeyvRedis from '@keyv/redis';
|
||||||
|
import type { CacheModuleAsyncOptions } from '@nestjs/cache-manager';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { CacheableMemory } from 'cacheable';
|
||||||
|
|
||||||
|
export function cacheConfig(): CacheModuleAsyncOptions {
|
||||||
|
return {
|
||||||
|
isGlobal: true,
|
||||||
|
inject: [ConfigService],
|
||||||
|
useFactory: async (configService: ConfigService) => {
|
||||||
|
const redisUri = configService.get('REDIS_URI');
|
||||||
|
const namespace = configService.get('CACHE_NAMESPACE', 'app-cache');
|
||||||
|
const ttl = +configService.get<number>('CACHE_TTL', 3600); //1 hour
|
||||||
|
|
||||||
|
return {
|
||||||
|
stores: [
|
||||||
|
new Keyv({
|
||||||
|
store: new CacheableMemory({ ttl: ttl * 1000, lruSize: 5000 }),
|
||||||
|
namespace: namespace
|
||||||
|
}),
|
||||||
|
new KeyvRedis(redisUri, { namespace: namespace }),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -11,10 +11,7 @@ import { MikroOrmModule } from '@mikro-orm/nestjs';
|
|||||||
import { AdminAuthGuard } from './guards/adminAuth.guard';
|
import { AdminAuthGuard } from './guards/adminAuth.guard';
|
||||||
import { RefreshToken } from './entities/refresh-token.entity';
|
import { RefreshToken } from './entities/refresh-token.entity';
|
||||||
import { NotificationsModule } from '../notification/notifications.module';
|
import { NotificationsModule } from '../notification/notifications.module';
|
||||||
import { OtpRepository } from './repository/otp.repository';
|
|
||||||
import { OtpService } from './services/otp.service';
|
import { OtpService } from './services/otp.service';
|
||||||
import { Otp } from './entities/otp.entity';
|
|
||||||
import { AuthCrone } from './crone/auth.crone';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -35,13 +32,12 @@ import { AuthCrone } from './crone/auth.crone';
|
|||||||
inject: [ConfigService],
|
inject: [ConfigService],
|
||||||
}),
|
}),
|
||||||
forwardRef(() => AdminModule),
|
forwardRef(() => AdminModule),
|
||||||
|
MikroOrmModule.forFeature([RefreshToken]),
|
||||||
MikroOrmModule.forFeature([RefreshToken, Otp]),
|
|
||||||
forwardRef(() => NotificationsModule),
|
forwardRef(() => NotificationsModule),
|
||||||
|
UtilsModule
|
||||||
],
|
],
|
||||||
controllers: [AuthController],
|
controllers: [AuthController],
|
||||||
providers: [AuthService, TokensService, AdminAuthGuard, OtpRepository, OtpService, AuthCrone],
|
providers: [AuthService, TokensService, AdminAuthGuard, OtpService],
|
||||||
exports: [AdminAuthGuard],
|
exports: [AdminAuthGuard],
|
||||||
})
|
})
|
||||||
export class AuthModule { }
|
export class AuthModule { }
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
|
||||||
import { Cron } from '@nestjs/schedule';
|
|
||||||
import { OtpRepository } from '../repository/otp.repository';
|
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class AuthCrone {
|
|
||||||
private readonly logger = new Logger(AuthCrone.name);
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly otpRepository: OtpRepository
|
|
||||||
) { }
|
|
||||||
|
|
||||||
@Cron('*/59 * * * *', {
|
|
||||||
name: 'completeOldDeliveredOrders',
|
|
||||||
timeZone: 'UTC',
|
|
||||||
})
|
|
||||||
async removeExpireOtp() {
|
|
||||||
this.logger.log("Delete expire otps")
|
|
||||||
await this.otpRepository.nativeDelete({
|
|
||||||
expiresAt: { $lt: new Date() }
|
|
||||||
})
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { Entity, Index, OptionalProps, PrimaryKey, Property } from '@mikro-orm/core';
|
|
||||||
|
|
||||||
@Entity({ tableName: 'otp' })
|
|
||||||
@Index({ properties: ['phone'] })
|
|
||||||
export class Otp {
|
|
||||||
[OptionalProps]?: 'createdAt'
|
|
||||||
|
|
||||||
@PrimaryKey({ type: 'varchar', })
|
|
||||||
phone!: string;
|
|
||||||
|
|
||||||
@Property()
|
|
||||||
code!: string;
|
|
||||||
|
|
||||||
@Property({ columnType: 'timestamptz' })
|
|
||||||
expiresAt!: Date
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
|
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { Otp } from '../entities/otp.entity';
|
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class OtpRepository extends EntityRepository<Otp> {
|
|
||||||
constructor(
|
|
||||||
readonly em: EntityManager,
|
|
||||||
) {
|
|
||||||
super(em, Otp);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,50 +1,39 @@
|
|||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
import { OtpRepository } from "../repository/otp.repository";
|
|
||||||
import { EntityManager } from "@mikro-orm/postgresql";
|
|
||||||
import { ConfigService } from "@nestjs/config";
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
import { CacheService } from "src/modules/util/cache.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OtpService {
|
export class OtpService {
|
||||||
private OTP_EXPIRATION_TIME: number;// in seconds
|
private OTP_EXPIRATION_TIME: number;// in seconds
|
||||||
constructor(
|
constructor(
|
||||||
private readonly otpRepository: OtpRepository,
|
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
private readonly em: EntityManager
|
private readonly cacheService: CacheService
|
||||||
) {
|
) {
|
||||||
this.OTP_EXPIRATION_TIME = this.configService.get<number>('OTP_EXPIRATION_TIME') ?? 240;
|
this.OTP_EXPIRATION_TIME = this.configService.get<number>('OTP_EXPIRATION_TIME') ?? 240;
|
||||||
}
|
}
|
||||||
|
|
||||||
async set(phone: string, code: string) {
|
async set(phone: string, code: string) {
|
||||||
let otpRecord = await this.otpRepository.findOne({ phone })
|
const key = `otp:${phone}`
|
||||||
const expiresAt = new Date(Date.now() + this.OTP_EXPIRATION_TIME * 1000)
|
|
||||||
|
|
||||||
if (!otpRecord) {
|
await this.cacheService.set(key, code, this.OTP_EXPIRATION_TIME)
|
||||||
otpRecord = this.otpRepository.create({
|
|
||||||
phone,
|
|
||||||
code,
|
|
||||||
expiresAt
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
otpRecord.code = code
|
|
||||||
otpRecord.expiresAt = expiresAt
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.em.persistAndFlush(otpRecord)
|
|
||||||
|
|
||||||
return { successs: true }
|
return { successs: true }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(phone: string) {
|
async get(phone: string) {
|
||||||
|
|
||||||
let otpRecord = await this.otpRepository.findOne({ phone, expiresAt: { $gt: new Date() } })
|
|
||||||
|
|
||||||
return otpRecord?.code
|
const key = `otp:${phone}`
|
||||||
|
|
||||||
|
return this.cacheService.get(key)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(phone: string) {
|
async delete(phone: string) {
|
||||||
await this.otpRepository.nativeDelete({ phone })
|
const key = `otp:${phone}`
|
||||||
|
|
||||||
|
await this.cacheService.del(key)
|
||||||
|
|
||||||
return { successs: true }
|
return { successs: true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Executable
+50
@@ -0,0 +1,50 @@
|
|||||||
|
import { CACHE_MANAGER, Cache } from '@nestjs/cache-manager';
|
||||||
|
import { Inject, Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CacheService implements OnModuleInit {
|
||||||
|
private readonly logger = new Logger(CacheService.name);
|
||||||
|
|
||||||
|
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
|
||||||
|
|
||||||
|
async onModuleInit() {
|
||||||
|
|
||||||
|
// Should log 'Keyv' or 'KeyvRedis'. If it says 'Memory', the config is still wrong.
|
||||||
|
this.logger.log('Pinging Redis to check connection...');
|
||||||
|
try {
|
||||||
|
await this.set('ping', 'pong', 10); // TTL in seconds
|
||||||
|
const result = await this.get('ping');
|
||||||
|
|
||||||
|
if (result === 'pong') {
|
||||||
|
this.logger.log('✅ Redis connection successful (set/get test passed).');
|
||||||
|
} else {
|
||||||
|
this.logger.error(`❌ Redis connection test failed. Expected 'pong', got '${result}'.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.del('ping');
|
||||||
|
} catch (error: any) {
|
||||||
|
this.logger.error('❌ Failed to connect to Redis during ping test:', error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get value from cache */
|
||||||
|
async get<T>(key: string): Promise<T | undefined> {
|
||||||
|
return await this.cacheManager.get<T>(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set value in cache with TTL in seconds */
|
||||||
|
async set(key: string, value: any, ttlSeconds: number) {
|
||||||
|
// cache-manager expects TTL in seconds
|
||||||
|
await this.cacheManager.set(key, value, ttlSeconds * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Delete a key from cache */
|
||||||
|
async del(key: string) {
|
||||||
|
await this.cacheManager.del(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get remaining TTL in seconds */
|
||||||
|
async getTtl(key: string): Promise<number | undefined> {
|
||||||
|
return await this.cacheManager.ttl(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { CacheService } from './cache.service';
|
||||||
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [],
|
imports: [],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
providers: [ ],
|
providers: [CacheService],
|
||||||
exports: [],
|
exports: [CacheService],
|
||||||
})
|
})
|
||||||
export class UtilsModule {}
|
export class UtilsModule { }
|
||||||
|
|||||||
Reference in New Issue
Block a user