@carlonicora/nestjs-neo4jsonapi 8.8.0 → 8.10.0

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.
Files changed (150) hide show
  1. package/dist/agents/graph/services/graph.search.service.d.ts +14 -1
  2. package/dist/agents/graph/services/graph.search.service.d.ts.map +1 -1
  3. package/dist/agents/graph/services/graph.search.service.js +15 -4
  4. package/dist/agents/graph/services/graph.search.service.js.map +1 -1
  5. package/dist/agents/graph/tools/read-entity.tool.d.ts.map +1 -1
  6. package/dist/agents/graph/tools/read-entity.tool.js +19 -7
  7. package/dist/agents/graph/tools/read-entity.tool.js.map +1 -1
  8. package/dist/agents/graph/tools/tool.factory.d.ts +7 -0
  9. package/dist/agents/graph/tools/tool.factory.d.ts.map +1 -1
  10. package/dist/agents/graph/tools/tool.factory.js +12 -0
  11. package/dist/agents/graph/tools/tool.factory.js.map +1 -1
  12. package/dist/common/constants/core-module.ids.d.ts +1 -0
  13. package/dist/common/constants/core-module.ids.d.ts.map +1 -1
  14. package/dist/common/constants/core-module.ids.js +1 -0
  15. package/dist/common/constants/core-module.ids.js.map +1 -1
  16. package/dist/common/interfaces/external.entity.source.interface.d.ts +28 -0
  17. package/dist/common/interfaces/external.entity.source.interface.d.ts.map +1 -1
  18. package/dist/common/interfaces/external.entity.source.interface.js +35 -0
  19. package/dist/common/interfaces/external.entity.source.interface.js.map +1 -1
  20. package/dist/config/enums/queue.id.d.ts +1 -0
  21. package/dist/config/enums/queue.id.d.ts.map +1 -1
  22. package/dist/config/enums/queue.id.js +1 -0
  23. package/dist/config/enums/queue.id.js.map +1 -1
  24. package/dist/core/email/templates/en/emailBlast.hbs +24 -0
  25. package/dist/foundations/account-deletion/repositories/account-deletion.wipe.repository.d.ts.map +1 -1
  26. package/dist/foundations/account-deletion/repositories/account-deletion.wipe.repository.js +1 -0
  27. package/dist/foundations/account-deletion/repositories/account-deletion.wipe.repository.js.map +1 -1
  28. package/dist/foundations/assistant/services/assistant-mention.extractor.d.ts +6 -0
  29. package/dist/foundations/assistant/services/assistant-mention.extractor.d.ts.map +1 -1
  30. package/dist/foundations/assistant/services/assistant-mention.extractor.js +18 -1
  31. package/dist/foundations/assistant/services/assistant-mention.extractor.js.map +1 -1
  32. package/dist/foundations/email-automation/index.d.ts +1 -0
  33. package/dist/foundations/email-automation/index.d.ts.map +1 -1
  34. package/dist/foundations/email-automation/index.js +2 -0
  35. package/dist/foundations/email-automation/index.js.map +1 -1
  36. package/dist/foundations/email-automation/services/email-automation-renderer.service.d.ts.map +1 -1
  37. package/dist/foundations/email-automation/services/email-automation-renderer.service.js +2 -12
  38. package/dist/foundations/email-automation/services/email-automation-renderer.service.js.map +1 -1
  39. package/dist/foundations/email-automation/utils/email-markdown.renderer.d.ts +20 -0
  40. package/dist/foundations/email-automation/utils/email-markdown.renderer.d.ts.map +1 -0
  41. package/dist/foundations/email-automation/utils/email-markdown.renderer.js +63 -0
  42. package/dist/foundations/email-automation/utils/email-markdown.renderer.js.map +1 -0
  43. package/dist/foundations/email-blast/controllers/email-blast.controller.d.ts +30 -0
  44. package/dist/foundations/email-blast/controllers/email-blast.controller.d.ts.map +1 -0
  45. package/dist/foundations/email-blast/controllers/email-blast.controller.js +171 -0
  46. package/dist/foundations/email-blast/controllers/email-blast.controller.js.map +1 -0
  47. package/dist/foundations/email-blast/controllers/email-blast.public.controller.d.ts +24 -0
  48. package/dist/foundations/email-blast/controllers/email-blast.public.controller.d.ts.map +1 -0
  49. package/dist/foundations/email-blast/controllers/email-blast.public.controller.js +61 -0
  50. package/dist/foundations/email-blast/controllers/email-blast.public.controller.js.map +1 -0
  51. package/dist/foundations/email-blast/cron/email-blast-recovery.cron.d.ts +15 -0
  52. package/dist/foundations/email-blast/cron/email-blast-recovery.cron.d.ts.map +1 -0
  53. package/dist/foundations/email-blast/cron/email-blast-recovery.cron.js +50 -0
  54. package/dist/foundations/email-blast/cron/email-blast-recovery.cron.js.map +1 -0
  55. package/dist/foundations/email-blast/dtos/email-blast-unsubscribe.dto.d.ts +7 -0
  56. package/dist/foundations/email-blast/dtos/email-blast-unsubscribe.dto.d.ts.map +1 -0
  57. package/dist/foundations/email-blast/dtos/email-blast-unsubscribe.dto.js +35 -0
  58. package/dist/foundations/email-blast/dtos/email-blast-unsubscribe.dto.js.map +1 -0
  59. package/dist/foundations/email-blast/dtos/email-blast.patch.dto.d.ts +14 -0
  60. package/dist/foundations/email-blast/dtos/email-blast.patch.dto.d.ts.map +1 -0
  61. package/dist/foundations/email-blast/dtos/email-blast.patch.dto.js +63 -0
  62. package/dist/foundations/email-blast/dtos/email-blast.patch.dto.js.map +1 -0
  63. package/dist/foundations/email-blast/dtos/email-blast.post.dto.d.ts +14 -0
  64. package/dist/foundations/email-blast/dtos/email-blast.post.dto.d.ts.map +1 -0
  65. package/dist/foundations/email-blast/dtos/email-blast.post.dto.js +63 -0
  66. package/dist/foundations/email-blast/dtos/email-blast.post.dto.js.map +1 -0
  67. package/dist/foundations/email-blast/email-blast.constants.d.ts +34 -0
  68. package/dist/foundations/email-blast/email-blast.constants.d.ts.map +1 -0
  69. package/dist/foundations/email-blast/email-blast.constants.js +49 -0
  70. package/dist/foundations/email-blast/email-blast.constants.js.map +1 -0
  71. package/dist/foundations/email-blast/email-blast.module.d.ts +21 -0
  72. package/dist/foundations/email-blast/email-blast.module.d.ts.map +1 -0
  73. package/dist/foundations/email-blast/email-blast.module.js +101 -0
  74. package/dist/foundations/email-blast/email-blast.module.js.map +1 -0
  75. package/dist/foundations/email-blast/entities/email-blast-audience.d.ts +10 -0
  76. package/dist/foundations/email-blast/entities/email-blast-audience.d.ts.map +1 -0
  77. package/dist/foundations/email-blast/entities/email-blast-audience.js +15 -0
  78. package/dist/foundations/email-blast/entities/email-blast-audience.js.map +1 -0
  79. package/dist/foundations/email-blast/entities/email-blast-audience.meta.d.ts +6 -0
  80. package/dist/foundations/email-blast/entities/email-blast-audience.meta.d.ts.map +1 -0
  81. package/dist/foundations/email-blast/entities/email-blast-audience.meta.js +13 -0
  82. package/dist/foundations/email-blast/entities/email-blast-audience.meta.js.map +1 -0
  83. package/dist/foundations/email-blast/entities/email-blast-delivery.d.ts +31 -0
  84. package/dist/foundations/email-blast/entities/email-blast-delivery.d.ts.map +1 -0
  85. package/dist/foundations/email-blast/entities/email-blast-delivery.js +34 -0
  86. package/dist/foundations/email-blast/entities/email-blast-delivery.js.map +1 -0
  87. package/dist/foundations/email-blast/entities/email-blast-delivery.meta.d.ts +3 -0
  88. package/dist/foundations/email-blast/entities/email-blast-delivery.meta.d.ts.map +1 -0
  89. package/dist/foundations/email-blast/entities/email-blast-delivery.meta.js +10 -0
  90. package/dist/foundations/email-blast/entities/email-blast-delivery.meta.js.map +1 -0
  91. package/dist/foundations/email-blast/entities/email-blast.d.ts +16 -0
  92. package/dist/foundations/email-blast/entities/email-blast.d.ts.map +1 -0
  93. package/dist/foundations/email-blast/entities/email-blast.js +25 -0
  94. package/dist/foundations/email-blast/entities/email-blast.js.map +1 -0
  95. package/dist/foundations/email-blast/entities/email-blast.meta.d.ts +13 -0
  96. package/dist/foundations/email-blast/entities/email-blast.meta.d.ts.map +1 -0
  97. package/dist/foundations/email-blast/entities/email-blast.meta.js +20 -0
  98. package/dist/foundations/email-blast/entities/email-blast.meta.js.map +1 -0
  99. package/dist/foundations/email-blast/index.d.ts +20 -0
  100. package/dist/foundations/email-blast/index.d.ts.map +1 -0
  101. package/dist/foundations/email-blast/index.js +44 -0
  102. package/dist/foundations/email-blast/index.js.map +1 -0
  103. package/dist/foundations/email-blast/processors/email-blast.processor.d.ts +23 -0
  104. package/dist/foundations/email-blast/processors/email-blast.processor.d.ts.map +1 -0
  105. package/dist/foundations/email-blast/processors/email-blast.processor.js +80 -0
  106. package/dist/foundations/email-blast/processors/email-blast.processor.js.map +1 -0
  107. package/dist/foundations/email-blast/repositories/email-blast-delivery.repository.d.ts +98 -0
  108. package/dist/foundations/email-blast/repositories/email-blast-delivery.repository.d.ts.map +1 -0
  109. package/dist/foundations/email-blast/repositories/email-blast-delivery.repository.js +273 -0
  110. package/dist/foundations/email-blast/repositories/email-blast-delivery.repository.js.map +1 -0
  111. package/dist/foundations/email-blast/repositories/email-blast.admin.repository.d.ts +19 -0
  112. package/dist/foundations/email-blast/repositories/email-blast.admin.repository.d.ts.map +1 -0
  113. package/dist/foundations/email-blast/repositories/email-blast.admin.repository.js +63 -0
  114. package/dist/foundations/email-blast/repositories/email-blast.admin.repository.js.map +1 -0
  115. package/dist/foundations/email-blast/repositories/email-blast.repository.d.ts +29 -0
  116. package/dist/foundations/email-blast/repositories/email-blast.repository.d.ts.map +1 -0
  117. package/dist/foundations/email-blast/repositories/email-blast.repository.js +97 -0
  118. package/dist/foundations/email-blast/repositories/email-blast.repository.js.map +1 -0
  119. package/dist/foundations/email-blast/services/email-blast-delivery.service.d.ts +79 -0
  120. package/dist/foundations/email-blast/services/email-blast-delivery.service.d.ts.map +1 -0
  121. package/dist/foundations/email-blast/services/email-blast-delivery.service.js +206 -0
  122. package/dist/foundations/email-blast/services/email-blast-delivery.service.js.map +1 -0
  123. package/dist/foundations/email-blast/services/email-blast.service.d.ts +57 -0
  124. package/dist/foundations/email-blast/services/email-blast.service.d.ts.map +1 -0
  125. package/dist/foundations/email-blast/services/email-blast.service.js +134 -0
  126. package/dist/foundations/email-blast/services/email-blast.service.js.map +1 -0
  127. package/dist/foundations/email-blast/utils/email-blast.renderer.d.ts +29 -0
  128. package/dist/foundations/email-blast/utils/email-blast.renderer.d.ts.map +1 -0
  129. package/dist/foundations/email-blast/utils/email-blast.renderer.js +70 -0
  130. package/dist/foundations/email-blast/utils/email-blast.renderer.js.map +1 -0
  131. package/dist/foundations/foundations.modules.d.ts.map +1 -1
  132. package/dist/foundations/foundations.modules.js +3 -0
  133. package/dist/foundations/foundations.modules.js.map +1 -1
  134. package/dist/foundations/index.d.ts +1 -0
  135. package/dist/foundations/index.d.ts.map +1 -1
  136. package/dist/foundations/index.js +1 -0
  137. package/dist/foundations/index.js.map +1 -1
  138. package/dist/mcp/services/mcp.entity.write.service.d.ts +11 -0
  139. package/dist/mcp/services/mcp.entity.write.service.d.ts.map +1 -1
  140. package/dist/mcp/services/mcp.entity.write.service.js +15 -0
  141. package/dist/mcp/services/mcp.entity.write.service.js.map +1 -1
  142. package/dist/mcp/services/mcp.errors.d.ts +6 -1
  143. package/dist/mcp/services/mcp.errors.d.ts.map +1 -1
  144. package/dist/mcp/services/mcp.errors.js +12 -3
  145. package/dist/mcp/services/mcp.errors.js.map +1 -1
  146. package/dist/mcp/services/mcp.generic.tools.service.d.ts +6 -2
  147. package/dist/mcp/services/mcp.generic.tools.service.d.ts.map +1 -1
  148. package/dist/mcp/services/mcp.generic.tools.service.js +18 -5
  149. package/dist/mcp/services/mcp.generic.tools.service.js.map +1 -1
  150. package/package.json +1 -1
