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.
Files changed (61) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/CLAUDE.md +14 -6
  3. package/README.md +6 -6
  4. package/TODO-MARKETING.md +3 -0
  5. package/TODO-PAYMENT-v2.md +71 -0
  6. package/TODO.md +7 -0
  7. package/package.json +3 -3
  8. package/src/cli/commands/{emulators.js → emulator.js} +15 -15
  9. package/src/cli/commands/index.js +1 -1
  10. package/src/cli/commands/setup-tests/{emulators-config.js → emulator-config.js} +4 -4
  11. package/src/cli/commands/setup-tests/index.js +2 -2
  12. package/src/cli/commands/setup-tests/project-id-consistency.js +1 -1
  13. package/src/cli/commands/test.js +16 -16
  14. package/src/cli/index.js +4 -4
  15. package/src/manager/events/auth/on-create.js +5 -158
  16. package/src/manager/events/firestore/payments-webhooks/analytics.js +4 -3
  17. package/src/manager/events/firestore/payments-webhooks/on-write.js +56 -6
  18. package/src/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-completed.js +3 -3
  19. package/src/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-failed.js +1 -1
  20. package/src/manager/events/firestore/payments-webhooks/transitions/send-email.js +32 -28
  21. package/src/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-requested.js +3 -3
  22. package/src/manager/events/firestore/payments-webhooks/transitions/subscription/new-subscription.js +3 -3
  23. package/src/manager/events/firestore/payments-webhooks/transitions/subscription/payment-failed.js +3 -3
  24. package/src/manager/events/firestore/payments-webhooks/transitions/subscription/payment-recovered.js +3 -3
  25. package/src/manager/events/firestore/payments-webhooks/transitions/subscription/plan-changed.js +3 -3
  26. package/src/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-cancelled.js +3 -3
  27. package/src/manager/functions/core/actions/api/admin/send-email.js +0 -131
  28. package/src/manager/functions/core/actions/api/general/add-marketing-contact.js +2 -137
  29. package/src/manager/functions/core/actions/api/general/emails/general:download-app-link.js +2 -2
  30. package/src/manager/functions/core/actions/api/user/sign-up.js +1 -1
  31. package/src/manager/index.js +12 -0
  32. package/src/manager/libraries/email.js +523 -0
  33. package/src/manager/libraries/infer-contact.js +140 -0
  34. package/src/manager/libraries/prompts/infer-contact.md +43 -0
  35. package/src/manager/routes/admin/backup/post.js +4 -3
  36. package/src/manager/routes/admin/email/post.js +11 -428
  37. package/src/manager/routes/admin/hook/post.js +3 -2
  38. package/src/manager/routes/admin/notification/post.js +14 -12
  39. package/src/manager/routes/admin/post/post.js +5 -6
  40. package/src/manager/routes/admin/post/put.js +3 -2
  41. package/src/manager/routes/admin/stats/get.js +19 -10
  42. package/src/manager/routes/general/email/post.js +8 -21
  43. package/src/manager/routes/general/email/templates/download-app-link.js +2 -2
  44. package/src/manager/routes/marketing/contact/post.js +2 -100
  45. package/src/manager/routes/payments/intent/post.js +0 -2
  46. package/src/manager/routes/payments/intent/processors/test.js +9 -10
  47. package/src/manager/routes/user/oauth2/_helpers.js +3 -2
  48. package/src/manager/routes/user/oauth2/delete.js +3 -3
  49. package/src/manager/routes/user/oauth2/get.js +2 -2
  50. package/src/manager/routes/user/oauth2/post.js +9 -9
  51. package/src/manager/routes/user/sessions/delete.js +4 -3
  52. package/src/manager/routes/user/signup/post.js +254 -54
  53. package/src/manager/schemas/admin/email/post.js +13 -8
  54. package/src/test/run-tests.js +1 -1
  55. package/test/functions/admin/send-email.js +1 -88
  56. package/test/helpers/email.js +421 -0
  57. package/test/helpers/infer-contact.js +299 -0
  58. package/test/routes/admin/email.js +41 -90
  59. package/REFACTOR-BEM-API.md +0 -76
  60. package/REFACTOR-MIDDLEWARE.md +0 -62
  61. 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 inferName(email, assistant);
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: 'd-1d730ac8cc544b7cbccc8fa4a4b3f9ce',
16
- group: 25927,
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 and welcome emails are now handled by auth:on-create
115
+ // Note: SendGrid list, welcome emails, and name inference are handled by user/signup route
116
116
 
117
117
  return resolve({
118
118
  data: {
@@ -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;