@dereekb/firebase-server 11.0.21 → 11.1.1

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 (54) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/mailgun/package.json +1 -1
  3. package/model/package.json +6 -0
  4. package/model/src/index.d.ts +1 -0
  5. package/model/src/index.js +5 -0
  6. package/model/src/index.js.map +1 -0
  7. package/model/src/lib/index.d.ts +2 -0
  8. package/model/src/lib/index.js +6 -0
  9. package/model/src/lib/index.js.map +1 -0
  10. package/model/src/lib/mailgun/index.d.ts +1 -0
  11. package/model/src/lib/mailgun/index.js +5 -0
  12. package/model/src/lib/mailgun/index.js.map +1 -0
  13. package/model/src/lib/mailgun/notification.send.service.mailgun.d.ts +51 -0
  14. package/model/src/lib/mailgun/notification.send.service.mailgun.js +67 -0
  15. package/model/src/lib/mailgun/notification.send.service.mailgun.js.map +1 -0
  16. package/model/src/lib/notification/index.d.ts +11 -0
  17. package/model/src/lib/notification/index.js +15 -0
  18. package/model/src/lib/notification/index.js.map +1 -0
  19. package/model/src/lib/notification/notification.action.init.server.d.ts +72 -0
  20. package/model/src/lib/notification/notification.action.init.server.js +228 -0
  21. package/model/src/lib/notification/notification.action.init.server.js.map +1 -0
  22. package/model/src/lib/notification/notification.action.server.d.ts +70 -0
  23. package/model/src/lib/notification/notification.action.server.js +1049 -0
  24. package/model/src/lib/notification/notification.action.server.js.map +1 -0
  25. package/model/src/lib/notification/notification.config.d.ts +31 -0
  26. package/model/src/lib/notification/notification.config.js +13 -0
  27. package/model/src/lib/notification/notification.config.js.map +1 -0
  28. package/model/src/lib/notification/notification.config.service.d.ts +49 -0
  29. package/model/src/lib/notification/notification.config.service.js +59 -0
  30. package/model/src/lib/notification/notification.config.service.js.map +1 -0
  31. package/model/src/lib/notification/notification.error.d.ts +9 -0
  32. package/model/src/lib/notification/notification.error.js +74 -0
  33. package/model/src/lib/notification/notification.error.js.map +1 -0
  34. package/model/src/lib/notification/notification.module.d.ts +49 -0
  35. package/model/src/lib/notification/notification.module.js +69 -0
  36. package/model/src/lib/notification/notification.module.js.map +1 -0
  37. package/model/src/lib/notification/notification.send.d.ts +4 -0
  38. package/model/src/lib/notification/notification.send.js +3 -0
  39. package/model/src/lib/notification/notification.send.js.map +1 -0
  40. package/model/src/lib/notification/notification.send.service.d.ts +65 -0
  41. package/model/src/lib/notification/notification.send.service.js +10 -0
  42. package/model/src/lib/notification/notification.send.service.js.map +1 -0
  43. package/model/src/lib/notification/notification.send.service.notificationsummary.d.ts +18 -0
  44. package/model/src/lib/notification/notification.send.service.notificationsummary.js +105 -0
  45. package/model/src/lib/notification/notification.send.service.notificationsummary.js.map +1 -0
  46. package/model/src/lib/notification/notification.send.service.text.d.ts +9 -0
  47. package/model/src/lib/notification/notification.send.service.text.js +30 -0
  48. package/model/src/lib/notification/notification.send.service.text.js.map +1 -0
  49. package/model/src/lib/notification/notification.util.d.ts +128 -0
  50. package/model/src/lib/notification/notification.util.js +465 -0
  51. package/model/src/lib/notification/notification.util.js.map +1 -0
  52. package/package.json +7 -1
  53. package/test/package.json +1 -1
  54. package/zoho/package.json +1 -1
