create-ailk 0.24.0 → 0.26.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/README.md +53 -22
- package/component-catalog.json +3 -3
- package/component-catalog.md +3 -3
- package/dist/add-module.js +4 -2
- package/dist/add-route.d.ts +12 -1
- package/dist/add-route.js +31 -3
- package/dist/cli.js +12 -7
- package/dist/derive-architecture.d.ts +3 -4
- package/dist/derive-architecture.js +65 -29
- package/dist/lib/design-checks.d.ts +9 -4
- package/dist/lib/design-checks.js +13 -7
- package/dist/lib/extract-module-bundle.js +29 -2
- package/dist/lib/fetch-template-snapshot.d.ts +2 -1
- package/dist/lib/fetch-template-snapshot.js +8 -3
- package/dist/lib/import-graph.d.ts +1 -1
- package/dist/lib/import-graph.js +4 -4
- package/dist/lib/module-license-gate.d.ts +12 -1
- package/dist/lib/module-license-gate.js +36 -15
- package/dist/lib/module-tier.d.ts +2 -8
- package/dist/lib/module-tier.js +13 -12
- package/dist/module-architecture.d.ts +20 -3
- package/dist/module-architecture.js +129 -552
- package/dist/paid-sections.d.ts +25 -1
- package/dist/paid-sections.js +88 -3
- package/dist/starter-grid.d.ts +6 -6
- package/dist/starter-grid.js +12 -5
- package/dist/surfaces.js +39 -2
- package/dist/template-record.d.ts +21 -3
- package/dist/template-record.js +38 -7
- package/dist/topology.d.ts +16 -3
- package/dist/topology.js +54 -23
- package/dist/update.d.ts +10 -1
- package/dist/update.js +715 -21
- package/package.json +3 -3
- package/release-scope.json +2 -2
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +8 -2
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +2 -2
- package/site-templates/marketing-site/ailk-template.json +7 -1
- package/site-templates/marketing-site/provenance.json +7 -1
- package/site-templates/saas-product/ailk-template.json +2 -1
- package/site-templates/saas-product/provenance.json +1 -0
- package/templates/.claude/cloudparity-repo-hooks.json +5 -0
- package/templates/.claude/hooks/template-guard.sh +137 -0
- package/templates/.claude/rules/extending.md +106 -0
- package/templates/.claude/rules/loop-contract.md +2 -2
- package/templates/.claude/settings.json +11 -0
- package/templates/.env.example +20 -5
- package/templates/.github/workflows/ci.yml +7 -4
- package/templates/CLAUDE.md +11 -1
- package/templates/CONVENTIONS.md +8 -10
- package/templates/README.md +14 -13
- package/templates/apps/api/.env.example +23 -2
- package/templates/apps/api/CLAUDE.md +119 -83
- package/templates/apps/api/jest.config.cjs +15 -5
- package/templates/apps/api/package.json +16 -20
- package/templates/apps/api/src/__tests__/trusted-origins.server.test.ts +2 -2
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +4 -4
- package/templates/apps/api/src/index.ts +1 -1
- package/templates/apps/api/src/lib/__mocks__/prisma-client.js +16 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -3
- package/templates/apps/api/src/modules/commerce.ts +10 -0
- package/templates/apps/api/src/modules/content-write.ts +10 -0
- package/templates/apps/api/src/modules/multi-tenant.ts +10 -0
- package/templates/apps/api/src/modules/scheduling.ts +10 -0
- package/templates/apps/api/src/modules/signal-collectors.ts +10 -0
- package/templates/apps/api/src/modules/workspace.ts +10 -0
- package/templates/apps/api/src/server.ts +117 -1882
- package/templates/apps/api/vercel.json +2 -2
- package/templates/apps/mcp/CLAUDE.md +90 -26
- package/templates/apps/mcp/__tests__/app-entry.test.ts +4 -254
- package/templates/apps/mcp/__tests__/boot-file.test.ts +92 -0
- package/templates/apps/mcp/jest.config.cjs +6 -1
- package/templates/apps/mcp/package.json +5 -13
- package/templates/apps/mcp/src/app.ts +8 -7
- package/templates/apps/mcp/src/index.ts +6 -34
- package/templates/apps/mcp/src/lib/paid-tools.generated.ts +10 -0
- package/templates/apps/mcp/src/lib/topology.generated.ts +2 -2
- package/templates/apps/mcp/src/site.ts +12 -0
- package/templates/apps/web/.env.example +1 -1
- package/templates/apps/web/CLAUDE.md +31 -3
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +9 -3
- package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +43 -4
- package/templates/apps/web/app/[locale]/(auth)/legal-footnote.tsx +35 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/page.test.tsx +117 -8
- package/templates/apps/web/app/[locale]/(auth)/sign-in/page.tsx +14 -37
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +6 -6
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-text.ts +20 -35
- package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/page.test.tsx +108 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/sign-up-card.test.tsx +111 -5
- package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +78 -16
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +132 -12
- package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +53 -1
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/billing.test.tsx +38 -0
- package/templates/apps/web/app/[locale]/(authed)/account/billing/page.tsx +4 -2
- package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +47 -24
- package/templates/apps/web/app/[locale]/(authed)/signed-out-gate.generated.tsx +23 -4
- package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +1 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +22 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +7 -3
- package/templates/apps/web/app/[locale]/layout.tsx +33 -13
- package/templates/apps/web/app/[locale]/meet/confirmed/BookingHandOff.tsx +128 -0
- package/templates/apps/web/app/[locale]/meet/confirmed/__tests__/BookingHandOff.test.tsx +126 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +43 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +7 -3
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/__tests__/route.test.ts +222 -0
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +10 -3
- package/templates/apps/web/app/api/revalidate/route.ts +3 -3
- package/templates/apps/web/auth.ts +10 -6
- package/templates/apps/web/i18n/request.ts +49 -1
- package/templates/apps/web/jest.config.cjs +1 -0
- package/templates/apps/web/lib/__tests__/account-config.test.ts +7 -0
- package/templates/apps/web/lib/__tests__/auth-email.test.ts +276 -0
- package/templates/apps/web/lib/auth-email.ts +103 -21
- package/templates/apps/web/lib/hub-session/__tests__/sign-in-gate.test.tsx +72 -23
- package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +94 -70
- package/templates/apps/web/lib/prisma-for-auth.ts +5 -2
- package/templates/apps/web/lib/relay.ts +2 -2
- package/templates/apps/web/lib/release-scope.ts +47 -0
- package/templates/apps/web/lib/server-api.ts +1 -1
- package/templates/apps/web/lib/sign-up-agreements.ts +291 -28
- package/templates/apps/web/lib/site-contributions.ts +18 -0
- package/templates/apps/web/messages/en.json +186 -20
- package/templates/apps/web/messages/site/en.json +1 -0
- package/templates/apps/web/src/lib/commerce-claim.ts +1 -1
- package/templates/customizations.yaml +37 -0
- package/templates/database/prisma.config.ts +10 -14
- package/templates/database/seed-test.ts +2 -2
- package/templates/database/seed.ts +5 -5
- package/templates/package.json +12 -6
- package/templates/pnpm-workspace.yaml +0 -1
- package/templates/project.yaml +16 -8
- package/templates/tools/jest/reporters.cjs +65 -0
- package/templates/tools/lint-architecture.ts +553 -7
- package/templates/tsconfig.json +1 -2
- package/versions.json +49 -36
- package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +0 -409
- package/templates/apps/api/src/__tests__/factories/agent-caller.ts +0 -102
- package/templates/apps/api/src/__tests__/factories/content-fixture.ts +0 -138
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +0 -441
- package/templates/apps/api/src/__tests__/factories/index.ts +0 -70
- package/templates/apps/api/src/__tests__/factories/lead-fixture.ts +0 -82
- package/templates/apps/api/src/__tests__/leads-forwarded-rate-limit.test.ts +0 -135
- package/templates/apps/api/src/__tests__/services/enrichment-shim.test.ts +0 -126
- package/templates/apps/api/src/__tests__/stripe-boot.test.ts +0 -132
- package/templates/apps/api/src/__tests__/support/hub-sign-in-fake-db.ts +0 -362
- package/templates/apps/api/src/bin/__tests__/rate-limit-probe.test.ts +0 -184
- package/templates/apps/api/src/bin/abandon-sweep.ts +0 -42
- package/templates/apps/api/src/bin/rate-limit-probe.ts +0 -53
- package/templates/apps/api/src/bin/spokes.ts +0 -198
- package/templates/apps/api/src/bin/stripe-connections-rekey.ts +0 -45
- package/templates/apps/api/src/config/__tests__/hub-require-spoke-key.test.ts +0 -196
- package/templates/apps/api/src/config/__tests__/index.test.ts +0 -233
- package/templates/apps/api/src/config/__tests__/modules.test.ts +0 -76
- package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +0 -185
- package/templates/apps/api/src/config/__tests__/trusted-origins.test.ts +0 -238
- package/templates/apps/api/src/config/index.ts +0 -596
- package/templates/apps/api/src/config/modules.ts +0 -83
- package/templates/apps/api/src/lib/__tests__/agent-store.test.ts +0 -581
- package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +0 -143
- package/templates/apps/api/src/lib/__tests__/consent-ledger-double.ts +0 -237
- package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +0 -353
- package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +0 -201
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +0 -224
- package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +0 -120
- package/templates/apps/api/src/lib/__tests__/errors.test.ts +0 -94
- package/templates/apps/api/src/lib/__tests__/file-fallback.test.ts +0 -144
- package/templates/apps/api/src/lib/__tests__/fixtures/HomePage/home.mdx +0 -8
- package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +0 -149
- package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +0 -116
- package/templates/apps/api/src/lib/__tests__/hub-sign-in-store.test.ts +0 -209
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +0 -257
- package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +0 -96
- package/templates/apps/api/src/lib/__tests__/outbound-http.test.ts +0 -125
- package/templates/apps/api/src/lib/__tests__/prisma.test.ts +0 -165
- package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +0 -317
- package/templates/apps/api/src/lib/__tests__/require-paid-tier.test.ts +0 -129
- package/templates/apps/api/src/lib/__tests__/site.test.ts +0 -308
- package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +0 -89
- package/templates/apps/api/src/lib/__tests__/spoke-store.test.ts +0 -202
- package/templates/apps/api/src/lib/__tests__/stripe-admission.test.ts +0 -217
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -82
- package/templates/apps/api/src/lib/agent-store.ts +0 -613
- package/templates/apps/api/src/lib/auth-email-budget.ts +0 -161
- package/templates/apps/api/src/lib/consent-store.ts +0 -363
- package/templates/apps/api/src/lib/content-adapter.ts +0 -112
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +0 -96
- package/templates/apps/api/src/lib/error-serializer.ts +0 -164
- package/templates/apps/api/src/lib/errors.ts +0 -95
- package/templates/apps/api/src/lib/file-fallback.ts +0 -87
- package/templates/apps/api/src/lib/flow-completion-store.ts +0 -136
- package/templates/apps/api/src/lib/hub-sign-in-store.ts +0 -455
- package/templates/apps/api/src/lib/lead-capture-store.ts +0 -122
- package/templates/apps/api/src/lib/lead-delivery.ts +0 -52
- package/templates/apps/api/src/lib/license-key-store.ts +0 -45
- package/templates/apps/api/src/lib/locales.ts +0 -201
- package/templates/apps/api/src/lib/outbound-http.ts +0 -127
- package/templates/apps/api/src/lib/prisma.ts +0 -21
- package/templates/apps/api/src/lib/proposal-paid-store.ts +0 -66
- package/templates/apps/api/src/lib/rate-limit-probe.ts +0 -165
- package/templates/apps/api/src/lib/rate-limit-store.ts +0 -142
- package/templates/apps/api/src/lib/require-commerce-claim.ts +0 -250
- package/templates/apps/api/src/lib/require-paid-tier.ts +0 -53
- package/templates/apps/api/src/lib/site.ts +0 -257
- package/templates/apps/api/src/lib/slice-load.ts +0 -90
- package/templates/apps/api/src/lib/spoke-store.ts +0 -180
- package/templates/apps/api/src/lib/stripe.ts +0 -321
- package/templates/apps/api/src/lib/webhook-idempotency.ts +0 -156
- package/templates/apps/api/src/middleware/__tests__/adversarial/bearer-token-format.test.ts +0 -111
- package/templates/apps/api/src/middleware/__tests__/adversarial/cookie-or-bearer-precedence.test.ts +0 -172
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-bearer-skip.test.ts +0 -119
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-cookie-path.test.ts +0 -138
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-trusted-origins.test.ts +0 -142
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +0 -155
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-timeout.test.ts +0 -139
- package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +0 -562
- package/templates/apps/api/src/middleware/__tests__/auth.both-present-precedence.test.ts +0 -76
- package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +0 -220
- package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +0 -97
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +0 -557
- package/templates/apps/api/src/middleware/__tests__/auth.neither-present.test.ts +0 -72
- package/templates/apps/api/src/middleware/__tests__/auth.optional-cookie.test.ts +0 -140
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +0 -721
- package/templates/apps/api/src/middleware/__tests__/rate-limit-cross-isolate.db.test.ts +0 -220
- package/templates/apps/api/src/middleware/__tests__/rate-limit-store.test.ts +0 -430
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +0 -1409
- package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +0 -174
- package/templates/apps/api/src/middleware/auth.ts +0 -864
- package/templates/apps/api/src/middleware/default-tenant.ts +0 -166
- package/templates/apps/api/src/middleware/rate-limit.ts +0 -983
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +0 -391
- package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +0 -182
- package/templates/apps/api/src/openapi/index.ts +0 -18
- package/templates/apps/api/src/openapi/spec.ts +0 -2148
- package/templates/apps/api/src/openapi/surface-drift.ts +0 -195
- package/templates/apps/api/src/routes/aeo/__tests__/index.test.ts +0 -175
- package/templates/apps/api/src/routes/aeo/index.ts +0 -107
- package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +0 -196
- package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +0 -249
- package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +0 -532
- package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +0 -313
- package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +0 -413
- package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +0 -609
- package/templates/apps/api/src/routes/auth/hub-sign-in.ts +0 -242
- package/templates/apps/api/src/routes/auth/tokens.ts +0 -211
- package/templates/apps/api/src/routes/auth/transactional-email.ts +0 -301
- package/templates/apps/api/src/routes/auth/user.ts +0 -305
- package/templates/apps/api/src/routes/billing/plan-checkout.ts +0 -332
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +0 -178
- package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +0 -1426
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +0 -448
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -1742
- package/templates/apps/api/src/routes/checkout/index.ts +0 -34
- package/templates/apps/api/src/routes/checkout/session.ts +0 -171
- package/templates/apps/api/src/routes/checkout/sessions.ts +0 -540
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +0 -386
- package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +0 -160
- package/templates/apps/api/src/routes/consent/account.ts +0 -268
- package/templates/apps/api/src/routes/consent/cookie.ts +0 -90
- package/templates/apps/api/src/routes/consent/index.ts +0 -26
- package/templates/apps/api/src/routes/consent/unsubscribe.ts +0 -127
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +0 -853
- package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +0 -230
- package/templates/apps/api/src/routes/content/__tests__/list-types.test.ts +0 -44
- package/templates/apps/api/src/routes/content/__tests__/locale.test.ts +0 -118
- package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +0 -731
- package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +0 -753
- package/templates/apps/api/src/routes/content/__tests__/rate-limit.test.ts +0 -51
- package/templates/apps/api/src/routes/content/__tests__/search.test.ts +0 -450
- package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +0 -762
- package/templates/apps/api/src/routes/content/get-page.ts +0 -183
- package/templates/apps/api/src/routes/content/index.ts +0 -49
- package/templates/apps/api/src/routes/content/list-pages.ts +0 -74
- package/templates/apps/api/src/routes/content/list-types.ts +0 -19
- package/templates/apps/api/src/routes/content/offerings.ts +0 -184
- package/templates/apps/api/src/routes/content/pricing.ts +0 -176
- package/templates/apps/api/src/routes/content/rate-limit.ts +0 -22
- package/templates/apps/api/src/routes/content/search.ts +0 -100
- package/templates/apps/api/src/routes/content/shown-offerings.ts +0 -229
- package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +0 -88
- package/templates/apps/api/src/routes/leads/__tests__/list.test.ts +0 -171
- package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +0 -515
- package/templates/apps/api/src/routes/leads/__tests__/spoke.test.ts +0 -392
- package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +0 -165
- package/templates/apps/api/src/routes/leads/index.ts +0 -639
- package/templates/apps/api/src/routes/leads/list.ts +0 -104
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +0 -626
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +0 -1268
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +0 -847
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +0 -491
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +0 -476
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +0 -872
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +0 -1022
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +0 -484
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +0 -535
- package/templates/apps/api/src/routes/project-listings/index.ts +0 -164
- package/templates/apps/api/src/routes/project-listings/me.ts +0 -87
- package/templates/apps/api/src/routes/project-listings/patch-copy.ts +0 -224
- package/templates/apps/api/src/routes/project-listings/patch-draft.ts +0 -102
- package/templates/apps/api/src/routes/project-listings/public.ts +0 -98
- package/templates/apps/api/src/routes/project-listings/respond.ts +0 -48
- package/templates/apps/api/src/routes/project-listings/resume-token.ts +0 -27
- package/templates/apps/api/src/routes/project-listings/start.ts +0 -140
- package/templates/apps/api/src/routes/project-listings/submit.ts +0 -130
- package/templates/apps/api/src/routes/project-listings/verify-email.ts +0 -150
- package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +0 -686
- package/templates/apps/api/src/routes/requests/confirm.ts +0 -601
- package/templates/apps/api/src/routes/requests/index.ts +0 -54
- package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +0 -306
- package/templates/apps/api/src/routes/test/auth/session.ts +0 -143
- package/templates/apps/api/src/routes/webhooks/README.md +0 -398
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +0 -649
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +0 -749
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -449
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -264
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode-live.test.ts +0 -159
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode.test.ts +0 -158
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +0 -814
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -301
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-proposal-paid.test.ts +0 -369
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -215
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +0 -301
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +0 -809
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -1155
- package/templates/apps/api/src/routes/webhooks/index.ts +0 -141
- package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +0 -384
- package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -2028
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +0 -645
- package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +0 -132
- package/templates/apps/api/src/services/__tests__/audit-log.test.ts +0 -1028
- package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +0 -313
- package/templates/apps/api/src/services/__tests__/consent-ledger.test.ts +0 -503
- package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +0 -65
- package/templates/apps/api/src/services/__tests__/consent.test.ts +0 -299
- package/templates/apps/api/src/services/__tests__/default-tenant.test.ts +0 -147
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +0 -877
- package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +0 -379
- package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +0 -368
- package/templates/apps/api/src/services/__tests__/entitlements.test.ts +0 -142
- package/templates/apps/api/src/services/__tests__/hub-sign-in.test.ts +0 -854
- package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +0 -234
- package/templates/apps/api/src/services/__tests__/lead-accept-user.test.ts +0 -251
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +0 -2660
- package/templates/apps/api/src/services/__tests__/listing-config.test.ts +0 -166
- package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +0 -389
- package/templates/apps/api/src/services/__tests__/person.test.ts +0 -1204
- package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +0 -450
- package/templates/apps/api/src/services/__tests__/project-listings.counts.test.ts +0 -202
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +0 -151
- package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +0 -137
- package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +0 -531
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +0 -372
- package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +0 -348
- package/templates/apps/api/src/services/__tests__/spoke-resolution.test.ts +0 -660
- package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +0 -941
- package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +0 -588
- package/templates/apps/api/src/services/__tests__/verified-email.test.ts +0 -78
- package/templates/apps/api/src/services/abandon-email.ts +0 -336
- package/templates/apps/api/src/services/aeo-score.ts +0 -325
- package/templates/apps/api/src/services/answer-distributions.ts +0 -138
- package/templates/apps/api/src/services/audit-log.ts +0 -961
- package/templates/apps/api/src/services/checkout-buyer.ts +0 -168
- package/templates/apps/api/src/services/consent.ts +0 -834
- package/templates/apps/api/src/services/default-tenant.ts +0 -416
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +0 -750
- package/templates/apps/api/src/services/dunning-policy.ts +0 -294
- package/templates/apps/api/src/services/enrichment-shim.ts +0 -53
- package/templates/apps/api/src/services/entitlements.ts +0 -338
- package/templates/apps/api/src/services/hub-sign-in.ts +0 -472
- package/templates/apps/api/src/services/lead-accept-user.ts +0 -126
- package/templates/apps/api/src/services/lead-promotion.ts +0 -160
- package/templates/apps/api/src/services/lead-routing.ts +0 -1372
- package/templates/apps/api/src/services/leads-inbox.ts +0 -312
- package/templates/apps/api/src/services/listing-config.ts +0 -178
- package/templates/apps/api/src/services/magnet-completion.ts +0 -242
- package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +0 -348
- package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +0 -268
- package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +0 -975
- package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +0 -416
- package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +0 -400
- package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +0 -685
- package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +0 -231
- package/templates/apps/api/src/services/offerings/catalog.ts +0 -318
- package/templates/apps/api/src/services/offerings/checkout-authorization.ts +0 -403
- package/templates/apps/api/src/services/offerings/checkout-session-record.ts +0 -192
- package/templates/apps/api/src/services/offerings/connection.ts +0 -1083
- package/templates/apps/api/src/services/offerings/index.ts +0 -614
- package/templates/apps/api/src/services/offerings/source.ts +0 -140
- package/templates/apps/api/src/services/offerings/store.ts +0 -150
- package/templates/apps/api/src/services/owner-workspaces.ts +0 -132
- package/templates/apps/api/src/services/person.ts +0 -1211
- package/templates/apps/api/src/services/plan-offerings.ts +0 -125
- package/templates/apps/api/src/services/project-listing-resume-email.ts +0 -326
- package/templates/apps/api/src/services/project-listings.ts +0 -2085
- package/templates/apps/api/src/services/public-site-resolver.ts +0 -396
- package/templates/apps/api/src/services/request-confirm.ts +0 -698
- package/templates/apps/api/src/services/revalidation.ts +0 -235
- package/templates/apps/api/src/services/slack-notify.ts +0 -291
- package/templates/apps/api/src/services/usage-aggregation.ts +0 -516
- package/templates/apps/api/src/services/usage-credits.ts +0 -340
- package/templates/apps/api/src/services/verified-email.ts +0 -71
- package/templates/apps/mcp/__tests__/abuse/daily-cap.test.ts +0 -52
- package/templates/apps/mcp/__tests__/abuse/email-validator.test.ts +0 -46
- package/templates/apps/mcp/__tests__/abuse/guard.test.ts +0 -198
- package/templates/apps/mcp/__tests__/abuse/honeypot.test.ts +0 -36
- package/templates/apps/mcp/__tests__/abuse/user-agent-deny.test.ts +0 -54
- package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +0 -116
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +0 -299
- package/templates/apps/mcp/__tests__/boot.test.ts +0 -280
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +0 -359
- package/templates/apps/mcp/__tests__/config/modules.test.ts +0 -78
- package/templates/apps/mcp/__tests__/get_business_hours.test.ts +0 -82
- package/templates/apps/mcp/__tests__/get_faq.test.ts +0 -80
- package/templates/apps/mcp/__tests__/get_locations.test.ts +0 -65
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +0 -557
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +0 -524
- package/templates/apps/mcp/__tests__/get_site_info.test.ts +0 -81
- package/templates/apps/mcp/__tests__/http-transport.test.ts +0 -1232
- package/templates/apps/mcp/__tests__/index.test.ts +0 -195
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +0 -191
- package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +0 -531
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +0 -70
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +0 -162
- package/templates/apps/mcp/__tests__/lib/content-root.test.ts +0 -93
- package/templates/apps/mcp/__tests__/lib/errors.test.ts +0 -75
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +0 -180
- package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +0 -124
- package/templates/apps/mcp/__tests__/list_pages.test.ts +0 -268
- package/templates/apps/mcp/__tests__/locale.test.ts +0 -120
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +0 -175
- package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +0 -101
- package/templates/apps/mcp/__tests__/rate-limit-write.test.ts +0 -189
- package/templates/apps/mcp/__tests__/read_content.test.ts +0 -511
- package/templates/apps/mcp/__tests__/request_callback.test.ts +0 -160
- package/templates/apps/mcp/__tests__/search_content.test.ts +0 -271
- package/templates/apps/mcp/__tests__/server.test.ts +0 -388
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +0 -448
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +0 -222
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +0 -152
- package/templates/apps/mcp/__tests__/tools/create_project.test.ts +0 -243
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +0 -451
- package/templates/apps/mcp/src/abuse/daily-cap.ts +0 -47
- package/templates/apps/mcp/src/abuse/email-validator.ts +0 -25
- package/templates/apps/mcp/src/abuse/honeypot.ts +0 -21
- package/templates/apps/mcp/src/abuse/index.ts +0 -349
- package/templates/apps/mcp/src/abuse/user-agent-deny.ts +0 -58
- package/templates/apps/mcp/src/boot.ts +0 -167
- package/templates/apps/mcp/src/config/modules.ts +0 -43
- package/templates/apps/mcp/src/http-transport.ts +0 -564
- package/templates/apps/mcp/src/lib/audit-single-page.ts +0 -308
- package/templates/apps/mcp/src/lib/commerce-claim.ts +0 -263
- package/templates/apps/mcp/src/lib/content-adapter.ts +0 -116
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +0 -140
- package/templates/apps/mcp/src/lib/errors.ts +0 -61
- package/templates/apps/mcp/src/lib/license-key-store.ts +0 -45
- package/templates/apps/mcp/src/lib/locales.ts +0 -171
- package/templates/apps/mcp/src/lib/prisma.ts +0 -39
- package/templates/apps/mcp/src/lib/public-workspace.ts +0 -133
- package/templates/apps/mcp/src/lib/resolve-locale.ts +0 -30
- package/templates/apps/mcp/src/lib/shown-offerings.ts +0 -105
- package/templates/apps/mcp/src/lib/site-config.ts +0 -53
- package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +0 -59
- package/templates/apps/mcp/src/request-context.ts +0 -52
- package/templates/apps/mcp/src/server.ts +0 -343
- package/templates/apps/mcp/src/serverless-app.ts +0 -88
- package/templates/apps/mcp/src/tools/audit.ts +0 -127
- package/templates/apps/mcp/src/tools/capture_lead.ts +0 -66
- package/templates/apps/mcp/src/tools/create_project.ts +0 -134
- package/templates/apps/mcp/src/tools/get_business_hours.ts +0 -68
- package/templates/apps/mcp/src/tools/get_faq.ts +0 -71
- package/templates/apps/mcp/src/tools/get_locations.ts +0 -88
- package/templates/apps/mcp/src/tools/get_offerings.ts +0 -96
- package/templates/apps/mcp/src/tools/get_pricing.ts +0 -94
- package/templates/apps/mcp/src/tools/get_site_info.ts +0 -86
- package/templates/apps/mcp/src/tools/index.ts +0 -138
- package/templates/apps/mcp/src/tools/list_pages.ts +0 -68
- package/templates/apps/mcp/src/tools/person_notes.ts +0 -25
- package/templates/apps/mcp/src/tools/proposals.ts +0 -27
- package/templates/apps/mcp/src/tools/purchase.ts +0 -205
- package/templates/apps/mcp/src/tools/read_content.ts +0 -81
- package/templates/apps/mcp/src/tools/request_callback.ts +0 -52
- package/templates/apps/mcp/src/tools/search_content.ts +0 -63
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +0 -54
- package/templates/apps/mcp/src/workspace-server.ts +0 -224
- package/templates/database/CHANGELOG.md +0 -981
- package/templates/database/LICENSE +0 -202
- package/templates/database/NOTICE +0 -2
- package/templates/database/README.md +0 -243
- package/templates/database/__tests__/agents.db.test.ts +0 -320
- package/templates/database/__tests__/appointment.db.test.ts +0 -106
- package/templates/database/__tests__/artifact-content-store.db.test.ts +0 -316
- package/templates/database/__tests__/composite-fence.db.test.ts +0 -206
- package/templates/database/__tests__/consent-ledger.db.test.ts +0 -345
- package/templates/database/__tests__/consent-source-checkout-migration.test.ts +0 -79
- package/templates/database/__tests__/fence-fan-out.db.test.ts +0 -715
- package/templates/database/__tests__/fixtures/appointment-create-probe.ts +0 -79
- package/templates/database/__tests__/fixtures/person-backfill.json +0 -264
- package/templates/database/__tests__/fixtures/person-contact-backfill.json +0 -182
- package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +0 -100
- package/templates/database/__tests__/flow-completion-migration.test.ts +0 -115
- package/templates/database/__tests__/flow-completion.db.test.ts +0 -136
- package/templates/database/__tests__/flow-workspace-key.db.test.ts +0 -263
- package/templates/database/__tests__/flows-listing-diagnostic.test.ts +0 -392
- package/templates/database/__tests__/flows-presets.test.ts +0 -173
- package/templates/database/__tests__/in-page-magnets-migration.test.ts +0 -161
- package/templates/database/__tests__/lead-idempotency-key.db.test.ts +0 -92
- package/templates/database/__tests__/member-area.db.test.ts +0 -257
- package/templates/database/__tests__/member-contract-migration.test.ts +0 -85
- package/templates/database/__tests__/member-contract.db.test.ts +0 -413
- package/templates/database/__tests__/offerings-checkout-session.db.test.ts +0 -270
- package/templates/database/__tests__/offerings-migration.test.ts +0 -197
- package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +0 -86
- package/templates/database/__tests__/offerings.db.test.ts +0 -303
- package/templates/database/__tests__/outbound-message-action.db.test.ts +0 -80
- package/templates/database/__tests__/person-contact.db.test.ts +0 -563
- package/templates/database/__tests__/person-notes.db.test.ts +0 -249
- package/templates/database/__tests__/person.db.test.ts +0 -586
- package/templates/database/__tests__/piece-kind-migration.test.ts +0 -67
- package/templates/database/__tests__/piece-kind.db.test.ts +0 -208
- package/templates/database/__tests__/prefixed-ids.db.test.ts +0 -222
- package/templates/database/__tests__/project-listing-consent-at.db.test.ts +0 -111
- package/templates/database/__tests__/seed.test.ts +0 -272
- package/templates/database/__tests__/spoke-registry.db.test.ts +0 -191
- package/templates/database/__tests__/tenancy.test.ts +0 -141
- package/templates/database/__tests__/trial-magnet-migration.test.ts +0 -186
- package/templates/database/__tests__/user-cascade.test.ts +0 -140
- package/templates/database/_gitignore +0 -8
- package/templates/database/auth/.gitkeep +0 -0
- package/templates/database/auth/schema.prisma +0 -641
- package/templates/database/config/.gitkeep +0 -0
- package/templates/database/config/schema.prisma +0 -441
- package/templates/database/content/.gitkeep +0 -0
- package/templates/database/content/schema.prisma +0 -339
- package/templates/database/eslint.config.mjs +0 -69
- package/templates/database/flows/listing-diagnostic.ts +0 -182
- package/templates/database/flows/presets.ts +0 -315
- package/templates/database/inbox/.gitkeep +0 -0
- package/templates/database/inbox/README.md +0 -3
- package/templates/database/inbox/__tests__/schema.test.ts +0 -44
- package/templates/database/inbox/schema.prisma +0 -1447
- package/templates/database/jest.config.cjs +0 -36
- package/templates/database/migrations/20260508000000_add_inbox_namespace/migration.sql +0 -328
- package/templates/database/migrations/20260513000000_better_auth_migration/migration.sql +0 -100
- package/templates/database/migrations/20260515000000_add_user_billing_fields/migration.sql +0 -28
- package/templates/database/migrations/20260516000000_add_user_tier_care_plan_seat/migration.sql +0 -25
- package/templates/database/migrations/20260518000000_add_telemetry_records/migration.sql +0 -42
- package/templates/database/migrations/20260525000000_theme_wiring/migration.sql +0 -67
- package/templates/database/migrations/20260710000000_retire_commercial_tier/migration.sql +0 -21
- package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +0 -201
- package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +0 -67
- package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +0 -46
- package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +0 -25
- package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +0 -49
- package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +0 -197
- package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +0 -32
- package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +0 -22
- package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +0 -151
- package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +0 -223
- package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +0 -22
- package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +0 -32
- package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +0 -40
- package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +0 -4
- package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +0 -2
- package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +0 -2
- package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +0 -29
- package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +0 -6
- package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +0 -39
- package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +0 -24
- package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +0 -3
- package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +0 -33
- package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +0 -30
- package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +0 -47
- package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +0 -16
- package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +0 -47
- package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +0 -10
- package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +0 -26
- package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +0 -90
- package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +0 -174
- package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +0 -14
- package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +0 -25
- package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +0 -32
- package/templates/database/migrations/20260911180000_consent_grants/migration.sql +0 -71
- package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +0 -30
- package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +0 -49
- package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +0 -123
- package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +0 -15
- package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +0 -158
- package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +0 -40
- package/templates/database/migrations/20260918120000_add_appointment/migration.sql +0 -67
- package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +0 -70
- package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +0 -181
- package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +0 -172
- package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +0 -41
- package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +0 -185
- package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +0 -70
- package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +0 -57
- package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +0 -68
- package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +0 -1067
- package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +0 -210
- package/templates/database/migrations/20261003120000_spoke_registry/migration.sql +0 -80
- package/templates/database/migrations/20261003130000_scheduling_connection/migration.sql +0 -49
- package/templates/database/migrations/20261005120000_agents/migration.sql +0 -84
- package/templates/database/migrations/20261008120000_user_last_workspace/migration.sql +0 -35
- package/templates/database/migrations/20261008130000_lead_idempotency_key/migration.sql +0 -20
- package/templates/database/migrations/20261009120000_agent_grants/migration.sql +0 -33
- package/templates/database/migrations/20261010120000_person/migration.sql +0 -106
- package/templates/database/migrations/20261010121000_person_backfill/migration.sql +0 -132
- package/templates/database/migrations/20261010130000_agent_grants_people_read/migration.sql +0 -20
- package/templates/database/migrations/20261011120000_member_area/migration.sql +0 -158
- package/templates/database/migrations/20261011130000_agent_grants_member_pieces_manage/migration.sql +0 -21
- package/templates/database/migrations/20261012120000_member_contract/migration.sql +0 -192
- package/templates/database/migrations/20261013120000_lead_spoke_checkout/migration.sql +0 -20
- package/templates/database/migrations/20261014120000_consent_ledger/migration.sql +0 -122
- package/templates/database/migrations/20261014120000_person_contact/migration.sql +0 -231
- package/templates/database/migrations/20261015120000_person_notes/migration.sql +0 -77
- package/templates/database/migrations/20261015120000_rate_limit_counters/migration.sql +0 -43
- package/templates/database/migrations/20261016120000_piece_kind/migration.sql +0 -95
- package/templates/database/migrations/20261017120000_outbound_message_action/migration.sql +0 -30
- package/templates/database/migrations/20261018120000_flow_completion/migration.sql +0 -52
- package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +0 -27
- package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +0 -146
- package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +0 -77
- package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +0 -45
- package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +0 -47
- package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +0 -16
- package/templates/database/migrations/20261025120000_trial_magnet/migration.sql +0 -28
- package/templates/database/migrations/20261026120000_trial_magnet_proof_line/migration.sql +0 -23
- package/templates/database/migrations/migration_lock.toml +0 -3
- package/templates/database/ops/.gitkeep +0 -0
- package/templates/database/ops/schema.prisma +0 -43
- package/templates/database/package.json +0 -89
- package/templates/database/schema.prisma +0 -71
- package/templates/database/scripts/db-generate-locked.sh +0 -80
- package/templates/database/scripts/person-backfill.sql +0 -132
- package/templates/database/tenancy.ts +0 -183
- package/templates/database/tsconfig.build.json +0 -16
- package/templates/database/tsconfig.flows.json +0 -15
- package/templates/database/tsconfig.json +0 -16
- package/templates/database/tsconfig.tenancy.json +0 -14
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file usage-credits.ts
|
|
3
|
-
* @description Credit consumption waterfall (FEAT-047 P3.S1 — buckets 1-2).
|
|
4
|
-
* Spec: docs/specs/3730-credit-waterfall-atomicity-spec.md (hardening pass over
|
|
5
|
-
* the original docs/specs/3706-s1-credit-waterfall-service-spec.md).
|
|
6
|
-
*
|
|
7
|
-
* Charges a billed cost across the two non-overage buckets, in order:
|
|
8
|
-
* 1. included plan credits
|
|
9
|
-
* 2. carry-forward credit balance
|
|
10
|
-
* Bucket 3 (on-demand overage, pre-charge, spend limit) is P4 — this service
|
|
11
|
-
* leaves `onDemandPortionCents = 0` and returns any remainder as
|
|
12
|
-
* `uncoveredCents` rather than throwing. The caller (P2.S1's
|
|
13
|
-
* `recordUsageEvent`) stamps the returned split back onto the UsageEvent row —
|
|
14
|
-
* this service owns balances only.
|
|
15
|
-
*
|
|
16
|
-
* ─── The four invariants this file must hold (#3730) ────────────────────────
|
|
17
|
-
* The original implementation shipped unreviewed and violated all four. Each
|
|
18
|
-
* carries a test that fails without its fix (usage-credits.test.ts).
|
|
19
|
-
*
|
|
20
|
-
* 1. THE DEBIT IS ATOMIC (H1). The balance write is a conditional
|
|
21
|
-
* `updateMany` COMPARE-AND-SWAP whose `where` carries every column value
|
|
22
|
-
* the read observed; a concurrent writer that moved any of them makes the
|
|
23
|
-
* predicate miss, the update match 0 rows, and this service re-read,
|
|
24
|
-
* recompute, and retry (bounded — then `conflict`).
|
|
25
|
-
*
|
|
26
|
-
* ⚠️ NOT a `$transaction`. The sibling `usage-metering.ts` uses one, which
|
|
27
|
-
* makes it the locally-obvious reach — but wrapping the same
|
|
28
|
-
* read-then-write in a Read-Committed transaction does NOT fix the TOCTOU.
|
|
29
|
-
* Atomicity is not isolation. A single conditional UPDATE *is* the atomic
|
|
30
|
-
* compare-and-swap and needs no transaction: Postgres re-evaluates the
|
|
31
|
-
* `where` against the newly-committed row after the row lock releases
|
|
32
|
-
* (EvalPlanQual) — exactly the semantics required (spec D1).
|
|
33
|
-
*
|
|
34
|
-
* The `where` guards the WHOLE read-set — the ALLOWANCE
|
|
35
|
-
* (`includedCreditsCents`) as well as the two counters. The debit math
|
|
36
|
-
* reads the allowance, so a concurrent plan change that moves it while the
|
|
37
|
-
* counters sit still would otherwise slip a STALE allowance past a
|
|
38
|
-
* counters-only predicate. Read-set ≡ CAS-set (spec AC-2b).
|
|
39
|
-
*
|
|
40
|
-
* 2. AVAILABILITY IS ZERO-CLAMPED (H3). A plan DOWNGRADE legitimately leaves
|
|
41
|
-
* `used > included`, so `included − used` goes negative — and an unclamped
|
|
42
|
-
* `Math.min(negative, cost)` INVERTS the debit: it refunds consumed
|
|
43
|
-
* allowance and drains real credits (a verified 41× overcharge). Both
|
|
44
|
-
* derived availabilities are `Math.max(0, …)`.
|
|
45
|
-
*
|
|
46
|
-
* 3. THE BOUNDARY IS VALIDATED (H2/H4/M1/M2). `costCents` and
|
|
47
|
-
* `organizationId` are zod-parsed BEFORE any read or math. `NaN <= 0` is
|
|
48
|
-
* `false`, so the old guard let `NaN` through; `Infinity <= 0` is `false`
|
|
49
|
-
* too, and `Infinity` yields VALID finite integers (`min(800, Infinity) =
|
|
50
|
-
* 800`) that silently zero a customer's entire credit position.
|
|
51
|
-
* `z.number().int()` rejects `NaN`, `±Infinity`, and `10.5` — and `.min(0)`
|
|
52
|
-
* is a SEPARATE, REQUIRED clause, because `.int()` alone ACCEPTS `-50`.
|
|
53
|
-
*
|
|
54
|
-
* 4. NOTHING THROWS, NOTHING LEAKS (L1/L2). Every failure is a discriminated
|
|
55
|
-
* result. Raw DB error text is logged server-side and NEVER returned
|
|
56
|
-
* (Prisma embeds the full `data` argument — org ids, amounts), and
|
|
57
|
-
* `org_not_found` carries no org id: the house route pattern
|
|
58
|
-
* (`routes/leads/index.ts`) returns `error.message` verbatim to the client,
|
|
59
|
-
* which would make an echoed id an org-existence oracle.
|
|
60
|
-
*
|
|
61
|
-
* PII boundary: this service touches no PII — only organization ids and cent
|
|
62
|
-
* amounts. No Stripe call (P4).
|
|
63
|
-
*/
|
|
64
|
-
|
|
65
|
-
import { zOrganizationId, type Id } from "@working-theory/ids";
|
|
66
|
-
import { z } from "zod";
|
|
67
|
-
|
|
68
|
-
import { prisma } from "../lib/prisma.js";
|
|
69
|
-
|
|
70
|
-
// ─── Boundary validation (H2/H4/M1/M2) ────────────────────────────────────────
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* An ABSURDITY ceiling, not a business limit — so a corrupt caller cannot drive
|
|
74
|
-
* the debit to an absurd magnitude. $1,000,000 in cents. Mirrors
|
|
75
|
-
* `MAX_TOKENS_PER_EVENT` (usage-metering.ts).
|
|
76
|
-
*/
|
|
77
|
-
export const MAX_COST_CENTS = 100_000_000;
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* A cent cost: finite, integral, non-negative, bounded.
|
|
81
|
-
* `.int()` rejects `NaN` and `±Infinity` (neither is an integer) and `10.5`.
|
|
82
|
-
* `.min(0)` is REQUIRED SEPARATELY — `.int()` alone accepts `-50`.
|
|
83
|
-
*/
|
|
84
|
-
const zCostCents = z
|
|
85
|
-
.number()
|
|
86
|
-
.int("costCents must be an integer")
|
|
87
|
-
.min(0, "costCents cannot be negative")
|
|
88
|
-
.max(MAX_COST_CENTS, "costCents exceeds the per-call maximum");
|
|
89
|
-
|
|
90
|
-
const zConsumeInput = z.object({
|
|
91
|
-
organizationId: zOrganizationId,
|
|
92
|
-
costCents: zCostCents,
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
/** The bounded CAS retry budget (spec OQ-1). */
|
|
96
|
-
const MAX_ATTEMPTS = 3;
|
|
97
|
-
|
|
98
|
-
const CREDIT_SELECT = {
|
|
99
|
-
includedCreditsCents: true,
|
|
100
|
-
includedCreditsUsedCents: true,
|
|
101
|
-
onDemandCreditBalanceCents: true,
|
|
102
|
-
} as const;
|
|
103
|
-
|
|
104
|
-
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
105
|
-
|
|
106
|
-
type OrganizationCreditRow = {
|
|
107
|
-
includedCreditsCents: number;
|
|
108
|
-
includedCreditsUsedCents: number;
|
|
109
|
-
onDemandCreditBalanceCents: number;
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
type CreditSplit = {
|
|
113
|
-
includedPortionCents: number;
|
|
114
|
-
creditBalancePortionCents: number;
|
|
115
|
-
onDemandPortionCents: 0;
|
|
116
|
-
uncoveredCents: number;
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
/** Every failure this service can produce — no path throws. */
|
|
120
|
-
type CreditErrorKind =
|
|
121
|
-
| "invalid_input"
|
|
122
|
-
| "org_not_found"
|
|
123
|
-
| "conflict"
|
|
124
|
-
| "db_error";
|
|
125
|
-
|
|
126
|
-
type CreditError = { kind: CreditErrorKind; message: string };
|
|
127
|
-
|
|
128
|
-
export type ConsumeResult =
|
|
129
|
-
| ({ ok: true } & CreditSplit)
|
|
130
|
-
| { ok: false; error: CreditError };
|
|
131
|
-
|
|
132
|
-
export type CreditStatusResult =
|
|
133
|
-
| {
|
|
134
|
-
ok: true;
|
|
135
|
-
includedRemaining: number;
|
|
136
|
-
creditBalance: number;
|
|
137
|
-
effectiveRemaining: number;
|
|
138
|
-
}
|
|
139
|
-
| { ok: false; error: CreditError };
|
|
140
|
-
|
|
141
|
-
const ALL_ZERO: { ok: true } & CreditSplit = {
|
|
142
|
-
ok: true,
|
|
143
|
-
includedPortionCents: 0,
|
|
144
|
-
creditBalancePortionCents: 0,
|
|
145
|
-
onDemandPortionCents: 0,
|
|
146
|
-
uncoveredCents: 0,
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
// ─── Internals ────────────────────────────────────────────────────────────────
|
|
150
|
-
|
|
151
|
-
function invalidInput(error: z.ZodError): { ok: false; error: CreditError } {
|
|
152
|
-
// Field names + our own messages only — never the received value.
|
|
153
|
-
const message = error.issues
|
|
154
|
-
.map((issue) => `${issue.path.join(".") || "input"}: ${issue.message}`)
|
|
155
|
-
.join("; ");
|
|
156
|
-
return { ok: false, error: { kind: "invalid_input", message } };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/** L1 — no org id in the message; the route returns it verbatim to the client. */
|
|
160
|
-
const ORG_NOT_FOUND: { ok: false; error: CreditError } = {
|
|
161
|
-
ok: false,
|
|
162
|
-
error: { kind: "org_not_found", message: "organization not found" },
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
/** L2 — log the detail server-side; NEVER return it (Prisma embeds `data`). */
|
|
166
|
-
function dbError(
|
|
167
|
-
operation: string,
|
|
168
|
-
organizationId: Id<"organization">,
|
|
169
|
-
error: unknown,
|
|
170
|
-
): { ok: false; error: CreditError } {
|
|
171
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
172
|
-
console.error("usage-credits: %s failed", operation, {
|
|
173
|
-
organizationId,
|
|
174
|
-
detail,
|
|
175
|
-
});
|
|
176
|
-
return {
|
|
177
|
-
ok: false,
|
|
178
|
-
error: { kind: "db_error", message: "credit operation failed" },
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* The waterfall, with BOTH availabilities zero-clamped (H3). The clamp is the
|
|
184
|
-
* correct expression of the invariant: a downgraded org has zero included
|
|
185
|
-
* availability, so the call spills straight to carry-forward — the right
|
|
186
|
-
* billing behaviour — instead of inverting the debit.
|
|
187
|
-
*/
|
|
188
|
-
function computeSplit(
|
|
189
|
-
org: OrganizationCreditRow,
|
|
190
|
-
costCents: number,
|
|
191
|
-
): CreditSplit {
|
|
192
|
-
const includedAvailable = Math.max(
|
|
193
|
-
0,
|
|
194
|
-
org.includedCreditsCents - org.includedCreditsUsedCents,
|
|
195
|
-
);
|
|
196
|
-
const includedPortionCents = Math.min(includedAvailable, costCents);
|
|
197
|
-
const remainingAfterIncluded = costCents - includedPortionCents;
|
|
198
|
-
|
|
199
|
-
const balanceAvailable = Math.max(0, org.onDemandCreditBalanceCents);
|
|
200
|
-
const creditBalancePortionCents = Math.min(
|
|
201
|
-
balanceAvailable,
|
|
202
|
-
remainingAfterIncluded,
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
return {
|
|
206
|
-
includedPortionCents,
|
|
207
|
-
creditBalancePortionCents,
|
|
208
|
-
onDemandPortionCents: 0,
|
|
209
|
-
uncoveredCents: remainingAfterIncluded - creditBalancePortionCents,
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// ─── Main exports ─────────────────────────────────────────────────────────────
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Charges `costCents` against `organizationId`'s included plan credits, then
|
|
217
|
-
* its carry-forward credit balance (buckets 1-2). Returns the attribution
|
|
218
|
-
* split; the caller stamps it onto the UsageEvent row.
|
|
219
|
-
*/
|
|
220
|
-
export async function consumeCredits(
|
|
221
|
-
organizationId: Id<"organization">,
|
|
222
|
-
costCents: number,
|
|
223
|
-
): Promise<ConsumeResult> {
|
|
224
|
-
const parsed = zConsumeInput.safeParse({ organizationId, costCents });
|
|
225
|
-
if (!parsed.success) {
|
|
226
|
-
return invalidInput(parsed.error);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// A genuinely free call — no read, no write. (A NEGATIVE cost is a caller
|
|
230
|
-
// bug and is rejected above as `invalid_input`, not treated as free.)
|
|
231
|
-
if (parsed.data.costCents === 0) {
|
|
232
|
-
return ALL_ZERO;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const { organizationId: orgId, costCents: cost } = parsed.data;
|
|
236
|
-
|
|
237
|
-
try {
|
|
238
|
-
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
239
|
-
const org = await prisma.organization.findUnique({
|
|
240
|
-
where: { id: orgId },
|
|
241
|
-
select: CREDIT_SELECT,
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
if (!org) {
|
|
245
|
-
return ORG_NOT_FOUND;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const split = computeSplit(org, cost);
|
|
249
|
-
|
|
250
|
-
// Nothing to debit (exhausted, or a clamped-to-zero availability): the
|
|
251
|
-
// remainder is reported as uncovered and no write is needed, so there is
|
|
252
|
-
// no CAS to lose and no spurious `conflict`.
|
|
253
|
-
if (
|
|
254
|
-
split.includedPortionCents === 0 &&
|
|
255
|
-
split.creditBalancePortionCents === 0
|
|
256
|
-
) {
|
|
257
|
-
return { ok: true, ...split };
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// H1 — COMPARE-AND-SWAP. The `where` restates every value the read
|
|
261
|
-
// observed (allowance INCLUDED — spec AC-2b), so any concurrent writer
|
|
262
|
-
// makes this match 0 rows rather than committing stale amounts.
|
|
263
|
-
const { count } = await prisma.organization.updateMany({
|
|
264
|
-
where: {
|
|
265
|
-
id: orgId,
|
|
266
|
-
includedCreditsCents: org.includedCreditsCents,
|
|
267
|
-
includedCreditsUsedCents: org.includedCreditsUsedCents,
|
|
268
|
-
onDemandCreditBalanceCents: org.onDemandCreditBalanceCents,
|
|
269
|
-
},
|
|
270
|
-
data: {
|
|
271
|
-
includedCreditsUsedCents: { increment: split.includedPortionCents },
|
|
272
|
-
onDemandCreditBalanceCents: {
|
|
273
|
-
decrement: split.creditBalancePortionCents,
|
|
274
|
-
},
|
|
275
|
-
},
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
if (count > 0) {
|
|
279
|
-
return { ok: true, ...split };
|
|
280
|
-
}
|
|
281
|
-
// count === 0 — someone moved the row under us. Re-read and recompute.
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// An honest `conflict` the caller can retry beats an unbounded spin on
|
|
285
|
-
// money code (spec OQ-1).
|
|
286
|
-
return {
|
|
287
|
-
ok: false,
|
|
288
|
-
error: {
|
|
289
|
-
kind: "conflict",
|
|
290
|
-
message: "credit balance changed concurrently; retry the request",
|
|
291
|
-
},
|
|
292
|
-
};
|
|
293
|
-
} catch (error) {
|
|
294
|
-
return dbError("debit", orgId, error);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Returns the current credit standing for `organizationId` across the two P3
|
|
300
|
-
* buckets. On-demand overage remaining is P4 — not surfaced here.
|
|
301
|
-
*
|
|
302
|
-
* Both availabilities are zero-clamped, so a downgraded org reads `0`
|
|
303
|
-
* remaining rather than a negative number — consistent with what the debit
|
|
304
|
-
* will actually charge.
|
|
305
|
-
*/
|
|
306
|
-
export async function getCreditStatus(
|
|
307
|
-
organizationId: Id<"organization">,
|
|
308
|
-
): Promise<CreditStatusResult> {
|
|
309
|
-
const parsed = zOrganizationId.safeParse(organizationId);
|
|
310
|
-
if (!parsed.success) {
|
|
311
|
-
return invalidInput(parsed.error);
|
|
312
|
-
}
|
|
313
|
-
const orgId = parsed.data;
|
|
314
|
-
|
|
315
|
-
try {
|
|
316
|
-
const org = await prisma.organization.findUnique({
|
|
317
|
-
where: { id: orgId },
|
|
318
|
-
select: CREDIT_SELECT,
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
if (!org) {
|
|
322
|
-
return ORG_NOT_FOUND;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const includedRemaining = Math.max(
|
|
326
|
-
0,
|
|
327
|
-
org.includedCreditsCents - org.includedCreditsUsedCents,
|
|
328
|
-
);
|
|
329
|
-
const creditBalance = Math.max(0, org.onDemandCreditBalanceCents);
|
|
330
|
-
|
|
331
|
-
return {
|
|
332
|
-
ok: true,
|
|
333
|
-
includedRemaining,
|
|
334
|
-
creditBalance,
|
|
335
|
-
effectiveRemaining: includedRemaining + creditBalance,
|
|
336
|
-
};
|
|
337
|
-
} catch (error) {
|
|
338
|
-
return dbError("status read", orgId, error);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file verified-email.ts
|
|
3
|
-
* @description The one `User {id, email, emailVerified}` read every
|
|
4
|
-
* email-keyed authorization path in `apps/api` goes through (#5701 D4).
|
|
5
|
-
*
|
|
6
|
-
* `emailVerified` is load-bearing wherever this codebase joins an identity to
|
|
7
|
-
* data by email. An unverified address is an unproven CLAIM to the identity it
|
|
8
|
-
* names: the operator pin it might match (`services/default-tenant.ts`), the
|
|
9
|
-
* anonymous Lead rows it would promote (`services/lead-promotion.ts`,
|
|
10
|
-
* `services/listing-promotion.ts`), the respondent rows it would read back
|
|
11
|
-
* (`services/waitlist-signups.ts`, `services/flow-checkouts.ts`). Each of
|
|
12
|
-
* those five sites wrote the same read and the same refusal by hand, and
|
|
13
|
-
* "the same read, written six times" is exactly the shape that let #3631
|
|
14
|
-
* ship a sixth one without the check.
|
|
15
|
-
*
|
|
16
|
-
* So the read lives here once, the refusal is inside it rather than at the
|
|
17
|
-
* call site — a service-level invariant, not a caller obligation, which is
|
|
18
|
-
* what makes it unbypassable by a wrong call site — and callers receive the
|
|
19
|
-
* email ONLY when `emailVerified === true`. The two refusals stay distinct
|
|
20
|
-
* (`user_not_found` vs `email_unverified`) because the callers map them to
|
|
21
|
-
* different outcomes on the wire.
|
|
22
|
-
*
|
|
23
|
-
* `select` names exactly the three fields, so nothing else about the User row
|
|
24
|
-
* is read on an authorization path; `tools/lint-email-authz.ts` is the
|
|
25
|
-
* mechanical backstop that fails any NEW `prisma.user` read which selects
|
|
26
|
-
* `email` for a decision and omits `emailVerified`.
|
|
27
|
-
*
|
|
28
|
-
* ONE KNOWN IMPRECISION IN THE CALLERS, deliberate and unchanged by this
|
|
29
|
-
* helper. `User.email` is `@unique` in Postgres, which is byte-exact, while
|
|
30
|
-
* every caller that compares the address lowercases both sides — so two
|
|
31
|
-
* distinct rows (`Operator@x.com` and `operator@x.com`) could both satisfy one
|
|
32
|
-
* comparison. It is not reachable today: `apps/web/auth.ts` configures social
|
|
33
|
-
* providers only, so the address is provider-asserted and normalized rather
|
|
34
|
-
* than caller-chosen. It becomes reachable the moment a credential provider is
|
|
35
|
-
* enabled, and the durable fix is case-insensitive email uniqueness in the
|
|
36
|
-
* schema (citext, or lowercase-at-write) so "matches" and "is unique" agree.
|
|
37
|
-
* Matching case-sensitively instead would be worse: it would lock out an
|
|
38
|
-
* operator who typed their own address with different capitalisation.
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
import { prisma } from "../lib/prisma.js";
|
|
42
|
-
|
|
43
|
-
/** Why a verified-email read refused. Callers map these to their own outcomes. */
|
|
44
|
-
export type VerifiedEmailRefusal = "user_not_found" | "email_unverified";
|
|
45
|
-
|
|
46
|
-
export type VerifiedEmailResult =
|
|
47
|
-
| { ok: true; value: { id: string; email: string } }
|
|
48
|
-
| { ok: false; reason: VerifiedEmailRefusal };
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Read the `User` row for `userId` and return its email ONLY when the address
|
|
52
|
-
* is verified.
|
|
53
|
-
*
|
|
54
|
-
* @param userId the authenticated caller's id — never a request-supplied email.
|
|
55
|
-
* @returns `{ok: true}` with the verified address, or the refusal reason.
|
|
56
|
-
*/
|
|
57
|
-
export async function findVerifiedEmail(
|
|
58
|
-
userId: string,
|
|
59
|
-
): Promise<VerifiedEmailResult> {
|
|
60
|
-
const user = await prisma.user.findUnique({
|
|
61
|
-
where: { id: userId },
|
|
62
|
-
select: { id: true, email: true, emailVerified: true },
|
|
63
|
-
});
|
|
64
|
-
if (!user) {
|
|
65
|
-
return { ok: false, reason: "user_not_found" };
|
|
66
|
-
}
|
|
67
|
-
if (user.emailVerified !== true) {
|
|
68
|
-
return { ok: false, reason: "email_unverified" };
|
|
69
|
-
}
|
|
70
|
-
return { ok: true, value: { id: user.id, email: user.email } };
|
|
71
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for abuse/daily-cap.ts
|
|
3
|
-
*
|
|
4
|
-
* Verifies per-IP daily cap counter with configurable limit and daily reset.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { DailyCapTracker } from "../../src/abuse/daily-cap.js";
|
|
8
|
-
|
|
9
|
-
describe("DailyCapTracker", () => {
|
|
10
|
-
it("allows requests below the cap", () => {
|
|
11
|
-
const tracker = new DailyCapTracker(3);
|
|
12
|
-
expect(tracker.isAllowed("1.2.3.4")).toBe(true);
|
|
13
|
-
expect(tracker.isAllowed("1.2.3.4")).toBe(true);
|
|
14
|
-
expect(tracker.isAllowed("1.2.3.4")).toBe(true);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("blocks requests at and above the cap", () => {
|
|
18
|
-
const tracker = new DailyCapTracker(2);
|
|
19
|
-
tracker.isAllowed("1.2.3.4");
|
|
20
|
-
tracker.isAllowed("1.2.3.4");
|
|
21
|
-
// Third call should be blocked
|
|
22
|
-
expect(tracker.isAllowed("1.2.3.4")).toBe(false);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("tracks IPs independently", () => {
|
|
26
|
-
const tracker = new DailyCapTracker(1);
|
|
27
|
-
tracker.isAllowed("1.2.3.4");
|
|
28
|
-
// Exhausted for .4 but .5 is fresh
|
|
29
|
-
expect(tracker.isAllowed("1.2.3.4")).toBe(false);
|
|
30
|
-
expect(tracker.isAllowed("1.2.3.5")).toBe(true);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("resets counters after the daily window expires", () => {
|
|
34
|
-
jest.useFakeTimers();
|
|
35
|
-
const tracker = new DailyCapTracker(1);
|
|
36
|
-
tracker.isAllowed("1.2.3.4");
|
|
37
|
-
expect(tracker.isAllowed("1.2.3.4")).toBe(false);
|
|
38
|
-
|
|
39
|
-
// Advance 24 hours + 1ms
|
|
40
|
-
jest.advanceTimersByTime(24 * 60 * 60 * 1000 + 1);
|
|
41
|
-
expect(tracker.isAllowed("1.2.3.4")).toBe(true);
|
|
42
|
-
jest.useRealTimers();
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("uses cap=50 when none supplied", () => {
|
|
46
|
-
const tracker = new DailyCapTracker();
|
|
47
|
-
for (let i = 0; i < 50; i++) {
|
|
48
|
-
expect(tracker.isAllowed("9.9.9.9")).toBe(true);
|
|
49
|
-
}
|
|
50
|
-
expect(tracker.isAllowed("9.9.9.9")).toBe(false);
|
|
51
|
-
});
|
|
52
|
-
});
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for abuse/email-validator.ts
|
|
3
|
-
*
|
|
4
|
-
* Verifies RFC 5322-style email format validation used by
|
|
5
|
-
* capture_lead and subscribe_newsletter abuse rules.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { isValidEmail } from "../../src/abuse/email-validator.js";
|
|
9
|
-
|
|
10
|
-
describe("isValidEmail", () => {
|
|
11
|
-
it("accepts a well-formed email address", () => {
|
|
12
|
-
expect(isValidEmail("jane@example.com")).toBe(true);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it("accepts email with subdomain", () => {
|
|
16
|
-
expect(isValidEmail("user@mail.example.com")).toBe(true);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("accepts email with plus addressing", () => {
|
|
20
|
-
expect(isValidEmail("user+tag@example.com")).toBe(true);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("rejects empty string", () => {
|
|
24
|
-
expect(isValidEmail("")).toBe(false);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("rejects missing @", () => {
|
|
28
|
-
expect(isValidEmail("notanemail")).toBe(false);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("rejects missing domain", () => {
|
|
32
|
-
expect(isValidEmail("user@")).toBe(false);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("rejects missing local part", () => {
|
|
36
|
-
expect(isValidEmail("@example.com")).toBe(false);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("rejects missing TLD dot", () => {
|
|
40
|
-
expect(isValidEmail("user@localhost")).toBe(false);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("rejects plainly malformed input", () => {
|
|
44
|
-
expect(isValidEmail("not@valid@example.com")).toBe(false);
|
|
45
|
-
});
|
|
46
|
-
});
|