@blackcode_sa/metaestetics-api 1.14.65 → 1.14.71

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.
@@ -3055,6 +3055,11 @@ var AppointmentMailingService = class extends BaseMailingService {
3055
3055
  };
3056
3056
 
3057
3057
  // src/admin/aggregation/appointment/appointment.aggregation.service.ts
3058
+ function sanitizeCancellationReason(reason) {
3059
+ if (!reason) return void 0;
3060
+ const isLikelyId = !reason.includes(" ") && /^[a-zA-Z0-9_-]+$/.test(reason) && reason.length > 15;
3061
+ return isLikelyId ? void 0 : reason;
3062
+ }
3058
3063
  var AppointmentAggregationService = class {
3059
3064
  /**
3060
3065
  * Constructor for AppointmentAggregationService.
@@ -3336,7 +3341,7 @@ var AppointmentAggregationService = class {
3336
3341
  appointment: after,
3337
3342
  recipientProfile: after.patientInfo,
3338
3343
  recipientRole: "patient",
3339
- cancellationReason: after.cancellationReason
3344
+ cancellationReason: sanitizeCancellationReason(after.cancellationReason)
3340
3345
  };
3341
3346
  await this.appointmentMailingService.sendAppointmentCancelledEmail(
3342
3347
  patientCancellationData
@@ -3350,7 +3355,7 @@ var AppointmentAggregationService = class {
3350
3355
  appointment: after,
3351
3356
  recipientProfile: after.practitionerInfo,
3352
3357
  recipientRole: "practitioner",
3353
- cancellationReason: after.cancellationReason
3358
+ cancellationReason: sanitizeCancellationReason(after.cancellationReason)
3354
3359
  };
3355
3360
  await this.appointmentMailingService.sendAppointmentCancelledEmail(
3356
3361
  practitionerCancellationData
@@ -14012,6 +14017,405 @@ var PatientInviteMailingService = class extends BaseMailingService {
14012
14017
  }
14013
14018
  };
14014
14019
 
14020
+ // src/admin/mailing/clinicWelcome/templates/welcome.template.ts
14021
+ var clinicWelcomeTemplate = `
14022
+ <!DOCTYPE html>
14023
+ <html lang="en">
14024
+ <head>
14025
+ <meta charset="UTF-8">
14026
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
14027
+ <title>Welcome to MetaEsthetics</title>
14028
+ <!--[if mso]>
14029
+ <noscript>
14030
+ <xml>
14031
+ <o:OfficeDocumentSettings>
14032
+ <o:PixelsPerInch>96</o:PixelsPerInch>
14033
+ </o:OfficeDocumentSettings>
14034
+ </xml>
14035
+ </noscript>
14036
+ <![endif]-->
14037
+ </head>
14038
+ <body style="margin: 0; padding: 0; background-color: #f8f6f5; font-family: Georgia, 'Times New Roman', serif;">
14039
+ <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background-color: #f8f6f5;">
14040
+ <tr>
14041
+ <td align="center" style="padding: 40px 20px;">
14042
+ <table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0" style="max-width: 600px; background-color: #ffffff; border-radius: 2px; box-shadow: 0 1px 3px rgba(103, 87, 74, 0.08);">
14043
+
14044
+ <!-- Header -->
14045
+ <tr>
14046
+ <td style="padding: 48px 48px 32px 48px; border-bottom: 1px solid #f0ebe6;">
14047
+ <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
14048
+ <tr>
14049
+ <td>
14050
+ <h1 style="margin: 0; font-size: 24px; font-weight: 400; color: #67574A; letter-spacing: 2px; text-transform: uppercase; font-family: Georgia, 'Times New Roman', serif;">MetaEsthetics</h1>
14051
+ </td>
14052
+ </tr>
14053
+ </table>
14054
+ </td>
14055
+ </tr>
14056
+
14057
+ <!-- Welcome Banner -->
14058
+ <tr>
14059
+ <td style="padding: 0;">
14060
+ <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background-color: #00BB38;">
14061
+ <tr>
14062
+ <td style="padding: 20px 48px;">
14063
+ <p style="margin: 0; font-size: 13px; font-weight: 600; color: #ffffff; letter-spacing: 1.5px; text-transform: uppercase; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">Welcome to MetaEsthetics</p>
14064
+ </td>
14065
+ </tr>
14066
+ </table>
14067
+ </td>
14068
+ </tr>
14069
+
14070
+ <!-- Main Content -->
14071
+ <tr>
14072
+ <td style="padding: 40px 48px;">
14073
+
14074
+ <!-- Greeting -->
14075
+ <p style="margin: 0 0 24px 0; font-size: 17px; color: #333333; line-height: 1.6; font-family: Georgia, 'Times New Roman', serif;">
14076
+ Dear {{adminName}},
14077
+ </p>
14078
+
14079
+ <p style="margin: 0 0 32px 0; font-size: 16px; color: #555555; line-height: 1.7; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">
14080
+ Thank you for registering <strong style="color: #67574A;">{{clinicGroupName}}</strong> with MetaEsthetics. Your account has been successfully created and you're now ready to start managing your aesthetic practice.
14081
+ </p>
14082
+
14083
+ <!-- Account Details Card -->
14084
+ <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="margin-bottom: 32px; background-color: #faf9f7; border-left: 3px solid #00BB38;">
14085
+ <tr>
14086
+ <td style="padding: 24px;">
14087
+ <p style="margin: 0 0 16px 0; font-size: 12px; font-weight: 600; color: #868686; letter-spacing: 1px; text-transform: uppercase; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">Account Details</p>
14088
+
14089
+ <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
14090
+ <tr>
14091
+ <td style="padding-bottom: 12px;">
14092
+ <p style="margin: 0 0 2px 0; font-size: 11px; color: #868686; text-transform: uppercase; letter-spacing: 0.5px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">Clinic Group</p>
14093
+ <p style="margin: 0; font-size: 15px; color: #67574A; font-weight: 500; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">{{clinicGroupName}}</p>
14094
+ </td>
14095
+ </tr>
14096
+ <tr>
14097
+ <td style="padding-bottom: 12px;">
14098
+ <p style="margin: 0 0 2px 0; font-size: 11px; color: #868686; text-transform: uppercase; letter-spacing: 0.5px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">Admin Email</p>
14099
+ <p style="margin: 0; font-size: 15px; color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">{{adminEmail}}</p>
14100
+ </td>
14101
+ </tr>
14102
+ <tr>
14103
+ <td>
14104
+ <p style="margin: 0 0 2px 0; font-size: 11px; color: #868686; text-transform: uppercase; letter-spacing: 0.5px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">Registration Date</p>
14105
+ <p style="margin: 0; font-size: 15px; color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">{{registrationDate}}</p>
14106
+ </td>
14107
+ </tr>
14108
+ </table>
14109
+ </td>
14110
+ </tr>
14111
+ </table>
14112
+
14113
+ <!-- Divider -->
14114
+ <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="margin-bottom: 32px;">
14115
+ <tr>
14116
+ <td style="height: 1px; background-color: #e8e4df;"></td>
14117
+ </tr>
14118
+ </table>
14119
+
14120
+ <!-- Getting Started Section -->
14121
+ <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="margin-bottom: 32px;">
14122
+ <tr>
14123
+ <td>
14124
+ <p style="margin: 0 0 20px 0; font-size: 14px; font-weight: 600; color: #67574A; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">Getting Started</p>
14125
+ </td>
14126
+ </tr>
14127
+ </table>
14128
+
14129
+ <!-- Step 1 -->
14130
+ <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="margin-bottom: 16px;">
14131
+ <tr>
14132
+ <td style="padding: 16px 20px; background-color: #faf9f7; border-left: 3px solid #a48a76;">
14133
+ <p style="margin: 0 0 4px 0; font-size: 11px; font-weight: 600; color: #a48a76; letter-spacing: 1px; text-transform: uppercase; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">Step 1</p>
14134
+ <p style="margin: 0 0 4px 0; font-size: 15px; color: #333333; font-weight: 500; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">Complete Your Profile</p>
14135
+ <p style="margin: 0; font-size: 14px; color: #555555; line-height: 1.5; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">Add your clinic details, working hours, and upload photos to attract patients.</p>
14136
+ </td>
14137
+ </tr>
14138
+ </table>
14139
+
14140
+ <!-- Step 2 -->
14141
+ <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="margin-bottom: 16px;">
14142
+ <tr>
14143
+ <td style="padding: 16px 20px; background-color: #faf9f7; border-left: 3px solid #a48a76;">
14144
+ <p style="margin: 0 0 4px 0; font-size: 11px; font-weight: 600; color: #a48a76; letter-spacing: 1px; text-transform: uppercase; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">Step 2</p>
14145
+ <p style="margin: 0 0 4px 0; font-size: 15px; color: #333333; font-weight: 500; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">Add Your Team</p>
14146
+ <p style="margin: 0; font-size: 14px; color: #555555; line-height: 1.5; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">Invite practitioners and staff members to join your clinic.</p>
14147
+ </td>
14148
+ </tr>
14149
+ </table>
14150
+
14151
+ <!-- Step 3 -->
14152
+ <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="margin-bottom: 16px;">
14153
+ <tr>
14154
+ <td style="padding: 16px 20px; background-color: #faf9f7; border-left: 3px solid #a48a76;">
14155
+ <p style="margin: 0 0 4px 0; font-size: 11px; font-weight: 600; color: #a48a76; letter-spacing: 1px; text-transform: uppercase; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">Step 3</p>
14156
+ <p style="margin: 0 0 4px 0; font-size: 15px; color: #333333; font-weight: 500; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">Set Up Services</p>
14157
+ <p style="margin: 0; font-size: 14px; color: #555555; line-height: 1.5; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">Configure your procedures, pricing, and availability for bookings.</p>
14158
+ </td>
14159
+ </tr>
14160
+ </table>
14161
+
14162
+ <!-- Step 4 -->
14163
+ <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="margin-bottom: 32px;">
14164
+ <tr>
14165
+ <td style="padding: 16px 20px; background-color: #f0faf4; border-left: 3px solid #00BB38;">
14166
+ <p style="margin: 0 0 4px 0; font-size: 11px; font-weight: 600; color: #00BB38; letter-spacing: 1px; text-transform: uppercase; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">Step 4</p>
14167
+ <p style="margin: 0 0 4px 0; font-size: 15px; color: #333333; font-weight: 500; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">Start Accepting Patients</p>
14168
+ <p style="margin: 0; font-size: 14px; color: #555555; line-height: 1.5; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">Begin managing appointments and growing your practice.</p>
14169
+ </td>
14170
+ </tr>
14171
+ </table>
14172
+
14173
+ <!-- CTA Button -->
14174
+ <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="margin-bottom: 32px;">
14175
+ <tr>
14176
+ <td align="center">
14177
+ <a href="{{dashboardUrl}}" style="display: inline-block; background-color: #67574A; color: #ffffff; text-decoration: none; padding: 14px 32px; border-radius: 4px; font-size: 14px; font-weight: 600; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; letter-spacing: 0.5px;">Go to Dashboard</a>
14178
+ </td>
14179
+ </tr>
14180
+ </table>
14181
+
14182
+ <!-- Divider -->
14183
+ <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="margin-bottom: 32px;">
14184
+ <tr>
14185
+ <td style="height: 1px; background-color: #e8e4df;"></td>
14186
+ </tr>
14187
+ </table>
14188
+
14189
+ <!-- Support Info -->
14190
+ <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="margin-bottom: 24px;">
14191
+ <tr>
14192
+ <td style="padding: 20px 24px; background-color: #faf9f7; border-left: 3px solid #a48a76;">
14193
+ <p style="margin: 0 0 8px 0; font-size: 13px; font-weight: 600; color: #67574A; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">Need Help?</p>
14194
+ <p style="margin: 0; font-size: 14px; color: #555555; line-height: 1.6; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">
14195
+ Our support team is here to help you get started. Contact us at <a href="mailto:{{supportEmail}}" style="color: #a48a76; text-decoration: none;">{{supportEmail}}</a>
14196
+ </p>
14197
+ </td>
14198
+ </tr>
14199
+ </table>
14200
+
14201
+ <!-- Thank You -->
14202
+ <p style="margin: 0; font-size: 14px; color: #868686; line-height: 1.7; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">
14203
+ Thank you for choosing MetaEsthetics. We look forward to supporting your practice.
14204
+ </p>
14205
+
14206
+ </td>
14207
+ </tr>
14208
+
14209
+ <!-- Footer -->
14210
+ <tr>
14211
+ <td style="padding: 32px 48px; background-color: #faf9f7; border-top: 1px solid #f0ebe6;">
14212
+ <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
14213
+ <tr>
14214
+ <td>
14215
+ <p style="margin: 0 0 8px 0; font-size: 14px; font-weight: 500; color: #67574A; font-family: Georgia, 'Times New Roman', serif;">MetaEsthetics</p>
14216
+ <p style="margin: 0 0 16px 0; font-size: 13px; color: #868686; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">Premium Aesthetic Services Platform</p>
14217
+ <p style="margin: 0; font-size: 11px; color: #aaaaaa; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">This is an automated message from MetaEsthetics. Please do not reply directly to this email.</p>
14218
+ </td>
14219
+ </tr>
14220
+ </table>
14221
+ </td>
14222
+ </tr>
14223
+
14224
+ </table>
14225
+
14226
+ <!-- Copyright -->
14227
+ <table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0" style="max-width: 600px;">
14228
+ <tr>
14229
+ <td style="padding: 24px 48px; text-align: center;">
14230
+ <p style="margin: 0; font-size: 11px; color: #aaaaaa; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">&copy; {{currentYear}} MetaEsthetics. All rights reserved.</p>
14231
+ </td>
14232
+ </tr>
14233
+ </table>
14234
+
14235
+ </td>
14236
+ </tr>
14237
+ </table>
14238
+ </body>
14239
+ </html>
14240
+ `;
14241
+
14242
+ // src/admin/mailing/clinicWelcome/clinicWelcome.mailing.ts
14243
+ var ClinicWelcomeMailingService = class extends BaseMailingService {
14244
+ /**
14245
+ * Constructor for ClinicWelcomeMailingService
14246
+ * @param firestore - Firestore instance provided by the caller
14247
+ * @param mailgunClient - Mailgun client instance (mailgun.js v10+) provided by the caller
14248
+ */
14249
+ constructor(firestore19, mailgunClient) {
14250
+ super(firestore19, mailgunClient);
14251
+ this.DEFAULT_DASHBOARD_URL = "https://clinic.metaesthetics.net/dashboard";
14252
+ this.DEFAULT_SUPPORT_EMAIL = "support@metaesthetics.net";
14253
+ this.DEFAULT_SUBJECT = "Welcome to MetaEsthetics - Your Clinic Registration is Complete";
14254
+ this.DEFAULT_MAILGUN_DOMAIN = "mg.metaesthetics.net";
14255
+ }
14256
+ /**
14257
+ * Sends a clinic welcome email
14258
+ * @param data - The clinic welcome email data
14259
+ * @returns Promise resolved when email is sent
14260
+ */
14261
+ async sendWelcomeEmail(data) {
14262
+ var _a, _b, _c, _d, _e, _f, _g;
14263
+ try {
14264
+ Logger.info(
14265
+ "[ClinicWelcomeMailingService] Sending welcome email to",
14266
+ data.admin.email
14267
+ );
14268
+ const registrationDate = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
14269
+ weekday: "long",
14270
+ year: "numeric",
14271
+ month: "long",
14272
+ day: "numeric"
14273
+ });
14274
+ const dashboardUrl = ((_a = data.options) == null ? void 0 : _a.dashboardUrl) || this.DEFAULT_DASHBOARD_URL;
14275
+ const supportEmail = ((_b = data.options) == null ? void 0 : _b.supportEmail) || this.DEFAULT_SUPPORT_EMAIL;
14276
+ const subject = ((_c = data.options) == null ? void 0 : _c.customSubject) || this.DEFAULT_SUBJECT;
14277
+ const fromAddress = ((_d = data.options) == null ? void 0 : _d.fromAddress) || `MetaEsthetics <no-reply@${((_e = data.options) == null ? void 0 : _e.mailgunDomain) || this.DEFAULT_MAILGUN_DOMAIN}>`;
14278
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
14279
+ const templateVariables = {
14280
+ adminName: data.admin.name,
14281
+ adminEmail: data.admin.email,
14282
+ clinicGroupName: data.clinicGroup.name,
14283
+ registrationDate,
14284
+ dashboardUrl,
14285
+ supportEmail,
14286
+ currentYear
14287
+ };
14288
+ Logger.info("[ClinicWelcomeMailingService] Template variables:", {
14289
+ adminName: templateVariables.adminName,
14290
+ adminEmail: templateVariables.adminEmail,
14291
+ clinicGroupName: templateVariables.clinicGroupName,
14292
+ dashboardUrl: templateVariables.dashboardUrl
14293
+ });
14294
+ const html = this.renderTemplate(clinicWelcomeTemplate, templateVariables);
14295
+ const mailgunSendData = {
14296
+ to: data.admin.email,
14297
+ from: fromAddress,
14298
+ subject,
14299
+ html
14300
+ };
14301
+ const domainToSendFrom = ((_f = data.options) == null ? void 0 : _f.mailgunDomain) || this.DEFAULT_MAILGUN_DOMAIN;
14302
+ Logger.info("[ClinicWelcomeMailingService] Sending email with data:", {
14303
+ domain: domainToSendFrom,
14304
+ to: mailgunSendData.to,
14305
+ from: mailgunSendData.from,
14306
+ subject: mailgunSendData.subject,
14307
+ hasHtml: !!mailgunSendData.html
14308
+ });
14309
+ const result = await this.sendEmail(domainToSendFrom, mailgunSendData);
14310
+ await this.logEmailAttempt(
14311
+ {
14312
+ to: data.admin.email,
14313
+ subject,
14314
+ templateName: "clinic_welcome"
14315
+ },
14316
+ true
14317
+ );
14318
+ return result;
14319
+ } catch (error) {
14320
+ Logger.error(
14321
+ "[ClinicWelcomeMailingService] Error sending welcome email:",
14322
+ {
14323
+ errorMessage: error.message,
14324
+ errorDetails: error.details,
14325
+ errorStatus: error.status,
14326
+ stack: error.stack
14327
+ }
14328
+ );
14329
+ await this.logEmailAttempt(
14330
+ {
14331
+ to: data.admin.email,
14332
+ subject: ((_g = data.options) == null ? void 0 : _g.customSubject) || this.DEFAULT_SUBJECT,
14333
+ templateName: "clinic_welcome"
14334
+ },
14335
+ false,
14336
+ error
14337
+ );
14338
+ throw error;
14339
+ }
14340
+ }
14341
+ /**
14342
+ * Handles the clinic group creation event from Cloud Functions.
14343
+ * Fetches necessary data and sends the welcome email to the admin.
14344
+ * @param clinicGroupData - The clinic group data including id
14345
+ * @param mailgunConfig - Mailgun configuration (from, domain)
14346
+ * @returns Promise resolved when the email is sent
14347
+ */
14348
+ async handleClinicGroupCreationEvent(clinicGroupData, mailgunConfig) {
14349
+ try {
14350
+ Logger.info(
14351
+ "[ClinicWelcomeMailingService] Handling clinic group creation event for:",
14352
+ clinicGroupData.id
14353
+ );
14354
+ if (!clinicGroupData || !clinicGroupData.id) {
14355
+ throw new Error(
14356
+ `Invalid clinic group data: Missing required properties.`
14357
+ );
14358
+ }
14359
+ if (!clinicGroupData.contactPerson) {
14360
+ throw new Error(
14361
+ `Clinic group ${clinicGroupData.id} is missing contactPerson`
14362
+ );
14363
+ }
14364
+ if (!clinicGroupData.contactPerson.email) {
14365
+ throw new Error(
14366
+ `Clinic group ${clinicGroupData.id} is missing admin email`
14367
+ );
14368
+ }
14369
+ const adminName = `${clinicGroupData.contactPerson.firstName || ""} ${clinicGroupData.contactPerson.lastName || ""}`.trim() || "Clinic Admin";
14370
+ const adminEmail = clinicGroupData.contactPerson.email;
14371
+ Logger.info(
14372
+ `[ClinicWelcomeMailingService] Sending welcome email to admin: ${adminName} (${adminEmail})`
14373
+ );
14374
+ if (!mailgunConfig.fromAddress) {
14375
+ Logger.warn(
14376
+ "[ClinicWelcomeMailingService] No fromAddress provided, using default"
14377
+ );
14378
+ mailgunConfig.fromAddress = `MetaEsthetics <no-reply@${this.DEFAULT_MAILGUN_DOMAIN}>`;
14379
+ }
14380
+ const emailData = {
14381
+ admin: {
14382
+ name: adminName,
14383
+ email: adminEmail
14384
+ },
14385
+ clinicGroup: {
14386
+ id: clinicGroupData.id,
14387
+ name: clinicGroupData.name || "Your Clinic"
14388
+ },
14389
+ options: {
14390
+ fromAddress: mailgunConfig.fromAddress,
14391
+ mailgunDomain: mailgunConfig.domain,
14392
+ dashboardUrl: mailgunConfig.dashboardUrl,
14393
+ supportEmail: mailgunConfig.supportEmail
14394
+ }
14395
+ };
14396
+ Logger.info(
14397
+ "[ClinicWelcomeMailingService] Email data prepared, sending welcome email"
14398
+ );
14399
+ await this.sendWelcomeEmail(emailData);
14400
+ Logger.info(
14401
+ "[ClinicWelcomeMailingService] Welcome email sent successfully"
14402
+ );
14403
+ } catch (error) {
14404
+ Logger.error(
14405
+ "[ClinicWelcomeMailingService] Error handling clinic group creation event:",
14406
+ {
14407
+ errorMessage: error.message,
14408
+ errorDetails: error.details,
14409
+ errorStatus: error.status,
14410
+ stack: error.stack,
14411
+ clinicGroupId: clinicGroupData == null ? void 0 : clinicGroupData.id
14412
+ }
14413
+ );
14414
+ throw error;
14415
+ }
14416
+ }
14417
+ };
14418
+
14015
14419
  // src/admin/users/user-profile.admin.ts
