@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.
@@ -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
@@ -277,10 +277,7 @@ export class NotificationsAdmin {
277
277
  .toLocaleDateString()} is confirmed.`;
278
278
  }
279
279
 
280
- const adminTsNow = admin.firestore.Timestamp.now();
281
- const clientCompatibleNotificationTime = TimestampUtils.adminToClient(
282
- adminTsNow
283
- ) as FirebaseClientTimestamp;
280
+ const notificationTimestampForDb = admin.firestore.Timestamp.now();
284
281
 
285
282
  const notificationData: Omit<
286
283
  Notification,
@@ -289,7 +286,7 @@ export class NotificationsAdmin {
289
286
  userId: recipientUserId,
290
287
  userRole: recipientRole,
291
288
  notificationType: NotificationType.APPOINTMENT_STATUS_CHANGE,
292
- notificationTime: clientCompatibleNotificationTime,
289
+ notificationTime: notificationTimestampForDb as any,
293
290
  notificationTokens: recipientExpoTokens,
294
291
  title,
295
292
  body,
@@ -353,10 +350,7 @@ export class NotificationsAdmin {
353
350
  }
354
351
  }
355
352
 
356
- const adminTsNow = admin.firestore.Timestamp.now();
357
- const clientCompatibleNotificationTime = TimestampUtils.adminToClient(
358
- adminTsNow
359
- ) as FirebaseClientTimestamp;
353
+ const notificationTimestampForDb = admin.firestore.Timestamp.now();
360
354
 
361
355
  const notificationData: Omit<
362
356
  Notification,
@@ -365,7 +359,7 @@ export class NotificationsAdmin {
365
359
  userId: recipientUserId,
366
360
  userRole: recipientRole,
367
361
  notificationType: NotificationType.APPOINTMENT_CANCELLED,
368
- notificationTime: clientCompatibleNotificationTime,
362
+ notificationTime: notificationTimestampForDb as any,
369
363
  notificationTokens: recipientExpoTokens,
370
364
  title,
371
365
  body,
@@ -404,10 +398,7 @@ export class NotificationsAdmin {
404
398
  const title = "Appointment Reschedule Proposed";
405
399
  const body = `Action Required: A new time has been proposed for your appointment for ${appointment.procedureInfo.name}. Please review in the app.`;
406
400
 
407
- const adminTsNow = admin.firestore.Timestamp.now();
408
- const clientCompatibleNotificationTime = TimestampUtils.adminToClient(
409
- adminTsNow
410
- ) as FirebaseClientTimestamp;
401
+ const notificationTimestampForDb = admin.firestore.Timestamp.now();
411
402
 
412
403
  const notificationData: Omit<
413
404
  Notification,
@@ -416,7 +407,7 @@ export class NotificationsAdmin {
416
407
  userId: patientUserId,
417
408
  userRole: UserRole.PATIENT,
418
409
  notificationType: NotificationType.APPOINTMENT_RESCHEDULED_PROPOSAL,
419
- notificationTime: clientCompatibleNotificationTime,
410
+ notificationTime: notificationTimestampForDb as any,
420
411
  notificationTokens: patientExpoTokens,
421
412
  title,
422
413
  body,
@@ -458,10 +449,7 @@ export class NotificationsAdmin {
458
449
  .toDate()
459
450
  .toLocaleDateString()} is now ${appointment.paymentStatus}.`;
460
451
 
461
- const adminTsNow = admin.firestore.Timestamp.now();
462
- const clientCompatibleNotificationTime = TimestampUtils.adminToClient(
463
- adminTsNow
464
- ) as FirebaseClientTimestamp;
452
+ const notificationTimestampForDb = admin.firestore.Timestamp.now();
465
453
 
466
454
  const notificationType =
467
455
  appointment.paymentStatus === PaymentStatus.PAID
