feat: Save chatwoot creds

This commit is contained in:
Davidson Gomes 2023-07-12 15:41:07 -03:00
parent 86ce00365a
commit 57b61070d9
19 changed files with 478 additions and 71 deletions

View File

@ -92,3 +92,6 @@ AUTHENTICATION_INSTANCE_MODE=server # container or server
# if you are using container mode, set the container name and the webhook url to default instance
AUTHENTICATION_INSTANCE_NAME=evolution
AUTHENTICATION_INSTANCE_WEBHOOK_URL='<url>'
AUTHENTICATION_INSTANCE_CHATWOOT_ACCOUNT_ID=1
AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN=123456
AUTHENTICATION_INSTANCE_CHATWOOT_URL='<url>'

View File

@ -88,6 +88,9 @@ ENV AUTHENTICATION_JWT_SECRET="L=0YWt]b2w[WF>#>:&E`"
ENV AUTHENTICATION_INSTANCE_NAME=$AUTHENTICATION_INSTANCE_NAME
ENV AUTHENTICATION_INSTANCE_WEBHOOK_URL=$AUTHENTICATION_INSTANCE_WEBHOOK_URL
ENV AUTHENTICATION_INSTANCE_CHATWOOT_ACCOUNT_ID=$AUTHENTICATION_INSTANCE_CHATWOOT_ACCOUNT_ID
ENV AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN=$AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN
ENV AUTHENTICATION_INSTANCE_CHATWOOT_URL=$AUTHENTICATION_INSTANCE_CHATWOOT_URL
ENV AUTHENTICATION_INSTANCE_MODE=$AUTHENTICATION_INSTANCE_MODE
RUN npm install

View File

@ -98,6 +98,9 @@ export type Instance = {
NAME: string;
WEBHOOK_URL: string;
MODE: string;
CHATWOOT_ACCOUNT_ID: string;
CHATWOOT_TOKEN: string;
CHATWOOT_URL: string;
};
export type Auth = {
API_KEY: ApiKey;
@ -275,6 +278,9 @@ export class ConfigService {
NAME: process.env.AUTHENTICATION_INSTANCE_NAME,
WEBHOOK_URL: process.env.AUTHENTICATION_INSTANCE_WEBHOOK_URL,
MODE: process.env.AUTHENTICATION_INSTANCE_MODE,
CHATWOOT_ACCOUNT_ID: process.env.AUTHENTICATION_INSTANCE_CHATWOOT_ACCOUNT_ID,
CHATWOOT_TOKEN: process.env.AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN,
CHATWOOT_URL: process.env.AUTHENTICATION_INSTANCE_CHATWOOT_URL,
},
},
};

View File

@ -11,7 +11,7 @@ SERVER:
CORS:
ORIGIN:
- '*'
- "*"
# - yourdomain.com
METHODS:
- POST
@ -63,7 +63,7 @@ CLEAN_STORE:
DATABASE:
ENABLED: false
CONNECTION:
URI: 'mongodb://root:root@localhost:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true'
URI: "mongodb://root:root@localhost:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true"
DB_PREFIX_NAME: evolution
# Choose the data you want to save in the application's database or store
SAVE_DATA:
@ -75,8 +75,8 @@ DATABASE:
REDIS:
ENABLED: false
URI: 'redis://localhost:6379'
PREFIX_KEY: 'evolution'
URI: "redis://localhost:6379"
PREFIX_KEY: "evolution"
# Webhook Settings
WEBHOOK:
@ -112,7 +112,7 @@ WEBHOOK:
CONFIG_SESSION_PHONE:
# Name that will be displayed on smartphone connection
CLIENT: 'Evolution API'
CLIENT: "Evolution API"
NAME: chrome # chrome | firefox | edge | opera | safari
# Set qrcode display limit
@ -136,8 +136,11 @@ AUTHENTICATION:
SECRET: L=0YWt]b2w[WF>#>:&E`
# Set the instance name and webhook url to create an instance in init the application
INSTANCE:
# With this option activated, you work with a url per webhook event, respecting the local url and the name of each event
# With this option activated, you work with a url per webhook event, respecting the local url and the name of each event
MODE: server # container or server
# if you are using container mode, set the container name and the webhook url to default instance
NAME: evolution
WEBHOOK_URL: <url>
CHATWOOT_ACCOUNT_ID: 1
CHATWOOT_TOKEN: 123456
CHATWOOT_URL: <url>

View File

@ -857,3 +857,16 @@ export const webhookSchema: JSONSchema7 = {
required: ['url', 'enabled'],
...isNotEmpty('url'),
};
export const chatwootSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
enabled: { type: 'boolean', enum: [true, false] },
account_id: { type: 'string' },
token: { type: 'string' },
url: { type: 'string' },
},
required: ['enabled', 'account_id', 'token', 'url'],
...isNotEmpty('account_id', 'token', 'url'),
};

