From 73d9cd62a56d56c070c54ccf39134b6e63f9a6c5 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 24 Jul 2023 09:42:29 -0300 Subject: [PATCH] feat: Created settings Controller --- CHANGELOG.md | 1 + src/validate/validate.schema.ts | 12 +++ .../controllers/settings.controller.ts | 29 +++++++ src/whatsapp/dto/settings.dto.ts | 5 ++ src/whatsapp/models/index.ts | 1 + src/whatsapp/models/settings.model.ts | 23 ++++++ src/whatsapp/repository/repository.manager.ts | 8 +- .../repository/settings.repository.ts | 75 +++++++++++++++++++ src/whatsapp/routers/index.router.ts | 4 +- src/whatsapp/routers/settings.router.ts | 52 +++++++++++++ src/whatsapp/services/settings.service.ts | 34 +++++++++ src/whatsapp/services/whatsapp.service.ts | 43 +++++++++++ src/whatsapp/types/wa.types.ts | 6 ++ src/whatsapp/whatsapp.module.ts | 10 +++ 14 files changed, 301 insertions(+), 2 deletions(-) create mode 100644 src/whatsapp/controllers/settings.controller.ts create mode 100644 src/whatsapp/dto/settings.dto.ts create mode 100644 src/whatsapp/models/settings.model.ts create mode 100644 src/whatsapp/repository/settings.repository.ts create mode 100644 src/whatsapp/routers/settings.router.ts create mode 100644 src/whatsapp/services/settings.service.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index fed20ae3..ea7401df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Added connection functionality via pairing code * Added fetch profile endpoint in chat controller +* Created settings controller ### Fixed diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts index 30763fcd..a2a8ef13 100644 --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -877,3 +877,15 @@ export const chatwootSchema: JSONSchema7 = { required: ['enabled', 'account_id', 'token', 'url', 'sign_msg'], ...isNotEmpty('account_id', 'token', 'url', 'sign_msg'), }; + +export const settingsSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + reject_call: { type: 'boolean', enum: [true, false] }, + msg_call: { type: 'string' }, + groups_ignore: { type: 'boolean', enum: [true, false] }, + }, + required: ['reject_call'], + ...isNotEmpty('reject_call'), +}; diff --git a/src/whatsapp/controllers/settings.controller.ts b/src/whatsapp/controllers/settings.controller.ts new file mode 100644 index 00000000..59031634 --- /dev/null +++ b/src/whatsapp/controllers/settings.controller.ts @@ -0,0 +1,29 @@ +import { isURL } from 'class-validator'; +import { BadRequestException } from '../../exceptions'; +import { InstanceDto } from '../dto/instance.dto'; +import { SettingsDto } from '../dto/settings.dto'; +import { SettingsService } from '../services/settings.service'; +import { Logger } from '../../config/logger.config'; + +const logger = new Logger('SettingsController'); + +export class SettingsController { + constructor(private readonly settingsService: SettingsService) {} + + public async createSettings(instance: InstanceDto, data: SettingsDto) { + logger.verbose( + 'requested createSettings from ' + instance.instanceName + ' instance', + ); + + if (data.reject_call && data.msg_call.trim() == '') { + throw new BadRequestException('msg_call is required'); + } + + return this.settingsService.create(instance, data); + } + + public async findSettings(instance: InstanceDto) { + logger.verbose('requested findSettings from ' + instance.instanceName + ' instance'); + return this.settingsService.find(instance); + } +} diff --git a/src/whatsapp/dto/settings.dto.ts b/src/whatsapp/dto/settings.dto.ts new file mode 100644 index 00000000..20a6cba0 --- /dev/null +++ b/src/whatsapp/dto/settings.dto.ts @@ -0,0 +1,5 @@ +export class SettingsDto { + reject_call?: boolean; + msg_call?: string; + groups_ignore?: boolean; +} diff --git a/src/whatsapp/models/index.ts b/src/whatsapp/models/index.ts index e0b773f0..e6c6d8b4 100644 --- a/src/whatsapp/models/index.ts +++ b/src/whatsapp/models/index.ts @@ -4,3 +4,4 @@ export * from './message.model'; export * from './auth.model'; export * from './webhook.model'; export * from './chatwoot.model'; +export * from './settings.model'; diff --git a/src/whatsapp/models/settings.model.ts b/src/whatsapp/models/settings.model.ts new file mode 100644 index 00000000..b5eb7fe7 --- /dev/null +++ b/src/whatsapp/models/settings.model.ts @@ -0,0 +1,23 @@ +import { Schema } from 'mongoose'; +import { dbserver } from '../../db/db.connect'; + +export class SettingsRaw { + _id?: string; + reject_call?: boolean; + msg_call?: string; + groups_ignore?: boolean; +} + +const settingsSchema = new Schema({ + _id: { type: String, _id: true }, + reject_call: { type: Boolean, required: true }, + msg_call: { type: String, required: true }, + groups_ignore: { type: Boolean, required: true }, +}); + +export const SettingsModel = dbserver?.model( + SettingsRaw.name, + settingsSchema, + 'settings', +); +export type ISettingsModel = typeof SettingsModel; diff --git a/src/whatsapp/repository/repository.manager.ts b/src/whatsapp/repository/repository.manager.ts index 6c2a3091..dde636c7 100644 --- a/src/whatsapp/repository/repository.manager.ts +++ b/src/whatsapp/repository/repository.manager.ts @@ -5,10 +5,10 @@ import { MessageUpRepository } from './messageUp.repository'; import { MongoClient } from 'mongodb'; import { WebhookRepository } from './webhook.repository'; import { ChatwootRepository } from './chatwoot.repository'; +import { SettingsRepository } from './settings.repository'; import { AuthRepository } from './auth.repository'; import { Auth, ConfigService, Database } from '../../config/env.config'; -import { execSync } from 'child_process'; import { join } from 'path'; import fs from 'fs'; import { Logger } from '../../config/logger.config'; @@ -20,6 +20,7 @@ export class RepositoryBroker { public readonly messageUpdate: MessageUpRepository, public readonly webhook: WebhookRepository, public readonly chatwoot: ChatwootRepository, + public readonly settings: SettingsRepository, public readonly auth: AuthRepository, private configService: ConfigService, dbServer?: MongoClient, @@ -53,6 +54,7 @@ export class RepositoryBroker { const messageUpDir = join(storePath, 'message-up'); const webhookDir = join(storePath, 'webhook'); const chatwootDir = join(storePath, 'chatwoot'); + const settingsDir = join(storePath, 'settings'); const tempDir = join(storePath, 'temp'); if (!fs.existsSync(authDir)) { @@ -83,6 +85,10 @@ export class RepositoryBroker { this.logger.verbose('creating chatwoot dir: ' + chatwootDir); fs.mkdirSync(chatwootDir, { recursive: true }); } + if (!fs.existsSync(settingsDir)) { + this.logger.verbose('creating settings dir: ' + settingsDir); + fs.mkdirSync(settingsDir, { recursive: true }); + } if (!fs.existsSync(tempDir)) { this.logger.verbose('creating temp dir: ' + tempDir); fs.mkdirSync(tempDir, { recursive: true }); diff --git a/src/whatsapp/repository/settings.repository.ts b/src/whatsapp/repository/settings.repository.ts new file mode 100644 index 00000000..d253643d --- /dev/null +++ b/src/whatsapp/repository/settings.repository.ts @@ -0,0 +1,75 @@ +import { IInsert, Repository } from '../abstract/abstract.repository'; +import { ConfigService } from '../../config/env.config'; +import { join } from 'path'; +import { readFileSync } from 'fs'; +import { ISettingsModel, SettingsRaw } from '../models'; +import { Logger } from '../../config/logger.config'; + +export class SettingsRepository extends Repository { + constructor( + private readonly settingsModel: ISettingsModel, + private readonly configService: ConfigService, + ) { + super(configService); + } + + private readonly logger = new Logger('SettingsRepository'); + + public async create(data: SettingsRaw, instance: string): Promise { + try { + this.logger.verbose('creating settings'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('saving settings to db'); + const insert = await this.settingsModel.replaceOne( + { _id: instance }, + { ...data }, + { upsert: true }, + ); + + this.logger.verbose( + 'settings saved to db: ' + insert.modifiedCount + ' settings', + ); + return { insertCount: insert.modifiedCount }; + } + + this.logger.verbose('saving settings to store'); + + this.writeStore({ + path: join(this.storePath, 'settings'), + fileName: instance, + data, + }); + + this.logger.verbose( + 'settings saved to store in path: ' + + join(this.storePath, 'settings') + + '/' + + instance, + ); + + this.logger.verbose('settings created'); + return { insertCount: 1 }; + } catch (error) { + return error; + } + } + + public async find(instance: string): Promise { + try { + this.logger.verbose('finding settings'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding settings in db'); + return await this.settingsModel.findOne({ _id: instance }); + } + + this.logger.verbose('finding settings in store'); + return JSON.parse( + readFileSync(join(this.storePath, 'settings', instance + '.json'), { + encoding: 'utf-8', + }), + ) as SettingsRaw; + } catch (error) { + return {}; + } + } +} diff --git a/src/whatsapp/routers/index.router.ts b/src/whatsapp/routers/index.router.ts index 5d8a2c05..4cf7befb 100644 --- a/src/whatsapp/routers/index.router.ts +++ b/src/whatsapp/routers/index.router.ts @@ -10,6 +10,7 @@ import { ViewsRouter } from './view.router'; import { WebhookRouter } from './webhook.router'; import { ChatwootRouter } from './chatwoot.router'; import fs from 'fs'; +import { SettingsRouter } from './settings.router'; enum HttpStatus { OK = 200, @@ -44,6 +45,7 @@ router .use('/chat', new ChatRouter(...guards).router) .use('/group', new GroupRouter(...guards).router) .use('/webhook', new WebhookRouter(...guards).router) - .use('/chatwoot', new ChatwootRouter(...guards).router); + .use('/chatwoot', new ChatwootRouter(...guards).router) + .use('/settings', new SettingsRouter(...guards).router); export { router, HttpStatus }; diff --git a/src/whatsapp/routers/settings.router.ts b/src/whatsapp/routers/settings.router.ts new file mode 100644 index 00000000..3ec3df83 --- /dev/null +++ b/src/whatsapp/routers/settings.router.ts @@ -0,0 +1,52 @@ +import { RequestHandler, Router } from 'express'; +import { instanceNameSchema, settingsSchema } from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; +import { InstanceDto } from '../dto/instance.dto'; +import { SettingsDto } from '../dto/settings.dto'; +import { settingsController } from '../whatsapp.module'; +import { SettingsService } from '../services/settings.service'; +import { HttpStatus } from './index.router'; +import { Logger } from '../../config/logger.config'; + +const logger = new Logger('SettingsRouter'); + +export class SettingsRouter extends RouterBroker { + constructor(...guards: RequestHandler[]) { + super(); + this.router + .post(this.routerPath('set'), ...guards, async (req, res) => { + logger.verbose('request received in setSettings'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: settingsSchema, + ClassRef: SettingsDto, + execute: (instance, data) => settingsController.createSettings(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .get(this.routerPath('find'), ...guards, async (req, res) => { + logger.verbose('request received in findSettings'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: instanceNameSchema, + ClassRef: InstanceDto, + execute: (instance) => settingsController.findSettings(instance), + }); + + res.status(HttpStatus.OK).json(response); + }); + } + + public readonly router = Router(); +} diff --git a/src/whatsapp/services/settings.service.ts b/src/whatsapp/services/settings.service.ts new file mode 100644 index 00000000..9a82046a --- /dev/null +++ b/src/whatsapp/services/settings.service.ts @@ -0,0 +1,34 @@ +import { InstanceDto } from '../dto/instance.dto'; +import { SettingsDto } from '../dto/settings.dto'; +import { WAMonitoringService } from './monitor.service'; +import { Logger } from '../../config/logger.config'; + +export class SettingsService { + constructor(private readonly waMonitor: WAMonitoringService) {} + + private readonly logger = new Logger(SettingsService.name); + + public create(instance: InstanceDto, data: SettingsDto) { + this.logger.verbose('create settings: ' + instance.instanceName); + this.waMonitor.waInstances[instance.instanceName].setSettings(data); + + return { settings: { ...instance, settings: data } }; + } + + public async find(instance: InstanceDto): Promise { + try { + this.logger.verbose('find settings: ' + instance.instanceName); + const result = await this.waMonitor.waInstances[ + instance.instanceName + ].findSettings(); + + if (Object.keys(result).length === 0) { + throw new Error('Settings not found'); + } + + return result; + } catch (error) { + return { reject_call: false, msg_call: '', groups_ignore: false }; + } + } +} diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 128183da..27019883 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -125,6 +125,7 @@ import { Log } from '../../config/env.config'; import ProxyAgent from 'proxy-agent'; import { ChatwootService } from './chatwoot.service'; import { waMonitor } from '../whatsapp.module'; +import { SettingsRaw } from '../models'; export class WAStartupService { constructor( @@ -143,6 +144,7 @@ export class WAStartupService { public client: WASocket; private readonly localWebhook: wa.LocalWebHook = {}; private readonly localChatwoot: wa.LocalChatwoot = {}; + private readonly localSettings: wa.LocalSettings = {}; private stateConnection: wa.StateConnection = { state: 'close' }; public readonly storePath = join(ROOT_DIR, 'store'); private readonly msgRetryCounterCache: CacheStore = new NodeCache(); @@ -341,6 +343,46 @@ export class WAStartupService { return data; } + private async loadSettings() { + this.logger.verbose('Loading settings'); + const data = await this.repository.settings.find(this.instanceName); + this.localSettings.reject_call = data?.reject_call; + this.logger.verbose(`Settings reject_call: ${this.localSettings.reject_call}`); + + this.localSettings.msg_call = data?.msg_call; + this.logger.verbose(`Settings msg_call: ${this.localSettings.msg_call}`); + + this.localSettings.groups_ignore = data?.groups_ignore; + this.logger.verbose(`Settings groups_ignore: ${this.localSettings.groups_ignore}`); + + this.logger.verbose('Settings loaded'); + } + + public async setSettings(data: SettingsRaw) { + this.logger.verbose('Setting settings'); + await this.repository.settings.create(data, this.instanceName); + this.logger.verbose(`Settings reject_call: ${data.reject_call}`); + this.logger.verbose(`Settings msg_call: ${data.msg_call}`); + this.logger.verbose(`Settings groups_ignore: ${data.groups_ignore}`); + Object.assign(this.localSettings, data); + this.logger.verbose('Settings set'); + } + + public async findSettings() { + this.logger.verbose('Finding settings'); + const data = await this.repository.settings.find(this.instanceName); + + if (!data) { + this.logger.verbose('Settings not found'); + throw new NotFoundException('Settings not found'); + } + + this.logger.verbose(`Settings url: ${data.reject_call}`); + this.logger.verbose(`Settings msg_call: ${data.msg_call}`); + this.logger.verbose(`Settings groups_ignore: ${data.groups_ignore}`); + return data; + } + public async sendDataWebhook(event: Events, data: T, local = true) { const webhookGlobal = this.configService.get('WEBHOOK'); const webhookLocal = this.localWebhook.events; @@ -761,6 +803,7 @@ export class WAStartupService { try { this.loadWebhook(); this.loadChatwoot(); + this.loadSettings(); this.instance.authState = await this.defineAuthState(); diff --git a/src/whatsapp/types/wa.types.ts b/src/whatsapp/types/wa.types.ts index fc71de31..4b699d7e 100644 --- a/src/whatsapp/types/wa.types.ts +++ b/src/whatsapp/types/wa.types.ts @@ -55,6 +55,12 @@ export declare namespace wa { sign_msg?: boolean; }; + export type LocalSettings = { + reject_call?: boolean; + msg_call?: string; + groups_ignore?: boolean; + }; + export type StateConnection = { instance?: string; state?: WAConnectionState | 'refused'; diff --git a/src/whatsapp/whatsapp.module.ts b/src/whatsapp/whatsapp.module.ts index c91ee9c4..9f2fed00 100644 --- a/src/whatsapp/whatsapp.module.ts +++ b/src/whatsapp/whatsapp.module.ts @@ -25,6 +25,7 @@ import { MessageUpModel, ChatwootModel, WebhookModel, + SettingsModel, } from './models'; import { dbserver } from '../db/db.connect'; import { WebhookRepository } from './repository/webhook.repository'; @@ -34,6 +35,9 @@ import { WAStartupService } from './services/whatsapp.service'; import { delay } from '@whiskeysockets/baileys'; import { Events } from './types/wa.types'; import { RedisCache } from '../db/redis.client'; +import { SettingsRepository } from './repository/settings.repository'; +import { SettingsService } from './services/settings.service'; +import { SettingsController } from './controllers/settings.controller'; const logger = new Logger('WA MODULE'); @@ -43,6 +47,7 @@ const contactRepository = new ContactRepository(ContactModel, configService); const messageUpdateRepository = new MessageUpRepository(MessageUpModel, configService); const webhookRepository = new WebhookRepository(WebhookModel, configService); const chatwootRepository = new ChatwootRepository(ChatwootModel, configService); +const settingsRepository = new SettingsRepository(SettingsModel, configService); const authRepository = new AuthRepository(AuthModel, configService); export const repository = new RepositoryBroker( @@ -52,6 +57,7 @@ export const repository = new RepositoryBroker( messageUpdateRepository, webhookRepository, chatwootRepository, + settingsRepository, authRepository, configService, dbserver?.getClient(), @@ -76,6 +82,10 @@ const chatwootService = new ChatwootService(waMonitor, configService); export const chatwootController = new ChatwootController(chatwootService, configService); +const settingsService = new SettingsService(waMonitor); + +export const settingsController = new SettingsController(settingsService); + export const instanceController = new InstanceController( waMonitor, configService,