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
8 changes: 5 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -346,15 +346,17 @@ EVOAI_ENABLED=false

# Cache - Environment variables
# Redis Cache enabled
CACHE_REDIS_ENABLED=true
# Set to true only if you have a Redis server available.
# When false (or URI is empty), the app uses local cache instead.
CACHE_REDIS_ENABLED=false
CACHE_REDIS_URI=redis://localhost:6379/6
CACHE_REDIS_TTL=604800
# Prefix serves to differentiate data from one installation to another that are using the same redis
CACHE_REDIS_PREFIX_KEY=evolution
# Enabling this variable will save the connection information in Redis and not in the database.
CACHE_REDIS_SAVE_INSTANCES=false
# Local Cache enabled
CACHE_LOCAL_ENABLED=false
# Local Cache enabled (recommended when Redis is not available)
CACHE_LOCAL_ENABLED=true

# Amazon S3 - Environment variables
S3_ENABLED=false
Expand Down
29 changes: 28 additions & 1 deletion src/api/guards/instance.guard.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { InstanceDto } from '@api/dto/instance.dto';
import { cache, prismaRepository, waMonitor } from '@api/server.module';
import { CacheConf, configService } from '@config/env.config';
import { Logger } from '@config/logger.config';
import { BadRequestException, ForbiddenException, InternalServerErrorException, NotFoundException } from '@exceptions';
import { NextFunction, Request, Response } from 'express';

const logger = new Logger('InstanceGuard');

async function getInstance(instanceName: string) {
try {
const cacheConf = configService.get<CacheConf>('CACHE');
Expand All @@ -22,6 +25,23 @@ async function getInstance(instanceName: string) {
}
}

async function isInstanceOnlyInDatabase(instanceName: string): Promise<boolean> {
const inMemory = !!waMonitor.waInstances[instanceName];
if (inMemory) return false;

const dbRecords = await prismaRepository.instance.findMany({ where: { name: instanceName } });
return dbRecords.length > 0;
}

async function removeStaleInstance(instanceName: string): Promise<void> {
try {
await waMonitor.cleaningStoreData(instanceName);
logger.warn(`Removed stale database record for instance "${instanceName}" (not loaded in memory)`);
} catch (error) {
logger.error(`Failed to remove stale instance "${instanceName}": ${error}`);
}
}

export async function instanceExistsGuard(req: Request, _: Response, next: NextFunction) {
if (req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) {
return next();
Expand All @@ -43,7 +63,14 @@ export async function instanceLoggedGuard(req: Request, _: Response, next: NextF
if (req.originalUrl.includes('/instance/create')) {
const instance = req.body as InstanceDto;
if (await getInstance(instance.instanceName)) {
throw new ForbiddenException(`This name "${instance.instanceName}" is already in use.`);
if (await isInstanceOnlyInDatabase(instance.instanceName)) {
logger.warn(
`Instance "${instance.instanceName}" exists in database but not in memory (stale after restart). Cleaning up for re-creation.`,
);
await removeStaleInstance(instance.instanceName);
} else {
throw new ForbiddenException(`This name "${instance.instanceName}" is already in use.`);
}
}

if (waMonitor.waInstances[instance.instanceName]) {
Expand Down
9 changes: 9 additions & 0 deletions src/cache/rediscache.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ class Redis {
}

getConnection(): RedisClientType {
if (!this.conf?.ENABLED) {
return null;
}

if (!this.conf?.URI) {
this.logger.warn('CACHE_REDIS_ENABLED is true but CACHE_REDIS_URI is empty β€” skipping Redis connection');
return null;
}

if (this.connected) {
return this.client;
} else {
Expand Down