@blackcode_sa/metaestetics-api 1.14.65 → 1.14.69

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.
@@ -232,6 +232,7 @@ __export(index_exports, {
232
232
  CLINIC_ANALYTICS_SUBCOLLECTION: () => CLINIC_ANALYTICS_SUBCOLLECTION,
233
233
  CalendarAdminService: () => CalendarAdminService,
234
234
  ClinicAggregationService: () => ClinicAggregationService,
235
+ ClinicWelcomeMailingService: () => ClinicWelcomeMailingService,
235
236
  DASHBOARD_ANALYTICS_SUBCOLLECTION: () => DASHBOARD_ANALYTICS_SUBCOLLECTION,
236
237
  DocumentManagerAdminService: () => DocumentManagerAdminService,
237
238
  ExistingPractitionerInviteMailingService: () => ExistingPractitionerInviteMailingService,
@@ -3131,6 +3132,11 @@ var AppointmentMailingService = class extends BaseMailingService {
3131
3132
  };
3132
3133
 
3133
3134
  // src/admin/aggregation/appointment/appointment.aggregation.service.ts
3135
+ function sanitizeCancellationReason(reason) {
3136
+ if (!reason) return void 0;
3137
+ const isLikelyId = !reason.includes(" ") && /^[a-zA-Z0-9_-]+$/.test(reason) && reason.length > 15;
3138
+ return isLikelyId ? void 0 : reason;
3139
+ }
3134
3140
  var AppointmentAggregationService = class {
3135
3141
  /**
3136
3142
  * Constructor for AppointmentAggregationService.
@@ -3412,7 +3418,7 @@ var AppointmentAggregationService = class {
3412
3418
  appointment: after,
3413
3419
  recipientProfile: after.patientInfo,
3414
3420
  recipientRole: "patient",
3415
- cancellationReason: after.cancellationReason
3421
+ cancellationReason: sanitizeCancellationReason(after.cancellationReason)
3416
3422
  };
3417
3423
  await this.appointmentMailingService.sendAppointmentCancelledEmail(
3418
3424
  patientCancellationData
@@ -3426,7 +3432,7 @@ var AppointmentAggregationService = class {
3426
3432
  appointment: after,
3427
3433
  recipientProfile: after.practitionerInfo,
3428
3434
  recipientRole: "practitioner",
3429
- cancellationReason: after.cancellationReason
3435
+ cancellationReason: sanitizeCancellationReason(after.cancellationReason)
3430
3436
  };
3431
3437
  await this.appointmentMailingService.sendAppointmentCancelledEmail(
3432
3438
  practitionerCancellationData
@@ -14088,6 +14094,433 @@ var PatientInviteMailingService = class extends BaseMailingService {
14088
14094
  }
14089
14095
  };
14090
14096
 
14097
+ // src/admin/mailing/clinicWelcome/templates/welcome.template.ts
14098
+ var clinicWelcomeTemplate = `
14099
+ <!DOCTYPE html>
14100
+ <html>
14101
+ <head>
14102
+ <meta charset="UTF-8">
14103
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
14104
+ <title>Welcome to MetaEsthetics</title>
14105
+ <style>
14106
+ body {
14107
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
14108
+ line-height: 1.6;
14109
+ color: #333;
14110
+ margin: 0;
14111
+ padding: 0;
14112
+ background-color: #f5f5f5;
14113
+ }
14114
+ .container {
14115
+ max-width: 600px;
14116
+ margin: 0 auto;
14117
+ padding: 20px;
14118
+ }
14119
+ .header {
14120
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
14121
+ padding: 40px 20px;
14122
+ text-align: center;
14123
+ border-radius: 8px 8px 0 0;
14124
+ }
14125
+ .header h1 {
14126
+ color: white;
14127
+ margin: 0;
14128
+ font-size: 28px;
14129
+ font-weight: 600;
14130
+ }
14131
+ .header p {
14132
+ color: rgba(255, 255, 255, 0.9);
14133
+ margin: 10px 0 0 0;
14134
+ font-size: 16px;
14135
+ }
14136
+ .content {
14137
+ padding: 40px 30px;
14138
+ background-color: #ffffff;
14139
+ }
14140
+ .greeting {
14141
+ font-size: 18px;
14142
+ color: #333;
14143
+ margin-bottom: 20px;
14144
+ }
14145
+ .message {
14146
+ font-size: 16px;
14147
+ color: #555;
14148
+ margin-bottom: 25px;
14149
+ }
14150
+ .highlight-box {
14151
+ background: linear-gradient(135deg, #f6f9fc 0%, #eef2f7 100%);
14152
+ border-left: 4px solid #667eea;
14153
+ padding: 20px;
14154
+ margin: 25px 0;
14155
+ border-radius: 0 8px 8px 0;
14156
+ }
14157
+ .highlight-box h3 {
14158
+ margin: 0 0 10px 0;
14159
+ color: #333;
14160
+ font-size: 16px;
14161
+ }
14162
+ .highlight-box p {
14163
+ margin: 0;
14164
+ color: #555;
14165
+ }
14166
+ .next-steps {
14167
+ margin: 30px 0;
14168
+ }
14169
+ .next-steps h3 {
14170
+ color: #333;
14171
+ font-size: 18px;
14172
+ margin-bottom: 15px;
14173
+ }
14174
+ .step {
14175
+ display: flex;
14176
+ align-items: flex-start;
14177
+ margin-bottom: 15px;
14178
+ }
14179
+ .step-number {
14180
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
14181
+ color: white;
14182
+ width: 28px;
14183
+ height: 28px;
14184
+ border-radius: 50%;
14185
+ display: flex;
14186
+ align-items: center;
14187
+ justify-content: center;
14188
+ font-weight: 600;
14189
+ font-size: 14px;
14190
+ margin-right: 12px;
14191
+ flex-shrink: 0;
14192
+ }
14193
+ .step-content {
14194
+ flex: 1;
14195
+ padding-top: 3px;
14196
+ }
14197
+ .step-title {
14198
+ font-weight: 600;
14199
+ color: #333;
14200
+ margin-bottom: 3px;
14201
+ }
14202
+ .step-description {
14203
+ color: #666;
14204
+ font-size: 14px;
14205
+ }
14206
+ .button-container {
14207
+ text-align: center;
14208
+ margin: 35px 0;
14209
+ }
14210
+ .button {
14211
+ display: inline-block;
14212
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
14213
+ color: white;
14214
+ text-decoration: none;
14215
+ padding: 14px 32px;
14216
+ border-radius: 6px;
14217
+ font-weight: 600;
14218
+ font-size: 16px;
14219
+ transition: transform 0.2s;
14220
+ }
14221
+ .button:hover {
14222
+ transform: translateY(-2px);
14223
+ }
14224
+ .support-box {
14225
+ background-color: #f8f9fa;
14226
+ padding: 20px;
14227
+ border-radius: 8px;
14228
+ margin-top: 30px;
14229
+ text-align: center;
14230
+ }
14231
+ .support-box p {
14232
+ margin: 0 0 10px 0;
14233
+ color: #555;
14234
+ }
14235
+ .support-box a {
14236
+ color: #667eea;
14237
+ text-decoration: none;
14238
+ font-weight: 500;
14239
+ }
14240
+ .footer {
14241
+ padding: 30px;
14242
+ text-align: center;
14243
+ background-color: #f8f9fa;
14244
+ border-radius: 0 0 8px 8px;
14245
+ }
14246
+ .footer p {
14247
+ margin: 5px 0;
14248
+ font-size: 13px;
14249
+ color: #888;
14250
+ }
14251
+ .footer a {
14252
+ color: #667eea;
14253
+ text-decoration: none;
14254
+ }
14255
+ .social-links {
14256
+ margin: 15px 0;
14257
+ }
14258
+ .social-links a {
14259
+ display: inline-block;
14260
+ margin: 0 8px;
14261
+ color: #888;
14262
+ text-decoration: none;
14263
+ }
14264
+ </style>
14265
+ </head>
14266
+ <body>
14267
+ <div class="container">
14268
+ <div class="header">
14269
+ <h1>Welcome to MetaEsthetics!</h1>
14270
+ <p>Your clinic registration is complete</p>
14271
+ </div>
14272
+ <div class="content">
14273
+ <p class="greeting">Hello {{adminName}},</p>
14274
+
14275
+ <p class="message">
14276
+ Thank you for registering <strong>{{clinicGroupName}}</strong> with MetaEsthetics.
14277
+ Your account has been successfully created and you're now ready to start managing
14278
+ your aesthetic practice with our comprehensive platform.
14279
+ </p>
14280
+
14281
+ <div class="highlight-box">
14282
+ <h3>Account Details</h3>
14283
+ <p><strong>Clinic Group:</strong> {{clinicGroupName}}</p>
14284
+ <p><strong>Admin Email:</strong> {{adminEmail}}</p>
14285
+ <p><strong>Registration Date:</strong> {{registrationDate}}</p>
14286
+ </div>
14287
+
14288
+ <div class="next-steps">
14289
+ <h3>Get Started with MetaEsthetics</h3>
14290
+
14291
+ <div class="step">
14292
+ <div class="step-number">1</div>
14293
+ <div class="step-content">
14294
+ <div class="step-title">Complete Your Profile</div>
14295
+ <div class="step-description">Add your clinic details, working hours, and upload photos to attract patients.</div>
14296
+ </div>
14297
+ </div>
14298
+
14299
+ <div class="step">
14300
+ <div class="step-number">2</div>
14301
+ <div class="step-content">
14302
+ <div class="step-title">Add Your Team</div>
14303
+ <div class="step-description">Invite practitioners and staff members to join your clinic.</div>
14304
+ </div>
14305
+ </div>
14306
+
14307
+ <div class="step">
14308
+ <div class="step-number">3</div>
14309
+ <div class="step-content">
14310
+ <div class="step-title">Set Up Services</div>
14311
+ <div class="step-description">Configure your procedures, pricing, and availability for bookings.</div>
14312
+ </div>
14313
+ </div>
14314
+
14315
+ <div class="step">
14316
+ <div class="step-number">4</div>
14317
+ <div class="step-content">
14318
+ <div class="step-title">Start Accepting Patients</div>
14319
+ <div class="step-description">Begin managing appointments and growing your practice.</div>
14320
+ </div>
14321
+ </div>
14322
+ </div>
14323
+
14324
+ <div class="button-container">
14325
+ <a href="{{dashboardUrl}}" class="button">Go to Dashboard</a>
14326
+ </div>
14327
+
14328
+ <div class="support-box">
14329
+ <p>Need help getting started?</p>
14330
+ <p>Contact our support team at <a href="mailto:{{supportEmail}}">{{supportEmail}}</a></p>
14331
+ </div>
14332
+ </div>
14333
+ <div class="footer">
14334
+ <p>This is an automated message from MetaEsthetics.</p>
14335
+ <p>&copy; {{currentYear}} MetaEsthetics. All rights reserved.</p>
14336
+ <div class="social-links">
14337
+ <a href="https://metaesthetics.net">Website</a> |
14338
+ <a href="https://metaesthetics.net/privacy">Privacy Policy</a> |
14339
+ <a href="https://metaesthetics.net/terms">Terms of Service</a>
14340
+ </div>
14341
+ </div>
14342
+ </div>
14343
+ </body>
14344
+ </html>
14345
+ `;
14346
+
14347
+ // src/admin/mailing/clinicWelcome/clinicWelcome.mailing.ts
14348
+ var ClinicWelcomeMailingService = class extends BaseMailingService {
14349
+ /**
14350
+ * Constructor for ClinicWelcomeMailingService
14351
+ * @param firestore - Firestore instance provided by the caller
14352
+ * @param mailgunClient - Mailgun client instance (mailgun.js v10+) provided by the caller
14353
+ */
14354
+ constructor(firestore19, mailgunClient) {
14355
+ super(firestore19, mailgunClient);
14356
+ this.DEFAULT_DASHBOARD_URL = "https://clinic.metaesthetics.net/dashboard";
14357
+ this.DEFAULT_SUPPORT_EMAIL = "support@metaesthetics.net";
14358
+ this.DEFAULT_SUBJECT = "Welcome to MetaEsthetics - Your Clinic Registration is Complete";
14359
+ this.DEFAULT_MAILGUN_DOMAIN = "mg.metaesthetics.net";
14360
+ }
14361
+ /**
14362
+ * Sends a clinic welcome email
14363
+ * @param data - The clinic welcome email data
14364
+ * @returns Promise resolved when email is sent
14365
+ */
14366
+ async sendWelcomeEmail(data) {
14367
+ var _a, _b, _c, _d, _e, _f, _g;
14368
+ try {
14369
+ Logger.info(
14370
+ "[ClinicWelcomeMailingService] Sending welcome email to",
14371
+ data.admin.email
14372
+ );
14373
+ const registrationDate = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
14374
+ weekday: "long",
14375
+ year: "numeric",
14376
+ month: "long",
14377
+ day: "numeric"
14378
+ });
14379
+ const dashboardUrl = ((_a = data.options) == null ? void 0 : _a.dashboardUrl) || this.DEFAULT_DASHBOARD_URL;
14380
+ const supportEmail = ((_b = data.options) == null ? void 0 : _b.supportEmail) || this.DEFAULT_SUPPORT_EMAIL;
14381
+ const subject = ((_c = data.options) == null ? void 0 : _c.customSubject) || this.DEFAULT_SUBJECT;
14382
+ 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}>`;
14383
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
14384
+ const templateVariables = {
14385
+ adminName: data.admin.name,
14386
+ adminEmail: data.admin.email,
14387
+ clinicGroupName: data.clinicGroup.name,
14388
+ registrationDate,
14389
+ dashboardUrl,
14390
+ supportEmail,
14391
+ currentYear
14392
+ };
14393
+ Logger.info("[ClinicWelcomeMailingService] Template variables:", {
14394
+ adminName: templateVariables.adminName,
14395
+ adminEmail: templateVariables.adminEmail,
14396
+ clinicGroupName: templateVariables.clinicGroupName,
14397
+ dashboardUrl: templateVariables.dashboardUrl
14398
+ });
14399
+ const html = this.renderTemplate(clinicWelcomeTemplate, templateVariables);
14400
+ const mailgunSendData = {
14401
+ to: data.admin.email,
14402
+ from: fromAddress,
14403
+ subject,
14404
+ html
14405
+ };
14406
+ const domainToSendFrom = ((_f = data.options) == null ? void 0 : _f.mailgunDomain) || this.DEFAULT_MAILGUN_DOMAIN;
14407
+ Logger.info("[ClinicWelcomeMailingService] Sending email with data:", {
14408
+ domain: domainToSendFrom,
14409
+ to: mailgunSendData.to,
14410
+ from: mailgunSendData.from,
14411
+ subject: mailgunSendData.subject,
14412
+ hasHtml: !!mailgunSendData.html
14413
+ });
14414
+ const result = await this.sendEmail(domainToSendFrom, mailgunSendData);
14415
+ await this.logEmailAttempt(
14416
+ {
14417
+ to: data.admin.email,
14418
+ subject,
14419
+ templateName: "clinic_welcome"
14420
+ },
14421
+ true
14422
+ );
14423
+ return result;
14424
+ } catch (error) {
14425
+ Logger.error(
14426
+ "[ClinicWelcomeMailingService] Error sending welcome email:",
14427
+ {
14428
+ errorMessage: error.message,
14429
+ errorDetails: error.details,
14430
+ errorStatus: error.status,
14431
+ stack: error.stack
14432
+ }
14433
+ );
14434
+ await this.logEmailAttempt(
14435
+ {
14436
+ to: data.admin.email,
14437
+ subject: ((_g = data.options) == null ? void 0 : _g.customSubject) || this.DEFAULT_SUBJECT,
14438
+ templateName: "clinic_welcome"
14439
+ },
14440
+ false,
14441
+ error
14442
+ );
14443
+ throw error;
14444
+ }
14445
+ }
14446
+ /**
14447
+ * Handles the clinic group creation event from Cloud Functions.
14448
+ * Fetches necessary data and sends the welcome email to the admin.
14449
+ * @param clinicGroupData - The clinic group data including id
14450
+ * @param mailgunConfig - Mailgun configuration (from, domain)
14451
+ * @returns Promise resolved when the email is sent
14452
+ */
14453
+ async handleClinicGroupCreationEvent(clinicGroupData, mailgunConfig) {
14454
+ try {
14455
+ Logger.info(
14456
+ "[ClinicWelcomeMailingService] Handling clinic group creation event for:",
14457
+ clinicGroupData.id
14458
+ );
14459
+ if (!clinicGroupData || !clinicGroupData.id) {
14460
+ throw new Error(
14461
+ `Invalid clinic group data: Missing required properties.`
14462
+ );
14463
+ }
14464
+ if (!clinicGroupData.contactPerson) {
14465
+ throw new Error(
14466
+ `Clinic group ${clinicGroupData.id} is missing contactPerson`
14467
+ );
14468
+ }
14469
+ if (!clinicGroupData.contactPerson.email) {
14470
+ throw new Error(
14471
+ `Clinic group ${clinicGroupData.id} is missing admin email`
14472
+ );
14473
+ }
14474
+ const adminName = `${clinicGroupData.contactPerson.firstName || ""} ${clinicGroupData.contactPerson.lastName || ""}`.trim() || "Clinic Admin";
14475
+ const adminEmail = clinicGroupData.contactPerson.email;
14476
+ Logger.info(
14477
+ `[ClinicWelcomeMailingService] Sending welcome email to admin: ${adminName} (${adminEmail})`
14478
+ );
14479
+ if (!mailgunConfig.fromAddress) {
14480
+ Logger.warn(
14481
+ "[ClinicWelcomeMailingService] No fromAddress provided, using default"
14482
+ );
14483
+ mailgunConfig.fromAddress = `MetaEsthetics <no-reply@${this.DEFAULT_MAILGUN_DOMAIN}>`;
14484
+ }
14485
+ const emailData = {
14486
+ admin: {
14487
+ name: adminName,
14488
+ email: adminEmail
14489
+ },
14490
+ clinicGroup: {
14491
+ id: clinicGroupData.id,
14492
+ name: clinicGroupData.name || "Your Clinic"
14493
+ },
14494
+ options: {
14495
+ fromAddress: mailgunConfig.fromAddress,
14496
+ mailgunDomain: mailgunConfig.domain,
14497
+ dashboardUrl: mailgunConfig.dashboardUrl,
14498
+ supportEmail: mailgunConfig.supportEmail
14499
+ }
14500
+ };
14501
+ Logger.info(
14502
+ "[ClinicWelcomeMailingService] Email data prepared, sending welcome email"
14503
+ );
14504
+ await this.sendWelcomeEmail(emailData);
14505
+ Logger.info(
14506
+ "[ClinicWelcomeMailingService] Welcome email sent successfully"
14507
+ );
14508
+ } catch (error) {
14509
+ Logger.error(
14510
+ "[ClinicWelcomeMailingService] Error handling clinic group creation event:",
14511
+ {
14512
+ errorMessage: error.message,
14513
+ errorDetails: error.details,
14514
+ errorStatus: error.status,
14515
+ stack: error.stack,
14516
+ clinicGroupId: clinicGroupData == null ? void 0 : clinicGroupData.id
14517
+ }
14518
+ );
14519
+ throw error;
14520
+ }
14521
+ }
14522
+ };
14523
+
14091
14524
  // src/admin/users/user-profile.admin.ts
14092
14525
  var admin18 = __toESM(require("firebase-admin"));
14093
14526
  var UserProfileAdminService = class {
@@ -14390,6 +14823,7 @@ TimestampUtils.enableServerMode();
14390
14823
  CLINIC_ANALYTICS_SUBCOLLECTION,
14391
14824
  CalendarAdminService,
14392
14825
  ClinicAggregationService,
14826
+ ClinicWelcomeMailingService,
14393
14827
  DASHBOARD_ANALYTICS_SUBCOLLECTION,
14394
14828
  DocumentManagerAdminService,
14395
14829
  ExistingPractitionerInviteMailingService,