@omega.js/backend 0.1.0
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/LICENSE +98 -0
- package/README.md +1053 -0
- package/bin/omega +2 -0
- package/bin/omega-backend +2 -0
- package/bin/omg +2 -0
- package/cli.js +3 -0
- package/dist/cli/command-table.js +268 -0
- package/dist/cli/commands/auth.js +259 -0
- package/dist/cli/commands/base-command.js +451 -0
- package/dist/cli/commands/build.js +27 -0
- package/dist/cli/commands/clean.js +13 -0
- package/dist/cli/commands/clear.js +11 -0
- package/dist/cli/commands/cwd.js +9 -0
- package/dist/cli/commands/deploy.js +205 -0
- package/dist/cli/commands/emulator-orphans.js +440 -0
- package/dist/cli/commands/emulator.js +1829 -0
- package/dist/cli/commands/firebase-init.js +101 -0
- package/dist/cli/commands/firestore.js +261 -0
- package/dist/cli/commands/indexes.js +51 -0
- package/dist/cli/commands/install.js +151 -0
- package/dist/cli/commands/logs.js +358 -0
- package/dist/cli/commands/mcp.js +41 -0
- package/dist/cli/commands/migrate-markers.js +176 -0
- package/dist/cli/commands/migrate-rules.js +90 -0
- package/dist/cli/commands/migrate.js +59 -0
- package/dist/cli/commands/serve.js +208 -0
- package/dist/cli/commands/setup-tests/base-test.js +174 -0
- package/dist/cli/commands/setup-tests/emulator-config.js +109 -0
- package/dist/cli/commands/setup-tests/env-runtime-config-deprecated.js +47 -0
- package/dist/cli/commands/setup-tests/firebase-admin.js +49 -0
- package/dist/cli/commands/setup-tests/firebase-auth.js +26 -0
- package/dist/cli/commands/setup-tests/firebase-cli.js +26 -0
- package/dist/cli/commands/setup-tests/firebase-functions.js +49 -0
- package/dist/cli/commands/setup-tests/firestore-indexes-file.js +72 -0
- package/dist/cli/commands/setup-tests/firestore-indexes-in-json.js +20 -0
- package/dist/cli/commands/setup-tests/firestore-indexes-required.js +108 -0
- package/dist/cli/commands/setup-tests/firestore-indexes-synced.js +236 -0
- package/dist/cli/commands/setup-tests/firestore-rules-file.js +115 -0
- package/dist/cli/commands/setup-tests/firestore-rules-in-json.js +49 -0
- package/dist/cli/commands/setup-tests/functions-package.js +44 -0
- package/dist/cli/commands/setup-tests/gcloud-cli.js +26 -0
- package/dist/cli/commands/setup-tests/gitignore.js +122 -0
- package/dist/cli/commands/setup-tests/helpers/merge-line-files.js +28 -0
- package/dist/cli/commands/setup-tests/helpers/required-indexes.js +109 -0
- package/dist/cli/commands/setup-tests/helpers/seed-campaigns.js +246 -0
- package/dist/cli/commands/setup-tests/helpers.js +72 -0
- package/dist/cli/commands/setup-tests/hosting-folder.js +23 -0
- package/dist/cli/commands/setup-tests/hosting-rewrites.js +52 -0
- package/dist/cli/commands/setup-tests/index.js +111 -0
- package/dist/cli/commands/setup-tests/is-firebase-project.js +21 -0
- package/dist/cli/commands/setup-tests/java-installed.js +27 -0
- package/dist/cli/commands/setup-tests/legacy-tests-cleanup.js +43 -0
- package/dist/cli/commands/setup-tests/marketing-campaigns-seeded.js +230 -0
- package/dist/cli/commands/setup-tests/node-version.js +60 -0
- package/dist/cli/commands/setup-tests/npm-project-scripts.js +42 -0
- package/dist/cli/commands/setup-tests/nvmrc-version.js +36 -0
- package/dist/cli/commands/setup-tests/omega-backend.js +42 -0
- package/dist/cli/commands/setup-tests/omega-config.js +84 -0
- package/dist/cli/commands/setup-tests/project-directories.js +34 -0
- package/dist/cli/commands/setup-tests/project-id-consistency.js +201 -0
- package/dist/cli/commands/setup-tests/public-html-files.js +22 -0
- package/dist/cli/commands/setup-tests/realtime-rules-file.js +69 -0
- package/dist/cli/commands/setup-tests/realtime-rules-in-json.js +20 -0
- package/dist/cli/commands/setup-tests/remoteconfig-template-file.js +32 -0
- package/dist/cli/commands/setup-tests/remoteconfig-template-in-json.js +31 -0
- package/dist/cli/commands/setup-tests/service-account.js +86 -0
- package/dist/cli/commands/setup-tests/storage-lifecycle-policy.js +81 -0
- package/dist/cli/commands/setup-tests/storage-rules-file.js +32 -0
- package/dist/cli/commands/setup-tests/storage-rules-in-json.js +20 -0
- package/dist/cli/commands/stripe.js +14 -0
- package/dist/cli/commands/test-lanes/stripe-live.js +440 -0
- package/dist/cli/commands/test.js +976 -0
- package/dist/cli/commands/update.js +27 -0
- package/dist/cli/commands/version.js +10 -0
- package/dist/cli/commands/watch.js +196 -0
- package/dist/cli/flags.js +13 -0
- package/dist/cli/index.js +173 -0
- package/dist/cli/run.js +33 -0
- package/dist/cli/utils/attach-log-file.js +4 -0
- package/dist/cli/utils/compile-rules.js +1155 -0
- package/dist/cli/utils/ensure-target.js +314 -0
- package/dist/cli/utils/project-type.js +140 -0
- package/dist/cli/utils/public-files.js +49 -0
- package/dist/cli/utils/safe-install.js +4 -0
- package/dist/cli/utils/spawn-shell.js +94 -0
- package/dist/cli/utils/stage-functions.js +338 -0
- package/dist/cli/utils/stage-local-packages.js +210 -0
- package/dist/cli/utils/target-checks.js +161 -0
- package/dist/cli/utils/target.js +24 -0
- package/dist/cli/utils/ui.js +292 -0
- package/dist/defaults/AGENTS.md +125 -0
- package/dist/defaults/CHANGELOG.md +15 -0
- package/dist/defaults/CLAUDE.md +1 -0
- package/dist/defaults/_.gitignore +65 -0
- package/dist/defaults/docs/README.md +17 -0
- package/dist/defaults/test/README.md +65 -0
- package/dist/defaults/test/_init.js +14 -0
- package/dist/defaults/test/helpers/connect-trap.js +72 -0
- package/dist/defaults/test/unit/registration.test.js +143 -0
- package/dist/defaults/test/unit/rules-posture.test.js +101 -0
- package/dist/defaults/test/unit/socket-free.test.js +43 -0
- package/dist/manager/events/auth/before-create.js +89 -0
- package/dist/manager/events/auth/before-signin.js +76 -0
- package/dist/manager/events/auth/on-create.js +112 -0
- package/dist/manager/events/auth/on-delete.js +141 -0
- package/dist/manager/events/auth/utils.js +93 -0
- package/dist/manager/events/cron/daily/blog-auto-publisher.js +287 -0
- package/dist/manager/events/cron/daily/data-requests.js +57 -0
- package/dist/manager/events/cron/daily/expire-paypal-cancellations.js +191 -0
- package/dist/manager/events/cron/daily/marketing-prune.js +486 -0
- package/dist/manager/events/cron/daily/reset-usage.js +188 -0
- package/dist/manager/events/cron/daily/trial-lapse-sweep.js +355 -0
- package/dist/manager/events/cron/daily.js +3 -0
- package/dist/manager/events/cron/frequent/abandoned-carts.js +204 -0
- package/dist/manager/events/cron/frequent/email-queue.js +71 -0
- package/dist/manager/events/cron/frequent/marketing-campaigns.js +463 -0
- package/dist/manager/events/cron/frequent/retry-failed-webhooks.js +70 -0
- package/dist/manager/events/cron/frequent.js +3 -0
- package/dist/manager/events/cron/runner.js +64 -0
- package/dist/manager/events/firestore/notifications/on-write.js +123 -0
- package/dist/manager/events/firestore/payments-disputes/on-write.js +256 -0
- package/dist/manager/events/firestore/payments-disputes/providers/stripe.js +218 -0
- package/dist/manager/events/firestore/payments-disputes/providers/test.js +231 -0
- package/dist/manager/events/firestore/payments-webhooks/analytics.js +687 -0
- package/dist/manager/events/firestore/payments-webhooks/on-write.js +1224 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/index.js +395 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-completed.js +58 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-failed.js +28 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-refunded.js +30 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/send-email.js +74 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-removed.js +14 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-requested.js +28 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/checkout-declined.js +18 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/new-subscription.js +67 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-failed.js +25 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-recovered.js +26 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-refunded.js +34 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/plan-changed.js +42 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-cancelled.js +33 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-winback.js +17 -0
- package/dist/manager/functions/_legacy/actions/create-post-handler.js +188 -0
- package/dist/manager/functions/_legacy/actions/generate-uuid.js +63 -0
- package/dist/manager/functions/_legacy/actions/sign-up-handler.js +205 -0
- package/dist/manager/functions/_legacy/admin/create-post.js +207 -0
- package/dist/manager/functions/_legacy/admin/firestore-write.js +72 -0
- package/dist/manager/functions/_legacy/admin/get-stats.js +218 -0
- package/dist/manager/functions/_legacy/admin/query.js +198 -0
- package/dist/manager/functions/_legacy/admin/send-notification.js +206 -0
- package/dist/manager/functions/_legacy/template.js +33 -0
- package/dist/manager/functions/_legacy/test/authenticate.js +40 -0
- package/dist/manager/functions/_legacy/test/webhook.js +37 -0
- package/dist/manager/functions/wrappers/mailchimp/addToList.js +25 -0
- package/dist/manager/helpers/analytics.js +498 -0
- package/dist/manager/helpers/api-manager.js +313 -0
- package/dist/manager/helpers/backend-router.js +37 -0
- package/dist/manager/helpers/context/authenticate.js +215 -0
- package/dist/manager/helpers/context/client-info.js +91 -0
- package/dist/manager/helpers/context/index.js +228 -0
- package/dist/manager/helpers/context/logging.js +150 -0
- package/dist/manager/helpers/context/parse.js +160 -0
- package/dist/manager/helpers/context/respond.js +247 -0
- package/dist/manager/helpers/event-middleware.js +109 -0
- package/dist/manager/helpers/metadata.js +32 -0
- package/dist/manager/helpers/middleware.js +541 -0
- package/dist/manager/helpers/redact-secret.js +25 -0
- package/dist/manager/helpers/resolved-config.js +34 -0
- package/dist/manager/helpers/roles.js +69 -0
- package/dist/manager/helpers/safe-compare.js +31 -0
- package/dist/manager/helpers/schema-engine.js +314 -0
- package/dist/manager/helpers/schema-zod.js +203 -0
- package/dist/manager/helpers/settings.js +215 -0
- package/dist/manager/helpers/usage.js +541 -0
- package/dist/manager/helpers/user.js +57 -0
- package/dist/manager/helpers/utilities.js +565 -0
- package/dist/manager/index.js +1404 -0
- package/dist/manager/libraries/abandoned-cart-config.js +12 -0
- package/dist/manager/libraries/ai/index.js +321 -0
- package/dist/manager/libraries/ai/prompt.js +85 -0
- package/dist/manager/libraries/ai/providers/anthropic-format.js +297 -0
- package/dist/manager/libraries/ai/providers/anthropic.js +169 -0
- package/dist/manager/libraries/ai/providers/claude-code.js +183 -0
- package/dist/manager/libraries/ai/providers/openai.js +1272 -0
- package/dist/manager/libraries/ai/providers/test.js +257 -0
- package/dist/manager/libraries/ai/tokens.js +54 -0
- package/dist/manager/libraries/analytics/conversions.js +478 -0
- package/dist/manager/libraries/analytics/match-data.js +573 -0
- package/dist/manager/libraries/analytics/signup.js +120 -0
- package/dist/manager/libraries/auth-user.js +46 -0
- package/dist/manager/libraries/content/feed-parser.js +198 -0
- package/dist/manager/libraries/content/ghostii.js +191 -0
- package/dist/manager/libraries/content/source-resolver.js +629 -0
- package/dist/manager/libraries/email/constants.js +532 -0
- package/dist/manager/libraries/email/data/blocked-local-parts.json +55 -0
- package/dist/manager/libraries/email/data/blocked-local-patterns.js +14 -0
- package/dist/manager/libraries/email/data/corporate-domains.json +23 -0
- package/dist/manager/libraries/email/data/custom-disposable-domains.json +58 -0
- package/dist/manager/libraries/email/data/disposable-domains.json +8184 -0
- package/dist/manager/libraries/email/data/typo-domains.js +83 -0
- package/dist/manager/libraries/email/disposable-domains.js +92 -0
- package/dist/manager/libraries/email/generators/lib/filter.js +179 -0
- package/dist/manager/libraries/email/generators/lib/image-host.js +344 -0
- package/dist/manager/libraries/email/generators/lib/image-illustrator.js +154 -0
- package/dist/manager/libraries/email/generators/lib/markdown-renderer.js +294 -0
- package/dist/manager/libraries/email/generators/lib/mjml-template.js +120 -0
- package/dist/manager/libraries/email/generators/lib/structure.js +299 -0
- package/dist/manager/libraries/email/generators/lib/svg-illustrator.js +197 -0
- package/dist/manager/libraries/email/generators/lib/templates/base.js +229 -0
- package/dist/manager/libraries/email/generators/lib/templates/card.js +33 -0
- package/dist/manager/libraries/email/generators/lib/templates/classic-schema.js +59 -0
- package/dist/manager/libraries/email/generators/lib/templates/clean.js +82 -0
- package/dist/manager/libraries/email/generators/lib/templates/editorial/helpers.js +100 -0
- package/dist/manager/libraries/email/generators/lib/templates/editorial/index.js +318 -0
- package/dist/manager/libraries/email/generators/lib/templates/feedback.js +101 -0
- package/dist/manager/libraries/email/generators/lib/templates/field-report/helpers.js +138 -0
- package/dist/manager/libraries/email/generators/lib/templates/field-report/index.js +494 -0
- package/dist/manager/libraries/email/generators/lib/templates/index.js +47 -0
- package/dist/manager/libraries/email/generators/lib/templates/newsletter-shared.js +610 -0
- package/dist/manager/libraries/email/generators/lib/templates/order.js +461 -0
- package/dist/manager/libraries/email/generators/lib/templates/plain.js +83 -0
- package/dist/manager/libraries/email/generators/lib/templates/shared-campaign.js +64 -0
- package/dist/manager/libraries/email/generators/newsletter.js +899 -0
- package/dist/manager/libraries/email/index.js +146 -0
- package/dist/manager/libraries/email/marketing/index.js +754 -0
- package/dist/manager/libraries/email/prepare.js +365 -0
- package/dist/manager/libraries/email/providers/beehiiv.js +589 -0
- package/dist/manager/libraries/email/providers/sendgrid.js +845 -0
- package/dist/manager/libraries/email/transactional/index.js +510 -0
- package/dist/manager/libraries/email/utm.js +86 -0
- package/dist/manager/libraries/email/validation-provider-neverbounce.js +70 -0
- package/dist/manager/libraries/email/validation-provider-zerobounce.js +42 -0
- package/dist/manager/libraries/email/validation.js +255 -0
- package/dist/manager/libraries/env.js +198 -0
- package/dist/manager/libraries/infer-contact.js +101 -0
- package/dist/manager/libraries/load-provider.js +65 -0
- package/dist/manager/libraries/notification.js +256 -0
- package/dist/manager/libraries/openai.js +13 -0
- package/dist/manager/libraries/payment/discount-codes.js +204 -0
- package/dist/manager/libraries/payment/fetch-failure.js +56 -0
- package/dist/manager/libraries/payment/license.js +44 -0
- package/dist/manager/libraries/payment/order-id.js +57 -0
- package/dist/manager/libraries/payment/provider-errors.js +71 -0
- package/dist/manager/libraries/payment/providers/chargebee.js +817 -0
- package/dist/manager/libraries/payment/providers/coinbase.js +330 -0
- package/dist/manager/libraries/payment/providers/paypal.js +987 -0
- package/dist/manager/libraries/payment/providers/stripe.js +755 -0
- package/dist/manager/libraries/payment/providers/test.js +250 -0
- package/dist/manager/libraries/payment/refund-linkage.js +78 -0
- package/dist/manager/libraries/payment/refund-policy.js +113 -0
- package/dist/manager/libraries/payment/winback.js +55 -0
- package/dist/manager/libraries/prompts/infer-contact.md +78 -0
- package/dist/manager/libraries/rate-limits.js +17 -0
- package/dist/manager/libraries/recaptcha.js +58 -0
- package/dist/manager/libraries/user-doc.js +234 -0
- package/dist/manager/routes/admin/backup/post.js +106 -0
- package/dist/manager/routes/admin/cron/post.js +37 -0
- package/dist/manager/routes/admin/database/get.js +35 -0
- package/dist/manager/routes/admin/database/post.js +34 -0
- package/dist/manager/routes/admin/email/post.js +74 -0
- package/dist/manager/routes/admin/firestore/get.js +35 -0
- package/dist/manager/routes/admin/firestore/post.js +56 -0
- package/dist/manager/routes/admin/firestore/query/post.js +128 -0
- package/dist/manager/routes/admin/hook/post.js +104 -0
- package/dist/manager/routes/admin/infer-contact/post.js +35 -0
- package/dist/manager/routes/admin/notification/post.js +37 -0
- package/dist/manager/routes/admin/payment/post.js +56 -0
- package/dist/manager/routes/admin/post/deduplicate-image-alts.js +52 -0
- package/dist/manager/routes/admin/post/dispatch-deploy.js +36 -0
- package/dist/manager/routes/admin/post/post.js +487 -0
- package/dist/manager/routes/admin/post/put.js +146 -0
- package/dist/manager/routes/admin/post/templates/post.html +14 -0
- package/dist/manager/routes/admin/repo/content/post.js +106 -0
- package/dist/manager/routes/admin/stats/get.js +222 -0
- package/dist/manager/routes/admin/users/disable/post.js +50 -0
- package/dist/manager/routes/admin/users/list/get.js +110 -0
- package/dist/manager/routes/admin/users/sync/post.js +119 -0
- package/dist/manager/routes/brand/get.js +26 -0
- package/dist/manager/routes/content/post/get.js +110 -0
- package/dist/manager/routes/general/email/post.js +89 -0
- package/dist/manager/routes/general/email/templates/general/download-app-link.js +42 -0
- package/dist/manager/routes/general/uuid/post.js +31 -0
- package/dist/manager/routes/handler/post/post.js +142 -0
- package/dist/manager/routes/health/get.js +43 -0
- package/dist/manager/routes/index.js +11 -0
- package/dist/manager/routes/marketing/campaign/delete.js +45 -0
- package/dist/manager/routes/marketing/campaign/get.js +69 -0
- package/dist/manager/routes/marketing/campaign/post.js +124 -0
- package/dist/manager/routes/marketing/campaign/put.js +86 -0
- package/dist/manager/routes/marketing/campaign/utils.js +59 -0
- package/dist/manager/routes/marketing/contact/delete.js +100 -0
- package/dist/manager/routes/marketing/contact/post.js +149 -0
- package/dist/manager/routes/marketing/contact/put.js +37 -0
- package/dist/manager/routes/marketing/email-preferences/post.js +275 -0
- package/dist/manager/routes/marketing/webhook/forward/post.js +170 -0
- package/dist/manager/routes/marketing/webhook/post.js +123 -0
- package/dist/manager/routes/marketing/webhook/providers/beehiiv.js +197 -0
- package/dist/manager/routes/marketing/webhook/providers/sendgrid.js +194 -0
- package/dist/manager/routes/payments/cancel/_is-trialing.js +40 -0
- package/dist/manager/routes/payments/cancel/post.js +149 -0
- package/dist/manager/routes/payments/cancel/providers/chargebee.js +41 -0
- package/dist/manager/routes/payments/cancel/providers/paypal.js +56 -0
- package/dist/manager/routes/payments/cancel/providers/stripe.js +33 -0
- package/dist/manager/routes/payments/cancel/providers/test.js +118 -0
- package/dist/manager/routes/payments/discount/get.js +25 -0
- package/dist/manager/routes/payments/dispute-alert/post.js +117 -0
- package/dist/manager/routes/payments/dispute-alert/providers/chargeblast.js +55 -0
- package/dist/manager/routes/payments/intent/post.js +341 -0
- package/dist/manager/routes/payments/intent/providers/chargebee.js +197 -0
- package/dist/manager/routes/payments/intent/providers/coinbase.js +94 -0
- package/dist/manager/routes/payments/intent/providers/paypal.js +244 -0
- package/dist/manager/routes/payments/intent/providers/stripe.js +141 -0
- package/dist/manager/routes/payments/intent/providers/test.js +341 -0
- package/dist/manager/routes/payments/plan/post.js +147 -0
- package/dist/manager/routes/payments/plan/providers/chargebee.js +62 -0
- package/dist/manager/routes/payments/plan/providers/paypal.js +62 -0
- package/dist/manager/routes/payments/plan/providers/stripe.js +59 -0
- package/dist/manager/routes/payments/plan/providers/test.js +113 -0
- package/dist/manager/routes/payments/portal/post.js +113 -0
- package/dist/manager/routes/payments/portal/providers/chargebee.js +62 -0
- package/dist/manager/routes/payments/portal/providers/paypal.js +25 -0
- package/dist/manager/routes/payments/portal/providers/stripe.js +62 -0
- package/dist/manager/routes/payments/portal/providers/test.js +18 -0
- package/dist/manager/routes/payments/refund/post.js +207 -0
- package/dist/manager/routes/payments/refund/providers/chargebee.js +147 -0
- package/dist/manager/routes/payments/refund/providers/coinbase.js +40 -0
- package/dist/manager/routes/payments/refund/providers/paypal.js +239 -0
- package/dist/manager/routes/payments/refund/providers/stripe.js +160 -0
- package/dist/manager/routes/payments/refund/providers/test.js +204 -0
- package/dist/manager/routes/payments/trial-eligibility/get.js +29 -0
- package/dist/manager/routes/payments/uncancel/post.js +106 -0
- package/dist/manager/routes/payments/uncancel/providers/chargebee.js +30 -0
- package/dist/manager/routes/payments/uncancel/providers/paypal.js +25 -0
- package/dist/manager/routes/payments/uncancel/providers/stripe.js +27 -0
- package/dist/manager/routes/payments/uncancel/providers/test.js +114 -0
- package/dist/manager/routes/payments/webhook/post.js +157 -0
- package/dist/manager/routes/payments/webhook/providers/chargebee.js +218 -0
- package/dist/manager/routes/payments/webhook/providers/coinbase.js +91 -0
- package/dist/manager/routes/payments/webhook/providers/paypal.js +214 -0
- package/dist/manager/routes/payments/webhook/providers/stripe.js +182 -0
- package/dist/manager/routes/payments/webhook/providers/test.js +18 -0
- package/dist/manager/routes/payments/winback/post.js +276 -0
- package/dist/manager/routes/payments/winback/providers/chargebee.js +24 -0
- package/dist/manager/routes/payments/winback/providers/paypal.js +22 -0
- package/dist/manager/routes/payments/winback/providers/stripe.js +41 -0
- package/dist/manager/routes/payments/winback/providers/test.js +42 -0
- package/dist/manager/routes/restart/index.js +37 -0
- package/dist/manager/routes/special/electron-client/post.js +59 -0
- package/dist/manager/routes/test/authenticate/get.js +11 -0
- package/dist/manager/routes/test/health/get.js +8 -0
- package/dist/manager/routes/test/index.js +16 -0
- package/dist/manager/routes/test/lab/post.js +17 -0
- package/dist/manager/routes/test/redirect/get.js +27 -0
- package/dist/manager/routes/test/reset-account/post.js +79 -0
- package/dist/manager/routes/test/roster/get.js +40 -0
- package/dist/manager/routes/test/schema/post.js +20 -0
- package/dist/manager/routes/test/usage/post.js +44 -0
- package/dist/manager/routes/test/webhook/post.js +21 -0
- package/dist/manager/routes/user/api-keys/post.js +51 -0
- package/dist/manager/routes/user/connections/_context.js +194 -0
- package/dist/manager/routes/user/connections/_grant.js +247 -0
- package/dist/manager/routes/user/connections/_lease.js +184 -0
- package/dist/manager/routes/user/connections/_providers.js +191 -0
- package/dist/manager/routes/user/connections/_state.js +117 -0
- package/dist/manager/routes/user/connections/delete.js +55 -0
- package/dist/manager/routes/user/connections/get.js +167 -0
- package/dist/manager/routes/user/connections/post.js +370 -0
- package/dist/manager/routes/user/connections/providers/discord.js +41 -0
- package/dist/manager/routes/user/connections/providers/google.js +55 -0
- package/dist/manager/routes/user/connections/providers/kick.js +68 -0
- package/dist/manager/routes/user/connections/providers/spotify.js +40 -0
- package/dist/manager/routes/user/connections/providers/twitch.js +52 -0
- package/dist/manager/routes/user/data-request/delete.js +84 -0
- package/dist/manager/routes/user/data-request/get.js +229 -0
- package/dist/manager/routes/user/data-request/post.js +136 -0
- package/dist/manager/routes/user/delete.js +149 -0
- package/dist/manager/routes/user/feedback/post.js +81 -0
- package/dist/manager/routes/user/get.js +14 -0
- package/dist/manager/routes/user/orders/get.js +95 -0
- package/dist/manager/routes/user/sessions/delete.js +95 -0
- package/dist/manager/routes/user/sessions/get.js +40 -0
- package/dist/manager/routes/user/settings/validate/post.js +86 -0
- package/dist/manager/routes/user/signup/post.js +662 -0
- package/dist/manager/routes/user/subscription/get.js +77 -0
- package/dist/manager/routes/user/token/post.js +30 -0
- package/dist/manager/routes/verts/delete.js +43 -0
- package/dist/manager/routes/verts/get.js +49 -0
- package/dist/manager/routes/verts/post.js +61 -0
- package/dist/manager/routes/verts/put.js +75 -0
- package/dist/manager/routes/verts/redirect/get.js +36 -0
- package/dist/manager/routes/verts/serve/get.js +67 -0
- package/dist/manager/routes/verts/utils.js +436 -0
- package/dist/manager/schemas/admin/backup/post.js +8 -0
- package/dist/manager/schemas/admin/cron/post.js +8 -0
- package/dist/manager/schemas/admin/database/get.js +5 -0
- package/dist/manager/schemas/admin/database/post.js +6 -0
- package/dist/manager/schemas/admin/email/post.js +31 -0
- package/dist/manager/schemas/admin/firestore/get.js +5 -0
- package/dist/manager/schemas/admin/firestore/post.js +8 -0
- package/dist/manager/schemas/admin/firestore/query/post.js +5 -0
- package/dist/manager/schemas/admin/hook/post.js +8 -0
- package/dist/manager/schemas/admin/infer-contact/post.js +6 -0
- package/dist/manager/schemas/admin/notification/post.js +23 -0
- package/dist/manager/schemas/admin/payment/post.js +8 -0
- package/dist/manager/schemas/admin/post/post.js +23 -0
- package/dist/manager/schemas/admin/post/put.js +13 -0
- package/dist/manager/schemas/admin/repo/content/post.js +10 -0
- package/dist/manager/schemas/admin/stats/get.js +5 -0
- package/dist/manager/schemas/admin/users/disable/post.js +9 -0
- package/dist/manager/schemas/admin/users/list/get.js +10 -0
- package/dist/manager/schemas/admin/users/sync/post.js +7 -0
- package/dist/manager/schemas/brand/get.js +3 -0
- package/dist/manager/schemas/content/post/get.js +8 -0
- package/dist/manager/schemas/general/email/post.js +10 -0
- package/dist/manager/schemas/general/uuid/post.js +9 -0
- package/dist/manager/schemas/handler/post/post.js +13 -0
- package/dist/manager/schemas/health/get.js +3 -0
- package/dist/manager/schemas/marketing/campaign/delete.js +8 -0
- package/dist/manager/schemas/marketing/campaign/get.js +13 -0
- package/dist/manager/schemas/marketing/campaign/post.js +43 -0
- package/dist/manager/schemas/marketing/campaign/put.js +37 -0
- package/dist/manager/schemas/marketing/contact/delete.js +8 -0
- package/dist/manager/schemas/marketing/contact/post.js +14 -0
- package/dist/manager/schemas/marketing/contact/put.js +8 -0
- package/dist/manager/schemas/marketing/email-preferences/post.js +15 -0
- package/dist/manager/schemas/marketing/webhook/forward/post.js +8 -0
- package/dist/manager/schemas/marketing/webhook/post.js +7 -0
- package/dist/manager/schemas/payments/cancel/post.js +15 -0
- package/dist/manager/schemas/payments/discount/get.js +8 -0
- package/dist/manager/schemas/payments/dispute-alert/post.js +6 -0
- package/dist/manager/schemas/payments/intent/post.js +28 -0
- package/dist/manager/schemas/payments/plan/post.js +11 -0
- package/dist/manager/schemas/payments/portal/post.js +12 -0
- package/dist/manager/schemas/payments/refund/post.js +14 -0
- package/dist/manager/schemas/payments/trial-eligibility/get.js +7 -0
- package/dist/manager/schemas/payments/uncancel/post.js +9 -0
- package/dist/manager/schemas/payments/webhook/post.js +6 -0
- package/dist/manager/schemas/payments/winback/post.js +12 -0
- package/dist/manager/schemas/restart/index.js +5 -0
- package/dist/manager/schemas/special/electron-client/post.js +11 -0
- package/dist/manager/schemas/test/authenticate/get.js +3 -0
- package/dist/manager/schemas/test/health/get.js +3 -0
- package/dist/manager/schemas/test/index.js +5 -0
- package/dist/manager/schemas/test/lab/post.js +3 -0
- package/dist/manager/schemas/test/redirect/get.js +7 -0
- package/dist/manager/schemas/test/reset-account/post.js +3 -0
- package/dist/manager/schemas/test/roster/get.js +3 -0
- package/dist/manager/schemas/test/schema/post.js +70 -0
- package/dist/manager/schemas/test/usage/post.js +8 -0
- package/dist/manager/schemas/test/webhook/post.js +7 -0
- package/dist/manager/schemas/user/api-keys/post.js +6 -0
- package/dist/manager/schemas/user/connections/delete.js +8 -0
- package/dist/manager/schemas/user/connections/get.js +21 -0
- package/dist/manager/schemas/user/connections/post.js +12 -0
- package/dist/manager/schemas/user/data-request/delete.js +3 -0
- package/dist/manager/schemas/user/data-request/get.js +5 -0
- package/dist/manager/schemas/user/data-request/post.js +6 -0
- package/dist/manager/schemas/user/delete.js +6 -0
- package/dist/manager/schemas/user/feedback/post.js +8 -0
- package/dist/manager/schemas/user/get.js +3 -0
- package/dist/manager/schemas/user/orders/get.js +6 -0
- package/dist/manager/schemas/user/sessions/delete.js +6 -0
- package/dist/manager/schemas/user/sessions/get.js +6 -0
- package/dist/manager/schemas/user/settings/validate/post.js +8 -0
- package/dist/manager/schemas/user/signup/post.js +27 -0
- package/dist/manager/schemas/user/subscription/get.js +5 -0
- package/dist/manager/schemas/user/token/post.js +5 -0
- package/dist/manager/schemas/verts/delete.js +8 -0
- package/dist/manager/schemas/verts/get.js +9 -0
- package/dist/manager/schemas/verts/post.js +23 -0
- package/dist/manager/schemas/verts/put.js +20 -0
- package/dist/manager/schemas/verts/redirect/get.js +10 -0
- package/dist/manager/schemas/verts/serve/get.js +14 -0
- package/dist/manager/server-manager.js +69 -0
- package/dist/mcp/client.js +98 -0
- package/dist/mcp/handler.js +521 -0
- package/dist/mcp/index.js +157 -0
- package/dist/mcp/tools.js +555 -0
- package/dist/mcp/utils.js +108 -0
- package/dist/omega-bin.js +6 -0
- package/dist/require.js +3 -0
- package/dist/test/fixtures/firebase-project/.firebaserc +5 -0
- package/dist/test/fixtures/firebase-project/config/omega.json5 +131 -0
- package/dist/test/fixtures/firebase-project/database.rules.json +6 -0
- package/dist/test/fixtures/firebase-project/firebase.json +49 -0
- package/dist/test/fixtures/firebase-project/firestore.indexes.json +4 -0
- package/dist/test/fixtures/firebase-project/package.json +14 -0
- package/dist/test/fixtures/firebase-project/src/index.js +11 -0
- package/dist/test/fixtures/firebase-project/storage.rules +8 -0
- package/dist/test/parse-audit.js +25 -0
- package/dist/test/run-tests.js +90 -0
- package/dist/test/runner.js +982 -0
- package/dist/test/seed.js +242 -0
- package/dist/test/test-accounts.js +2660 -0
- package/dist/test/utils/assertions.js +189 -0
- package/dist/test/utils/email-capture.js +252 -0
- package/dist/test/utils/extended-mode-warning.js +11 -0
- package/dist/test/utils/firestore-rules-client.js +277 -0
- package/dist/test/utils/http-client.js +228 -0
- package/dist/test/utils/test-mode-file.js +192 -0
- package/dist/utils/merge-line-files.js +6 -0
- package/dist/utils/scaffold-defaults.js +71 -0
- package/dist/utils/test-lanes.js +62 -0
- package/dist/vendor/account/engine.js +182 -0
- package/dist/vendor/account/features.js +220 -0
- package/dist/vendor/account/index.js +53 -0
- package/dist/vendor/account/schema.js +272 -0
- package/dist/vendor/account/subscription.js +38 -0
- package/dist/vendor/analytics/adapters/ga4.js +26 -0
- package/dist/vendor/analytics/adapters/meta.js +26 -0
- package/dist/vendor/analytics/adapters/resolve.js +130 -0
- package/dist/vendor/analytics/adapters/tiktok.js +27 -0
- package/dist/vendor/analytics/catalog.js +908 -0
- package/dist/vendor/analytics/identity.js +136 -0
- package/dist/vendor/config/company.js +31 -0
- package/dist/vendor/config/defaults.js +173 -0
- package/dist/vendor/config/demo.js +18 -0
- package/dist/vendor/config/desktop-artifacts.js +110 -0
- package/dist/vendor/config/edit.js +769 -0
- package/dist/vendor/config/env-delivery.js +145 -0
- package/dist/vendor/config/env-rules.js +93 -0
- package/dist/vendor/config/env-schema.js +1078 -0
- package/dist/vendor/config/env.js +445 -0
- package/dist/vendor/config/hooks.js +97 -0
- package/dist/vendor/config/index.js +237 -0
- package/dist/vendor/config/instances.js +208 -0
- package/dist/vendor/config/load.js +490 -0
- package/dist/vendor/config/merge.js +68 -0
- package/dist/vendor/config/order.js +139 -0
- package/dist/vendor/config/ports.js +374 -0
- package/dist/vendor/config/providers.js +32 -0
- package/dist/vendor/config/repo.js +142 -0
- package/dist/vendor/config/retired-keys.js +430 -0
- package/dist/vendor/config/schema.js +1610 -0
- package/dist/vendor/config/secrets.js +50 -0
- package/dist/vendor/config/seed.js +34 -0
- package/dist/vendor/config/site-global.js +205 -0
- package/dist/vendor/config/validate.js +554 -0
- package/dist/vendor/config/winback.js +61 -0
- package/dist/vendor/devkit/attach-log-file.js +262 -0
- package/dist/vendor/devkit/bare-requires.js +153 -0
- package/dist/vendor/devkit/command-path.js +46 -0
- package/dist/vendor/devkit/defaults-engine.js +372 -0
- package/dist/vendor/devkit/deploy-record.js +180 -0
- package/dist/vendor/devkit/deploy.js +286 -0
- package/dist/vendor/devkit/license.js +155 -0
- package/dist/vendor/devkit/local-https.js +360 -0
- package/dist/vendor/devkit/local.js +1905 -0
- package/dist/vendor/devkit/logger.js +128 -0
- package/dist/vendor/devkit/merge-line-files.js +296 -0
- package/dist/vendor/devkit/npm-registry.js +52 -0
- package/dist/vendor/devkit/omega-bin.js +345 -0
- package/dist/vendor/devkit/parse-audit.js +74 -0
- package/dist/vendor/devkit/safe-install.js +18 -0
- package/dist/vendor/devkit/scaffold-guard.js +96 -0
- package/dist/vendor/devkit/stop-signals.js +28 -0
- package/dist/vendor/devkit/test/assert.js +120 -0
- package/dist/vendor/devkit/test/define-cases.js +104 -0
- package/dist/vendor/devkit/test/runner-core.js +554 -0
- package/dist/vendor/devkit/test/scope.js +162 -0
- package/dist/vendor/devkit/update.js +569 -0
- package/dist/vendor/monitoring/core.js +180 -0
- package/dist/vendor/monitoring/env.js +49 -0
- package/dist/vendor/monitoring/logger.js +39 -0
- package/dist/vendor/monitoring/node.js +72 -0
- package/docs/admin-post-route.md +57 -0
- package/docs/ai-library.md +171 -0
- package/docs/architecture.md +62 -0
- package/docs/audit.md +70 -0
- package/docs/auth-hooks.md +74 -0
- package/docs/build-system.md +65 -0
- package/docs/cdp-debugging.md +29 -0
- package/docs/cli-firestore-auth.md +85 -0
- package/docs/cli-logs.md +67 -0
- package/docs/cli-output.md +146 -0
- package/docs/code-patterns.md +77 -0
- package/docs/common-mistakes.md +12 -0
- package/docs/common-operations.md +60 -0
- package/docs/connections.md +212 -0
- package/docs/consent.md +362 -0
- package/docs/directory-structure.md +150 -0
- package/docs/email-system.md +459 -0
- package/docs/environment-detection.md +93 -0
- package/docs/file-naming.md +10 -0
- package/docs/firestore.md +134 -0
- package/docs/ghostii.md +240 -0
- package/docs/index.md +299 -0
- package/docs/key-files.md +37 -0
- package/docs/logging.md +60 -0
- package/docs/marketing-campaigns.md +407 -0
- package/docs/marketing-fields.md +25 -0
- package/docs/mcp.md +222 -0
- package/docs/migration.md +129 -0
- package/docs/payment-system.md +856 -0
- package/docs/paypal-sandbox-qa.md +105 -0
- package/docs/response-headers.md +9 -0
- package/docs/routes.md +211 -0
- package/docs/sanitization.md +71 -0
- package/docs/schemas.md +163 -0
- package/docs/shared/agent-docs.md +89 -0
- package/docs/shared/analytics.md +612 -0
- package/docs/shared/brands.md +51 -0
- package/docs/shared/breaking-changes.md +497 -0
- package/docs/shared/config.md +1387 -0
- package/docs/shared/deploys.md +215 -0
- package/docs/shared/icons.md +201 -0
- package/docs/shared/local-dev.md +147 -0
- package/docs/shared/logging.md +202 -0
- package/docs/shared/monitoring.md +153 -0
- package/docs/shared/publishing.md +183 -0
- package/docs/shared/rulings.md +34 -0
- package/docs/shared/testing.md +147 -0
- package/docs/shared/theming.md +604 -0
- package/docs/shared/translation.md +291 -0
- package/docs/shared/updates.md +61 -0
- package/docs/stripe-webhook-forwarding.md +20 -0
- package/docs/test-boot-layer.md +67 -0
- package/docs/test-framework.md +583 -0
- package/docs/usage-rate-limiting.md +121 -0
- package/docs/verts.md +29 -0
- package/package.json +143 -0
- package/templates/config/omega.json5 +322 -0
- package/templates/database.rules.json +82 -0
- package/templates/firebase.json +67 -0
- package/templates/firestore.framework.rules +191 -0
- package/templates/firestore.indexes.json +4 -0
- package/templates/firestore.rules +59 -0
- package/templates/index.js +12 -0
- package/templates/public/404.html +26 -0
- package/templates/public/index.html +24 -0
- package/templates/remoteconfig.template.json +1 -0
- package/templates/storage-lifecycle-config-1-day.json +9 -0
- package/templates/storage-lifecycle-config-30-days.json +9 -0
- package/templates/storage.rules +11 -0
- package/test/_init/accounts-validation.js +58 -0
- package/test/ai/tools-live.test.js +171 -0
- package/test/analytics/conversion-delivery.test.js +1013 -0
- package/test/analytics/match-normalization.test.js +238 -0
- package/test/analytics/signup-conversion.test.js +135 -0
- package/test/boot/cli-dispatch.test.js +142 -0
- package/test/boot/defaults-scaffold.test.js +248 -0
- package/test/boot/deploy-staging.test.js +446 -0
- package/test/boot/disposable-domains.test.js +154 -0
- package/test/boot/emulator-boots.test.js +39 -0
- package/test/boot/emulator-port-preflight.test.js +126 -0
- package/test/boot/emulator-ready-timeout.test.js +46 -0
- package/test/boot/project-id-refresh.test.js +73 -0
- package/test/boot/suite-portability.test.js +278 -0
- package/test/boot/update-command.test.js +63 -0
- package/test/cli/cross-platform.test.js +168 -0
- package/test/cli/custom-project-type.test.js +426 -0
- package/test/cli/deploy-license-env.test.js +100 -0
- package/test/cli/emulator-adoption.test.js +254 -0
- package/test/cli/emulator-orphans.test.js +658 -0
- package/test/cli/emulator-ownership.test.js +177 -0
- package/test/cli/emulator-port-retry.test.js +395 -0
- package/test/cli/emulator-shutdown.test.js +788 -0
- package/test/cli/emulator-stale-reap.test.js +574 -0
- package/test/cli/ensure-target.test.js +325 -0
- package/test/cli/flags.test.js +64 -0
- package/test/cli/https-trust.test.js +38 -0
- package/test/cli/install-update-dispatch.test.js +47 -0
- package/test/cli/lane-environments.test.js +159 -0
- package/test/cli/marketing-campaigns-seeded.test.js +194 -0
- package/test/cli/migrate-bare-requires.test.js +128 -0
- package/test/cli/migrate-markers.test.js +530 -0
- package/test/cli/required-indexes.test.js +64 -0
- package/test/cli/rules-compile.test.js +622 -0
- package/test/cli/rules-migration-deferral.test.js +259 -0
- package/test/cli/rules-version.test.js +104 -0
- package/test/cli/setup-load-files.test.js +66 -0
- package/test/cli/setup-offline-mode.test.js +276 -0
- package/test/cli/setup-retired.test.js +64 -0
- package/test/cli/setup-shared-project-indexes.test.js +262 -0
- package/test/cli/setup-tests-requires.test.js +45 -0
- package/test/cli/stage-env-compose.test.js +316 -0
- package/test/cli/stripe-live-lane.test.js +303 -0
- package/test/cli/target.test.js +182 -0
- package/test/cli/templates.test.js +66 -0
- package/test/cli/test-runner-env.test.js +115 -0
- package/test/cli/test-stack-shutdown.test.js +275 -0
- package/test/cli/test-target-no-match.test.js +128 -0
- package/test/cli/verb-logs.test.js +118 -0
- package/test/cli/version-dispatch.test.js +39 -0
- package/test/content/blog-generate.test.js +164 -0
- package/test/email/campaign-config-fault.test.js +376 -0
- package/test/email/campaign-cron-pipeline.test.js +536 -0
- package/test/email/campaign-send.test.js +47 -0
- package/test/email/consent-lifecycle.test.js +258 -0
- package/test/email/content-html-policy.test.js +270 -0
- package/test/email/feedback-and-plain-send.test.js +54 -0
- package/test/email/fixtures/clean.json +30 -0
- package/test/email/fixtures/editorial.json +30 -0
- package/test/email/fixtures/field-report.json +53 -0
- package/test/email/identity.test.js +321 -0
- package/test/email/marketing/consent-gate.test.js +265 -0
- package/test/email/marketing/custom-fields-catalog.test.js +259 -0
- package/test/email/marketing/prune-per-provider.test.js +762 -0
- package/test/email/marketing/remove-log-privacy.test.js +225 -0
- package/test/email/marketing-lifecycle.test.js +139 -0
- package/test/email/newsletter-generate.test.js +853 -0
- package/test/email/newsletter-svg-tokens.test.js +51 -0
- package/test/email/newsletter-templates.test.js +492 -0
- package/test/email/order-one-time-cta.test.js +97 -0
- package/test/email/render-content.test.js +248 -0
- package/test/email/safe-url.test.js +0 -0
- package/test/email/sanitize-images.test.js +66 -0
- package/test/email/send-log-privacy.test.js +107 -0
- package/test/email/templates.test.js +288 -0
- package/test/email/testing-capture.test.js +260 -0
- package/test/email/transactional-send.test.js +36 -0
- package/test/email/transactional.test.js +562 -0
- package/test/email/unsubscribe-groups.test.js +155 -0
- package/test/email/unsubscribe-key.test.js +91 -0
- package/test/email/validation-cases.test.js +152 -0
- package/test/email/validation.test.js +746 -0
- package/test/events/auth-delete-conversion.test.js +138 -0
- package/test/events/auth-delete-race.test.js +211 -0
- package/test/events/auth-on-create-log.test.js +164 -0
- package/test/events/auth-signup-conversion.test.js +141 -0
- package/test/events/auth-signup-limit.test.js +142 -0
- package/test/events/auth-trigger-log-privacy.test.js +256 -0
- package/test/events/cron-job-doc-shape.test.js +101 -0
- package/test/events/cron-reset-usage.test.js +116 -0
- package/test/events/notification-conversion.test.js +182 -0
- package/test/events/payments/_webhook-harness.js +307 -0
- package/test/events/payments/abandoned-cart-activity.test.js +135 -0
- package/test/events/payments/analytics-payment-events.test.js +945 -0
- package/test/events/payments/dispute-email-status.test.js +55 -0
- package/test/events/payments/journey-payments-abandoned.test.js +126 -0
- package/test/events/payments/journey-payments-cancel-endpoint.test.js +102 -0
- package/test/events/payments/journey-payments-cancel-no-order.test.js +76 -0
- package/test/events/payments/journey-payments-cancel.test.js +184 -0
- package/test/events/payments/journey-payments-decline.test.js +225 -0
- package/test/events/payments/journey-payments-discount.test.js +91 -0
- package/test/events/payments/journey-payments-dispute.test.js +190 -0
- package/test/events/payments/journey-payments-failure.test.js +148 -0
- package/test/events/payments/journey-payments-legacy-product.test.js +151 -0
- package/test/events/payments/journey-payments-one-time-decline.test.js +127 -0
- package/test/events/payments/journey-payments-one-time-failure.test.js +113 -0
- package/test/events/payments/journey-payments-one-time-refund.test.js +186 -0
- package/test/events/payments/journey-payments-one-time.test.js +175 -0
- package/test/events/payments/journey-payments-plan-change.test.js +146 -0
- package/test/events/payments/journey-payments-plan-switch-trial.test.js +161 -0
- package/test/events/payments/journey-payments-plan-switch.test.js +182 -0
- package/test/events/payments/journey-payments-refund-no-order.test.js +76 -0
- package/test/events/payments/journey-payments-refund-webhook.test.js +225 -0
- package/test/events/payments/journey-payments-suspend.test.js +183 -0
- package/test/events/payments/journey-payments-trial-cancel.test.js +132 -0
- package/test/events/payments/journey-payments-trial.test.js +173 -0
- package/test/events/payments/journey-payments-uid-resolution.test.js +134 -0
- package/test/events/payments/journey-payments-uncancel.test.js +157 -0
- package/test/events/payments/journey-payments-upgrade.test.js +137 -0
- package/test/events/payments/journey-payments-winback-decline.test.js +171 -0
- package/test/events/payments/journey-payments-winback.test.js +195 -0
- package/test/events/payments/paypal-expiry-cron.test.js +166 -0
- package/test/events/payments/purchase-failed-handler.test.js +80 -0
- package/test/events/payments/purchase-refunded-handler.test.js +108 -0
- package/test/events/payments/test-processor-doc-shape.test.js +162 -0
- package/test/events/payments/transition-order-emails.test.js +185 -0
- package/test/events/payments/transition-promo-lines.test.js +192 -0
- package/test/events/payments/transitions-detect.test.js +767 -0
- package/test/events/payments/trial-lapse-sweep-staleness.test.js +173 -0
- package/test/events/payments/trial-lapse-sweep.test.js +359 -0
- package/test/events/payments/webhook-atomic-writes.test.js +69 -0
- package/test/events/payments/webhook-chargebee-unreachable.test.js +106 -0
- package/test/events/payments/webhook-discount-clear.test.js +189 -0
- package/test/events/payments/webhook-failure-intent.test.js +93 -0
- package/test/events/payments/webhook-hosted-page-uid-trust.test.js +212 -0
- package/test/events/payments/webhook-ordering.test.js +326 -0
- package/test/events/payments/webhook-provider-lookup-trust.test.js +206 -0
- package/test/events/payments/webhook-refund-amount-trust.test.js +223 -0
- package/test/events/payments/webhook-refund-envelope-shape.test.js +148 -0
- package/test/events/payments/webhook-refund-linkage-trust.test.js +404 -0
- package/test/events/payments/webhook-refund-transaction-fallback.test.js +184 -0
- package/test/events/payments/webhook-refund-without-order.test.js +170 -0
- package/test/events/payments/webhook-refusal-reporting.test.js +194 -0
- package/test/events/payments/webhook-refused-intent.test.js +142 -0
- package/test/events/payments/webhook-retry-sweep.test.js +252 -0
- package/test/events/payments/webhook-transition-claim.test.js +389 -0
- package/test/events/payments/webhook-uid-trust.test.js +203 -0
- package/test/events/payments/webhook-user-without-auth.test.js +161 -0
- package/test/fixtures/chargebee/invoice-one-time.json +27 -0
- package/test/fixtures/chargebee/subscription-active.json +44 -0
- package/test/fixtures/chargebee/subscription-cancelled.json +42 -0
- package/test/fixtures/chargebee/subscription-in-trial.json +41 -0
- package/test/fixtures/chargebee/subscription-legacy-plan.json +41 -0
- package/test/fixtures/chargebee/subscription-non-renewing.json +41 -0
- package/test/fixtures/chargebee/subscription-paused.json +42 -0
- package/test/fixtures/chargebee/webhook-payment-failed.json +51 -0
- package/test/fixtures/chargebee/webhook-subscription-created.json +47 -0
- package/test/fixtures/coinbase/charge-confirmed.json +50 -0
- package/test/fixtures/coinbase/charge-failed.json +34 -0
- package/test/fixtures/coinbase/charge-pending.json +34 -0
- package/test/fixtures/migrate/ported-route.js.txt +11 -0
- package/test/fixtures/paypal/capture-completed.json +51 -0
- package/test/fixtures/paypal/capture-refunded.json +43 -0
- package/test/fixtures/paypal/order-approved.json +62 -0
- package/test/fixtures/paypal/order-completed.json +110 -0
- package/test/fixtures/paypal/sale-refunded.json +38 -0
- package/test/fixtures/paypal/subscription-active.json +76 -0
- package/test/fixtures/paypal/subscription-cancelled.json +50 -0
- package/test/fixtures/paypal/subscription-suspended.json +65 -0
- package/test/fixtures/stripe/checkout-session-completed.json +130 -0
- package/test/fixtures/stripe/invoice-payment-failed.json +148 -0
- package/test/fixtures/stripe/invoice-subscription-payment-failed.json +28 -0
- package/test/fixtures/stripe/invoice-subscription-payment-succeeded.json +28 -0
- package/test/fixtures/stripe/subscription-active.json +161 -0
- package/test/fixtures/stripe/subscription-canceled.json +161 -0
- package/test/fixtures/stripe/subscription-trialing.json +161 -0
- package/test/helpers/_shared-config.js +15 -0
- package/test/helpers/ai-request-payload.test.js +619 -0
- package/test/helpers/ai-schema-resolve.test.js +125 -0
- package/test/helpers/ai-test-provider.test.js +227 -0
- package/test/helpers/ai-token-accounting.test.js +150 -0
- package/test/helpers/ai-tools-format.test.js +384 -0
- package/test/helpers/analytics-no-id-notice.test.js +146 -0
- package/test/helpers/analytics-user-data.test.js +194 -0
- package/test/helpers/api-manager.test.js +311 -0
- package/test/helpers/backend-router.test.js +136 -0
- package/test/helpers/content/blog-auto-publisher.test.js +482 -0
- package/test/helpers/content/feed-parser.test.js +529 -0
- package/test/helpers/content/ghostii-blocks.test.js +135 -0
- package/test/helpers/content/ghostii-feed-integration.test.js +405 -0
- package/test/helpers/content/ghostii-write-article.test.js +244 -0
- package/test/helpers/dev-only-routes.test.js +225 -0
- package/test/helpers/env-reader.test.js +362 -0
- package/test/helpers/environment.test.js +263 -0
- package/test/helpers/event-middleware.test.js +407 -0
- package/test/helpers/infer-contact.test.js +157 -0
- package/test/helpers/lane-url.test.js +90 -0
- package/test/helpers/merge-line-files.test.js +272 -0
- package/test/helpers/metadata.test.js +122 -0
- package/test/helpers/middleware-request-log.test.js +134 -0
- package/test/helpers/middleware-user-log.test.js +124 -0
- package/test/helpers/payment/chargebee/parse-webhook.test.js +490 -0
- package/test/helpers/payment/chargebee/refund-details.test.js +198 -0
- package/test/helpers/payment/chargebee/to-unified-one-time.test.js +148 -0
- package/test/helpers/payment/chargebee/to-unified-subscription.test.js +649 -0
- package/test/helpers/payment/coinbase/create-intent.test.js +247 -0
- package/test/helpers/payment/coinbase/parse-webhook.test.js +195 -0
- package/test/helpers/payment/coinbase/refund-unsupported.test.js +141 -0
- package/test/helpers/payment/coinbase/to-unified-one-time.test.js +201 -0
- package/test/helpers/payment/discount-codes.test.js +141 -0
- package/test/helpers/payment/extract-resource.test.js +113 -0
- package/test/helpers/payment/fetch-failure.test.js +215 -0
- package/test/helpers/payment/license-gate.test.js +108 -0
- package/test/helpers/payment/order-id.test.js +99 -0
- package/test/helpers/payment/paypal/create-intent.test.js +382 -0
- package/test/helpers/payment/paypal/fetch-capture.test.js +123 -0
- package/test/helpers/payment/paypal/fetch-sale.test.js +183 -0
- package/test/helpers/payment/paypal/parse-webhook.test.js +678 -0
- package/test/helpers/payment/paypal/refund-details.test.js +202 -0
- package/test/helpers/payment/paypal/resolve-plan-id.test.js +108 -0
- package/test/helpers/payment/paypal/switch-plan.test.js +92 -0
- package/test/helpers/payment/paypal/to-unified-one-time.test.js +383 -0
- package/test/helpers/payment/paypal/to-unified-subscription.test.js +884 -0
- package/test/helpers/payment/stripe/fetch-charge.test.js +147 -0
- package/test/helpers/payment/stripe/parse-webhook.test.js +448 -0
- package/test/helpers/payment/stripe/refund-details.test.js +147 -0
- package/test/helpers/payment/stripe/to-unified-one-time.test.js +307 -0
- package/test/helpers/payment/stripe/to-unified-subscription.test.js +709 -0
- package/test/helpers/persona-domain.test.js +87 -0
- package/test/helpers/recaptcha.test.js +166 -0
- package/test/helpers/resolved-config.test.js +106 -0
- package/test/helpers/response-log-redaction.test.js +262 -0
- package/test/helpers/roles.test.js +155 -0
- package/test/helpers/route-context-debug-gate.test.js +118 -0
- package/test/helpers/route-context-logging.test.js +214 -0
- package/test/helpers/route-context.test.js +129 -0
- package/test/helpers/safe-compare.test.js +114 -0
- package/test/helpers/sanitize.test.js +223 -0
- package/test/helpers/schema-engine.test.js +537 -0
- package/test/helpers/schema-zod.test.js +554 -0
- package/test/helpers/seed-accounts-load.test.js +163 -0
- package/test/helpers/seed-google-personas.test.js +114 -0
- package/test/helpers/seeded-personas.test.js +823 -0
- package/test/helpers/settings.test.js +292 -0
- package/test/helpers/setup-engines-pin.test.js +68 -0
- package/test/helpers/setup-manifest-sync.test.js +116 -0
- package/test/helpers/slugify.test.js +395 -0
- package/test/helpers/storage.test.js +195 -0
- package/test/helpers/test-banner-latch.test.js +139 -0
- package/test/helpers/usage-consume.test.js +460 -0
- package/test/helpers/usage-log-privacy.test.js +121 -0
- package/test/helpers/user-doc-heal.test.js +540 -0
- package/test/helpers/user.test.js +771 -0
- package/test/helpers/webhook-forward.test.js +419 -0
- package/test/helpers/wipe-auth-project.test.js +143 -0
- package/test/mcp/discovery.test.js +52 -0
- package/test/mcp/oauth.test.js +160 -0
- package/test/mcp/protocol.test.js +278 -0
- package/test/mcp/roles.test.js +203 -0
- package/test/mcp/utils.test.js +245 -0
- package/test/notification/identity.test.js +155 -0
- package/test/routes/admin/create-post.test.js +362 -0
- package/test/routes/admin/database.test.js +133 -0
- package/test/routes/admin/deduplicate-image-alts.test.js +191 -0
- package/test/routes/admin/email-content-html.test.js +58 -0
- package/test/routes/admin/email-request-log.test.js +86 -0
- package/test/routes/admin/email.test.js +116 -0
- package/test/routes/admin/firestore-query.test.js +206 -0
- package/test/routes/admin/firestore.test.js +129 -0
- package/test/routes/admin/infer-contact.test.js +220 -0
- package/test/routes/admin/notification.test.js +199 -0
- package/test/routes/admin/post-convert-image.test.js +159 -0
- package/test/routes/admin/post-deploy-flag.test.js +81 -0
- package/test/routes/admin/post-download-error.test.js +90 -0
- package/test/routes/admin/post-resize-image.test.js +185 -0
- package/test/routes/admin/post.test.js +369 -0
- package/test/routes/admin/repo-content.test.js +223 -0
- package/test/routes/admin/stats.test.js +114 -0
- package/test/routes/admin/users-disable.test.js +63 -0
- package/test/routes/admin/users-list.test.js +71 -0
- package/test/routes/content/post.test.js +60 -0
- package/test/routes/general/uuid.test.js +133 -0
- package/test/routes/health.test.js +110 -0
- package/test/routes/marketing/campaign.test.js +184 -0
- package/test/routes/marketing/contact.test.js +416 -0
- package/test/routes/marketing/email-preferences.test.js +293 -0
- package/test/routes/marketing/push-send.test.js +34 -0
- package/test/routes/marketing/webhook-forward.test.js +63 -0
- package/test/routes/marketing/webhook.test.js +641 -0
- package/test/routes/payments/_route-harness.js +145 -0
- package/test/routes/payments/cancel-provider-errors.test.js +227 -0
- package/test/routes/payments/cancel-skip-guards.test.js +126 -0
- package/test/routes/payments/cancel-trialing.test.js +428 -0
- package/test/routes/payments/cancel.test.js +165 -0
- package/test/routes/payments/dedup-race.test.js +186 -0
- package/test/routes/payments/discount.test.js +82 -0
- package/test/routes/payments/dispute-alert.test.js +324 -0
- package/test/routes/payments/intent-discount-amounts.test.js +550 -0
- package/test/routes/payments/intent-discount-coupons.test.js +293 -0
- package/test/routes/payments/intent-one-time-metadata.test.js +122 -0
- package/test/routes/payments/intent-purchaser-guard.test.js +139 -0
- package/test/routes/payments/intent-zero-total.test.js +260 -0
- package/test/routes/payments/intent.test.js +413 -0
- package/test/routes/payments/plan.test.js +367 -0
- package/test/routes/payments/portal-return-url.test.js +107 -0
- package/test/routes/payments/portal.test.js +94 -0
- package/test/routes/payments/refund-one-time.test.js +254 -0
- package/test/routes/payments/refund-paypal-proration.test.js +190 -0
- package/test/routes/payments/refund.test.js +181 -0
- package/test/routes/payments/trial-eligibility.test.js +73 -0
- package/test/routes/payments/uncancel.test.js +270 -0
- package/test/routes/payments/webhook-stripe-invoice.test.js +129 -0
- package/test/routes/payments/webhook-stripe-refund-one-time.test.js +130 -0
- package/test/routes/payments/webhook-test-provider.test.js +156 -0
- package/test/routes/payments/webhook.test.js +115 -0
- package/test/routes/payments/winback-stripe-coupon.test.js +126 -0
- package/test/routes/payments/winback.test.js +597 -0
- package/test/routes/test/authenticate.test.js +79 -0
- package/test/routes/test/redirect.test.js +66 -0
- package/test/routes/test/reset-account.test.js +126 -0
- package/test/routes/test/roster.test.js +101 -0
- package/test/routes/test/schema.test.js +556 -0
- package/test/routes/test/usage.test.js +374 -0
- package/test/routes/user/api-keys.test.js +158 -0
- package/test/routes/user/connections-grant.test.js +759 -0
- package/test/routes/user/connections-identity.test.js +260 -0
- package/test/routes/user/connections-log-privacy.test.js +206 -0
- package/test/routes/user/connections-refresh-lease.test.js +696 -0
- package/test/routes/user/connections-return.test.js +261 -0
- package/test/routes/user/connections-uid.test.js +516 -0
- package/test/routes/user/delete.test.js +138 -0
- package/test/routes/user/feedback.test.js +106 -0
- package/test/routes/user/orders.test.js +233 -0
- package/test/routes/user/sessions.test.js +231 -0
- package/test/routes/user/settings-validate.test.js +84 -0
- package/test/routes/user/signup-emails.test.js +113 -0
- package/test/routes/user/signup-location.test.js +84 -0
- package/test/routes/user/signup-log-privacy.test.js +112 -0
- package/test/routes/user/signup.test.js +579 -0
- package/test/routes/user/subscription.test.js +101 -0
- package/test/routes/user/token.test.js +111 -0
- package/test/routes/user/user.test.js +158 -0
- package/test/routes/verts/cache.test.js +78 -0
- package/test/routes/verts/click-destination.test.js +76 -0
- package/test/routes/verts/crud.test.js +194 -0
- package/test/routes/verts/redirect.test.js +147 -0
- package/test/routes/verts/selection.test.js +237 -0
- package/test/routes/verts/serve.test.js +185 -0
- package/test/routes/verts/unit-document.test.js +117 -0
- package/test/rules/_environment.js +66 -0
- package/test/rules/brand-merge.test.js +195 -0
- package/test/rules/field-helpers.test.js +309 -0
- package/test/rules/notifications.test.js +577 -0
- package/test/rules/payments-carts.test.js +406 -0
- package/test/rules/sessions.test.js +170 -0
- package/test/rules/user.test.js +517 -0
- package/test/rules/verts.test.js +101 -0
- package/test/security/fetch-log-secrets.test.js +124 -0
- package/test/security/primitives.test.js +105 -0
- package/test/security/repo-pinning.test.js +77 -0
- package/test/stripe-live/subscription-lifecycle.test.js +141 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
const powertools = require('node-powertools');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Expire PayPal pending cancellations
|
|
5
|
+
*
|
|
6
|
+
* PayPal has no cancel_at_period_end like Stripe — cancellation is immediate on PayPal's side.
|
|
7
|
+
* @omega.js/backend keeps users active with cancellation.pending=true until the billing period ends.
|
|
8
|
+
* Since PayPal does NOT fire a webhook at period end, this cron job transitions those
|
|
9
|
+
* users to 'cancelled' status once their paid period has expired.
|
|
10
|
+
*
|
|
11
|
+
* Flow:
|
|
12
|
+
* 1. Query users with PayPal subscriptions where cancellation.pending=true
|
|
13
|
+
* 2. Check if subscription.expires.timestampUNIX < now
|
|
14
|
+
* 3. Re-read the user + order, and skip anything a newer webhook has since written
|
|
15
|
+
* 4. Update status to 'cancelled' and cancellation.pending to false — on the user doc
|
|
16
|
+
* AND on payments-orders/{orderId}, whose `unified` mirror is what handlers and the
|
|
17
|
+
* UI read; the two land together in one batch
|
|
18
|
+
* 5. Dispatch 'subscription-cancelled' transition (sends email)
|
|
19
|
+
*
|
|
20
|
+
* The staleness discipline is the webhook trigger's: the batch below is read once and
|
|
21
|
+
* written to one user at a time, so a webhook may land between the read and the write.
|
|
22
|
+
* Anything stamped after this run's read time is the newer truth, and the cron stands
|
|
23
|
+
* down rather than overwriting it ([#221]).
|
|
24
|
+
*/
|
|
25
|
+
module.exports = async ({ Manager, ctx, context, libraries }) => {
|
|
26
|
+
const { admin } = libraries;
|
|
27
|
+
const transitions = require('../../firestore/payments-webhooks/transitions/index.js');
|
|
28
|
+
|
|
29
|
+
const now = new Date();
|
|
30
|
+
const nowStr = powertools.timestamp(now, { output: 'string' });
|
|
31
|
+
const nowUNIX = powertools.timestamp(nowStr, { output: 'unix' });
|
|
32
|
+
|
|
33
|
+
// The read stamp: any payment write newer than this landed AFTER this run's snapshot
|
|
34
|
+
const sweepReadUNIX = nowUNIX;
|
|
35
|
+
|
|
36
|
+
ctx.log('Checking for expired PayPal pending cancellations...');
|
|
37
|
+
|
|
38
|
+
let processed = 0;
|
|
39
|
+
let skipped = 0;
|
|
40
|
+
|
|
41
|
+
await Manager.Utilities().iterateCollection(async (batch, index) => {
|
|
42
|
+
for (const doc of batch.docs) {
|
|
43
|
+
const uid = doc.id;
|
|
44
|
+
|
|
45
|
+
// Re-read: the batch snapshot above may be many writes old by the time this
|
|
46
|
+
// candidate's turn comes, and the doc in hand is the one being written to
|
|
47
|
+
const fresh = await doc.ref.get();
|
|
48
|
+
const data = fresh.data() || {};
|
|
49
|
+
const sub = data.subscription;
|
|
50
|
+
|
|
51
|
+
// Double-check: skip if expires is in the future
|
|
52
|
+
if (!sub?.expires?.timestampUNIX || sub.expires.timestampUNIX > nowUNIX) {
|
|
53
|
+
ctx.log(`skip ${uid}: expires=${sub?.expires?.timestamp || 'null'} is still in the future (now=${nowStr})`);
|
|
54
|
+
skipped++;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// The candidate may have been answered since the snapshot — by a webhook, or by
|
|
59
|
+
// the user re-subscribing. Only the two facts the query selected on are checked:
|
|
60
|
+
// the status the cron would write, and the pending flag it would clear.
|
|
61
|
+
if (sub.status === 'cancelled' || sub.cancellation?.pending !== true) {
|
|
62
|
+
ctx.log(`skip ${uid}: no longer a pending cancellation (status=${sub.status}, pending=${sub.cancellation?.pending})`);
|
|
63
|
+
skipped++;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// A subscription written after this run's read is the newer truth
|
|
68
|
+
const subUpdatedUNIX = sub.payment?.updatedBy?.date?.timestampUNIX || 0;
|
|
69
|
+
|
|
70
|
+
if (subUpdatedUNIX > sweepReadUNIX) {
|
|
71
|
+
ctx.log(`skip ${uid}: a newer subscription write landed (updatedBy=${subUpdatedUNIX} > read=${sweepReadUNIX})`);
|
|
72
|
+
skipped++;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const orderId = sub.payment?.orderId || null;
|
|
77
|
+
let orderSnap = null;
|
|
78
|
+
|
|
79
|
+
if (orderId) {
|
|
80
|
+
orderSnap = await admin.firestore().doc(`payments-orders/${orderId}`).get();
|
|
81
|
+
|
|
82
|
+
// Same guard on the order, the same way the webhook trigger applies it
|
|
83
|
+
const orderUpdatedUNIX = orderSnap.exists ? (orderSnap.data()?.metadata?.updated?.timestampUNIX || 0) : 0;
|
|
84
|
+
|
|
85
|
+
if (orderUpdatedUNIX > sweepReadUNIX) {
|
|
86
|
+
ctx.log(`skip ${uid}: a newer webhook already wrote payments-orders/${orderId} (updated=${orderUpdatedUNIX} > read=${sweepReadUNIX})`);
|
|
87
|
+
skipped++;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!orderSnap.exists) {
|
|
92
|
+
ctx.warn(`expire ${uid}: payments-orders/${orderId} does not exist — the subscription expires, but there is no order record to mirror it onto`);
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
ctx.warn(`expire ${uid}: subscription carries no orderId — nothing mirrors this expiry on the order side, and the order-level staleness guard cannot run`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
ctx.log(`expire ${uid}: expires=${sub.expires.timestamp}, product=${sub.product?.id}, provider=${sub.payment?.provider}, orderId=${orderId || 'null'}`);
|
|
99
|
+
|
|
100
|
+
// Snapshot the before state for transition detection
|
|
101
|
+
const before = { ...sub };
|
|
102
|
+
|
|
103
|
+
// Build the after state — transition to cancelled
|
|
104
|
+
const after = {
|
|
105
|
+
...sub,
|
|
106
|
+
status: 'cancelled',
|
|
107
|
+
cancellation: {
|
|
108
|
+
...sub.cancellation,
|
|
109
|
+
pending: false,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// The user doc and the order move TOGETHER, and each is written as the DELTA this
|
|
114
|
+
// cron owns — never the whole map read from a snapshot, which would restore every
|
|
115
|
+
// other field to the value it had at read time and undo a concurrent write.
|
|
116
|
+
const writes = admin.firestore().batch();
|
|
117
|
+
|
|
118
|
+
writes.set(doc.ref, {
|
|
119
|
+
subscription: {
|
|
120
|
+
status: 'cancelled',
|
|
121
|
+
cancellation: { pending: false },
|
|
122
|
+
},
|
|
123
|
+
}, { merge: true });
|
|
124
|
+
|
|
125
|
+
if (orderSnap?.exists) {
|
|
126
|
+
writes.set(orderSnap.ref, {
|
|
127
|
+
unified: {
|
|
128
|
+
status: 'cancelled',
|
|
129
|
+
cancellation: { pending: false },
|
|
130
|
+
},
|
|
131
|
+
metadata: {
|
|
132
|
+
updated: {
|
|
133
|
+
timestamp: nowStr,
|
|
134
|
+
timestampUNIX: nowUNIX,
|
|
135
|
+
},
|
|
136
|
+
updatedBy: {
|
|
137
|
+
event: {
|
|
138
|
+
name: 'cron/daily/expire-paypal-cancellations',
|
|
139
|
+
id: null,
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
}, { merge: true });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
await writes.commit();
|
|
147
|
+
|
|
148
|
+
ctx.log(`expire ${uid}: Updated status=cancelled, cancellation.pending=false${orderSnap?.exists ? ` (and payments-orders/${orderId})` : ''}`);
|
|
149
|
+
|
|
150
|
+
// Detect and dispatch transition (should fire 'subscription-cancelled')
|
|
151
|
+
const transitionName = transitions.detectTransition('subscription', before, after, null);
|
|
152
|
+
|
|
153
|
+
if (transitionName) {
|
|
154
|
+
ctx.log(`expire ${uid}: Transition detected: subscription/${transitionName}`);
|
|
155
|
+
|
|
156
|
+
// Build minimal order context for the handler
|
|
157
|
+
const order = {
|
|
158
|
+
id: orderId,
|
|
159
|
+
type: 'subscription',
|
|
160
|
+
owner: uid,
|
|
161
|
+
provider: 'paypal',
|
|
162
|
+
resourceId: sub.payment?.resourceId || null,
|
|
163
|
+
unified: after,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
transitions.dispatch(transitionName, 'subscription', {
|
|
167
|
+
before,
|
|
168
|
+
after,
|
|
169
|
+
order,
|
|
170
|
+
uid,
|
|
171
|
+
userDoc: data,
|
|
172
|
+
ctx,
|
|
173
|
+
});
|
|
174
|
+
} else {
|
|
175
|
+
ctx.log(`expire ${uid}: No transition detected (before.status=${before.status}, after.status=${after.status})`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
processed++;
|
|
179
|
+
}
|
|
180
|
+
}, {
|
|
181
|
+
collection: 'users',
|
|
182
|
+
where: [
|
|
183
|
+
{ field: 'subscription.payment.provider', operator: '==', value: 'paypal' },
|
|
184
|
+
{ field: 'subscription.cancellation.pending', operator: '==', value: true },
|
|
185
|
+
],
|
|
186
|
+
batchSize: 5000,
|
|
187
|
+
log: true,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
ctx.log(`Completed! Processed=${processed}, Skipped=${skipped}`);
|
|
191
|
+
};
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marketing contact pruning cron job
|
|
3
|
+
*
|
|
4
|
+
* Runs daily but only acts on the 1st of each month.
|
|
5
|
+
*
|
|
6
|
+
* Pruning is STRICTLY PER-PROVIDER ([#365](https://github.com/Omega-JS-Stack/omega/issues/365)):
|
|
7
|
+
* each provider's own engagement decides that provider's removals, never the
|
|
8
|
+
* other's. SendGrid carries offers and product mail, Beehiiv carries the
|
|
9
|
+
* newsletter, and a reader who devours every newsletter while ignoring offers
|
|
10
|
+
* is engaged on one channel and quiet on the other — deleting them from the
|
|
11
|
+
* newsletter on a SendGrid-only signal was the bug this shape replaces.
|
|
12
|
+
*
|
|
13
|
+
* Stage 1 (engagement_inactive_5m) — both providers, each on its own data:
|
|
14
|
+
* → Send re-engagement email via sendCampaign (brand-scoped internally)
|
|
15
|
+
* → Excludes engagement_inactive_6m (those get pruned instead)
|
|
16
|
+
* → Already per-provider: each provider resolves the segment key against
|
|
17
|
+
* its OWN engagement tracking, so nobody is targeted on foreign data
|
|
18
|
+
*
|
|
19
|
+
* Stage 2 (engagement_inactive_6m) — SENDGRID ONLY:
|
|
20
|
+
* → Create brand-scoped temp segment via createBrandScopedSegment
|
|
21
|
+
* → Export contacts, exclude paying customers, bulk delete
|
|
22
|
+
* → Log deleted emails to Firestore for recoverability
|
|
23
|
+
*
|
|
24
|
+
* Stage 3 — BEEHIIV ONLY:
|
|
25
|
+
* → List the publication's active subscriptions with Beehiiv's own
|
|
26
|
+
* per-subscriber stats, delete the ones inactive by THOSE stats
|
|
27
|
+
* → Exclude paying customers (the subscription_paid definition, resolved
|
|
28
|
+
* from Firestore since Beehiiv cannot see app subscriptions)
|
|
29
|
+
* → Log deleted emails to Firestore for recoverability
|
|
30
|
+
*
|
|
31
|
+
* Removal is always a DELETE, never an unsubscribe: an unsubscribe revokes
|
|
32
|
+
* marketing consent in Firestore permanently, and a maintenance prune must
|
|
33
|
+
* never spend a consent it can't give back.
|
|
34
|
+
*
|
|
35
|
+
* Segment keys are resolved to provider-specific IDs at runtime.
|
|
36
|
+
*
|
|
37
|
+
* ON BY DEFAULT ([#478](https://github.com/Omega-JS-Stack/omega/issues/478),
|
|
38
|
+
* superseding #422's opt-in): the schema default supplies
|
|
39
|
+
* marketing.prune.enabled = true through the resolved config, so the gate
|
|
40
|
+
* below only stops a brand that set it false — see
|
|
41
|
+
* docs/marketing-campaigns.md § Contact Pruning for the contract.
|
|
42
|
+
*
|
|
43
|
+
* Runs on omega_cronDaily.
|
|
44
|
+
*/
|
|
45
|
+
const sendgridProvider = require('../../../libraries/email/providers/sendgrid.js');
|
|
46
|
+
const beehiivProvider = require('../../../libraries/email/providers/beehiiv.js');
|
|
47
|
+
const Marketing = require('../../../libraries/email/marketing/index.js');
|
|
48
|
+
const User = require('../../../helpers/user.js');
|
|
49
|
+
const env = require('../../../libraries/env.js');
|
|
50
|
+
|
|
51
|
+
// The Beehiiv lane's inactivity rule, mirroring the SendGrid engagement_inactive_6m
|
|
52
|
+
// segment (libraries/email/constants.js) with Beehiiv's own numbers:
|
|
53
|
+
//
|
|
54
|
+
// NEWSLETTER_RECEIVED_FLOOR mirrors `received_gte: 5` — a subscriber must have
|
|
55
|
+
// been SENT enough newsletters for silence to mean anything. A quiet channel
|
|
56
|
+
// prunes nobody, which is the guard working, not a bug to fix.
|
|
57
|
+
//
|
|
58
|
+
// NEWSLETTER_MIN_AGE_DAYS mirrors the segment's `signup_date not_within 180d`
|
|
59
|
+
// condition — a recent subscriber gets the same grace period on both channels.
|
|
60
|
+
const NEWSLETTER_RECEIVED_FLOOR = 5;
|
|
61
|
+
const NEWSLETTER_MIN_AGE_DAYS = 180;
|
|
62
|
+
|
|
63
|
+
// How many Beehiiv subscribers are deleted at once. Each removal is TWO API
|
|
64
|
+
// calls (look up the subscription, then delete it), so 50 keeps the in-flight
|
|
65
|
+
// call count at the same 100 the SendGrid batch loop below already uses.
|
|
66
|
+
const NEWSLETTER_DELETE_CHUNK = 50;
|
|
67
|
+
|
|
68
|
+
module.exports = async ({ Manager, ctx, libraries }) => {
|
|
69
|
+
if (new Date().getDate() !== 1) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// `!== false`, not `!== true`: the switch is default-ON (#478), and reading it
|
|
74
|
+
// the opt-in way meant any config assembled outside loadConfig()'s
|
|
75
|
+
// materialization — a hand-built Manager.config, a test fixture — silently
|
|
76
|
+
// skipped the monthly run while the schema promised it
|
|
77
|
+
// ([#551](https://github.com/Omega-JS-Stack/omega/issues/551)).
|
|
78
|
+
if (Manager.config?.marketing?.prune?.enabled === false) {
|
|
79
|
+
ctx.log('Marketing prune: disabled (marketing.prune.enabled resolved false — the schema default is true)');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const brand = Manager.config?.brand;
|
|
84
|
+
|
|
85
|
+
ctx.log(`Marketing prune: Starting monthly prune cycle for ${brand?.id || 'unknown'}`);
|
|
86
|
+
|
|
87
|
+
// --- Stage 1: Re-engagement email ---
|
|
88
|
+
try {
|
|
89
|
+
await stageReengage(Manager, ctx);
|
|
90
|
+
} catch (e) {
|
|
91
|
+
ctx.error('Marketing prune: Stage 1 (re-engagement) failed:', e.message);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// --- Stage 2: Delete SendGrid-inactive contacts from SendGrid ---
|
|
95
|
+
// Each stage is caught on its own: the lanes are independent by design, so a
|
|
96
|
+
// provider outage in one must never cost the others their monthly run.
|
|
97
|
+
try {
|
|
98
|
+
await stagePrune(Manager, ctx, libraries);
|
|
99
|
+
} catch (e) {
|
|
100
|
+
ctx.error('Marketing prune: Stage 2 (SendGrid prune) failed:', e.message);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// --- Stage 3: Delete Beehiiv-inactive subscribers from Beehiiv ---
|
|
104
|
+
try {
|
|
105
|
+
await stageNewsletterPrune(Manager, ctx, libraries);
|
|
106
|
+
} catch (e) {
|
|
107
|
+
ctx.error('Marketing prune: Stage 3 (Beehiiv prune) failed:', e.message);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
ctx.log(`Marketing prune: Completed for ${brand?.id || 'unknown'}`);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Stage 1: Send re-engagement email to contacts inactive 5+ months
|
|
115
|
+
* (excluding 6+ months — those get pruned in stage 2).
|
|
116
|
+
*
|
|
117
|
+
* sendCampaign handles brand-scoping internally via _resolveAudience →
|
|
118
|
+
* createBrandScopedSegment, so this stage is already brand-safe.
|
|
119
|
+
*/
|
|
120
|
+
async function stageReengage(Manager, ctx) {
|
|
121
|
+
ctx.log('Marketing prune: Stage 1 — Re-engagement');
|
|
122
|
+
|
|
123
|
+
const mailer = Manager.Email(ctx);
|
|
124
|
+
const brand = Manager.config?.brand;
|
|
125
|
+
|
|
126
|
+
const result = await mailer.sendCampaign({
|
|
127
|
+
name: 'Re-engagement: Are you still with us?',
|
|
128
|
+
subject: `We miss you at ${brand?.name || 'our service'}!`,
|
|
129
|
+
preheader: 'Update your preferences or say goodbye',
|
|
130
|
+
content: [
|
|
131
|
+
'# We miss you!',
|
|
132
|
+
'',
|
|
133
|
+
'It\'s been a while since you\'ve opened one of our emails. We want to make sure we\'re sending you content you actually want.',
|
|
134
|
+
'',
|
|
135
|
+
'**If you\'d like to keep hearing from us**, simply open this email — no action needed!',
|
|
136
|
+
'',
|
|
137
|
+
'If we don\'t hear from you, we\'ll remove you from our mailing list next month to keep your inbox clean.',
|
|
138
|
+
'',
|
|
139
|
+
`Thanks for being part of the ${brand?.name || ''} community.`,
|
|
140
|
+
].join('\n'),
|
|
141
|
+
sender: 'hello',
|
|
142
|
+
segments: ['engagement_inactive_5m'],
|
|
143
|
+
excludeSegments: ['engagement_inactive_6m'],
|
|
144
|
+
sendAt: 'now',
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
ctx.log('Marketing prune: Re-engagement result:', result);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Stage 2: Delete SENDGRID contacts inactive 6+ months in SENDGRID (brand-scoped).
|
|
152
|
+
*
|
|
153
|
+
* Uses createBrandScopedSegment to AND the engagement_inactive_6m query
|
|
154
|
+
* with brand_id = '<brandId>' — each brand only prunes its own contacts.
|
|
155
|
+
* Excludes paying customers (subscription_paid segment).
|
|
156
|
+
* Logs deleted emails to Firestore for recoverability.
|
|
157
|
+
*
|
|
158
|
+
* Stops at SendGrid: the same emails stay in Beehiiv, which prunes on its own
|
|
159
|
+
* engagement in stage 3.
|
|
160
|
+
*/
|
|
161
|
+
async function stagePrune(Manager, ctx, libraries) {
|
|
162
|
+
ctx.log('Marketing prune: Stage 2 — Prune (SendGrid)');
|
|
163
|
+
|
|
164
|
+
const marketing = Manager.config?.marketing || {};
|
|
165
|
+
const brand = Manager.config?.brand;
|
|
166
|
+
const { admin } = libraries;
|
|
167
|
+
|
|
168
|
+
if (!brand?.id) {
|
|
169
|
+
ctx.error('Marketing prune: brand.id is missing — aborting to prevent account-global deletion');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (marketing.campaigns?.enabled === false || !env.has('SENDGRID_API_KEY')) {
|
|
174
|
+
ctx.log('Marketing prune: SendGrid not configured, skipping');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const segmentIdMap = await sendgridProvider.resolveSegmentIds();
|
|
179
|
+
const pruneSegmentId = segmentIdMap['engagement_inactive_6m'];
|
|
180
|
+
|
|
181
|
+
if (!pruneSegmentId) {
|
|
182
|
+
ctx.error('Marketing prune: engagement_inactive_6m segment not found in SendGrid');
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Brand-scope the prune segment
|
|
187
|
+
const tempPrune = await sendgridProvider.createBrandScopedSegment(
|
|
188
|
+
[pruneSegmentId],
|
|
189
|
+
brand.id,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
if (!tempPrune) {
|
|
193
|
+
ctx.error('Marketing prune: Failed to create brand-scoped prune segment');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const exportResult = await sendgridProvider.getSegmentContacts(tempPrune.segmentId, 180000);
|
|
199
|
+
|
|
200
|
+
if (!exportResult.success) {
|
|
201
|
+
ctx.error('Marketing prune: Failed to export segment:', exportResult.error);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (exportResult.contacts.length === 0) {
|
|
206
|
+
ctx.log('Marketing prune: No contacts to prune');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Exclude paying customers
|
|
211
|
+
let contactsToPrune = exportResult.contacts;
|
|
212
|
+
let skippedPaid = 0;
|
|
213
|
+
|
|
214
|
+
const paidSegmentId = segmentIdMap['subscription_paid'];
|
|
215
|
+
|
|
216
|
+
if (paidSegmentId) {
|
|
217
|
+
const tempPaid = await sendgridProvider.createBrandScopedSegment(
|
|
218
|
+
[paidSegmentId],
|
|
219
|
+
brand.id,
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
if (tempPaid) {
|
|
223
|
+
try {
|
|
224
|
+
const paidExport = await sendgridProvider.getSegmentContacts(tempPaid.segmentId, 180000);
|
|
225
|
+
|
|
226
|
+
if (paidExport.success && paidExport.contacts.length > 0) {
|
|
227
|
+
const paidEmails = new Set(paidExport.contacts.map(c => c.email));
|
|
228
|
+
contactsToPrune = contactsToPrune.filter(c => !paidEmails.has(c.email));
|
|
229
|
+
skippedPaid = exportResult.contacts.length - contactsToPrune.length;
|
|
230
|
+
ctx.log(`Marketing prune: Excluded ${skippedPaid} paying customers`);
|
|
231
|
+
}
|
|
232
|
+
} finally {
|
|
233
|
+
await tempPaid.cleanup();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (contactsToPrune.length === 0) {
|
|
239
|
+
ctx.log('Marketing prune: No contacts to prune after paid exclusion');
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const emails = contactsToPrune.map(c => c.email).filter(Boolean);
|
|
244
|
+
|
|
245
|
+
ctx.log(`Marketing prune: Deleting ${contactsToPrune.length} contacts for ${brand.id}`);
|
|
246
|
+
|
|
247
|
+
// Delete from SendGrid
|
|
248
|
+
const ids = contactsToPrune.map(c => c.id).filter(Boolean);
|
|
249
|
+
let totalDeleted = 0;
|
|
250
|
+
|
|
251
|
+
for (let i = 0; i < ids.length; i += 100) {
|
|
252
|
+
const batch = ids.slice(i, i + 100);
|
|
253
|
+
const deleteResult = await sendgridProvider.bulkDeleteContacts(batch);
|
|
254
|
+
|
|
255
|
+
if (deleteResult.success) {
|
|
256
|
+
totalDeleted += batch.length;
|
|
257
|
+
} else {
|
|
258
|
+
ctx.error('Marketing prune: Batch delete failed:', deleteResult.error);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
ctx.log(`Marketing prune: Deleted ${totalDeleted} SendGrid contacts for ${brand.id}`);
|
|
263
|
+
|
|
264
|
+
await logPrunedEmails(admin, ctx, brand.id, '', { emails, skippedPaid });
|
|
265
|
+
} finally {
|
|
266
|
+
await tempPrune.cleanup();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Stage 3: Delete BEEHIIV subscribers inactive by BEEHIIV's own engagement.
|
|
272
|
+
*
|
|
273
|
+
* Beehiiv reports lifetime per-subscriber stats rather than a windowed
|
|
274
|
+
* last-engaged date, so this lane reads what it has: a subscriber who has
|
|
275
|
+
* received enough newsletters, has been on the list long enough, and has never
|
|
276
|
+
* once opened or clicked is the Beehiiv-native shape of "gone".
|
|
277
|
+
*
|
|
278
|
+
* One publication belongs to one brand, so the listing is brand-scoped by
|
|
279
|
+
* construction — no equivalent of stage 2's brand-scoped temp segment.
|
|
280
|
+
* Paying customers are excluded exactly as stage 2 excludes them, removal
|
|
281
|
+
* deletes the subscription (never unsubscribes it), and the deleted emails are
|
|
282
|
+
* logged to Firestore for recoverability.
|
|
283
|
+
*/
|
|
284
|
+
async function stageNewsletterPrune(Manager, ctx, libraries) {
|
|
285
|
+
ctx.log('Marketing prune: Stage 3 — Prune (Beehiiv)');
|
|
286
|
+
|
|
287
|
+
const marketing = Manager.config?.marketing || {};
|
|
288
|
+
const brand = Manager.config?.brand;
|
|
289
|
+
const { admin } = libraries;
|
|
290
|
+
|
|
291
|
+
if (!brand?.id) {
|
|
292
|
+
ctx.error('Marketing prune: brand.id is missing — aborting to prevent account-global deletion');
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (marketing.newsletter?.enabled === false || !env.has('BEEHIIV_API_KEY')) {
|
|
297
|
+
ctx.log('Marketing prune: Beehiiv not configured, skipping');
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const listing = await beehiivProvider.listSubscriptions({
|
|
302
|
+
status: 'active',
|
|
303
|
+
expand: ['stats'],
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
if (!listing.success) {
|
|
307
|
+
ctx.error('Marketing prune: Failed to list Beehiiv subscriptions:', listing.error);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const now = Date.now();
|
|
312
|
+
const candidates = listing.subscriptions.filter(subscription => isNewsletterInactive(subscription, now));
|
|
313
|
+
|
|
314
|
+
if (candidates.length === 0) {
|
|
315
|
+
ctx.log(`Marketing prune: No Beehiiv subscribers to prune (${listing.subscriptions.length} scanned)`);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const { targets, skippedPaid } = await excludePayingSubscribers(admin, ctx, candidates);
|
|
320
|
+
|
|
321
|
+
if (targets.length === 0) {
|
|
322
|
+
ctx.log('Marketing prune: No Beehiiv subscribers to prune after paid exclusion');
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
ctx.log(`Marketing prune: Deleting ${targets.length} Beehiiv subscribers for ${brand.id} (${listing.subscriptions.length} scanned)`);
|
|
327
|
+
|
|
328
|
+
const emails = [];
|
|
329
|
+
|
|
330
|
+
for (let i = 0; i < targets.length; i += NEWSLETTER_DELETE_CHUNK) {
|
|
331
|
+
const batch = targets.slice(i, i + NEWSLETTER_DELETE_CHUNK);
|
|
332
|
+
const results = await Promise.allSettled(
|
|
333
|
+
batch.map(subscription => beehiivProvider.removeContact(subscription.email))
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
results.forEach((result, index) => {
|
|
337
|
+
const email = batch[index].email;
|
|
338
|
+
|
|
339
|
+
// `deleted`, not `success`: a subscriber who vanished between the listing
|
|
340
|
+
// and the delete comes back { success: true, skipped: true }, and counting
|
|
341
|
+
// that as a prune would inflate the recoverability log with emails this
|
|
342
|
+
// run never removed.
|
|
343
|
+
if (result.status === 'fulfilled' && result.value?.deleted) {
|
|
344
|
+
emails.push(email);
|
|
345
|
+
} else if (result.status === 'fulfilled' && result.value?.skipped) {
|
|
346
|
+
ctx.log(`Marketing prune: Beehiiv subscriber already gone, skipping: ${email}`);
|
|
347
|
+
} else {
|
|
348
|
+
ctx.error(`Marketing prune: Beehiiv delete failed for ${email}:`, result.reason?.message || result.value?.error);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
ctx.log(`Marketing prune: Deleted ${emails.length} Beehiiv subscribers for ${brand.id}`);
|
|
354
|
+
|
|
355
|
+
await logPrunedEmails(admin, ctx, brand.id, '-newsletter', { emails, skippedPaid, provider: 'newsletter' });
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Split prune candidates into the ones to delete and the paying customers to
|
|
360
|
+
* keep — stage 2's subscription_paid exclusion, done the only way Beehiiv
|
|
361
|
+
* allows.
|
|
362
|
+
*
|
|
363
|
+
* Beehiiv has no view of app subscriptions (its own `subscription_tier` is the
|
|
364
|
+
* paid-NEWSLETTER tier, a different thing), so paid-ness comes from Firestore:
|
|
365
|
+
* the marketing library's canonical by-email user lookup, then the account
|
|
366
|
+
* package's resolveSubscription. Its `active` IS the subscription_paid
|
|
367
|
+
* segment's two conditions (plan not basic AND status active), so no second
|
|
368
|
+
* definition of "paying" enters the codebase.
|
|
369
|
+
*
|
|
370
|
+
* No user doc means a pure newsletter contact with nothing to pay with, and a
|
|
371
|
+
* failed lookup reads the same way — the same fail-open posture stage 2 takes
|
|
372
|
+
* when its paid export comes back empty.
|
|
373
|
+
*
|
|
374
|
+
* @param {object} admin - firebase-admin instance
|
|
375
|
+
* @param {object} ctx - Assistant (for logging)
|
|
376
|
+
* @param {Array<object>} candidates - Subscriptions inactive by Beehiiv's stats
|
|
377
|
+
* @returns {{ targets: Array<object>, skippedPaid: number }}
|
|
378
|
+
*/
|
|
379
|
+
async function excludePayingSubscribers(admin, ctx, candidates) {
|
|
380
|
+
const targets = [];
|
|
381
|
+
let skippedPaid = 0;
|
|
382
|
+
|
|
383
|
+
for (let i = 0; i < candidates.length; i += NEWSLETTER_DELETE_CHUNK) {
|
|
384
|
+
const batch = candidates.slice(i, i + NEWSLETTER_DELETE_CHUNK);
|
|
385
|
+
const userDocs = await Promise.all(
|
|
386
|
+
batch.map(subscription => Marketing.findUserByEmail(admin, ctx, subscription.email))
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
userDocs.forEach((userDoc, index) => {
|
|
390
|
+
if (userDoc && User.resolveSubscription(userDoc).active) {
|
|
391
|
+
skippedPaid++;
|
|
392
|
+
} else {
|
|
393
|
+
targets.push(batch[index]);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (skippedPaid > 0) {
|
|
399
|
+
ctx.log(`Marketing prune: Excluded ${skippedPaid} paying customers`);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return { targets, skippedPaid };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Is this Beehiiv subscription prunable on BEEHIIV's own engagement?
|
|
407
|
+
*
|
|
408
|
+
* Beehiiv's `expand[]=stats` reports lifetime counters — emails_received plus
|
|
409
|
+
* open_rate and click_through_rate as percentages — so "inactive" here means
|
|
410
|
+
* never engaged at all, across a meaningful number of sends. That is stricter
|
|
411
|
+
* than SendGrid's 180-day window: anyone who has EVER opened or clicked a
|
|
412
|
+
* newsletter carries a non-zero rate forever and is safe from this lane.
|
|
413
|
+
*
|
|
414
|
+
* Stats the expand did not return leave the subscriber unjudgeable, and an
|
|
415
|
+
* unjudgeable subscriber is never deleted — a destructive lane acts on data it
|
|
416
|
+
* has, never on data it assumed.
|
|
417
|
+
*
|
|
418
|
+
* @param {object} subscription - A Beehiiv subscription with `stats` expanded
|
|
419
|
+
* @param {number} now - Milliseconds since epoch
|
|
420
|
+
* @returns {boolean}
|
|
421
|
+
*/
|
|
422
|
+
function isNewsletterInactive(subscription, now) {
|
|
423
|
+
const stats = subscription?.stats;
|
|
424
|
+
|
|
425
|
+
if (
|
|
426
|
+
typeof stats?.emails_received !== 'number'
|
|
427
|
+
|| typeof stats?.open_rate !== 'number'
|
|
428
|
+
|| typeof stats?.click_through_rate !== 'number'
|
|
429
|
+
) {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (stats.emails_received < NEWSLETTER_RECEIVED_FLOOR) {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Beehiiv's `created` is unix SECONDS
|
|
438
|
+
const createdAt = Number(subscription.created) * 1000;
|
|
439
|
+
|
|
440
|
+
if (!createdAt || (now - createdAt) < (NEWSLETTER_MIN_AGE_DAYS * 24 * 60 * 60 * 1000)) {
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return stats.open_rate <= 0 && stats.click_through_rate <= 0;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Log a lane's deleted emails to Firestore for recoverability.
|
|
449
|
+
*
|
|
450
|
+
* One doc per brand per month per lane (`suffix` keeps the two lanes from
|
|
451
|
+
* overwriting each other). Non-fatal: a failed log must never abort a prune
|
|
452
|
+
* that already deleted.
|
|
453
|
+
*
|
|
454
|
+
* @param {object} admin - firebase-admin instance
|
|
455
|
+
* @param {object} ctx - Assistant (for logging)
|
|
456
|
+
* @param {string} brandId
|
|
457
|
+
* @param {string} suffix - Lane suffix on the run doc id ('' for SendGrid)
|
|
458
|
+
* @param {object} payload - Extra fields to store alongside the emails
|
|
459
|
+
*/
|
|
460
|
+
async function logPrunedEmails(admin, ctx, brandId, suffix, payload) {
|
|
461
|
+
try {
|
|
462
|
+
const now = new Date();
|
|
463
|
+
const logKey = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
|
|
464
|
+
|
|
465
|
+
await admin.firestore()
|
|
466
|
+
.doc(`marketing-prune-logs/${brandId}/runs/${logKey}${suffix}`)
|
|
467
|
+
.set({
|
|
468
|
+
brandId,
|
|
469
|
+
date: now.toISOString(),
|
|
470
|
+
count: payload.emails.length,
|
|
471
|
+
...payload,
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
ctx.log(`Marketing prune: Logged ${payload.emails.length} pruned emails to Firestore (${logKey}${suffix})`);
|
|
475
|
+
} catch (e) {
|
|
476
|
+
ctx.error('Marketing prune: Failed to write Firestore log:', e.message);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Exposed as statics for plain-node unit tests (prune-per-provider.test.js) —
|
|
481
|
+
// the cron entry itself only acts on the 1st of the month.
|
|
482
|
+
module.exports.stagePrune = stagePrune;
|
|
483
|
+
module.exports.stageNewsletterPrune = stageNewsletterPrune;
|
|
484
|
+
module.exports.isNewsletterInactive = isNewsletterInactive;
|
|
485
|
+
module.exports.NEWSLETTER_RECEIVED_FLOOR = NEWSLETTER_RECEIVED_FLOOR;
|
|
486
|
+
module.exports.NEWSLETTER_MIN_AGE_DAYS = NEWSLETTER_MIN_AGE_DAYS;
|