backend-manager 5.0.147 → 5.0.149
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 +58 -0
- package/CLAUDE.md +26 -0
- package/package.json +1 -1
- package/src/cli/commands/emulator.js +14 -4
- package/src/cli/commands/test.js +4 -10
- package/src/manager/cron/daily/ghostii-auto-publisher.js +25 -25
- package/src/manager/cron/frequent/abandoned-carts.js +7 -5
- package/src/manager/cron/frequent/email-queue.js +56 -0
- package/src/manager/events/auth/before-signin.js +3 -0
- package/src/manager/events/auth/on-delete.js +8 -0
- package/src/manager/events/firestore/payments-disputes/on-write.js +2 -1
- package/src/manager/events/firestore/payments-webhooks/on-write.js +9 -0
- package/src/manager/events/firestore/payments-webhooks/transitions/send-email.js +7 -21
- package/src/manager/functions/core/actions/api/admin/get-stats.js +2 -2
- package/src/manager/functions/core/actions/api/admin/send-email.js +14 -14
- package/src/manager/functions/core/actions/api/general/add-marketing-contact.js +22 -318
- package/src/manager/functions/core/actions/api/general/emails/general:download-app-link.js +1 -1
- package/src/manager/functions/core/actions/api/general/remove-marketing-contact.js +2 -185
- package/src/manager/functions/core/actions/api/general/send-email.js +1 -1
- package/src/manager/functions/core/actions/api/special/setup-electron-manager-client.js +2 -2
- package/src/manager/functions/core/actions/api/test/health.js +1 -0
- package/src/manager/helpers/api-manager.js +2 -2
- package/src/manager/helpers/user.js +3 -1
- package/src/manager/index.js +15 -10
- package/src/manager/libraries/email/constants.js +243 -0
- package/src/manager/libraries/email/index.js +145 -0
- package/src/manager/libraries/email/marketing/index.js +377 -0
- package/src/manager/libraries/email/providers/beehiiv.js +258 -0
- package/src/manager/libraries/email/providers/sendgrid.js +429 -0
- package/src/manager/libraries/{email.js → email/transactional/index.js} +91 -99
- package/src/manager/libraries/email/validation.js +168 -0
- package/src/manager/libraries/infer-contact.js +1 -1
- package/src/manager/routes/admin/cron/post.js +3 -3
- package/src/manager/routes/admin/email/post.js +1 -1
- package/src/manager/routes/admin/stats/get.js +2 -2
- package/src/manager/routes/{app → brand}/get.js +1 -1
- package/src/manager/routes/general/email/templates/download-app-link.js +1 -1
- package/src/manager/routes/marketing/contact/delete.js +2 -164
- package/src/manager/routes/marketing/contact/post.js +45 -298
- package/src/manager/routes/marketing/contact/put.js +39 -0
- package/src/manager/routes/payments/cancel/post.js +11 -0
- package/src/manager/routes/special/electron-client/post.js +3 -3
- package/src/manager/routes/test/health/get.js +1 -0
- package/src/manager/routes/user/data-request/delete.js +2 -2
- package/src/manager/routes/user/data-request/get.js +2 -2
- package/src/manager/routes/user/data-request/post.js +2 -2
- package/src/manager/routes/user/delete.js +1 -1
- package/src/manager/routes/user/feedback/post.js +12 -8
- package/src/manager/routes/user/signup/post.js +48 -37
- package/src/manager/schemas/admin/email/post.js +4 -4
- package/src/manager/schemas/marketing/contact/delete.js +3 -1
- package/src/manager/schemas/marketing/contact/post.js +3 -1
- package/src/manager/schemas/marketing/contact/put.js +6 -0
- package/src/manager/schemas/special/electron-client/post.js +2 -2
- package/src/manager/schemas/user/feedback/post.js +2 -2
- package/src/test/run-tests.js +1 -1
- package/src/test/runner.js +22 -10
- package/src/test/test-accounts.js +9 -0
- package/src/test/utils/extended-mode-warning.js +11 -0
- package/templates/_.env +1 -0
- package/test/events/payments/journey-payments-cancel-endpoint.js +11 -0
- package/test/events/payments/journey-payments-trial-cancel.js +11 -0
- package/test/functions/admin/edit-post.js +2 -2
- package/test/functions/admin/write-repo-content.js +2 -2
- package/test/functions/general/add-marketing-contact.js +21 -23
- package/test/helpers/email-validation.js +420 -0
- package/test/helpers/email.js +119 -6
- package/test/helpers/marketing-lifecycle.js +121 -0
- package/test/helpers/user.js +2 -2
- package/test/routes/admin/create-post.js +2 -2
- package/test/routes/admin/post.js +2 -2
- package/test/routes/admin/repo-content.js +2 -2
- package/test/routes/marketing/contact.js +21 -24
- package/test/routes/payments/cancel.js +18 -0
package/test/helpers/user.js
CHANGED
|
@@ -410,7 +410,7 @@ module.exports = {
|
|
|
410
410
|
const user = createUser({});
|
|
411
411
|
const expectedKeys = [
|
|
412
412
|
'auth', 'subscription', 'roles', 'flags', 'affiliate',
|
|
413
|
-
'activity', 'api', 'usage', 'personal', 'oauth2', 'attribution',
|
|
413
|
+
'activity', 'api', 'usage', 'personal', 'oauth2', 'attribution', 'metadata',
|
|
414
414
|
];
|
|
415
415
|
|
|
416
416
|
for (const key of expectedKeys) {
|
|
@@ -425,7 +425,7 @@ module.exports = {
|
|
|
425
425
|
const user = createUser({});
|
|
426
426
|
const expectedKeys = [
|
|
427
427
|
'auth', 'subscription', 'roles', 'flags', 'affiliate',
|
|
428
|
-
'activity', 'api', 'usage', 'personal', 'oauth2', 'attribution',
|
|
428
|
+
'activity', 'api', 'usage', 'personal', 'oauth2', 'attribution', 'metadata',
|
|
429
429
|
];
|
|
430
430
|
|
|
431
431
|
for (const key of Object.keys(user)) {
|
|
@@ -113,13 +113,13 @@ module.exports = {
|
|
|
113
113
|
return;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
if (!config.
|
|
116
|
+
if (!config.github?.repo_website) {
|
|
117
117
|
assert.fail('githubRepoWebsite not configured');
|
|
118
118
|
return;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
// Parse owner/repo for cleanup later
|
|
122
|
-
const repoMatch = config.
|
|
122
|
+
const repoMatch = config.github?.repo_website.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
123
123
|
if (!repoMatch) {
|
|
124
124
|
assert.fail('Could not parse githubRepoWebsite');
|
|
125
125
|
return;
|
|
@@ -115,7 +115,7 @@ module.exports = {
|
|
|
115
115
|
skip: !process.env.GITHUB_TOKEN ? 'GITHUB_TOKEN env var not set' : false,
|
|
116
116
|
|
|
117
117
|
async run({ assert, state, config }) {
|
|
118
|
-
if (!config.
|
|
118
|
+
if (!config.github?.repo_website) {
|
|
119
119
|
assert.fail('githubRepoWebsite not configured');
|
|
120
120
|
return;
|
|
121
121
|
}
|
|
@@ -123,7 +123,7 @@ module.exports = {
|
|
|
123
123
|
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
|
|
124
124
|
|
|
125
125
|
// Parse owner/repo from githubRepoWebsite
|
|
126
|
-
const repoMatch = config.
|
|
126
|
+
const repoMatch = config.github?.repo_website.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
127
127
|
if (!repoMatch) {
|
|
128
128
|
assert.fail('Could not parse githubRepoWebsite');
|
|
129
129
|
return;
|
|
@@ -155,14 +155,14 @@ module.exports = {
|
|
|
155
155
|
timeout: 60000,
|
|
156
156
|
|
|
157
157
|
async run({ state, config }) {
|
|
158
|
-
if (!process.env.GITHUB_TOKEN || !config.
|
|
158
|
+
if (!process.env.GITHUB_TOKEN || !config.github?.repo_website) {
|
|
159
159
|
return;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
|
|
163
163
|
|
|
164
164
|
// Parse owner/repo from githubRepoWebsite (e.g., 'https://github.com/owner/repo')
|
|
165
|
-
const repoMatch = config.
|
|
165
|
+
const repoMatch = config.github?.repo_website.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
166
166
|
if (!repoMatch) {
|
|
167
167
|
return;
|
|
168
168
|
}
|
|
@@ -245,9 +245,9 @@ module.exports = {
|
|
|
245
245
|
},
|
|
246
246
|
},
|
|
247
247
|
|
|
248
|
-
// Test 8:
|
|
248
|
+
// Test 8: Mailbox verification (only runs if TEST_EXTENDED_MODE and ZEROBOUNCE_API_KEY are set)
|
|
249
249
|
{
|
|
250
|
-
name: 'add-
|
|
250
|
+
name: 'add-mailbox-validation',
|
|
251
251
|
auth: 'admin',
|
|
252
252
|
timeout: 30000,
|
|
253
253
|
skip: !process.env.TEST_EXTENDED_MODE || !process.env.ZEROBOUNCE_API_KEY
|
|
@@ -261,7 +261,6 @@ module.exports = {
|
|
|
261
261
|
const response = await http.post('marketing/contact', {
|
|
262
262
|
email: testEmail,
|
|
263
263
|
source: 'bem-test',
|
|
264
|
-
// No firstName/lastName - should be inferred as "Rachel Greene"
|
|
265
264
|
});
|
|
266
265
|
|
|
267
266
|
assert.isSuccess(response, 'Add marketing contact should succeed');
|
|
@@ -270,17 +269,17 @@ module.exports = {
|
|
|
270
269
|
assert.hasProperty(response, 'data.validation', 'Response should contain validation');
|
|
271
270
|
assert.hasProperty(response, 'data.validation.checks', 'Validation should contain checks');
|
|
272
271
|
|
|
273
|
-
//
|
|
274
|
-
assert.hasProperty(response, 'data.validation.checks.
|
|
272
|
+
// Mailbox check should be in checks when key is set
|
|
273
|
+
assert.hasProperty(response, 'data.validation.checks.mailbox', 'Should have mailbox check');
|
|
275
274
|
|
|
276
|
-
const
|
|
275
|
+
const mbResult = response.data.validation.checks.mailbox;
|
|
277
276
|
|
|
278
|
-
// If
|
|
279
|
-
if (
|
|
280
|
-
skip('
|
|
277
|
+
// If out of credits, skip test - not a failure
|
|
278
|
+
if (mbResult.error?.includes('out of credits')) {
|
|
279
|
+
skip('Mailbox verification out of credits');
|
|
281
280
|
}
|
|
282
281
|
|
|
283
|
-
assert.hasProperty(
|
|
282
|
+
assert.hasProperty(mbResult, 'status', 'Mailbox check should return status');
|
|
284
283
|
|
|
285
284
|
state.sendgridAdded = response.data?.providers?.sendgrid?.success;
|
|
286
285
|
state.beehiivAdded = response.data?.providers?.beehiiv?.success;
|
|
@@ -295,9 +294,9 @@ module.exports = {
|
|
|
295
294
|
},
|
|
296
295
|
},
|
|
297
296
|
|
|
298
|
-
// Test 9:
|
|
297
|
+
// Test 9: Mailbox verification rejects invalid email (only runs if TEST_EXTENDED_MODE and ZEROBOUNCE_API_KEY are set)
|
|
299
298
|
{
|
|
300
|
-
name: 'add-
|
|
299
|
+
name: 'add-mailbox-rejects-invalid',
|
|
301
300
|
auth: 'admin',
|
|
302
301
|
timeout: 30000,
|
|
303
302
|
skip: !process.env.TEST_EXTENDED_MODE || !process.env.ZEROBOUNCE_API_KEY
|
|
@@ -305,30 +304,28 @@ module.exports = {
|
|
|
305
304
|
: false,
|
|
306
305
|
|
|
307
306
|
async run({ http, assert, skip }) {
|
|
308
|
-
// Use fake email that
|
|
307
|
+
// Use fake email that mailbox verification should flag as invalid
|
|
309
308
|
const testEmail = TEST_EMAILS.invalid();
|
|
310
309
|
|
|
311
310
|
const response = await http.post('marketing/contact', {
|
|
312
311
|
email: testEmail,
|
|
313
312
|
source: 'bem-test',
|
|
314
|
-
// No firstName/lastName - AI will try to infer from "test"
|
|
315
313
|
});
|
|
316
314
|
|
|
317
|
-
// Should still succeed (we fail open) but
|
|
315
|
+
// Should still succeed (we fail open) but mailbox should report invalid
|
|
318
316
|
assert.isSuccess(response, 'Request should succeed even with invalid email');
|
|
319
317
|
|
|
320
|
-
const
|
|
318
|
+
const mbResult = response.data?.validation?.checks?.mailbox;
|
|
321
319
|
|
|
322
|
-
// If
|
|
323
|
-
if (
|
|
324
|
-
skip('
|
|
320
|
+
// If out of credits, skip test - not a failure
|
|
321
|
+
if (mbResult?.error?.includes('out of credits')) {
|
|
322
|
+
skip('Mailbox verification out of credits');
|
|
325
323
|
}
|
|
326
324
|
|
|
327
|
-
//
|
|
328
|
-
if (
|
|
329
|
-
assert.hasProperty(
|
|
330
|
-
|
|
331
|
-
assert.notEqual(zbResult.status, 'valid', 'Fake email should not be marked valid');
|
|
325
|
+
// Mailbox should return a status indicating the email is not valid
|
|
326
|
+
if (mbResult) {
|
|
327
|
+
assert.hasProperty(mbResult, 'status', 'Should have status');
|
|
328
|
+
assert.notEqual(mbResult.status, 'valid', 'Fake email should not be marked valid');
|
|
332
329
|
}
|
|
333
330
|
},
|
|
334
331
|
},
|
|
@@ -66,6 +66,18 @@ module.exports = {
|
|
|
66
66
|
},
|
|
67
67
|
},
|
|
68
68
|
|
|
69
|
+
{
|
|
70
|
+
name: 'rejects-subscription-younger-than-24-hours',
|
|
71
|
+
async run({ http, assert }) {
|
|
72
|
+
// cancel-too-young starts with startDate set to now (< 24 hours old)
|
|
73
|
+
const response = await http.as('cancel-too-young').post('payments/cancel', {
|
|
74
|
+
confirmed: true,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
assert.isError(response, 400, 'Should reject subscription younger than 24 hours');
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
|
|
69
81
|
{
|
|
70
82
|
name: 'rejects-unknown-processor',
|
|
71
83
|
async run({ http, assert }) {
|
|
@@ -101,6 +113,12 @@ module.exports = {
|
|
|
101
113
|
&& userDoc?.subscription?.status === 'active';
|
|
102
114
|
}, 15000, 500);
|
|
103
115
|
|
|
116
|
+
// Backdate startDate so the 24-hour guard doesn't block cancellation
|
|
117
|
+
const twoDaysAgo = new Date(Date.now() - 2 * 24 * 60 * 60 * 1000);
|
|
118
|
+
await firestore.set(`users/${uid}`, {
|
|
119
|
+
subscription: { payment: { startDate: { timestamp: twoDaysAgo.toISOString(), timestampUNIX: twoDaysAgo.getTime() } } },
|
|
120
|
+
}, { merge: true });
|
|
121
|
+
|
|
104
122
|
// Step 2: Call the cancel endpoint
|
|
105
123
|
const cancelResponse = await http.as('route-cancel-success').post('payments/cancel', {
|
|
106
124
|
confirmed: true,
|