@blackcode_sa/metaestetics-api 1.5.50 → 1.5.52
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 +35 -37
- package/dist/admin/index.d.ts +35 -37
- package/dist/admin/index.js +109 -222
- package/dist/admin/index.mjs +109 -222
- package/package.json +1 -1
- package/src/admin/mailing/base.mailing.service.ts +99 -273
- package/src/admin/mailing/practitionerInvite/practitionerInvite.mailing.ts +64 -30
|
@@ -1,308 +1,136 @@
|
|
|
1
|
-
import * as mailgun from "mailgun-js";
|
|
2
1
|
import * as admin from "firebase-admin";
|
|
3
|
-
|
|
2
|
+
// Import the new Mailgun types if available, or use a generic 'any' if not directly importable
|
|
3
|
+
// For a shared library, it's often better to define a minimal interface for what you need
|
|
4
|
+
// or expect the caller (Cloud Function) to pass a pre-configured client of 'any' type.
|
|
5
|
+
// import Mailgun from "mailgun.js"; // This might not be a direct dep of the API pkg
|
|
6
|
+
import { Logger } from "../logger"; // Assuming logger is in Api/src/logger
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
|
-
*
|
|
7
|
-
* This
|
|
9
|
+
* Minimal interface for the new mailgun.js client's messages API
|
|
10
|
+
* This helps avoid a direct dependency on mailgun.js in the API package if not desired,
|
|
11
|
+
* or provides clearer typing if it is.
|
|
8
12
|
*/
|
|
9
|
-
interface
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
interface NewMailgunMessagesAPI {
|
|
14
|
+
create(domain: string, data: any): Promise<any>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface NewMailgunClient {
|
|
18
|
+
messages: NewMailgunMessagesAPI;
|
|
19
|
+
// Add other methods/properties if your BaseMailingService uses them
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
/**
|
|
17
23
|
* Base mailing service class that provides common functionality for all mailing services
|
|
24
|
+
* This version is updated to expect a mailgun.js v10+ client.
|
|
18
25
|
*/
|
|
19
26
|
export class BaseMailingService {
|
|
20
27
|
protected db: FirebaseFirestore.Firestore;
|
|
21
|
-
protected mailgunClient: mailgun.
|
|
22
|
-
protected isNewMailgunClient: boolean = false;
|
|
23
|
-
// Removed config property as it's no longer managed here
|
|
24
|
-
// import {
|
|
25
|
-
// getMailgunConfig,
|
|
26
|
-
// createMailgunClient,
|
|
27
|
-
// MailgunConfig,
|
|
28
|
-
// } from "./mailgun.config";
|
|
28
|
+
protected mailgunClient: NewMailgunClient; // Expecting the new mailgun.js client
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Constructor for BaseMailingService
|
|
32
32
|
* @param firestore Firestore instance provided by the caller
|
|
33
|
-
* @param mailgunClient Mailgun client instance provided by the caller
|
|
33
|
+
* @param mailgunClient Mailgun client instance (mailgun.js v10+) provided by the caller
|
|
34
34
|
*/
|
|
35
35
|
constructor(
|
|
36
36
|
firestore: FirebaseFirestore.Firestore,
|
|
37
|
-
mailgunClient:
|
|
37
|
+
mailgunClient: NewMailgunClient // Expect the new client
|
|
38
38
|
) {
|
|
39
|
-
// Use provided instances
|
|
40
39
|
this.db = firestore;
|
|
41
40
|
this.mailgunClient = mailgunClient;
|
|
42
41
|
|
|
43
|
-
// Validate instances
|
|
44
42
|
if (!this.db) {
|
|
45
43
|
Logger.error("[BaseMailingService] No Firestore instance provided");
|
|
46
44
|
throw new Error("Firestore instance is required");
|
|
47
45
|
}
|
|
48
46
|
|
|
49
|
-
if (
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
if (
|
|
48
|
+
!this.mailgunClient ||
|
|
49
|
+
typeof this.mailgunClient.messages?.create !== "function"
|
|
50
|
+
) {
|
|
51
|
+
Logger.error(
|
|
52
|
+
"[BaseMailingService] Invalid Mailgun client provided (must be mailgun.js v10+ client)"
|
|
53
|
+
);
|
|
54
|
+
throw new Error("A valid mailgun.js v10+ client is required");
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
//
|
|
55
|
-
this
|
|
56
|
-
|
|
57
|
+
// The new client is configured with the URL (e.g., EU endpoint) by the caller.
|
|
58
|
+
// Validation of this configuration can be logged if details are accessible,
|
|
59
|
+
// but the primary responsibility for correct endpoint is now with client setup.
|
|
57
60
|
Logger.info(
|
|
58
|
-
|
|
59
|
-
this.isNewMailgunClient ? "new" : "legacy"
|
|
60
|
-
} Mailgun client`
|
|
61
|
+
"[BaseMailingService] Service initialized with mailgun.js client."
|
|
61
62
|
);
|
|
62
|
-
|
|
63
|
-
// Validate the Mailgun client configuration
|
|
64
|
-
this.validateMailgunClient();
|
|
65
|
-
|
|
66
|
-
// Log successful initialization
|
|
67
|
-
Logger.info("[BaseMailingService] Service initialized successfully");
|
|
68
63
|
}
|
|
69
64
|
|
|
70
65
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
|
|
74
|
-
private validateMailgunClient(): void {
|
|
75
|
-
try {
|
|
76
|
-
// For the legacy client, check EU region configuration
|
|
77
|
-
if (!this.isNewMailgunClient) {
|
|
78
|
-
const clientOptions = (this.mailgunClient as any).options || {};
|
|
79
|
-
const host = clientOptions.host || "";
|
|
80
|
-
const isEuRegion = host.includes("eu.mailgun.net");
|
|
81
|
-
|
|
82
|
-
Logger.info("[BaseMailingService] Mailgun client configuration:", {
|
|
83
|
-
host: host || "default",
|
|
84
|
-
isEuRegion,
|
|
85
|
-
domain: clientOptions.domain || "unknown",
|
|
86
|
-
clientType: "legacy",
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// Check if this appears to be an EU domain but not using EU endpoint
|
|
90
|
-
if (
|
|
91
|
-
clientOptions.domain &&
|
|
92
|
-
clientOptions.domain.endsWith(".eu") &&
|
|
93
|
-
!isEuRegion
|
|
94
|
-
) {
|
|
95
|
-
Logger.warn(
|
|
96
|
-
"[BaseMailingService] Domain appears to be in EU region but not using EU endpoint. " +
|
|
97
|
-
"If you're getting 401 Forbidden errors, ensure the host is set to 'api.eu.mailgun.net'"
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
} else {
|
|
101
|
-
// For the new client, we can't easily check the configuration
|
|
102
|
-
// but it should be properly configured with the URL parameter
|
|
103
|
-
Logger.info("[BaseMailingService] Using new Mailgun client", {
|
|
104
|
-
clientType: "mailgun.js",
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
} catch (error) {
|
|
108
|
-
// Just log the error, don't throw
|
|
109
|
-
Logger.warn("[BaseMailingService] Could not validate Mailgun client:", {
|
|
110
|
-
error: error instanceof Error ? error.message : String(error),
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Sends an email using Mailgun
|
|
117
|
-
* @param data Email data to send, including the 'from' address
|
|
66
|
+
* Sends an email using the new Mailgun client API.
|
|
67
|
+
* @param domain The Mailgun domain to send from (e.g., mg.metaesthetics.net)
|
|
68
|
+
* @param data Email data to send, compatible with mailgun.js messages.create API
|
|
118
69
|
* @returns Promise with the sending result
|
|
119
70
|
*/
|
|
120
71
|
protected async sendEmail(
|
|
121
|
-
|
|
72
|
+
domain: string, // The new API requires the domain as the first argument to messages.create
|
|
73
|
+
data: any // Data format according to mailgun.js messages.create
|
|
122
74
|
): Promise<any> {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (!data.subject) {
|
|
142
|
-
throw new Error("Email 'subject' is required");
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (!data.html && !data.text) {
|
|
146
|
-
throw new Error("Email must have either 'html' or 'text' content");
|
|
147
|
-
}
|
|
75
|
+
if (!domain) {
|
|
76
|
+
throw new Error("Mailgun domain is required for sending email.");
|
|
77
|
+
}
|
|
78
|
+
if (!data) {
|
|
79
|
+
throw new Error("Email data object is required");
|
|
80
|
+
}
|
|
81
|
+
if (!data.to) {
|
|
82
|
+
throw new Error("Email 'to' address is required");
|
|
83
|
+
}
|
|
84
|
+
if (!data.from) {
|
|
85
|
+
throw new Error("Email 'from' address is required");
|
|
86
|
+
}
|
|
87
|
+
if (!data.subject) {
|
|
88
|
+
throw new Error("Email 'subject' is required");
|
|
89
|
+
}
|
|
90
|
+
if (!data.html && !data.text) {
|
|
91
|
+
throw new Error("Email must have either 'html' or 'text' content");
|
|
92
|
+
}
|
|
148
93
|
|
|
149
|
-
|
|
94
|
+
Logger.info(
|
|
95
|
+
"[BaseMailingService] Sending email via Mailgun (mailgun.js API)",
|
|
96
|
+
{
|
|
97
|
+
domain,
|
|
150
98
|
to: data.to,
|
|
151
99
|
from: data.from,
|
|
152
100
|
subject: data.subject,
|
|
153
101
|
hasHtml: !!data.html,
|
|
154
102
|
hasText: !!data.text,
|
|
155
|
-
clientType: this.isNewMailgunClient ? "mailgun.js" : "legacy",
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
// Use the appropriate API based on the client type
|
|
159
|
-
if (this.isNewMailgunClient) {
|
|
160
|
-
// New mailgun.js client
|
|
161
|
-
try {
|
|
162
|
-
const createMethod = (this.mailgunClient.messages as any).create;
|
|
163
|
-
// Get domain from client if possible, or from the from address as fallback
|
|
164
|
-
const domain =
|
|
165
|
-
this.getDomainFromOptions() || this.getDomainFromEmail(data.from);
|
|
166
|
-
|
|
167
|
-
if (!domain) {
|
|
168
|
-
throw new Error("Could not determine domain for Mailgun API call");
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
Logger.info(
|
|
172
|
-
"[BaseMailingService] Using domain for new Mailgun client:",
|
|
173
|
-
{ domain }
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
// Call the create method with domain and data
|
|
177
|
-
const result = await createMethod(domain, data);
|
|
178
|
-
Logger.info(
|
|
179
|
-
"[BaseMailingService] Email sent successfully with new client"
|
|
180
|
-
);
|
|
181
|
-
return result;
|
|
182
|
-
} catch (error) {
|
|
183
|
-
Logger.error(
|
|
184
|
-
"[BaseMailingService] Error sending with new Mailgun client:",
|
|
185
|
-
{
|
|
186
|
-
error: error instanceof Error ? error.message : String(error),
|
|
187
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
188
|
-
}
|
|
189
|
-
);
|
|
190
|
-
throw error;
|
|
191
|
-
}
|
|
192
|
-
} else {
|
|
193
|
-
// Legacy mailgun-js client
|
|
194
|
-
return await new Promise<mailgun.messages.SendResponse>(
|
|
195
|
-
(resolve, reject) => {
|
|
196
|
-
try {
|
|
197
|
-
const messagesApi = this.mailgunClient.messages as any;
|
|
198
|
-
if (!messagesApi || !messagesApi.send) {
|
|
199
|
-
throw new Error("Could not get Mailgun messages API");
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
messagesApi.send(data, (error: any, body: any) => {
|
|
203
|
-
if (error) {
|
|
204
|
-
// Enhanced error logging for auth/region issues
|
|
205
|
-
if (error.statusCode === 401 || error.statusCode === 403) {
|
|
206
|
-
const clientOptions =
|
|
207
|
-
(this.mailgunClient as any).options || {};
|
|
208
|
-
Logger.error(
|
|
209
|
-
"[BaseMailingService] Mailgun authentication error - possible region mismatch:",
|
|
210
|
-
{
|
|
211
|
-
error: error instanceof Error ? error.message : error,
|
|
212
|
-
statusCode: error.statusCode,
|
|
213
|
-
domain: clientOptions.domain || "unknown",
|
|
214
|
-
host: clientOptions.host || "default api.mailgun.net",
|
|
215
|
-
suggestion:
|
|
216
|
-
"If using EU region domains, ensure host is set to 'api.eu.mailgun.net'",
|
|
217
|
-
response: (error as any).response
|
|
218
|
-
? JSON.stringify((error as any).response)
|
|
219
|
-
: "No response details",
|
|
220
|
-
request: (error as any).request
|
|
221
|
-
? {
|
|
222
|
-
method: (error as any).request?.method,
|
|
223
|
-
path: (error as any).request?.path,
|
|
224
|
-
host: (error as any).request?.host,
|
|
225
|
-
}
|
|
226
|
-
: "No request details",
|
|
227
|
-
}
|
|
228
|
-
);
|
|
229
|
-
} else {
|
|
230
|
-
Logger.error("[BaseMailingService] Mailgun API error:", {
|
|
231
|
-
error: error instanceof Error ? error.message : error,
|
|
232
|
-
statusCode: error.statusCode,
|
|
233
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
reject(error);
|
|
237
|
-
} else {
|
|
238
|
-
Logger.info(
|
|
239
|
-
"[BaseMailingService] Email sent successfully:",
|
|
240
|
-
body
|
|
241
|
-
);
|
|
242
|
-
resolve(body);
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
} catch (sendError) {
|
|
246
|
-
Logger.error(
|
|
247
|
-
"[BaseMailingService] Error in mailgun.messages().send():",
|
|
248
|
-
{
|
|
249
|
-
error:
|
|
250
|
-
sendError instanceof Error ? sendError.message : sendError,
|
|
251
|
-
stack:
|
|
252
|
-
sendError instanceof Error ? sendError.stack : undefined,
|
|
253
|
-
}
|
|
254
|
-
);
|
|
255
|
-
reject(sendError);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
);
|
|
259
103
|
}
|
|
260
|
-
|
|
261
|
-
Logger.error("[BaseMailingService] Error in sendEmail:", {
|
|
262
|
-
error: error instanceof Error ? error.message : error,
|
|
263
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
264
|
-
});
|
|
265
|
-
throw error;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Tries to get domain from mailgun client options
|
|
271
|
-
* @returns Domain string or undefined
|
|
272
|
-
*/
|
|
273
|
-
private getDomainFromOptions(): string | undefined {
|
|
274
|
-
try {
|
|
275
|
-
// Different ways to get domain depending on client type
|
|
276
|
-
if (this.isNewMailgunClient) {
|
|
277
|
-
return (this.mailgunClient as any).domain;
|
|
278
|
-
} else {
|
|
279
|
-
const options = (this.mailgunClient as any).options;
|
|
280
|
-
return options?.domain;
|
|
281
|
-
}
|
|
282
|
-
} catch (e) {
|
|
283
|
-
return undefined;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
104
|
+
);
|
|
286
105
|
|
|
287
|
-
/**
|
|
288
|
-
* Extracts domain from an email address
|
|
289
|
-
* @param email Email address
|
|
290
|
-
* @returns Domain part or undefined
|
|
291
|
-
*/
|
|
292
|
-
private getDomainFromEmail(email: string): string | undefined {
|
|
293
106
|
try {
|
|
294
|
-
//
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
107
|
+
// Use the new mailgun.js client API: client.messages.create(domain, data)
|
|
108
|
+
const result = await this.mailgunClient.messages.create(domain, data);
|
|
109
|
+
Logger.info(
|
|
110
|
+
"[BaseMailingService] Email sent successfully with mailgun.js client",
|
|
111
|
+
result
|
|
112
|
+
);
|
|
113
|
+
return result;
|
|
114
|
+
} catch (error: any) {
|
|
115
|
+
Logger.error(
|
|
116
|
+
"[BaseMailingService] Error sending email with mailgun.js client",
|
|
117
|
+
{
|
|
118
|
+
errorMessage: error?.message,
|
|
119
|
+
errorDetails: error?.details, // mailgun.js errors often have a details field
|
|
120
|
+
errorStatusCode: error?.status, // and a status field for HTTP status code
|
|
121
|
+
stack: error?.stack,
|
|
122
|
+
domain,
|
|
123
|
+
to: data.to,
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
// Re-throw a more specific error or the original
|
|
127
|
+
const mailgunError = new Error(
|
|
128
|
+
`Mailgun API Error (${error?.status || "unknown"}): ${
|
|
129
|
+
error?.details || error?.message || "Failed to send email"
|
|
130
|
+
}`
|
|
131
|
+
);
|
|
132
|
+
(mailgunError as any).originalError = error;
|
|
133
|
+
throw mailgunError;
|
|
306
134
|
}
|
|
307
135
|
}
|
|
308
136
|
|
|
@@ -325,9 +153,12 @@ export class BaseMailingService {
|
|
|
325
153
|
templateName: emailData.templateName,
|
|
326
154
|
success,
|
|
327
155
|
error: error
|
|
328
|
-
?
|
|
329
|
-
|
|
330
|
-
|
|
156
|
+
? {
|
|
157
|
+
message: error.message,
|
|
158
|
+
details: (error as any).details,
|
|
159
|
+
status: (error as any).status,
|
|
160
|
+
stack: error.stack,
|
|
161
|
+
}
|
|
331
162
|
: null,
|
|
332
163
|
sentAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
333
164
|
});
|
|
@@ -335,10 +166,10 @@ export class BaseMailingService {
|
|
|
335
166
|
Logger.info(
|
|
336
167
|
`[BaseMailingService] Email log recorded. Success: ${success}`
|
|
337
168
|
);
|
|
338
|
-
} catch (logError) {
|
|
169
|
+
} catch (logError: any) {
|
|
339
170
|
Logger.error("[BaseMailingService] Error logging email attempt:", {
|
|
340
|
-
|
|
341
|
-
stack: logError
|
|
171
|
+
errorMessage: logError.message,
|
|
172
|
+
stack: logError.stack,
|
|
342
173
|
});
|
|
343
174
|
// Don't throw here to prevent disrupting the main flow
|
|
344
175
|
}
|
|
@@ -360,22 +191,17 @@ export class BaseMailingService {
|
|
|
360
191
|
|
|
361
192
|
try {
|
|
362
193
|
let rendered = template;
|
|
363
|
-
|
|
364
|
-
// Replace template variables (format: {{variable_name}})
|
|
365
194
|
Object.entries(variables).forEach(([key, value]) => {
|
|
366
|
-
const regex = new RegExp(`{{
|
|
195
|
+
const regex = new RegExp(`{{\s*${key}\s*}}`, "g");
|
|
367
196
|
rendered = rendered.replace(regex, value || "");
|
|
368
197
|
});
|
|
369
|
-
|
|
370
198
|
return rendered;
|
|
371
|
-
} catch (renderError) {
|
|
199
|
+
} catch (renderError: any) {
|
|
372
200
|
Logger.error("[BaseMailingService] Error rendering template:", {
|
|
373
|
-
|
|
201
|
+
errorMessage: renderError.message,
|
|
374
202
|
});
|
|
375
203
|
throw new Error(
|
|
376
|
-
`Template rendering failed: ${
|
|
377
|
-
renderError instanceof Error ? renderError.message : "Unknown error"
|
|
378
|
-
}`
|
|
204
|
+
`Template rendering failed: ${renderError.message || "Unknown error"}`
|
|
379
205
|
);
|
|
380
206
|
}
|
|
381
207
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as admin from "firebase-admin";
|
|
2
|
-
import * as mailgun from "mailgun-js";
|
|
3
2
|
import { BaseMailingService } from "../base.mailing.service";
|
|
4
3
|
import { practitionerInvitationTemplate } from "./templates/invitation.template";
|
|
5
4
|
import { Logger } from "../../logger";
|
|
@@ -8,9 +7,18 @@ import {
|
|
|
8
7
|
Practitioner,
|
|
9
8
|
PractitionerToken,
|
|
10
9
|
PRACTITIONERS_COLLECTION,
|
|
10
|
+
PractitionerTokenStatus,
|
|
11
11
|
} from "../../../types/practitioner";
|
|
12
12
|
import { Clinic, CLINICS_COLLECTION } from "../../../types/clinic";
|
|
13
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
|
+
|
|
14
22
|
/**
|
|
15
23
|
* Interface for the data required to send a practitioner invitation email
|
|
16
24
|
*/
|
|
@@ -43,28 +51,28 @@ export interface PractitionerInviteEmailData {
|
|
|
43
51
|
registrationUrl?: string;
|
|
44
52
|
customSubject?: string;
|
|
45
53
|
fromAddress?: string;
|
|
54
|
+
mailgunDomain?: string;
|
|
46
55
|
};
|
|
47
56
|
}
|
|
48
57
|
|
|
49
58
|
/**
|
|
50
|
-
* Service for sending practitioner invitation emails
|
|
59
|
+
* Service for sending practitioner invitation emails, updated for mailgun.js v10+
|
|
51
60
|
*/
|
|
52
61
|
export class PractitionerInviteMailingService extends BaseMailingService {
|
|
53
62
|
private readonly DEFAULT_REGISTRATION_URL =
|
|
54
|
-
"https://
|
|
63
|
+
"https://metaesthetics.net/register";
|
|
55
64
|
private readonly DEFAULT_SUBJECT =
|
|
56
65
|
"You've Been Invited to Join as a Practitioner";
|
|
57
|
-
private readonly
|
|
58
|
-
"MedClinic <no-reply@mg.metaesthetics.net>";
|
|
66
|
+
private readonly DEFAULT_MAILGUN_DOMAIN = "mg.metaesthetics.net";
|
|
59
67
|
|
|
60
68
|
/**
|
|
61
69
|
* Constructor for PractitionerInviteMailingService
|
|
62
70
|
* @param firestore Firestore instance provided by the caller
|
|
63
|
-
* @param mailgunClient Mailgun client instance provided by the caller
|
|
71
|
+
* @param mailgunClient Mailgun client instance (mailgun.js v10+) provided by the caller
|
|
64
72
|
*/
|
|
65
73
|
constructor(
|
|
66
74
|
firestore: FirebaseFirestore.Firestore,
|
|
67
|
-
mailgunClient:
|
|
75
|
+
mailgunClient: NewMailgunClient
|
|
68
76
|
) {
|
|
69
77
|
super(firestore, mailgunClient);
|
|
70
78
|
}
|
|
@@ -74,9 +82,7 @@ export class PractitionerInviteMailingService extends BaseMailingService {
|
|
|
74
82
|
* @param data The practitioner invitation data
|
|
75
83
|
* @returns Promise resolved when email is sent
|
|
76
84
|
*/
|
|
77
|
-
async sendInvitationEmail(
|
|
78
|
-
data: PractitionerInviteEmailData
|
|
79
|
-
): Promise<mailgun.messages.SendResponse> {
|
|
85
|
+
async sendInvitationEmail(data: PractitionerInviteEmailData): Promise<any> {
|
|
80
86
|
try {
|
|
81
87
|
Logger.info(
|
|
82
88
|
"[PractitionerInviteMailingService] Sending invitation email to",
|
|
@@ -106,7 +112,10 @@ export class PractitionerInviteMailingService extends BaseMailingService {
|
|
|
106
112
|
|
|
107
113
|
// Determine 'from' address
|
|
108
114
|
const fromAddress =
|
|
109
|
-
data.options?.fromAddress ||
|
|
115
|
+
data.options?.fromAddress ||
|
|
116
|
+
`MetaEstetics <no-reply@${
|
|
117
|
+
data.options?.mailgunDomain || this.DEFAULT_MAILGUN_DOMAIN
|
|
118
|
+
}>`;
|
|
110
119
|
|
|
111
120
|
// Current year for copyright
|
|
112
121
|
const currentYear = new Date().getFullYear().toString();
|
|
@@ -144,25 +153,31 @@ export class PractitionerInviteMailingService extends BaseMailingService {
|
|
|
144
153
|
templateVariables
|
|
145
154
|
);
|
|
146
155
|
|
|
147
|
-
//
|
|
148
|
-
const
|
|
156
|
+
// Data for the new sendEmail signature
|
|
157
|
+
const mailgunSendData = {
|
|
149
158
|
to: data.token.email,
|
|
150
159
|
from: fromAddress,
|
|
151
160
|
subject,
|
|
152
161
|
html,
|
|
153
162
|
};
|
|
154
163
|
|
|
164
|
+
// Determine the domain to use for sending
|
|
165
|
+
const domainToSendFrom =
|
|
166
|
+
data.options?.mailgunDomain || this.DEFAULT_MAILGUN_DOMAIN;
|
|
167
|
+
|
|
155
168
|
Logger.info(
|
|
156
169
|
"[PractitionerInviteMailingService] Sending email with data:",
|
|
157
170
|
{
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
171
|
+
domain: domainToSendFrom,
|
|
172
|
+
to: mailgunSendData.to,
|
|
173
|
+
from: mailgunSendData.from,
|
|
174
|
+
subject: mailgunSendData.subject,
|
|
175
|
+
hasHtml: !!mailgunSendData.html,
|
|
162
176
|
}
|
|
163
177
|
);
|
|
164
178
|
|
|
165
|
-
|
|
179
|
+
// Call the updated sendEmail method from BaseMailingService
|
|
180
|
+
const result = await this.sendEmail(domainToSendFrom, mailgunSendData);
|
|
166
181
|
|
|
167
182
|
// Log success
|
|
168
183
|
await this.logEmailAttempt(
|
|
@@ -175,12 +190,14 @@ export class PractitionerInviteMailingService extends BaseMailingService {
|
|
|
175
190
|
);
|
|
176
191
|
|
|
177
192
|
return result;
|
|
178
|
-
} catch (error) {
|
|
193
|
+
} catch (error: any) {
|
|
179
194
|
Logger.error(
|
|
180
195
|
"[PractitionerInviteMailingService] Error sending invitation email:",
|
|
181
196
|
{
|
|
182
|
-
|
|
183
|
-
|
|
197
|
+
errorMessage: error.message,
|
|
198
|
+
errorDetails: error.details,
|
|
199
|
+
errorStatus: error.status,
|
|
200
|
+
stack: error.stack,
|
|
184
201
|
}
|
|
185
202
|
);
|
|
186
203
|
|
|
@@ -205,11 +222,16 @@ export class PractitionerInviteMailingService extends BaseMailingService {
|
|
|
205
222
|
* and sends the invitation email.
|
|
206
223
|
* @param tokenData The fully typed token object including its id
|
|
207
224
|
* @param fromAddress The 'from' email address to use, obtained from config
|
|
225
|
+
* @param mailgunDomain The mailgun domain to use for sending
|
|
208
226
|
* @returns Promise resolved when the email is sent
|
|
209
227
|
*/
|
|
210
228
|
async handleTokenCreationEvent(
|
|
211
229
|
tokenData: PractitionerToken,
|
|
212
|
-
|
|
230
|
+
mailgunConfig: {
|
|
231
|
+
fromAddress: string;
|
|
232
|
+
domain: string;
|
|
233
|
+
registrationUrl?: string;
|
|
234
|
+
}
|
|
213
235
|
): Promise<void> {
|
|
214
236
|
try {
|
|
215
237
|
Logger.info(
|
|
@@ -239,8 +261,15 @@ export class PractitionerInviteMailingService extends BaseMailingService {
|
|
|
239
261
|
}
|
|
240
262
|
|
|
241
263
|
// Validate token status (handle both enum and string values)
|
|
242
|
-
if (
|
|
243
|
-
|
|
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;
|
|
244
273
|
}
|
|
245
274
|
|
|
246
275
|
// Log token status to help with debugging
|
|
@@ -305,11 +334,11 @@ export class PractitionerInviteMailingService extends BaseMailingService {
|
|
|
305
334
|
// So we'll use simple contact information from contactInfo
|
|
306
335
|
|
|
307
336
|
// Validate fromAddress
|
|
308
|
-
if (!fromAddress) {
|
|
337
|
+
if (!mailgunConfig.fromAddress) {
|
|
309
338
|
Logger.warn(
|
|
310
339
|
"[PractitionerInviteMailingService] No fromAddress provided, using default"
|
|
311
340
|
);
|
|
312
|
-
fromAddress = this.
|
|
341
|
+
mailgunConfig.fromAddress = `MetaEstetics <no-reply@${this.DEFAULT_MAILGUN_DOMAIN}>`;
|
|
313
342
|
}
|
|
314
343
|
|
|
315
344
|
// Prepare email data using typed data
|
|
@@ -333,7 +362,9 @@ export class PractitionerInviteMailingService extends BaseMailingService {
|
|
|
333
362
|
contactName: "Clinic Admin",
|
|
334
363
|
},
|
|
335
364
|
options: {
|
|
336
|
-
fromAddress: fromAddress,
|
|
365
|
+
fromAddress: mailgunConfig.fromAddress,
|
|
366
|
+
mailgunDomain: mailgunConfig.domain,
|
|
367
|
+
registrationUrl: mailgunConfig.registrationUrl,
|
|
337
368
|
},
|
|
338
369
|
};
|
|
339
370
|
|
|
@@ -347,12 +378,15 @@ export class PractitionerInviteMailingService extends BaseMailingService {
|
|
|
347
378
|
Logger.info(
|
|
348
379
|
"[PractitionerInviteMailingService] Invitation email sent successfully"
|
|
349
380
|
);
|
|
350
|
-
} catch (error) {
|
|
381
|
+
} catch (error: any) {
|
|
351
382
|
Logger.error(
|
|
352
383
|
"[PractitionerInviteMailingService] Error handling token creation event:",
|
|
353
384
|
{
|
|
354
|
-
|
|
355
|
-
|
|
385
|
+
errorMessage: error.message,
|
|
386
|
+
errorDetails: error.details,
|
|
387
|
+
errorStatus: error.status,
|
|
388
|
+
stack: error.stack,
|
|
389
|
+
tokenId: tokenData?.id,
|
|
356
390
|
}
|
|
357
391
|
);
|
|
358
392
|
throw error;
|