View File

@ -0,0 +1,48 @@
import { isURL } from 'class-validator';
import { BadRequestException } from '../../exceptions';
import { InstanceDto } from '../dto/instance.dto';
import { ChatwootDto } from '../dto/chatwoot.dto';
import { ChatwootService } from '../services/chatwoot.service';
import { Logger } from '../../config/logger.config';
const logger = new Logger('ChatwootController');
export class ChatwootController {
constructor(private readonly chatwootService: ChatwootService) {}
public async createChatwoot(instance: InstanceDto, data: ChatwootDto) {
logger.verbose(
'requested createChatwoot from ' + instance.instanceName + ' instance',
);
if (data.enabled) {
if (!isURL(data.url, { require_tld: false })) {
throw new BadRequestException('url is not valid');
}
if (!data.account_id) {
throw new BadRequestException('account_id is required');
}
if (!data.token) {
throw new BadRequestException('token is required');
}
}
if (!data.enabled) {
logger.verbose('chatwoot disabled');
data.account_id = '';
data.token = '';
data.url = '';
}
data.name_inbox = instance.instanceName;
return this.chatwootService.create(instance, data);
}
public async findChatwoot(instance: InstanceDto) {
logger.verbose('requested findChatwoot from ' + instance.instanceName + ' instance');
return this.chatwootService.find(instance);
}
}

View File

