create-ailk 0.24.0 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -20
- package/component-catalog.json +3 -3
- package/component-catalog.md +3 -3
- package/dist/add-module.js +4 -2
- package/dist/add-route.d.ts +12 -1
- package/dist/add-route.js +31 -3
- package/dist/cli.js +12 -7
- package/dist/derive-architecture.d.ts +3 -4
- package/dist/derive-architecture.js +65 -29
- package/dist/lib/design-checks.d.ts +9 -4
- package/dist/lib/design-checks.js +13 -7
- package/dist/lib/extract-module-bundle.js +29 -2
- package/dist/lib/fetch-template-snapshot.d.ts +1 -1
- package/dist/lib/fetch-template-snapshot.js +7 -3
- package/dist/lib/import-graph.d.ts +1 -1
- package/dist/lib/import-graph.js +4 -4
- package/dist/lib/module-license-gate.d.ts +12 -1
- package/dist/lib/module-license-gate.js +34 -12
- package/dist/module-architecture.d.ts +20 -3
- package/dist/module-architecture.js +117 -550
- package/dist/paid-sections.d.ts +25 -1
- package/dist/paid-sections.js +88 -3
- package/dist/surfaces.js +10 -2
- package/dist/template-record.d.ts +14 -4
- package/dist/template-record.js +23 -5
- package/dist/topology.d.ts +16 -3
- package/dist/topology.js +31 -19
- package/dist/update.d.ts +10 -1
- package/dist/update.js +540 -16
- package/package.json +3 -3
- package/release-scope.json +1 -1
- package/templates/.claude/cloudparity-repo-hooks.json +5 -0
- package/templates/.claude/hooks/template-guard.sh +137 -0
- package/templates/.claude/rules/extending.md +106 -0
- package/templates/.claude/rules/loop-contract.md +2 -2
- package/templates/.claude/settings.json +11 -0
- package/templates/.env.example +5 -5
- package/templates/.github/workflows/ci.yml +7 -4
- package/templates/CLAUDE.md +11 -1
- package/templates/CONVENTIONS.md +8 -10
- package/templates/README.md +14 -13
- package/templates/apps/api/.env.example +2 -2
- package/templates/apps/api/CLAUDE.md +102 -76
- package/templates/apps/api/jest.config.cjs +15 -5
- package/templates/apps/api/package.json +16 -20
- package/templates/apps/api/src/__tests__/trusted-origins.server.test.ts +2 -2
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +4 -4
- package/templates/apps/api/src/index.ts +1 -1
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -3
- package/templates/apps/api/src/modules/commerce.ts +10 -0
- package/templates/apps/api/src/modules/content-write.ts +10 -0
- package/templates/apps/api/src/modules/multi-tenant.ts +10 -0
- package/templates/apps/api/src/modules/scheduling.ts +10 -0
- package/templates/apps/api/src/modules/signal-collectors.ts +10 -0
- package/templates/apps/api/src/modules/workspace.ts +10 -0
- package/templates/apps/api/src/server.ts +117 -1882
- package/templates/apps/api/vercel.json +2 -2
- package/templates/apps/mcp/CLAUDE.md +67 -20
- package/templates/apps/mcp/__tests__/app-entry.test.ts +4 -254
- package/templates/apps/mcp/__tests__/boot-file.test.ts +92 -0
- package/templates/apps/mcp/jest.config.cjs +6 -1
- package/templates/apps/mcp/package.json +5 -13
- package/templates/apps/mcp/src/app.ts +8 -7
- package/templates/apps/mcp/src/index.ts +6 -34
- package/templates/apps/mcp/src/lib/paid-tools.generated.ts +10 -0
- package/templates/apps/mcp/src/lib/topology.generated.ts +2 -2
- package/templates/apps/mcp/src/site.ts +12 -0
- package/templates/apps/web/.env.example +1 -1
- package/templates/apps/web/CLAUDE.md +10 -3
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +9 -3
- package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +1 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +22 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +7 -3
- package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +43 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +7 -3
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/__tests__/route.test.ts +222 -0
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +10 -3
- package/templates/apps/web/app/api/revalidate/route.ts +3 -3
- package/templates/apps/web/auth.ts +2 -2
- package/templates/apps/web/jest.config.cjs +1 -0
- package/templates/apps/web/lib/auth-email.ts +1 -1
- package/templates/apps/web/lib/relay.ts +2 -2
- package/templates/apps/web/lib/release-scope.ts +47 -0
- package/templates/apps/web/lib/server-api.ts +1 -1
- package/templates/apps/web/src/lib/commerce-claim.ts +1 -1
- package/templates/customizations.yaml +37 -0
- package/templates/database/prisma.config.ts +10 -14
- package/templates/database/seed-test.ts +2 -2
- package/templates/database/seed.ts +5 -5
- package/templates/package.json +12 -6
- package/templates/pnpm-workspace.yaml +0 -1
- package/templates/project.yaml +16 -8
- package/templates/tools/jest/reporters.cjs +65 -0
- package/templates/tools/lint-architecture.ts +553 -7
- package/templates/tsconfig.json +1 -2
- package/versions.json +48 -35
- package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +0 -409
- package/templates/apps/api/src/__tests__/factories/agent-caller.ts +0 -102
- package/templates/apps/api/src/__tests__/factories/content-fixture.ts +0 -138
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +0 -441
- package/templates/apps/api/src/__tests__/factories/index.ts +0 -70
- package/templates/apps/api/src/__tests__/factories/lead-fixture.ts +0 -82
- package/templates/apps/api/src/__tests__/leads-forwarded-rate-limit.test.ts +0 -135
- package/templates/apps/api/src/__tests__/services/enrichment-shim.test.ts +0 -126
- package/templates/apps/api/src/__tests__/stripe-boot.test.ts +0 -132
- package/templates/apps/api/src/__tests__/support/hub-sign-in-fake-db.ts +0 -362
- package/templates/apps/api/src/bin/__tests__/rate-limit-probe.test.ts +0 -184
- package/templates/apps/api/src/bin/abandon-sweep.ts +0 -42
- package/templates/apps/api/src/bin/rate-limit-probe.ts +0 -53
- package/templates/apps/api/src/bin/spokes.ts +0 -198
- package/templates/apps/api/src/bin/stripe-connections-rekey.ts +0 -45
- package/templates/apps/api/src/config/__tests__/hub-require-spoke-key.test.ts +0 -196
- package/templates/apps/api/src/config/__tests__/index.test.ts +0 -233
- package/templates/apps/api/src/config/__tests__/modules.test.ts +0 -76
- package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +0 -185
- package/templates/apps/api/src/config/__tests__/trusted-origins.test.ts +0 -238
- package/templates/apps/api/src/config/index.ts +0 -596
- package/templates/apps/api/src/config/modules.ts +0 -83
- package/templates/apps/api/src/lib/__tests__/agent-store.test.ts +0 -581
- package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +0 -143
- package/templates/apps/api/src/lib/__tests__/consent-ledger-double.ts +0 -237
- package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +0 -353
- package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +0 -201
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +0 -224
- package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +0 -120
- package/templates/apps/api/src/lib/__tests__/errors.test.ts +0 -94
- package/templates/apps/api/src/lib/__tests__/file-fallback.test.ts +0 -144
- package/templates/apps/api/src/lib/__tests__/fixtures/HomePage/home.mdx +0 -8
- package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +0 -149
- package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +0 -116
- package/templates/apps/api/src/lib/__tests__/hub-sign-in-store.test.ts +0 -209
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +0 -257
- package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +0 -96
- package/templates/apps/api/src/lib/__tests__/outbound-http.test.ts +0 -125
- package/templates/apps/api/src/lib/__tests__/prisma.test.ts +0 -165
- package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +0 -317
- package/templates/apps/api/src/lib/__tests__/require-paid-tier.test.ts +0 -129
- package/templates/apps/api/src/lib/__tests__/site.test.ts +0 -308
- package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +0 -89
- package/templates/apps/api/src/lib/__tests__/spoke-store.test.ts +0 -202
- package/templates/apps/api/src/lib/__tests__/stripe-admission.test.ts +0 -217
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -82
- package/templates/apps/api/src/lib/agent-store.ts +0 -613
- package/templates/apps/api/src/lib/auth-email-budget.ts +0 -161
- package/templates/apps/api/src/lib/consent-store.ts +0 -363
- package/templates/apps/api/src/lib/content-adapter.ts +0 -112
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +0 -96
- package/templates/apps/api/src/lib/error-serializer.ts +0 -164
- package/templates/apps/api/src/lib/errors.ts +0 -95
- package/templates/apps/api/src/lib/file-fallback.ts +0 -87
- package/templates/apps/api/src/lib/flow-completion-store.ts +0 -136
- package/templates/apps/api/src/lib/hub-sign-in-store.ts +0 -455
- package/templates/apps/api/src/lib/lead-capture-store.ts +0 -122
- package/templates/apps/api/src/lib/lead-delivery.ts +0 -52
- package/templates/apps/api/src/lib/license-key-store.ts +0 -45
- package/templates/apps/api/src/lib/locales.ts +0 -201
- package/templates/apps/api/src/lib/outbound-http.ts +0 -127
- package/templates/apps/api/src/lib/prisma.ts +0 -21
- package/templates/apps/api/src/lib/proposal-paid-store.ts +0 -66
- package/templates/apps/api/src/lib/rate-limit-probe.ts +0 -165
- package/templates/apps/api/src/lib/rate-limit-store.ts +0 -142
- package/templates/apps/api/src/lib/require-commerce-claim.ts +0 -250
- package/templates/apps/api/src/lib/require-paid-tier.ts +0 -53
- package/templates/apps/api/src/lib/site.ts +0 -257
- package/templates/apps/api/src/lib/slice-load.ts +0 -90
- package/templates/apps/api/src/lib/spoke-store.ts +0 -180
- package/templates/apps/api/src/lib/stripe.ts +0 -321
- package/templates/apps/api/src/lib/webhook-idempotency.ts +0 -156
- package/templates/apps/api/src/middleware/__tests__/adversarial/bearer-token-format.test.ts +0 -111
- package/templates/apps/api/src/middleware/__tests__/adversarial/cookie-or-bearer-precedence.test.ts +0 -172
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-bearer-skip.test.ts +0 -119
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-cookie-path.test.ts +0 -138
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-trusted-origins.test.ts +0 -142
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +0 -155
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-timeout.test.ts +0 -139
- package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +0 -562
- package/templates/apps/api/src/middleware/__tests__/auth.both-present-precedence.test.ts +0 -76
- package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +0 -220
- package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +0 -97
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +0 -557
- package/templates/apps/api/src/middleware/__tests__/auth.neither-present.test.ts +0 -72
- package/templates/apps/api/src/middleware/__tests__/auth.optional-cookie.test.ts +0 -140
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +0 -721
- package/templates/apps/api/src/middleware/__tests__/rate-limit-cross-isolate.db.test.ts +0 -220
- package/templates/apps/api/src/middleware/__tests__/rate-limit-store.test.ts +0 -430
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +0 -1409
- package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +0 -174
- package/templates/apps/api/src/middleware/auth.ts +0 -864
- package/templates/apps/api/src/middleware/default-tenant.ts +0 -166
- package/templates/apps/api/src/middleware/rate-limit.ts +0 -983
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +0 -391
- package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +0 -182
- package/templates/apps/api/src/openapi/index.ts +0 -18
- package/templates/apps/api/src/openapi/spec.ts +0 -2148
- package/templates/apps/api/src/openapi/surface-drift.ts +0 -195
- package/templates/apps/api/src/routes/aeo/__tests__/index.test.ts +0 -175
- package/templates/apps/api/src/routes/aeo/index.ts +0 -107
- package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +0 -196
- package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +0 -249
- package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +0 -532
- package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +0 -313
- package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +0 -413
- package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +0 -609
- package/templates/apps/api/src/routes/auth/hub-sign-in.ts +0 -242
- package/templates/apps/api/src/routes/auth/tokens.ts +0 -211
- package/templates/apps/api/src/routes/auth/transactional-email.ts +0 -301
- package/templates/apps/api/src/routes/auth/user.ts +0 -305
- package/templates/apps/api/src/routes/billing/plan-checkout.ts +0 -332
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +0 -178
- package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +0 -1426
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +0 -448
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -1742
- package/templates/apps/api/src/routes/checkout/index.ts +0 -34
- package/templates/apps/api/src/routes/checkout/session.ts +0 -171
- package/templates/apps/api/src/routes/checkout/sessions.ts +0 -540
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +0 -386
- package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +0 -160
- package/templates/apps/api/src/routes/consent/account.ts +0 -268
- package/templates/apps/api/src/routes/consent/cookie.ts +0 -90
- package/templates/apps/api/src/routes/consent/index.ts +0 -26
- package/templates/apps/api/src/routes/consent/unsubscribe.ts +0 -127
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +0 -853
- package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +0 -230
- package/templates/apps/api/src/routes/content/__tests__/list-types.test.ts +0 -44
- package/templates/apps/api/src/routes/content/__tests__/locale.test.ts +0 -118
- package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +0 -731
- package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +0 -753
- package/templates/apps/api/src/routes/content/__tests__/rate-limit.test.ts +0 -51
- package/templates/apps/api/src/routes/content/__tests__/search.test.ts +0 -450
- package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +0 -762
- package/templates/apps/api/src/routes/content/get-page.ts +0 -183
- package/templates/apps/api/src/routes/content/index.ts +0 -49
- package/templates/apps/api/src/routes/content/list-pages.ts +0 -74
- package/templates/apps/api/src/routes/content/list-types.ts +0 -19
- package/templates/apps/api/src/routes/content/offerings.ts +0 -184
- package/templates/apps/api/src/routes/content/pricing.ts +0 -176
- package/templates/apps/api/src/routes/content/rate-limit.ts +0 -22
- package/templates/apps/api/src/routes/content/search.ts +0 -100
- package/templates/apps/api/src/routes/content/shown-offerings.ts +0 -229
- package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +0 -88
- package/templates/apps/api/src/routes/leads/__tests__/list.test.ts +0 -171
- package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +0 -515
- package/templates/apps/api/src/routes/leads/__tests__/spoke.test.ts +0 -392
- package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +0 -165
- package/templates/apps/api/src/routes/leads/index.ts +0 -639
- package/templates/apps/api/src/routes/leads/list.ts +0 -104
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +0 -626
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +0 -1268
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +0 -847
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +0 -491
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +0 -476
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +0 -872
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +0 -1022
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +0 -484
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +0 -535
- package/templates/apps/api/src/routes/project-listings/index.ts +0 -164
- package/templates/apps/api/src/routes/project-listings/me.ts +0 -87
- package/templates/apps/api/src/routes/project-listings/patch-copy.ts +0 -224
- package/templates/apps/api/src/routes/project-listings/patch-draft.ts +0 -102
- package/templates/apps/api/src/routes/project-listings/public.ts +0 -98
- package/templates/apps/api/src/routes/project-listings/respond.ts +0 -48
- package/templates/apps/api/src/routes/project-listings/resume-token.ts +0 -27
- package/templates/apps/api/src/routes/project-listings/start.ts +0 -140
- package/templates/apps/api/src/routes/project-listings/submit.ts +0 -130
- package/templates/apps/api/src/routes/project-listings/verify-email.ts +0 -150
- package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +0 -686
- package/templates/apps/api/src/routes/requests/confirm.ts +0 -601
- package/templates/apps/api/src/routes/requests/index.ts +0 -54
- package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +0 -306
- package/templates/apps/api/src/routes/test/auth/session.ts +0 -143
- package/templates/apps/api/src/routes/webhooks/README.md +0 -398
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +0 -649
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +0 -749
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -449
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -264
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode-live.test.ts +0 -159
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode.test.ts +0 -158
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +0 -814
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -301
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-proposal-paid.test.ts +0 -369
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -215
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +0 -301
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +0 -809
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -1155
- package/templates/apps/api/src/routes/webhooks/index.ts +0 -141
- package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +0 -384
- package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -2028
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +0 -645
- package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +0 -132
- package/templates/apps/api/src/services/__tests__/audit-log.test.ts +0 -1028
- package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +0 -313
- package/templates/apps/api/src/services/__tests__/consent-ledger.test.ts +0 -503
- package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +0 -65
- package/templates/apps/api/src/services/__tests__/consent.test.ts +0 -299
- package/templates/apps/api/src/services/__tests__/default-tenant.test.ts +0 -147
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +0 -877
- package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +0 -379
- package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +0 -368
- package/templates/apps/api/src/services/__tests__/entitlements.test.ts +0 -142
- package/templates/apps/api/src/services/__tests__/hub-sign-in.test.ts +0 -854
- package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +0 -234
- package/templates/apps/api/src/services/__tests__/lead-accept-user.test.ts +0 -251
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +0 -2660
- package/templates/apps/api/src/services/__tests__/listing-config.test.ts +0 -166
- package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +0 -389
- package/templates/apps/api/src/services/__tests__/person.test.ts +0 -1204
- package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +0 -450
- package/templates/apps/api/src/services/__tests__/project-listings.counts.test.ts +0 -202
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +0 -151
- package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +0 -137
- package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +0 -531
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +0 -372
- package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +0 -348
- package/templates/apps/api/src/services/__tests__/spoke-resolution.test.ts +0 -660
- package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +0 -941
- package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +0 -588
- package/templates/apps/api/src/services/__tests__/verified-email.test.ts +0 -78
- package/templates/apps/api/src/services/abandon-email.ts +0 -336
- package/templates/apps/api/src/services/aeo-score.ts +0 -325
- package/templates/apps/api/src/services/answer-distributions.ts +0 -138
- package/templates/apps/api/src/services/audit-log.ts +0 -961
- package/templates/apps/api/src/services/checkout-buyer.ts +0 -168
- package/templates/apps/api/src/services/consent.ts +0 -834
- package/templates/apps/api/src/services/default-tenant.ts +0 -416
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +0 -750
- package/templates/apps/api/src/services/dunning-policy.ts +0 -294
- package/templates/apps/api/src/services/enrichment-shim.ts +0 -53
- package/templates/apps/api/src/services/entitlements.ts +0 -338
- package/templates/apps/api/src/services/hub-sign-in.ts +0 -472
- package/templates/apps/api/src/services/lead-accept-user.ts +0 -126
- package/templates/apps/api/src/services/lead-promotion.ts +0 -160
- package/templates/apps/api/src/services/lead-routing.ts +0 -1372
- package/templates/apps/api/src/services/leads-inbox.ts +0 -312
- package/templates/apps/api/src/services/listing-config.ts +0 -178
- package/templates/apps/api/src/services/magnet-completion.ts +0 -242
- package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +0 -348
- package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +0 -268
- package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +0 -975
- package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +0 -416
- package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +0 -400
- package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +0 -685
- package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +0 -231
- package/templates/apps/api/src/services/offerings/catalog.ts +0 -318
- package/templates/apps/api/src/services/offerings/checkout-authorization.ts +0 -403
- package/templates/apps/api/src/services/offerings/checkout-session-record.ts +0 -192
- package/templates/apps/api/src/services/offerings/connection.ts +0 -1083
- package/templates/apps/api/src/services/offerings/index.ts +0 -614
- package/templates/apps/api/src/services/offerings/source.ts +0 -140
- package/templates/apps/api/src/services/offerings/store.ts +0 -150
- package/templates/apps/api/src/services/owner-workspaces.ts +0 -132
- package/templates/apps/api/src/services/person.ts +0 -1211
- package/templates/apps/api/src/services/plan-offerings.ts +0 -125
- package/templates/apps/api/src/services/project-listing-resume-email.ts +0 -326
- package/templates/apps/api/src/services/project-listings.ts +0 -2085
- package/templates/apps/api/src/services/public-site-resolver.ts +0 -396
- package/templates/apps/api/src/services/request-confirm.ts +0 -698
- package/templates/apps/api/src/services/revalidation.ts +0 -235
- package/templates/apps/api/src/services/slack-notify.ts +0 -291
- package/templates/apps/api/src/services/usage-aggregation.ts +0 -516
- package/templates/apps/api/src/services/usage-credits.ts +0 -340
- package/templates/apps/api/src/services/verified-email.ts +0 -71
- package/templates/apps/mcp/__tests__/abuse/daily-cap.test.ts +0 -52
- package/templates/apps/mcp/__tests__/abuse/email-validator.test.ts +0 -46
- package/templates/apps/mcp/__tests__/abuse/guard.test.ts +0 -198
- package/templates/apps/mcp/__tests__/abuse/honeypot.test.ts +0 -36
- package/templates/apps/mcp/__tests__/abuse/user-agent-deny.test.ts +0 -54
- package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +0 -116
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +0 -299
- package/templates/apps/mcp/__tests__/boot.test.ts +0 -280
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +0 -359
- package/templates/apps/mcp/__tests__/config/modules.test.ts +0 -78
- package/templates/apps/mcp/__tests__/get_business_hours.test.ts +0 -82
- package/templates/apps/mcp/__tests__/get_faq.test.ts +0 -80
- package/templates/apps/mcp/__tests__/get_locations.test.ts +0 -65
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +0 -557
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +0 -524
- package/templates/apps/mcp/__tests__/get_site_info.test.ts +0 -81
- package/templates/apps/mcp/__tests__/http-transport.test.ts +0 -1232
- package/templates/apps/mcp/__tests__/index.test.ts +0 -195
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +0 -191
- package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +0 -531
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +0 -70
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +0 -162
- package/templates/apps/mcp/__tests__/lib/content-root.test.ts +0 -93
- package/templates/apps/mcp/__tests__/lib/errors.test.ts +0 -75
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +0 -180
- package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +0 -124
- package/templates/apps/mcp/__tests__/list_pages.test.ts +0 -268
- package/templates/apps/mcp/__tests__/locale.test.ts +0 -120
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +0 -175
- package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +0 -101
- package/templates/apps/mcp/__tests__/rate-limit-write.test.ts +0 -189
- package/templates/apps/mcp/__tests__/read_content.test.ts +0 -511
- package/templates/apps/mcp/__tests__/request_callback.test.ts +0 -160
- package/templates/apps/mcp/__tests__/search_content.test.ts +0 -271
- package/templates/apps/mcp/__tests__/server.test.ts +0 -388
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +0 -448
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +0 -222
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +0 -152
- package/templates/apps/mcp/__tests__/tools/create_project.test.ts +0 -243
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +0 -451
- package/templates/apps/mcp/src/abuse/daily-cap.ts +0 -47
- package/templates/apps/mcp/src/abuse/email-validator.ts +0 -25
- package/templates/apps/mcp/src/abuse/honeypot.ts +0 -21
- package/templates/apps/mcp/src/abuse/index.ts +0 -349
- package/templates/apps/mcp/src/abuse/user-agent-deny.ts +0 -58
- package/templates/apps/mcp/src/boot.ts +0 -167
- package/templates/apps/mcp/src/config/modules.ts +0 -43
- package/templates/apps/mcp/src/http-transport.ts +0 -564
- package/templates/apps/mcp/src/lib/audit-single-page.ts +0 -308
- package/templates/apps/mcp/src/lib/commerce-claim.ts +0 -263
- package/templates/apps/mcp/src/lib/content-adapter.ts +0 -116
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +0 -140
- package/templates/apps/mcp/src/lib/errors.ts +0 -61
- package/templates/apps/mcp/src/lib/license-key-store.ts +0 -45
- package/templates/apps/mcp/src/lib/locales.ts +0 -171
- package/templates/apps/mcp/src/lib/prisma.ts +0 -39
- package/templates/apps/mcp/src/lib/public-workspace.ts +0 -133
- package/templates/apps/mcp/src/lib/resolve-locale.ts +0 -30
- package/templates/apps/mcp/src/lib/shown-offerings.ts +0 -105
- package/templates/apps/mcp/src/lib/site-config.ts +0 -53
- package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +0 -59
- package/templates/apps/mcp/src/request-context.ts +0 -52
- package/templates/apps/mcp/src/server.ts +0 -343
- package/templates/apps/mcp/src/serverless-app.ts +0 -88
- package/templates/apps/mcp/src/tools/audit.ts +0 -127
- package/templates/apps/mcp/src/tools/capture_lead.ts +0 -66
- package/templates/apps/mcp/src/tools/create_project.ts +0 -134
- package/templates/apps/mcp/src/tools/get_business_hours.ts +0 -68
- package/templates/apps/mcp/src/tools/get_faq.ts +0 -71
- package/templates/apps/mcp/src/tools/get_locations.ts +0 -88
- package/templates/apps/mcp/src/tools/get_offerings.ts +0 -96
- package/templates/apps/mcp/src/tools/get_pricing.ts +0 -94
- package/templates/apps/mcp/src/tools/get_site_info.ts +0 -86
- package/templates/apps/mcp/src/tools/index.ts +0 -138
- package/templates/apps/mcp/src/tools/list_pages.ts +0 -68
- package/templates/apps/mcp/src/tools/person_notes.ts +0 -25
- package/templates/apps/mcp/src/tools/proposals.ts +0 -27
- package/templates/apps/mcp/src/tools/purchase.ts +0 -205
- package/templates/apps/mcp/src/tools/read_content.ts +0 -81
- package/templates/apps/mcp/src/tools/request_callback.ts +0 -52
- package/templates/apps/mcp/src/tools/search_content.ts +0 -63
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +0 -54
- package/templates/apps/mcp/src/workspace-server.ts +0 -224
- package/templates/database/CHANGELOG.md +0 -981
- package/templates/database/LICENSE +0 -202
- package/templates/database/NOTICE +0 -2
- package/templates/database/README.md +0 -243
- package/templates/database/__tests__/agents.db.test.ts +0 -320
- package/templates/database/__tests__/appointment.db.test.ts +0 -106
- package/templates/database/__tests__/artifact-content-store.db.test.ts +0 -316
- package/templates/database/__tests__/composite-fence.db.test.ts +0 -206
- package/templates/database/__tests__/consent-ledger.db.test.ts +0 -345
- package/templates/database/__tests__/consent-source-checkout-migration.test.ts +0 -79
- package/templates/database/__tests__/fence-fan-out.db.test.ts +0 -715
- package/templates/database/__tests__/fixtures/appointment-create-probe.ts +0 -79
- package/templates/database/__tests__/fixtures/person-backfill.json +0 -264
- package/templates/database/__tests__/fixtures/person-contact-backfill.json +0 -182
- package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +0 -100
- package/templates/database/__tests__/flow-completion-migration.test.ts +0 -115
- package/templates/database/__tests__/flow-completion.db.test.ts +0 -136
- package/templates/database/__tests__/flow-workspace-key.db.test.ts +0 -263
- package/templates/database/__tests__/flows-listing-diagnostic.test.ts +0 -392
- package/templates/database/__tests__/flows-presets.test.ts +0 -173
- package/templates/database/__tests__/in-page-magnets-migration.test.ts +0 -161
- package/templates/database/__tests__/lead-idempotency-key.db.test.ts +0 -92
- package/templates/database/__tests__/member-area.db.test.ts +0 -257
- package/templates/database/__tests__/member-contract-migration.test.ts +0 -85
- package/templates/database/__tests__/member-contract.db.test.ts +0 -413
- package/templates/database/__tests__/offerings-checkout-session.db.test.ts +0 -270
- package/templates/database/__tests__/offerings-migration.test.ts +0 -197
- package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +0 -86
- package/templates/database/__tests__/offerings.db.test.ts +0 -303
- package/templates/database/__tests__/outbound-message-action.db.test.ts +0 -80
- package/templates/database/__tests__/person-contact.db.test.ts +0 -563
- package/templates/database/__tests__/person-notes.db.test.ts +0 -249
- package/templates/database/__tests__/person.db.test.ts +0 -586
- package/templates/database/__tests__/piece-kind-migration.test.ts +0 -67
- package/templates/database/__tests__/piece-kind.db.test.ts +0 -208
- package/templates/database/__tests__/prefixed-ids.db.test.ts +0 -222
- package/templates/database/__tests__/project-listing-consent-at.db.test.ts +0 -111
- package/templates/database/__tests__/seed.test.ts +0 -272
- package/templates/database/__tests__/spoke-registry.db.test.ts +0 -191
- package/templates/database/__tests__/tenancy.test.ts +0 -141
- package/templates/database/__tests__/trial-magnet-migration.test.ts +0 -186
- package/templates/database/__tests__/user-cascade.test.ts +0 -140
- package/templates/database/_gitignore +0 -8
- package/templates/database/auth/.gitkeep +0 -0
- package/templates/database/auth/schema.prisma +0 -641
- package/templates/database/config/.gitkeep +0 -0
- package/templates/database/config/schema.prisma +0 -441
- package/templates/database/content/.gitkeep +0 -0
- package/templates/database/content/schema.prisma +0 -339
- package/templates/database/eslint.config.mjs +0 -69
- package/templates/database/flows/listing-diagnostic.ts +0 -182
- package/templates/database/flows/presets.ts +0 -315
- package/templates/database/inbox/.gitkeep +0 -0
- package/templates/database/inbox/README.md +0 -3
- package/templates/database/inbox/__tests__/schema.test.ts +0 -44
- package/templates/database/inbox/schema.prisma +0 -1447
- package/templates/database/jest.config.cjs +0 -36
- package/templates/database/migrations/20260508000000_add_inbox_namespace/migration.sql +0 -328
- package/templates/database/migrations/20260513000000_better_auth_migration/migration.sql +0 -100
- package/templates/database/migrations/20260515000000_add_user_billing_fields/migration.sql +0 -28
- package/templates/database/migrations/20260516000000_add_user_tier_care_plan_seat/migration.sql +0 -25
- package/templates/database/migrations/20260518000000_add_telemetry_records/migration.sql +0 -42
- package/templates/database/migrations/20260525000000_theme_wiring/migration.sql +0 -67
- package/templates/database/migrations/20260710000000_retire_commercial_tier/migration.sql +0 -21
- package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +0 -201
- package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +0 -67
- package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +0 -46
- package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +0 -25
- package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +0 -49
- package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +0 -197
- package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +0 -32
- package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +0 -22
- package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +0 -151
- package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +0 -223
- package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +0 -22
- package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +0 -32
- package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +0 -40
- package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +0 -4
- package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +0 -2
- package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +0 -2
- package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +0 -29
- package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +0 -6
- package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +0 -39
- package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +0 -24
- package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +0 -3
- package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +0 -33
- package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +0 -30
- package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +0 -47
- package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +0 -16
- package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +0 -47
- package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +0 -10
- package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +0 -26
- package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +0 -90
- package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +0 -174
- package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +0 -14
- package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +0 -25
- package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +0 -32
- package/templates/database/migrations/20260911180000_consent_grants/migration.sql +0 -71
- package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +0 -30
- package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +0 -49
- package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +0 -123
- package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +0 -15
- package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +0 -158
- package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +0 -40
- package/templates/database/migrations/20260918120000_add_appointment/migration.sql +0 -67
- package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +0 -70
- package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +0 -181
- package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +0 -172
- package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +0 -41
- package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +0 -185
- package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +0 -70
- package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +0 -57
- package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +0 -68
- package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +0 -1067
- package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +0 -210
- package/templates/database/migrations/20261003120000_spoke_registry/migration.sql +0 -80
- package/templates/database/migrations/20261003130000_scheduling_connection/migration.sql +0 -49
- package/templates/database/migrations/20261005120000_agents/migration.sql +0 -84
- package/templates/database/migrations/20261008120000_user_last_workspace/migration.sql +0 -35
- package/templates/database/migrations/20261008130000_lead_idempotency_key/migration.sql +0 -20
- package/templates/database/migrations/20261009120000_agent_grants/migration.sql +0 -33
- package/templates/database/migrations/20261010120000_person/migration.sql +0 -106
- package/templates/database/migrations/20261010121000_person_backfill/migration.sql +0 -132
- package/templates/database/migrations/20261010130000_agent_grants_people_read/migration.sql +0 -20
- package/templates/database/migrations/20261011120000_member_area/migration.sql +0 -158
- package/templates/database/migrations/20261011130000_agent_grants_member_pieces_manage/migration.sql +0 -21
- package/templates/database/migrations/20261012120000_member_contract/migration.sql +0 -192
- package/templates/database/migrations/20261013120000_lead_spoke_checkout/migration.sql +0 -20
- package/templates/database/migrations/20261014120000_consent_ledger/migration.sql +0 -122
- package/templates/database/migrations/20261014120000_person_contact/migration.sql +0 -231
- package/templates/database/migrations/20261015120000_person_notes/migration.sql +0 -77
- package/templates/database/migrations/20261015120000_rate_limit_counters/migration.sql +0 -43
- package/templates/database/migrations/20261016120000_piece_kind/migration.sql +0 -95
- package/templates/database/migrations/20261017120000_outbound_message_action/migration.sql +0 -30
- package/templates/database/migrations/20261018120000_flow_completion/migration.sql +0 -52
- package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +0 -27
- package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +0 -146
- package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +0 -77
- package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +0 -45
- package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +0 -47
- package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +0 -16
- package/templates/database/migrations/20261025120000_trial_magnet/migration.sql +0 -28
- package/templates/database/migrations/20261026120000_trial_magnet_proof_line/migration.sql +0 -23
- package/templates/database/migrations/migration_lock.toml +0 -3
- package/templates/database/ops/.gitkeep +0 -0
- package/templates/database/ops/schema.prisma +0 -43
- package/templates/database/package.json +0 -89
- package/templates/database/schema.prisma +0 -71
- package/templates/database/scripts/db-generate-locked.sh +0 -80
- package/templates/database/scripts/person-backfill.sql +0 -132
- package/templates/database/tenancy.ts +0 -183
- package/templates/database/tsconfig.build.json +0 -16
- package/templates/database/tsconfig.flows.json +0 -15
- package/templates/database/tsconfig.json +0 -16
- package/templates/database/tsconfig.tenancy.json +0 -14
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NOT marked `oss: false` (D-48/#5366 escalation, reversing this pass's
|
|
3
|
-
* own D6 read — see `./me.ts`'s header comment): `sendValidated` is imported
|
|
4
|
-
* by `./me.ts`, `./patch-draft.ts` and `./public.ts`, each of which must ship
|
|
5
|
-
* unconditionally because `./index.ts`'s multi-tenant literal `import()`
|
|
6
|
-
* forces it. A file imported by an always-shipping file cannot itself be
|
|
7
|
-
* excluded — this is the ORIGINAL multi-tenant/#5088 card's own conclusion
|
|
8
|
-
* about this exact file (module-architecture.ts lines 569-571), which D6's
|
|
9
|
-
* "every other consumer is Pro Plus" premise doesn't override: two of its
|
|
10
|
-
* three non-tenant consumers are these three always-shipping files, not a
|
|
11
|
-
* gated one.
|
|
12
|
-
*
|
|
13
|
-
* Response-schema validation for the project-listings routes (#4998),
|
|
14
|
-
* mirroring `waitlist-experiments/respond.ts`'s discipline: response schemas
|
|
15
|
-
* are contract-only unless re-parsed against their own `.strict()` schema
|
|
16
|
-
* before the value leaves the process. A shape drift here is a genuine
|
|
17
|
-
* internal error (the service layer already guards every field), never a
|
|
18
|
-
* client-facing 4xx.
|
|
19
|
-
*/
|
|
20
|
-
import { logger } from "@working-theory/observability";
|
|
21
|
-
import type { FastifyReply } from "fastify";
|
|
22
|
-
import type { z } from "zod";
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Parse `data` against `schema`; on success send the ORIGINAL `data` object
|
|
26
|
-
* (not the parsed clone) at `200`, matching the waitlist-experiments
|
|
27
|
-
* envelope. On failure, log the zod issues (never the raw `data`, which may
|
|
28
|
-
* carry founder PII) and reply `500`.
|
|
29
|
-
*/
|
|
30
|
-
export function sendValidated<T>(
|
|
31
|
-
reply: FastifyReply,
|
|
32
|
-
schema: z.ZodType<T>,
|
|
33
|
-
data: T,
|
|
34
|
-
routeName: string,
|
|
35
|
-
): FastifyReply {
|
|
36
|
-
const parsed = schema.safeParse(data);
|
|
37
|
-
if (!parsed.success) {
|
|
38
|
-
logger.error(
|
|
39
|
-
{ route: routeName, issues: parsed.error.issues },
|
|
40
|
-
"[project-listings] response failed its own contract schema",
|
|
41
|
-
);
|
|
42
|
-
return reply.status(500).send({
|
|
43
|
-
ok: false,
|
|
44
|
-
error: { code: "internal_error", message: "Response failed contract validation" },
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
return reply.status(200).send({ ok: true, data });
|
|
48
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NOT marked `oss: false` — see `./me.ts`'s header comment (D-48/#5366
|
|
3
|
-
* escalation): imported only by `./patch-draft.ts` and `./submit.ts`, both
|
|
4
|
-
* of which ship unconditionally (`./index.ts`'s multi-tenant literal
|
|
5
|
-
* `import()`), so this file cannot be excluded either.
|
|
6
|
-
*
|
|
7
|
-
* The resume token's transport (#5036 D1).
|
|
8
|
-
*
|
|
9
|
-
* `start` hands the client an opaque token; `patch` and `submit` present it
|
|
10
|
-
* back. It travels in the `Authorization: Bearer` header rather than in the
|
|
11
|
-
* body or the query string for two reasons that matter on an anonymous
|
|
12
|
-
* surface: a query parameter lands in access logs, proxy logs and
|
|
13
|
-
* `Referer` headers, and a body field cannot be sent on a future GET. The
|
|
14
|
-
* header is where a credential belongs, and these routes sit in `leadsScope`
|
|
15
|
-
* where no auth preHandler runs, so nothing else in the stack reads it.
|
|
16
|
-
*/
|
|
17
|
-
import type { FastifyRequest } from "fastify";
|
|
18
|
-
|
|
19
|
-
const BEARER = /^Bearer (.+)$/;
|
|
20
|
-
|
|
21
|
-
/** The presented token, or null when the header is absent or malformed. */
|
|
22
|
-
export function resumeTokenFrom(request: FastifyRequest): string | null {
|
|
23
|
-
const header = request.headers.authorization;
|
|
24
|
-
if (typeof header !== "string") return null;
|
|
25
|
-
const match = BEARER.exec(header.trim());
|
|
26
|
-
return match ? match[1]! : null;
|
|
27
|
-
}
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NOT marked `oss: false` — see `./me.ts`'s header comment (D-48/#5366
|
|
3
|
-
* escalation: `./index.ts` is a mixed barrel multi-tenant's `server.ts`
|
|
4
|
-
* reaches by a literal, always-resolved `import()`; this file ships
|
|
5
|
-
* unconditionally, registration stays gated).
|
|
6
|
-
*
|
|
7
|
-
* POST /v1/project-listings
|
|
8
|
-
*
|
|
9
|
-
* `start` — step one of the application (#5036 D1). Anonymous, registered in
|
|
10
|
-
* the same rate-limited `leadsScope` as `/v1/leads/*`. Validates with
|
|
11
|
-
* `projectListingStartSchema` (the founder's name, email and consent, and
|
|
12
|
-
* optionally a requested window) and writes the `ProjectListing` row at
|
|
13
|
-
* `status: "draft"`. Returns 201 `{ ok: true, data: { id, resumeToken } }`.
|
|
14
|
-
*
|
|
15
|
-
* NO Lead is written here. The founder's contact details are already captured
|
|
16
|
-
* on the listing row itself at step one, so a Lead would add no capture — and
|
|
17
|
-
* `routeLead` sends an operator notification, which on an anonymous route
|
|
18
|
-
* means a stranger could mail the operator by opening a draft they never
|
|
19
|
-
* finish (PR #5042 security review, finding 4). `submit` writes the Lead.
|
|
20
|
-
*
|
|
21
|
-
* This route REPLACES the single anonymous create. The application became a
|
|
22
|
-
* ten-step form that saves on every advance, so the create had to split into
|
|
23
|
-
* start / patch / submit; validation moved with it, from write-time to
|
|
24
|
-
* submit-time. Nothing here asserts the application is complete, because at
|
|
25
|
-
* step one it never is.
|
|
26
|
-
*
|
|
27
|
-
* The response body is the ONLY place the resume token is ever readable: the
|
|
28
|
-
* server stores a digest of it. A client that loses it has lost the draft.
|
|
29
|
-
*
|
|
30
|
-
* NO `siteId` (#5048 D2). The payload names the site by its PUBLIC KEY and
|
|
31
|
-
* this route resolves the id through `siteIdForPublicKey` — a body-supplied
|
|
32
|
-
* tenant identifier is exactly what the consuming site's proxy route existed
|
|
33
|
-
* to prevent, and the resolution moving here is what lets that route go away.
|
|
34
|
-
* An unknown key is a 400; it never falls back to the deployment's own site.
|
|
35
|
-
*
|
|
36
|
-
* NO `slug`, and no site config values (#5033 D1/D2): the slug is derived from
|
|
37
|
-
* `name` at submit, and the site's window bounds are read server-side. The
|
|
38
|
-
* applicant may REQUEST a window; the server clamps it rather than rejecting
|
|
39
|
-
* the application, because a founder who could post their own bound could post
|
|
40
|
-
* an unbounded one.
|
|
41
|
-
*
|
|
42
|
-
* PII-safe logging (mirrors `routes/leads/index.ts`): routing metadata only —
|
|
43
|
-
* never founderEmail, founderName, or the resume token.
|
|
44
|
-
*
|
|
45
|
-
* SENDS THE RESUME EMAIL (#5406), fire-and-forget, after the row exists. The
|
|
46
|
-
* 201 below is never delayed or failed by it: `sendListingResumeLink` returns
|
|
47
|
-
* `void` and swallows everything, exactly as `sendListingDecisionEmail`'s
|
|
48
|
-
* `fileIssueOnApprove` does on the approve path. The email is OPT-IN — a site
|
|
49
|
-
* whose `SiteListingConfig` carries no `resumeEmail.fromAddress` gets none, so
|
|
50
|
-
* a site sending its own today does not start sending two — and the request
|
|
51
|
-
* and response shapes of this hard-floor route are unchanged.
|
|
52
|
-
*
|
|
53
|
-
* THE EMAIL IS ALSO THE ONLY CARRIER OF THE VERIFICATION NONCE (#5407).
|
|
54
|
-
* `startProjectListingDraft` mints it, stores only its digest, and returns it
|
|
55
|
-
* as a SIBLING of the result — never inside the value parsed below, because
|
|
56
|
-
* `projectListingStartResponseSchema` is this route's hard-floor contract and
|
|
57
|
-
* carries `id` and `resumeToken` and nothing else. The nonce reaches
|
|
58
|
-
* `sendListingResumeLink` from a local variable and goes nowhere else: not
|
|
59
|
-
* into the response, not into the log line above it. That is precisely what
|
|
60
|
-
* makes `email_verified_at` mean something the resume token alone could not
|
|
61
|
-
* prove — see `verifyProjectListingDraftEmail`.
|
|
62
|
-
*
|
|
63
|
-
* The response uses the API's own `{ ok, data }` / `{ ok, error }` envelope
|
|
64
|
-
* (`apps/api/CLAUDE.md`) on BOTH the success and the failure path — the
|
|
65
|
-
* anonymous create it replaces answered bare on success and enveloped on
|
|
66
|
-
* failure, so a client branching on `body.ok` read a success as a failure.
|
|
67
|
-
*/
|
|
68
|
-
import {
|
|
69
|
-
projectListingStartResponseSchema,
|
|
70
|
-
projectListingStartSchema,
|
|
71
|
-
} from "@working-theory/validation";
|
|
72
|
-
import type { FastifyPluginAsync } from "fastify";
|
|
73
|
-
|
|
74
|
-
import { siteIdForPublicKey } from "../../services/listing-config.js";
|
|
75
|
-
import { sendListingResumeLink } from "../../services/project-listing-resume-email.js";
|
|
76
|
-
import { startProjectListingDraft } from "../../services/project-listings.js";
|
|
77
|
-
|
|
78
|
-
export const startProjectListingRoute: FastifyPluginAsync = async (fastify) => {
|
|
79
|
-
fastify.post("/v1/project-listings", async (request, reply) => {
|
|
80
|
-
const parsed = projectListingStartSchema.safeParse(request.body);
|
|
81
|
-
if (!parsed.success) {
|
|
82
|
-
return reply.status(400).send({
|
|
83
|
-
ok: false,
|
|
84
|
-
error: {
|
|
85
|
-
code: "invalid_input",
|
|
86
|
-
message: parsed.error.issues[0]?.message ?? "invalid input",
|
|
87
|
-
},
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// #5048 D2 — the site is resolved SERVER-SIDE from the payload's public
|
|
92
|
-
// key. The body never carries a `siteId`, and `projectListingStartSchema`
|
|
93
|
-
// is `.strict()`, so a body that still does is the 400 above naming the
|
|
94
|
-
// unrecognised key rather than a silently-honoured tenant override. This
|
|
95
|
-
// is the one property the consuming site's proxy route existed to
|
|
96
|
-
// guarantee, and resolving it here is what lets that route go away.
|
|
97
|
-
//
|
|
98
|
-
// An unknown key is a 400 and never falls back to the deployment's own
|
|
99
|
-
// site: a typo would otherwise file one site's applications against
|
|
100
|
-
// another with nothing to notice it.
|
|
101
|
-
const siteId = await siteIdForPublicKey(parsed.data.siteKey);
|
|
102
|
-
if (siteId === null) {
|
|
103
|
-
return reply.status(400).send({
|
|
104
|
-
ok: false,
|
|
105
|
-
error: { code: "unknown_site_key", message: "Unknown site key" },
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const result = await startProjectListingDraft(siteId, parsed.data);
|
|
110
|
-
// #5275 — a `consents` map declining a REQUIRED purpose contradicts the
|
|
111
|
-
// body's own `consent: true`; no row was created.
|
|
112
|
-
if (!result.ok) {
|
|
113
|
-
// #6203 — `tenant_unresolved` is a server-side tenancy
|
|
114
|
-
// misconfiguration (the resolved `siteId` names no workspace this
|
|
115
|
-
// database holds), not a client input problem — 503, never 400.
|
|
116
|
-
const status = result.error.kind === "tenant_unresolved" ? 503 : 400;
|
|
117
|
-
return reply.status(status).send({
|
|
118
|
-
ok: false,
|
|
119
|
-
error: { code: result.error.kind, message: result.error.message },
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
request.log.info(
|
|
124
|
-
{ siteId, listingId: result.id, ip: request.ip },
|
|
125
|
-
"project listing draft started",
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
// #5406 — the founder's way back to this draft. NOT awaited: the 201 must
|
|
129
|
-
// not wait on Resend, and must not fail if Resend does. Both credentials
|
|
130
|
-
// are handed over here and neither is ever logged — they ride the link
|
|
131
|
-
// inside the email, and the #5407 nonce rides NOTHING ELSE anywhere.
|
|
132
|
-
sendListingResumeLink(result.id, result.resumeToken, result.verifyNonce);
|
|
133
|
-
|
|
134
|
-
const body = projectListingStartResponseSchema.parse({
|
|
135
|
-
id: result.id,
|
|
136
|
-
resumeToken: result.resumeToken,
|
|
137
|
-
});
|
|
138
|
-
return reply.status(201).send({ ok: true, data: body });
|
|
139
|
-
});
|
|
140
|
-
};
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NOT marked `oss: false` — see `./me.ts`'s header comment (D-48/#5366
|
|
3
|
-
* escalation: `./index.ts` is a mixed barrel multi-tenant's `server.ts`
|
|
4
|
-
* reaches by a literal, always-resolved `import()`; this file ships
|
|
5
|
-
* unconditionally, registration stays gated).
|
|
6
|
-
*
|
|
7
|
-
* POST /v1/project-listings/drafts/:id/submit
|
|
8
|
-
*
|
|
9
|
-
* `submit` — the moment an application stops being a draft (#5036 D1).
|
|
10
|
-
* Anonymous, token-gated, registered in the rate-limited `leadsScope`.
|
|
11
|
-
*
|
|
12
|
-
* It carries NO body. Everything it validates is already on the row, put
|
|
13
|
-
* there by ten steps of `patch`, and the server assembles and validates it
|
|
14
|
-
* against `projectListingSubmitSchema`. An incomplete draft comes back `422`
|
|
15
|
-
* with the offending fields NAMED, so the form can send the founder back to
|
|
16
|
-
* the step that owns each one rather than saying "something is missing".
|
|
17
|
-
*
|
|
18
|
-
* Three server-side rules bind here and only here: completeness; the
|
|
19
|
-
* concurrent-listing limit (`409 listing_limit_reached` — at most two
|
|
20
|
-
* non-terminal listings per founder, drafts included, #5036 D7); and the
|
|
21
|
-
* slug, derived from `name` at last (#5033 D2) and returned in the response,
|
|
22
|
-
* which is the only place a caller learns it.
|
|
23
|
-
*
|
|
24
|
-
* The Lead write also happens here rather than at step one, via `routeLead` —
|
|
25
|
-
* so an anonymous stranger cannot cause an operator notification by opening a
|
|
26
|
-
* draft they never finish (PR #5042 security review, finding 4). The Slack
|
|
27
|
-
* notification fires AFTER the transition, never before, and never fails a
|
|
28
|
-
* submit that already succeeded (`postListingApplication` never throws). Both
|
|
29
|
-
* moved here from the old create route because this — not step one — is the
|
|
30
|
-
* moment an operator has an application worth looking at.
|
|
31
|
-
*
|
|
32
|
-
* Every response uses the API's own `{ ok, data }` / `{ ok, error }` envelope
|
|
33
|
-
* (`apps/api/CLAUDE.md`), success included.
|
|
34
|
-
*/
|
|
35
|
-
import { zProjectListingId } from "@working-theory/ids";
|
|
36
|
-
import { projectListingSubmitResponseSchema } from "@working-theory/validation";
|
|
37
|
-
import type { FastifyPluginAsync } from "fastify";
|
|
38
|
-
import { z } from "zod";
|
|
39
|
-
|
|
40
|
-
import { submitProjectListingDraft } from "../../services/project-listings.js";
|
|
41
|
-
import { postListingApplication } from "../../services/slack-notify.js";
|
|
42
|
-
|
|
43
|
-
import { resumeTokenFrom } from "./resume-token.js";
|
|
44
|
-
|
|
45
|
-
const paramsSchema = z.object({ id: zProjectListingId });
|
|
46
|
-
|
|
47
|
-
export const submitProjectListingRoute: FastifyPluginAsync = async (fastify) => {
|
|
48
|
-
fastify.post("/v1/project-listings/drafts/:id/submit", async (request, reply) => {
|
|
49
|
-
const parsedParams = paramsSchema.safeParse(request.params);
|
|
50
|
-
if (!parsedParams.success) {
|
|
51
|
-
return reply.status(400).send({
|
|
52
|
-
ok: false,
|
|
53
|
-
error: { code: "validation_failed", message: "Invalid project listing id" },
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const resumeToken = resumeTokenFrom(request);
|
|
58
|
-
if (resumeToken === null) {
|
|
59
|
-
return reply.status(401).send({
|
|
60
|
-
ok: false,
|
|
61
|
-
error: { code: "unauthorized", message: "A resume token is required" },
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const sourceMeta = {
|
|
66
|
-
ip: request.ip,
|
|
67
|
-
userAgent: request.headers["user-agent"],
|
|
68
|
-
receivedAt: new Date().toISOString(),
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const result = await submitProjectListingDraft(
|
|
72
|
-
parsedParams.data.id,
|
|
73
|
-
resumeToken,
|
|
74
|
-
sourceMeta,
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
if (!result.ok) {
|
|
78
|
-
if (result.error.kind === "not_found") {
|
|
79
|
-
return reply.status(404).send({
|
|
80
|
-
ok: false,
|
|
81
|
-
error: { code: "not_found", message: "Draft not found" },
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
if (result.error.kind === "incomplete") {
|
|
85
|
-
return reply.status(422).send({
|
|
86
|
-
ok: false,
|
|
87
|
-
error: {
|
|
88
|
-
code: "incomplete_application",
|
|
89
|
-
message: result.error.message,
|
|
90
|
-
missingFields: result.error.missingFields,
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
if (result.error.kind === "listing_limit_reached") {
|
|
95
|
-
return reply.status(409).send({
|
|
96
|
-
ok: false,
|
|
97
|
-
error: { code: "listing_limit_reached", message: result.error.message },
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
return reply.status(409).send({
|
|
101
|
-
ok: false,
|
|
102
|
-
error: { code: "slug_taken", message: result.error.message },
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
request.log.info(
|
|
107
|
-
{ siteId: result.listing.siteId, slug: result.slug, ip: request.ip },
|
|
108
|
-
"project listing applied",
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
// Awaited (not fire-and-forget) so it can't be killed mid-flight by a
|
|
112
|
-
// serverless runtime tearing down after the response.
|
|
113
|
-
await postListingApplication({
|
|
114
|
-
id: result.listing.id,
|
|
115
|
-
name: result.listing.name,
|
|
116
|
-
oneLiner: result.listing.oneLiner,
|
|
117
|
-
problem: result.listing.problem,
|
|
118
|
-
url: result.listing.url,
|
|
119
|
-
product: result.listing.product,
|
|
120
|
-
founderName: result.listing.founderName,
|
|
121
|
-
founderCompany: result.listing.founderCompany,
|
|
122
|
-
// #7522 D5 — the actions block names the listing's workspace; `siteId`
|
|
123
|
-
// IS the workspace id (the `project_listings_workspace_site_agree` CHECK).
|
|
124
|
-
workspaceId: result.listing.siteId,
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
const body = projectListingSubmitResponseSchema.parse({ id: result.id, slug: result.slug });
|
|
128
|
-
return reply.status(200).send({ ok: true, data: body });
|
|
129
|
-
});
|
|
130
|
-
};
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* POST /v1/project-listings/drafts/:id/verify-email
|
|
3
|
-
*
|
|
4
|
-
* Records that the founder proved control of `founderEmail`, by opening the
|
|
5
|
-
* resume link emailed at step one (#5407). Anonymous, credential-gated,
|
|
6
|
-
* registered in the same rate-limited `leadsScope` as the other three
|
|
7
|
-
* anonymous listing routes.
|
|
8
|
-
*
|
|
9
|
-
* WHY THE ROUTE EXISTS. The resume email (#5406) tells the founder to "verify
|
|
10
|
-
* your email and continue", and until this route nothing made that sentence
|
|
11
|
-
* true: `ListingApplicationFlow` rehydrated from `?draft=&token=` entirely in
|
|
12
|
-
* the browser and touched the server only on the NEXT advance — so a founder
|
|
13
|
-
* who opened the link and closed the tab left no record, and the operator had
|
|
14
|
-
* no way to tell a proved address from a typed one at review (samhenry spec
|
|
15
|
-
* 316 D6b).
|
|
16
|
-
*
|
|
17
|
-
* WHAT IT PROVES. That somebody who can read mail at `founderEmail` opened
|
|
18
|
-
* this link — evidence about the ADDRESS, not about who is driving the draft
|
|
19
|
-
* (`verifyProjectListingDraftEmail` carries the full note). It takes
|
|
20
|
-
* TWO credentials and only the second carries that: the `Authorization:
|
|
21
|
-
* Bearer` resume token says "you opened this draft" (it is in `start`'s own
|
|
22
|
-
* 201 body, so anyone who posted the form has it), and the `verifyNonce` in
|
|
23
|
-
* the body says "you read the email" — it is minted at `start`, digested onto
|
|
24
|
-
* the row, and handed to nothing but the resume-email builder, reaching the
|
|
25
|
-
* outside world only as the `?verify=` parameter inside the message. Keying
|
|
26
|
-
* this route on the resume token ALONE was the first design and was
|
|
27
|
-
* forgeable in two HTTP calls; `verifyProjectListingDraftEmail` carries the
|
|
28
|
-
* full note.
|
|
29
|
-
*
|
|
30
|
-
* It still says nothing about WHO the founder is. `ownerUserId` (#5295) is
|
|
31
|
-
* the column that carries an identity claim.
|
|
32
|
-
*
|
|
33
|
-
* A SITE THAT SENDS NO RESUME EMAIL CAN NEVER VERIFY, and that is correct:
|
|
34
|
-
* the nonce is minted on every draft but delivered only by the email, so
|
|
35
|
-
* where `SiteListingConfig` carries no `resumeEmail` the column stays null
|
|
36
|
-
* forever — nobody has read anything at that address.
|
|
37
|
-
*
|
|
38
|
-
* IDEMPOTENT, AND WRITE-ONCE. The service stamps by compare-and-swap on
|
|
39
|
-
* `emailVerifiedAt: null`, so a founder who opens the link five times is
|
|
40
|
-
* stamped once and every call answers with that FIRST stamp.
|
|
41
|
-
*
|
|
42
|
-
* THE ERROR SURFACE IS `patch`'s AND `submit`'s, deliberately unchanged: a
|
|
43
|
-
* malformed id or a body that is not `{ verifyNonce }` is `400
|
|
44
|
-
* validation_failed`, a missing or malformed `Authorization: Bearer` is `401
|
|
45
|
-
* unauthorized`, and a WRONG token, a WRONG nonce, a credential bound to
|
|
46
|
-
* another listing, an unknown id and an already-submitted row are all one
|
|
47
|
-
* `404 not_found`. The 400s describe the REQUEST's shape and reveal nothing
|
|
48
|
-
* about any row; everything that depends on a row is the single 404. An
|
|
49
|
-
* anonymous surface must not distinguish "exists but not yours" from "does
|
|
50
|
-
* not exist", and this route least of all — a distinguishable answer would
|
|
51
|
-
* make it both an oracle for which drafts exist and a guessing channel for
|
|
52
|
-
* the nonce.
|
|
53
|
-
*
|
|
54
|
-
* THE NONCE TRAVELS IN THE BODY, not the query string, for the reason
|
|
55
|
-
* `resume-token.ts` gives about the resume token: a query parameter lands in
|
|
56
|
-
* access logs, proxy logs and `Referer` headers. The claim is about THIS hop
|
|
57
|
-
* only, and it is worth being exact about that — the nonce's DELIVERY hop is
|
|
58
|
-
* itself a query parameter on the consuming site's URL, so it lands in that
|
|
59
|
-
* site's own access logs exactly as the resume token does. What the body
|
|
60
|
-
* placement buys is that the API's logs and any proxy between the browser and
|
|
61
|
-
* this server never see it. The resume token keeps its `Authorization:
|
|
62
|
-
* Bearer` header. Neither credential is ever logged and neither is ever
|
|
63
|
-
* returned.
|
|
64
|
-
*/
|
|
65
|
-
import { zProjectListingId } from "@working-theory/ids";
|
|
66
|
-
import { projectListingVerifyEmailResponseSchema } from "@working-theory/validation";
|
|
67
|
-
import type { FastifyPluginAsync } from "fastify";
|
|
68
|
-
import { z } from "zod";
|
|
69
|
-
|
|
70
|
-
import { verifyProjectListingDraftEmail } from "../../services/project-listings.js";
|
|
71
|
-
|
|
72
|
-
import { sendValidated } from "./respond.js";
|
|
73
|
-
import { resumeTokenFrom } from "./resume-token.js";
|
|
74
|
-
|
|
75
|
-
const paramsSchema = z.object({ id: zProjectListingId });
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* The nonce, as the emailed link carried it. Bounded so an oversized body is
|
|
79
|
-
* refused before any digest is computed; the value is 256 bits of CSPRNG
|
|
80
|
-
* output in base64url, which is 43 characters.
|
|
81
|
-
*/
|
|
82
|
-
const bodySchema = z.object({ verifyNonce: z.string().min(1).max(256) }).strict();
|
|
83
|
-
|
|
84
|
-
export const verifyProjectListingDraftEmailRoute: FastifyPluginAsync = async (
|
|
85
|
-
fastify,
|
|
86
|
-
) => {
|
|
87
|
-
fastify.post(
|
|
88
|
-
"/v1/project-listings/drafts/:id/verify-email",
|
|
89
|
-
async (request, reply) => {
|
|
90
|
-
const parsedParams = paramsSchema.safeParse(request.params);
|
|
91
|
-
if (!parsedParams.success) {
|
|
92
|
-
return reply.status(400).send({
|
|
93
|
-
ok: false,
|
|
94
|
-
error: { code: "validation_failed", message: "Invalid project listing id" },
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const resumeToken = resumeTokenFrom(request);
|
|
99
|
-
if (resumeToken === null) {
|
|
100
|
-
return reply.status(401).send({
|
|
101
|
-
ok: false,
|
|
102
|
-
error: { code: "unauthorized", message: "A resume token is required" },
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// The message names the FIELD, never whether the value would have
|
|
107
|
-
// matched — that distinction is the 404's, below.
|
|
108
|
-
const parsedBody = bodySchema.safeParse(request.body);
|
|
109
|
-
if (!parsedBody.success) {
|
|
110
|
-
return reply.status(400).send({
|
|
111
|
-
ok: false,
|
|
112
|
-
error: {
|
|
113
|
-
code: "validation_failed",
|
|
114
|
-
message: "A verification nonce is required",
|
|
115
|
-
},
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const result = await verifyProjectListingDraftEmail(
|
|
120
|
-
parsedParams.data.id,
|
|
121
|
-
resumeToken,
|
|
122
|
-
parsedBody.data.verifyNonce,
|
|
123
|
-
);
|
|
124
|
-
if (!result.ok) {
|
|
125
|
-
return reply.status(404).send({
|
|
126
|
-
ok: false,
|
|
127
|
-
error: { code: "not_found", message: "Draft not found" },
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Routing metadata only, the same PII boundary `start` documents: the
|
|
132
|
-
// listing id and the caller's ip, never the founder's email and never
|
|
133
|
-
// the token.
|
|
134
|
-
request.log.info(
|
|
135
|
-
{ listingId: result.value.id, ip: request.ip },
|
|
136
|
-
"project listing draft email verified",
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
return sendValidated(
|
|
140
|
-
reply,
|
|
141
|
-
projectListingVerifyEmailResponseSchema,
|
|
142
|
-
{
|
|
143
|
-
id: result.value.id,
|
|
144
|
-
emailVerifiedAt: result.value.emailVerifiedAt.toISOString(),
|
|
145
|
-
},
|
|
146
|
-
"verify-email",
|
|
147
|
-
);
|
|
148
|
-
},
|
|
149
|
-
);
|
|
150
|
-
};
|