Skip to content

Commit 1e3a235

Browse files
Merge pull request #2163 from ricaelchiquetti/feat/update_delete_meta_templates
Feat/update and delete meta templates
2 parents 27be03e + 1aaad54 commit 1e3a235

File tree

7 files changed

+233
-1
lines changed

7 files changed

+233
-1
lines changed

src/api/controllers/template.controller.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,15 @@ export class TemplateController {
1212
public async findTemplate(instance: InstanceDto) {
1313
return this.templateService.find(instance);
1414
}
15+
16+
public async editTemplate(
17+
instance: InstanceDto,
18+
data: { templateId: string; category?: string; components?: any; allowCategoryChange?: boolean; ttl?: number },
19+
) {
20+
return this.templateService.edit(instance, data);
21+
}
22+
23+
public async deleteTemplate(instance: InstanceDto, data: { name: string; hsmId?: string }) {
24+
return this.templateService.delete(instance, data);
25+
}
1526
}

src/api/dto/template.dto.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,16 @@ export class TemplateDto {
66
components: any;
77
webhookUrl?: string;
88
}
9+
10+
export class TemplateEditDto {
11+
templateId: string;
12+
category?: 'AUTHENTICATION' | 'MARKETING' | 'UTILITY';
13+
allowCategoryChange?: boolean;
14+
ttl?: number;
15+
components?: any;
16+
}
17+
18+
export class TemplateDeleteDto {
19+
name: string;
20+
hsmId?: string;
21+
}

src/api/routes/template.router.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { RouterBroker } from '@api/abstract/abstract.router';
22
import { InstanceDto } from '@api/dto/instance.dto';
3-
import { TemplateDto } from '@api/dto/template.dto';
3+
import { TemplateDeleteDto, TemplateDto, TemplateEditDto } from '@api/dto/template.dto';
44
import { templateController } from '@api/server.module';
55
import { ConfigService } from '@config/env.config';
66
import { createMetaErrorResponse } from '@utils/errorResponse';
7+
import { templateDeleteSchema } from '@validate/templateDelete.schema';
8+
import { templateEditSchema } from '@validate/templateEdit.schema';
79
import { instanceSchema, templateSchema } from '@validate/validate.schema';
810
import { RequestHandler, Router } from 'express';
911

