@adatechnology/meta-whatsapp-module 0.2.0-rc.13 → 0.2.0-rc.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +279 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +363 -3
- package/dist/index.d.ts +363 -3
- package/dist/index.js +274 -8
- package/dist/index.js.map +1 -1
- package/dist/migrations/0007_flow_media.sql +18 -0
- package/dist/migrations/meta/_journal.json +7 -0
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -22,12 +22,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
22
22
|
var index_exports = {};
|
|
23
23
|
__export(index_exports, {
|
|
24
24
|
CreateFlowGraphUseCase: () => CreateFlowGraphUseCase,
|
|
25
|
+
DEFAULT_FLOW_GRAPH_CACHE_TTL_SECONDS: () => DEFAULT_FLOW_GRAPH_CACHE_TTL_SECONDS,
|
|
25
26
|
DeleteConversationUseCase: () => DeleteConversationUseCase,
|
|
26
27
|
DeleteFlowGraphUseCase: () => DeleteFlowGraphUseCase,
|
|
27
28
|
DocumentRepository: () => DocumentRepository,
|
|
28
29
|
ExportConversationUseCase: () => ExportConversationUseCase,
|
|
30
|
+
FlowGraphCache: () => FlowGraphCache,
|
|
29
31
|
FlowGraphRepository: () => FlowGraphRepository,
|
|
30
32
|
FlowInterpreter: () => FlowInterpreter,
|
|
33
|
+
FlowMediaRepository: () => FlowMediaRepository,
|
|
31
34
|
GetFlowGraphUseCase: () => GetFlowGraphUseCase,
|
|
32
35
|
GetLiveFlowPositionsUseCase: () => GetLiveFlowPositionsUseCase,
|
|
33
36
|
IngestInboundMediaUseCase: () => IngestInboundMediaUseCase,
|
|
@@ -54,9 +57,11 @@ __export(index_exports, {
|
|
|
54
57
|
WhatsAppChannelAdapter: () => WhatsAppChannelAdapter,
|
|
55
58
|
claimWebhookDelivery: () => claimWebhookDelivery,
|
|
56
59
|
createMetaWhatsAppModule: () => createMetaWhatsAppModule,
|
|
60
|
+
createSendMediaAction: () => createSendMediaAction,
|
|
57
61
|
documents: () => documents,
|
|
58
62
|
extractMediaDescriptor: () => extractMediaDescriptor,
|
|
59
63
|
flowGraphs: () => flowGraphs,
|
|
64
|
+
flowMedia: () => flowMedia,
|
|
60
65
|
issueSseTicket: () => issueSseTicket,
|
|
61
66
|
messages: () => messages,
|
|
62
67
|
metaWhatsAppMigrationsFolder: () => metaWhatsAppMigrationsFolder,
|
|
@@ -72,6 +77,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
72
77
|
|
|
73
78
|
// src/createMetaWhatsAppModule.ts
|
|
74
79
|
var import_meta_whatsapp_provider = require("@adatechnology/meta-whatsapp-provider");
|
|
80
|
+
var import_meta_whatsapp_contracts11 = require("@adatechnology/meta-whatsapp-contracts");
|
|
75
81
|
|
|
76
82
|
// src/repositories/SessionRepository.ts
|
|
77
83
|
var import_drizzle_orm2 = require("drizzle-orm");
|
|
@@ -259,6 +265,46 @@ var flowGraphs = metaWhatsAppSchema.table("flow_graphs", {
|
|
|
259
265
|
}, (table) => [
|
|
260
266
|
(0, import_pg_core.uniqueIndex)("idx_flow_graphs_company_key").on(table.companyId, table.key)
|
|
261
267
|
]);
|
|
268
|
+
var flowMedia = metaWhatsAppSchema.table("flow_media", {
|
|
269
|
+
id: (0, import_pg_core.uuid)("id").primaryKey().defaultRandom(),
|
|
270
|
+
companyId: (0, import_pg_core.uuid)("company_id").notNull(),
|
|
271
|
+
flowKey: (0, import_pg_core.varchar)("flow_key", {
|
|
272
|
+
length: 64
|
|
273
|
+
}).notNull(),
|
|
274
|
+
nodeId: (0, import_pg_core.varchar)("node_id", {
|
|
275
|
+
length: 64
|
|
276
|
+
}).notNull(),
|
|
277
|
+
uploadId: (0, import_pg_core.varchar)("upload_id", {
|
|
278
|
+
length: 256
|
|
279
|
+
}).notNull(),
|
|
280
|
+
filename: (0, import_pg_core.varchar)("filename", {
|
|
281
|
+
length: 512
|
|
282
|
+
}).notNull(),
|
|
283
|
+
mimeType: (0, import_pg_core.varchar)("mime_type", {
|
|
284
|
+
length: 128
|
|
285
|
+
}).notNull(),
|
|
286
|
+
sizeBytes: (0, import_pg_core.integer)("size_bytes").notNull(),
|
|
287
|
+
// Legenda da mídia no WhatsApp. Por arquivo, não por nó: um nó que manda tabela de preços e
|
|
288
|
+
// um folder precisa de textos diferentes para cada um.
|
|
289
|
+
caption: (0, import_pg_core.text)("caption"),
|
|
290
|
+
// Ordem de envio dentro do nó — o cliente recebe as mensagens em sequência, e "tabela antes
|
|
291
|
+
// do folder" é decisão de quem edita, não do banco.
|
|
292
|
+
sortOrder: (0, import_pg_core.integer)("sort_order").notNull().default(0),
|
|
293
|
+
// Desligar sem desanexar: trocar o material da campanha é o caso comum, e apagar a linha
|
|
294
|
+
// perderia a ordem e a legenda já ajustadas.
|
|
295
|
+
active: (0, import_pg_core.boolean)("active").notNull().default(true),
|
|
296
|
+
createdAt: (0, import_pg_core.timestamp)("created_at", {
|
|
297
|
+
withTimezone: true
|
|
298
|
+
}).notNull().defaultNow(),
|
|
299
|
+
updatedAt: (0, import_pg_core.timestamp)("updated_at", {
|
|
300
|
+
withTimezone: true
|
|
301
|
+
}).notNull().defaultNow()
|
|
302
|
+
}, (table) => [
|
|
303
|
+
(0, import_pg_core.index)("idx_flow_media_node").on(table.companyId, table.flowKey, table.nodeId, table.sortOrder),
|
|
304
|
+
// O mesmo arquivo anexado duas vezes ao MESMO nó é erro de clique no editor, e o cliente
|
|
305
|
+
// receberia o documento repetido.
|
|
306
|
+
(0, import_pg_core.uniqueIndex)("idx_flow_media_node_upload").on(table.companyId, table.flowKey, table.nodeId, table.uploadId)
|
|
307
|
+
]);
|
|
262
308
|
var settings = metaWhatsAppSchema.table("settings", {
|
|
263
309
|
companyId: (0, import_pg_core.uuid)("company_id").primaryKey(),
|
|
264
310
|
templateName: (0, import_pg_core.varchar)("template_name", {
|
|
@@ -583,12 +629,21 @@ var FlowGraphRepository = class {
|
|
|
583
629
|
__name(this, "FlowGraphRepository");
|
|
584
630
|
}
|
|
585
631
|
db;
|
|
586
|
-
|
|
632
|
+
cache;
|
|
633
|
+
// Cache opcional: sem ele o repositório se comporta exatamente como antes, lendo sempre do
|
|
634
|
+
// banco. É o host que decide se quer cachear e com qual provedor (ver CacheInterface).
|
|
635
|
+
constructor(db, cache) {
|
|
587
636
|
this.db = db;
|
|
637
|
+
this.cache = cache;
|
|
588
638
|
}
|
|
589
639
|
async get(companyId, key) {
|
|
640
|
+
const cached = await this.cache?.read(companyId, key);
|
|
641
|
+
if (cached) return cached;
|
|
590
642
|
const [row] = await this.db.select().from(flowGraphs).where((0, import_drizzle_orm4.and)((0, import_drizzle_orm4.eq)(flowGraphs.companyId, companyId), (0, import_drizzle_orm4.eq)(flowGraphs.key, key))).limit(1);
|
|
591
|
-
|
|
643
|
+
if (!row) return void 0;
|
|
644
|
+
const graph = toContractGraph(row);
|
|
645
|
+
await this.cache?.write(companyId, graph);
|
|
646
|
+
return graph;
|
|
592
647
|
}
|
|
593
648
|
async list(companyId) {
|
|
594
649
|
const rows = await this.db.select().from(flowGraphs).where((0, import_drizzle_orm4.eq)(flowGraphs.companyId, companyId));
|
|
@@ -618,7 +673,9 @@ var FlowGraphRepository = class {
|
|
|
618
673
|
showInMenu: graph.showInMenu ?? false,
|
|
619
674
|
menuOptionLabel: graph.menuOptionLabel
|
|
620
675
|
}).returning();
|
|
621
|
-
|
|
676
|
+
const createdGraph = toContractGraph(created);
|
|
677
|
+
await this.cache?.invalidate(companyId, createdGraph.key);
|
|
678
|
+
return createdGraph;
|
|
622
679
|
}
|
|
623
680
|
// Lock otimista: a escrita só aplica se `expectedVersion` ainda bater com o que está salvo —
|
|
624
681
|
// senão, alguém mais salvou entretanto e o editor precisa recarregar (ver comentário no schema).
|
|
@@ -632,10 +689,12 @@ var FlowGraphRepository = class {
|
|
|
632
689
|
updatedAt: /* @__PURE__ */ new Date()
|
|
633
690
|
}).where((0, import_drizzle_orm4.and)((0, import_drizzle_orm4.eq)(flowGraphs.companyId, companyId), (0, import_drizzle_orm4.eq)(flowGraphs.key, graph.key), (0, import_drizzle_orm4.eq)(flowGraphs.version, expectedVersion))).returning();
|
|
634
691
|
if (rows.length === 0) throw new OptimisticLockError(graph.key);
|
|
692
|
+
await this.cache?.invalidate(companyId, graph.key);
|
|
635
693
|
return toContractGraph(rows[0]);
|
|
636
694
|
}
|
|
637
695
|
async delete(companyId, key) {
|
|
638
696
|
await this.db.delete(flowGraphs).where((0, import_drizzle_orm4.and)((0, import_drizzle_orm4.eq)(flowGraphs.companyId, companyId), (0, import_drizzle_orm4.eq)(flowGraphs.key, key)));
|
|
697
|
+
await this.cache?.invalidate(companyId, key);
|
|
639
698
|
}
|
|
640
699
|
// T4.2 — GetLiveFlowPositions: agrega sessões ativas por (flowKey, currentNodeId), lendo as
|
|
641
700
|
// colunas dedicadas gravadas por SessionRepository.setFlowPosition. Agrega no banco (GROUP BY,
|
|
@@ -655,6 +714,48 @@ var FlowGraphRepository = class {
|
|
|
655
714
|
}
|
|
656
715
|
};
|
|
657
716
|
|
|
717
|
+
// src/repositories/FlowGraphCache.ts
|
|
718
|
+
var KEY_PREFIX = "meta-whatsapp:flow-graph";
|
|
719
|
+
var DEFAULT_FLOW_GRAPH_CACHE_TTL_SECONDS = 300;
|
|
720
|
+
var FlowGraphCache = class {
|
|
721
|
+
static {
|
|
722
|
+
__name(this, "FlowGraphCache");
|
|
723
|
+
}
|
|
724
|
+
provider;
|
|
725
|
+
ttlSeconds;
|
|
726
|
+
constructor(provider, ttlSeconds = DEFAULT_FLOW_GRAPH_CACHE_TTL_SECONDS) {
|
|
727
|
+
this.provider = provider;
|
|
728
|
+
this.ttlSeconds = ttlSeconds;
|
|
729
|
+
}
|
|
730
|
+
// companyId na chave, e não só a flowKey: a chave do fluxo é escolhida por quem edita e se
|
|
731
|
+
// repete entre empresas — 'consorcio' existe em todas — então uma chave sem tenant serviria o
|
|
732
|
+
// grafo de uma empresa para a conversa de outra.
|
|
733
|
+
keyFor(companyId, flowKey) {
|
|
734
|
+
return `${KEY_PREFIX}:${companyId}:${flowKey}`;
|
|
735
|
+
}
|
|
736
|
+
async read(companyId, flowKey) {
|
|
737
|
+
try {
|
|
738
|
+
const cached = await this.provider.get(this.keyFor(companyId, flowKey));
|
|
739
|
+
if (!cached) return void 0;
|
|
740
|
+
return JSON.parse(cached);
|
|
741
|
+
} catch {
|
|
742
|
+
return void 0;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
async write(companyId, graph) {
|
|
746
|
+
try {
|
|
747
|
+
await this.provider.set(this.keyFor(companyId, graph.key), JSON.stringify(graph), this.ttlSeconds);
|
|
748
|
+
} catch {
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
async invalidate(companyId, flowKey) {
|
|
752
|
+
try {
|
|
753
|
+
await this.provider.delete(this.keyFor(companyId, flowKey));
|
|
754
|
+
} catch {
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
|
|
658
759
|
// src/repositories/SettingsRepository.ts
|
|
659
760
|
var import_drizzle_orm5 = require("drizzle-orm");
|
|
660
761
|
function toContractSettings(row) {
|
|
@@ -1551,6 +1652,151 @@ var FlowInterpreter = class {
|
|
|
1551
1652
|
}
|
|
1552
1653
|
};
|
|
1553
1654
|
|
|
1655
|
+
// src/flows/createSendMediaAction.ts
|
|
1656
|
+
function mediaTypeFor2(mimeType) {
|
|
1657
|
+
if (mimeType.startsWith("image/")) return "image";
|
|
1658
|
+
if (mimeType.startsWith("audio/")) return "audio";
|
|
1659
|
+
if (mimeType.startsWith("video/")) return "video";
|
|
1660
|
+
return "document";
|
|
1661
|
+
}
|
|
1662
|
+
__name(mediaTypeFor2, "mediaTypeFor");
|
|
1663
|
+
function createSendMediaAction(params) {
|
|
1664
|
+
return async ({ node, session, channel }) => {
|
|
1665
|
+
if (!session.flowKey) return;
|
|
1666
|
+
const location = {
|
|
1667
|
+
companyId: session.companyId,
|
|
1668
|
+
flowKey: session.flowKey,
|
|
1669
|
+
nodeId: node.id
|
|
1670
|
+
};
|
|
1671
|
+
const attachments = await params.flowMediaRepository.listActive(location);
|
|
1672
|
+
for (const attachment of attachments) {
|
|
1673
|
+
try {
|
|
1674
|
+
const buffer = await params.objectStorage.getObject(attachment.uploadId);
|
|
1675
|
+
const { externalMessageId } = await channel.sendMedia({
|
|
1676
|
+
to: session.whatsappNumber,
|
|
1677
|
+
buffer,
|
|
1678
|
+
mimeType: attachment.mimeType,
|
|
1679
|
+
filename: attachment.filename,
|
|
1680
|
+
caption: attachment.caption ?? void 0
|
|
1681
|
+
});
|
|
1682
|
+
await params.logMessage.execute({
|
|
1683
|
+
companyId: session.companyId,
|
|
1684
|
+
whatsappNumber: session.whatsappNumber,
|
|
1685
|
+
direction: "outbound",
|
|
1686
|
+
sender: "bot",
|
|
1687
|
+
agentUserId: null,
|
|
1688
|
+
type: mediaTypeFor2(attachment.mimeType),
|
|
1689
|
+
content: attachment.caption ?? attachment.filename,
|
|
1690
|
+
payload: {
|
|
1691
|
+
filename: attachment.filename,
|
|
1692
|
+
mimeType: attachment.mimeType,
|
|
1693
|
+
uploadId: attachment.uploadId,
|
|
1694
|
+
flowMediaId: attachment.id
|
|
1695
|
+
},
|
|
1696
|
+
waMessageId: externalMessageId,
|
|
1697
|
+
status: "sent",
|
|
1698
|
+
startState: params.startState
|
|
1699
|
+
});
|
|
1700
|
+
} catch (error) {
|
|
1701
|
+
params.onError?.(error, {
|
|
1702
|
+
flowKey: location.flowKey,
|
|
1703
|
+
nodeId: node.id,
|
|
1704
|
+
uploadId: attachment.uploadId
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
__name(createSendMediaAction, "createSendMediaAction");
|
|
1711
|
+
|
|
1712
|
+
// src/repositories/FlowMediaRepository.ts
|
|
1713
|
+
var import_drizzle_orm7 = require("drizzle-orm");
|
|
1714
|
+
var FlowMediaRepository = class {
|
|
1715
|
+
static {
|
|
1716
|
+
__name(this, "FlowMediaRepository");
|
|
1717
|
+
}
|
|
1718
|
+
db;
|
|
1719
|
+
constructor(db) {
|
|
1720
|
+
this.db = db;
|
|
1721
|
+
}
|
|
1722
|
+
locationFilter(location) {
|
|
1723
|
+
return (0, import_drizzle_orm7.and)((0, import_drizzle_orm7.eq)(flowMedia.companyId, location.companyId), (0, import_drizzle_orm7.eq)(flowMedia.flowKey, location.flowKey), (0, import_drizzle_orm7.eq)(flowMedia.nodeId, location.nodeId));
|
|
1724
|
+
}
|
|
1725
|
+
// O que o nó realmente envia, na ordem de envio. `active` filtrado aqui e não no chamador:
|
|
1726
|
+
// é a razão de a coluna existir, e um chamador que esquecesse do filtro mandaria ao cliente
|
|
1727
|
+
// justamente o material que alguém desligou.
|
|
1728
|
+
async listActive(location) {
|
|
1729
|
+
return this.db.select().from(flowMedia).where((0, import_drizzle_orm7.and)(this.locationFilter(location), (0, import_drizzle_orm7.eq)(flowMedia.active, true))).orderBy((0, import_drizzle_orm7.asc)(flowMedia.sortOrder), (0, import_drizzle_orm7.asc)(flowMedia.createdAt));
|
|
1730
|
+
}
|
|
1731
|
+
// Inclui os desligados — é a visão do editor, onde desligar precisa continuar visível para
|
|
1732
|
+
// poder ser religado.
|
|
1733
|
+
async listAll(location) {
|
|
1734
|
+
return this.db.select().from(flowMedia).where(this.locationFilter(location)).orderBy((0, import_drizzle_orm7.asc)(flowMedia.sortOrder), (0, import_drizzle_orm7.asc)(flowMedia.createdAt));
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* Anexa um arquivo já existente no storage ao nó.
|
|
1738
|
+
*
|
|
1739
|
+
* `onConflictDoUpdate` em vez de deixar estourar: reanexar o mesmo arquivo é clique repetido no
|
|
1740
|
+
* editor, e o esperado ali é atualizar a legenda/ordem — não um erro de índice único na cara de
|
|
1741
|
+
* quem está montando o fluxo.
|
|
1742
|
+
*/
|
|
1743
|
+
async attach(params) {
|
|
1744
|
+
const [row] = await this.db.insert(flowMedia).values({
|
|
1745
|
+
companyId: params.companyId,
|
|
1746
|
+
flowKey: params.flowKey,
|
|
1747
|
+
nodeId: params.nodeId,
|
|
1748
|
+
uploadId: params.uploadId,
|
|
1749
|
+
filename: params.filename,
|
|
1750
|
+
mimeType: params.mimeType,
|
|
1751
|
+
sizeBytes: params.sizeBytes,
|
|
1752
|
+
caption: params.caption ?? null,
|
|
1753
|
+
sortOrder: params.sortOrder ?? 0
|
|
1754
|
+
}).onConflictDoUpdate({
|
|
1755
|
+
target: [
|
|
1756
|
+
flowMedia.companyId,
|
|
1757
|
+
flowMedia.flowKey,
|
|
1758
|
+
flowMedia.nodeId,
|
|
1759
|
+
flowMedia.uploadId
|
|
1760
|
+
],
|
|
1761
|
+
set: {
|
|
1762
|
+
caption: params.caption ?? null,
|
|
1763
|
+
sortOrder: params.sortOrder ?? 0,
|
|
1764
|
+
active: true,
|
|
1765
|
+
updatedAt: import_drizzle_orm7.sql`now()`
|
|
1766
|
+
}
|
|
1767
|
+
}).returning();
|
|
1768
|
+
return row;
|
|
1769
|
+
}
|
|
1770
|
+
async update(params) {
|
|
1771
|
+
const [row] = await this.db.update(flowMedia).set({
|
|
1772
|
+
...params.caption !== void 0 ? {
|
|
1773
|
+
caption: params.caption
|
|
1774
|
+
} : {},
|
|
1775
|
+
...params.sortOrder !== void 0 ? {
|
|
1776
|
+
sortOrder: params.sortOrder
|
|
1777
|
+
} : {},
|
|
1778
|
+
...params.active !== void 0 ? {
|
|
1779
|
+
active: params.active
|
|
1780
|
+
} : {},
|
|
1781
|
+
updatedAt: import_drizzle_orm7.sql`now()`
|
|
1782
|
+
}).where((0, import_drizzle_orm7.and)((0, import_drizzle_orm7.eq)(flowMedia.companyId, params.companyId), (0, import_drizzle_orm7.eq)(flowMedia.id, params.id))).returning();
|
|
1783
|
+
return row;
|
|
1784
|
+
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Desanexa do nó. Não toca no storage de propósito: o mesmo `uploadId` pode estar anexado a
|
|
1787
|
+
* outro nó ou a outro fluxo, e apagar o binário aqui quebraria os demais. Quem apaga objeto é o
|
|
1788
|
+
* host, que é dono da biblioteca de arquivos.
|
|
1789
|
+
*/
|
|
1790
|
+
async detach(params) {
|
|
1791
|
+
await this.db.delete(flowMedia).where((0, import_drizzle_orm7.and)((0, import_drizzle_orm7.eq)(flowMedia.companyId, params.companyId), (0, import_drizzle_orm7.eq)(flowMedia.id, params.id)));
|
|
1792
|
+
}
|
|
1793
|
+
// Chamado ao salvar o grafo: nós apagados no editor deixam linhas que nada mais alcança.
|
|
1794
|
+
async detachRemovedNodes(params) {
|
|
1795
|
+
const condition = params.existingNodeIds.length === 0 ? void 0 : import_drizzle_orm7.sql`${flowMedia.nodeId} NOT IN ${params.existingNodeIds}`;
|
|
1796
|
+
await this.db.delete(flowMedia).where((0, import_drizzle_orm7.and)((0, import_drizzle_orm7.eq)(flowMedia.companyId, params.companyId), (0, import_drizzle_orm7.eq)(flowMedia.flowKey, params.flowKey), condition));
|
|
1797
|
+
}
|
|
1798
|
+
};
|
|
1799
|
+
|
|
1554
1800
|
// src/channel/WhatsAppChannelAdapter.ts
|
|
1555
1801
|
var import_meta_graph_core = require("@adatechnology/meta-graph-core");
|
|
1556
1802
|
var import_meta_whatsapp_contracts8 = require("@adatechnology/meta-whatsapp-contracts");
|
|
@@ -1647,7 +1893,7 @@ async function claimWebhookDelivery(params) {
|
|
|
1647
1893
|
__name(claimWebhookDelivery, "claimWebhookDelivery");
|
|
1648
1894
|
|
|
1649
1895
|
// src/channel/IngestInboundMedia.use-case.ts
|
|
1650
|
-
var
|
|
1896
|
+
var import_drizzle_orm8 = require("drizzle-orm");
|
|
1651
1897
|
var IngestInboundMediaUseCase = class {
|
|
1652
1898
|
static {
|
|
1653
1899
|
__name(this, "IngestInboundMediaUseCase");
|
|
@@ -1663,7 +1909,7 @@ var IngestInboundMediaUseCase = class {
|
|
|
1663
1909
|
this.documentRepository = documentRepository;
|
|
1664
1910
|
}
|
|
1665
1911
|
async execute(params) {
|
|
1666
|
-
const [message] = await this.db.select().from(messages).where((0,
|
|
1912
|
+
const [message] = await this.db.select().from(messages).where((0, import_drizzle_orm8.and)((0, import_drizzle_orm8.eq)(messages.companyId, params.companyId), (0, import_drizzle_orm8.eq)(messages.id, params.messageId))).limit(1);
|
|
1667
1913
|
if (!message) throw new Error(`Mensagem ${params.messageId} n\xE3o encontrada para ingest\xE3o de m\xEDdia`);
|
|
1668
1914
|
const payload = message.payload ?? {};
|
|
1669
1915
|
if (payload["uploadId"] && payload["sourceMediaId"] === params.sourceMediaId) {
|
|
@@ -1684,13 +1930,17 @@ var IngestInboundMediaUseCase = class {
|
|
|
1684
1930
|
uploadId,
|
|
1685
1931
|
sourceMediaId: params.sourceMediaId,
|
|
1686
1932
|
mimeType: mimeType || params.mimeType,
|
|
1933
|
+
// O tamanho já está na mão (é o buffer que acabou de ser copiado) e a bolha de documento o
|
|
1934
|
+
// exibe ao lado do tipo. Sem gravar aqui, a UI mostraria "PDF" sem o "· 180 KB", e buscá-lo
|
|
1935
|
+
// depois custaria uma consulta à tabela de documentos por mensagem renderizada.
|
|
1936
|
+
sizeBytes: buffer.length,
|
|
1687
1937
|
...params.filename ? {
|
|
1688
1938
|
filename: params.filename
|
|
1689
1939
|
} : {}
|
|
1690
1940
|
};
|
|
1691
1941
|
await this.db.update(messages).set({
|
|
1692
1942
|
payload: updatedPayload
|
|
1693
|
-
}).where((0,
|
|
1943
|
+
}).where((0, import_drizzle_orm8.and)((0, import_drizzle_orm8.eq)(messages.companyId, params.companyId), (0, import_drizzle_orm8.eq)(messages.id, params.messageId)));
|
|
1694
1944
|
await this.documentRepository?.link({
|
|
1695
1945
|
companyId: params.companyId,
|
|
1696
1946
|
sessionId: message.sessionId,
|
|
@@ -1890,8 +2140,11 @@ function createMetaWhatsAppModule(params) {
|
|
|
1890
2140
|
const sessionRepository = new SessionRepository(db);
|
|
1891
2141
|
const messageRepository = new MessageRepository(db);
|
|
1892
2142
|
const settingsRepository = new SettingsRepository(db);
|
|
1893
|
-
const
|
|
2143
|
+
const flowGraphCacheFeature = params.features?.flowGraphCache ?? false;
|
|
2144
|
+
const flowGraphCache = flowGraphCacheFeature && providers.cache ? new FlowGraphCache(providers.cache, typeof flowGraphCacheFeature === "object" ? flowGraphCacheFeature.ttlSeconds ?? DEFAULT_FLOW_GRAPH_CACHE_TTL_SECONDS : DEFAULT_FLOW_GRAPH_CACHE_TTL_SECONDS) : void 0;
|
|
2145
|
+
const flowGraphRepository = new FlowGraphRepository(db, flowGraphCache);
|
|
1894
2146
|
const documentRepository = new DocumentRepository(db);
|
|
2147
|
+
const flowMediaRepository = new FlowMediaRepository(db);
|
|
1895
2148
|
const logMessage = new LogMessageUseCase(sessionRepository, messageRepository, providers.realtime, providers.moderator);
|
|
1896
2149
|
const sendMessage = new SendMessageUseCase(channel, sessionRepository, logMessage, providers.objectStorage, documentRepository);
|
|
1897
2150
|
const receiveWebhook = new ReceiveWebhookUseCase({
|
|
@@ -1905,6 +2158,15 @@ function createMetaWhatsAppModule(params) {
|
|
|
1905
2158
|
realtime: providers.realtime
|
|
1906
2159
|
});
|
|
1907
2160
|
const flowInterpreter = flowEngineEnabled ? new FlowInterpreter() : void 0;
|
|
2161
|
+
if (flowInterpreter && providers.objectStorage?.getObject) {
|
|
2162
|
+
flowInterpreter.registerFlowAction(import_meta_whatsapp_contracts11.FLOW_ACTION_KIND.SEND_MEDIA, createSendMediaAction({
|
|
2163
|
+
flowMediaRepository,
|
|
2164
|
+
objectStorage: providers.objectStorage,
|
|
2165
|
+
logMessage,
|
|
2166
|
+
startState,
|
|
2167
|
+
onError: hooks?.onFlowMediaError
|
|
2168
|
+
}));
|
|
2169
|
+
}
|
|
1908
2170
|
const ingestInboundMedia = providers.objectStorage ? new IngestInboundMediaUseCase(db, channel, providers.objectStorage, documentRepository) : void 0;
|
|
1909
2171
|
const listDocuments = new ListConversationDocumentsUseCase(sessionRepository, documentRepository);
|
|
1910
2172
|
return {
|
|
@@ -1948,7 +2210,11 @@ function createMetaWhatsAppModule(params) {
|
|
|
1948
2210
|
save: new SaveFlowGraphUseCase(flowGraphRepository),
|
|
1949
2211
|
delete: new DeleteFlowGraphUseCase(flowGraphRepository),
|
|
1950
2212
|
livePositions: new GetLiveFlowPositionsUseCase(flowGraphRepository),
|
|
1951
|
-
repository: flowGraphRepository
|
|
2213
|
+
repository: flowGraphRepository,
|
|
2214
|
+
// Biblioteca de mídia dos nós `send_media` — o host liga nas rotas do editor
|
|
2215
|
+
// (anexar/reordenar/desligar). Existe mesmo sem storage injetado: gerenciar anexos é
|
|
2216
|
+
// consultar a tabela, e só o ENVIO precisa dos bytes.
|
|
2217
|
+
mediaRepository: flowMediaRepository
|
|
1952
2218
|
} : void 0,
|
|
1953
2219
|
catalog: providers.catalog
|
|
1954
2220
|
};
|
|
@@ -2048,12 +2314,15 @@ __name(redeemSseTicket, "redeemSseTicket");
|
|
|
2048
2314
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2049
2315
|
0 && (module.exports = {
|
|
2050
2316
|
CreateFlowGraphUseCase,
|
|
2317
|
+
DEFAULT_FLOW_GRAPH_CACHE_TTL_SECONDS,
|
|
2051
2318
|
DeleteConversationUseCase,
|
|
2052
2319
|
DeleteFlowGraphUseCase,
|
|
2053
2320
|
DocumentRepository,
|
|
2054
2321
|
ExportConversationUseCase,
|
|
2322
|
+
FlowGraphCache,
|
|
2055
2323
|
FlowGraphRepository,
|
|
2056
2324
|
FlowInterpreter,
|
|
2325
|
+
FlowMediaRepository,
|
|
2057
2326
|
GetFlowGraphUseCase,
|
|
2058
2327
|
GetLiveFlowPositionsUseCase,
|
|
2059
2328
|
IngestInboundMediaUseCase,
|
|
@@ -2080,9 +2349,11 @@ __name(redeemSseTicket, "redeemSseTicket");
|
|
|
2080
2349
|
WhatsAppChannelAdapter,
|
|
2081
2350
|
claimWebhookDelivery,
|
|
2082
2351
|
createMetaWhatsAppModule,
|
|
2352
|
+
createSendMediaAction,
|
|
2083
2353
|
documents,
|
|
2084
2354
|
extractMediaDescriptor,
|
|
2085
2355
|
flowGraphs,
|
|
2356
|
+
flowMedia,
|
|
2086
2357
|
issueSseTicket,
|
|
2087
2358
|
messages,
|
|
2088
2359
|
metaWhatsAppMigrationsFolder,
|