FEAT: syncFromMeta importa templates pre-aprovados da Meta
Busca components (body, header, footer, botoes) e importa templates que existem na Meta mas nao estao cadastrados localmente. Retorna synced e imported separados.
This commit is contained in:
parent
980b36fadd
commit
85d89ae934
@ -163,6 +163,52 @@ export class WhatsappTemplateRepository {
|
||||
return result.rowCount || 0;
|
||||
}
|
||||
|
||||
async importFromMeta(input: {
|
||||
metaTemplateId: string;
|
||||
metaTemplateName: string;
|
||||
metaStatus: string;
|
||||
language: string;
|
||||
category: string;
|
||||
content: string;
|
||||
headerType?: string | null;
|
||||
headerText?: string | null;
|
||||
footerText?: string | null;
|
||||
buttonsJson: string | null;
|
||||
}): Promise<boolean> {
|
||||
try {
|
||||
const result = await this.database.query(
|
||||
`INSERT INTO whatsapp_templates (
|
||||
name, content, category, language,
|
||||
meta_template_name, meta_template_id, meta_status,
|
||||
header_type, header_text, footer_text, buttons, body_variables,
|
||||
status, meta_approved_at, updated_at
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11::jsonb, NULL,
|
||||
CASE WHEN $7 = 'APPROVED' THEN 'approved' ELSE 'pending' END,
|
||||
CASE WHEN $7 = 'APPROVED' THEN CURRENT_TIMESTAMP ELSE NULL END,
|
||||
CURRENT_TIMESTAMP
|
||||
)
|
||||
ON CONFLICT (meta_template_name) WHERE meta_template_name IS NOT NULL DO NOTHING`,
|
||||
[
|
||||
input.metaTemplateName,
|
||||
input.content,
|
||||
input.category,
|
||||
input.language,
|
||||
input.metaTemplateName,
|
||||
input.metaTemplateId,
|
||||
input.metaStatus,
|
||||
input.headerType || null,
|
||||
input.headerText || null,
|
||||
input.footerText || null,
|
||||
input.buttonsJson,
|
||||
],
|
||||
);
|
||||
return (result.rowCount || 0) > 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
deleteTemplate(id: number) {
|
||||
return this.database.query('DELETE FROM whatsapp_templates WHERE id = $1 RETURNING meta_template_name', [id]);
|
||||
}
|
||||
|
||||
@ -114,12 +114,12 @@ export class WhatsappTemplateService {
|
||||
async syncFromMeta() {
|
||||
const config = await this.configService.getConfig();
|
||||
if (!config.access_token || !config.waba_id) {
|
||||
return { synced: 0, error: 'WhatsApp não configurado com WABA ID' };
|
||||
return { synced: 0, imported: 0, error: 'WhatsApp não configurado com WABA ID' };
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://graph.facebook.com/${config.api_version}/${config.waba_id}/message_templates?fields=id,name,status&limit=250`,
|
||||
`https://graph.facebook.com/${config.api_version}/${config.waba_id}/message_templates?fields=id,name,status,language,category,components&limit=250`,
|
||||
{ headers: { Authorization: `Bearer ${config.access_token}` } },
|
||||
);
|
||||
|
||||
@ -127,19 +127,44 @@ export class WhatsappTemplateService {
|
||||
|
||||
if (!response.ok || !Array.isArray(data?.data)) {
|
||||
this.logger.error('[Meta Templates] Erro ao buscar lista:', data?.error);
|
||||
return { synced: 0, error: data?.error?.message || 'Erro ao sincronizar' };
|
||||
return { synced: 0, imported: 0, error: data?.error?.message || 'Erro ao sincronizar' };
|
||||
}
|
||||
|
||||
let synced = 0;
|
||||
let imported = 0;
|
||||
|
||||
for (const t of data.data) {
|
||||
const updated = await this.repo.updateMetaStatusByName(t.name, t.status, String(t.id));
|
||||
if (updated > 0) synced++;
|
||||
const metaId = String(t.id);
|
||||
const updated = await this.repo.updateMetaStatusByName(t.name, t.status, metaId);
|
||||
if (updated > 0) {
|
||||
synced++;
|
||||
continue;
|
||||
}
|
||||
|
||||
return { synced };
|
||||
const parsed = this.parseComponents(t.components || []);
|
||||
const wasInserted = await this.repo.importFromMeta({
|
||||
metaTemplateId: metaId,
|
||||
metaTemplateName: t.name,
|
||||
metaStatus: t.status,
|
||||
language: t.language || 'pt_BR',
|
||||
category: this.normalizeCategory(t.category),
|
||||
content: parsed.content,
|
||||
headerType: parsed.headerType,
|
||||
headerText: parsed.headerText,
|
||||
footerText: parsed.footerText,
|
||||
buttonsJson: parsed.buttons?.length ? JSON.stringify(parsed.buttons) : null,
|
||||
});
|
||||
|
||||
if (wasInserted) {
|
||||
imported++;
|
||||
this.logger.log(`[Meta Templates] Importado: "${t.name}" (${t.status})`);
|
||||
}
|
||||
}
|
||||
|
||||
return { synced, imported };
|
||||
} catch (err: any) {
|
||||
this.logger.error('[Meta Templates] Falha na sincronização:', err?.message);
|
||||
return { synced: 0, error: 'Erro de conexão com a Meta' };
|
||||
return { synced: 0, imported: 0, error: 'Erro de conexão com a Meta' };
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,6 +248,33 @@ export class WhatsappTemplateService {
|
||||
return components;
|
||||
}
|
||||
|
||||
private parseComponents(components: any[]): {
|
||||
content: string;
|
||||
headerType?: string | null;
|
||||
headerText?: string | null;
|
||||
footerText?: string | null;
|
||||
buttons?: any[];
|
||||
} {
|
||||
const result: any = { content: '', headerType: null, headerText: null, footerText: null, buttons: [] };
|
||||
for (const c of components || []) {
|
||||
if (c.type === 'BODY') result.content = c.text || '';
|
||||
else if (c.type === 'HEADER') {
|
||||
result.headerType = c.format || null;
|
||||
result.headerText = c.format === 'TEXT' ? (c.text || null) : null;
|
||||
}
|
||||
else if (c.type === 'FOOTER') result.footerText = c.text || null;
|
||||
else if (c.type === 'BUTTONS') {
|
||||
result.buttons = (c.buttons || []).map((b: any) => ({
|
||||
type: b.type,
|
||||
text: b.text || '',
|
||||
url: b.url || '',
|
||||
phone: b.phone_number || '',
|
||||
}));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private toMetaName(displayName: string): string {
|
||||
return String(displayName || '')
|
||||
.normalize('NFD')
|
||||
|
||||
Loading…
Reference in New Issue
Block a user