@@ -0,0 +1,273 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.EmailBlastDeliveryRepository = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const neo4j_driver_1 = require("neo4j-driver");
15
+ const nestjs_cls_1 = require("nestjs-cls");
16
+ const abstract_repository_1 = require("../../../core/neo4j/abstracts/abstract.repository");
17
+ const neo4j_service_1 = require("../../../core/neo4j/services/neo4j.service");
18
+ const security_service_1 = require("../../../core/security/services/security.service");
19
+ const email_blast_constants_1 = require("../email-blast.constants");
20
+ const email_blast_delivery_1 = require("../entities/email-blast-delivery");
21
+ const email_blast_delivery_meta_1 = require("../entities/email-blast-delivery.meta");
22
+ const email_blast_meta_1 = require("../entities/email-blast.meta");
23
+ /** Alias of the blast whose counter a delivery write raises (never the return statement's alias). */
24
+ const COUNTED_BLAST = "countedBlast";
25
+ /**
26
+ * Every guarded write that changes a counter takes the delivery's write lock first and re-checks
27
+ * its guard after it (SET _lock / REMOVE _lock / WITH / WHERE), so two concurrent calls never both
28
+ * pass. The blast's lock is taken before its counter is read, so concurrent increments never lose
29
+ * one. A counter moves in the same query as the status it counts.
30
+ */
31
+ let EmailBlastDeliveryRepository = class EmailBlastDeliveryRepository extends abstract_repository_1.AbstractRepository {
32
+ constructor(neo4j, securityService, clsService) {
33
+ super(neo4j, securityService, clsService);
34
+ this.descriptor = email_blast_delivery_1.EmailBlastDeliveryDescriptor;
35
+ }
36
+ /**
37
+ * Builds the recipient list of a blast once: one pending delivery per eligible, distinct
38
+ * lower-cased email, and the blast's recipientCount (0 when nobody is eligible).
39
+ *
40
+ * Owner-approved exception (Carlo, 2026-10-27, decision D-1 of the email-blasts plan):
41
+ * - Only caller: EmailBlastDeliveryService.processBlast in the worker job, never an HTTP request.
42
+ * - The recipients are every company's users, so the query reads User nodes across companies
43
+ * instead of buildDefaultMatch(), which scopes to one company.
44
+ * - initQuery() is not used: the worker CLS carries no company, and the query needs no
45
+ * request alias. The blast is matched by an explicit id parameter.
46
+ * - Deliveries store userId as a field, never an edge to User.
47
+ * - validateRelatedNodes is not called: the only relationship target is the blast, matched by
48
+ * id in the same query, and for a tenancy "internal" target with no company in context the
49
+ * check is existence only.
50
+ * - The write returns no node, so it runs through executeInTransaction.
51
+ * - A second run does nothing: the blast's write lock is taken first, then the query stops
52
+ * when the blast already has a delivery.
53
+ */
54
+ async createSnapshot(params) {
55
+ const blast = email_blast_meta_1.emailBlastMeta.nodeName;
56
+ const delivery = email_blast_delivery_meta_1.emailBlastDeliveryMeta.nodeName;
57
+ const query = `
58
+ MATCH (${blast}:${email_blast_meta_1.emailBlastMeta.labelName} {id: $blastId})
59
+ SET ${blast}._lock = true
60
+ REMOVE ${blast}._lock
61
+ WITH ${blast}
62
+ WHERE NOT EXISTS {
63
+ MATCH (${blast})-[:HAS_DELIVERY]->(:${email_blast_delivery_meta_1.emailBlastDeliveryMeta.labelName})
64
+ }
65
+ CALL {
66
+ ${email_blast_constants_1.ELIGIBLE_RECIPIENTS_CYPHER}
67
+ RETURN collect({ email: email, recipient: recipient }) AS rows
68
+ }
69
+ FOREACH (row IN rows |
70
+ CREATE (${delivery}:${email_blast_delivery_meta_1.emailBlastDeliveryMeta.labelName} {
71
+ id: randomUUID(),
72
+ email: row.email,
73
+ name: row.recipient.name,
74
+ userId: row.recipient.id,
75
+ status: "pending",
76
+ unsubscribeToken: randomUUID(),
77
+ createdAt: datetime(),
78
+ updatedAt: datetime()
79
+ })
80
+ CREATE (${blast})-[:HAS_DELIVERY]->(${delivery})
81
+ )
82
+ SET ${blast}.recipientCount = size(rows),
83
+ ${blast}.updatedAt = datetime()
84
+ `;
85
+ await this.neo4j.executeInTransaction([{ query, params: { blastId: params.blastId } }]);
86
+ }
87
+ /**
88
+ * Claims up to `limit` pending deliveries of a blast (pending to sending), oldest first.
89
+ */
90
+ async claimBatch(params) {
91
+ const { nodeName } = email_blast_delivery_meta_1.emailBlastDeliveryMeta;
92
+ const query = this.neo4j.initQuery({ serialiser: email_blast_delivery_1.EmailBlastDeliveryDescriptor.model });
93
+ query.queryParams = {
94
+ ...query.queryParams,
95
+ blastId: params.blastId,
96
+ limit: (0, neo4j_driver_1.int)(params.limit),
97
+ claimedAt: params.claimedAt.toISOString(),
98
+ };
99
+ query.query += `
100
+ ${this.buildDefaultMatch()}
101
+ WHERE ${nodeName}.status = "pending"
102
+ AND EXISTS {
103
+ MATCH (${nodeName})<-[:HAS_DELIVERY]-(:${email_blast_meta_1.emailBlastMeta.labelName} {id: $blastId})
104
+ }
105
+ WITH ${nodeName}
106
+ ORDER BY ${nodeName}.createdAt ASC
107
+ LIMIT $limit
108
+ SET ${nodeName}._lock = true
109
+ REMOVE ${nodeName}._lock
110
+ WITH ${nodeName}
111
+ WHERE ${nodeName}.status = "pending"
112
+ SET ${nodeName}.status = "sending",
113
+ ${nodeName}.claimedAt = datetime($claimedAt),
114
+ ${nodeName}.updatedAt = datetime()
115
+ WITH ${nodeName}
116
+ ${this.buildReturnStatement()}
117
+ `;
118
+ return (await this.neo4j.writeAndReturnMany(query)) ?? [];
119
+ }
120
+ /**
121
+ * A claimed delivery was sent: sending to sent, and the blast's sentCount + 1.
122
+ */
123
+ async markSent(params) {
124
+ const { nodeName } = email_blast_delivery_meta_1.emailBlastDeliveryMeta;
125
+ const query = this.neo4j.initQuery({ serialiser: email_blast_delivery_1.EmailBlastDeliveryDescriptor.model });
126
+ query.queryParams = {
127
+ ...query.queryParams,
128
+ searchValue: params.id,
129
+ sentAt: params.sentAt.toISOString(),
130
+ };
131
+ query.query += `
132
+ ${this.buildDefaultMatch({ searchField: "id" })}
133
+ SET ${nodeName}._lock = true
134
+ REMOVE ${nodeName}._lock
135
+ WITH ${nodeName}
136
+ WHERE ${nodeName}.status = "sending"
137
+ ${this._lockCountedBlast()}
138
+ SET ${nodeName}.status = "sent",
139
+ ${nodeName}.sentAt = datetime($sentAt),
140
+ ${nodeName}.updatedAt = datetime(),
141
+ ${COUNTED_BLAST}.sentCount = coalesce(${COUNTED_BLAST}.sentCount, 0) + 1
142
+ WITH ${nodeName}
143
+ ${this.buildReturnStatement()}
144
+ `;
145
+ await this.neo4j.writeOne(query);
146
+ }
147
+ /**
148
+ * A claimed delivery failed to send: sending to failed (never retried), and the blast's
149
+ * failedCount + 1.
150
+ */
151
+ async markFailed(params) {
152
+ const { nodeName } = email_blast_delivery_meta_1.emailBlastDeliveryMeta;
153
+ const query = this.neo4j.initQuery({ serialiser: email_blast_delivery_1.EmailBlastDeliveryDescriptor.model });
154
+ query.queryParams = { ...query.queryParams, searchValue: params.id, error: params.error };
155
+ query.query += `
156
+ ${this.buildDefaultMatch({ searchField: "id" })}
157
+ SET ${nodeName}._lock = true
158
+ REMOVE ${nodeName}._lock
159
+ WITH ${nodeName}
160
+ WHERE ${nodeName}.status = "sending"
161
+ ${this._lockCountedBlast()}
162
+ SET ${nodeName}.status = "failed",
163
+ ${nodeName}.error = $error,
164
+ ${nodeName}.updatedAt = datetime(),
165
+ ${COUNTED_BLAST}.failedCount = coalesce(${COUNTED_BLAST}.failedCount, 0) + 1
166
+ WITH ${nodeName}
167
+ ${this.buildReturnStatement()}
168
+ `;
169
+ await this.neo4j.writeOne(query);
170
+ }
171
+ /**
172
+ * A claimed delivery whose address opted out after the snapshot: sending to skipped. No counter.
173
+ */
174
+ async markSkipped(params) {
175
+ const { nodeName } = email_blast_delivery_meta_1.emailBlastDeliveryMeta;
176
+ const query = this.neo4j.initQuery({ serialiser: email_blast_delivery_1.EmailBlastDeliveryDescriptor.model });
177
+ query.queryParams = { ...query.queryParams, searchValue: params.id };
178
+ query.query += `
179
+ ${this.buildDefaultMatch({ searchField: "id" })}
180
+ SET ${nodeName}._lock = true
181
+ REMOVE ${nodeName}._lock
182
+ WITH ${nodeName}
183
+ WHERE ${nodeName}.status = "sending"
184
+ SET ${nodeName}.status = "skipped",
185
+ ${nodeName}.updatedAt = datetime()
186
+ WITH ${nodeName}
187
+ ${this.buildReturnStatement()}
188
+ `;
189
+ await this.neo4j.writeOne(query);
190
+ }
191
+ /**
192
+ * Every delivery claimed before `claimedBefore` and still sending goes to failed (never resent),
193
+ * and its blast's failedCount rises by one per delivery, in one query.
194
+ */
195
+ async failStuck(params) {
196
+ const { nodeName } = email_blast_delivery_meta_1.emailBlastDeliveryMeta;
197
+ const query = this.neo4j.initQuery({ serialiser: email_blast_delivery_1.EmailBlastDeliveryDescriptor.model });
198
+ query.queryParams = { ...query.queryParams, claimedBefore: params.claimedBefore.toISOString() };
199
+ query.query += `
200
+ ${this.buildDefaultMatch()}
201
+ WHERE ${nodeName}.status = "sending"
202
+ AND ${nodeName}.claimedAt < datetime($claimedBefore)
203
+ SET ${nodeName}._lock = true
204
+ REMOVE ${nodeName}._lock
205
+ WITH ${nodeName}
206
+ WHERE ${nodeName}.status = "sending"
207
+ AND ${nodeName}.claimedAt < datetime($claimedBefore)
208
+ ${this._lockCountedBlast()}
209
+ SET ${nodeName}.status = "failed",
210
+ ${nodeName}.error = "stuck",
211
+ ${nodeName}.updatedAt = datetime(),
212
+ ${COUNTED_BLAST}.failedCount = coalesce(${COUNTED_BLAST}.failedCount, 0) + 1
213
+ WITH ${nodeName}
214
+ ${this.buildReturnStatement()}
215
+ `;
216
+ await this.neo4j.writeAndReturnMany(query);
217
+ }
218
+ /**
219
+ * The delivery an unsubscribe link belongs to, with its blast.
220
+ */
221
+ async findByUnsubscribeToken(params) {
222
+ const query = this.neo4j.initQuery({ serialiser: email_blast_delivery_1.EmailBlastDeliveryDescriptor.model });
223
+ query.queryParams = { ...query.queryParams, searchValue: params.token };
224
+ query.query += `
225
+ ${this.buildDefaultMatch({ searchField: "unsubscribeToken" })}
226
+ ${this.buildReturnStatement()}
227
+ `;
228
+ return this.neo4j.readOne(query);
229
+ }
230
+ /**
231
+ * Stamps the first unsubscribe of a delivery and raises the blast's unsubscribedCount, once.
232
+ * True when this call was the one that matched.
233
+ */
234
+ async markUnsubscribed(params) {
235
+ const { nodeName } = email_blast_delivery_meta_1.emailBlastDeliveryMeta;
236
+ const query = this.neo4j.initQuery({ serialiser: email_blast_delivery_1.EmailBlastDeliveryDescriptor.model });
237
+ query.queryParams = {
238
+ ...query.queryParams,
239
+ searchValue: params.id,
240
+ unsubscribedAt: params.unsubscribedAt.toISOString(),
241
+ };
242
+ query.query += `
243
+ ${this.buildDefaultMatch({ searchField: "id" })}
244
+ SET ${nodeName}._lock = true
245
+ REMOVE ${nodeName}._lock
246
+ WITH ${nodeName}
247
+ WHERE ${nodeName}.unsubscribedAt IS NULL
248
+ ${this._lockCountedBlast()}
249
+ SET ${nodeName}.unsubscribedAt = datetime($unsubscribedAt),
250
+ ${nodeName}.updatedAt = datetime(),
251
+ ${COUNTED_BLAST}.unsubscribedCount = coalesce(${COUNTED_BLAST}.unsubscribedCount, 0) + 1
252
+ WITH ${nodeName}
253
+ ${this.buildReturnStatement()}
254
+ `;
255
+ return !!(await this.neo4j.writeOne(query));
256
+ }
257
+ /** Binds the delivery's blast as COUNTED_BLAST and takes its write lock before a counter is read. */
258
+ _lockCountedBlast() {
259
+ const { nodeName } = email_blast_delivery_meta_1.emailBlastDeliveryMeta;
260
+ return `
261
+ MATCH (${nodeName})<-[:HAS_DELIVERY]-(${COUNTED_BLAST}:${email_blast_meta_1.emailBlastMeta.labelName})
262
+ SET ${COUNTED_BLAST}._lock = true
263
+ REMOVE ${COUNTED_BLAST}._lock
264
+ WITH ${nodeName}, ${COUNTED_BLAST}
265
+ `;
266
+ }
267
+ };
268
+ exports.EmailBlastDeliveryRepository = EmailBlastDeliveryRepository;
269
+ exports.EmailBlastDeliveryRepository = EmailBlastDeliveryRepository = __decorate([
270
+ (0, common_1.Injectable)(),
271
+ __metadata("design:paramtypes", [neo4j_service_1.Neo4jService, security_service_1.SecurityService, nestjs_cls_1.ClsService])
272
+ ], EmailBlastDeliveryRepository);
273
+ //# sourceMappingURL=email-blast-delivery.repository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-blast-delivery.repository.js","sourceRoot":"","sources":["../../../../src/foundations/email-blast/repositories/email-blast-delivery.repository.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { ClsService } from "nestjs-cls";
2
+ import { AbstractRepository } from "../../../core/neo4j/abstracts/abstract.repository";
3
+ import { Neo4jService } from "../../../core/neo4j/services/neo4j.service";
4
+ import { SecurityService } from "../../../core/security/services/security.service";
5
+ import { EmailBlastAudience, EmailBlastAudienceDescriptor } from "../entities/email-blast-audience";
6
+ export declare class EmailBlastAdminRepository extends AbstractRepository<EmailBlastAudience, typeof EmailBlastAudienceDescriptor.relationships> {
7
+ protected readonly descriptor: import("../../..").EntityDescriptor<EmailBlastAudience, {}>;
8
+ constructor(neo4j: Neo4jService, securityService: SecurityService, clsService: ClsService);
9
+ /**
10
+ * How many distinct addresses a blast would reach right now (the same eligibility block the
11
+ * snapshot uses). `id` is the blast id.
12
+ */
13
+ countEligibleRecipients(params: {
14
+ blastId: string;
15
+ }): Promise<EmailBlastAudience>;
16
+ /** Seeds a query with only the parameters it needs: none (never the caller's companyId). */
17
+ private _adminQuery;
18
+ }
19
+ //# sourceMappingURL=email-blast.admin.repository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-blast.admin.repository.d.ts","sourceRoot":"","sources":["../../../../src/foundations/email-blast/repositories/email-blast.admin.repository.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,mDAAmD,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,MAAM,4CAA4C,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,kDAAkD,CAAC;AAEnF,OAAO,EAAE,kBAAkB,EAAE,4BAA4B,EAAE,MAAM,kCAAkC,CAAC;AAIpG,qBACa,yBAA0B,SAAQ,kBAAkB,CAC/D,kBAAkB,EAClB,OAAO,4BAA4B,CAAC,aAAa,CAClD;IAEC,SAAS,CAAC,QAAQ,CAAC,UAAU,8DAAgC;gBAEjD,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU;IAIzF;;;OAGG;IACG,uBAAuB,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAiBvF,4FAA4F;IAC5F,OAAO,CAAC,WAAW;CAGpB"}
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.EmailBlastAdminRepository = void 0;
13
+ // nja-lint-ignore-file: cross-tenant administrative read repository.
14
+ //
15
+ // The recipient count of a blast covers every company's users, so buildDefaultMatch(), which
16
+ // scopes to the caller's company, would undercount; initQuery() is skipped for the same reason
17
+ // (it prepends the caller's company MATCH). The query returns one scalar count, which
18
+ // readOne()/readMany() cannot map. Only EmailBlastController.findAudience and .send reach it,
19
+ // gated by JwtAuthGuard and Roles(SystemRoles.Administrator).
20
+ //
21
+ // Queries seed themselves from _adminQuery(); no query here binds a parameter named companyId.
22
+ const common_1 = require("@nestjs/common");
23
+ const neo4j_driver_1 = require("neo4j-driver");
24
+ const nestjs_cls_1 = require("nestjs-cls");
25
+ const abstract_repository_1 = require("../../../core/neo4j/abstracts/abstract.repository");
26
+ const neo4j_service_1 = require("../../../core/neo4j/services/neo4j.service");
27
+ const security_service_1 = require("../../../core/security/services/security.service");
28
+ const email_blast_constants_1 = require("../email-blast.constants");
29
+ const email_blast_audience_1 = require("../entities/email-blast-audience");
30
+ let EmailBlastAdminRepository = class EmailBlastAdminRepository extends abstract_repository_1.AbstractRepository {
31
+ constructor(neo4j, securityService, clsService) {
32
+ super(neo4j, securityService, clsService);
33
+ // Only satisfies the base class: nothing here goes through the inherited CRUD.
34
+ this.descriptor = email_blast_audience_1.EmailBlastAudienceDescriptor;
35
+ }
36
+ /**
37
+ * How many distinct addresses a blast would reach right now (the same eligibility block the
38
+ * snapshot uses). `id` is the blast id.
39
+ */
40
+ async countEligibleRecipients(params) {
41
+ const seed = this._adminQuery();
42
+ seed.query = `
43
+ ${email_blast_constants_1.ELIGIBLE_RECIPIENTS_CYPHER}
44
+ RETURN count(email) AS recipientCount
45
+ `;
46
+ const result = await this.neo4j.read(seed.query, seed.queryParams);
47
+ const raw = result.records[0]?.get("recipientCount");
48
+ return {
49
+ id: params.blastId,
50
+ recipientCount: (0, neo4j_driver_1.isInt)(raw) ? raw.toNumber() : Number(raw ?? 0),
51
+ };
52
+ }
53
+ /** Seeds a query with only the parameters it needs: none (never the caller's companyId). */
54
+ _adminQuery() {
55
+ return { query: "", queryParams: {} };
56
+ }
57
+ };
58
+ exports.EmailBlastAdminRepository = EmailBlastAdminRepository;
59
+ exports.EmailBlastAdminRepository = EmailBlastAdminRepository = __decorate([
60
+ (0, common_1.Injectable)(),
61
+ __metadata("design:paramtypes", [neo4j_service_1.Neo4jService, security_service_1.SecurityService, nestjs_cls_1.ClsService])
62
+ ], EmailBlastAdminRepository);
63
+ //# sourceMappingURL=email-blast.admin.repository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-blast.admin.repository.js","sourceRoot":"","sources":["../../../../src/foundations/email-blast/repositories/email-blast.admin.repository.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qEAAqE;AACrE,EAAE;AACF,6FAA6F;AAC7F,+FAA+F;AAC/F,sFAAsF;AACtF,8FAA8F;AAC9F,8DAA8D;AAC9D,EAAE;AACF,+FAA+F;AAC/F,2CAA4C;AAC5C,+CAAqC;AACrC,2CAAwC;AACxC,2FAAuF;AACvF,8EAA0E;AAC1E,uFAAmF;AACnF,oEAAsE;AACtE,2EAAoG;AAK7F,IAAM,yBAAyB,GAA/B,MAAM,yBAA0B,SAAQ,wCAG9C;IAIC,YAAY,KAAmB,EAAE,eAAgC,EAAE,UAAsB;QACvF,KAAK,CAAC,KAAK,EAAE,eAAe,EAAE,UAAU,CAAC,CAAC;QAJ5C,+EAA+E;QAC5D,eAAU,GAAG,mDAA4B,CAAC;IAI7D,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,uBAAuB,CAAC,MAA2B;QACvD,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAEhC,IAAI,CAAC,KAAK,GAAG;QACT,kDAA0B;;KAE7B,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QACnE,MAAM,GAAG,GAAI,MAAM,CAAC,OAAiB,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAEhE,OAAO;YACL,EAAE,EAAE,MAAM,CAAC,OAAO;YAClB,cAAc,EAAE,IAAA,oBAAK,EAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;SACzC,CAAC;IAC1B,CAAC;IAED,4FAA4F;IACpF,WAAW;QACjB,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;IACxC,CAAC;CACF,CAAA;AApCY,8DAAyB;oCAAzB,yBAAyB;IADrC,IAAA,mBAAU,GAAE;qCAQQ,4BAAY,EAAmB,kCAAe,EAAc,uBAAU;GAP9E,yBAAyB,CAoCrC"}
@@ -0,0 +1,29 @@
1
+ import { ClsService } from "nestjs-cls";
2
+ import { AbstractRepository } from "../../../core/neo4j/abstracts/abstract.repository";
3
+ import { Neo4jService } from "../../../core/neo4j/services/neo4j.service";
4
+ import { SecurityService } from "../../../core/security/services/security.service";
5
+ import { EmailBlast, EmailBlastDescriptor } from "../entities/email-blast";
6
+ export declare class EmailBlastRepository extends AbstractRepository<EmailBlast, typeof EmailBlastDescriptor.relationships> {
7
+ protected readonly descriptor: import("../../..").EntityDescriptor<EmailBlast, {}>;
8
+ constructor(neo4j: Neo4jService, securityService: SecurityService, clsService: ClsService);
9
+ /**
10
+ * Moves a draft to `sending` and stamps `sentAt`. The node's write lock is taken before the
11
+ * draft check, so of two concurrent calls only one matches; null when it is not a draft.
12
+ */
13
+ markSending(params: {
14
+ id: string;
15
+ sentAt: Date;
16
+ }): Promise<EmailBlast | null>;
17
+ /**
18
+ * Moves a `sending` blast to `sent` once none of its deliveries is `pending` or `sending`.
19
+ * Null when the blast is not done yet (or not sending).
20
+ */
21
+ markSentIfDone(params: {
22
+ id: string;
23
+ }): Promise<EmailBlast | null>;
24
+ /**
25
+ * Every blast still sending (the recovery cron re-enqueues them).
26
+ */
27
+ findSending(): Promise<EmailBlast[]>;
28
+ }
29
+ //# sourceMappingURL=email-blast.repository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-blast.repository.d.ts","sourceRoot":"","sources":["../../../../src/foundations/email-blast/repositories/email-blast.repository.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,mDAAmD,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,MAAM,4CAA4C,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,kDAAkD,CAAC;AACnF,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAI3E,qBACa,oBAAqB,SAAQ,kBAAkB,CAAC,UAAU,EAAE,OAAO,oBAAoB,CAAC,aAAa,CAAC;IACjH,SAAS,CAAC,QAAQ,CAAC,UAAU,sDAAwB;gBAEzC,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU;IAIzF;;;OAGG;IACG,WAAW,CAAC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IA0BnF;;;OAGG;IACG,cAAc,CAAC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAyBxE;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;CAa3C"}
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.EmailBlastRepository = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const nestjs_cls_1 = require("nestjs-cls");
15
+ const abstract_repository_1 = require("../../../core/neo4j/abstracts/abstract.repository");
16
+ const neo4j_service_1 = require("../../../core/neo4j/services/neo4j.service");
17
+ const security_service_1 = require("../../../core/security/services/security.service");
18
+ const email_blast_1 = require("../entities/email-blast");
19
+ const email_blast_delivery_meta_1 = require("../entities/email-blast-delivery.meta");
20
+ const email_blast_meta_1 = require("../entities/email-blast.meta");
21
+ let EmailBlastRepository = class EmailBlastRepository extends abstract_repository_1.AbstractRepository {
22
+ constructor(neo4j, securityService, clsService) {
23
+ super(neo4j, securityService, clsService);
24
+ this.descriptor = email_blast_1.EmailBlastDescriptor;
25
+ }
26
+ /**
27
+ * Moves a draft to `sending` and stamps `sentAt`. The node's write lock is taken before the
28
+ * draft check, so of two concurrent calls only one matches; null when it is not a draft.
29
+ */
30
+ async markSending(params) {
31
+ const { nodeName } = email_blast_meta_1.emailBlastMeta;
32
+ const query = this.neo4j.initQuery({ serialiser: email_blast_1.EmailBlastDescriptor.model });
33
+ query.queryParams = {
34
+ ...query.queryParams,
35
+ searchValue: params.id,
36
+ sentAt: params.sentAt.toISOString(),
37
+ };
38
+ query.query += `
39
+ ${this.buildDefaultMatch({ searchField: "id" })}
40
+ SET ${nodeName}._lock = true
41
+ REMOVE ${nodeName}._lock
42
+ WITH ${nodeName}
43
+ WHERE ${nodeName}.status = "draft"
44
+ SET ${nodeName}.status = "sending",
45
+ ${nodeName}.sentAt = datetime($sentAt),
46
+ ${nodeName}.updatedAt = datetime()
47
+ WITH ${nodeName}
48
+ ${this.buildReturnStatement()}
49
+ `;
50
+ return this.neo4j.writeOne(query);
51
+ }
52
+ /**
53
+ * Moves a `sending` blast to `sent` once none of its deliveries is `pending` or `sending`.
54
+ * Null when the blast is not done yet (or not sending).
55
+ */
56
+ async markSentIfDone(params) {
57
+ const { nodeName } = email_blast_meta_1.emailBlastMeta;
58
+ const query = this.neo4j.initQuery({ serialiser: email_blast_1.EmailBlastDescriptor.model });
59
+ query.queryParams = { ...query.queryParams, searchValue: params.id };
60
+ query.query += `
61
+ ${this.buildDefaultMatch({ searchField: "id" })}
62
+ SET ${nodeName}._lock = true
63
+ REMOVE ${nodeName}._lock
64
+ WITH ${nodeName}
65
+ WHERE ${nodeName}.status = "sending"
66
+ AND NOT EXISTS {
67
+ MATCH (${nodeName})-[:HAS_DELIVERY]->(open:${email_blast_delivery_meta_1.emailBlastDeliveryMeta.labelName})
68
+ WHERE open.status IN ["pending", "sending"]
69
+ }
70
+ SET ${nodeName}.status = "sent",
71
+ ${nodeName}.updatedAt = datetime()
72
+ WITH ${nodeName}
73
+ ${this.buildReturnStatement()}
74
+ `;
75
+ return this.neo4j.writeOne(query);
76
+ }
77
+ /**
78
+ * Every blast still sending (the recovery cron re-enqueues them).
79
+ */
80
+ async findSending() {
81
+ const { nodeName } = email_blast_meta_1.emailBlastMeta;
82
+ const query = this.neo4j.initQuery({ serialiser: email_blast_1.EmailBlastDescriptor.model });
83
+ query.query += `
84
+ ${this.buildDefaultMatch()}
85
+ WHERE ${nodeName}.status = "sending"
86
+ WITH ${nodeName}
87
+ ${this.buildReturnStatement()}
88
+ `;
89
+ return this.neo4j.readMany(query);
90
+ }
91
+ };
92
+ exports.EmailBlastRepository = EmailBlastRepository;
93
+ exports.EmailBlastRepository = EmailBlastRepository = __decorate([
94
+ (0, common_1.Injectable)(),
95
+ __metadata("design:paramtypes", [neo4j_service_1.Neo4jService, security_service_1.SecurityService, nestjs_cls_1.ClsService])
96
+ ], EmailBlastRepository);
97
+ //# sourceMappingURL=email-blast.repository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-blast.repository.js","sourceRoot":"","sources":["../../../../src/foundations/email-blast/repositories/email-blast.repository.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,2CAAwC;AACxC,2FAAuF;AACvF,8EAA0E;AAC1E,uFAAmF;AACnF,yDAA2E;AAC3E,qFAA+E;AAC/E,mEAA8D;AAGvD,IAAM,oBAAoB,GAA1B,MAAM,oBAAqB,SAAQ,wCAAyE;IAGjH,YAAY,KAAmB,EAAE,eAAgC,EAAE,UAAsB;QACvF,KAAK,CAAC,KAAK,EAAE,eAAe,EAAE,UAAU,CAAC,CAAC;QAHzB,eAAU,GAAG,kCAAoB,CAAC;IAIrD,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,WAAW,CAAC,MAAoC;QACpD,MAAM,EAAE,QAAQ,EAAE,GAAG,iCAAc,CAAC;QACpC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,kCAAoB,CAAC,KAAK,EAAE,CAAC,CAAC;QAE/E,KAAK,CAAC,WAAW,GAAG;YAClB,GAAG,KAAK,CAAC,WAAW;YACpB,WAAW,EAAE,MAAM,CAAC,EAAE;YACtB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE;SACpC,CAAC;QAEF,KAAK,CAAC,KAAK,IAAI;QACX,IAAI,CAAC,iBAAiB,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;YACzC,QAAQ;eACL,QAAQ;aACV,QAAQ;cACP,QAAQ;YACV,QAAQ;YACR,QAAQ;YACR,QAAQ;aACP,QAAQ;QACb,IAAI,CAAC,oBAAoB,EAAE;KAC9B,CAAC;QAEF,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,cAAc,CAAC,MAAsB;QACzC,MAAM,EAAE,QAAQ,EAAE,GAAG,iCAAc,CAAC;QACpC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,kCAAoB,CAAC,KAAK,EAAE,CAAC,CAAC;QAE/E,KAAK,CAAC,WAAW,GAAG,EAAE,GAAG,KAAK,CAAC,WAAW,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC;QAErE,KAAK,CAAC,KAAK,IAAI;QACX,IAAI,CAAC,iBAAiB,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;YACzC,QAAQ;eACL,QAAQ;aACV,QAAQ;cACP,QAAQ;;iBAEL,QAAQ,4BAA4B,kDAAsB,CAAC,SAAS;;;YAGzE,QAAQ;YACR,QAAQ;aACP,QAAQ;QACb,IAAI,CAAC,oBAAoB,EAAE;KAC9B,CAAC;QAEF,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW;QACf,MAAM,EAAE,QAAQ,EAAE,GAAG,iCAAc,CAAC;QACpC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,kCAAoB,CAAC,KAAK,EAAE,CAAC,CAAC;QAE/E,KAAK,CAAC,KAAK,IAAI;QACX,IAAI,CAAC,iBAAiB,EAAE;cAClB,QAAQ;aACT,QAAQ;QACb,IAAI,CAAC,oBAAoB,EAAE;KAC9B,CAAC;QAEF,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;CACF,CAAA;AAlFY,oDAAoB;+BAApB,oBAAoB;IADhC,IAAA,mBAAU,GAAE;qCAIQ,4BAAY,EAAmB,kCAAe,EAAc,uBAAU;GAH9E,oBAAoB,CAkFhC"}
@@ -0,0 +1,79 @@
1
+ import { ConfigService } from "@nestjs/config";
2
+ import { Queue } from "bullmq";
3
+ import { ClsService } from "nestjs-cls";
4
+ import { BaseConfigInterface } from "../../../config/interfaces";
5
+ import { CacheService } from "../../../core/cache/services/cache.service";
6
+ import { EmailService } from "../../../core/email/services/email.service";
7
+ import { JsonApiService } from "../../../core/jsonapi/services/jsonapi.service";
8
+ import { AppLoggingService } from "../../../core/logging/services/logging.service";
9
+ import { AbstractService } from "../../../core/neo4j/abstracts/abstract.service";
10
+ import { EmailAutomationEnrollmentRepository } from "../../email-automation/repositories/email-automation-enrollment.repository";
11
+ import { EmailOptOutRepository } from "../../email-automation/repositories/email-opt-out.repository";
12
+ import { ResponseLanguageService } from "../../user/services/response-language.service";
13
+ import { EmailBlastDelivery, EmailBlastDeliveryDescriptor } from "../entities/email-blast-delivery";
14
+ import { EmailBlastDeliveryRepository } from "../repositories/email-blast-delivery.repository";
15
+ import { EmailBlastRepository } from "../repositories/email-blast.repository";
16
+ /**
17
+ * The whole delivery lifecycle of a blast: the worker send run, the recovery of stuck
18
+ * deliveries and the public unsubscribe.
19
+ *
20
+ * Never twice beats never missing anyone: a delivery is claimed (`pending` → `sending`)
21
+ * before the provider call; a send error marks it `failed` and it is never retried; a
22
+ * delivery left in `sending` by a dead worker is failed by `recoverStuck`, never resent.
23
+ *
24
+ * The worker has no route for `@CacheInvalidate`, so the send run and the recovery
25
+ * invalidate the `email-blasts` cache by hand (owner decision D-7).
26
+ */
27
+ export declare class EmailBlastDeliveryService extends AbstractService<EmailBlastDelivery, typeof EmailBlastDeliveryDescriptor.relationships> {
28
+ private readonly deliveryRepository;
29
+ private readonly blastRepository;
30
+ private readonly optOutRepository;
31
+ private readonly enrollmentRepository;
32
+ private readonly emailService;
33
+ private readonly responseLanguage;
34
+ private readonly configService;
35
+ private readonly cacheService;
36
+ private readonly queue;
37
+ private readonly logger;
38
+ protected readonly descriptor: import("../../..").EntityDescriptor<EmailBlastDelivery, {
39
+ blast: {
40
+ model: import("../../..").DataMeta;
41
+ direction: "in";
42
+ relationship: string;
43
+ cardinality: "one";
44
+ required: true;
45
+ dtoKey: string;
46
+ };
47
+ }>;
48
+ constructor(jsonApiService: JsonApiService, deliveryRepository: EmailBlastDeliveryRepository, clsService: ClsService, blastRepository: EmailBlastRepository, optOutRepository: EmailOptOutRepository, enrollmentRepository: EmailAutomationEnrollmentRepository, emailService: EmailService, responseLanguage: ResponseLanguageService, configService: ConfigService<BaseConfigInterface>, cacheService: CacheService, queue: Queue, logger: AppLoggingService);
49
+ /**
50
+ * Snapshots the recipients (once per blast) and sends every pending delivery in batches.
51
+ * Safe to run again after a crash: claimed deliveries are never claimed twice.
52
+ */
53
+ processBlast(params: {
54
+ blastId: string;
55
+ }): Promise<{
56
+ sent: number;
57
+ skipped: number;
58
+ failed: number;
59
+ }>;
60
+ /**
61
+ * Fails every delivery claimed more than EMAIL_BLAST_STUCK_MINUTES ago (never resent),
62
+ * then queues the send job again for every blast still `sending`, so a crashed run ends.
63
+ */
64
+ recoverStuck(params: {
65
+ now: Date;
66
+ }): Promise<void>;
67
+ /**
68
+ * Stops every marketing email to the delivery's address: counts the unsubscribe on the
69
+ * blast once, finishes the address's active automation enrolments and writes the shared
70
+ * opt-out. Idempotent. Answers nothing, so a token holder never learns the email or user.
71
+ */
72
+ unsubscribe(params: {
73
+ token: string;
74
+ source: "link" | "header";
75
+ }): Promise<void>;
76
+ private deliverOne;
77
+ private invalidateCache;
78
+ }
79
+ //# sourceMappingURL=email-blast-delivery.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-blast-delivery.service.d.ts","sourceRoot":"","sources":["../../../../src/foundations/email-blast/services/email-blast-delivery.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAE/B,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,mBAAmB,EAA0C,MAAM,4BAA4B,CAAC;AAEzG,OAAO,EAAE,YAAY,EAAE,MAAM,4CAA4C,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,4CAA4C,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,MAAM,gDAAgD,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,MAAM,gDAAgD,CAAC;AACnF,OAAO,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAC;AACjF,OAAO,EAAE,mCAAmC,EAAE,MAAM,4EAA4E,CAAC;AACjI,OAAO,EAAE,qBAAqB,EAAE,MAAM,8DAA8D,CAAC;AACrG,OAAO,EAAE,uBAAuB,EAAE,MAAM,+CAA+C,CAAC;AAUxF,OAAO,EAAE,kBAAkB,EAAE,4BAA4B,EAAE,MAAM,kCAAkC,CAAC;AAEpG,OAAO,EAAE,4BAA4B,EAAE,MAAM,iDAAiD,CAAC;AAC/F,OAAO,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AAO9E;;;;;;;;;;GAUG;AACH,qBACa,yBAA0B,SAAQ,eAAe,CAC5D,kBAAkB,EAClB,OAAO,4BAA4B,CAAC,aAAa,CAClD;IAKG,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IAEnC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,oBAAoB;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,YAAY;IACK,OAAO,CAAC,QAAQ,CAAC,KAAK;IACxD,OAAO,CAAC,QAAQ,CAAC,MAAM;IAdzB,SAAS,CAAC,QAAQ,CAAC,UAAU;;;;;;;;;OAAgC;gBAG3D,cAAc,EAAE,cAAc,EACb,kBAAkB,EAAE,4BAA4B,EACjE,UAAU,EAAE,UAAU,EACL,eAAe,EAAE,oBAAoB,EACrC,gBAAgB,EAAE,qBAAqB,EACvC,oBAAoB,EAAE,mCAAmC,EACzD,YAAY,EAAE,YAAY,EAC1B,gBAAgB,EAAE,uBAAuB,EACzC,aAAa,EAAE,aAAa,CAAC,mBAAmB,CAAC,EACjD,YAAY,EAAE,YAAY,EACQ,KAAK,EAAE,KAAK,EAC9C,MAAM,EAAE,iBAAiB;IAK5C;;;OAGG;IACG,YAAY,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IA2C3G;;;OAGG;IACG,YAAY,CAAC,MAAM,EAAE;QAAE,GAAG,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAyBxD;;;;OAIG;IACG,WAAW,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;YAqBxE,UAAU;YAoDV,eAAe;CAG9B"}