create-ailk 0.2.0 → 0.4.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.md +50 -53
- package/dist/cli.js +19 -9
- package/dist/lib/apply-module-patches.d.ts +47 -0
- package/dist/lib/apply-module-patches.js +370 -0
- package/dist/lib/extract-module-bundle.d.ts +51 -3
- package/dist/lib/extract-module-bundle.js +327 -28
- package/dist/lib/fetch-module.d.ts +3 -38
- package/dist/lib/fetch-module.js +26 -8
- package/dist/lib/fetch-template.d.ts +20 -0
- package/dist/lib/fetch-template.js +129 -0
- package/dist/lib/module-license-gate.d.ts +10 -0
- package/dist/lib/module-license-gate.js +13 -4
- package/dist/lib/paid-paths.d.ts +16 -0
- package/dist/lib/paid-paths.js +90 -0
- package/dist/module-architecture.d.ts +197 -23
- package/dist/module-architecture.js +820 -25
- package/dist/parse-args.d.ts +9 -2
- package/dist/parse-args.js +46 -1
- package/dist/programmatic.d.ts +28 -2
- package/dist/programmatic.js +115 -8
- package/dist/surfaces.d.ts +12 -36
- package/dist/surfaces.js +77 -5
- package/dist/sync-routes.js +31 -1
- package/dist/template-manifest.d.ts +92 -0
- package/dist/template-manifest.js +168 -0
- package/dist/template-names.d.ts +41 -0
- package/dist/template-names.js +44 -0
- package/package.json +3 -2
- package/site-templates/marketing-site-free/ailk-template.json +8 -0
- package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +103 -0
- package/site-templates/marketing-site-free/content/en/PrivacyPage/privacy.mdx +79 -0
- package/site-templates/marketing-site-free/content/en/TermsPage/terms.mdx +80 -0
- package/site-templates/marketing-site-free/provenance.json +4 -0
- package/templates/.claude/agents/web.md +2 -3
- package/templates/.claude/rules/architecture.md +5 -5
- package/templates/.claude/skills/README.md +2 -1
- package/templates/.env.example +21 -9
- package/templates/CONVENTIONS.md +6 -7
- package/templates/apps/api/.env.example +9 -0
- package/templates/apps/api/CLAUDE.md +33 -5
- package/templates/apps/api/package.json +0 -1
- package/templates/apps/api/src/__tests__/cors.test.ts +195 -0
- package/templates/apps/api/src/__tests__/module-exclusion.test.ts +60 -2
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +159 -0
- package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +166 -0
- package/templates/apps/api/src/config/__tests__/modules.test.ts +76 -0
- package/templates/apps/api/src/config/index.ts +19 -0
- package/templates/apps/api/src/config/modules.ts +12 -14
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -0
- package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +89 -0
- package/templates/apps/api/src/lib/slice-load.ts +90 -0
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +52 -32
- package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +182 -0
- package/templates/apps/api/src/openapi/__tests__/surface-drift.test.ts +293 -0
- package/templates/apps/api/src/openapi/index.ts +9 -3
- package/templates/apps/api/src/openapi/spec.ts +505 -65
- package/templates/apps/api/src/openapi/surface-drift.ts +195 -0
- package/templates/apps/api/src/routes/content/__tests__/create.test.ts +14 -0
- package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +14 -0
- package/templates/apps/api/src/routes/content/__tests__/update.test.ts +14 -0
- package/templates/apps/api/src/routes/content/index.ts +38 -11
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +17 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +17 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +796 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +10 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +38 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +814 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +10 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +24 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +17 -0
- package/templates/apps/api/src/routes/project-listings/index.ts +37 -10
- package/templates/apps/api/src/routes/project-listings/me.ts +10 -0
- package/templates/apps/api/src/routes/project-listings/patch-draft.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/public.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/respond.ts +11 -0
- package/templates/apps/api/src/routes/project-listings/resume-token.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/start.ts +30 -0
- package/templates/apps/api/src/routes/project-listings/submit.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/verify-email.ts +150 -0
- package/templates/apps/api/src/server.ts +504 -137
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +10 -0
- package/templates/apps/api/src/services/listing-config.ts +58 -0
- package/templates/apps/api/src/services/project-listing-resume-email.ts +326 -0
- package/templates/apps/api/src/services/project-listings.ts +229 -4
- package/templates/apps/api/src/vercel-handler.ts +60 -26
- package/templates/apps/mcp/.env.example +8 -0
- package/templates/apps/mcp/CLAUDE.md +2 -2
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +21 -0
- package/templates/apps/mcp/__tests__/config/modules.test.ts +78 -0
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +1 -1
- package/templates/apps/mcp/src/config/modules.ts +14 -13
- package/templates/apps/mcp/src/server.ts +8 -4
- package/templates/apps/mcp/src/tools/index.ts +7 -22
- package/templates/apps/web/.env.example +15 -0
- package/templates/apps/web/app/[locale]/(authed)/{waitlist/__tests__ → __tests__}/gate.test.tsx +4 -4
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +1 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +1 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +1 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +1 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +40 -0
- package/templates/apps/web/app/[locale]/privacy/page.tsx +40 -0
- package/templates/apps/web/app/[locale]/terms/page.tsx +40 -0
- package/templates/apps/web/app/llms.txt/route.ts +12 -9
- package/templates/apps/web/jest.config.cjs +19 -13
- package/templates/apps/web/lib/__tests__/server-api.test.ts +61 -0
- package/templates/apps/web/lib/server-api.ts +28 -12
- package/templates/apps/web/messages/en.json +58 -0
- package/templates/apps/web/next.config.mjs +1 -3
- package/templates/apps/web/package.json +0 -6
- package/templates/apps/web/public/android-chrome-192x192.png +0 -0
- package/templates/apps/web/public/android-chrome-512x512.png +0 -0
- package/templates/apps/web/public/apple-touch-icon.png +0 -0
- package/templates/apps/web/public/favicon-16x16.png +0 -0
- package/templates/apps/web/public/favicon-32x32.png +0 -0
- package/templates/apps/web/public/favicon.ico +0 -0
- package/templates/apps/web/public/favicon.svg +6 -3
- package/templates/apps/web/public/lockup-horizontal.svg +4 -0
- package/templates/apps/web/public/logomark.svg +4 -0
- package/templates/apps/web/public/site.webmanifest +2 -2
- package/templates/apps/web/public/wordmark.svg +4 -0
- package/templates/database/CHANGELOG.md +47 -0
- package/templates/database/inbox/schema.prisma +67 -0
- package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +123 -0
- package/templates/database/package.json +1 -1
- package/templates/package.json +1 -1
- package/templates/.claude/skills/scaffold-commerce/SKILL.md +0 -807
- package/templates/.claude/skills/scaffold-commerce/references/lookup-keys-template.md +0 -71
- package/templates/.claude/skills/scaffold-commerce/references/marketplace-brand-pages.md +0 -83
- package/templates/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts +0 -370
- package/templates/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts +0 -376
- package/templates/.claude/skills/scaffold-commerce/templates/marketplace-cta-route.tsx.tmpl +0 -72
- package/templates/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts +0 -471
- package/templates/.claude/skills/scaffold-commerce/templates/portal-route.template.ts +0 -365
- package/templates/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts +0 -60
- package/templates/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts +0 -446
- package/templates/apps/api/src/__tests__/server.test.ts +0 -253
- package/templates/apps/api/src/bin/deliverable-resend.ts +0 -144
- package/templates/apps/api/src/bin/deliverable-upload.ts +0 -114
- package/templates/apps/api/src/bin/followup-sweep.ts +0 -44
- package/templates/apps/api/src/bin/listing-csv-sweep.ts +0 -46
- package/templates/apps/api/src/bin/seed-presets.ts +0 -58
- package/templates/apps/api/src/bin/seed-waitlist-experiments.ts +0 -104
- package/templates/apps/api/src/bin/session-retention-sweep.ts +0 -46
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -74
- package/templates/apps/api/src/lib/stripe.ts +0 -52
- package/templates/apps/api/src/lib/tenant-db.ts +0 -225
- package/templates/apps/api/src/lib/ws-token.ts +0 -91
- package/templates/apps/api/src/middleware/tenant.ts +0 -99
- package/templates/apps/api/src/routes/billing/__tests__/portal.test.ts +0 -450
- package/templates/apps/api/src/routes/billing/__tests__/read.test.ts +0 -262
- package/templates/apps/api/src/routes/billing/__tests__/usage.test.ts +0 -436
- package/templates/apps/api/src/routes/billing/index.ts +0 -36
- package/templates/apps/api/src/routes/billing/portal.ts +0 -182
- package/templates/apps/api/src/routes/billing/read.ts +0 -75
- package/templates/apps/api/src/routes/billing/usage.ts +0 -153
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -687
- package/templates/apps/api/src/routes/checkout/index.ts +0 -13
- package/templates/apps/api/src/routes/checkout/sessions.ts +0 -197
- package/templates/apps/api/src/routes/deliverables/__tests__/index.test.ts +0 -393
- package/templates/apps/api/src/routes/deliverables/index.ts +0 -200
- package/templates/apps/api/src/routes/flow-checkouts/__tests__/index.test.ts +0 -443
- package/templates/apps/api/src/routes/flow-checkouts/index.ts +0 -82
- package/templates/apps/api/src/routes/flows/README.md +0 -147
- package/templates/apps/api/src/routes/flows/__tests__/index.test.ts +0 -752
- package/templates/apps/api/src/routes/flows/__tests__/recommender.test.ts +0 -671
- package/templates/apps/api/src/routes/flows/index.ts +0 -202
- package/templates/apps/api/src/routes/flows/recommender.ts +0 -443
- package/templates/apps/api/src/routes/project-listings/__tests__/copy-edit.test.ts +0 -815
- package/templates/apps/api/src/routes/project-listings/__tests__/me-route-precedence.test.ts +0 -221
- package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +0 -566
- package/templates/apps/api/src/routes/project-listings/get.ts +0 -70
- package/templates/apps/api/src/routes/project-listings/list.ts +0 -47
- package/templates/apps/api/src/routes/project-listings/patch.ts +0 -136
- package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +0 -490
- package/templates/apps/api/src/routes/schedule/index.ts +0 -249
- package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +0 -385
- package/templates/apps/api/src/routes/slack/actions.ts +0 -177
- package/templates/apps/api/src/routes/slack/index.ts +0 -38
- package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +0 -577
- package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +0 -78
- package/templates/apps/api/src/routes/waitlist-experiments/index.ts +0 -41
- package/templates/apps/api/src/routes/waitlist-experiments/list.ts +0 -64
- package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +0 -42
- package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +0 -84
- package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +0 -119
- package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +0 -238
- package/templates/apps/api/src/routes/waitlist-signups/index.ts +0 -101
- package/templates/apps/api/src/routes/webhooks/README.md +0 -80
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -315
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -247
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -270
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -198
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -633
- package/templates/apps/api/src/routes/webhooks/index.ts +0 -41
- package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -481
- package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +0 -356
- package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +0 -671
- package/templates/apps/api/src/routes/workspaces/config.ts +0 -125
- package/templates/apps/api/src/routes/workspaces/index.ts +0 -194
- package/templates/apps/api/src/routes/workspaces/sessions.ts +0 -218
- package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +0 -425
- package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +0 -2840
- package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +0 -393
- package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +0 -560
- package/templates/apps/api/src/services/__tests__/listing-promotion.test.ts +0 -684
- package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +0 -406
- package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +0 -290
- package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +0 -736
- package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +0 -475
- package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +0 -340
- package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +0 -983
- package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +0 -71
- package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +0 -379
- package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +0 -768
- package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +0 -1314
- package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +0 -341
- package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +0 -567
- package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +0 -474
- package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +0 -354
- package/templates/apps/api/src/services/flow-aggregate.ts +0 -232
- package/templates/apps/api/src/services/flow-checkouts.ts +0 -123
- package/templates/apps/api/src/services/flow-engine.ts +0 -1296
- package/templates/apps/api/src/services/lead-promotion.ts +0 -176
- package/templates/apps/api/src/services/listing-csv-sweep.ts +0 -455
- package/templates/apps/api/src/services/listing-promotion.ts +0 -342
- package/templates/apps/api/src/services/playbook-compile.ts +0 -398
- package/templates/apps/api/src/services/playbook-render.ts +0 -263
- package/templates/apps/api/src/services/project-listing-decision.ts +0 -510
- package/templates/apps/api/src/services/project-listing-flow.ts +0 -426
- package/templates/apps/api/src/services/project-listing-issue.ts +0 -251
- package/templates/apps/api/src/services/recommender-capture.ts +0 -835
- package/templates/apps/api/src/services/scheduling/cal-provider.ts +0 -392
- package/templates/apps/api/src/services/scheduling/index.ts +0 -63
- package/templates/apps/api/src/services/scheduling/types.ts +0 -88
- package/templates/apps/api/src/services/tenant-context.ts +0 -451
- package/templates/apps/api/src/services/usage-metering.ts +0 -699
- package/templates/apps/api/src/services/waitlist-dashboard.ts +0 -947
- package/templates/apps/api/src/services/waitlist-experiments.ts +0 -213
- package/templates/apps/api/src/services/waitlist-followup.ts +0 -486
- package/templates/apps/api/src/services/waitlist-scoring.ts +0 -166
- package/templates/apps/api/src/services/waitlist-signups.ts +0 -165
- package/templates/apps/mcp/__tests__/schedule_tools.test.ts +0 -242
- package/templates/apps/mcp/src/tools/create_booking.ts +0 -58
- package/templates/apps/mcp/src/tools/get_event_meta.ts +0 -55
- package/templates/apps/mcp/src/tools/get_flow.ts +0 -61
- package/templates/apps/mcp/src/tools/list_availability.ts +0 -67
- package/templates/apps/mcp/src/tools/submit_flow_step.ts +0 -83
- package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +0 -309
- package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +0 -149
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +0 -273
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +0 -171
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +0 -179
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +0 -116
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +0 -66
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +0 -61
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +0 -114
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +0 -139
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +0 -85
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +0 -67
- package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +0 -179
- package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +0 -178
- package/templates/apps/web/app/[locale]/dev/purchase/__tests__/actions.test.ts +0 -160
- package/templates/apps/web/app/[locale]/dev/purchase/error.tsx +0 -25
- package/templates/apps/web/app/[locale]/dev/purchase/page.tsx +0 -117
- package/templates/apps/web/app/[locale]/dev/purchase/purchase.actions.ts +0 -58
- package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +0 -119
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +0 -287
- package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +0 -254
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +0 -532
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +0 -121
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +0 -457
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +0 -396
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +0 -114
- package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +0 -110
- package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +0 -96
- package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +0 -117
- package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +0 -96
- package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +0 -92
- package/templates/apps/web/app/[locale]/schedule/page.tsx +0 -82
- package/templates/apps/web/content/en/blog/README.txt +0 -25
- package/templates/apps/web/content/en/docs/index.mdx +0 -28
- package/templates/apps/web/lib/__mocks__/source-server.js +0 -31
- package/templates/apps/web/lib/blog-list-data.ts +0 -95
- package/templates/apps/web/lib/blog-post-data.ts +0 -79
- package/templates/apps/web/lib/source.ts +0 -34
- package/templates/apps/web/public/logomark-dark.svg +0 -4
- package/templates/apps/web/public/logomark-light.svg +0 -4
- package/templates/apps/web/source.config.ts +0 -78
|
@@ -9,53 +9,50 @@
|
|
|
9
9
|
* - Vercel serverless deployment (api/index.ts)
|
|
10
10
|
* - Local development (src/index.ts)
|
|
11
11
|
*/
|
|
12
|
-
import cors from
|
|
13
|
-
import rateLimit from
|
|
14
|
-
import Fastify from
|
|
15
|
-
import type { FastifyPluginAsync, FastifyPluginCallback } from
|
|
16
|
-
|
|
17
|
-
import { config } from
|
|
18
|
-
import { isModuleEnabled } from
|
|
19
|
-
import { registerErrorSerializer } from
|
|
20
|
-
import { validateLeadCaptureDir } from
|
|
21
|
-
import {
|
|
22
|
-
import { authPreHandlerPlugin } from
|
|
12
|
+
import cors from "@fastify/cors";
|
|
13
|
+
import rateLimit from "@fastify/rate-limit";
|
|
14
|
+
import Fastify from "fastify";
|
|
15
|
+
import type { FastifyPluginAsync, FastifyPluginCallback } from "fastify";
|
|
16
|
+
|
|
17
|
+
import { config } from "./config/index.js";
|
|
18
|
+
import { isModuleEnabled } from "./config/modules.js";
|
|
19
|
+
import { registerErrorSerializer } from "./lib/error-serializer.js";
|
|
20
|
+
import { validateLeadCaptureDir } from "./lib/lead-capture-store.js";
|
|
21
|
+
import { loadSlice } from "./lib/slice-load.js";
|
|
22
|
+
import { authPreHandlerPlugin } from "./middleware/auth.js";
|
|
23
23
|
import {
|
|
24
24
|
rateLimitMiddleware,
|
|
25
25
|
DEFAULT_PER_IP_PER_MINUTE_STRICT,
|
|
26
|
-
} from
|
|
27
|
-
import { openapiRoutes } from
|
|
28
|
-
import {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
} from "./middleware/rate-limit.js";
|
|
27
|
+
import { openapiRoutes } from "./openapi/index.js";
|
|
28
|
+
import {
|
|
29
|
+
deriveRootEndpoints,
|
|
30
|
+
REGISTERED_ROUTES_DECORATOR,
|
|
31
|
+
API_VERSION,
|
|
32
|
+
apiDescription,
|
|
33
|
+
apiDisplayName,
|
|
34
|
+
resolveApiIdentity,
|
|
35
|
+
} from "./openapi/spec.js";
|
|
36
|
+
import type { RegisteredRoute } from "./openapi/surface-drift.js";
|
|
37
|
+
import { exampleAeoRoute, scoreAeoRoute } from "./routes/aeo/index.js";
|
|
38
|
+
import { tokenRoutes } from "./routes/auth/tokens.js";
|
|
39
|
+
import { userRoutes } from "./routes/auth/user.js";
|
|
40
|
+
import { getPageRoute } from "./routes/content/get-page.js";
|
|
41
|
+
import { listPagesRoute } from "./routes/content/list-pages.js";
|
|
42
|
+
import { listTypesRoute } from "./routes/content/list-types.js";
|
|
35
43
|
import {
|
|
36
44
|
CONTENT_RATE_LIMIT_UNAUTH,
|
|
37
45
|
CONTENT_RATE_LIMIT_WINDOW,
|
|
38
|
-
} from
|
|
39
|
-
import { deliverableDownloadRoute } from './routes/deliverables/index.js';
|
|
40
|
-
import { flowCheckoutRoutes } from './routes/flow-checkouts/index.js';
|
|
41
|
-
import { getFlowAggregateRoute, getFlowRoute, submitFlowStepRoute } from './routes/flows/index.js';
|
|
42
|
-
import { checkoutRecommendationRoute, saveRecommendationRoute } from './routes/flows/recommender.js';
|
|
46
|
+
} from "./routes/content/rate-limit.js";
|
|
43
47
|
import {
|
|
44
48
|
leadCallbackRoute,
|
|
45
49
|
leadSubmitRoute,
|
|
46
50
|
leadSubscribeRoute,
|
|
47
|
-
} from
|
|
51
|
+
} from "./routes/leads/index.js";
|
|
48
52
|
import {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
projectListingStartRoute,
|
|
53
|
-
projectListingSubmitRoute,
|
|
54
|
-
} from './routes/project-listings/index.js';
|
|
55
|
-
import { slackActionsRoute } from './routes/slack/index.js';
|
|
56
|
-
import { registerTestAuthRoute, shouldMountTestAuth } from './routes/test/auth/session.js';
|
|
57
|
-
import { waitlistSignupRoutes } from './routes/waitlist-signups/index.js';
|
|
58
|
-
import { webhooksRoutes } from './routes/webhooks/index.js';
|
|
53
|
+
registerTestAuthRoute,
|
|
54
|
+
shouldMountTestAuth,
|
|
55
|
+
} from "./routes/test/auth/session.js";
|
|
59
56
|
|
|
60
57
|
/**
|
|
61
58
|
* Paid-module SLICE entry points, keyed by the export each branch needs
|
|
@@ -70,9 +67,15 @@ import { webhooksRoutes } from './routes/webhooks/index.js';
|
|
|
70
67
|
* `./middleware/tenant.js` import was an eleventh — measured on a stripped
|
|
71
68
|
* tree, not argued.
|
|
72
69
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
70
|
+
* Keeping the specifier off the `import()` call site is what defeats that
|
|
71
|
+
* resolution, and the INDIRECTION — not the type of the value — is what does
|
|
72
|
+
* the work. WHICH FORM of indirection is not free, and #5304 is the correction:
|
|
73
|
+
* reading the specifier out of a TABLE, as this file did until then, hid it
|
|
74
|
+
* from the deploy-time file tracer as well as from `tsc`, so the files were
|
|
75
|
+
* compiled and never shipped. The form below — a module-level constant
|
|
76
|
+
* dereferenced into `import()` — is unresolvable to `tsc` and traceable to
|
|
77
|
+
* `@vercel/nft`, the only pair of properties that satisfies both the strip and
|
|
78
|
+
* the deployment. S1's `isModuleEnabled()` gate is unchanged and still decides
|
|
76
79
|
* whether the branch runs; `AILK_ENABLED_MODULES` unset still means every
|
|
77
80
|
* module on, so a default build registers exactly what it registered before.
|
|
78
81
|
*
|
|
@@ -82,27 +85,210 @@ import { webhooksRoutes } from './routes/webhooks/index.js';
|
|
|
82
85
|
* with no members, so the module silently fails to load on the tree that HAS
|
|
83
86
|
* it. See the note above the imports in `packages/ui/src/sections/SectionRenderer.tsx`.
|
|
84
87
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
88
|
+
* BUNDLING `apps/api` IS NOT A SUPPORTED DEPLOY PATH, and that is a property of
|
|
89
|
+
* the analysers rather than a posture (#5334). esbuild bundles a dynamic
|
|
90
|
+
* `import()` only when its argument is a string literal at the call site —
|
|
91
|
+
* exactly the predicate `tsc` resolves on, and exactly the one the strip
|
|
92
|
+
* forbids. So no expression shape can satisfy both, and in a bundled build
|
|
93
|
+
* these specifiers survive verbatim and resolve against the BUNDLE OUTPUT
|
|
94
|
+
* directory instead of their source module's: `"./list.js"` in
|
|
95
|
+
* `routes/project-listings/index.ts` becomes `dist/list.js` and is not there.
|
|
96
|
+
* esbuild does not warn. `build()` then throws during CONSTRUCTION, so every
|
|
97
|
+
* route 500s rather than one — measured in production, nine hours of total API
|
|
98
|
+
* outage on a consuming site. The supported path is what the scaffold ships:
|
|
99
|
+
* plain `tsc` output traced by `@vercel/node` (`apps/api/vercel.json`,
|
|
100
|
+
* `framework: null`). If you are syncing this file into a repo that bundles,
|
|
101
|
+
* that is the constraint you have just inherited; `apps/api/CLAUDE.md`
|
|
102
|
+
* § Deploy path says what to do instead. Every load site below routes through
|
|
103
|
+
* `loadSlice()` so a bundled build says so at boot rather than failing mute.
|
|
104
|
+
*
|
|
105
|
+
* `./routes/project-listings/index.js` carries two independent load paths on
|
|
106
|
+
* purpose: its five anonymous-surface exports are reached below through
|
|
107
|
+
* `PROJECT_LISTINGS_INDEX` / `SLICE_PLUGINS`, now that signal-collectors gates
|
|
108
|
+
* their registration (D4), while the tenant scope's own `import()` of the same
|
|
109
|
+
* specifier stays a literal — the file is a MIXED directory that always ships
|
|
110
|
+
* (multi-tenant's `TENANT_HANDLERS` table lives in the same file), so a literal
|
|
111
|
+
* there resolves fine on every tree regardless of module selection.
|
|
112
|
+
*/
|
|
113
|
+
/**
|
|
114
|
+
* One module-level constant per slice module. The call sites below read the
|
|
115
|
+
* loader out of `SLICE_PLUGINS`; the specifier itself is never written at an
|
|
116
|
+
* `import()` call site, which is what keeps `tsc` from resolving it (#5182 D1).
|
|
117
|
+
*
|
|
118
|
+
* The constant is not a style choice — it is the mechanism (#5304 D3).
|
|
119
|
+
* `@vercel/nft` constant-propagates a module-level binding into `import()` and
|
|
120
|
+
* emits the file; it cannot evaluate a table lookup or a for-of destructure,
|
|
121
|
+
* which is what the previous `Record<string, string>` shape forced it to do.
|
|
122
|
+
* So the old shape was invisible to the DEPLOY tracer as well as to `tsc`, the
|
|
123
|
+
* files went untraced and unshipped, and `build()` threw ERR_MODULE_NOT_FOUND
|
|
124
|
+
* on the first slice it reached — measured in production, nine hours of total
|
|
125
|
+
* API outage on a consuming site. Both analysers are re-measured on every run
|
|
126
|
+
* by `scripts/__tests__/enumerate-slice-specifiers.test.ts`, and
|
|
127
|
+
* `scripts/check-slice-reachability.ts` fails when any specifier here stops
|
|
128
|
+
* being traced.
|
|
129
|
+
*/
|
|
130
|
+
const WORKSPACES_INDEX = "./routes/workspaces/index.js";
|
|
131
|
+
const WORKSPACES_SESSIONS = "./routes/workspaces/sessions.js";
|
|
132
|
+
const WORKSPACES_CONFIG = "./routes/workspaces/config.js";
|
|
133
|
+
const WORKSPACES_GENERAL = "./routes/workspaces/general.js";
|
|
134
|
+
const WORKSPACES_MEMBERS = "./routes/workspaces/members.js";
|
|
135
|
+
const BILLING_INDEX = "./routes/billing/index.js";
|
|
136
|
+
const WAITLIST_EXPERIMENTS_INDEX = "./routes/waitlist-experiments/index.js";
|
|
137
|
+
const CONTENT_CREATE = "./routes/content/create.js";
|
|
138
|
+
const CONTENT_UPDATE = "./routes/content/update.js";
|
|
139
|
+
const CONTENT_DELETE = "./routes/content/delete.js";
|
|
140
|
+
const SCHEDULE_INDEX = "./routes/schedule/index.js";
|
|
141
|
+
const FLOWS_INDEX = "./routes/flows/index.js";
|
|
142
|
+
const DELIVERABLES_INDEX = "./routes/deliverables/index.js";
|
|
143
|
+
const PROJECT_LISTINGS_INDEX = "./routes/project-listings/index.js";
|
|
144
|
+
const SLACK_INDEX = "./routes/slack/index.js";
|
|
145
|
+
const WAITLIST_SIGNUPS_INDEX = "./routes/waitlist-signups/index.js";
|
|
146
|
+
const CHECKOUT_INDEX = "./routes/checkout/index.js";
|
|
147
|
+
const WEBHOOKS_INDEX = "./routes/webhooks/index.js";
|
|
148
|
+
const FLOW_CHECKOUTS_INDEX = "./routes/flow-checkouts/index.js";
|
|
149
|
+
const RECOMMENDER_INDEX = "./routes/flows/recommender.js";
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Paid-module SLICE entry points, keyed by the export each branch needs.
|
|
153
|
+
*
|
|
154
|
+
* `specifier` is carried alongside `load` for the drift message below, and both
|
|
155
|
+
* read the SAME constant, so the two can never disagree. `load` is what runs:
|
|
156
|
+
* an arrow closing over that constant, which is the only shape both analysers
|
|
157
|
+
* treat as intended — unresolvable to `tsc`, traceable to `nft`.
|
|
89
158
|
*/
|
|
90
|
-
const
|
|
91
|
-
workspaceContextRoutes:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
159
|
+
const SLICE_PLUGINS = {
|
|
160
|
+
workspaceContextRoutes: {
|
|
161
|
+
specifier: WORKSPACES_INDEX,
|
|
162
|
+
load: () => import(WORKSPACES_INDEX),
|
|
163
|
+
},
|
|
164
|
+
workspaceSessionRoutes: {
|
|
165
|
+
specifier: WORKSPACES_INDEX,
|
|
166
|
+
load: () => import(WORKSPACES_INDEX),
|
|
167
|
+
},
|
|
168
|
+
sessionRoutes: {
|
|
169
|
+
specifier: WORKSPACES_SESSIONS,
|
|
170
|
+
load: () => import(WORKSPACES_SESSIONS),
|
|
171
|
+
},
|
|
172
|
+
workspaceConfigRoutes: {
|
|
173
|
+
specifier: WORKSPACES_CONFIG,
|
|
174
|
+
load: () => import(WORKSPACES_CONFIG),
|
|
175
|
+
},
|
|
176
|
+
workspaceGeneralRoutes: {
|
|
177
|
+
specifier: WORKSPACES_GENERAL,
|
|
178
|
+
load: () => import(WORKSPACES_GENERAL),
|
|
179
|
+
},
|
|
180
|
+
workspaceMembersRoutes: {
|
|
181
|
+
specifier: WORKSPACES_MEMBERS,
|
|
182
|
+
load: () => import(WORKSPACES_MEMBERS),
|
|
183
|
+
},
|
|
184
|
+
billingRoutes: {
|
|
185
|
+
specifier: BILLING_INDEX,
|
|
186
|
+
load: () => import(BILLING_INDEX),
|
|
187
|
+
},
|
|
188
|
+
waitlistExperimentRoutes: {
|
|
189
|
+
specifier: WAITLIST_EXPERIMENTS_INDEX,
|
|
190
|
+
load: () => import(WAITLIST_EXPERIMENTS_INDEX),
|
|
191
|
+
},
|
|
192
|
+
createPageRoute: {
|
|
193
|
+
specifier: CONTENT_CREATE,
|
|
194
|
+
load: () => import(CONTENT_CREATE),
|
|
195
|
+
},
|
|
196
|
+
updatePageRoute: {
|
|
197
|
+
specifier: CONTENT_UPDATE,
|
|
198
|
+
load: () => import(CONTENT_UPDATE),
|
|
199
|
+
},
|
|
200
|
+
deletePageRoute: {
|
|
201
|
+
specifier: CONTENT_DELETE,
|
|
202
|
+
load: () => import(CONTENT_DELETE),
|
|
203
|
+
},
|
|
204
|
+
listAvailabilityRoute: {
|
|
205
|
+
specifier: SCHEDULE_INDEX,
|
|
206
|
+
load: () => import(SCHEDULE_INDEX),
|
|
207
|
+
},
|
|
208
|
+
getEventMetaRoute: {
|
|
209
|
+
specifier: SCHEDULE_INDEX,
|
|
210
|
+
load: () => import(SCHEDULE_INDEX),
|
|
211
|
+
},
|
|
212
|
+
createBookingRoute: {
|
|
213
|
+
specifier: SCHEDULE_INDEX,
|
|
214
|
+
load: () => import(SCHEDULE_INDEX),
|
|
215
|
+
},
|
|
216
|
+
getFlowRoute: {
|
|
217
|
+
specifier: FLOWS_INDEX,
|
|
218
|
+
load: () => import(FLOWS_INDEX),
|
|
219
|
+
},
|
|
220
|
+
submitFlowStepRoute: {
|
|
221
|
+
specifier: FLOWS_INDEX,
|
|
222
|
+
load: () => import(FLOWS_INDEX),
|
|
223
|
+
},
|
|
224
|
+
getFlowAggregateRoute: {
|
|
225
|
+
specifier: FLOWS_INDEX,
|
|
226
|
+
load: () => import(FLOWS_INDEX),
|
|
227
|
+
},
|
|
228
|
+
deliverableDownloadRoute: {
|
|
229
|
+
specifier: DELIVERABLES_INDEX,
|
|
230
|
+
load: () => import(DELIVERABLES_INDEX),
|
|
231
|
+
},
|
|
232
|
+
projectListingStartRoute: {
|
|
233
|
+
specifier: PROJECT_LISTINGS_INDEX,
|
|
234
|
+
load: () => import(PROJECT_LISTINGS_INDEX),
|
|
235
|
+
},
|
|
236
|
+
projectListingDraftPatchRoute: {
|
|
237
|
+
specifier: PROJECT_LISTINGS_INDEX,
|
|
238
|
+
load: () => import(PROJECT_LISTINGS_INDEX),
|
|
239
|
+
},
|
|
240
|
+
projectListingSubmitRoute: {
|
|
241
|
+
specifier: PROJECT_LISTINGS_INDEX,
|
|
242
|
+
load: () => import(PROJECT_LISTINGS_INDEX),
|
|
243
|
+
},
|
|
244
|
+
// #5407 — the resume link's verification write. Anonymous surface,
|
|
245
|
+
// module-conditional (signal-collectors, D-48) like its four siblings.
|
|
246
|
+
projectListingVerifyEmailRoute: {
|
|
247
|
+
specifier: PROJECT_LISTINGS_INDEX,
|
|
248
|
+
load: () => import(PROJECT_LISTINGS_INDEX),
|
|
249
|
+
},
|
|
250
|
+
projectListingPublicRoute: {
|
|
251
|
+
specifier: PROJECT_LISTINGS_INDEX,
|
|
252
|
+
load: () => import(PROJECT_LISTINGS_INDEX),
|
|
253
|
+
},
|
|
254
|
+
projectListingMeRoute: {
|
|
255
|
+
specifier: PROJECT_LISTINGS_INDEX,
|
|
256
|
+
load: () => import(PROJECT_LISTINGS_INDEX),
|
|
257
|
+
},
|
|
258
|
+
slackActionsRoute: {
|
|
259
|
+
specifier: SLACK_INDEX,
|
|
260
|
+
load: () => import(SLACK_INDEX),
|
|
261
|
+
},
|
|
262
|
+
waitlistSignupRoutes: {
|
|
263
|
+
specifier: WAITLIST_SIGNUPS_INDEX,
|
|
264
|
+
load: () => import(WAITLIST_SIGNUPS_INDEX),
|
|
265
|
+
},
|
|
266
|
+
// Commerce (D-47, #5364) — checkout, the Stripe webhook receiver, flow
|
|
267
|
+
// checkouts and the recommender. Module-conditional (isModuleEnabled
|
|
268
|
+
// "commerce") like every other paid slice above.
|
|
269
|
+
checkoutRoutes: {
|
|
270
|
+
specifier: CHECKOUT_INDEX,
|
|
271
|
+
load: () => import(CHECKOUT_INDEX),
|
|
272
|
+
},
|
|
273
|
+
webhooksRoutes: {
|
|
274
|
+
specifier: WEBHOOKS_INDEX,
|
|
275
|
+
load: () => import(WEBHOOKS_INDEX),
|
|
276
|
+
},
|
|
277
|
+
flowCheckoutRoutes: {
|
|
278
|
+
specifier: FLOW_CHECKOUTS_INDEX,
|
|
279
|
+
load: () => import(FLOW_CHECKOUTS_INDEX),
|
|
280
|
+
},
|
|
281
|
+
saveRecommendationRoute: {
|
|
282
|
+
specifier: RECOMMENDER_INDEX,
|
|
283
|
+
load: () => import(RECOMMENDER_INDEX),
|
|
284
|
+
},
|
|
285
|
+
checkoutRecommendationRoute: {
|
|
286
|
+
specifier: RECOMMENDER_INDEX,
|
|
287
|
+
load: () => import(RECOMMENDER_INDEX),
|
|
288
|
+
},
|
|
289
|
+
} satisfies Record<string, { specifier: string; load: () => Promise<unknown> }>;
|
|
290
|
+
|
|
291
|
+
type SlicePluginName = keyof typeof SLICE_PLUGINS;
|
|
106
292
|
|
|
107
293
|
/**
|
|
108
294
|
* Load one slice route plugin. Every entry above is a `FastifyPluginAsync`
|
|
@@ -117,8 +303,8 @@ type SlicePluginName = keyof typeof SLICE_PLUGIN_SPECIFIERS;
|
|
|
117
303
|
async function sliceRoutePlugin(
|
|
118
304
|
name: SlicePluginName,
|
|
119
305
|
): Promise<FastifyPluginAsync> {
|
|
120
|
-
const specifier
|
|
121
|
-
const mod = (await
|
|
306
|
+
const { specifier, load } = SLICE_PLUGINS[name];
|
|
307
|
+
const mod = (await loadSlice(specifier, load)) as Record<
|
|
122
308
|
string,
|
|
123
309
|
FastifyPluginAsync | undefined
|
|
124
310
|
>;
|
|
@@ -135,17 +321,40 @@ async function sliceRoutePlugin(
|
|
|
135
321
|
* (apps/api/src/middleware/tenant.ts:83).
|
|
136
322
|
*/
|
|
137
323
|
async function tenantPreHandlerSlicePlugin(): Promise<FastifyPluginCallback> {
|
|
138
|
-
const specifier: string =
|
|
139
|
-
const mod = (await import(specifier)) as {
|
|
324
|
+
const specifier: string = "./middleware/tenant.js";
|
|
325
|
+
const mod = (await loadSlice(specifier, () => import(specifier))) as {
|
|
140
326
|
tenantPreHandlerPlugin?: FastifyPluginCallback;
|
|
141
327
|
};
|
|
142
328
|
const plugin = mod.tenantPreHandlerPlugin;
|
|
143
329
|
if (!plugin) {
|
|
144
|
-
throw new Error(
|
|
330
|
+
throw new Error(
|
|
331
|
+
"slice export tenantPreHandlerPlugin missing from " + specifier,
|
|
332
|
+
);
|
|
145
333
|
}
|
|
146
334
|
return plugin;
|
|
147
335
|
}
|
|
148
336
|
|
|
337
|
+
/**
|
|
338
|
+
* The commerce boot-time sandbox guard — the one other slice entry that is
|
|
339
|
+
* not a route plugin, mirroring `tenantPreHandlerSlicePlugin()` above (D3).
|
|
340
|
+
* `lib/stripe.ts` is commerce-owned (D5) and its `assertSandboxKey` is the
|
|
341
|
+
* sole `apps/api` reach into the Stripe SDK wrapper package, so it can no
|
|
342
|
+
* longer be a static import (spec D1) once the module is stripped. The
|
|
343
|
+
* widened `const specifier: string` annotation is what defeats `tsc`
|
|
344
|
+
* resolution, exactly as it does above.
|
|
345
|
+
*/
|
|
346
|
+
async function assertCommerceSandboxKey(key: string): Promise<void> {
|
|
347
|
+
const specifier: string = "./lib/stripe.js";
|
|
348
|
+
const mod = (await loadSlice(specifier, () => import(specifier))) as {
|
|
349
|
+
assertSandboxKey?: (key: string) => void;
|
|
350
|
+
};
|
|
351
|
+
const assertSandboxKeyFn = mod.assertSandboxKey;
|
|
352
|
+
if (!assertSandboxKeyFn) {
|
|
353
|
+
throw new Error("slice export assertSandboxKey missing from " + specifier);
|
|
354
|
+
}
|
|
355
|
+
assertSandboxKeyFn(key);
|
|
356
|
+
}
|
|
357
|
+
|
|
149
358
|
/**
|
|
150
359
|
* Build Fastify server instance (for serverless or standalone)
|
|
151
360
|
* @returns Configured Fastify instance ready for requests
|
|
@@ -156,9 +365,11 @@ export async function build() {
|
|
|
156
365
|
|
|
157
366
|
// Runtime sandbox-mode guard: reject live-mode Stripe keys before any
|
|
158
367
|
// webhook or checkout call can exercise them. Idempotent — safe even if
|
|
159
|
-
// assertSandboxKey is called again inside a route handler.
|
|
160
|
-
|
|
161
|
-
|
|
368
|
+
// assertSandboxKey is called again inside a route handler. Module-
|
|
369
|
+
// conditional (commerce, D-47/D3): a build without commerce has no
|
|
370
|
+
// commerce request path to guard, and lib/stripe.ts is stripped with it.
|
|
371
|
+
if (isModuleEnabled("commerce") && config.STRIPE_SECRET_KEY) {
|
|
372
|
+
await assertCommerceSandboxKey(config.STRIPE_SECRET_KEY);
|
|
162
373
|
}
|
|
163
374
|
|
|
164
375
|
// trustProxy: true (#4730) — the Vercel serverless handler
|
|
@@ -188,6 +399,32 @@ export async function build() {
|
|
|
188
399
|
trustProxy: true,
|
|
189
400
|
});
|
|
190
401
|
|
|
402
|
+
// Collect the route table this server registers (#5314).
|
|
403
|
+
//
|
|
404
|
+
// Installed here — after `Fastify()` and before the first `register()` — so
|
|
405
|
+
// no route can precede it: a root-instance `onRoute` hook fires for every
|
|
406
|
+
// route added afterwards in ANY encapsulated child scope, which is what lets
|
|
407
|
+
// one hook see the auth, tenant, webhooks, leads, flows, aeo and schedule
|
|
408
|
+
// scopes without touching any of them.
|
|
409
|
+
//
|
|
410
|
+
// HEAD is dropped: Fastify synthesises it from every GET, so publishing it
|
|
411
|
+
// would describe routes no handler declared.
|
|
412
|
+
//
|
|
413
|
+
// The table lives on THIS instance, not in module state, so two servers
|
|
414
|
+
// built concurrently in one process cannot corrupt each other's — see the
|
|
415
|
+
// note on `readRegisteredRoutes` in openapi/spec.ts for the concrete race.
|
|
416
|
+
// The decorator holds the array by reference and the hook pushes into it, so
|
|
417
|
+
// every reader sees the current contents without re-publishing.
|
|
418
|
+
const registeredRoutes: RegisteredRoute[] = [];
|
|
419
|
+
server.decorate(REGISTERED_ROUTES_DECORATOR, registeredRoutes);
|
|
420
|
+
server.addHook("onRoute", (route) => {
|
|
421
|
+
const methods = Array.isArray(route.method) ? route.method : [route.method];
|
|
422
|
+
for (const method of methods) {
|
|
423
|
+
if (method === "HEAD") continue;
|
|
424
|
+
registeredRoutes.push({ method, url: route.url });
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
|
|
191
428
|
// Register error serializer — maps internal exceptions to generic strings
|
|
192
429
|
// so connection strings, hostnames, stack traces, and SDK error details
|
|
193
430
|
// never reach the client. Full detail still goes to pino at error level.
|
|
@@ -199,9 +436,32 @@ export async function build() {
|
|
|
199
436
|
// `credentials: true` would let any website read credentialed responses
|
|
200
437
|
// (security review M1). `false` disables cross-origin access entirely;
|
|
201
438
|
// same-origin and non-browser callers are unaffected.
|
|
439
|
+
//
|
|
440
|
+
// `methods` is EXPLICIT because @fastify/cors defaults it to `GET,HEAD,POST`
|
|
441
|
+
// (its own `defaultOptions`), which is narrower than the surface this server
|
|
442
|
+
// registers (#5397). With only `origin` + `credentials` passed, the preflight
|
|
443
|
+
// for `PATCH /v1/project-listings/drafts/:id` answered
|
|
444
|
+
// `access-control-allow-methods: GET, HEAD, POST`, so a browser refused every
|
|
445
|
+
// draft save on a split-origin deploy — a site could create a listing draft
|
|
446
|
+
// and never save one. The list below is the union of the methods the route
|
|
447
|
+
// table actually serves (GET/POST/PATCH/PUT/DELETE — `/v1/auth/tokens/:id`,
|
|
448
|
+
// `/v1/content/:type/:slug`, `/v1/user/data/:key`, the workspace-config PUTs),
|
|
449
|
+
// plus HEAD (Fastify synthesises it from every GET) and OPTIONS (the plugin's
|
|
450
|
+
// own wildcard preflight route). `cors.test.ts` re-derives that union from the
|
|
451
|
+
// live route table on every run, so a route added in a NEW method fails there
|
|
452
|
+
// rather than silently becoming unreachable from a browser.
|
|
453
|
+
//
|
|
454
|
+
// This widens WHICH METHODS the allowed origin may use, never WHO is allowed:
|
|
455
|
+
// `origin` and `credentials` are untouched.
|
|
456
|
+
//
|
|
457
|
+
// `allowedHeaders` is deliberately left unset: null means @fastify/cors
|
|
458
|
+
// reflects the request's `Access-Control-Request-Headers`, so `authorization`
|
|
459
|
+
// and `content-type` pass without this file having to maintain a list that
|
|
460
|
+
// silently drops a header a future route needs.
|
|
202
461
|
await server.register(cors, {
|
|
203
|
-
origin: config.FRONTEND_URL || config.NODE_ENV !==
|
|
462
|
+
origin: config.FRONTEND_URL || config.NODE_ENV !== "production",
|
|
204
463
|
credentials: true,
|
|
464
|
+
methods: ["GET", "HEAD", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"],
|
|
205
465
|
});
|
|
206
466
|
|
|
207
467
|
// Register rate limiting — applied globally with the unauth ceiling. The
|
|
@@ -214,32 +474,35 @@ export async function build() {
|
|
|
214
474
|
});
|
|
215
475
|
|
|
216
476
|
// Health check
|
|
217
|
-
server.get(
|
|
218
|
-
status:
|
|
477
|
+
server.get("/health", async () => ({
|
|
478
|
+
status: "healthy",
|
|
219
479
|
timestamp: new Date().toISOString(),
|
|
220
480
|
}));
|
|
221
481
|
|
|
222
|
-
// Root endpoint
|
|
223
|
-
|
|
482
|
+
// Root endpoint — introduces this deployment as the SITE it serves, not as
|
|
483
|
+
// the kit it was built with (#5307). `_site.mdx` is read once per process;
|
|
484
|
+
// a site without one degrades to a neutral name, never to the kit's.
|
|
485
|
+
server.get("/", async () => {
|
|
486
|
+
const identity = resolveApiIdentity();
|
|
224
487
|
return {
|
|
225
|
-
name:
|
|
226
|
-
version:
|
|
227
|
-
description:
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
488
|
+
name: apiDisplayName(identity),
|
|
489
|
+
version: API_VERSION,
|
|
490
|
+
description: apiDescription(identity),
|
|
491
|
+
// Derived from the routes this process actually registered (#5314),
|
|
492
|
+
// not a hand-maintained literal. The literal listed 11 endpoints against
|
|
493
|
+
// 68 registered routes, omitted `/v1/project-listings/public` and
|
|
494
|
+
// `/v1/billing/usage`, advertised `DELETE /v1/auth/tokens` at a path
|
|
495
|
+
// that 404s (the route is `/v1/auth/tokens/:id`), and named two billing
|
|
496
|
+
// routes on builds where the multi-tenant module registers neither.
|
|
497
|
+
// Same mechanism now covers commerce (D-47, #5364): checkout, the
|
|
498
|
+
// Stripe webhook, the flow-checkouts read and the two recommender
|
|
499
|
+
// routes are derived exactly like the billing routes above and
|
|
500
|
+
// disappear from this list on a build where `commerce` is off, rather
|
|
501
|
+
// than advertising a route that 404s.
|
|
502
|
+
//
|
|
503
|
+
// Read at REQUEST time, so `ready()` has fired and the set is complete
|
|
504
|
+
// even though this handler is registered before most routes.
|
|
505
|
+
endpoints: deriveRootEndpoints(registeredRoutes),
|
|
243
506
|
};
|
|
244
507
|
});
|
|
245
508
|
|
|
@@ -258,20 +521,30 @@ export async function build() {
|
|
|
258
521
|
// apply to them; each enforces membership in the service layer.
|
|
259
522
|
// Module-conditional (S1, #5088): without the multi-tenant module there
|
|
260
523
|
// is no workspace concept to switch or inspect.
|
|
261
|
-
if (isModuleEnabled(
|
|
262
|
-
await authScope.register(
|
|
524
|
+
if (isModuleEnabled("multi-tenant")) {
|
|
525
|
+
await authScope.register(
|
|
526
|
+
await sliceRoutePlugin("workspaceContextRoutes"),
|
|
527
|
+
);
|
|
263
528
|
}
|
|
264
529
|
|
|
265
530
|
// Respondent signup read (#4731) — auth only, NOT tenant-guarded: a
|
|
266
531
|
// respondent has no workspace membership and the tenant guard is
|
|
267
532
|
// default-deny. Identity is request.userId; the service re-checks
|
|
268
|
-
// User.emailVerified before any Lead read.
|
|
269
|
-
|
|
533
|
+
// User.emailVerified before any Lead read. Module-conditional
|
|
534
|
+
// (signal-collectors, D-48): the waitlist instrument's respondent half.
|
|
535
|
+
if (isModuleEnabled("signal-collectors")) {
|
|
536
|
+
await authScope.register(await sliceRoutePlugin("waitlistSignupRoutes"));
|
|
537
|
+
}
|
|
270
538
|
|
|
271
539
|
// Respondent checkouts read (#4984) — the same posture as the signup
|
|
272
540
|
// read above: auth only, NOT tenant-guarded. Identity is request.userId;
|
|
273
541
|
// the only join is the lead-promotion link (never an email match).
|
|
274
|
-
|
|
542
|
+
// Module-conditional (commerce, D-47): the `/account` Purchases panel
|
|
543
|
+
// already degrades to its empty state with no code change when this is
|
|
544
|
+
// absent (`SKIPPED_STATUSES` covers 404 — spec §4.2).
|
|
545
|
+
if (isModuleEnabled("commerce")) {
|
|
546
|
+
await authScope.register(await sliceRoutePlugin("flowCheckoutRoutes"));
|
|
547
|
+
}
|
|
275
548
|
|
|
276
549
|
// The founder's OWN listings read (#5295) — the same posture again: auth
|
|
277
550
|
// only, NOT tenant-guarded, because a founder is not a workspace member
|
|
@@ -284,7 +557,12 @@ export async function build() {
|
|
|
284
557
|
// build without the module; the static `/me` path takes router precedence
|
|
285
558
|
// over the tenant scope's parametric `/v1/project-listings/:id`, exactly as
|
|
286
559
|
// the already-shipped static `/v1/project-listings/public` does.
|
|
287
|
-
|
|
560
|
+
// Module-conditional (signal-collectors, D-48 / D4): the listing
|
|
561
|
+
// instrument's founder-read half; not severable from the four anonymous
|
|
562
|
+
// listing routes below (same static barrel, D4).
|
|
563
|
+
if (isModuleEnabled("signal-collectors")) {
|
|
564
|
+
await authScope.register(await sliceRoutePlugin("projectListingMeRoute"));
|
|
565
|
+
}
|
|
288
566
|
|
|
289
567
|
// Tenant-guarded domain routes (multi-tenant module) — the tenant
|
|
290
568
|
// preHandler (default-deny guard, see middleware/tenant.ts) runs after
|
|
@@ -293,27 +571,63 @@ export async function build() {
|
|
|
293
571
|
// no tenant concept at all — the whole scope is module-conditional
|
|
294
572
|
// (S1, #5088), so an OSS build that excludes multi-tenant registers
|
|
295
573
|
// none of it.
|
|
296
|
-
if (isModuleEnabled(
|
|
574
|
+
if (isModuleEnabled("multi-tenant")) {
|
|
297
575
|
await authScope.register(async (tenantScope) => {
|
|
298
576
|
await tenantScope.register(await tenantPreHandlerSlicePlugin());
|
|
299
|
-
await tenantScope.register(
|
|
300
|
-
|
|
301
|
-
|
|
577
|
+
await tenantScope.register(
|
|
578
|
+
await sliceRoutePlugin("workspaceSessionRoutes"),
|
|
579
|
+
);
|
|
580
|
+
await tenantScope.register(await sliceRoutePlugin("sessionRoutes"));
|
|
302
581
|
// Org-billing routes (#3701) — org-scoped read + Stripe customer-portal.
|
|
303
|
-
await tenantScope.register(await sliceRoutePlugin(
|
|
582
|
+
await tenantScope.register(await sliceRoutePlugin("billingRoutes"));
|
|
304
583
|
// Waitlist operator-dashboard read API (#4665 D7, B0) — tenant-scoped
|
|
305
584
|
// list/comparison/signups reads over A1's waitlist-experiment rows.
|
|
306
585
|
await tenantScope.register(
|
|
307
|
-
await sliceRoutePlugin(
|
|
586
|
+
await sliceRoutePlugin("waitlistExperimentRoutes"),
|
|
308
587
|
);
|
|
309
588
|
// Project-listings dashboard (#4998) — tenant-scoped list/get/patch
|
|
310
589
|
// over the founder's-application-row lifecycle.
|
|
311
|
-
const { projectListingTenantRoutes } =
|
|
312
|
-
|
|
313
|
-
);
|
|
590
|
+
const { projectListingTenantRoutes } =
|
|
591
|
+
await import("./routes/project-listings/index.js");
|
|
314
592
|
await tenantScope.register(projectListingTenantRoutes);
|
|
315
593
|
});
|
|
316
594
|
}
|
|
595
|
+
|
|
596
|
+
// Workspace SETTINGS routes — a sibling tenant scope, gated on EITHER
|
|
597
|
+
// module (#5371 D2). The site-config routes were registered inside the
|
|
598
|
+
// multi-tenant scope above, which was too narrow: nothing in their logic
|
|
599
|
+
// is multi-tenant-specific, and since Phase 2 `resolveActiveTenant`
|
|
600
|
+
// resolves a single-tenant session's TenantContext from the default
|
|
601
|
+
// Organization/Workspace exactly like a multi-tenant one. Widening the
|
|
602
|
+
// gate is the correction; duplicating the route file would encode the
|
|
603
|
+
// original mis-scoping twice.
|
|
604
|
+
//
|
|
605
|
+
// This is a REGISTRATION change, not a tenant-isolation change: the scope
|
|
606
|
+
// registers its own `tenantPreHandlerSlicePlugin()`, the same default-deny
|
|
607
|
+
// guard the scope above uses, with the same `resolveActiveTenant` behind
|
|
608
|
+
// it. Every other route in that scope (workspaceSessionRoutes,
|
|
609
|
+
// sessionRoutes, billingRoutes, waitlistExperimentRoutes, the
|
|
610
|
+
// project-listings tenant routes) stays multi-tenant-only.
|
|
611
|
+
//
|
|
612
|
+
// A build with BOTH modules enabled registers each of the three route
|
|
613
|
+
// plugins exactly once: they live here and nowhere else, so the two
|
|
614
|
+
// scopes cannot collide on a path.
|
|
615
|
+
if (isModuleEnabled("workspace") || isModuleEnabled("multi-tenant")) {
|
|
616
|
+
await authScope.register(async (tenantScope) => {
|
|
617
|
+
await tenantScope.register(await tenantPreHandlerSlicePlugin());
|
|
618
|
+
await tenantScope.register(
|
|
619
|
+
await sliceRoutePlugin("workspaceConfigRoutes"),
|
|
620
|
+
);
|
|
621
|
+
// The Pro workspace module's own two routes (#5371): the workspace
|
|
622
|
+
// name, and the read-only member roster.
|
|
623
|
+
await tenantScope.register(
|
|
624
|
+
await sliceRoutePlugin("workspaceGeneralRoutes"),
|
|
625
|
+
);
|
|
626
|
+
await tenantScope.register(
|
|
627
|
+
await sliceRoutePlugin("workspaceMembersRoutes"),
|
|
628
|
+
);
|
|
629
|
+
});
|
|
630
|
+
}
|
|
317
631
|
});
|
|
318
632
|
|
|
319
633
|
// Public routes — no auth required.
|
|
@@ -325,22 +639,28 @@ export async function build() {
|
|
|
325
639
|
// module-conditional at REGISTRATION too (S1, #5088) so an OSS build that
|
|
326
640
|
// excludes the content-write slice never wires the handler in at all. Not
|
|
327
641
|
// a MODULE_SLICES entry — see config/modules.ts.
|
|
328
|
-
if (isModuleEnabled(
|
|
329
|
-
await server.register(await sliceRoutePlugin(
|
|
330
|
-
await server.register(await sliceRoutePlugin(
|
|
331
|
-
await server.register(await sliceRoutePlugin(
|
|
642
|
+
if (isModuleEnabled("content-write")) {
|
|
643
|
+
await server.register(await sliceRoutePlugin("createPageRoute"));
|
|
644
|
+
await server.register(await sliceRoutePlugin("updatePageRoute"));
|
|
645
|
+
await server.register(await sliceRoutePlugin("deletePageRoute"));
|
|
646
|
+
}
|
|
647
|
+
// Module-conditional (commerce, D-47): checkout moved to its own module.
|
|
648
|
+
if (isModuleEnabled("commerce")) {
|
|
649
|
+
await server.register(await sliceRoutePlugin("checkoutRoutes"));
|
|
332
650
|
}
|
|
333
|
-
await server.register(checkoutRoutes);
|
|
334
651
|
// Deliverable download — a capability-token URL (#3961 D5); the token
|
|
335
|
-
// itself is the sole gate, so no auth preHandler applies.
|
|
336
|
-
|
|
652
|
+
// itself is the sole gate, so no auth preHandler applies. Module-conditional
|
|
653
|
+
// (signal-collectors, D-48): gated PDFs and reports.
|
|
654
|
+
if (isModuleEnabled("signal-collectors")) {
|
|
655
|
+
await server.register(await sliceRoutePlugin("deliverableDownloadRoute"));
|
|
656
|
+
}
|
|
337
657
|
|
|
338
658
|
// Webhooks routes — raw-body parser scoped to this sub-router only so the
|
|
339
659
|
// global JSON parser on all other routes is unaffected.
|
|
340
660
|
await server.register(async (webhooksScope) => {
|
|
341
661
|
webhooksScope.addContentTypeParser(
|
|
342
|
-
|
|
343
|
-
{ parseAs:
|
|
662
|
+
"application/json",
|
|
663
|
+
{ parseAs: "buffer" },
|
|
344
664
|
(_req, body, done) => {
|
|
345
665
|
done(null, body);
|
|
346
666
|
},
|
|
@@ -349,14 +669,24 @@ export async function build() {
|
|
|
349
669
|
// field (#4999) — raw buffer here too, so the signature is verified over
|
|
350
670
|
// the EXACT bytes Slack signed, never a re-serialized parse.
|
|
351
671
|
webhooksScope.addContentTypeParser(
|
|
352
|
-
|
|
353
|
-
{ parseAs:
|
|
672
|
+
"application/x-www-form-urlencoded",
|
|
673
|
+
{ parseAs: "buffer" },
|
|
354
674
|
(_req, body, done) => {
|
|
355
675
|
done(null, body);
|
|
356
676
|
},
|
|
357
677
|
);
|
|
358
|
-
|
|
359
|
-
|
|
678
|
+
// Stripe webhook receiver — module-conditional (commerce, D-47). The
|
|
679
|
+
// scope wrapper and both raw-body parsers above stay unconditional: the
|
|
680
|
+
// Slack parser below needs them too (D2).
|
|
681
|
+
if (isModuleEnabled("commerce")) {
|
|
682
|
+
await webhooksScope.register(await sliceRoutePlugin("webhooksRoutes"));
|
|
683
|
+
}
|
|
684
|
+
// Project-listing operator decision (Slack Approve/Reject) — the "hand an
|
|
685
|
+
// operator a decision" half of D-48's grouping sentence. Module-conditional
|
|
686
|
+
// (signal-collectors).
|
|
687
|
+
if (isModuleEnabled("signal-collectors")) {
|
|
688
|
+
await webhooksScope.register(await sliceRoutePlugin("slackActionsRoute"));
|
|
689
|
+
}
|
|
360
690
|
});
|
|
361
691
|
|
|
362
692
|
// Leads routes — rate-limit middleware scoped to this sub-router. Own
|
|
@@ -373,20 +703,43 @@ export async function build() {
|
|
|
373
703
|
// Project-listings anonymous surface (#4998, reshaped by #5036) — the
|
|
374
704
|
// three-operation draft lifecycle plus the public page-render read, all
|
|
375
705
|
// on the same rate-limited, identity-blind posture as the leads routes
|
|
376
|
-
// above.
|
|
706
|
+
// above. Module-conditional (signal-collectors, D-48): the listing
|
|
707
|
+
// instrument's application-lifecycle half.
|
|
377
708
|
//
|
|
378
709
|
// Do NOT read that rate limit as a bound on anonymous draft creation. It
|
|
379
710
|
// keys on `req.ip`, and `trustProxy` is on (see the note at the top of this
|
|
380
711
|
// file), so a caller reaching the API directly can vary
|
|
381
712
|
// `X-Forwarded-For` and get a fresh budget per request. That is a
|
|
382
713
|
// pre-existing property of the limiter, not of these routes; what these
|
|
383
|
-
// routes do is keep the cost of an unbounded call small —
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
714
|
+
// routes do is keep the cost of an unbounded call small — the Lead write
|
|
715
|
+
// with its operator notification waits for `submit` (PR #5042 security
|
|
716
|
+
// review, finding 4).
|
|
717
|
+
//
|
|
718
|
+
// `start` DOES now send one email (#5406, the founder's resume link), so
|
|
719
|
+
// the older form of this note — "`start` writes one row and sends
|
|
720
|
+
// nothing" — no longer holds. That send is opt-in per site, and where it
|
|
721
|
+
// is on it is bounded in the send path rather than per IP
|
|
722
|
+
// (`services/project-listing-resume-email.ts`), precisely because this IP
|
|
723
|
+
// limiter cannot carry the weight of an outbound-mail decision. Two bounds,
|
|
724
|
+
// both counted in the database so they hold across instances: PER RECIPIENT
|
|
725
|
+
// (PR #5411 security review, finding 1) and, since #5414, PER SITE across
|
|
726
|
+
// every recipient — the residual the first one left, and the one that
|
|
727
|
+
// decides whether a flood can cost the site its sending domain.
|
|
728
|
+
if (isModuleEnabled("signal-collectors")) {
|
|
729
|
+
await leadsScope.register(await sliceRoutePlugin("projectListingStartRoute"));
|
|
730
|
+
await leadsScope.register(
|
|
731
|
+
await sliceRoutePlugin("projectListingDraftPatchRoute"),
|
|
732
|
+
);
|
|
733
|
+
await leadsScope.register(await sliceRoutePlugin("projectListingSubmitRoute"));
|
|
734
|
+
// #5407 — the resume link's verification write. Anonymous, and gated
|
|
735
|
+
// on the resume token AND the nonce that only the resume email
|
|
736
|
+
// carries, so it belongs in the same rate-limited scope as the other
|
|
737
|
+
// three.
|
|
738
|
+
await leadsScope.register(
|
|
739
|
+
await sliceRoutePlugin("projectListingVerifyEmailRoute"),
|
|
740
|
+
);
|
|
741
|
+
await leadsScope.register(await sliceRoutePlugin("projectListingPublicRoute"));
|
|
742
|
+
}
|
|
390
743
|
});
|
|
391
744
|
|
|
392
745
|
// Flows routes — anonymous surface; rate-limit middleware scoped to this
|
|
@@ -397,13 +750,23 @@ export async function build() {
|
|
|
397
750
|
// scope #4730's bump was actually sized for; unchanged by #5111.
|
|
398
751
|
await server.register(async (flowsScope) => {
|
|
399
752
|
flowsScope.register(rateLimitMiddleware);
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
753
|
+
// Flow engine reads (#3956) — module-conditional (signal-collectors,
|
|
754
|
+
// D-48). The scope itself and its rate limiter above stay unconditional
|
|
755
|
+
// (D2) — commerce's recommender routes below need them too.
|
|
756
|
+
if (isModuleEnabled("signal-collectors")) {
|
|
757
|
+
await flowsScope.register(await sliceRoutePlugin("getFlowRoute"));
|
|
758
|
+
await flowsScope.register(await sliceRoutePlugin("submitFlowStepRoute"));
|
|
759
|
+
await flowsScope.register(await sliceRoutePlugin("getFlowAggregateRoute"));
|
|
760
|
+
}
|
|
403
761
|
// The recommender's email-moment routes (#4841) — same anonymous,
|
|
404
|
-
// rate-limited posture as the three above.
|
|
405
|
-
|
|
406
|
-
|
|
762
|
+
// rate-limited posture as the three above. Module-conditional (commerce,
|
|
763
|
+
// D-47): the recommender leaves Pro flows and moves to commerce whole.
|
|
764
|
+
if (isModuleEnabled("commerce")) {
|
|
765
|
+
await flowsScope.register(await sliceRoutePlugin("saveRecommendationRoute"));
|
|
766
|
+
await flowsScope.register(
|
|
767
|
+
await sliceRoutePlugin("checkoutRecommendationRoute"),
|
|
768
|
+
);
|
|
769
|
+
}
|
|
407
770
|
});
|
|
408
771
|
|
|
409
772
|
// AEO score routes (#4666) — anonymous surface, same posture as the flows
|
|
@@ -428,14 +791,18 @@ export async function build() {
|
|
|
428
791
|
// (#5111, DEF-004). Module-conditional (S1, #5088): the scheduling module
|
|
429
792
|
// owns this whole route set, so an OSS build that excludes it registers
|
|
430
793
|
// neither the routes nor their imports.
|
|
431
|
-
if (isModuleEnabled(
|
|
794
|
+
if (isModuleEnabled("scheduling")) {
|
|
432
795
|
await server.register(async (scheduleScope) => {
|
|
433
796
|
scheduleScope.register(rateLimitMiddleware, {
|
|
434
797
|
perIpPerMinute: DEFAULT_PER_IP_PER_MINUTE_STRICT,
|
|
435
798
|
});
|
|
436
|
-
await scheduleScope.register(
|
|
437
|
-
|
|
438
|
-
|
|
799
|
+
await scheduleScope.register(
|
|
800
|
+
await sliceRoutePlugin("listAvailabilityRoute"),
|
|
801
|
+
);
|
|
802
|
+
await scheduleScope.register(await sliceRoutePlugin("getEventMetaRoute"));
|
|
803
|
+
await scheduleScope.register(
|
|
804
|
+
await sliceRoutePlugin("createBookingRoute"),
|
|
805
|
+
);
|
|
439
806
|
});
|
|
440
807
|
}
|
|
441
808
|
|
|
@@ -447,7 +814,7 @@ export async function build() {
|
|
|
447
814
|
if (shouldMountTestAuth()) {
|
|
448
815
|
await server.register(registerTestAuthRoute);
|
|
449
816
|
server.log.warn(
|
|
450
|
-
|
|
817
|
+
"test-auth route MOUNTED — only safe in dev/test environments",
|
|
451
818
|
);
|
|
452
819
|
}
|
|
453
820
|
|