create-ailk 0.5.0 → 0.6.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 +55 -8
- package/component-catalog.json +336 -27
- package/component-catalog.md +25 -12
- package/dist/cli.js +54 -5
- package/dist/copy.d.ts +14 -0
- package/dist/copy.js +38 -1
- package/dist/lib/api-boot-assertion.d.ts +46 -0
- package/dist/lib/api-boot-assertion.js +70 -0
- package/dist/lib/extract-module-bundle.d.ts +20 -1
- package/dist/lib/extract-module-bundle.js +64 -5
- package/dist/lib/import-graph.d.ts +29 -0
- package/dist/lib/import-graph.js +140 -0
- package/dist/lib/module-license-gate.d.ts +6 -1
- package/dist/lib/module-license-gate.js +6 -1
- package/dist/lib/pro-callout.js +1 -1
- package/dist/module-architecture.d.ts +1 -0
- package/dist/module-architecture.js +253 -127
- package/dist/modules/commerce/0.6.0.tar.gz +0 -0
- package/dist/modules/docs-blog/0.6.0.tar.gz +0 -0
- package/dist/modules/multi-tenant/0.6.0.tar.gz +0 -0
- package/dist/modules/scheduling/0.6.0.tar.gz +0 -0
- package/dist/modules/signal-collectors/0.6.0.tar.gz +0 -0
- package/dist/modules/workspace/0.6.0.tar.gz +0 -0
- package/dist/programmatic.js +14 -10
- package/dist/surfaces.js +105 -7
- package/dist/template-manifest.d.ts +33 -0
- package/dist/template-manifest.js +99 -16
- package/dist/template-names.d.ts +1 -1
- package/dist/template-names.js +1 -0
- package/package.json +3 -2
- package/site-templates/marketing-site/DESIGN.md +148 -0
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +22 -2
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +9 -2
- package/site-templates/marketing-site/ailk-template.json +1 -1
- package/site-templates/marketing-site-free/DESIGN.md +148 -0
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +2 -2
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +1 -0
- package/site-templates/minimalism-site/DESIGN.md +148 -0
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +10 -2
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +47 -35
- package/site-templates/minimalism-site/ailk-template.json +2 -1
- package/site-templates/minimalism-site/assets/wordmark-placeholder.svg +28 -0
- package/site-templates/minimalism-site/content/en/HomePage/home.mdx +49 -19
- package/site-templates/saas-product/DESIGN.md +148 -0
- package/site-templates/simple-service/DESIGN.md +148 -0
- package/site-templates/simple-service/TEMPLATE-MANIFEST.json +240 -0
- package/site-templates/simple-service/TEMPLATE-MANIFEST.md +278 -0
- package/site-templates/simple-service/ailk-template.json +10 -0
- package/site-templates/simple-service/content/en/AboutPage/about.mdx +38 -0
- package/site-templates/simple-service/content/en/ContactPage/contact.mdx +67 -0
- package/site-templates/simple-service/content/en/FAQPage/faq.mdx +56 -0
- package/site-templates/simple-service/content/en/HomePage/home.mdx +85 -0
- package/site-templates/simple-service/content/en/Service/landscape-design.mdx +40 -0
- package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +40 -0
- package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +40 -0
- package/site-templates/simple-service/content/en/ServicesList/services.mdx +72 -0
- package/site-templates/simple-service/provenance.json +4 -0
- package/templates/.env.example +72 -7
- package/templates/CLAUDE.md +2 -3
- package/templates/DESIGN.md +155 -0
- package/templates/README.md +42 -8
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +8 -0
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +12 -0
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +12 -0
- package/templates/apps/api/.env.example +13 -0
- package/templates/apps/api/CLAUDE.md +26 -2
- package/templates/apps/api/jest.config.cjs +3 -0
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +407 -0
- package/templates/apps/api/src/__tests__/module-exclusion.test.ts +83 -7
- package/templates/apps/api/src/__tests__/server.test.ts +471 -0
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +45 -0
- package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +2 -0
- package/templates/apps/api/src/config/index.ts +62 -0
- package/templates/apps/api/src/config/modules.ts +7 -3
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +238 -2
- package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +143 -0
- package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +23 -0
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +125 -0
- package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +20 -0
- package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +317 -0
- package/templates/apps/api/src/lib/__tests__/site.test.ts +238 -0
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +75 -0
- package/templates/apps/api/src/lib/auth-email-budget.ts +160 -0
- package/templates/apps/api/src/lib/content-adapter.ts +57 -31
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +76 -0
- package/templates/apps/api/src/lib/error-serializer.ts +23 -3
- package/templates/apps/api/src/lib/outbound-http.ts +7 -2
- package/templates/apps/api/src/lib/require-commerce-claim.ts +250 -0
- package/templates/apps/api/src/lib/site.ts +208 -9
- package/templates/apps/api/src/lib/stripe.ts +66 -0
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +182 -0
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +42 -0
- package/templates/apps/api/src/middleware/rate-limit.ts +7 -1
- package/templates/apps/api/src/openapi/spec.ts +543 -2
- package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +48 -0
- package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +309 -0
- package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +123 -0
- package/templates/apps/api/src/routes/auth/transactional-email.ts +273 -0
- package/templates/apps/api/src/routes/auth/user.ts +61 -0
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +434 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +888 -0
- package/templates/apps/api/src/routes/checkout/index.ts +34 -0
- package/templates/apps/api/src/routes/checkout/session.ts +139 -0
- package/templates/apps/api/src/routes/checkout/sessions.ts +197 -0
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +554 -0
- package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +108 -0
- package/templates/apps/api/src/routes/content/__tests__/search.test.ts +450 -0
- package/templates/apps/api/src/routes/content/get-page.ts +94 -12
- package/templates/apps/api/src/routes/content/index.ts +13 -73
- package/templates/apps/api/src/routes/content/list-pages.ts +15 -3
- package/templates/apps/api/src/routes/content/rate-limit.ts +15 -6
- package/templates/apps/api/src/routes/content/search.ts +129 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +447 -31
- package/templates/apps/api/src/routes/leads/index.ts +100 -13
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +51 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +351 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +51 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +5 -2
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +4 -1
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +58 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +107 -6
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +87 -2
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +51 -0
- package/templates/apps/api/src/routes/project-listings/start.ts +5 -1
- package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +686 -0
- package/templates/apps/api/src/routes/requests/confirm.ts +601 -0
- package/templates/apps/api/src/routes/requests/index.ts +54 -0
- package/templates/apps/api/src/routes/webhooks/README.md +265 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +749 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +330 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +262 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +285 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +213 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +301 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +648 -0
- package/templates/apps/api/src/routes/webhooks/index.ts +89 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +1399 -0
- package/templates/apps/api/src/server.ts +439 -49
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +39 -0
- package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +217 -40
- package/templates/apps/api/src/services/__tests__/audit-log.test.ts +620 -0
- package/templates/apps/api/src/services/__tests__/consent.test.ts +2 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +300 -5
- package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +20 -0
- package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +368 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +160 -0
- package/templates/apps/api/src/services/__tests__/listing-config.test.ts +166 -0
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +154 -0
- package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +137 -0
- package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +444 -0
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +238 -0
- package/templates/apps/api/src/services/__tests__/verified-email.test.ts +78 -0
- package/templates/apps/api/src/services/abandon-email.ts +8 -2
- package/templates/apps/api/src/services/aeo-score.ts +49 -12
- package/templates/apps/api/src/services/audit-log.ts +668 -0
- package/templates/apps/api/src/services/consent.ts +18 -0
- package/templates/apps/api/src/services/default-tenant.ts +70 -31
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +141 -13
- package/templates/apps/api/src/services/dunning-policy.ts +294 -0
- package/templates/apps/api/src/services/entitlements.ts +80 -0
- package/templates/apps/api/src/services/lead-promotion.ts +13 -14
- package/templates/apps/api/src/services/lead-routing.ts +161 -2
- package/templates/apps/api/src/services/leads-inbox.ts +23 -2
- package/templates/apps/api/src/services/listing-config.ts +27 -9
- package/templates/apps/api/src/services/project-listings.ts +201 -6
- package/templates/apps/api/src/services/public-site-resolver.ts +106 -0
- package/templates/apps/api/src/services/request-confirm.ts +646 -0
- package/templates/apps/api/src/services/revalidation.ts +173 -0
- package/templates/apps/api/src/services/verified-email.ts +71 -0
- package/templates/apps/api/src/vercel-handler.ts +75 -35
- package/templates/apps/mcp/.env.example +17 -0
- package/templates/apps/mcp/CLAUDE.md +57 -24
- package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +6 -0
- package/templates/apps/mcp/__tests__/audit.test.ts +137 -8
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +79 -36
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +111 -2
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +82 -2
- package/templates/apps/mcp/__tests__/http-transport.test.ts +541 -2
- package/templates/apps/mcp/__tests__/index.test.ts +39 -0
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +143 -0
- package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +111 -0
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +65 -0
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +188 -0
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +165 -0
- package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +124 -0
- package/templates/apps/mcp/__tests__/list_pages.test.ts +116 -0
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +7 -1
- package/templates/apps/mcp/__tests__/parity.test.ts +10 -7
- package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +89 -22
- package/templates/apps/mcp/__tests__/read_content.test.ts +322 -1
- package/templates/apps/mcp/__tests__/search_content.test.ts +105 -2
- package/templates/apps/mcp/__tests__/server.test.ts +67 -14
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +252 -0
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +300 -123
- package/templates/apps/mcp/jest.config.cjs +13 -0
- package/templates/apps/mcp/package.json +2 -0
- package/templates/apps/mcp/src/abuse/index.ts +159 -24
- package/templates/apps/mcp/src/config/modules.ts +10 -5
- package/templates/apps/mcp/src/http-transport.ts +102 -29
- package/templates/apps/mcp/src/index.ts +73 -11
- package/templates/apps/mcp/src/lib/audit-single-page.ts +90 -3
- package/templates/apps/mcp/src/lib/commerce-claim.ts +94 -0
- package/templates/apps/mcp/src/lib/content-adapter.ts +113 -0
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +139 -0
- package/templates/apps/mcp/src/lib/prisma.ts +39 -0
- package/templates/apps/mcp/src/lib/public-workspace.ts +133 -0
- package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +59 -0
- package/templates/apps/mcp/src/server.ts +59 -35
- package/templates/apps/mcp/src/tools/audit.ts +26 -2
- package/templates/apps/mcp/src/tools/get_business_hours.ts +4 -3
- package/templates/apps/mcp/src/tools/get_faq.ts +4 -3
- package/templates/apps/mcp/src/tools/get_locations.ts +5 -3
- package/templates/apps/mcp/src/tools/get_offerings.ts +16 -4
- package/templates/apps/mcp/src/tools/get_pricing.ts +25 -6
- package/templates/apps/mcp/src/tools/get_site_info.ts +4 -3
- package/templates/apps/mcp/src/tools/index.ts +6 -4
- package/templates/apps/mcp/src/tools/list_pages.ts +4 -3
- package/templates/apps/mcp/src/tools/purchase.ts +128 -45
- package/templates/apps/mcp/src/tools/read_content.ts +20 -6
- package/templates/apps/mcp/src/tools/search_content.ts +11 -4
- package/templates/apps/web/.env.example +71 -5
- package/templates/apps/web/CLAUDE.md +44 -0
- package/templates/apps/web/PRODUCT.md +86 -0
- package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +18 -2
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +11 -3
- package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +282 -0
- package/templates/apps/web/app/[locale]/(auth)/__tests__/password-reset.test.tsx +515 -0
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +89 -0
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +109 -0
- package/templates/apps/web/app/[locale]/(auth)/layout.tsx +92 -0
- package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +135 -0
- package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +109 -0
- package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/page.test.tsx +48 -2
- package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/page.tsx +20 -6
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +125 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +96 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +101 -0
- package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +102 -0
- package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +87 -0
- package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +20 -10
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +84 -0
- package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +58 -0
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +63 -8
- package/templates/apps/web/app/[locale]/(authed)/layout.tsx +18 -1
- package/templates/apps/web/app/[locale]/cancellation/page.tsx +44 -0
- package/templates/apps/web/app/[locale]/layout.tsx +133 -87
- package/templates/apps/web/app/[locale]/purchase/__tests__/cancel.test.tsx +162 -0
- package/templates/apps/web/app/[locale]/purchase/__tests__/module-scope.test.ts +68 -0
- package/templates/apps/web/app/[locale]/purchase/__tests__/success.test.tsx +361 -0
- package/templates/apps/web/app/[locale]/purchase/cancel/page.tsx +115 -0
- package/templates/apps/web/app/[locale]/purchase/success/page.tsx +307 -0
- package/templates/apps/web/app/[locale]/returns/page.tsx +44 -0
- package/templates/apps/web/app/[locale]/shipping/page.tsx +44 -0
- package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +670 -0
- package/templates/apps/web/app/api/revalidate/route.ts +194 -0
- package/templates/apps/web/auth.ts +407 -8
- package/templates/apps/web/jest.config.cjs +8 -4
- package/templates/apps/web/lib/__tests__/auth-email.test.ts +214 -0
- package/templates/apps/web/lib/__tests__/auth-routes.test.ts +74 -0
- package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +72 -0
- package/templates/apps/web/lib/__tests__/site-theme.test.ts +135 -20
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +18 -1
- package/templates/apps/web/lib/auth-email.ts +318 -0
- package/templates/apps/web/lib/auth-env.ts +85 -0
- package/templates/apps/web/lib/auth-routes.ts +55 -0
- package/templates/apps/web/lib/meeting-slugs.ts +53 -0
- package/templates/apps/web/lib/site-brand.tsx +4 -8
- package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
- package/templates/apps/web/messages/en.json +187 -0
- package/templates/apps/web/middleware.ts +1 -1
- package/templates/apps/web/package.json +2 -1
- package/templates/apps/web/src/lib/commerce-claim.ts +33 -0
- package/templates/apps/web/src/lib/commerce-sections.ts +93 -0
- package/templates/content/_site.mdx +18 -3
- package/templates/content/en/CancellationPolicyPage/cancellation.mdx +108 -0
- package/templates/content/en/ReturnsPolicyPage/returns.mdx +108 -0
- package/templates/content/en/ShippingPolicyPage/shipping.mdx +108 -0
- package/templates/database/CHANGELOG.md +149 -0
- package/templates/database/README.md +10 -3
- package/templates/database/__tests__/appointment.db.test.ts +106 -0
- package/templates/database/__tests__/artifact-content-store.db.test.ts +316 -0
- package/templates/database/__tests__/composite-fence.db.test.ts +206 -0
- package/templates/database/__tests__/fence-fan-out.db.test.ts +711 -0
- package/templates/database/__tests__/fixtures/appointment-create-probe.ts +79 -0
- package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +100 -0
- package/templates/database/__tests__/flow-workspace-key.db.test.ts +263 -0
- package/templates/database/__tests__/prefixed-ids.db.test.ts +85 -9
- package/templates/database/__tests__/project-listing-consent-at.db.test.ts +111 -0
- package/templates/database/__tests__/tenancy.test.ts +141 -0
- package/templates/database/config/schema.prisma +79 -2
- package/templates/database/content/schema.prisma +87 -11
- package/templates/database/inbox/schema.prisma +452 -9
- package/templates/database/jest.config.cjs +5 -3
- package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +15 -0
- package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +158 -0
- package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +40 -0
- package/templates/database/migrations/20260918120000_add_appointment/migration.sql +67 -0
- package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +70 -0
- package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +181 -0
- package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +172 -0
- package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +41 -0
- package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +185 -0
- package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +70 -0
- package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +57 -0
- package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +68 -0
- package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +1067 -0
- package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +210 -0
- package/templates/database/package.json +3 -2
- package/templates/database/tenancy.ts +86 -1
- package/templates/database/tsconfig.json +1 -0
- package/versions.json +34 -34
- package/templates/apps/api/src/routes/content/__tests__/create.test.ts +0 -253
- package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +0 -215
- package/templates/apps/api/src/routes/content/__tests__/update.test.ts +0 -266
- package/templates/apps/api/src/routes/content/create.ts +0 -115
- package/templates/apps/api/src/routes/content/delete.ts +0 -82
- package/templates/apps/api/src/routes/content/update.ts +0 -112
- package/templates/apps/mcp/__tests__/create_page.test.ts +0 -202
- package/templates/apps/mcp/__tests__/delete_page.test.ts +0 -177
- package/templates/apps/mcp/__tests__/update_page.test.ts +0 -196
- package/templates/apps/mcp/src/tools/create_page.ts +0 -96
- package/templates/apps/mcp/src/tools/delete_page.ts +0 -74
- package/templates/apps/mcp/src/tools/update_page.ts +0 -92
- package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +0 -123
- package/templates/apps/web/public/logo-dark.svg +0 -21
- package/templates/apps/web/public/logo-light.svg +0 -21
- /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/next-path.test.ts +0 -0
- /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/next-path.ts +0 -0
|
@@ -19,6 +19,7 @@ import { join } from "node:path";
|
|
|
19
19
|
|
|
20
20
|
import { SCHEMA_ORG_TYPES } from "@working-theory/content-adapters";
|
|
21
21
|
import { logger } from "@working-theory/observability";
|
|
22
|
+
import { SITE_CATEGORIES } from "@working-theory/schema/categories";
|
|
22
23
|
import { parse as parseYaml } from "yaml";
|
|
23
24
|
|
|
24
25
|
import { config } from "../config/index.js";
|
|
@@ -251,6 +252,29 @@ export function readRegisteredRoutes(source: unknown): RegisteredRoute[] {
|
|
|
251
252
|
return Array.isArray(routes) ? (routes as RegisteredRoute[]) : [];
|
|
252
253
|
}
|
|
253
254
|
|
|
255
|
+
/**
|
|
256
|
+
* The `{ ok: false, error: { code, message } }` envelope (apps/api/CLAUDE.md)
|
|
257
|
+
* identity-bound tenant routes answer on 400/401/403 — distinct from the
|
|
258
|
+
* `{ error: string }` shape `ErrorResponse` documents for the anonymous
|
|
259
|
+
* content routes. Shared here so `/v1/schedule/appointments`'s three error
|
|
260
|
+
* responses (#6085) reference one literal rather than three copies.
|
|
261
|
+
*/
|
|
262
|
+
const tenantErrorResponseSchema = {
|
|
263
|
+
type: "object",
|
|
264
|
+
required: ["ok", "error"],
|
|
265
|
+
properties: {
|
|
266
|
+
ok: { type: "boolean", enum: [false] },
|
|
267
|
+
error: {
|
|
268
|
+
type: "object",
|
|
269
|
+
required: ["code", "message"],
|
|
270
|
+
properties: {
|
|
271
|
+
code: { type: "string" },
|
|
272
|
+
message: { type: "string" },
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
} as const;
|
|
277
|
+
|
|
254
278
|
/**
|
|
255
279
|
* Root-index keys this API has already published, by Fastify path (#5314 D6).
|
|
256
280
|
*
|
|
@@ -271,9 +295,13 @@ const CURATED_ROOT_INDEX_KEYS: Record<string, string> = {
|
|
|
271
295
|
"/v1/user/profile": "profile",
|
|
272
296
|
"/v1/user/data/:key": "userData",
|
|
273
297
|
"/v1/content/types": "contentTypes",
|
|
298
|
+
"/v1/content/search": "contentSearch",
|
|
274
299
|
"/v1/content/:type": "contentList",
|
|
275
300
|
"/v1/content/:type/:slug": "contentPage",
|
|
276
301
|
"/v1/checkout/sessions": "checkoutSessions",
|
|
302
|
+
// #6084 §4.2 — one entry, so the root index publishes a stable
|
|
303
|
+
// `checkoutSession` key rather than a `generatedRootIndexKey` fallback.
|
|
304
|
+
"/v1/checkout/sessions/:id": "checkoutSession",
|
|
277
305
|
"/v1/billing": "billing",
|
|
278
306
|
"/v1/billing/portal-session": "billingPortalSession",
|
|
279
307
|
};
|
|
@@ -437,6 +465,9 @@ export function buildOpenApiSpec(
|
|
|
437
465
|
registeredRoutes: RegisteredRoute[] = [],
|
|
438
466
|
) {
|
|
439
467
|
const typeEnum = [...SCHEMA_ORG_TYPES];
|
|
468
|
+
// Derived, never hand-listed: widening the registry widens the documented
|
|
469
|
+
// enum with no second edit here.
|
|
470
|
+
const categoryEnum = SITE_CATEGORIES.map((category) => category.slug);
|
|
440
471
|
|
|
441
472
|
return {
|
|
442
473
|
openapi: "3.1.0",
|
|
@@ -544,6 +575,168 @@ export function buildOpenApiSpec(
|
|
|
544
575
|
},
|
|
545
576
|
},
|
|
546
577
|
},
|
|
578
|
+
// #6084 — the read half, under the SAME commerce gate as the POST
|
|
579
|
+
// above (AC-3). PII-free by construction: the response schema names
|
|
580
|
+
// the D3 six-key allow-list, never a spread.
|
|
581
|
+
"/v1/checkout/sessions/{id}": {
|
|
582
|
+
get: {
|
|
583
|
+
operationId: "getCheckoutSession",
|
|
584
|
+
summary: "Read a Checkout Session's payment status and paid line items",
|
|
585
|
+
description:
|
|
586
|
+
"Reads a Checkout Session's status, payment status, and paid line " +
|
|
587
|
+
"items. Read-only — never creates or mutates a Stripe object. The " +
|
|
588
|
+
"response is PII-free: no customer identity, payment method, or " +
|
|
589
|
+
"metadata field is ever returned.",
|
|
590
|
+
parameters: [
|
|
591
|
+
{
|
|
592
|
+
name: "id",
|
|
593
|
+
in: "path",
|
|
594
|
+
required: true,
|
|
595
|
+
description: "Stripe Checkout Session id (cs_…)",
|
|
596
|
+
schema: { type: "string", pattern: "^cs_[A-Za-z0-9_]{1,250}$" },
|
|
597
|
+
},
|
|
598
|
+
],
|
|
599
|
+
responses: {
|
|
600
|
+
"200": {
|
|
601
|
+
description: "Checkout Session status, payment status, and paid line items",
|
|
602
|
+
content: {
|
|
603
|
+
"application/json": {
|
|
604
|
+
schema: {
|
|
605
|
+
$ref: "#/components/schemas/CheckoutSessionReadResponse",
|
|
606
|
+
},
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
"400": {
|
|
611
|
+
description: "Validation error — the id fails the shape check",
|
|
612
|
+
content: {
|
|
613
|
+
"application/json": {
|
|
614
|
+
schema: {
|
|
615
|
+
$ref: "#/components/schemas/ValidationErrorResponse",
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
},
|
|
619
|
+
},
|
|
620
|
+
"404": {
|
|
621
|
+
description: "Unknown session — no Checkout Session matches the given id",
|
|
622
|
+
content: {
|
|
623
|
+
"application/json": {
|
|
624
|
+
schema: {
|
|
625
|
+
$ref: "#/components/schemas/UnknownSessionResponse",
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
},
|
|
630
|
+
"500": {
|
|
631
|
+
description:
|
|
632
|
+
"Live-mode forbidden — STRIPE_SECRET_KEY is a live key",
|
|
633
|
+
content: {
|
|
634
|
+
"application/json": {
|
|
635
|
+
schema: {
|
|
636
|
+
$ref: "#/components/schemas/LiveModeForbiddenResponse",
|
|
637
|
+
},
|
|
638
|
+
},
|
|
639
|
+
},
|
|
640
|
+
},
|
|
641
|
+
"502": {
|
|
642
|
+
description:
|
|
643
|
+
"Checkout unavailable — Stripe returned an unexpected error",
|
|
644
|
+
content: {
|
|
645
|
+
"application/json": {
|
|
646
|
+
schema: {
|
|
647
|
+
$ref: "#/components/schemas/CheckoutUnavailableResponse",
|
|
648
|
+
},
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
},
|
|
654
|
+
},
|
|
655
|
+
}),
|
|
656
|
+
// Module-conditional (scheduling, #6085 brief H7) — mirrors the
|
|
657
|
+
// `/v1/checkout/sessions` gate immediately above: without it a build
|
|
658
|
+
// with the module off would still advertise the operation while
|
|
659
|
+
// `registeredRoutes` carries no matching entry (surface-drift.test.ts,
|
|
660
|
+
// #5314). The doc comment below deliberately never names the
|
|
661
|
+
// scheduling provider (§ 1.12, § 9 SEC-13) — this file is scanned by
|
|
662
|
+
// `architecture.yaml`'s `cal\.com` forbidden pattern like every other
|
|
663
|
+
// `apps/api/**` file, which is EXCLUDED from it, but the wording
|
|
664
|
+
// itself is written provider-neutral anyway since it documents the
|
|
665
|
+
// AILK-side contract, not the provider's.
|
|
666
|
+
...(isModuleEnabled("scheduling") && {
|
|
667
|
+
"/v1/schedule/appointments": {
|
|
668
|
+
get: {
|
|
669
|
+
operationId: "listAppointments",
|
|
670
|
+
summary: "List this site's own appointments",
|
|
671
|
+
description:
|
|
672
|
+
"The tenant-guarded read over this site's normalized booking " +
|
|
673
|
+
"rows (#6085). Identity-bound: requires an authenticated " +
|
|
674
|
+
"session carrying an owner|admin role on the active workspace " +
|
|
675
|
+
"— the rows carry attendee contact PII. `window` selects " +
|
|
676
|
+
"upcoming (the default), past, or all; `cursor` is the " +
|
|
677
|
+
"previous page's last row id.",
|
|
678
|
+
parameters: [
|
|
679
|
+
{
|
|
680
|
+
name: "limit",
|
|
681
|
+
in: "query",
|
|
682
|
+
required: false,
|
|
683
|
+
schema: { type: "integer", minimum: 1, maximum: 200, default: 50 },
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
name: "cursor",
|
|
687
|
+
in: "query",
|
|
688
|
+
required: false,
|
|
689
|
+
description: "The previous page's last row id.",
|
|
690
|
+
schema: { type: "string" },
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
name: "window",
|
|
694
|
+
in: "query",
|
|
695
|
+
required: false,
|
|
696
|
+
schema: {
|
|
697
|
+
type: "string",
|
|
698
|
+
enum: ["upcoming", "past", "all"],
|
|
699
|
+
default: "upcoming",
|
|
700
|
+
},
|
|
701
|
+
},
|
|
702
|
+
],
|
|
703
|
+
responses: {
|
|
704
|
+
"200": {
|
|
705
|
+
description: "OK",
|
|
706
|
+
content: {
|
|
707
|
+
"application/json": {
|
|
708
|
+
schema: { $ref: "#/components/schemas/AppointmentListResponse" },
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
},
|
|
712
|
+
// The `{ ok: false, error: { code, message } }` shape
|
|
713
|
+
// (apps/api/CLAUDE.md) — distinct from the `{ error }` string
|
|
714
|
+
// shape `ErrorResponse` documents for the anonymous content
|
|
715
|
+
// routes, so 400/401/403 each get this inline schema rather
|
|
716
|
+
// than `$ref`ing that one.
|
|
717
|
+
"400": {
|
|
718
|
+
description: "Invalid query parameters, or an unresolvable cursor",
|
|
719
|
+
content: {
|
|
720
|
+
"application/json": { schema: tenantErrorResponseSchema },
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
"401": {
|
|
724
|
+
description: "Authentication required",
|
|
725
|
+
content: {
|
|
726
|
+
"application/json": { schema: tenantErrorResponseSchema },
|
|
727
|
+
},
|
|
728
|
+
},
|
|
729
|
+
"403": {
|
|
730
|
+
description: "Forbidden — caller lacks the owner|admin role floor",
|
|
731
|
+
content: {
|
|
732
|
+
"application/json": { schema: tenantErrorResponseSchema },
|
|
733
|
+
},
|
|
734
|
+
},
|
|
735
|
+
"429": { $ref: "#/components/responses/RateLimited" },
|
|
736
|
+
"500": { $ref: "#/components/responses/ServerError" },
|
|
737
|
+
},
|
|
738
|
+
},
|
|
739
|
+
},
|
|
547
740
|
}),
|
|
548
741
|
"/v1/leads/submit": {
|
|
549
742
|
post: {
|
|
@@ -658,6 +851,150 @@ export function buildOpenApiSpec(
|
|
|
658
851
|
},
|
|
659
852
|
},
|
|
660
853
|
},
|
|
854
|
+
"/v1/requests/{id}/confirm": {
|
|
855
|
+
get: {
|
|
856
|
+
operationId: "getRequestConfirmForm",
|
|
857
|
+
summary: "Open a callback request's confirm form",
|
|
858
|
+
description:
|
|
859
|
+
"Renders the site owner's confirm form for one callback request " +
|
|
860
|
+
"(#5970). The `token` query parameter is a one-time, expiring " +
|
|
861
|
+
"credential emailed to the owner when the request landed; it is " +
|
|
862
|
+
"the sole gate on this route, so there is no auth header. This " +
|
|
863
|
+
"operation answers `text/html` rather than JSON — it is a page a " +
|
|
864
|
+
"person opens from a mail client, not an agent surface. A token " +
|
|
865
|
+
"that is tampered with, expired, already used, or valid for a " +
|
|
866
|
+
"different request all answer the same 404. Requires the " +
|
|
867
|
+
"`commerce` licence claim; without it the route answers 404, " +
|
|
868
|
+
"indistinguishable from a build without the module.",
|
|
869
|
+
parameters: [
|
|
870
|
+
{
|
|
871
|
+
name: "id",
|
|
872
|
+
in: "path",
|
|
873
|
+
required: true,
|
|
874
|
+
schema: { type: "string" },
|
|
875
|
+
description: "The callback request's id.",
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
name: "token",
|
|
879
|
+
in: "query",
|
|
880
|
+
required: true,
|
|
881
|
+
schema: { type: "string" },
|
|
882
|
+
description: "The one-time confirm credential from the email.",
|
|
883
|
+
},
|
|
884
|
+
],
|
|
885
|
+
responses: {
|
|
886
|
+
"200": {
|
|
887
|
+
description: "The confirm form",
|
|
888
|
+
content: { "text/html": { schema: { type: "string" } } },
|
|
889
|
+
},
|
|
890
|
+
"404": {
|
|
891
|
+
description:
|
|
892
|
+
"No live request for that id and token — or the module is not " +
|
|
893
|
+
"licensed",
|
|
894
|
+
content: { "text/html": { schema: { type: "string" } } },
|
|
895
|
+
},
|
|
896
|
+
},
|
|
897
|
+
},
|
|
898
|
+
post: {
|
|
899
|
+
operationId: "confirmRequest",
|
|
900
|
+
summary: "Confirm a callback request and send a payment link",
|
|
901
|
+
description:
|
|
902
|
+
"Stamps the confirmed time, creates one Stripe Payment Link on " +
|
|
903
|
+
"the site's own Stripe account, stores its id on the request, and " +
|
|
904
|
+
"emails the customer that hosted URL (#5970). The credential is " +
|
|
905
|
+
"spent in the same statement that stamps the confirmation, so a " +
|
|
906
|
+
"second submission answers 404 and no second Payment Link is ever " +
|
|
907
|
+
"created. The body carries EITHER `amountMinor` + `currency` (a " +
|
|
908
|
+
"price set here and now) OR `lookupKey` (a price already in the " +
|
|
909
|
+
"site's Stripe catalogue) — both, or neither, is a 400 before any " +
|
|
910
|
+
"Stripe call. Sandbox-only: a live Stripe key is refused before " +
|
|
911
|
+
"the credential is claimed.",
|
|
912
|
+
parameters: [
|
|
913
|
+
{
|
|
914
|
+
name: "id",
|
|
915
|
+
in: "path",
|
|
916
|
+
required: true,
|
|
917
|
+
schema: { type: "string" },
|
|
918
|
+
description: "The callback request's id.",
|
|
919
|
+
},
|
|
920
|
+
],
|
|
921
|
+
requestBody: {
|
|
922
|
+
required: true,
|
|
923
|
+
content: {
|
|
924
|
+
"application/x-www-form-urlencoded": {
|
|
925
|
+
schema: {
|
|
926
|
+
type: "object",
|
|
927
|
+
required: ["token", "confirmedTime", "buyerEmail"],
|
|
928
|
+
properties: {
|
|
929
|
+
token: {
|
|
930
|
+
type: "string",
|
|
931
|
+
description: "The one-time confirm credential.",
|
|
932
|
+
},
|
|
933
|
+
confirmedTime: {
|
|
934
|
+
type: "string",
|
|
935
|
+
description:
|
|
936
|
+
"The agreed time, as the site owner typed it.",
|
|
937
|
+
},
|
|
938
|
+
buyerEmail: {
|
|
939
|
+
type: "string",
|
|
940
|
+
format: "email",
|
|
941
|
+
description: "Where the payment link is sent.",
|
|
942
|
+
},
|
|
943
|
+
amountMinor: {
|
|
944
|
+
type: "integer",
|
|
945
|
+
minimum: 1,
|
|
946
|
+
description:
|
|
947
|
+
"The price in MINOR units. Mutually exclusive with " +
|
|
948
|
+
"lookupKey, and capped by AILK_REQUEST_CONFIRM_MAX_MINOR.",
|
|
949
|
+
},
|
|
950
|
+
currency: {
|
|
951
|
+
type: "string",
|
|
952
|
+
description:
|
|
953
|
+
"Lowercase ISO-4217. Defaults to the site's configured " +
|
|
954
|
+
"currency. Only meaningful beside amountMinor.",
|
|
955
|
+
},
|
|
956
|
+
lookupKey: {
|
|
957
|
+
type: "string",
|
|
958
|
+
description:
|
|
959
|
+
"An existing Stripe price's lookup_key. Mutually " +
|
|
960
|
+
"exclusive with amountMinor.",
|
|
961
|
+
},
|
|
962
|
+
},
|
|
963
|
+
},
|
|
964
|
+
},
|
|
965
|
+
},
|
|
966
|
+
},
|
|
967
|
+
responses: {
|
|
968
|
+
"200": {
|
|
969
|
+
description:
|
|
970
|
+
"Confirmed — the page carries the hosted Stripe payment URL",
|
|
971
|
+
content: { "text/html": { schema: { type: "string" } } },
|
|
972
|
+
},
|
|
973
|
+
"400": {
|
|
974
|
+
description:
|
|
975
|
+
"Invalid form, both or neither price path, an amount above the " +
|
|
976
|
+
"site's ceiling, or a lookup key no active Stripe price matches",
|
|
977
|
+
content: { "text/html": { schema: { type: "string" } } },
|
|
978
|
+
},
|
|
979
|
+
"404": {
|
|
980
|
+
description:
|
|
981
|
+
"No live request for that id and token — or the module is not " +
|
|
982
|
+
"licensed",
|
|
983
|
+
content: { "text/html": { schema: { type: "string" } } },
|
|
984
|
+
},
|
|
985
|
+
"500": {
|
|
986
|
+
description: "Stripe is not configured, or the key is not test-mode",
|
|
987
|
+
content: { "text/html": { schema: { type: "string" } } },
|
|
988
|
+
},
|
|
989
|
+
"502": {
|
|
990
|
+
description:
|
|
991
|
+
"Stripe could not create the Payment Link. The request is " +
|
|
992
|
+
"confirmed; the credential is spent.",
|
|
993
|
+
content: { "text/html": { schema: { type: "string" } } },
|
|
994
|
+
},
|
|
995
|
+
},
|
|
996
|
+
},
|
|
997
|
+
},
|
|
661
998
|
"/v1/leads/callback": {
|
|
662
999
|
post: {
|
|
663
1000
|
operationId: "requestCallback",
|
|
@@ -733,6 +1070,70 @@ export function buildOpenApiSpec(
|
|
|
733
1070
|
},
|
|
734
1071
|
},
|
|
735
1072
|
},
|
|
1073
|
+
"/v1/content/search": {
|
|
1074
|
+
get: {
|
|
1075
|
+
operationId: "searchContent",
|
|
1076
|
+
summary: "Search published pages by keyword",
|
|
1077
|
+
description:
|
|
1078
|
+
"Keyword search across published pages, scored on title, description and body and returned highest-first. The HTTP twin of the `search_content` MCP tool — the same arguments return the same result set. Optional `category` filters to pages whose `categories` frontmatter includes the given LEAF slug from the closed site-category registry; unlike `locale`, a slug outside the registry is a 400 rather than a silent fallback. Optional locale query parameter selects the locale to search; invalid or unknown locales silently fall back to the project's default locale (AC-i18n-07).",
|
|
1079
|
+
parameters: [
|
|
1080
|
+
{
|
|
1081
|
+
name: "q",
|
|
1082
|
+
in: "query",
|
|
1083
|
+
required: true,
|
|
1084
|
+
description:
|
|
1085
|
+
"The search string. 1–200 characters; the ceiling matches the `search_content` MCP tool's own cap.",
|
|
1086
|
+
schema: { type: "string", minLength: 1, maxLength: 200 },
|
|
1087
|
+
},
|
|
1088
|
+
{
|
|
1089
|
+
name: "type",
|
|
1090
|
+
in: "query",
|
|
1091
|
+
required: false,
|
|
1092
|
+
schema: { type: "string", enum: typeEnum },
|
|
1093
|
+
},
|
|
1094
|
+
{
|
|
1095
|
+
name: "category",
|
|
1096
|
+
in: "query",
|
|
1097
|
+
required: false,
|
|
1098
|
+
description:
|
|
1099
|
+
"A leaf slug from the site-category registry. Group slugs are not valid values — a group is always derived from its leaves.",
|
|
1100
|
+
schema: { type: "string", enum: categoryEnum },
|
|
1101
|
+
},
|
|
1102
|
+
{
|
|
1103
|
+
name: "limit",
|
|
1104
|
+
in: "query",
|
|
1105
|
+
required: false,
|
|
1106
|
+
schema: {
|
|
1107
|
+
type: "integer",
|
|
1108
|
+
minimum: 1,
|
|
1109
|
+
maximum: 50,
|
|
1110
|
+
default: 10,
|
|
1111
|
+
},
|
|
1112
|
+
},
|
|
1113
|
+
{
|
|
1114
|
+
name: "locale",
|
|
1115
|
+
in: "query",
|
|
1116
|
+
required: false,
|
|
1117
|
+
description:
|
|
1118
|
+
"BCP-47 locale tag (e.g. `en`, `es`, `zh-CN`). Invalid or unknown locales silently fall back to the project's default locale.",
|
|
1119
|
+
schema: { type: "string", pattern: "^[a-z]{2}(-[A-Z]{2})?$" },
|
|
1120
|
+
},
|
|
1121
|
+
],
|
|
1122
|
+
responses: {
|
|
1123
|
+
"200": {
|
|
1124
|
+
description: "OK",
|
|
1125
|
+
content: {
|
|
1126
|
+
"application/json": {
|
|
1127
|
+
schema: { $ref: "#/components/schemas/SearchResponse" },
|
|
1128
|
+
},
|
|
1129
|
+
},
|
|
1130
|
+
},
|
|
1131
|
+
"400": { $ref: "#/components/responses/BadRequest" },
|
|
1132
|
+
"429": { $ref: "#/components/responses/RateLimited" },
|
|
1133
|
+
"500": { $ref: "#/components/responses/ServerError" },
|
|
1134
|
+
},
|
|
1135
|
+
},
|
|
1136
|
+
},
|
|
736
1137
|
"/v1/content/{type}": {
|
|
737
1138
|
get: {
|
|
738
1139
|
operationId: "listPagesByType",
|
|
@@ -792,7 +1193,7 @@ export function buildOpenApiSpec(
|
|
|
792
1193
|
operationId: "getPageBySlug",
|
|
793
1194
|
summary: "Get a single page by type and slug",
|
|
794
1195
|
description:
|
|
795
|
-
"Returns a single page with resolved schema.org JSON-LD spread at the document root, plus the raw frontmatter under `page`. Optional locale query parameter selects the locale to read from; invalid or unknown locales silently fall back to the project's default locale (AC-i18n-07).",
|
|
1196
|
+
"Returns a single page with resolved schema.org JSON-LD spread at the document root, plus the raw frontmatter under `page`. Optional locale query parameter selects the locale to read from; invalid or unknown locales silently fall back to the project's default locale (AC-i18n-07). Optional `include=body` adds the raw published MDX body under `body`; a deployment whose content adapter cannot serve a raw body answers 400.",
|
|
796
1197
|
parameters: [
|
|
797
1198
|
{
|
|
798
1199
|
name: "type",
|
|
@@ -814,6 +1215,14 @@ export function buildOpenApiSpec(
|
|
|
814
1215
|
"BCP-47 locale tag (e.g. `en`, `es`, `zh-CN`). Invalid or unknown locales silently fall back to the project's default locale.",
|
|
815
1216
|
schema: { type: "string", pattern: "^[a-z]{2}(-[A-Z]{2})?$" },
|
|
816
1217
|
},
|
|
1218
|
+
{
|
|
1219
|
+
name: "include",
|
|
1220
|
+
in: "query",
|
|
1221
|
+
required: false,
|
|
1222
|
+
description:
|
|
1223
|
+
"Opt-in extra field. `body` adds the raw published MDX body under `body`; omitting the parameter serves the base shape unchanged (#5890).",
|
|
1224
|
+
schema: { type: "string", enum: ["body"] },
|
|
1225
|
+
},
|
|
817
1226
|
],
|
|
818
1227
|
responses: {
|
|
819
1228
|
"200": {
|
|
@@ -915,6 +1324,67 @@ export function buildOpenApiSpec(
|
|
|
915
1324
|
error: { type: "string", enum: ["checkout-unavailable"] },
|
|
916
1325
|
},
|
|
917
1326
|
},
|
|
1327
|
+
// #6084 D3 — the six-key allow-list. Nothing else may appear here;
|
|
1328
|
+
// AC-3's own test asserts set equality against exactly these names.
|
|
1329
|
+
CheckoutSessionReadResponse: {
|
|
1330
|
+
type: "object",
|
|
1331
|
+
required: [
|
|
1332
|
+
"id",
|
|
1333
|
+
"status",
|
|
1334
|
+
"paymentStatus",
|
|
1335
|
+
"currency",
|
|
1336
|
+
"amountTotal",
|
|
1337
|
+
"lineItems",
|
|
1338
|
+
],
|
|
1339
|
+
properties: {
|
|
1340
|
+
id: {
|
|
1341
|
+
type: "string",
|
|
1342
|
+
description: "The echoed cs_… id, for the buyer to quote to support",
|
|
1343
|
+
},
|
|
1344
|
+
status: {
|
|
1345
|
+
type: "string",
|
|
1346
|
+
enum: ["open", "complete", "expired"],
|
|
1347
|
+
},
|
|
1348
|
+
paymentStatus: {
|
|
1349
|
+
type: "string",
|
|
1350
|
+
enum: ["paid", "unpaid", "no_payment_required"],
|
|
1351
|
+
},
|
|
1352
|
+
currency: {
|
|
1353
|
+
type: "string",
|
|
1354
|
+
description: "ISO-4217 currency code, lowercase",
|
|
1355
|
+
},
|
|
1356
|
+
amountTotal: {
|
|
1357
|
+
type: ["integer", "null"],
|
|
1358
|
+
description: "Session total, in minor currency units",
|
|
1359
|
+
},
|
|
1360
|
+
lineItems: {
|
|
1361
|
+
type: "array",
|
|
1362
|
+
items: {
|
|
1363
|
+
type: "object",
|
|
1364
|
+
required: ["description", "quantity", "amountTotal", "currency"],
|
|
1365
|
+
properties: {
|
|
1366
|
+
description: {
|
|
1367
|
+
type: "string",
|
|
1368
|
+
description: "Stripe's own line-item description (the product name)",
|
|
1369
|
+
},
|
|
1370
|
+
quantity: { type: "integer" },
|
|
1371
|
+
amountTotal: {
|
|
1372
|
+
type: "integer",
|
|
1373
|
+
description: "Line total, in minor currency units",
|
|
1374
|
+
},
|
|
1375
|
+
currency: { type: "string" },
|
|
1376
|
+
},
|
|
1377
|
+
},
|
|
1378
|
+
},
|
|
1379
|
+
},
|
|
1380
|
+
},
|
|
1381
|
+
UnknownSessionResponse: {
|
|
1382
|
+
type: "object",
|
|
1383
|
+
required: ["error"],
|
|
1384
|
+
properties: {
|
|
1385
|
+
error: { type: "string", enum: ["unknown-session"] },
|
|
1386
|
+
},
|
|
1387
|
+
},
|
|
918
1388
|
LeadSubmitRequest: {
|
|
919
1389
|
type: "object",
|
|
920
1390
|
additionalProperties: false,
|
|
@@ -1085,7 +1555,32 @@ export function buildOpenApiSpec(
|
|
|
1085
1555
|
},
|
|
1086
1556
|
},
|
|
1087
1557
|
},
|
|
1088
|
-
|
|
1558
|
+
SearchResult: {
|
|
1559
|
+
type: "object",
|
|
1560
|
+
required: ["slug", "type", "title", "snippet", "score"],
|
|
1561
|
+
properties: {
|
|
1562
|
+
slug: { type: "string" },
|
|
1563
|
+
type: { type: "string", enum: typeEnum },
|
|
1564
|
+
title: { type: "string" },
|
|
1565
|
+
snippet: { type: "string" },
|
|
1566
|
+
score: { type: "number" },
|
|
1567
|
+
},
|
|
1568
|
+
},
|
|
1569
|
+
SearchResponse: {
|
|
1570
|
+
type: "object",
|
|
1571
|
+
required: ["@context", "@type", "query", "items", "totalItems"],
|
|
1572
|
+
properties: {
|
|
1573
|
+
"@context": { type: "string", enum: ["https://schema.org"] },
|
|
1574
|
+
"@type": { type: "string", enum: ["ItemList"] },
|
|
1575
|
+
query: { type: "string" },
|
|
1576
|
+
items: {
|
|
1577
|
+
type: "array",
|
|
1578
|
+
items: { $ref: "#/components/schemas/SearchResult" },
|
|
1579
|
+
},
|
|
1580
|
+
totalItems: { type: "integer", minimum: 0 },
|
|
1581
|
+
},
|
|
1582
|
+
},
|
|
1583
|
+
ListResponse: {
|
|
1089
1584
|
type: "object",
|
|
1090
1585
|
required: ["@context", "@type", "type", "items", "pagination"],
|
|
1091
1586
|
properties: {
|
|
@@ -1106,9 +1601,55 @@ export function buildOpenApiSpec(
|
|
|
1106
1601
|
"@context": { type: "string", enum: ["https://schema.org"] },
|
|
1107
1602
|
"@type": { type: "string" },
|
|
1108
1603
|
page: { $ref: "#/components/schemas/ContentPage" },
|
|
1604
|
+
body: {
|
|
1605
|
+
type: "string",
|
|
1606
|
+
description:
|
|
1607
|
+
"Raw published MDX body. Present only when the request carried `include=body` (#5890).",
|
|
1608
|
+
},
|
|
1109
1609
|
},
|
|
1110
1610
|
additionalProperties: true,
|
|
1111
1611
|
},
|
|
1612
|
+
Appointment: {
|
|
1613
|
+
type: "object",
|
|
1614
|
+
required: [
|
|
1615
|
+
"id",
|
|
1616
|
+
"startTime",
|
|
1617
|
+
"endTime",
|
|
1618
|
+
"title",
|
|
1619
|
+
"attendeeName",
|
|
1620
|
+
"attendeeEmail",
|
|
1621
|
+
"status",
|
|
1622
|
+
"paid",
|
|
1623
|
+
],
|
|
1624
|
+
properties: {
|
|
1625
|
+
id: { type: "string" },
|
|
1626
|
+
startTime: { type: "string", format: "date-time" },
|
|
1627
|
+
endTime: { type: "string", format: "date-time" },
|
|
1628
|
+
title: { type: "string", nullable: true },
|
|
1629
|
+
attendeeName: { type: "string" },
|
|
1630
|
+
attendeeEmail: { type: "string", format: "email" },
|
|
1631
|
+
status: { type: "string", enum: ["pending", "confirmed", "cancelled"] },
|
|
1632
|
+
paid: { type: "boolean" },
|
|
1633
|
+
},
|
|
1634
|
+
},
|
|
1635
|
+
AppointmentListResponse: {
|
|
1636
|
+
type: "object",
|
|
1637
|
+
required: ["ok", "data"],
|
|
1638
|
+
properties: {
|
|
1639
|
+
ok: { type: "boolean", enum: [true] },
|
|
1640
|
+
data: {
|
|
1641
|
+
type: "object",
|
|
1642
|
+
required: ["appointments", "nextCursor"],
|
|
1643
|
+
properties: {
|
|
1644
|
+
appointments: {
|
|
1645
|
+
type: "array",
|
|
1646
|
+
items: { $ref: "#/components/schemas/Appointment" },
|
|
1647
|
+
},
|
|
1648
|
+
nextCursor: { type: "string", nullable: true },
|
|
1649
|
+
},
|
|
1650
|
+
},
|
|
1651
|
+
},
|
|
1652
|
+
},
|
|
1112
1653
|
ErrorResponse: {
|
|
1113
1654
|
type: "object",
|
|
1114
1655
|
required: ["error"],
|
|
@@ -176,6 +176,54 @@ describe("user routes — functional via dual-mode preHandler", () => {
|
|
|
176
176
|
});
|
|
177
177
|
});
|
|
178
178
|
|
|
179
|
+
describe("reserved user-data keys — #6034 § D12", () => {
|
|
180
|
+
it.each([
|
|
181
|
+
["GET", "commerce.subscription_dunning"],
|
|
182
|
+
["PUT", "commerce.subscription_dunning"],
|
|
183
|
+
["DELETE", "commerce.subscription_dunning"],
|
|
184
|
+
["GET", "Commerce.Subscription_Dunning"],
|
|
185
|
+
["GET", "system.anything"],
|
|
186
|
+
])("%s /api/user/data/%s → 403 before any Prisma call", async (method, key) => {
|
|
187
|
+
withBearer("user_bear");
|
|
188
|
+
|
|
189
|
+
app = await buildApp();
|
|
190
|
+
const res = await app.inject({
|
|
191
|
+
method: method as "GET" | "PUT" | "DELETE",
|
|
192
|
+
url: `/v1/user/data/${key}`,
|
|
193
|
+
headers: { authorization: "Bearer tok_live_xx" },
|
|
194
|
+
payload: method === "PUT" ? { status: "active" } : undefined,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
expect(res.statusCode).toBe(403);
|
|
198
|
+
expect(JSON.parse(res.body)).toEqual({
|
|
199
|
+
success: false,
|
|
200
|
+
error: "reserved-key",
|
|
201
|
+
});
|
|
202
|
+
expect(prisma.userData.findUnique).not.toHaveBeenCalled();
|
|
203
|
+
expect(prisma.userData.upsert).not.toHaveBeenCalled();
|
|
204
|
+
expect(prisma.userData.deleteMany).not.toHaveBeenCalled();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("the unreserved `prefs` path is unchanged", async () => {
|
|
208
|
+
withBearer("user_bear");
|
|
209
|
+
(prisma.userData.findUnique as jest.Mock).mockResolvedValue({
|
|
210
|
+
value: { theme: "dark" },
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
app = await buildApp();
|
|
214
|
+
const res = await app.inject({
|
|
215
|
+
method: "GET",
|
|
216
|
+
url: "/v1/user/data/prefs",
|
|
217
|
+
headers: { authorization: "Bearer tok_live_xx" },
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
expect(res.statusCode).toBe(200);
|
|
221
|
+
expect(prisma.userData.findUnique).toHaveBeenCalledWith({
|
|
222
|
+
where: { userId_key: { userId: "user_bear", key: "prefs" } },
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
179
227
|
describe("unauthenticated", () => {
|
|
180
228
|
it.each([
|
|
181
229
|
["GET", "/v1/user/profile", undefined],
|