@blackcode_sa/metaestetics-api 1.6.14 → 1.6.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/index.d.mts +343 -2
- package/dist/admin/index.d.ts +343 -2
- package/dist/admin/index.js +390 -217
- package/dist/admin/index.mjs +382 -217
- package/package.json +1 -1
- package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +2 -2
- package/src/admin/booking/booking.admin.ts +8 -4
- package/src/admin/calendar/calendar.admin.service.ts +8 -11
- package/src/admin/index.ts +21 -0
- package/src/admin/notifications/notifications.admin.ts +152 -58
- package/src/admin/requirements/patient-requirements.admin.service.ts +1 -1
- package/src/utils/TimestampUtils.ts +122 -17
package/package.json
CHANGED
|
@@ -565,7 +565,7 @@ export class AppointmentAggregationService {
|
|
|
565
565
|
status: PatientInstructionStatus.PENDING_NOTIFICATION,
|
|
566
566
|
originalNotifyAtValue: notifyAtValue,
|
|
567
567
|
originalTimeframeUnit: template.timeframe.unit,
|
|
568
|
-
updatedAt: admin.firestore.
|
|
568
|
+
updatedAt: admin.firestore.Timestamp.now() as any, // Use current server timestamp
|
|
569
569
|
notificationId: undefined,
|
|
570
570
|
actionTakenAt: undefined,
|
|
571
571
|
};
|
|
@@ -707,7 +707,7 @@ export class AppointmentAggregationService {
|
|
|
707
707
|
status: PatientInstructionStatus.PENDING_NOTIFICATION,
|
|
708
708
|
originalNotifyAtValue: notifyAtValue,
|
|
709
709
|
originalTimeframeUnit: template.timeframe.unit,
|
|
710
|
-
updatedAt: admin.firestore.
|
|
710
|
+
updatedAt: admin.firestore.Timestamp.now() as any, // Use current server timestamp
|
|
711
711
|
notificationId: undefined,
|
|
712
712
|
actionTakenAt: undefined,
|
|
713
713
|
} as PatientRequirementInstruction;
|
|
@@ -205,9 +205,9 @@ export class BookingAdmin {
|
|
|
205
205
|
*/
|
|
206
206
|
private adminTimestampToClientTimestamp(
|
|
207
207
|
timestamp: admin.firestore.Timestamp
|
|
208
|
-
):
|
|
208
|
+
): FirebaseClientTimestamp {
|
|
209
209
|
// Use TimestampUtils instead of custom implementation
|
|
210
|
-
return TimestampUtils.adminToClient(timestamp);
|
|
210
|
+
return TimestampUtils.adminToClient(timestamp) as FirebaseClientTimestamp;
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
/**
|
|
@@ -217,8 +217,12 @@ export class BookingAdmin {
|
|
|
217
217
|
return events.map((event) => ({
|
|
218
218
|
...event,
|
|
219
219
|
eventTime: {
|
|
220
|
-
start: TimestampUtils.adminToClient(
|
|
221
|
-
|
|
220
|
+
start: TimestampUtils.adminToClient(
|
|
221
|
+
event.eventTime.start
|
|
222
|
+
) as FirebaseClientTimestamp,
|
|
223
|
+
end: TimestampUtils.adminToClient(
|
|
224
|
+
event.eventTime.end
|
|
225
|
+
) as FirebaseClientTimestamp,
|
|
222
226
|
},
|
|
223
227
|
// Convert any other timestamps in the event if needed
|
|
224
228
|
}));
|
|
@@ -10,6 +10,7 @@ import { Logger } from "../logger";
|
|
|
10
10
|
import { PRACTITIONERS_COLLECTION } from "../../types/practitioner";
|
|
11
11
|
import { PATIENTS_COLLECTION } from "../../types/patient";
|
|
12
12
|
import { CLINICS_COLLECTION } from "../../types/clinic";
|
|
13
|
+
import { TimestampUtils } from "../../utils/TimestampUtils";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* @class CalendarAdminService
|
|
@@ -103,18 +104,14 @@ export class CalendarAdminService {
|
|
|
103
104
|
const batch = this.db.batch();
|
|
104
105
|
const serverTimestamp = admin.firestore.FieldValue.serverTimestamp();
|
|
105
106
|
|
|
106
|
-
// Convert
|
|
107
|
-
// or ensure the CalendarEventTime type is directly compatible.
|
|
108
|
-
// Firestore admin SDK usually handles { seconds: X, nanoseconds: Y } objects correctly.
|
|
107
|
+
// Convert client timestamps to admin timestamps since we're in server mode
|
|
109
108
|
const firestoreCompatibleEventTime = {
|
|
110
|
-
start:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
nanoseconds: newEventTime.end.nanoseconds,
|
|
117
|
-
},
|
|
109
|
+
start:
|
|
110
|
+
TimestampUtils.clientToAdmin(newEventTime.start) ||
|
|
111
|
+
admin.firestore.Timestamp.now(),
|
|
112
|
+
end:
|
|
113
|
+
TimestampUtils.clientToAdmin(newEventTime.end) ||
|
|
114
|
+
admin.firestore.Timestamp.now(),
|
|
118
115
|
};
|
|
119
116
|
|
|
120
117
|
// TODO: Confirm paths as in updateAppointmentCalendarEventsStatus
|
package/src/admin/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { NotificationsAdmin } from "./notifications/notifications.admin";
|
|
2
|
+
import * as admin from "firebase-admin";
|
|
3
|
+
import { TimestampUtils } from "../utils/TimestampUtils";
|
|
2
4
|
|
|
3
5
|
import { UserRole } from "../types";
|
|
4
6
|
// Import types needed by admin consumers (like Cloud Functions)
|
|
@@ -111,3 +113,22 @@ console.log("[Admin Module] Initialized and services exported.");
|
|
|
111
113
|
// if they have dependencies (like Firestore db) that need to be injected.
|
|
112
114
|
// The initialization pattern might differ depending on how this module is consumed
|
|
113
115
|
// (e.g., within a larger NestJS app vs. standalone Cloud Functions).
|
|
116
|
+
|
|
117
|
+
// Initialize TimestampUtils for server-side use
|
|
118
|
+
TimestampUtils.enableServerMode();
|
|
119
|
+
|
|
120
|
+
// Export all admin services that are needed
|
|
121
|
+
export * from "./booking/booking.admin";
|
|
122
|
+
export * from "./booking/booking.calculator";
|
|
123
|
+
export * from "./booking/booking.types";
|
|
124
|
+
export * from "./calendar/calendar.admin.service";
|
|
125
|
+
export * from "./documentation-templates/document-manager.admin";
|
|
126
|
+
export * from "./logger";
|
|
127
|
+
export * from "./mailing/appointment/appointment.mailing.service";
|
|
128
|
+
export * from "./notifications/notifications.admin";
|
|
129
|
+
export * from "./requirements/patient-requirements.admin.service";
|
|
130
|
+
export * from "./aggregation/appointment/appointment.aggregation.service";
|
|
131
|
+
|
|
132
|
+
// Re-export types that Cloud Functions might need direct access to
|
|
133
|
+
export * from "../types/appointment";
|
|
134
|
+
// Add other types as needed
|
|
@@ -9,6 +9,7 @@ import { Appointment, PaymentStatus } from "../../types/appointment";
|
|
|
9
9
|
import { UserRole } from "../../types";
|
|
10
10
|
import { Timestamp as FirebaseClientTimestamp } from "@firebase/firestore";
|
|
11
11
|
import { TimestampUtils } from "../../utils/TimestampUtils";
|
|
12
|
+
import { Logger } from "../logger";
|
|
12
13
|
|
|
13
14
|
export class NotificationsAdmin {
|
|
14
15
|
private expo: Expo;
|
|
@@ -46,19 +47,31 @@ export class NotificationsAdmin {
|
|
|
46
47
|
* Priprema Expo poruku za slanje
|
|
47
48
|
*/
|
|
48
49
|
private prepareExpoMessage(notification: Notification): ExpoPushMessage[] {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
50
|
+
const validTokens = notification.notificationTokens.filter((token) =>
|
|
51
|
+
Expo.isExpoPushToken(token)
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
Logger.info(
|
|
55
|
+
`[NotificationsAdmin] Preparing Expo messages for notification ${notification.id}`,
|
|
56
|
+
{
|
|
57
|
+
totalTokens: notification.notificationTokens.length,
|
|
58
|
+
validTokens: validTokens.length,
|
|
59
|
+
invalidTokensCount:
|
|
60
|
+
notification.notificationTokens.length - validTokens.length,
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
return validTokens.map((token) => ({
|
|
65
|
+
to: token,
|
|
66
|
+
sound: "default",
|
|
67
|
+
title: notification.title,
|
|
68
|
+
body: notification.body,
|
|
69
|
+
data: {
|
|
70
|
+
notificationId: notification.id,
|
|
71
|
+
notificationType: notification.notificationType,
|
|
72
|
+
userId: notification.userId,
|
|
73
|
+
},
|
|
74
|
+
}));
|
|
62
75
|
}
|
|
63
76
|
|
|
64
77
|
/**
|
|
@@ -93,27 +106,58 @@ export class NotificationsAdmin {
|
|
|
93
106
|
*/
|
|
94
107
|
async sendPushNotification(notification: Notification): Promise<boolean> {
|
|
95
108
|
try {
|
|
109
|
+
Logger.info(
|
|
110
|
+
`[NotificationsAdmin] Processing notification ${notification.id} for sending`,
|
|
111
|
+
{
|
|
112
|
+
userId: notification.userId,
|
|
113
|
+
tokenCount: notification.notificationTokens?.length || 0,
|
|
114
|
+
type: notification.notificationType,
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
|
|
96
118
|
const messages = this.prepareExpoMessage(notification);
|
|
97
119
|
|
|
98
120
|
if (messages.length === 0) {
|
|
121
|
+
const errorMsg = "No valid notification tokens found";
|
|
122
|
+
Logger.error(
|
|
123
|
+
`[NotificationsAdmin] ${errorMsg} for notification ${notification.id}`
|
|
124
|
+
);
|
|
99
125
|
await this.updateNotificationStatus(
|
|
100
126
|
notification.id!,
|
|
101
127
|
NotificationStatus.FAILED,
|
|
102
|
-
|
|
128
|
+
errorMsg
|
|
103
129
|
);
|
|
104
130
|
return false;
|
|
105
131
|
}
|
|
106
132
|
|
|
107
133
|
const chunks = this.expo.chunkPushNotifications(messages);
|
|
134
|
+
Logger.info(
|
|
135
|
+
`[NotificationsAdmin] Sending ${messages.length} messages in ${chunks.length} chunks for notification ${notification.id}`
|
|
136
|
+
);
|
|
137
|
+
|
|
108
138
|
const tickets: ExpoPushTicket[][] = [];
|
|
109
139
|
|
|
110
140
|
// Šaljemo sve chunks
|
|
111
|
-
for (
|
|
141
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
142
|
+
const chunk = chunks[i];
|
|
112
143
|
try {
|
|
144
|
+
Logger.info(
|
|
145
|
+
`[NotificationsAdmin] Sending chunk ${i + 1}/${
|
|
146
|
+
chunks.length
|
|
147
|
+
} with ${chunk.length} messages`
|
|
148
|
+
);
|
|
113
149
|
const ticketChunk = await this.expo.sendPushNotificationsAsync(chunk);
|
|
150
|
+
Logger.info(
|
|
151
|
+
`[NotificationsAdmin] Received ${
|
|
152
|
+
ticketChunk.length
|
|
153
|
+
} tickets for chunk ${i + 1}`
|
|
154
|
+
);
|
|
114
155
|
tickets.push(ticketChunk);
|
|
115
156
|
} catch (error) {
|
|
116
|
-
|
|
157
|
+
Logger.error(
|
|
158
|
+
`[NotificationsAdmin] Chunk ${i + 1} sending error:`,
|
|
159
|
+
error
|
|
160
|
+
);
|
|
117
161
|
throw error;
|
|
118
162
|
}
|
|
119
163
|
}
|
|
@@ -121,25 +165,62 @@ export class NotificationsAdmin {
|
|
|
121
165
|
// Proveravamo rezultate
|
|
122
166
|
let hasErrors = false;
|
|
123
167
|
const errors: string[] = [];
|
|
168
|
+
const ticketsFlat = tickets.flat();
|
|
124
169
|
|
|
125
|
-
|
|
170
|
+
// Log detailed ticket information
|
|
171
|
+
const ticketResults = {
|
|
172
|
+
total: ticketsFlat.length,
|
|
173
|
+
success: 0,
|
|
174
|
+
error: 0,
|
|
175
|
+
errorDetails: {} as Record<string, number>,
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
ticketsFlat.forEach((ticket, index) => {
|
|
126
179
|
if (ticket.status === "error") {
|
|
127
180
|
hasErrors = true;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
181
|
+
ticketResults.error++;
|
|
182
|
+
|
|
183
|
+
// Count each error type
|
|
184
|
+
const errorMessage = ticket.message || "Unknown error";
|
|
185
|
+
ticketResults.errorDetails[errorMessage] =
|
|
186
|
+
(ticketResults.errorDetails[errorMessage] || 0) + 1;
|
|
187
|
+
|
|
188
|
+
const tokenInfo =
|
|
189
|
+
index < notification.notificationTokens.length
|
|
190
|
+
? `Token ${notification.notificationTokens[index]}`
|
|
191
|
+
: `Token at index ${index}`;
|
|
192
|
+
|
|
193
|
+
errors.push(`${tokenInfo}: ${errorMessage}`);
|
|
194
|
+
} else {
|
|
195
|
+
ticketResults.success++;
|
|
131
196
|
}
|
|
132
197
|
});
|
|
133
198
|
|
|
199
|
+
Logger.info(
|
|
200
|
+
`[NotificationsAdmin] Ticket results for notification ${notification.id}`,
|
|
201
|
+
ticketResults
|
|
202
|
+
);
|
|
203
|
+
|
|
134
204
|
if (hasErrors) {
|
|
205
|
+
const errorSummary = errors.join("; ");
|
|
206
|
+
Logger.warn(
|
|
207
|
+
`[NotificationsAdmin] Partial success or errors in notification ${notification.id}`,
|
|
208
|
+
{ errorCount: errors.length, errorSummary }
|
|
209
|
+
);
|
|
210
|
+
|
|
135
211
|
await this.updateNotificationStatus(
|
|
136
212
|
notification.id!,
|
|
137
|
-
|
|
138
|
-
|
|
213
|
+
ticketResults.success > 0
|
|
214
|
+
? NotificationStatus.PARTIAL_SUCCESS
|
|
215
|
+
: NotificationStatus.FAILED,
|
|
216
|
+
errorSummary
|
|
139
217
|
);
|
|
140
|
-
return
|
|
218
|
+
return ticketResults.success > 0;
|
|
141
219
|
}
|
|
142
220
|
|
|
221
|
+
Logger.info(
|
|
222
|
+
`[NotificationsAdmin] Successfully sent notification ${notification.id} to all recipients`
|
|
223
|
+
);
|
|
143
224
|
await this.updateNotificationStatus(
|
|
144
225
|
notification.id!,
|
|
145
226
|
NotificationStatus.SENT
|
|
@@ -148,7 +229,10 @@ export class NotificationsAdmin {
|
|
|
148
229
|
} catch (error) {
|
|
149
230
|
const errorMessage =
|
|
150
231
|
error instanceof Error ? error.message : "Unknown error";
|
|
151
|
-
|
|
232
|
+
Logger.error(
|
|
233
|
+
`[NotificationsAdmin] Critical error sending notification ${notification.id}:`,
|
|
234
|
+
error
|
|
235
|
+
);
|
|
152
236
|
await this.updateNotificationStatus(
|
|
153
237
|
notification.id!,
|
|
154
238
|
NotificationStatus.FAILED,
|
|
@@ -164,6 +248,10 @@ export class NotificationsAdmin {
|
|
|
164
248
|
async processPendingNotifications(batchSize: number = 100): Promise<void> {
|
|
165
249
|
const now = admin.firestore.Timestamp.now();
|
|
166
250
|
|
|
251
|
+
Logger.info(
|
|
252
|
+
`[NotificationsAdmin] Starting to process pending notifications with batch size ${batchSize}`
|
|
253
|
+
);
|
|
254
|
+
|
|
167
255
|
const pendingNotifications = await this.db
|
|
168
256
|
.collection("notifications")
|
|
169
257
|
.where("status", "==", NotificationStatus.PENDING)
|
|
@@ -171,12 +259,27 @@ export class NotificationsAdmin {
|
|
|
171
259
|
.limit(batchSize)
|
|
172
260
|
.get();
|
|
173
261
|
|
|
262
|
+
Logger.info(
|
|
263
|
+
`[NotificationsAdmin] Found ${pendingNotifications.size} pending notifications to process`
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
if (pendingNotifications.empty) {
|
|
267
|
+
Logger.info(
|
|
268
|
+
"[NotificationsAdmin] No pending notifications found to process"
|
|
269
|
+
);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
174
273
|
const results = await Promise.allSettled(
|
|
175
274
|
pendingNotifications.docs.map(async (doc) => {
|
|
176
275
|
const notification = {
|
|
177
276
|
id: doc.id,
|
|
178
277
|
...doc.data(),
|
|
179
278
|
} as Notification;
|
|
279
|
+
|
|
280
|
+
Logger.info(
|
|
281
|
+
`[NotificationsAdmin] Processing notification ${notification.id} of type ${notification.notificationType}`
|
|
282
|
+
);
|
|
180
283
|
return this.sendPushNotification(notification);
|
|
181
284
|
})
|
|
182
285
|
);
|
|
@@ -189,8 +292,8 @@ export class NotificationsAdmin {
|
|
|
189
292
|
(r) => r.status === "rejected" || (r.status === "fulfilled" && !r.value)
|
|
190
293
|
).length;
|
|
191
294
|
|
|
192
|
-
|
|
193
|
-
`Processed ${results.length} notifications: ${successful} successful, ${failed} failed`
|
|
295
|
+
Logger.info(
|
|
296
|
+
`[NotificationsAdmin] Processed ${results.length} notifications: ${successful} successful, ${failed} failed`
|
|
194
297
|
);
|
|
195
298
|
}
|
|
196
299
|
|
|
@@ -204,6 +307,11 @@ export class NotificationsAdmin {
|
|
|
204
307
|
const cutoffDate = new Date();
|
|
205
308
|
cutoffDate.setDate(cutoffDate.getDate() - daysOld);
|
|
206
309
|
|
|
310
|
+
Logger.info(
|
|
311
|
+
`[NotificationsAdmin] Starting cleanup of notifications older than ${daysOld} days`
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
let totalDeleted = 0;
|
|
207
315
|
while (true) {
|
|
208
316
|
const oldNotifications = await this.db
|
|
209
317
|
.collection("notifications")
|
|
@@ -216,6 +324,9 @@ export class NotificationsAdmin {
|
|
|
216
324
|
.get();
|
|
217
325
|
|
|
218
326
|
if (oldNotifications.empty) {
|
|
327
|
+
Logger.info(
|
|
328
|
+
`[NotificationsAdmin] No more old notifications to delete. Total deleted: ${totalDeleted}`
|
|
329
|
+
);
|
|
219
330
|
break;
|
|
220
331
|
}
|
|
221
332
|
|
|
@@ -225,8 +336,9 @@ export class NotificationsAdmin {
|
|
|
225
336
|
});
|
|
226
337
|
|
|
227
338
|
await batch.commit();
|
|
228
|
-
|
|
229
|
-
|
|
339
|
+
totalDeleted += oldNotifications.size;
|
|
340
|
+
Logger.info(
|
|
341
|
+
`[NotificationsAdmin] Deleted batch of ${oldNotifications.size} old notifications. Running total: ${totalDeleted}`
|
|
230
342
|
);
|
|
231
343
|
}
|
|
232
344
|
}
|
|
@@ -277,10 +389,7 @@ export class NotificationsAdmin {
|
|
|
277
389
|
.toLocaleDateString()} is confirmed.`;
|
|
278
390
|
}
|
|
279
391
|
|
|
280
|
-
const
|
|
281
|
-
const clientCompatibleNotificationTime = TimestampUtils.adminToClient(
|
|
282
|
-
adminTsNow
|
|
283
|
-
) as FirebaseClientTimestamp;
|
|
392
|
+
const notificationTimestampForDb = admin.firestore.Timestamp.now();
|
|
284
393
|
|
|
285
394
|
const notificationData: Omit<
|
|
286
395
|
Notification,
|
|
@@ -289,7 +398,7 @@ export class NotificationsAdmin {
|
|
|
289
398
|
userId: recipientUserId,
|
|
290
399
|
userRole: recipientRole,
|
|
291
400
|
notificationType: NotificationType.APPOINTMENT_STATUS_CHANGE,
|
|
292
|
-
notificationTime:
|
|
401
|
+
notificationTime: notificationTimestampForDb as any,
|
|
293
402
|
notificationTokens: recipientExpoTokens,
|
|
294
403
|
title,
|
|
295
404
|
body,
|
|
@@ -353,10 +462,7 @@ export class NotificationsAdmin {
|
|
|
353
462
|
}
|
|
354
463
|
}
|
|
355
464
|
|
|
356
|
-
const
|
|
357
|
-
const clientCompatibleNotificationTime = TimestampUtils.adminToClient(
|
|
358
|
-
adminTsNow
|
|
359
|
-
) as FirebaseClientTimestamp;
|
|
465
|
+
const notificationTimestampForDb = admin.firestore.Timestamp.now();
|
|
360
466
|
|
|
361
467
|
const notificationData: Omit<
|
|
362
468
|
Notification,
|
|
@@ -365,7 +471,7 @@ export class NotificationsAdmin {
|
|
|
365
471
|
userId: recipientUserId,
|
|
366
472
|
userRole: recipientRole,
|
|
367
473
|
notificationType: NotificationType.APPOINTMENT_CANCELLED,
|
|
368
|
-
notificationTime:
|
|
474
|
+
notificationTime: notificationTimestampForDb as any,
|
|
369
475
|
notificationTokens: recipientExpoTokens,
|
|
370
476
|
title,
|
|
371
477
|
body,
|
|
@@ -404,10 +510,7 @@ export class NotificationsAdmin {
|
|
|
404
510
|
const title = "Appointment Reschedule Proposed";
|
|
405
511
|
const body = `Action Required: A new time has been proposed for your appointment for ${appointment.procedureInfo.name}. Please review in the app.`;
|
|
406
512
|
|
|
407
|
-
const
|
|
408
|
-
const clientCompatibleNotificationTime = TimestampUtils.adminToClient(
|
|
409
|
-
adminTsNow
|
|
410
|
-
) as FirebaseClientTimestamp;
|
|
513
|
+
const notificationTimestampForDb = admin.firestore.Timestamp.now();
|
|
411
514
|
|
|
412
515
|
const notificationData: Omit<
|
|
413
516
|
Notification,
|
|
@@ -416,7 +519,7 @@ export class NotificationsAdmin {
|
|
|
416
519
|
userId: patientUserId,
|
|
417
520
|
userRole: UserRole.PATIENT,
|
|
418
521
|
notificationType: NotificationType.APPOINTMENT_RESCHEDULED_PROPOSAL,
|
|
419
|
-
notificationTime:
|
|
522
|
+
notificationTime: notificationTimestampForDb as any,
|
|
420
523
|
notificationTokens: patientExpoTokens,
|
|
421
524
|
title,
|
|
422
525
|
body,
|
|
@@ -458,10 +561,7 @@ export class NotificationsAdmin {
|
|
|
458
561
|
.toDate()
|
|
459
562
|
.toLocaleDateString()} is now ${appointment.paymentStatus}.`;
|
|
460
563
|
|
|
461
|
-
const
|
|
462
|
-
const clientCompatibleNotificationTime = TimestampUtils.adminToClient(
|
|
463
|
-
adminTsNow
|
|
464
|
-
) as FirebaseClientTimestamp;
|
|
564
|
+
const notificationTimestampForDb = admin.firestore.Timestamp.now();
|
|
465
565
|
|
|
466
566
|
const notificationType =
|
|
467
567
|
appointment.paymentStatus === PaymentStatus.PAID
|
|
@@ -475,7 +575,7 @@ export class NotificationsAdmin {
|
|
|
475
575
|
userId: patientUserId,
|
|
476
576
|
userRole: UserRole.PATIENT,
|
|
477
577
|
notificationType: notificationType,
|
|
478
|
-
notificationTime:
|
|
578
|
+
notificationTime: notificationTimestampForDb as any,
|
|
479
579
|
notificationTokens: patientExpoTokens,
|
|
480
580
|
title,
|
|
481
581
|
body,
|
|
@@ -514,10 +614,7 @@ export class NotificationsAdmin {
|
|
|
514
614
|
const title = "Leave a Review";
|
|
515
615
|
const body = `How was your recent appointment for ${appointment.procedureInfo.name}? We'd love to hear your feedback!`;
|
|
516
616
|
|
|
517
|
-
const
|
|
518
|
-
const clientCompatibleNotificationTime = TimestampUtils.adminToClient(
|
|
519
|
-
adminTsNow
|
|
520
|
-
) as FirebaseClientTimestamp;
|
|
617
|
+
const notificationTimestampForDb = admin.firestore.Timestamp.now();
|
|
521
618
|
|
|
522
619
|
const notificationData: Omit<
|
|
523
620
|
Notification,
|
|
@@ -526,7 +623,7 @@ export class NotificationsAdmin {
|
|
|
526
623
|
userId: patientUserId,
|
|
527
624
|
userRole: UserRole.PATIENT,
|
|
528
625
|
notificationType: NotificationType.REVIEW_REQUEST,
|
|
529
|
-
notificationTime:
|
|
626
|
+
notificationTime: notificationTimestampForDb as any,
|
|
530
627
|
notificationTokens: patientExpoTokens,
|
|
531
628
|
title,
|
|
532
629
|
body,
|
|
@@ -576,10 +673,7 @@ export class NotificationsAdmin {
|
|
|
576
673
|
.toDate()
|
|
577
674
|
.toLocaleDateString()}.`;
|
|
578
675
|
|
|
579
|
-
const
|
|
580
|
-
const clientCompatibleNotificationTime = TimestampUtils.adminToClient(
|
|
581
|
-
adminTsNow
|
|
582
|
-
) as FirebaseClientTimestamp;
|
|
676
|
+
const notificationTimestampForDb = admin.firestore.Timestamp.now();
|
|
583
677
|
|
|
584
678
|
const tempNotificationType = NotificationType.GENERAL_MESSAGE;
|
|
585
679
|
|
|
@@ -590,7 +684,7 @@ export class NotificationsAdmin {
|
|
|
590
684
|
userId: recipientUserId,
|
|
591
685
|
userRole: UserRole.PRACTITIONER,
|
|
592
686
|
notificationType: tempNotificationType,
|
|
593
|
-
notificationTime:
|
|
687
|
+
notificationTime: notificationTimestampForDb as any,
|
|
594
688
|
notificationTokens: recipientExpoTokens,
|
|
595
689
|
title,
|
|
596
690
|
body,
|
|
@@ -176,7 +176,7 @@ export class PatientRequirementsAdminService {
|
|
|
176
176
|
userId: patientId,
|
|
177
177
|
userRole: UserRole.PATIENT,
|
|
178
178
|
notificationType: NotificationType.REQUIREMENT_INSTRUCTION_DUE,
|
|
179
|
-
notificationTime: currentInstruction.dueTime, //
|
|
179
|
+
notificationTime: currentInstruction.dueTime as any, // dueTime should be an admin.firestore.Timestamp already
|
|
180
180
|
notificationTokens: patientExpoTokens,
|
|
181
181
|
title: `Reminder: ${instance.requirementName}`,
|
|
182
182
|
body: currentInstruction.instructionText,
|