create-ailk 0.1.1 → 0.3.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 +210 -52
- package/dist/cli.js +0 -0
- 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 +12 -3
- package/dist/lib/extract-module-bundle.js +236 -24
- package/dist/lib/fetch-module.d.ts +3 -38
- package/dist/lib/fetch-module.js +26 -8
- package/dist/lib/module-license-gate.d.ts +2 -0
- package/dist/lib/module-license-gate.js +1 -0
- package/dist/lib/paid-paths.d.ts +16 -0
- package/dist/lib/paid-paths.js +90 -0
- package/dist/module-architecture.d.ts +139 -23
- package/dist/module-architecture.js +668 -25
- package/dist/parse-args.d.ts +2 -2
- package/dist/parse-args.js +3 -1
- package/dist/programmatic.d.ts +2 -1
- package/dist/programmatic.js +15 -4
- package/dist/surfaces.d.ts +12 -36
- package/dist/surfaces.js +35 -5
- package/dist/sync-routes.js +31 -1
- package/package.json +14 -15
- 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 +35 -4
- 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/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 +13 -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 +28 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +144 -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 +478 -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 +717 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +35 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +484 -0
- package/templates/apps/api/src/routes/project-listings/index.ts +52 -10
- package/templates/apps/api/src/routes/project-listings/me.ts +86 -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 +38 -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 +471 -133
- package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +65 -0
- package/templates/apps/api/src/services/__tests__/consent.test.ts +282 -0
- package/templates/apps/api/src/services/consent.ts +236 -0
- 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 +602 -29
- 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]/layout.tsx +13 -1
- package/templates/apps/web/app/llms.txt/route.ts +12 -9
- package/templates/apps/web/jest.config.cjs +20 -13
- package/templates/apps/web/lib/__tests__/site-theme.test.ts +112 -0
- package/templates/apps/web/lib/site-brand.tsx +4 -1
- package/templates/apps/web/lib/site-theme.ts +74 -0
- package/templates/apps/web/next.config.mjs +1 -3
- package/templates/apps/web/package.json +1 -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/content/_site.mdx +12 -0
- package/templates/database/CHANGELOG.md +94 -0
- package/templates/database/inbox/schema.prisma +165 -0
- package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +32 -0
- package/templates/database/migrations/20260911180000_consent_grants/migration.sql +71 -0
- package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +30 -0
- package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +49 -0
- package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +123 -0
- package/templates/database/package.json +1 -1
- package/templates/database/scripts/db-generate-locked.sh +0 -0
- 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__/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__/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 -1278
- 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/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 -490
- 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
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Flow-checkouts respondent route (#4984 — the "my purchases" read behind
|
|
3
|
-
* the respondent account surface).
|
|
4
|
-
*
|
|
5
|
-
* Routes:
|
|
6
|
-
* GET /v1/flow-checkouts/me — the caller's OWN flow checkouts, newest first
|
|
7
|
-
*
|
|
8
|
-
* Registered INSIDE server.ts's `authScope` (auth only — NOT the inner
|
|
9
|
-
* `tenantScope`: a respondent has no workspace membership, and the tenant
|
|
10
|
-
* guard is default-deny) so `request.userId` is populated by
|
|
11
|
-
* `authPreHandlerPlugin` before the handler runs. An unauthenticated caller
|
|
12
|
-
* receives the preHandler's existing 401, untouched. The identity is
|
|
13
|
-
* `request.userId` ONLY: the route declares no query schema and reads no
|
|
14
|
-
* query or body key (an `email` parameter is never parsed, never read),
|
|
15
|
-
* never enumerates, never reads across sites — see
|
|
16
|
-
* `services/flow-checkouts.ts`, where the only join is the lead-promotion
|
|
17
|
-
* link (`Lead.promotedUserId`), never an email match.
|
|
18
|
-
*
|
|
19
|
-
* Envelope per apps/api/CLAUDE.md: `{ ok: true, data }` /
|
|
20
|
-
* `{ ok: false, error: { code, message } }`, real HTTP status codes:
|
|
21
|
-
* 401 unauthorized · 403 email_unverified · 404 user_not_found ·
|
|
22
|
-
* 500 internal_error (the 200 body failed its own contract schema — a
|
|
23
|
-
* drift is never a leak). A verified user with nothing to show is a 200
|
|
24
|
-
* with an empty list.
|
|
25
|
-
*
|
|
26
|
-
* API↔MCP parity: identity-bound (the session IS the identity; the OSS MCP
|
|
27
|
-
* surface is identity-blind) — no MCP twin (#3591 exception, the same shape
|
|
28
|
-
* as `/v1/billing/*`, `/v1/workspaces/*`, `/v1/waitlist-signups/me`).
|
|
29
|
-
*/
|
|
30
|
-
import { logger } from "@working-theory/observability";
|
|
31
|
-
import { flowCheckoutMeResponseSchema } from "@working-theory/validation";
|
|
32
|
-
import type { FastifyPluginAsync, FastifyReply } from "fastify";
|
|
33
|
-
|
|
34
|
-
import { readMyCheckouts } from "../../services/flow-checkouts.js";
|
|
35
|
-
|
|
36
|
-
function unauthorized(reply: FastifyReply) {
|
|
37
|
-
return reply.status(401).send({
|
|
38
|
-
ok: false,
|
|
39
|
-
error: { code: "unauthorized", message: "Authentication required" },
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const ERROR_STATUS = {
|
|
44
|
-
not_found_user: 404,
|
|
45
|
-
email_unverified: 403,
|
|
46
|
-
} as const;
|
|
47
|
-
|
|
48
|
-
const ERROR_CODE = {
|
|
49
|
-
not_found_user: "user_not_found",
|
|
50
|
-
email_unverified: "email_unverified",
|
|
51
|
-
} as const;
|
|
52
|
-
|
|
53
|
-
export const flowCheckoutRoutes: FastifyPluginAsync = async (fastify) => {
|
|
54
|
-
fastify.get("/v1/flow-checkouts/me", async (request, reply) => {
|
|
55
|
-
const userId = request.userId;
|
|
56
|
-
if (!userId) return unauthorized(reply);
|
|
57
|
-
|
|
58
|
-
const result = await readMyCheckouts(userId);
|
|
59
|
-
if (!result.ok) {
|
|
60
|
-
return reply.status(ERROR_STATUS[result.error.kind]).send({
|
|
61
|
-
ok: false,
|
|
62
|
-
error: { code: ERROR_CODE[result.error.kind], message: result.error.message },
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// D5 — re-validate the body against its own strict contract before it
|
|
67
|
-
// leaves the process (the waitlist-signups idiom): a shape drift is an
|
|
68
|
-
// internal error, never a leak. Log issues only — never the value.
|
|
69
|
-
const parsed = flowCheckoutMeResponseSchema.safeParse(result.value);
|
|
70
|
-
if (!parsed.success) {
|
|
71
|
-
logger.error(
|
|
72
|
-
{ route: "flow-checkouts/me", issues: parsed.error.issues },
|
|
73
|
-
"[flow-checkouts] response failed its own contract schema",
|
|
74
|
-
);
|
|
75
|
-
return reply.status(500).send({
|
|
76
|
-
ok: false,
|
|
77
|
-
error: { code: "internal_error", message: "Response failed contract validation" },
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
return reply.status(200).send({ ok: true, data: parsed.data });
|
|
81
|
-
});
|
|
82
|
-
};
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
# Flows — `apps/api/src/routes/flows/`
|
|
2
|
-
|
|
3
|
-
```
|
|
4
|
-
GET /v1/flows/:slug — serve a validated flow config
|
|
5
|
-
POST /v1/flows/:slug/steps — submit one step's answers; persists
|
|
6
|
-
server-side on landing
|
|
7
|
-
GET /v1/flows/:slug/aggregate — anonymous per-question response counts
|
|
8
|
-
for the results reveal (#4771)
|
|
9
|
-
POST /v1/flows/:slug/save — the recommender's "email me this plan"
|
|
10
|
-
moment: FlowSession + Lead (+ T+0 grant)
|
|
11
|
-
for the permalink's answers (#4841)
|
|
12
|
-
POST /v1/flows/:slug/checkout — the purchase moment: the email moment,
|
|
13
|
-
the FlowCheckout buyer row at a
|
|
14
|
-
SERVER-recomputed price, then a Stripe
|
|
15
|
-
TEST-mode Checkout Session (#4841)
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
Every route is anonymous — no auth, no `siteId` on the wire (see the
|
|
19
|
-
module docstring in `index.ts` for the single-site default). All sit
|
|
20
|
-
behind the per-IP rate limiter (`middleware/rate-limit.ts`), registered
|
|
21
|
-
once for the whole `flowsScope` in `server.ts`.
|
|
22
|
-
|
|
23
|
-
## The email moment (`recommender.ts`, #4841)
|
|
24
|
-
|
|
25
|
-
A recommending flow captures no PII until the visitor chooses save or
|
|
26
|
-
checkout (PRD L108). Both routes take the permalink's answer map + an
|
|
27
|
-
email; `checkout` additionally takes `offerKey` / `period` (+ `seats`,
|
|
28
|
-
`creditStop`) and the two redirect URLs — and **no amount of any name**:
|
|
29
|
-
the body schema is `.strict()`, and the server recomputes `price()` from
|
|
30
|
-
the register it served (PRD L104). Checkout refuses a register with
|
|
31
|
-
`stripeConfigRef: null` or no `rateTable.currency` (409), refuses any key
|
|
32
|
-
outside the `sk_test_` allow-list before any side effect (500), and only
|
|
33
|
-
opens the Stripe session AFTER the session, lead and buyer row exist — a
|
|
34
|
-
Stripe outage returns 502 and the record survives. The Stripe session's
|
|
35
|
-
`metadata` carries `flowCheckoutId`, `flowSlug`, `offerKey`, `sessionId`,
|
|
36
|
-
`leadId` and the `permalink` (chunked `permalink_2…` past 500 chars) — the
|
|
37
|
-
join the webhook (#4838) stamps on.
|
|
38
|
-
|
|
39
|
-
## Checkout redirect origins (#4912)
|
|
40
|
-
|
|
41
|
-
`successUrl` and `cancelUrl` must be on **this site's own origin**. The
|
|
42
|
-
shared `flowRedirectUrlSchema` pins only "an `http(s)` URL with a host",
|
|
43
|
-
so without this gate a caller posting straight at the API — bypassing the
|
|
44
|
-
consumer's Server Action, which builds both URLs itself — could mint a
|
|
45
|
-
real Checkout Session whose post-payment redirect lands on a host they
|
|
46
|
-
control: an open redirect carrying the credibility of a completed
|
|
47
|
-
payment, and `cancelUrl` is the same vector without the victim even
|
|
48
|
-
paying.
|
|
49
|
-
|
|
50
|
-
| Var | Default | Meaning |
|
|
51
|
-
| -------------- | ----------------------- | -------------------------------------------------------------------------- |
|
|
52
|
-
| `FRONTEND_URL` | `http://localhost:3000` | The site origin checkout redirects are locked to — the allow-list, of one. |
|
|
53
|
-
|
|
54
|
-
**The allow-list source is `FRONTEND_URL`**, read per request. It is the
|
|
55
|
-
same variable this API already uses as its CORS origin
|
|
56
|
-
(`apps/api/src/server.ts`), for the origin check in
|
|
57
|
-
`middleware/auth.ts`, and to compose the billing-portal return URL
|
|
58
|
-
(`routes/billing/portal.ts`) — reused rather than inventing a second
|
|
59
|
-
site-origin contract, exactly as the scaffolded commerce checkout route
|
|
60
|
-
does (`packages/create-ailk/templates/.claude/skills/scaffold-commerce/
|
|
61
|
-
templates/checkout-route.template.ts`). The default is byte-identical to
|
|
62
|
-
the one `apps/api/src/config/index.ts` applies.
|
|
63
|
-
|
|
64
|
-
The comparison is **exact origin equality** — scheme, host and port must
|
|
65
|
-
all match. A prefix or suffix test would accept
|
|
66
|
-
`https://<site-origin>.evil.example`. A relative path cannot arrive: the
|
|
67
|
-
shared schema refuses it before this gate runs. A refused redirect is a
|
|
68
|
-
`400 invalid_input` raised **before** the register read, so no session,
|
|
69
|
-
no lead and no buyer row is written and Stripe is never called; the
|
|
70
|
-
offending URL is never echoed back or logged.
|
|
71
|
-
|
|
72
|
-
**Operator consequence.** In production, leave `FRONTEND_URL` unset and
|
|
73
|
-
your own checkout redirects will 400 — the same fail-closed direction
|
|
74
|
-
CORS already takes on this variable. Set it to the origin visitors reach
|
|
75
|
-
the site on. A value that is set but not a parseable absolute URL is a
|
|
76
|
-
`500 config-error`, never a silently widened allow-list.
|
|
77
|
-
|
|
78
|
-
## Rate limit — env var and default
|
|
79
|
-
|
|
80
|
-
| Var | Default | Meaning |
|
|
81
|
-
| ----------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
82
|
-
| `AILK_RATE_LIMIT_PER_IP_PER_MINUTE` | `20` | Requests one visitor IP may make against a rate-limited anonymous scope (flows/leads/aeo/schedule) in a 60-second window. |
|
|
83
|
-
|
|
84
|
-
**Why 20.** The default is sized to a full flow — one visitor completing
|
|
85
|
-
the longest shipped flow in one sitting should never trip the limit. The
|
|
86
|
-
longest shipped flow, `ailk-founder-diagnostic`, is **13 steps + 1 GET**
|
|
87
|
-
(the initial config fetch) = 14 requests. 20 leaves headroom for an
|
|
88
|
-
invisible `aeo_score` step's own submission (fired alongside a visible
|
|
89
|
-
step, #4663 D7) and one legitimate resubmit (e.g. a transient failure the
|
|
90
|
-
visitor retries).
|
|
91
|
-
|
|
92
|
-
If a customer fork ships a longer flow, raise this env var to
|
|
93
|
-
`<step count> + 1 (GET) + 1 (aeo_score, if the flow has one) + a small
|
|
94
|
-
margin` rather than lowering the floor for every other flow.
|
|
95
|
-
|
|
96
|
-
## Visitor-IP forwarding (#4730)
|
|
97
|
-
|
|
98
|
-
A consumer site's own Server Action (`apps/web/app/[locale]/flows/[slug]/
|
|
99
|
-
flow.actions.ts`) calls these routes from ITS OWN server process — every
|
|
100
|
-
visitor of that site shares one calling process. Without forwarding, the
|
|
101
|
-
API would see that process's egress IP for every request, and every
|
|
102
|
-
visitor would share ONE rate-limit bucket.
|
|
103
|
-
|
|
104
|
-
The fix has two halves:
|
|
105
|
-
|
|
106
|
-
1. `apps/api/src/server.ts` sets `trustProxy: true` on the Fastify
|
|
107
|
-
instance, so `req.ip` resolves the LEFTMOST address of a multi-hop
|
|
108
|
-
`X-Forwarded-For` chain (Fastify's built-in `@fastify/proxy-addr`
|
|
109
|
-
parsing) instead of a socket address that's meaningless once requests
|
|
110
|
-
are bridged through `.inject()` (the Vercel serverless handler,
|
|
111
|
-
`apps/api/api/index.ts`, never opens a real socket).
|
|
112
|
-
2. The Server Action reads ITS OWN incoming request's `x-forwarded-for`
|
|
113
|
-
(falling back to `x-real-ip`) via Next's `headers()` and forwards it
|
|
114
|
-
as an explicit `x-forwarded-for` header on the outbound
|
|
115
|
-
`flows.submitStep` call. This APPENDS to, rather than replaces,
|
|
116
|
-
whatever hop this API's own edge subsequently adds — the leftmost
|
|
117
|
-
entry in the resulting chain is still the real visitor.
|
|
118
|
-
|
|
119
|
-
**Trade-off, stated plainly.** A caller hitting these routes directly
|
|
120
|
-
(bypassing a consumer's Server Action) can set `x-forwarded-for` to
|
|
121
|
-
anything — there is no fixed, allowlistable upstream proxy to trust,
|
|
122
|
-
because this API's actual "proxies" are arbitrary consumer web
|
|
123
|
-
processes. Per-IP rate limiting on these anonymous surfaces is a
|
|
124
|
-
best-effort abuse deterrent, not a hard security boundary; this fix
|
|
125
|
-
trades a small amount of spoof-resistance for correctness on the
|
|
126
|
-
overwhelmingly common path (Server-Action-forwarded traffic), which was
|
|
127
|
-
previously broken for every visitor.
|
|
128
|
-
|
|
129
|
-
## GET config fetch is NOT IP-forwarded (yet)
|
|
130
|
-
|
|
131
|
-
`apps/web/app/[locale]/flows/[slug]/page.tsx` (a Server Component) calls
|
|
132
|
-
`flows.get(slug)` directly — it does not forward a visitor IP header.
|
|
133
|
-
That GET therefore still buckets on whatever `req.ip` resolves to for the
|
|
134
|
-
calling process (not the visitor), separately from the POST-step bucket
|
|
135
|
-
above. This is a known, narrower gap than the one #4730 fixes (a
|
|
136
|
-
single-visitor lockout across 13+ POST steps); extending `flows.get` +
|
|
137
|
-
`page.tsx` the same way is a natural follow-up if it proves to matter in
|
|
138
|
-
practice.
|
|
139
|
-
|
|
140
|
-
**What this means concretely, so the gap isn't understated:** a site
|
|
141
|
-
receiving more than the default 20 flow-page loads per minute from one
|
|
142
|
-
calling process would 429 its own config GET — and that failure would
|
|
143
|
-
surface **nowhere in the UI**, because `flows.get` (unlike
|
|
144
|
-
`flows.submitStep`) has no 429-to-`rate_limited` mapping at all; the page
|
|
145
|
-
would simply fail to render the flow. This is not a regression (the
|
|
146
|
-
pre-#4730 shared bucket was smaller, at 10), and no acceptance criterion
|
|
147
|
-
covers it — named here so the follow-up starts from the right severity.
|