@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
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/admin/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ BaseMailingService: () => BaseMailingService,
33
34
  ClinicAggregationService: () => ClinicAggregationService,
34
35
  NOTIFICATIONS_COLLECTION: () => NOTIFICATIONS_COLLECTION,
35
36
  NotificationStatus: () => NotificationStatus,
@@ -37,6 +38,7 @@ __export(index_exports, {
37
38
  NotificationsAdmin: () => NotificationsAdmin,
38
39
  PatientAggregationService: () => PatientAggregationService,
39
40
  PractitionerAggregationService: () => PractitionerAggregationService,
41
+ PractitionerInviteMailingService: () => PractitionerInviteMailingService,
40
42
  ProcedureAggregationService: () => ProcedureAggregationService,
41
43
  UserRole: () => UserRole
42
44
  });
@@ -64,9 +66,9 @@ var NotificationStatus = /* @__PURE__ */ ((NotificationStatus2) => {
64
66
  var admin = __toESM(require("firebase-admin"));
65
67
  var import_expo_server_sdk = require("expo-server-sdk");
66
68
  var NotificationsAdmin = class {
67
- constructor(firestore6) {
69
+ constructor(firestore7) {
68
70
  this.expo = new import_expo_server_sdk.Expo();
69
- this.db = firestore6 || admin.firestore();
71
+ this.db = firestore7 || admin.firestore();
70
72
  }
71
73
  /**
72
74
  * Dohvata notifikaciju po ID-u
@@ -253,8 +255,8 @@ var ClinicAggregationService = class {
253
255
  * Constructor for ClinicAggregationService.
254
256
  * @param firestore Optional Firestore instance. If not provided, it uses the default admin SDK instance.
255
257
  */
256
- constructor(firestore6) {
257
- this.db = firestore6 || admin2.firestore();
258
+ constructor(firestore7) {
259
+ this.db = firestore7 || admin2.firestore();
258
260
  }
259
261
  /**
260
262
  * Adds clinic information to a clinic group when a new clinic is created
@@ -728,8 +730,8 @@ var ClinicAggregationService = class {
728
730
  var admin3 = __toESM(require("firebase-admin"));
729
731
  var CALENDAR_SUBCOLLECTION_ID2 = "calendar";
730
732
  var PractitionerAggregationService = class {
731
- constructor(firestore6) {
732
- this.db = firestore6 || admin3.firestore();
733
+ constructor(firestore7) {
734
+ this.db = firestore7 || admin3.firestore();
733
735
  }
734
736
  /**
735
737
  * Adds practitioner information to a clinic when a new practitioner is created
@@ -1064,8 +1066,8 @@ var PractitionerAggregationService = class {
1064
1066
  var admin4 = __toESM(require("firebase-admin"));
1065
1067
  var CALENDAR_SUBCOLLECTION_ID3 = "calendar";
1066
1068
  var ProcedureAggregationService = class {
1067
- constructor(firestore6) {
1068
- this.db = firestore6 || admin4.firestore();
1069
+ constructor(firestore7) {
1070
+ this.db = firestore7 || admin4.firestore();
1069
1071
  }
1070
1072
  /**
1071
1073
  * Adds procedure information to a practitioner when a new procedure is created
@@ -1449,8 +1451,8 @@ var ProcedureAggregationService = class {
1449
1451
  var admin5 = __toESM(require("firebase-admin"));
1450
1452
  var CALENDAR_SUBCOLLECTION_ID4 = "calendar";
1451
1453
  var PatientAggregationService = class {
1452
- constructor(firestore6) {
1453
- this.db = firestore6 || admin5.firestore();
1454
+ constructor(firestore7) {
1455
+ this.db = firestore7 || admin5.firestore();
1454
1456
  }
1455
1457
  // --- Methods for Patient Creation --- >
1456
1458
  // No specific aggregations defined for patient creation in the plan.
@@ -1554,6 +1556,339 @@ var PatientAggregationService = class {
1554
1556
  }
1555
1557
  };
1556
1558
 
1559
+ // src/admin/mailing/base.mailing.service.ts
1560
+ var admin6 = __toESM(require("firebase-admin"));
1561
+ var BaseMailingService = class {
1562
+ // Removed config property as it's no longer managed here
1563
+ // protected config: MailgunConfig;
1564
+ /**
1565
+ * Constructor for BaseMailingService
1566
+ * @param firestore Firestore instance provided by the caller
1567
+ * @param mailgunClient Mailgun client instance provided by the caller
1568
+ */
1569
+ constructor(firestore7, mailgunClient) {
1570
+ this.db = firestore7;
1571
+ this.mailgunClient = mailgunClient;
1572
+ }
1573
+ /**
1574
+ * Sends an email using Mailgun
1575
+ * @param data Email data to send, including the 'from' address
1576
+ * @returns Promise with the sending result
1577
+ */
1578
+ async sendEmail(data) {
1579
+ try {
1580
+ if (!data.from) {
1581
+ throw new Error(
1582
+ "Email 'from' address must be provided in sendEmail data."
1583
+ );
1584
+ }
1585
+ return await new Promise(
1586
+ (resolve, reject) => {
1587
+ this.mailgunClient.messages().send(data, (error, body) => {
1588
+ if (error) {
1589
+ console.error("[BaseMailingService] Error sending email:", error);
1590
+ reject(error);
1591
+ } else {
1592
+ console.log(
1593
+ "[BaseMailingService] Email sent successfully:",
1594
+ body
1595
+ );
1596
+ resolve(body);
1597
+ }
1598
+ });
1599
+ }
1600
+ );
1601
+ } catch (error) {
1602
+ console.error("[BaseMailingService] Error in sendEmail:", error);
1603
+ throw error;
1604
+ }
1605
+ }
1606
+ /**
1607
+ * Logs email sending attempt to Firestore for tracking
1608
+ * @param emailData Email data that was sent
1609
+ * @param success Whether the email was sent successfully
1610
+ * @param error Error object if the email failed to send
1611
+ */
1612
+ async logEmailAttempt(emailData, success, error) {
1613
+ try {
1614
+ const emailLogRef = this.db.collection("email_logs").doc();
1615
+ await emailLogRef.set({
1616
+ to: emailData.to,
1617
+ subject: emailData.subject,
1618
+ templateName: emailData.templateName,
1619
+ success,
1620
+ error: error ? JSON.stringify(error) : null,
1621
+ sentAt: admin6.firestore.FieldValue.serverTimestamp()
1622
+ });
1623
+ } catch (logError) {
1624
+ console.error(
1625
+ "[BaseMailingService] Error logging email attempt:",
1626
+ logError
1627
+ );
1628
+ }
1629
+ }
1630
+ /**
1631
+ * Renders a simple HTML email template with variables
1632
+ * @param template HTML template string
1633
+ * @param variables Key-value pairs to replace in the template
1634
+ * @returns Rendered HTML string
1635
+ */
1636
+ renderTemplate(template, variables) {
1637
+ let rendered = template;
1638
+ Object.entries(variables).forEach(([key, value]) => {
1639
+ const regex = new RegExp(`{{\\s*${key}\\s*}}`, "g");
1640
+ rendered = rendered.replace(regex, value);
1641
+ });
1642
+ return rendered;
1643
+ }
1644
+ };
1645
+
1646
+ // src/admin/mailing/practitionerInvite/templates/invitation.template.ts
1647
+ var practitionerInvitationTemplate = `
1648
+ <!DOCTYPE html>
1649
+ <html>
1650
+ <head>
1651
+ <meta charset="UTF-8">
1652
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1653
+ <title>Join {{clinicName}} as a Practitioner</title>
1654
+ <style>
1655
+ body {
1656
+ font-family: Arial, sans-serif;
1657
+ line-height: 1.6;
1658
+ color: #333;
1659
+ margin: 0;
1660
+ padding: 0;
1661
+ }
1662
+ .container {
1663
+ max-width: 600px;
1664
+ margin: 0 auto;
1665
+ padding: 20px;
1666
+ }
1667
+ .header {
1668
+ background-color: #4A90E2;
1669
+ padding: 20px;
1670
+ text-align: center;
1671
+ color: white;
1672
+ }
1673
+ .content {
1674
+ padding: 20px;
1675
+ background-color: #f9f9f9;
1676
+ }
1677
+ .footer {
1678
+ padding: 20px;
1679
+ text-align: center;
1680
+ font-size: 12px;
1681
+ color: #888;
1682
+ }
1683
+ .button {
1684
+ display: inline-block;
1685
+ background-color: #4A90E2;
1686
+ color: white;
1687
+ text-decoration: none;
1688
+ padding: 12px 24px;
1689
+ border-radius: 4px;
1690
+ margin: 20px 0;
1691
+ font-weight: bold;
1692
+ }
1693
+ .token {
1694
+ font-size: 24px;
1695
+ font-weight: bold;
1696
+ color: #4A90E2;
1697
+ padding: 10px;
1698
+ background-color: #e9f0f9;
1699
+ border-radius: 4px;
1700
+ display: inline-block;
1701
+ letter-spacing: 2px;
1702
+ margin: 10px 0;
1703
+ }
1704
+ </style>
1705
+ </head>
1706
+ <body>
1707
+ <div class="container">
1708
+ <div class="header">
1709
+ <h1>You've Been Invited</h1>
1710
+ </div>
1711
+ <div class="content">
1712
+ <p>Hello {{practitionerName}},</p>
1713
+
1714
+ <p>You have been invited to join <strong>{{clinicName}}</strong> as a healthcare practitioner.</p>
1715
+
1716
+ <p>Your profile has been created and is ready for you to claim. Please use the following token to register:</p>
1717
+
1718
+ <div style="text-align: center;">
1719
+ <span class="token">{{inviteToken}}</span>
1720
+ </div>
1721
+
1722
+ <p>This token will expire on <strong>{{expirationDate}}</strong>.</p>
1723
+
1724
+ <p>To create your account:</p>
1725
+ <ol>
1726
+ <li>Visit {{registrationUrl}}</li>
1727
+ <li>Enter your email and create a password</li>
1728
+ <li>When prompted, enter the token above</li>
1729
+ </ol>
1730
+
1731
+ <div style="text-align: center;">
1732
+ <a href="{{registrationUrl}}" class="button">Create Your Account</a>
1733
+ </div>
1734
+
1735
+ <p>If you have any questions, please contact {{contactName}} at {{contactEmail}}.</p>
1736
+ </div>
1737
+ <div class="footer">
1738
+ <p>This is an automated message from {{clinicName}}. Please do not reply to this email.</p>
1739
+ <p>&copy; {{currentYear}} {{clinicName}}. All rights reserved.</p>
1740
+ </div>
1741
+ </div>
1742
+ </body>
1743
+ </html>
1744
+ `;
1745
+
1746
+ // src/admin/mailing/practitionerInvite/practitionerInvite.mailing.ts
1747
+ var PractitionerInviteMailingService = class extends BaseMailingService {
1748
+ /**
1749
+ * Constructor for PractitionerInviteMailingService
1750
+ * @param firestore Firestore instance provided by the caller
1751
+ * @param mailgunClient Mailgun client instance provided by the caller
1752
+ */
1753
+ constructor(firestore7, mailgunClient) {
1754
+ super(firestore7, mailgunClient);
1755
+ this.DEFAULT_REGISTRATION_URL = "https://app.medclinic.com/register";
1756
+ this.DEFAULT_SUBJECT = "You've Been Invited to Join as a Practitioner";
1757
+ this.DEFAULT_FROM_ADDRESS = "MedClinic <no-reply@your-domain.com>";
1758
+ }
1759
+ /**
1760
+ * Sends a practitioner invitation email
1761
+ * @param data The practitioner invitation data
1762
+ * @returns Promise resolved when email is sent
1763
+ */
1764
+ async sendInvitationEmail(data) {
1765
+ var _a, _b, _c, _d;
1766
+ try {
1767
+ console.log(
1768
+ "[PractitionerInviteMailingService] Sending invitation email to",
1769
+ data.token.email
1770
+ );
1771
+ const expirationDate = data.token.expiresAt.toDate().toLocaleDateString("en-US", {
1772
+ weekday: "long",
1773
+ year: "numeric",
1774
+ month: "long",
1775
+ day: "numeric"
1776
+ });
1777
+ const registrationUrl = ((_a = data.options) == null ? void 0 : _a.registrationUrl) || this.DEFAULT_REGISTRATION_URL;
1778
+ const contactName = data.clinic.contactName || "Clinic Administrator";
1779
+ const contactEmail = data.clinic.contactEmail;
1780
+ const subject = ((_b = data.options) == null ? void 0 : _b.customSubject) || this.DEFAULT_SUBJECT;
1781
+ const fromAddress = ((_c = data.options) == null ? void 0 : _c.fromAddress) || this.DEFAULT_FROM_ADDRESS;
1782
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
1783
+ const practitionerName = `${data.practitioner.firstName} ${data.practitioner.lastName}`;
1784
+ const templateVariables = {
1785
+ clinicName: data.clinic.name,
1786
+ practitionerName,
1787
+ inviteToken: data.token.token,
1788
+ expirationDate,
1789
+ registrationUrl,
1790
+ contactName,
1791
+ contactEmail,
1792
+ currentYear
1793
+ };
1794
+ const html = this.renderTemplate(
1795
+ practitionerInvitationTemplate,
1796
+ templateVariables
1797
+ );
1798
+ const emailData = {
1799
+ to: data.token.email,
1800
+ from: fromAddress,
1801
+ subject,
1802
+ html
1803
+ };
1804
+ const result = await this.sendEmail(emailData);
1805
+ await this.logEmailAttempt(
1806
+ {
1807
+ to: data.token.email,
1808
+ subject,
1809
+ templateName: "practitioner_invitation"
1810
+ },
1811
+ true
1812
+ );
1813
+ return result;
1814
+ } catch (error) {
1815
+ console.error(
1816
+ "[PractitionerInviteMailingService] Error sending invitation email:",
1817
+ error
1818
+ );
1819
+ await this.logEmailAttempt(
1820
+ {
1821
+ to: data.token.email,
1822
+ subject: ((_d = data.options) == null ? void 0 : _d.customSubject) || this.DEFAULT_SUBJECT,
1823
+ templateName: "practitioner_invitation"
1824
+ },
1825
+ false,
1826
+ error
1827
+ );
1828
+ throw error;
1829
+ }
1830
+ }
1831
+ /**
1832
+ * Handles the practitioner token creation event from Cloud Functions
1833
+ * Fetches necessary data using defined types and collection constants,
1834
+ * and sends the invitation email.
1835
+ * @param tokenData The fully typed token object including its id
1836
+ * @param fromAddress The 'from' email address to use, obtained from config
1837
+ * @returns Promise resolved when the email is sent
1838
+ */
1839
+ async handleTokenCreationEvent(tokenData, fromAddress) {
1840
+ try {
1841
+ console.log(
1842
+ "[PractitionerInviteMailingService] Handling token creation event for token:",
1843
+ tokenData.id
1844
+ );
1845
+ const practitionerRef = this.db.collection(PRACTITIONERS_COLLECTION).doc(tokenData.practitionerId);
1846
+ const practitionerDoc = await practitionerRef.get();
1847
+ if (!practitionerDoc.exists) {
1848
+ throw new Error(`Practitioner ${tokenData.practitionerId} not found`);
1849
+ }
1850
+ const practitionerData = practitionerDoc.data();
1851
+ const clinicRef = this.db.collection(CLINICS_COLLECTION).doc(tokenData.clinicId);
1852
+ const clinicDoc = await clinicRef.get();
1853
+ if (!clinicDoc.exists) {
1854
+ throw new Error(`Clinic ${tokenData.clinicId} not found`);
1855
+ }
1856
+ const clinicData = clinicDoc.data();
1857
+ const emailData = {
1858
+ token: {
1859
+ id: tokenData.id,
1860
+ token: tokenData.token,
1861
+ practitionerId: tokenData.practitionerId,
1862
+ email: tokenData.email,
1863
+ clinicId: tokenData.clinicId,
1864
+ expiresAt: tokenData.expiresAt
1865
+ },
1866
+ practitioner: {
1867
+ firstName: practitionerData.basicInfo.firstName || "",
1868
+ lastName: practitionerData.basicInfo.lastName || ""
1869
+ },
1870
+ clinic: {
1871
+ name: clinicData.name || "Medical Clinic",
1872
+ contactEmail: clinicData.contactInfo.email || "contact@medclinic.com"
1873
+ },
1874
+ options: {
1875
+ fromAddress
1876
+ }
1877
+ };
1878
+ await this.sendInvitationEmail(emailData);
1879
+ console.log(
1880
+ "[PractitionerInviteMailingService] Invitation email sent successfully"
1881
+ );
1882
+ } catch (error) {
1883
+ console.error(
1884
+ "[PractitionerInviteMailingService] Error handling token creation event:",
1885
+ error
1886
+ );
1887
+ throw error;
1888
+ }
1889
+ }
1890
+ };
1891
+
1557
1892
  // src/types/index.ts
1558
1893
  var UserRole = /* @__PURE__ */ ((UserRole2) => {
1559
1894
  UserRole2["PATIENT"] = "patient";
@@ -1567,6 +1902,7 @@ var UserRole = /* @__PURE__ */ ((UserRole2) => {
1567
1902
  console.log("[Admin Module] Initialized and services exported.");
1568
1903
  // Annotate the CommonJS export names for ESM import in node:
1569
1904
  0 && (module.exports = {
1905
+ BaseMailingService,
1570
1906
  ClinicAggregationService,
1571
1907
  NOTIFICATIONS_COLLECTION,
1572
1908
  NotificationStatus,
@@ -1574,6 +1910,7 @@ console.log("[Admin Module] Initialized and services exported.");
1574
1910
  NotificationsAdmin,
1575
1911
  PatientAggregationService,
1576
1912
  PractitionerAggregationService,
1913
+ PractitionerInviteMailingService,
1577
1914
  ProcedureAggregationService,
1578
1915
  UserRole
1579
1916
  });