Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,8 @@ SMTP_PASS=

ASSET_ID_PREFIX=AST
ASSET_ID_START=1000

# Caching Configuration
REDIS_HOST=localhost
REDIS_PORT=6379
CACHE_TTL=300
56 changes: 47 additions & 9 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,71 @@
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CacheModule } from '@nestjs/cache-manager';
import { redisStore } from 'cache-manager-redis-store';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';
import { AuthModule } from './auth/auth.module';
import { CacheService } from './cache/cache.service';

@Module({
imports: [
// Global environment configuration provider
ConfigModule.forRoot({ isGlobal: true }),

// Asynchronous Database Configuration Management
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
type: 'postgres',
host: configService.get('DB_HOST', 'localhost'),
port: configService.get('DB_PORT', 5432),
username: configService.get('DB_USERNAME', 'postgres'),
password: configService.get('DB_PASSWORD', 'password'),
database: configService.get('DB_DATABASE', 'manage_assets'),
host: configService.get<string>('DB_HOST', 'localhost'),
port: parseInt(configService.get<string>('DB_PORT', '5432'), 10),
username: configService.get<string>('DB_USERNAME', 'postgres'),
password: configService.get<string>('DB_PASSWORD', 'password'),
database: configService.get<string>('DB_DATABASE', 'manage_assets'),
autoLoadEntities: true,
synchronize: configService.get('NODE_ENV') === 'development',
synchronize: configService.get<string>('NODE_ENV') === 'development',
}),
inject: [ConfigService],
}),

// #878 [BE-05] Asynchronous Redis Cache Layer Registration
CacheModule.registerAsync({
isGlobal: true,
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => {
const host = configService.get<string>('REDIS_HOST', 'localhost');
const port = parseInt(configService.get<string>('REDIS_PORT', '6379'), 10);
const ttl = parseInt(configService.get<string>('CACHE_TTL', '300'), 10);

return {
store: redisStore as any,
host,
port,
ttl,
// Guard/Fallback: Non-crashing error mitigation loop for missing/offline redis engines
retry_strategy: (options: any) => {
if (options.error && options.error.code === 'ECONNREFUSED') {
return new Error('Redis connection refused. Operating with inline graceful fallback.');
}
return Math.min(options.attempt * 100, 3000); // Backoff connection retry
},
};
},
}),

UsersModule,
AuthModule,
],
controllers: [AppController],
providers: [AppService],
providers: [
AppService,
CacheService, // Registering the shared custom cache service utility directly within global context
],
exports: [
CacheService, // Exporting CacheService so modules implementing custom invalidation can resolve it cleanly
],
})
export class AppModule {}
export class AppModule {}
36 changes: 36 additions & 0 deletions backend/src/cache/cache.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Injectable, Inject, Logger } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';

@Injectable()
export class CacheService {
private readonly logger = new Logger(CacheService.name);

constructor(@Inject(CACHE_MANAGER) private readonly cacheManager: Cache) {}

async get<T>(key: string): Promise<T | null> {
try {
return await this.cacheManager.get<T>(key);
} catch (error) {
this.logger.warn(`Failed to fetch key "${key}" from cache: ${error.message}`);
return null; // Graceful fallback
}
}

async set<T>(key: string, value: T, ttl?: number): Promise<void> {
try {
// Pass configurations safely matching your cache-manager library specification
await this.cacheManager.set(key, value, ttl);
} catch (error) {
this.logger.warn(`Failed to set key "${key}" into cache: ${error.message}`);
}
}

async del(key: string): Promise<void> {
try {
await this.cacheManager.del(key);
} catch (error) {
this.logger.warn(`Failed to delete key "${key}" from cache: ${error.message}`);
}
}
}
27 changes: 27 additions & 0 deletions backend/src/categories/categories.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Controller, Get, Post, Body, Put, Param, Delete, UseInterceptors } from '@nestjs/common';
import { CacheInterceptor, CacheKey } from '@nestjs/cache-manager';
import { CacheService } from '../cache/cache.service';

@Controller('categories')
export class CategoriesController {

Check failure on line 6 in backend/src/categories/categories.controller.ts

View workflow job for this annotation

GitHub Actions / Backend (NestJS)

'Put' is defined but never used
private readonly CATEGORIES_CACHE_KEY = 'categories_list_all';

Check failure on line 7 in backend/src/categories/categories.controller.ts

View workflow job for this annotation

GitHub Actions / Backend (NestJS)

'Param' is defined but never used

Check failure on line 8 in backend/src/categories/categories.controller.ts

View workflow job for this annotation

GitHub Actions / Backend (NestJS)

'Delete' is defined but never used
constructor(private readonly cacheService: CacheService) {}

@Get()
@UseInterceptors(CacheInterceptor)
@CacheKey('categories_list_all')
async getAllCategories() {
// Database retrieval execution logic goes here
return [{ id: 1, name: 'DeFi Vaults' }];
}

@Post()
async createCategory(@Body() body: any) {
// 1. Process standard write operations to database repository context

// 2. Clear out the cached asset layout lists instantly to invalidate stale states
await this.cacheService.del(this.CATEGORIES_CACHE_KEY);
return { success: true };
}
}
7 changes: 6 additions & 1 deletion backend/src/mail/mail.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ export class MailService {

async sendPasswordResetEmail(email: string, resetLink: string) {
this.logger.log(`[MailService] Sending password reset email to ${email}. Link: ${resetLink}`);
// In a real application, implement NodeMailer or similar here.
// Inn a real application, implement NodeMailer or similar here.
}
}
// async sendPasswordResetEmail(email: string, resetLink: string) {
// this.logger.log(`[MailService] Sending password reset email to ${email}. Link: ${resetLink}`);
// // Inn a real application, implement NodeMailer or similar here.
// }
// }
Loading