@wopr-network/platform-core 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/biome.json +61 -0
- package/dist/admin/admin-audit-log-repository.d.ts +33 -0
- package/dist/admin/admin-audit-log-repository.js +102 -0
- package/dist/admin/audit-log.d.ts +49 -0
- package/dist/admin/audit-log.js +63 -0
- package/dist/admin/index.d.ts +6 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/role-store.d.ts +37 -0
- package/dist/admin/role-store.js +106 -0
- package/dist/auth/api-key-repository.d.ts +11 -0
- package/dist/auth/api-key-repository.js +33 -0
- package/dist/auth/api-key-repository.test.d.ts +1 -0
- package/dist/auth/api-key-repository.test.js +46 -0
- package/dist/auth/auth.test.d.ts +1 -0
- package/dist/auth/auth.test.js +140 -0
- package/dist/auth/better-auth.d.ts +42 -0
- package/dist/auth/better-auth.js +196 -0
- package/dist/auth/index.d.ts +186 -0
- package/dist/auth/index.js +422 -0
- package/dist/auth/login-history-repository.d.ts +14 -0
- package/dist/auth/login-history-repository.js +15 -0
- package/dist/auth/login-history-repository.test.d.ts +1 -0
- package/dist/auth/login-history-repository.test.js +47 -0
- package/dist/auth/middleware.d.ts +55 -0
- package/dist/auth/middleware.js +101 -0
- package/dist/auth/middleware.test.d.ts +1 -0
- package/dist/auth/middleware.test.js +213 -0
- package/dist/auth/scoped-tokens.test.d.ts +1 -0
- package/dist/auth/scoped-tokens.test.js +306 -0
- package/dist/auth/tenant-access.test.d.ts +1 -0
- package/dist/auth/tenant-access.test.js +62 -0
- package/dist/auth/user-creator.d.ts +9 -0
- package/dist/auth/user-creator.js +47 -0
- package/dist/auth/user-creator.test.d.ts +1 -0
- package/dist/auth/user-creator.test.js +78 -0
- package/dist/auth/user-role-repository.d.ts +31 -0
- package/dist/auth/user-role-repository.js +53 -0
- package/dist/auth/user-role-repository.test.d.ts +1 -0
- package/dist/auth/user-role-repository.test.js +122 -0
- package/dist/billing/drizzle-webhook-seen-repository.d.ts +10 -0
- package/dist/billing/drizzle-webhook-seen-repository.js +28 -0
- package/dist/billing/index.d.ts +7 -0
- package/dist/billing/index.js +7 -0
- package/dist/billing/payment-processor.d.ts +127 -0
- package/dist/billing/payment-processor.js +8 -0
- package/dist/billing/payment-processor.test.d.ts +1 -0
- package/dist/billing/payment-processor.test.js +71 -0
- package/dist/billing/payram/cents-credits-boundary.test.d.ts +1 -0
- package/dist/billing/payram/cents-credits-boundary.test.js +75 -0
- package/dist/billing/payram/charge-store.d.ts +41 -0
- package/dist/billing/payram/charge-store.js +72 -0
- package/dist/billing/payram/charge-store.test.d.ts +1 -0
- package/dist/billing/payram/charge-store.test.js +64 -0
- package/dist/billing/payram/checkout.d.ts +15 -0
- package/dist/billing/payram/checkout.js +24 -0
- package/dist/billing/payram/checkout.test.d.ts +1 -0
- package/dist/billing/payram/checkout.test.js +74 -0
- package/dist/billing/payram/client.d.ts +7 -0
- package/dist/billing/payram/client.js +15 -0
- package/dist/billing/payram/client.test.d.ts +1 -0
- package/dist/billing/payram/client.test.js +52 -0
- package/dist/billing/payram/index.d.ts +8 -0
- package/dist/billing/payram/index.js +4 -0
- package/dist/billing/payram/types.d.ts +40 -0
- package/dist/billing/payram/types.js +1 -0
- package/dist/billing/payram/webhook.d.ts +19 -0
- package/dist/billing/payram/webhook.js +67 -0
- package/dist/billing/payram/webhook.test.d.ts +7 -0
- package/dist/billing/payram/webhook.test.js +248 -0
- package/dist/billing/stripe/cents-credits-boundary.test.d.ts +1 -0
- package/dist/billing/stripe/cents-credits-boundary.test.js +62 -0
- package/dist/billing/stripe/checkout.d.ts +20 -0
- package/dist/billing/stripe/checkout.js +63 -0
- package/dist/billing/stripe/checkout.test.d.ts +1 -0
- package/dist/billing/stripe/checkout.test.js +148 -0
- package/dist/billing/stripe/client.d.ts +14 -0
- package/dist/billing/stripe/client.js +33 -0
- package/dist/billing/stripe/client.test.d.ts +1 -0
- package/dist/billing/stripe/client.test.js +58 -0
- package/dist/billing/stripe/credit-prices.d.ts +63 -0
- package/dist/billing/stripe/credit-prices.js +81 -0
- package/dist/billing/stripe/credit-prices.test.d.ts +1 -0
- package/dist/billing/stripe/credit-prices.test.js +87 -0
- package/dist/billing/stripe/index.d.ts +14 -0
- package/dist/billing/stripe/index.js +8 -0
- package/dist/billing/stripe/payment-methods-detach-all.test.d.ts +1 -0
- package/dist/billing/stripe/payment-methods-detach-all.test.js +40 -0
- package/dist/billing/stripe/payment-methods.d.ts +25 -0
- package/dist/billing/stripe/payment-methods.js +53 -0
- package/dist/billing/stripe/payment-methods.test.d.ts +1 -0
- package/dist/billing/stripe/payment-methods.test.js +122 -0
- package/dist/billing/stripe/portal.d.ts +10 -0
- package/dist/billing/stripe/portal.js +16 -0
- package/dist/billing/stripe/portal.test.d.ts +1 -0
- package/dist/billing/stripe/portal.test.js +48 -0
- package/dist/billing/stripe/setup-intent.d.ts +16 -0
- package/dist/billing/stripe/setup-intent.js +22 -0
- package/dist/billing/stripe/setup-intent.test.d.ts +1 -0
- package/dist/billing/stripe/setup-intent.test.js +58 -0
- package/dist/billing/stripe/stripe-payment-processor.d.ts +49 -0
- package/dist/billing/stripe/stripe-payment-processor.js +166 -0
- package/dist/billing/stripe/stripe-payment-processor.test.d.ts +1 -0
- package/dist/billing/stripe/stripe-payment-processor.test.js +413 -0
- package/dist/billing/stripe/tenant-store.d.ts +56 -0
- package/dist/billing/stripe/tenant-store.js +119 -0
- package/dist/billing/stripe/tenant-store.test.d.ts +1 -0
- package/dist/billing/stripe/tenant-store.test.js +97 -0
- package/dist/billing/stripe/types.d.ts +49 -0
- package/dist/billing/stripe/types.js +1 -0
- package/dist/billing/webhook-seen-repository.d.ts +14 -0
- package/dist/billing/webhook-seen-repository.js +13 -0
- package/dist/config/billing-env.test.d.ts +1 -0
- package/dist/config/billing-env.test.js +48 -0
- package/dist/config/index.d.ts +46 -0
- package/dist/config/index.js +38 -0
- package/dist/config/logger.d.ts +2 -0
- package/dist/config/logger.js +11 -0
- package/dist/config/provider-endpoints.d.ts +6 -0
- package/dist/config/provider-endpoints.js +12 -0
- package/dist/credits/auto-topup-charge.d.ts +27 -0
- package/dist/credits/auto-topup-charge.js +139 -0
- package/dist/credits/auto-topup-charge.test.d.ts +1 -0
- package/dist/credits/auto-topup-charge.test.js +242 -0
- package/dist/credits/auto-topup-event-log-repository.d.ts +16 -0
- package/dist/credits/auto-topup-event-log-repository.js +18 -0
- package/dist/credits/auto-topup-event-log-repository.test.d.ts +1 -0
- package/dist/credits/auto-topup-event-log-repository.test.js +83 -0
- package/dist/credits/auto-topup-schedule.d.ts +27 -0
- package/dist/credits/auto-topup-schedule.js +66 -0
- package/dist/credits/auto-topup-schedule.test.d.ts +1 -0
- package/dist/credits/auto-topup-schedule.test.js +145 -0
- package/dist/credits/auto-topup-settings-repository.d.ts +54 -0
- package/dist/credits/auto-topup-settings-repository.js +184 -0
- package/dist/credits/auto-topup-settings-repository.test.d.ts +1 -0
- package/dist/credits/auto-topup-settings-repository.test.js +104 -0
- package/dist/credits/auto-topup-usage.d.ts +22 -0
- package/dist/credits/auto-topup-usage.js +56 -0
- package/dist/credits/auto-topup-usage.test.d.ts +1 -0
- package/dist/credits/auto-topup-usage.test.js +181 -0
- package/dist/credits/credit-expiry-cron.d.ts +19 -0
- package/dist/credits/credit-expiry-cron.js +50 -0
- package/dist/credits/credit-expiry-cron.test.d.ts +1 -0
- package/dist/credits/credit-expiry-cron.test.js +67 -0
- package/dist/credits/credit-ledger-extra.test.d.ts +1 -0
- package/dist/credits/credit-ledger-extra.test.js +40 -0
- package/dist/credits/credit-ledger.bench.d.ts +1 -0
- package/dist/credits/credit-ledger.bench.js +33 -0
- package/dist/credits/credit-ledger.d.ts +130 -0
- package/dist/credits/credit-ledger.js +293 -0
- package/dist/credits/credit-ledger.test.d.ts +4 -0
- package/dist/credits/credit-ledger.test.js +203 -0
- package/dist/credits/credit-transaction-repository.d.ts +17 -0
- package/dist/credits/credit-transaction-repository.js +35 -0
- package/dist/credits/credit-transaction-repository.test.d.ts +1 -0
- package/dist/credits/credit-transaction-repository.test.js +232 -0
- package/dist/credits/credit.d.ts +75 -0
- package/dist/credits/credit.js +139 -0
- package/dist/credits/credit.test.d.ts +1 -0
- package/dist/credits/credit.test.js +196 -0
- package/dist/credits/dividend-cron.d.ts +29 -0
- package/dist/credits/dividend-cron.js +88 -0
- package/dist/credits/dividend-cron.test.d.ts +1 -0
- package/dist/credits/dividend-cron.test.js +128 -0
- package/dist/credits/dividend-repository.d.ts +29 -0
- package/dist/credits/dividend-repository.js +126 -0
- package/dist/credits/dividend-repository.test.d.ts +1 -0
- package/dist/credits/dividend-repository.test.js +176 -0
- package/dist/credits/index.d.ts +9 -0
- package/dist/credits/index.js +5 -0
- package/dist/credits/repository-types.d.ts +29 -0
- package/dist/credits/repository-types.js +1 -0
- package/dist/credits/signup-grant.d.ts +12 -0
- package/dist/credits/signup-grant.js +35 -0
- package/dist/credits/signup-grant.test.d.ts +1 -0
- package/dist/credits/signup-grant.test.js +51 -0
- package/dist/credits/tenant-customer-repository.d.ts +30 -0
- package/dist/credits/tenant-customer-repository.js +5 -0
- package/dist/db/auth-user-repository.d.ts +46 -0
- package/dist/db/auth-user-repository.js +90 -0
- package/dist/db/credit-column.d.ts +27 -0
- package/dist/db/credit-column.js +13 -0
- package/dist/db/index.d.ts +14 -0
- package/dist/db/index.js +8 -0
- package/dist/db/schema/account-deletion-requests.d.ts +203 -0
- package/dist/db/schema/account-deletion-requests.js +36 -0
- package/dist/db/schema/account-export-requests.d.ts +148 -0
- package/dist/db/schema/account-export-requests.js +19 -0
- package/dist/db/schema/admin-audit.d.ts +194 -0
- package/dist/db/schema/admin-audit.js +21 -0
- package/dist/db/schema/admin-users.d.ts +177 -0
- package/dist/db/schema/admin-users.js +23 -0
- package/dist/db/schema/affiliate-fraud.d.ts +160 -0
- package/dist/db/schema/affiliate-fraud.js +18 -0
- package/dist/db/schema/affiliate.d.ts +277 -0
- package/dist/db/schema/affiliate.js +32 -0
- package/dist/db/schema/coupon-codes.d.ts +143 -0
- package/dist/db/schema/coupon-codes.js +17 -0
- package/dist/db/schema/credit-auto-topup-settings.d.ts +232 -0
- package/dist/db/schema/credit-auto-topup-settings.js +27 -0
- package/dist/db/schema/credit-auto-topup.d.ts +130 -0
- package/dist/db/schema/credit-auto-topup.js +21 -0
- package/dist/db/schema/credits.d.ts +283 -0
- package/dist/db/schema/credits.js +38 -0
- package/dist/db/schema/dividend-distributions.d.ts +130 -0
- package/dist/db/schema/dividend-distributions.js +19 -0
- package/dist/db/schema/email-notifications.d.ts +99 -0
- package/dist/db/schema/email-notifications.js +21 -0
- package/dist/db/schema/index.d.ts +33 -0
- package/dist/db/schema/index.js +33 -0
- package/dist/db/schema/meter-events.d.ts +599 -0
- package/dist/db/schema/meter-events.js +55 -0
- package/dist/db/schema/notification-preferences.d.ts +165 -0
- package/dist/db/schema/notification-preferences.js +18 -0
- package/dist/db/schema/notification-queue.d.ts +236 -0
- package/dist/db/schema/notification-queue.js +40 -0
- package/dist/db/schema/org-memberships.d.ts +63 -0
- package/dist/db/schema/org-memberships.js +15 -0
- package/dist/db/schema/organization-members.d.ts +235 -0
- package/dist/db/schema/organization-members.js +27 -0
- package/dist/db/schema/payram.d.ts +164 -0
- package/dist/db/schema/payram.js +21 -0
- package/dist/db/schema/platform-api-keys.d.ts +143 -0
- package/dist/db/schema/platform-api-keys.js +20 -0
- package/dist/db/schema/promotion-redemptions.d.ts +143 -0
- package/dist/db/schema/promotion-redemptions.js +18 -0
- package/dist/db/schema/promotions.d.ts +445 -0
- package/dist/db/schema/promotions.js +48 -0
- package/dist/db/schema/provider-credentials.d.ts +201 -0
- package/dist/db/schema/provider-credentials.js +36 -0
- package/dist/db/schema/rate-limit-entries.d.ts +75 -0
- package/dist/db/schema/rate-limit-entries.js +7 -0
- package/dist/db/schema/secret-audit-log.d.ts +109 -0
- package/dist/db/schema/secret-audit-log.js +15 -0
- package/dist/db/schema/session-usage.d.ts +194 -0
- package/dist/db/schema/session-usage.js +19 -0
- package/dist/db/schema/spending-limits.d.ts +92 -0
- package/dist/db/schema/spending-limits.js +8 -0
- package/dist/db/schema/tenant-addons.d.ts +58 -0
- package/dist/db/schema/tenant-addons.js +9 -0
- package/dist/db/schema/tenant-api-keys.d.ts +131 -0
- package/dist/db/schema/tenant-api-keys.js +21 -0
- package/dist/db/schema/tenant-capability-settings.d.ts +79 -0
- package/dist/db/schema/tenant-capability-settings.js +12 -0
- package/dist/db/schema/tenant-customers.d.ts +303 -0
- package/dist/db/schema/tenant-customers.js +25 -0
- package/dist/db/schema/tenants.d.ts +126 -0
- package/dist/db/schema/tenants.js +18 -0
- package/dist/db/schema/user-roles.d.ts +98 -0
- package/dist/db/schema/user-roles.js +18 -0
- package/dist/db/schema/webhook-seen-events.d.ts +58 -0
- package/dist/db/schema/webhook-seen-events.js +9 -0
- package/dist/email/billing-emails.d.ts +51 -0
- package/dist/email/billing-emails.js +163 -0
- package/dist/email/billing-emails.test.d.ts +1 -0
- package/dist/email/billing-emails.test.js +162 -0
- package/dist/email/client.d.ts +51 -0
- package/dist/email/client.js +102 -0
- package/dist/email/client.test.d.ts +1 -0
- package/dist/email/client.test.js +120 -0
- package/dist/email/drizzle-billing-email-repository.d.ts +21 -0
- package/dist/email/drizzle-billing-email-repository.js +36 -0
- package/dist/email/drizzle-billing-email-repository.test.d.ts +1 -0
- package/dist/email/drizzle-billing-email-repository.test.js +42 -0
- package/dist/email/index.d.ts +33 -0
- package/dist/email/index.js +22 -0
- package/dist/email/notification-preferences-store.d.ts +12 -0
- package/dist/email/notification-preferences-store.js +82 -0
- package/dist/email/notification-preferences-store.test.d.ts +1 -0
- package/dist/email/notification-preferences-store.test.js +86 -0
- package/dist/email/notification-queue-store.d.ts +25 -0
- package/dist/email/notification-queue-store.js +97 -0
- package/dist/email/notification-queue-store.test.d.ts +1 -0
- package/dist/email/notification-queue-store.test.js +177 -0
- package/dist/email/notification-repository-types.d.ts +70 -0
- package/dist/email/notification-repository-types.js +6 -0
- package/dist/email/notification-service.d.ts +41 -0
- package/dist/email/notification-service.js +196 -0
- package/dist/email/notification-service.test.d.ts +1 -0
- package/dist/email/notification-service.test.js +160 -0
- package/dist/email/notification-templates.d.ts +18 -0
- package/dist/email/notification-templates.js +574 -0
- package/dist/email/notification-templates.test.d.ts +1 -0
- package/dist/email/notification-templates.test.js +238 -0
- package/dist/email/notification-worker.d.ts +24 -0
- package/dist/email/notification-worker.js +109 -0
- package/dist/email/notification-worker.test.d.ts +1 -0
- package/dist/email/notification-worker.test.js +153 -0
- package/dist/email/require-verified.d.ts +25 -0
- package/dist/email/require-verified.js +52 -0
- package/dist/email/require-verified.test.d.ts +1 -0
- package/dist/email/require-verified.test.js +62 -0
- package/dist/email/resend-adapter.d.ts +47 -0
- package/dist/email/resend-adapter.js +137 -0
- package/dist/email/resend-adapter.test.d.ts +1 -0
- package/dist/email/resend-adapter.test.js +190 -0
- package/dist/email/templates.d.ts +22 -0
- package/dist/email/templates.js +359 -0
- package/dist/email/templates.test.d.ts +1 -0
- package/dist/email/templates.test.js +170 -0
- package/dist/email/verification.d.ts +42 -0
- package/dist/email/verification.js +83 -0
- package/dist/email/verification.test.d.ts +1 -0
- package/dist/email/verification.test.js +141 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +23 -0
- package/dist/metering/aggregator.d.ts +54 -0
- package/dist/metering/aggregator.js +123 -0
- package/dist/metering/aggregator.test.d.ts +1 -0
- package/dist/metering/aggregator.test.js +179 -0
- package/dist/metering/dlq.d.ts +31 -0
- package/dist/metering/dlq.js +82 -0
- package/dist/metering/dlq.test.d.ts +1 -0
- package/dist/metering/dlq.test.js +117 -0
- package/dist/metering/drizzle-usage-summary-repository.d.ts +67 -0
- package/dist/metering/drizzle-usage-summary-repository.js +98 -0
- package/dist/metering/emitter.d.ts +66 -0
- package/dist/metering/emitter.js +185 -0
- package/dist/metering/emitter.test.d.ts +1 -0
- package/dist/metering/emitter.test.js +171 -0
- package/dist/metering/index.d.ts +11 -0
- package/dist/metering/index.js +5 -0
- package/dist/metering/load-test.bench.d.ts +1 -0
- package/dist/metering/load-test.bench.js +103 -0
- package/dist/metering/meter-event-repository.d.ts +33 -0
- package/dist/metering/meter-event-repository.js +58 -0
- package/dist/metering/meter-repositories.test.d.ts +1 -0
- package/dist/metering/meter-repositories.test.js +419 -0
- package/dist/metering/metering.test.d.ts +1 -0
- package/dist/metering/metering.test.js +1046 -0
- package/dist/metering/reconciliation-cron.d.ts +37 -0
- package/dist/metering/reconciliation-cron.js +85 -0
- package/dist/metering/reconciliation-cron.test.d.ts +1 -0
- package/dist/metering/reconciliation-cron.test.js +162 -0
- package/dist/metering/reconciliation-repository.d.ts +27 -0
- package/dist/metering/reconciliation-repository.js +43 -0
- package/dist/metering/reconciliation-repository.test.d.ts +1 -0
- package/dist/metering/reconciliation-repository.test.js +160 -0
- package/dist/metering/types.d.ts +88 -0
- package/dist/metering/types.js +1 -0
- package/dist/metering/wal.d.ts +49 -0
- package/dist/metering/wal.js +124 -0
- package/dist/metering/wal.test.d.ts +1 -0
- package/dist/metering/wal.test.js +175 -0
- package/dist/middleware/csrf.d.ts +24 -0
- package/dist/middleware/csrf.js +80 -0
- package/dist/middleware/csrf.test.d.ts +1 -0
- package/dist/middleware/csrf.test.js +152 -0
- package/dist/middleware/drizzle-rate-limit-repository.d.ts +9 -0
- package/dist/middleware/drizzle-rate-limit-repository.js +52 -0
- package/dist/middleware/drizzle-rate-limit-repository.test.d.ts +1 -0
- package/dist/middleware/drizzle-rate-limit-repository.test.js +74 -0
- package/dist/middleware/get-client-ip.d.ts +22 -0
- package/dist/middleware/get-client-ip.js +51 -0
- package/dist/middleware/get-client-ip.test.d.ts +1 -0
- package/dist/middleware/get-client-ip.test.js +40 -0
- package/dist/middleware/index.d.ts +5 -0
- package/dist/middleware/index.js +4 -0
- package/dist/middleware/rate-limit-repository.d.ts +19 -0
- package/dist/middleware/rate-limit-repository.js +1 -0
- package/dist/middleware/rate-limit.d.ts +57 -0
- package/dist/middleware/rate-limit.js +109 -0
- package/dist/middleware/rate-limit.test.d.ts +1 -0
- package/dist/middleware/rate-limit.test.js +247 -0
- package/dist/security/credential-vault/audit-repository.d.ts +27 -0
- package/dist/security/credential-vault/audit-repository.js +42 -0
- package/dist/security/credential-vault/audit-repository.test.d.ts +1 -0
- package/dist/security/credential-vault/audit-repository.test.js +78 -0
- package/dist/security/credential-vault/credential-repository.d.ts +94 -0
- package/dist/security/credential-vault/credential-repository.js +145 -0
- package/dist/security/credential-vault/credential-repository.test.d.ts +1 -0
- package/dist/security/credential-vault/credential-repository.test.js +206 -0
- package/dist/security/credential-vault/index.d.ts +12 -0
- package/dist/security/credential-vault/index.js +6 -0
- package/dist/security/credential-vault/key-rotation.d.ts +18 -0
- package/dist/security/credential-vault/key-rotation.js +52 -0
- package/dist/security/credential-vault/key-rotation.test.d.ts +1 -0
- package/dist/security/credential-vault/key-rotation.test.js +95 -0
- package/dist/security/credential-vault/migrate-plaintext.d.ts +15 -0
- package/dist/security/credential-vault/migrate-plaintext.js +80 -0
- package/dist/security/credential-vault/migrate-plaintext.test.d.ts +1 -0
- package/dist/security/credential-vault/migrate-plaintext.test.js +111 -0
- package/dist/security/credential-vault/migration-check.d.ts +15 -0
- package/dist/security/credential-vault/migration-check.js +71 -0
- package/dist/security/credential-vault/migration-check.test.d.ts +1 -0
- package/dist/security/credential-vault/migration-check.test.js +457 -0
- package/dist/security/credential-vault/store.d.ts +106 -0
- package/dist/security/credential-vault/store.js +181 -0
- package/dist/security/credential-vault/store.test.d.ts +1 -0
- package/dist/security/credential-vault/store.test.js +482 -0
- package/dist/security/encryption.d.ts +22 -0
- package/dist/security/encryption.js +53 -0
- package/dist/security/encryption.test.d.ts +1 -0
- package/dist/security/encryption.test.js +95 -0
- package/dist/security/host-validation.d.ts +11 -0
- package/dist/security/host-validation.js +108 -0
- package/dist/security/host-validation.test.d.ts +1 -0
- package/dist/security/host-validation.test.js +106 -0
- package/dist/security/index.d.ts +11 -0
- package/dist/security/index.js +11 -0
- package/dist/security/key-audit.d.ts +16 -0
- package/dist/security/key-audit.js +35 -0
- package/dist/security/key-audit.test.d.ts +1 -0
- package/dist/security/key-audit.test.js +50 -0
- package/dist/security/key-injection.d.ts +28 -0
- package/dist/security/key-injection.js +57 -0
- package/dist/security/key-injection.test.d.ts +1 -0
- package/dist/security/key-injection.test.js +97 -0
- package/dist/security/key-validation.d.ts +16 -0
- package/dist/security/key-validation.js +78 -0
- package/dist/security/key-validation.test.d.ts +1 -0
- package/dist/security/key-validation.test.js +87 -0
- package/dist/security/redirect-allowlist.d.ts +6 -0
- package/dist/security/redirect-allowlist.js +36 -0
- package/dist/security/redirect-allowlist.test.d.ts +1 -0
- package/dist/security/redirect-allowlist.test.js +55 -0
- package/dist/security/tenant-keys/capability-settings-store.d.ts +22 -0
- package/dist/security/tenant-keys/capability-settings-store.js +33 -0
- package/dist/security/tenant-keys/capability-settings-store.test.d.ts +1 -0
- package/dist/security/tenant-keys/capability-settings-store.test.js +77 -0
- package/dist/security/tenant-keys/index.d.ts +10 -0
- package/dist/security/tenant-keys/index.js +5 -0
- package/dist/security/tenant-keys/key-resolution-repository.d.ts +15 -0
- package/dist/security/tenant-keys/key-resolution-repository.js +18 -0
- package/dist/security/tenant-keys/key-resolution-repository.test.d.ts +1 -0
- package/dist/security/tenant-keys/key-resolution-repository.test.js +72 -0
- package/dist/security/tenant-keys/key-resolution.d.ts +39 -0
- package/dist/security/tenant-keys/key-resolution.js +59 -0
- package/dist/security/tenant-keys/key-resolution.test.d.ts +1 -0
- package/dist/security/tenant-keys/key-resolution.test.js +97 -0
- package/dist/security/tenant-keys/org-key-resolution.d.ts +30 -0
- package/dist/security/tenant-keys/org-key-resolution.js +50 -0
- package/dist/security/tenant-keys/org-key-resolution.test.d.ts +1 -0
- package/dist/security/tenant-keys/org-key-resolution.test.js +103 -0
- package/dist/security/tenant-keys/tenant-key-repository.d.ts +36 -0
- package/dist/security/tenant-keys/tenant-key-repository.js +96 -0
- package/dist/security/tenant-keys/tenant-key-repository.test.d.ts +1 -0
- package/dist/security/tenant-keys/tenant-key-repository.test.js +114 -0
- package/dist/security/types.d.ts +35 -0
- package/dist/security/types.js +15 -0
- package/dist/tenancy/drizzle-org-repository.d.ts +40 -0
- package/dist/tenancy/drizzle-org-repository.js +126 -0
- package/dist/tenancy/index.d.ts +6 -0
- package/dist/tenancy/index.js +3 -0
- package/dist/tenancy/org-member-repository.d.ts +57 -0
- package/dist/tenancy/org-member-repository.js +99 -0
- package/dist/tenancy/org-repository.test.d.ts +1 -0
- package/dist/tenancy/org-repository.test.js +143 -0
- package/dist/tenancy/org-service.d.ts +70 -0
- package/dist/tenancy/org-service.js +223 -0
- package/dist/tenancy/org-service.test.d.ts +1 -0
- package/dist/tenancy/org-service.test.js +550 -0
- package/dist/test/db.d.ts +33 -0
- package/dist/test/db.js +65 -0
- package/dist/trpc/index.d.ts +1 -0
- package/dist/trpc/index.js +1 -0
- package/dist/trpc/init.d.ts +49 -0
- package/dist/trpc/init.js +108 -0
- package/dist/trpc/init.test.d.ts +1 -0
- package/dist/trpc/init.test.js +154 -0
- package/drizzle/migrations/0000_slippery_mandrill.sql +559 -0
- package/drizzle/migrations/meta/0000_snapshot.json +4374 -0
- package/drizzle/migrations/meta/_journal.json +13 -0
- package/drizzle.config.ts +41 -0
- package/package.json +64 -0
- package/src/admin/admin-audit-log-repository.ts +135 -0
- package/src/admin/audit-log.ts +111 -0
- package/src/admin/index.ts +6 -0
- package/src/admin/role-store.ts +134 -0
- package/src/auth/api-key-repository.test.ts +63 -0
- package/src/auth/api-key-repository.ts +46 -0
- package/src/auth/auth.test.ts +166 -0
- package/src/auth/better-auth.ts +216 -0
- package/src/auth/index.ts +520 -0
- package/src/auth/login-history-repository.test.ts +54 -0
- package/src/auth/login-history-repository.ts +28 -0
- package/src/auth/middleware.test.ts +264 -0
- package/src/auth/middleware.ts +117 -0
- package/src/auth/scoped-tokens.test.ts +362 -0
- package/src/auth/tenant-access.test.ts +69 -0
- package/src/auth/user-creator.test.ts +98 -0
- package/src/auth/user-creator.ts +54 -0
- package/src/auth/user-role-repository.test.ts +149 -0
- package/src/auth/user-role-repository.ts +67 -0
- package/src/billing/drizzle-webhook-seen-repository.ts +34 -0
- package/src/billing/index.ts +22 -0
- package/src/billing/payment-processor.test.ts +93 -0
- package/src/billing/payment-processor.ts +150 -0
- package/src/billing/payram/cents-credits-boundary.test.ts +84 -0
- package/src/billing/payram/charge-store.test.ts +84 -0
- package/src/billing/payram/charge-store.ts +109 -0
- package/src/billing/payram/checkout.test.ts +99 -0
- package/src/billing/payram/checkout.ts +40 -0
- package/src/billing/payram/client.test.ts +62 -0
- package/src/billing/payram/client.ts +21 -0
- package/src/billing/payram/index.ts +14 -0
- package/src/billing/payram/types.ts +44 -0
- package/src/billing/payram/webhook.test.ts +318 -0
- package/src/billing/payram/webhook.ts +97 -0
- package/src/billing/stripe/cents-credits-boundary.test.ts +70 -0
- package/src/billing/stripe/checkout.test.ts +186 -0
- package/src/billing/stripe/checkout.ts +82 -0
- package/src/billing/stripe/client.test.ts +64 -0
- package/src/billing/stripe/client.ts +39 -0
- package/src/billing/stripe/credit-prices.test.ts +114 -0
- package/src/billing/stripe/credit-prices.ts +113 -0
- package/src/billing/stripe/index.ts +14 -0
- package/src/billing/stripe/payment-methods-detach-all.test.ts +53 -0
- package/src/billing/stripe/payment-methods.test.ts +157 -0
- package/src/billing/stripe/payment-methods.ts +76 -0
- package/src/billing/stripe/portal.test.ts +63 -0
- package/src/billing/stripe/portal.ts +25 -0
- package/src/billing/stripe/setup-intent.test.ts +78 -0
- package/src/billing/stripe/setup-intent.ts +34 -0
- package/src/billing/stripe/stripe-payment-processor.test.ts +517 -0
- package/src/billing/stripe/stripe-payment-processor.ts +255 -0
- package/src/billing/stripe/tenant-store.test.ts +124 -0
- package/src/billing/stripe/tenant-store.ts +151 -0
- package/src/billing/stripe/types.ts +53 -0
- package/src/billing/webhook-seen-repository.ts +24 -0
- package/src/config/billing-env.test.ts +54 -0
- package/src/config/index.ts +44 -0
- package/src/config/logger.ts +12 -0
- package/src/config/provider-endpoints.ts +14 -0
- package/src/credits/auto-topup-charge.test.ts +292 -0
- package/src/credits/auto-topup-charge.ts +171 -0
- package/src/credits/auto-topup-event-log-repository.test.ts +99 -0
- package/src/credits/auto-topup-event-log-repository.ts +30 -0
- package/src/credits/auto-topup-schedule.test.ts +179 -0
- package/src/credits/auto-topup-schedule.ts +93 -0
- package/src/credits/auto-topup-settings-repository.test.ts +123 -0
- package/src/credits/auto-topup-settings-repository.ts +245 -0
- package/src/credits/auto-topup-usage.test.ts +220 -0
- package/src/credits/auto-topup-usage.ts +68 -0
- package/src/credits/credit-expiry-cron.test.ts +125 -0
- package/src/credits/credit-expiry-cron.ts +76 -0
- package/src/credits/credit-ledger-extra.test.ts +57 -0
- package/src/credits/credit-ledger.bench.ts +56 -0
- package/src/credits/credit-ledger.test.ts +276 -0
- package/src/credits/credit-ledger.ts +450 -0
- package/src/credits/credit-transaction-repository.test.ts +274 -0
- package/src/credits/credit-transaction-repository.ts +62 -0
- package/src/credits/credit.test.ts +234 -0
- package/src/credits/credit.ts +160 -0
- package/src/credits/dividend-cron.test.ts +158 -0
- package/src/credits/dividend-cron.ts +127 -0
- package/src/credits/dividend-repository.test.ts +223 -0
- package/src/credits/dividend-repository.ts +182 -0
- package/src/credits/index.ts +25 -0
- package/src/credits/repository-types.ts +33 -0
- package/src/credits/signup-grant.test.ts +63 -0
- package/src/credits/signup-grant.ts +44 -0
- package/src/credits/tenant-customer-repository.ts +28 -0
- package/src/db/auth-user-repository.ts +124 -0
- package/src/db/credit-column.ts +17 -0
- package/src/db/index.ts +21 -0
- package/src/db/schema/account-deletion-requests.ts +41 -0
- package/src/db/schema/account-export-requests.ts +24 -0
- package/src/db/schema/admin-audit.ts +26 -0
- package/src/db/schema/admin-users.ts +31 -0
- package/src/db/schema/affiliate-fraud.ts +23 -0
- package/src/db/schema/affiliate.ts +38 -0
- package/src/db/schema/coupon-codes.ts +22 -0
- package/src/db/schema/credit-auto-topup-settings.ts +32 -0
- package/src/db/schema/credit-auto-topup.ts +26 -0
- package/src/db/schema/credits.ts +44 -0
- package/src/db/schema/dividend-distributions.ts +24 -0
- package/src/db/schema/email-notifications.ts +26 -0
- package/src/db/schema/index.ts +33 -0
- package/src/db/schema/meter-events.ts +70 -0
- package/src/db/schema/notification-preferences.ts +19 -0
- package/src/db/schema/notification-queue.ts +45 -0
- package/src/db/schema/org-memberships.ts +20 -0
- package/src/db/schema/organization-members.ts +37 -0
- package/src/db/schema/payram.ts +26 -0
- package/src/db/schema/platform-api-keys.ts +25 -0
- package/src/db/schema/promotion-redemptions.ts +23 -0
- package/src/db/schema/promotions.ts +57 -0
- package/src/db/schema/provider-credentials.ts +41 -0
- package/src/db/schema/rate-limit-entries.ts +12 -0
- package/src/db/schema/secret-audit-log.ts +20 -0
- package/src/db/schema/session-usage.ts +24 -0
- package/src/db/schema/spending-limits.ts +9 -0
- package/src/db/schema/tenant-addons.ts +14 -0
- package/src/db/schema/tenant-api-keys.ts +26 -0
- package/src/db/schema/tenant-capability-settings.ts +17 -0
- package/src/db/schema/tenant-customers.ts +35 -0
- package/src/db/schema/tenants.ts +23 -0
- package/src/db/schema/user-roles.ts +23 -0
- package/src/db/schema/webhook-seen-events.ts +14 -0
- package/src/email/billing-emails.test.ts +198 -0
- package/src/email/billing-emails.ts +211 -0
- package/src/email/client.test.ts +149 -0
- package/src/email/client.ts +137 -0
- package/src/email/drizzle-billing-email-repository.test.ts +52 -0
- package/src/email/drizzle-billing-email-repository.ts +59 -0
- package/src/email/index.ts +57 -0
- package/src/email/notification-preferences-store.test.ts +102 -0
- package/src/email/notification-preferences-store.ts +90 -0
- package/src/email/notification-queue-store.test.ts +215 -0
- package/src/email/notification-queue-store.ts +127 -0
- package/src/email/notification-repository-types.ts +101 -0
- package/src/email/notification-service.test.ts +178 -0
- package/src/email/notification-service.ts +265 -0
- package/src/email/notification-templates.test.ts +261 -0
- package/src/email/notification-templates.ts +727 -0
- package/src/email/notification-worker.test.ts +189 -0
- package/src/email/notification-worker.ts +133 -0
- package/src/email/require-verified.ts +65 -0
- package/src/email/resend-adapter.test.ts +253 -0
- package/src/email/resend-adapter.ts +157 -0
- package/src/email/templates.test.ts +217 -0
- package/src/email/templates.ts +469 -0
- package/src/email/verification.test.ts +185 -0
- package/src/email/verification.ts +110 -0
- package/src/index.ts +51 -0
- package/src/metering/aggregator.test.ts +239 -0
- package/src/metering/aggregator.ts +160 -0
- package/src/metering/dlq.test.ts +134 -0
- package/src/metering/dlq.ts +102 -0
- package/src/metering/drizzle-usage-summary-repository.ts +167 -0
- package/src/metering/emitter.test.ts +202 -0
- package/src/metering/emitter.ts +227 -0
- package/src/metering/index.ts +21 -0
- package/src/metering/load-test.bench.ts +130 -0
- package/src/metering/meter-event-repository.ts +87 -0
- package/src/metering/meter-repositories.test.ts +491 -0
- package/src/metering/metering.test.ts +1317 -0
- package/src/metering/reconciliation-cron.test.ts +202 -0
- package/src/metering/reconciliation-cron.ts +134 -0
- package/src/metering/reconciliation-repository.test.ts +196 -0
- package/src/metering/reconciliation-repository.ts +83 -0
- package/src/metering/types.ts +93 -0
- package/src/metering/wal.test.ts +222 -0
- package/src/metering/wal.ts +139 -0
- package/src/middleware/csrf.test.ts +178 -0
- package/src/middleware/csrf.ts +101 -0
- package/src/middleware/drizzle-rate-limit-repository.test.ts +97 -0
- package/src/middleware/drizzle-rate-limit-repository.ts +57 -0
- package/src/middleware/get-client-ip.test.ts +49 -0
- package/src/middleware/get-client-ip.ts +62 -0
- package/src/middleware/index.ts +12 -0
- package/src/middleware/rate-limit-repository.ts +22 -0
- package/src/middleware/rate-limit.test.ts +338 -0
- package/src/middleware/rate-limit.ts +169 -0
- package/src/security/credential-vault/audit-repository.test.ts +91 -0
- package/src/security/credential-vault/audit-repository.ts +64 -0
- package/src/security/credential-vault/credential-repository.test.ts +264 -0
- package/src/security/credential-vault/credential-repository.ts +233 -0
- package/src/security/credential-vault/index.ts +26 -0
- package/src/security/credential-vault/key-rotation.test.ts +139 -0
- package/src/security/credential-vault/key-rotation.ts +70 -0
- package/src/security/credential-vault/migrate-plaintext.test.ts +138 -0
- package/src/security/credential-vault/migrate-plaintext.ts +101 -0
- package/src/security/credential-vault/migration-check.test.ts +533 -0
- package/src/security/credential-vault/migration-check.ts +88 -0
- package/src/security/credential-vault/store.test.ts +569 -0
- package/src/security/credential-vault/store.ts +284 -0
- package/src/security/encryption.test.ts +114 -0
- package/src/security/encryption.ts +65 -0
- package/src/security/host-validation.test.ts +136 -0
- package/src/security/host-validation.ts +116 -0
- package/src/security/index.ts +59 -0
- package/src/security/key-audit.test.ts +57 -0
- package/src/security/key-audit.ts +45 -0
- package/src/security/key-injection.test.ts +131 -0
- package/src/security/key-injection.ts +71 -0
- package/src/security/key-validation.test.ts +111 -0
- package/src/security/key-validation.ts +84 -0
- package/src/security/redirect-allowlist.test.ts +70 -0
- package/src/security/redirect-allowlist.ts +35 -0
- package/src/security/tenant-keys/capability-settings-store.test.ts +98 -0
- package/src/security/tenant-keys/capability-settings-store.ts +53 -0
- package/src/security/tenant-keys/index.ts +10 -0
- package/src/security/tenant-keys/key-resolution-repository.test.ts +95 -0
- package/src/security/tenant-keys/key-resolution-repository.ts +31 -0
- package/src/security/tenant-keys/key-resolution.test.ts +173 -0
- package/src/security/tenant-keys/key-resolution.ts +87 -0
- package/src/security/tenant-keys/org-key-resolution.test.ts +217 -0
- package/src/security/tenant-keys/org-key-resolution.ts +76 -0
- package/src/security/tenant-keys/tenant-key-repository.test.ts +143 -0
- package/src/security/tenant-keys/tenant-key-repository.ts +130 -0
- package/src/security/types.ts +43 -0
- package/src/tenancy/drizzle-org-repository.ts +169 -0
- package/src/tenancy/index.ts +6 -0
- package/src/tenancy/org-member-repository.ts +159 -0
- package/src/tenancy/org-repository.test.ts +172 -0
- package/src/tenancy/org-service.test.ts +634 -0
- package/src/tenancy/org-service.ts +290 -0
- package/src/test/db.ts +97 -0
- package/src/trpc/index.ts +11 -0
- package/src/trpc/init.test.ts +196 -0
- package/src/trpc/init.ts +138 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email Templates — HTML and plain text templates for all transactional emails.
|
|
3
|
+
*
|
|
4
|
+
* Each template has an HTML and a plain text version. HTML uses inline styles
|
|
5
|
+
* for maximum email client compatibility. All user-supplied values are escaped
|
|
6
|
+
* to prevent XSS.
|
|
7
|
+
*/
|
|
8
|
+
import { escapeHtml } from "./resend-adapter.js";
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Shared layout helpers
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
function wrapHtml(title, bodyContent) {
|
|
13
|
+
return `<!DOCTYPE html>
|
|
14
|
+
<html>
|
|
15
|
+
<head>
|
|
16
|
+
<meta charset="utf-8">
|
|
17
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
18
|
+
<title>${escapeHtml(title)}</title>
|
|
19
|
+
</head>
|
|
20
|
+
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f4f4f4;">
|
|
21
|
+
<table role="presentation" style="width: 100%; border-collapse: collapse;">
|
|
22
|
+
<tr>
|
|
23
|
+
<td style="padding: 40px 0; text-align: center;">
|
|
24
|
+
<table role="presentation" style="width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
|
25
|
+
${bodyContent}
|
|
26
|
+
</table>
|
|
27
|
+
<p style="margin-top: 20px; color: #a0aec0; font-size: 12px;">© ${new Date().getFullYear()} WOPR Network. All rights reserved.</p>
|
|
28
|
+
</td>
|
|
29
|
+
</tr>
|
|
30
|
+
</table>
|
|
31
|
+
</body>
|
|
32
|
+
</html>`;
|
|
33
|
+
}
|
|
34
|
+
function heading(text) {
|
|
35
|
+
return `<tr>
|
|
36
|
+
<td style="padding: 40px 40px 20px 40px; text-align: center;">
|
|
37
|
+
<h1 style="margin: 0; font-size: 24px; font-weight: 600; color: #1a1a1a;">${escapeHtml(text)}</h1>
|
|
38
|
+
</td>
|
|
39
|
+
</tr>`;
|
|
40
|
+
}
|
|
41
|
+
function paragraph(html) {
|
|
42
|
+
return `<tr>
|
|
43
|
+
<td style="padding: 0 40px 20px 40px; color: #4a5568; font-size: 16px; line-height: 24px;">
|
|
44
|
+
${html}
|
|
45
|
+
</td>
|
|
46
|
+
</tr>`;
|
|
47
|
+
}
|
|
48
|
+
function button(url, label, color = "#2563eb") {
|
|
49
|
+
return `<tr>
|
|
50
|
+
<td style="padding: 0 40px 30px 40px; text-align: center;">
|
|
51
|
+
<a href="${escapeHtml(url)}" style="display: inline-block; padding: 12px 32px; background-color: ${color}; color: #ffffff; text-decoration: none; font-weight: 600; border-radius: 6px; font-size: 16px;">${escapeHtml(label)}</a>
|
|
52
|
+
</td>
|
|
53
|
+
</tr>`;
|
|
54
|
+
}
|
|
55
|
+
function footer(text) {
|
|
56
|
+
return `<tr>
|
|
57
|
+
<td style="padding: 0 40px 40px 40px; color: #718096; font-size: 14px; line-height: 20px; border-top: 1px solid #e2e8f0;">
|
|
58
|
+
<p style="margin-top: 20px;">${text}</p>
|
|
59
|
+
</td>
|
|
60
|
+
</tr>`;
|
|
61
|
+
}
|
|
62
|
+
function unsubscribeFooter(unsubscribeUrl) {
|
|
63
|
+
if (!unsubscribeUrl)
|
|
64
|
+
return "";
|
|
65
|
+
return `<tr>
|
|
66
|
+
<td style="padding: 0 40px 20px 40px; text-align: center; color: #a0aec0; font-size: 12px;">
|
|
67
|
+
<a href="${escapeHtml(unsubscribeUrl)}" style="color: #a0aec0; text-decoration: underline;">Unsubscribe from billing notifications</a>
|
|
68
|
+
</td>
|
|
69
|
+
</tr>`;
|
|
70
|
+
}
|
|
71
|
+
function unsubscribeText(unsubscribeUrl) {
|
|
72
|
+
if (!unsubscribeUrl)
|
|
73
|
+
return "";
|
|
74
|
+
return `\n\nTo unsubscribe from billing notifications: ${unsubscribeUrl}`;
|
|
75
|
+
}
|
|
76
|
+
/** Build the unsubscribe URL from a creditsUrl by deriving the origin. */
|
|
77
|
+
function buildUnsubscribeUrl(creditsUrl) {
|
|
78
|
+
try {
|
|
79
|
+
const base = new URL(creditsUrl);
|
|
80
|
+
return `${base.origin}/settings/notifications`;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// If creditsUrl is not a valid absolute URL, fall back to simple concatenation.
|
|
84
|
+
return `${creditsUrl.replace(/\/+$/, "").split("/").slice(0, 3).join("/")}/settings/notifications`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// 1. Verify Email
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
export function verifyEmailTemplate(verifyUrl, email) {
|
|
91
|
+
const escapedEmail = escapeHtml(email);
|
|
92
|
+
const escapedUrl = escapeHtml(verifyUrl);
|
|
93
|
+
const html = wrapHtml("Verify Your Email", [
|
|
94
|
+
heading("Verify Your Email"),
|
|
95
|
+
paragraph(`<p>Thanks for signing up for WOPR! Please verify your email address (<strong>${escapedEmail}</strong>) to activate your account.</p>
|
|
96
|
+
<p>Click the button below to verify. This link will expire in 24 hours.</p>`),
|
|
97
|
+
button(verifyUrl, "Verify Email"),
|
|
98
|
+
paragraph(`<p style="color: #718096; font-size: 14px;">Or copy and paste this URL into your browser:</p>
|
|
99
|
+
<p style="word-break: break-all; color: #2563eb; font-size: 14px;">${escapedUrl}</p>`),
|
|
100
|
+
footer("If you didn't create a WOPR account, you can safely ignore this email."),
|
|
101
|
+
].join("\n"));
|
|
102
|
+
const text = `Verify Your Email
|
|
103
|
+
|
|
104
|
+
Thanks for signing up for WOPR! Please verify your email address (${email}) to activate your account.
|
|
105
|
+
|
|
106
|
+
Click the link below to verify. This link will expire in 24 hours.
|
|
107
|
+
|
|
108
|
+
${verifyUrl}
|
|
109
|
+
|
|
110
|
+
If you didn't create a WOPR account, you can safely ignore this email.
|
|
111
|
+
|
|
112
|
+
(c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
|
|
113
|
+
return { subject: "Verify your WOPR account", html, text };
|
|
114
|
+
}
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// 2. Welcome
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
export function welcomeTemplate(email) {
|
|
119
|
+
const escapedEmail = escapeHtml(email);
|
|
120
|
+
const html = wrapHtml("Welcome to WOPR", [
|
|
121
|
+
heading("Welcome to WOPR!"),
|
|
122
|
+
paragraph(`<p>Hi <strong>${escapedEmail}</strong>,</p>
|
|
123
|
+
<p>Your email has been verified and your account is now active. You've been granted <strong>$5.00 in free credits</strong> to get started.</p>
|
|
124
|
+
<p>You can now create bots, connect them to Discord, Slack, and more.</p>`),
|
|
125
|
+
footer("Happy building!"),
|
|
126
|
+
].join("\n"));
|
|
127
|
+
const text = `Welcome to WOPR!
|
|
128
|
+
|
|
129
|
+
Hi ${email},
|
|
130
|
+
|
|
131
|
+
Your email has been verified and your account is now active. You've been granted $5.00 in free credits to get started.
|
|
132
|
+
|
|
133
|
+
You can now create bots, connect them to Discord, Slack, and more.
|
|
134
|
+
|
|
135
|
+
Happy building!
|
|
136
|
+
|
|
137
|
+
(c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
|
|
138
|
+
return { subject: "Welcome to WOPR", html, text };
|
|
139
|
+
}
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// 3. Password Reset (supersedes WOP-346 inline template)
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
export function passwordResetEmailTemplate(resetUrl, email) {
|
|
144
|
+
const escapedEmail = escapeHtml(email);
|
|
145
|
+
const escapedUrl = escapeHtml(resetUrl);
|
|
146
|
+
const html = wrapHtml("Reset Your Password", [
|
|
147
|
+
heading("Reset Your Password"),
|
|
148
|
+
paragraph(`<p>Hi there,</p>
|
|
149
|
+
<p>You requested a password reset for your WOPR account (<strong>${escapedEmail}</strong>).</p>
|
|
150
|
+
<p>Click the button below to create a new password. This link will expire in 1 hour.</p>`),
|
|
151
|
+
button(resetUrl, "Reset Password"),
|
|
152
|
+
paragraph(`<p style="color: #718096; font-size: 14px;">Or copy and paste this URL into your browser:</p>
|
|
153
|
+
<p style="word-break: break-all; color: #2563eb; font-size: 14px;">${escapedUrl}</p>`),
|
|
154
|
+
footer("If you didn't request this password reset, you can safely ignore this email."),
|
|
155
|
+
].join("\n"));
|
|
156
|
+
const text = `Reset Your Password
|
|
157
|
+
|
|
158
|
+
Hi there,
|
|
159
|
+
|
|
160
|
+
You requested a password reset for your WOPR account (${email}).
|
|
161
|
+
|
|
162
|
+
Click the link below to create a new password. This link will expire in 1 hour.
|
|
163
|
+
|
|
164
|
+
${resetUrl}
|
|
165
|
+
|
|
166
|
+
If you didn't request this password reset, you can safely ignore this email.
|
|
167
|
+
|
|
168
|
+
(c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
|
|
169
|
+
return { subject: "Reset your WOPR password", html, text };
|
|
170
|
+
}
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// 4. Credit Purchase
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
export function creditPurchaseTemplate(email, amountDollars, newBalanceDollars, creditsUrl) {
|
|
175
|
+
const escapedEmail = escapeHtml(email);
|
|
176
|
+
const escapedAmount = escapeHtml(amountDollars);
|
|
177
|
+
const balanceLine = newBalanceDollars
|
|
178
|
+
? `<p>Your new balance is <strong>${escapeHtml(newBalanceDollars)}</strong>.</p>`
|
|
179
|
+
: "<p>Your updated balance is now available in your dashboard.</p>";
|
|
180
|
+
const balanceTextLine = newBalanceDollars
|
|
181
|
+
? `Your new balance is ${newBalanceDollars}.`
|
|
182
|
+
: "Your updated balance is now available in your dashboard.";
|
|
183
|
+
const parts = [
|
|
184
|
+
heading("Credits Added to Your Account"),
|
|
185
|
+
paragraph(`<p>Hi <strong>${escapedEmail}</strong>,</p>
|
|
186
|
+
<p><strong>${escapedAmount}</strong> in credits has been added to your WOPR account.</p>
|
|
187
|
+
${balanceLine}`),
|
|
188
|
+
];
|
|
189
|
+
if (creditsUrl)
|
|
190
|
+
parts.push(button(creditsUrl, "View Credits"));
|
|
191
|
+
parts.push(footer("Thank you for supporting WOPR!"));
|
|
192
|
+
parts.push(unsubscribeFooter(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined));
|
|
193
|
+
const html = wrapHtml("Credits Added", parts.join("\n"));
|
|
194
|
+
const text = `Credits Added to Your Account
|
|
195
|
+
|
|
196
|
+
Hi ${email},
|
|
197
|
+
|
|
198
|
+
${amountDollars} in credits has been added to your WOPR account.
|
|
199
|
+
${balanceTextLine}
|
|
200
|
+
${creditsUrl ? `\nView your credits: ${creditsUrl}` : ""}
|
|
201
|
+
Thank you for supporting WOPR!${unsubscribeText(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined)}
|
|
202
|
+
|
|
203
|
+
(c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
|
|
204
|
+
return { subject: "Credits added to your account", html, text };
|
|
205
|
+
}
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
// 5. Low Balance
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
export function lowBalanceTemplate(email, balanceDollars, estimatedDaysRemaining, creditsUrl) {
|
|
210
|
+
const escapedEmail = escapeHtml(email);
|
|
211
|
+
const escapedBalance = escapeHtml(balanceDollars);
|
|
212
|
+
const daysLine = estimatedDaysRemaining != null
|
|
213
|
+
? `<p>At your current usage, your credits will run out in approximately <strong>${estimatedDaysRemaining} day${estimatedDaysRemaining === 1 ? "" : "s"}</strong>.</p>`
|
|
214
|
+
: "";
|
|
215
|
+
const daysTextLine = estimatedDaysRemaining != null
|
|
216
|
+
? `At your current usage, your credits will run out in approximately ${estimatedDaysRemaining} day${estimatedDaysRemaining === 1 ? "" : "s"}.`
|
|
217
|
+
: "";
|
|
218
|
+
const parts = [
|
|
219
|
+
heading("Your WOPR Credits Are Running Low"),
|
|
220
|
+
paragraph(`<p>Hi <strong>${escapedEmail}</strong>,</p>
|
|
221
|
+
<p>Your WOPR credit balance is now <strong>${escapedBalance}</strong>. When your balance reaches $0, your bots will be paused.</p>
|
|
222
|
+
${daysLine}
|
|
223
|
+
<p>Top up your credits to keep your bots running.</p>`),
|
|
224
|
+
];
|
|
225
|
+
if (creditsUrl)
|
|
226
|
+
parts.push(button(creditsUrl, "Buy Credits"));
|
|
227
|
+
parts.push(footer("This is an automated notification based on your account balance."));
|
|
228
|
+
parts.push(unsubscribeFooter(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined));
|
|
229
|
+
const html = wrapHtml("Low Balance", parts.join("\n"));
|
|
230
|
+
const text = `Your WOPR Credits Are Running Low
|
|
231
|
+
|
|
232
|
+
Hi ${email},
|
|
233
|
+
|
|
234
|
+
Your WOPR credit balance is now ${balanceDollars}. When your balance reaches $0, your bots will be paused.
|
|
235
|
+
${daysTextLine ? `${daysTextLine}\n` : ""}
|
|
236
|
+
Top up your credits to keep your bots running.
|
|
237
|
+
${creditsUrl ? `\nBuy credits: ${creditsUrl}` : ""}${unsubscribeText(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined)}
|
|
238
|
+
|
|
239
|
+
(c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
|
|
240
|
+
return { subject: "Your WOPR credits are running low", html, text };
|
|
241
|
+
}
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
// 6. Bot Suspended
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
export function botSuspendedTemplate(email, botName, reason, creditsUrl) {
|
|
246
|
+
const escapedEmail = escapeHtml(email);
|
|
247
|
+
const escapedBotName = escapeHtml(botName);
|
|
248
|
+
const escapedReason = escapeHtml(reason);
|
|
249
|
+
const parts = [
|
|
250
|
+
heading("Your Bot Has Been Suspended"),
|
|
251
|
+
paragraph(`<p>Hi <strong>${escapedEmail}</strong>,</p>
|
|
252
|
+
<p>Your bot <strong>${escapedBotName}</strong> has been suspended.</p>
|
|
253
|
+
<p><strong>Reason:</strong> ${escapedReason}</p>
|
|
254
|
+
<p>Buy credits to reactivate instantly. Your data is preserved for 30 days.</p>`),
|
|
255
|
+
];
|
|
256
|
+
if (creditsUrl)
|
|
257
|
+
parts.push(button(creditsUrl, "Buy Credits to Reactivate"));
|
|
258
|
+
parts.push(footer("If you need help, reply to this email or contact support@wopr.bot."));
|
|
259
|
+
parts.push(unsubscribeFooter(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined));
|
|
260
|
+
const html = wrapHtml("Bot Suspended", parts.join("\n"));
|
|
261
|
+
const text = `Your Bot Has Been Suspended
|
|
262
|
+
|
|
263
|
+
Hi ${email},
|
|
264
|
+
|
|
265
|
+
Your bot "${botName}" has been suspended.
|
|
266
|
+
|
|
267
|
+
Reason: ${reason}
|
|
268
|
+
|
|
269
|
+
Buy credits to reactivate instantly. Your data is preserved for 30 days.
|
|
270
|
+
${creditsUrl ? `\nBuy credits: ${creditsUrl}` : ""}
|
|
271
|
+
If you need help, reply to this email or contact support@wopr.bot.${unsubscribeText(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined)}
|
|
272
|
+
|
|
273
|
+
(c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
|
|
274
|
+
return { subject: "Your bot has been suspended", html, text };
|
|
275
|
+
}
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
// 7. Bot Destruction Warning
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
export function botDestructionTemplate(email, botName, daysRemaining, creditsUrl) {
|
|
280
|
+
const escapedEmail = escapeHtml(email);
|
|
281
|
+
const escapedBotName = escapeHtml(botName);
|
|
282
|
+
const days = String(daysRemaining);
|
|
283
|
+
const deadline = new Date(Date.now() + daysRemaining * 24 * 60 * 60 * 1000).toLocaleDateString("en-US", {
|
|
284
|
+
month: "long",
|
|
285
|
+
day: "numeric",
|
|
286
|
+
year: "numeric",
|
|
287
|
+
});
|
|
288
|
+
const parts = [
|
|
289
|
+
heading("URGENT: Bot Data Will Be Deleted"),
|
|
290
|
+
paragraph(`<p>Hi <strong>${escapedEmail}</strong>,</p>
|
|
291
|
+
<p>Your bot <strong>${escapedBotName}</strong> has been suspended and its data will be permanently deleted in <strong>${escapeHtml(days)} days</strong> (by ${escapeHtml(deadline)}).</p>
|
|
292
|
+
<p>Buy credits before the deadline to save your data.</p>`),
|
|
293
|
+
];
|
|
294
|
+
if (creditsUrl)
|
|
295
|
+
parts.push(button(creditsUrl, "Buy Credits Now", "#dc2626"));
|
|
296
|
+
parts.push(footer("This action is irreversible. All bot configuration, history, and connected services will be removed."));
|
|
297
|
+
parts.push(unsubscribeFooter(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined));
|
|
298
|
+
const html = wrapHtml("Bot Data Deletion", parts.join("\n"));
|
|
299
|
+
const text = `URGENT: Bot Data Will Be Deleted
|
|
300
|
+
|
|
301
|
+
Hi ${email},
|
|
302
|
+
|
|
303
|
+
Your bot "${botName}" has been suspended and its data will be permanently deleted in ${daysRemaining} days (by ${deadline}).
|
|
304
|
+
|
|
305
|
+
Buy credits before the deadline to save your data.
|
|
306
|
+
${creditsUrl ? `\nBuy credits now: ${creditsUrl}` : ""}
|
|
307
|
+
This action is irreversible. All bot configuration, history, and connected services will be removed.${unsubscribeText(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined)}
|
|
308
|
+
|
|
309
|
+
(c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
|
|
310
|
+
return { subject: `URGENT: Your bot data will be deleted in ${daysRemaining} days`, html, text };
|
|
311
|
+
}
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
313
|
+
// 8. Data Deleted Confirmation
|
|
314
|
+
// ---------------------------------------------------------------------------
|
|
315
|
+
export function dataDeletedTemplate(email, creditsUrl) {
|
|
316
|
+
const escapedEmail = escapeHtml(email);
|
|
317
|
+
const parts = [
|
|
318
|
+
heading("Your Bot Data Has Been Deleted"),
|
|
319
|
+
paragraph(`<p>Hi <strong>${escapedEmail}</strong>,</p>
|
|
320
|
+
<p>Your suspended bot data has been permanently deleted after 30 days of inactivity.</p>
|
|
321
|
+
<p>You can create a new bot anytime by adding credits to your account.</p>`),
|
|
322
|
+
];
|
|
323
|
+
if (creditsUrl)
|
|
324
|
+
parts.push(button(creditsUrl, "Add Credits"));
|
|
325
|
+
parts.push(footer("If you have questions, contact support@wopr.bot."));
|
|
326
|
+
parts.push(unsubscribeFooter(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined));
|
|
327
|
+
const html = wrapHtml("Data Deleted", parts.join("\n"));
|
|
328
|
+
const text = `Your Bot Data Has Been Deleted
|
|
329
|
+
|
|
330
|
+
Hi ${email},
|
|
331
|
+
|
|
332
|
+
Your suspended bot data has been permanently deleted after 30 days of inactivity.
|
|
333
|
+
|
|
334
|
+
You can create a new bot anytime by adding credits to your account.
|
|
335
|
+
${creditsUrl ? `\nAdd credits: ${creditsUrl}` : ""}
|
|
336
|
+
If you have questions, contact support@wopr.bot.${unsubscribeText(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined)}
|
|
337
|
+
|
|
338
|
+
(c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
|
|
339
|
+
return { subject: "Your bot data has been deleted", html, text };
|
|
340
|
+
}
|
|
341
|
+
// ---------------------------------------------------------------------------
|
|
342
|
+
// Org Invite
|
|
343
|
+
// ---------------------------------------------------------------------------
|
|
344
|
+
export function orgInviteEmailTemplate(inviteUrl, orgName) {
|
|
345
|
+
const safeOrg = escapeHtml(orgName);
|
|
346
|
+
const safeUrl = escapeHtml(inviteUrl);
|
|
347
|
+
const html = wrapHtml(`You're invited to join ${safeOrg}`, [
|
|
348
|
+
heading(`Join ${safeOrg}`),
|
|
349
|
+
paragraph(`You've been invited to join <strong>${safeOrg}</strong> on WOPR Network. Click the button below to accept the invitation.`),
|
|
350
|
+
button(safeUrl, "Accept Invitation"),
|
|
351
|
+
footer("This invitation expires in 7 days. If you didn't expect this email, you can safely ignore it."),
|
|
352
|
+
].join("\n"));
|
|
353
|
+
const text = `You're invited to join ${orgName} on WOPR Network.
|
|
354
|
+
|
|
355
|
+
Accept the invitation: ${inviteUrl}
|
|
356
|
+
|
|
357
|
+
This invitation expires in 7 days.`;
|
|
358
|
+
return { subject: `You're invited to join ${orgName}`, html, text };
|
|
359
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { botDestructionTemplate, botSuspendedTemplate, creditPurchaseTemplate, dataDeletedTemplate, lowBalanceTemplate, passwordResetEmailTemplate, verifyEmailTemplate, welcomeTemplate, } from "./templates.js";
|
|
3
|
+
describe("verifyEmailTemplate", () => {
|
|
4
|
+
it("should generate HTML and text with verify URL", () => {
|
|
5
|
+
const result = verifyEmailTemplate("https://wopr.bot/auth/verify?token=abc123", "user@test.com");
|
|
6
|
+
expect(result.subject).toBe("Verify your WOPR account");
|
|
7
|
+
expect(result.html).toContain("Verify Your Email");
|
|
8
|
+
expect(result.html).toContain("https://wopr.bot/auth/verify?token=abc123");
|
|
9
|
+
expect(result.html).toContain("user@test.com");
|
|
10
|
+
expect(result.html).toContain("<!DOCTYPE html>");
|
|
11
|
+
expect(result.text).toContain("https://wopr.bot/auth/verify?token=abc123");
|
|
12
|
+
expect(result.text).toContain("user@test.com");
|
|
13
|
+
expect(result.text).not.toContain("<");
|
|
14
|
+
});
|
|
15
|
+
it("should escape HTML in email and URL", () => {
|
|
16
|
+
const result = verifyEmailTemplate("https://evil.com/<script>", "<script>alert('xss')</script>@evil.com");
|
|
17
|
+
expect(result.html).toContain("<script>alert('xss')</script>@evil.com");
|
|
18
|
+
expect(result.html).toContain("<script>");
|
|
19
|
+
});
|
|
20
|
+
it("should include 24-hour expiry notice", () => {
|
|
21
|
+
const result = verifyEmailTemplate("https://wopr.bot/verify?token=x", "a@b.com");
|
|
22
|
+
expect(result.html).toContain("24 hours");
|
|
23
|
+
expect(result.text).toContain("24 hours");
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe("welcomeTemplate", () => {
|
|
27
|
+
it("should generate welcome email with credit info", () => {
|
|
28
|
+
const result = welcomeTemplate("user@test.com");
|
|
29
|
+
expect(result.subject).toBe("Welcome to WOPR");
|
|
30
|
+
expect(result.html).toContain("Welcome to WOPR");
|
|
31
|
+
expect(result.html).toContain("$5.00 in free credits");
|
|
32
|
+
expect(result.html).toContain("user@test.com");
|
|
33
|
+
expect(result.text).toContain("$5.00 in free credits");
|
|
34
|
+
expect(result.text).not.toContain("<");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe("passwordResetEmailTemplate", () => {
|
|
38
|
+
it("should generate password reset email", () => {
|
|
39
|
+
const result = passwordResetEmailTemplate("https://wopr.bot/reset?token=abc", "user@test.com");
|
|
40
|
+
expect(result.subject).toBe("Reset your WOPR password");
|
|
41
|
+
expect(result.html).toContain("Reset Your Password");
|
|
42
|
+
expect(result.html).toContain("https://wopr.bot/reset?token=abc");
|
|
43
|
+
expect(result.html).toContain("1 hour");
|
|
44
|
+
expect(result.text).toContain("https://wopr.bot/reset?token=abc");
|
|
45
|
+
expect(result.text).not.toContain("<");
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe("creditPurchaseTemplate", () => {
|
|
49
|
+
it("should generate credit purchase confirmation", () => {
|
|
50
|
+
const result = creditPurchaseTemplate("user@test.com", "$10.00");
|
|
51
|
+
expect(result.subject).toBe("Credits added to your account");
|
|
52
|
+
expect(result.html).toContain("$10.00");
|
|
53
|
+
expect(result.html).toContain("Credits Added");
|
|
54
|
+
expect(result.text).toContain("$10.00");
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe("lowBalanceTemplate", () => {
|
|
58
|
+
it("should generate low balance warning", () => {
|
|
59
|
+
const result = lowBalanceTemplate("user@test.com", "$0.50");
|
|
60
|
+
expect(result.subject).toBe("Your WOPR credits are running low");
|
|
61
|
+
expect(result.html).toContain("$0.50");
|
|
62
|
+
expect(result.html).toContain("Running Low");
|
|
63
|
+
expect(result.text).toContain("$0.50");
|
|
64
|
+
expect(result.text).toContain("paused");
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe("botSuspendedTemplate", () => {
|
|
68
|
+
it("should generate bot suspended notification", () => {
|
|
69
|
+
const result = botSuspendedTemplate("user@test.com", "MyBot", "Terms of service violation");
|
|
70
|
+
expect(result.subject).toBe("Your bot has been suspended");
|
|
71
|
+
expect(result.html).toContain("MyBot");
|
|
72
|
+
expect(result.html).toContain("Terms of service violation");
|
|
73
|
+
expect(result.html).toContain("30 days");
|
|
74
|
+
expect(result.text).toContain("MyBot");
|
|
75
|
+
expect(result.text).toContain("Terms of service violation");
|
|
76
|
+
expect(result.text).toContain("30 days");
|
|
77
|
+
});
|
|
78
|
+
it("should escape HTML in bot name and reason", () => {
|
|
79
|
+
const result = botSuspendedTemplate("user@test.com", "<script>bot</script>", "<b>reason</b>");
|
|
80
|
+
expect(result.html).toContain("<script>bot</script>");
|
|
81
|
+
expect(result.html).toContain("<b>reason</b>");
|
|
82
|
+
});
|
|
83
|
+
it("should include CTA button when creditsUrl is provided", () => {
|
|
84
|
+
const result = botSuspendedTemplate("user@test.com", "MyBot", "Insufficient credits", "https://app.wopr.bot/credits");
|
|
85
|
+
expect(result.html).toContain("Buy Credits to Reactivate");
|
|
86
|
+
expect(result.html).toContain("https://app.wopr.bot/credits");
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
describe("botDestructionTemplate", () => {
|
|
90
|
+
it("should generate bot destruction warning with days", () => {
|
|
91
|
+
const result = botDestructionTemplate("user@test.com", "MyBot", 7);
|
|
92
|
+
expect(result.subject).toBe("URGENT: Your bot data will be deleted in 7 days");
|
|
93
|
+
expect(result.html).toContain("MyBot");
|
|
94
|
+
expect(result.html).toContain("7 days");
|
|
95
|
+
expect(result.html).toContain("permanently deleted");
|
|
96
|
+
expect(result.text).toContain("7 days");
|
|
97
|
+
expect(result.text).toContain("irreversible");
|
|
98
|
+
});
|
|
99
|
+
it("should include CTA button when creditsUrl is provided", () => {
|
|
100
|
+
const result = botDestructionTemplate("user@test.com", "MyBot", 5, "https://app.wopr.bot/credits");
|
|
101
|
+
expect(result.html).toContain("Buy Credits Now");
|
|
102
|
+
expect(result.html).toContain("https://app.wopr.bot/credits");
|
|
103
|
+
expect(result.text).toContain("https://app.wopr.bot/credits");
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe("dataDeletedTemplate", () => {
|
|
107
|
+
it("should generate data deleted confirmation", () => {
|
|
108
|
+
const result = dataDeletedTemplate("user@test.com");
|
|
109
|
+
expect(result.subject).toBe("Your bot data has been deleted");
|
|
110
|
+
expect(result.html).toContain("permanently deleted");
|
|
111
|
+
expect(result.html).toContain("new bot");
|
|
112
|
+
expect(result.text).toContain("permanently deleted");
|
|
113
|
+
expect(result.text).toContain("new bot");
|
|
114
|
+
});
|
|
115
|
+
it("should include CTA button when creditsUrl is provided", () => {
|
|
116
|
+
const result = dataDeletedTemplate("user@test.com", "https://app.wopr.bot/credits");
|
|
117
|
+
expect(result.html).toContain("Add Credits");
|
|
118
|
+
expect(result.html).toContain("https://app.wopr.bot/credits");
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe("all templates", () => {
|
|
122
|
+
it("should produce valid HTML structure for every template", () => {
|
|
123
|
+
const templates = [
|
|
124
|
+
verifyEmailTemplate("https://x.com/verify", "a@b.com"),
|
|
125
|
+
welcomeTemplate("a@b.com"),
|
|
126
|
+
passwordResetEmailTemplate("https://x.com/reset", "a@b.com"),
|
|
127
|
+
creditPurchaseTemplate("a@b.com", "$5"),
|
|
128
|
+
lowBalanceTemplate("a@b.com", "$0.50"),
|
|
129
|
+
botSuspendedTemplate("a@b.com", "Bot", "Reason"),
|
|
130
|
+
botDestructionTemplate("a@b.com", "Bot", 3),
|
|
131
|
+
dataDeletedTemplate("a@b.com"),
|
|
132
|
+
];
|
|
133
|
+
for (const t of templates) {
|
|
134
|
+
expect(t.html).toContain("<!DOCTYPE html>");
|
|
135
|
+
expect(t.html).toContain("<html>");
|
|
136
|
+
expect(t.html).toContain("</html>");
|
|
137
|
+
expect(t.html).toContain("WOPR Network");
|
|
138
|
+
expect(t.subject.length).toBeGreaterThan(0);
|
|
139
|
+
expect(t.text.length).toBeGreaterThan(0);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
it("should produce plain text without HTML tags for every template", () => {
|
|
143
|
+
const templates = [
|
|
144
|
+
verifyEmailTemplate("https://x.com/verify", "a@b.com"),
|
|
145
|
+
welcomeTemplate("a@b.com"),
|
|
146
|
+
passwordResetEmailTemplate("https://x.com/reset", "a@b.com"),
|
|
147
|
+
creditPurchaseTemplate("a@b.com", "$5"),
|
|
148
|
+
lowBalanceTemplate("a@b.com", "$0.50"),
|
|
149
|
+
botSuspendedTemplate("a@b.com", "Bot", "Reason"),
|
|
150
|
+
botDestructionTemplate("a@b.com", "Bot", 3),
|
|
151
|
+
dataDeletedTemplate("a@b.com"),
|
|
152
|
+
];
|
|
153
|
+
for (const t of templates) {
|
|
154
|
+
expect(t.text).not.toMatch(/<[a-z]/i);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
it("should include unsubscribe link in billing emails when creditsUrl provided", () => {
|
|
158
|
+
const billingTemplates = [
|
|
159
|
+
creditPurchaseTemplate("a@b.com", "$5", "$10", "https://app.wopr.bot/credits"),
|
|
160
|
+
lowBalanceTemplate("a@b.com", "$0.50", 3, "https://app.wopr.bot/credits"),
|
|
161
|
+
botSuspendedTemplate("a@b.com", "Bot", "Reason", "https://app.wopr.bot/credits"),
|
|
162
|
+
botDestructionTemplate("a@b.com", "Bot", 3, "https://app.wopr.bot/credits"),
|
|
163
|
+
dataDeletedTemplate("a@b.com", "https://app.wopr.bot/credits"),
|
|
164
|
+
];
|
|
165
|
+
for (const t of billingTemplates) {
|
|
166
|
+
expect(t.html).toContain("Unsubscribe");
|
|
167
|
+
expect(t.text).toContain("unsubscribe");
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email Verification — Token generation, validation, and verification flow.
|
|
3
|
+
*
|
|
4
|
+
* Manages the signup verification lifecycle:
|
|
5
|
+
* 1. Generate signed token on signup
|
|
6
|
+
* 2. Store token + expiry in the auth database
|
|
7
|
+
* 3. Verify token when user clicks the link
|
|
8
|
+
* 4. Mark user as verified, send welcome email, grant credits
|
|
9
|
+
*/
|
|
10
|
+
import type { Pool } from "pg";
|
|
11
|
+
import type { IEmailVerifier } from "./require-verified.js";
|
|
12
|
+
/** Add email verification columns to the better-auth user table. */
|
|
13
|
+
export declare function initVerificationSchema(pool: Pool): Promise<void>;
|
|
14
|
+
export interface VerificationToken {
|
|
15
|
+
token: string;
|
|
16
|
+
expiresAt: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Generate a verification token and store it against a user.
|
|
20
|
+
*/
|
|
21
|
+
export declare function generateVerificationToken(pool: Pool, userId: string): Promise<VerificationToken>;
|
|
22
|
+
/**
|
|
23
|
+
* Verify a token: check it exists, hasn't expired, and mark the user as verified.
|
|
24
|
+
*/
|
|
25
|
+
export declare function verifyToken(pool: Pool, token: string): Promise<{
|
|
26
|
+
userId: string;
|
|
27
|
+
email: string;
|
|
28
|
+
} | null>;
|
|
29
|
+
/**
|
|
30
|
+
* Check whether a user has verified their email.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isEmailVerified(pool: Pool, userId: string): Promise<boolean>;
|
|
33
|
+
/**
|
|
34
|
+
* Get a user's email by their ID.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getUserEmail(pool: Pool, userId: string): Promise<string | null>;
|
|
37
|
+
/** PostgreSQL-backed implementation of IEmailVerifier for the auth database. */
|
|
38
|
+
export declare class PgEmailVerifier implements IEmailVerifier {
|
|
39
|
+
private readonly pool;
|
|
40
|
+
constructor(pool: Pool);
|
|
41
|
+
isVerified(userId: string): Promise<boolean>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email Verification — Token generation, validation, and verification flow.
|
|
3
|
+
*
|
|
4
|
+
* Manages the signup verification lifecycle:
|
|
5
|
+
* 1. Generate signed token on signup
|
|
6
|
+
* 2. Store token + expiry in the auth database
|
|
7
|
+
* 3. Verify token when user clicks the link
|
|
8
|
+
* 4. Mark user as verified, send welcome email, grant credits
|
|
9
|
+
*/
|
|
10
|
+
import crypto from "node:crypto";
|
|
11
|
+
const TOKEN_EXPIRY_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Schema
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
/** Add email verification columns to the better-auth user table. */
|
|
16
|
+
export async function initVerificationSchema(pool) {
|
|
17
|
+
// raw SQL: better-auth manages its own schema outside Drizzle
|
|
18
|
+
await pool.query(`ALTER TABLE "user" ADD COLUMN IF NOT EXISTS email_verified BOOLEAN NOT NULL DEFAULT false`);
|
|
19
|
+
// raw SQL: better-auth manages its own schema outside Drizzle
|
|
20
|
+
await pool.query(`ALTER TABLE "user" ADD COLUMN IF NOT EXISTS verification_token TEXT`);
|
|
21
|
+
// raw SQL: better-auth manages its own schema outside Drizzle
|
|
22
|
+
await pool.query(`ALTER TABLE "user" ADD COLUMN IF NOT EXISTS verification_expires TEXT`);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Generate a verification token and store it against a user.
|
|
26
|
+
*/
|
|
27
|
+
export async function generateVerificationToken(pool, userId) {
|
|
28
|
+
const token = crypto.randomBytes(32).toString("hex");
|
|
29
|
+
const expiresAt = new Date(Date.now() + TOKEN_EXPIRY_MS).toISOString();
|
|
30
|
+
// raw SQL: better-auth manages its own schema outside Drizzle
|
|
31
|
+
await pool.query(`UPDATE "user" SET verification_token = $1, verification_expires = $2 WHERE id = $3`, [
|
|
32
|
+
token,
|
|
33
|
+
expiresAt,
|
|
34
|
+
userId,
|
|
35
|
+
]);
|
|
36
|
+
return { token, expiresAt };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Verify a token: check it exists, hasn't expired, and mark the user as verified.
|
|
40
|
+
*/
|
|
41
|
+
export async function verifyToken(pool, token) {
|
|
42
|
+
if (!token || token.length !== 64)
|
|
43
|
+
return null;
|
|
44
|
+
// raw SQL: better-auth manages its own schema outside Drizzle
|
|
45
|
+
const { rows } = await pool.query(`SELECT id, email, verification_token, verification_expires, email_verified FROM "user" WHERE verification_token = $1`, [token]);
|
|
46
|
+
const row = rows[0];
|
|
47
|
+
if (!row)
|
|
48
|
+
return null;
|
|
49
|
+
if (row.email_verified === true)
|
|
50
|
+
return null;
|
|
51
|
+
const expiresAt = new Date(row.verification_expires).getTime();
|
|
52
|
+
if (Date.now() > expiresAt)
|
|
53
|
+
return null;
|
|
54
|
+
// raw SQL: better-auth manages its own schema outside Drizzle
|
|
55
|
+
await pool.query(`UPDATE "user" SET email_verified = true, "emailVerified" = true, verification_token = NULL, verification_expires = NULL WHERE id = $1`, [row.id]);
|
|
56
|
+
return { userId: row.id, email: row.email };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Check whether a user has verified their email.
|
|
60
|
+
*/
|
|
61
|
+
export async function isEmailVerified(pool, userId) {
|
|
62
|
+
// raw SQL: better-auth manages its own schema outside Drizzle
|
|
63
|
+
const { rows } = await pool.query(`SELECT email_verified FROM "user" WHERE id = $1`, [userId]);
|
|
64
|
+
return rows[0]?.email_verified === true;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get a user's email by their ID.
|
|
68
|
+
*/
|
|
69
|
+
export async function getUserEmail(pool, userId) {
|
|
70
|
+
// raw SQL: better-auth manages its own schema outside Drizzle
|
|
71
|
+
const { rows } = await pool.query(`SELECT email FROM "user" WHERE id = $1`, [userId]);
|
|
72
|
+
return rows[0]?.email ?? null;
|
|
73
|
+
}
|
|
74
|
+
/** PostgreSQL-backed implementation of IEmailVerifier for the auth database. */
|
|
75
|
+
export class PgEmailVerifier {
|
|
76
|
+
pool;
|
|
77
|
+
constructor(pool) {
|
|
78
|
+
this.pool = pool;
|
|
79
|
+
}
|
|
80
|
+
async isVerified(userId) {
|
|
81
|
+
return isEmailVerified(this.pool, userId);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|