diff --git a/prisma/migrations/20240722173259_add_name_column_to_openai_creds/migration.sql b/prisma/migrations/20240722173259_add_name_column_to_openai_creds/migration.sql new file mode 100644 index 00000000..e7538a27 --- /dev/null +++ b/prisma/migrations/20240722173259_add_name_column_to_openai_creds/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - A unique constraint covering the columns `[name]` on the table `OpenaiCreds` will be added. If there are existing duplicate values, this will fail. + +*/ +-- AlterTable +ALTER TABLE "OpenaiCreds" ADD COLUMN "name" VARCHAR(255), +ALTER COLUMN "apiKey" DROP NOT NULL; + +-- CreateIndex +CREATE UNIQUE INDEX "OpenaiCreds_name_key" ON "OpenaiCreds"("name"); diff --git a/prisma/migrations/20240722173518_add_name_column_to_openai_creds/migration.sql b/prisma/migrations/20240722173518_add_name_column_to_openai_creds/migration.sql new file mode 100644 index 00000000..856abf67 --- /dev/null +++ b/prisma/migrations/20240722173518_add_name_column_to_openai_creds/migration.sql @@ -0,0 +1,2 @@ +-- DropIndex +DROP INDEX "OpenaiCreds_instanceId_key"; diff --git a/prisma/migrations/20240723152648_adjusts_in_column_openai_creds/migration.sql b/prisma/migrations/20240723152648_adjusts_in_column_openai_creds/migration.sql new file mode 100644 index 00000000..7f1af2e6 --- /dev/null +++ b/prisma/migrations/20240723152648_adjusts_in_column_openai_creds/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - A unique constraint covering the columns `[openaiCredsId]` on the table `OpenaiSetting` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE UNIQUE INDEX "OpenaiSetting_openaiCredsId_key" ON "OpenaiSetting"("openaiCredsId"); diff --git a/prisma/postgresql-schema.prisma b/prisma/postgresql-schema.prisma index 3df3c3c6..358e1ccf 100644 --- a/prisma/postgresql-schema.prisma +++ b/prisma/postgresql-schema.prisma @@ -77,7 +77,7 @@ model Instance { TypebotSession TypebotSession[] TypebotSetting TypebotSetting? Media Media[] - OpenaiCreds OpenaiCreds? + OpenaiCreds OpenaiCreds[] OpenaiBot OpenaiBot[] OpenaiSession OpenaiSession[] OpenaiSetting OpenaiSetting? @@ -333,14 +333,15 @@ model Media { } model OpenaiCreds { - id String @id @default(cuid()) - apiKey String @unique @db.VarChar(255) - createdAt DateTime? @default(now()) @db.Timestamp - updatedAt DateTime @updatedAt @db.Timestamp - Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade) - instanceId String @unique + id String @id @default(cuid()) + name String? @unique @db.VarChar(255) + apiKey String? @unique @db.VarChar(255) + createdAt DateTime? @default(now()) @db.Timestamp + updatedAt DateTime @updatedAt @db.Timestamp + Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade) + instanceId String OpenaiAssistant OpenaiBot[] - OpenaiSetting OpenaiSetting[] + OpenaiSetting OpenaiSetting? } model OpenaiBot { @@ -404,7 +405,7 @@ model OpenaiSetting { createdAt DateTime? @default(now()) @db.Timestamp updatedAt DateTime @updatedAt @db.Timestamp OpenaiCreds OpenaiCreds? @relation(fields: [openaiCredsId], references: [id]) - openaiCredsId String + openaiCredsId String @unique Fallback OpenaiBot? @relation(fields: [openaiIdFallback], references: [id]) openaiIdFallback String? @db.VarChar(100) Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade) diff --git a/src/api/controllers/chat.controller.ts b/src/api/controllers/chat.controller.ts index 17905089..044a9833 100644 --- a/src/api/controllers/chat.controller.ts +++ b/src/api/controllers/chat.controller.ts @@ -67,8 +67,8 @@ export class ChatController { return await this.waMonitor.waInstances[instanceName].fetchStatusMessage(query); } - public async fetchChats({ instanceName }: InstanceDto) { - return await this.waMonitor.waInstances[instanceName].fetchChats(); + public async fetchChats({ instanceName }: InstanceDto, query: Query) { + return await this.waMonitor.waInstances[instanceName].fetchChats(query); } public async sendPresence({ instanceName }: InstanceDto, data: SendPresenceDto) { diff --git a/src/api/controllers/instance.controller.ts b/src/api/controllers/instance.controller.ts index 8310aa1a..abbbd34d 100644 --- a/src/api/controllers/instance.controller.ts +++ b/src/api/controllers/instance.controller.ts @@ -594,7 +594,8 @@ export class InstanceController { switch (state) { case 'open': if (this.configService.get('CHATWOOT').ENABLED) instance.clearCacheChatwoot(); - await instance.reloadConnection(); + // await instance.reloadConnection(); + await instance.client?.ws?.close(); await delay(2000); return await this.connectionState({ instanceName }); diff --git a/src/api/integrations/openai/controllers/openai.controller.ts b/src/api/integrations/openai/controllers/openai.controller.ts index 74675f23..ffe669c7 100644 --- a/src/api/integrations/openai/controllers/openai.controller.ts +++ b/src/api/integrations/openai/controllers/openai.controller.ts @@ -19,6 +19,12 @@ export class OpenaiController { return this.openaiService.findCreds(instance); } + public async deleteCreds(instance: InstanceDto, openaiCredsId: string) { + if (!configService.get('OPENAI').ENABLED) throw new BadRequestException('Openai is disabled'); + + return this.openaiService.deleteCreds(instance, openaiCredsId); + } + public async createOpenai(instance: InstanceDto, data: OpenaiDto) { if (!configService.get('OPENAI').ENABLED) throw new BadRequestException('Openai is disabled'); diff --git a/src/api/integrations/openai/dto/openai.dto.ts b/src/api/integrations/openai/dto/openai.dto.ts index 7ce8caba..c4b7ebef 100644 --- a/src/api/integrations/openai/dto/openai.dto.ts +++ b/src/api/integrations/openai/dto/openai.dto.ts @@ -9,6 +9,7 @@ export class Session { } export class OpenaiCredsDto { + name: string; apiKey: string; } diff --git a/src/api/integrations/openai/routes/openai.router.ts b/src/api/integrations/openai/routes/openai.router.ts index 2de473e3..798a95b3 100644 --- a/src/api/integrations/openai/routes/openai.router.ts +++ b/src/api/integrations/openai/routes/openai.router.ts @@ -38,6 +38,16 @@ export class OpenaiRouter extends RouterBroker { res.status(HttpStatus.OK).json(response); }) + .delete(this.routerPath('creds/:openaiCredsId'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: instanceSchema, + ClassRef: InstanceDto, + execute: (instance) => openaiController.deleteCreds(instance, req.params.openaiCredsId), + }); + + res.status(HttpStatus.OK).json(response); + }) .post(this.routerPath('create'), ...guards, async (req, res) => { const response = await this.dataValidate({ request: req, diff --git a/src/api/integrations/openai/services/openai.service.ts b/src/api/integrations/openai/services/openai.service.ts index 02dcacdb..d41b8a7a 100644 --- a/src/api/integrations/openai/services/openai.service.ts +++ b/src/api/integrations/openai/services/openai.service.ts @@ -32,10 +32,12 @@ export class OpenaiService { .then((instance) => instance.id); if (!data.apiKey) throw new Error('API Key is required'); + if (!data.name) throw new Error('Name is required'); try { const creds = await this.prismaRepository.openaiCreds.create({ data: { + name: data.name, apiKey: data.apiKey, instanceId: instanceId, }, @@ -69,6 +71,43 @@ export class OpenaiService { return creds; } + public async deleteCreds(instance: InstanceDto, openaiCredsId: string) { + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + const creds = await this.prismaRepository.openaiCreds.findFirst({ + where: { + id: openaiCredsId, + }, + }); + + if (!creds) { + throw new Error('Openai Creds not found'); + } + + if (creds.instanceId !== instanceId) { + throw new Error('Openai Creds not found'); + } + + try { + await this.prismaRepository.openaiCreds.delete({ + where: { + id: openaiCredsId, + }, + }); + + return { openaiCreds: { id: openaiCredsId } }; + } catch (error) { + this.logger.error(error); + throw new Error('Error deleting openai creds'); + } + } + public async create(instance: InstanceDto, data: OpenaiDto) { const instanceId = await this.prismaRepository.instance .findFirst({ @@ -98,9 +137,9 @@ export class OpenaiService { if (!data.openaiCredsId) data.openaiCredsId = defaultSettingCheck?.openaiCredsId || null; if (!data.expire) data.expire = defaultSettingCheck?.expire || 0; - if (!data.keywordFinish) data.keywordFinish = defaultSettingCheck?.keywordFinish || '#SAIR'; + if (!data.keywordFinish) data.keywordFinish = defaultSettingCheck?.keywordFinish || ''; if (!data.delayMessage) data.delayMessage = defaultSettingCheck?.delayMessage || 1000; - if (!data.unknownMessage) data.unknownMessage = defaultSettingCheck?.unknownMessage || 'Desculpe, não entendi'; + if (!data.unknownMessage) data.unknownMessage = defaultSettingCheck?.unknownMessage || ''; if (!data.listeningFromMe) data.listeningFromMe = defaultSettingCheck?.listeningFromMe || false; if (!data.stopBotFromMe) data.stopBotFromMe = defaultSettingCheck?.stopBotFromMe || false; if (!data.keepOpen) data.keepOpen = defaultSettingCheck?.keepOpen || false; @@ -149,6 +188,7 @@ export class OpenaiService { whereDuplication = { ...whereDuplication, assistantId: data.assistantId, + botType: data.botType, }; } else if (data.botType === 'chatCompletion') { if (!data.model) throw new Error('Model is required'); @@ -158,6 +198,7 @@ export class OpenaiService { ...whereDuplication, model: data.model, maxTokens: data.maxTokens, + botType: data.botType, }; } else { throw new Error('Bot type is required'); diff --git a/src/api/integrations/openai/validate/openai.schema.ts b/src/api/integrations/openai/validate/openai.schema.ts index 71112ae1..f7ee30fe 100644 --- a/src/api/integrations/openai/validate/openai.schema.ts +++ b/src/api/integrations/openai/validate/openai.schema.ts @@ -54,10 +54,11 @@ export const openaiCredsSchema: JSONSchema7 = { $id: v4(), type: 'object', properties: { + name: { type: 'string' }, apiKey: { type: 'string' }, }, - required: ['apiKey'], - ...isNotEmpty('apiKey'), + required: ['name', 'apiKey'], + ...isNotEmpty('name', 'apiKey'), }; export const openaiStatusSchema: JSONSchema7 = { diff --git a/src/api/integrations/typebot/services/typebot.service.ts b/src/api/integrations/typebot/services/typebot.service.ts index bf0f209f..2b35423c 100644 --- a/src/api/integrations/typebot/services/typebot.service.ts +++ b/src/api/integrations/typebot/services/typebot.service.ts @@ -557,6 +557,7 @@ export class TypebotService { return await this.prismaRepository.typebotSession.findMany({ where: { remoteJid: remoteJid, + instanceId: instanceId, }, }); } @@ -583,6 +584,7 @@ export class TypebotService { await this.prismaRepository.typebotSession.deleteMany({ where: { remoteJid: remoteJid, + instanceId: instanceId, }, }); @@ -714,6 +716,7 @@ export class TypebotService { where: { url: url, typebot: typebot, + instanceId: instance.instanceId, }, }); @@ -738,6 +741,7 @@ export class TypebotService { await this.prismaRepository.typebotSession.deleteMany({ where: { remoteJid: remoteJid, + instanceId: instance.instanceId, }, }); diff --git a/src/api/routes/chat.router.ts b/src/api/routes/chat.router.ts index 5985685b..936c63f9 100644 --- a/src/api/routes/chat.router.ts +++ b/src/api/routes/chat.router.ts @@ -177,11 +177,11 @@ export class ChatRouter extends RouterBroker { return res.status(HttpStatus.OK).json(response); }) .post(this.routerPath('findChats'), ...guards, async (req, res) => { - const response = await this.dataValidate({ + const response = await this.dataValidate>({ request: req, - schema: null, - ClassRef: InstanceDto, - execute: (instance) => chatController.fetchChats(instance), + schema: contactValidateSchema, + ClassRef: Query, + execute: (instance, data) => chatController.fetchChats(instance, data), }); return res.status(HttpStatus.OK).json(response); diff --git a/src/api/services/channel.service.ts b/src/api/services/channel.service.ts index 4bec11bf..6584ecd4 100644 --- a/src/api/services/channel.service.ts +++ b/src/api/services/channel.service.ts @@ -1138,15 +1138,22 @@ export class ChannelStartupService { } public async fetchContacts(query: Query) { - const remoteJid = query.where?.remoteJid.includes('@') - ? query.where?.remoteJid - : this.createJid(query.where?.remoteJid); + const remoteJid = query?.where?.remoteJid + ? query?.where?.remoteJid.includes('@') + ? query.where?.remoteJid + : this.createJid(query.where?.remoteJid) + : null; + + const where = { + instanceId: this.instanceId, + }; + + if (remoteJid) { + where['remoteJid'] = remoteJid; + } return await this.prismaRepository.contact.findMany({ - where: { - instanceId: this.instanceId, - remoteJid, - }, + where, }); } @@ -1164,8 +1171,6 @@ export class ChannelStartupService { : this.createJid(keyFilters?.remoteJid) : null; - console.log(remoteJid); - const count = await this.prismaRepository.message.count({ where: { instanceId: this.instanceId, @@ -1246,9 +1251,47 @@ export class ChannelStartupService { }); } - public async fetchChats() { - return await this.prismaRepository.chat.findMany({ - where: { instanceId: this.instanceId }, - }); + public async fetchChats(query: any) { + const remoteJid = query?.where?.remoteJid + ? query?.where?.remoteJid.includes('@') + ? query.where?.remoteJid + : this.createJid(query.where?.remoteJid) + : null; + + let result; + if (remoteJid) { + result = await this.prismaRepository.$queryRaw` + SELECT + "Chat"."id", + "Chat"."remoteJid", + "Chat"."labels", + "Chat"."createdAt", + "Chat"."updatedAt", + "Contact"."pushName", + "Contact"."profilePicUrl" + FROM "Chat" + LEFT JOIN "Contact" ON "Chat"."remoteJid" = "Contact"."remoteJid" + WHERE "Chat"."instanceId" = ${this.instanceId} + AND "Chat"."remoteJid" = ${remoteJid} + ORDER BY "Chat"."updatedAt" DESC + `; + } else { + result = await this.prismaRepository.$queryRaw` + SELECT + "Chat"."id", + "Chat"."remoteJid", + "Chat"."labels", + "Chat"."createdAt", + "Chat"."updatedAt", + "Contact"."pushName", + "Contact"."profilePicUrl" + FROM "Chat" + LEFT JOIN "Contact" ON "Chat"."remoteJid" = "Contact"."remoteJid" + WHERE "Chat"."instanceId" = ${this.instanceId} + ORDER BY "Chat"."updatedAt" DESC + `; + } + + return result; } } diff --git a/src/api/services/channels/whatsapp.baileys.service.ts b/src/api/services/channels/whatsapp.baileys.service.ts index 8248c4ac..e5a834b5 100644 --- a/src/api/services/channels/whatsapp.baileys.service.ts +++ b/src/api/services/channels/whatsapp.baileys.service.ts @@ -416,6 +416,17 @@ export class BaileysStartupService extends ChannelStartupService { } } + if (connection === 'connecting') { + if (this.configService.get('DATABASE').ENABLED) { + await this.prismaRepository.instance.update({ + where: { id: this.instanceId }, + data: { + connectionStatus: 'connecting', + }, + }); + } + } + if (connection === 'open') { this.instance.wuid = this.client.user.id.replace(/:\d+/, ''); this.instance.profilePictureUrl = (await this.profilePicture(this.instance.wuid)).profilePictureUrl; diff --git a/src/api/services/monitor.service.ts b/src/api/services/monitor.service.ts index e764f8e2..acdbc4e6 100644 --- a/src/api/services/monitor.service.ts +++ b/src/api/services/monitor.service.ts @@ -79,6 +79,13 @@ export class WAMonitoringService { Sqs: true, Websocket: true, Setting: true, + _count: { + select: { + Message: true, + Contact: true, + Chat: true, + }, + }, }, });