create-ailk 0.21.0 → 0.23.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/component-catalog.json +186 -8
- package/component-catalog.md +9 -7
- package/dist/add-module.js +20 -14
- package/dist/add-route.d.ts +55 -1
- package/dist/add-route.js +294 -12
- package/dist/cli.js +27 -10
- package/dist/derive-architecture.d.ts +12 -3
- package/dist/derive-architecture.js +20 -8
- package/dist/design-package.js +7 -2
- package/dist/lib/design-checks.js +4 -2
- package/dist/lib/env-catalog.js +5 -5
- package/dist/lib/npm-auth.d.ts +9 -0
- package/dist/lib/npm-auth.js +48 -0
- package/dist/lib/scaffold-render-sweep.d.ts +3 -2
- package/dist/lib/scaffold-render-sweep.js +5 -8
- package/dist/module-architecture.js +99 -9
- package/dist/paid-sections.d.ts +21 -2
- package/dist/paid-sections.js +69 -7
- package/dist/programmatic.js +19 -5
- package/dist/seed-flows.d.ts +4 -1
- package/dist/seed-flows.js +50 -4
- package/dist/site-chrome.d.ts +8 -0
- package/dist/site-chrome.js +17 -1
- package/dist/spoke-checkout.d.ts +65 -41
- package/dist/spoke-checkout.js +208 -430
- package/dist/starter-grid.d.ts +0 -3
- package/dist/starter-grid.js +24 -14
- package/dist/surfaces.js +52 -26
- package/dist/sync-routes.d.ts +38 -1
- package/dist/sync-routes.js +187 -13
- package/dist/template-record.d.ts +4 -3
- package/dist/template-record.js +8 -4
- package/dist/topology.js +25 -1
- package/dist/update.js +15 -0
- package/package.json +3 -3
- package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +4 -49
- package/site-templates/_base/content/en/Pricing/pricing.mdx +3 -21
- package/site-templates/designs/minimalism/DESIGN.md +1 -1
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +23 -20
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +28 -14
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +15 -39
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +38 -34
- package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/templates/.env.example +9 -7
- package/templates/.github/workflows/ci.yml +13 -0
- package/templates/README.md +1 -0
- package/templates/_gitignore +4 -0
- package/templates/apps/api/.env.example +27 -0
- package/templates/apps/api/CLAUDE.md +87 -17
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +2 -0
- package/templates/apps/api/src/bin/stripe-connections-rekey.ts +45 -0
- package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +185 -0
- package/templates/apps/api/src/config/index.ts +109 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -0
- package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +19 -0
- package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +149 -0
- package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +116 -0
- package/templates/apps/api/src/lib/consent-store.ts +28 -0
- package/templates/apps/api/src/lib/error-serializer.ts +74 -7
- package/templates/apps/api/src/lib/flow-completion-store.ts +136 -0
- package/templates/apps/api/src/lib/hub-sign-in-store.ts +38 -2
- package/templates/apps/api/src/lib/proposal-paid-store.ts +21 -4
- package/templates/apps/api/src/lib/stripe.ts +74 -4
- package/templates/apps/api/src/lib/webhook-idempotency.ts +66 -2
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +14 -3
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +218 -0
- package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +174 -0
- package/templates/apps/api/src/middleware/auth.ts +13 -6
- package/templates/apps/api/src/middleware/rate-limit.ts +226 -4
- package/templates/apps/api/src/openapi/spec.ts +33 -3
- package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +49 -0
- package/templates/apps/api/src/routes/auth/hub-sign-in.ts +41 -0
- package/templates/apps/api/src/routes/billing/plan-checkout.ts +325 -0
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +11 -0
- package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +1300 -0
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +11 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +49 -0
- package/templates/apps/api/src/routes/checkout/session.ts +57 -25
- package/templates/apps/api/src/routes/checkout/sessions.ts +232 -82
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +386 -0
- package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +160 -0
- package/templates/apps/api/src/routes/consent/account.ts +268 -0
- package/templates/apps/api/src/routes/consent/index.ts +15 -7
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +36 -0
- package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +731 -0
- package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +753 -0
- package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +762 -0
- package/templates/apps/api/src/routes/content/get-page.ts +4 -0
- package/templates/apps/api/src/routes/content/index.ts +16 -2
- package/templates/apps/api/src/routes/content/offerings.ts +184 -0
- package/templates/apps/api/src/routes/content/pricing.ts +176 -0
- package/templates/apps/api/src/routes/content/shown-offerings.ts +229 -0
- package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +515 -0
- package/templates/apps/api/src/routes/leads/index.ts +164 -7
- package/templates/apps/api/src/routes/webhooks/README.md +66 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +649 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +814 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +80 -68
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +5 -5
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +8 -2
- package/templates/apps/api/src/routes/webhooks/index.ts +23 -0
- package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +384 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +245 -36
- package/templates/apps/api/src/server.ts +163 -46
- package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +313 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +51 -2
- package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +234 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +1383 -133
- package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +389 -0
- package/templates/apps/api/src/services/__tests__/person.test.ts +232 -1
- package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +444 -0
- package/templates/apps/api/src/services/checkout-buyer.ts +100 -4
- package/templates/apps/api/src/services/consent.ts +95 -0
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +13 -5
- package/templates/apps/api/src/services/entitlements.ts +157 -1
- package/templates/apps/api/src/services/lead-routing.ts +431 -47
- package/templates/apps/api/src/services/magnet-completion.ts +242 -0
- package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +295 -0
- package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +268 -0
- package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +868 -0
- package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +416 -0
- package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +396 -0
- package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +681 -0
- package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +231 -0
- package/templates/apps/api/src/services/offerings/catalog.ts +308 -0
- package/templates/apps/api/src/services/offerings/checkout-authorization.ts +385 -0
- package/templates/apps/api/src/services/offerings/checkout-session-record.ts +192 -0
- package/templates/apps/api/src/services/offerings/connection.ts +1003 -0
- package/templates/apps/api/src/services/offerings/index.ts +614 -0
- package/templates/apps/api/src/services/offerings/source.ts +140 -0
- package/templates/apps/api/src/services/offerings/store.ts +150 -0
- package/templates/apps/api/src/services/person.ts +164 -17
- package/templates/apps/api/src/services/plan-offerings.ts +125 -0
- package/templates/apps/api/src/services/public-site-resolver.ts +27 -15
- package/templates/apps/mcp/CLAUDE.md +22 -14
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +9 -4
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +358 -0
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +364 -0
- package/templates/apps/mcp/__tests__/http-transport.test.ts +4 -4
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +2 -2
- package/templates/apps/mcp/__tests__/server.test.ts +13 -11
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +208 -12
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +11 -11
- package/templates/apps/mcp/src/lib/shown-offerings.ts +105 -0
- package/templates/apps/mcp/src/server.ts +5 -0
- package/templates/apps/mcp/src/tools/get_offerings.ts +47 -30
- package/templates/apps/mcp/src/tools/get_pricing.ts +40 -40
- package/templates/apps/mcp/src/tools/purchase.ts +30 -2
- package/templates/apps/mcp/src/workspace-server.ts +15 -0
- package/templates/apps/web/.env.example +24 -31
- package/templates/apps/web/CLAUDE.md +393 -9
- package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +6 -2
- package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +5 -4
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +5 -1
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +2 -0
- package/templates/apps/web/app/[locale]/(auth)/layout.tsx +3 -14
- package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +2 -0
- package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +5 -1
- package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/page.test.tsx +160 -8
- package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/sign-in-card.test.tsx +12 -9
- package/templates/apps/web/app/[locale]/(auth)/sign-in/page.tsx +105 -2
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +36 -3
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-text.ts +47 -1
- package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/sign-up-card.test.tsx +60 -22
- package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +16 -8
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +85 -31
- package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +18 -4
- package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +3 -1
- package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +7 -4
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +227 -207
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout-sign-out.test.tsx +17 -1
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +143 -1
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-menu.test.tsx +4 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +385 -89
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/popups.test.tsx +5 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +44 -5
- package/templates/apps/web/app/[locale]/(authed)/account/account-frame.tsx +68 -34
- package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +0 -8
- package/templates/apps/web/app/[locale]/(authed)/account/agreements-actions.ts +39 -151
- package/templates/apps/web/app/[locale]/(authed)/account/agreements-gate.ts +61 -0
- package/templates/apps/web/app/[locale]/(authed)/account/agreements-prompt.tsx +143 -140
- package/templates/apps/web/app/[locale]/(authed)/account/layout.tsx +40 -6
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +68 -54
- package/templates/apps/web/app/[locale]/(authed)/account/privacy/page.tsx +5 -1
- package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +189 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +295 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +120 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +23 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +319 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/careers-hub.test.tsx +172 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +149 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +141 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +424 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +72 -11
- package/templates/apps/web/app/[locale]/careers/page.tsx +129 -5
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/checkout/__tests__/pages.test.tsx +139 -0
- package/templates/apps/web/app/[locale]/checkout/__tests__/thank-you-account.test.tsx +136 -0
- package/templates/apps/web/app/[locale]/checkout/thank-you/page.tsx +114 -0
- package/templates/apps/web/app/[locale]/checkout/unavailable/page.tsx +51 -0
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +17 -1
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +79 -4
- package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +160 -13
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +466 -36
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +7 -0
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +172 -57
- package/templates/apps/web/app/[locale]/contact/intake-script.ts +26 -0
- package/templates/apps/web/app/[locale]/contact/page.tsx +99 -60
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +341 -4
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.count.test.tsx +43 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.deliverable.test.tsx +200 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.ending.test.tsx +197 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.invite.test.tsx +257 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/account-invite.actions.test.ts +69 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +430 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +21 -9
- package/templates/apps/web/app/[locale]/flows/[slug]/account-invite.actions.ts +35 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +352 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +12 -73
- package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +10 -31
- package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/layout.tsx +55 -4
- package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +174 -104
- package/templates/apps/web/app/[locale]/newsletter/page.tsx +95 -58
- package/templates/apps/web/app/[locale]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/pricing/__tests__/__snapshots__/unconnected.test.tsx.snap +7 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +599 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/page.test.tsx +216 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +236 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +367 -6
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +4 -1
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/thanks/[slug]/AccountInviteLink.tsx +89 -0
- package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/AccountInviteLink.test.tsx +104 -0
- package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +38 -0
- package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe-rsc-boundary.test.tsx +17 -0
- package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe.test.tsx +62 -3
- package/templates/apps/web/app/[locale]/unsubscribe/page.tsx +70 -18
- package/templates/apps/web/app/[locale]/unsubscribe/unsubscribe-button.tsx +30 -9
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +114 -0
- package/templates/apps/web/app/api/auth/hub/authorize/route.ts +20 -3
- package/templates/apps/web/app/api/hub-session/__tests__/routes.test.ts +44 -1
- package/templates/apps/web/app/api/hub-session/sign-in/route.ts +22 -3
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +316 -0
- package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +810 -0
- package/templates/apps/web/auth.ts +28 -2
- package/templates/apps/web/lib/__tests__/account-invite.test.ts +63 -0
- package/templates/apps/web/lib/__tests__/auth-routes.test.ts +27 -1
- package/templates/apps/web/lib/__tests__/consent-policies.test.ts +23 -0
- package/templates/apps/web/lib/__tests__/deck-page.test.ts +7 -1
- package/templates/apps/web/lib/__tests__/in-page-magnet.test.ts +265 -0
- package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +57 -0
- package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +293 -0
- package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +601 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +101 -25
- package/templates/apps/web/lib/account-invite.ts +37 -0
- package/templates/apps/web/lib/auth-routes.ts +36 -2
- package/templates/apps/web/lib/checkout-config.ts +16 -0
- package/templates/apps/web/lib/consent-policies.ts +22 -0
- package/templates/apps/web/lib/deck-page.ts +7 -0
- package/templates/apps/web/lib/deliverable-grant.ts +18 -0
- package/templates/apps/web/lib/hub-session/__tests__/sign-in-gate.test.tsx +111 -0
- package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +88 -32
- package/templates/apps/web/lib/in-page-magnet.ts +416 -0
- package/templates/apps/web/lib/magnet-routes.ts +42 -0
- package/templates/apps/web/lib/modules.generated.ts +9 -0
- package/templates/apps/web/lib/newsletter.actions.ts +202 -0
- package/templates/apps/web/lib/offerings-pricing.ts +326 -0
- package/templates/apps/web/lib/posting-title.ts +17 -0
- package/templates/apps/web/lib/prisma-for-auth.ts +8 -0
- package/templates/apps/web/lib/sign-up-agreements.ts +449 -0
- package/templates/apps/web/lib/site-chrome.ts +2 -0
- package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
- package/templates/apps/web/messages/en.json +71 -20
- package/templates/apps/web/middleware.ts +12 -2
- package/templates/apps/web/package.json +1 -0
- package/templates/apps/web/security-headers.config.ts +17 -0
- package/templates/apps/web/src/lib/__tests__/lead-write.test.ts +187 -0
- package/templates/apps/web/src/lib/__tests__/rate-limit.test.ts +67 -0
- package/templates/apps/web/src/lib/lead-write.ts +90 -0
- package/templates/apps/web/src/lib/rate-limit.ts +79 -0
- package/templates/content/en/BlogList/blog.mdx +4 -71
- package/templates/content/en/BookCall/book-a-call.mdx +1 -41
- package/templates/content/en/BookCall/onsite-estimate.mdx +1 -17
- package/templates/content/en/BookCall/onsite-service.mdx +1 -17
- package/templates/content/en/BookCall/start-project.mdx +1 -17
- package/templates/content/en/ContactPage/contact.mdx +6 -63
- package/templates/content/en/HomePage/home.mdx +3 -21
- package/templates/database/CHANGELOG.md +69 -0
- package/templates/database/__tests__/consent-source-checkout-migration.test.ts +79 -0
- package/templates/database/__tests__/flow-completion-migration.test.ts +115 -0
- package/templates/database/__tests__/flow-completion.db.test.ts +136 -0
- package/templates/database/__tests__/in-page-magnets-migration.test.ts +161 -0
- package/templates/database/__tests__/offerings-checkout-session.db.test.ts +270 -0
- package/templates/database/__tests__/offerings-migration.test.ts +197 -0
- package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +86 -0
- package/templates/database/__tests__/offerings.db.test.ts +303 -0
- package/templates/database/__tests__/seed.test.ts +64 -1
- package/templates/database/auth/schema.prisma +4 -0
- package/templates/database/config/schema.prisma +91 -0
- package/templates/database/inbox/schema.prisma +17 -2
- package/templates/database/migrations/20261018120000_flow_completion/migration.sql +52 -0
- package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +27 -0
- package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +146 -0
- package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +77 -0
- package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +45 -0
- package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +47 -0
- package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +16 -0
- package/templates/database/package.json +1 -1
- package/templates/database/seed.ts +62 -3
- package/versions.json +35 -35
- package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -199
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up.actions.ts +0 -125
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +0 -93
- package/templates/apps/web/app/[locale]/downloads/page.tsx +0 -37
- package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +0 -139
- package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +0 -93
- package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +0 -120
- package/templates/apps/web/lib/meeting-slugs.ts +0 -63
|
@@ -11,18 +11,18 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import cors from "@fastify/cors";
|
|
13
13
|
import rateLimit from "@fastify/rate-limit";
|
|
14
|
-
import { idRegex } from "@working-theory/ids";
|
|
15
14
|
import { logger } from "@working-theory/observability";
|
|
16
15
|
import Fastify from "fastify";
|
|
17
16
|
import type {
|
|
18
17
|
FastifyPluginAsync,
|
|
19
18
|
FastifyPluginCallback,
|
|
19
|
+
FastifyReply,
|
|
20
20
|
FastifyRequest,
|
|
21
21
|
} from "fastify";
|
|
22
22
|
|
|
23
23
|
import { config } from "./config/index.js";
|
|
24
24
|
import { isModuleEnabled, servedModules } from "./config/modules.js";
|
|
25
|
-
import { registerErrorSerializer } from "./lib/error-serializer.js";
|
|
25
|
+
import { redactLogPath, registerErrorSerializer } from "./lib/error-serializer.js";
|
|
26
26
|
import { validateLeadCaptureDir } from "./lib/lead-capture-store.js";
|
|
27
27
|
import { registerLicenseKeyStore } from "./lib/license-key-store.js";
|
|
28
28
|
import { requireCommerceClaim } from "./lib/require-commerce-claim.js";
|
|
@@ -35,7 +35,10 @@ import {
|
|
|
35
35
|
buildTrustedKeyDigests,
|
|
36
36
|
DEFAULT_SCHEDULE_PER_KEY_PER_MINUTE,
|
|
37
37
|
DEFAULT_PER_IP_PER_MINUTE_STRICT,
|
|
38
|
+
DEFAULT_SIGNUPEVIDENCE_PER_KEY_PER_MINUTE,
|
|
38
39
|
resolveRateLimitIdentity,
|
|
40
|
+
SITE_KEY_IDENTITY_PREFIX,
|
|
41
|
+
siteKeyReader,
|
|
39
42
|
} from "./middleware/rate-limit.js";
|
|
40
43
|
import { openapiRoutes } from "./openapi/index.js";
|
|
41
44
|
import {
|
|
@@ -50,24 +53,33 @@ import type { RegisteredRoute } from "./openapi/surface-drift.js";
|
|
|
50
53
|
import { exampleAeoRoute, scoreAeoRoute } from "./routes/aeo/index.js";
|
|
51
54
|
import {
|
|
52
55
|
hubSignInClientRoutes,
|
|
56
|
+
hubSignInPublicRoutes,
|
|
53
57
|
hubSignInSessionRoutes,
|
|
54
58
|
} from "./routes/auth/hub-sign-in.js";
|
|
55
59
|
import { tokenRoutes } from "./routes/auth/tokens.js";
|
|
56
60
|
import { transactionalEmailRoute } from "./routes/auth/transactional-email.js";
|
|
57
61
|
import { userRoutes } from "./routes/auth/user.js";
|
|
58
62
|
import {
|
|
63
|
+
consentAccountRoutes,
|
|
59
64
|
consentCookieRoute,
|
|
65
|
+
consentSignUpRoute,
|
|
60
66
|
consentUnsubscribeRoute,
|
|
61
67
|
} from "./routes/consent/index.js";
|
|
62
68
|
import { getPageRoute } from "./routes/content/get-page.js";
|
|
63
69
|
import { listPagesRoute } from "./routes/content/list-pages.js";
|
|
64
70
|
import { listTypesRoute } from "./routes/content/list-types.js";
|
|
71
|
+
import { offeringsRoute } from "./routes/content/offerings.js";
|
|
72
|
+
import { pricingRoute } from "./routes/content/pricing.js";
|
|
65
73
|
import {
|
|
66
74
|
CONTENT_RATE_LIMIT_AUTH,
|
|
67
75
|
CONTENT_RATE_LIMIT_UNAUTH,
|
|
68
76
|
CONTENT_RATE_LIMIT_WINDOW,
|
|
69
77
|
} from "./routes/content/rate-limit.js";
|
|
70
78
|
import { searchContentRoute } from "./routes/content/search.js";
|
|
79
|
+
import {
|
|
80
|
+
shownOfferingsRoute,
|
|
81
|
+
type ShownOfferingsRouteOptions,
|
|
82
|
+
} from "./routes/content/shown-offerings.js";
|
|
71
83
|
import {
|
|
72
84
|
leadCallbackRoute,
|
|
73
85
|
leadCaptureStatusRoute,
|
|
@@ -81,6 +93,8 @@ import {
|
|
|
81
93
|
shouldMountTestAuth,
|
|
82
94
|
} from "./routes/test/auth/session.js";
|
|
83
95
|
import { consentLinkSecretConfigured } from "./services/consent.js";
|
|
96
|
+
import { stripeCatalogSourceFor } from "./services/offerings/source.js";
|
|
97
|
+
import { resolveCallerSite } from "./services/public-site-resolver.js";
|
|
84
98
|
|
|
85
99
|
/**
|
|
86
100
|
* What a request looks like in a log line — Fastify's own default shape, minus
|
|
@@ -96,56 +110,43 @@ import { consentLinkSecretConfigured } from "./services/consent.js";
|
|
|
96
110
|
* Nothing that identifies a request is lost: a route parameter such as `:id`
|
|
97
111
|
* lives in the PATH and survives. Only the secret after `?` is dropped.
|
|
98
112
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* #6216 D11/§ 9 A-3 — the SAME rule now carries a second arm for
|
|
109
|
-
* `lead_…`-shaped segments. Fastify's automatic "incoming request" line is
|
|
110
|
-
* emitted by its internal hook BEFORE any route hook can intervene, so
|
|
111
|
-
* every `GET`/`PATCH .../workspaces/:workspaceId/messages/:leadId` would
|
|
112
|
-
* otherwise write the lead id into the log beside `remoteAddress` —
|
|
113
|
-
* completing exactly the `leadId` ↔ `siteId`/`ip`/`user-agent` correlation
|
|
114
|
-
* `routes/leads/index.ts` and this file's own PII posture refuse. No
|
|
115
|
-
* route-level discipline can fix it; this is the only place that can.
|
|
113
|
+
* Path segments that are themselves credentials or identifiers — a `cs_…`
|
|
114
|
+
* Checkout Session id (#6084 D14), a `lead_…` id (#6216 D11), a `person_…` id
|
|
115
|
+
* (#8438 SEC-8438-10) and a webhook connection token (#8932 D10) — are
|
|
116
|
+
* redacted by `redactLogPath` (`lib/error-serializer.ts`), the ONE path
|
|
117
|
+
* redactor this serializer and the global error handler share, so a request
|
|
118
|
+
* line and an error line can never disagree about what a path may carry.
|
|
119
|
+
* Fastify's automatic "incoming request" line is emitted by its internal hook
|
|
120
|
+
* BEFORE any route hook can intervene, so no route-level discipline can fix
|
|
121
|
+
* it; this is the only place that can.
|
|
116
122
|
*/
|
|
117
|
-
const SESSION_ID_LOG_SEGMENT = /^cs_[A-Za-z0-9_]+$/;
|
|
118
|
-
const LEAD_ID_LOG_SEGMENT = idRegex("lead");
|
|
119
|
-
// #8438 SEC-8438-10 — a third arm for `person_…` segments. The consents route
|
|
120
|
-
// (and every other `…/people/:personId…` route) would otherwise write a full
|
|
121
|
-
// Person id into the request log line, beside `remoteAddress`.
|
|
122
|
-
const PERSON_ID_LOG_SEGMENT = idRegex("person");
|
|
123
|
-
|
|
124
|
-
function redactSensitiveIdSegments(url: string): string {
|
|
125
|
-
return url
|
|
126
|
-
.split("/")
|
|
127
|
-
.map((segment) =>
|
|
128
|
-
SESSION_ID_LOG_SEGMENT.test(segment) ||
|
|
129
|
-
LEAD_ID_LOG_SEGMENT.test(segment) ||
|
|
130
|
-
PERSON_ID_LOG_SEGMENT.test(segment)
|
|
131
|
-
? `${segment.slice(0, 8)}…`
|
|
132
|
-
: segment,
|
|
133
|
-
)
|
|
134
|
-
.join("/");
|
|
135
|
-
}
|
|
136
|
-
|
|
137
123
|
export function requestLogSerializer(request: FastifyRequest) {
|
|
138
|
-
const raw = request.url;
|
|
139
|
-
const queryAt = raw.indexOf("?");
|
|
140
|
-
const path = queryAt === -1 ? raw : raw.slice(0, queryAt);
|
|
141
124
|
return {
|
|
142
125
|
method: request.method,
|
|
143
|
-
url:
|
|
126
|
+
url: redactLogPath(request.url),
|
|
144
127
|
host: request.hostname,
|
|
145
128
|
remoteAddress: request.ip,
|
|
146
129
|
};
|
|
147
130
|
}
|
|
148
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Fastify's default 404, with the logged path redacted (#8932 SEC-8932-10).
|
|
134
|
+
* The default handler logs `Route METHOD:URL not found` with the raw URL in
|
|
135
|
+
* the message, where `requestLogSerializer` never reaches, so a mis-pasted
|
|
136
|
+
* webhook URL — a trailing slash, a double slash, an extra segment — would
|
|
137
|
+
* log its connection token on every Stripe retry. The answer is the default
|
|
138
|
+
* one, byte for byte; only the log line changes.
|
|
139
|
+
*/
|
|
140
|
+
export function notFoundHandler(request: FastifyRequest, reply: FastifyReply) {
|
|
141
|
+
const { method, url = "" } = request.raw;
|
|
142
|
+
request.log.info(`Route ${method}:${redactLogPath(url)} not found`);
|
|
143
|
+
return reply.code(404).send({
|
|
144
|
+
message: `Route ${method}:${url} not found`,
|
|
145
|
+
error: "Not Found",
|
|
146
|
+
statusCode: 404,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
149
150
|
/**
|
|
150
151
|
* Paid-module SLICE entry points, keyed by the export each branch needs
|
|
151
152
|
* (#5182).
|
|
@@ -231,6 +232,8 @@ const WORKSPACES_MESSAGES = "./routes/workspaces/messages.js";
|
|
|
231
232
|
// #7535 — the Inbox submissions and subscribers reads, siblings of
|
|
232
233
|
// WORKSPACES_MESSAGES in the same workspace/multi-tenant scope.
|
|
233
234
|
const WORKSPACES_SUBMISSIONS = "./routes/workspaces/submissions.js";
|
|
235
|
+
// #9170 — the Inbox orders read, the SAME scope.
|
|
236
|
+
const WORKSPACES_ORDERS = "./routes/workspaces/orders.js";
|
|
234
237
|
const WORKSPACES_SUBSCRIBERS = "./routes/workspaces/subscribers.js";
|
|
235
238
|
// #852 D8 — the People reads (the workspace's clients), the SAME scope.
|
|
236
239
|
const WORKSPACES_PEOPLE = "./routes/workspaces/people.js";
|
|
@@ -286,6 +289,7 @@ const ASSETS_UPLOAD = "./routes/assets/upload.js";
|
|
|
286
289
|
const ASSETS_READ = "./routes/assets/read.js";
|
|
287
290
|
const ASSETS_MANAGE = "./routes/assets/manage.js";
|
|
288
291
|
const BILLING_INDEX = "./routes/billing/index.js";
|
|
292
|
+
const WORKSPACES_OFFERINGS = "./routes/workspaces/offerings.js";
|
|
289
293
|
const WAITLIST_EXPERIMENTS_INDEX = "./routes/waitlist-experiments/index.js";
|
|
290
294
|
const SCHEDULE_INDEX = "./routes/schedule/index.js";
|
|
291
295
|
const FLOWS_INDEX = "./routes/flows/index.js";
|
|
@@ -343,6 +347,11 @@ const SLICE_PLUGINS = {
|
|
|
343
347
|
specifier: WORKSPACES_SUBMISSIONS,
|
|
344
348
|
load: () => import(WORKSPACES_SUBMISSIONS),
|
|
345
349
|
},
|
|
350
|
+
// #9170 — the Inbox orders read, the SAME scope.
|
|
351
|
+
workspaceOrderRoutes: {
|
|
352
|
+
specifier: WORKSPACES_ORDERS,
|
|
353
|
+
load: () => import(WORKSPACES_ORDERS),
|
|
354
|
+
},
|
|
346
355
|
workspaceSubscriberRoutes: {
|
|
347
356
|
specifier: WORKSPACES_SUBSCRIBERS,
|
|
348
357
|
load: () => import(WORKSPACES_SUBSCRIBERS),
|
|
@@ -478,6 +487,11 @@ const SLICE_PLUGINS = {
|
|
|
478
487
|
specifier: BILLING_INDEX,
|
|
479
488
|
load: () => import(BILLING_INDEX),
|
|
480
489
|
},
|
|
490
|
+
// #8926 D1 — the workspace Offerings routes, multi-tenant only.
|
|
491
|
+
workspaceOfferingsRoutes: {
|
|
492
|
+
specifier: WORKSPACES_OFFERINGS,
|
|
493
|
+
load: () => import(WORKSPACES_OFFERINGS),
|
|
494
|
+
},
|
|
481
495
|
waitlistExperimentRoutes: {
|
|
482
496
|
specifier: WAITLIST_EXPERIMENTS_INDEX,
|
|
483
497
|
load: () => import(WAITLIST_EXPERIMENTS_INDEX),
|
|
@@ -501,6 +515,11 @@ const SLICE_PLUGINS = {
|
|
|
501
515
|
specifier: SCHEDULE_INDEX,
|
|
502
516
|
load: () => import(SCHEDULE_INDEX),
|
|
503
517
|
},
|
|
518
|
+
// #8843 D1 — the meetings read; same specifier as its schedule siblings.
|
|
519
|
+
listMeetingsRoute: {
|
|
520
|
+
specifier: SCHEDULE_INDEX,
|
|
521
|
+
load: () => import(SCHEDULE_INDEX),
|
|
522
|
+
},
|
|
504
523
|
// #6085 — the tenant-guarded appointments read; same specifier as the
|
|
505
524
|
// three anonymous schedule routes above (§ D6, no new SLICE_PLUGINS
|
|
506
525
|
// specifier constant needed).
|
|
@@ -536,6 +555,11 @@ const SLICE_PLUGINS = {
|
|
|
536
555
|
specifier: DELIVERABLES_INDEX,
|
|
537
556
|
load: () => import(DELIVERABLES_INDEX),
|
|
538
557
|
},
|
|
558
|
+
// #8848 D5 — an ungated file's bytes, in its own `deliverableopen` scope.
|
|
559
|
+
deliverableOpenRoute: {
|
|
560
|
+
specifier: DELIVERABLES_INDEX,
|
|
561
|
+
load: () => import(DELIVERABLES_INDEX),
|
|
562
|
+
},
|
|
539
563
|
projectListingStartRoute: {
|
|
540
564
|
specifier: PROJECT_LISTINGS_INDEX,
|
|
541
565
|
load: () => import(PROJECT_LISTINGS_INDEX),
|
|
@@ -837,6 +861,17 @@ export async function build() {
|
|
|
837
861
|
(msg) => server.log.warn(msg),
|
|
838
862
|
);
|
|
839
863
|
|
|
864
|
+
// #8805 — the sign-up evidence read's site key, verified once per request
|
|
865
|
+
// and shared by the global ceiling and the `signupevidence` scope below. A
|
|
866
|
+
// registered, unrevoked spoke key (`resolveCallerSite`, the registry lookup
|
|
867
|
+
// every spoke-aware route uses) is counted per key; a malformed, unknown or
|
|
868
|
+
// revoked one, or none, keeps `req.ip`. It decides the bucket only — the
|
|
869
|
+
// route still serves `resolveSiteWorkspaceId()`.
|
|
870
|
+
const signUpEvidenceSiteKey = siteKeyReader(async (req) => {
|
|
871
|
+
const caller = await resolveCallerSite(req);
|
|
872
|
+
return caller.kind === "spoke" ? caller.spokeId : null;
|
|
873
|
+
});
|
|
874
|
+
|
|
840
875
|
// Collect the route table this server registers (#5314).
|
|
841
876
|
//
|
|
842
877
|
// Installed here — after `Fastify()` and before the first `register()` — so
|
|
@@ -867,6 +902,7 @@ export async function build() {
|
|
|
867
902
|
// so connection strings, hostnames, stack traces, and SDK error details
|
|
868
903
|
// never reach the client. Full detail still goes to pino at error level.
|
|
869
904
|
registerErrorSerializer(server);
|
|
905
|
+
server.setNotFoundHandler(notFoundHandler);
|
|
870
906
|
|
|
871
907
|
// Register CORS — public read API + the trusted browser origins (#7395).
|
|
872
908
|
// `origin` is ALWAYS a copy of `config.TRUSTED_ORIGINS`, the frozen list
|
|
@@ -915,11 +951,23 @@ export async function build() {
|
|
|
915
951
|
// visitor's own ceiling here too, or a spoke whose visitors share one
|
|
916
952
|
// egress IP would still be capped at this limit in total. For every other
|
|
917
953
|
// request it returns `req.ip`, the plugin's own default key.
|
|
954
|
+
//
|
|
955
|
+
// #8805: a sign-up evidence read with a verified site key is keyed
|
|
956
|
+
// `site,<spokeId>` and let through here — its scope's per-key ceiling is
|
|
957
|
+
// its one limit, so an anonymous caller spoofing the web's egress address
|
|
958
|
+
// cannot spend it, and many web instances behind that address share no
|
|
959
|
+
// IP bucket.
|
|
918
960
|
await server.register(rateLimit, {
|
|
919
961
|
max: CONTENT_RATE_LIMIT_UNAUTH,
|
|
920
962
|
timeWindow: CONTENT_RATE_LIMIT_WINDOW,
|
|
921
|
-
keyGenerator: (req) =>
|
|
922
|
-
|
|
963
|
+
keyGenerator: async (req) => {
|
|
964
|
+
const siteId = await signUpEvidenceSiteKey(req);
|
|
965
|
+
return siteId === null
|
|
966
|
+
? resolveRateLimitIdentity(req, visitorForwardKeyDigests).identity
|
|
967
|
+
: `${SITE_KEY_IDENTITY_PREFIX}${siteId}`;
|
|
968
|
+
},
|
|
969
|
+
allowList: (_req, key) =>
|
|
970
|
+
typeof key === "string" && key.startsWith(SITE_KEY_IDENTITY_PREFIX),
|
|
923
971
|
});
|
|
924
972
|
|
|
925
973
|
// Health check
|
|
@@ -980,6 +1028,14 @@ export async function build() {
|
|
|
980
1028
|
// preHandler's hub-credential branch (D6).
|
|
981
1029
|
await authScope.register(hubSignInSessionRoutes);
|
|
982
1030
|
|
|
1031
|
+
// #8765 D5 — the caller's account-level sign-up agreements. Auth only,
|
|
1032
|
+
// NOT tenant-guarded: the agreement is about the `User` row, read and
|
|
1033
|
+
// written in the deployment's own site workspace whatever site asks.
|
|
1034
|
+
// Unconditional — auth and consent are never-gate surfaces — and both
|
|
1035
|
+
// are in `HUB_SESSION_ROUTES`, so a spoke reaches them with its member's
|
|
1036
|
+
// credential (the account layout's interstitial, D7).
|
|
1037
|
+
await authScope.register(consentAccountRoutes);
|
|
1038
|
+
|
|
983
1039
|
// Tenant-context routes (multi-tenant module) — auth only. They
|
|
984
1040
|
// establish/inspect the active workspace, so the tenant guard does not
|
|
985
1041
|
// apply to them; each enforces membership in the service layer.
|
|
@@ -1111,6 +1167,10 @@ export async function build() {
|
|
|
1111
1167
|
await tenantScope.register(await sliceRoutePlugin("sessionRoutes"));
|
|
1112
1168
|
// Org-billing routes (#3701) — org-scoped read + Stripe customer-portal.
|
|
1113
1169
|
await tenantScope.register(await sliceRoutePlugin("billingRoutes"));
|
|
1170
|
+
// The workspace Offerings routes (#8926 D1) — the Library → Offerings
|
|
1171
|
+
// tab's read and its selection write, beside the billing routes that
|
|
1172
|
+
// write the Stripe connection they need.
|
|
1173
|
+
await tenantScope.register(await sliceRoutePlugin("workspaceOfferingsRoutes"));
|
|
1114
1174
|
});
|
|
1115
1175
|
}
|
|
1116
1176
|
|
|
@@ -1257,6 +1317,11 @@ export async function build() {
|
|
|
1257
1317
|
await tenantScope.register(
|
|
1258
1318
|
await sliceRoutePlugin("workspaceSubscriberRoutes"),
|
|
1259
1319
|
);
|
|
1320
|
+
// The Inbox orders read (#9170) — the SAME scope and guard: the
|
|
1321
|
+
// workspace's purchases (list, item read), behind `inbox-read`.
|
|
1322
|
+
await tenantScope.register(
|
|
1323
|
+
await sliceRoutePlugin("workspaceOrderRoutes"),
|
|
1324
|
+
);
|
|
1260
1325
|
// The People reads (#852 D8) — the SAME scope and guard: the
|
|
1261
1326
|
// workspace's clients and one Person with its derived timeline,
|
|
1262
1327
|
// behind `people-read` (owner, admin, a granted agent).
|
|
@@ -1374,10 +1439,26 @@ export async function build() {
|
|
|
1374
1439
|
await server.register(await sliceRoutePlugin("publicAssetReadRoute"));
|
|
1375
1440
|
}
|
|
1376
1441
|
await server.register(listTypesRoute);
|
|
1377
|
-
// Before `listPagesRoute`: `search`
|
|
1442
|
+
// Before `listPagesRoute`: `search`, `offerings`, `pricing` and
|
|
1443
|
+
// `shown-offerings` must never be read as a `:type`.
|
|
1378
1444
|
await server.register(searchContentRoute);
|
|
1445
|
+
// The Organization's Stripe catalog source (#8922), so a stale snapshot
|
|
1446
|
+
// refreshes on the service's bounded, never-forced path (#8924 D7, #9124;
|
|
1447
|
+
// the bounds are `refreshWorkspaceSnapshot`'s). The two agent twins read
|
|
1448
|
+
// the shown set through the same read with these same options, so all
|
|
1449
|
+
// three refresh together (#8933 D5).
|
|
1450
|
+
const shownSetRouteOptions: ShownOfferingsRouteOptions = {
|
|
1451
|
+
sourceFor: stripeCatalogSourceFor,
|
|
1452
|
+
};
|
|
1453
|
+
await server.register(offeringsRoute, shownSetRouteOptions);
|
|
1454
|
+
await server.register(pricingRoute, shownSetRouteOptions);
|
|
1455
|
+
await server.register(shownOfferingsRoute, shownSetRouteOptions);
|
|
1379
1456
|
await server.register(listPagesRoute);
|
|
1380
1457
|
await server.register(getPageRoute);
|
|
1458
|
+
// The hub's sign-in page names the spoke that sent a visitor (#8790 D2):
|
|
1459
|
+
// a public read of one client's name + origin from the registry. No auth —
|
|
1460
|
+
// the visitor is signed out by definition when the page asks.
|
|
1461
|
+
await server.register(hubSignInPublicRoutes);
|
|
1381
1462
|
// Checkout launcher + reader — commerce PRIMITIVES (D-64, #5796; the GET
|
|
1382
1463
|
// added by #6084). Registered UNCONDITIONALLY and gated at CALL time on
|
|
1383
1464
|
// the JWT `commerce` claim, the same posture content-write has
|
|
@@ -1456,6 +1537,20 @@ export async function build() {
|
|
|
1456
1537
|
// (signal-collectors, D-48): gated PDFs and reports.
|
|
1457
1538
|
if (isModuleEnabled("signal-collectors")) {
|
|
1458
1539
|
await server.register(await sliceRoutePlugin("deliverableDownloadRoute"));
|
|
1540
|
+
// #8848 D5 — the ungated file read, anonymous and keyed by slug, so it
|
|
1541
|
+
// gets its OWN encapsulated scope: `rateLimitMiddleware` breaks
|
|
1542
|
+
// encapsulation, so it is never installed on the grant route's
|
|
1543
|
+
// registration above, whose budget stays as it is. With the hub's
|
|
1544
|
+
// forward-key digests (the route is on FORWARDED_VISITOR_ROUTES), a
|
|
1545
|
+
// site's proxied reads are counted per visitor (SEC-8848-C).
|
|
1546
|
+
await server.register(async (deliverableOpenScope) => {
|
|
1547
|
+
deliverableOpenScope.register(rateLimitMiddleware, {
|
|
1548
|
+
perIpPerMinute: 60,
|
|
1549
|
+
scope: "deliverableopen",
|
|
1550
|
+
trustedKeyDigests: visitorForwardKeyDigests,
|
|
1551
|
+
});
|
|
1552
|
+
await deliverableOpenScope.register(await sliceRoutePlugin("deliverableOpenRoute"));
|
|
1553
|
+
});
|
|
1459
1554
|
}
|
|
1460
1555
|
|
|
1461
1556
|
// Webhooks routes — raw-body parser scoped to this sub-router only so the
|
|
@@ -1570,6 +1665,24 @@ export async function build() {
|
|
|
1570
1665
|
});
|
|
1571
1666
|
await unsubscribeScope.register(consentUnsubscribeRoute);
|
|
1572
1667
|
});
|
|
1668
|
+
// #8765 D2 — the sign-up agreements' evidence, beside the consent floor:
|
|
1669
|
+
// anonymous, outside every module gate (sign-up and consent are never-gate
|
|
1670
|
+
// surfaces). Its own scope, `signupevidence`, so an anonymous burst never
|
|
1671
|
+
// shares a bucket with the cookie banner's log (SEC-8765-D). Its main
|
|
1672
|
+
// caller is the web's own server, whose cache keeps it to one call a minute
|
|
1673
|
+
// per enabled locale per instance. #8805: a call with a verified site key
|
|
1674
|
+
// is counted per key (DEFAULT_SIGNUPEVIDENCE_PER_KEY_PER_MINUTE,
|
|
1675
|
+
// AILK_RATE_LIMIT_SIGNUPEVIDENCE_PER_KEY_PER_MINUTE), never per IP; any
|
|
1676
|
+
// other call keeps 10 a minute per IP.
|
|
1677
|
+
await server.register(async (signUpEvidenceScope) => {
|
|
1678
|
+
signUpEvidenceScope.register(rateLimitMiddleware, {
|
|
1679
|
+
perIpPerMinute: DEFAULT_PER_IP_PER_MINUTE_STRICT,
|
|
1680
|
+
perKeyPerMinute: DEFAULT_SIGNUPEVIDENCE_PER_KEY_PER_MINUTE,
|
|
1681
|
+
scope: "signupevidence",
|
|
1682
|
+
siteKeyOf: signUpEvidenceSiteKey,
|
|
1683
|
+
});
|
|
1684
|
+
await signUpEvidenceScope.register(consentSignUpRoute);
|
|
1685
|
+
});
|
|
1573
1686
|
|
|
1574
1687
|
// Leads routes — rate-limit middleware scoped to this sub-router. Own
|
|
1575
1688
|
// default (10/min, pre-#4730) rather than inheriting the wider
|
|
@@ -1744,6 +1857,10 @@ export async function build() {
|
|
|
1744
1857
|
await scheduleScope.register(
|
|
1745
1858
|
await sliceRoutePlugin("meetingTypesRoute"),
|
|
1746
1859
|
);
|
|
1860
|
+
// #8843 D1 — the meetings read: the same gate and per-IP limit.
|
|
1861
|
+
await scheduleScope.register(
|
|
1862
|
+
await sliceRoutePlugin("listMeetingsRoute"),
|
|
1863
|
+
);
|
|
1747
1864
|
});
|
|
1748
1865
|
}
|
|
1749
1866
|
|