14016
14420
  import * as admin18 from "firebase-admin";
14017
14421
  var UserProfileAdminService = class {
@@ -14313,6 +14717,7 @@ export {
14313
14717
  CLINIC_ANALYTICS_SUBCOLLECTION,
14314
14718
  CalendarAdminService,
14315
14719
  ClinicAggregationService,
14720
+ ClinicWelcomeMailingService,
14316
14721
  DASHBOARD_ANALYTICS_SUBCOLLECTION,
14317
14722
  DocumentManagerAdminService,
14318
14723
  ExistingPractitionerInviteMailingService,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blackcode_sa/metaestetics-api",
3
3
  "private": false,
4
- "version": "1.14.65",
4
+ "version": "1.14.71",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.mjs",
@@ -41,6 +41,23 @@ import { NotificationType } from '../../../types/notifications';
41
41
 
42
42
  // Mailgun client will be injected via constructor
43
43
 
44
+ /**
45
+ * Helper to sanitize cancellation reason - filters out ID-like strings
46
+ * that were mistakenly passed as reasons (e.g., clinicId)
47
+ */
48
+ function sanitizeCancellationReason(reason: string | null | undefined): string | undefined {
49
+ if (!reason) return undefined;
50
+
51
+ // Detect if the reason looks like an ID rather than actual text
52
+ // IDs typically: no spaces, alphanumeric with dashes/underscores, length > 15
53
+ const isLikelyId =
54
+ !reason.includes(' ') &&
55
+ /^[a-zA-Z0-9_-]+$/.test(reason) &&
56
+ reason.length > 15;
57
+
58
+ return isLikelyId ? undefined : reason;
59
+ }
60
+
44
61
  /**
45
62
  * Type for requirement with source procedure tracking
46
63
  */
@@ -408,7 +425,7 @@ export class AppointmentAggregationService {
408
425
  appointment: after,
409
426
  recipientProfile: after.patientInfo,
410
427
  recipientRole: 'patient' as const,
411
- cancellationReason: after.cancellationReason,
428
+ cancellationReason: sanitizeCancellationReason(after.cancellationReason),
412
429
  };
413
430
  await this.appointmentMailingService.sendAppointmentCancelledEmail(
414
431
  patientCancellationData as any,
@@ -424,7 +441,7 @@ export class AppointmentAggregationService {
424
441
  appointment: after,
425
442
  recipientProfile: after.practitionerInfo,
426
443
  recipientRole: 'practitioner' as const,
427
- cancellationReason: after.cancellationReason,
444
+ cancellationReason: sanitizeCancellationReason(after.cancellationReason),
428
445
  };
429
446
  await this.appointmentMailingService.sendAppointmentCancelledEmail(
430
447
  practitionerCancellationData as any,
@@ -10,7 +10,7 @@ export type {
10
10
  } from '../types/notifications';
11
11
  export type { PatientProfile, PatientSensitiveInfo } from '../types/patient';
12
12
  export { PATIENTS_COLLECTION, PATIENT_SENSITIVE_INFO_COLLECTION } from '../types/patient';
13
- export type { Clinic, ClinicLocation } from '../types/clinic';
13
+ export type { Clinic, ClinicLocation, ClinicGroup } from '../types/clinic';
14
14
  export type { ClinicInfo } from '../types/profile';
15
15
  export type { Practitioner, PractitionerToken } from '../types/practitioner';
16
16
  export type { PractitionerInvite } from '../types/clinic/practitioner-invite.types';