This commit is contained in:
2025-11-09 11:25:26 +03:30
commit 06f814b331
62 changed files with 31547 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
import KeyvRedis from '@keyv/redis';
import Keyv from 'keyv';
import type { CacheModuleAsyncOptions } from '@nestjs/cache-manager';
import { ConfigService } from '@nestjs/config';
import { Logger } from '@nestjs/common'; // 1. Import Logger
export function cacheConfig(): CacheModuleAsyncOptions {
// 2. Create a logger instance
const logger = new Logger('CacheConfig');
return {
isGlobal: true,
inject: [ConfigService],
useFactory: (configService: ConfigService) => {
const ttl = configService.get<number>('CACHE_TTL', 120);
const namespace = configService.get<string>('CACHE_NAMESPACE', 'app-cache');
const redisUri = configService.getOrThrow<string>('REDIS_URI');
// 3. Create the KeyvRedis store first
const keyvRedisStore = new KeyvRedis(redisUri);
// 4. Attach event listeners
keyvRedisStore.client.on('connect', () => {
logger.log('✅ Redis connected successfully.');
});
keyvRedisStore.client.on('error', (error) => {
logger.error('❌ Redis connection error:', error);
});
// 5. Pass the store to Keyv
const store = new Keyv({
store: keyvRedisStore, // Use the instance
namespace,
ttl: ttl * 1000,
});
return {
store,
ttl,
};
},
};
}
+16
View File
@@ -0,0 +1,16 @@
import type { HttpModuleAsyncOptions } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config';
export function httpConfig(): HttpModuleAsyncOptions {
return {
inject: [ConfigService],
global: true,
useFactory: (configService: ConfigService) => {
return {
timeout: configService.getOrThrow<number>('AXIOS_TIMEOUT'),
headers: { 'Content-Type': 'application/json' },
global: true,
};
},
};
}
+96
View File
@@ -0,0 +1,96 @@
import { defineConfig, PostgreSqlDriver } from '@mikro-orm/postgresql';
import * as dotenv from 'dotenv';
// 1. Load environment variables from your .env file
dotenv.config();
// 2. Read variables directly from process.env
const DB_PASS = process.env.DB_PASS;
const DB_USER = process.env.DB_USER;
const DB_HOST = process.env.DB_HOST;
const DB_PORT = process.env.DB_PORT ? +process.env.DB_PORT : 5432;
const DB_NAME = process.env.DB_NAME;
const isProduction = process.env.NODE_ENV === 'production';
// 3. Add checks to ensure variables are loaded
if (!DB_PASS || !DB_USER || !DB_HOST || !DB_PORT || !DB_NAME) {
throw new Error('One or more database environment variables are not set.');
}
const encodedPassword = encodeURIComponent(DB_PASS);
// 4. Export the result of defineConfig as the default
export default defineConfig({
entities: ['dist/**/*.entity.js'],
entitiesTs: ['src/**/*.entity.ts'],
driver: PostgreSqlDriver,
dbName: DB_NAME,
debug: !isProduction,
clientUrl: `postgres://${DB_USER}:${encodedPassword}@${DB_HOST}:${DB_PORT}`,
ensureDatabase: isProduction
? false
: {
forceCheck: true,
create: true,
schema: 'update',
},
forceUtcTimezone: true,
pool: {
min: isProduction ? 5 : 2,
max: isProduction ? 20 : 10,
idleTimeoutMillis: 30000,
acquireTimeoutMillis: 30000,
reapIntervalMillis: 1000,
createTimeoutMillis: 15000,
destroyTimeoutMillis: 5000,
},
// 5. Use console for CLI logging, not the injected Nest logger
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
logger: isProduction ? console.log.bind(console) : console.debug.bind(console),
schemaGenerator: {
disableForeignKeys: false,
},
migrations: {
path: './database/migrations',
pathTs: './database/migrations',
tableName: 'migrations',
transactional: true,
allOrNothing: true,
dropTables: false,
safe: true,
emit: 'ts',
},
connect: true,
allowGlobalContext: true,
driverOptions: {
connection: {
keepAlive: true,
keepAliveInitialDelayMillis: 10000,
statement_timeout: 60000,
query_timeout: 60000,
idle_in_transaction_session_timeout: 60000,
connectionTimeoutMillis: 10000,
application_name: DB_NAME,
},
connectionRetries: 5,
connectionRetryDelay: 3000,
validateConnection: (connection: any) => {
try {
return !!(connection && !connection.closed);
} catch (e: unknown) {
// Use console.error directly
console.error(`Connection validation failed: ${(e as Error).message}`);
return false;
}
},
poolErrorHandler: (err: Error) => {
// Use console.error directly
console.error(`PostgreSQL pool error: ${err.message}`);
if (err.message.includes('ECONNRESET') || err.message.includes('Connection terminated unexpectedly')) {
console.warn('Connection reset detected, will attempt to reconnect');
return true;
}
return false;
},
},
});
+109
View File
@@ -0,0 +1,109 @@
import type { MikroOrmModuleAsyncOptions } from '@mikro-orm/nestjs';
import { ConfigService } from '@nestjs/config';
import { PostgreSqlDriver } from '@mikro-orm/postgresql';
// import { defineConfig } from '@mikro-orm/postgresql';
import type { Logger } from '@nestjs/common';
import { MIKRO_ORM_QUERY_LOGGER } from '../common/constants';
import { mikroOrmQueryLoggerProvider } from '../common/providers/mikro-orm-logger.provider';
const dataBaseConfig: MikroOrmModuleAsyncOptions = {
// entities: ['dist/**/*.entity.js'],
// entitiesTs: ['src/**/*.entity.ts'],
// dbName: 'your_db_name',
// type: 'postgresql',
// user: 'your_user',
// password: 'your_password',
// host: 'localhost',
// port: 5432,
// debug: true,
inject: [ConfigService, MIKRO_ORM_QUERY_LOGGER],
providers: [mikroOrmQueryLoggerProvider],
useFactory: (configService: ConfigService, logger: Logger) => {
const DB_PASS = configService.getOrThrow<string>('DB_PASS');
const DB_USER = configService.getOrThrow<string>('DB_USER');
const DB_HOST = configService.getOrThrow<string>('DB_HOST');
const DB_PORT = configService.getOrThrow<number>('DB_PORT');
const DB_NAME = configService.getOrThrow<string>('DB_NAME');
const encodedPassword = encodeURIComponent(DB_PASS);
const isProduction = configService.getOrThrow<string>('NODE_ENV') === 'production';
return ({
// entities: ['dist/**/*.entity.js'],
entitiesTs: ['src/**/*.entity.ts'],
driver: PostgreSqlDriver,
autoLoadEntities: true,
dbName: DB_NAME,
debug: !isProduction,
clientUrl: `postgres://${DB_USER}:${encodedPassword}@${DB_HOST}:${DB_PORT}`,
ensureDatabase: isProduction
? false
: {
forceCheck: true,
create: true,
schema: 'update',
},
forceUtcTimezone: true,
pool: {
min: isProduction ? 5 : 2,
max: isProduction ? 20 : 10,
idleTimeoutMillis: 30000,
acquireTimeoutMillis: 30000,
reapIntervalMillis: 1000,
createTimeoutMillis: 15000,
destroyTimeoutMillis: 5000,
},
logger: isProduction ? message => logger.log(message) : message => logger.debug(message),
schemaGenerator: {
// createForeignKey: !isProduction,
disableForeignKeys: false,
// createIndex: true,
},
migrations: {
path: './database/migrations',
pathTs: './database/migrations',
tableName: 'migrations',
transactional: true,
allOrNothing: true,
dropTables: false,
safe: true,
emit: 'ts',
},
connect: true,
allowGlobalContext: true,
driverOptions: {
connection: {
keepAlive: true,
keepAliveInitialDelayMillis: 10000,
statement_timeout: 60000,
query_timeout: 60000,
idle_in_transaction_session_timeout: 60000,
connectionTimeoutMillis: 10000,
application_name: configService.getOrThrow<string>('DB_NAME'),
},
connectionRetries: 5,
connectionRetryDelay: 3000,
validateConnection: (connection: any) => {
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return !!(connection && !connection.closed);
} catch (e: unknown) {
logger.error(`Connection validation failed: ${(e as Error).message}`);
return false;
}
},
poolErrorHandler: (err: Error) => {
logger.error(`PostgreSQL pool error: ${err.message}`);
if (err.message.includes('ECONNRESET') || err.message.includes('Connection terminated unexpectedly')) {
logger.warn('Connection reset detected, will attempt to reconnect');
return true;
}
return false;
},
},
});
},
};
export default dataBaseConfig;
+22
View File
@@ -0,0 +1,22 @@
import type { INestApplication } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
export function getSwaggerConfig(app: INestApplication) {
const config = new DocumentBuilder()
.setTitle('Tahavol API')
.setDescription('API documentation for Tahavol backend')
.setVersion('1.0')
.addBearerAuth() // optional: for JWT endpoints
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, document, {
swaggerOptions: {
persistAuthorization: true,
displayRequestDuration: true,
filter: true,
showExtensions: true,
},
});
}