create-ailk 0.1.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/LICENSE +201 -0
- package/README.md +39 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +80 -0
- package/dist/copy.d.ts +8 -0
- package/dist/copy.js +109 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/lib/pro-callout.d.ts +12 -0
- package/dist/lib/pro-callout.js +20 -0
- package/dist/parse-args.d.ts +13 -0
- package/dist/parse-args.js +81 -0
- package/dist/programmatic.d.ts +49 -0
- package/dist/programmatic.js +89 -0
- package/dist/substitute.d.ts +31 -0
- package/dist/substitute.js +45 -0
- package/dist/validate.d.ts +8 -0
- package/dist/validate.js +70 -0
- package/package.json +47 -0
- package/templates/.claude/.gitkeep +0 -0
- package/templates/.claude/agents/web.md +80 -0
- package/templates/.claude/rules/architecture.md +62 -0
- package/templates/.claude/rules/preserve-existing-style.md +51 -0
- package/templates/.claude/skills/README.md +43 -0
- package/templates/.claude/skills/add-schema/SKILL.md +270 -0
- package/templates/.claude/skills/add-schema/references/conflict-handling.md +200 -0
- package/templates/.claude/skills/add-schema/references/file-finding.md +123 -0
- package/templates/.claude/skills/add-schema/references/schema-types.md +131 -0
- package/templates/.claude/skills/audit-site/SKILL.md +213 -0
- package/templates/.claude/skills/audit-site/references/common-errors.md +113 -0
- package/templates/.claude/skills/audit-site/references/findings-glossary.md +138 -0
- package/templates/.claude/skills/audit-site/references/severity-levels.md +114 -0
- package/templates/.claude/skills/provision-config/SKILL.md +243 -0
- package/templates/.claude/skills/provision-config/references/env-vars-template.md +184 -0
- package/templates/.claude/skills/provision-config/references/runbook-template.md +248 -0
- package/templates/.claude/skills/provision-config/references/vercel-json-template.md +98 -0
- package/templates/.claude/skills/scaffold-auth/SKILL.md +261 -0
- package/templates/.claude/skills/scaffold-auth/references/env-vars.md +91 -0
- package/templates/.claude/skills/scaffold-auth/references/oauth-app-setup.md +171 -0
- package/templates/.claude/skills/scaffold-commerce/SKILL.md +310 -0
- package/templates/.claude/skills/scaffold-commerce/references/lookup-keys-template.md +71 -0
- package/templates/.claude/skills/scaffold-commerce/references/marketplace-brand-pages.md +83 -0
- package/templates/.claude/skills/scaffold-commerce/templates/marketplace-cta-route.tsx.tmpl +72 -0
- package/templates/.claude/skills/scaffold-page/SKILL.md +243 -0
- package/templates/.claude/skills/scaffold-page/references/page-types.md +169 -0
- package/templates/.claude/skills/scaffold-page/templates/content.mdx.tmpl +28 -0
- package/templates/.claude/skills/scaffold-page/templates/route.tsx.tmpl +28 -0
- package/templates/.claude/skills/suggest-site-pages/SKILL.md +242 -0
- package/templates/.claude/skills/suggest-site-pages/references/categories.md +287 -0
- package/templates/.env.example +205 -0
- package/templates/.gitkeep +2 -0
- package/templates/CLAUDE.md +142 -0
- package/templates/CONVENTIONS.md +222 -0
- package/templates/README.md +110 -0
- package/templates/apps/api/.env.example +40 -0
- package/templates/apps/api/CLAUDE.md +63 -0
- package/templates/apps/api/api/index.ts +144 -0
- package/templates/apps/api/jest.config.cjs +74 -0
- package/templates/apps/api/package.json +54 -0
- package/templates/apps/api/src/__tests__/factories/content-fixture.ts +138 -0
- package/templates/apps/api/src/__tests__/factories/index.ts +70 -0
- package/templates/apps/api/src/__tests__/factories/lead-fixture.ts +82 -0
- package/templates/apps/api/src/__tests__/index.test.ts +118 -0
- package/templates/apps/api/src/__tests__/server.test.ts +207 -0
- package/templates/apps/api/src/__tests__/services/enrichment-shim.test.ts +126 -0
- package/templates/apps/api/src/__tests__/setup.ts +24 -0
- package/templates/apps/api/src/cli.ts +30 -0
- package/templates/apps/api/src/config/__tests__/index.test.ts +97 -0
- package/templates/apps/api/src/config/index.ts +56 -0
- package/templates/apps/api/src/index.ts +32 -0
- package/templates/apps/api/src/lib/__mocks__/prisma-client.js +15 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +45 -0
- package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +178 -0
- package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +100 -0
- package/templates/apps/api/src/lib/__tests__/errors.test.ts +94 -0
- package/templates/apps/api/src/lib/__tests__/file-fallback.test.ts +144 -0
- package/templates/apps/api/src/lib/__tests__/fixtures/HomePage/home.mdx +8 -0
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +227 -0
- package/templates/apps/api/src/lib/__tests__/outbound-http.test.ts +125 -0
- package/templates/apps/api/src/lib/__tests__/prisma.test.ts +165 -0
- package/templates/apps/api/src/lib/__tests__/require-paid-tier.test.ts +129 -0
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +74 -0
- package/templates/apps/api/src/lib/content-adapter.ts +86 -0
- package/templates/apps/api/src/lib/error-serializer.ts +77 -0
- package/templates/apps/api/src/lib/errors.ts +95 -0
- package/templates/apps/api/src/lib/file-fallback.ts +87 -0
- package/templates/apps/api/src/lib/lead-capture-store.ts +110 -0
- package/templates/apps/api/src/lib/locales.ts +201 -0
- package/templates/apps/api/src/lib/outbound-http.ts +122 -0
- package/templates/apps/api/src/lib/prisma.ts +21 -0
- package/templates/apps/api/src/lib/require-paid-tier.ts +53 -0
- package/templates/apps/api/src/lib/stripe.ts +30 -0
- package/templates/apps/api/src/lib/webhook-idempotency.ts +92 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/bearer-token-format.test.ts +111 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/cookie-or-bearer-precedence.test.ts +172 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-bearer-skip.test.ts +119 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-cookie-path.test.ts +138 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +149 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-timeout.test.ts +139 -0
- package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +175 -0
- package/templates/apps/api/src/middleware/__tests__/auth.both-present-precedence.test.ts +76 -0
- package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +153 -0
- package/templates/apps/api/src/middleware/__tests__/auth.neither-present.test.ts +72 -0
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +292 -0
- package/templates/apps/api/src/middleware/auth.ts +246 -0
- package/templates/apps/api/src/middleware/rate-limit.ts +132 -0
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +129 -0
- package/templates/apps/api/src/openapi/index.ts +12 -0
- package/templates/apps/api/src/openapi/spec.ts +408 -0
- package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +193 -0
- package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +201 -0
- package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +294 -0
- package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +486 -0
- package/templates/apps/api/src/routes/auth/tokens.ts +199 -0
- package/templates/apps/api/src/routes/auth/user.ts +244 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +588 -0
- package/templates/apps/api/src/routes/checkout/index.ts +13 -0
- package/templates/apps/api/src/routes/checkout/sessions.ts +185 -0
- package/templates/apps/api/src/routes/content/__tests__/create.test.ts +239 -0
- package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +201 -0
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +141 -0
- package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +122 -0
- package/templates/apps/api/src/routes/content/__tests__/list-types.test.ts +44 -0
- package/templates/apps/api/src/routes/content/__tests__/locale.test.ts +118 -0
- package/templates/apps/api/src/routes/content/__tests__/rate-limit.test.ts +51 -0
- package/templates/apps/api/src/routes/content/__tests__/update.test.ts +252 -0
- package/templates/apps/api/src/routes/content/create.ts +114 -0
- package/templates/apps/api/src/routes/content/delete.ts +81 -0
- package/templates/apps/api/src/routes/content/get-page.ts +98 -0
- package/templates/apps/api/src/routes/content/index.ts +32 -0
- package/templates/apps/api/src/routes/content/list-pages.ts +83 -0
- package/templates/apps/api/src/routes/content/list-types.ts +19 -0
- package/templates/apps/api/src/routes/content/rate-limit.ts +13 -0
- package/templates/apps/api/src/routes/content/slug.ts +9 -0
- package/templates/apps/api/src/routes/content/update.ts +111 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +415 -0
- package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +165 -0
- package/templates/apps/api/src/routes/leads/index.ts +130 -0
- package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +296 -0
- package/templates/apps/api/src/routes/test/auth/session.ts +141 -0
- package/templates/apps/api/src/routes/webhooks/README.md +57 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +247 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +198 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +633 -0
- package/templates/apps/api/src/routes/webhooks/index.ts +41 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +253 -0
- package/templates/apps/api/src/server.ts +157 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +395 -0
- package/templates/apps/api/src/services/enrichment-shim.ts +53 -0
- package/templates/apps/api/src/services/lead-routing.ts +338 -0
- package/templates/apps/api/tsconfig.json +34 -0
- package/templates/apps/api/vercel.json +17 -0
- package/templates/apps/mcp/.env.example +52 -0
- package/templates/apps/mcp/CLAUDE.md +84 -0
- package/templates/apps/mcp/__tests__/abuse/daily-cap.test.ts +52 -0
- package/templates/apps/mcp/__tests__/abuse/email-validator.test.ts +46 -0
- package/templates/apps/mcp/__tests__/abuse/honeypot.test.ts +36 -0
- package/templates/apps/mcp/__tests__/abuse/user-agent-deny.test.ts +54 -0
- package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +110 -0
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +213 -0
- package/templates/apps/mcp/__tests__/audit.test.ts +217 -0
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +52 -0
- package/templates/apps/mcp/__tests__/create_page.test.ts +201 -0
- package/templates/apps/mcp/__tests__/delete_page.test.ts +176 -0
- package/templates/apps/mcp/__tests__/get_business_hours.test.ts +82 -0
- package/templates/apps/mcp/__tests__/get_faq.test.ts +80 -0
- package/templates/apps/mcp/__tests__/get_locations.test.ts +65 -0
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +90 -0
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +80 -0
- package/templates/apps/mcp/__tests__/get_site_info.test.ts +81 -0
- package/templates/apps/mcp/__tests__/http-transport.test.ts +323 -0
- package/templates/apps/mcp/__tests__/lib/errors.test.ts +75 -0
- package/templates/apps/mcp/__tests__/lib/require-paid-tier.test.ts +75 -0
- package/templates/apps/mcp/__tests__/list_pages.test.ts +112 -0
- package/templates/apps/mcp/__tests__/locale.test.ts +135 -0
- package/templates/apps/mcp/__tests__/parity.test.ts +205 -0
- package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +34 -0
- package/templates/apps/mcp/__tests__/read_content.test.ts +153 -0
- package/templates/apps/mcp/__tests__/request_callback.test.ts +163 -0
- package/templates/apps/mcp/__tests__/search_content.test.ts +153 -0
- package/templates/apps/mcp/__tests__/server.test.ts +115 -0
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +165 -0
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +161 -0
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +227 -0
- package/templates/apps/mcp/__tests__/tools/scaffold.test.ts +237 -0
- package/templates/apps/mcp/__tests__/update_page.test.ts +195 -0
- package/templates/apps/mcp/jest.config.cjs +82 -0
- package/templates/apps/mcp/package.json +37 -0
- package/templates/apps/mcp/src/abuse/daily-cap.ts +47 -0
- package/templates/apps/mcp/src/abuse/email-validator.ts +25 -0
- package/templates/apps/mcp/src/abuse/honeypot.ts +21 -0
- package/templates/apps/mcp/src/abuse/index.ts +111 -0
- package/templates/apps/mcp/src/abuse/user-agent-deny.ts +58 -0
- package/templates/apps/mcp/src/http-transport.ts +303 -0
- package/templates/apps/mcp/src/index.ts +67 -0
- package/templates/apps/mcp/src/lib/audit-single-page.ts +194 -0
- package/templates/apps/mcp/src/lib/errors.ts +61 -0
- package/templates/apps/mcp/src/lib/locales.ts +171 -0
- package/templates/apps/mcp/src/lib/require-paid-tier.ts +44 -0
- package/templates/apps/mcp/src/lib/resolve-locale.ts +30 -0
- package/templates/apps/mcp/src/server.ts +139 -0
- package/templates/apps/mcp/src/tools/audit.ts +103 -0
- package/templates/apps/mcp/src/tools/capture_lead.ts +52 -0
- package/templates/apps/mcp/src/tools/create_page.ts +95 -0
- package/templates/apps/mcp/src/tools/delete_page.ts +73 -0
- package/templates/apps/mcp/src/tools/get_business_hours.ts +66 -0
- package/templates/apps/mcp/src/tools/get_faq.ts +69 -0
- package/templates/apps/mcp/src/tools/get_locations.ts +85 -0
- package/templates/apps/mcp/src/tools/get_offerings.ts +66 -0
- package/templates/apps/mcp/src/tools/get_pricing.ts +74 -0
- package/templates/apps/mcp/src/tools/get_site_info.ts +84 -0
- package/templates/apps/mcp/src/tools/index.ts +125 -0
- package/templates/apps/mcp/src/tools/list_pages.ts +72 -0
- package/templates/apps/mcp/src/tools/purchase.ts +89 -0
- package/templates/apps/mcp/src/tools/read_content.ts +61 -0
- package/templates/apps/mcp/src/tools/request_callback.ts +51 -0
- package/templates/apps/mcp/src/tools/scaffold.ts +119 -0
- package/templates/apps/mcp/src/tools/search_content.ts +59 -0
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +47 -0
- package/templates/apps/mcp/src/tools/update_page.ts +91 -0
- package/templates/apps/mcp/tsconfig.json +19 -0
- package/templates/apps/web/.env.example +47 -0
- package/templates/apps/web/CLAUDE.md +81 -0
- package/templates/apps/web/__tests__/auth/adversarial/cookie-attributes.test.ts +94 -0
- package/templates/apps/web/__tests__/auth/adversarial/oauth-state-parameter.test.ts +75 -0
- package/templates/apps/web/__tests__/auth/adversarial/redirect-uri-allowlist.test.ts +82 -0
- package/templates/apps/web/__tests__/auth/env-helpers.test.ts +168 -0
- package/templates/apps/web/__tests__/authority-list-routes.test.tsx +208 -0
- package/templates/apps/web/__tests__/better-stack.test.ts +66 -0
- package/templates/apps/web/__tests__/blog-dir-resolution.test.ts +87 -0
- package/templates/apps/web/__tests__/blog-source-populated.test.ts +130 -0
- package/templates/apps/web/__tests__/data-testid-coverage.test.tsx +60 -0
- package/templates/apps/web/__tests__/google-analytics.test.tsx +104 -0
- package/templates/apps/web/__tests__/i18n/middleware.test.ts +119 -0
- package/templates/apps/web/__tests__/i18n/routing.test.ts +114 -0
- package/templates/apps/web/__tests__/i18n/translations.test.ts +191 -0
- package/templates/apps/web/__tests__/json-ld.test.ts +98 -0
- package/templates/apps/web/__tests__/knowledge-events-routes.test.tsx +580 -0
- package/templates/apps/web/__tests__/layout-stealth.test.ts +39 -0
- package/templates/apps/web/__tests__/lighthouse-fixtures/improvement-image-shrink.tsx +103 -0
- package/templates/apps/web/__tests__/lighthouse-fixtures/override-fixture.md +114 -0
- package/templates/apps/web/__tests__/lighthouse-fixtures/regression-bloat.tsx +94 -0
- package/templates/apps/web/__tests__/page.test.tsx +230 -0
- package/templates/apps/web/__tests__/products-services-phase1.test.tsx +321 -0
- package/templates/apps/web/__tests__/projects-routes.test.tsx +392 -0
- package/templates/apps/web/__tests__/resources-downloads-integrations-phase2.test.tsx +475 -0
- package/templates/apps/web/__tests__/routes/ai-plugin-json.test.ts +53 -0
- package/templates/apps/web/__tests__/routes/blog-route.test.tsx +191 -0
- package/templates/apps/web/__tests__/routes/llms-txt.test.ts +123 -0
- package/templates/apps/web/__tests__/routes/robots-txt.test.ts +52 -0
- package/templates/apps/web/__tests__/routes/sitemap-xml.test.ts +78 -0
- package/templates/apps/web/__tests__/routes/well-known-mcp.test.ts +203 -0
- package/templates/apps/web/__tests__/section-routes.test.tsx +693 -0
- package/templates/apps/web/__tests__/security-headers.test.ts +201 -0
- package/templates/apps/web/__tests__/sentry-config.test.ts +116 -0
- package/templates/apps/web/__tests__/setup.ts +82 -0
- package/templates/apps/web/__tests__/sitemap-locales.test.ts +415 -0
- package/templates/apps/web/__tests__/standalone-informational-routes.test.tsx +844 -0
- package/templates/apps/web/app/.well-known/ai-plugin.json/route.ts +81 -0
- package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +110 -0
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +58 -0
- package/templates/apps/web/app/[locale]/__tests__/layout.test.tsx +120 -0
- package/templates/apps/web/app/[locale]/__tests__/page-renderer-migration.test.tsx +202 -0
- package/templates/apps/web/app/[locale]/about/page.tsx +31 -0
- package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +173 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +45 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +117 -0
- package/templates/apps/web/app/[locale]/careers/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +44 -0
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +113 -0
- package/templates/apps/web/app/[locale]/case-studies/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +63 -0
- package/templates/apps/web/app/[locale]/clients/page.tsx +27 -0
- package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +130 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +152 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +92 -0
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +79 -0
- package/templates/apps/web/app/[locale]/contact/error.tsx +24 -0
- package/templates/apps/web/app/[locale]/contact/page.tsx +38 -0
- package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +34 -0
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +119 -0
- package/templates/apps/web/app/[locale]/courses/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/dev/purchase/__tests__/actions.test.ts +160 -0
- package/templates/apps/web/app/[locale]/dev/purchase/error.tsx +25 -0
- package/templates/apps/web/app/[locale]/dev/purchase/page.tsx +117 -0
- package/templates/apps/web/app/[locale]/dev/purchase/purchase.actions.ts +58 -0
- package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +118 -0
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +72 -0
- package/templates/apps/web/app/[locale]/downloads/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +47 -0
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +130 -0
- package/templates/apps/web/app/[locale]/events/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/faq/page.tsx +36 -0
- package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +34 -0
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +78 -0
- package/templates/apps/web/app/[locale]/how-to/page.tsx +40 -0
- package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +43 -0
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +129 -0
- package/templates/apps/web/app/[locale]/integrations/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/launch-service/page.tsx +31 -0
- package/templates/apps/web/app/[locale]/layout.tsx +241 -0
- package/templates/apps/web/app/[locale]/page.tsx +41 -0
- package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +44 -0
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +113 -0
- package/templates/apps/web/app/[locale]/partners/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +43 -0
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +129 -0
- package/templates/apps/web/app/[locale]/products/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/projects/[slug]/page.tsx +39 -0
- package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +138 -0
- package/templates/apps/web/app/[locale]/projects/page.tsx +30 -0
- package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +51 -0
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +117 -0
- package/templates/apps/web/app/[locale]/resources/page.tsx +27 -0
- package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +43 -0
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +129 -0
- package/templates/apps/web/app/[locale]/services/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +34 -0
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +81 -0
- package/templates/apps/web/app/[locale]/software/page.tsx +39 -0
- package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +34 -0
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +82 -0
- package/templates/apps/web/app/[locale]/tech-articles/page.tsx +39 -0
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +63 -0
- package/templates/apps/web/app/[locale]/testimonials/page.tsx +27 -0
- package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +63 -0
- package/templates/apps/web/app/[locale]/use-cases/page.tsx +27 -0
- package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +34 -0
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +82 -0
- package/templates/apps/web/app/[locale]/whitepapers/page.tsx +39 -0
- package/templates/apps/web/app/__tests__/error-boundaries.test.tsx +152 -0
- package/templates/apps/web/app/api/auth/[...all]/route.ts +16 -0
- package/templates/apps/web/app/error-fallback.tsx +75 -0
- package/templates/apps/web/app/error.tsx +23 -0
- package/templates/apps/web/app/layout.tsx +18 -0
- package/templates/apps/web/app/llms.txt/route.ts +101 -0
- package/templates/apps/web/app/not-found.tsx +48 -0
- package/templates/apps/web/app/og/__tests__/route.test.ts +164 -0
- package/templates/apps/web/app/og/route.tsx +135 -0
- package/templates/apps/web/app/robots.txt/route.ts +36 -0
- package/templates/apps/web/app/sitemap.xml/route.ts +146 -0
- package/templates/apps/web/auth.ts +141 -0
- package/templates/apps/web/components/ThemeToggle.tsx +60 -0
- package/templates/apps/web/components/google-analytics.tsx +34 -0
- package/templates/apps/web/content/en/blog/index.mdx +15 -0
- package/templates/apps/web/content/en/blog/launching-ailk.mdx +20 -0
- package/templates/apps/web/content/en/docs/getting-started.mdx +30 -0
- package/templates/apps/web/i18n/request.ts +36 -0
- package/templates/apps/web/jest.config.cjs +100 -0
- package/templates/apps/web/lib/__mocks__/next-mdx-remote-rsc.js +12 -0
- package/templates/apps/web/lib/__mocks__/source-server.js +31 -0
- package/templates/apps/web/lib/__tests__/og.test.ts +65 -0
- package/templates/apps/web/lib/auth-env.ts +66 -0
- package/templates/apps/web/lib/blog-list-data.ts +95 -0
- package/templates/apps/web/lib/blog-post-data.ts +79 -0
- package/templates/apps/web/lib/nav-config.ts +13 -0
- package/templates/apps/web/lib/observability/better-stack.ts +27 -0
- package/templates/apps/web/lib/og.ts +23 -0
- package/templates/apps/web/lib/prisma-for-auth.ts +34 -0
- package/templates/apps/web/lib/source.ts +34 -0
- package/templates/apps/web/messages/en.json +131 -0
- package/templates/apps/web/middleware.ts +80 -0
- package/templates/apps/web/next.config.mjs +167 -0
- package/templates/apps/web/package.json +62 -0
- package/templates/apps/web/public/.well-known/security.txt +17 -0
- package/templates/apps/web/public/android-chrome-192x192.png +0 -0
- package/templates/apps/web/public/android-chrome-512x512.png +0 -0
- package/templates/apps/web/public/apple-touch-icon.png +0 -0
- package/templates/apps/web/public/favicon-16x16.png +0 -0
- package/templates/apps/web/public/favicon-32x32.png +0 -0
- package/templates/apps/web/public/favicon.ico +0 -0
- package/templates/apps/web/public/favicon.svg +4 -0
- package/templates/apps/web/public/logo-dark.svg +21 -0
- package/templates/apps/web/public/logo-light.svg +21 -0
- package/templates/apps/web/public/logomark-dark.svg +4 -0
- package/templates/apps/web/public/logomark-light.svg +4 -0
- package/templates/apps/web/public/og-default.png +0 -0
- package/templates/apps/web/public/site.webmanifest +19 -0
- package/templates/apps/web/scripts/assert-content-bundled.ts +55 -0
- package/templates/apps/web/security-headers.config.ts +132 -0
- package/templates/apps/web/sentry.client.config.ts +27 -0
- package/templates/apps/web/sentry.edge.config.ts +15 -0
- package/templates/apps/web/sentry.server.config.ts +20 -0
- package/templates/apps/web/source.config.ts +78 -0
- package/templates/apps/web/src/components/SiteNav.tsx +21 -0
- package/templates/apps/web/src/lib/routes.ts +37 -0
- package/templates/apps/web/tsconfig.json +35 -0
- package/templates/apps/web/types/messages.d.ts +19 -0
- package/templates/content/.gitkeep +0 -0
- package/templates/content/CLAUDE.md +85 -0
- package/templates/content/_site.mdx +38 -0
- package/templates/content/ar/HomePage/home.mdx +123 -0
- package/templates/content/ar/_site.mdx +35 -0
- package/templates/content/de/_site.mdx +29 -0
- package/templates/content/en/.gitkeep +0 -0
- package/templates/content/en/AboutPage/about.mdx +19 -0
- package/templates/content/en/AboutPage/about.schema.json +82 -0
- package/templates/content/en/BookCall/book-call.mdx +19 -0
- package/templates/content/en/BookCall/book-call.schema.json +121 -0
- package/templates/content/en/BreadcrumbList/site-breadcrumbs.mdx +20 -0
- package/templates/content/en/CareersList/careers.mdx +24 -0
- package/templates/content/en/CaseStudiesList/case-studies.mdx +22 -0
- package/templates/content/en/CaseStudiesList/showcase.mdx +8 -0
- package/templates/content/en/CaseStudy/ailk-aeo-rollout.mdx +32 -0
- package/templates/content/en/CaseStudy/schema-builder-migration.mdx +41 -0
- package/templates/content/en/ClientsList/clients.mdx +38 -0
- package/templates/content/en/ContactPage/contact.mdx +19 -0
- package/templates/content/en/ContactPage/contact.schema.json +100 -0
- package/templates/content/en/ContactPoint/primary.mdx +14 -0
- package/templates/content/en/Course/aeo-fundamentals.mdx +37 -0
- package/templates/content/en/Course/schema-builder-workshop.mdx +62 -0
- package/templates/content/en/CoursesList/courses.mdx +22 -0
- package/templates/content/en/DownloadsList/downloads.mdx +22 -0
- package/templates/content/en/Event/aeo-office-hours-may-2026.mdx +39 -0
- package/templates/content/en/Event/ailk-launch-day-2026.mdx +35 -0
- package/templates/content/en/EventsList/events.mdx +26 -0
- package/templates/content/en/FAQPage/faq.mdx +420 -0
- package/templates/content/en/FAQPage/faq.schema.json +231 -0
- package/templates/content/en/GlossaryList/glossary.mdx +19 -0
- package/templates/content/en/GlossaryList/glossary.schema.json +300 -0
- package/templates/content/en/HomePage/home.mdx +312 -0
- package/templates/content/en/HomePage/home.schema.json +88 -0
- package/templates/content/en/HomePage/launch-service.mdx +475 -0
- package/templates/content/en/HomePage/launch-service.schema.json +138 -0
- package/templates/content/en/HowTo/add-mdx-content.mdx +38 -0
- package/templates/content/en/HowTo/configure-aeo-scoring.mdx +35 -0
- package/templates/content/en/HowTo/set-up-json-ld.mdx +36 -0
- package/templates/content/en/Integration/github-actions.mdx +31 -0
- package/templates/content/en/Integration/openai-api.mdx +26 -0
- package/templates/content/en/Integration/vercel.mdx +32 -0
- package/templates/content/en/IntegrationsList/integrations.mdx +22 -0
- package/templates/content/en/JobPosting/content-strategist.mdx +46 -0
- package/templates/content/en/JobPosting/developer-advocate.mdx +47 -0
- package/templates/content/en/JobPosting/senior-fullstack-engineer.mdx +57 -0
- package/templates/content/en/LandingPage/aeo-audit.mdx +19 -0
- package/templates/content/en/LandingPage/aeo-audit.schema.json +92 -0
- package/templates/content/en/LandingPage/agency-waitlist.mdx +340 -0
- package/templates/content/en/LandingPage/agency-waitlist.schema.json +152 -0
- package/templates/content/en/LandingPage/developers.mdx +19 -0
- package/templates/content/en/LandingPage/developers.schema.json +109 -0
- package/templates/content/en/LandingPage/for-agencies.mdx +390 -0
- package/templates/content/en/LandingPage/for-agencies.schema.json +107 -0
- package/templates/content/en/LandingPage/for-ai-search-agencies.mdx +417 -0
- package/templates/content/en/LandingPage/for-ai-search-agencies.schema.json +105 -0
- package/templates/content/en/LandingPage/for-businesses.mdx +19 -0
- package/templates/content/en/LandingPage/for-businesses.schema.json +96 -0
- package/templates/content/en/LandingPage/platform.mdx +369 -0
- package/templates/content/en/LandingPage/platform.schema.json +166 -0
- package/templates/content/en/LandingPage/vs-ai-website-builders.mdx +19 -0
- package/templates/content/en/LandingPage/vs-ai-website-builders.schema.json +102 -0
- package/templates/content/en/LandingPage/vs-wordpress.mdx +19 -0
- package/templates/content/en/LandingPage/vs-wordpress.schema.json +53 -0
- package/templates/content/en/NewsletterSignup/newsletter.mdx +236 -0
- package/templates/content/en/NewsletterSignup/newsletter.schema.json +91 -0
- package/templates/content/en/Organization/site-owner.mdx +16 -0
- package/templates/content/en/Partner/prisma.mdx +31 -0
- package/templates/content/en/Partner/supabase.mdx +30 -0
- package/templates/content/en/Partner/vercel.mdx +28 -0
- package/templates/content/en/PartnersList/partners.mdx +22 -0
- package/templates/content/en/Person/founder.mdx +18 -0
- package/templates/content/en/Pricing/pricing.mdx +538 -0
- package/templates/content/en/Pricing/pricing.schema.json +209 -0
- package/templates/content/en/PrivacyPage/privacy.mdx +165 -0
- package/templates/content/en/PrivacyPage/privacy.schema.json +64 -0
- package/templates/content/en/Product/ailk.mdx +19 -0
- package/templates/content/en/Product/ailk.schema.json +94 -0
- package/templates/content/en/Product/api-launch-kit.mdx +26 -0
- package/templates/content/en/Product/launch-kit.mdx +28 -0
- package/templates/content/en/ProductsList/products.mdx +26 -0
- package/templates/content/en/Project/ailk-phase-2.mdx +54 -0
- package/templates/content/en/ProjectsList/projects.mdx +25 -0
- package/templates/content/en/ResourceDetail/aeo-scoring-guide.mdx +27 -0
- package/templates/content/en/ResourceDetail/content-model-whitepaper.mdx +28 -0
- package/templates/content/en/ResourceDetail/schema-org-quick-reference.mdx +39 -0
- package/templates/content/en/ResourcesHub/resources.mdx +22 -0
- package/templates/content/en/Service/consulting.mdx +22 -0
- package/templates/content/en/Service/implementation.mdx +30 -0
- package/templates/content/en/Service/launch-service.mdx +232 -0
- package/templates/content/en/Service/launch.mdx +228 -0
- package/templates/content/en/ServicesList/services.mdx +84 -0
- package/templates/content/en/SoftwareProduct/ailk-aeo.mdx +44 -0
- package/templates/content/en/SoftwareProduct/ailk-content-adapters.mdx +47 -0
- package/templates/content/en/SoftwareProduct/ailk-schema.mdx +49 -0
- package/templates/content/en/TeamList/team.mdx +63 -0
- package/templates/content/en/TeamMember/founder.mdx +72 -0
- package/templates/content/en/TermsPage/terms.mdx +19 -0
- package/templates/content/en/TermsPage/terms.schema.json +99 -0
- package/templates/content/en/TestimonialsList/testimonials.mdx +44 -0
- package/templates/content/en/UseCasesList/use-cases.mdx +30 -0
- package/templates/content/en/Whitepaper/ax-first-product-development.mdx +27 -0
- package/templates/content/en/Whitepaper/open-core-commercial-strategy.mdx +27 -0
- package/templates/content/en/Whitepaper/structured-data-for-answer-engines.mdx +31 -0
- package/templates/content/en-XA/AboutPage/about.mdx +8 -0
- package/templates/content/en-XA/FAQPage/faq.mdx +8 -0
- package/templates/content/en-XA/HomePage/home.mdx +8 -0
- package/templates/content/es/_site.mdx +30 -0
- package/templates/content/fr/_site.mdx +30 -0
- package/templates/content/ja/_site.mdx +30 -0
- package/templates/content/pt-BR/AboutPage/about.mdx +8 -0
- package/templates/content/pt-BR/FAQPage/faq.mdx +8 -0
- package/templates/content/pt-BR/HomePage/home.mdx +23 -0
- package/templates/database/CHANGELOG.md +5 -0
- package/templates/database/README.md +234 -0
- package/templates/database/__tests__/user-cascade.test.ts +125 -0
- package/templates/database/auth/.gitkeep +0 -0
- package/templates/database/config/.gitkeep +0 -0
- package/templates/database/content/.gitkeep +0 -0
- package/templates/database/inbox/.gitkeep +0 -0
- package/templates/database/inbox/README.md +3 -0
- package/templates/database/inbox/__tests__/schema.test.ts +40 -0
- package/templates/database/inbox/schema.prisma +52 -0
- package/templates/database/jest.config.cjs +29 -0
- package/templates/database/migrations/20260508000000_add_inbox_namespace/migration.sql +328 -0
- package/templates/database/migrations/20260513000000_better_auth_migration/migration.sql +100 -0
- package/templates/database/migrations/20260515000000_add_user_billing_fields/migration.sql +28 -0
- package/templates/database/migrations/20260516000000_add_user_tier_care_plan_seat/migration.sql +25 -0
- package/templates/database/migrations/20260518000000_add_telemetry_records/migration.sql +42 -0
- package/templates/database/migrations/20260525000000_theme_wiring/migration.sql +67 -0
- package/templates/database/migrations/20260710000000_retire_commercial_tier/migration.sql +21 -0
- package/templates/database/migrations/migration_lock.toml +3 -0
- package/templates/database/ops/.gitkeep +0 -0
- package/templates/database/package.json +45 -0
- package/templates/database/prisma.config.ts +20 -0
- package/templates/database/schema.prisma +487 -0
- package/templates/database/scripts/db-generate-locked.sh +80 -0
- package/templates/database/seed-test.ts +85 -0
- package/templates/database/tsconfig.build.json +16 -0
- package/templates/database/tsconfig.json +9 -0
- package/templates/package.json +125 -0
- package/templates/pnpm-workspace.yaml +6 -0
- package/templates/project.yaml +59 -0
- package/templates/tsconfig.base.json +18 -0
- package/templates/tsconfig.json +45 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
// Customer Template Database Schema
|
|
2
|
+
//
|
|
3
|
+
// MINIMAL BUT EXTENSIBLE - Only stores auth and customer extensions
|
|
4
|
+
// All business data (leads, orders, analytics) lives on the Platform
|
|
5
|
+
//
|
|
6
|
+
// Tables (14 total — 11 auth/config + 3 inbox added in B1):
|
|
7
|
+
// - Better Auth (4): User, Session, Account, Verification (post-#587)
|
|
8
|
+
// - API Auth (2): ApiToken, RefreshToken
|
|
9
|
+
// - OAuth (2): OAuthClient, OAuthAuthorization
|
|
10
|
+
// - Push (1): PushToken
|
|
11
|
+
// - Extensions (2): UserData, SiteSetting
|
|
12
|
+
// - Inbox (3): Lead, Subscriber, Callback [namespace: database/inbox/]
|
|
13
|
+
//
|
|
14
|
+
// Aggregation strategy: Namespace-local schema files (e.g. database/inbox/schema.prisma)
|
|
15
|
+
// define models canonically for lint and documentation. Those same models are
|
|
16
|
+
// registered here so Prisma can generate from the single root schema.prisma.
|
|
17
|
+
// Phase 2 (ADR 0009 D4) will migrate to prismaSchemaFolder once multi-file
|
|
18
|
+
// generation is adopted repo-wide.
|
|
19
|
+
|
|
20
|
+
generator client {
|
|
21
|
+
provider = "prisma-client"
|
|
22
|
+
output = "./generated/client"
|
|
23
|
+
importFileExtension = "js"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
datasource db {
|
|
27
|
+
provider = "postgresql"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// Better Auth Models (Web Dashboard Authentication — #587)
|
|
32
|
+
// ============================================================================
|
|
33
|
+
|
|
34
|
+
/// User - Dashboard users (site owners, admins, end-users)
|
|
35
|
+
///
|
|
36
|
+
/// emailVerified is a Boolean per Better Auth's contract (NextAuth previously
|
|
37
|
+
/// stored the verification timestamp as DateTime?). The five custom relations
|
|
38
|
+
/// — ApiToken, RefreshToken, OAuthAuthorization, PushToken, UserData — keep
|
|
39
|
+
/// their onDelete: Cascade semantics so user removal cleanly tears down all
|
|
40
|
+
/// dependent rows.
|
|
41
|
+
model User {
|
|
42
|
+
id String @id @default(cuid())
|
|
43
|
+
email String @unique
|
|
44
|
+
name String?
|
|
45
|
+
image String?
|
|
46
|
+
emailVerified Boolean @default(false) @map("email_verified")
|
|
47
|
+
|
|
48
|
+
// Profile extensions
|
|
49
|
+
role String @default("user") // "user", "admin", "owner"
|
|
50
|
+
|
|
51
|
+
// Billing identity — provider-agnostic so Stripe is v1 value, not baked-in name.
|
|
52
|
+
// Webhook is the only writer (see apps/api/src/routes/webhooks/stripe.ts).
|
|
53
|
+
// #771 self-serve dashboard reads billingCustomerId for Stripe customer-portal URL.
|
|
54
|
+
billingProvider String? @map("billing_provider") // "stripe"; future: "lemonsqueezy", "paddle"
|
|
55
|
+
billingCustomerId String? @map("billing_customer_id") // cus_xxx (Stripe) or equivalent
|
|
56
|
+
|
|
57
|
+
// Subscription tier projection — derived from the Stripe webhook's product-aware
|
|
58
|
+
// routing (#898). Defaults to "free" until a customer.subscription.created event
|
|
59
|
+
// fires; flips to "pro" / "pro_plus" / etc. based on the line item's product.
|
|
60
|
+
// hasCarePlan is true only for pro_plus today; seatCount mirrors the line item
|
|
61
|
+
// quantity from Stripe's adjustable-quantity widget (1–50).
|
|
62
|
+
tier String @default("free") // "free" | "pro" | "pro_plus" | "enterprise" (retired "agency"/"commercial" rows migrated to "enterprise" — #3516)
|
|
63
|
+
hasCarePlan Boolean @default(false) @map("has_care_plan")
|
|
64
|
+
seatCount Int @default(1) @map("seat_count")
|
|
65
|
+
|
|
66
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
67
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
68
|
+
|
|
69
|
+
// Better Auth relations
|
|
70
|
+
sessions Session[]
|
|
71
|
+
accounts Account[]
|
|
72
|
+
|
|
73
|
+
// API & OAuth
|
|
74
|
+
apiTokens ApiToken[]
|
|
75
|
+
refreshTokens RefreshToken[]
|
|
76
|
+
oauthAuthorizations OAuthAuthorization[]
|
|
77
|
+
pushTokens PushToken[]
|
|
78
|
+
|
|
79
|
+
// Extensions
|
|
80
|
+
userData UserData[]
|
|
81
|
+
|
|
82
|
+
// Theme Wiring (TWP3-MAT #1359)
|
|
83
|
+
tenantTheme TenantTheme?
|
|
84
|
+
tenantSiteIdentity TenantSiteIdentity?
|
|
85
|
+
tenantSeo TenantSEO?
|
|
86
|
+
|
|
87
|
+
@@unique([billingProvider, billingCustomerId])
|
|
88
|
+
@@map("users")
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Session - Active user sessions (Better Auth schema)
|
|
92
|
+
///
|
|
93
|
+
/// Session.token is the opaque cookie value the dual-mode preHandler reads.
|
|
94
|
+
/// Deleting a row revokes the session; deleting the parent User cascades.
|
|
95
|
+
model Session {
|
|
96
|
+
id String @id
|
|
97
|
+
expiresAt DateTime @map("expires_at")
|
|
98
|
+
token String @unique
|
|
99
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
100
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
101
|
+
ipAddress String? @map("ip_address")
|
|
102
|
+
userAgent String? @map("user_agent")
|
|
103
|
+
userId String @map("user_id")
|
|
104
|
+
|
|
105
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
106
|
+
|
|
107
|
+
@@index([userId])
|
|
108
|
+
@@index([token])
|
|
109
|
+
@@map("sessions")
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// Account - OAuth provider connections (Google, GitHub, etc.)
|
|
113
|
+
///
|
|
114
|
+
/// Better Auth's account shape: one row per (providerId, accountId) pair.
|
|
115
|
+
/// Cascade on User removal keeps OAuth links from orphaning.
|
|
116
|
+
model Account {
|
|
117
|
+
id String @id
|
|
118
|
+
accountId String @map("account_id")
|
|
119
|
+
providerId String @map("provider_id")
|
|
120
|
+
userId String @map("user_id")
|
|
121
|
+
accessToken String? @map("access_token") @db.Text
|
|
122
|
+
refreshToken String? @map("refresh_token") @db.Text
|
|
123
|
+
idToken String? @map("id_token") @db.Text
|
|
124
|
+
accessTokenExpiresAt DateTime? @map("access_token_expires_at")
|
|
125
|
+
refreshTokenExpiresAt DateTime? @map("refresh_token_expires_at")
|
|
126
|
+
scope String?
|
|
127
|
+
password String?
|
|
128
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
129
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
130
|
+
|
|
131
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
132
|
+
|
|
133
|
+
@@unique([providerId, accountId])
|
|
134
|
+
@@index([userId])
|
|
135
|
+
@@map("accounts")
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// Verification - Email / magic-link verification tokens (Better Auth schema)
|
|
139
|
+
model Verification {
|
|
140
|
+
id String @id
|
|
141
|
+
identifier String
|
|
142
|
+
value String
|
|
143
|
+
expiresAt DateTime @map("expires_at")
|
|
144
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
145
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
146
|
+
|
|
147
|
+
@@index([identifier])
|
|
148
|
+
@@map("verifications")
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ============================================================================
|
|
152
|
+
// API Token Authentication (Mobile Apps, CLI, External Clients)
|
|
153
|
+
// ============================================================================
|
|
154
|
+
|
|
155
|
+
/// ApiToken - Bearer tokens for non-web clients (mobile apps, API integrations)
|
|
156
|
+
model ApiToken {
|
|
157
|
+
id String @id @default(cuid())
|
|
158
|
+
userId String @map("user_id")
|
|
159
|
+
|
|
160
|
+
// Token identification
|
|
161
|
+
name String // "iPhone", "Android App", "API Integration"
|
|
162
|
+
token String @unique // Hashed token
|
|
163
|
+
tokenPrefix String @map("token_prefix") // First 8 chars for identification
|
|
164
|
+
|
|
165
|
+
// Client info
|
|
166
|
+
clientType String @default("api") @map("client_type") // "mobile", "api", "cli"
|
|
167
|
+
deviceInfo String? @map("device_info") // "iPhone 15 Pro", "Android 14"
|
|
168
|
+
|
|
169
|
+
// Permissions
|
|
170
|
+
scopes String[] @default([]) // ["read:profile", "write:data"]
|
|
171
|
+
|
|
172
|
+
// Lifecycle
|
|
173
|
+
expiresAt DateTime? @map("expires_at")
|
|
174
|
+
lastUsedAt DateTime? @map("last_used_at")
|
|
175
|
+
lastUsedIp String? @map("last_used_ip")
|
|
176
|
+
revokedAt DateTime? @map("revoked_at")
|
|
177
|
+
|
|
178
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
179
|
+
|
|
180
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
181
|
+
refreshTokens RefreshToken[]
|
|
182
|
+
|
|
183
|
+
@@index([userId])
|
|
184
|
+
@@index([token])
|
|
185
|
+
@@index([tokenPrefix])
|
|
186
|
+
@@map("api_tokens")
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// RefreshToken - Long-lived tokens for mobile session refresh
|
|
190
|
+
model RefreshToken {
|
|
191
|
+
id String @id @default(cuid())
|
|
192
|
+
userId String @map("user_id")
|
|
193
|
+
apiTokenId String @map("api_token_id")
|
|
194
|
+
|
|
195
|
+
token String @unique // Hashed
|
|
196
|
+
|
|
197
|
+
// Lifecycle
|
|
198
|
+
expiresAt DateTime @map("expires_at")
|
|
199
|
+
usedAt DateTime? @map("used_at") // Last time used to get new access token
|
|
200
|
+
revokedAt DateTime? @map("revoked_at")
|
|
201
|
+
|
|
202
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
203
|
+
|
|
204
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
205
|
+
apiToken ApiToken @relation(fields: [apiTokenId], references: [id], onDelete: Cascade)
|
|
206
|
+
|
|
207
|
+
@@index([userId])
|
|
208
|
+
@@index([apiTokenId])
|
|
209
|
+
@@index([token])
|
|
210
|
+
@@map("refresh_tokens")
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ============================================================================
|
|
214
|
+
// OAuth 2.0 (ChatGPT Plugin, Slack Bot, External Integrations)
|
|
215
|
+
// ============================================================================
|
|
216
|
+
|
|
217
|
+
/// OAuthClient - Registered OAuth applications (ChatGPT, Slack, etc.)
|
|
218
|
+
model OAuthClient {
|
|
219
|
+
id String @id @default(cuid())
|
|
220
|
+
|
|
221
|
+
// Client identification
|
|
222
|
+
name String // "ChatGPT Plugin", "Slack Bot"
|
|
223
|
+
description String?
|
|
224
|
+
logoUrl String? @map("logo_url")
|
|
225
|
+
|
|
226
|
+
// OAuth credentials
|
|
227
|
+
clientId String @unique @map("client_id") // Public identifier
|
|
228
|
+
clientSecret String @map("client_secret") // Hashed secret
|
|
229
|
+
|
|
230
|
+
// OAuth config
|
|
231
|
+
redirectUris String[] @map("redirect_uris") // Allowed callback URLs
|
|
232
|
+
scopes String[] @default([]) // Allowed scopes
|
|
233
|
+
|
|
234
|
+
// Settings
|
|
235
|
+
enabled Boolean @default(true)
|
|
236
|
+
trusted Boolean @default(false) // Skip consent screen
|
|
237
|
+
|
|
238
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
239
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
240
|
+
|
|
241
|
+
authorizations OAuthAuthorization[]
|
|
242
|
+
|
|
243
|
+
@@map("oauth_clients")
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/// OAuthAuthorization - User grants to OAuth clients
|
|
247
|
+
model OAuthAuthorization {
|
|
248
|
+
id String @id @default(cuid())
|
|
249
|
+
userId String @map("user_id")
|
|
250
|
+
clientId String @map("client_id")
|
|
251
|
+
|
|
252
|
+
// Granted scopes (may be subset of client's allowed scopes)
|
|
253
|
+
scopes String[]
|
|
254
|
+
|
|
255
|
+
// Tokens
|
|
256
|
+
accessToken String @unique @map("access_token") // Hashed
|
|
257
|
+
refreshToken String? @unique @map("refresh_token") // Hashed
|
|
258
|
+
|
|
259
|
+
// Lifecycle
|
|
260
|
+
expiresAt DateTime @map("expires_at")
|
|
261
|
+
revokedAt DateTime? @map("revoked_at")
|
|
262
|
+
|
|
263
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
264
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
265
|
+
|
|
266
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
267
|
+
client OAuthClient @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
268
|
+
|
|
269
|
+
@@unique([userId, clientId])
|
|
270
|
+
@@index([userId])
|
|
271
|
+
@@index([clientId])
|
|
272
|
+
@@index([accessToken])
|
|
273
|
+
@@map("oauth_authorizations")
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ============================================================================
|
|
277
|
+
// Push Notifications (Mobile Apps, Web Push)
|
|
278
|
+
// ============================================================================
|
|
279
|
+
|
|
280
|
+
/// PushToken - Device tokens for push notifications
|
|
281
|
+
model PushToken {
|
|
282
|
+
id String @id @default(cuid())
|
|
283
|
+
userId String @map("user_id")
|
|
284
|
+
|
|
285
|
+
// Token details
|
|
286
|
+
token String @unique // FCM or APNs token
|
|
287
|
+
platform String // "ios", "android", "web"
|
|
288
|
+
|
|
289
|
+
// Device info
|
|
290
|
+
deviceId String? @map("device_id") // Unique device identifier
|
|
291
|
+
deviceName String? @map("device_name") // "iPhone 15 Pro"
|
|
292
|
+
appVersion String? @map("app_version") // "1.2.3"
|
|
293
|
+
|
|
294
|
+
// Status
|
|
295
|
+
enabled Boolean @default(true)
|
|
296
|
+
failedAt DateTime? @map("failed_at") // Last delivery failure
|
|
297
|
+
failCount Int @default(0) @map("fail_count")
|
|
298
|
+
|
|
299
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
300
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
301
|
+
|
|
302
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
303
|
+
|
|
304
|
+
@@index([userId])
|
|
305
|
+
@@index([platform])
|
|
306
|
+
@@map("push_tokens")
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ============================================================================
|
|
310
|
+
// User Data Extensions (Customer Customizations)
|
|
311
|
+
// ============================================================================
|
|
312
|
+
|
|
313
|
+
/// UserData - Flexible key-value storage for customer extensions
|
|
314
|
+
/// Use this for: preferences, saved items, custom fields, feature flags
|
|
315
|
+
model UserData {
|
|
316
|
+
id String @id @default(cuid())
|
|
317
|
+
userId String @map("user_id")
|
|
318
|
+
|
|
319
|
+
// Key-value storage
|
|
320
|
+
key String // "preferences", "saved_products", "notification_settings"
|
|
321
|
+
value Json // Flexible JSON storage
|
|
322
|
+
|
|
323
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
324
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
325
|
+
|
|
326
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
327
|
+
|
|
328
|
+
@@unique([userId, key])
|
|
329
|
+
@@index([userId])
|
|
330
|
+
@@index([key])
|
|
331
|
+
@@map("user_data")
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ============================================================================
|
|
335
|
+
// Site Settings (Site-wide Configuration)
|
|
336
|
+
// ============================================================================
|
|
337
|
+
|
|
338
|
+
/// SiteSetting - Key-value settings for the customer site
|
|
339
|
+
/// Use for: feature flags, custom config, cached Platform data
|
|
340
|
+
model SiteSetting {
|
|
341
|
+
id String @id @default(cuid())
|
|
342
|
+
key String @unique // "feature_flags", "cached_config", "custom_theme"
|
|
343
|
+
value Json
|
|
344
|
+
|
|
345
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
346
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
347
|
+
|
|
348
|
+
@@map("site_settings")
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ============================================================================
|
|
352
|
+
// Inbox Namespace (database/inbox/schema.prisma — B1)
|
|
353
|
+
//
|
|
354
|
+
// Anonymous lead/subscriber/callback data. siteId is a plain String — no FK
|
|
355
|
+
// to auth-namespace models per ADR 0009 D2. Cross-namespace assembly routes
|
|
356
|
+
// through apps/api/src/services/ (D3).
|
|
357
|
+
// ============================================================================
|
|
358
|
+
|
|
359
|
+
/// Lead - anonymous lead submission (Phase B submit_lead tool / B6 route)
|
|
360
|
+
model Lead {
|
|
361
|
+
id String @id @default(cuid())
|
|
362
|
+
siteId String @map("site_id")
|
|
363
|
+
source String
|
|
364
|
+
email String
|
|
365
|
+
payload Json
|
|
366
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
367
|
+
|
|
368
|
+
@@index([siteId])
|
|
369
|
+
@@index([createdAt])
|
|
370
|
+
@@map("leads")
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/// Subscriber - newsletter subscription (Phase B subscribe_newsletter tool / B6 route)
|
|
374
|
+
model Subscriber {
|
|
375
|
+
id String @id @default(cuid())
|
|
376
|
+
siteId String @map("site_id")
|
|
377
|
+
email String
|
|
378
|
+
source String
|
|
379
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
380
|
+
|
|
381
|
+
@@unique([siteId, email])
|
|
382
|
+
@@index([siteId])
|
|
383
|
+
@@map("subscribers")
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/// Callback - callback request (Phase B request_callback tool / B6 route)
|
|
387
|
+
model Callback {
|
|
388
|
+
id String @id @default(cuid())
|
|
389
|
+
siteId String @map("site_id")
|
|
390
|
+
name String
|
|
391
|
+
phone String
|
|
392
|
+
preferredTime String? @map("preferred_time")
|
|
393
|
+
payload Json
|
|
394
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
395
|
+
|
|
396
|
+
@@index([siteId])
|
|
397
|
+
@@index([createdAt])
|
|
398
|
+
@@map("callbacks")
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ============================================================================
|
|
402
|
+
// License Telemetry (apps-paid/license-issuer — S2 #1031)
|
|
403
|
+
//
|
|
404
|
+
// Time-series records of opt-in seat-usage telemetry from customer deployments.
|
|
405
|
+
// Written by POST /telemetry on the license-issuer when AILK_TELEMETRY=1.
|
|
406
|
+
// Enables queries computing the distribution of active_user_count / seat_count
|
|
407
|
+
// across the customer base over a time window (S2 trigger condition monitoring).
|
|
408
|
+
//
|
|
409
|
+
// No PII beyond customer_id (Stripe customer ID — pseudo-anonymous).
|
|
410
|
+
// ============================================================================
|
|
411
|
+
|
|
412
|
+
/// TelemetryRecord - opt-in seat-usage telemetry from customer deployments.
|
|
413
|
+
///
|
|
414
|
+
/// timestamp is the client-side observation time (from the telemetry payload).
|
|
415
|
+
/// recordedAt is the server-side receipt time (default now()).
|
|
416
|
+
/// The (customerId, recordedAt DESC) index supports "latest record per customer"
|
|
417
|
+
/// queries; the recordedAt DESC index supports time-window aggregate queries.
|
|
418
|
+
model TelemetryRecord {
|
|
419
|
+
id String @id @default(cuid())
|
|
420
|
+
customerId String @map("customer_id")
|
|
421
|
+
activeUserCount Int @map("active_user_count")
|
|
422
|
+
seatCount Int @map("seat_count")
|
|
423
|
+
timestamp DateTime
|
|
424
|
+
recordedAt DateTime @default(now()) @map("recorded_at")
|
|
425
|
+
|
|
426
|
+
@@index([customerId, recordedAt(sort: Desc)])
|
|
427
|
+
@@index([recordedAt(sort: Desc)])
|
|
428
|
+
@@map("telemetry_records")
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ============================================================================
|
|
432
|
+
// Theme Wiring — Per-tenant design system persistence (TWP3-MAT #1359)
|
|
433
|
+
//
|
|
434
|
+
// Three tables, one per @working-theory/site-config schema. Each row stores the
|
|
435
|
+
// Zod-validated JSON blob for a single tenant (identified by userId from the
|
|
436
|
+
// Better Auth session). Validation is enforced at the API layer; the DB stores
|
|
437
|
+
// the already-validated blob in a Json column.
|
|
438
|
+
//
|
|
439
|
+
// Design decisions (spec §3):
|
|
440
|
+
// - One table per schema (not a polymorphic config table) — different update
|
|
441
|
+
// cadences + consumer access patterns.
|
|
442
|
+
// - Json column for the blob — schema evolves without DB migration on change;
|
|
443
|
+
// Zod validates at API boundary.
|
|
444
|
+
// - userId is the Better Auth User id — plain String FK; onDelete Cascade
|
|
445
|
+
// ensures rows are removed when the user account is deleted.
|
|
446
|
+
// - @@unique([userId]) enforces one active record per tenant per config type
|
|
447
|
+
// (upsert semantics at the API layer).
|
|
448
|
+
// ============================================================================
|
|
449
|
+
|
|
450
|
+
/// TenantTheme — per-tenant ThemeSchema blob (TWP3-MAT #1359)
|
|
451
|
+
model TenantTheme {
|
|
452
|
+
id String @id @default(cuid())
|
|
453
|
+
userId String @unique @map("user_id")
|
|
454
|
+
data Json
|
|
455
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
456
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
457
|
+
|
|
458
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
459
|
+
|
|
460
|
+
@@map("tenant_themes")
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/// TenantSiteIdentity — per-tenant SiteIdentitySchema blob (TWP3-MAT #1359)
|
|
464
|
+
model TenantSiteIdentity {
|
|
465
|
+
id String @id @default(cuid())
|
|
466
|
+
userId String @unique @map("user_id")
|
|
467
|
+
data Json
|
|
468
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
469
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
470
|
+
|
|
471
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
472
|
+
|
|
473
|
+
@@map("tenant_site_identities")
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/// TenantSEO — per-tenant SEOSchema blob (TWP3-MAT #1359)
|
|
477
|
+
model TenantSEO {
|
|
478
|
+
id String @id @default(cuid())
|
|
479
|
+
userId String @unique @map("user_id")
|
|
480
|
+
data Json
|
|
481
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
482
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
483
|
+
|
|
484
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
485
|
+
|
|
486
|
+
@@map("tenant_seos")
|
|
487
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# db-generate-locked.sh — serialise concurrent `pnpm --filter @working-theory/database db:generate`
|
|
3
|
+
# calls so parallel prebuild scripts don't race on database/generated/client/.
|
|
4
|
+
#
|
|
5
|
+
# Background: four apps-paid/* packages each declare a `prebuild` that calls
|
|
6
|
+
# db:generate. When `pnpm -r build` triggers them in parallel the concurrent
|
|
7
|
+
# Prisma invocations race on the shared database/generated/client/ output dir,
|
|
8
|
+
# producing either "directory not empty" rmdir errors or "doesn't look like a
|
|
9
|
+
# generated Prisma Client" sanity-check failures. See issue #1690.
|
|
10
|
+
#
|
|
11
|
+
# Lock mechanism: mkdir(1) is atomic on POSIX filesystems (including macOS APFS).
|
|
12
|
+
# The first caller wins the lock dir and runs the generate; concurrent callers
|
|
13
|
+
# spin-wait. If the winner finishes before they time out, they see the output
|
|
14
|
+
# dir already present and skip. Stale locks are detected via a PID file stored
|
|
15
|
+
# inside the lock dir; if the owning PID is no longer alive the lock is removed
|
|
16
|
+
# and the next caller re-runs the generate.
|
|
17
|
+
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
# Workspace root: two levels up from database/scripts/
|
|
21
|
+
WORKSPACE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
22
|
+
DATABASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
23
|
+
|
|
24
|
+
LOCK_DIR="$WORKSPACE_ROOT/.claude/.db-generate.lock"
|
|
25
|
+
TIMEOUT_SECONDS=120
|
|
26
|
+
POLL_INTERVAL=1
|
|
27
|
+
|
|
28
|
+
# --- Acquire lock via atomic mkdir ---
|
|
29
|
+
acquire_lock() {
|
|
30
|
+
if mkdir "$LOCK_DIR" 2>/dev/null; then
|
|
31
|
+
echo "$$" > "$LOCK_DIR/pid"
|
|
32
|
+
return 0 # acquired
|
|
33
|
+
fi
|
|
34
|
+
return 1 # not acquired
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# --- Check if a stale lock can be cleared ---
|
|
38
|
+
maybe_clear_stale_lock() {
|
|
39
|
+
local pid_file="$LOCK_DIR/pid"
|
|
40
|
+
if [ -f "$pid_file" ]; then
|
|
41
|
+
local owner_pid
|
|
42
|
+
owner_pid="$(cat "$pid_file" 2>/dev/null || echo "")"
|
|
43
|
+
if [ -n "$owner_pid" ] && ! kill -0 "$owner_pid" 2>/dev/null; then
|
|
44
|
+
# Owner process is gone — lock is stale; remove it
|
|
45
|
+
rm -rf "$LOCK_DIR"
|
|
46
|
+
fi
|
|
47
|
+
fi
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# --- Release lock on exit ---
|
|
51
|
+
release_lock() {
|
|
52
|
+
rm -rf "$LOCK_DIR" 2>/dev/null || true
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# --- Main ---
|
|
56
|
+
elapsed=0
|
|
57
|
+
|
|
58
|
+
while true; do
|
|
59
|
+
maybe_clear_stale_lock
|
|
60
|
+
if acquire_lock; then
|
|
61
|
+
trap release_lock EXIT
|
|
62
|
+
break
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# Another process holds the lock — if it already wrote the output, skip
|
|
66
|
+
if [ -d "$DATABASE_DIR/generated/client" ]; then
|
|
67
|
+
echo "db-generate-locked: client already present (written by concurrent process), skipping."
|
|
68
|
+
exit 0
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
elapsed=$((elapsed + POLL_INTERVAL))
|
|
72
|
+
if [ "$elapsed" -ge "$TIMEOUT_SECONDS" ]; then
|
|
73
|
+
echo "db-generate-locked: timed out waiting for lock after ${TIMEOUT_SECONDS}s; running generate anyway." >&2
|
|
74
|
+
break
|
|
75
|
+
fi
|
|
76
|
+
sleep "$POLL_INTERVAL"
|
|
77
|
+
done
|
|
78
|
+
|
|
79
|
+
echo "db-generate-locked: running db:generate for @working-theory/database"
|
|
80
|
+
pnpm --filter @working-theory/database db:generate
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test-user seed for the pre-OSS e2e seam (#517 — §5.1).
|
|
3
|
+
*
|
|
4
|
+
* Seeds three Better Auth User rows distinguished by the `role` field
|
|
5
|
+
* (`user`, `admin`, `owner` — matching the inline comment in schema.prisma).
|
|
6
|
+
* Pair with the triple-gated /api/test/auth/session route to mint sessions
|
|
7
|
+
* for these users from a CI e2e harness without going through OAuth.
|
|
8
|
+
*
|
|
9
|
+
* GUARD — this script REFUSES to run unless AILK_TEST_AUTH_ENABLED === 'true'.
|
|
10
|
+
* A production deployment with this script accidentally invoked but the gate
|
|
11
|
+
* unset will exit non-zero with a loud error rather than silently seeding
|
|
12
|
+
* test-user rows into a real customer DB.
|
|
13
|
+
*
|
|
14
|
+
* Re-run safe — uses `upsert` keyed by email so re-running the seed is
|
|
15
|
+
* idempotent. Will NOT overwrite name or role on subsequent runs (the
|
|
16
|
+
* `update: {}` clause leaves existing rows untouched).
|
|
17
|
+
*
|
|
18
|
+
* Run via:
|
|
19
|
+
* pnpm --filter @working-theory/database db:seed:test
|
|
20
|
+
*
|
|
21
|
+
* Environment variables read:
|
|
22
|
+
* AILK_TEST_AUTH_ENABLED — must be the literal string "true" or this
|
|
23
|
+
* script exits 1 without writing anything.
|
|
24
|
+
* DATABASE_URL — required (the Prisma adapter uses it).
|
|
25
|
+
*/
|
|
26
|
+
import "dotenv/config";
|
|
27
|
+
|
|
28
|
+
import { PrismaClient } from "./generated/client/client.js";
|
|
29
|
+
|
|
30
|
+
const TEST_USERS = [
|
|
31
|
+
{
|
|
32
|
+
id: "test-e2e-user",
|
|
33
|
+
email: "test:e2e-user@ailaunchkit.test",
|
|
34
|
+
name: "E2E Test User",
|
|
35
|
+
role: "user",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "test-e2e-admin",
|
|
39
|
+
email: "test:e2e-admin@ailaunchkit.test",
|
|
40
|
+
name: "E2E Test Admin",
|
|
41
|
+
role: "admin",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "test-e2e-owner",
|
|
45
|
+
email: "test:e2e-owner@ailaunchkit.test",
|
|
46
|
+
name: "E2E Test Owner",
|
|
47
|
+
role: "owner",
|
|
48
|
+
},
|
|
49
|
+
] as const;
|
|
50
|
+
|
|
51
|
+
async function main(): Promise<void> {
|
|
52
|
+
if (process.env.AILK_TEST_AUTH_ENABLED !== "true") {
|
|
53
|
+
console.error(
|
|
54
|
+
"[seed-test] REFUSING to run: AILK_TEST_AUTH_ENABLED must be exactly 'true'. " +
|
|
55
|
+
"This guard prevents accidental seeding of test users into a production DB.",
|
|
56
|
+
);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const prisma = new PrismaClient();
|
|
61
|
+
try {
|
|
62
|
+
for (const user of TEST_USERS) {
|
|
63
|
+
await prisma.user.upsert({
|
|
64
|
+
where: { email: user.email },
|
|
65
|
+
create: {
|
|
66
|
+
id: user.id,
|
|
67
|
+
email: user.email,
|
|
68
|
+
name: user.name,
|
|
69
|
+
emailVerified: true,
|
|
70
|
+
role: user.role,
|
|
71
|
+
},
|
|
72
|
+
update: {},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
console.log(`[seed-test] upserted ${user.email} (role=${user.role})`);
|
|
76
|
+
}
|
|
77
|
+
} finally {
|
|
78
|
+
await prisma.$disconnect();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
main().catch((err) => {
|
|
83
|
+
console.error("[seed-test] failed:", err);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./generated",
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"composite": true,
|
|
10
|
+
"noEmit": false,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"types": ["node"]
|
|
13
|
+
},
|
|
14
|
+
"include": ["generated/**/*.ts"],
|
|
15
|
+
"exclude": ["node_modules", "dist", "__tests__"]
|
|
16
|
+
}
|