@@ -475,7 +463,7 @@ export class NotificationsAdmin {
475
463
  userId: patientUserId,
476
464
  userRole: UserRole.PATIENT,
477
465
  notificationType: notificationType,
478
- notificationTime: clientCompatibleNotificationTime,
466
+ notificationTime: notificationTimestampForDb as any,
479
467
  notificationTokens: patientExpoTokens,
480
468
  title,
481
469
  body,
@@ -514,10 +502,7 @@ export class NotificationsAdmin {
514
502
  const title = "Leave a Review";
515
503
  const body = `How was your recent appointment for ${appointment.procedureInfo.name}? We'd love to hear your feedback!`;
516
504
 
517
- const adminTsNow = admin.firestore.Timestamp.now();
518
- const clientCompatibleNotificationTime = TimestampUtils.adminToClient(
519
- adminTsNow
520
- ) as FirebaseClientTimestamp;
505
+ const notificationTimestampForDb = admin.firestore.Timestamp.now();
521
506
 
522
507
  const notificationData: Omit<
523
508
  Notification,
@@ -526,7 +511,7 @@ export class NotificationsAdmin {
526
511
  userId: patientUserId,
527
512
  userRole: UserRole.PATIENT,
528
513
  notificationType: NotificationType.REVIEW_REQUEST,
529
- notificationTime: clientCompatibleNotificationTime,
514
+ notificationTime: notificationTimestampForDb as any,
530
515
  notificationTokens: patientExpoTokens,
531
516
  title,
532
517
  body,
@@ -576,10 +561,7 @@ export class NotificationsAdmin {
576
561
  .toDate()
577
562
  .toLocaleDateString()}.`;
578
563
 
579
- const adminTsNow = admin.firestore.Timestamp.now();
580
- const clientCompatibleNotificationTime = TimestampUtils.adminToClient(
581
- adminTsNow
582
- ) as FirebaseClientTimestamp;
564
+ const notificationTimestampForDb = admin.firestore.Timestamp.now();
583
565
 
584
566
  const tempNotificationType = NotificationType.GENERAL_MESSAGE;
585
567
 
@@ -590,7 +572,7 @@ export class NotificationsAdmin {
590
572
  userId: recipientUserId,
591
573
  userRole: UserRole.PRACTITIONER,
592
574
  notificationType: tempNotificationType,
593
- notificationTime: clientCompatibleNotificationTime,
575
+ notificationTime: notificationTimestampForDb as any,
594
576
  notificationTokens: recipientExpoTokens,
595
577
  title,
596
578
  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, // This is a Firestore Timestamp
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,
@@ -1,22 +1,62 @@
1
1
  import * as admin from "firebase-admin";
2
2
  import { Timestamp as ClientTimestamp } from "firebase/firestore";
3
3
 
4
+ /**
5
+ * Detect if we're running in a server environment (like Cloud Functions)
6
+ * or compiled for server usage
7
+ */
8
+ const IS_SERVER_ENV =
9
+ process.env.NODE_ENV === "production" ||
10
+ process.env.FUNCTIONS_EMULATOR === "true" ||
11
+ process.env.FIREBASE_CONFIG !== undefined;
12
+
4
13
  /**
5
14
  * Utilities for managing timestamps across client and server-side Firebase code.
6
15
  * This module provides conversion functions to ensure timestamp compatibility
7
16
  * between admin and client Firebase SDKs.
8
17
  */
9
18
  export class TimestampUtils {
19
+ /**
20
+ * Flag to force server mode where admin timestamps are preserved
21
+ * This should be true in Cloud Functions environments
22
+ */
23
+ private static serverMode = IS_SERVER_ENV;
24
+
25
+ /**
26
+ * Enables server mode where admin timestamps are preserved when saving
27
+ * to Firestore via the admin SDK
28
+ */
29
+ static enableServerMode(): void {
30
+ this.serverMode = true;
31
+ }
32
+
33
+ /**
34
+ * Disables server mode - use this only for client-side or mixed environments
35
+ */
36
+ static disableServerMode(): void {
37
+ this.serverMode = false;
38
+ }
39
+
10
40
  /**
11
41
  * Converts an admin Firestore Timestamp to a client Firestore Timestamp
42
+ * In server mode, returns the original admin timestamp for storage consistency
43
+ *
12
44
  * @param adminTimestamp - Admin SDK Timestamp (from firebase-admin)
13
- * @returns A client SDK Timestamp (from firebase/firestore) with same seconds/nanoseconds or null if input is null
45
+ * @returns A client SDK Timestamp (from firebase/firestore) with same seconds/nanoseconds,
46
+ * or the original admin timestamp in server mode,
47
+ * or null if input is null
14
48
  */
15
49
  static adminToClient(
16
50
  adminTimestamp: admin.firestore.Timestamp | null
17
- ): ClientTimestamp | null {
51
+ ): ClientTimestamp | admin.firestore.Timestamp | null {
18
52
  if (!adminTimestamp) return null;
19
53
 
54
+ // In server mode (Cloud Functions), just return the admin timestamp
55
+ if (this.serverMode) {
56
+ return adminTimestamp;
57
+ }
58
+
59
+ // In client mode, convert to client timestamp
20
60
  return new ClientTimestamp(
21
61
  adminTimestamp.seconds,
22
62
  adminTimestamp.nanoseconds
@@ -40,8 +80,21 @@ export class TimestampUtils {
40
80
  }
41
81
 
42
82
  /**
43
- * Creates a client-compatible timestamp for the current time
44
- * @returns A client SDK Timestamp (from firebase/firestore) for the current time
83
+ * Creates a timestamp for the current time in the appropriate format based on environment
84
+ * @returns A timestamp for the current time (admin timestamp in server mode, client in client mode)
85
+ */
86
+ static nowAsTimestamp(): admin.firestore.Timestamp | ClientTimestamp {
87
+ const now = admin.firestore.Timestamp.now();
88
+ // In server mode, return the admin timestamp directly
89
+ if (this.serverMode) {
90
+ return now;
91
+ }
92
+ // In client mode, convert to client timestamp
93
+ return this.adminToClient(now) as ClientTimestamp;
94
+ }
95
+
96
+ /**
97
+ * @deprecated Use nowAsTimestamp() instead for better cross-environment compatibility
45
98
  */
46
99
  static nowAsClient(): ClientTimestamp {
47
100
  const now = admin.firestore.Timestamp.now();
@@ -49,33 +102,87 @@ export class TimestampUtils {
49
102
  }
50
103
 
51
104
  /**
52
- * Converts a Date object to a client Firestore Timestamp
105
+ * Converts a Date object to a timestamp in the appropriate format based on environment
53
106
  * @param date - JavaScript Date object
54
- * @returns A client SDK Timestamp (from firebase/firestore) or null if input is null
107
+ * @returns A timestamp (admin timestamp in server mode, client in client mode) or null if input is null
55
108
  */
56
- static dateToClientTimestamp(date: Date | null): ClientTimestamp | null {
109
+ static dateToTimestamp(
110
+ date: Date | null
111
+ ): admin.firestore.Timestamp | ClientTimestamp | null {
57
112
  if (!date) return null;
58
113
 
114
+ if (this.serverMode) {
115
+ return admin.firestore.Timestamp.fromDate(date);
116
+ }
117
+
59
118
  return ClientTimestamp.fromDate(date);
60
119
  }
61
120
 
62
121
  /**
63
- * Converts a Date object to an admin Firestore Timestamp
64
- * @param date - JavaScript Date object
65
- * @returns An admin SDK Timestamp (from firebase-admin) or null if input is null
122
+ * @deprecated Use dateToTimestamp() instead for better cross-environment compatibility
123
+ */
124
+ static dateToClientTimestamp(date: Date | null): ClientTimestamp | null {
125
+ if (!date) return null;
126
+ return ClientTimestamp.fromDate(date);
127
+ }
128
+
129
+ /**
130
+ * @deprecated Use dateToTimestamp() instead for better cross-environment compatibility
66
131
  */
67
132
  static dateToAdminTimestamp(
68
133
  date: Date | null
69
134
  ): admin.firestore.Timestamp | null {
70
135
  if (!date) return null;
71
-
72
136
  return admin.firestore.Timestamp.fromDate(date);
73
137
  }
74
138
 
75
139
  /**
76
- * Deeply converts all admin Firestore Timestamps in an object to client Firestore Timestamps
77
- * @param obj - Object that may contain admin Firestore Timestamps
78
- * @returns The same object with all admin Timestamps converted to client Timestamps
140
+ * Gets a server timestamp field value for use in create/update operations
141
+ * Works in both admin and client environments
142
+ */
143
+ static serverTimestamp(): admin.firestore.FieldValue | any {
144
+ if (this.serverMode) {
145
+ return admin.firestore.FieldValue.serverTimestamp();
146
+ }
147
+ // In client mode, we'd need to import from firebase/firestore
148
+ // This would be implemented for client environments
149
+ throw new Error("Server timestamp in client mode not implemented");
150
+ }
151
+
152
+ /**
153
+ * For objects with mixed timestamp types, ensures all timestamps are
154
+ * in the correct format for the current environment
155
+ */
156
+ static normalizeTimestamps<T>(obj: T): T {
157
+ if (!obj || typeof obj !== "object") {
158
+ return obj;
159
+ }
160
+
161
+ if (obj instanceof admin.firestore.Timestamp) {
162
+ return this.serverMode ? obj : (this.adminToClient(obj) as unknown as T);
163
+ }
164
+
165
+ if (obj instanceof ClientTimestamp && this.serverMode) {
166
+ return this.clientToAdmin(obj) as unknown as T;
167
+ }
168
+
169
+ if (Array.isArray(obj)) {
170
+ return obj.map((item) => this.normalizeTimestamps(item)) as unknown as T;
171
+ }
172
+
173
+ const result = { ...obj } as any;
174
+
175
+ for (const key in result) {
176
+ if (Object.prototype.hasOwnProperty.call(result, key)) {
177
+ result[key] = this.normalizeTimestamps(result[key]);
178
+ }
179
+ }
180
+
181
+ return result as T;
182
+ }
183
+
184
+ /**
185
+ * @deprecated Use normalizeTimestamps() instead for better cross-environment compatibility
79
186
  */
80
187
  static convertObjectTimestampsAdminToClient<T>(obj: T): T {
81
188
  if (!obj || typeof obj !== "object") {
@@ -104,9 +211,7 @@ export class TimestampUtils {
104
211
  }
105
212
 
106
213
  /**
107
- * Deeply converts all client Firestore Timestamps in an object to admin Firestore Timestamps
108
- * @param obj - Object that may contain client Firestore Timestamps
109
- * @returns The same object with all client Timestamps converted to admin Timestamps
214
+ * @deprecated Use normalizeTimestamps() instead for better cross-environment compatibility
110
215
  */
111
216
  static convertObjectTimestampsClientToAdmin<T>(obj: T): T {
112
217
  if (!obj || typeof obj !== "object") {