@blackcode_sa/metaestetics-api 1.6.14 → 1.6.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/index.d.mts +343 -2
- package/dist/admin/index.d.ts +343 -2
- package/dist/admin/index.js +246 -157
- package/dist/admin/index.mjs +238 -157
- 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 +12 -30
- package/src/admin/requirements/patient-requirements.admin.service.ts +1 -1
- package/src/utils/TimestampUtils.ts +122 -17
package/dist/admin/index.mjs
CHANGED
|
@@ -37,7 +37,7 @@ var NotificationStatus = /* @__PURE__ */ ((NotificationStatus2) => {
|
|
|
37
37
|
})(NotificationStatus || {});
|
|
38
38
|
|
|
39
39
|
// src/admin/notifications/notifications.admin.ts
|
|
40
|
-
import * as
|
|
40
|
+
import * as admin from "firebase-admin";
|
|
41
41
|
import { Expo } from "expo-server-sdk";
|
|
42
42
|
|
|
43
43
|
// src/types/appointment/index.ts
|
|
@@ -54,6 +54,21 @@ var AppointmentStatus = /* @__PURE__ */ ((AppointmentStatus2) => {
|
|
|
54
54
|
AppointmentStatus2["RESCHEDULED_BY_CLINIC"] = "rescheduled_by_clinic";
|
|
55
55
|
return AppointmentStatus2;
|
|
56
56
|
})(AppointmentStatus || {});
|
|
57
|
+
var PaymentStatus = /* @__PURE__ */ ((PaymentStatus2) => {
|
|
58
|
+
PaymentStatus2["UNPAID"] = "unpaid";
|
|
59
|
+
PaymentStatus2["PAID"] = "paid";
|
|
60
|
+
PaymentStatus2["PARTIALLY_PAID"] = "partially_paid";
|
|
61
|
+
PaymentStatus2["REFUNDED"] = "refunded";
|
|
62
|
+
PaymentStatus2["NOT_APPLICABLE"] = "not_applicable";
|
|
63
|
+
return PaymentStatus2;
|
|
64
|
+
})(PaymentStatus || {});
|
|
65
|
+
var MediaType = /* @__PURE__ */ ((MediaType2) => {
|
|
66
|
+
MediaType2["BEFORE_PHOTO"] = "before_photo";
|
|
67
|
+
MediaType2["AFTER_PHOTO"] = "after_photo";
|
|
68
|
+
MediaType2["CONSENT_SCAN"] = "consent_scan";
|
|
69
|
+
MediaType2["OTHER_DOCUMENT"] = "other_document";
|
|
70
|
+
return MediaType2;
|
|
71
|
+
})(MediaType || {});
|
|
57
72
|
var APPOINTMENTS_COLLECTION = "appointments";
|
|
58
73
|
|
|
59
74
|
// src/types/documentation-templates/index.ts
|
|
@@ -72,117 +87,11 @@ var UserRole = /* @__PURE__ */ ((UserRole2) => {
|
|
|
72
87
|
return UserRole2;
|
|
73
88
|
})(UserRole || {});
|
|
74
89
|
|
|
75
|
-
// src/utils/TimestampUtils.ts
|
|
76
|
-
import * as admin from "firebase-admin";
|
|
77
|
-
import { Timestamp as ClientTimestamp } from "firebase/firestore";
|
|
78
|
-
var TimestampUtils = class {
|
|
79
|
-
/**
|
|
80
|
-
* Converts an admin Firestore Timestamp to a client Firestore Timestamp
|
|
81
|
-
* @param adminTimestamp - Admin SDK Timestamp (from firebase-admin)
|
|
82
|
-
* @returns A client SDK Timestamp (from firebase/firestore) with same seconds/nanoseconds or null if input is null
|
|
83
|
-
*/
|
|
84
|
-
static adminToClient(adminTimestamp) {
|
|
85
|
-
if (!adminTimestamp) return null;
|
|
86
|
-
return new ClientTimestamp(
|
|
87
|
-
adminTimestamp.seconds,
|
|
88
|
-
adminTimestamp.nanoseconds
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Converts a client Firestore Timestamp to an admin Firestore Timestamp
|
|
93
|
-
* @param clientTimestamp - Client SDK Timestamp (from firebase/firestore)
|
|
94
|
-
* @returns An admin SDK Timestamp (from firebase-admin) with same seconds/nanoseconds or null if input is null
|
|
95
|
-
*/
|
|
96
|
-
static clientToAdmin(clientTimestamp) {
|
|
97
|
-
if (!clientTimestamp) return null;
|
|
98
|
-
return new admin.firestore.Timestamp(
|
|
99
|
-
clientTimestamp.seconds,
|
|
100
|
-
clientTimestamp.nanoseconds
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Creates a client-compatible timestamp for the current time
|
|
105
|
-
* @returns A client SDK Timestamp (from firebase/firestore) for the current time
|
|
106
|
-
*/
|
|
107
|
-
static nowAsClient() {
|
|
108
|
-
const now = admin.firestore.Timestamp.now();
|
|
109
|
-
return this.adminToClient(now);
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Converts a Date object to a client Firestore Timestamp
|
|
113
|
-
* @param date - JavaScript Date object
|
|
114
|
-
* @returns A client SDK Timestamp (from firebase/firestore) or null if input is null
|
|
115
|
-
*/
|
|
116
|
-
static dateToClientTimestamp(date) {
|
|
117
|
-
if (!date) return null;
|
|
118
|
-
return ClientTimestamp.fromDate(date);
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Converts a Date object to an admin Firestore Timestamp
|
|
122
|
-
* @param date - JavaScript Date object
|
|
123
|
-
* @returns An admin SDK Timestamp (from firebase-admin) or null if input is null
|
|
124
|
-
*/
|
|
125
|
-
static dateToAdminTimestamp(date) {
|
|
126
|
-
if (!date) return null;
|
|
127
|
-
return admin.firestore.Timestamp.fromDate(date);
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Deeply converts all admin Firestore Timestamps in an object to client Firestore Timestamps
|
|
131
|
-
* @param obj - Object that may contain admin Firestore Timestamps
|
|
132
|
-
* @returns The same object with all admin Timestamps converted to client Timestamps
|
|
133
|
-
*/
|
|
134
|
-
static convertObjectTimestampsAdminToClient(obj) {
|
|
135
|
-
if (!obj || typeof obj !== "object") {
|
|
136
|
-
return obj;
|
|
137
|
-
}
|
|
138
|
-
if (obj instanceof admin.firestore.Timestamp) {
|
|
139
|
-
return this.adminToClient(obj);
|
|
140
|
-
}
|
|
141
|
-
if (Array.isArray(obj)) {
|
|
142
|
-
return obj.map(
|
|
143
|
-
(item) => this.convertObjectTimestampsAdminToClient(item)
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
const result = { ...obj };
|
|
147
|
-
for (const key in result) {
|
|
148
|
-
if (Object.prototype.hasOwnProperty.call(result, key)) {
|
|
149
|
-
result[key] = this.convertObjectTimestampsAdminToClient(result[key]);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return result;
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Deeply converts all client Firestore Timestamps in an object to admin Firestore Timestamps
|
|
156
|
-
* @param obj - Object that may contain client Firestore Timestamps
|
|
157
|
-
* @returns The same object with all client Timestamps converted to admin Timestamps
|
|
158
|
-
*/
|
|
159
|
-
static convertObjectTimestampsClientToAdmin(obj) {
|
|
160
|
-
if (!obj || typeof obj !== "object") {
|
|
161
|
-
return obj;
|
|
162
|
-
}
|
|
163
|
-
if (obj instanceof ClientTimestamp) {
|
|
164
|
-
return this.clientToAdmin(obj);
|
|
165
|
-
}
|
|
166
|
-
if (Array.isArray(obj)) {
|
|
167
|
-
return obj.map(
|
|
168
|
-
(item) => this.convertObjectTimestampsClientToAdmin(item)
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
const result = { ...obj };
|
|
172
|
-
for (const key in result) {
|
|
173
|
-
if (Object.prototype.hasOwnProperty.call(result, key)) {
|
|
174
|
-
result[key] = this.convertObjectTimestampsClientToAdmin(result[key]);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return result;
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
|
|
181
90
|
// src/admin/notifications/notifications.admin.ts
|
|
182
91
|
var NotificationsAdmin = class {
|
|
183
92
|
constructor(firestore12) {
|
|
184
93
|
this.expo = new Expo();
|
|
185
|
-
this.db = firestore12 ||
|
|
94
|
+
this.db = firestore12 || admin.firestore();
|
|
186
95
|
}
|
|
187
96
|
/**
|
|
188
97
|
* Dohvata notifikaciju po ID-u
|
|
@@ -198,8 +107,8 @@ var NotificationsAdmin = class {
|
|
|
198
107
|
const docRef = await this.db.collection("notifications").add({
|
|
199
108
|
...notification,
|
|
200
109
|
status: "pending" /* PENDING */,
|
|
201
|
-
createdAt:
|
|
202
|
-
updatedAt:
|
|
110
|
+
createdAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
111
|
+
updatedAt: admin.firestore.FieldValue.serverTimestamp()
|
|
203
112
|
});
|
|
204
113
|
return docRef.id;
|
|
205
114
|
}
|
|
@@ -225,10 +134,10 @@ var NotificationsAdmin = class {
|
|
|
225
134
|
async updateNotificationStatus(notificationId, status, error) {
|
|
226
135
|
const update = {
|
|
227
136
|
status,
|
|
228
|
-
updatedAt:
|
|
137
|
+
updatedAt: admin.firestore.FieldValue.serverTimestamp()
|
|
229
138
|
};
|
|
230
139
|
if (status === "sent" /* SENT */) {
|
|
231
|
-
update.sentAt =
|
|
140
|
+
update.sentAt = admin.firestore.FieldValue.serverTimestamp();
|
|
232
141
|
}
|
|
233
142
|
if (error) {
|
|
234
143
|
update.error = error;
|
|
@@ -298,7 +207,7 @@ var NotificationsAdmin = class {
|
|
|
298
207
|
* Procesira notifikacije koje čekaju na slanje sa batch limitom
|
|
299
208
|
*/
|
|
300
209
|
async processPendingNotifications(batchSize = 100) {
|
|
301
|
-
const now =
|
|
210
|
+
const now = admin.firestore.Timestamp.now();
|
|
302
211
|
const pendingNotifications = await this.db.collection("notifications").where("status", "==", "pending" /* PENDING */).where("notificationTime", "<=", now).limit(batchSize).get();
|
|
303
212
|
const results = await Promise.allSettled(
|
|
304
213
|
pendingNotifications.docs.map(async (doc) => {
|
|
@@ -329,7 +238,7 @@ var NotificationsAdmin = class {
|
|
|
329
238
|
const oldNotifications = await this.db.collection("notifications").where(
|
|
330
239
|
"createdAt",
|
|
331
240
|
"<=",
|
|
332
|
-
|
|
241
|
+
admin.firestore.Timestamp.fromDate(cutoffDate)
|
|
333
242
|
).limit(batchSize).get();
|
|
334
243
|
if (oldNotifications.empty) {
|
|
335
244
|
break;
|
|
@@ -371,15 +280,12 @@ var NotificationsAdmin = class {
|
|
|
371
280
|
title = "New Appointment Confirmed";
|
|
372
281
|
body = `Appointment for ${appointment.procedureInfo.name} with ${appointment.patientInfo.fullName} on ${appointment.appointmentStartTime.toDate().toLocaleDateString()} is confirmed.`;
|
|
373
282
|
}
|
|
374
|
-
const
|
|
375
|
-
const clientCompatibleNotificationTime = TimestampUtils.adminToClient(
|
|
376
|
-
adminTsNow
|
|
377
|
-
);
|
|
283
|
+
const notificationTimestampForDb = admin.firestore.Timestamp.now();
|
|
378
284
|
const notificationData = {
|
|
379
285
|
userId: recipientUserId,
|
|
380
286
|
userRole: recipientRole,
|
|
381
287
|
notificationType: "appointmentStatusChange" /* APPOINTMENT_STATUS_CHANGE */,
|
|
382
|
-
notificationTime:
|
|
288
|
+
notificationTime: notificationTimestampForDb,
|
|
383
289
|
notificationTokens: recipientExpoTokens,
|
|
384
290
|
title,
|
|
385
291
|
body,
|
|
@@ -425,15 +331,12 @@ var NotificationsAdmin = class {
|
|
|
425
331
|
body += ` Reason: ${appointment.cancellationReason}`;
|
|
426
332
|
}
|
|
427
333
|
}
|
|
428
|
-
const
|
|
429
|
-
const clientCompatibleNotificationTime = TimestampUtils.adminToClient(
|
|
430
|
-
adminTsNow
|
|
431
|
-
);
|
|
334
|
+
const notificationTimestampForDb = admin.firestore.Timestamp.now();
|
|
432
335
|
const notificationData = {
|
|
433
336
|
userId: recipientUserId,
|
|
434
337
|
userRole: recipientRole,
|
|
435
338
|
notificationType: "appointmentCancelled" /* APPOINTMENT_CANCELLED */,
|
|
436
|
-
notificationTime:
|
|
339
|
+
notificationTime: notificationTimestampForDb,
|
|
437
340
|
notificationTokens: recipientExpoTokens,
|
|
438
341
|
title,
|
|
439
342
|
body,
|
|
@@ -464,15 +367,12 @@ var NotificationsAdmin = class {
|
|
|
464
367
|
}
|
|
465
368
|
const title = "Appointment Reschedule Proposed";
|
|
466
369
|
const body = `Action Required: A new time has been proposed for your appointment for ${appointment.procedureInfo.name}. Please review in the app.`;
|
|
467
|
-
const
|
|
468
|
-
const clientCompatibleNotificationTime = TimestampUtils.adminToClient(
|
|
469
|
-
adminTsNow
|
|
470
|
-
);
|
|
370
|
+
const notificationTimestampForDb = admin.firestore.Timestamp.now();
|
|
471
371
|
const notificationData = {
|
|
472
372
|
userId: patientUserId,
|
|
473
373
|
userRole: "patient" /* PATIENT */,
|
|
474
374
|
notificationType: "appointmentRescheduledProposal" /* APPOINTMENT_RESCHEDULED_PROPOSAL */,
|
|
475
|
-
notificationTime:
|
|
375
|
+
notificationTime: notificationTimestampForDb,
|
|
476
376
|
notificationTokens: patientExpoTokens,
|
|
477
377
|
title,
|
|
478
378
|
body,
|
|
@@ -503,16 +403,13 @@ var NotificationsAdmin = class {
|
|
|
503
403
|
}
|
|
504
404
|
const title = "Payment Updated";
|
|
505
405
|
const body = `Your payment status for the appointment (${appointment.procedureInfo.name}) on ${appointment.appointmentStartTime.toDate().toLocaleDateString()} is now ${appointment.paymentStatus}.`;
|
|
506
|
-
const
|
|
507
|
-
const clientCompatibleNotificationTime = TimestampUtils.adminToClient(
|
|
508
|
-
adminTsNow
|
|
509
|
-
);
|
|
406
|
+
const notificationTimestampForDb = admin.firestore.Timestamp.now();
|
|
510
407
|
const notificationType = appointment.paymentStatus === "paid" /* PAID */ ? "paymentConfirmation" /* PAYMENT_CONFIRMATION */ : "generalMessage" /* GENERAL_MESSAGE */;
|
|
511
408
|
const notificationData = {
|
|
512
409
|
userId: patientUserId,
|
|
513
410
|
userRole: "patient" /* PATIENT */,
|
|
514
411
|
notificationType,
|
|
515
|
-
notificationTime:
|
|
412
|
+
notificationTime: notificationTimestampForDb,
|
|
516
413
|
notificationTokens: patientExpoTokens,
|
|
517
414
|
title,
|
|
518
415
|
body,
|
|
@@ -543,15 +440,12 @@ var NotificationsAdmin = class {
|
|
|
543
440
|
}
|
|
544
441
|
const title = "Leave a Review";
|
|
545
442
|
const body = `How was your recent appointment for ${appointment.procedureInfo.name}? We'd love to hear your feedback!`;
|
|
546
|
-
const
|
|
547
|
-
const clientCompatibleNotificationTime = TimestampUtils.adminToClient(
|
|
548
|
-
adminTsNow
|
|
549
|
-
);
|
|
443
|
+
const notificationTimestampForDb = admin.firestore.Timestamp.now();
|
|
550
444
|
const notificationData = {
|
|
551
445
|
userId: patientUserId,
|
|
552
446
|
userRole: "patient" /* PATIENT */,
|
|
553
447
|
notificationType: "reviewRequest" /* REVIEW_REQUEST */,
|
|
554
|
-
notificationTime:
|
|
448
|
+
notificationTime: notificationTimestampForDb,
|
|
555
449
|
notificationTokens: patientExpoTokens,
|
|
556
450
|
title,
|
|
557
451
|
body,
|
|
@@ -588,16 +482,13 @@ var NotificationsAdmin = class {
|
|
|
588
482
|
}
|
|
589
483
|
const title = "New Review Received";
|
|
590
484
|
const body = `A new review has been added by ${appointment.patientInfo.fullName} for your appointment on ${appointment.appointmentStartTime.toDate().toLocaleDateString()}.`;
|
|
591
|
-
const
|
|
592
|
-
const clientCompatibleNotificationTime = TimestampUtils.adminToClient(
|
|
593
|
-
adminTsNow
|
|
594
|
-
);
|
|
485
|
+
const notificationTimestampForDb = admin.firestore.Timestamp.now();
|
|
595
486
|
const tempNotificationType = "generalMessage" /* GENERAL_MESSAGE */;
|
|
596
487
|
const notificationData = {
|
|
597
488
|
userId: recipientUserId,
|
|
598
489
|
userRole: "practitioner" /* PRACTITIONER */,
|
|
599
490
|
notificationType: tempNotificationType,
|
|
600
|
-
notificationTime:
|
|
491
|
+
notificationTime: notificationTimestampForDb,
|
|
601
492
|
notificationTokens: recipientExpoTokens,
|
|
602
493
|
title,
|
|
603
494
|
body,
|
|
@@ -621,6 +512,187 @@ var NotificationsAdmin = class {
|
|
|
621
512
|
}
|
|
622
513
|
};
|
|
623
514
|
|
|
515
|
+
// src/utils/TimestampUtils.ts
|
|
516
|
+
import * as admin2 from "firebase-admin";
|
|
517
|
+
import { Timestamp as ClientTimestamp } from "firebase/firestore";
|
|
518
|
+
var IS_SERVER_ENV = process.env.NODE_ENV === "production" || process.env.FUNCTIONS_EMULATOR === "true" || process.env.FIREBASE_CONFIG !== void 0;
|
|
519
|
+
var TimestampUtils = class {
|
|
520
|
+
/**
|
|
521
|
+
* Enables server mode where admin timestamps are preserved when saving
|
|
522
|
+
* to Firestore via the admin SDK
|
|
523
|
+
*/
|
|
524
|
+
static enableServerMode() {
|
|
525
|
+
this.serverMode = true;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Disables server mode - use this only for client-side or mixed environments
|
|
529
|
+
*/
|
|
530
|
+
static disableServerMode() {
|
|
531
|
+
this.serverMode = false;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Converts an admin Firestore Timestamp to a client Firestore Timestamp
|
|
535
|
+
* In server mode, returns the original admin timestamp for storage consistency
|
|
536
|
+
*
|
|
537
|
+
* @param adminTimestamp - Admin SDK Timestamp (from firebase-admin)
|
|
538
|
+
* @returns A client SDK Timestamp (from firebase/firestore) with same seconds/nanoseconds,
|
|
539
|
+
* or the original admin timestamp in server mode,
|
|
540
|
+
* or null if input is null
|
|
541
|
+
*/
|
|
542
|
+
static adminToClient(adminTimestamp) {
|
|
543
|
+
if (!adminTimestamp) return null;
|
|
544
|
+
if (this.serverMode) {
|
|
545
|
+
return adminTimestamp;
|
|
546
|
+
}
|
|
547
|
+
return new ClientTimestamp(
|
|
548
|
+
adminTimestamp.seconds,
|
|
549
|
+
adminTimestamp.nanoseconds
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Converts a client Firestore Timestamp to an admin Firestore Timestamp
|
|
554
|
+
* @param clientTimestamp - Client SDK Timestamp (from firebase/firestore)
|
|
555
|
+
* @returns An admin SDK Timestamp (from firebase-admin) with same seconds/nanoseconds or null if input is null
|
|
556
|
+
*/
|
|
557
|
+
static clientToAdmin(clientTimestamp) {
|
|
558
|
+
if (!clientTimestamp) return null;
|
|
559
|
+
return new admin2.firestore.Timestamp(
|
|
560
|
+
clientTimestamp.seconds,
|
|
561
|
+
clientTimestamp.nanoseconds
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Creates a timestamp for the current time in the appropriate format based on environment
|
|
566
|
+
* @returns A timestamp for the current time (admin timestamp in server mode, client in client mode)
|
|
567
|
+
*/
|
|
568
|
+
static nowAsTimestamp() {
|
|
569
|
+
const now = admin2.firestore.Timestamp.now();
|
|
570
|
+
if (this.serverMode) {
|
|
571
|
+
return now;
|
|
572
|
+
}
|
|
573
|
+
return this.adminToClient(now);
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* @deprecated Use nowAsTimestamp() instead for better cross-environment compatibility
|
|
577
|
+
*/
|
|
578
|
+
static nowAsClient() {
|
|
579
|
+
const now = admin2.firestore.Timestamp.now();
|
|
580
|
+
return this.adminToClient(now);
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Converts a Date object to a timestamp in the appropriate format based on environment
|
|
584
|
+
* @param date - JavaScript Date object
|
|
585
|
+
* @returns A timestamp (admin timestamp in server mode, client in client mode) or null if input is null
|
|
586
|
+
*/
|
|
587
|
+
static dateToTimestamp(date) {
|
|
588
|
+
if (!date) return null;
|
|
589
|
+
if (this.serverMode) {
|
|
590
|
+
return admin2.firestore.Timestamp.fromDate(date);
|
|
591
|
+
}
|
|
592
|
+
return ClientTimestamp.fromDate(date);
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* @deprecated Use dateToTimestamp() instead for better cross-environment compatibility
|
|
596
|
+
*/
|
|
597
|
+
static dateToClientTimestamp(date) {
|
|
598
|
+
if (!date) return null;
|
|
599
|
+
return ClientTimestamp.fromDate(date);
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* @deprecated Use dateToTimestamp() instead for better cross-environment compatibility
|
|
603
|
+
*/
|
|
604
|
+
static dateToAdminTimestamp(date) {
|
|
605
|
+
if (!date) return null;
|
|
606
|
+
return admin2.firestore.Timestamp.fromDate(date);
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Gets a server timestamp field value for use in create/update operations
|
|
610
|
+
* Works in both admin and client environments
|
|
611
|
+
*/
|
|
612
|
+
static serverTimestamp() {
|
|
613
|
+
if (this.serverMode) {
|
|
614
|
+
return admin2.firestore.FieldValue.serverTimestamp();
|
|
615
|
+
}
|
|
616
|
+
throw new Error("Server timestamp in client mode not implemented");
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* For objects with mixed timestamp types, ensures all timestamps are
|
|
620
|
+
* in the correct format for the current environment
|
|
621
|
+
*/
|
|
622
|
+
static normalizeTimestamps(obj) {
|
|
623
|
+
if (!obj || typeof obj !== "object") {
|
|
624
|
+
return obj;
|
|
625
|
+
}
|
|
626
|
+
if (obj instanceof admin2.firestore.Timestamp) {
|
|
627
|
+
return this.serverMode ? obj : this.adminToClient(obj);
|
|
628
|
+
}
|
|
629
|
+
if (obj instanceof ClientTimestamp && this.serverMode) {
|
|
630
|
+
return this.clientToAdmin(obj);
|
|
631
|
+
}
|
|
632
|
+
if (Array.isArray(obj)) {
|
|
633
|
+
return obj.map((item) => this.normalizeTimestamps(item));
|
|
634
|
+
}
|
|
635
|
+
const result = { ...obj };
|
|
636
|
+
for (const key in result) {
|
|
637
|
+
if (Object.prototype.hasOwnProperty.call(result, key)) {
|
|
638
|
+
result[key] = this.normalizeTimestamps(result[key]);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return result;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* @deprecated Use normalizeTimestamps() instead for better cross-environment compatibility
|
|
645
|
+
*/
|
|
646
|
+
static convertObjectTimestampsAdminToClient(obj) {
|
|
647
|
+
if (!obj || typeof obj !== "object") {
|
|
648
|
+
return obj;
|
|
649
|
+
}
|
|
650
|
+
if (obj instanceof admin2.firestore.Timestamp) {
|
|
651
|
+
return this.adminToClient(obj);
|
|
652
|
+
}
|
|
653
|
+
if (Array.isArray(obj)) {
|
|
654
|
+
return obj.map(
|
|
655
|
+
(item) => this.convertObjectTimestampsAdminToClient(item)
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
const result = { ...obj };
|
|
659
|
+
for (const key in result) {
|
|
660
|
+
if (Object.prototype.hasOwnProperty.call(result, key)) {
|
|
661
|
+
result[key] = this.convertObjectTimestampsAdminToClient(result[key]);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return result;
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* @deprecated Use normalizeTimestamps() instead for better cross-environment compatibility
|
|
668
|
+
*/
|
|
669
|
+
static convertObjectTimestampsClientToAdmin(obj) {
|
|
670
|
+
if (!obj || typeof obj !== "object") {
|
|
671
|
+
return obj;
|
|
672
|
+
}
|
|
673
|
+
if (obj instanceof ClientTimestamp) {
|
|
674
|
+
return this.clientToAdmin(obj);
|
|
675
|
+
}
|
|
676
|
+
if (Array.isArray(obj)) {
|
|
677
|
+
return obj.map(
|
|
678
|
+
(item) => this.convertObjectTimestampsClientToAdmin(item)
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
const result = { ...obj };
|
|
682
|
+
for (const key in result) {
|
|
683
|
+
if (Object.prototype.hasOwnProperty.call(result, key)) {
|
|
684
|
+
result[key] = this.convertObjectTimestampsClientToAdmin(result[key]);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return result;
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
/**
|
|
691
|
+
* Flag to force server mode where admin timestamps are preserved
|
|
692
|
+
* This should be true in Cloud Functions environments
|
|
693
|
+
*/
|
|
694
|
+
TimestampUtils.serverMode = IS_SERVER_ENV;
|
|
695
|
+
|
|
624
696
|
// src/admin/aggregation/clinic/clinic.aggregation.service.ts
|
|
625
697
|
import * as admin3 from "firebase-admin";
|
|
626
698
|
|
|
@@ -2081,7 +2153,7 @@ var PatientRequirementsAdminService = class {
|
|
|
2081
2153
|
userRole: "patient" /* PATIENT */,
|
|
2082
2154
|
notificationType: "requirementInstructionDue" /* REQUIREMENT_INSTRUCTION_DUE */,
|
|
2083
2155
|
notificationTime: currentInstruction.dueTime,
|
|
2084
|
-
//
|
|
2156
|
+
// dueTime should be an admin.firestore.Timestamp already
|
|
2085
2157
|
notificationTokens: patientExpoTokens,
|
|
2086
2158
|
title: `Reminder: ${instance.requirementName}`,
|
|
2087
2159
|
body: currentInstruction.instructionText,
|
|
@@ -2423,14 +2495,8 @@ var CalendarAdminService = class {
|
|
|
2423
2495
|
const batch = this.db.batch();
|
|
2424
2496
|
const serverTimestamp = admin8.firestore.FieldValue.serverTimestamp();
|
|
2425
2497
|
const firestoreCompatibleEventTime = {
|
|
2426
|
-
start:
|
|
2427
|
-
|
|
2428
|
-
nanoseconds: newEventTime.start.nanoseconds
|
|
2429
|
-
},
|
|
2430
|
-
end: {
|
|
2431
|
-
seconds: newEventTime.end.seconds,
|
|
2432
|
-
nanoseconds: newEventTime.end.nanoseconds
|
|
2433
|
-
}
|
|
2498
|
+
start: TimestampUtils.clientToAdmin(newEventTime.start) || admin8.firestore.Timestamp.now(),
|
|
2499
|
+
end: TimestampUtils.clientToAdmin(newEventTime.end) || admin8.firestore.Timestamp.now()
|
|
2434
2500
|
};
|
|
2435
2501
|
if (appointment.calendarEventId) {
|
|
2436
2502
|
const practitionerEventRef = this.db.doc(
|
|
@@ -3178,7 +3244,8 @@ var AppointmentAggregationService = class {
|
|
|
3178
3244
|
status: "pendingNotification" /* PENDING_NOTIFICATION */,
|
|
3179
3245
|
originalNotifyAtValue: notifyAtValue,
|
|
3180
3246
|
originalTimeframeUnit: template.timeframe.unit,
|
|
3181
|
-
updatedAt: admin10.firestore.
|
|
3247
|
+
updatedAt: admin10.firestore.Timestamp.now(),
|
|
3248
|
+
// Use current server timestamp
|
|
3182
3249
|
notificationId: void 0,
|
|
3183
3250
|
actionTakenAt: void 0
|
|
3184
3251
|
};
|
|
@@ -3288,7 +3355,8 @@ var AppointmentAggregationService = class {
|
|
|
3288
3355
|
status: "pendingNotification" /* PENDING_NOTIFICATION */,
|
|
3289
3356
|
originalNotifyAtValue: notifyAtValue,
|
|
3290
3357
|
originalTimeframeUnit: template.timeframe.unit,
|
|
3291
|
-
updatedAt: admin10.firestore.
|
|
3358
|
+
updatedAt: admin10.firestore.Timestamp.now(),
|
|
3359
|
+
// Use current server timestamp
|
|
3292
3360
|
notificationId: void 0,
|
|
3293
3361
|
actionTakenAt: void 0
|
|
3294
3362
|
};
|
|
@@ -4499,8 +4567,12 @@ var BookingAdmin = class {
|
|
|
4499
4567
|
return events.map((event) => ({
|
|
4500
4568
|
...event,
|
|
4501
4569
|
eventTime: {
|
|
4502
|
-
start: TimestampUtils.adminToClient(
|
|
4503
|
-
|
|
4570
|
+
start: TimestampUtils.adminToClient(
|
|
4571
|
+
event.eventTime.start
|
|
4572
|
+
),
|
|
4573
|
+
end: TimestampUtils.adminToClient(
|
|
4574
|
+
event.eventTime.end
|
|
4575
|
+
)
|
|
4504
4576
|
}
|
|
4505
4577
|
// Convert any other timestamps in the event if needed
|
|
4506
4578
|
}));
|
|
@@ -4965,12 +5037,20 @@ var BookingAdmin = class {
|
|
|
4965
5037
|
|
|
4966
5038
|
// src/admin/index.ts
|
|
4967
5039
|
console.log("[Admin Module] Initialized and services exported.");
|
|
5040
|
+
TimestampUtils.enableServerMode();
|
|
4968
5041
|
export {
|
|
5042
|
+
APPOINTMENTS_COLLECTION,
|
|
4969
5043
|
AppointmentAggregationService,
|
|
5044
|
+
AppointmentMailingService,
|
|
4970
5045
|
AppointmentStatus,
|
|
4971
5046
|
BaseMailingService,
|
|
4972
5047
|
BookingAdmin,
|
|
5048
|
+
BookingAvailabilityCalculator,
|
|
5049
|
+
CalendarAdminService,
|
|
4973
5050
|
ClinicAggregationService,
|
|
5051
|
+
DocumentManagerAdminService,
|
|
5052
|
+
Logger,
|
|
5053
|
+
MediaType,
|
|
4974
5054
|
NOTIFICATIONS_COLLECTION,
|
|
4975
5055
|
NotificationStatus,
|
|
4976
5056
|
NotificationType,
|
|
@@ -4980,6 +5060,7 @@ export {
|
|
|
4980
5060
|
PatientInstructionStatus,
|
|
4981
5061
|
PatientRequirementOverallStatus,
|
|
4982
5062
|
PatientRequirementsAdminService,
|
|
5063
|
+
PaymentStatus,
|
|
4983
5064
|
PractitionerAggregationService,
|
|
4984
5065
|
PractitionerInviteMailingService,
|
|
4985
5066
|
PractitionerTokenStatus,
|
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
|