@blackcode_sa/metaestetics-api 1.5.28 → 1.5.30

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 (49) hide show
  1. package/dist/admin/index.d.mts +1324 -1
  2. package/dist/admin/index.d.ts +1324 -1
  3. package/dist/admin/index.js +1674 -2
  4. package/dist/admin/index.mjs +1668 -2
  5. package/dist/backoffice/index.d.mts +99 -7
  6. package/dist/backoffice/index.d.ts +99 -7
  7. package/dist/index.d.mts +4036 -2372
  8. package/dist/index.d.ts +4036 -2372
  9. package/dist/index.js +2331 -2009
  10. package/dist/index.mjs +2279 -1954
  11. package/package.json +2 -1
  12. package/src/admin/aggregation/README.md +79 -0
  13. package/src/admin/aggregation/clinic/README.md +52 -0
  14. package/src/admin/aggregation/clinic/clinic.aggregation.service.ts +642 -0
  15. package/src/admin/aggregation/patient/README.md +27 -0
  16. package/src/admin/aggregation/patient/patient.aggregation.service.ts +141 -0
  17. package/src/admin/aggregation/practitioner/README.md +42 -0
  18. package/src/admin/aggregation/practitioner/practitioner.aggregation.service.ts +433 -0
  19. package/src/admin/aggregation/procedure/README.md +43 -0
  20. package/src/admin/aggregation/procedure/procedure.aggregation.service.ts +508 -0
  21. package/src/admin/index.ts +60 -4
  22. package/src/admin/mailing/README.md +95 -0
  23. package/src/admin/mailing/base.mailing.service.ts +131 -0
  24. package/src/admin/mailing/index.ts +2 -0
  25. package/src/admin/mailing/practitionerInvite/index.ts +1 -0
  26. package/src/admin/mailing/practitionerInvite/practitionerInvite.mailing.ts +256 -0
  27. package/src/admin/mailing/practitionerInvite/templates/invitation.template.ts +101 -0
  28. package/src/index.ts +28 -4
  29. package/src/services/README.md +106 -0
  30. package/src/services/clinic/README.md +87 -0
  31. package/src/services/clinic/clinic.service.ts +197 -107
  32. package/src/services/clinic/utils/clinic.utils.ts +68 -119
  33. package/src/services/clinic/utils/filter.utils.d.ts +23 -0
  34. package/src/services/clinic/utils/filter.utils.ts +264 -0
  35. package/src/services/practitioner/README.md +145 -0
  36. package/src/services/practitioner/practitioner.service.ts +439 -104
  37. package/src/services/procedure/README.md +88 -0
  38. package/src/services/procedure/procedure.service.ts +521 -311
  39. package/src/services/reviews/reviews.service.ts +842 -0
  40. package/src/types/clinic/index.ts +24 -56
  41. package/src/types/practitioner/index.ts +34 -33
  42. package/src/types/procedure/index.ts +32 -0
  43. package/src/types/profile/index.ts +1 -1
  44. package/src/types/reviews/index.ts +126 -0
  45. package/src/validations/clinic.schema.ts +37 -64
  46. package/src/validations/practitioner.schema.ts +42 -32
  47. package/src/validations/procedure.schema.ts +11 -3
  48. package/src/validations/reviews.schema.ts +189 -0
  49. package/src/services/clinic/utils/review.utils.ts +0 -93
