mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-12-10 18:39:38 -06:00
feat(template): add edit/delete endpoints, DTOs and validation"
This commit is contained in:
parent
c555048783
commit
a95c843e77
@ -12,4 +12,15 @@ export class TemplateController {
|
|||||||
public async findTemplate(instance: InstanceDto) {
|
public async findTemplate(instance: InstanceDto) {
|
||||||
return this.templateService.find(instance);
|
return this.templateService.find(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async editTemplate(
|
||||||
|
instance: InstanceDto,
|
||||||
|
data: { templateId: string; category?: string; components?: any; allowCategoryChange?: boolean; ttl?: number },
|
||||||
|
) {
|
||||||
|
return this.templateService.edit(instance, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteTemplate(instance: InstanceDto, data: { name: string; hsmId?: string }) {
|
||||||
|
return this.templateService.delete(instance, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,3 +6,16 @@ export class TemplateDto {
|
|||||||
components: any;
|
components: any;
|
||||||
webhookUrl?: string;
|
webhookUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class TemplateEditDto {
|
||||||
|
templateId: string;
|
||||||
|
category?: 'AUTHENTICATION' | 'MARKETING' | 'UTILITY';
|
||||||
|
allowCategoryChange?: boolean;
|
||||||
|
ttl?: number;
|
||||||
|
components?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TemplateDeleteDto {
|
||||||
|
name: string;
|
||||||
|
hsmId?: string;
|
||||||
|
}
|
||||||
|
|||||||
0
src/api/dto/templateDelete.dto.ts
Normal file
0
src/api/dto/templateDelete.dto.ts
Normal file
0
src/api/dto/templateEdit.dto.ts
Normal file
0
src/api/dto/templateEdit.dto.ts
Normal file
@ -1,9 +1,11 @@
|
|||||||
import { RouterBroker } from '@api/abstract/abstract.router';
|
import { RouterBroker } from '@api/abstract/abstract.router';
|
||||||
import { InstanceDto } from '@api/dto/instance.dto';
|
import { InstanceDto } from '@api/dto/instance.dto';
|
||||||
import { TemplateDto } from '@api/dto/template.dto';
|
import { TemplateDeleteDto, TemplateDto, TemplateEditDto } from '@api/dto/template.dto';
|
||||||
import { templateController } from '@api/server.module';
|
import { templateController } from '@api/server.module';
|
||||||
import { ConfigService } from '@config/env.config';
|
import { ConfigService } from '@config/env.config';
|
||||||
import { createMetaErrorResponse } from '@utils/errorResponse';
|
import { createMetaErrorResponse } from '@utils/errorResponse';
|
||||||
|
import { templateDeleteSchema } from '@validate/templateDelete.schema';
|
||||||
|
import { templateEditSchema } from '@validate/templateEdit.schema';
|
||||||
import { instanceSchema, templateSchema } from '@validate/validate.schema';
|
import { instanceSchema, templateSchema } from '@validate/validate.schema';
|
||||||
import { RequestHandler, Router } from 'express';
|
import { RequestHandler, Router } from 'express';
|
||||||
|
|
||||||
@ -35,6 +37,38 @@ export class TemplateRouter extends RouterBroker {
|
|||||||
res.status(errorResponse.status).json(errorResponse);
|
res.status(errorResponse.status).json(errorResponse);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.post(this.routerPath('edit'), ...guards, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const response = await this.dataValidate<TemplateEditDto>({
|
||||||
|
request: req,
|
||||||
|
schema: templateEditSchema,
|
||||||
|
ClassRef: TemplateEditDto,
|
||||||
|
execute: (instance, data) => templateController.editTemplate(instance, data),
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(HttpStatus.OK).json(response);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Template edit error:', error);
|
||||||
|
const errorResponse = createMetaErrorResponse(error, 'template_edit');
|
||||||
|
res.status(errorResponse.status).json(errorResponse);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.delete(this.routerPath('delete'), ...guards, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const response = await this.dataValidate<TemplateDeleteDto>({
|
||||||
|
request: req,
|
||||||
|
schema: templateDeleteSchema,
|
||||||
|
ClassRef: TemplateDeleteDto,
|
||||||
|
execute: (instance, data) => templateController.deleteTemplate(instance, data),
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(HttpStatus.OK).json(response);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Template delete error:', error);
|
||||||
|
const errorResponse = createMetaErrorResponse(error, 'template_delete');
|
||||||
|
res.status(errorResponse.status).json(errorResponse);
|
||||||
|
}
|
||||||
|
})
|
||||||
.get(this.routerPath('find'), ...guards, async (req, res) => {
|
.get(this.routerPath('find'), ...guards, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const response = await this.dataValidate<InstanceDto>({
|
const response = await this.dataValidate<InstanceDto>({
|
||||||
|
|||||||
@ -88,6 +88,77 @@ export class TemplateService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async edit(
|
||||||
|
instance: InstanceDto,
|
||||||
|
data: { templateId: string; category?: string; components?: any; allowCategoryChange?: boolean; ttl?: number },
|
||||||
|
) {
|
||||||
|
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 payload: Record<string, unknown> = {};
|
||||||
|
if (typeof data.category === 'string') payload.category = data.category;
|
||||||
|
if (typeof data.allowCategoryChange === 'boolean') payload.allow_category_change = data.allowCategoryChange;
|
||||||
|
if (typeof data.ttl === 'number') payload.time_to_live = data.ttl;
|
||||||
|
if (data.components) payload.components = data.components;
|
||||||
|
|
||||||
|
const response = await this.requestEditTemplate(data.templateId, payload);
|
||||||
|
|
||||||
|
if (!response || response.error) {
|
||||||
|
if (response && response.error) {
|
||||||
|
const metaError = new Error(response.error.message || 'WhatsApp API Error');
|
||||||
|
(metaError as any).template = response.error;
|
||||||
|
throw metaError;
|
||||||
|
}
|
||||||
|
throw new Error('Error to edit template');
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete(instance: InstanceDto, data: { name: string; hsmId?: string }) {
|
||||||
|
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.requestDeleteTemplate({ name: data.name, hsm_id: data.hsmId });
|
||||||
|
|
||||||
|
if (!response || response.error) {
|
||||||
|
if (response && response.error) {
|
||||||
|
const metaError = new Error(response.error.message || 'WhatsApp API Error');
|
||||||
|
(metaError as any).template = response.error;
|
||||||
|
throw metaError;
|
||||||
|
}
|
||||||
|
throw new Error('Error to delete template');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Best-effort local cleanup of stored template metadata
|
||||||
|
await this.prismaRepository.template.deleteMany({
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{ name: data.name, instanceId: getInstance.id },
|
||||||
|
data.hsmId ? { templateId: data.hsmId, instanceId: getInstance.id } : undefined,
|
||||||
|
].filter(Boolean) as any,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.warn(
|
||||||
|
`Failed to cleanup local template records after delete: ${(err as Error)?.message || String(err)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
private async requestTemplate(data: any, method: string) {
|
private async requestTemplate(data: any, method: string) {
|
||||||
try {
|
try {
|
||||||
let urlServer = this.configService.get<WaBusiness>('WA_BUSINESS').URL;
|
let urlServer = this.configService.get<WaBusiness>('WA_BUSINESS').URL;
|
||||||
@ -116,4 +187,38 @@ export class TemplateService {
|
|||||||
throw new Error(`Connection error: ${e.message}`);
|
throw new Error(`Connection error: ${e.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async requestEditTemplate(templateId: string, data: any) {
|
||||||
|
try {
|
||||||
|
let urlServer = this.configService.get<WaBusiness>('WA_BUSINESS').URL;
|
||||||
|
const version = this.configService.get<WaBusiness>('WA_BUSINESS').VERSION;
|
||||||
|
urlServer = `${urlServer}/${version}/${templateId}`;
|
||||||
|
const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${this.token}` };
|
||||||
|
const result = await axios.post(urlServer, data, { headers });
|
||||||
|
return result.data;
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error(
|
||||||
|
'WhatsApp API request error: ' + (e.response?.data ? JSON.stringify(e.response?.data) : e.message),
|
||||||
|
);
|
||||||
|
if (e.response?.data) return e.response.data;
|
||||||
|
throw new Error(`Connection error: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async requestDeleteTemplate(params: { name: string; hsm_id?: 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 = { Authorization: `Bearer ${this.token}` };
|
||||||
|
const result = await axios.delete(urlServer, { headers, params });
|
||||||
|
return result.data;
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error(
|
||||||
|
'WhatsApp API request error: ' + (e.response?.data ? JSON.stringify(e.response?.data) : e.message),
|
||||||
|
);
|
||||||
|
if (e.response?.data) return e.response.data;
|
||||||
|
throw new Error(`Connection error: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/validate/templateDelete.schema.ts
Normal file
32
src/validate/templateDelete.schema.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { JSONSchema7 } from 'json-schema';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
|
||||||
|
const properties: Record<string, unknown> = {};
|
||||||
|
propertyNames.forEach(
|
||||||
|
(property) =>
|
||||||
|
(properties[property] = {
|
||||||
|
minLength: 1,
|
||||||
|
description: `The "${property}" cannot be empty`,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
if: {
|
||||||
|
propertyNames: {
|
||||||
|
enum: [...propertyNames],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
then: { properties },
|
||||||
|
} as JSONSchema7;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const templateDeleteSchema: JSONSchema7 = {
|
||||||
|
$id: v4(),
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: { type: 'string' },
|
||||||
|
hsmId: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['name'],
|
||||||
|
...isNotEmpty('name'),
|
||||||
|
};
|
||||||
35
src/validate/templateEdit.schema.ts
Normal file
35
src/validate/templateEdit.schema.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { JSONSchema7 } from 'json-schema';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
|
||||||
|
const properties: Record<string, unknown> = {};
|
||||||
|
propertyNames.forEach(
|
||||||
|
(property) =>
|
||||||
|
(properties[property] = {
|
||||||
|
minLength: 1,
|
||||||
|
description: `The "${property}" cannot be empty`,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
if: {
|
||||||
|
propertyNames: {
|
||||||
|
enum: [...propertyNames],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
then: { properties },
|
||||||
|
} as JSONSchema7;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const templateEditSchema: JSONSchema7 = {
|
||||||
|
$id: v4(),
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
templateId: { type: 'string' },
|
||||||
|
category: { type: 'string', enum: ['AUTHENTICATION', 'MARKETING', 'UTILITY'] },
|
||||||
|
allowCategoryChange: { type: 'boolean' },
|
||||||
|
ttl: { type: 'number' },
|
||||||
|
components: { type: 'array' },
|
||||||
|
},
|
||||||
|
required: ['templateId'],
|
||||||
|
...isNotEmpty('templateId'),
|
||||||
|
};
|
||||||
@ -8,5 +8,7 @@ export * from './message.schema';
|
|||||||
export * from './proxy.schema';
|
export * from './proxy.schema';
|
||||||
export * from './settings.schema';
|
export * from './settings.schema';
|
||||||
export * from './template.schema';
|
export * from './template.schema';
|
||||||
|
export * from './templateDelete.schema';
|
||||||
|
export * from './templateEdit.schema';
|
||||||
export * from '@api/integrations/chatbot/chatbot.schema';
|
export * from '@api/integrations/chatbot/chatbot.schema';
|
||||||
export * from '@api/integrations/event/event.schema';
|
export * from '@api/integrations/event/event.schema';
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user