create-ailk 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/component-catalog.md +50 -53
- package/dist/lib/apply-module-patches.d.ts +47 -0
- package/dist/lib/apply-module-patches.js +370 -0
- package/dist/lib/extract-module-bundle.d.ts +12 -3
- package/dist/lib/extract-module-bundle.js +236 -24
- package/dist/lib/fetch-module.d.ts +3 -38
- package/dist/lib/fetch-module.js +26 -8
- package/dist/lib/module-license-gate.d.ts +2 -0
- package/dist/lib/module-license-gate.js +1 -0
- package/dist/lib/paid-paths.d.ts +16 -0
- package/dist/lib/paid-paths.js +90 -0
- package/dist/module-architecture.d.ts +139 -23
- package/dist/module-architecture.js +668 -25
- package/dist/parse-args.d.ts +2 -2
- package/dist/parse-args.js +3 -1
- package/dist/programmatic.d.ts +2 -1
- package/dist/programmatic.js +15 -4
- package/dist/surfaces.d.ts +12 -36
- package/dist/surfaces.js +35 -5
- package/dist/sync-routes.js +31 -1
- package/package.json +2 -2
- package/templates/.claude/agents/web.md +2 -3
- package/templates/.claude/rules/architecture.md +5 -5
- package/templates/.claude/skills/README.md +2 -1
- package/templates/.env.example +21 -9
- package/templates/CONVENTIONS.md +6 -7
- package/templates/apps/api/.env.example +9 -0
- package/templates/apps/api/CLAUDE.md +33 -5
- package/templates/apps/api/package.json +0 -1
- package/templates/apps/api/src/__tests__/cors.test.ts +195 -0
- package/templates/apps/api/src/__tests__/module-exclusion.test.ts +60 -2
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +159 -0
- package/templates/apps/api/src/config/__tests__/modules.test.ts +76 -0
- package/templates/apps/api/src/config/index.ts +19 -0
- package/templates/apps/api/src/config/modules.ts +12 -14
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -0
- package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +89 -0
- package/templates/apps/api/src/lib/slice-load.ts +90 -0
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +52 -32
- package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +182 -0
- package/templates/apps/api/src/openapi/__tests__/surface-drift.test.ts +293 -0
- package/templates/apps/api/src/openapi/index.ts +9 -3
- package/templates/apps/api/src/openapi/spec.ts +505 -65
- package/templates/apps/api/src/openapi/surface-drift.ts +195 -0
- package/templates/apps/api/src/routes/content/__tests__/create.test.ts +14 -0
- package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +14 -0
- package/templates/apps/api/src/routes/content/__tests__/update.test.ts +14 -0
- package/templates/apps/api/src/routes/content/index.ts +38 -11
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +17 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +17 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +796 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +10 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +38 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +814 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +10 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +24 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +17 -0
- package/templates/apps/api/src/routes/project-listings/index.ts +37 -10
- package/templates/apps/api/src/routes/project-listings/me.ts +10 -0
- package/templates/apps/api/src/routes/project-listings/patch-draft.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/public.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/respond.ts +11 -0
- package/templates/apps/api/src/routes/project-listings/resume-token.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/start.ts +30 -0
- package/templates/apps/api/src/routes/project-listings/submit.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/verify-email.ts +150 -0
- package/templates/apps/api/src/server.ts +459 -135
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +10 -0
- package/templates/apps/api/src/services/listing-config.ts +58 -0
- package/templates/apps/api/src/services/project-listing-resume-email.ts +326 -0
- package/templates/apps/api/src/services/project-listings.ts +229 -4
- package/templates/apps/api/src/vercel-handler.ts +60 -26
- package/templates/apps/mcp/.env.example +8 -0
- package/templates/apps/mcp/CLAUDE.md +2 -2
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +21 -0
- package/templates/apps/mcp/__tests__/config/modules.test.ts +78 -0
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +1 -1
- package/templates/apps/mcp/src/config/modules.ts +14 -13
- package/templates/apps/mcp/src/server.ts +8 -4
- package/templates/apps/mcp/src/tools/index.ts +7 -22
- package/templates/apps/web/.env.example +15 -0
- package/templates/apps/web/app/[locale]/(authed)/{waitlist/__tests__ → __tests__}/gate.test.tsx +4 -4
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +1 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +1 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +1 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +1 -0
- package/templates/apps/web/app/llms.txt/route.ts +12 -9
- package/templates/apps/web/jest.config.cjs +14 -13
- package/templates/apps/web/next.config.mjs +1 -3
- package/templates/apps/web/package.json +0 -6
- package/templates/apps/web/public/android-chrome-192x192.png +0 -0
- package/templates/apps/web/public/android-chrome-512x512.png +0 -0
- package/templates/apps/web/public/apple-touch-icon.png +0 -0
- package/templates/apps/web/public/favicon-16x16.png +0 -0
- package/templates/apps/web/public/favicon-32x32.png +0 -0
- package/templates/apps/web/public/favicon.ico +0 -0
- package/templates/apps/web/public/favicon.svg +6 -3
- package/templates/apps/web/public/lockup-horizontal.svg +4 -0
- package/templates/apps/web/public/logomark.svg +4 -0
- package/templates/apps/web/public/site.webmanifest +2 -2
- package/templates/apps/web/public/wordmark.svg +4 -0
- package/templates/database/CHANGELOG.md +33 -0
- package/templates/database/inbox/schema.prisma +67 -0
- package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +123 -0
- package/templates/database/package.json +1 -1
- package/templates/package.json +1 -1
- package/templates/.claude/skills/scaffold-commerce/SKILL.md +0 -807
- package/templates/.claude/skills/scaffold-commerce/references/lookup-keys-template.md +0 -71
- package/templates/.claude/skills/scaffold-commerce/references/marketplace-brand-pages.md +0 -83
- package/templates/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts +0 -370
- package/templates/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts +0 -376
- package/templates/.claude/skills/scaffold-commerce/templates/marketplace-cta-route.tsx.tmpl +0 -72
- package/templates/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts +0 -471
- package/templates/.claude/skills/scaffold-commerce/templates/portal-route.template.ts +0 -365
- package/templates/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts +0 -60
- package/templates/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts +0 -446
- package/templates/apps/api/src/__tests__/server.test.ts +0 -253
- package/templates/apps/api/src/bin/deliverable-resend.ts +0 -144
- package/templates/apps/api/src/bin/deliverable-upload.ts +0 -114
- package/templates/apps/api/src/bin/followup-sweep.ts +0 -44
- package/templates/apps/api/src/bin/listing-csv-sweep.ts +0 -46
- package/templates/apps/api/src/bin/seed-presets.ts +0 -58
- package/templates/apps/api/src/bin/seed-waitlist-experiments.ts +0 -104
- package/templates/apps/api/src/bin/session-retention-sweep.ts +0 -46
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -74
- package/templates/apps/api/src/lib/stripe.ts +0 -52
- package/templates/apps/api/src/lib/tenant-db.ts +0 -225
- package/templates/apps/api/src/lib/ws-token.ts +0 -91
- package/templates/apps/api/src/middleware/tenant.ts +0 -99
- package/templates/apps/api/src/routes/billing/__tests__/portal.test.ts +0 -450
- package/templates/apps/api/src/routes/billing/__tests__/read.test.ts +0 -262
- package/templates/apps/api/src/routes/billing/__tests__/usage.test.ts +0 -436
- package/templates/apps/api/src/routes/billing/index.ts +0 -36
- package/templates/apps/api/src/routes/billing/portal.ts +0 -182
- package/templates/apps/api/src/routes/billing/read.ts +0 -75
- package/templates/apps/api/src/routes/billing/usage.ts +0 -153
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -687
- package/templates/apps/api/src/routes/checkout/index.ts +0 -13
- package/templates/apps/api/src/routes/checkout/sessions.ts +0 -197
- package/templates/apps/api/src/routes/deliverables/__tests__/index.test.ts +0 -393
- package/templates/apps/api/src/routes/deliverables/index.ts +0 -200
- package/templates/apps/api/src/routes/flow-checkouts/__tests__/index.test.ts +0 -443
- package/templates/apps/api/src/routes/flow-checkouts/index.ts +0 -82
- package/templates/apps/api/src/routes/flows/README.md +0 -147
- package/templates/apps/api/src/routes/flows/__tests__/index.test.ts +0 -752
- package/templates/apps/api/src/routes/flows/__tests__/recommender.test.ts +0 -671
- package/templates/apps/api/src/routes/flows/index.ts +0 -202
- package/templates/apps/api/src/routes/flows/recommender.ts +0 -443
- package/templates/apps/api/src/routes/project-listings/__tests__/copy-edit.test.ts +0 -815
- package/templates/apps/api/src/routes/project-listings/__tests__/me-route-precedence.test.ts +0 -221
- package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +0 -566
- package/templates/apps/api/src/routes/project-listings/get.ts +0 -70
- package/templates/apps/api/src/routes/project-listings/list.ts +0 -47
- package/templates/apps/api/src/routes/project-listings/patch.ts +0 -136
- package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +0 -490
- package/templates/apps/api/src/routes/schedule/index.ts +0 -249
- package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +0 -385
- package/templates/apps/api/src/routes/slack/actions.ts +0 -177
- package/templates/apps/api/src/routes/slack/index.ts +0 -38
- package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +0 -577
- package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +0 -78
- package/templates/apps/api/src/routes/waitlist-experiments/index.ts +0 -41
- package/templates/apps/api/src/routes/waitlist-experiments/list.ts +0 -64
- package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +0 -42
- package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +0 -84
- package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +0 -119
- package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +0 -238
- package/templates/apps/api/src/routes/waitlist-signups/index.ts +0 -101
- package/templates/apps/api/src/routes/webhooks/README.md +0 -80
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -315
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -247
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -270
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -198
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -633
- package/templates/apps/api/src/routes/webhooks/index.ts +0 -41
- package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -481
- package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +0 -356
- package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +0 -671
- package/templates/apps/api/src/routes/workspaces/config.ts +0 -125
- package/templates/apps/api/src/routes/workspaces/index.ts +0 -194
- package/templates/apps/api/src/routes/workspaces/sessions.ts +0 -218
- package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +0 -425
- package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +0 -2840
- package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +0 -393
- package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +0 -560
- package/templates/apps/api/src/services/__tests__/listing-promotion.test.ts +0 -684
- package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +0 -406
- package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +0 -290
- package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +0 -736
- package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +0 -475
- package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +0 -340
- package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +0 -983
- package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +0 -71
- package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +0 -379
- package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +0 -768
- package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +0 -1314
- package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +0 -341
- package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +0 -567
- package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +0 -474
- package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +0 -354
- package/templates/apps/api/src/services/flow-aggregate.ts +0 -232
- package/templates/apps/api/src/services/flow-checkouts.ts +0 -123
- package/templates/apps/api/src/services/flow-engine.ts +0 -1296
- package/templates/apps/api/src/services/lead-promotion.ts +0 -176
- package/templates/apps/api/src/services/listing-csv-sweep.ts +0 -455
- package/templates/apps/api/src/services/listing-promotion.ts +0 -342
- package/templates/apps/api/src/services/playbook-compile.ts +0 -398
- package/templates/apps/api/src/services/playbook-render.ts +0 -263
- package/templates/apps/api/src/services/project-listing-decision.ts +0 -510
- package/templates/apps/api/src/services/project-listing-flow.ts +0 -426
- package/templates/apps/api/src/services/project-listing-issue.ts +0 -251
- package/templates/apps/api/src/services/recommender-capture.ts +0 -835
- package/templates/apps/api/src/services/scheduling/cal-provider.ts +0 -392
- package/templates/apps/api/src/services/scheduling/index.ts +0 -63
- package/templates/apps/api/src/services/scheduling/types.ts +0 -88
- package/templates/apps/api/src/services/tenant-context.ts +0 -451
- package/templates/apps/api/src/services/usage-metering.ts +0 -699
- package/templates/apps/api/src/services/waitlist-dashboard.ts +0 -947
- package/templates/apps/api/src/services/waitlist-experiments.ts +0 -213
- package/templates/apps/api/src/services/waitlist-followup.ts +0 -486
- package/templates/apps/api/src/services/waitlist-scoring.ts +0 -166
- package/templates/apps/api/src/services/waitlist-signups.ts +0 -165
- package/templates/apps/mcp/__tests__/schedule_tools.test.ts +0 -242
- package/templates/apps/mcp/src/tools/create_booking.ts +0 -58
- package/templates/apps/mcp/src/tools/get_event_meta.ts +0 -55
- package/templates/apps/mcp/src/tools/get_flow.ts +0 -61
- package/templates/apps/mcp/src/tools/list_availability.ts +0 -67
- package/templates/apps/mcp/src/tools/submit_flow_step.ts +0 -83
- package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +0 -309
- package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +0 -149
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +0 -273
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +0 -171
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +0 -179
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +0 -116
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +0 -66
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +0 -61
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +0 -114
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +0 -139
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +0 -85
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +0 -67
- package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +0 -179
- package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +0 -178
- package/templates/apps/web/app/[locale]/dev/purchase/__tests__/actions.test.ts +0 -160
- package/templates/apps/web/app/[locale]/dev/purchase/error.tsx +0 -25
- package/templates/apps/web/app/[locale]/dev/purchase/page.tsx +0 -117
- package/templates/apps/web/app/[locale]/dev/purchase/purchase.actions.ts +0 -58
- package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +0 -119
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +0 -287
- package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +0 -254
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +0 -532
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +0 -121
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +0 -457
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +0 -396
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +0 -114
- package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +0 -110
- package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +0 -96
- package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +0 -117
- package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +0 -96
- package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +0 -92
- package/templates/apps/web/app/[locale]/schedule/page.tsx +0 -82
- package/templates/apps/web/content/en/blog/README.txt +0 -25
- package/templates/apps/web/content/en/docs/index.mdx +0 -28
- package/templates/apps/web/lib/__mocks__/source-server.js +0 -31
- package/templates/apps/web/lib/blog-list-data.ts +0 -95
- package/templates/apps/web/lib/blog-post-data.ts +0 -79
- package/templates/apps/web/lib/source.ts +0 -34
- package/templates/apps/web/public/logomark-dark.svg +0 -4
- package/templates/apps/web/public/logomark-light.svg +0 -4
- package/templates/apps/web/source.config.ts +0 -78
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
// oss: false
|
|
2
|
-
/**
|
|
3
|
-
* Workspace site-config routes (multi-tenant module — FEAT-042; STC-P4 #3634).
|
|
4
|
-
*
|
|
5
|
-
* The write surface an end-tenant uses to theme THEIR OWN workspace at runtime:
|
|
6
|
-
*
|
|
7
|
-
* GET /v1/workspaces/:workspaceId/theme · PUT /v1/workspaces/:workspaceId/theme
|
|
8
|
-
* GET /v1/workspaces/:workspaceId/identity · PUT /v1/workspaces/:workspaceId/identity
|
|
9
|
-
* GET /v1/workspaces/:workspaceId/seo · PUT /v1/workspaces/:workspaceId/seo
|
|
10
|
-
*
|
|
11
|
-
* All tenant-guarded (tenantPreHandlerPlugin runs first): the cross-workspace
|
|
12
|
-
* 403 is enforced by the guard's `params.workspaceId ≠ resolved` check BEFORE
|
|
13
|
-
* any handler runs — no config query executes for a foreign workspace. The
|
|
14
|
-
* scope is ALWAYS `request.tenant.workspaceId` (server-resolved from the
|
|
15
|
-
* validated session); no handler reads a scope from the body/query. Each config
|
|
16
|
-
* model is `workspaceId @unique`, so the `tenantDb` accessor's
|
|
17
|
-
* `findUnique`/`upsert` on `{ workspaceId }` is inherently tenant-scoped.
|
|
18
|
-
*
|
|
19
|
-
* PUT validates the body against the shared @working-theory/site-config schema
|
|
20
|
-
* (400 on failure) and stores the validated blob in the `data` Json column. GET
|
|
21
|
-
* returns the stored blob, or `{ ok: true, data: null }` for a not-yet-configured
|
|
22
|
-
* workspace (a valid runtime state — the render/seed default is Phase 5).
|
|
23
|
-
*
|
|
24
|
-
* Envelope per apps/api/CLAUDE.md: `{ ok: true, data }` /
|
|
25
|
-
* `{ ok: false, error: { code, message } }`, real HTTP status codes.
|
|
26
|
-
*
|
|
27
|
-
* MCP parity note: identity-bound tenant routes; the OSS MCP surface is
|
|
28
|
-
* identity-blind (apps/mcp/CLAUDE.md), so — like the sibling /v1/workspaces
|
|
29
|
-
* routes — they have no anonymous MCP twin. The tenant-scoped authed MCP
|
|
30
|
-
* surface is tracked in #3591.
|
|
31
|
-
*/
|
|
32
|
-
import { zWorkspaceId } from "@working-theory/ids";
|
|
33
|
-
import {
|
|
34
|
-
SEOSchema,
|
|
35
|
-
SiteIdentitySchema,
|
|
36
|
-
ThemeSchema,
|
|
37
|
-
} from "@working-theory/site-config/schema";
|
|
38
|
-
import type { FastifyPluginAsync, FastifyReply } from "fastify";
|
|
39
|
-
import type { ZodType } from "zod";
|
|
40
|
-
import { z } from "zod";
|
|
41
|
-
|
|
42
|
-
import { tenantDb } from "../../lib/tenant-db.js";
|
|
43
|
-
import type { TenantDb } from "../../lib/tenant-db.js";
|
|
44
|
-
|
|
45
|
-
function unauthorized(reply: FastifyReply) {
|
|
46
|
-
return reply.status(401).send({
|
|
47
|
-
ok: false,
|
|
48
|
-
error: { code: "unauthorized", message: "Authentication required" },
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function invalidId(reply: FastifyReply) {
|
|
53
|
-
return reply.status(400).send({
|
|
54
|
-
ok: false,
|
|
55
|
-
error: { code: "validation_failed", message: "Invalid workspace id" },
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Boundary id validation (ADR-0031 rule 5): the :workspaceId param must be a
|
|
60
|
-
// well-formed workspace id before any handler logic runs.
|
|
61
|
-
const workspaceParamsSchema = z.object({ workspaceId: zWorkspaceId });
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* The three config types share one GET/PUT shape — the only per-type differences
|
|
65
|
-
* are the URL segment, the validation schema, and which `tenantDb` accessor to
|
|
66
|
-
* reach. `accessor` names a config accessor on `TenantDb` (`get` + `upsert`).
|
|
67
|
-
*/
|
|
68
|
-
type ConfigAccessorKey = "theme" | "siteIdentity" | "seo";
|
|
69
|
-
|
|
70
|
-
interface ConfigType {
|
|
71
|
-
segment: string;
|
|
72
|
-
schema: ZodType;
|
|
73
|
-
accessor: ConfigAccessorKey;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const CONFIG_TYPES: ConfigType[] = [
|
|
77
|
-
{ segment: "theme", schema: ThemeSchema, accessor: "theme" },
|
|
78
|
-
{ segment: "identity", schema: SiteIdentitySchema, accessor: "siteIdentity" },
|
|
79
|
-
{ segment: "seo", schema: SEOSchema, accessor: "seo" },
|
|
80
|
-
];
|
|
81
|
-
|
|
82
|
-
export const workspaceConfigRoutes: FastifyPluginAsync = async (fastify) => {
|
|
83
|
-
for (const { segment, schema, accessor } of CONFIG_TYPES) {
|
|
84
|
-
fastify.get<{ Params: { workspaceId: string } }>(
|
|
85
|
-
`/v1/workspaces/:workspaceId/${segment}`,
|
|
86
|
-
async (request, reply) => {
|
|
87
|
-
const tenant = request.tenant;
|
|
88
|
-
if (!tenant) return unauthorized(reply);
|
|
89
|
-
|
|
90
|
-
const params = workspaceParamsSchema.safeParse(request.params);
|
|
91
|
-
if (!params.success) return invalidId(reply);
|
|
92
|
-
|
|
93
|
-
const db: TenantDb = tenantDb(tenant);
|
|
94
|
-
const row = await db[accessor].get();
|
|
95
|
-
// A not-yet-configured workspace is a valid state, not an error (D6).
|
|
96
|
-
return reply.send({ ok: true, data: row?.data ?? null });
|
|
97
|
-
},
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
fastify.put<{ Params: { workspaceId: string } }>(
|
|
101
|
-
`/v1/workspaces/:workspaceId/${segment}`,
|
|
102
|
-
async (request, reply) => {
|
|
103
|
-
const tenant = request.tenant;
|
|
104
|
-
if (!tenant) return unauthorized(reply);
|
|
105
|
-
|
|
106
|
-
const params = workspaceParamsSchema.safeParse(request.params);
|
|
107
|
-
if (!params.success) return invalidId(reply);
|
|
108
|
-
|
|
109
|
-
// The ONLY untrusted input: validate against the shared schema. The
|
|
110
|
-
// scope is NEVER read from the body — it is tenant.workspaceId.
|
|
111
|
-
const parsed = schema.safeParse(request.body);
|
|
112
|
-
if (!parsed.success) {
|
|
113
|
-
return reply.status(400).send({
|
|
114
|
-
ok: false,
|
|
115
|
-
error: { code: "validation_failed", message: "Invalid config" },
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const db: TenantDb = tenantDb(tenant);
|
|
120
|
-
const row = await db[accessor].upsert(parsed.data);
|
|
121
|
-
return reply.send({ ok: true, data: row.data });
|
|
122
|
-
},
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
};
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
// oss: false
|
|
2
|
-
/**
|
|
3
|
-
* Workspace routes (multi-tenant module — FEAT-042).
|
|
4
|
-
*
|
|
5
|
-
* Two registration scopes (see server.ts):
|
|
6
|
-
*
|
|
7
|
-
* AUTH-ONLY (no tenant guard — they establish/inspect tenant context):
|
|
8
|
-
* GET /v1/workspaces — switcher listing (orgs + workspaces)
|
|
9
|
-
* POST /v1/workspaces — create workspace (role admin+)
|
|
10
|
-
* POST /v1/workspaces/:workspaceId/activate — switch active workspace
|
|
11
|
-
*
|
|
12
|
-
* TENANT-GUARDED (tenantPreHandlerPlugin runs first; request.tenant set):
|
|
13
|
-
* GET /v1/workspaces/:workspaceId/sessions — list agent sessions (+unread)
|
|
14
|
-
* POST /v1/workspaces/:workspaceId/sessions — create agent session
|
|
15
|
-
*
|
|
16
|
-
* Envelope per apps/api/CLAUDE.md: `{ ok: true, data }` /
|
|
17
|
-
* `{ ok: false, error: { code, message } }`, real HTTP status codes.
|
|
18
|
-
*
|
|
19
|
-
* MCP parity note: these are identity-bound tenant routes; the OSS MCP
|
|
20
|
-
* surface is identity-blind (apps/mcp/CLAUDE.md), so — like the existing
|
|
21
|
-
* /v1/auth/tokens and /v1/user routes — they have no anonymous MCP twin.
|
|
22
|
-
* The tenant-scoped authed MCP surface is tracked in #3591.
|
|
23
|
-
*/
|
|
24
|
-
import { zOrganizationId, zWorkspaceId } from "@working-theory/ids";
|
|
25
|
-
import type { FastifyPluginAsync, FastifyReply } from "fastify";
|
|
26
|
-
import { z } from "zod";
|
|
27
|
-
|
|
28
|
-
import { tenantDb } from "../../lib/tenant-db.js";
|
|
29
|
-
import {
|
|
30
|
-
activateWorkspace,
|
|
31
|
-
createWorkspace,
|
|
32
|
-
listTenancy,
|
|
33
|
-
type TenantErrorKind,
|
|
34
|
-
} from "../../services/tenant-context.js";
|
|
35
|
-
|
|
36
|
-
const TENANT_ERROR_STATUS: Record<TenantErrorKind, number> = {
|
|
37
|
-
forbidden: 403,
|
|
38
|
-
not_found: 404,
|
|
39
|
-
conflict: 409,
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
function sendTenantError(
|
|
43
|
-
reply: FastifyReply,
|
|
44
|
-
error: { kind: TenantErrorKind; message: string },
|
|
45
|
-
) {
|
|
46
|
-
return reply.status(TENANT_ERROR_STATUS[error.kind]).send({
|
|
47
|
-
ok: false,
|
|
48
|
-
error: { code: error.kind, message: error.message },
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function unauthorized(reply: FastifyReply) {
|
|
53
|
-
return reply.status(401).send({
|
|
54
|
-
ok: false,
|
|
55
|
-
error: { code: "unauthorized", message: "Authentication required" },
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const createWorkspaceSchema = z.object({
|
|
60
|
-
// Boundary id validation (ADR-0031 rule 5): the entity-typed Zod schema,
|
|
61
|
-
// not z.string().min(1) — a user_/wksp_ id passed here is a 400, not a query.
|
|
62
|
-
organizationId: zOrganizationId,
|
|
63
|
-
name: z.string().min(1).max(80),
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const workspaceParamsSchema = z.object({ workspaceId: zWorkspaceId });
|
|
67
|
-
|
|
68
|
-
const createSessionSchema = z.object({
|
|
69
|
-
title: z.string().min(1).max(200).optional(),
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
const listSessionsQuerySchema = z.object({
|
|
73
|
-
limit: z.coerce.number().int().min(1).max(100).default(50),
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
/** Auth-only workspace routes — establish/inspect tenant context. */
|
|
77
|
-
export const workspaceContextRoutes: FastifyPluginAsync = async (fastify) => {
|
|
78
|
-
fastify.get("/v1/workspaces", async (request, reply) => {
|
|
79
|
-
const userId = request.userId;
|
|
80
|
-
if (!userId) return unauthorized(reply);
|
|
81
|
-
|
|
82
|
-
const result = await listTenancy({
|
|
83
|
-
userId,
|
|
84
|
-
sessionTenant: request.sessionTenant,
|
|
85
|
-
});
|
|
86
|
-
if (!result.ok) return sendTenantError(reply, result.error);
|
|
87
|
-
return reply.send({ ok: true, data: result.value });
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
fastify.post("/v1/workspaces", async (request, reply) => {
|
|
91
|
-
const userId = request.userId;
|
|
92
|
-
if (!userId) return unauthorized(reply);
|
|
93
|
-
|
|
94
|
-
const parsed = createWorkspaceSchema.safeParse(request.body);
|
|
95
|
-
if (!parsed.success) {
|
|
96
|
-
return reply.status(400).send({
|
|
97
|
-
ok: false,
|
|
98
|
-
error: { code: "validation_failed", message: "Invalid workspace input" },
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const result = await createWorkspace({ userId, ...parsed.data });
|
|
103
|
-
if (!result.ok) return sendTenantError(reply, result.error);
|
|
104
|
-
return reply.status(201).send({ ok: true, data: result.value });
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
fastify.post<{ Params: { workspaceId: string } }>(
|
|
108
|
-
"/v1/workspaces/:workspaceId/activate",
|
|
109
|
-
async (request, reply) => {
|
|
110
|
-
const userId = request.userId;
|
|
111
|
-
if (!userId) return unauthorized(reply);
|
|
112
|
-
|
|
113
|
-
const params = workspaceParamsSchema.safeParse(request.params);
|
|
114
|
-
if (!params.success) {
|
|
115
|
-
return reply.status(400).send({
|
|
116
|
-
ok: false,
|
|
117
|
-
error: { code: "validation_failed", message: "Invalid workspace id" },
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const result = await activateWorkspace({
|
|
122
|
-
userId,
|
|
123
|
-
sessionTenant: request.sessionTenant,
|
|
124
|
-
workspaceId: params.data.workspaceId,
|
|
125
|
-
});
|
|
126
|
-
if (!result.ok) return sendTenantError(reply, result.error);
|
|
127
|
-
return reply.send({ ok: true, data: result.value });
|
|
128
|
-
},
|
|
129
|
-
);
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
/** Tenant-guarded, workspace-scoped domain routes. */
|
|
133
|
-
export const workspaceSessionRoutes: FastifyPluginAsync = async (fastify) => {
|
|
134
|
-
fastify.get<{ Params: { workspaceId: string } }>(
|
|
135
|
-
"/v1/workspaces/:workspaceId/sessions",
|
|
136
|
-
async (request, reply) => {
|
|
137
|
-
const tenant = request.tenant;
|
|
138
|
-
if (!tenant) return unauthorized(reply);
|
|
139
|
-
|
|
140
|
-
const query = listSessionsQuerySchema.safeParse(request.query ?? {});
|
|
141
|
-
if (!query.success) {
|
|
142
|
-
return reply.status(400).send({
|
|
143
|
-
ok: false,
|
|
144
|
-
error: { code: "validation_failed", message: "Invalid query" },
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const db = tenantDb(tenant);
|
|
149
|
-
const [sessions, receipts] = await Promise.all([
|
|
150
|
-
db.agentSession.list(query.data.limit),
|
|
151
|
-
db.readReceipt.listForUser(),
|
|
152
|
-
]);
|
|
153
|
-
const readBySession = new Map(
|
|
154
|
-
receipts.map((r) => [r.sessionId, r.lastReadSeq]),
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
return reply.send({
|
|
158
|
-
ok: true,
|
|
159
|
-
data: {
|
|
160
|
-
sessions: sessions.map((s) => ({
|
|
161
|
-
id: s.id,
|
|
162
|
-
title: s.title,
|
|
163
|
-
status: s.status,
|
|
164
|
-
lastMessageAt: s.lastMessageAt,
|
|
165
|
-
isWorking: s.status === "working",
|
|
166
|
-
hasUnread: s.lastSeq > (readBySession.get(s.id) ?? 0),
|
|
167
|
-
createdAt: s.createdAt,
|
|
168
|
-
})),
|
|
169
|
-
},
|
|
170
|
-
});
|
|
171
|
-
},
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
fastify.post<{ Params: { workspaceId: string } }>(
|
|
175
|
-
"/v1/workspaces/:workspaceId/sessions",
|
|
176
|
-
async (request, reply) => {
|
|
177
|
-
const tenant = request.tenant;
|
|
178
|
-
if (!tenant) return unauthorized(reply);
|
|
179
|
-
|
|
180
|
-
const parsed = createSessionSchema.safeParse(request.body ?? {});
|
|
181
|
-
if (!parsed.success) {
|
|
182
|
-
return reply.status(400).send({
|
|
183
|
-
ok: false,
|
|
184
|
-
error: { code: "validation_failed", message: "Invalid session input" },
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const session = await tenantDb(tenant).agentSession.create({
|
|
189
|
-
title: parsed.data.title ?? null,
|
|
190
|
-
});
|
|
191
|
-
return reply.status(201).send({ ok: true, data: session });
|
|
192
|
-
},
|
|
193
|
-
);
|
|
194
|
-
};
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
// oss: false
|
|
2
|
-
/**
|
|
3
|
-
* Session-target routes (multi-tenant module — FEAT-042; transport contract
|
|
4
|
-
* from docs/research/ploy-capture/BACKEND-REQUIREMENTS.md §1/§3).
|
|
5
|
-
*
|
|
6
|
-
* All tenant-guarded (tenantPreHandlerPlugin): request.tenant is the caller's
|
|
7
|
-
* validated active workspace. Targets are session/artifact ids; every lookup
|
|
8
|
-
* goes through tenantDb's workspace-scoped `findFirst({ id, workspaceId })`,
|
|
9
|
-
* so a foreign-tenant target 404s exactly like a missing one — cross-org
|
|
10
|
-
* reads AND writes are denied without revealing existence.
|
|
11
|
-
*
|
|
12
|
-
* GET /v1/sessions/:sessionId/conversation-events?sinceSeq=N&limit=M
|
|
13
|
-
* → { events[], lastSeq } (R-T4 replay)
|
|
14
|
-
* POST /v1/sessions/:sessionId/mark-read (R-T7)
|
|
15
|
-
* GET /v1/sessions/:sessionId/ws-token (R-T3 short-lived token)
|
|
16
|
-
* GET /v1/sessions/:sessionId/artifacts (canvas hydration)
|
|
17
|
-
* GET /v1/artifacts/:artifactId/versions (VersionHistory hydration)
|
|
18
|
-
*
|
|
19
|
-
* The realtime push channel itself (WS server consuming these tokens) is
|
|
20
|
-
* Phase-6 scope; this run supplies the replayable, workspace-scoped data
|
|
21
|
-
* plane the frame reads.
|
|
22
|
-
*/
|
|
23
|
-
import { zAgentSessionId, zArtifactId } from "@working-theory/ids";
|
|
24
|
-
import type { FastifyPluginAsync, FastifyReply } from "fastify";
|
|
25
|
-
import { z } from "zod";
|
|
26
|
-
|
|
27
|
-
import { tenantDb } from "../../lib/tenant-db.js";
|
|
28
|
-
import { mintWsToken } from "../../lib/ws-token.js";
|
|
29
|
-
|
|
30
|
-
function notFound(reply: FastifyReply) {
|
|
31
|
-
return reply.status(404).send({
|
|
32
|
-
ok: false,
|
|
33
|
-
error: { code: "not_found", message: "Not found" },
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function invalidId(reply: FastifyReply) {
|
|
38
|
-
return reply.status(400).send({
|
|
39
|
-
ok: false,
|
|
40
|
-
error: { code: "validation_failed", message: "Invalid id" },
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Boundary id validation (ADR-0031 rule 5): target params must be well-formed
|
|
45
|
-
// entity ids (agsess_/artif_ prefixed uuids) before any lookup runs.
|
|
46
|
-
const sessionParamsSchema = z.object({ sessionId: zAgentSessionId });
|
|
47
|
-
const artifactParamsSchema = z.object({ artifactId: zArtifactId });
|
|
48
|
-
|
|
49
|
-
function unauthorized(reply: FastifyReply) {
|
|
50
|
-
return reply.status(401).send({
|
|
51
|
-
ok: false,
|
|
52
|
-
error: { code: "unauthorized", message: "Authentication required" },
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const eventsQuerySchema = z.object({
|
|
57
|
-
sinceSeq: z.coerce.number().int().min(0).default(0),
|
|
58
|
-
limit: z.coerce.number().int().min(1).max(500).default(200),
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const markReadSchema = z.object({
|
|
62
|
-
seq: z.coerce.number().int().min(0).optional(),
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
export const sessionRoutes: FastifyPluginAsync = async (fastify) => {
|
|
66
|
-
fastify.get<{ Params: { sessionId: string } }>(
|
|
67
|
-
"/v1/sessions/:sessionId/conversation-events",
|
|
68
|
-
async (request, reply) => {
|
|
69
|
-
const tenant = request.tenant;
|
|
70
|
-
if (!tenant) return unauthorized(reply);
|
|
71
|
-
|
|
72
|
-
const query = eventsQuerySchema.safeParse(request.query ?? {});
|
|
73
|
-
if (!query.success) {
|
|
74
|
-
return reply.status(400).send({
|
|
75
|
-
ok: false,
|
|
76
|
-
error: { code: "validation_failed", message: "Invalid query" },
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const params = sessionParamsSchema.safeParse(request.params);
|
|
81
|
-
if (!params.success) return invalidId(reply);
|
|
82
|
-
|
|
83
|
-
const db = tenantDb(tenant);
|
|
84
|
-
const session = await db.agentSession.byId(params.data.sessionId);
|
|
85
|
-
if (!session) return notFound(reply);
|
|
86
|
-
|
|
87
|
-
const events = await db.conversationEvent.listSince(
|
|
88
|
-
params.data.sessionId,
|
|
89
|
-
query.data.sinceSeq,
|
|
90
|
-
query.data.limit,
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
return reply.send({
|
|
94
|
-
ok: true,
|
|
95
|
-
data: {
|
|
96
|
-
events: events.map((e) => ({
|
|
97
|
-
agentId: e.agentId,
|
|
98
|
-
seq: e.seq,
|
|
99
|
-
type: e.type,
|
|
100
|
-
turnId: e.turnId,
|
|
101
|
-
payload: e.payload,
|
|
102
|
-
createdAt: e.createdAt,
|
|
103
|
-
})),
|
|
104
|
-
lastSeq: session.lastSeq,
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
},
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
fastify.post<{ Params: { sessionId: string } }>(
|
|
111
|
-
"/v1/sessions/:sessionId/mark-read",
|
|
112
|
-
async (request, reply) => {
|
|
113
|
-
const tenant = request.tenant;
|
|
114
|
-
if (!tenant) return unauthorized(reply);
|
|
115
|
-
|
|
116
|
-
const parsed = markReadSchema.safeParse(request.body ?? {});
|
|
117
|
-
if (!parsed.success) {
|
|
118
|
-
return reply.status(400).send({
|
|
119
|
-
ok: false,
|
|
120
|
-
error: { code: "validation_failed", message: "Invalid body" },
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const params = sessionParamsSchema.safeParse(request.params);
|
|
125
|
-
if (!params.success) return invalidId(reply);
|
|
126
|
-
|
|
127
|
-
const db = tenantDb(tenant);
|
|
128
|
-
// Scoped resolve FIRST — the upsert below must never run for a session
|
|
129
|
-
// outside the tenant (a receipt row would attach to a foreign session).
|
|
130
|
-
const session = await db.agentSession.byId(params.data.sessionId);
|
|
131
|
-
if (!session) return notFound(reply);
|
|
132
|
-
|
|
133
|
-
const lastReadSeq = parsed.data.seq ?? session.lastSeq;
|
|
134
|
-
await db.readReceipt.upsert(params.data.sessionId, lastReadSeq);
|
|
135
|
-
return reply.send({ ok: true, data: { sessionId: session.id, lastReadSeq } });
|
|
136
|
-
},
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
fastify.get<{ Params: { sessionId: string } }>(
|
|
140
|
-
"/v1/sessions/:sessionId/ws-token",
|
|
141
|
-
async (request, reply) => {
|
|
142
|
-
const tenant = request.tenant;
|
|
143
|
-
if (!tenant) return unauthorized(reply);
|
|
144
|
-
|
|
145
|
-
const params = sessionParamsSchema.safeParse(request.params);
|
|
146
|
-
if (!params.success) return invalidId(reply);
|
|
147
|
-
|
|
148
|
-
const session = await tenantDb(tenant).agentSession.byId(
|
|
149
|
-
params.data.sessionId,
|
|
150
|
-
);
|
|
151
|
-
if (!session) return notFound(reply);
|
|
152
|
-
|
|
153
|
-
const minted = mintWsToken({
|
|
154
|
-
userId: tenant.userId,
|
|
155
|
-
sessionId: params.data.sessionId,
|
|
156
|
-
workspaceId: tenant.workspaceId,
|
|
157
|
-
});
|
|
158
|
-
return reply.send({ ok: true, data: minted });
|
|
159
|
-
},
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
fastify.get<{ Params: { sessionId: string } }>(
|
|
163
|
-
"/v1/sessions/:sessionId/artifacts",
|
|
164
|
-
async (request, reply) => {
|
|
165
|
-
const tenant = request.tenant;
|
|
166
|
-
if (!tenant) return unauthorized(reply);
|
|
167
|
-
|
|
168
|
-
const params = sessionParamsSchema.safeParse(request.params);
|
|
169
|
-
if (!params.success) return invalidId(reply);
|
|
170
|
-
|
|
171
|
-
const db = tenantDb(tenant);
|
|
172
|
-
const session = await db.agentSession.byId(params.data.sessionId);
|
|
173
|
-
if (!session) return notFound(reply);
|
|
174
|
-
|
|
175
|
-
const artifacts = await db.artifact.listBySession(params.data.sessionId);
|
|
176
|
-
return reply.send({
|
|
177
|
-
ok: true,
|
|
178
|
-
data: {
|
|
179
|
-
artifacts: artifacts.map((a) => ({
|
|
180
|
-
id: a.id,
|
|
181
|
-
type: a.type,
|
|
182
|
-
title: a.title,
|
|
183
|
-
updatedAt: a.updatedAt,
|
|
184
|
-
})),
|
|
185
|
-
},
|
|
186
|
-
});
|
|
187
|
-
},
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
fastify.get<{ Params: { artifactId: string } }>(
|
|
191
|
-
"/v1/artifacts/:artifactId/versions",
|
|
192
|
-
async (request, reply) => {
|
|
193
|
-
const tenant = request.tenant;
|
|
194
|
-
if (!tenant) return unauthorized(reply);
|
|
195
|
-
|
|
196
|
-
const params = artifactParamsSchema.safeParse(request.params);
|
|
197
|
-
if (!params.success) return invalidId(reply);
|
|
198
|
-
|
|
199
|
-
const db = tenantDb(tenant);
|
|
200
|
-
const artifact = await db.artifact.byId(params.data.artifactId);
|
|
201
|
-
if (!artifact) return notFound(reply);
|
|
202
|
-
|
|
203
|
-
const versions = await db.artifactVersion.listByArtifact(
|
|
204
|
-
params.data.artifactId,
|
|
205
|
-
);
|
|
206
|
-
return reply.send({
|
|
207
|
-
ok: true,
|
|
208
|
-
data: {
|
|
209
|
-
versions: versions.map((v) => ({
|
|
210
|
-
id: v.id,
|
|
211
|
-
savedAt: v.savedAt,
|
|
212
|
-
isCurrent: v.isCurrent,
|
|
213
|
-
})),
|
|
214
|
-
},
|
|
215
|
-
});
|
|
216
|
-
},
|
|
217
|
-
);
|
|
218
|
-
};
|