@@ -0,0 +1,508 @@
1
+ import * as admin from "firebase-admin";
2
+ import { PRACTITIONERS_COLLECTION } from "../../../types/practitioner";
3
+ import { PROCEDURES_COLLECTION } from "../../../types/procedure";
4
+ import { CLINICS_COLLECTION } from "../../../types/clinic";
5
+ import { ProcedureSummaryInfo } from "../../../types/procedure";
6
+
7
+ const CALENDAR_SUBCOLLECTION_ID = "calendar";
8
+
9
+ /**
10
+ * @class ProcedureAggregationService
11
+ * @description Handles aggregation tasks related to procedure data updates/deletions.
12
+ */
13
+ export class ProcedureAggregationService {
14
+ private db: admin.firestore.Firestore;
15
+
16
+ constructor(firestore?: admin.firestore.Firestore) {
17
+ this.db = firestore || admin.firestore();
18
+ }
19
+
20
+ /**
21
+ * Adds procedure information to a practitioner when a new procedure is created
22
+ * @param practitionerId - ID of the practitioner who performs the procedure
23
+ * @param procedureSummary - Summary information about the procedure
24
+ * @returns {Promise<void>}
25
+ */
26
+ async addProcedureToPractitioner(
27
+ practitionerId: string,
28
+ procedureSummary: ProcedureSummaryInfo
29
+ ): Promise<void> {
30
+ if (!practitionerId || !procedureSummary) {
31
+ console.log(
32
+ "[ProcedureAggregationService] Missing practitionerId or procedureSummary for adding procedure to practitioner. Skipping."
33
+ );
34
+ return;
35
+ }
36
+
37
+ const procedureId = procedureSummary.id;
38
+ console.log(
39
+ `[ProcedureAggregationService] Adding procedure ${procedureId} to practitioner ${practitionerId}.`
40
+ );
41
+
42
+ const practitionerRef = this.db
43
+ .collection(PRACTITIONERS_COLLECTION)
44
+ .doc(practitionerId);
45
+
46
+ try {
47
+ await practitionerRef.update({
48
+ procedureIds: admin.firestore.FieldValue.arrayUnion(procedureId),
49
+ proceduresInfo: admin.firestore.FieldValue.arrayUnion(procedureSummary),
50
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
51
+ });
52
+
53
+ console.log(
54
+ `[ProcedureAggregationService] Successfully added procedure ${procedureId} to practitioner ${practitionerId}.`
55
+ );
56
+ } catch (error) {
57
+ console.error(
58
+ `[ProcedureAggregationService] Error adding procedure ${procedureId} to practitioner ${practitionerId}:`,
59
+ error
60
+ );
61
+ throw error;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Adds procedure information to a clinic when a new procedure is created
67
+ * @param clinicId - ID of the clinic where the procedure is performed
68
+ * @param procedureSummary - Summary information about the procedure
69
+ * @returns {Promise<void>}
70
+ */
71
+ async addProcedureToClinic(
72
+ clinicId: string,
73
+ procedureSummary: ProcedureSummaryInfo
74
+ ): Promise<void> {
75
+ if (!clinicId || !procedureSummary) {
76
+ console.log(
77
+ "[ProcedureAggregationService] Missing clinicId or procedureSummary for adding procedure to clinic. Skipping."
78
+ );
79
+ return;
80
+ }
81
+
82
+ const procedureId = procedureSummary.id;
83
+ console.log(
84
+ `[ProcedureAggregationService] Adding procedure ${procedureId} to clinic ${clinicId}.`
85
+ );
86
+
87
+ const clinicRef = this.db.collection(CLINICS_COLLECTION).doc(clinicId);
88
+
89
+ try {
90
+ await clinicRef.update({
91
+ procedures: admin.firestore.FieldValue.arrayUnion(procedureId),
92
+ proceduresInfo: admin.firestore.FieldValue.arrayUnion(procedureSummary),
93
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
94
+ });
95
+
96
+ console.log(
97
+ `[ProcedureAggregationService] Successfully added procedure ${procedureId} to clinic ${clinicId}.`
98
+ );
99
+ } catch (error) {
100
+ console.error(
101
+ `[ProcedureAggregationService] Error adding procedure ${procedureId} to clinic ${clinicId}:`,
102
+ error
103
+ );
104
+ throw error;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Updates procedure information in a practitioner document
110
+ * @param practitionerId - ID of the practitioner who performs the procedure
111
+ * @param procedureSummary - Updated summary information about the procedure
112
+ * @returns {Promise<void>}
113
+ */
114
+ async updateProcedureInfoInPractitioner(
115
+ practitionerId: string,
116
+ procedureSummary: ProcedureSummaryInfo
117
+ ): Promise<void> {
118
+ if (!practitionerId || !procedureSummary) {
119
+ console.log(
120
+ "[ProcedureAggregationService] Missing practitionerId or procedureSummary for updating procedure in practitioner. Skipping."
121
+ );
122
+ return;
123
+ }
124
+
125
+ const procedureId = procedureSummary.id;
126
+ console.log(
127
+ `[ProcedureAggregationService] Updating procedure ${procedureId} info in practitioner ${practitionerId}.`
128
+ );
129
+
130
+ const practitionerRef = this.db
131
+ .collection(PRACTITIONERS_COLLECTION)
132
+ .doc(practitionerId);
133
+
134
+ // This requires a transaction to avoid race conditions
135
+ try {
136
+ await this.db.runTransaction(async (transaction) => {
137
+ const practitionerDoc = await transaction.get(practitionerRef);
138
+ if (!practitionerDoc.exists) {
139
+ throw new Error(
140
+ `Practitioner ${practitionerId} does not exist for procedure update`
141
+ );
142
+ }
143
+
144
+ const practitionerData = practitionerDoc.data();
145
+ if (!practitionerData) {
146
+ throw new Error(
147
+ `Practitioner ${practitionerId} data is empty for procedure update`
148
+ );
149
+ }
150
+
151
+ // Get current procedures info array
152
+ const proceduresInfo = practitionerData.proceduresInfo || [];
153
+
154
+ // Remove the old procedure summary
155
+ const updatedProceduresInfo = proceduresInfo.filter(
156
+ (p: ProcedureSummaryInfo) => p.id !== procedureId
157
+ );
158
+
159
+ // Add the updated procedure summary
160
+ updatedProceduresInfo.push(procedureSummary);
161
+
162
+ // Update the practitioner document
163
+ transaction.update(practitionerRef, {
164
+ proceduresInfo: updatedProceduresInfo,
165
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
166
+ });
167
+ });
168
+
169
+ console.log(
170
+ `[ProcedureAggregationService] Successfully updated procedure ${procedureId} info in practitioner ${practitionerId}.`
171
+ );
172
+ } catch (error) {
173
+ console.error(
174
+ `[ProcedureAggregationService] Error updating procedure ${procedureId} info in practitioner ${practitionerId}:`,
175
+ error
176
+ );
177
+ throw error;
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Updates procedure information in a clinic document
183
+ * @param clinicId - ID of the clinic where the procedure is performed
184
+ * @param procedureSummary - Updated summary information about the procedure
185
+ * @returns {Promise<void>}
186
+ */
187
+ async updateProcedureInfoInClinic(
188
+ clinicId: string,
189
+ procedureSummary: ProcedureSummaryInfo
190
+ ): Promise<void> {
191
+ if (!clinicId || !procedureSummary) {
192
+ console.log(
193
+ "[ProcedureAggregationService] Missing clinicId or procedureSummary for updating procedure in clinic. Skipping."
194
+ );
195
+ return;
196
+ }
197
+
198
+ const procedureId = procedureSummary.id;
199
+ console.log(
200
+ `[ProcedureAggregationService] Updating procedure ${procedureId} info in clinic ${clinicId}.`
201
+ );
202
+
203
+ const clinicRef = this.db.collection(CLINICS_COLLECTION).doc(clinicId);
204
+
205
+ // This requires a transaction to avoid race conditions
206
+ try {
207
+ await this.db.runTransaction(async (transaction) => {
208
+ const clinicDoc = await transaction.get(clinicRef);
209
+ if (!clinicDoc.exists) {
210
+ throw new Error(
211
+ `Clinic ${clinicId} does not exist for procedure update`
212
+ );
213
+ }
214
+
215
+ const clinicData = clinicDoc.data();
216
+ if (!clinicData) {
217
+ throw new Error(
218
+ `Clinic ${clinicId} data is empty for procedure update`
219
+ );
220
+ }
221
+
222
+ // Get current procedures info array
223
+ const proceduresInfo = clinicData.proceduresInfo || [];
224
+
225
+ // Remove the old procedure summary
226
+ const updatedProceduresInfo = proceduresInfo.filter(
227
+ (p: ProcedureSummaryInfo) => p.id !== procedureId
228
+ );
229
+
230
+ // Add the updated procedure summary
231
+ updatedProceduresInfo.push(procedureSummary);
232
+
233
+ // Update the clinic document
234
+ transaction.update(clinicRef, {
235
+ proceduresInfo: updatedProceduresInfo,
236
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
237
+ });
238
+ });
239
+
240
+ console.log(
241
+ `[ProcedureAggregationService] Successfully updated procedure ${procedureId} info in clinic ${clinicId}.`
242
+ );
243
+ } catch (error) {
244
+ console.error(
245
+ `[ProcedureAggregationService] Error updating procedure ${procedureId} info in clinic ${clinicId}:`,
246
+ error
247
+ );
248
+ throw error;
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Updates procedure information in calendar events
254
+ * @param procedureId - ID of the procedure
255
+ * @param procedureInfo - Updated procedure information
256
+ * @returns {Promise<void>}
257
+ */
258
+ async updateProcedureInfoInCalendarEvents(
259
+ procedureId: string,
260
+ procedureInfo: any
261
+ ): Promise<void> {
262
+ if (!procedureId || !procedureInfo) {
263
+ console.log(
264
+ "[ProcedureAggregationService] Missing procedureId or procedureInfo for calendar update. Skipping."
265
+ );
266
+ return;
267
+ }
268
+
269
+ console.log(
270
+ `[ProcedureAggregationService] Querying upcoming calendar events for procedure ${procedureId} to update procedure info.`
271
+ );
272
+
273
+ const now = admin.firestore.Timestamp.now();
274
+ // Use a Collection Group query
275
+ const calendarEventsQuery = this.db
276
+ .collectionGroup(CALENDAR_SUBCOLLECTION_ID)
277
+ .where("procedureId", "==", procedureId)
278
+ .where("eventTime.start", ">", now);
279
+
280
+ try {
281
+ const snapshot = await calendarEventsQuery.get();
282
+ if (snapshot.empty) {
283
+ console.log(
284
+ `[ProcedureAggregationService] No upcoming calendar events found for procedure ${procedureId}. No procedure info updates needed.`
285
+ );
286
+ return;
287
+ }
288
+
289
+ const batch = this.db.batch();
290
+ snapshot.docs.forEach((doc) => {
291
+ console.log(
292
+ `[ProcedureAggregationService] Updating procedure info for calendar event ${doc.ref.path}`
293
+ );
294
+ batch.update(doc.ref, {
295
+ procedureInfo: procedureInfo,
296
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
297
+ });
298
+ });
299
+
300
+ await batch.commit();
301
+ console.log(
302
+ `[ProcedureAggregationService] Successfully updated procedure info in ${snapshot.size} upcoming calendar events for procedure ${procedureId}.`
303
+ );
304
+ } catch (error) {
305
+ console.error(
306
+ `[ProcedureAggregationService] Error updating procedure info in calendar events for procedure ${procedureId}:`,
307
+ error
308
+ );
309
+ throw error;
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Cancels all upcoming calendar events for a procedure
315
+ * @param procedureId - ID of the procedure
316
+ * @returns {Promise<void>}
317
+ */
318
+ async cancelUpcomingCalendarEventsForProcedure(
319
+ procedureId: string
320
+ ): Promise<void> {
321
+ if (!procedureId) {
322
+ console.log(
323
+ "[ProcedureAggregationService] Missing procedureId for canceling calendar events. Skipping."
324
+ );
325
+ return;
326
+ }
327
+
328
+ console.log(
329
+ `[ProcedureAggregationService] Querying upcoming calendar events for procedure ${procedureId} to cancel.`
330
+ );
331
+
332
+ const now = admin.firestore.Timestamp.now();
333
+ // Use a Collection Group query
334
+ const calendarEventsQuery = this.db
335
+ .collectionGroup(CALENDAR_SUBCOLLECTION_ID)
336
+ .where("procedureId", "==", procedureId)
337
+ .where("eventTime.start", ">", now);
338
+
339
+ try {
340
+ const snapshot = await calendarEventsQuery.get();
341
+ if (snapshot.empty) {
342
+ console.log(
343
+ `[ProcedureAggregationService] No upcoming calendar events found for procedure ${procedureId}. No events to cancel.`
344
+ );
345
+ return;
346
+ }
347
+
348
+ const batch = this.db.batch();
349
+ snapshot.docs.forEach((doc) => {
350
+ console.log(
351
+ `[ProcedureAggregationService] Canceling calendar event ${doc.ref.path}`
352
+ );
353
+ batch.update(doc.ref, {
354
+ status: "CANCELED",
355
+ cancelReason: "Procedure deleted or inactivated",
356
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
357
+ });
358
+ });
359
+
360
+ await batch.commit();
361
+ console.log(
362
+ `[ProcedureAggregationService] Successfully canceled ${snapshot.size} upcoming calendar events for procedure ${procedureId}.`
363
+ );
364
+ } catch (error) {
365
+ console.error(
366
+ `[ProcedureAggregationService] Error canceling calendar events for procedure ${procedureId}:`,
367
+ error
368
+ );
369
+ throw error;
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Removes procedure from a practitioner when a procedure is deleted or inactivated
375
+ * @param practitionerId - ID of the practitioner who performs the procedure
376
+ * @param procedureId - ID of the procedure
377
+ * @returns {Promise<void>}
378
+ */
379
+ async removeProcedureFromPractitioner(
380
+ practitionerId: string,
381
+ procedureId: string
382
+ ): Promise<void> {
383
+ if (!practitionerId || !procedureId) {
384
+ console.log(
385
+ "[ProcedureAggregationService] Missing practitionerId or procedureId for removing procedure from practitioner. Skipping."
386
+ );
387
+ return;
388
+ }
389
+
390
+ console.log(
391
+ `[ProcedureAggregationService] Removing procedure ${procedureId} from practitioner ${practitionerId}.`
392
+ );
393
+
394
+ const practitionerRef = this.db
395
+ .collection(PRACTITIONERS_COLLECTION)
396
+ .doc(practitionerId);
397
+
398
+ try {
399
+ await this.db.runTransaction(async (transaction) => {
400
+ const practitionerDoc = await transaction.get(practitionerRef);
401
+ if (!practitionerDoc.exists) {
402
+ throw new Error(
403
+ `Practitioner ${practitionerId} does not exist for procedure removal`
404
+ );
405
+ }
406
+
407
+ const practitionerData = practitionerDoc.data();
408
+ if (!practitionerData) {
409
+ throw new Error(
410
+ `Practitioner ${practitionerId} data is empty for procedure removal`
411
+ );
412
+ }
413
+
414
+ // Get current procedures info array
415
+ const proceduresInfo = practitionerData.proceduresInfo || [];
416
+
417
+ // Remove the procedure summary
418
+ const updatedProceduresInfo = proceduresInfo.filter(
419
+ (p: ProcedureSummaryInfo) => p.id !== procedureId
420
+ );
421
+
422
+ // Update the practitioner document
423
+ transaction.update(practitionerRef, {
424
+ procedureIds: admin.firestore.FieldValue.arrayRemove(procedureId),
425
+ proceduresInfo: updatedProceduresInfo,
426
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
427
+ });
428
+ });
429
+
430
+ console.log(
431
+ `[ProcedureAggregationService] Successfully removed procedure ${procedureId} from practitioner ${practitionerId}.`
432
+ );
433
+ } catch (error) {
434
+ console.error(
435
+ `[ProcedureAggregationService] Error removing procedure ${procedureId} from practitioner ${practitionerId}:`,
436
+ error
437
+ );
438
+ throw error;
439
+ }
440
+ }
441
+
442
+ /**
443
+ * Removes procedure from a clinic when a procedure is deleted or inactivated
444
+ * @param clinicId - ID of the clinic where the procedure is performed
445
+ * @param procedureId - ID of the procedure
446
+ * @returns {Promise<void>}
447
+ */
448
+ async removeProcedureFromClinic(
449
+ clinicId: string,
450
+ procedureId: string
451
+ ): Promise<void> {
452
+ if (!clinicId || !procedureId) {
453
+ console.log(
454
+ "[ProcedureAggregationService] Missing clinicId or procedureId for removing procedure from clinic. Skipping."
455
+ );
456
+ return;
457
+ }
458
+
459
+ console.log(
460
+ `[ProcedureAggregationService] Removing procedure ${procedureId} from clinic ${clinicId}.`
461
+ );
462
+
463
+ const clinicRef = this.db.collection(CLINICS_COLLECTION).doc(clinicId);
464
+
465
+ try {
466
+ await this.db.runTransaction(async (transaction) => {
467
+ const clinicDoc = await transaction.get(clinicRef);
468
+ if (!clinicDoc.exists) {
469
+ throw new Error(
470
+ `Clinic ${clinicId} does not exist for procedure removal`
471
+ );
472
+ }
473
+
474
+ const clinicData = clinicDoc.data();
475
+ if (!clinicData) {
476
+ throw new Error(
477
+ `Clinic ${clinicId} data is empty for procedure removal`
478
+ );
479
+ }
480
+
481
+ // Get current procedures info array
482
+ const proceduresInfo = clinicData.proceduresInfo || [];
483
+
484
+ // Remove the procedure summary
485
+ const updatedProceduresInfo = proceduresInfo.filter(
486
+ (p: ProcedureSummaryInfo) => p.id !== procedureId
487
+ );
488
+
489
+ // Update the clinic document
490
+ transaction.update(clinicRef, {
491
+ procedures: admin.firestore.FieldValue.arrayRemove(procedureId),
492
+ proceduresInfo: updatedProceduresInfo,
493
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
494
+ });
495
+ });
496
+
497
+ console.log(
498
+ `[ProcedureAggregationService] Successfully removed procedure ${procedureId} from clinic ${clinicId}.`
499
+ );
500
+ } catch (error) {
501
+ console.error(
502
+ `[ProcedureAggregationService] Error removing procedure ${procedureId} from clinic ${clinicId}:`,
503
+ error
504
+ );
505
+ throw error;
506
+ }
507
+ }
508
+ }
@@ -5,8 +5,25 @@ import {
5
5
  NotificationType,
6
6
  } from "../types/notifications";
7
7
  import { UserRole } from "../types";
8
+ // Import types needed by admin consumers (like Cloud Functions)
9
+ import { Clinic, ClinicLocation } from "../types/clinic";
10
+ import { ClinicInfo } from "../types/profile";
11
+ import { Practitioner, PractitionerToken } from "../types/practitioner";
12
+ import { DoctorInfo } from "../types/clinic";
13
+ import { Procedure, ProcedureSummaryInfo } from "../types/procedure";
14
+ import { PatientProfile } from "../types/patient";
8
15
 
9
- // Re-export tipova koji su potrebni za admin modul
16
+ // Explicitly import the services to re-export them by name
17
+ import { ClinicAggregationService } from "./aggregation/clinic/clinic.aggregation.service";
18
+ import { PractitionerAggregationService } from "./aggregation/practitioner/practitioner.aggregation.service";
19
+ import { ProcedureAggregationService } from "./aggregation/procedure/procedure.aggregation.service";
20
+ import { PatientAggregationService } from "./aggregation/patient/patient.aggregation.service";
21
+
22
+ // Import mailing services
23
+ import { BaseMailingService } from "./mailing/base.mailing.service";
24
+ import { PractitionerInviteMailingService } from "./mailing/practitionerInvite/practitionerInvite.mailing";
25
+
26
+ // Re-export types
10
27
  export type {
11
28
  Notification,
12
29
  BaseNotification,
@@ -16,13 +33,52 @@ export type {
16
33
  AppointmentNotification,
17
34
  } from "../types/notifications";
18
35
 
36
+ // Re-export types needed by cloud functions
37
+ export type { Clinic, ClinicLocation } from "../types/clinic";
38
+ export type { ClinicInfo } from "../types/profile";
39
+ export type { Practitioner, PractitionerToken } from "../types/practitioner";
40
+ export type { DoctorInfo } from "../types/clinic";
41
+ export type { Procedure, ProcedureSummaryInfo } from "../types/procedure";
42
+ export type { PatientProfile as Patient } from "../types/patient";
43
+
44
+ // Re-export enums/consts
19
45
  export {
20
46
  NotificationType,
21
47
  NotificationStatus,
22
48
  NOTIFICATIONS_COLLECTION,
23
49
  } from "../types/notifications";
24
-
25
50
  export { UserRole } from "../types";
26
51
 
27
- // Export admin klasa
28
- export { NotificationsAdmin };
52
+ // Export admin classes/services explicitly by name
53
+ export {
54
+ NotificationsAdmin,
55
+ ClinicAggregationService,
56
+ PractitionerAggregationService,
57
+ ProcedureAggregationService,
58
+ PatientAggregationService,
59
+ };
60
+
61
+ // Export mailing services
62
+ export { BaseMailingService, PractitionerInviteMailingService };
63
+
64
+ /**
65
+ * Main entry point for the Admin module.
66
+ * This module contains services and utilities intended for administrative tasks,
67
+ * background processing, and potentially direct use by admin interfaces or Cloud Functions.
68
+ */
69
+
70
+ // --- Aggregation Services --- //
71
+ // Placeholder: export * from "./aggregation/practitioner/practitioner.aggregation.service";
72
+ // Placeholder: export * from "./aggregation/procedure/procedure.aggregation.service";
73
+ // Placeholder: export * from "./aggregation/patient/patient.aggregation.service";
74
+
75
+ // --- Other Admin Services/Utilities (Add as needed) --- //
76
+ // Example: export * from './user-management/user-management.service';
77
+ // Example: export * from './reporting/reporting.service';
78
+
79
+ console.log("[Admin Module] Initialized and services exported.");
80
+
81
+ // Note: Ensure that services exported here are properly initialized
82
+ // if they have dependencies (like Firestore db) that need to be injected.
83
+ // The initialization pattern might differ depending on how this module is consumed
84
+ // (e.g., within a larger NestJS app vs. standalone Cloud Functions).
@@ -0,0 +1,95 @@
1
+ # Mailing Services
2
+
3
+ This module provides mailing services for sending automated emails from the application using Mailgun.
4
+
5
+ ## Setup
6
+
7
+ ### 1. Install Dependencies
8
+
9
+ Make sure to install the required dependencies:
10
+
11
+ ```bash
12
+ npm install mailgun-js firebase-admin firebase-functions
13
+ ```
14
+
15
+ ### 2. Configure Mailgun
16
+
17
+ To use the mailing services, you need to configure Mailgun credentials in your Firebase project:
18
+
19
+ ```bash
20
+ firebase functions:config:set mailgun.api_key="your-mailgun-api-key" mailgun.domain="your-mailgun-domain" mailgun.from="MedClinic <no-reply@your-domain.com>"
21
+ ```
22
+
23
+ To enable test mode (emails won't actually be sent):
24
+
25
+ ```bash
26
+ firebase functions:config:set mailgun.test_mode="true"
27
+ ```
28
+
29
+ ### 3. Deploy Cloud Functions
30
+
31
+ Deploy the cloud functions to start handling email sending events:
32
+
33
+ ```bash
34
+ firebase deploy --only functions
35
+ ```
36
+
37
+ ## Available Services
38
+
39
+ ### Practitioner Invitation Service
40
+
41
+ Sends email invitations to practitioners when they are invited to join a clinic.
42
+
43
+ #### How it works:
44
+
45
+ 1. When a new practitioner token is created in the Firestore database, the `onPractitionerTokenCreated` cloud function is triggered.
46
+ 2. The function uses the `PractitionerInviteMailingService` to send an invitation email to the practitioner.
47
+ 3. The email contains the token needed to claim their profile and instructions for registration.
48
+
49
+ #### Example Usage in Code:
50
+
51
+ ```typescript
52
+ import { PractitionerInviteMailingService } from "./admin/mailing";
53
+
54
+ // Create a new instance of the service
55
+ const mailingService = new PractitionerInviteMailingService();
56
+
57
+ // Send an invitation email
58
+ await mailingService.sendInvitationEmail({
59
+ token: {
60
+ id: "token-id",
61
+ token: "ABC123",
62
+ practitionerId: "practitioner-id",
63
+ email: "doctor@example.com",
64
+ clinicId: "clinic-id",
65
+ expiresAt: admin.firestore.Timestamp.fromDate(new Date()),
66
+ },
67
+ practitioner: {
68
+ firstName: "John",
69
+ lastName: "Doe",
70
+ },
71
+ clinic: {
72
+ name: "Example Clinic",
73
+ contactEmail: "admin@example.com",
74
+ contactName: "Clinic Admin",
75
+ },
76
+ options: {
77
+ registrationUrl: "https://your-app.com/register",
78
+ customSubject: "Join Our Clinic",
79
+ },
80
+ });
81
+ ```
82
+
83
+ ## Adding New Mailing Services
84
+
85
+ To add a new mailing service:
86
+
87
+ 1. Create a new directory in the `mailing` folder for your service
88
+ 2. Create a service class that extends `BaseMailingService`
89
+ 3. Create an HTML template in a `templates` subfolder
90
+ 4. Create a cloud function to trigger the email sending
91
+ 5. Export your new service in the index files
92
+
93
+ ## Logging
94
+
95
+ All email sending attempts are logged to the `email_logs` collection in Firestore for tracking and debugging purposes.