backend-manager 5.0.91 → 5.0.93
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/CHANGELOG.md +2 -2
- package/CLAUDE.md +14 -6
- package/README.md +6 -6
- package/TODO-MARKETING.md +3 -0
- package/TODO-PAYMENT-v2.md +71 -0
- package/TODO.md +7 -0
- package/package.json +3 -3
- package/src/cli/commands/{emulators.js → emulator.js} +15 -15
- package/src/cli/commands/index.js +1 -1
- package/src/cli/commands/setup-tests/{emulators-config.js → emulator-config.js} +4 -4
- package/src/cli/commands/setup-tests/index.js +2 -2
- package/src/cli/commands/setup-tests/project-id-consistency.js +1 -1
- package/src/cli/commands/test.js +16 -16
- package/src/cli/index.js +4 -4
- package/src/manager/events/auth/on-create.js +5 -158
- package/src/manager/events/firestore/payments-webhooks/analytics.js +4 -3
- package/src/manager/events/firestore/payments-webhooks/on-write.js +56 -6
- package/src/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-completed.js +3 -3
- package/src/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-failed.js +1 -1
- package/src/manager/events/firestore/payments-webhooks/transitions/send-email.js +32 -28
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-requested.js +3 -3
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/new-subscription.js +3 -3
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/payment-failed.js +3 -3
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/payment-recovered.js +3 -3
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/plan-changed.js +3 -3
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-cancelled.js +3 -3
- package/src/manager/functions/core/actions/api/admin/send-email.js +0 -131
- package/src/manager/functions/core/actions/api/general/add-marketing-contact.js +2 -137
- package/src/manager/functions/core/actions/api/general/emails/general:download-app-link.js +2 -2
- package/src/manager/functions/core/actions/api/user/sign-up.js +1 -1
- package/src/manager/index.js +12 -0
- package/src/manager/libraries/email.js +523 -0
- package/src/manager/libraries/infer-contact.js +140 -0
- package/src/manager/libraries/prompts/infer-contact.md +43 -0
- package/src/manager/routes/admin/backup/post.js +4 -3
- package/src/manager/routes/admin/email/post.js +11 -428
- package/src/manager/routes/admin/hook/post.js +3 -2
- package/src/manager/routes/admin/notification/post.js +14 -12
- package/src/manager/routes/admin/post/post.js +5 -6
- package/src/manager/routes/admin/post/put.js +3 -2
- package/src/manager/routes/admin/stats/get.js +19 -10
- package/src/manager/routes/general/email/post.js +8 -21
- package/src/manager/routes/general/email/templates/download-app-link.js +2 -2
- package/src/manager/routes/marketing/contact/post.js +2 -100
- package/src/manager/routes/payments/intent/post.js +0 -2
- package/src/manager/routes/payments/intent/processors/test.js +9 -10
- package/src/manager/routes/user/oauth2/_helpers.js +3 -2
- package/src/manager/routes/user/oauth2/delete.js +3 -3
- package/src/manager/routes/user/oauth2/get.js +2 -2
- package/src/manager/routes/user/oauth2/post.js +9 -9
- package/src/manager/routes/user/sessions/delete.js +4 -3
- package/src/manager/routes/user/signup/post.js +254 -54
- package/src/manager/schemas/admin/email/post.js +13 -8
- package/src/test/run-tests.js +1 -1
- package/test/functions/admin/send-email.js +1 -88
- package/test/helpers/email.js +421 -0
- package/test/helpers/infer-contact.js +299 -0
- package/test/routes/admin/email.js +41 -90
- package/REFACTOR-BEM-API.md +0 -76
- package/REFACTOR-MIDDLEWARE.md +0 -62
- package/REFACTOR-PAYMENT.md +0 -66
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
const fetch = require('wonderful-fetch');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const dns = require('dns').promises;
|
|
4
|
-
const OpenAI = require(path.join(__dirname, '..', '..', '..', '..', '..', 'libraries', 'openai'));
|
|
5
4
|
|
|
6
5
|
// Load disposable domains list
|
|
7
6
|
const DISPOSABLE_DOMAINS = require(path.join(__dirname, '..', '..', '..', '..', '..', 'libraries', 'disposable-domains.json'));
|
|
8
7
|
const DISPOSABLE_SET = new Set(DISPOSABLE_DOMAINS.map(d => d.toLowerCase()));
|
|
8
|
+
const { inferContact } = require(path.join(__dirname, '..', '..', '..', '..', '..', 'libraries', 'infer-contact.js'));
|
|
9
9
|
|
|
10
10
|
function Module() {}
|
|
11
11
|
|
|
@@ -108,7 +108,7 @@ Module.prototype.main = function () {
|
|
|
108
108
|
// Infer name if not provided
|
|
109
109
|
let nameInferred = null;
|
|
110
110
|
if (!firstName && !lastName) {
|
|
111
|
-
nameInferred = await
|
|
111
|
+
nameInferred = await inferContact(email, assistant);
|
|
112
112
|
firstName = nameInferred.firstName;
|
|
113
113
|
lastName = nameInferred.lastName;
|
|
114
114
|
}
|
|
@@ -248,141 +248,6 @@ async function checkMxRecord(domain) {
|
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
-
/**
|
|
252
|
-
* Infer first/last name from email address
|
|
253
|
-
* Uses ChatGPT if OPENAI_API_KEY exists, otherwise regex parsing
|
|
254
|
-
*/
|
|
255
|
-
async function inferName(email, assistant) {
|
|
256
|
-
// Try ChatGPT first if available
|
|
257
|
-
if (process.env.OPENAI_API_KEY) {
|
|
258
|
-
const aiResult = await inferNameWithAI(email, assistant);
|
|
259
|
-
if (aiResult && (aiResult.firstName || aiResult.lastName)) {
|
|
260
|
-
return aiResult;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Fallback to regex parsing
|
|
265
|
-
return inferNameFromEmail(email);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Use ChatGPT to infer name from email
|
|
270
|
-
*/
|
|
271
|
-
async function inferNameWithAI(email, assistant) {
|
|
272
|
-
try {
|
|
273
|
-
const ai = new OpenAI(assistant);
|
|
274
|
-
const result = await ai.request({
|
|
275
|
-
// model: 'gpt-4.1-nano',
|
|
276
|
-
model: 'gpt-5-mini',
|
|
277
|
-
timeout: 30000,
|
|
278
|
-
maxTokens: 1024,
|
|
279
|
-
moderate: false,
|
|
280
|
-
response: 'json',
|
|
281
|
-
prompt: {
|
|
282
|
-
content: `
|
|
283
|
-
<identity>
|
|
284
|
-
You extract names and company from email addresses.
|
|
285
|
-
</identity>
|
|
286
|
-
|
|
287
|
-
<format>
|
|
288
|
-
Return ONLY valid JSON like so:
|
|
289
|
-
{
|
|
290
|
-
"firstName": "...", // First name <string>, capitalized
|
|
291
|
-
"lastName": "...", // Last name <string>, capitalized
|
|
292
|
-
"company": "...", // Company name <string>, capitalized
|
|
293
|
-
"confidence": "..." // Confidence level <number>, 0-1 scale
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
If you cannot determine a name, use empty strings.
|
|
297
|
-
</format>
|
|
298
|
-
|
|
299
|
-
<examples>
|
|
300
|
-
<example>
|
|
301
|
-
<input>john.smith@acme.com</input>
|
|
302
|
-
<output>{"firstName": "John", "lastName": "Smith", "company": "Acme", "confidence": 0.9}</output>
|
|
303
|
-
</example>
|
|
304
|
-
<example>
|
|
305
|
-
<input>jsmith123@gmail.com</input>
|
|
306
|
-
<output>{"firstName": "J", "lastName": "Smith", "company": "", "confidence": 0.4}</output>
|
|
307
|
-
</example>
|
|
308
|
-
<example>
|
|
309
|
-
<input>support@bigcorp.io</input>
|
|
310
|
-
<output>{"firstName": "", "lastName": "", "company": "Bigcorp", "confidence": 0.7}</output>
|
|
311
|
-
</example>
|
|
312
|
-
<example>
|
|
313
|
-
<input>mary_jane_watson@stark-industries.com</input>
|
|
314
|
-
<output>{"firstName": "Mary", "lastName": "Watson", "company": "Stark Industries", "confidence": 0.85}</output>
|
|
315
|
-
</example>
|
|
316
|
-
<example>
|
|
317
|
-
<input>info@company.org</input>
|
|
318
|
-
<output>{"firstName": "", "lastName": "", "company": "Company", "confidence": 0.6}</output>
|
|
319
|
-
</example>
|
|
320
|
-
</examples>
|
|
321
|
-
`,
|
|
322
|
-
},
|
|
323
|
-
message: {
|
|
324
|
-
content: `Email: ${email}`,
|
|
325
|
-
},
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
if (result?.firstName !== undefined) {
|
|
329
|
-
return {
|
|
330
|
-
firstName: capitalize(result.firstName || ''),
|
|
331
|
-
lastName: capitalize(result.lastName || ''),
|
|
332
|
-
company: capitalize(result.company || ''),
|
|
333
|
-
confidence: typeof result.confidence === 'number' ? result.confidence : 0.5,
|
|
334
|
-
method: 'ai',
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
} catch (e) {
|
|
338
|
-
console.error('AI name inference error:', e);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
return null;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Regex-based name inference from email
|
|
346
|
-
*/
|
|
347
|
-
function inferNameFromEmail(email) {
|
|
348
|
-
const local = email.split('@')[0];
|
|
349
|
-
|
|
350
|
-
// Remove trailing numbers
|
|
351
|
-
const cleaned = local.replace(/[0-9]+$/, '');
|
|
352
|
-
|
|
353
|
-
// Split on common separators
|
|
354
|
-
const parts = cleaned.split(/[._-]/);
|
|
355
|
-
|
|
356
|
-
if (parts.length >= 2) {
|
|
357
|
-
return {
|
|
358
|
-
firstName: capitalize(parts[0]),
|
|
359
|
-
lastName: capitalize(parts.slice(1).join(' ')),
|
|
360
|
-
confidence: 0.5,
|
|
361
|
-
method: 'regex',
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
return {
|
|
366
|
-
firstName: capitalize(cleaned),
|
|
367
|
-
lastName: '',
|
|
368
|
-
confidence: 0.25,
|
|
369
|
-
method: 'regex',
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Capitalize first letter of each word
|
|
375
|
-
*/
|
|
376
|
-
function capitalize(str) {
|
|
377
|
-
if (!str) {
|
|
378
|
-
return '';
|
|
379
|
-
}
|
|
380
|
-
return str
|
|
381
|
-
.split(' ')
|
|
382
|
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
383
|
-
.join(' ');
|
|
384
|
-
}
|
|
385
|
-
|
|
386
251
|
/**
|
|
387
252
|
* Add contact to SendGrid Marketing Contacts
|
|
388
253
|
*/
|
|
@@ -12,8 +12,8 @@ module.exports = function (payload, config) {
|
|
|
12
12
|
},
|
|
13
13
|
categories: ['download'],
|
|
14
14
|
subject: `Free ${config.brand.name} download link for ${payload.name || 'you'}!`,
|
|
15
|
-
template: '
|
|
16
|
-
group:
|
|
15
|
+
template: 'main/misc/app-download-link',
|
|
16
|
+
group: 'marketing',
|
|
17
17
|
copy: false,
|
|
18
18
|
data: {},
|
|
19
19
|
}
|
|
@@ -112,7 +112,7 @@ Module.prototype.main = function () {
|
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
// Note: SendGrid list
|
|
115
|
+
// Note: SendGrid list, welcome emails, and name inference are handled by user/signup route
|
|
116
116
|
|
|
117
117
|
return resolve({
|
|
118
118
|
data: {
|
package/src/manager/index.js
CHANGED
|
@@ -507,6 +507,18 @@ Manager.prototype.Metadata = function () {
|
|
|
507
507
|
return new self.libraries.Metadata(self, ...arguments);
|
|
508
508
|
};
|
|
509
509
|
|
|
510
|
+
Manager.prototype.Email = function (assistant) {
|
|
511
|
+
const self = this;
|
|
512
|
+
self.libraries.Email = self.libraries.Email || require('./libraries/email.js');
|
|
513
|
+
return new self.libraries.Email(assistant);
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
Manager.prototype.AI = function (assistant, key) {
|
|
517
|
+
const self = this;
|
|
518
|
+
self.libraries.AI = self.libraries.AI || require('./libraries/openai.js');
|
|
519
|
+
return new self.libraries.AI(assistant, key);
|
|
520
|
+
};
|
|
521
|
+
|
|
510
522
|
// For importing API libraries
|
|
511
523
|
Manager.prototype.Api = function () {
|
|
512
524
|
const self = this;
|