@blackcode_sa/metaestetics-api 1.5.44 → 1.5.45
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.js +159 -23
- package/dist/admin/index.mjs +159 -23
- package/dist/index.d.mts +28 -2
- package/dist/index.d.ts +28 -2
- package/dist/index.js +1096 -942
- package/dist/index.mjs +969 -797
- package/package.json +1 -1
- package/src/admin/mailing/base.mailing.service.ts +108 -29
- package/src/admin/mailing/practitionerInvite/practitionerInvite.mailing.ts +88 -2
- package/src/services/patient/patient.service.ts +180 -57
- package/src/services/patient/utils/clinic.utils.ts +80 -0
- package/src/services/patient/utils/practitioner.utils.ts +80 -0
package/package.json
CHANGED
|
@@ -29,9 +29,20 @@ export class BaseMailingService {
|
|
|
29
29
|
// Use provided instances
|
|
30
30
|
this.db = firestore;
|
|
31
31
|
this.mailgunClient = mailgunClient;
|
|
32
|
-
|
|
33
|
-
//
|
|
34
|
-
|
|
32
|
+
|
|
33
|
+
// Validate instances
|
|
34
|
+
if (!this.db) {
|
|
35
|
+
console.error("[BaseMailingService] No Firestore instance provided");
|
|
36
|
+
throw new Error("Firestore instance is required");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!this.mailgunClient) {
|
|
40
|
+
console.error("[BaseMailingService] No Mailgun client provided");
|
|
41
|
+
throw new Error("Mailgun client is required");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Log successful initialization
|
|
45
|
+
console.log("[BaseMailingService] Service initialized successfully");
|
|
35
46
|
}
|
|
36
47
|
|
|
37
48
|
/**
|
|
@@ -43,37 +54,80 @@ export class BaseMailingService {
|
|
|
43
54
|
data: mailgun.messages.SendData // Caller must provide 'from'
|
|
44
55
|
): Promise<mailgun.messages.SendResponse> {
|
|
45
56
|
try {
|
|
57
|
+
// Validate email data fields
|
|
58
|
+
if (!data) {
|
|
59
|
+
throw new Error("Email data object is required");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Ensure all required fields are provided
|
|
63
|
+
if (!data.to) {
|
|
64
|
+
throw new Error("Email 'to' address is required");
|
|
65
|
+
}
|
|
66
|
+
|
|
46
67
|
// Ensure 'from' field is provided by the caller
|
|
47
68
|
if (!data.from) {
|
|
48
69
|
throw new Error(
|
|
49
70
|
"Email 'from' address must be provided in sendEmail data."
|
|
50
71
|
);
|
|
51
72
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
73
|
+
|
|
74
|
+
if (!data.subject) {
|
|
75
|
+
throw new Error("Email 'subject' is required");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!data.html && !data.text) {
|
|
79
|
+
throw new Error("Email must have either 'html' or 'text' content");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log("[BaseMailingService] Sending email via Mailgun", {
|
|
83
|
+
to: data.to,
|
|
84
|
+
from: data.from,
|
|
85
|
+
subject: data.subject,
|
|
86
|
+
hasHtml: !!data.html,
|
|
87
|
+
hasText: !!data.text,
|
|
88
|
+
});
|
|
57
89
|
|
|
58
90
|
// Send the email
|
|
59
91
|
return await new Promise<mailgun.messages.SendResponse>(
|
|
60
92
|
(resolve, reject) => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
} else {
|
|
66
|
-
console.log(
|
|
67
|
-
"[BaseMailingService] Email sent successfully:",
|
|
68
|
-
body
|
|
69
|
-
);
|
|
70
|
-
resolve(body);
|
|
93
|
+
try {
|
|
94
|
+
const messagesApi = this.mailgunClient.messages();
|
|
95
|
+
if (!messagesApi) {
|
|
96
|
+
throw new Error("Could not get Mailgun messages API");
|
|
71
97
|
}
|
|
72
|
-
|
|
98
|
+
|
|
99
|
+
messagesApi.send(data, (error, body) => {
|
|
100
|
+
if (error) {
|
|
101
|
+
console.error(
|
|
102
|
+
"[BaseMailingService] Mailgun API error:",
|
|
103
|
+
error instanceof Error ? error.message : error,
|
|
104
|
+
error instanceof Error ? error.stack : ""
|
|
105
|
+
);
|
|
106
|
+
reject(error);
|
|
107
|
+
} else {
|
|
108
|
+
console.log(
|
|
109
|
+
"[BaseMailingService] Email sent successfully:",
|
|
110
|
+
body
|
|
111
|
+
);
|
|
112
|
+
resolve(body);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
} catch (sendError) {
|
|
116
|
+
console.error(
|
|
117
|
+
"[BaseMailingService] Error in mailgun.messages().send():",
|
|
118
|
+
sendError instanceof Error ? sendError.message : sendError,
|
|
119
|
+
sendError instanceof Error ? sendError.stack : ""
|
|
120
|
+
);
|
|
121
|
+
reject(sendError);
|
|
122
|
+
}
|
|
73
123
|
}
|
|
74
124
|
);
|
|
75
125
|
} catch (error) {
|
|
76
|
-
console.error(
|
|
126
|
+
console.error(
|
|
127
|
+
"[BaseMailingService] Error in sendEmail:",
|
|
128
|
+
error instanceof Error ? error.message : error,
|
|
129
|
+
error instanceof Error ? error.stack : ""
|
|
130
|
+
);
|
|
77
131
|
throw error;
|
|
78
132
|
}
|
|
79
133
|
}
|
|
@@ -96,13 +150,22 @@ export class BaseMailingService {
|
|
|
96
150
|
subject: emailData.subject,
|
|
97
151
|
templateName: emailData.templateName,
|
|
98
152
|
success,
|
|
99
|
-
error: error
|
|
153
|
+
error: error
|
|
154
|
+
? error instanceof Error
|
|
155
|
+
? { message: error.message, stack: error.stack }
|
|
156
|
+
: JSON.stringify(error)
|
|
157
|
+
: null,
|
|
100
158
|
sentAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
101
159
|
});
|
|
160
|
+
|
|
161
|
+
console.log(
|
|
162
|
+
`[BaseMailingService] Email log recorded. Success: ${success}`
|
|
163
|
+
);
|
|
102
164
|
} catch (logError) {
|
|
103
165
|
console.error(
|
|
104
166
|
"[BaseMailingService] Error logging email attempt:",
|
|
105
|
-
logError
|
|
167
|
+
logError instanceof Error ? logError.message : logError,
|
|
168
|
+
logError instanceof Error ? logError.stack : ""
|
|
106
169
|
);
|
|
107
170
|
// Don't throw here to prevent disrupting the main flow
|
|
108
171
|
}
|
|
@@ -118,14 +181,30 @@ export class BaseMailingService {
|
|
|
118
181
|
template: string,
|
|
119
182
|
variables: Record<string, string>
|
|
120
183
|
): string {
|
|
121
|
-
|
|
184
|
+
if (!template) {
|
|
185
|
+
throw new Error("Email template is required");
|
|
186
|
+
}
|
|
122
187
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
188
|
+
try {
|
|
189
|
+
let rendered = template;
|
|
190
|
+
|
|
191
|
+
// Replace template variables (format: {{variable_name}})
|
|
192
|
+
Object.entries(variables).forEach(([key, value]) => {
|
|
193
|
+
const regex = new RegExp(`{{\\s*${key}\\s*}}`, "g");
|
|
194
|
+
rendered = rendered.replace(regex, value || "");
|
|
195
|
+
});
|
|
128
196
|
|
|
129
|
-
|
|
197
|
+
return rendered;
|
|
198
|
+
} catch (renderError) {
|
|
199
|
+
console.error(
|
|
200
|
+
"[BaseMailingService] Error rendering template:",
|
|
201
|
+
renderError instanceof Error ? renderError.message : renderError
|
|
202
|
+
);
|
|
203
|
+
throw new Error(
|
|
204
|
+
`Template rendering failed: ${
|
|
205
|
+
renderError instanceof Error ? renderError.message : "Unknown error"
|
|
206
|
+
}`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
130
209
|
}
|
|
131
210
|
}
|
|
@@ -126,6 +126,18 @@ export class PractitionerInviteMailingService extends BaseMailingService {
|
|
|
126
126
|
currentYear,
|
|
127
127
|
};
|
|
128
128
|
|
|
129
|
+
// Debug log for template variables (excluding token for security)
|
|
130
|
+
console.log("[PractitionerInviteMailingService] Template variables:", {
|
|
131
|
+
clinicName: templateVariables.clinicName,
|
|
132
|
+
practitionerName: templateVariables.practitionerName,
|
|
133
|
+
expirationDate: templateVariables.expirationDate,
|
|
134
|
+
registrationUrl: templateVariables.registrationUrl,
|
|
135
|
+
contactName: templateVariables.contactName,
|
|
136
|
+
contactEmail: templateVariables.contactEmail,
|
|
137
|
+
// Don't log the invite token for security
|
|
138
|
+
hasInviteToken: !!templateVariables.inviteToken,
|
|
139
|
+
});
|
|
140
|
+
|
|
129
141
|
// Render HTML email
|
|
130
142
|
const html = this.renderTemplate(
|
|
131
143
|
practitionerInvitationTemplate,
|
|
@@ -140,6 +152,16 @@ export class PractitionerInviteMailingService extends BaseMailingService {
|
|
|
140
152
|
html,
|
|
141
153
|
};
|
|
142
154
|
|
|
155
|
+
console.log(
|
|
156
|
+
"[PractitionerInviteMailingService] Sending email with data:",
|
|
157
|
+
{
|
|
158
|
+
to: emailData.to,
|
|
159
|
+
from: emailData.from,
|
|
160
|
+
subject: emailData.subject,
|
|
161
|
+
hasHtml: !!emailData.html,
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
|
|
143
165
|
const result = await this.sendEmail(emailData);
|
|
144
166
|
|
|
145
167
|
// Log success
|
|
@@ -156,7 +178,8 @@ export class PractitionerInviteMailingService extends BaseMailingService {
|
|
|
156
178
|
} catch (error) {
|
|
157
179
|
console.error(
|
|
158
180
|
"[PractitionerInviteMailingService] Error sending invitation email:",
|
|
159
|
-
error
|
|
181
|
+
error instanceof Error ? error.message : error,
|
|
182
|
+
error instanceof Error ? error.stack : ""
|
|
160
183
|
);
|
|
161
184
|
|
|
162
185
|
// Log failure
|
|
@@ -192,7 +215,31 @@ export class PractitionerInviteMailingService extends BaseMailingService {
|
|
|
192
215
|
tokenData.id
|
|
193
216
|
);
|
|
194
217
|
|
|
218
|
+
// Validate token data
|
|
219
|
+
if (!tokenData || !tokenData.id || !tokenData.token || !tokenData.email) {
|
|
220
|
+
throw new Error(
|
|
221
|
+
`Invalid token data: Missing required properties. Token ID: ${tokenData?.id}`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!tokenData.practitionerId) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
`Token ${tokenData.id} is missing practitionerId reference`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (!tokenData.clinicId) {
|
|
232
|
+
throw new Error(`Token ${tokenData.id} is missing clinicId reference`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (!tokenData.expiresAt) {
|
|
236
|
+
throw new Error(`Token ${tokenData.id} is missing expiration date`);
|
|
237
|
+
}
|
|
238
|
+
|
|
195
239
|
// Get practitioner data using constant and type
|
|
240
|
+
console.log(
|
|
241
|
+
`[PractitionerInviteMailingService] Fetching practitioner data: ${tokenData.practitionerId}`
|
|
242
|
+
);
|
|
196
243
|
const practitionerRef = this.db
|
|
197
244
|
.collection(PRACTITIONERS_COLLECTION)
|
|
198
245
|
.doc(tokenData.practitionerId);
|
|
@@ -203,8 +250,20 @@ export class PractitionerInviteMailingService extends BaseMailingService {
|
|
|
203
250
|
}
|
|
204
251
|
|
|
205
252
|
const practitionerData = practitionerDoc.data() as Practitioner;
|
|
253
|
+
if (!practitionerData || !practitionerData.basicInfo) {
|
|
254
|
+
throw new Error(
|
|
255
|
+
`Practitioner ${tokenData.practitionerId} has invalid data structure`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
console.log(
|
|
260
|
+
`[PractitionerInviteMailingService] Practitioner found: ${practitionerData.basicInfo.firstName} ${practitionerData.basicInfo.lastName}`
|
|
261
|
+
);
|
|
206
262
|
|
|
207
263
|
// Get clinic data using constant and type
|
|
264
|
+
console.log(
|
|
265
|
+
`[PractitionerInviteMailingService] Fetching clinic data: ${tokenData.clinicId}`
|
|
266
|
+
);
|
|
208
267
|
const clinicRef = this.db
|
|
209
268
|
.collection(CLINICS_COLLECTION)
|
|
210
269
|
.doc(tokenData.clinicId);
|
|
@@ -215,6 +274,26 @@ export class PractitionerInviteMailingService extends BaseMailingService {
|
|
|
215
274
|
}
|
|
216
275
|
|
|
217
276
|
const clinicData = clinicDoc.data() as Clinic;
|
|
277
|
+
if (!clinicData || !clinicData.contactInfo) {
|
|
278
|
+
throw new Error(
|
|
279
|
+
`Clinic ${tokenData.clinicId} has invalid data structure`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
console.log(
|
|
284
|
+
`[PractitionerInviteMailingService] Clinic found: ${clinicData.name}`
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
// Clinic model doesn't have contactPerson, only contactInfo
|
|
288
|
+
// So we'll use simple contact information from contactInfo
|
|
289
|
+
|
|
290
|
+
// Validate fromAddress
|
|
291
|
+
if (!fromAddress) {
|
|
292
|
+
console.warn(
|
|
293
|
+
"[PractitionerInviteMailingService] No fromAddress provided, using default"
|
|
294
|
+
);
|
|
295
|
+
fromAddress = this.DEFAULT_FROM_ADDRESS;
|
|
296
|
+
}
|
|
218
297
|
|
|
219
298
|
// Prepare email data using typed data
|
|
220
299
|
const emailData: PractitionerInviteEmailData = {
|
|
@@ -233,12 +312,18 @@ export class PractitionerInviteMailingService extends BaseMailingService {
|
|
|
233
312
|
clinic: {
|
|
234
313
|
name: clinicData.name || "Medical Clinic",
|
|
235
314
|
contactEmail: clinicData.contactInfo.email || "contact@medclinic.com",
|
|
315
|
+
// Since there's no contactPerson in the Clinic model, we'll just use "Clinic Admin"
|
|
316
|
+
contactName: "Clinic Admin",
|
|
236
317
|
},
|
|
237
318
|
options: {
|
|
238
319
|
fromAddress: fromAddress,
|
|
239
320
|
},
|
|
240
321
|
};
|
|
241
322
|
|
|
323
|
+
console.log(
|
|
324
|
+
"[PractitionerInviteMailingService] Email data prepared, sending invitation"
|
|
325
|
+
);
|
|
326
|
+
|
|
242
327
|
// Send the invitation email
|
|
243
328
|
await this.sendInvitationEmail(emailData);
|
|
244
329
|
|
|
@@ -248,7 +333,8 @@ export class PractitionerInviteMailingService extends BaseMailingService {
|
|
|
248
333
|
} catch (error) {
|
|
249
334
|
console.error(
|
|
250
335
|
"[PractitionerInviteMailingService] Error handling token creation event:",
|
|
251
|
-
error
|
|
336
|
+
error instanceof Error ? error.message : error,
|
|
337
|
+
error instanceof Error ? error.stack : ""
|
|
252
338
|
);
|
|
253
339
|
throw error;
|
|
254
340
|
}
|