change otp

This commit is contained in:
2026-04-09 10:32:25 +03:30
parent e61211829c
commit 58e1d97294
9 changed files with 98 additions and 88 deletions
+2
View File
@@ -24,10 +24,12 @@ import { LearningModule } from './modules/learnings/learning.module';
import { CriticismModule } from './modules/criticisms/criticisms.module';
import { AnnouncementModule } from './modules/announcements/announcement.module';
import { TicketModule } from './modules/ticket/ticket.module';
import { cacheConfig } from './config/cache.config';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true, cache: true }),
CacheModule.registerAsync(cacheConfig()),
MikroOrmModule.forRootAsync(dataBaseConfig),
UserModule,
UtilsModule,
+27
View File
@@ -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 }),
],
};
},
};
}
+3 -7
View File
@@ -11,10 +11,7 @@ import { MikroOrmModule } from '@mikro-orm/nestjs';
import { AdminAuthGuard } from './guards/adminAuth.guard';
import { RefreshToken } from './entities/refresh-token.entity';
import { NotificationsModule } from '../notification/notifications.module';
import { OtpRepository } from './repository/otp.repository';
import { OtpService } from './services/otp.service';
import { Otp } from './entities/otp.entity';
import { AuthCrone } from './crone/auth.crone';
@Module({
imports: [
@@ -35,13 +32,12 @@ import { AuthCrone } from './crone/auth.crone';
inject: [ConfigService],
}),
forwardRef(() => AdminModule),
MikroOrmModule.forFeature([RefreshToken, Otp]),
MikroOrmModule.forFeature([RefreshToken]),
forwardRef(() => NotificationsModule),
UtilsModule
],
controllers: [AuthController],
providers: [AuthService, TokensService, AdminAuthGuard, OtpRepository, OtpService, AuthCrone],
providers: [AuthService, TokensService, AdminAuthGuard, OtpService],
exports: [AdminAuthGuard],
})
export class AuthModule { }
-25
View File
@@ -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
}
}
-16
View File
@@ -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);
}
}
+11 -22
View File
@@ -1,50 +1,39 @@
import { Injectable } from "@nestjs/common";
import { OtpRepository } from "../repository/otp.repository";
import { EntityManager } from "@mikro-orm/postgresql";
import { ConfigService } from "@nestjs/config";
import { CacheService } from "src/modules/util/cache.service";
@Injectable()
export class OtpService {
private OTP_EXPIRATION_TIME: number;// in seconds
constructor(
private readonly otpRepository: OtpRepository,
private readonly configService: ConfigService,
private readonly em: EntityManager
private readonly cacheService: CacheService
) {
this.OTP_EXPIRATION_TIME = this.configService.get<number>('OTP_EXPIRATION_TIME') ?? 240;
}
async set(phone: string, code: string) {
let otpRecord = await this.otpRepository.findOne({ phone })
const expiresAt = new Date(Date.now() + this.OTP_EXPIRATION_TIME * 1000)
const key = `otp:${phone}`
if (!otpRecord) {
otpRecord = this.otpRepository.create({
phone,
code,
expiresAt
})
} else {
otpRecord.code = code
otpRecord.expiresAt = expiresAt
}
await this.em.persistAndFlush(otpRecord)
await this.cacheService.set(key, code, this.OTP_EXPIRATION_TIME)
return { successs: true }
}
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) {
await this.otpRepository.nativeDelete({ phone })
const key = `otp:${phone}`
await this.cacheService.del(key)
return { successs: true }
}
}
+50
View File
@@ -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);
}
}
+5 -4
View File
@@ -1,10 +1,11 @@
import { Module } from '@nestjs/common';
import { CacheService } from './cache.service';
@Module({
imports: [],
controllers: [],
providers: [ ],
exports: [],
providers: [CacheService],
exports: [CacheService],
})
export class UtilsModule {}
export class UtilsModule { }