@@ -35,6 +37,38 @@ export class TemplateRouter extends RouterBroker {
3537
res.status(errorResponse.status).json(errorResponse);
3638
}
3739
})
40+
.post(this.routerPath('edit'), ...guards, async (req, res) => {
41+
try {
42+
const response = await this.dataValidate<TemplateEditDto>({
43+
request: req,
44+
schema: templateEditSchema,
45+
ClassRef: TemplateEditDto,
46+
execute: (instance, data) => templateController.editTemplate(instance, data),
47+
});
48+
49+
res.status(HttpStatus.OK).json(response);
50+
} catch (error) {
51+
console.error('Template edit error:', error);
52+
const errorResponse = createMetaErrorResponse(error, 'template_edit');
53+
res.status(errorResponse.status).json(errorResponse);
54+
}
55+
})
56+
.delete(this.routerPath('delete'), ...guards, async (req, res) => {
57+
try {
58+
const response = await this.dataValidate<TemplateDeleteDto>({
59+
request: req,
60+
schema: templateDeleteSchema,
61+
ClassRef: TemplateDeleteDto,
62+
execute: (instance, data) => templateController.deleteTemplate(instance, data),
63+
});
64+
65+
res.status(HttpStatus.OK).json(response);
66+
} catch (error) {
67+
console.error('Template delete error:', error);
68+
const errorResponse = createMetaErrorResponse(error, 'template_delete');
69+
res.status(errorResponse.status).json(errorResponse);
70+
}
71+
})
3872
.get(this.routerPath('find'), ...guards, async (req, res) => {
3973
try {
4074
const response = await this.dataValidate<InstanceDto>({

src/api/services/template.service.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,77 @@ export class TemplateService {
8888
}
8989
}
9090

91+
public async edit(
92+
instance: InstanceDto,
93+
data: { templateId: string; category?: string; components?: any; allowCategoryChange?: boolean; ttl?: number },
94+
) {
95+
const getInstance = await this.waMonitor.waInstances[instance.instanceName].instance;
96+
if (!getInstance) {
97+
throw new Error('Instance not found');
98+
}
99+
100+
this.businessId = getInstance.businessId;
101+
this.token = getInstance.token;
102+
103+
const payload: Record<string, unknown> = {};
104+
if (typeof data.category === 'string') payload.category = data.category;
105+
if (typeof data.allowCategoryChange === 'boolean') payload.allow_category_change = data.allowCategoryChange;
106+
if (typeof data.ttl === 'number') payload.time_to_live = data.ttl;
107+
if (data.components) payload.components = data.components;
108+
109+
const response = await this.requestEditTemplate(data.templateId, payload);
110+
111+
if (!response || response.error) {
112+
if (response && response.error) {
113+
const metaError = new Error(response.error.message || 'WhatsApp API Error');
114+
(metaError as any).template = response.error;
115+
throw metaError;
116+
}
117+
throw new Error('Error to edit template');
118+
}
119+
120+
return response;
121+
}
122+
123+
public async delete(instance: InstanceDto, data: { name: string; hsmId?: string }) {
124+
const getInstance = await this.waMonitor.waInstances[instance.instanceName].instance;
125+
if (!getInstance) {
126+
throw new Error('Instance not found');
127+
}
128+
129+
this.businessId = getInstance.businessId;
130+
this.token = getInstance.token;
131+
132+
const response = await this.requestDeleteTemplate({ name: data.name, hsm_id: data.hsmId });
133+
134+
if (!response || response.error) {
135+
if (response && response.error) {
136+
const metaError = new Error(response.error.message || 'WhatsApp API Error');
137+
(metaError as any).template = response.error;
138+
throw metaError;
139+
}
140+
throw new Error('Error to delete template');
141+
}
142+
143+
try {
144+
// Best-effort local cleanup of stored template metadata
145+
await this.prismaRepository.template.deleteMany({
146+
where: {
147+
OR: [
148+
{ name: data.name, instanceId: getInstance.id },
149+
data.hsmId ? { templateId: data.hsmId, instanceId: getInstance.id } : undefined,
150+
].filter(Boolean) as any,
151+
},
152+
});
153+
} catch (err) {
154+
this.logger.warn(
155+
`Failed to cleanup local template records after delete: ${(err as Error)?.message || String(err)}`,
156+
);
157+
}
158+
159+
return response;
160+
}
161+
91162
private async requestTemplate(data: any, method: string) {
92163
try {
93164
let urlServer = this.configService.get<WaBusiness>('WA_BUSINESS').URL;
@@ -116,4 +187,38 @@ export class TemplateService {
116187
throw new Error(`Connection error: ${e.message}`);
117188
}
118189
}
190+
191+
private async requestEditTemplate(templateId: string, data: any) {
192+
try {
193+
let urlServer = this.configService.get<WaBusiness>('WA_BUSINESS').URL;
194+
const version = this.configService.get<WaBusiness>('WA_BUSINESS').VERSION;
195+
urlServer = `${urlServer}/${version}/${templateId}`;
196+
const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${this.token}` };
197+
const result = await axios.post(urlServer, data, { headers });
198+
return result.data;
199+
} catch (e) {
200+
this.logger.error(
201+
'WhatsApp API request error: ' + (e.response?.data ? JSON.stringify(e.response?.data) : e.message),
202+
);
203+
if (e.response?.data) return e.response.data;
204+
throw new Error(`Connection error: ${e.message}`);
205+
}
206+
}
207+
208+
private async requestDeleteTemplate(params: { name: string; hsm_id?: string }) {
209+
try {
210+
let urlServer = this.configService.get<WaBusiness>('WA_BUSINESS').URL;
211+
const version = this.configService.get<WaBusiness>('WA_BUSINESS').VERSION;
212+
urlServer = `${urlServer}/${version}/${this.businessId}/message_templates`;
213+
const headers = { Authorization: `Bearer ${this.token}` };
214+
const result = await axios.delete(urlServer, { headers, params });
215+
return result.data;
216+
} catch (e) {
217+
this.logger.error(
218+
'WhatsApp API request error: ' + (e.response?.data ? JSON.stringify(e.response?.data) : e.message),
219+
);
220+
if (e.response?.data) return e.response.data;
221+
throw new Error(`Connection error: ${e.message}`);
222+
}
223+
}
119224
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { JSONSchema7 } from 'json-schema';
2+
import { v4 } from 'uuid';
3+
4+
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
5+
const properties: Record<string, unknown> = {};
6+
propertyNames.forEach(
7+
(property) =>
8+
(properties[property] = {
9+
minLength: 1,
10+
description: `The "${property}" cannot be empty`,
11+
}),
12+
);
13+
return {
14+
if: {
15+
propertyNames: {
16+
enum: [...propertyNames],
17+
},
18+
},
19+
then: { properties },
20+
} as JSONSchema7;
21+
};
22+
23+
export const templateDeleteSchema: JSONSchema7 = {
24+
$id: v4(),
25+
type: 'object',
26+
properties: {
27+
name: { type: 'string' },
28+
hsmId: { type: 'string' },
29+
},
30+
required: ['name'],
31+
...isNotEmpty('name'),
32+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { JSONSchema7 } from 'json-schema';
2+
import { v4 } from 'uuid';
3+
4+
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
5+
const properties: Record<string, unknown> = {};
6+
propertyNames.forEach(
7+
(property) =>
8+
(properties[property] = {
9+
minLength: 1,
10+
description: `The "${property}" cannot be empty`,
11+
}),
12+
);
13+
return {
14+
if: {
15+
propertyNames: {
16+
enum: [...propertyNames],
17+
},
18+
},
19+
then: { properties },
20+
} as JSONSchema7;
21+
};
22+
23+
export const templateEditSchema: JSONSchema7 = {
24+
$id: v4(),
25+
type: 'object',
26+
properties: {
27+
templateId: { type: 'string' },
28+
category: { type: 'string', enum: ['AUTHENTICATION', 'MARKETING', 'UTILITY'] },
29+
allowCategoryChange: { type: 'boolean' },
30+
ttl: { type: 'number' },
31+
components: { type: 'array' },
32+
},
33+
required: ['templateId'],
34+
...isNotEmpty('templateId'),
35+
};

src/validate/validate.schema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@ export * from './message.schema';
88
export * from './proxy.schema';
99
export * from './settings.schema';
1010
export * from './template.schema';
11+
export * from './templateDelete.schema';
12+
export * from './templateEdit.schema';
1113
export * from '@api/integrations/chatbot/chatbot.schema';
1214
export * from '@api/integrations/event/event.schema';

0 commit comments

Comments
 (0)