@@ -0,0 +1,1049 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cleanupSentNotificationsFactory = exports.sendQueuedNotificationsFactory = exports.sendNotificationFactory = exports.NOTIFICATION_BOX_NOT_INITIALIZED_DELAY_MINUTES = exports.NOTIFICATION_MAX_SEND_ATTEMPTS = exports.KNOWN_BUT_UNCONFIGURED_NOTIFICATION_TEMPLATE_TYPE_DELETE_AFTER_RETRY_ATTEMPTS = exports.KNOWN_BUT_UNCONFIGURED_NOTIFICATION_TEMPLATE_TYPE_HOURS_DELAY = exports.UNKNOWN_NOTIFICATION_TEMPLATE_TYPE_DELETE_AFTER_RETRY_ATTEMPTS = exports.UNKNOWN_NOTIFICATION_TEMPLATE_TYPE_HOURS_DELAY = exports.updateNotificationBoxRecipientFactory = exports.updateNotificationBoxFactory = exports.createNotificationBoxFactory = exports.createNotificationBoxInTransactionFactory = exports.updateNotificationSummaryFactory = exports.createNotificationSummaryFactory = exports.resyncAllNotificationUsersFactory = exports.resyncNotificationUserFactory = exports.updateNotificationUserFactory = exports.createNotificationUserFactory = exports.notificationServerActions = exports.NotificationServerActions = exports.NOTIFICATION_SERVER_ACTION_CONTEXT_TOKEN = exports.BASE_NOTIFICATION_SERVER_ACTION_CONTEXT_TOKEN = void 0;
4
+ const date_1 = require("@dereekb/date");
5
+ const firebase_1 = require("@dereekb/firebase");
6
+ const firebase_server_1 = require("@dereekb/firebase-server");
7
+ const util_1 = require("@dereekb/util");
8
+ const date_fns_1 = require("date-fns");
9
+ const notification_error_1 = require("./notification.error");
10
+ const notification_util_1 = require("./notification.util");
11
+ /**
12
+ * Injection token for the BaseNotificationServerActionsContext
13
+ */
14
+ exports.BASE_NOTIFICATION_SERVER_ACTION_CONTEXT_TOKEN = 'BASE_NOTIFICATION_SERVER_ACTION_CONTEXT';
15
+ /**
16
+ * Injection token for the NotificationServerActionsContext
17
+ */
18
+ exports.NOTIFICATION_SERVER_ACTION_CONTEXT_TOKEN = 'NOTIFICATION_SERVER_ACTION_CONTEXT';
19
+ class NotificationServerActions {
20
+ }
21
+ exports.NotificationServerActions = NotificationServerActions;
22
+ function notificationServerActions(context) {
23
+ return {
24
+ createNotificationUser: createNotificationUserFactory(context),
25
+ updateNotificationUser: updateNotificationUserFactory(context),
26
+ resyncNotificationUser: resyncNotificationUserFactory(context),
27
+ resyncAllNotificationUsers: resyncAllNotificationUsersFactory(context),
28
+ createNotificationSummary: createNotificationSummaryFactory(context),
29
+ updateNotificationSummary: updateNotificationSummaryFactory(context),
30
+ createNotificationBox: createNotificationBoxFactory(context),
31
+ updateNotificationBox: updateNotificationBoxFactory(context),
32
+ updateNotificationBoxRecipient: updateNotificationBoxRecipientFactory(context),
33
+ sendNotification: sendNotificationFactory(context),
34
+ sendQueuedNotifications: sendQueuedNotificationsFactory(context),
35
+ cleanupSentNotifications: cleanupSentNotificationsFactory(context)
36
+ };
37
+ }
38
+ exports.notificationServerActions = notificationServerActions;
39
+ // MARK: Actions
40
+ function createNotificationUserFactory(context) {
41
+ const { firebaseServerActionTransformFunctionFactory, notificationUserCollection, authService } = context;
42
+ return firebaseServerActionTransformFunctionFactory(firebase_1.CreateNotificationUserParams, async (params) => {
43
+ const { uid } = params;
44
+ return async () => {
45
+ // assert they exist in the auth system
46
+ const userContext = authService.userContext(uid);
47
+ const userExistsInAuth = await userContext.exists();
48
+ if (!userExistsInAuth) {
49
+ throw (0, notification_error_1.notificationUserInvalidUidForCreateError)(uid);
50
+ }
51
+ const notificationUserDocument = notificationUserCollection.documentAccessor().loadDocumentForId(uid);
52
+ const newUserTemplate = {
53
+ uid,
54
+ bc: [],
55
+ b: [],
56
+ dc: {
57
+ c: {}
58
+ },
59
+ gc: {
60
+ c: {}
61
+ }
62
+ };
63
+ await notificationUserDocument.create(newUserTemplate);
64
+ return notificationUserDocument;
65
+ };
66
+ });
67
+ }
68
+ exports.createNotificationUserFactory = createNotificationUserFactory;
69
+ function updateNotificationUserFactory(context) {
70
+ const { firestoreContext, firebaseServerActionTransformFunctionFactory, notificationUserCollection, appNotificationTemplateTypeInfoRecordService } = context;
71
+ return firebaseServerActionTransformFunctionFactory(firebase_1.UpdateNotificationUserParams, async (params) => {
72
+ const { gc: inputGc, dc: inputDc, bc: inputBc } = params;
73
+ return async (notificationUserDocument) => {
74
+ await firestoreContext.runTransaction(async (transaction) => {
75
+ const notificationUserDocumentInTransaction = notificationUserCollection.documentAccessorForTransaction(transaction).loadDocumentFrom(notificationUserDocument);
76
+ const notificationUser = await (0, firebase_server_1.assertSnapshotData)(notificationUserDocumentInTransaction);
77
+ const updateTemplate = {};
78
+ const allKnownNotificationTypes = appNotificationTemplateTypeInfoRecordService.getAllKnownTemplateTypes();
79
+ if (inputDc != null) {
80
+ updateTemplate.dc = (0, firebase_1.updateNotificationUserDefaultNotificationBoxRecipientConfig)(notificationUser.dc, inputDc, allKnownNotificationTypes);
81
+ }
82
+ if (inputGc != null) {
83
+ const nextGc = (0, firebase_1.updateNotificationUserDefaultNotificationBoxRecipientConfig)(notificationUser.gc, inputGc, allKnownNotificationTypes);
84
+ if (!(0, util_1.areEqualPOJOValues)(notificationUser.gc, nextGc)) {
85
+ updateTemplate.gc = nextGc;
86
+ // iterate and update any box config that has the effective recipient change
87
+ updateTemplate.bc = notificationUser.bc.map((currentConfig) => {
88
+ // check item isn't already marked for sync or marked as removed
89
+ if (currentConfig.ns === true || currentConfig.rm === true) {
90
+ return currentConfig;
91
+ }
92
+ const currentEffectiveRecipient = (0, firebase_1.effectiveNotificationBoxRecipientConfig)({
93
+ uid: notificationUser.uid,
94
+ appNotificationTemplateTypeInfoRecordService,
95
+ gc: notificationUser.gc,
96
+ boxConfig: currentConfig
97
+ });
98
+ const nextEffectiveRecipient = (0, firebase_1.effectiveNotificationBoxRecipientConfig)({
99
+ uid: notificationUser.uid,
100
+ appNotificationTemplateTypeInfoRecordService,
101
+ gc: nextGc,
102
+ boxConfig: currentConfig
103
+ });
104
+ const effectiveConfigChanged = !(0, util_1.areEqualPOJOValues)(currentEffectiveRecipient, nextEffectiveRecipient);
105
+ return effectiveConfigChanged ? { ...currentConfig, ns: true } : currentConfig;
106
+ });
107
+ }
108
+ }
109
+ if (inputBc != null) {
110
+ const updateTemplateBc = (0, firebase_1.updateNotificationUserNotificationBoxRecipientConfigs)(updateTemplate.bc ?? notificationUser.bc, inputBc, appNotificationTemplateTypeInfoRecordService);
111
+ if (updateTemplateBc != null) {
112
+ updateTemplate.bc = updateTemplateBc;
113
+ updateTemplate.b = updateTemplateBc.map((x) => x.nb);
114
+ }
115
+ }
116
+ // if bc is being updated, then also update ns
117
+ if (updateTemplate.bc != null) {
118
+ updateTemplate.ns = updateTemplate.bc.some((x) => x.ns);
119
+ }
120
+ await notificationUserDocumentInTransaction.update(updateTemplate);
121
+ });
122
+ return notificationUserDocument;
123
+ };
124
+ });
125
+ }
126
+ exports.updateNotificationUserFactory = updateNotificationUserFactory;
127
+ const MAX_NOTIFICATION_BOXES_TO_UPDATE_PER_BATCH = 50;
128
+ function resyncNotificationUserFactory(context) {
129
+ const { firestoreContext, firebaseServerActionTransformFunctionFactory, notificationBoxCollection, notificationUserCollection, appNotificationTemplateTypeInfoRecordService } = context;
130
+ return firebaseServerActionTransformFunctionFactory(firebase_1.ResyncNotificationUserParams, async (params) => {
131
+ return async (notificationUserDocument) => {
132
+ // run updates in batches
133
+ let notificationBoxesUpdated = 0;
134
+ let hasMoreNotificationBoxesToSync = true;
135
+ while (hasMoreNotificationBoxesToSync) {
136
+ const batchResult = await firestoreContext.runTransaction(async (transaction) => {
137
+ const notificationUserDocumentInTransaction = notificationUserCollection.documentAccessorForTransaction(transaction).loadDocumentFrom(notificationUserDocument);
138
+ const notificationUser = await (0, firebase_server_1.assertSnapshotData)(notificationUserDocumentInTransaction);
139
+ const { gc } = notificationUser;
140
+ const notificationBoxConfigsToSync = notificationUser.bc.filter((x) => x.ns);
141
+ const notificationBoxConfigsToSyncInThisBatch = (0, util_1.takeFront)(notificationBoxConfigsToSync, MAX_NOTIFICATION_BOXES_TO_UPDATE_PER_BATCH);
142
+ /**
143
+ * These are the actual number of NotificationBox values that had recipients updated.
144
+ */
145
+ let notificationBoxesUpdatedInBatch = 0;
146
+ let hasUnsyncedNotificationBoxConfigs = false;
147
+ if (notificationBoxConfigsToSyncInThisBatch.length > 0) {
148
+ const notificationBoxConfigsToSyncInThisBatchMap = (0, util_1.makeModelMap)(notificationBoxConfigsToSyncInThisBatch, (x) => x.nb);
149
+ const notificationBoxIdsToSyncInThisBatch = Array.from(notificationBoxConfigsToSyncInThisBatchMap.keys());
150
+ const notificationBoxDocuments = (0, firebase_1.loadDocumentsForIds)(notificationBoxCollection.documentAccessorForTransaction(transaction), notificationBoxIdsToSyncInThisBatch);
151
+ const notificationBoxDocumentSnapshotDataPairs = await (0, firebase_1.getDocumentSnapshotDataPairs)(notificationBoxDocuments);
152
+ const notificationBoxConfigsToRemoveFromNotificationUser = new Set();
153
+ const notificationUserNotificationBoxConfigsToMarkAsRemoved = new Set();
154
+ const nextRecipientsMap = new Map();
155
+ // update each NotificationBoxDocument
156
+ await (0, util_1.performAsyncTasks)(notificationBoxDocumentSnapshotDataPairs, async (notificationBoxDocumentSnapshotDataPair) => {
157
+ const { data: notificationBox, document } = notificationBoxDocumentSnapshotDataPair;
158
+ const nb = document.id;
159
+ const notificationUserNotificationBoxConfig = notificationBoxConfigsToSyncInThisBatchMap.get(nb); // always exists
160
+ if (!notificationBox) {
161
+ // if the entire NotificationBox no longer exists, flag to remove it from the user as a cleanup measure
162
+ notificationBoxConfigsToRemoveFromNotificationUser.add(nb);
163
+ }
164
+ else {
165
+ // update in the NotificationBox
166
+ const recipientIndex = notificationBox.r.findIndex((x) => x.uid === notificationUser.uid);
167
+ let r;
168
+ if (recipientIndex === -1) {
169
+ // if they are not in the NotificationBox, then mark them as removed on the user
170
+ notificationUserNotificationBoxConfigsToMarkAsRemoved.add(nb);
171
+ }
172
+ else if (notificationUserNotificationBoxConfig.rm) {
173
+ // remove from the notification box if it is flagged
174
+ r = (0, util_1.removeValuesAtIndexesFromArrayCopy)(notificationBox.r, recipientIndex);
175
+ }
176
+ else {
177
+ const { m } = notificationBox;
178
+ const recipient = notificationBox.r[recipientIndex];
179
+ const nextRecipient = (0, firebase_1.effectiveNotificationBoxRecipientConfig)({
180
+ uid: notificationUser.uid,
181
+ m,
182
+ appNotificationTemplateTypeInfoRecordService,
183
+ gc,
184
+ boxConfig: notificationUserNotificationBoxConfig,
185
+ recipient
186
+ });
187
+ const recipientHasChange = !(0, util_1.areEqualPOJOValues)(nextRecipient, recipient);
188
+ // only update recipients if the next/new recipient is not equal to the existing one
189
+ if (recipientHasChange) {
190
+ r = [...notificationBox.r];
191
+ r[recipientIndex] = nextRecipient;
192
+ nextRecipientsMap.set(nb, nextRecipient);
193
+ }
194
+ else {
195
+ nextRecipientsMap.set(nb, recipient);
196
+ }
197
+ }
198
+ // update recipients if needed
199
+ if (r != null) {
200
+ await document.update({ r });
201
+ notificationBoxesUpdatedInBatch += 1;
202
+ }
203
+ }
204
+ });
205
+ // Update the NotificationUser
206
+ const notificationBoxIdsSynced = new Set(notificationBoxIdsToSyncInThisBatch);
207
+ // start nextConfigs off as a new array with none of the sync'd ids
208
+ const nextConfigs = notificationBoxConfigsToSyncInThisBatch.filter((x) => !notificationBoxIdsSynced.has(x.nb));
209
+ notificationBoxIdsToSyncInThisBatch.forEach((nb) => {
210
+ let nextConfig;
211
+ if (notificationBoxConfigsToRemoveFromNotificationUser.has(nb)) {
212
+ // do nothing, as it should be removed
213
+ }
214
+ else {
215
+ const existingConfig = notificationBoxConfigsToSyncInThisBatchMap.get(nb);
216
+ if (notificationUserNotificationBoxConfigsToMarkAsRemoved.has(nb) || existingConfig.rm) {
217
+ // if the recipient was being removed or is marked as removed, then update the config to confirm removal
218
+ nextConfig = {
219
+ ...existingConfig,
220
+ nb,
221
+ rm: true,
222
+ i: util_1.UNSET_INDEX_NUMBER
223
+ };
224
+ }
225
+ else {
226
+ // else, use the updated recipient and keep/copy the
227
+ const updatedRecipient = nextRecipientsMap.get(nb);
228
+ nextConfig = {
229
+ ...existingConfig,
230
+ nb,
231
+ rm: false,
232
+ i: updatedRecipient.i ?? util_1.UNSET_INDEX_NUMBER
233
+ };
234
+ }
235
+ }
236
+ if (nextConfig != null) {
237
+ nextConfig.ns = false; // mark as synced
238
+ nextConfigs.push(nextConfig);
239
+ }
240
+ });
241
+ const ns = nextConfigs.some((x) => x.ns);
242
+ await notificationUserDocumentInTransaction.update({ bc: nextConfigs, ns });
243
+ hasUnsyncedNotificationBoxConfigs = ns;
244
+ }
245
+ const batchResult = {
246
+ hasMoreNotificationBoxesToSync: hasUnsyncedNotificationBoxConfigs,
247
+ notificationBoxesUpdatedInBatch
248
+ };
249
+ return batchResult;
250
+ });
251
+ hasMoreNotificationBoxesToSync = batchResult.hasMoreNotificationBoxesToSync;
252
+ notificationBoxesUpdated += batchResult.notificationBoxesUpdatedInBatch;
253
+ }
254
+ const result = {
255
+ notificationBoxesUpdated
256
+ };
257
+ return result;
258
+ };
259
+ });
260
+ }
261
+ exports.resyncNotificationUserFactory = resyncNotificationUserFactory;
262
+ function resyncAllNotificationUsersFactory(context) {
263
+ const { firestoreContext, firebaseServerActionTransformFunctionFactory, notificationUserCollection } = context;
264
+ const resyncNotificationUser = resyncNotificationUserFactory(context);
265
+ return async (params) => {
266
+ let notificationBoxesUpdated = 0;
267
+ const resyncNotificationUserParams = { key: (0, firebase_1.firestoreDummyKey)() };
268
+ const resyncNotificationUserInstance = await resyncNotificationUser(resyncNotificationUserParams);
269
+ const iterateResult = await (0, firebase_1.iterateFirestoreDocumentSnapshotPairs)({
270
+ documentAccessor: notificationUserCollection.documentAccessor(),
271
+ iterateSnapshotPair: async (snapshotPair) => {
272
+ const { document: notificationUserDocument } = snapshotPair;
273
+ const result = await resyncNotificationUserInstance(notificationUserDocument);
274
+ notificationBoxesUpdated += result.notificationBoxesUpdated;
275
+ },
276
+ constraintsFactory: () => (0, firebase_1.notificationUsersFlaggedForNeedsSyncQuery)(),
277
+ snapshotsPerformTasksConfig: {
278
+ // prevent NotificationUsers with the same NotificationBoxes from being updated/sync'd at the same time
279
+ nonConcurrentTaskKeyFactory: (x) => {
280
+ const notificationBoxIdsToSync = x
281
+ .data()
282
+ .bc.filter((x) => x.ns)
283
+ .map((x) => x.nb);
284
+ return notificationBoxIdsToSync;
285
+ }
286
+ },
287
+ queryFactory: notificationUserCollection,
288
+ batchSize: undefined,
289
+ performTasksConfig: {
290
+ maxParallelTasks: 10
291
+ }
292
+ });
293
+ const result = {
294
+ notificationUsersResynced: iterateResult.totalSnapshotsVisited,
295
+ notificationBoxesUpdated
296
+ };
297
+ return result;
298
+ };
299
+ }
300
+ exports.resyncAllNotificationUsersFactory = resyncAllNotificationUsersFactory;
301
+ function createNotificationSummaryFactory(context) {
302
+ const { firebaseServerActionTransformFunctionFactory, notificationSummaryCollection, authService } = context;
303
+ return firebaseServerActionTransformFunctionFactory(firebase_1.CreateNotificationSummaryParams, async (params) => {
304
+ const { model } = params;
305
+ return async () => {
306
+ const notificationSummaryId = (0, firebase_1.notificationSummaryIdForModel)(model);
307
+ const notificationSummaryDocument = notificationSummaryCollection.documentAccessor().loadDocumentForId(notificationSummaryId);
308
+ const newSummaryTemplate = (0, notification_util_1.makeNewNotificationSummaryTemplate)(model);
309
+ await notificationSummaryDocument.create(newSummaryTemplate);
310
+ return notificationSummaryDocument;
311
+ };
312
+ });
313
+ }
314
+ exports.createNotificationSummaryFactory = createNotificationSummaryFactory;
315
+ function updateNotificationSummaryFactory(context) {
316
+ const { firebaseServerActionTransformFunctionFactory, notificationSummaryCollection } = context;
317
+ return firebaseServerActionTransformFunctionFactory(firebase_1.UpdateNotificationSummaryParams, async (params) => {
318
+ const { setReadAtTime, flagAllRead } = params;
319
+ return async (notificationSummaryDocument) => {
320
+ let updateTemplate;
321
+ if (setReadAtTime != null) {
322
+ updateTemplate = { rat: setReadAtTime };
323
+ }
324
+ else if (flagAllRead === true) {
325
+ updateTemplate = { rat: new Date() };
326
+ }
327
+ if (updateTemplate != null) {
328
+ await notificationSummaryDocument.update(updateTemplate);
329
+ }
330
+ return notificationSummaryDocument;
331
+ };
332
+ });
333
+ }
334
+ exports.updateNotificationSummaryFactory = updateNotificationSummaryFactory;
335
+ function createNotificationBoxInTransactionFactory(context) {
336
+ const { notificationBoxCollection } = context;
337
+ return async (params, transaction) => {
338
+ const { now = new Date(), model } = params;
339
+ const notificationBoxId = (0, firebase_1.notificationBoxIdForModel)(model);
340
+ const notificationBoxDocument = notificationBoxCollection.documentAccessorForTransaction(transaction).loadDocumentForId(notificationBoxId);
341
+ const notificationBoxTemplate = {
342
+ m: model,
343
+ o: (0, firebase_1.firestoreDummyKey)(),
344
+ r: [],
345
+ cat: now,
346
+ w: (0, date_1.yearWeekCode)(now),
347
+ s: true // requires initialization
348
+ };
349
+ await notificationBoxDocument.create(notificationBoxTemplate);
350
+ return {
351
+ notificationBoxTemplate,
352
+ notificationBoxDocument
353
+ };
354
+ };
355
+ }
356
+ exports.createNotificationBoxInTransactionFactory = createNotificationBoxInTransactionFactory;
357
+ function createNotificationBoxFactory(context) {
358
+ const { firestoreContext, authService, notificationBoxCollection, firebaseServerActionTransformFunctionFactory } = context;
359
+ const createNotificationBoxInTransaction = createNotificationBoxInTransactionFactory(context);
360
+ return firebaseServerActionTransformFunctionFactory(firebase_1.CreateNotificationBoxParams, async (params) => {
361
+ const { model } = params;
362
+ return async () => {
363
+ const result = await firestoreContext.runTransaction(async (transaction) => {
364
+ const { notificationBoxDocument } = await createNotificationBoxInTransaction({
365
+ model
366
+ }, transaction);
367
+ return notificationBoxDocument;
368
+ });
369
+ return notificationBoxCollection.documentAccessor().loadDocumentFrom(result);
370
+ };
371
+ });
372
+ }
373
+ exports.createNotificationBoxFactory = createNotificationBoxFactory;
374
+ function updateNotificationBoxFactory({ firebaseServerActionTransformFunctionFactory }) {
375
+ return firebaseServerActionTransformFunctionFactory(firebase_1.UpdateNotificationBoxParams, async () => {
376
+ return async (notificationBoxDocument) => {
377
+ // does nothing currently.
378
+ return notificationBoxDocument;
379
+ };
380
+ });
381
+ }
382
+ exports.updateNotificationBoxFactory = updateNotificationBoxFactory;
383
+ function updateNotificationBoxRecipientFactory({ firestoreContext, authService, notificationBoxCollection, notificationUserCollection, firebaseServerActionTransformFunctionFactory }) {
384
+ return firebaseServerActionTransformFunctionFactory(firebase_1.UpdateNotificationBoxRecipientParams, async (params) => {
385
+ const { uid, i, insert, remove, configs: inputC } = params;
386
+ const findRecipientFn = (x) => (uid != null && x.uid === uid) || (i != null && i === i);
387
+ return async (notificationBoxDocument) => {
388
+ await firestoreContext.runTransaction(async (transaction) => {
389
+ const notificationBoxDocumentInTransaction = notificationBoxCollection.documentAccessorForTransaction(transaction).loadDocumentFrom(notificationBoxDocument);
390
+ const notificationBox = await (0, firebase_server_1.assertSnapshotData)(notificationBoxDocumentInTransaction);
391
+ const { m } = notificationBox;
392
+ let r;
393
+ let targetRecipientIndex = notificationBox.r.findIndex(findRecipientFn);
394
+ const targetRecipient = notificationBox.r[targetRecipientIndex];
395
+ let nextRecipient;
396
+ if (remove) {
397
+ if (targetRecipientIndex != null) {
398
+ r = [...notificationBox.r]; // remove if they exist.
399
+ delete r[targetRecipientIndex];
400
+ }
401
+ }
402
+ else {
403
+ if (!targetRecipient && !insert) {
404
+ throw (0, notification_error_1.notificationBoxRecipientDoesNotExistsError)();
405
+ }
406
+ const c = (inputC != null ? (0, firebase_1.notificationBoxRecipientTemplateConfigArrayToRecord)(inputC) : targetRecipient?.c) ?? {};
407
+ nextRecipient = {
408
+ uid,
409
+ i: targetRecipient?.i ?? util_1.UNSET_INDEX_NUMBER,
410
+ c,
411
+ ...(0, firebase_1.updateNotificationRecipient)(targetRecipient ?? {}, params)
412
+ };
413
+ r = [...notificationBox.r];
414
+ if (targetRecipient) {
415
+ nextRecipient.i = targetRecipient.i;
416
+ nextRecipient = (0, firebase_1.mergeNotificationBoxRecipients)(targetRecipient, nextRecipient);
417
+ r[targetRecipientIndex] = nextRecipient; // override in the array
418
+ }
419
+ else {
420
+ const nextI = (0, util_1.computeNextFreeIndexOnSortedValuesFunction)(util_1.readIndexNumber)(notificationBox.r); // r is sorted by index in ascending order, so the last value is the largest i
421
+ nextRecipient.i = nextI;
422
+ // should have the greatest i value, push to end
423
+ r.push(nextRecipient);
424
+ targetRecipientIndex = r.length - 1;
425
+ }
426
+ }
427
+ // save changes to r if it changed
428
+ if (r != null) {
429
+ const notificationUserId = targetRecipient?.uid ?? nextRecipient?.uid;
430
+ // sync with the notification user's document, if it exists
431
+ if (notificationUserId != null) {
432
+ const notificationBoxId = notificationBoxDocument.id;
433
+ const notificationUserDocument = await notificationUserCollection.documentAccessorForTransaction(transaction).loadDocumentForId(notificationUserId);
434
+ let notificationUser = await notificationUserDocument.snapshotData();
435
+ const createNotificationUser = !notificationUser && !remove && insert;
436
+ if (createNotificationUser) {
437
+ // assert they exist in the auth system
438
+ const userContext = authService.userContext(notificationUserId);
439
+ const userExistsInAuth = await userContext.exists();
440
+ if (!userExistsInAuth) {
441
+ throw (0, notification_error_1.notificationUserInvalidUidForCreateError)(notificationUserId);
442
+ }
443
+ const notificationUserTemplate = {
444
+ uid: notificationUserId,
445
+ b: [],
446
+ bc: [],
447
+ ns: false,
448
+ dc: {
449
+ c: {}
450
+ },
451
+ gc: {
452
+ c: {}
453
+ }
454
+ };
455
+ notificationUser = notificationUserTemplate;
456
+ }
457
+ // if the user is being inserted or exists, then make updates
458
+ if (notificationUser != null) {
459
+ const { updatedBc, updatedNotificationBoxRecipient } = (0, notification_util_1.updateNotificationUserNotificationBoxRecipientConfig)({
460
+ notificationBoxId,
461
+ notificationUserId,
462
+ notificationBoxAssociatedModelKey: m,
463
+ notificationUser,
464
+ insertingRecipientIntoNotificationBox: insert,
465
+ removeRecipientFromNotificationBox: remove,
466
+ notificationBoxRecipient: nextRecipient
467
+ });
468
+ const updatedB = updatedBc ? updatedBc.map((x) => x.nb) : undefined;
469
+ if (createNotificationUser) {
470
+ const newUserTemplate = {
471
+ ...notificationUser,
472
+ bc: updatedBc ?? [],
473
+ b: updatedB ?? []
474
+ };
475
+ await notificationUserDocument.create(newUserTemplate);
476
+ }
477
+ else if (updatedBc != null) {
478
+ await notificationUserDocument.update({ bc: updatedBc, b: updatedB });
479
+ }
480
+ // Set if nextRecipient is updated/influence from existing configuration
481
+ if (targetRecipientIndex != null && updatedNotificationBoxRecipient && !remove) {
482
+ r[targetRecipientIndex] = updatedNotificationBoxRecipient; // set the updated value in r
483
+ }
484
+ }
485
+ // else, if removing and they don't exist, nothing to update
486
+ }
487
+ await notificationBoxDocumentInTransaction.update({ r });
488
+ }
489
+ });
490
+ return notificationBoxDocument;
491
+ };
492
+ });
493
+ }
494
+ exports.updateNotificationBoxRecipientFactory = updateNotificationBoxRecipientFactory;
495
+ exports.UNKNOWN_NOTIFICATION_TEMPLATE_TYPE_HOURS_DELAY = 8;
496
+ exports.UNKNOWN_NOTIFICATION_TEMPLATE_TYPE_DELETE_AFTER_RETRY_ATTEMPTS = 1;
497
+ exports.KNOWN_BUT_UNCONFIGURED_NOTIFICATION_TEMPLATE_TYPE_HOURS_DELAY = exports.UNKNOWN_NOTIFICATION_TEMPLATE_TYPE_HOURS_DELAY;
498
+ exports.KNOWN_BUT_UNCONFIGURED_NOTIFICATION_TEMPLATE_TYPE_DELETE_AFTER_RETRY_ATTEMPTS = 5;
499
+ exports.NOTIFICATION_MAX_SEND_ATTEMPTS = 5;
500
+ exports.NOTIFICATION_BOX_NOT_INITIALIZED_DELAY_MINUTES = 8;
501
+ function sendNotificationFactory(context) {
502
+ const { appNotificationTemplateTypeInfoRecordService, notificationSendService, notificationTemplateService, authService, notificationBoxCollection, notificationCollectionGroup, notificationUserCollection, firestoreContext, firebaseServerActionTransformFunctionFactory } = context;
503
+ const createNotificationBoxInTransaction = createNotificationBoxInTransactionFactory(context);
504
+ const notificationUserAccessor = notificationUserCollection.documentAccessor();
505
+ return firebaseServerActionTransformFunctionFactory(firebase_1.SendNotificationParams, async (params) => {
506
+ const { ignoreSendAtThrottle } = params;
507
+ return async (notificationDocument) => {
508
+ // does nothing currently.
509
+ const { throttled, tryRun, notification, createdBox, notificationBoxNeedsInitialization, notificationBox, notificationBoxModelKey, deletedNotification, templateInstance, isConfiguredTemplateType, isKnownTemplateType } = await firestoreContext.runTransaction(async (transaction) => {
510
+ const notificationBoxDocument = notificationBoxCollection.documentAccessorForTransaction(transaction).loadDocument(notificationDocument.parent);
511
+ const notificationDocumentInTransaction = notificationCollectionGroup.documentAccessorForTransaction(transaction).loadDocumentFrom(notificationDocument);
512
+ let [notificationBox, notification] = await Promise.all([notificationBoxDocument.snapshotData(), (0, firebase_1.getDocumentSnapshotData)(notificationDocumentInTransaction)]);
513
+ const model = (0, firebase_1.inferKeyFromTwoWayFlatFirestoreModelKey)(notificationBoxDocument.id);
514
+ let tryRun = true;
515
+ let throttled = false;
516
+ let nextSat;
517
+ if (!notification) {
518
+ tryRun = false;
519
+ }
520
+ else if (!ignoreSendAtThrottle) {
521
+ tryRun = (0, date_fns_1.isPast)(notification.sat);
522
+ if (tryRun) {
523
+ nextSat = (0, date_fns_1.addMinutes)(new Date(), 10); // try again in 10 minutes if not successful
524
+ }
525
+ else {
526
+ throttled = true;
527
+ }
528
+ }
529
+ let createdBox = false;
530
+ let deletedNotification = false;
531
+ let notificationBoxNeedsInitialization = false;
532
+ let isKnownTemplateType;
533
+ let isConfiguredTemplateType;
534
+ let templateInstance;
535
+ async function deleteNotification() {
536
+ tryRun = false;
537
+ await notificationDocumentInTransaction.accessor.delete();
538
+ deletedNotification = true;
539
+ }
540
+ // create/init the notification box if necessary/configured.
541
+ if (notification && tryRun) {
542
+ // if we're still trying to run, check the template is ok. If not, cancel the run.
543
+ const { t } = notification.n;
544
+ templateInstance = notificationTemplateService.templateInstanceForType(t);
545
+ isKnownTemplateType = isConfiguredTemplateType = templateInstance.isConfiguredType;
546
+ if (!isConfiguredTemplateType) {
547
+ // log the issue that an notification with an unconfigured type was queued
548
+ const templateInfo = appNotificationTemplateTypeInfoRecordService.appNotificationTemplateTypeInfoRecord[t];
549
+ isKnownTemplateType = templateInfo != null;
550
+ const retryAttempts = isKnownTemplateType ? exports.KNOWN_BUT_UNCONFIGURED_NOTIFICATION_TEMPLATE_TYPE_DELETE_AFTER_RETRY_ATTEMPTS : exports.UNKNOWN_NOTIFICATION_TEMPLATE_TYPE_DELETE_AFTER_RETRY_ATTEMPTS;
551
+ const delay = isKnownTemplateType ? exports.KNOWN_BUT_UNCONFIGURED_NOTIFICATION_TEMPLATE_TYPE_HOURS_DELAY : exports.UNKNOWN_NOTIFICATION_TEMPLATE_TYPE_HOURS_DELAY;
552
+ if (notification.a < retryAttempts) {
553
+ if (isKnownTemplateType) {
554
+ console.warn(`Unconfigured but known template type of "${t}" (${templateInfo.name}) was found in a Notification. Send is being delayed by ${delay} hours.`);
555
+ }
556
+ else {
557
+ console.warn(`Unknown template type of "${t}" was found in a Notification. Send is being delayed by ${delay} hours.`);
558
+ }
559
+ // delay send for 12 hours, for a max of 24 hours incase it is an issue.
560
+ nextSat = (0, date_fns_1.addHours)(new Date(), delay);
561
+ tryRun = false;
562
+ }
563
+ else {
564
+ console.warn(`Unconfigured template type of "${t}" was found in a Notification. The Notification has reached the delete threshhold after failing to send due to misconfiguration multiple times and is being deleted.`);
565
+ // after attempting to send 3 times, delete it.
566
+ await deleteNotification();
567
+ }
568
+ }
569
+ // handle the notification box's absence
570
+ if (!notificationBox && tryRun) {
571
+ switch (notification.st) {
572
+ case firebase_1.NotificationSendType.INIT_BOX_AND_SEND:
573
+ const { notificationBoxTemplate } = await createNotificationBoxInTransaction({
574
+ model
575
+ }, transaction);
576
+ notificationBox = notificationBoxTemplate;
577
+ createdBox = true;
578
+ break;
579
+ case firebase_1.NotificationSendType.SEND_IF_BOX_EXISTS:
580
+ // delete the notification since it won't get sent.
581
+ await deleteNotification();
582
+ break;
583
+ case firebase_1.NotificationSendType.SEND_WITHOUT_CREATING_BOX:
584
+ // continue with current tryRun
585
+ break;
586
+ }
587
+ }
588
+ // if the notification box is not initialized/synchronized yet, do not run.
589
+ if (tryRun && notificationBox && notificationBox.s) {
590
+ notificationBoxNeedsInitialization = true;
591
+ tryRun = false;
592
+ nextSat = (0, date_fns_1.addMinutes)(new Date(), exports.NOTIFICATION_BOX_NOT_INITIALIZED_DELAY_MINUTES);
593
+ }
594
+ }
595
+ // update the notification send at time and attempt count
596
+ if (notification != null && nextSat != null && !deletedNotification) {
597
+ const isAtMaxAttempts = notification.a >= exports.NOTIFICATION_MAX_SEND_ATTEMPTS;
598
+ if (isAtMaxAttempts && notificationBoxNeedsInitialization) {
599
+ await deleteNotification(); // just delete the notification if the box still hasn't been initialized successfully at this point.
600
+ }
601
+ if (!deletedNotification) {
602
+ await notificationDocumentInTransaction.update({ sat: nextSat, a: notification.a + 1 });
603
+ }
604
+ }
605
+ return {
606
+ throttled,
607
+ deletedNotification,
608
+ createdBox,
609
+ notificationBoxModelKey: model,
610
+ notificationBoxNeedsInitialization,
611
+ notificationBox,
612
+ notification,
613
+ templateInstance,
614
+ isKnownTemplateType,
615
+ isConfiguredTemplateType,
616
+ tryRun
617
+ };
618
+ });
619
+ let success = false;
620
+ let sendEmailsResult;
621
+ let sendTextsResult;
622
+ let sendNotificationSummaryResult;
623
+ let loadMessageFunctionFailure = false;
624
+ let buildMessageFailure = false;
625
+ let notificationMarkedDone = false;
626
+ const notificationTemplateType = templateInstance?.type;
627
+ // notification is only null/undefined if it didn't exist.
628
+ if (notification != null) {
629
+ if (tryRun && templateInstance != null) {
630
+ // first load the message function
631
+ const messageFunction = await templateInstance
632
+ .loadMessageFunction({
633
+ item: notification.n,
634
+ notification,
635
+ notificationBox: {
636
+ m: notificationBoxModelKey
637
+ }
638
+ })
639
+ .catch((e) => {
640
+ loadMessageFunctionFailure = true;
641
+ success = false;
642
+ console.error(`Failed loading message function for type ${notificationTemplateType}: `, e);
643
+ return undefined;
644
+ });
645
+ if (messageFunction) {
646
+ // expand recipients
647
+ const { emails: emailRecipients, texts: textRecipients, notificationSummaries: notificationSummaryRecipients } = await (0, notification_util_1.expandNotificationRecipients)({
648
+ notification,
649
+ notificationBox,
650
+ authService,
651
+ notificationUserAccessor,
652
+ globalRecipients: messageFunction.globalRecipients,
653
+ notificationSummaryIdForUid: notificationSendService.notificationSummaryIdForUidFunction
654
+ });
655
+ let { es, ts, ps, ns, esr: currentEsr, tsr: currentTsr } = notification;
656
+ // do emails
657
+ let esr;
658
+ if (es === firebase_1.NotificationSendState.QUEUED || es === firebase_1.NotificationSendState.SENT_PARTIAL) {
659
+ const emailRecipientsAlreadySentTo = new Set(currentEsr.map((x) => x.toLowerCase()));
660
+ const emailInputContexts = emailRecipients
661
+ .filter((x) => !emailRecipientsAlreadySentTo.has(x.emailAddress.toLowerCase()))
662
+ .map((x) => {
663
+ const context = {
664
+ recipient: {
665
+ n: x.name,
666
+ e: x.emailAddress,
667
+ t: x.phoneNumber
668
+ }
669
+ };
670
+ return context;
671
+ });
672
+ const emailMessages = await Promise.all(emailInputContexts.map(messageFunction)).catch((e) => {
673
+ console.error(`Failed building message function for type ${notificationTemplateType}: `, e);
674
+ buildMessageFailure = true;
675
+ return undefined;
676
+ });
677
+ if (emailMessages?.length) {
678
+ if (notificationSendService.emailSendService != null) {
679
+ let sendInstance;
680
+ try {
681
+ sendInstance = await notificationSendService.emailSendService.buildSendInstanceForEmailNotificationMessages(emailMessages);
682
+ }
683
+ catch (e) {
684
+ console.error(`Failed building email send instance for notification "${notification.id}" with type "${notificationTemplateType}": `, e);
685
+ es = firebase_1.NotificationSendState.CONFIG_ERROR;
686
+ }
687
+ if (sendInstance) {
688
+ try {
689
+ sendEmailsResult = await sendInstance();
690
+ }
691
+ catch (e) {
692
+ console.error(`Failed sending email notification "${notification.id}" with type "${notificationTemplateType}": `, e);
693
+ es = firebase_1.NotificationSendState.SEND_ERROR;
694
+ }
695
+ }
696
+ }
697
+ else {
698
+ console.error(`Failed sending email notification "${notification.id}" with type "${notificationTemplateType}" due to no email service being configured.`);
699
+ es = firebase_1.NotificationSendState.CONFIG_ERROR;
700
+ }
701
+ if (sendEmailsResult != null) {
702
+ const { success, failed } = sendEmailsResult;
703
+ esr = success.length ? currentEsr.concat(success.map((x) => x.toLowerCase())) : undefined;
704
+ if (failed.length > 0) {
705
+ es = firebase_1.NotificationSendState.SENT_PARTIAL;
706
+ }
707
+ else {
708
+ es = firebase_1.NotificationSendState.SENT;
709
+ }
710
+ }
711
+ }
712
+ else {
713
+ es = firebase_1.NotificationSendState.SENT;
714
+ }
715
+ }
716
+ // do phone numbers
717
+ let tsr;
718
+ if (ts === firebase_1.NotificationSendState.QUEUED || ts === firebase_1.NotificationSendState.SENT_PARTIAL) {
719
+ const textRecipientsAlreadySentTo = new Set(currentTsr);
720
+ const textInputContexts = textRecipients
721
+ .filter((x) => !textRecipientsAlreadySentTo.has(x.phoneNumber))
722
+ .map((x) => {
723
+ const context = {
724
+ recipient: {
725
+ n: x.name,
726
+ e: x.emailAddress,
727
+ t: x.phoneNumber
728
+ }
729
+ };
730
+ return context;
731
+ });
732
+ const textMessages = await Promise.all(textInputContexts.map(messageFunction)).catch((e) => {
733
+ console.error(`Failed building message function for type ${notificationTemplateType}: `, e);
734
+ buildMessageFailure = true;
735
+ return undefined;
736
+ });
737
+ if (textMessages?.length) {
738
+ if (notificationSendService.textSendService != null) {
739
+ let sendInstance;
740
+ try {
741
+ sendInstance = await notificationSendService.textSendService.buildSendInstanceForTextNotificationMessages(textMessages);
742
+ }
743
+ catch (e) {
744
+ console.error(`Failed building text send instance for notification "${notification.id}" with type "${notificationTemplateType}": `, e);
745
+ ts = firebase_1.NotificationSendState.CONFIG_ERROR;
746
+ }
747
+ if (sendInstance) {
748
+ try {
749
+ sendTextsResult = await sendInstance();
750
+ }
751
+ catch (e) {
752
+ console.error(`Failed sending text notification "${notification.id}" with type "${notificationTemplateType}": `, e);
753
+ ts = firebase_1.NotificationSendState.SEND_ERROR;
754
+ }
755
+ }
756
+ }
757
+ else {
758
+ console.error(`Failed sending text notification "${notification.id}" with type "${notificationTemplateType}" due to no text service being configured.`);
759
+ ts = firebase_1.NotificationSendState.CONFIG_ERROR;
760
+ }
761
+ if (sendTextsResult != null) {
762
+ const { success, failed, ignored } = sendTextsResult;
763
+ tsr = success.length ? currentTsr.concat(success) : undefined;
764
+ if (failed.length > 0) {
765
+ ts = firebase_1.NotificationSendState.SENT_PARTIAL;
766
+ }
767
+ else {
768
+ ts = firebase_1.NotificationSendState.SENT;
769
+ }
770
+ }
771
+ }
772
+ else {
773
+ ts = firebase_1.NotificationSendState.SENT;
774
+ }
775
+ }
776
+ ps = firebase_1.NotificationSendState.NO_TRY;
777
+ // NOTE: FCM token management will probably done with a separate system within Notification that stores FCMs for specific users in the app. May also use UIDs to determine who got the push notificdation or not...
778
+ // do notification summaries
779
+ if (ns === firebase_1.NotificationSendState.QUEUED || ns === firebase_1.NotificationSendState.SENT_PARTIAL) {
780
+ const notificationSummaryInputContexts = notificationSummaryRecipients.map((x) => {
781
+ const context = {
782
+ recipient: {
783
+ n: x.name,
784
+ s: x.notificationSummaryId
785
+ }
786
+ };
787
+ return context;
788
+ });
789
+ const notificationSummaryMessages = await Promise.all(notificationSummaryInputContexts.map(messageFunction)).catch((e) => {
790
+ console.error(`Failed building message function for type ${notificationTemplateType}: `, e);
791
+ buildMessageFailure = true;
792
+ return undefined;
793
+ });
794
+ if (notificationSummaryMessages?.length) {
795
+ if (notificationSendService.notificationSummarySendService != null) {
796
+ let sendInstance;
797
+ try {
798
+ sendInstance = await notificationSendService.notificationSummarySendService.buildSendInstanceForNotificationSummaryMessages(notificationSummaryMessages);
799
+ }
800
+ catch (e) {
801
+ console.error(`Failed building notification summary send instance for notification "${notification.id}" with type "${notificationTemplateType}": `, e);
802
+ ns = firebase_1.NotificationSendState.CONFIG_ERROR;
803
+ }
804
+ if (sendInstance) {
805
+ try {
806
+ sendNotificationSummaryResult = await sendInstance();
807
+ ns = firebase_1.NotificationSendState.SENT;
808
+ }
809
+ catch (e) {
810
+ console.error(`Failed sending notification summary notification "${notification.id}" with type "${notificationTemplateType}": `, e);
811
+ ns = firebase_1.NotificationSendState.SEND_ERROR;
812
+ }
813
+ }
814
+ }
815
+ else {
816
+ console.error(`Failed sending notification summary notification "${notification.id}" with type "${notificationTemplateType}" due to no notification summary service being configured.`);
817
+ ns = firebase_1.NotificationSendState.CONFIG_ERROR;
818
+ }
819
+ }
820
+ else {
821
+ ns = firebase_1.NotificationSendState.SENT;
822
+ }
823
+ }
824
+ // calculate results
825
+ const notificationTemplate = { es, ts, ps, ns, esr, tsr };
826
+ success = (0, firebase_1.notificationSendFlagsImplyIsComplete)(notificationTemplate);
827
+ if (success) {
828
+ notificationTemplate.d = true;
829
+ }
830
+ else {
831
+ notificationTemplate.a = notification.a + 1;
832
+ if (notificationTemplate.a >= exports.NOTIFICATION_MAX_SEND_ATTEMPTS) {
833
+ notificationTemplate.d = true;
834
+ }
835
+ }
836
+ await notificationDocument.update(notificationTemplate);
837
+ notificationMarkedDone = notificationTemplate.d === true;
838
+ }
839
+ }
840
+ else {
841
+ switch (notification.st) {
842
+ case firebase_1.NotificationSendType.SEND_IF_BOX_EXISTS:
843
+ // deleted successfully
844
+ success = deletedNotification;
845
+ break;
846
+ }
847
+ }
848
+ }
849
+ const result = {
850
+ notificationTemplateType,
851
+ isKnownTemplateType,
852
+ isConfiguredTemplateType,
853
+ throttled,
854
+ exists: notification != null,
855
+ boxExists: notificationBox != null,
856
+ notificationBoxNeedsInitialization,
857
+ createdBox,
858
+ deletedNotification,
859
+ notificationMarkedDone,
860
+ tryRun,
861
+ success,
862
+ sendEmailsResult,
863
+ sendTextsResult,
864
+ sendNotificationSummaryResult,
865
+ loadMessageFunctionFailure,
866
+ buildMessageFailure
867
+ };
868
+ return result;
869
+ };
870
+ });
871
+ }
872
+ exports.sendNotificationFactory = sendNotificationFactory;
873
+ function sendQueuedNotificationsFactory(context) {
874
+ const { firebaseServerActionTransformFunctionFactory, notificationCollectionGroup } = context;
875
+ const sendNotification = sendNotificationFactory(context);
876
+ return firebaseServerActionTransformFunctionFactory(firebase_1.SendQueuedNotificationsParams, async () => {
877
+ return async () => {
878
+ let notificationBoxesCreated = 0;
879
+ let notificationsDeleted = 0;
880
+ let notificationsVisited = 0;
881
+ let notificationsSucceeded = 0;
882
+ let notificationsDelayed = 0;
883
+ let notificationsFailed = 0;
884
+ let sendEmailsResult;
885
+ let sendTextsResult;
886
+ let sendNotificationSummaryResult;
887
+ const sendNotificationParams = { key: (0, firebase_1.firestoreDummyKey)(), throwErrorIfSent: false };
888
+ const sendNotificationInstance = await sendNotification(sendNotificationParams);
889
+ // iterate through all JobApplication items that need to be synced
890
+ while (true) {
891
+ const sendQueuedNotificationsResults = await sendQueuedNotifications();
892
+ sendQueuedNotificationsResults.results.forEach((x) => {
893
+ const result = x[1];
894
+ if (result.success) {
895
+ notificationsSucceeded += 1;
896
+ }
897
+ else if (result.createdBox || result.notificationBoxNeedsInitialization) {
898
+ notificationsDelayed = 1;
899
+ }
900
+ else {
901
+ notificationsFailed += 1;
902
+ }
903
+ if (result.deletedNotification) {
904
+ notificationsDeleted += 1;
905
+ }
906
+ if (result.createdBox) {
907
+ notificationBoxesCreated += 1;
908
+ }
909
+ sendEmailsResult = (0, firebase_1.mergeNotificationSendMessagesResult)(sendEmailsResult, result.sendEmailsResult);
910
+ sendTextsResult = (0, firebase_1.mergeNotificationSendMessagesResult)(sendTextsResult, result.sendTextsResult);
911
+ sendNotificationSummaryResult = (0, firebase_1.mergeNotificationSendMessagesResult)(sendNotificationSummaryResult, result.sendNotificationSummaryResult);
912
+ });
913
+ const found = sendQueuedNotificationsResults.results.length;
914
+ notificationsVisited += found;
915
+ if (!found) {
916
+ break;
917
+ }
918
+ }
919
+ async function sendQueuedNotifications() {
920
+ const query = notificationCollectionGroup.queryDocument((0, firebase_1.notificationsPastSendAtTimeQuery)());
921
+ const notificationDocuments = await query.getDocs();
922
+ const result = await (0, util_1.performAsyncTasks)(notificationDocuments, async (notificationDocument) => {
923
+ const result = await sendNotificationInstance(notificationDocument);
924
+ return result;
925
+ }, {
926
+ maxParallelTasks: 10
927
+ });
928
+ return result;
929
+ }
930
+ const result = {
931
+ notificationBoxesCreated,
932
+ notificationsDeleted,
933
+ notificationsVisited,
934
+ notificationsSucceeded,
935
+ notificationsDelayed,
936
+ notificationsFailed,
937
+ sendEmailsResult,
938
+ sendTextsResult,
939
+ sendNotificationSummaryResult
940
+ };
941
+ return result;
942
+ };
943
+ });
944
+ }
945
+ exports.sendQueuedNotificationsFactory = sendQueuedNotificationsFactory;
946
+ function cleanupSentNotificationsFactory(context) {
947
+ const { firestoreContext, firebaseServerActionTransformFunctionFactory, notificationCollectionGroup, notificationCollectionFactory, notificationBoxCollection, notificationWeekCollectionFactory } = context;
948
+ return firebaseServerActionTransformFunctionFactory(firebase_1.CleanupSentNotificationsParams, async () => {
949
+ return async () => {
950
+ let notificationBoxesUpdatesCount = 0;
951
+ let notificationsDeleted = 0;
952
+ let notificationWeeksCreated = 0;
953
+ let notificationWeeksUpdated = 0;
954
+ // iterate through all JobApplication items that need to be synced
955
+ while (true) {
956
+ const cleanupSentNotificationsResults = await cleanupSentNotifications();
957
+ cleanupSentNotificationsResults.results.forEach((x) => {
958
+ const { itemsDeleted, weeksCreated, weeksUpdated } = x[1];
959
+ notificationsDeleted += itemsDeleted;
960
+ notificationWeeksCreated += weeksCreated;
961
+ notificationWeeksUpdated += weeksUpdated;
962
+ });
963
+ const notificationBoxesUpdated = cleanupSentNotificationsResults.results.length;
964
+ notificationBoxesUpdatesCount += notificationBoxesUpdated;
965
+ if (!notificationBoxesUpdated) {
966
+ break;
967
+ }
968
+ }
969
+ async function cleanupSentNotifications() {
970
+ const query = notificationCollectionGroup.queryDocument((0, firebase_1.notificationsReadyForCleanupQuery)());
971
+ const notificationDocuments = await query.getDocs();
972
+ const notificationDocumentsGroupedByNotificationBox = Array.from((0, util_1.makeValuesGroupMap)(notificationDocuments, (x) => x.parent.id).values());
973
+ const result = await (0, util_1.performAsyncTasks)(notificationDocumentsGroupedByNotificationBox, async (notificationDocumentsInSameBox) => {
974
+ const allPairs = await (0, firebase_1.getDocumentSnapshotDataPairs)(notificationDocumentsInSameBox);
975
+ const allPairsWithDataAndMarkedDeleted = allPairs.filter((x) => x.data?.d);
976
+ const pairsGroupedByWeek = Array.from((0, util_1.makeValuesGroupMap)(allPairsWithDataAndMarkedDeleted, (x) => (0, date_1.yearWeekCode)(x.data.sat)).entries());
977
+ // batch incase there are a lot of new notifications to move to week
978
+ const pairsGroupedByWeekInBatches = pairsGroupedByWeek
979
+ .map((x) => {
980
+ const batches = (0, util_1.batch)(x[1], 40);
981
+ return batches.map((batch) => [x[0], batch]);
982
+ })
983
+ .flat();
984
+ const notificationBoxDocument = await notificationBoxCollection.documentAccessor().loadDocument(notificationDocumentsInSameBox[0].parent);
985
+ // create/update the NotificationWeek
986
+ const notificationWeekResults = await (0, util_1.performAsyncTasks)(pairsGroupedByWeekInBatches, async ([yearWeekCode, notificationDocumentsInSameWeek]) => {
987
+ return firestoreContext.runTransaction(async (transaction) => {
988
+ const notificationWeekDocument = notificationWeekCollectionFactory(notificationBoxDocument).documentAccessorForTransaction(transaction).loadDocumentForId(`${yearWeekCode}`);
989
+ const notificationDocumentsInTransaction = (0, firebase_1.loadDocumentsForDocumentReferencesFromValues)(notificationCollectionGroup.documentAccessorForTransaction(transaction), notificationDocumentsInSameWeek, (x) => x.snapshot.ref);
990
+ const notificationWeek = await notificationWeekDocument.snapshotData();
991
+ const newItems = (0, util_1.filterMaybeArrayValues)(notificationDocumentsInSameWeek.map((x) => {
992
+ const data = x.data;
993
+ const shouldSaveToNotificationWeek = (0, firebase_1.shouldSaveNotificationToNotificationWeek)(data);
994
+ return shouldSaveToNotificationWeek ? data.n : undefined;
995
+ }));
996
+ const n = [...(notificationWeek?.n ?? []), ...newItems];
997
+ if (!notificationWeek) {
998
+ // create
999
+ await notificationWeekDocument.create({
1000
+ w: yearWeekCode,
1001
+ n
1002
+ });
1003
+ }
1004
+ else {
1005
+ // update
1006
+ await notificationWeekDocument.update({
1007
+ n
1008
+ });
1009
+ }
1010
+ // delete the notification items
1011
+ await Promise.all(notificationDocumentsInTransaction.map((x) => x.accessor.delete()));
1012
+ return {
1013
+ created: !notificationWeek
1014
+ };
1015
+ });
1016
+ });
1017
+ let weeksCreated = 0;
1018
+ let weeksUpdated = 0;
1019
+ notificationWeekResults.results.forEach((x) => {
1020
+ if (x[1].created) {
1021
+ weeksCreated += 1;
1022
+ }
1023
+ else {
1024
+ weeksUpdated += 1;
1025
+ }
1026
+ });
1027
+ const result = {
1028
+ weeksCreated,
1029
+ weeksUpdated,
1030
+ itemsDeleted: allPairsWithDataAndMarkedDeleted.length
1031
+ };
1032
+ return result;
1033
+ }, {
1034
+ maxParallelTasks: 10
1035
+ });
1036
+ return result;
1037
+ }
1038
+ const result = {
1039
+ notificationBoxesUpdatesCount,
1040
+ notificationsDeleted,
1041
+ notificationWeeksCreated,
1042
+ notificationWeeksUpdated
1043
+ };
1044
+ return result;
1045
+ };
1046
+ });
1047
+ }
1048
+ exports.cleanupSentNotificationsFactory = cleanupSentNotificationsFactory;
1049
+ //# sourceMappingURL=notification.action.server.js.map