create-ailk 0.18.0 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +6 -5
- package/NOTICE +2 -0
- package/dist/add-module.js +11 -3
- package/dist/category-profile.d.ts +10 -0
- package/dist/category-profile.js +21 -0
- package/dist/derive-architecture.d.ts +5 -1
- package/dist/derive-architecture.js +8 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/license-key-files.d.ts +6 -3
- package/dist/lib/license-key-files.js +8 -2
- package/dist/lib/scaffold-render-sweep.d.ts +32 -0
- package/dist/lib/scaffold-render-sweep.js +76 -3
- package/dist/license-set.d.ts +1 -1
- package/dist/license-set.js +13 -8
- package/dist/module-architecture.js +128 -3
- package/dist/programmatic.js +11 -1
- package/dist/site-roster.js +21 -4
- package/dist/spoke-checkout.d.ts +11 -3
- package/dist/spoke-checkout.js +62 -12
- package/dist/starter-grid.d.ts +1 -1
- package/dist/surfaces.d.ts +14 -0
- package/dist/surfaces.js +170 -1
- package/dist/template-manifest.d.ts +4 -3
- package/dist/topology.d.ts +7 -2
- package/dist/topology.js +88 -3
- package/package.json +5 -4
- package/templates/.env.example +3 -3
- package/templates/.github/workflows/ci.yml +5 -0
- package/templates/apps/api/.env.example +17 -1
- package/templates/apps/api/CLAUDE.md +31 -7
- package/templates/apps/api/LICENSE +6 -5
- package/templates/apps/api/NOTICE +2 -0
- package/templates/apps/api/jest.config.cjs +4 -29
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +19 -0
- package/templates/apps/api/src/__tests__/stripe-boot.test.ts +132 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +17 -0
- package/templates/apps/api/src/lib/__tests__/consent-ledger-double.ts +237 -0
- package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +334 -0
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +38 -0
- package/templates/apps/api/src/lib/__tests__/stripe-admission.test.ts +217 -0
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +7 -0
- package/templates/apps/api/src/lib/consent-store.ts +335 -0
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +5 -4
- package/templates/apps/api/src/lib/stripe.ts +176 -3
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +272 -39
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +51 -0
- package/templates/apps/api/src/middleware/auth.ts +214 -46
- package/templates/apps/api/src/middleware/rate-limit.ts +5 -0
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +167 -0
- package/templates/apps/api/src/routes/checkout/session.ts +3 -3
- package/templates/apps/api/src/routes/checkout/sessions.ts +6 -4
- package/templates/apps/api/src/routes/consent/cookie.ts +90 -0
- package/templates/apps/api/src/routes/consent/index.ts +18 -0
- package/templates/apps/api/src/routes/consent/unsubscribe.ts +127 -0
- package/templates/apps/api/src/routes/leads/index.ts +19 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +5 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +17 -4
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +2 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode-live.test.ts +159 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode.test.ts +158 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +2 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +17 -0
- package/templates/apps/api/src/server.ts +218 -16
- package/templates/apps/api/src/services/__tests__/consent-ledger.test.ts +503 -0
- package/templates/apps/api/src/services/__tests__/consent.test.ts +18 -3
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +173 -4
- package/templates/apps/api/src/services/__tests__/person.test.ts +392 -0
- package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +87 -0
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +57 -17
- package/templates/apps/api/src/services/consent.ts +488 -3
- package/templates/apps/api/src/services/lead-routing.ts +135 -14
- package/templates/apps/api/src/services/person.ts +501 -1
- package/templates/apps/api/src/services/project-listings.ts +7 -2
- package/templates/apps/api/src/services/request-confirm.ts +52 -0
- package/templates/apps/api/src/services/revalidation.ts +11 -1
- package/templates/apps/api/vercel.json +2 -2
- package/templates/apps/mcp/.env.example +10 -0
- package/templates/apps/mcp/CLAUDE.md +9 -4
- package/templates/apps/mcp/LICENSE +6 -5
- package/templates/apps/mcp/NOTICE +2 -0
- package/templates/apps/mcp/__tests__/abuse/guard.test.ts +198 -0
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +15 -3
- package/templates/apps/mcp/__tests__/boot.test.ts +78 -1
- package/templates/apps/mcp/__tests__/http-transport.test.ts +291 -2
- package/templates/apps/mcp/__tests__/request_callback.test.ts +69 -7
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +69 -8
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +69 -7
- package/templates/apps/mcp/jest.config.cjs +3 -44
- package/templates/apps/mcp/src/abuse/index.ts +117 -21
- package/templates/apps/mcp/src/boot.ts +5 -23
- package/templates/apps/mcp/src/http-transport.ts +93 -73
- package/templates/apps/mcp/src/index.ts +2 -0
- package/templates/apps/mcp/src/request-context.ts +52 -0
- package/templates/apps/mcp/src/tools/capture_lead.ts +9 -1
- package/templates/apps/mcp/src/tools/request_callback.ts +9 -1
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +9 -1
- package/templates/apps/web/LICENSE +6 -5
- package/templates/apps/web/NOTICE +2 -0
- package/templates/apps/web/app/.well-known/ai-plugin.json/route.ts +3 -3
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/account-frame-sign-out.test.tsx +175 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/billing.test.tsx +302 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/general.test.tsx +724 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/hub-mode.test.tsx +346 -41
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/item.test.tsx +11 -13
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout-sign-out.test.tsx +146 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +232 -29
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-menu.test.tsx +305 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-popups.test.tsx +82 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/notifications.test.tsx +140 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +163 -72
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/popups.test.tsx +451 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/security.test.tsx +409 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/settings.test.tsx +65 -380
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/support.test.tsx +16 -11
- package/templates/apps/web/app/[locale]/(authed)/account/account-data.ts +1 -1
- package/templates/apps/web/app/[locale]/(authed)/account/account-frame.tsx +188 -54
- package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +24 -45
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/page.tsx +253 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/review-form.tsx +145 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/versions/[version]/file/route.ts +100 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/file-route.test.ts +179 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/page.test.tsx +514 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/review.test.tsx +288 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/artifact-actions.ts +123 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/artifact-data.ts +82 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/page.tsx +174 -0
- package/templates/apps/web/app/[locale]/(authed)/account/billing/page.tsx +172 -0
- package/templates/apps/web/app/[locale]/(authed)/account/billing-source.ts +147 -0
- package/templates/apps/web/app/[locale]/(authed)/account/components/request-quote.tsx +229 -0
- package/templates/apps/web/app/[locale]/(authed)/account/components/schedule-service.tsx +276 -0
- package/templates/apps/web/app/[locale]/(authed)/account/components/send-message.tsx +128 -0
- package/templates/apps/web/app/[locale]/(authed)/account/general/billing-anchor.tsx +27 -0
- package/templates/apps/web/app/[locale]/(authed)/account/general/contact-actions.ts +81 -0
- package/templates/apps/web/app/[locale]/(authed)/account/general/deletion-request.tsx +132 -0
- package/templates/apps/web/app/[locale]/(authed)/account/general/page.tsx +193 -0
- package/templates/apps/web/app/[locale]/(authed)/account/general/profile-form.tsx +104 -0
- package/templates/apps/web/app/[locale]/(authed)/account/general/profile-source.ts +73 -0
- package/templates/apps/web/app/[locale]/(authed)/account/inbox/message/[id]/file/__tests__/route.test.ts +18 -8
- package/templates/apps/web/app/[locale]/(authed)/account/inbox/message/[id]/file/route.ts +4 -6
- package/templates/apps/web/app/[locale]/(authed)/account/layout.tsx +30 -4
- package/templates/apps/web/app/[locale]/(authed)/account/new-actions.ts +219 -0
- package/templates/apps/web/app/[locale]/(authed)/account/new-menu.tsx +340 -0
- package/templates/apps/web/app/[locale]/(authed)/account/new-popups.tsx +107 -0
- package/templates/apps/web/app/[locale]/(authed)/account/notifications/page.tsx +57 -0
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +85 -26
- package/templates/apps/web/app/[locale]/(authed)/account/profile-actions.ts +116 -0
- package/templates/apps/web/app/[locale]/(authed)/account/security/page.tsx +132 -0
- package/templates/apps/web/app/[locale]/(authed)/account/security/security-panel.tsx +102 -0
- package/templates/apps/web/app/[locale]/(authed)/account/security/security-source.ts +62 -0
- package/templates/apps/web/app/[locale]/(authed)/account/settings/page.tsx +13 -354
- package/templates/apps/web/app/[locale]/(authed)/account/settings-frame.tsx +116 -0
- package/templates/apps/web/app/[locale]/(authed)/account/sign-in-method.ts +37 -0
- package/templates/apps/web/app/[locale]/(authed)/account/support-actions.ts +9 -10
- package/templates/apps/web/app/[locale]/layout.tsx +23 -4
- package/templates/apps/web/app/api/auth/hub/__tests__/continuation.test.ts +143 -0
- package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +62 -10
- package/templates/apps/web/app/api/auth/hub/authorize/route.ts +22 -7
- package/templates/apps/web/app/api/auth/hub/continuation.ts +73 -0
- package/templates/apps/web/app/api/auth/hub/continue/[ref]/__tests__/route.test.ts +188 -0
- package/templates/apps/web/app/api/auth/hub/continue/[ref]/route.ts +53 -0
- package/templates/apps/web/app/robots.txt/route.ts +10 -4
- package/templates/apps/web/auth.ts +25 -2
- package/templates/apps/web/lib/__tests__/account-config.test.ts +258 -46
- package/templates/apps/web/lib/__tests__/auth-email.test.ts +84 -0
- package/templates/apps/web/lib/account-config.ts +188 -52
- package/templates/apps/web/lib/auth-email.ts +45 -10
- package/templates/apps/web/lib/hub-session/__tests__/hub-session.test.ts +143 -0
- package/templates/apps/web/lib/hub-session/index.ts +1 -0
- package/templates/apps/web/lib/hub-session/reseal.ts +90 -0
- package/templates/apps/web/lib/hub-session/seal.ts +13 -9
- package/templates/apps/web/lib/topology.generated.ts +33 -0
- package/templates/apps/web/messages/en.json +194 -33
- package/templates/database/CHANGELOG.md +37 -0
- package/templates/database/LICENSE +6 -5
- package/templates/database/NOTICE +2 -0
- package/templates/database/README.md +1 -1
- package/templates/database/__tests__/consent-ledger.db.test.ts +345 -0
- package/templates/database/__tests__/fence-fan-out.db.test.ts +6 -2
- package/templates/database/__tests__/fixtures/person-contact-backfill.json +182 -0
- package/templates/database/__tests__/member-area.db.test.ts +15 -3
- package/templates/database/__tests__/member-contract-migration.test.ts +85 -0
- package/templates/database/__tests__/member-contract.db.test.ts +413 -0
- package/templates/database/__tests__/person-contact.db.test.ts +563 -0
- package/templates/database/__tests__/person.db.test.ts +3 -1
- package/templates/database/__tests__/seed.test.ts +54 -2
- package/templates/database/inbox/schema.prisma +166 -25
- package/templates/database/migrations/20261012120000_member_contract/migration.sql +192 -0
- package/templates/database/migrations/20261013120000_lead_spoke_checkout/migration.sql +20 -0
- package/templates/database/migrations/20261014120000_consent_ledger/migration.sql +122 -0
- package/templates/database/migrations/20261014120000_person_contact/migration.sql +231 -0
- package/templates/database/package.json +3 -2
- package/templates/database/seed.ts +34 -2
- package/versions.json +36 -36
- package/templates/apps/api/src/__tests__/content-write-production-boot.test.ts +0 -104
- package/templates/apps/api/src/__tests__/cors.test.ts +0 -276
- package/templates/apps/api/src/__tests__/module-exclusion.test.ts +0 -279
- package/templates/apps/api/src/__tests__/openapi-keyless-hub.test.ts +0 -122
- package/templates/apps/api/src/__tests__/review-surface-registration.test.ts +0 -167
- package/templates/apps/api/src/__tests__/root-modules.test.ts +0 -175
- package/templates/apps/api/src/__tests__/schedule-forwarded-rate-limit.test.ts +0 -117
- package/templates/apps/api/src/__tests__/server.test.ts +0 -591
- package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +0 -175
- package/templates/apps/api/src/openapi/__tests__/surface-drift.test.ts +0 -293
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +0 -1620
- package/templates/apps/api/src/routes/project-listings/__tests__/directory.test.ts +0 -528
- package/templates/apps/api/src/routes/workspaces/__tests__/inbox-wire-schemas.test.ts +0 -198
- package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +0 -652
- package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +0 -408
- package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +0 -476
- package/templates/apps/mcp/__tests__/audit.test.ts +0 -346
- package/templates/apps/mcp/__tests__/parity.test.ts +0 -325
- package/templates/apps/mcp/__tests__/workspace-surface.test.ts +0 -364
- package/templates/apps/web/app/[locale]/(authed)/account/settings/color-mode.tsx +0 -68
- package/templates/apps/web/app/[locale]/(authed)/account/settings/deletion-request.tsx +0 -55
- package/templates/apps/web/app/[locale]/(authed)/account/settings/name-form.tsx +0 -118
- package/templates/database/__tests__/build.test.ts +0 -131
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
-- #8435 — contact details on the client record (spec
|
|
2
|
+
-- docs/specs/8435-contact-details-on-the-client-spec.md D1, D2).
|
|
3
|
+
--
|
|
4
|
+
-- people (13 columns) — given name, family name, an E.164 telephone, the
|
|
5
|
+
-- magnet's six-part address, a company name, a job title,
|
|
6
|
+
-- and the confirmation pair (who last saved the contact by
|
|
7
|
+
-- hand, and when). Every column is nullable and has no
|
|
8
|
+
-- default, so Postgres changes only the catalog.
|
|
9
|
+
-- callbacks.person_id, subscribers.person_id — the Person a request or a
|
|
10
|
+
-- subscription belongs to: a composite foreign key onto
|
|
11
|
+
-- people(id, workspace_id), the #7877 shape, so "a Person
|
|
12
|
+
-- of the row's own workspace" is a database fact. NULL is
|
|
13
|
+
-- not checked (MATCH SIMPLE).
|
|
14
|
+
--
|
|
15
|
+
-- PURELY ADDITIVE, DDL ONLY. No existing row's values change here: the
|
|
16
|
+
-- backfill is the next migration, 20261014121000_person_contact_backfill,
|
|
17
|
+
-- split out so its row updates never run under the exclusive locks these
|
|
18
|
+
-- ALTER TABLEs take. Prisma runs this file as one transaction, so each ALTER
|
|
19
|
+
-- TABLE's ACCESS EXCLUSIVE lock on "people", "callbacks" and "subscribers"
|
|
20
|
+
-- is held until the file commits. Every CHECK and foreign key below is added
|
|
21
|
+
-- NOT VALID and then VALIDATEd over an all-NULL column or a table with no
|
|
22
|
+
-- person_id yet, and none of the three is the large "leads" table; the window
|
|
23
|
+
-- is accepted, as #852 accepted the same on "leads" (SEC-852-E, SEC-8435-C).
|
|
24
|
+
--
|
|
25
|
+
-- Rollout: deploy this migration BEFORE the API code that writes the columns;
|
|
26
|
+
-- code without them fails every callback and subscription write.
|
|
27
|
+
--
|
|
28
|
+
-- Rollback: ALTER TABLE "callbacks" DROP CONSTRAINT "callbacks_person_fk",
|
|
29
|
+
-- DROP COLUMN "person_id"; ALTER TABLE "subscribers" DROP CONSTRAINT
|
|
30
|
+
-- "subscribers_person_fk", DROP COLUMN "person_id"; ALTER TABLE "people"
|
|
31
|
+
-- DROP COLUMN "given_name", DROP COLUMN "family_name", DROP COLUMN
|
|
32
|
+
-- "telephone", DROP COLUMN "address_line1", DROP COLUMN "address_line2",
|
|
33
|
+
-- DROP COLUMN "address_city", DROP COLUMN "address_region", DROP COLUMN
|
|
34
|
+
-- "address_postal_code", DROP COLUMN "address_country", DROP COLUMN
|
|
35
|
+
-- "company_name", DROP COLUMN "job_title", DROP COLUMN "contact_confirmed_at",
|
|
36
|
+
-- DROP COLUMN "contact_confirmed_by"; (the indexes and CHECKs go with them).
|
|
37
|
+
|
|
38
|
+
-- AlterTable
|
|
39
|
+
ALTER TABLE "people" ADD COLUMN "address_city" VARCHAR(60),
|
|
40
|
+
ADD COLUMN "address_country" CHAR(2),
|
|
41
|
+
ADD COLUMN "address_line1" VARCHAR(100),
|
|
42
|
+
ADD COLUMN "address_line2" VARCHAR(60),
|
|
43
|
+
ADD COLUMN "address_postal_code" VARCHAR(20),
|
|
44
|
+
ADD COLUMN "address_region" VARCHAR(40),
|
|
45
|
+
ADD COLUMN "company_name" VARCHAR(200),
|
|
46
|
+
ADD COLUMN "contact_confirmed_at" TIMESTAMP(3),
|
|
47
|
+
ADD COLUMN "contact_confirmed_by" VARCHAR(16),
|
|
48
|
+
ADD COLUMN "family_name" VARCHAR(100),
|
|
49
|
+
ADD COLUMN "given_name" VARCHAR(100),
|
|
50
|
+
ADD COLUMN "job_title" VARCHAR(100),
|
|
51
|
+
ADD COLUMN "telephone" VARCHAR(16);
|
|
52
|
+
|
|
53
|
+
-- AlterTable
|
|
54
|
+
ALTER TABLE "callbacks" ADD COLUMN "person_id" TEXT;
|
|
55
|
+
|
|
56
|
+
-- AlterTable
|
|
57
|
+
ALTER TABLE "subscribers" ADD COLUMN "person_id" TEXT;
|
|
58
|
+
|
|
59
|
+
-- CreateIndex
|
|
60
|
+
CREATE INDEX "callbacks_person_id_idx" ON "callbacks"("person_id");
|
|
61
|
+
|
|
62
|
+
-- CreateIndex
|
|
63
|
+
CREATE INDEX "subscribers_person_id_idx" ON "subscribers"("person_id");
|
|
64
|
+
|
|
65
|
+
-- A stored telephone is E.164 (the form the phone field posts) or NULL.
|
|
66
|
+
ALTER TABLE "people" ADD CONSTRAINT "people_telephone_e164"
|
|
67
|
+
CHECK ("telephone" IS NULL OR "telephone" ~ '^\+[1-9][0-9]{6,14}$') NOT VALID;
|
|
68
|
+
ALTER TABLE "people" VALIDATE CONSTRAINT "people_telephone_e164";
|
|
69
|
+
|
|
70
|
+
-- The country is the upper-case ISO 3166-1 alpha-2 code.
|
|
71
|
+
ALTER TABLE "people" ADD CONSTRAINT "people_address_country_format"
|
|
72
|
+
CHECK ("address_country" IS NULL OR "address_country" ~ '^[A-Z]{2}$') NOT VALID;
|
|
73
|
+
ALTER TABLE "people" VALIDATE CONSTRAINT "people_address_country_format";
|
|
74
|
+
|
|
75
|
+
-- An address is stored whole or not at all: all six parts NULL, or every
|
|
76
|
+
-- part but the optional second line present.
|
|
77
|
+
ALTER TABLE "people" ADD CONSTRAINT "people_address_complete"
|
|
78
|
+
CHECK (
|
|
79
|
+
("address_line1" IS NULL AND "address_line2" IS NULL AND "address_city" IS NULL AND "address_region" IS NULL AND "address_postal_code" IS NULL AND "address_country" IS NULL)
|
|
80
|
+
OR
|
|
81
|
+
("address_line1" IS NOT NULL AND "address_city" IS NOT NULL AND "address_region" IS NOT NULL AND "address_postal_code" IS NOT NULL AND "address_country" IS NOT NULL)
|
|
82
|
+
) NOT VALID;
|
|
83
|
+
ALTER TABLE "people" VALIDATE CONSTRAINT "people_address_complete";
|
|
84
|
+
|
|
85
|
+
-- The confirmation pair is written together, and the actor is one of two.
|
|
86
|
+
ALTER TABLE "people" ADD CONSTRAINT "people_contact_confirmed"
|
|
87
|
+
CHECK (
|
|
88
|
+
("contact_confirmed_at" IS NULL AND "contact_confirmed_by" IS NULL)
|
|
89
|
+
OR
|
|
90
|
+
("contact_confirmed_at" IS NOT NULL AND "contact_confirmed_by" IS NOT NULL AND "contact_confirmed_by" IN ('operator', 'client'))
|
|
91
|
+
) NOT VALID;
|
|
92
|
+
ALTER TABLE "people" VALIDATE CONSTRAINT "people_contact_confirmed";
|
|
93
|
+
|
|
94
|
+
-- The two new reference columns carry the person_ format CHECK (the #852
|
|
95
|
+
-- shape), added NOT VALID and then validated over an all-NULL column.
|
|
96
|
+
ALTER TABLE "callbacks" ADD CONSTRAINT "callbacks_person_id_format" CHECK ("person_id" ~ '^person_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$') NOT VALID;
|
|
97
|
+
ALTER TABLE "callbacks" VALIDATE CONSTRAINT "callbacks_person_id_format";
|
|
98
|
+
ALTER TABLE "subscribers" ADD CONSTRAINT "subscribers_person_id_format" CHECK ("person_id" ~ '^person_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$') NOT VALID;
|
|
99
|
+
ALTER TABLE "subscribers" VALIDATE CONSTRAINT "subscribers_person_id_format";
|
|
100
|
+
|
|
101
|
+
-- AddForeignKey
|
|
102
|
+
-- A callback / subscriber names a Person of ITS OWN workspace; deleting a
|
|
103
|
+
-- Person with one is refused (the pieces_person_fk shape, #7877).
|
|
104
|
+
ALTER TABLE "callbacks" ADD CONSTRAINT "callbacks_person_fk" FOREIGN KEY ("person_id", "workspace_id") REFERENCES "people"("id", "workspace_id") ON DELETE RESTRICT ON UPDATE CASCADE NOT VALID;
|
|
105
|
+
ALTER TABLE "callbacks" VALIDATE CONSTRAINT "callbacks_person_fk";
|
|
106
|
+
ALTER TABLE "subscribers" ADD CONSTRAINT "subscribers_person_fk" FOREIGN KEY ("person_id", "workspace_id") REFERENCES "people"("id", "workspace_id") ON DELETE RESTRICT ON UPDATE CASCADE NOT VALID;
|
|
107
|
+
ALTER TABLE "subscribers" VALIDATE CONSTRAINT "subscribers_person_fk";
|
|
108
|
+
|
|
109
|
+
-- ===== #8435 contact backfill (idempotent; runs after the DDL above) =====
|
|
110
|
+
-- #8435 — the contact backfill (spec
|
|
111
|
+
-- docs/specs/8435-contact-details-on-the-client-spec.md D2).
|
|
112
|
+
--
|
|
113
|
+
-- Gives every newsletter subscriber and every callback that carries a
|
|
114
|
+
-- storable email a Person, stamps subscribers.person_id and
|
|
115
|
+
-- callbacks.person_id, and fills a Person's blank telephone from the oldest
|
|
116
|
+
-- callback phone that is already E.164 once spaces and punctuation are
|
|
117
|
+
-- removed. Set-based and idempotent: every insert is ON CONFLICT DO NOTHING
|
|
118
|
+
-- and every update is guarded by IS NULL, so running this text twice changes
|
|
119
|
+
-- nothing the first run wrote.
|
|
120
|
+
--
|
|
121
|
+
--
|
|
122
|
+
-- Normalization — the SQL twin of apps/api/src/services/person.ts's
|
|
123
|
+
-- normalizePersonEmail / isStorablePersonEmail (#852's expressions verbatim):
|
|
124
|
+
-- N(x) = lower(btrim(x, E' \t\n\r\f\x0B') COLLATE "C")
|
|
125
|
+
-- (\x0B is vertical tab; an E-string has no \v escape — E'\v'
|
|
126
|
+
-- is the letter v, which would strip the v of every .dev)
|
|
127
|
+
-- VALID(e) = e ~ '^[!-?A-~]+@[!-?A-~]+$' AND length(e) <= 254
|
|
128
|
+
-- NAME(x) = C0/C1 controls and bidi controls removed, trimmed, cut to
|
|
129
|
+
-- 200 characters; empty → NULL
|
|
130
|
+
-- A row whose email is not VALID makes no Person and keeps person_id NULL.
|
|
131
|
+
--
|
|
132
|
+
-- A callback's email is its payload.email when that is a JSON string and
|
|
133
|
+
-- VALID, else its buyer_email when VALID — the order the live code links in
|
|
134
|
+
-- (capture first, the confirm step only when capture left it NULL;
|
|
135
|
+
-- SEC-8435-G).
|
|
136
|
+
--
|
|
137
|
+
-- Rollback: nothing to undo on its own — dropping 20261014120000_person_contact's
|
|
138
|
+
-- columns removes the stamps and the telephones this wrote; the Persons it
|
|
139
|
+
-- created are ordinary Persons and are left in place.
|
|
140
|
+
|
|
141
|
+
-- 1. Subscribers, oldest first: one Person per (workspace, normalized email).
|
|
142
|
+
INSERT INTO "people" ("workspace_id", "email", "created_at", "updated_at")
|
|
143
|
+
SELECT DISTINCT ON (s."workspace_id", lower(btrim(s."email", E' \t\n\r\f\x0B') COLLATE "C"))
|
|
144
|
+
s."workspace_id",
|
|
145
|
+
lower(btrim(s."email", E' \t\n\r\f\x0B') COLLATE "C"),
|
|
146
|
+
s."created_at",
|
|
147
|
+
now()
|
|
148
|
+
FROM "subscribers" s
|
|
149
|
+
WHERE lower(btrim(s."email", E' \t\n\r\f\x0B') COLLATE "C") ~ '^[!-?A-~]+@[!-?A-~]+$'
|
|
150
|
+
AND length(lower(btrim(s."email", E' \t\n\r\f\x0B') COLLATE "C")) <= 254
|
|
151
|
+
ORDER BY s."workspace_id", lower(btrim(s."email", E' \t\n\r\f\x0B') COLLATE "C"), s."created_at" ASC, s."id" ASC
|
|
152
|
+
ON CONFLICT ("workspace_id", "email") DO NOTHING;
|
|
153
|
+
|
|
154
|
+
-- 2. Callbacks, oldest first: one Person per (workspace, normalized email),
|
|
155
|
+
-- named from the callback's own name. A Person a lead or a subscriber
|
|
156
|
+
-- already made keeps its name and created_at (ON CONFLICT DO NOTHING).
|
|
157
|
+
INSERT INTO "people" ("workspace_id", "email", "name", "created_at", "updated_at")
|
|
158
|
+
SELECT DISTINCT ON (ce."workspace_id", ce."email_n")
|
|
159
|
+
ce."workspace_id",
|
|
160
|
+
ce."email_n",
|
|
161
|
+
NULLIF(left(btrim(regexp_replace(ce."name", '[\x01-\x1F\x7F-\x9F--]', '', 'g')), 200), ''),
|
|
162
|
+
ce."created_at",
|
|
163
|
+
now()
|
|
164
|
+
FROM (
|
|
165
|
+
SELECT c."id", c."workspace_id", c."name", c."phone", c."created_at",
|
|
166
|
+
(
|
|
167
|
+
SELECT v."e"
|
|
168
|
+
FROM (VALUES
|
|
169
|
+
(1, lower(btrim(CASE WHEN jsonb_typeof(c."payload" -> 'email') = 'string' THEN c."payload" ->> 'email' END, E' \t\n\r\f\x0B') COLLATE "C")),
|
|
170
|
+
(2, lower(btrim(c."buyer_email", E' \t\n\r\f\x0B') COLLATE "C"))
|
|
171
|
+
) v("ord", "e")
|
|
172
|
+
WHERE v."e" ~ '^[!-?A-~]+@[!-?A-~]+$' AND length(v."e") <= 254
|
|
173
|
+
ORDER BY v."ord"
|
|
174
|
+
LIMIT 1
|
|
175
|
+
) AS "email_n"
|
|
176
|
+
FROM "callbacks" c
|
|
177
|
+
) ce
|
|
178
|
+
WHERE ce."email_n" IS NOT NULL
|
|
179
|
+
ORDER BY ce."workspace_id", ce."email_n", ce."created_at" ASC, ce."id" ASC
|
|
180
|
+
ON CONFLICT ("workspace_id", "email") DO NOTHING;
|
|
181
|
+
|
|
182
|
+
-- 3. Stamps: every subscriber and callback with a storable email points at
|
|
183
|
+
-- its workspace's Person; a row already stamped is left alone.
|
|
184
|
+
UPDATE "subscribers"
|
|
185
|
+
SET "person_id" = p."id"
|
|
186
|
+
FROM "people" p
|
|
187
|
+
WHERE "subscribers"."person_id" IS NULL
|
|
188
|
+
AND p."workspace_id" = "subscribers"."workspace_id"
|
|
189
|
+
AND p."email" = lower(btrim("subscribers"."email", E' \t\n\r\f\x0B') COLLATE "C");
|
|
190
|
+
|
|
191
|
+
UPDATE "callbacks"
|
|
192
|
+
SET "person_id" = p."id"
|
|
193
|
+
FROM (
|
|
194
|
+
SELECT c."id" AS "callback_id", c."workspace_id",
|
|
195
|
+
(
|
|
196
|
+
SELECT v."e"
|
|
197
|
+
FROM (VALUES
|
|
198
|
+
(1, lower(btrim(CASE WHEN jsonb_typeof(c."payload" -> 'email') = 'string' THEN c."payload" ->> 'email' END, E' \t\n\r\f\x0B') COLLATE "C")),
|
|
199
|
+
(2, lower(btrim(c."buyer_email", E' \t\n\r\f\x0B') COLLATE "C"))
|
|
200
|
+
) v("ord", "e")
|
|
201
|
+
WHERE v."e" ~ '^[!-?A-~]+@[!-?A-~]+$' AND length(v."e") <= 254
|
|
202
|
+
ORDER BY v."ord"
|
|
203
|
+
LIMIT 1
|
|
204
|
+
) AS "email_n"
|
|
205
|
+
FROM "callbacks" c
|
|
206
|
+
WHERE c."person_id" IS NULL
|
|
207
|
+
) ce
|
|
208
|
+
JOIN "people" p ON p."workspace_id" = ce."workspace_id" AND p."email" = ce."email_n"
|
|
209
|
+
WHERE "callbacks"."id" = ce."callback_id"
|
|
210
|
+
AND "callbacks"."person_id" IS NULL;
|
|
211
|
+
|
|
212
|
+
-- 4. Telephone: for an uncurated Person (no linked user, never confirmed by
|
|
213
|
+
-- hand — the rule a capture obeys, D4) with no telephone, the oldest
|
|
214
|
+
-- stamped callback's phone with spaces and punctuation removed, when that
|
|
215
|
+
-- is E.164. A national-format phone fills nothing.
|
|
216
|
+
UPDATE "people" p
|
|
217
|
+
SET "telephone" = c."tel",
|
|
218
|
+
"updated_at" = now()
|
|
219
|
+
FROM (
|
|
220
|
+
SELECT DISTINCT ON (cb."person_id")
|
|
221
|
+
cb."person_id",
|
|
222
|
+
regexp_replace(cb."phone", '[ ().-]', '', 'g') AS "tel"
|
|
223
|
+
FROM "callbacks" cb
|
|
224
|
+
WHERE cb."person_id" IS NOT NULL
|
|
225
|
+
AND regexp_replace(cb."phone", '[ ().-]', '', 'g') ~ '^\+[1-9][0-9]{6,14}$'
|
|
226
|
+
ORDER BY cb."person_id", cb."created_at" ASC, cb."id" ASC
|
|
227
|
+
) c
|
|
228
|
+
WHERE p."id" = c."person_id"
|
|
229
|
+
AND p."telephone" IS NULL
|
|
230
|
+
AND p."user_id" IS NULL
|
|
231
|
+
AND p."contact_confirmed_at" IS NULL;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@working-theory/database",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"inbox",
|
|
17
17
|
"ops",
|
|
18
18
|
"tenancy.ts",
|
|
19
|
-
"prisma.config.ts"
|
|
19
|
+
"prisma.config.ts",
|
|
20
|
+
"NOTICE"
|
|
20
21
|
],
|
|
21
22
|
"description": "AI Launch Kit database schema (single-tenant Prisma)",
|
|
22
23
|
"type": "module",
|
|
@@ -25,6 +25,12 @@ import "dotenv/config";
|
|
|
25
25
|
|
|
26
26
|
import { basename } from "node:path";
|
|
27
27
|
|
|
28
|
+
import {
|
|
29
|
+
REQUEST_QUOTE_FLOW_SLUG,
|
|
30
|
+
REQUEST_QUOTE_FLOW_TITLE,
|
|
31
|
+
requestQuoteFlowConfig,
|
|
32
|
+
} from "@working-theory/validation";
|
|
33
|
+
|
|
28
34
|
import type { PrismaClient } from "./generated/client/client.js";
|
|
29
35
|
import {
|
|
30
36
|
DEFAULT_ORG_ID,
|
|
@@ -47,14 +53,14 @@ export {
|
|
|
47
53
|
};
|
|
48
54
|
|
|
49
55
|
/**
|
|
50
|
-
* The DI surface `provisionDefaultWorkspace` needs — just the
|
|
56
|
+
* The DI surface `provisionDefaultWorkspace` needs — just the three Prisma
|
|
51
57
|
* delegates it touches. A `Pick` off the real `PrismaClient` type (not a
|
|
52
58
|
* hand-rolled shape), so a real client satisfies it by construction and the
|
|
53
59
|
* DB-free unit test can pass a lightweight stub with no live Postgres.
|
|
54
60
|
*/
|
|
55
61
|
export type DefaultWorkspacePrismaClient = Pick<
|
|
56
62
|
PrismaClient,
|
|
57
|
-
"organization" | "workspace"
|
|
63
|
+
"organization" | "workspace" | "flowDefinition"
|
|
58
64
|
>;
|
|
59
65
|
|
|
60
66
|
/**
|
|
@@ -62,6 +68,12 @@ export type DefaultWorkspacePrismaClient = Pick<
|
|
|
62
68
|
* yet; no-op (no create/upsert calls) once any Workspace is present — covering
|
|
63
69
|
* both single-tenant re-run idempotency and a multi-tenant deployment that
|
|
64
70
|
* already has real Workspaces.
|
|
71
|
+
*
|
|
72
|
+
* #8168 D8 (C4) — the default workspace also gets its `request-quote` magnet.
|
|
73
|
+
* A fresh install runs `migrate deploy` BEFORE this seed, so the migration's
|
|
74
|
+
* backfill finds no workspace and seeds nothing; this is where the default
|
|
75
|
+
* workspace's magnet comes from. Insert-if-absent: an `upsert` with an empty
|
|
76
|
+
* `update`, so an existing `(site_id, slug)` row is never overwritten.
|
|
65
77
|
*/
|
|
66
78
|
export async function provisionDefaultWorkspace(
|
|
67
79
|
prisma: DefaultWorkspacePrismaClient,
|
|
@@ -97,6 +109,26 @@ export async function provisionDefaultWorkspace(
|
|
|
97
109
|
update: {},
|
|
98
110
|
});
|
|
99
111
|
|
|
112
|
+
await prisma.flowDefinition.upsert({
|
|
113
|
+
where: {
|
|
114
|
+
siteId_slug: {
|
|
115
|
+
siteId: DEFAULT_WORKSPACE_ID,
|
|
116
|
+
slug: REQUEST_QUOTE_FLOW_SLUG,
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
create: {
|
|
120
|
+
siteId: DEFAULT_WORKSPACE_ID,
|
|
121
|
+
workspaceId: DEFAULT_WORKSPACE_ID,
|
|
122
|
+
slug: REQUEST_QUOTE_FLOW_SLUG,
|
|
123
|
+
config: requestQuoteFlowConfig,
|
|
124
|
+
kind: "contact",
|
|
125
|
+
title: REQUEST_QUOTE_FLOW_TITLE,
|
|
126
|
+
status: "draft",
|
|
127
|
+
customersEnabled: true,
|
|
128
|
+
},
|
|
129
|
+
update: {},
|
|
130
|
+
});
|
|
131
|
+
|
|
100
132
|
return { created: true };
|
|
101
133
|
}
|
|
102
134
|
|
package/versions.json
CHANGED
|
@@ -1,53 +1,53 @@
|
|
|
1
1
|
{
|
|
2
|
-
"@working-theory/admin-ui": "0.
|
|
3
|
-
"@working-theory/aeo": "0.
|
|
4
|
-
"@working-theory/agent-context": "0.
|
|
2
|
+
"@working-theory/admin-ui": "0.40.0",
|
|
3
|
+
"@working-theory/aeo": "0.40.0",
|
|
4
|
+
"@working-theory/agent-context": "0.40.0",
|
|
5
5
|
"@working-theory/agent-contract": "0.1.0",
|
|
6
6
|
"@working-theory/agent-memory": "0.0.0",
|
|
7
7
|
"@working-theory/ai": "0.1.0",
|
|
8
|
-
"@working-theory/analytics": "0.
|
|
9
|
-
"@working-theory/api-client": "0.
|
|
8
|
+
"@working-theory/analytics": "0.40.0",
|
|
9
|
+
"@working-theory/api-client": "0.40.0",
|
|
10
10
|
"@working-theory/cda-filesystem": "0.0.0",
|
|
11
|
-
"@working-theory/cli": "0.
|
|
12
|
-
"@working-theory/common": "0.
|
|
11
|
+
"@working-theory/cli": "0.40.0",
|
|
12
|
+
"@working-theory/common": "0.40.0",
|
|
13
13
|
"@working-theory/consent": "0.1.0",
|
|
14
|
-
"@working-theory/content-adapters": "0.
|
|
15
|
-
"@working-theory/content-projection": "0.
|
|
16
|
-
"@working-theory/email": "0.
|
|
14
|
+
"@working-theory/content-adapters": "0.40.0",
|
|
15
|
+
"@working-theory/content-projection": "0.40.0",
|
|
16
|
+
"@working-theory/email": "0.40.0",
|
|
17
17
|
"@working-theory/env": "0.0.0",
|
|
18
|
-
"@working-theory/eslint-config": "0.
|
|
19
|
-
"@working-theory/fumadocs-schema": "0.
|
|
20
|
-
"@working-theory/ids": "0.
|
|
21
|
-
"@working-theory/jsonld": "0.
|
|
22
|
-
"@working-theory/license": "0.
|
|
23
|
-
"@working-theory/metadata": "0.
|
|
24
|
-
"@working-theory/observability": "0.
|
|
25
|
-
"@working-theory/page-renderer": "0.
|
|
26
|
-
"@working-theory/plugin-mcp": "0.1.
|
|
27
|
-
"@working-theory/schema": "0.
|
|
28
|
-
"@working-theory/schema-pack-registry": "0.
|
|
29
|
-
"@working-theory/schema-validator": "0.
|
|
30
|
-
"@working-theory/sections-registry": "0.
|
|
31
|
-
"@working-theory/site-config": "0.
|
|
32
|
-
"@working-theory/spoke-checkout": "0.
|
|
33
|
-
"@working-theory/stripe-client": "0.
|
|
34
|
-
"@working-theory/templates": "0.
|
|
35
|
-
"@working-theory/theme": "0.
|
|
36
|
-
"@working-theory/ui": "0.
|
|
37
|
-
"@working-theory/validation": "0.
|
|
38
|
-
"@working-theory/web-i18n": "0.
|
|
18
|
+
"@working-theory/eslint-config": "0.40.0",
|
|
19
|
+
"@working-theory/fumadocs-schema": "0.40.0",
|
|
20
|
+
"@working-theory/ids": "0.13.0",
|
|
21
|
+
"@working-theory/jsonld": "0.40.0",
|
|
22
|
+
"@working-theory/license": "0.40.0",
|
|
23
|
+
"@working-theory/metadata": "0.40.0",
|
|
24
|
+
"@working-theory/observability": "0.40.0",
|
|
25
|
+
"@working-theory/page-renderer": "0.40.0",
|
|
26
|
+
"@working-theory/plugin-mcp": "0.1.3",
|
|
27
|
+
"@working-theory/schema": "0.40.0",
|
|
28
|
+
"@working-theory/schema-pack-registry": "0.40.0",
|
|
29
|
+
"@working-theory/schema-validator": "0.40.0",
|
|
30
|
+
"@working-theory/sections-registry": "0.40.0",
|
|
31
|
+
"@working-theory/site-config": "0.40.0",
|
|
32
|
+
"@working-theory/spoke-checkout": "0.5.1",
|
|
33
|
+
"@working-theory/stripe-client": "0.40.0",
|
|
34
|
+
"@working-theory/templates": "0.40.0",
|
|
35
|
+
"@working-theory/theme": "0.40.0",
|
|
36
|
+
"@working-theory/ui": "0.40.0",
|
|
37
|
+
"@working-theory/validation": "0.40.0",
|
|
38
|
+
"@working-theory/web-i18n": "0.40.0",
|
|
39
39
|
"@working-theory/billing-client": "0.0.0",
|
|
40
40
|
"@working-theory/branding": "0.0.0",
|
|
41
41
|
"@working-theory/license-client": "0.0.0",
|
|
42
42
|
"@working-theory/license-manifest": "0.0.0",
|
|
43
43
|
"@working-theory/partner-session": "0.0.0",
|
|
44
44
|
"@working-theory/preview-protocol": "0.0.0",
|
|
45
|
-
"@working-theory/ui-commerce": "0.23.
|
|
46
|
-
"@working-theory/ui-docs-blog": "0.23.
|
|
47
|
-
"@working-theory/ui-scheduling": "0.23.
|
|
48
|
-
"@working-theory/ui-signal-collectors": "0.25.
|
|
45
|
+
"@working-theory/ui-commerce": "0.23.18",
|
|
46
|
+
"@working-theory/ui-docs-blog": "0.23.18",
|
|
47
|
+
"@working-theory/ui-scheduling": "0.23.18",
|
|
48
|
+
"@working-theory/ui-signal-collectors": "0.25.7",
|
|
49
49
|
"@working-theory/web": "0.0.0",
|
|
50
50
|
"@working-theory/api": "1.0.0",
|
|
51
51
|
"@working-theory/mcp": "0.0.0",
|
|
52
|
-
"@working-theory/database": "0.
|
|
52
|
+
"@working-theory/database": "0.40.0"
|
|
53
53
|
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file content-write-production-boot.test.ts
|
|
3
|
-
* @description A production build with `content-write` enabled boots without
|
|
4
|
-
* pino-pretty (#7502).
|
|
5
|
-
*
|
|
6
|
-
* `services/revalidation.ts` is loaded only by the content-write routes, and it
|
|
7
|
-
* used to build its logger with `createLogger({ service })` alone. The
|
|
8
|
-
* observability package read a missing `environment` as development and asked
|
|
9
|
-
* pino for a `pino-pretty` transport, which a production install does not carry:
|
|
10
|
-
* pino threw `unable to determine transport target for "pino-pretty"` at module
|
|
11
|
-
* load, `build()` rejected, and every route — `/health` included — answered 500.
|
|
12
|
-
*
|
|
13
|
-
* "Without pino-pretty installed" is simulated at the pino boundary: the mock
|
|
14
|
-
* below throws pino's own error whenever a `pino-pretty` transport is requested,
|
|
15
|
-
* and passes every other call through to the real pino. pino is mocked by its
|
|
16
|
-
* resolved path because `apps/api` does not depend on it directly — only
|
|
17
|
-
* `@working-theory/observability` does.
|
|
18
|
-
*
|
|
19
|
-
* Mirrors `module-exclusion.test.ts`'s external-boundary mocks (CORS,
|
|
20
|
-
* rate-limit, email).
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import type { FastifyInstance } from "fastify";
|
|
24
|
-
|
|
25
|
-
jest.mock(
|
|
26
|
-
require.resolve("pino", {
|
|
27
|
-
paths: [`${__dirname}/../../../../packages/observability`],
|
|
28
|
-
}),
|
|
29
|
-
() => {
|
|
30
|
-
const realPino = jest.requireActual(
|
|
31
|
-
require.resolve("pino", {
|
|
32
|
-
paths: [`${__dirname}/../../../../packages/observability`],
|
|
33
|
-
}),
|
|
34
|
-
);
|
|
35
|
-
const withoutPinoPretty = (
|
|
36
|
-
options?: { transport?: { target?: string } },
|
|
37
|
-
...rest: unknown[]
|
|
38
|
-
) => {
|
|
39
|
-
if (options?.transport?.target === "pino-pretty") {
|
|
40
|
-
throw new Error(
|
|
41
|
-
'unable to determine transport target for "pino-pretty"',
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
return realPino(options, ...rest);
|
|
45
|
-
};
|
|
46
|
-
// pino exports itself as `default` and as `pino` too; both are replaced.
|
|
47
|
-
return Object.assign(withoutPinoPretty, realPino, {
|
|
48
|
-
default: withoutPinoPretty,
|
|
49
|
-
pino: withoutPinoPretty,
|
|
50
|
-
});
|
|
51
|
-
},
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
jest.mock("@fastify/cors", () => {
|
|
55
|
-
return jest.fn().mockResolvedValue(undefined);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
jest.mock("@fastify/rate-limit", () => {
|
|
59
|
-
return jest.fn().mockResolvedValue(undefined);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
jest.mock("@working-theory/email", () => ({
|
|
63
|
-
sendLeadNotification: jest.fn().mockResolvedValue({ ok: true }),
|
|
64
|
-
}));
|
|
65
|
-
|
|
66
|
-
describe("production boot with content-write enabled (#7502)", () => {
|
|
67
|
-
const prior = {
|
|
68
|
-
NODE_ENV: process.env.NODE_ENV,
|
|
69
|
-
AILK_ENABLED_MODULES: process.env.AILK_ENABLED_MODULES,
|
|
70
|
-
};
|
|
71
|
-
let app: FastifyInstance;
|
|
72
|
-
|
|
73
|
-
beforeAll(async () => {
|
|
74
|
-
process.env.NODE_ENV = "production";
|
|
75
|
-
// content-write registers only alongside a tenancy module (server.ts).
|
|
76
|
-
process.env.AILK_ENABLED_MODULES = "multi-tenant,content-write";
|
|
77
|
-
// The config and the observability singleton read NODE_ENV at import, so
|
|
78
|
-
// the server is loaded only after the environment is set.
|
|
79
|
-
const { build } = await import("../server");
|
|
80
|
-
app = await build();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
afterAll(async () => {
|
|
84
|
-
await app?.close();
|
|
85
|
-
for (const [key, value] of Object.entries(prior)) {
|
|
86
|
-
if (value === undefined) delete process.env[key];
|
|
87
|
-
else process.env[key] = value;
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("answers /health with 200", async () => {
|
|
92
|
-
const res = await app.inject({ method: "GET", url: "/health" });
|
|
93
|
-
expect(res.statusCode).toBe(200);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it("registers the content-write routes, so revalidation.ts was loaded", async () => {
|
|
97
|
-
// Registered and session-less: 401, where an unregistered route is a 404.
|
|
98
|
-
const res = await app.inject({
|
|
99
|
-
method: "POST",
|
|
100
|
-
url: "/v1/workspaces/wksp_00000000-0000-7000-8000-0000000000aa/content",
|
|
101
|
-
});
|
|
102
|
-
expect(res.statusCode).toBe(401);
|
|
103
|
-
});
|
|
104
|
-
});
|