@blackcode_sa/metaestetics-api 1.6.13 → 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 +4712 -37532
- package/dist/admin/index.mjs +4660 -37510
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/package.json +1 -1
- package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +2 -2
- package/src/admin/booking/booking.admin.ts +10 -12
- package/src/admin/calendar/calendar.admin.service.ts +8 -11
- package/src/admin/index.ts +21 -0
- package/src/admin/notifications/notifications.admin.ts +13 -36
- package/src/admin/requirements/patient-requirements.admin.service.ts +26 -33
- package/src/types/clinic/index.ts +19 -27
- package/src/types/patient/index.ts +20 -22
- package/src/utils/TIMESTAMPS.md +176 -0
- package/src/utils/TimestampUtils.ts +241 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Timestamp Management Strategy
|
|
2
|
+
|
|
3
|
+
## Problem Statement
|
|
4
|
+
|
|
5
|
+
Firebase provides two different Timestamp implementations across its SDKs:
|
|
6
|
+
|
|
7
|
+
1. **Client-side Timestamp**: `import { Timestamp } from 'firebase/firestore'`
|
|
8
|
+
|
|
9
|
+
- Used in client applications (web, mobile)
|
|
10
|
+
- Lives in the `firebase/firestore` package
|
|
11
|
+
|
|
12
|
+
2. **Admin-side Timestamp**: `import * as admin from 'firebase-admin'; admin.firestore.Timestamp`
|
|
13
|
+
- Used in server-side code (Cloud Functions)
|
|
14
|
+
- Lives in the `firebase-admin` package
|
|
15
|
+
|
|
16
|
+
These types are structurally similar but are different JavaScript classes, causing type conflicts when:
|
|
17
|
+
|
|
18
|
+
- Data with client Timestamps is processed in admin code
|
|
19
|
+
- Data with admin Timestamps is sent to client code
|
|
20
|
+
- Shared type definitions across client and admin code
|
|
21
|
+
|
|
22
|
+
## Solution: TimestampUtils
|
|
23
|
+
|
|
24
|
+
We've created a central `TimestampUtils` class providing:
|
|
25
|
+
|
|
26
|
+
1. Conversion utilities between admin and client Timestamps
|
|
27
|
+
2. Best practices for timestamp handling
|
|
28
|
+
3. Consistent patterns for timestamp operations
|
|
29
|
+
|
|
30
|
+
## Usage Guidelines
|
|
31
|
+
|
|
32
|
+
### 1. Type Definitions
|
|
33
|
+
|
|
34
|
+
All shared type definitions (in `src/types/*`) should:
|
|
35
|
+
|
|
36
|
+
- Import Timestamp from client SDK: `import { Timestamp } from 'firebase/firestore'`
|
|
37
|
+
- Define fields using this client Timestamp: `timestamp: Timestamp`
|
|
38
|
+
|
|
39
|
+
This ensures types are consumable by both client and admin code.
|
|
40
|
+
|
|
41
|
+
### 2. Admin Code (Cloud Functions)
|
|
42
|
+
|
|
43
|
+
When writing admin code that:
|
|
44
|
+
|
|
45
|
+
- **Reads data from Firestore**: No conversion needed, as Firestore returns admin Timestamps
|
|
46
|
+
- **Processes timestamps**: Use standard admin timestamp methods
|
|
47
|
+
- **Writes data to shared types**: Convert admin → client using `TimestampUtils.adminToClient()`
|
|
48
|
+
- **Creates server timestamps**: For fields created with `serverTimestamp()`, use Firebase Admin's version: `admin.firestore.FieldValue.serverTimestamp()`
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import * as admin from "firebase-admin";
|
|
54
|
+
import { TimestampUtils } from "../../utils/TimestampUtils";
|
|
55
|
+
import { YourSharedType } from "../../types/shared";
|
|
56
|
+
|
|
57
|
+
// In your admin service
|
|
58
|
+
async function processAndUpdateData() {
|
|
59
|
+
const adminTsNow = admin.firestore.Timestamp.now();
|
|
60
|
+
|
|
61
|
+
// When populating a shared type that clients will use
|
|
62
|
+
const data: YourSharedType = {
|
|
63
|
+
// Convert admin timestamp to client timestamp
|
|
64
|
+
createdAt: TimestampUtils.adminToClient(adminTsNow),
|
|
65
|
+
|
|
66
|
+
// For server timestamps, use admin version
|
|
67
|
+
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
68
|
+
|
|
69
|
+
// Other fields...
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// When you need to process objects with nested timestamps
|
|
73
|
+
const objectWithNestedTimestamps = {
|
|
74
|
+
// Complex data from another source
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Convert all timestamps in the object from admin to client
|
|
78
|
+
const clientCompatible = TimestampUtils.convertObjectTimestampsAdminToClient(
|
|
79
|
+
objectWithNestedTimestamps
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 3. Client Code (Web/Mobile Apps)
|
|
85
|
+
|
|
86
|
+
Client code should:
|
|
87
|
+
|
|
88
|
+
- Use `firebase/firestore` Timestamp when needed
|
|
89
|
+
- No conversion is necessary for data returned by `getDocs()` or similar client SDK methods
|
|
90
|
+
- For new timestamps, use `Timestamp.now()` or `Timestamp.fromDate()`
|
|
91
|
+
|
|
92
|
+
### 4. Utility Functions
|
|
93
|
+
|
|
94
|
+
The `TimestampUtils` class provides these key functions:
|
|
95
|
+
|
|
96
|
+
- `adminToClient(adminTimestamp)`: Converts admin → client Timestamp
|
|
97
|
+
- `clientToAdmin(clientTimestamp)`: Converts client → admin Timestamp
|
|
98
|
+
- `nowAsClient()`: Creates current timestamp as client Timestamp
|
|
99
|
+
- `dateToClientTimestamp(date)`: Converts Date → client Timestamp
|
|
100
|
+
- `dateToAdminTimestamp(date)`: Converts Date → admin Timestamp
|
|
101
|
+
- `convertObjectTimestampsAdminToClient(obj)`: Deep conversion of all timestamps in an object from admin → client
|
|
102
|
+
- `convertObjectTimestampsClientToAdmin(obj)`: Deep conversion of all timestamps in an object from client → admin
|
|
103
|
+
|
|
104
|
+
## Common Patterns
|
|
105
|
+
|
|
106
|
+
### 1. Creating New Documents
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// In admin code
|
|
110
|
+
const adminTimestamp = admin.firestore.Timestamp.now();
|
|
111
|
+
const clientCompatibleTimestamp = TimestampUtils.adminToClient(adminTimestamp);
|
|
112
|
+
|
|
113
|
+
const newDocument = {
|
|
114
|
+
id: "doc123",
|
|
115
|
+
createdAt: clientCompatibleTimestamp,
|
|
116
|
+
// For server-managed timestamps, use serverTimestamp()
|
|
117
|
+
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
await docRef.set(newDocument);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 2. Updating Fields in a Document
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
// In admin code
|
|
127
|
+
await docRef.update({
|
|
128
|
+
someField: "new value",
|
|
129
|
+
// For immediate timestamp, convert admin to client
|
|
130
|
+
processedAt: TimestampUtils.adminToClient(admin.firestore.Timestamp.now()),
|
|
131
|
+
// For server-managed timestamps, use serverTimestamp()
|
|
132
|
+
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 3. Processing Calendar Events
|
|
137
|
+
|
|
138
|
+
Calendar events and date ranges should be consistently handled:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// Calendar event time conversion
|
|
142
|
+
const firestoreCompatibleEventTime = {
|
|
143
|
+
start: {
|
|
144
|
+
seconds: newEventTime.start.seconds,
|
|
145
|
+
nanoseconds: newEventTime.start.nanoseconds,
|
|
146
|
+
},
|
|
147
|
+
end: {
|
|
148
|
+
seconds: newEventTime.end.seconds,
|
|
149
|
+
nanoseconds: newEventTime.end.nanoseconds,
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Edge Cases and Limitations
|
|
155
|
+
|
|
156
|
+
1. **null/undefined Handling**: All utility methods handle null safely
|
|
157
|
+
2. **Serialization**: If serializing for JSON, convert to ISO strings first
|
|
158
|
+
3. **Performance**: The conversion is lightweight, but for large batches, process efficiently
|
|
159
|
+
|
|
160
|
+
## Migration Strategy
|
|
161
|
+
|
|
162
|
+
1. Identify timestamp usage in your code using static analysis or grep
|
|
163
|
+
2. Replace direct timestamp conversions with TimestampUtils methods
|
|
164
|
+
3. Update methods like:
|
|
165
|
+
- `adminTimestampToClientTimestamp` → `TimestampUtils.adminToClient`
|
|
166
|
+
- Direct creation of client timestamps → `TimestampUtils.nowAsClient`
|
|
167
|
+
4. Test thoroughly after each change
|
|
168
|
+
|
|
169
|
+
## Conclusion
|
|
170
|
+
|
|
171
|
+
Following this strategy will:
|
|
172
|
+
|
|
173
|
+
- Eliminate type errors between admin and client code
|
|
174
|
+
- Provide consistent timestamp handling throughout the codebase
|
|
175
|
+
- Minimize conversion bugs by centralizing conversion logic
|
|
176
|
+
- Make it easier to onboard new developers to the codebase
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import * as admin from "firebase-admin";
|
|
2
|
+
import { Timestamp as ClientTimestamp } from "firebase/firestore";
|
|
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
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Utilities for managing timestamps across client and server-side Firebase code.
|
|
15
|
+
* This module provides conversion functions to ensure timestamp compatibility
|
|
16
|
+
* between admin and client Firebase SDKs.
|
|
17
|
+
*/
|
|
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
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Converts an admin Firestore Timestamp to a client Firestore Timestamp
|
|
42
|
+
* In server mode, returns the original admin timestamp for storage consistency
|
|
43
|
+
*
|
|
44
|
+
* @param adminTimestamp - Admin SDK Timestamp (from firebase-admin)
|
|
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
|
|
48
|
+
*/
|
|
49
|
+
static adminToClient(
|
|
50
|
+
adminTimestamp: admin.firestore.Timestamp | null
|
|
51
|
+
): ClientTimestamp | admin.firestore.Timestamp | null {
|
|
52
|
+
if (!adminTimestamp) return null;
|
|
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
|
|
60
|
+
return new ClientTimestamp(
|
|
61
|
+
adminTimestamp.seconds,
|
|
62
|
+
adminTimestamp.nanoseconds
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Converts a client Firestore Timestamp to an admin Firestore Timestamp
|
|
68
|
+
* @param clientTimestamp - Client SDK Timestamp (from firebase/firestore)
|
|
69
|
+
* @returns An admin SDK Timestamp (from firebase-admin) with same seconds/nanoseconds or null if input is null
|
|
70
|
+
*/
|
|
71
|
+
static clientToAdmin(
|
|
72
|
+
clientTimestamp: ClientTimestamp | null
|
|
73
|
+
): admin.firestore.Timestamp | null {
|
|
74
|
+
if (!clientTimestamp) return null;
|
|
75
|
+
|
|
76
|
+
return new admin.firestore.Timestamp(
|
|
77
|
+
clientTimestamp.seconds,
|
|
78
|
+
clientTimestamp.nanoseconds
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
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
|
|
98
|
+
*/
|
|
99
|
+
static nowAsClient(): ClientTimestamp {
|
|
100
|
+
const now = admin.firestore.Timestamp.now();
|
|
101
|
+
return this.adminToClient(now) as ClientTimestamp;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Converts a Date object to a timestamp in the appropriate format based on environment
|
|
106
|
+
* @param date - JavaScript Date object
|
|
107
|
+
* @returns A timestamp (admin timestamp in server mode, client in client mode) or null if input is null
|
|
108
|
+
*/
|
|
109
|
+
static dateToTimestamp(
|
|
110
|
+
date: Date | null
|
|
111
|
+
): admin.firestore.Timestamp | ClientTimestamp | null {
|
|
112
|
+
if (!date) return null;
|
|
113
|
+
|
|
114
|
+
if (this.serverMode) {
|
|
115
|
+
return admin.firestore.Timestamp.fromDate(date);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return ClientTimestamp.fromDate(date);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
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
|
|
131
|
+
*/
|
|
132
|
+
static dateToAdminTimestamp(
|
|
133
|
+
date: Date | null
|
|
134
|
+
): admin.firestore.Timestamp | null {
|
|
135
|
+
if (!date) return null;
|
|
136
|
+
return admin.firestore.Timestamp.fromDate(date);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
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
|
|
186
|
+
*/
|
|
187
|
+
static convertObjectTimestampsAdminToClient<T>(obj: T): T {
|
|
188
|
+
if (!obj || typeof obj !== "object") {
|
|
189
|
+
return obj;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (obj instanceof admin.firestore.Timestamp) {
|
|
193
|
+
return this.adminToClient(obj) as unknown as T;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (Array.isArray(obj)) {
|
|
197
|
+
return obj.map((item) =>
|
|
198
|
+
this.convertObjectTimestampsAdminToClient(item)
|
|
199
|
+
) as unknown as T;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const result = { ...obj } as any;
|
|
203
|
+
|
|
204
|
+
for (const key in result) {
|
|
205
|
+
if (Object.prototype.hasOwnProperty.call(result, key)) {
|
|
206
|
+
result[key] = this.convertObjectTimestampsAdminToClient(result[key]);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return result as T;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @deprecated Use normalizeTimestamps() instead for better cross-environment compatibility
|
|
215
|
+
*/
|
|
216
|
+
static convertObjectTimestampsClientToAdmin<T>(obj: T): T {
|
|
217
|
+
if (!obj || typeof obj !== "object") {
|
|
218
|
+
return obj;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (obj instanceof ClientTimestamp) {
|
|
222
|
+
return this.clientToAdmin(obj) as unknown as T;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (Array.isArray(obj)) {
|
|
226
|
+
return obj.map((item) =>
|
|
227
|
+
this.convertObjectTimestampsClientToAdmin(item)
|
|
228
|
+
) as unknown as T;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const result = { ...obj } as any;
|
|
232
|
+
|
|
233
|
+
for (const key in result) {
|
|
234
|
+
if (Object.prototype.hasOwnProperty.call(result, key)) {
|
|
235
|
+
result[key] = this.convertObjectTimestampsClientToAdmin(result[key]);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return result as T;
|
|
240
|
+
}
|
|
241
|
+
}
|