-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Feat/update and delete meta templates #2163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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, | ||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+145
to
+152
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Avoid using 'as any' in Prisma query filter. Explicitly define the filter type or refactor the query to maintain type safety and prevent hidden type errors.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||
| } 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) { | ||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||
| let urlServer = this.configService.get<WaBusiness>('WA_BUSINESS').URL; | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -116,4 +187,38 @@ export class TemplateService { | |||||||||||||||||||||||||||||||||||||||
| 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}`); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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'), | ||
| }; |
| 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: 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' }, | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Specify item type for components array in schema. Defining items: { type: 'object' } for the components array will ensure proper validation and prevent invalid data.
Suggested change
|
||||||||||||
| }, | ||||||||||||
| required: ['templateId'], | ||||||||||||
| ...isNotEmpty('templateId'), | ||||||||||||
| }; | ||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Check for empty array or object in components assignment.
The current logic excludes empty arrays or objects from assignment, which may be valid. Use a check like data.components !== undefined to allow empty values if appropriate.