From 61bd5b34847d0fbaea14581f61bce1c41ef564ed Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Thu, 18 Apr 2024 10:39:24 -0300 Subject: [PATCH] fix: Adjusts in redis for save instances --- CHANGELOG.md | 3 +- Docker/.env.example | 21 ++- .../evolution-api-all-services/.env.example | 21 ++- Dockerfile | 12 +- src/api/abstract/abstract.cache.ts | 6 + src/api/controllers/instance.controller.ts | 3 +- src/api/guards/instance.guard.ts | 9 +- src/api/server.module.ts | 3 +- src/api/services/cache.service.ts | 37 ++++++ src/api/services/monitor.service.ts | 23 ++-- .../whatsapp/whatsapp.baileys.service.ts | 40 +++--- .../whatsapp/whatsapp.business.service.ts | 3 +- src/cache/localcache.ts | 13 ++ src/cache/rediscache.ts | 34 ++++- src/config/env.config.ts | 14 +- src/dev-env.yml | 8 +- src/libs/redis.client.ts | 123 ------------------ .../use-multi-file-auth-state-redis-db.ts | 13 +- 18 files changed, 178 insertions(+), 208 deletions(-) delete mode 100644 src/libs/redis.client.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index f58e9e2f..b103298e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,12 @@ ### Fixed * Revert fix audio encoding * Recovering messages lost with redis cache +* Adjusts in redis for save instances * Adjusts in proxy * Revert pull request #523 * Added instance name on logs * Added support for Spanish -* Fix error: invalid operator. The allowed operators for identifier are equal_to,not_equal_to +* Fix error: invalid operator. The allowed operators for identifier are equal_to,not_equal_to in chatwoot # 1.7.2 (2024-04-12 17:31) diff --git a/Docker/.env.example b/Docker/.env.example index 865ef877..04dd0805 100644 --- a/Docker/.env.example +++ b/Docker/.env.example @@ -33,7 +33,10 @@ CLEAN_STORE_CHATS=true # Permanent data storage DATABASE_ENABLED=false -DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true +DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin & +readPreference=primary & +ssl=false & +directConnection=true DATABASE_CONNECTION_DB_PREFIX_NAME=evdocker # Choose the data you want to save in the application's database or store @@ -43,10 +46,6 @@ DATABASE_SAVE_MESSAGE_UPDATE=false DATABASE_SAVE_DATA_CONTACTS=false DATABASE_SAVE_DATA_CHATS=false -REDIS_ENABLED=false -REDIS_URI=redis://redis:6379 -REDIS_PREFIX_KEY=evdocker - RABBITMQ_ENABLED=false RABBITMQ_RABBITMQ_MODE=global RABBITMQ_EXCHANGE_NAME=evolution_exchange @@ -73,7 +72,7 @@ WEBHOOK_GLOBAL_URL='' WEBHOOK_GLOBAL_ENABLED=false # With this option activated, you work with a url per webhook event, respecting the global url and the name of each event WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=false -## Set the events you want to hear +## Set the events you want to hear WEBHOOK_EVENTS_APPLICATION_STARTUP=false WEBHOOK_EVENTS_QRCODE_UPDATED=true WEBHOOK_EVENTS_MESSAGES_SET=true @@ -129,6 +128,14 @@ CHATWOOT_MESSAGE_READ=false # false | true CHATWOOT_IMPORT_DATABASE_CONNECTION_URI=postgres://user:password@hostname:port/dbname CHATWOOT_IMPORT_DATABASE_PLACEHOLDER_MEDIA_MESSAGE=true +CACHE_REDIS_ENABLED=false +CACHE_REDIS_URI=redis://redis:6379 +CACHE_REDIS_PREFIX_KEY=evolution +CACHE_REDIS_TTL=604800 +CACHE_REDIS_SAVE_INSTANCES=false +CACHE_LOCAL_ENABLED=false +CACHE_LOCAL_TTL=604800 + # Defines an authentication type for the api # We recommend using the apikey because it will allow you to use a custom token, # if you use jwt, a random token will be generated and may be expired and you will have to generate a new token @@ -143,4 +150,4 @@ AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=true AUTHENTICATION_JWT_EXPIRIN_IN=0 AUTHENTICATION_JWT_SECRET='L=0YWt]b2w[WF>#>:&E`' -LANGUAGE=en # pt-BR, en \ No newline at end of file +LANGUAGE=en # pt-BR, en diff --git a/Docker/evolution-api-all-services/.env.example b/Docker/evolution-api-all-services/.env.example index a28ad50f..d08fee49 100644 --- a/Docker/evolution-api-all-services/.env.example +++ b/Docker/evolution-api-all-services/.env.example @@ -33,7 +33,10 @@ CLEAN_STORE_CHATS=true # Permanent data storage DATABASE_ENABLED=true -DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true +DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin & +readPreference=primary & +ssl=false & +directConnection=true DATABASE_CONNECTION_DB_PREFIX_NAME=evolution # Choose the data you want to save in the application's database or store @@ -43,10 +46,6 @@ DATABASE_SAVE_MESSAGE_UPDATE=false DATABASE_SAVE_DATA_CONTACTS=false DATABASE_SAVE_DATA_CHATS=false -REDIS_ENABLED=true -REDIS_URI=redis://redis:6379 -REDIS_PREFIX_KEY=evolution - # Global Webhook Settings # Each instance's Webhook URL and events will be requested at the time it is created ## Define a global webhook that will listen for enabled events from all instances @@ -54,7 +53,7 @@ WEBHOOK_GLOBAL_URL='' WEBHOOK_GLOBAL_ENABLED=false # With this option activated, you work with a url per webhook event, respecting the global url and the name of each event WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=false -## Set the events you want to hear +## Set the events you want to hear WEBHOOK_EVENTS_APPLICATION_STARTUP=false WEBHOOK_EVENTS_QRCODE_UPDATED=true WEBHOOK_EVENTS_MESSAGES_SET=true @@ -87,6 +86,14 @@ CONFIG_SESSION_PHONE_NAME=chrome # Set qrcode display limit QRCODE_LIMIT=30 +CACHE_REDIS_ENABLED=false +CACHE_REDIS_URI=redis://redis:6379 +CACHE_REDIS_PREFIX_KEY=evolution +CACHE_REDIS_TTL=604800 +CACHE_REDIS_SAVE_INSTANCES=false +CACHE_LOCAL_ENABLED=false +CACHE_LOCAL_TTL=604800 + # Defines an authentication type for the api # We recommend using the apikey because it will allow you to use a custom token, # if you use jwt, a random token will be generated and may be expired and you will have to generate a new token @@ -109,4 +116,4 @@ AUTHENTICATION_INSTANCE_NAME=evolution AUTHENTICATION_INSTANCE_WEBHOOK_URL='' AUTHENTICATION_INSTANCE_CHATWOOT_ACCOUNT_ID=1 AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN=123456 -AUTHENTICATION_INSTANCE_CHATWOOT_URL='' \ No newline at end of file +AUTHENTICATION_INSTANCE_CHATWOOT_URL='' diff --git a/Dockerfile b/Dockerfile index b913a37e..a5e5c8b2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -58,10 +58,6 @@ ENV DATABASE_SAVE_MESSAGE_UPDATE=false ENV DATABASE_SAVE_DATA_CONTACTS=false ENV DATABASE_SAVE_DATA_CHATS=false -ENV REDIS_ENABLED=false -ENV REDIS_URI=redis://redis:6379 -ENV REDIS_PREFIX_KEY=evolution - ENV RABBITMQ_ENABLED=false ENV RABBITMQ_MODE=global ENV RABBITMQ_EXCHANGE_NAME=evolution_exchange @@ -129,6 +125,14 @@ ENV QRCODE_COLOR=#198754 ENV TYPEBOT_API_VERSION=latest +ENV CACHE_REDIS_ENABLED=false +ENV CACHE_REDIS_URI=redis://redis:6379 +ENV CACHE_REDIS_PREFIX_KEY=evolution +ENV CACHE_REDIS_TTL=604800 +ENV CACHE_REDIS_SAVE_INSTANCES=false +ENV CACHE_LOCAL_ENABLED=false +ENV CACHE_LOCAL_TTL=604800 + ENV AUTHENTICATION_TYPE=apikey ENV AUTHENTICATION_API_KEY=B6D711FCDE4D4FD5936544120E713976 diff --git a/src/api/abstract/abstract.cache.ts b/src/api/abstract/abstract.cache.ts index caad2691..2d93f323 100644 --- a/src/api/abstract/abstract.cache.ts +++ b/src/api/abstract/abstract.cache.ts @@ -1,13 +1,19 @@ export interface ICache { get(key: string): Promise; + hGet(key: string, field: string): Promise; + set(key: string, value: any, ttl?: number): void; + hSet(key: string, field: string, value: any): Promise; + has(key: string): Promise; keys(appendCriteria?: string): Promise; delete(key: string | string[]): Promise; + hDelete(key: string, field: string): Promise; + deleteAll(appendCriteria?: string): Promise; } diff --git a/src/api/controllers/instance.controller.ts b/src/api/controllers/instance.controller.ts index 2ca0b031..abe1dd3b 100644 --- a/src/api/controllers/instance.controller.ts +++ b/src/api/controllers/instance.controller.ts @@ -6,7 +6,6 @@ import { v4 } from 'uuid'; import { ConfigService, HttpServer, WaBusiness } from '../../config/env.config'; import { Logger } from '../../config/logger.config'; import { BadRequestException, InternalServerErrorException } from '../../exceptions'; -import { RedisCache } from '../../libs/redis.client'; import { InstanceDto, SetPresenceDto } from '../dto/instance.dto'; import { ChatwootService } from '../integrations/chatwoot/services/chatwoot.service'; import { RabbitmqService } from '../integrations/rabbitmq/services/rabbitmq.service'; @@ -41,7 +40,7 @@ export class InstanceController { private readonly typebotService: TypebotService, private readonly integrationService: IntegrationService, private readonly proxyService: ProxyController, - private readonly cache: RedisCache, + private readonly cache: CacheService, private readonly chatwootCache: CacheService, private readonly messagesLostCache: CacheService, ) {} diff --git a/src/api/guards/instance.guard.ts b/src/api/guards/instance.guard.ts index 2214bd43..84cabb12 100644 --- a/src/api/guards/instance.guard.ts +++ b/src/api/guards/instance.guard.ts @@ -2,7 +2,7 @@ import { NextFunction, Request, Response } from 'express'; import { existsSync } from 'fs'; import { join } from 'path'; -import { configService, Database, Redis } from '../../config/env.config'; +import { CacheConf, configService, Database } from '../../config/env.config'; import { INSTANCE_DIR } from '../../config/path.config'; import { BadRequestException, @@ -17,12 +17,13 @@ import { cache, waMonitor } from '../server.module'; async function getInstance(instanceName: string) { try { const db = configService.get('DATABASE'); - const redisConf = configService.get('REDIS'); + const cacheConf = configService.get('CACHE'); const exists = !!waMonitor.waInstances[instanceName]; - if (redisConf.ENABLED) { - const keyExists = await cache.keyExists(); + if (cacheConf.REDIS.ENABLED && cacheConf.REDIS.SAVE_INSTANCES) { + const keyExists = await cache.has(instanceName); + return exists || keyExists; } diff --git a/src/api/server.module.ts b/src/api/server.module.ts index e41eefeb..97df81a3 100644 --- a/src/api/server.module.ts +++ b/src/api/server.module.ts @@ -3,7 +3,6 @@ import { configService } from '../config/env.config'; import { eventEmitter } from '../config/event.config'; import { Logger } from '../config/logger.config'; import { dbserver } from '../libs/db.connect'; -import { RedisCache } from '../libs/redis.client'; import { ChatController } from './controllers/chat.controller'; import { GroupController } from './controllers/group.controller'; import { InstanceController } from './controllers/instance.controller'; @@ -107,7 +106,7 @@ export const repository = new RepositoryBroker( dbserver?.getClient(), ); -export const cache = new RedisCache(); +export const cache = new CacheService(new CacheEngine(configService, 'instance').getEngine()); const chatwootCache = new CacheService(new CacheEngine(configService, ChatwootService.name).getEngine()); const messagesLostCache = new CacheService(new CacheEngine(configService, 'baileys').getEngine()); diff --git a/src/api/services/cache.service.ts b/src/api/services/cache.service.ts index 0db39a44..caf3dbfa 100644 --- a/src/api/services/cache.service.ts +++ b/src/api/services/cache.service.ts @@ -1,3 +1,5 @@ +import { BufferJSON } from '@whiskeysockets/baileys'; + import { Logger } from '../../config/logger.config'; import { ICache } from '../abstract/abstract.cache'; @@ -20,6 +22,21 @@ export class CacheService { return this.cache.get(key); } + public async hGet(key: string, field: string) { + try { + const data = await this.cache.hGet(key, field); + + if (data) { + return JSON.parse(data, BufferJSON.reviver); + } + + return null; + } catch (error) { + this.logger.error(error); + return null; + } + } + async set(key: string, value: any) { if (!this.cache) { return; @@ -28,6 +45,16 @@ export class CacheService { this.cache.set(key, value); } + public async hSet(key: string, field: string, value: any) { + try { + const json = JSON.stringify(value, BufferJSON.replacer); + + await this.cache.hSet(key, field, json); + } catch (error) { + this.logger.error(error); + } + } + async has(key: string) { if (!this.cache) { return; @@ -44,6 +71,16 @@ export class CacheService { return this.cache.delete(key); } + async hDelete(key: string, field: string) { + try { + await this.cache.hDelete(key, field); + return true; + } catch (error) { + this.logger.error(error); + return false; + } + } + async deleteAll(appendCriteria?: string) { if (!this.cache) { return; diff --git a/src/api/services/monitor.service.ts b/src/api/services/monitor.service.ts index 87c00774..df6b333a 100644 --- a/src/api/services/monitor.service.ts +++ b/src/api/services/monitor.service.ts @@ -5,11 +5,10 @@ import { Db } from 'mongodb'; import { Collection } from 'mongoose'; import { join } from 'path'; -import { Auth, ConfigService, Database, DelInstance, HttpServer, Redis } from '../../config/env.config'; +import { Auth, CacheConf, ConfigService, Database, DelInstance, HttpServer } from '../../config/env.config'; import { Logger } from '../../config/logger.config'; import { INSTANCE_DIR, STORE_DIR } from '../../config/path.config'; import { NotFoundException } from '../../exceptions'; -import { RedisCache } from '../../libs/redis.client'; import { AuthModel, ChamaaiModel, @@ -34,7 +33,7 @@ export class WAMonitoringService { private readonly eventEmitter: EventEmitter2, private readonly configService: ConfigService, private readonly repository: RepositoryBroker, - private readonly cache: RedisCache, + private readonly cache: CacheService, private readonly chatwootCache: CacheService, private readonly messagesLostCache: CacheService, ) { @@ -44,7 +43,7 @@ export class WAMonitoringService { this.noConnection(); Object.assign(this.db, configService.get('DATABASE')); - Object.assign(this.redis, configService.get('REDIS')); + Object.assign(this.redis, configService.get('CACHE')); this.dbInstance = this.db.ENABLED ? this.repository.dbServer?.db(this.db.CONNECTION.DB_PREFIX_NAME + '-instances') @@ -52,7 +51,7 @@ export class WAMonitoringService { } private readonly db: Partial = {}; - private readonly redis: Partial = {}; + private readonly redis: Partial = {}; private dbInstance: Db; @@ -213,7 +212,7 @@ export class WAMonitoringService { }); this.logger.verbose('instance files deleted: ' + name); }); - } else if (!this.redis.ENABLED) { + } else if (!this.redis.REDIS.ENABLED && !this.redis.REDIS.SAVE_INSTANCES) { const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' }); for await (const dirent of dir) { if (dirent.isDirectory()) { @@ -248,10 +247,9 @@ export class WAMonitoringService { return; } - if (this.redis.ENABLED) { + if (this.redis.REDIS.ENABLED && this.redis.REDIS.SAVE_INSTANCES) { this.logger.verbose('cleaning up instance in redis: ' + instanceName); - this.cache.reference = instanceName; - await this.cache.delAll(); + await this.cache.delete(instanceName); return; } @@ -304,7 +302,7 @@ export class WAMonitoringService { this.logger.verbose('Loading instances'); try { - if (this.redis.ENABLED) { + if (this.redis.REDIS.ENABLED && this.redis.REDIS.SAVE_INSTANCES) { await this.loadInstancesFromRedis(); } else if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) { await this.loadInstancesFromDatabase(); @@ -377,12 +375,11 @@ export class WAMonitoringService { private async loadInstancesFromRedis() { this.logger.verbose('Redis enabled'); - await this.cache.connect(this.redis as Redis); - const keys = await this.cache.getInstanceKeys(); + const keys = await this.cache.keys(); if (keys?.length > 0) { this.logger.verbose('Reading instance keys and setting instances'); - await Promise.all(keys.map((k) => this.setInstance(k.split(':')[1]))); + await Promise.all(keys.map((k) => this.setInstance(k.split(':')[2]))); } else { this.logger.verbose('No instance keys found'); } diff --git a/src/api/services/whatsapp/whatsapp.baileys.service.ts b/src/api/services/whatsapp/whatsapp.baileys.service.ts index ac7520b7..732f82a0 100644 --- a/src/api/services/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/services/whatsapp/whatsapp.baileys.service.ts @@ -55,11 +55,10 @@ import qrcode, { QRCodeToDataURLOptions } from 'qrcode'; import qrcodeTerminal from 'qrcode-terminal'; import sharp from 'sharp'; -import { ConfigService, ConfigSessionPhone, Database, Log, QrCode, Redis } from '../../../config/env.config'; +import { CacheConf, ConfigService, ConfigSessionPhone, Database, Log, QrCode } from '../../../config/env.config'; import { INSTANCE_DIR } from '../../../config/path.config'; import { BadRequestException, InternalServerErrorException, NotFoundException } from '../../../exceptions'; import { dbserver } from '../../../libs/db.connect'; -import { RedisCache } from '../../../libs/redis.client'; import { makeProxyAgent } from '../../../utils/makeProxyAgent'; import { useMultiFileAuthStateDb } from '../../../utils/use-multi-file-auth-state-db'; import { useMultiFileAuthStateRedisDb } from '../../../utils/use-multi-file-auth-state-redis-db'; @@ -126,7 +125,7 @@ export class BaileysStartupService extends WAStartupService { public readonly configService: ConfigService, public readonly eventEmitter: EventEmitter2, public readonly repository: RepositoryBroker, - public readonly cache: RedisCache, + public readonly cache: CacheService, public readonly chatwootCache: CacheService, public readonly messagesLostCache: CacheService, ) { @@ -149,19 +148,23 @@ export class BaileysStartupService extends WAStartupService { public mobile: boolean; private async recoveringMessages() { - setTimeout(async () => { - this.logger.info('Recovering messages'); - this.messagesLostCache.keys().then((keys) => { - keys.forEach(async (key) => { - const message = await this.messagesLostCache.get(key.split(':')[2]); + const cacheConf = this.configService.get('CACHE'); - if (message.messageStubParameters && message.messageStubParameters[0] === 'Message absent from node') { - this.logger.verbose('Message absent from node, retrying to send, key: ' + key.split(':')[2]); - await this.client.sendMessageAck(JSON.parse(message.messageStubParameters[1], BufferJSON.reviver)); - } + if ((cacheConf?.REDIS?.ENABLED && cacheConf?.REDIS?.URI !== '') || cacheConf?.LOCAL?.ENABLED) { + setTimeout(async () => { + this.logger.info('Recovering messages'); + this.messagesLostCache.keys().then((keys) => { + keys.forEach(async (key) => { + const message = await this.messagesLostCache.get(key.split(':')[2]); + + if (message.messageStubParameters && message.messageStubParameters[0] === 'Message absent from node') { + this.logger.verbose('Message absent from node, retrying to send, key: ' + key.split(':')[2]); + await this.client.sendMessageAck(JSON.parse(message.messageStubParameters[1], BufferJSON.reviver)); + } + }); }); - }); - }, 30000); + }, 30000); + } } public get connectionStatus() { @@ -456,12 +459,11 @@ export class BaileysStartupService extends WAStartupService { private async defineAuthState() { this.logger.verbose('Defining auth state'); const db = this.configService.get('DATABASE'); - const redis = this.configService.get('REDIS'); + const cache = this.configService.get('CACHE'); - if (redis?.ENABLED) { - this.logger.verbose('Redis enabled'); - this.cache.reference = this.instance.name; - return await useMultiFileAuthStateRedisDb(this.cache); + if (cache?.REDIS.ENABLED && cache?.REDIS.SAVE_INSTANCES) { + this.logger.info('Redis enabled'); + return await useMultiFileAuthStateRedisDb(this.instance.name, this.cache); } if (db.SAVE_DATA.INSTANCE && db.ENABLED) { diff --git a/src/api/services/whatsapp/whatsapp.business.service.ts b/src/api/services/whatsapp/whatsapp.business.service.ts index 23ae3621..1ed2ddcd 100644 --- a/src/api/services/whatsapp/whatsapp.business.service.ts +++ b/src/api/services/whatsapp/whatsapp.business.service.ts @@ -7,7 +7,6 @@ import { getMIMEType } from 'node-mime-types'; import { ConfigService, Database, WaBusiness } from '../../../config/env.config'; import { BadRequestException, InternalServerErrorException } from '../../../exceptions'; -import { RedisCache } from '../../../libs/redis.client'; import { NumberBusiness } from '../../dto/chat.dto'; import { ContactMessage, @@ -34,7 +33,7 @@ export class BusinessStartupService extends WAStartupService { public readonly configService: ConfigService, public readonly eventEmitter: EventEmitter2, public readonly repository: RepositoryBroker, - public readonly cache: RedisCache, + public readonly cache: CacheService, public readonly chatwootCache: CacheService, public readonly messagesLostCache: CacheService, ) { diff --git a/src/cache/localcache.ts b/src/cache/localcache.ts index 323b0343..54a51d90 100644 --- a/src/cache/localcache.ts +++ b/src/cache/localcache.ts @@ -45,4 +45,17 @@ export class LocalCache implements ICache { buildKey(key: string) { return `${this.module}:${key}`; } + + async hGet() { + console.log('hGet not implemented'); + } + + async hSet() { + console.log('hSet not implemented'); + } + + async hDelete() { + console.log('hDelete not implemented'); + return 0; + } } diff --git a/src/cache/rediscache.ts b/src/cache/rediscache.ts index e010665f..6e209ef1 100644 --- a/src/cache/rediscache.ts +++ b/src/cache/rediscache.ts @@ -1,3 +1,4 @@ +import { BufferJSON } from '@whiskeysockets/baileys'; import { RedisClientType } from 'redis'; import { ICache } from '../api/abstract/abstract.cache'; @@ -14,7 +15,6 @@ export class RedisCache implements ICache { this.conf = this.configService.get('CACHE')?.REDIS; this.client = redisClient.getConnection(); } - async get(key: string): Promise { try { return JSON.parse(await this.client.get(this.buildKey(key))); @@ -23,6 +23,20 @@ export class RedisCache implements ICache { } } + async hGet(key: string, field: string) { + try { + const data = await this.client.hGet(this.buildKey(key), field); + + if (data) { + return JSON.parse(data, BufferJSON.reviver); + } + + return null; + } catch (error) { + this.logger.error(error); + } + } + async set(key: string, value: any, ttl?: number) { try { await this.client.setEx(this.buildKey(key), ttl || this.conf?.TTL, JSON.stringify(value)); @@ -31,6 +45,16 @@ export class RedisCache implements ICache { } } + async hSet(key: string, field: string, value: any) { + try { + const json = JSON.stringify(value, BufferJSON.replacer); + + await this.client.hSet(this.buildKey(key), field, json); + } catch (error) { + this.logger.error(error); + } + } + async has(key: string) { try { return (await this.client.exists(this.buildKey(key))) > 0; @@ -47,6 +71,14 @@ export class RedisCache implements ICache { } } + async hDelete(key: string, field: string) { + try { + return await this.client.hDel(this.buildKey(key), field); + } catch (error) { + this.logger.error(error); + } + } + async deleteAll(appendCriteria?: string) { try { const keys = await this.keys(appendCriteria); diff --git a/src/config/env.config.ts b/src/config/env.config.ts index b3991d08..69b6771a 100644 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -63,12 +63,6 @@ export type Database = { SAVE_DATA: SaveData; }; -export type Redis = { - ENABLED: boolean; - URI: string; - PREFIX_KEY: string; -}; - export type Rabbitmq = { ENABLED: boolean; MODE: string; // global, single, isolated @@ -153,6 +147,7 @@ export type CacheConfRedis = { URI: string; PREFIX_KEY: string; TTL: number; + SAVE_INSTANCES: boolean; }; export type CacheConfLocal = { ENABLED: boolean; @@ -186,7 +181,6 @@ export interface Env { STORE: StoreConf; CLEAN_STORE: CleanStoreConf; DATABASE: Database; - REDIS: Redis; RABBITMQ: Rabbitmq; SQS: Sqs; WEBSOCKET: Websocket; @@ -280,11 +274,6 @@ export class ConfigService { LABELS: process.env?.DATABASE_SAVE_DATA_LABELS === 'true', }, }, - REDIS: { - ENABLED: process.env?.REDIS_ENABLED === 'true', - URI: process.env.REDIS_URI || '', - PREFIX_KEY: process.env.REDIS_PREFIX_KEY || 'evolution', - }, RABBITMQ: { ENABLED: process.env?.RABBITMQ_ENABLED === 'true', MODE: process.env?.RABBITMQ_MODE || 'isolated', @@ -398,6 +387,7 @@ export class ConfigService { URI: process.env?.CACHE_REDIS_URI || '', PREFIX_KEY: process.env?.CACHE_REDIS_PREFIX_KEY || 'evolution-cache', TTL: Number.parseInt(process.env?.CACHE_REDIS_TTL) || 604800, + SAVE_INSTANCES: process.env?.CACHE_REDIS_SAVE_INSTANCES === 'true', }, LOCAL: { ENABLED: process.env?.CACHE_LOCAL_ENABLED === 'true', diff --git a/src/dev-env.yml b/src/dev-env.yml index f791b721..e7f0fae3 100644 --- a/src/dev-env.yml +++ b/src/dev-env.yml @@ -77,11 +77,6 @@ DATABASE: CONTACTS: false CHATS: false -REDIS: - ENABLED: false - URI: "redis://localhost:6379" - PREFIX_KEY: "evolution" - RABBITMQ: ENABLED: false MODE: "global" @@ -181,8 +176,9 @@ CACHE: REDIS: ENABLED: false URI: "redis://localhost:6379" - PREFIX_KEY: "evolution-cache" + PREFIX_KEY: "evolution" TTL: 604800 + SAVE_INSTANCES: false LOCAL: ENABLED: false TTL: 86400 diff --git a/src/libs/redis.client.ts b/src/libs/redis.client.ts deleted file mode 100644 index 4b3e1991..00000000 --- a/src/libs/redis.client.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { createClient, RedisClientType } from '@redis/client'; -import { BufferJSON } from '@whiskeysockets/baileys'; - -import { Redis } from '../config/env.config'; -import { Logger } from '../config/logger.config'; - -export class RedisCache { - private readonly logger = new Logger(RedisCache.name); - private client: RedisClientType; - private statusConnection = false; - private instanceName: string; - private redisEnv: Redis; - - constructor() { - this.logger.verbose('RedisCache instance created'); - process.on('beforeExit', () => { - this.logger.verbose('RedisCache instance destroyed'); - this.disconnect(); - }); - } - - public set reference(reference: string) { - this.logger.verbose('set reference: ' + reference); - this.instanceName = reference; - } - - public async connect(redisEnv: Redis) { - this.logger.verbose('Connecting to Redis...'); - this.client = createClient({ url: redisEnv.URI }); - this.client.on('error', (err) => this.logger.error('Redis Client Error ' + err)); - - await this.client.connect(); - this.statusConnection = true; - this.redisEnv = redisEnv; - this.logger.verbose(`Connected to ${redisEnv.URI}`); - } - - public async disconnect() { - if (this.statusConnection) { - await this.client.disconnect(); - this.statusConnection = false; - this.logger.verbose('Redis client disconnected'); - } - } - - public async getInstanceKeys(): Promise { - const keys: string[] = []; - try { - this.logger.verbose('Fetching instance keys'); - for await (const key of this.client.scanIterator({ MATCH: `${this.redisEnv.PREFIX_KEY}:*` })) { - keys.push(key); - } - return keys; - } catch (error) { - this.logger.error('Error fetching instance keys ' + error); - throw error; - } - } - - public async keyExists(key?: string) { - try { - const keys = await this.getInstanceKeys(); - const targetKey = key || this.instanceName; - this.logger.verbose('keyExists: ' + targetKey); - return keys.includes(targetKey); - } catch (error) { - return false; - } - } - - public async setData(field: string, data: any) { - try { - this.logger.verbose('setData: ' + field); - const json = JSON.stringify(data, BufferJSON.replacer); - await this.client.hSet(this.redisEnv.PREFIX_KEY + ':' + this.instanceName, field, json); - return true; - } catch (error) { - this.logger.error(error); - return false; - } - } - - public async getData(field: string): Promise { - try { - this.logger.verbose('getData: ' + field); - const data = await this.client.hGet(this.redisEnv.PREFIX_KEY + ':' + this.instanceName, field); - - if (data) { - this.logger.verbose('getData: ' + field + ' success'); - return JSON.parse(data, BufferJSON.reviver); - } - - this.logger.verbose('getData: ' + field + ' not found'); - return null; - } catch (error) { - this.logger.error(error); - return null; - } - } - - public async removeData(field: string): Promise { - try { - this.logger.verbose('removeData: ' + field); - await this.client.hDel(this.redisEnv.PREFIX_KEY + ':' + this.instanceName, field); - return true; - } catch (error) { - this.logger.error(error); - return false; - } - } - - public async delAll(hash?: string): Promise { - try { - const targetHash = hash || this.redisEnv.PREFIX_KEY + ':' + this.instanceName; - this.logger.verbose('instance delAll: ' + targetHash); - const result = await this.client.del(targetHash); - return !!result; - } catch (error) { - this.logger.error(error); - return false; - } - } -} diff --git a/src/utils/use-multi-file-auth-state-redis-db.ts b/src/utils/use-multi-file-auth-state-redis-db.ts index eb88d678..66bb89ea 100644 --- a/src/utils/use-multi-file-auth-state-redis-db.ts +++ b/src/utils/use-multi-file-auth-state-redis-db.ts @@ -6,10 +6,13 @@ import { SignalDataTypeMap, } from '@whiskeysockets/baileys'; +import { CacheService } from '../api/services/cache.service'; import { Logger } from '../config/logger.config'; -import { RedisCache } from '../libs/redis.client'; -export async function useMultiFileAuthStateRedisDb(cache: RedisCache): Promise<{ +export async function useMultiFileAuthStateRedisDb( + instanceName: string, + cache: CacheService, +): Promise<{ state: AuthenticationState; saveCreds: () => Promise; }> { @@ -17,7 +20,7 @@ export async function useMultiFileAuthStateRedisDb(cache: RedisCache): Promise<{ const writeData = async (data: any, key: string): Promise => { try { - return await cache.setData(key, data); + return await cache.hSet(instanceName, key, data); } catch (error) { return logger.error({ localError: 'writeData', error }); } @@ -25,7 +28,7 @@ export async function useMultiFileAuthStateRedisDb(cache: RedisCache): Promise<{ const readData = async (key: string): Promise => { try { - return await cache.getData(key); + return await cache.hGet(instanceName, key); } catch (error) { logger.error({ localError: 'readData', error }); return; @@ -34,7 +37,7 @@ export async function useMultiFileAuthStateRedisDb(cache: RedisCache): Promise<{ const removeData = async (key: string) => { try { - return await cache.removeData(key); + return await cache.hDelete(instanceName, key); } catch (error) { logger.error({ readData: 'removeData', error }); }