@ -8,6 +8,7 @@ import { AuthService, OldToken } from '../services/auth.service';
import { WAMonitoringService } from '../services/monitor.service';
import { WAStartupService } from '../services/whatsapp.service';
import { WebhookService } from '../services/webhook.service';
import { ChatwootService } from '../services/chatwoot.service';
import { Logger } from '../../config/logger.config';
import { wa } from '../types/wa.types';
import { RedisCache } from '../../db/redis.client';
@ -20,6 +21,7 @@ export class InstanceController {
private readonly eventEmitter: EventEmitter2,
private readonly authService: AuthService,
private readonly webhookService: WebhookService,
private readonly chatwootService: ChatwootService,
private readonly cache: RedisCache,
) {}
@ -32,6 +34,9 @@ export class InstanceController {
events,
qrcode,
token,
chatwoot_account_id,
chatwoot_token,
chatwoot_url,
}: InstanceDto) {
this.logger.verbose('requested createInstance from ' + instanceName + ' instance');
@ -73,34 +78,70 @@ export class InstanceController {
this.logger.verbose('hash: ' + hash + ' generated');
let getEvents: string[];
if (!chatwoot_account_id || !chatwoot_token || !chatwoot_url) {
let getEvents: string[];
if (webhook) {
this.logger.verbose('creating webhook');
try {
this.webhookService.create(instance, {
enabled: true,
url: webhook,
events,
webhook_by_events,
});
if (webhook) {
this.logger.verbose('creating webhook');
try {
this.webhookService.create(instance, {
enabled: true,
url: webhook,
events,
webhook_by_events,
});
getEvents = (await this.webhookService.find(instance)).events;
} catch (error) {
this.logger.log(error);
getEvents = (await this.webhookService.find(instance)).events;
} catch (error) {
this.logger.log(error);
}
}
this.logger.verbose('instance created');
this.logger.verbose({
instance: {
instanceName: instance.instanceName,
status: 'created',
},
hash,
webhook,
events: getEvents,
});
return {
instance: {
instanceName: instance.instanceName,
status: 'created',
},
hash,
webhook,
events: getEvents,
};
}
this.logger.verbose('instance created');
this.logger.verbose({
instance: {
instanceName: instance.instanceName,
status: 'created',
},
hash,
webhook,
events: getEvents,
});
if (!chatwoot_account_id) {
throw new BadRequestException('account_id is required');
}
if (!chatwoot_token) {
throw new BadRequestException('token is required');
}
if (!chatwoot_url) {
throw new BadRequestException('url is required');
}
try {
this.chatwootService.create(instance, {
enabled: true,
account_id: chatwoot_account_id,
token: chatwoot_token,
url: chatwoot_url,
name_inbox: instance.instanceName,
});
} catch (error) {
this.logger.log(error);
}
return {
instance: {
@ -108,8 +149,13 @@ export class InstanceController {
status: 'created',
},
hash,
webhook,
events: getEvents,
chatwoot: {
enabled: true,
account_id: chatwoot_account_id,
token: chatwoot_token,
url: chatwoot_url,
name_inbox: instance.instanceName,
},
};
} else {
this.logger.verbose('server mode');
@ -141,45 +187,83 @@ export class InstanceController {
this.logger.verbose('hash: ' + hash + ' generated');
let getEvents: string[];
if (!chatwoot_account_id || !chatwoot_token || !chatwoot_url) {
let getEvents: string[];
if (webhook) {
this.logger.verbose('creating webhook');
try {
this.webhookService.create(instance, {
enabled: true,
url: webhook,
events,
webhook_by_events,
});
if (webhook) {
this.logger.verbose('creating webhook');
try {
this.webhookService.create(instance, {
enabled: true,
url: webhook,
events,
webhook_by_events,
});
getEvents = (await this.webhookService.find(instance)).events;
} catch (error) {
this.logger.log(error);
getEvents = (await this.webhookService.find(instance)).events;
} catch (error) {
this.logger.log(error);
}
}
let getQrcode: wa.QrCode;
if (qrcode) {
this.logger.verbose('creating qrcode');
await instance.connectToWhatsapp();
await delay(2000);
getQrcode = instance.qrCode;
}
this.logger.verbose('instance created');
this.logger.verbose({
instance: {
instanceName: instance.instanceName,
status: 'created',
},
hash,
webhook,
webhook_by_events,
events: getEvents,
qrcode: getQrcode,
});
return {
instance: {
instanceName: instance.instanceName,
status: 'created',
},
hash,
webhook,
webhook_by_events,
events: getEvents,
qrcode: getQrcode,
};
}
let getQrcode: wa.QrCode;
if (qrcode) {
this.logger.verbose('creating qrcode');
await instance.connectToWhatsapp();
await delay(2000);
getQrcode = instance.qrCode;
if (!chatwoot_account_id) {
throw new BadRequestException('account_id is required');
}
this.logger.verbose('instance created');
this.logger.verbose({
instance: {
instanceName: instance.instanceName,
status: 'created',
},
hash,
webhook,
webhook_by_events,
events: getEvents,
qrcode: getQrcode,
});
if (!chatwoot_token) {
throw new BadRequestException('token is required');
}
if (!chatwoot_url) {
throw new BadRequestException('url is required');
}
try {
this.chatwootService.create(instance, {
enabled: true,
account_id: chatwoot_account_id,
token: chatwoot_token,
url: chatwoot_url,
name_inbox: instance.instanceName,
});
} catch (error) {
this.logger.log(error);
}
return {
instance: {
@ -187,10 +271,13 @@ export class InstanceController {
status: 'created',
},
hash,
webhook,
webhook_by_events,
events: getEvents,
qrcode: getQrcode,
chatwoot: {
enabled: true,
account_id: chatwoot_account_id,
token: chatwoot_token,
url: chatwoot_url,
name_inbox: instance.instanceName,
},
};
}
}

View File

@ -0,0 +1,7 @@
export class ChatwootDto {
enabled?: boolean;
account_id?: string;
token?: string;
url?: string;
name_inbox?: string;
}

View File

@ -5,4 +5,7 @@ export class InstanceDto {
events?: string[];
qrcode?: boolean;
token?: string;
chatwoot_account_id?: string;
chatwoot_token?: string;
chatwoot_url?: string;
}

View File

@ -0,0 +1,25 @@
import { Schema } from 'mongoose';
import { dbserver } from '../../db/db.connect';
export class ChatwootRaw {
_id?: string;
account_id?: string;
token?: string;
url?: string;
name_inbox?: string;
}
const chatwootSchema = new Schema<ChatwootRaw>({
_id: { type: String, _id: true },
account_id: { type: String, required: true },
token: { type: String, required: true },
url: { type: String, required: true },
name_inbox: { type: String, required: true },
});
export const ChatwootModel = dbserver?.model(
ChatwootRaw.name,
chatwootSchema,
'chatwoot',
);
export type IChatwootModel = typeof ChatwootModel;

View File

@ -3,3 +3,4 @@ export * from './contact.model';
export * from './message.model';
export * from './auth.model';
export * from './webhook.model';
export * from './chatwoot.model';

View File

@ -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 { IChatwootModel, ChatwootRaw } from '../models';
import { Logger } from '../../config/logger.config';
export class ChatwootRepository extends Repository {
constructor(
private readonly chatwootModel: IChatwootModel,
private readonly configService: ConfigService,
) {
super(configService);
}
private readonly logger = new Logger('ChatwootRepository');
public async create(data: ChatwootRaw, instance: string): Promise<IInsert> {
try {
this.logger.verbose('creating chatwoot');
if (this.dbSettings.ENABLED) {
this.logger.verbose('saving chatwoot to db');
const insert = await this.chatwootModel.replaceOne(
{ _id: instance },
{ ...data },
{ upsert: true },
);
this.logger.verbose(
'chatwoot saved to db: ' + insert.modifiedCount + ' chatwoot',
);
return { insertCount: insert.modifiedCount };
}
this.logger.verbose('saving chatwoot to store');
this.writeStore<ChatwootRaw>({
path: join(this.storePath, 'chatwoot'),
fileName: instance,
data,
});
this.logger.verbose(
'chatwoot saved to store in path: ' +
join(this.storePath, 'chatwoot') +
'/' +
instance,
);
this.logger.verbose('chatwoot created');
return { insertCount: 1 };
} catch (error) {
return error;
}
}
public async find(instance: string): Promise<ChatwootRaw> {
try {
this.logger.verbose('finding chatwoot');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding chatwoot in db');
return await this.chatwootModel.findOne({ _id: instance });
}
this.logger.verbose('finding chatwoot in store');
return JSON.parse(
readFileSync(join(this.storePath, 'chatwoot', instance + '.json'), {
encoding: 'utf-8',
}),
) as ChatwootRaw;
} catch (error) {
return {};
}
}
}

View File

@ -4,6 +4,7 @@ import { ContactRepository } from './contact.repository';
import { MessageUpRepository } from './messageUp.repository';
import { MongoClient } from 'mongodb';
import { WebhookRepository } from './webhook.repository';
import { ChatwootRepository } from './chatwoot.repository';
import { AuthRepository } from './auth.repository';
import { Auth, ConfigService, Database } from '../../config/env.config';
import { execSync } from 'child_process';
@ -17,6 +18,7 @@ export class RepositoryBroker {
public readonly contact: ContactRepository,
public readonly messageUpdate: MessageUpRepository,
public readonly webhook: WebhookRepository,
public readonly chatwoot: ChatwootRepository,
public readonly auth: AuthRepository,
private configService: ConfigService,
dbServer?: MongoClient,
@ -64,6 +66,9 @@ export class RepositoryBroker {
this.logger.verbose('creating webhook path: ' + join(storePath, 'webhook'));
execSync(`mkdir -p ${join(storePath, 'webhook')}`);
this.logger.verbose('creating chatwoot path: ' + join(storePath, 'chatwoot'));
execSync(`mkdir -p ${join(storePath, 'chatwoot')}`);
this.logger.verbose('creating temp path: ' + join(storePath, 'temp'));
execSync(`mkdir -p ${join(storePath, 'temp')}`);
}

View File

@ -0,0 +1,51 @@
import { RequestHandler, Router } from 'express';
import { instanceNameSchema, chatwootSchema } from '../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router';
import { InstanceDto } from '../dto/instance.dto';
import { ChatwootDto } from '../dto/chatwoot.dto';
import { chatwootController } from '../whatsapp.module';
import { HttpStatus } from './index.router';
import { Logger } from '../../config/logger.config';
const logger = new Logger('ChatwootRouter');
export class ChatwootRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('set'), ...guards, async (req, res) => {
logger.verbose('request received in setChatwoot');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<ChatwootDto>({
request: req,
schema: chatwootSchema,
ClassRef: ChatwootDto,
execute: (instance, data) => chatwootController.createChatwoot(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.get(this.routerPath('find'), ...guards, async (req, res) => {
logger.verbose('request received in findChatwoot');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceNameSchema,
ClassRef: InstanceDto,
execute: (instance) => chatwootController.findChatwoot(instance),
});
res.status(HttpStatus.OK).json(response);
});
}
public readonly router = Router();
}

View File

@ -8,6 +8,7 @@ import { InstanceRouter } from './instance.router';
import { MessageRouter } from './sendMessage.router';
import { ViewsRouter } from './view.router';
import { WebhookRouter } from './webhook.router';
import { ChatwootRouter } from './chatwoot.router';
enum HttpStatus {
OK = 200,
@ -32,6 +33,7 @@ router
.use('/message', new MessageRouter(...guards).router)
.use('/chat', new ChatRouter(...guards).router)
.use('/group', new GroupRouter(...guards).router)
.use('/webhook', new WebhookRouter(...guards).router);
.use('/webhook', new WebhookRouter(...guards).router)
.use('/chatwoot', new ChatwootRouter(...guards).router);
export { router, HttpStatus };

View File

@ -0,0 +1,26 @@
import { InstanceDto } from '../dto/instance.dto';
import { ChatwootDto } from '../dto/chatwoot.dto';
import { WAMonitoringService } from './monitor.service';
import { Logger } from '../../config/logger.config';
export class ChatwootService {
constructor(private readonly waMonitor: WAMonitoringService) {}
private readonly logger = new Logger(ChatwootService.name);
public create(instance: InstanceDto, data: ChatwootDto) {
this.logger.verbose('create chatwoot: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setChatwoot(data);
return { chatwoot: { ...instance, chatwoot: data } };
}
public async find(instance: InstanceDto): Promise<ChatwootDto> {
try {
this.logger.verbose('find chatwoot: ' + instance.instanceName);
return await this.waMonitor.waInstances[instance.instanceName].findChatwoot();
} catch (error) {
return { enabled: null, url: '' };
}
}
}

View File

@ -114,6 +114,7 @@ import { MessageUpQuery } from '../repository/messageUp.repository';
import { useMultiFileAuthStateDb } from '../../utils/use-multi-file-auth-state-db';
import Long from 'long';
import { WebhookRaw } from '../models/webhook.model';
import { ChatwootRaw } from '../models/chatwoot.model';
import { dbserver } from '../../db/db.connect';
import NodeCache from 'node-cache';
import { useMultiFileAuthStateRedisDb } from '../../utils/use-multi-file-auth-state-redis-db';
@ -138,6 +139,7 @@ export class WAStartupService {
private readonly instance: wa.Instance = {};
public client: WASocket;
private readonly localWebhook: wa.LocalWebHook = {};
private readonly localChatwoot: wa.LocalChatwoot = {};
private stateConnection: wa.StateConnection = { state: 'close' };
private readonly storePath = join(ROOT_DIR, 'store');
private readonly msgRetryCounterCache: CacheStore = new NodeCache();
@ -268,6 +270,34 @@ export class WAStartupService {
return data;
}
public async setChatwoot(data: ChatwootRaw) {
this.logger.verbose('Setting chatwoot');
await this.repository.chatwoot.create(data, this.instanceName);
this.logger.verbose(`Chatwoot account id: ${data.account_id}`);
this.logger.verbose(`Chatwoot token: ${data.token}`);
this.logger.verbose(`Chatwoot url: ${data.url}`);
this.logger.verbose(`Chatwoot inbox name: ${data.name_inbox}`);
Object.assign(this.localChatwoot, data);
this.logger.verbose('Chatwoot set');
}
public async findChatwoot() {
this.logger.verbose('Finding chatwoot');
const data = await this.repository.chatwoot.find(this.instanceName);
if (!data) {
this.logger.verbose('Chatwoot not found');
throw new NotFoundException('Chatwoot not found');
}
this.logger.verbose(`Chatwoot account id: ${data.account_id}`);
this.logger.verbose(`Chatwoot token: ${data.token}`);
this.logger.verbose(`Chatwoot url: ${data.url}`);
this.logger.verbose(`Chatwoot inbox name: ${data.name_inbox}`);
return data;
}
public async sendDataWebhook<T = any>(event: Events, data: T, local = true) {
const webhookGlobal = this.configService.get<Webhook>('WEBHOOK');
const webhookLocal = this.localWebhook.events;

View File

@ -41,6 +41,14 @@ export declare namespace wa {
webhook_by_events?: boolean;
};
export type LocalChatwoot = {
enabled?: boolean;
account_id?: string;
token?: string;
url?: string;
name_inbox?: string;
};
export type StateConnection = {
instance?: string;
state?: WAConnectionState | 'refused';

View File

@ -14,6 +14,8 @@ import { GroupController } from './controllers/group.controller';
import { ViewsController } from './controllers/views.controller';
import { WebhookService } from './services/webhook.service';
import { WebhookController } from './controllers/webhook.controller';
import { ChatwootService } from './services/chatwoot.service';
import { ChatwootController } from './controllers/chatwoot.controller';
import { RepositoryBroker } from './repository/repository.manager';
import {
AuthModel,
@ -21,10 +23,12 @@ import {
ContactModel,
MessageModel,
MessageUpModel,
ChatwootModel,
WebhookModel,
} from './models';
import { dbserver } from '../db/db.connect';
import { WebhookRepository } from './repository/webhook.repository';
import { WebhookModel } from './models/webhook.model';
import { ChatwootRepository } from './repository/chatwoot.repository';
import { AuthRepository } from './repository/auth.repository';
import { WAStartupService } from './services/whatsapp.service';
import { delay } from '@whiskeysockets/baileys';
@ -38,6 +42,7 @@ const chatRepository = new ChatRepository(ChatModel, configService);
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 authRepository = new AuthRepository(AuthModel, configService);
export const repository = new RepositoryBroker(
@ -46,6 +51,7 @@ export const repository = new RepositoryBroker(
contactRepository,
messageUpdateRepository,
webhookRepository,
chatwootRepository,
authRepository,
configService,
dbserver?.getClient(),
@ -66,6 +72,10 @@ const webhookService = new WebhookService(waMonitor);
export const webhookController = new WebhookController(webhookService);
const chatwootService = new ChatwootService(waMonitor);
export const chatwootController = new ChatwootController(chatwootService);
export const instanceController = new InstanceController(
waMonitor,
configService,
@ -73,6 +83,7 @@ export const instanceController = new InstanceController(
eventEmitter,
authService,
webhookService,
chatwootService,
cache,
);
export const viewsController = new ViewsController(waMonitor, configService);