create-ailk 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -22
- package/component-catalog.json +3 -3
- package/component-catalog.md +3 -3
- package/dist/add-module.js +4 -2
- package/dist/add-route.d.ts +12 -1
- package/dist/add-route.js +31 -3
- package/dist/cli.js +12 -7
- package/dist/derive-architecture.d.ts +3 -4
- package/dist/derive-architecture.js +65 -29
- package/dist/lib/design-checks.d.ts +9 -4
- package/dist/lib/design-checks.js +13 -7
- package/dist/lib/extract-module-bundle.js +29 -2
- package/dist/lib/fetch-template-snapshot.d.ts +2 -1
- package/dist/lib/fetch-template-snapshot.js +8 -3
- package/dist/lib/import-graph.d.ts +1 -1
- package/dist/lib/import-graph.js +4 -4
- package/dist/lib/module-license-gate.d.ts +12 -1
- package/dist/lib/module-license-gate.js +36 -15
- package/dist/lib/module-tier.d.ts +2 -8
- package/dist/lib/module-tier.js +13 -12
- package/dist/module-architecture.d.ts +20 -3
- package/dist/module-architecture.js +129 -552
- package/dist/paid-sections.d.ts +25 -1
- package/dist/paid-sections.js +88 -3
- package/dist/starter-grid.d.ts +6 -6
- package/dist/starter-grid.js +12 -5
- package/dist/surfaces.js +39 -2
- package/dist/template-record.d.ts +21 -3
- package/dist/template-record.js +38 -7
- package/dist/topology.d.ts +16 -3
- package/dist/topology.js +54 -23
- package/dist/update.d.ts +10 -1
- package/dist/update.js +715 -21
- package/package.json +3 -3
- package/release-scope.json +2 -2
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +8 -2
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +2 -2
- package/site-templates/marketing-site/ailk-template.json +7 -1
- package/site-templates/marketing-site/provenance.json +7 -1
- package/site-templates/saas-product/ailk-template.json +2 -1
- package/site-templates/saas-product/provenance.json +1 -0
- package/templates/.claude/cloudparity-repo-hooks.json +5 -0
- package/templates/.claude/hooks/template-guard.sh +137 -0
- package/templates/.claude/rules/extending.md +106 -0
- package/templates/.claude/rules/loop-contract.md +2 -2
- package/templates/.claude/settings.json +11 -0
- package/templates/.env.example +20 -5
- package/templates/.github/workflows/ci.yml +7 -4
- package/templates/CLAUDE.md +11 -1
- package/templates/CONVENTIONS.md +8 -10
- package/templates/README.md +14 -13
- package/templates/apps/api/.env.example +23 -2
- package/templates/apps/api/CLAUDE.md +119 -83
- package/templates/apps/api/jest.config.cjs +15 -5
- package/templates/apps/api/package.json +16 -20
- package/templates/apps/api/src/__tests__/trusted-origins.server.test.ts +2 -2
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +4 -4
- package/templates/apps/api/src/index.ts +1 -1
- package/templates/apps/api/src/lib/__mocks__/prisma-client.js +16 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -3
- package/templates/apps/api/src/modules/commerce.ts +10 -0
- package/templates/apps/api/src/modules/content-write.ts +10 -0
- package/templates/apps/api/src/modules/multi-tenant.ts +10 -0
- package/templates/apps/api/src/modules/scheduling.ts +10 -0
- package/templates/apps/api/src/modules/signal-collectors.ts +10 -0
- package/templates/apps/api/src/modules/workspace.ts +10 -0
- package/templates/apps/api/src/server.ts +117 -1882
- package/templates/apps/api/vercel.json +2 -2
- package/templates/apps/mcp/CLAUDE.md +90 -26
- package/templates/apps/mcp/__tests__/app-entry.test.ts +4 -254
- package/templates/apps/mcp/__tests__/boot-file.test.ts +92 -0
- package/templates/apps/mcp/jest.config.cjs +6 -1
- package/templates/apps/mcp/package.json +5 -13
- package/templates/apps/mcp/src/app.ts +8 -7
- package/templates/apps/mcp/src/index.ts +6 -34
- package/templates/apps/mcp/src/lib/paid-tools.generated.ts +10 -0
- package/templates/apps/mcp/src/lib/topology.generated.ts +2 -2
- package/templates/apps/mcp/src/site.ts +12 -0
- package/templates/apps/web/.env.example +1 -1
- package/templates/apps/web/CLAUDE.md +31 -3
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +9 -3
- package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +43 -4
- package/templates/apps/web/app/[locale]/(auth)/legal-footnote.tsx +35 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/page.test.tsx +117 -8
- package/templates/apps/web/app/[locale]/(auth)/sign-in/page.tsx +14 -37
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +6 -6
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-text.ts +20 -35
- package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/page.test.tsx +108 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/sign-up-card.test.tsx +111 -5
- package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +78 -16
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +132 -12
- package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +53 -1
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/billing.test.tsx +38 -0
- package/templates/apps/web/app/[locale]/(authed)/account/billing/page.tsx +4 -2
- package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +47 -24
- package/templates/apps/web/app/[locale]/(authed)/signed-out-gate.generated.tsx +23 -4
- package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +1 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +22 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +7 -3
- package/templates/apps/web/app/[locale]/layout.tsx +33 -13
- package/templates/apps/web/app/[locale]/meet/confirmed/BookingHandOff.tsx +128 -0
- package/templates/apps/web/app/[locale]/meet/confirmed/__tests__/BookingHandOff.test.tsx +126 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +43 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +7 -3
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/__tests__/route.test.ts +222 -0
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +10 -3
- package/templates/apps/web/app/api/revalidate/route.ts +3 -3
- package/templates/apps/web/auth.ts +10 -6
- package/templates/apps/web/i18n/request.ts +49 -1
- package/templates/apps/web/jest.config.cjs +1 -0
- package/templates/apps/web/lib/__tests__/account-config.test.ts +7 -0
- package/templates/apps/web/lib/__tests__/auth-email.test.ts +276 -0
- package/templates/apps/web/lib/auth-email.ts +103 -21
- package/templates/apps/web/lib/hub-session/__tests__/sign-in-gate.test.tsx +72 -23
- package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +94 -70
- package/templates/apps/web/lib/prisma-for-auth.ts +5 -2
- package/templates/apps/web/lib/relay.ts +2 -2
- package/templates/apps/web/lib/release-scope.ts +47 -0
- package/templates/apps/web/lib/server-api.ts +1 -1
- package/templates/apps/web/lib/sign-up-agreements.ts +291 -28
- package/templates/apps/web/lib/site-contributions.ts +18 -0
- package/templates/apps/web/messages/en.json +186 -20
- package/templates/apps/web/messages/site/en.json +1 -0
- package/templates/apps/web/src/lib/commerce-claim.ts +1 -1
- package/templates/customizations.yaml +37 -0
- package/templates/database/prisma.config.ts +10 -14
- package/templates/database/seed-test.ts +2 -2
- package/templates/database/seed.ts +5 -5
- package/templates/package.json +12 -6
- package/templates/pnpm-workspace.yaml +0 -1
- package/templates/project.yaml +16 -8
- package/templates/tools/jest/reporters.cjs +65 -0
- package/templates/tools/lint-architecture.ts +553 -7
- package/templates/tsconfig.json +1 -2
- package/versions.json +49 -36
- package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +0 -409
- package/templates/apps/api/src/__tests__/factories/agent-caller.ts +0 -102
- package/templates/apps/api/src/__tests__/factories/content-fixture.ts +0 -138
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +0 -441
- package/templates/apps/api/src/__tests__/factories/index.ts +0 -70
- package/templates/apps/api/src/__tests__/factories/lead-fixture.ts +0 -82
- package/templates/apps/api/src/__tests__/leads-forwarded-rate-limit.test.ts +0 -135
- package/templates/apps/api/src/__tests__/services/enrichment-shim.test.ts +0 -126
- package/templates/apps/api/src/__tests__/stripe-boot.test.ts +0 -132
- package/templates/apps/api/src/__tests__/support/hub-sign-in-fake-db.ts +0 -362
- package/templates/apps/api/src/bin/__tests__/rate-limit-probe.test.ts +0 -184
- package/templates/apps/api/src/bin/abandon-sweep.ts +0 -42
- package/templates/apps/api/src/bin/rate-limit-probe.ts +0 -53
- package/templates/apps/api/src/bin/spokes.ts +0 -198
- package/templates/apps/api/src/bin/stripe-connections-rekey.ts +0 -45
- package/templates/apps/api/src/config/__tests__/hub-require-spoke-key.test.ts +0 -196
- package/templates/apps/api/src/config/__tests__/index.test.ts +0 -233
- package/templates/apps/api/src/config/__tests__/modules.test.ts +0 -76
- package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +0 -185
- package/templates/apps/api/src/config/__tests__/trusted-origins.test.ts +0 -238
- package/templates/apps/api/src/config/index.ts +0 -596
- package/templates/apps/api/src/config/modules.ts +0 -83
- package/templates/apps/api/src/lib/__tests__/agent-store.test.ts +0 -581
- package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +0 -143
- package/templates/apps/api/src/lib/__tests__/consent-ledger-double.ts +0 -237
- package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +0 -353
- package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +0 -201
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +0 -224
- package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +0 -120
- package/templates/apps/api/src/lib/__tests__/errors.test.ts +0 -94
- package/templates/apps/api/src/lib/__tests__/file-fallback.test.ts +0 -144
- package/templates/apps/api/src/lib/__tests__/fixtures/HomePage/home.mdx +0 -8
- package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +0 -149
- package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +0 -116
- package/templates/apps/api/src/lib/__tests__/hub-sign-in-store.test.ts +0 -209
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +0 -257
- package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +0 -96
- package/templates/apps/api/src/lib/__tests__/outbound-http.test.ts +0 -125
- package/templates/apps/api/src/lib/__tests__/prisma.test.ts +0 -165
- package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +0 -317
- package/templates/apps/api/src/lib/__tests__/require-paid-tier.test.ts +0 -129
- package/templates/apps/api/src/lib/__tests__/site.test.ts +0 -308
- package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +0 -89
- package/templates/apps/api/src/lib/__tests__/spoke-store.test.ts +0 -202
- package/templates/apps/api/src/lib/__tests__/stripe-admission.test.ts +0 -217
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -82
- package/templates/apps/api/src/lib/agent-store.ts +0 -613
- package/templates/apps/api/src/lib/auth-email-budget.ts +0 -161
- package/templates/apps/api/src/lib/consent-store.ts +0 -363
- package/templates/apps/api/src/lib/content-adapter.ts +0 -112
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +0 -96
- package/templates/apps/api/src/lib/error-serializer.ts +0 -164
- package/templates/apps/api/src/lib/errors.ts +0 -95
- package/templates/apps/api/src/lib/file-fallback.ts +0 -87
- package/templates/apps/api/src/lib/flow-completion-store.ts +0 -136
- package/templates/apps/api/src/lib/hub-sign-in-store.ts +0 -455
- package/templates/apps/api/src/lib/lead-capture-store.ts +0 -122
- package/templates/apps/api/src/lib/lead-delivery.ts +0 -52
- package/templates/apps/api/src/lib/license-key-store.ts +0 -45
- package/templates/apps/api/src/lib/locales.ts +0 -201
- package/templates/apps/api/src/lib/outbound-http.ts +0 -127
- package/templates/apps/api/src/lib/prisma.ts +0 -21
- package/templates/apps/api/src/lib/proposal-paid-store.ts +0 -66
- package/templates/apps/api/src/lib/rate-limit-probe.ts +0 -165
- package/templates/apps/api/src/lib/rate-limit-store.ts +0 -142
- package/templates/apps/api/src/lib/require-commerce-claim.ts +0 -250
- package/templates/apps/api/src/lib/require-paid-tier.ts +0 -53
- package/templates/apps/api/src/lib/site.ts +0 -257
- package/templates/apps/api/src/lib/slice-load.ts +0 -90
- package/templates/apps/api/src/lib/spoke-store.ts +0 -180
- package/templates/apps/api/src/lib/stripe.ts +0 -321
- package/templates/apps/api/src/lib/webhook-idempotency.ts +0 -156
- package/templates/apps/api/src/middleware/__tests__/adversarial/bearer-token-format.test.ts +0 -111
- package/templates/apps/api/src/middleware/__tests__/adversarial/cookie-or-bearer-precedence.test.ts +0 -172
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-bearer-skip.test.ts +0 -119
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-cookie-path.test.ts +0 -138
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-trusted-origins.test.ts +0 -142
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +0 -155
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-timeout.test.ts +0 -139
- package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +0 -562
- package/templates/apps/api/src/middleware/__tests__/auth.both-present-precedence.test.ts +0 -76
- package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +0 -220
- package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +0 -97
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +0 -557
- package/templates/apps/api/src/middleware/__tests__/auth.neither-present.test.ts +0 -72
- package/templates/apps/api/src/middleware/__tests__/auth.optional-cookie.test.ts +0 -140
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +0 -721
- package/templates/apps/api/src/middleware/__tests__/rate-limit-cross-isolate.db.test.ts +0 -220
- package/templates/apps/api/src/middleware/__tests__/rate-limit-store.test.ts +0 -430
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +0 -1409
- package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +0 -174
- package/templates/apps/api/src/middleware/auth.ts +0 -864
- package/templates/apps/api/src/middleware/default-tenant.ts +0 -166
- package/templates/apps/api/src/middleware/rate-limit.ts +0 -983
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +0 -391
- package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +0 -182
- package/templates/apps/api/src/openapi/index.ts +0 -18
- package/templates/apps/api/src/openapi/spec.ts +0 -2148
- package/templates/apps/api/src/openapi/surface-drift.ts +0 -195
- package/templates/apps/api/src/routes/aeo/__tests__/index.test.ts +0 -175
- package/templates/apps/api/src/routes/aeo/index.ts +0 -107
- package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +0 -196
- package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +0 -249
- package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +0 -532
- package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +0 -313
- package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +0 -413
- package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +0 -609
- package/templates/apps/api/src/routes/auth/hub-sign-in.ts +0 -242
- package/templates/apps/api/src/routes/auth/tokens.ts +0 -211
- package/templates/apps/api/src/routes/auth/transactional-email.ts +0 -301
- package/templates/apps/api/src/routes/auth/user.ts +0 -305
- package/templates/apps/api/src/routes/billing/plan-checkout.ts +0 -332
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +0 -178
- package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +0 -1426
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +0 -448
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -1742
- package/templates/apps/api/src/routes/checkout/index.ts +0 -34
- package/templates/apps/api/src/routes/checkout/session.ts +0 -171
- package/templates/apps/api/src/routes/checkout/sessions.ts +0 -540
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +0 -386
- package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +0 -160
- package/templates/apps/api/src/routes/consent/account.ts +0 -268
- package/templates/apps/api/src/routes/consent/cookie.ts +0 -90
- package/templates/apps/api/src/routes/consent/index.ts +0 -26
- package/templates/apps/api/src/routes/consent/unsubscribe.ts +0 -127
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +0 -853
- package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +0 -230
- package/templates/apps/api/src/routes/content/__tests__/list-types.test.ts +0 -44
- package/templates/apps/api/src/routes/content/__tests__/locale.test.ts +0 -118
- package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +0 -731
- package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +0 -753
- package/templates/apps/api/src/routes/content/__tests__/rate-limit.test.ts +0 -51
- package/templates/apps/api/src/routes/content/__tests__/search.test.ts +0 -450
- package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +0 -762
- package/templates/apps/api/src/routes/content/get-page.ts +0 -183
- package/templates/apps/api/src/routes/content/index.ts +0 -49
- package/templates/apps/api/src/routes/content/list-pages.ts +0 -74
- package/templates/apps/api/src/routes/content/list-types.ts +0 -19
- package/templates/apps/api/src/routes/content/offerings.ts +0 -184
- package/templates/apps/api/src/routes/content/pricing.ts +0 -176
- package/templates/apps/api/src/routes/content/rate-limit.ts +0 -22
- package/templates/apps/api/src/routes/content/search.ts +0 -100
- package/templates/apps/api/src/routes/content/shown-offerings.ts +0 -229
- package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +0 -88
- package/templates/apps/api/src/routes/leads/__tests__/list.test.ts +0 -171
- package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +0 -515
- package/templates/apps/api/src/routes/leads/__tests__/spoke.test.ts +0 -392
- package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +0 -165
- package/templates/apps/api/src/routes/leads/index.ts +0 -639
- package/templates/apps/api/src/routes/leads/list.ts +0 -104
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +0 -626
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +0 -1268
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +0 -847
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +0 -491
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +0 -476
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +0 -872
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +0 -1022
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +0 -484
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +0 -535
- package/templates/apps/api/src/routes/project-listings/index.ts +0 -164
- package/templates/apps/api/src/routes/project-listings/me.ts +0 -87
- package/templates/apps/api/src/routes/project-listings/patch-copy.ts +0 -224
- package/templates/apps/api/src/routes/project-listings/patch-draft.ts +0 -102
- package/templates/apps/api/src/routes/project-listings/public.ts +0 -98
- package/templates/apps/api/src/routes/project-listings/respond.ts +0 -48
- package/templates/apps/api/src/routes/project-listings/resume-token.ts +0 -27
- package/templates/apps/api/src/routes/project-listings/start.ts +0 -140
- package/templates/apps/api/src/routes/project-listings/submit.ts +0 -130
- package/templates/apps/api/src/routes/project-listings/verify-email.ts +0 -150
- package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +0 -686
- package/templates/apps/api/src/routes/requests/confirm.ts +0 -601
- package/templates/apps/api/src/routes/requests/index.ts +0 -54
- package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +0 -306
- package/templates/apps/api/src/routes/test/auth/session.ts +0 -143
- package/templates/apps/api/src/routes/webhooks/README.md +0 -398
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +0 -649
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +0 -749
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -449
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -264
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode-live.test.ts +0 -159
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode.test.ts +0 -158
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +0 -814
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -301
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-proposal-paid.test.ts +0 -369
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -215
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +0 -301
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +0 -809
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -1155
- package/templates/apps/api/src/routes/webhooks/index.ts +0 -141
- package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +0 -384
- package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -2028
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +0 -645
- package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +0 -132
- package/templates/apps/api/src/services/__tests__/audit-log.test.ts +0 -1028
- package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +0 -313
- package/templates/apps/api/src/services/__tests__/consent-ledger.test.ts +0 -503
- package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +0 -65
- package/templates/apps/api/src/services/__tests__/consent.test.ts +0 -299
- package/templates/apps/api/src/services/__tests__/default-tenant.test.ts +0 -147
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +0 -877
- package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +0 -379
- package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +0 -368
- package/templates/apps/api/src/services/__tests__/entitlements.test.ts +0 -142
- package/templates/apps/api/src/services/__tests__/hub-sign-in.test.ts +0 -854
- package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +0 -234
- package/templates/apps/api/src/services/__tests__/lead-accept-user.test.ts +0 -251
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +0 -2660
- package/templates/apps/api/src/services/__tests__/listing-config.test.ts +0 -166
- package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +0 -389
- package/templates/apps/api/src/services/__tests__/person.test.ts +0 -1204
- package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +0 -450
- package/templates/apps/api/src/services/__tests__/project-listings.counts.test.ts +0 -202
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +0 -151
- package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +0 -137
- package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +0 -531
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +0 -372
- package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +0 -348
- package/templates/apps/api/src/services/__tests__/spoke-resolution.test.ts +0 -660
- package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +0 -941
- package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +0 -588
- package/templates/apps/api/src/services/__tests__/verified-email.test.ts +0 -78
- package/templates/apps/api/src/services/abandon-email.ts +0 -336
- package/templates/apps/api/src/services/aeo-score.ts +0 -325
- package/templates/apps/api/src/services/answer-distributions.ts +0 -138
- package/templates/apps/api/src/services/audit-log.ts +0 -961
- package/templates/apps/api/src/services/checkout-buyer.ts +0 -168
- package/templates/apps/api/src/services/consent.ts +0 -834
- package/templates/apps/api/src/services/default-tenant.ts +0 -416
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +0 -750
- package/templates/apps/api/src/services/dunning-policy.ts +0 -294
- package/templates/apps/api/src/services/enrichment-shim.ts +0 -53
- package/templates/apps/api/src/services/entitlements.ts +0 -338
- package/templates/apps/api/src/services/hub-sign-in.ts +0 -472
- package/templates/apps/api/src/services/lead-accept-user.ts +0 -126
- package/templates/apps/api/src/services/lead-promotion.ts +0 -160
- package/templates/apps/api/src/services/lead-routing.ts +0 -1372
- package/templates/apps/api/src/services/leads-inbox.ts +0 -312
- package/templates/apps/api/src/services/listing-config.ts +0 -178
- package/templates/apps/api/src/services/magnet-completion.ts +0 -242
- package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +0 -348
- package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +0 -268
- package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +0 -975
- package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +0 -416
- package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +0 -400
- package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +0 -685
- package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +0 -231
- package/templates/apps/api/src/services/offerings/catalog.ts +0 -318
- package/templates/apps/api/src/services/offerings/checkout-authorization.ts +0 -403
- package/templates/apps/api/src/services/offerings/checkout-session-record.ts +0 -192
- package/templates/apps/api/src/services/offerings/connection.ts +0 -1083
- package/templates/apps/api/src/services/offerings/index.ts +0 -614
- package/templates/apps/api/src/services/offerings/source.ts +0 -140
- package/templates/apps/api/src/services/offerings/store.ts +0 -150
- package/templates/apps/api/src/services/owner-workspaces.ts +0 -132
- package/templates/apps/api/src/services/person.ts +0 -1211
- package/templates/apps/api/src/services/plan-offerings.ts +0 -125
- package/templates/apps/api/src/services/project-listing-resume-email.ts +0 -326
- package/templates/apps/api/src/services/project-listings.ts +0 -2085
- package/templates/apps/api/src/services/public-site-resolver.ts +0 -396
- package/templates/apps/api/src/services/request-confirm.ts +0 -698
- package/templates/apps/api/src/services/revalidation.ts +0 -235
- package/templates/apps/api/src/services/slack-notify.ts +0 -291
- package/templates/apps/api/src/services/usage-aggregation.ts +0 -516
- package/templates/apps/api/src/services/usage-credits.ts +0 -340
- package/templates/apps/api/src/services/verified-email.ts +0 -71
- package/templates/apps/mcp/__tests__/abuse/daily-cap.test.ts +0 -52
- package/templates/apps/mcp/__tests__/abuse/email-validator.test.ts +0 -46
- package/templates/apps/mcp/__tests__/abuse/guard.test.ts +0 -198
- package/templates/apps/mcp/__tests__/abuse/honeypot.test.ts +0 -36
- package/templates/apps/mcp/__tests__/abuse/user-agent-deny.test.ts +0 -54
- package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +0 -116
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +0 -299
- package/templates/apps/mcp/__tests__/boot.test.ts +0 -280
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +0 -359
- package/templates/apps/mcp/__tests__/config/modules.test.ts +0 -78
- package/templates/apps/mcp/__tests__/get_business_hours.test.ts +0 -82
- package/templates/apps/mcp/__tests__/get_faq.test.ts +0 -80
- package/templates/apps/mcp/__tests__/get_locations.test.ts +0 -65
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +0 -557
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +0 -524
- package/templates/apps/mcp/__tests__/get_site_info.test.ts +0 -81
- package/templates/apps/mcp/__tests__/http-transport.test.ts +0 -1232
- package/templates/apps/mcp/__tests__/index.test.ts +0 -195
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +0 -191
- package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +0 -531
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +0 -70
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +0 -162
- package/templates/apps/mcp/__tests__/lib/content-root.test.ts +0 -93
- package/templates/apps/mcp/__tests__/lib/errors.test.ts +0 -75
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +0 -180
- package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +0 -124
- package/templates/apps/mcp/__tests__/list_pages.test.ts +0 -268
- package/templates/apps/mcp/__tests__/locale.test.ts +0 -120
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +0 -175
- package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +0 -101
- package/templates/apps/mcp/__tests__/rate-limit-write.test.ts +0 -189
- package/templates/apps/mcp/__tests__/read_content.test.ts +0 -511
- package/templates/apps/mcp/__tests__/request_callback.test.ts +0 -160
- package/templates/apps/mcp/__tests__/search_content.test.ts +0 -271
- package/templates/apps/mcp/__tests__/server.test.ts +0 -388
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +0 -448
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +0 -222
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +0 -152
- package/templates/apps/mcp/__tests__/tools/create_project.test.ts +0 -243
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +0 -451
- package/templates/apps/mcp/src/abuse/daily-cap.ts +0 -47
- package/templates/apps/mcp/src/abuse/email-validator.ts +0 -25
- package/templates/apps/mcp/src/abuse/honeypot.ts +0 -21
- package/templates/apps/mcp/src/abuse/index.ts +0 -349
- package/templates/apps/mcp/src/abuse/user-agent-deny.ts +0 -58
- package/templates/apps/mcp/src/boot.ts +0 -167
- package/templates/apps/mcp/src/config/modules.ts +0 -43
- package/templates/apps/mcp/src/http-transport.ts +0 -564
- package/templates/apps/mcp/src/lib/audit-single-page.ts +0 -308
- package/templates/apps/mcp/src/lib/commerce-claim.ts +0 -263
- package/templates/apps/mcp/src/lib/content-adapter.ts +0 -116
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +0 -140
- package/templates/apps/mcp/src/lib/errors.ts +0 -61
- package/templates/apps/mcp/src/lib/license-key-store.ts +0 -45
- package/templates/apps/mcp/src/lib/locales.ts +0 -171
- package/templates/apps/mcp/src/lib/prisma.ts +0 -39
- package/templates/apps/mcp/src/lib/public-workspace.ts +0 -133
- package/templates/apps/mcp/src/lib/resolve-locale.ts +0 -30
- package/templates/apps/mcp/src/lib/shown-offerings.ts +0 -105
- package/templates/apps/mcp/src/lib/site-config.ts +0 -53
- package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +0 -59
- package/templates/apps/mcp/src/request-context.ts +0 -52
- package/templates/apps/mcp/src/server.ts +0 -343
- package/templates/apps/mcp/src/serverless-app.ts +0 -88
- package/templates/apps/mcp/src/tools/audit.ts +0 -127
- package/templates/apps/mcp/src/tools/capture_lead.ts +0 -66
- package/templates/apps/mcp/src/tools/create_project.ts +0 -134
- package/templates/apps/mcp/src/tools/get_business_hours.ts +0 -68
- package/templates/apps/mcp/src/tools/get_faq.ts +0 -71
- package/templates/apps/mcp/src/tools/get_locations.ts +0 -88
- package/templates/apps/mcp/src/tools/get_offerings.ts +0 -96
- package/templates/apps/mcp/src/tools/get_pricing.ts +0 -94
- package/templates/apps/mcp/src/tools/get_site_info.ts +0 -86
- package/templates/apps/mcp/src/tools/index.ts +0 -138
- package/templates/apps/mcp/src/tools/list_pages.ts +0 -68
- package/templates/apps/mcp/src/tools/person_notes.ts +0 -25
- package/templates/apps/mcp/src/tools/proposals.ts +0 -27
- package/templates/apps/mcp/src/tools/purchase.ts +0 -205
- package/templates/apps/mcp/src/tools/read_content.ts +0 -81
- package/templates/apps/mcp/src/tools/request_callback.ts +0 -52
- package/templates/apps/mcp/src/tools/search_content.ts +0 -63
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +0 -54
- package/templates/apps/mcp/src/workspace-server.ts +0 -224
- package/templates/database/CHANGELOG.md +0 -981
- package/templates/database/LICENSE +0 -202
- package/templates/database/NOTICE +0 -2
- package/templates/database/README.md +0 -243
- package/templates/database/__tests__/agents.db.test.ts +0 -320
- package/templates/database/__tests__/appointment.db.test.ts +0 -106
- package/templates/database/__tests__/artifact-content-store.db.test.ts +0 -316
- package/templates/database/__tests__/composite-fence.db.test.ts +0 -206
- package/templates/database/__tests__/consent-ledger.db.test.ts +0 -345
- package/templates/database/__tests__/consent-source-checkout-migration.test.ts +0 -79
- package/templates/database/__tests__/fence-fan-out.db.test.ts +0 -715
- package/templates/database/__tests__/fixtures/appointment-create-probe.ts +0 -79
- package/templates/database/__tests__/fixtures/person-backfill.json +0 -264
- package/templates/database/__tests__/fixtures/person-contact-backfill.json +0 -182
- package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +0 -100
- package/templates/database/__tests__/flow-completion-migration.test.ts +0 -115
- package/templates/database/__tests__/flow-completion.db.test.ts +0 -136
- package/templates/database/__tests__/flow-workspace-key.db.test.ts +0 -263
- package/templates/database/__tests__/flows-listing-diagnostic.test.ts +0 -392
- package/templates/database/__tests__/flows-presets.test.ts +0 -173
- package/templates/database/__tests__/in-page-magnets-migration.test.ts +0 -161
- package/templates/database/__tests__/lead-idempotency-key.db.test.ts +0 -92
- package/templates/database/__tests__/member-area.db.test.ts +0 -257
- package/templates/database/__tests__/member-contract-migration.test.ts +0 -85
- package/templates/database/__tests__/member-contract.db.test.ts +0 -413
- package/templates/database/__tests__/offerings-checkout-session.db.test.ts +0 -270
- package/templates/database/__tests__/offerings-migration.test.ts +0 -197
- package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +0 -86
- package/templates/database/__tests__/offerings.db.test.ts +0 -303
- package/templates/database/__tests__/outbound-message-action.db.test.ts +0 -80
- package/templates/database/__tests__/person-contact.db.test.ts +0 -563
- package/templates/database/__tests__/person-notes.db.test.ts +0 -249
- package/templates/database/__tests__/person.db.test.ts +0 -586
- package/templates/database/__tests__/piece-kind-migration.test.ts +0 -67
- package/templates/database/__tests__/piece-kind.db.test.ts +0 -208
- package/templates/database/__tests__/prefixed-ids.db.test.ts +0 -222
- package/templates/database/__tests__/project-listing-consent-at.db.test.ts +0 -111
- package/templates/database/__tests__/seed.test.ts +0 -272
- package/templates/database/__tests__/spoke-registry.db.test.ts +0 -191
- package/templates/database/__tests__/tenancy.test.ts +0 -141
- package/templates/database/__tests__/trial-magnet-migration.test.ts +0 -186
- package/templates/database/__tests__/user-cascade.test.ts +0 -140
- package/templates/database/_gitignore +0 -8
- package/templates/database/auth/.gitkeep +0 -0
- package/templates/database/auth/schema.prisma +0 -641
- package/templates/database/config/.gitkeep +0 -0
- package/templates/database/config/schema.prisma +0 -441
- package/templates/database/content/.gitkeep +0 -0
- package/templates/database/content/schema.prisma +0 -339
- package/templates/database/eslint.config.mjs +0 -69
- package/templates/database/flows/listing-diagnostic.ts +0 -182
- package/templates/database/flows/presets.ts +0 -315
- package/templates/database/inbox/.gitkeep +0 -0
- package/templates/database/inbox/README.md +0 -3
- package/templates/database/inbox/__tests__/schema.test.ts +0 -44
- package/templates/database/inbox/schema.prisma +0 -1447
- package/templates/database/jest.config.cjs +0 -36
- package/templates/database/migrations/20260508000000_add_inbox_namespace/migration.sql +0 -328
- package/templates/database/migrations/20260513000000_better_auth_migration/migration.sql +0 -100
- package/templates/database/migrations/20260515000000_add_user_billing_fields/migration.sql +0 -28
- package/templates/database/migrations/20260516000000_add_user_tier_care_plan_seat/migration.sql +0 -25
- package/templates/database/migrations/20260518000000_add_telemetry_records/migration.sql +0 -42
- package/templates/database/migrations/20260525000000_theme_wiring/migration.sql +0 -67
- package/templates/database/migrations/20260710000000_retire_commercial_tier/migration.sql +0 -21
- package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +0 -201
- package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +0 -67
- package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +0 -46
- package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +0 -25
- package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +0 -49
- package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +0 -197
- package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +0 -32
- package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +0 -22
- package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +0 -151
- package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +0 -223
- package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +0 -22
- package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +0 -32
- package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +0 -40
- package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +0 -4
- package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +0 -2
- package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +0 -2
- package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +0 -29
- package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +0 -6
- package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +0 -39
- package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +0 -24
- package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +0 -3
- package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +0 -33
- package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +0 -30
- package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +0 -47
- package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +0 -16
- package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +0 -47
- package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +0 -10
- package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +0 -26
- package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +0 -90
- package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +0 -174
- package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +0 -14
- package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +0 -25
- package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +0 -32
- package/templates/database/migrations/20260911180000_consent_grants/migration.sql +0 -71
- package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +0 -30
- package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +0 -49
- package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +0 -123
- package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +0 -15
- package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +0 -158
- package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +0 -40
- package/templates/database/migrations/20260918120000_add_appointment/migration.sql +0 -67
- package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +0 -70
- package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +0 -181
- package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +0 -172
- package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +0 -41
- package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +0 -185
- package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +0 -70
- package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +0 -57
- package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +0 -68
- package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +0 -1067
- package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +0 -210
- package/templates/database/migrations/20261003120000_spoke_registry/migration.sql +0 -80
- package/templates/database/migrations/20261003130000_scheduling_connection/migration.sql +0 -49
- package/templates/database/migrations/20261005120000_agents/migration.sql +0 -84
- package/templates/database/migrations/20261008120000_user_last_workspace/migration.sql +0 -35
- package/templates/database/migrations/20261008130000_lead_idempotency_key/migration.sql +0 -20
- package/templates/database/migrations/20261009120000_agent_grants/migration.sql +0 -33
- package/templates/database/migrations/20261010120000_person/migration.sql +0 -106
- package/templates/database/migrations/20261010121000_person_backfill/migration.sql +0 -132
- package/templates/database/migrations/20261010130000_agent_grants_people_read/migration.sql +0 -20
- package/templates/database/migrations/20261011120000_member_area/migration.sql +0 -158
- package/templates/database/migrations/20261011130000_agent_grants_member_pieces_manage/migration.sql +0 -21
- package/templates/database/migrations/20261012120000_member_contract/migration.sql +0 -192
- package/templates/database/migrations/20261013120000_lead_spoke_checkout/migration.sql +0 -20
- package/templates/database/migrations/20261014120000_consent_ledger/migration.sql +0 -122
- package/templates/database/migrations/20261014120000_person_contact/migration.sql +0 -231
- package/templates/database/migrations/20261015120000_person_notes/migration.sql +0 -77
- package/templates/database/migrations/20261015120000_rate_limit_counters/migration.sql +0 -43
- package/templates/database/migrations/20261016120000_piece_kind/migration.sql +0 -95
- package/templates/database/migrations/20261017120000_outbound_message_action/migration.sql +0 -30
- package/templates/database/migrations/20261018120000_flow_completion/migration.sql +0 -52
- package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +0 -27
- package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +0 -146
- package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +0 -77
- package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +0 -45
- package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +0 -47
- package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +0 -16
- package/templates/database/migrations/20261025120000_trial_magnet/migration.sql +0 -28
- package/templates/database/migrations/20261026120000_trial_magnet_proof_line/migration.sql +0 -23
- package/templates/database/migrations/migration_lock.toml +0 -3
- package/templates/database/ops/.gitkeep +0 -0
- package/templates/database/ops/schema.prisma +0 -43
- package/templates/database/package.json +0 -89
- package/templates/database/schema.prisma +0 -71
- package/templates/database/scripts/db-generate-locked.sh +0 -80
- package/templates/database/scripts/person-backfill.sql +0 -132
- package/templates/database/tenancy.ts +0 -183
- package/templates/database/tsconfig.build.json +0 -16
- package/templates/database/tsconfig.flows.json +0 -15
- package/templates/database/tsconfig.json +0 -16
- package/templates/database/tsconfig.tenancy.json +0 -14
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Project-listings route group (#4998 — the founder's application row;
|
|
3
|
-
* reshaped by #5036 — the application became a resumable draft).
|
|
4
|
-
*
|
|
5
|
-
* Routes:
|
|
6
|
-
* POST /v1/project-listings — anonymous start (leadsScope)
|
|
7
|
-
* PATCH /v1/project-listings/drafts/:id — anonymous save-as-you-go, token-gated
|
|
8
|
-
* POST /v1/project-listings/drafts/:id/submit — anonymous submit, token-gated
|
|
9
|
-
* POST /v1/project-listings/drafts/:id/verify-email — anonymous, token- AND
|
|
10
|
-
* nonce-gated (#5407)
|
|
11
|
-
* GET /v1/project-listings/public — anonymous page-render read (leadsScope)
|
|
12
|
-
* GET /v1/project-listings/directory — anonymous landing-page directory (own scope, #7521)
|
|
13
|
-
* GET /v1/project-listings/me — the founder's OWN listings (authScope)
|
|
14
|
-
* GET /v1/project-listings — tenant-scoped dashboard list
|
|
15
|
-
* GET /v1/project-listings/:id — tenant-scoped single read
|
|
16
|
-
* PATCH /v1/project-listings/:id — tenant-scoped decision + featured flag
|
|
17
|
-
* PATCH /v1/project-listings/:id/copy — tenant-scoped operator copy edit
|
|
18
|
-
*
|
|
19
|
-
* The anonymous set is registered in `server.ts`'s rate-limited `leadsScope`
|
|
20
|
-
* alongside `/v1/leads/*`; the tenant-scoped set is registered inside
|
|
21
|
-
* `tenantScope`, behind `tenantPreHandlerPlugin`, the same as
|
|
22
|
-
* `waitlistExperimentRoutes`. The `/drafts/` path segment is what keeps the
|
|
23
|
-
* anonymous token-gated PATCH and the operator's tenant-guarded PATCH apart
|
|
24
|
-
* at the router — they differ in segment count, so neither shadows the other.
|
|
25
|
-
*
|
|
26
|
-
* `/me` (#5295) sits between the two scopes: `authScope` only, no tenant guard.
|
|
27
|
-
* A founder is not a workspace member, so the guard's default-deny answers 403
|
|
28
|
-
* for their own submission — and the fix is this ADDITIVE route, joined on the
|
|
29
|
-
* owner link, rather than a relaxed `inbox-read` permission. Its static `/me`
|
|
30
|
-
* segment takes router precedence over the parametric `/:id` below, exactly as
|
|
31
|
-
* the already-shipped static `/public` does.
|
|
32
|
-
*
|
|
33
|
-
* API<->MCP parity: the anonymous set has no MCP twin — it mirrors the leads
|
|
34
|
-
* routes' own identity-blind posture (an applying founder has no account, and
|
|
35
|
-
* the resume token is a per-draft capability rather than an identity). Three
|
|
36
|
-
* tenant-scoped routes have a twin on the signed-in workspace MCP surface,
|
|
37
|
-
* `/mcp/workspace` (#3591 D13), each parsing with a
|
|
38
|
-
* `@working-theory/validation` export (D10):
|
|
39
|
-
* `GET /v1/project-listings` — `list_inbox_applications`,
|
|
40
|
-
* `GET /v1/project-listings/:id` — `get_inbox_application`,
|
|
41
|
-
* `PATCH /v1/project-listings/:id` — `decide_inbox_application`. The copy
|
|
42
|
-
* edit (`PATCH /:id/copy`) keeps the no-twin posture of the sibling
|
|
43
|
-
* `/v1/waitlist-experiments/*` routes. The directory read (#7521) takes the
|
|
44
|
-
* same no-twin posture as `/public`: agents read a directory from its page's
|
|
45
|
-
* JSON-LD, and a `list_directory` tool is a follow-up.
|
|
46
|
-
*/
|
|
47
|
-
import type { FastifyPluginAsync } from "fastify";
|
|
48
|
-
|
|
49
|
-
import { loadSlice } from "../../lib/slice-load.js";
|
|
50
|
-
|
|
51
|
-
import { projectListingMeRoute as meRoute } from "./me.js";
|
|
52
|
-
import { patchProjectListingDraftRoute } from "./patch-draft.js";
|
|
53
|
-
import {
|
|
54
|
-
projectListingDirectoryRoute as directoryRoute,
|
|
55
|
-
publicProjectListingsRoute,
|
|
56
|
-
} from "./public.js";
|
|
57
|
-
import { startProjectListingRoute } from "./start.js";
|
|
58
|
-
import { submitProjectListingRoute } from "./submit.js";
|
|
59
|
-
import { verifyProjectListingDraftEmailRoute } from "./verify-email.js";
|
|
60
|
-
|
|
61
|
-
export const projectListingStartRoute = startProjectListingRoute;
|
|
62
|
-
export const projectListingDraftPatchRoute = patchProjectListingDraftRoute;
|
|
63
|
-
export const projectListingSubmitRoute = submitProjectListingRoute;
|
|
64
|
-
export const projectListingPublicRoute = publicProjectListingsRoute;
|
|
65
|
-
/**
|
|
66
|
-
* The landing-page directory read (#7521). Static, like `/public`; `server.ts`
|
|
67
|
-
* registers it in its own rate-limited scope, not `leadsScope`.
|
|
68
|
-
*/
|
|
69
|
-
export const projectListingDirectoryRoute = directoryRoute;
|
|
70
|
-
/**
|
|
71
|
-
* The resume link's verification write (#5407). Static, not a slice: it is
|
|
72
|
-
* part of the free anonymous surface, exactly like the three routes above.
|
|
73
|
-
*/
|
|
74
|
-
export const projectListingVerifyEmailRoute = verifyProjectListingDraftEmailRoute;
|
|
75
|
-
/**
|
|
76
|
-
* The founder's own listings read (#5295). Static, not a slice: it depends on
|
|
77
|
-
* the owner link alone, so it is registered — and works — on a build with no
|
|
78
|
-
* multi-tenant module, the same as `flowCheckoutRoutes`.
|
|
79
|
-
*/
|
|
80
|
-
export const projectListingMeRoute = meRoute;
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Tenant-scoped handlers, as { specifier, exportName }, reached at REGISTRATION
|
|
84
|
-
* time rather than named as static imports (#5182).
|
|
85
|
-
*
|
|
86
|
-
* `get.ts`, `list.ts`, `patch.ts` and `patch-copy.ts` are multi-tenant SLICE
|
|
87
|
-
* files the OSS strip removes; the other four route modules in this directory
|
|
88
|
-
* are the free anonymous surface and stay static. This directory is mixed on
|
|
89
|
-
* purpose, which is why the boundary is drawn here at the registration
|
|
90
|
-
* call-site rather than by splitting the directory.
|
|
91
|
-
*
|
|
92
|
-
* The specifier is kept off the `import()` call site because `tsc` resolves a
|
|
93
|
-
* string-literal `import("...")` EXPRESSION whether or not the branch runs, and
|
|
94
|
-
* would report TS2307 on the stripped tree. It is held in a module-level
|
|
95
|
-
* constant rather than read out of the table per iteration (#5304): the table
|
|
96
|
-
* form was invisible to the deploy-time file tracer too, so these files were
|
|
97
|
-
* compiled and never shipped, and the constant form is the one shape `tsc`
|
|
98
|
-
* refuses to resolve and `@vercel/nft` still traces. `specifier` stays on each
|
|
99
|
-
* row for the drift message and reads the same constant as `load`. The plugin body
|
|
100
|
-
* only executes when `server.ts` registers it, which it does inside
|
|
101
|
-
* `isModuleEnabled('multi-tenant')` — so nothing here runs on an OSS build.
|
|
102
|
-
*
|
|
103
|
-
* The export name is unchanged so `server.ts` needs no edit: it still reaches
|
|
104
|
-
* `projectListingTenantRoutes` from this module.
|
|
105
|
-
*/
|
|
106
|
-
const LIST_HANDLER = "./list.js";
|
|
107
|
-
const GET_HANDLER = "./get.js";
|
|
108
|
-
const PATCH_HANDLER = "./patch.js";
|
|
109
|
-
const PATCH_COPY_HANDLER = "./patch-copy.js";
|
|
110
|
-
|
|
111
|
-
const TENANT_HANDLERS: ReadonlyArray<{
|
|
112
|
-
method: "get" | "patch";
|
|
113
|
-
url: string;
|
|
114
|
-
specifier: string;
|
|
115
|
-
exportName: string;
|
|
116
|
-
load: () => Promise<unknown>;
|
|
117
|
-
}> = [
|
|
118
|
-
{
|
|
119
|
-
method: "get",
|
|
120
|
-
url: "/v1/project-listings",
|
|
121
|
-
specifier: LIST_HANDLER,
|
|
122
|
-
exportName: "listProjectListingsHandler",
|
|
123
|
-
load: () => import(LIST_HANDLER),
|
|
124
|
-
},
|
|
125
|
-
{
|
|
126
|
-
method: "get",
|
|
127
|
-
url: "/v1/project-listings/:id",
|
|
128
|
-
specifier: GET_HANDLER,
|
|
129
|
-
exportName: "getProjectListingHandler",
|
|
130
|
-
load: () => import(GET_HANDLER),
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
method: "patch",
|
|
134
|
-
url: "/v1/project-listings/:id",
|
|
135
|
-
specifier: PATCH_HANDLER,
|
|
136
|
-
exportName: "patchProjectListingHandler",
|
|
137
|
-
load: () => import(PATCH_HANDLER),
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
method: "patch",
|
|
141
|
-
url: "/v1/project-listings/:id/copy",
|
|
142
|
-
specifier: PATCH_COPY_HANDLER,
|
|
143
|
-
exportName: "patchProjectListingCopyHandler",
|
|
144
|
-
load: () => import(PATCH_COPY_HANDLER),
|
|
145
|
-
},
|
|
146
|
-
];
|
|
147
|
-
|
|
148
|
-
export const projectListingTenantRoutes: FastifyPluginAsync = async (
|
|
149
|
-
fastify,
|
|
150
|
-
) => {
|
|
151
|
-
for (const { method, url, specifier, exportName, load } of TENANT_HANDLERS) {
|
|
152
|
-
const mod = (await loadSlice(specifier, load)) as Record<
|
|
153
|
-
string,
|
|
154
|
-
Parameters<typeof fastify.get>[1] | undefined
|
|
155
|
-
>;
|
|
156
|
-
const handler = mod[exportName];
|
|
157
|
-
if (!handler) {
|
|
158
|
-
throw new Error(
|
|
159
|
-
`multi-tenant slice drift: '${specifier}' does not export '${exportName}'`,
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
fastify[method](url, handler);
|
|
163
|
-
}
|
|
164
|
-
};
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NOT marked `oss: false` (D-48/#5366 escalation, mirrors the original
|
|
3
|
-
* multi-tenant/#5088 card's own conclusion about this exact file, lines
|
|
4
|
-
* 556-571 of module-architecture.ts): `./index.ts` in this directory is a
|
|
5
|
-
* MIXED barrel that statically imports this file, and `server.ts` reaches
|
|
6
|
-
* `./index.ts` via a plain (non-slice) literal `import()` for multi-tenant's
|
|
7
|
-
* tenant-scoped routes — a specifier `tsc` always resolves, module selection
|
|
8
|
-
* or not. Stripping this file leaves that barrel with a dangling import on
|
|
9
|
-
* every tree. Registration stays gated (`isModuleEnabled("signal-collectors")`
|
|
10
|
-
* in server.ts); only the source file itself must ship unconditionally.
|
|
11
|
-
*
|
|
12
|
-
* GET /v1/project-listings/me
|
|
13
|
-
*
|
|
14
|
-
* The FOUNDER'S OWN listings read (#5295) — the `/v1/flow-checkouts/me` and
|
|
15
|
-
* `/v1/waitlist-signups/me` posture, for `ProjectListing`.
|
|
16
|
-
*
|
|
17
|
-
* Registered INSIDE `server.ts`'s `authScope` (auth only — NOT the inner
|
|
18
|
-
* `tenantScope`): a founder is not a workspace member, and the tenant guard is
|
|
19
|
-
* default-deny, so a founder reaching the tenant-scoped list route gets a 403
|
|
20
|
-
* for their own submission. That is the problem this route exists to solve, and
|
|
21
|
-
* it is solved by ADDING this route rather than by relaxing the tenant
|
|
22
|
-
* routes' permissions — `GET|PATCH /v1/project-listings[/:id]` stay on the
|
|
23
|
-
* permission table's `inbox-read` / `applications-decide` rows (#7636 D2),
|
|
24
|
-
* which a founder, holding no Membership, never reaches.
|
|
25
|
-
*
|
|
26
|
-
* Identity is `request.userId` ONLY. The route declares no query schema and
|
|
27
|
-
* reads no query or body key — an `email` parameter is never parsed and never
|
|
28
|
-
* read — and the service joins on the owner link and nothing else
|
|
29
|
-
* (`services/project-listings.ts`'s `listMyProjectListings`: `ownerUserId ===
|
|
30
|
-
* userId`, never a `founderEmail` comparison at read time).
|
|
31
|
-
*
|
|
32
|
-
* Route precedence: this static path is registered alongside the parametric
|
|
33
|
-
* `/v1/project-listings/:id` in the tenant scope. Fastify's router prefers a
|
|
34
|
-
* static segment over a parametric one, so `/me` reaches this handler, exactly
|
|
35
|
-
* as the already-shipped static `/v1/project-listings/public` coexists with the
|
|
36
|
-
* same parametric route.
|
|
37
|
-
*
|
|
38
|
-
* Envelope per apps/api/CLAUDE.md: `{ ok: true, data }` /
|
|
39
|
-
* `{ ok: false, error: { code, message } }`, real HTTP status codes:
|
|
40
|
-
* 401 unauthorized · 500 internal_error (the 200 body failed its own
|
|
41
|
-
* contract schema — a drift is never a leak). A signed-in caller who owns
|
|
42
|
-
* NOTHING is a 200 with an empty list, never a 403: they are legitimately
|
|
43
|
-
* signed in and simply own no listing yet.
|
|
44
|
-
*
|
|
45
|
-
* API↔MCP parity: identity-bound (the session IS the identity; the OSS MCP
|
|
46
|
-
* surface is identity-blind) — no MCP twin (#3591 exception, the same shape as
|
|
47
|
-
* `/v1/flow-checkouts/me` and `/v1/waitlist-signups/me`).
|
|
48
|
-
*/
|
|
49
|
-
import { projectListingMeResponseSchema } from "@working-theory/validation";
|
|
50
|
-
import type { FastifyPluginAsync, FastifyReply } from "fastify";
|
|
51
|
-
|
|
52
|
-
import { listMyProjectListings } from "../../services/project-listings.js";
|
|
53
|
-
|
|
54
|
-
import { sendValidated } from "./respond.js";
|
|
55
|
-
|
|
56
|
-
function unauthorized(reply: FastifyReply) {
|
|
57
|
-
return reply.status(401).send({
|
|
58
|
-
ok: false,
|
|
59
|
-
error: { code: "unauthorized", message: "Authentication required" },
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export const projectListingMeRoute: FastifyPluginAsync = async (fastify) => {
|
|
64
|
-
fastify.get("/v1/project-listings/me", async (request, reply) => {
|
|
65
|
-
const userId = request.userId;
|
|
66
|
-
if (!userId) return unauthorized(reply);
|
|
67
|
-
|
|
68
|
-
const listings = await listMyProjectListings(userId);
|
|
69
|
-
|
|
70
|
-
// The omission is explicit at the send site rather than hidden in a schema
|
|
71
|
-
// strip, the idiom `patch-draft.ts` uses: `projectListingDraftSchema` is
|
|
72
|
-
// `.strict()`, so a key that stops being dropped here becomes a 500 instead
|
|
73
|
-
// of quietly shipping an internal reference to the founder.
|
|
74
|
-
const data = listings.map(
|
|
75
|
-
({
|
|
76
|
-
siteId: _siteId,
|
|
77
|
-
leadId: _leadId,
|
|
78
|
-
decidedBy: _decidedBy,
|
|
79
|
-
issueUrl: _issueUrl,
|
|
80
|
-
experimentKey: _experimentKey,
|
|
81
|
-
...mine
|
|
82
|
-
}) => mine,
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
return sendValidated(reply, projectListingMeResponseSchema, data, "me");
|
|
86
|
-
});
|
|
87
|
-
};
|
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
// oss: false
|
|
2
|
-
/**
|
|
3
|
-
* PATCH /v1/project-listings/:id/copy
|
|
4
|
-
*
|
|
5
|
-
* The operator's copy edit — the THIRD review outcome, beside approve and
|
|
6
|
-
* reject.
|
|
7
|
-
*
|
|
8
|
-
* Before this route a submitted listing's copy was frozen for everyone: the
|
|
9
|
-
* legal transitions out of `requested` are `active` and `rejected` only, there
|
|
10
|
-
* is no route back to `draft`, and `PATCH /v1/project-listings/drafts/:id` is
|
|
11
|
-
* scoped to `status: "draft"` plus the applicant's own resume-token hash. A
|
|
12
|
-
* listing whose headline needed a trim could therefore be published exactly as
|
|
13
|
-
* written or rejected outright, and the founder could not revise it either.
|
|
14
|
-
*
|
|
15
|
-
* Deliberately NOT folded into `PATCH /v1/project-listings/:id`. That body is
|
|
16
|
-
* `.strict()`, carries at most `status` and `featured`, and exists to express
|
|
17
|
-
* the DECISION transition; entangling "edit the copy" with "decide the
|
|
18
|
-
* listing" would muddy a contract that is currently clean. The lifecycle is
|
|
19
|
-
* untouched by this route — it adds no status, no transition and no decision
|
|
20
|
-
* target.
|
|
21
|
-
*
|
|
22
|
-
* Auth, tenant scoping and the error shapes are `patch.ts`'s, with the
|
|
23
|
-
* `listings-edit` permission (owner, admin — #7636 D2) as the floor, the
|
|
24
|
-
* same `siteId` scoping from `request.tenant`, and the same 401 / 403 / 400 /
|
|
25
|
-
* 404 bodies. A foreign or missing id is `404`, so a
|
|
26
|
-
* cross-tenant probe cannot tell "exists but not yours" from "does not exist".
|
|
27
|
-
*
|
|
28
|
-
* `questions` is replaced wholesale, and its `extras` array carries a
|
|
29
|
-
* `.default([])` in the package schema, so a payload omitting that key would
|
|
30
|
-
* parse to an empty array and silently delete the founder's extra questions.
|
|
31
|
-
* A `questions` body without an explicit `extras` is therefore a `400`, which
|
|
32
|
-
* makes clearing them something the caller has to say rather than something it
|
|
33
|
-
* can do by forgetting.
|
|
34
|
-
*
|
|
35
|
-
* The status floor (`requested` or `active` only) lives in the service's WHERE
|
|
36
|
-
* clause rather than in a read-then-write check, so a concurrent decision
|
|
37
|
-
* cannot slip the row out of the editable set between the check and the write.
|
|
38
|
-
* A `draft`, `rejected` or `closed` row is `404` and unchanged — `closed`
|
|
39
|
-
* included, even though nothing stores it: the WHERE transcribes
|
|
40
|
-
* `computeListingStatus`'s accepting branches rather than its stored string,
|
|
41
|
-
* so an elapsed window refuses the edit exactly as it refuses on a read.
|
|
42
|
-
*/
|
|
43
|
-
import { zProjectListingId } from "@working-theory/ids";
|
|
44
|
-
import {
|
|
45
|
-
buildProjectListingDraftPatchSchema,
|
|
46
|
-
can,
|
|
47
|
-
projectListingSchema,
|
|
48
|
-
} from "@working-theory/validation";
|
|
49
|
-
import type { FastifyReply, FastifyRequest } from "fastify";
|
|
50
|
-
import { z } from "zod";
|
|
51
|
-
|
|
52
|
-
import { listingConfigFor } from "../../services/listing-config.js";
|
|
53
|
-
import { patchListingCopy } from "../../services/project-listings.js";
|
|
54
|
-
|
|
55
|
-
import { sendValidated } from "./respond.js";
|
|
56
|
-
|
|
57
|
-
const paramsSchema = z.object({ id: zProjectListingId });
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* The five PUBLISHED copy fields this route may rewrite.
|
|
61
|
-
*
|
|
62
|
-
* `questions` is in because its prompts and option labels render on the public
|
|
63
|
-
* page, which is exactly where abusive language would land. `name` is out
|
|
64
|
-
* because the slug is derived from it at submit and stored, so editing it would
|
|
65
|
-
* desync the public URL from the name. `url`, `product`, `commitment`,
|
|
66
|
-
* `teamComposition`, `address` and `founderCompany` are out because they are
|
|
67
|
-
* facts rather than copy — a wrong fact is a question to the founder or a
|
|
68
|
-
* reject, not an edit. `anythingElse` is out because it is a note to the
|
|
69
|
-
* reviewer and never renders.
|
|
70
|
-
*/
|
|
71
|
-
const COPY_FIELDS = ["headline", "oneLiner", "problem", "faqs", "questions"] as const;
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* The copy-edit body, DERIVED from the application's own schema at THIS SITE's
|
|
75
|
-
* configured bounds rather than re-declared.
|
|
76
|
-
*
|
|
77
|
-
* Deriving is what carries the word caps, the exactly-three-FAQ rule and every
|
|
78
|
-
* question refinement into this route for free, and what keeps them from
|
|
79
|
-
* drifting: a bound the application gains is a bound the copy edit gains in the
|
|
80
|
-
* same commit.
|
|
81
|
-
*
|
|
82
|
-
* Built from `buildProjectListingDraftPatchSchema` — the same builder the
|
|
83
|
-
* applicant's own write path uses — rather than from the fixed
|
|
84
|
-
* `projectListingApplicationFieldsSchema`, because since #5048 D5/D6 the word
|
|
85
|
-
* caps and the collected field set are the SITE's configuration, not platform
|
|
86
|
-
* constants. Against the fixed schema an operator could not re-save copy the
|
|
87
|
-
* applicant was legitimately allowed to submit: a site configured to fifteen
|
|
88
|
-
* words would accept a fourteen-word headline at submit and refuse the same
|
|
89
|
-
* text here. Passing the intersection of the site's `collectedFields` with
|
|
90
|
-
* `COPY_FIELDS` as the collected set is what narrows the builder's full roster
|
|
91
|
-
* to this route's five, so a field the site does not collect cannot be authored
|
|
92
|
-
* onto a founder's public page by the operator either — the same security
|
|
93
|
-
* property the builder's own docblock records.
|
|
94
|
-
*
|
|
95
|
-
* Unlike `patch-draft.ts`, which must resolve the draft before it knows the
|
|
96
|
-
* site and therefore validates inside the service, this route reads the site
|
|
97
|
-
* from the caller's own session, so the schema is built here and the editable
|
|
98
|
-
* set stays visible in this file.
|
|
99
|
-
*
|
|
100
|
-
* NOT lifted into `@working-theory/validation` as a sibling of
|
|
101
|
-
* `projectListingPatchSchema`, tempting as the symmetry is. This file is
|
|
102
|
-
* vendored byte-for-byte into a consuming repo that installs that package from
|
|
103
|
-
* a PUBLISHED version, so a new export would leave the synced copy referencing
|
|
104
|
-
* a symbol its installed version does not have — broken until a publish and a
|
|
105
|
-
* version bump land. `buildProjectListingDraftPatchSchema` is already public in
|
|
106
|
-
* the version that repo has (`services/project-listings.ts` imports it), so
|
|
107
|
-
* this local derivation syncs cleanly.
|
|
108
|
-
*
|
|
109
|
-
* The builder supplies the at-least-one-key refine, so an empty body is a 400
|
|
110
|
-
* rather than a silent no-op the caller would read as a successful save.
|
|
111
|
-
*/
|
|
112
|
-
function copyPatchSchemaFor(
|
|
113
|
-
wordCaps: Parameters<typeof buildProjectListingDraftPatchSchema>[0],
|
|
114
|
-
collectedFields: readonly string[],
|
|
115
|
-
) {
|
|
116
|
-
return buildProjectListingDraftPatchSchema(
|
|
117
|
-
wordCaps,
|
|
118
|
-
COPY_FIELDS.filter((field) => collectedFields.includes(field)),
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function unauthorized(reply: FastifyReply) {
|
|
123
|
-
return reply.status(401).send({
|
|
124
|
-
ok: false,
|
|
125
|
-
error: { code: "unauthorized", message: "Authentication required" },
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function forbidden(reply: FastifyReply) {
|
|
130
|
-
return reply.status(403).send({
|
|
131
|
-
ok: false,
|
|
132
|
-
error: { code: "forbidden", message: "Forbidden" },
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function invalidParams(reply: FastifyReply) {
|
|
137
|
-
return reply.status(400).send({
|
|
138
|
-
ok: false,
|
|
139
|
-
error: { code: "validation_failed", message: "Invalid project listing id" },
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function invalidBody(reply: FastifyReply, message: string) {
|
|
144
|
-
return reply.status(400).send({
|
|
145
|
-
ok: false,
|
|
146
|
-
error: { code: "invalid_input", message },
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function notFound(reply: FastifyReply) {
|
|
151
|
-
return reply.status(404).send({
|
|
152
|
-
ok: false,
|
|
153
|
-
error: { code: "not_found", message: "Project listing not found" },
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export async function patchProjectListingCopyHandler(
|
|
158
|
-
request: FastifyRequest,
|
|
159
|
-
reply: FastifyReply,
|
|
160
|
-
): Promise<void> {
|
|
161
|
-
const tenant = request.tenant;
|
|
162
|
-
if (!tenant) return unauthorized(reply);
|
|
163
|
-
if (!can(tenant.role, "listings-edit")) {
|
|
164
|
-
request.log.warn(
|
|
165
|
-
{ method: request.method, path: request.url, status: 403 },
|
|
166
|
-
"[project-listings] copy patch denied; insufficient role",
|
|
167
|
-
);
|
|
168
|
-
return forbidden(reply);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const parsedParams = paramsSchema.safeParse(request.params);
|
|
172
|
-
if (!parsedParams.success) return invalidParams(reply);
|
|
173
|
-
|
|
174
|
-
const listingConfig = await listingConfigFor(tenant.workspaceId);
|
|
175
|
-
const editableFields = COPY_FIELDS.filter((field) =>
|
|
176
|
-
listingConfig.collectedFields.includes(field),
|
|
177
|
-
);
|
|
178
|
-
if (editableFields.length === 0) {
|
|
179
|
-
// A site whose application collects none of the five copy fields has no
|
|
180
|
-
// copy to edit. Refused here rather than handed to the builder, whose
|
|
181
|
-
// `pickCollected` throws on an empty mask — that throw guards the
|
|
182
|
-
// applicant's surface and would be a 500 on this one.
|
|
183
|
-
return invalidBody(reply, "this site collects none of the editable copy fields");
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const parsedBody = copyPatchSchemaFor(
|
|
187
|
-
listingConfig.wordCaps,
|
|
188
|
-
listingConfig.collectedFields,
|
|
189
|
-
).safeParse(request.body);
|
|
190
|
-
if (!parsedBody.success) {
|
|
191
|
-
return invalidBody(reply, parsedBody.error.issues[0]?.message ?? "invalid input");
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// `questions.extras` carries a `.default([])` in the package schema, so a
|
|
195
|
-
// `questions` payload that OMITS the key parses to `extras: []` —
|
|
196
|
-
// indistinguishable from a deliberate clear. `questions` is replaced
|
|
197
|
-
// wholesale, so accepting the ambiguous body would silently delete the
|
|
198
|
-
// founder's up-to-three extra questions with no error. Refuse it instead and
|
|
199
|
-
// make the caller say which it meant. Checked against the RAW body because
|
|
200
|
-
// the parsed value always carries the key once zod has applied the default.
|
|
201
|
-
if (parsedBody.data.questions !== undefined) {
|
|
202
|
-
const rawQuestions = (request.body as { questions?: unknown }).questions;
|
|
203
|
-
if (
|
|
204
|
-
typeof rawQuestions !== "object" ||
|
|
205
|
-
rawQuestions === null ||
|
|
206
|
-
!("extras" in rawQuestions)
|
|
207
|
-
) {
|
|
208
|
-
return invalidBody(
|
|
209
|
-
reply,
|
|
210
|
-
"questions must carry an explicit 'extras' array; send [] to clear it",
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const result = await patchListingCopy(
|
|
216
|
-
tenant.workspaceId,
|
|
217
|
-
parsedParams.data.id,
|
|
218
|
-
parsedBody.data,
|
|
219
|
-
tenant.userId,
|
|
220
|
-
);
|
|
221
|
-
if (!result.ok) return notFound(reply);
|
|
222
|
-
|
|
223
|
-
return sendValidated(reply, projectListingSchema, result.value, "patch-copy");
|
|
224
|
-
}
|
|
@@ -1,102 +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
|
-
* PATCH /v1/project-listings/drafts/:id
|
|
8
|
-
*
|
|
9
|
-
* `patch` — the save-as-you-go write (#5036 D1). Anonymous, token-gated,
|
|
10
|
-
* registered in the rate-limited `leadsScope`.
|
|
11
|
-
*
|
|
12
|
-
* It merges a partial application into a draft and ENFORCES NO COMPLETENESS.
|
|
13
|
-
* A present field is still checked against its own bounds (length, enum
|
|
14
|
-
* membership, and the SITE's own word caps — #5048 D6, which is why the body
|
|
15
|
-
* is validated inside the service, where the draft's site is known) because
|
|
16
|
-
* this is an anonymous write surface; what
|
|
17
|
-
* it deliberately does not assert is that the application is finished. That is
|
|
18
|
-
* `submit`'s job alone, and the split is the whole point of the lifecycle.
|
|
19
|
-
*
|
|
20
|
-
* The credential is the `Authorization: Bearer` resume token minted at
|
|
21
|
-
* `start`, and it is bound to ONE listing: a valid token presented against a
|
|
22
|
-
* different id resolves to nothing. A wrong token, a spent token, an unknown
|
|
23
|
-
* id and an already-submitted row are all the same `404` — the anonymous
|
|
24
|
-
* surface must not distinguish "exists but not yours" from "does not exist".
|
|
25
|
-
*
|
|
26
|
-
* The response is `projectListingDraftSchema`, NOT the tenant dashboard's
|
|
27
|
-
* row shape: an anonymous caller addresses its site by an opaque public key
|
|
28
|
-
* and must not be handed the real `siteId` back on every save (security
|
|
29
|
-
* review on this change, finding 3).
|
|
30
|
-
*
|
|
31
|
-
* Distinct from the tenant-scoped `PATCH /v1/project-listings/:id`, which
|
|
32
|
-
* carries the operator's decision and lives behind the tenant guard. The
|
|
33
|
-
* `/drafts/` segment keeps the two apart at the router.
|
|
34
|
-
*/
|
|
35
|
-
import { zProjectListingId } from "@working-theory/ids";
|
|
36
|
-
import { projectListingDraftSchema } from "@working-theory/validation";
|
|
37
|
-
import type { FastifyPluginAsync } from "fastify";
|
|
38
|
-
import { z } from "zod";
|
|
39
|
-
|
|
40
|
-
import { patchProjectListingDraft } from "../../services/project-listings.js";
|
|
41
|
-
|
|
42
|
-
import { sendValidated } from "./respond.js";
|
|
43
|
-
import { resumeTokenFrom } from "./resume-token.js";
|
|
44
|
-
|
|
45
|
-
const paramsSchema = z.object({ id: zProjectListingId });
|
|
46
|
-
|
|
47
|
-
export const patchProjectListingDraftRoute: FastifyPluginAsync = async (fastify) => {
|
|
48
|
-
fastify.patch("/v1/project-listings/drafts/:id", 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
|
-
// The body is validated INSIDE the service (#5048 D6), not here: the word
|
|
66
|
-
// caps are the site's own configuration, and which site this draft belongs
|
|
67
|
-
// to is not knowable until the draft is resolved. A caller holding no valid
|
|
68
|
-
// token still never reaches body validation — the service answers
|
|
69
|
-
// `not_found` first.
|
|
70
|
-
const result = await patchProjectListingDraft(
|
|
71
|
-
parsedParams.data.id,
|
|
72
|
-
resumeToken,
|
|
73
|
-
request.body,
|
|
74
|
-
);
|
|
75
|
-
if (!result.ok) {
|
|
76
|
-
if (result.error.kind === "invalid_input") {
|
|
77
|
-
return reply.status(400).send({
|
|
78
|
-
ok: false,
|
|
79
|
-
error: { code: "invalid_input", message: result.error.message },
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
return reply.status(404).send({
|
|
83
|
-
ok: false,
|
|
84
|
-
error: { code: "not_found", message: "Draft not found" },
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// The omission is explicit at the send site rather than hidden in a
|
|
89
|
-
// schema strip: `projectListingDraftSchema` is `.strict()`, so a key that
|
|
90
|
-
// stops being dropped here becomes a 500 instead of quietly shipping.
|
|
91
|
-
const {
|
|
92
|
-
siteId: _siteId,
|
|
93
|
-
leadId: _leadId,
|
|
94
|
-
decidedBy: _decidedBy,
|
|
95
|
-
issueUrl: _issueUrl,
|
|
96
|
-
experimentKey: _experimentKey,
|
|
97
|
-
...draft
|
|
98
|
-
} = result.value;
|
|
99
|
-
|
|
100
|
-
return sendValidated(reply, projectListingDraftSchema, draft, "patch-draft");
|
|
101
|
-
});
|
|
102
|
-
};
|
|
@@ -1,98 +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
|
-
* GET /v1/project-listings/public?siteKey=
|
|
8
|
-
*
|
|
9
|
-
* The anonymous page-render read (#4998 AC4, reshaped by #5033). Every
|
|
10
|
-
* `active` row for the site, ordered: the three newest by `decidedAt` among
|
|
11
|
-
* them first, then the rest by `waitlistCount` desc, then `decidedAt` desc.
|
|
12
|
-
* A row whose window has elapsed comes back with a COMPUTED `closed` status —
|
|
13
|
-
* no job runs, nothing is stored, and a consumer partitions its shelves on
|
|
14
|
-
* what it is handed. `featured` surfaces only where the site's
|
|
15
|
-
* `SiteListingConfig` enables it. `founderEmail` never leaves this route —
|
|
16
|
-
* `projectListingPublicSchema` (`.omit({founderEmail})`) is what the response
|
|
17
|
-
* is re-parsed against before send.
|
|
18
|
-
*
|
|
19
|
-
* GET /v1/project-listings/directory?siteKey=&landingPage=&locale= (#7521 D3)
|
|
20
|
-
*
|
|
21
|
-
* The generic directory read beside it: the live listicle naming
|
|
22
|
-
* `landingPage`, its schema.org entry type and its accepted entries, each
|
|
23
|
-
* with a public profile of the listicle's own Application answers — or
|
|
24
|
-
* `data: null` when no live listicle names the page, which is almost every
|
|
25
|
-
* page (a 404 per page view would be noise). Its own plugin, so `server.ts`
|
|
26
|
-
* registers it in its own rate-limited scope rather than `leadsScope`: the
|
|
27
|
-
* web server renders it from one egress address (SEC-7521-D1).
|
|
28
|
-
*/
|
|
29
|
-
import {
|
|
30
|
-
listingDirectoryQuerySchema,
|
|
31
|
-
listingDirectoryResponseSchema,
|
|
32
|
-
projectListingPublicListResponseSchema,
|
|
33
|
-
publicSiteKeySchema,
|
|
34
|
-
} from "@working-theory/validation";
|
|
35
|
-
import type { FastifyPluginAsync } from "fastify";
|
|
36
|
-
import { z } from "zod";
|
|
37
|
-
|
|
38
|
-
import { siteIdForPublicKey } from "../../services/listing-config.js";
|
|
39
|
-
import {
|
|
40
|
-
listingDirectoryFor,
|
|
41
|
-
publicProjectListings,
|
|
42
|
-
} from "../../services/project-listings.js";
|
|
43
|
-
|
|
44
|
-
import { sendValidated } from "./respond.js";
|
|
45
|
-
|
|
46
|
-
const querySchema = z.object({ siteKey: publicSiteKeySchema });
|
|
47
|
-
|
|
48
|
-
export const publicProjectListingsRoute: FastifyPluginAsync = async (fastify) => {
|
|
49
|
-
fastify.get("/v1/project-listings/public", async (request, reply) => {
|
|
50
|
-
const parsedQuery = querySchema.safeParse(request.query);
|
|
51
|
-
if (!parsedQuery.success) {
|
|
52
|
-
return reply.status(400).send({
|
|
53
|
-
ok: false,
|
|
54
|
-
error: { code: "validation_failed", message: "Invalid siteKey" },
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// #5048 D2 — one identifier for the whole mountable contract. The shelves
|
|
59
|
-
// and the application form are configured with the same public key, so a
|
|
60
|
-
// site never has to hold two different names for itself.
|
|
61
|
-
const siteId = await siteIdForPublicKey(parsedQuery.data.siteKey);
|
|
62
|
-
if (siteId === null) {
|
|
63
|
-
return reply.status(400).send({
|
|
64
|
-
ok: false,
|
|
65
|
-
error: { code: "unknown_site_key", message: "Unknown site key" },
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const data = await publicProjectListings(siteId);
|
|
70
|
-
return sendValidated(reply, projectListingPublicListResponseSchema, data, "public");
|
|
71
|
-
});
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
export const projectListingDirectoryRoute: FastifyPluginAsync = async (fastify) => {
|
|
75
|
-
fastify.get("/v1/project-listings/directory", async (request, reply) => {
|
|
76
|
-
const parsedQuery = listingDirectoryQuerySchema.safeParse(request.query);
|
|
77
|
-
if (!parsedQuery.success) {
|
|
78
|
-
return reply.status(400).send({
|
|
79
|
-
ok: false,
|
|
80
|
-
error: { code: "validation_failed", message: "Invalid directory query" },
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// The same one-key resolution `/public` makes: a `SiteListingConfig` row
|
|
85
|
-
// under multi-tenant, the env key under single-tenant (#5048 D2).
|
|
86
|
-
const { siteKey, landingPage, locale } = parsedQuery.data;
|
|
87
|
-
const siteId = await siteIdForPublicKey(siteKey);
|
|
88
|
-
if (siteId === null) {
|
|
89
|
-
return reply.status(400).send({
|
|
90
|
-
ok: false,
|
|
91
|
-
error: { code: "unknown_site_key", message: "Unknown site key" },
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const data = await listingDirectoryFor(siteId, landingPage, locale);
|
|
96
|
-
return sendValidated(reply, listingDirectoryResponseSchema, data, "directory");
|
|
97
|
-
});
|
|
98
|
-
};
|