@blackcode_sa/metaestetics-api 1.5.29 → 1.5.31

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 (30) hide show
  1. package/dist/admin/index.d.mts +126 -1
  2. package/dist/admin/index.d.ts +126 -1
  3. package/dist/admin/index.js +347 -10
  4. package/dist/admin/index.mjs +345 -10
  5. package/dist/index.d.mts +64 -71
  6. package/dist/index.d.ts +64 -71
  7. package/dist/index.js +327 -710
  8. package/dist/index.mjs +363 -750
  9. package/package.json +2 -1
  10. package/src/admin/aggregation/README.md +79 -0
  11. package/src/admin/aggregation/clinic/README.md +52 -0
  12. package/src/admin/aggregation/patient/README.md +27 -0
  13. package/src/admin/aggregation/practitioner/README.md +42 -0
  14. package/src/admin/aggregation/procedure/README.md +43 -0
  15. package/src/admin/index.ts +17 -2
  16. package/src/admin/mailing/README.md +95 -0
  17. package/src/admin/mailing/base.mailing.service.ts +131 -0
  18. package/src/admin/mailing/index.ts +2 -0
  19. package/src/admin/mailing/practitionerInvite/index.ts +1 -0
  20. package/src/admin/mailing/practitionerInvite/practitionerInvite.mailing.ts +256 -0
  21. package/src/admin/mailing/practitionerInvite/templates/invitation.template.ts +101 -0
  22. package/src/services/README.md +106 -0
  23. package/src/services/calendar/utils/appointment.utils.ts +42 -91
  24. package/src/services/clinic/README.md +87 -0
  25. package/src/services/clinic/clinic.service.ts +3 -126
  26. package/src/services/clinic/utils/clinic.utils.ts +2 -2
  27. package/src/services/practitioner/README.md +145 -0
  28. package/src/services/practitioner/practitioner.service.ts +119 -395
  29. package/src/services/procedure/README.md +88 -0
  30. package/src/services/procedure/procedure.service.ts +332 -369
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blackcode_sa/metaestetics-api",
3
3
  "private": false,
4
- "version": "1.5.29",
4
+ "version": "1.5.31",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
@@ -83,6 +83,7 @@
83
83
  "devDependencies": {
84
84
  "@testing-library/jest-dom": "^6.6.3",
85
85
  "@types/jest": "^29.5.14",
86
+ "@types/mailgun-js": "^0.22.18",
86
87
  "@types/node": "^20.17.13",
87
88
  "@types/react": "~18.3.12",
88
89
  "expo-crypto": "^14.0.0",
@@ -0,0 +1,79 @@
1
+ # Data Aggregation Services
2
+
3
+ This directory contains services responsible for maintaining data consistency across related Firestore collections. These aggregation services are typically used by Cloud Functions that react to document changes (create/update/delete operations).
4
+
5
+ ## Purpose
6
+
7
+ When a document is created, updated, or deleted in one collection, related documents in other collections often need to be updated to maintain data consistency and support efficient querying. For example:
8
+
9
+ - When a practitioner is updated, their information needs to be updated in associated clinics
10
+ - When a procedure is added, summaries need to be added to both practitioner and clinic documents
11
+ - When a clinic is deleted, related calendar events need to be canceled
12
+
13
+ Rather than embedding this logic in the main service classes, these aggregation services keep the concerns separate and can be independently triggered by Cloud Functions.
14
+
15
+ ## Design Pattern
16
+
17
+ Each aggregation service:
18
+
19
+ 1. Handles a specific domain entity (clinic, practitioner, procedure, etc.)
20
+ 2. Contains methods organized by event type (creation, update, deletion)
21
+ 3. Focuses solely on updating related collections
22
+ 4. Uses transactions where appropriate to ensure consistency
23
+
24
+ ## Available Aggregation Services
25
+
26
+ - `ClinicAggregationService`: Handles clinic-related aggregations
27
+ - `PractitionerAggregationService`: Handles practitioner-related aggregations
28
+ - `ProcedureAggregationService`: Handles procedure-related aggregations
29
+ - `PatientAggregationService`: Handles patient-related aggregations
30
+
31
+ ## Implementation Notes
32
+
33
+ - These services are for admin/internal use only
34
+ - They operate with admin Firestore privileges
35
+ - They are designed for use by Cloud Functions, not directly by client applications
36
+ - They include detailed logging for monitoring and debugging
37
+
38
+ ## Example Usage
39
+
40
+ In a Cloud Function:
41
+
42
+ ```typescript
43
+ exports.onPractitionerUpdate = functions.firestore
44
+ .document('practitioners/{practitionerId}')
45
+ .onUpdate(async (change, context) => {
46
+ const beforeData = change.before.data();
47
+ const afterData = change.after.data();
48
+ const practitionerId = context.params.practitionerId;
49
+
50
+ // Only run aggregation if specific fields changed
51
+ if (beforeData.basicInfo.firstName !== afterData.basicInfo.firstName ||
52
+ beforeData.basicInfo.lastName !== afterData.basicInfo.lastName ||
53
+ beforeData.basicInfo.profileImageUrl !== afterData.basicInfo.profileImageUrl) {
54
+
55
+ const aggregationService = new PractitionerAggregationService();
56
+
57
+ // Build doctorInfo object from updated data
58
+ const doctorInfo = {
59
+ id: practitionerId,
60
+ name: `${afterData.basicInfo.firstName} ${afterData.basicInfo.lastName}`,
61
+ photo: afterData.basicInfo.profileImageUrl || '',
62
+ // other fields...
63
+ };
64
+
65
+ // Update practitioner info in all related clinics
66
+ await aggregationService.updatePractitionerInfoInClinics(
67
+ afterData.clinics,
68
+ doctorInfo
69
+ );
70
+
71
+ // Update practitioner info in all related procedures
72
+ await aggregationService.updatePractitionerInfoInProcedures(
73
+ afterData.procedures,
74
+ doctorInfo
75
+ );
76
+ }
77
+ });
78
+ </rewritten_file>
79
+ ```
@@ -0,0 +1,52 @@
1
+ # Clinic Aggregation Service (Admin)
2
+
3
+ This service centralizes the logic for updating aggregated data in various Firestore collections _when a clinic is created, updated, or deleted_. It is designed primarily for use by Cloud Functions triggered by changes in the `clinics` collection.
4
+
5
+ ## `ClinicAggregationService` Class
6
+
7
+ ### Constructor
8
+
9
+ ```typescript
10
+ constructor(firestore?: admin.firestore.Firestore)
11
+ ```
12
+
13
+ Initializes the service with an Admin Firestore instance. Uses the default instance if none is provided.
14
+
15
+ ### Methods Triggered by Clinic **Creation**
16
+
17
+ - **`addClinicToClinicGroup(clinicGroupId: string, clinicInfo: ClinicInfo): Promise<void>`**
18
+ - Adds the `clinicInfo` object and `clinicId` to the `clinicsInfo` and `clinicIds` arrays respectively in the specified parent `ClinicGroup` document.
19
+
20
+ ### Methods Triggered by Clinic **Update**
21
+
22
+ - **`updateClinicInfoInPractitioners(practitionerIds: string[], clinicInfo: ClinicInfo): Promise<void>`**
23
+ - Updates the aggregated `clinicsInfo` array within multiple `Practitioner` documents.
24
+ - Uses a batch write to first remove the old `ClinicInfo` (matching by `id`) and then add the updated `clinicInfo`.
25
+ - **`updateClinicInfoInProcedures(procedureIds: string[], clinicInfo: ClinicInfo): Promise<void>`**
26
+ - Updates the embedded `clinicInfo` object within multiple `Procedure` documents associated with the updated clinic.
27
+ - **`updateClinicInfoInClinicGroup(clinicGroupId: string, clinicInfo: ClinicInfo): Promise<void>`**
28
+ - Updates the `clinicsInfo` array within the parent `ClinicGroup` document.
29
+ - Uses a batch write to first remove the old `ClinicInfo` (matching by `id`) and then add the updated `clinicInfo`.
30
+ - **`updateClinicInfoInPatients(patientIds: string[], clinicInfo: ClinicInfo, clinicIsActive: boolean): Promise<void>`**
31
+ - Currently logs a warning as the `PatientProfile` doesn't store full `ClinicInfo`.
32
+ - Intended place for logic if patient records need updates based on clinic changes (e.g., clinic deactivation).
33
+ - **`updateClinicLocationInCalendarEvents(clinicId: string, newLocation: ClinicLocation): Promise<void>`**
34
+ - Uses a Collection Group query (`calendar` subcollection) to find all _upcoming_ calendar events associated with the `clinicId`.
35
+ - Updates the `eventLocation` field in these calendar events with the `newLocation`.
36
+ - **`updateClinicInfoInCalendarEvents(clinicId: string, clinicInfo: ClinicInfo): Promise<void>`**
37
+ - Uses a Collection Group query (`calendar` subcollection) to find all _upcoming_ calendar events associated with the `clinicId`.
38
+ - Updates the embedded `clinicInfo` object in these calendar events.
39
+
40
+ ### Methods Triggered by Clinic **Deletion**
41
+
42
+ - **`removeClinicFromPractitioners(practitionerIds: string[], clinicId: string): Promise<void>`**
43
+ - Removes the `clinicId` from the `clinicIds` array and the corresponding `ClinicInfo` object (matching by `id`) from the `clinicsInfo` array in multiple `Practitioner` documents.
44
+ - **`inactivateProceduresForClinic(procedureIds: string[]): Promise<void>`**
45
+ - Sets the `isActive` flag to `false` for multiple `Procedure` documents associated with the deleted clinic.
46
+ - **`removeClinicFromClinicGroup(clinicGroupId: string, clinicId: string): Promise<void>`**
47
+ - Removes the `clinicId` from the `clinicIds` array and the corresponding `ClinicInfo` object (matching by `id`) from the `clinicsInfo` array in the parent `ClinicGroup` document.
48
+ - **`removeClinicFromPatients(patientIds: string[], clinicId: string): Promise<void>`**
49
+ - Removes the `clinicId` from the `clinicIds` array in multiple `Patient` documents.
50
+ - **`cancelUpcomingCalendarEventsForClinic(clinicId: string): Promise<void>`**
51
+ - Uses a Collection Group query (`calendar` subcollection) to find all _upcoming_ calendar events associated with the `clinicId`.
52
+ - Sets the `status` of these events to `CANCELED` and adds a `cancelReason`.
@@ -0,0 +1,27 @@
1
+ # Patient Aggregation Service (Admin)
2
+
3
+ This service centralizes the logic for updating aggregated data in various Firestore collections _when patient data is updated or deleted_. It is designed primarily for use by Cloud Functions triggered by changes in the `patients` collection.
4
+
5
+ **Note:** No specific aggregations are currently defined for patient _creation_ events in this service.
6
+
7
+ ## `PatientAggregationService` Class
8
+
9
+ ### Constructor
10
+
11
+ ```typescript
12
+ constructor(firestore?: admin.firestore.Firestore)
13
+ ```
14
+
15
+ Initializes the service with an Admin Firestore instance. Uses the default instance if none is provided.
16
+
17
+ ### Methods Triggered by Patient **Update**
18
+
19
+ - **`updatePatientInfoInCalendarEvents(patientId: string, patientInfo: any): Promise<void>`**
20
+ - Uses a Collection Group query (`calendar` subcollection) to find all _upcoming_ calendar events associated with the `patientId`.
21
+ - Updates the embedded `patientInfo` object in these calendar events with the provided `patientInfo`.
22
+
23
+ ### Methods Triggered by Patient **Deletion**
24
+
25
+ - **`cancelUpcomingCalendarEventsForPatient(patientId: string): Promise<void>`**
26
+ - Uses a Collection Group query (`calendar` subcollection) to find all _upcoming_ calendar events associated with the `patientId`.
27
+ - Sets the `status` of these events to `CANCELED` and adds a `cancelReason` ("Patient deleted").
@@ -0,0 +1,42 @@
1
+ # Practitioner Aggregation Service (Admin)
2
+
3
+ This service centralizes the logic for updating aggregated data in various Firestore collections _when a practitioner is created, updated, or deleted_. It is designed primarily for use by Cloud Functions triggered by changes in the `practitioners` collection.
4
+
5
+ ## `PractitionerAggregationService` Class
6
+
7
+ ### Constructor
8
+
9
+ ```typescript
10
+ constructor(firestore?: admin.firestore.Firestore)
11
+ ```
12
+
13
+ Initializes the service with an Admin Firestore instance. Uses the default instance if none is provided.
14
+
15
+ ### Methods Triggered by Practitioner **Creation**
16
+
17
+ - **`addPractitionerToClinic(clinicId: string, doctorInfo: DoctorInfo): Promise<void>`**
18
+ - Adds the `practitionerId` (from `doctorInfo.id`) and the `doctorInfo` object to the `doctors` and `doctorsInfo` arrays respectively in the specified `Clinic` document.
19
+ - Typically called for each clinic the new practitioner is associated with.
20
+
21
+ ### Methods Triggered by Practitioner **Update**
22
+
23
+ - **`updatePractitionerInfoInClinics(clinicIds: string[], doctorInfo: DoctorInfo): Promise<void>`**
24
+ - Updates the aggregated `doctorsInfo` array within multiple `Clinic` documents associated with the practitioner.
25
+ - Uses a batch write to first remove the old `DoctorInfo` (matching by `id`) and then add the updated `doctorInfo`.
26
+ - **`updatePractitionerInfoInProcedures(procedureIds: string[], doctorInfo: DoctorInfo): Promise<void>`**
27
+ - Updates the embedded `doctorInfo` object within multiple `Procedure` documents associated with the updated practitioner.
28
+ - **`updatePractitionerInfoInCalendarEvents(practitionerId: string, practitionerInfo: DoctorInfo): Promise<void>`**
29
+ - Uses a Collection Group query (`calendar` subcollection) to find all _upcoming_ calendar events associated with the `practitionerId`.
30
+ - Updates the embedded `practitionerInfo` object in these calendar events.
31
+
32
+ ### Methods Triggered by Practitioner **Deletion**
33
+
34
+ - **`removePractitionerFromClinics(clinicIds: string[], practitionerId: string): Promise<void>`**
35
+ - Removes the `practitionerId` from the `doctors` array and the corresponding `DoctorInfo` object (matching by `id`) from the `doctorsInfo` array in multiple `Clinic` documents.
36
+ - **`cancelUpcomingCalendarEventsForPractitioner(practitionerId: string): Promise<void>`**
37
+ - Uses a Collection Group query (`calendar` subcollection) to find all _upcoming_ calendar events associated with the `practitionerId`.
38
+ - Sets the `status` of these events to `CANCELED` and adds a `cancelReason` ("Practitioner deleted").
39
+ - **`removePractitionerFromPatients(patientIds: string[], practitionerId: string): Promise<void>`**
40
+ - Removes the `practitionerId` from the `doctorIds` array in multiple `Patient` documents.
41
+ - **`inactivateProceduresForPractitioner(procedureIds: string[]): Promise<void>`**
42
+ - Sets the `isActive` flag to `false` for multiple `Procedure` documents associated with the deleted practitioner.
@@ -0,0 +1,43 @@
1
+ # Procedure Aggregation Service (Admin)
2
+
3
+ This service centralizes the logic for updating aggregated data in various Firestore collections _when a procedure is created, updated, or deleted/inactivated_. It is designed primarily for use by Cloud Functions triggered by changes in the `procedures` collection.
4
+
5
+ ## `ProcedureAggregationService` Class
6
+
7
+ ### Constructor
8
+
9
+ ```typescript
10
+ constructor(firestore?: admin.firestore.Firestore)
11
+ ```
12
+
13
+ Initializes the service with an Admin Firestore instance. Uses the default instance if none is provided.
14
+
15
+ ### Methods Triggered by Procedure **Creation**
16
+
17
+ - **`addProcedureToPractitioner(practitionerId: string, procedureSummary: ProcedureSummaryInfo): Promise<void>`**
18
+ - Adds the `procedureId` (from `procedureSummary.id`) and the `procedureSummary` object to the `procedureIds` and `proceduresInfo` arrays respectively in the specified `Practitioner` document.
19
+ - **`addProcedureToClinic(clinicId: string, procedureSummary: ProcedureSummaryInfo): Promise<void>`**
20
+ - Adds the `procedureId` (from `procedureSummary.id`) and the `procedureSummary` object to the `procedures` and `proceduresInfo` arrays respectively in the specified `Clinic` document.
21
+
22
+ ### Methods Triggered by Procedure **Update**
23
+
24
+ - **`updateProcedureInfoInPractitioner(practitionerId: string, procedureSummary: ProcedureSummaryInfo): Promise<void>`**
25
+ - Updates the `proceduresInfo` array within the specified `Practitioner` document.
26
+ - Uses a transaction to read the existing array, filter out the old summary (matching by `id`), add the updated `procedureSummary`, and write the modified array back. This prevents race conditions.
27
+ - **`updateProcedureInfoInClinic(clinicId: string, procedureSummary: ProcedureSummaryInfo): Promise<void>`**
28
+ - Updates the `proceduresInfo` array within the specified `Clinic` document using the same transaction-based read-modify-write pattern as above.
29
+ - **`updateProcedureInfoInCalendarEvents(procedureId: string, procedureInfo: any): Promise<void>`**
30
+ - Uses a Collection Group query (`calendar` subcollection) to find all _upcoming_ calendar events associated with the `procedureId`.
31
+ - Updates the embedded `procedureInfo` object in these calendar events with the provided `procedureInfo`.
32
+
33
+ ### Methods Triggered by Procedure **Deletion / Deactivation**
34
+
35
+ - **`cancelUpcomingCalendarEventsForProcedure(procedureId: string): Promise<void>`**
36
+ - Uses a Collection Group query (`calendar` subcollection) to find all _upcoming_ calendar events associated with the `procedureId`.
37
+ - Sets the `status` of these events to `CANCELED` and adds a `cancelReason` ("Procedure deleted or inactivated").
38
+ - **`removeProcedureFromPractitioner(practitionerId: string, procedureId: string): Promise<void>`**
39
+ - Removes the `procedureId` from the `procedureIds` array and the corresponding `ProcedureSummaryInfo` object (matching by `id`) from the `proceduresInfo` array in the specified `Practitioner` document.
40
+ - Uses a transaction-based read-modify-write pattern.
41
+ - **`removeProcedureFromClinic(clinicId: string, procedureId: string): Promise<void>`**
42
+ - Removes the `procedureId` from the `procedures` array and the corresponding `ProcedureSummaryInfo` object (matching by `id`) from the `proceduresInfo` array in the specified `Clinic` document.
43
+ - Uses a transaction-based read-modify-write pattern.
@@ -8,7 +8,11 @@ import { UserRole } from "../types";
8
8
  // Import types needed by admin consumers (like Cloud Functions)
9
9
  import { Clinic, ClinicLocation } from "../types/clinic";
10
10
  import { ClinicInfo } from "../types/profile";
11
- import { Practitioner } from "../types/practitioner";
11
+ import {
12
+ Practitioner,
13
+ PractitionerToken,
14
+ PractitionerTokenStatus,
15
+ } from "../types/practitioner";
12
16
  import { DoctorInfo } from "../types/clinic";
13
17
  import { Procedure, ProcedureSummaryInfo } from "../types/procedure";
14
18
  import { PatientProfile } from "../types/patient";
@@ -19,6 +23,10 @@ import { PractitionerAggregationService } from "./aggregation/practitioner/pract
19
23
  import { ProcedureAggregationService } from "./aggregation/procedure/procedure.aggregation.service";
20
24
  import { PatientAggregationService } from "./aggregation/patient/patient.aggregation.service";
21
25
 
26
+ // Import mailing services
27
+ import { BaseMailingService } from "./mailing/base.mailing.service";
28
+ import { PractitionerInviteMailingService } from "./mailing/practitionerInvite/practitionerInvite.mailing";
29
+
22
30
  // Re-export types
23
31
  export type {
24
32
  Notification,
@@ -32,7 +40,11 @@ export type {
32
40
  // Re-export types needed by cloud functions
33
41
  export type { Clinic, ClinicLocation } from "../types/clinic";
34
42
  export type { ClinicInfo } from "../types/profile";
35
- export type { Practitioner } from "../types/practitioner";
43
+ export type {
44
+ Practitioner,
45
+ PractitionerToken,
46
+ PractitionerTokenStatus,
47
+ } from "../types/practitioner";
36
48
  export type { DoctorInfo } from "../types/clinic";
37
49
  export type { Procedure, ProcedureSummaryInfo } from "../types/procedure";
38
50
  export type { PatientProfile as Patient } from "../types/patient";
@@ -54,6 +66,9 @@ export {
54
66
  PatientAggregationService,
55
67
  };
56
68
 
69
+ // Export mailing services
70
+ export { BaseMailingService, PractitionerInviteMailingService };
71
+
57
72
  /**
58
73
  * Main entry point for the Admin module.
59
74
  * This module contains services and utilities intended for administrative tasks,
@@ -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.
@@ -0,0 +1,131 @@
1
+ import * as mailgun from "mailgun-js";
2
+ import * as admin from "firebase-admin";
3
+ // Configuration is no longer read here
4
+ // import {
5
+ // getMailgunConfig,
6
+ // createMailgunClient,
7
+ // MailgunConfig,
8
+ // } from "./mailgun.config";
9
+
10
+ /**
11
+ * Base mailing service class that provides common functionality for all mailing services
12
+ */
13
+ export class BaseMailingService {
14
+ protected db: FirebaseFirestore.Firestore;
15
+ protected mailgunClient: mailgun.Mailgun;
16
+ // Removed config property as it's no longer managed here
17
+ // protected config: MailgunConfig;
18
+
19
+ /**
20
+ * Constructor for BaseMailingService
21
+ * @param firestore Firestore instance provided by the caller
22
+ * @param mailgunClient Mailgun client instance provided by the caller
23
+ */
24
+ constructor(
25
+ firestore: FirebaseFirestore.Firestore,
26
+ mailgunClient: mailgun.Mailgun // Mailgun client is now required
27
+ // mailgunConfig?: MailgunConfig // Removed config parameter
28
+ ) {
29
+ // Use provided instances
30
+ this.db = firestore;
31
+ this.mailgunClient = mailgunClient;
32
+ // Removed internal config reading and client creation
33
+ // this.config = mailgunConfig || getMailgunConfig();
34
+ // this.mailgunClient = createMailgunClient(this.config);
35
+ }
36
+
37
+ /**
38
+ * Sends an email using Mailgun
39
+ * @param data Email data to send, including the 'from' address
40
+ * @returns Promise with the sending result
41
+ */
42
+ protected async sendEmail(
43
+ data: mailgun.messages.SendData // Caller must provide 'from'
44
+ ): Promise<mailgun.messages.SendResponse> {
45
+ try {
46
+ // Ensure 'from' field is provided by the caller
47
+ if (!data.from) {
48
+ throw new Error(
49
+ "Email 'from' address must be provided in sendEmail data."
50
+ );
51
+ }
52
+ // Removed fallback to internal config.from
53
+ // const emailData = {
54
+ // ...data,
55
+ // from: data.from || this.config.from,
56
+ // };
57
+
58
+ // Send the email
59
+ return await new Promise<mailgun.messages.SendResponse>(
60
+ (resolve, reject) => {
61
+ this.mailgunClient.messages().send(data, (error, body) => {
62
+ if (error) {
63
+ console.error("[BaseMailingService] Error sending email:", error);
64
+ reject(error);
65
+ } else {
66
+ console.log(
67
+ "[BaseMailingService] Email sent successfully:",
68
+ body
69
+ );
70
+ resolve(body);
71
+ }
72
+ });
73
+ }
74
+ );
75
+ } catch (error) {
76
+ console.error("[BaseMailingService] Error in sendEmail:", error);
77
+ throw error;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Logs email sending attempt to Firestore for tracking
83
+ * @param emailData Email data that was sent
84
+ * @param success Whether the email was sent successfully
85
+ * @param error Error object if the email failed to send
86
+ */
87
+ protected async logEmailAttempt(
88
+ emailData: { to: string; subject: string; templateName?: string },
89
+ success: boolean,
90
+ error?: any
91
+ ): Promise<void> {
92
+ try {
93
+ const emailLogRef = this.db.collection("email_logs").doc();
94
+ await emailLogRef.set({
95
+ to: emailData.to,
96
+ subject: emailData.subject,
97
+ templateName: emailData.templateName,
98
+ success,
99
+ error: error ? JSON.stringify(error) : null,
100
+ sentAt: admin.firestore.FieldValue.serverTimestamp(),
101
+ });
102
+ } catch (logError) {
103
+ console.error(
104
+ "[BaseMailingService] Error logging email attempt:",
105
+ logError
106
+ );
107
+ // Don't throw here to prevent disrupting the main flow
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Renders a simple HTML email template with variables
113
+ * @param template HTML template string
114
+ * @param variables Key-value pairs to replace in the template
115
+ * @returns Rendered HTML string
116
+ */
117
+ protected renderTemplate(
118
+ template: string,
119
+ variables: Record<string, string>
120
+ ): string {
121
+ let rendered = template;
122
+
123
+ // Replace template variables (format: {{variable_name}})
124
+ Object.entries(variables).forEach(([key, value]) => {
125
+ const regex = new RegExp(`{{\\s*${key}\\s*}}`, "g");
126
+ rendered = rendered.replace(regex, value);
127
+ });
128
+
129
+ return rendered;
130
+ }
131
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./base.mailing.service";
2
+ export * from "./practitionerInvite";
@@ -0,0 +1 @@
1
+ export * from "./practitionerInvite.mailing";