-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for managing WhatsApp templates via official API
This commit introduces changes to support managing WhatsApp templates using the official WhatsApp Business API. The following modifications have been made: - Implemented a new Template model in the Prisma schema, including fields for template ID, name, language, and associated Instance (business ID, instance ID, and created/updated timestamps). - Modified the Instance model in the Prisma schema to include a Template relationship. - Updated InstanceController to include a new `businessId` property in the InstanceDto. - Added a new TemplateRouter, TemplateController, and TemplateService to handle template-related requests and services. - Updated the WebhookService to utilize the new TemplateService. - Added new TypebotController, WebhookController, and WAMonitoringService methods to handle template-related events. - Updated the validate schema to include a new template schema. The main goal of this commit is to enable managing WhatsApp templates, including creating, updating, and deleting templates, as well as associating them with specific instances.
- Loading branch information
1 parent
a145935
commit 26bddf3
Showing
15 changed files
with
255 additions
and
6 deletions.
There are no files selected for viewing
21 changes: 21 additions & 0 deletions
21
prisma/migrations/20240712150256_create_templates_table/migration.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
-- CreateTable | ||
CREATE TABLE "Template" ( | ||
"id" TEXT NOT NULL, | ||
"name" VARCHAR(255) NOT NULL, | ||
"language" VARCHAR(255) NOT NULL, | ||
"templateId" VARCHAR(255) NOT NULL, | ||
"createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | ||
"updatedAt" TIMESTAMP NOT NULL, | ||
"instanceId" TEXT NOT NULL, | ||
|
||
CONSTRAINT "Template_pkey" PRIMARY KEY ("id") | ||
); | ||
|
||
-- CreateIndex | ||
CREATE UNIQUE INDEX "Template_templateId_key" ON "Template"("templateId"); | ||
|
||
-- CreateIndex | ||
CREATE UNIQUE INDEX "Template_instanceId_key" ON "Template"("instanceId"); | ||
|
||
-- AddForeignKey | ||
ALTER TABLE "Template" ADD CONSTRAINT "Template_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "Instance"("id") ON DELETE CASCADE ON UPDATE CASCADE; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { InstanceDto } from '../dto/instance.dto'; | ||
import { TemplateDto } from '../dto/template.dto'; | ||
import { TemplateService } from '../services/template.service'; | ||
|
||
export class TemplateController { | ||
constructor(private readonly templateService: TemplateService) {} | ||
|
||
public async createTemplate(instance: InstanceDto, data: TemplateDto) { | ||
return this.templateService.create(instance, data); | ||
} | ||
|
||
public async findTemplate(instance: InstanceDto) { | ||
return this.templateService.find(instance); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export class TemplateDto { | ||
name: string; | ||
category: string; | ||
allowCategoryChange: boolean; | ||
language: string; | ||
components: any; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { RequestHandler, Router } from 'express'; | ||
|
||
import { ConfigService } from '../../config/env.config'; | ||
import { instanceSchema, templateSchema } from '../../validate/validate.schema'; | ||
import { RouterBroker } from '../abstract/abstract.router'; | ||
import { InstanceDto } from '../dto/instance.dto'; | ||
import { TemplateDto } from '../dto/template.dto'; | ||
import { templateController } from '../server.module'; | ||
import { HttpStatus } from './index.router'; | ||
|
||
export class TemplateRouter extends RouterBroker { | ||
constructor(readonly configService: ConfigService, ...guards: RequestHandler[]) { | ||
super(); | ||
this.router | ||
.post(this.routerPath('create'), ...guards, async (req, res) => { | ||
const response = await this.dataValidate<TemplateDto>({ | ||
request: req, | ||
schema: templateSchema, | ||
ClassRef: TemplateDto, | ||
execute: (instance, data) => templateController.createTemplate(instance, data), | ||
}); | ||
|
||
res.status(HttpStatus.CREATED).json(response); | ||
}) | ||
.get(this.routerPath('find'), ...guards, async (req, res) => { | ||
const response = await this.dataValidate<InstanceDto>({ | ||
request: req, | ||
schema: instanceSchema, | ||
ClassRef: InstanceDto, | ||
execute: (instance) => templateController.findTemplate(instance), | ||
}); | ||
|
||
res.status(HttpStatus.OK).json(response); | ||
}); | ||
} | ||
|
||
public readonly router = Router(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import { Template } from '@prisma/client'; | ||
import axios from 'axios'; | ||
|
||
import { ConfigService, WaBusiness } from '../../config/env.config'; | ||
import { Logger } from '../../config/logger.config'; | ||
import { InstanceDto } from '../dto/instance.dto'; | ||
import { TemplateDto } from '../dto/template.dto'; | ||
import { PrismaRepository } from '../repository/repository.service'; | ||
import { WAMonitoringService } from './monitor.service'; | ||
|
||
export class TemplateService { | ||
constructor( | ||
private readonly waMonitor: WAMonitoringService, | ||
public readonly prismaRepository: PrismaRepository, | ||
private readonly configService: ConfigService, | ||
) {} | ||
|
||
private readonly logger = new Logger(TemplateService.name); | ||
|
||
private businessId: string; | ||
private token: string; | ||
|
||
public async find(instance: InstanceDto) { | ||
const getInstance = await this.waMonitor.waInstances[instance.instanceName].instance; | ||
|
||
if (!getInstance) { | ||
throw new Error('Instance not found'); | ||
} | ||
|
||
this.businessId = getInstance.businessId; | ||
this.token = getInstance.token; | ||
|
||
const response = await this.requestTemplate({}, 'GET'); | ||
|
||
if (!response) { | ||
throw new Error('Error to create template'); | ||
} | ||
|
||
console.log(response); | ||
|
||
return response.data; | ||
} | ||
|
||
public async create(instance: InstanceDto, data: TemplateDto): Promise<Template> { | ||
try { | ||
const getInstance = await this.waMonitor.waInstances[instance.instanceName].instance; | ||
|
||
if (!getInstance) { | ||
throw new Error('Instance not found'); | ||
} | ||
|
||
this.businessId = getInstance.businessId; | ||
this.token = getInstance.token; | ||
|
||
const postData = { | ||
name: data.name, | ||
category: data.category, | ||
allow_category_change: data.allowCategoryChange, | ||
language: data.language, | ||
components: data.components, | ||
}; | ||
|
||
const response = await this.requestTemplate(postData, 'POST'); | ||
|
||
if (!response) { | ||
throw new Error('Error to create template'); | ||
} | ||
|
||
console.log(response); | ||
|
||
const template = await this.prismaRepository.template.create({ | ||
data: { | ||
instanceId: getInstance.id, | ||
templateId: response.id, | ||
name: data.name, | ||
language: data.language, | ||
}, | ||
}); | ||
|
||
return template; | ||
} catch (error) { | ||
this.logger.error(error); | ||
throw new Error('Error to create template'); | ||
} | ||
} | ||
|
||
private async requestTemplate(data: any, method: string) { | ||
try { | ||
let urlServer = this.configService.get<WaBusiness>('WA_BUSINESS').URL; | ||
const version = this.configService.get<WaBusiness>('WA_BUSINESS').VERSION; | ||
urlServer = `${urlServer}/${version}/${this.businessId}/message_templates`; | ||
const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${this.token}` }; | ||
if (method === 'GET') { | ||
const result = await axios.get(urlServer, { headers }); | ||
return result.data; | ||
} else if (method === 'POST') { | ||
const result = await axios.post(urlServer, data, { headers }); | ||
return result.data; | ||
} | ||
} catch (e) { | ||
this.logger.error(e.response.data); | ||
return null; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { JSONSchema7 } from 'json-schema'; | ||
import { v4 } from 'uuid'; | ||
|
||
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => { | ||
const properties = {}; | ||
propertyNames.forEach( | ||
(property) => | ||
(properties[property] = { | ||
minLength: 1, | ||
description: `The "${property}" cannot be empty`, | ||
}), | ||
); | ||
return { | ||
if: { | ||
propertyNames: { | ||
enum: [...propertyNames], | ||
}, | ||
}, | ||
then: { properties }, | ||
}; | ||
}; | ||
|
||
export const templateSchema: JSONSchema7 = { | ||
$id: v4(), | ||
type: 'object', | ||
properties: { | ||
name: { type: 'string' }, | ||
category: { type: 'string', enum: ['AUTHENTICATION', 'MARKETING', 'UTILITY'] }, | ||
allowCategoryChange: { type: 'boolean' }, | ||
language: { type: 'string' }, | ||
components: { type: 'array' }, | ||
}, | ||
required: ['name', 'category', 'language', 'components'], | ||
...isNotEmpty('name', 'category', 'language', 'components'), | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters