@blackcode_sa/metaestetics-api 1.13.4 → 1.13.5
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 +15 -28
- package/dist/admin/index.d.ts +15 -28
- package/dist/index.d.mts +16 -29
- package/dist/index.d.ts +16 -29
- package/dist/index.js +1 -0
- package/dist/index.mjs +1 -0
- package/package.json +121 -119
- package/src/__mocks__/firstore.ts +10 -10
- package/src/admin/aggregation/README.md +79 -79
- package/src/admin/aggregation/appointment/README.md +128 -128
- package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +1984 -1984
- package/src/admin/aggregation/appointment/index.ts +1 -1
- package/src/admin/aggregation/clinic/README.md +52 -52
- package/src/admin/aggregation/clinic/clinic.aggregation.service.ts +703 -703
- package/src/admin/aggregation/clinic/index.ts +1 -1
- package/src/admin/aggregation/forms/README.md +13 -13
- package/src/admin/aggregation/forms/filled-forms.aggregation.service.ts +322 -322
- package/src/admin/aggregation/forms/index.ts +1 -1
- package/src/admin/aggregation/index.ts +8 -8
- package/src/admin/aggregation/patient/README.md +27 -27
- package/src/admin/aggregation/patient/index.ts +1 -1
- package/src/admin/aggregation/patient/patient.aggregation.service.ts +141 -141
- package/src/admin/aggregation/practitioner/README.md +42 -42
- package/src/admin/aggregation/practitioner/index.ts +1 -1
- package/src/admin/aggregation/practitioner/practitioner.aggregation.service.ts +433 -433
- package/src/admin/aggregation/practitioner-invite/index.ts +1 -1
- package/src/admin/aggregation/practitioner-invite/practitioner-invite.aggregation.service.ts +961 -961
- package/src/admin/aggregation/procedure/README.md +43 -43
- package/src/admin/aggregation/procedure/index.ts +1 -1
- package/src/admin/aggregation/procedure/procedure.aggregation.service.ts +702 -702
- package/src/admin/aggregation/reviews/index.ts +1 -1
- package/src/admin/aggregation/reviews/reviews.aggregation.service.ts +689 -689
- package/src/admin/analytics/analytics.admin.service.ts +278 -278
- package/src/admin/analytics/index.ts +2 -2
- package/src/admin/booking/README.md +125 -125
- package/src/admin/booking/booking.admin.ts +1037 -1037
- package/src/admin/booking/booking.calculator.ts +712 -712
- package/src/admin/booking/booking.types.ts +59 -59
- package/src/admin/booking/index.ts +3 -3
- package/src/admin/booking/timezones-problem.md +185 -185
- package/src/admin/calendar/README.md +7 -7
- package/src/admin/calendar/calendar.admin.service.ts +345 -345
- package/src/admin/calendar/index.ts +1 -1
- package/src/admin/documentation-templates/document-manager.admin.ts +260 -260
- package/src/admin/documentation-templates/index.ts +1 -1
- package/src/admin/free-consultation/free-consultation-utils.admin.ts +148 -148
- package/src/admin/free-consultation/index.ts +1 -1
- package/src/admin/index.ts +81 -81
- package/src/admin/logger/index.ts +78 -78
- package/src/admin/mailing/README.md +95 -95
- package/src/admin/mailing/appointment/appointment.mailing.service.ts +732 -732
- package/src/admin/mailing/appointment/index.ts +1 -1
- package/src/admin/mailing/appointment/templates/patient/appointment-confirmed.html +40 -40
- package/src/admin/mailing/base.mailing.service.ts +208 -208
- package/src/admin/mailing/index.ts +3 -3
- package/src/admin/mailing/practitionerInvite/existing-practitioner-invite.mailing.ts +611 -611
- package/src/admin/mailing/practitionerInvite/index.ts +2 -2
- package/src/admin/mailing/practitionerInvite/practitionerInvite.mailing.ts +395 -395
- package/src/admin/mailing/practitionerInvite/templates/existing-practitioner-invitation.template.ts +155 -155
- package/src/admin/mailing/practitionerInvite/templates/invitation.template.ts +101 -101
- package/src/admin/mailing/practitionerInvite/templates/invite-accepted-notification.template.ts +228 -228
- package/src/admin/mailing/practitionerInvite/templates/invite-rejected-notification.template.ts +242 -242
- package/src/admin/notifications/index.ts +1 -1
- package/src/admin/notifications/notifications.admin.ts +710 -710
- package/src/admin/requirements/README.md +128 -128
- package/src/admin/requirements/index.ts +1 -1
- package/src/admin/requirements/patient-requirements.admin.service.ts +475 -475
- package/src/admin/users/index.ts +1 -1
- package/src/admin/users/user-profile.admin.ts +405 -405
- package/src/backoffice/constants/certification.constants.ts +13 -13
- package/src/backoffice/constants/index.ts +1 -1
- package/src/backoffice/errors/backoffice.errors.ts +181 -181
- package/src/backoffice/errors/index.ts +1 -1
- package/src/backoffice/expo-safe/README.md +26 -26
- package/src/backoffice/expo-safe/index.ts +41 -41
- package/src/backoffice/index.ts +5 -5
- package/src/backoffice/services/FIXES_README.md +102 -102
- package/src/backoffice/services/README.md +57 -57
- package/src/backoffice/services/analytics.service.proposal.md +863 -863
- package/src/backoffice/services/analytics.service.summary.md +143 -143
- package/src/backoffice/services/brand.service.ts +256 -256
- package/src/backoffice/services/category.service.ts +384 -384
- package/src/backoffice/services/constants.service.ts +385 -385
- package/src/backoffice/services/documentation-template.service.ts +202 -202
- package/src/backoffice/services/index.ts +10 -10
- package/src/backoffice/services/migrate-products.ts +116 -116
- package/src/backoffice/services/product.service.ts +553 -553
- package/src/backoffice/services/requirement.service.ts +235 -235
- package/src/backoffice/services/subcategory.service.ts +461 -461
- package/src/backoffice/services/technology.service.ts +1151 -1151
- package/src/backoffice/types/README.md +12 -12
- package/src/backoffice/types/admin-constants.types.ts +69 -69
- package/src/backoffice/types/brand.types.ts +29 -29
- package/src/backoffice/types/category.types.ts +67 -67
- package/src/backoffice/types/documentation-templates.types.ts +28 -28
- package/src/backoffice/types/index.ts +10 -10
- package/src/backoffice/types/procedure-product.types.ts +38 -38
- package/src/backoffice/types/product.types.ts +240 -240
- package/src/backoffice/types/requirement.types.ts +63 -63
- package/src/backoffice/types/static/README.md +18 -18
- package/src/backoffice/types/static/blocking-condition.types.ts +21 -21
- package/src/backoffice/types/static/certification.types.ts +37 -37
- package/src/backoffice/types/static/contraindication.types.ts +19 -19
- package/src/backoffice/types/static/index.ts +6 -6
- package/src/backoffice/types/static/pricing.types.ts +16 -16
- package/src/backoffice/types/static/procedure-family.types.ts +14 -14
- package/src/backoffice/types/static/treatment-benefit.types.ts +22 -22
- package/src/backoffice/types/subcategory.types.ts +34 -34
- package/src/backoffice/types/technology.types.ts +168 -168
- package/src/backoffice/validations/index.ts +1 -1
- package/src/backoffice/validations/schemas.ts +164 -164
- package/src/config/__mocks__/firebase.ts +99 -99
- package/src/config/firebase.ts +78 -78
- package/src/config/index.ts +9 -9
- package/src/errors/auth.error.ts +6 -6
- package/src/errors/auth.errors.ts +200 -200
- package/src/errors/clinic.errors.ts +32 -32
- package/src/errors/firebase.errors.ts +47 -47
- package/src/errors/user.errors.ts +99 -99
- package/src/index.backup.ts +407 -407
- package/src/index.ts +6 -6
- package/src/locales/en.ts +31 -31
- package/src/recommender/admin/index.ts +1 -1
- package/src/recommender/admin/services/recommender.service.admin.ts +5 -5
- package/src/recommender/front/index.ts +1 -1
- package/src/recommender/front/services/onboarding.service.ts +5 -5
- package/src/recommender/front/services/recommender.service.ts +3 -3
- package/src/recommender/index.ts +1 -1
- package/src/services/PATIENTAUTH.MD +197 -197
- package/src/services/README.md +106 -106
- package/src/services/__tests__/auth/auth.mock.test.ts +17 -17
- package/src/services/__tests__/auth/auth.setup.ts +293 -293
- package/src/services/__tests__/auth.service.test.ts +346 -346
- package/src/services/__tests__/base.service.test.ts +77 -77
- package/src/services/__tests__/user.service.test.ts +528 -528
- package/src/services/analytics/ARCHITECTURE.md +199 -199
- package/src/services/analytics/CLOUD_FUNCTIONS.md +225 -225
- package/src/services/analytics/GROUPED_ANALYTICS.md +501 -501
- package/src/services/analytics/QUICK_START.md +393 -393
- package/src/services/analytics/README.md +304 -304
- package/src/services/analytics/SUMMARY.md +141 -141
- package/src/services/analytics/TRENDS.md +380 -380
- package/src/services/analytics/USAGE_GUIDE.md +518 -518
- package/src/services/analytics/analytics-cloud.service.ts +222 -222
- package/src/services/analytics/analytics.service.ts +2142 -2142
- package/src/services/analytics/index.ts +4 -4
- package/src/services/analytics/review-analytics.service.ts +941 -941
- package/src/services/analytics/utils/appointment-filtering.utils.ts +138 -138
- package/src/services/analytics/utils/cost-calculation.utils.ts +182 -182
- package/src/services/analytics/utils/grouping.utils.ts +434 -434
- package/src/services/analytics/utils/stored-analytics.utils.ts +347 -347
- package/src/services/analytics/utils/time-calculation.utils.ts +186 -186
- package/src/services/analytics/utils/trend-calculation.utils.ts +200 -200
- package/src/services/appointment/README.md +17 -17
- package/src/services/appointment/appointment.service.ts +2558 -2558
- package/src/services/appointment/index.ts +1 -1
- package/src/services/appointment/utils/appointment.utils.ts +552 -552
- package/src/services/appointment/utils/extended-procedure.utils.ts +314 -314
- package/src/services/appointment/utils/form-initialization.utils.ts +225 -225
- package/src/services/appointment/utils/recommended-procedure.utils.ts +195 -195
- package/src/services/appointment/utils/zone-management.utils.ts +353 -353
- package/src/services/appointment/utils/zone-photo.utils.ts +152 -152
- package/src/services/auth/auth.service.ts +989 -989
- package/src/services/auth/auth.v2.service.ts +961 -961
- package/src/services/auth/index.ts +7 -7
- package/src/services/auth/utils/error.utils.ts +90 -90
- package/src/services/auth/utils/firebase.utils.ts +49 -49
- package/src/services/auth/utils/index.ts +21 -21
- package/src/services/auth/utils/practitioner.utils.ts +125 -125
- package/src/services/base.service.ts +41 -41
- package/src/services/calendar/calendar.service.ts +1077 -1077
- package/src/services/calendar/calendar.v2.service.ts +1683 -1683
- package/src/services/calendar/calendar.v3.service.ts +313 -313
- package/src/services/calendar/externalCalendar.service.ts +178 -178
- package/src/services/calendar/index.ts +5 -5
- package/src/services/calendar/synced-calendars.service.ts +743 -743
- package/src/services/calendar/utils/appointment.utils.ts +265 -265
- package/src/services/calendar/utils/calendar-event.utils.ts +646 -646
- package/src/services/calendar/utils/clinic.utils.ts +237 -237
- package/src/services/calendar/utils/docs.utils.ts +157 -157
- package/src/services/calendar/utils/google-calendar.utils.ts +697 -697
- package/src/services/calendar/utils/index.ts +8 -8
- package/src/services/calendar/utils/patient.utils.ts +198 -198
- package/src/services/calendar/utils/practitioner.utils.ts +221 -221
- package/src/services/calendar/utils/synced-calendar.utils.ts +472 -472
- package/src/services/clinic/README.md +204 -204
- package/src/services/clinic/__tests__/clinic-admin.service.test.ts +287 -287
- package/src/services/clinic/__tests__/clinic-group.service.test.ts +352 -352
- package/src/services/clinic/__tests__/clinic.service.test.ts +354 -354
- package/src/services/clinic/billing-transactions.service.ts +217 -217
- package/src/services/clinic/clinic-admin.service.ts +202 -202
- package/src/services/clinic/clinic-group.service.ts +310 -310
- package/src/services/clinic/clinic.service.ts +708 -708
- package/src/services/clinic/index.ts +5 -5
- package/src/services/clinic/practitioner-invite.service.ts +519 -519
- package/src/services/clinic/utils/admin.utils.ts +551 -551
- package/src/services/clinic/utils/clinic-group.utils.ts +646 -646
- package/src/services/clinic/utils/clinic.utils.ts +949 -949
- package/src/services/clinic/utils/filter.utils.d.ts +23 -23
- package/src/services/clinic/utils/filter.utils.ts +446 -446
- package/src/services/clinic/utils/index.ts +11 -11
- package/src/services/clinic/utils/photos.utils.ts +188 -188
- package/src/services/clinic/utils/search.utils.ts +84 -84
- package/src/services/clinic/utils/tag.utils.ts +124 -124
- package/src/services/documentation-templates/documentation-template.service.ts +537 -537
- package/src/services/documentation-templates/filled-document.service.ts +587 -587
- package/src/services/documentation-templates/index.ts +2 -2
- package/src/services/index.ts +14 -14
- package/src/services/media/index.ts +1 -1
- package/src/services/media/media.service.ts +418 -418
- package/src/services/notifications/__tests__/notification.service.test.ts +242 -242
- package/src/services/notifications/index.ts +1 -1
- package/src/services/notifications/notification.service.ts +215 -215
- package/src/services/patient/README.md +48 -48
- package/src/services/patient/To-Do.md +43 -43
- package/src/services/patient/__tests__/patient.service.test.ts +294 -294
- package/src/services/patient/index.ts +2 -2
- package/src/services/patient/patient.service.ts +883 -883
- package/src/services/patient/patientRequirements.service.ts +285 -285
- package/src/services/patient/utils/aesthetic-analysis.utils.ts +176 -176
- package/src/services/patient/utils/clinic.utils.ts +80 -80
- package/src/services/patient/utils/docs.utils.ts +142 -142
- package/src/services/patient/utils/index.ts +9 -9
- package/src/services/patient/utils/location.utils.ts +126 -126
- package/src/services/patient/utils/medical-stuff.utils.ts +143 -143
- package/src/services/patient/utils/medical.utils.ts +458 -458
- package/src/services/patient/utils/practitioner.utils.ts +260 -260
- package/src/services/patient/utils/profile.utils.ts +510 -510
- package/src/services/patient/utils/sensitive.utils.ts +260 -260
- package/src/services/patient/utils/token.utils.ts +211 -211
- package/src/services/practitioner/README.md +145 -145
- package/src/services/practitioner/index.ts +1 -1
- package/src/services/practitioner/practitioner.service.ts +1742 -1742
- package/src/services/procedure/README.md +163 -163
- package/src/services/procedure/index.ts +1 -1
- package/src/services/procedure/procedure.service.ts +2200 -2200
- package/src/services/reviews/index.ts +1 -1
- package/src/services/reviews/reviews.service.ts +734 -734
- package/src/services/user/index.ts +1 -1
- package/src/services/user/user.service.ts +489 -489
- package/src/services/user/user.v2.service.ts +466 -466
- package/src/types/analytics/analytics.types.ts +597 -597
- package/src/types/analytics/grouped-analytics.types.ts +173 -173
- package/src/types/analytics/index.ts +4 -4
- package/src/types/analytics/stored-analytics.types.ts +137 -137
- package/src/types/appointment/index.ts +480 -480
- package/src/types/calendar/index.ts +258 -258
- package/src/types/calendar/synced-calendar.types.ts +66 -66
- package/src/types/clinic/index.ts +498 -489
- package/src/types/clinic/practitioner-invite.types.ts +91 -91
- package/src/types/clinic/preferences.types.ts +159 -159
- package/src/types/clinic/to-do +3 -3
- package/src/types/documentation-templates/index.ts +308 -308
- package/src/types/index.ts +47 -47
- package/src/types/notifications/README.md +77 -77
- package/src/types/notifications/index.ts +286 -286
- package/src/types/patient/aesthetic-analysis.types.ts +66 -66
- package/src/types/patient/allergies.ts +58 -58
- package/src/types/patient/index.ts +275 -275
- package/src/types/patient/medical-info.types.ts +152 -152
- package/src/types/patient/patient-requirements.ts +92 -92
- package/src/types/patient/token.types.ts +61 -61
- package/src/types/practitioner/index.ts +206 -206
- package/src/types/procedure/index.ts +181 -181
- package/src/types/profile/index.ts +39 -39
- package/src/types/reviews/index.ts +132 -132
- package/src/types/tz-lookup.d.ts +4 -4
- package/src/types/user/index.ts +38 -38
- package/src/utils/TIMESTAMPS.md +176 -176
- package/src/utils/TimestampUtils.ts +241 -241
- package/src/utils/index.ts +1 -1
- package/src/validations/appointment.schema.ts +574 -574
- package/src/validations/calendar.schema.ts +225 -225
- package/src/validations/clinic.schema.ts +494 -493
- package/src/validations/common.schema.ts +25 -25
- package/src/validations/documentation-templates/index.ts +1 -1
- package/src/validations/documentation-templates/template.schema.ts +220 -220
- package/src/validations/documentation-templates.schema.ts +10 -10
- package/src/validations/index.ts +20 -20
- package/src/validations/media.schema.ts +10 -10
- package/src/validations/notification.schema.ts +90 -90
- package/src/validations/patient/aesthetic-analysis.schema.ts +55 -55
- package/src/validations/patient/medical-info.schema.ts +125 -125
- package/src/validations/patient/patient-requirements.schema.ts +84 -84
- package/src/validations/patient/token.schema.ts +29 -29
- package/src/validations/patient.schema.ts +217 -217
- package/src/validations/practitioner.schema.ts +222 -222
- package/src/validations/procedure-product.schema.ts +41 -41
- package/src/validations/procedure.schema.ts +124 -124
- package/src/validations/profile-info.schema.ts +41 -41
- package/src/validations/reviews.schema.ts +195 -195
- package/src/validations/schemas.ts +104 -104
- package/src/validations/shared.schema.ts +78 -78
|
@@ -1,395 +1,395 @@
|
|
|
1
|
-
import * as admin from "firebase-admin";
|
|
2
|
-
import { BaseMailingService } from "../base.mailing.service";
|
|
3
|
-
import { practitionerInvitationTemplate } from "./templates/invitation.template";
|
|
4
|
-
import { Logger } from "../../logger";
|
|
5
|
-
// Import specific types and collection constants
|
|
6
|
-
import {
|
|
7
|
-
Practitioner,
|
|
8
|
-
PractitionerToken,
|
|
9
|
-
PRACTITIONERS_COLLECTION,
|
|
10
|
-
PractitionerTokenStatus,
|
|
11
|
-
} from "../../../types/practitioner";
|
|
12
|
-
import { Clinic, CLINICS_COLLECTION } from "../../../types/clinic";
|
|
13
|
-
|
|
14
|
-
// Define a minimal interface for the mailgun.js client if not importing full types
|
|
15
|
-
interface NewMailgunMessagesAPI {
|
|
16
|
-
create(domain: string, data: any): Promise<any>;
|
|
17
|
-
}
|
|
18
|
-
interface NewMailgunClient {
|
|
19
|
-
messages: NewMailgunMessagesAPI;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Interface for the data required to send a practitioner invitation email
|
|
24
|
-
*/
|
|
25
|
-
export interface PractitionerInviteEmailData {
|
|
26
|
-
/** The token object from the practitioner service */
|
|
27
|
-
token: {
|
|
28
|
-
id: string;
|
|
29
|
-
token: string;
|
|
30
|
-
practitionerId: string;
|
|
31
|
-
email: string;
|
|
32
|
-
clinicId: string;
|
|
33
|
-
expiresAt: admin.firestore.Timestamp;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
/** Practitioner basic info */
|
|
37
|
-
practitioner: {
|
|
38
|
-
firstName: string;
|
|
39
|
-
lastName: string;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
/** Clinic info */
|
|
43
|
-
clinic: {
|
|
44
|
-
name: string;
|
|
45
|
-
contactEmail: string;
|
|
46
|
-
contactName?: string;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
/** Config options */
|
|
50
|
-
options?: {
|
|
51
|
-
registrationUrl?: string;
|
|
52
|
-
customSubject?: string;
|
|
53
|
-
fromAddress?: string;
|
|
54
|
-
mailgunDomain?: string;
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Service for sending practitioner invitation emails, updated for mailgun.js v10+
|
|
60
|
-
*/
|
|
61
|
-
export class PractitionerInviteMailingService extends BaseMailingService {
|
|
62
|
-
private readonly DEFAULT_REGISTRATION_URL =
|
|
63
|
-
"https://metaesthetics.net/register";
|
|
64
|
-
private readonly DEFAULT_SUBJECT =
|
|
65
|
-
"You've Been Invited to Join as a Practitioner";
|
|
66
|
-
private readonly DEFAULT_MAILGUN_DOMAIN = "mg.metaesthetics.net";
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Constructor for PractitionerInviteMailingService
|
|
70
|
-
* @param firestore Firestore instance provided by the caller
|
|
71
|
-
* @param mailgunClient Mailgun client instance (mailgun.js v10+) provided by the caller
|
|
72
|
-
*/
|
|
73
|
-
constructor(
|
|
74
|
-
firestore: FirebaseFirestore.Firestore,
|
|
75
|
-
mailgunClient: NewMailgunClient
|
|
76
|
-
) {
|
|
77
|
-
super(firestore, mailgunClient);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Sends a practitioner invitation email
|
|
82
|
-
* @param data The practitioner invitation data
|
|
83
|
-
* @returns Promise resolved when email is sent
|
|
84
|
-
*/
|
|
85
|
-
async sendInvitationEmail(data: PractitionerInviteEmailData): Promise<any> {
|
|
86
|
-
try {
|
|
87
|
-
Logger.info(
|
|
88
|
-
"[PractitionerInviteMailingService] Sending invitation email to",
|
|
89
|
-
data.token.email
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
// Format expiration date
|
|
93
|
-
const expirationDate = data.token.expiresAt
|
|
94
|
-
.toDate()
|
|
95
|
-
.toLocaleDateString("en-US", {
|
|
96
|
-
weekday: "long",
|
|
97
|
-
year: "numeric",
|
|
98
|
-
month: "long",
|
|
99
|
-
day: "numeric",
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// Registration URL
|
|
103
|
-
const registrationUrl =
|
|
104
|
-
data.options?.registrationUrl || this.DEFAULT_REGISTRATION_URL;
|
|
105
|
-
|
|
106
|
-
// Contact information
|
|
107
|
-
const contactName = data.clinic.contactName || "Clinic Administrator";
|
|
108
|
-
const contactEmail = data.clinic.contactEmail;
|
|
109
|
-
|
|
110
|
-
// Subject line
|
|
111
|
-
const subject = data.options?.customSubject || this.DEFAULT_SUBJECT;
|
|
112
|
-
|
|
113
|
-
// Determine 'from' address
|
|
114
|
-
const fromAddress =
|
|
115
|
-
data.options?.fromAddress ||
|
|
116
|
-
`MetaEstetics <no-reply@${
|
|
117
|
-
data.options?.mailgunDomain || this.DEFAULT_MAILGUN_DOMAIN
|
|
118
|
-
}>`;
|
|
119
|
-
|
|
120
|
-
// Current year for copyright
|
|
121
|
-
const currentYear = new Date().getFullYear().toString();
|
|
122
|
-
|
|
123
|
-
// Practitioner full name
|
|
124
|
-
const practitionerName = `${data.practitioner.firstName} ${data.practitioner.lastName}`;
|
|
125
|
-
|
|
126
|
-
// Prepare template variables
|
|
127
|
-
const templateVariables = {
|
|
128
|
-
clinicName: data.clinic.name,
|
|
129
|
-
practitionerName,
|
|
130
|
-
inviteToken: data.token.token,
|
|
131
|
-
expirationDate,
|
|
132
|
-
registrationUrl,
|
|
133
|
-
contactName,
|
|
134
|
-
contactEmail,
|
|
135
|
-
currentYear,
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
// Debug log for template variables (excluding token for security)
|
|
139
|
-
Logger.info("[PractitionerInviteMailingService] Template variables:", {
|
|
140
|
-
clinicName: templateVariables.clinicName,
|
|
141
|
-
practitionerName: templateVariables.practitionerName,
|
|
142
|
-
expirationDate: templateVariables.expirationDate,
|
|
143
|
-
registrationUrl: templateVariables.registrationUrl,
|
|
144
|
-
contactName: templateVariables.contactName,
|
|
145
|
-
contactEmail: templateVariables.contactEmail,
|
|
146
|
-
// Don't log the invite token for security
|
|
147
|
-
hasInviteToken: !!templateVariables.inviteToken,
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
// Render HTML email
|
|
151
|
-
const html = this.renderTemplate(
|
|
152
|
-
practitionerInvitationTemplate,
|
|
153
|
-
templateVariables
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
// Data for the new sendEmail signature
|
|
157
|
-
const mailgunSendData = {
|
|
158
|
-
to: data.token.email,
|
|
159
|
-
from: fromAddress,
|
|
160
|
-
subject,
|
|
161
|
-
html,
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
// Determine the domain to use for sending
|
|
165
|
-
const domainToSendFrom =
|
|
166
|
-
data.options?.mailgunDomain || this.DEFAULT_MAILGUN_DOMAIN;
|
|
167
|
-
|
|
168
|
-
Logger.info(
|
|
169
|
-
"[PractitionerInviteMailingService] Sending email with data:",
|
|
170
|
-
{
|
|
171
|
-
domain: domainToSendFrom,
|
|
172
|
-
to: mailgunSendData.to,
|
|
173
|
-
from: mailgunSendData.from,
|
|
174
|
-
subject: mailgunSendData.subject,
|
|
175
|
-
hasHtml: !!mailgunSendData.html,
|
|
176
|
-
}
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
// Call the updated sendEmail method from BaseMailingService
|
|
180
|
-
const result = await this.sendEmail(domainToSendFrom, mailgunSendData);
|
|
181
|
-
|
|
182
|
-
// Log success
|
|
183
|
-
await this.logEmailAttempt(
|
|
184
|
-
{
|
|
185
|
-
to: data.token.email,
|
|
186
|
-
subject,
|
|
187
|
-
templateName: "practitioner_invitation",
|
|
188
|
-
},
|
|
189
|
-
true
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
return result;
|
|
193
|
-
} catch (error: any) {
|
|
194
|
-
Logger.error(
|
|
195
|
-
"[PractitionerInviteMailingService] Error sending invitation email:",
|
|
196
|
-
{
|
|
197
|
-
errorMessage: error.message,
|
|
198
|
-
errorDetails: error.details,
|
|
199
|
-
errorStatus: error.status,
|
|
200
|
-
stack: error.stack,
|
|
201
|
-
}
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
// Log failure
|
|
205
|
-
await this.logEmailAttempt(
|
|
206
|
-
{
|
|
207
|
-
to: data.token.email,
|
|
208
|
-
subject: data.options?.customSubject || this.DEFAULT_SUBJECT,
|
|
209
|
-
templateName: "practitioner_invitation",
|
|
210
|
-
},
|
|
211
|
-
false,
|
|
212
|
-
error
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
throw error;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Handles the practitioner token creation event from Cloud Functions
|
|
221
|
-
* Fetches necessary data using defined types and collection constants,
|
|
222
|
-
* and sends the invitation email.
|
|
223
|
-
* @param tokenData The fully typed token object including its id
|
|
224
|
-
* @param fromAddress The 'from' email address to use, obtained from config
|
|
225
|
-
* @param mailgunDomain The mailgun domain to use for sending
|
|
226
|
-
* @returns Promise resolved when the email is sent
|
|
227
|
-
*/
|
|
228
|
-
async handleTokenCreationEvent(
|
|
229
|
-
tokenData: PractitionerToken,
|
|
230
|
-
mailgunConfig: {
|
|
231
|
-
fromAddress: string;
|
|
232
|
-
domain: string;
|
|
233
|
-
registrationUrl?: string;
|
|
234
|
-
}
|
|
235
|
-
): Promise<void> {
|
|
236
|
-
try {
|
|
237
|
-
Logger.info(
|
|
238
|
-
"[PractitionerInviteMailingService] Handling token creation event for token:",
|
|
239
|
-
tokenData.id
|
|
240
|
-
);
|
|
241
|
-
|
|
242
|
-
// Validate token data
|
|
243
|
-
if (!tokenData || !tokenData.id || !tokenData.token || !tokenData.email) {
|
|
244
|
-
throw new Error(
|
|
245
|
-
`Invalid token data: Missing required properties. Token ID: ${tokenData?.id}`
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (!tokenData.practitionerId) {
|
|
250
|
-
throw new Error(
|
|
251
|
-
`Token ${tokenData.id} is missing practitionerId reference`
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (!tokenData.clinicId) {
|
|
256
|
-
throw new Error(`Token ${tokenData.id} is missing clinicId reference`);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (!tokenData.expiresAt) {
|
|
260
|
-
throw new Error(`Token ${tokenData.id} is missing expiration date`);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Validate token status (handle both enum and string values)
|
|
264
|
-
if (
|
|
265
|
-
tokenData.status !== PractitionerTokenStatus.ACTIVE &&
|
|
266
|
-
String(tokenData.status).toLowerCase() !== "active"
|
|
267
|
-
) {
|
|
268
|
-
Logger.warn(
|
|
269
|
-
"[PractitionerInviteMailingService] Token is not active, skipping email.",
|
|
270
|
-
{ tokenId: tokenData.id, status: tokenData.status }
|
|
271
|
-
);
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Log token status to help with debugging
|
|
276
|
-
Logger.info(
|
|
277
|
-
`[PractitionerInviteMailingService] Token status validation:`,
|
|
278
|
-
{
|
|
279
|
-
tokenId: tokenData.id,
|
|
280
|
-
status: tokenData.status,
|
|
281
|
-
statusType: typeof tokenData.status,
|
|
282
|
-
}
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
// Get practitioner data using constant and type
|
|
286
|
-
Logger.info(
|
|
287
|
-
`[PractitionerInviteMailingService] Fetching practitioner data: ${tokenData.practitionerId}`
|
|
288
|
-
);
|
|
289
|
-
const practitionerRef = this.db
|
|
290
|
-
.collection(PRACTITIONERS_COLLECTION)
|
|
291
|
-
.doc(tokenData.practitionerId);
|
|
292
|
-
const practitionerDoc = await practitionerRef.get();
|
|
293
|
-
|
|
294
|
-
if (!practitionerDoc.exists) {
|
|
295
|
-
throw new Error(`Practitioner ${tokenData.practitionerId} not found`);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const practitionerData = practitionerDoc.data() as Practitioner;
|
|
299
|
-
if (!practitionerData || !practitionerData.basicInfo) {
|
|
300
|
-
throw new Error(
|
|
301
|
-
`Practitioner ${tokenData.practitionerId} has invalid data structure`
|
|
302
|
-
);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
Logger.info(
|
|
306
|
-
`[PractitionerInviteMailingService] Practitioner found: ${practitionerData.basicInfo.firstName} ${practitionerData.basicInfo.lastName}`
|
|
307
|
-
);
|
|
308
|
-
|
|
309
|
-
// Get clinic data using constant and type
|
|
310
|
-
Logger.info(
|
|
311
|
-
`[PractitionerInviteMailingService] Fetching clinic data: ${tokenData.clinicId}`
|
|
312
|
-
);
|
|
313
|
-
const clinicRef = this.db
|
|
314
|
-
.collection(CLINICS_COLLECTION)
|
|
315
|
-
.doc(tokenData.clinicId);
|
|
316
|
-
const clinicDoc = await clinicRef.get();
|
|
317
|
-
|
|
318
|
-
if (!clinicDoc.exists) {
|
|
319
|
-
throw new Error(`Clinic ${tokenData.clinicId} not found`);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const clinicData = clinicDoc.data() as Clinic;
|
|
323
|
-
if (!clinicData || !clinicData.contactInfo) {
|
|
324
|
-
throw new Error(
|
|
325
|
-
`Clinic ${tokenData.clinicId} has invalid data structure`
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
Logger.info(
|
|
330
|
-
`[PractitionerInviteMailingService] Clinic found: ${clinicData.name}`
|
|
331
|
-
);
|
|
332
|
-
|
|
333
|
-
// Clinic model doesn't have contactPerson, only contactInfo
|
|
334
|
-
// So we'll use simple contact information from contactInfo
|
|
335
|
-
|
|
336
|
-
// Validate fromAddress
|
|
337
|
-
if (!mailgunConfig.fromAddress) {
|
|
338
|
-
Logger.warn(
|
|
339
|
-
"[PractitionerInviteMailingService] No fromAddress provided, using default"
|
|
340
|
-
);
|
|
341
|
-
mailgunConfig.fromAddress = `MetaEstetics <no-reply@${this.DEFAULT_MAILGUN_DOMAIN}>`;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// Prepare email data using typed data
|
|
345
|
-
const emailData: PractitionerInviteEmailData = {
|
|
346
|
-
token: {
|
|
347
|
-
id: tokenData.id,
|
|
348
|
-
token: tokenData.token,
|
|
349
|
-
practitionerId: tokenData.practitionerId,
|
|
350
|
-
email: tokenData.email,
|
|
351
|
-
clinicId: tokenData.clinicId,
|
|
352
|
-
expiresAt: tokenData.expiresAt,
|
|
353
|
-
},
|
|
354
|
-
practitioner: {
|
|
355
|
-
firstName: practitionerData.basicInfo.firstName || "",
|
|
356
|
-
lastName: practitionerData.basicInfo.lastName || "",
|
|
357
|
-
},
|
|
358
|
-
clinic: {
|
|
359
|
-
name: clinicData.name || "Medical Clinic",
|
|
360
|
-
contactEmail: clinicData.contactInfo.email || "contact@medclinic.com",
|
|
361
|
-
// Since there's no contactPerson in the Clinic model, we'll just use "Clinic Admin"
|
|
362
|
-
contactName: "Clinic Admin",
|
|
363
|
-
},
|
|
364
|
-
options: {
|
|
365
|
-
fromAddress: mailgunConfig.fromAddress,
|
|
366
|
-
mailgunDomain: mailgunConfig.domain,
|
|
367
|
-
registrationUrl: mailgunConfig.registrationUrl,
|
|
368
|
-
},
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
Logger.info(
|
|
372
|
-
"[PractitionerInviteMailingService] Email data prepared, sending invitation"
|
|
373
|
-
);
|
|
374
|
-
|
|
375
|
-
// Send the invitation email
|
|
376
|
-
await this.sendInvitationEmail(emailData);
|
|
377
|
-
|
|
378
|
-
Logger.info(
|
|
379
|
-
"[PractitionerInviteMailingService] Invitation email sent successfully"
|
|
380
|
-
);
|
|
381
|
-
} catch (error: any) {
|
|
382
|
-
Logger.error(
|
|
383
|
-
"[PractitionerInviteMailingService] Error handling token creation event:",
|
|
384
|
-
{
|
|
385
|
-
errorMessage: error.message,
|
|
386
|
-
errorDetails: error.details,
|
|
387
|
-
errorStatus: error.status,
|
|
388
|
-
stack: error.stack,
|
|
389
|
-
tokenId: tokenData?.id,
|
|
390
|
-
}
|
|
391
|
-
);
|
|
392
|
-
throw error;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
1
|
+
import * as admin from "firebase-admin";
|
|
2
|
+
import { BaseMailingService } from "../base.mailing.service";
|
|
3
|
+
import { practitionerInvitationTemplate } from "./templates/invitation.template";
|
|
4
|
+
import { Logger } from "../../logger";
|
|
5
|
+
// Import specific types and collection constants
|
|
6
|
+
import {
|
|
7
|
+
Practitioner,
|
|
8
|
+
PractitionerToken,
|
|
9
|
+
PRACTITIONERS_COLLECTION,
|
|
10
|
+
PractitionerTokenStatus,
|
|
11
|
+
} from "../../../types/practitioner";
|
|
12
|
+
import { Clinic, CLINICS_COLLECTION } from "../../../types/clinic";
|
|
13
|
+
|
|
14
|
+
// Define a minimal interface for the mailgun.js client if not importing full types
|
|
15
|
+
interface NewMailgunMessagesAPI {
|
|
16
|
+
create(domain: string, data: any): Promise<any>;
|
|
17
|
+
}
|
|
18
|
+
interface NewMailgunClient {
|
|
19
|
+
messages: NewMailgunMessagesAPI;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Interface for the data required to send a practitioner invitation email
|
|
24
|
+
*/
|
|
25
|
+
export interface PractitionerInviteEmailData {
|
|
26
|
+
/** The token object from the practitioner service */
|
|
27
|
+
token: {
|
|
28
|
+
id: string;
|
|
29
|
+
token: string;
|
|
30
|
+
practitionerId: string;
|
|
31
|
+
email: string;
|
|
32
|
+
clinicId: string;
|
|
33
|
+
expiresAt: admin.firestore.Timestamp;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** Practitioner basic info */
|
|
37
|
+
practitioner: {
|
|
38
|
+
firstName: string;
|
|
39
|
+
lastName: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Clinic info */
|
|
43
|
+
clinic: {
|
|
44
|
+
name: string;
|
|
45
|
+
contactEmail: string;
|
|
46
|
+
contactName?: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** Config options */
|
|
50
|
+
options?: {
|
|
51
|
+
registrationUrl?: string;
|
|
52
|
+
customSubject?: string;
|
|
53
|
+
fromAddress?: string;
|
|
54
|
+
mailgunDomain?: string;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Service for sending practitioner invitation emails, updated for mailgun.js v10+
|
|
60
|
+
*/
|
|
61
|
+
export class PractitionerInviteMailingService extends BaseMailingService {
|
|
62
|
+
private readonly DEFAULT_REGISTRATION_URL =
|
|
63
|
+
"https://metaesthetics.net/register";
|
|
64
|
+
private readonly DEFAULT_SUBJECT =
|
|
65
|
+
"You've Been Invited to Join as a Practitioner";
|
|
66
|
+
private readonly DEFAULT_MAILGUN_DOMAIN = "mg.metaesthetics.net";
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Constructor for PractitionerInviteMailingService
|
|
70
|
+
* @param firestore Firestore instance provided by the caller
|
|
71
|
+
* @param mailgunClient Mailgun client instance (mailgun.js v10+) provided by the caller
|
|
72
|
+
*/
|
|
73
|
+
constructor(
|
|
74
|
+
firestore: FirebaseFirestore.Firestore,
|
|
75
|
+
mailgunClient: NewMailgunClient
|
|
76
|
+
) {
|
|
77
|
+
super(firestore, mailgunClient);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Sends a practitioner invitation email
|
|
82
|
+
* @param data The practitioner invitation data
|
|
83
|
+
* @returns Promise resolved when email is sent
|
|
84
|
+
*/
|
|
85
|
+
async sendInvitationEmail(data: PractitionerInviteEmailData): Promise<any> {
|
|
86
|
+
try {
|
|
87
|
+
Logger.info(
|
|
88
|
+
"[PractitionerInviteMailingService] Sending invitation email to",
|
|
89
|
+
data.token.email
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Format expiration date
|
|
93
|
+
const expirationDate = data.token.expiresAt
|
|
94
|
+
.toDate()
|
|
95
|
+
.toLocaleDateString("en-US", {
|
|
96
|
+
weekday: "long",
|
|
97
|
+
year: "numeric",
|
|
98
|
+
month: "long",
|
|
99
|
+
day: "numeric",
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Registration URL
|
|
103
|
+
const registrationUrl =
|
|
104
|
+
data.options?.registrationUrl || this.DEFAULT_REGISTRATION_URL;
|
|
105
|
+
|
|
106
|
+
// Contact information
|
|
107
|
+
const contactName = data.clinic.contactName || "Clinic Administrator";
|
|
108
|
+
const contactEmail = data.clinic.contactEmail;
|
|
109
|
+
|
|
110
|
+
// Subject line
|
|
111
|
+
const subject = data.options?.customSubject || this.DEFAULT_SUBJECT;
|
|
112
|
+
|
|
113
|
+
// Determine 'from' address
|
|
114
|
+
const fromAddress =
|
|
115
|
+
data.options?.fromAddress ||
|
|
116
|
+
`MetaEstetics <no-reply@${
|
|
117
|
+
data.options?.mailgunDomain || this.DEFAULT_MAILGUN_DOMAIN
|
|
118
|
+
}>`;
|
|
119
|
+
|
|
120
|
+
// Current year for copyright
|
|
121
|
+
const currentYear = new Date().getFullYear().toString();
|
|
122
|
+
|
|
123
|
+
// Practitioner full name
|
|
124
|
+
const practitionerName = `${data.practitioner.firstName} ${data.practitioner.lastName}`;
|
|
125
|
+
|
|
126
|
+
// Prepare template variables
|
|
127
|
+
const templateVariables = {
|
|
128
|
+
clinicName: data.clinic.name,
|
|
129
|
+
practitionerName,
|
|
130
|
+
inviteToken: data.token.token,
|
|
131
|
+
expirationDate,
|
|
132
|
+
registrationUrl,
|
|
133
|
+
contactName,
|
|
134
|
+
contactEmail,
|
|
135
|
+
currentYear,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Debug log for template variables (excluding token for security)
|
|
139
|
+
Logger.info("[PractitionerInviteMailingService] Template variables:", {
|
|
140
|
+
clinicName: templateVariables.clinicName,
|
|
141
|
+
practitionerName: templateVariables.practitionerName,
|
|
142
|
+
expirationDate: templateVariables.expirationDate,
|
|
143
|
+
registrationUrl: templateVariables.registrationUrl,
|
|
144
|
+
contactName: templateVariables.contactName,
|
|
145
|
+
contactEmail: templateVariables.contactEmail,
|
|
146
|
+
// Don't log the invite token for security
|
|
147
|
+
hasInviteToken: !!templateVariables.inviteToken,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Render HTML email
|
|
151
|
+
const html = this.renderTemplate(
|
|
152
|
+
practitionerInvitationTemplate,
|
|
153
|
+
templateVariables
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// Data for the new sendEmail signature
|
|
157
|
+
const mailgunSendData = {
|
|
158
|
+
to: data.token.email,
|
|
159
|
+
from: fromAddress,
|
|
160
|
+
subject,
|
|
161
|
+
html,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Determine the domain to use for sending
|
|
165
|
+
const domainToSendFrom =
|
|
166
|
+
data.options?.mailgunDomain || this.DEFAULT_MAILGUN_DOMAIN;
|
|
167
|
+
|
|
168
|
+
Logger.info(
|
|
169
|
+
"[PractitionerInviteMailingService] Sending email with data:",
|
|
170
|
+
{
|
|
171
|
+
domain: domainToSendFrom,
|
|
172
|
+
to: mailgunSendData.to,
|
|
173
|
+
from: mailgunSendData.from,
|
|
174
|
+
subject: mailgunSendData.subject,
|
|
175
|
+
hasHtml: !!mailgunSendData.html,
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// Call the updated sendEmail method from BaseMailingService
|
|
180
|
+
const result = await this.sendEmail(domainToSendFrom, mailgunSendData);
|
|
181
|
+
|
|
182
|
+
// Log success
|
|
183
|
+
await this.logEmailAttempt(
|
|
184
|
+
{
|
|
185
|
+
to: data.token.email,
|
|
186
|
+
subject,
|
|
187
|
+
templateName: "practitioner_invitation",
|
|
188
|
+
},
|
|
189
|
+
true
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
return result;
|
|
193
|
+
} catch (error: any) {
|
|
194
|
+
Logger.error(
|
|
195
|
+
"[PractitionerInviteMailingService] Error sending invitation email:",
|
|
196
|
+
{
|
|
197
|
+
errorMessage: error.message,
|
|
198
|
+
errorDetails: error.details,
|
|
199
|
+
errorStatus: error.status,
|
|
200
|
+
stack: error.stack,
|
|
201
|
+
}
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// Log failure
|
|
205
|
+
await this.logEmailAttempt(
|
|
206
|
+
{
|
|
207
|
+
to: data.token.email,
|
|
208
|
+
subject: data.options?.customSubject || this.DEFAULT_SUBJECT,
|
|
209
|
+
templateName: "practitioner_invitation",
|
|
210
|
+
},
|
|
211
|
+
false,
|
|
212
|
+
error
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Handles the practitioner token creation event from Cloud Functions
|
|
221
|
+
* Fetches necessary data using defined types and collection constants,
|
|
222
|
+
* and sends the invitation email.
|
|
223
|
+
* @param tokenData The fully typed token object including its id
|
|
224
|
+
* @param fromAddress The 'from' email address to use, obtained from config
|
|
225
|
+
* @param mailgunDomain The mailgun domain to use for sending
|
|
226
|
+
* @returns Promise resolved when the email is sent
|
|
227
|
+
*/
|
|
228
|
+
async handleTokenCreationEvent(
|
|
229
|
+
tokenData: PractitionerToken,
|
|
230
|
+
mailgunConfig: {
|
|
231
|
+
fromAddress: string;
|
|
232
|
+
domain: string;
|
|
233
|
+
registrationUrl?: string;
|
|
234
|
+
}
|
|
235
|
+
): Promise<void> {
|
|
236
|
+
try {
|
|
237
|
+
Logger.info(
|
|
238
|
+
"[PractitionerInviteMailingService] Handling token creation event for token:",
|
|
239
|
+
tokenData.id
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// Validate token data
|
|
243
|
+
if (!tokenData || !tokenData.id || !tokenData.token || !tokenData.email) {
|
|
244
|
+
throw new Error(
|
|
245
|
+
`Invalid token data: Missing required properties. Token ID: ${tokenData?.id}`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!tokenData.practitionerId) {
|
|
250
|
+
throw new Error(
|
|
251
|
+
`Token ${tokenData.id} is missing practitionerId reference`
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!tokenData.clinicId) {
|
|
256
|
+
throw new Error(`Token ${tokenData.id} is missing clinicId reference`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!tokenData.expiresAt) {
|
|
260
|
+
throw new Error(`Token ${tokenData.id} is missing expiration date`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Validate token status (handle both enum and string values)
|
|
264
|
+
if (
|
|
265
|
+
tokenData.status !== PractitionerTokenStatus.ACTIVE &&
|
|
266
|
+
String(tokenData.status).toLowerCase() !== "active"
|
|
267
|
+
) {
|
|
268
|
+
Logger.warn(
|
|
269
|
+
"[PractitionerInviteMailingService] Token is not active, skipping email.",
|
|
270
|
+
{ tokenId: tokenData.id, status: tokenData.status }
|
|
271
|
+
);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Log token status to help with debugging
|
|
276
|
+
Logger.info(
|
|
277
|
+
`[PractitionerInviteMailingService] Token status validation:`,
|
|
278
|
+
{
|
|
279
|
+
tokenId: tokenData.id,
|
|
280
|
+
status: tokenData.status,
|
|
281
|
+
statusType: typeof tokenData.status,
|
|
282
|
+
}
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
// Get practitioner data using constant and type
|
|
286
|
+
Logger.info(
|
|
287
|
+
`[PractitionerInviteMailingService] Fetching practitioner data: ${tokenData.practitionerId}`
|
|
288
|
+
);
|
|
289
|
+
const practitionerRef = this.db
|
|
290
|
+
.collection(PRACTITIONERS_COLLECTION)
|
|
291
|
+
.doc(tokenData.practitionerId);
|
|
292
|
+
const practitionerDoc = await practitionerRef.get();
|
|
293
|
+
|
|
294
|
+
if (!practitionerDoc.exists) {
|
|
295
|
+
throw new Error(`Practitioner ${tokenData.practitionerId} not found`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const practitionerData = practitionerDoc.data() as Practitioner;
|
|
299
|
+
if (!practitionerData || !practitionerData.basicInfo) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`Practitioner ${tokenData.practitionerId} has invalid data structure`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
Logger.info(
|
|
306
|
+
`[PractitionerInviteMailingService] Practitioner found: ${practitionerData.basicInfo.firstName} ${practitionerData.basicInfo.lastName}`
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
// Get clinic data using constant and type
|
|
310
|
+
Logger.info(
|
|
311
|
+
`[PractitionerInviteMailingService] Fetching clinic data: ${tokenData.clinicId}`
|
|
312
|
+
);
|
|
313
|
+
const clinicRef = this.db
|
|
314
|
+
.collection(CLINICS_COLLECTION)
|
|
315
|
+
.doc(tokenData.clinicId);
|
|
316
|
+
const clinicDoc = await clinicRef.get();
|
|
317
|
+
|
|
318
|
+
if (!clinicDoc.exists) {
|
|
319
|
+
throw new Error(`Clinic ${tokenData.clinicId} not found`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const clinicData = clinicDoc.data() as Clinic;
|
|
323
|
+
if (!clinicData || !clinicData.contactInfo) {
|
|
324
|
+
throw new Error(
|
|
325
|
+
`Clinic ${tokenData.clinicId} has invalid data structure`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
Logger.info(
|
|
330
|
+
`[PractitionerInviteMailingService] Clinic found: ${clinicData.name}`
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
// Clinic model doesn't have contactPerson, only contactInfo
|
|
334
|
+
// So we'll use simple contact information from contactInfo
|
|
335
|
+
|
|
336
|
+
// Validate fromAddress
|
|
337
|
+
if (!mailgunConfig.fromAddress) {
|
|
338
|
+
Logger.warn(
|
|
339
|
+
"[PractitionerInviteMailingService] No fromAddress provided, using default"
|
|
340
|
+
);
|
|
341
|
+
mailgunConfig.fromAddress = `MetaEstetics <no-reply@${this.DEFAULT_MAILGUN_DOMAIN}>`;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Prepare email data using typed data
|
|
345
|
+
const emailData: PractitionerInviteEmailData = {
|
|
346
|
+
token: {
|
|
347
|
+
id: tokenData.id,
|
|
348
|
+
token: tokenData.token,
|
|
349
|
+
practitionerId: tokenData.practitionerId,
|
|
350
|
+
email: tokenData.email,
|
|
351
|
+
clinicId: tokenData.clinicId,
|
|
352
|
+
expiresAt: tokenData.expiresAt,
|
|
353
|
+
},
|
|
354
|
+
practitioner: {
|
|
355
|
+
firstName: practitionerData.basicInfo.firstName || "",
|
|
356
|
+
lastName: practitionerData.basicInfo.lastName || "",
|
|
357
|
+
},
|
|
358
|
+
clinic: {
|
|
359
|
+
name: clinicData.name || "Medical Clinic",
|
|
360
|
+
contactEmail: clinicData.contactInfo.email || "contact@medclinic.com",
|
|
361
|
+
// Since there's no contactPerson in the Clinic model, we'll just use "Clinic Admin"
|
|
362
|
+
contactName: "Clinic Admin",
|
|
363
|
+
},
|
|
364
|
+
options: {
|
|
365
|
+
fromAddress: mailgunConfig.fromAddress,
|
|
366
|
+
mailgunDomain: mailgunConfig.domain,
|
|
367
|
+
registrationUrl: mailgunConfig.registrationUrl,
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
Logger.info(
|
|
372
|
+
"[PractitionerInviteMailingService] Email data prepared, sending invitation"
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
// Send the invitation email
|
|
376
|
+
await this.sendInvitationEmail(emailData);
|
|
377
|
+
|
|
378
|
+
Logger.info(
|
|
379
|
+
"[PractitionerInviteMailingService] Invitation email sent successfully"
|
|
380
|
+
);
|
|
381
|
+
} catch (error: any) {
|
|
382
|
+
Logger.error(
|
|
383
|
+
"[PractitionerInviteMailingService] Error handling token creation event:",
|
|
384
|
+
{
|
|
385
|
+
errorMessage: error.message,
|
|
386
|
+
errorDetails: error.details,
|
|
387
|
+
errorStatus: error.status,
|
|
388
|
+
stack: error.stack,
|
|
389
|
+
tokenId: tokenData?.id,
|
|
390
|
+
}
|
|
391
|
+
);
|
|
392
|
+
throw error;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|