create-ailk 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -14
- package/component-catalog.json +149 -5
- package/component-catalog.md +7 -6
- package/dist/add-module.js +17 -10
- package/dist/add-route.d.ts +55 -1
- package/dist/add-route.js +301 -13
- package/dist/cli.js +27 -10
- package/dist/derive-architecture.d.ts +7 -5
- package/dist/derive-architecture.js +26 -32
- package/dist/lib/design-checks.js +4 -2
- package/dist/lib/env-catalog.js +0 -4
- package/dist/lib/npm-auth.d.ts +9 -0
- package/dist/lib/npm-auth.js +48 -0
- package/dist/lib/scaffold-render-sweep.d.ts +36 -2
- package/dist/lib/scaffold-render-sweep.js +50 -8
- package/dist/module-architecture.js +115 -10
- package/dist/paid-sections.d.ts +21 -2
- package/dist/paid-sections.js +69 -7
- package/dist/seed-flows.d.ts +4 -1
- package/dist/seed-flows.js +50 -4
- package/dist/spoke-checkout.d.ts +38 -35
- package/dist/spoke-checkout.js +66 -229
- package/dist/starter-grid.d.ts +0 -3
- package/dist/starter-grid.js +24 -14
- package/dist/surfaces.js +33 -12
- package/dist/sync-routes.d.ts +68 -5
- package/dist/sync-routes.js +455 -29
- package/dist/template-record.d.ts +1 -1
- package/dist/template-record.js +3 -2
- package/dist/update.js +10 -0
- package/package.json +3 -3
- package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +4 -49
- package/site-templates/_base/content/en/Pricing/pricing.mdx +3 -21
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +23 -20
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +28 -14
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +15 -39
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +38 -34
- package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/templates/.env.example +13 -7
- package/templates/.github/workflows/ci.yml +13 -0
- package/templates/CLAUDE.md +4 -2
- package/templates/README.md +1 -0
- package/templates/_gitignore +4 -0
- package/templates/apps/api/.env.example +34 -0
- package/templates/apps/api/CLAUDE.md +94 -19
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +2 -0
- package/templates/apps/api/src/bin/stripe-connections-rekey.ts +45 -0
- package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +185 -0
- package/templates/apps/api/src/config/index.ts +109 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +7 -1
- package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +116 -0
- package/templates/apps/api/src/lib/__tests__/site.test.ts +16 -0
- package/templates/apps/api/src/lib/error-serializer.ts +74 -7
- package/templates/apps/api/src/lib/site.ts +4 -1
- package/templates/apps/api/src/lib/stripe.ts +77 -4
- package/templates/apps/api/src/lib/webhook-idempotency.ts +66 -2
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +218 -0
- package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +174 -0
- package/templates/apps/api/src/middleware/rate-limit.ts +226 -4
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +127 -0
- package/templates/apps/api/src/openapi/spec.ts +343 -7
- package/templates/apps/api/src/routes/billing/plan-checkout.ts +332 -0
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +11 -0
- package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +1426 -0
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +11 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +49 -0
- package/templates/apps/api/src/routes/checkout/session.ts +57 -25
- package/templates/apps/api/src/routes/checkout/sessions.ts +232 -82
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +5 -5
- package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +160 -0
- package/templates/apps/api/src/routes/consent/account.ts +4 -2
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +2 -2
- package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +731 -0
- package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +753 -0
- package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +762 -0
- package/templates/apps/api/src/routes/content/get-page.ts +34 -119
- package/templates/apps/api/src/routes/content/index.ts +16 -2
- package/templates/apps/api/src/routes/content/list-pages.ts +15 -36
- package/templates/apps/api/src/routes/content/offerings.ts +184 -0
- package/templates/apps/api/src/routes/content/pricing.ts +176 -0
- package/templates/apps/api/src/routes/content/search.ts +16 -45
- package/templates/apps/api/src/routes/content/shown-offerings.ts +229 -0
- package/templates/apps/api/src/routes/leads/index.ts +377 -308
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +20 -9
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +17 -6
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +1 -1
- package/templates/apps/api/src/routes/webhooks/README.md +66 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +1 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +814 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +80 -68
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +6 -5
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +8 -2
- package/templates/apps/api/src/routes/webhooks/index.ts +23 -0
- package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +384 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +93 -17
- package/templates/apps/api/src/server.ts +165 -51
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +121 -4
- package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +313 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +61 -2
- package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +234 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +374 -44
- package/templates/apps/api/src/services/__tests__/person.test.ts +260 -1
- package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +450 -0
- package/templates/apps/api/src/services/__tests__/project-listings.counts.test.ts +202 -0
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +15 -18
- package/templates/apps/api/src/services/abandon-email.ts +28 -5
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +15 -12
- package/templates/apps/api/src/services/entitlements.ts +157 -1
- package/templates/apps/api/src/services/lead-routing.ts +113 -58
- package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +348 -0
- package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +268 -0
- package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +975 -0
- package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +416 -0
- package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +400 -0
- package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +685 -0
- package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +231 -0
- package/templates/apps/api/src/services/offerings/catalog.ts +318 -0
- package/templates/apps/api/src/services/offerings/checkout-authorization.ts +403 -0
- package/templates/apps/api/src/services/offerings/checkout-session-record.ts +192 -0
- package/templates/apps/api/src/services/offerings/connection.ts +1083 -0
- package/templates/apps/api/src/services/offerings/index.ts +614 -0
- package/templates/apps/api/src/services/offerings/source.ts +140 -0
- package/templates/apps/api/src/services/offerings/store.ts +150 -0
- package/templates/apps/api/src/services/person.ts +147 -9
- package/templates/apps/api/src/services/plan-offerings.ts +96 -30
- package/templates/apps/api/src/services/project-listings.ts +43 -31
- package/templates/apps/api/src/services/public-site-resolver.ts +19 -14
- package/templates/apps/mcp/CLAUDE.md +32 -18
- package/templates/apps/mcp/__tests__/app-entry.test.ts +2 -2
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +9 -4
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +358 -0
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +364 -0
- package/templates/apps/mcp/__tests__/http-transport.test.ts +95 -14
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +8 -3
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +0 -25
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +22 -7
- package/templates/apps/mcp/__tests__/list_pages.test.ts +84 -44
- package/templates/apps/mcp/__tests__/locale.test.ts +41 -56
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +2 -2
- package/templates/apps/mcp/__tests__/rate-limit-write.test.ts +189 -0
- package/templates/apps/mcp/__tests__/read_content.test.ts +115 -78
- package/templates/apps/mcp/__tests__/request_callback.test.ts +63 -128
- package/templates/apps/mcp/__tests__/search_content.test.ts +76 -61
- package/templates/apps/mcp/__tests__/server.test.ts +13 -11
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +208 -12
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +82 -167
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +59 -130
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +11 -11
- package/templates/apps/mcp/package.json +1 -0
- package/templates/apps/mcp/src/abuse/index.ts +18 -11
- package/templates/apps/mcp/src/http-transport.ts +47 -9
- package/templates/apps/mcp/src/lib/content-adapter.ts +0 -26
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +5 -4
- package/templates/apps/mcp/src/lib/shown-offerings.ts +105 -0
- package/templates/apps/mcp/src/lib/site-config.ts +53 -0
- package/templates/apps/mcp/src/server.ts +5 -0
- package/templates/apps/mcp/src/tools/capture_lead.ts +30 -28
- package/templates/apps/mcp/src/tools/get_offerings.ts +47 -30
- package/templates/apps/mcp/src/tools/get_pricing.ts +40 -40
- package/templates/apps/mcp/src/tools/index.ts +22 -21
- package/templates/apps/mcp/src/tools/list_pages.ts +27 -33
- package/templates/apps/mcp/src/tools/purchase.ts +30 -2
- package/templates/apps/mcp/src/tools/read_content.ts +46 -40
- package/templates/apps/mcp/src/tools/request_callback.ts +21 -28
- package/templates/apps/mcp/src/tools/search_content.ts +20 -24
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +23 -26
- package/templates/apps/mcp/src/workspace-server.ts +15 -0
- package/templates/apps/web/.env.example +10 -31
- package/templates/apps/web/CLAUDE.md +324 -42
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +189 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +4 -2
- package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +8 -3
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +22 -144
- package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +9 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/careers-hub.test.tsx +172 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +149 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +22 -24
- package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +140 -66
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +57 -22
- package/templates/apps/web/app/[locale]/careers/page.tsx +129 -5
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +119 -11
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +28 -5
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +20 -6
- package/templates/apps/web/app/[locale]/contact/intake-script.ts +1 -1
- package/templates/apps/web/app/[locale]/contact/page.tsx +99 -92
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +237 -5
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.count.test.tsx +43 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.deliverable.test.tsx +200 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.ending.test.tsx +197 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +430 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +21 -9
- package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +369 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +12 -73
- package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +10 -31
- package/templates/apps/web/app/[locale]/layout.tsx +55 -4
- package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +174 -104
- package/templates/apps/web/app/[locale]/newsletter/page.tsx +95 -58
- package/templates/apps/web/app/[locale]/pricing/__tests__/__snapshots__/unconnected.test.tsx.snap +7 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +599 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/page.test.tsx +216 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +236 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +367 -6
- package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +4 -1
- package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe-rsc-boundary.test.tsx +17 -0
- package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe.test.tsx +62 -3
- package/templates/apps/web/app/[locale]/unsubscribe/page.tsx +70 -18
- package/templates/apps/web/app/[locale]/unsubscribe/unsubscribe-button.tsx +30 -9
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +316 -0
- package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +810 -0
- package/templates/apps/web/lib/__tests__/auth-routes.test.ts +27 -1
- package/templates/apps/web/lib/__tests__/deck-page.test.ts +2 -1
- package/templates/apps/web/lib/__tests__/in-page-magnet.test.ts +265 -0
- package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +64 -0
- package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +293 -0
- package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +601 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +67 -8
- package/templates/apps/web/lib/auth-routes.ts +36 -2
- package/templates/apps/web/lib/checkout-config.ts +9 -20
- package/templates/apps/web/lib/deck-page.ts +1 -0
- package/templates/apps/web/lib/deliverable-grant.ts +18 -0
- package/templates/apps/web/lib/in-page-magnet.ts +416 -0
- package/templates/apps/web/lib/magnet-routes.ts +44 -0
- package/templates/apps/web/lib/modules.generated.ts +9 -0
- package/templates/apps/web/lib/newsletter.actions.ts +205 -0
- package/templates/apps/web/lib/offerings-pricing.ts +326 -0
- package/templates/apps/web/lib/posting-title.ts +17 -0
- package/templates/apps/web/lib/sign-up-agreements.ts +4 -2
- package/templates/apps/web/messages/en.json +49 -15
- package/templates/apps/web/middleware.ts +12 -2
- package/templates/apps/web/security-headers.config.ts +17 -0
- package/templates/content/en/BlogList/blog.mdx +4 -71
- package/templates/content/en/BookCall/book-a-call.mdx +1 -41
- package/templates/content/en/BookCall/onsite-estimate.mdx +1 -17
- package/templates/content/en/BookCall/onsite-service.mdx +1 -17
- package/templates/content/en/BookCall/start-project.mdx +1 -17
- package/templates/content/en/ContactPage/contact.mdx +6 -63
- package/templates/content/en/HomePage/home.mdx +3 -21
- package/templates/database/CHANGELOG.md +51 -0
- package/templates/database/__tests__/in-page-magnets-migration.test.ts +161 -0
- package/templates/database/__tests__/offerings-checkout-session.db.test.ts +270 -0
- package/templates/database/__tests__/offerings-migration.test.ts +197 -0
- package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +86 -0
- package/templates/database/__tests__/offerings.db.test.ts +303 -0
- package/templates/database/__tests__/seed.test.ts +63 -1
- package/templates/database/__tests__/trial-magnet-migration.test.ts +186 -0
- package/templates/database/auth/schema.prisma +4 -0
- package/templates/database/config/schema.prisma +91 -0
- package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +146 -0
- package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +77 -0
- package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +45 -0
- package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +47 -0
- package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +16 -0
- package/templates/database/migrations/20261025120000_trial_magnet/migration.sql +28 -0
- package/templates/database/migrations/20261026120000_trial_magnet_proof_line/migration.sql +23 -0
- package/templates/database/package.json +1 -1
- package/templates/database/seed.ts +57 -2
- package/templates/tools/lint-architecture.ts +205 -1
- package/versions.json +35 -35
- package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -199
- package/templates/apps/api/src/routes/content/slug.ts +0 -9
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +0 -93
- package/templates/apps/web/app/[locale]/downloads/page.tsx +0 -37
- package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +0 -139
- package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +0 -93
- package/templates/apps/web/app/api/checkout/[offering]/route.ts +0 -48
- package/templates/apps/web/app/api/checkout/__tests__/route.test.ts +0 -120
- package/templates/apps/web/app/api/checkout/__tests__/success-path.test.ts +0 -86
- package/templates/apps/web/app/api/webhooks/stripe/__tests__/route.test.ts +0 -92
- package/templates/apps/web/app/api/webhooks/stripe/route.ts +0 -31
- package/templates/apps/web/lib/__tests__/checkout.test.ts +0 -243
- package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +0 -120
- package/templates/apps/web/lib/checkout.ts +0 -93
- package/templates/apps/web/lib/meeting-slugs.ts +0 -63
|
@@ -50,7 +50,7 @@ jest.mock("../../../lib/prisma.js", () => ({
|
|
|
50
50
|
},
|
|
51
51
|
user: { findUnique: jest.fn() },
|
|
52
52
|
projectListing: { findMany: jest.fn() },
|
|
53
|
-
waitlistScore: {
|
|
53
|
+
waitlistScore: { groupBy: jest.fn() },
|
|
54
54
|
},
|
|
55
55
|
}));
|
|
56
56
|
|
|
@@ -258,9 +258,16 @@ function installFixtureMocks(): void {
|
|
|
258
258
|
},
|
|
259
259
|
);
|
|
260
260
|
|
|
261
|
-
(prisma.waitlistScore.
|
|
262
|
-
({ where }: { where: { siteId: string; experimentKey: string } }) =>
|
|
263
|
-
Promise.resolve(
|
|
261
|
+
(prisma.waitlistScore.groupBy as jest.Mock).mockImplementation(
|
|
262
|
+
({ where }: { where: { siteId: string; experimentKey: { in: string[] } } }) =>
|
|
263
|
+
Promise.resolve(
|
|
264
|
+
where.experimentKey.in
|
|
265
|
+
.filter((experimentKey) => (WAITLIST_COUNTS[where.siteId]?.[experimentKey] ?? 0) > 0)
|
|
266
|
+
.map((experimentKey) => ({
|
|
267
|
+
experimentKey,
|
|
268
|
+
_count: { _all: WAITLIST_COUNTS[where.siteId]![experimentKey] },
|
|
269
|
+
})),
|
|
270
|
+
),
|
|
264
271
|
);
|
|
265
272
|
}
|
|
266
273
|
|
|
@@ -378,7 +385,7 @@ describe("GET /v1/project-listings/me — the empty case is not a refusal", () =
|
|
|
378
385
|
it("no waitlist count is read when the caller owns nothing", async () => {
|
|
379
386
|
await app.inject({ method: "GET", url: URL_ME, headers: cookie("cookie_cleo") });
|
|
380
387
|
|
|
381
|
-
expect(prisma.waitlistScore.
|
|
388
|
+
expect(prisma.waitlistScore.groupBy).not.toHaveBeenCalled();
|
|
382
389
|
});
|
|
383
390
|
});
|
|
384
391
|
|
|
@@ -419,13 +426,17 @@ describe("GET /v1/project-listings/me — the response shape", () => {
|
|
|
419
426
|
// One shared site id would have reported 0 for one of them.
|
|
420
427
|
expect(byId["prjl_ada_one"]).toBe(7);
|
|
421
428
|
expect(byId["prjl_ada_two"]).toBe(3);
|
|
422
|
-
expect(prisma.waitlistScore.
|
|
429
|
+
expect(prisma.waitlistScore.groupBy).toHaveBeenCalledWith({
|
|
430
|
+
by: ["experimentKey"],
|
|
423
431
|
// #6258 AC-6 — the fenced read now filters the CONSTRAINED column beside
|
|
424
432
|
// `siteId`; the two are equal by the workspace_site_agree CHECK.
|
|
425
|
-
where: { siteId: SITE_A, workspaceId: SITE_A, experimentKey: "atlas-waitlist" },
|
|
433
|
+
where: { siteId: SITE_A, workspaceId: SITE_A, experimentKey: { in: ["atlas-waitlist"] } },
|
|
434
|
+
_count: { _all: true },
|
|
426
435
|
});
|
|
427
|
-
expect(prisma.waitlistScore.
|
|
428
|
-
|
|
436
|
+
expect(prisma.waitlistScore.groupBy).toHaveBeenCalledWith({
|
|
437
|
+
by: ["experimentKey"],
|
|
438
|
+
where: { siteId: SITE_B, workspaceId: SITE_B, experimentKey: { in: ["atlas-two-waitlist"] } },
|
|
439
|
+
_count: { _all: true },
|
|
429
440
|
});
|
|
430
441
|
});
|
|
431
442
|
|
|
@@ -61,7 +61,7 @@ jest.mock("../../../lib/prisma.js", () => ({
|
|
|
61
61
|
projectListing: { findMany: jest.fn() },
|
|
62
62
|
siteListingConfig: { findUnique: jest.fn() },
|
|
63
63
|
waitlistExperiment: { findMany: jest.fn() },
|
|
64
|
-
waitlistScore: {
|
|
64
|
+
waitlistScore: { groupBy: jest.fn() },
|
|
65
65
|
},
|
|
66
66
|
}));
|
|
67
67
|
|
|
@@ -236,9 +236,13 @@ function installFixtureMocks(): void {
|
|
|
236
236
|
// count is keyed on the `experimentKey` the listing already holds, so the
|
|
237
237
|
// id-to-key hop this route used to make is gone. A test below asserts it
|
|
238
238
|
// stays gone.
|
|
239
|
-
(prisma.waitlistScore.
|
|
240
|
-
({ where }: { where: { experimentKey: string } }) =>
|
|
241
|
-
Promise.resolve(
|
|
239
|
+
(prisma.waitlistScore.groupBy as jest.Mock).mockImplementation(
|
|
240
|
+
({ where }: { where: { experimentKey: { in: string[] } } }) =>
|
|
241
|
+
Promise.resolve(
|
|
242
|
+
where.experimentKey.in
|
|
243
|
+
.filter((experimentKey) => (COUNTS_BY_KEY[experimentKey] ?? 0) > 0)
|
|
244
|
+
.map((experimentKey) => ({ experimentKey, _count: { _all: COUNTS_BY_KEY[experimentKey] } })),
|
|
245
|
+
),
|
|
242
246
|
);
|
|
243
247
|
}
|
|
244
248
|
|
|
@@ -413,10 +417,17 @@ describe("GET /v1/project-listings/public", () => {
|
|
|
413
417
|
|
|
414
418
|
it("counts the waitlist through the listing's own experimentKey, in one hop", async () => {
|
|
415
419
|
await app.inject({ method: "GET", url: `/v1/project-listings/public?siteKey=${SITE_KEY}` });
|
|
416
|
-
expect(prisma.waitlistScore.
|
|
420
|
+
expect(prisma.waitlistScore.groupBy).toHaveBeenCalledTimes(1);
|
|
421
|
+
expect(prisma.waitlistScore.groupBy).toHaveBeenCalledWith({
|
|
422
|
+
by: ["experimentKey"],
|
|
417
423
|
// #6258 AC-6 — the fenced read now filters the CONSTRAINED column
|
|
418
424
|
// beside `siteId`; the two are equal by the workspace_site_agree CHECK.
|
|
419
|
-
where: {
|
|
425
|
+
where: {
|
|
426
|
+
siteId: SITE,
|
|
427
|
+
workspaceId: SITE,
|
|
428
|
+
experimentKey: { in: expect.arrayContaining(["exp-newest"]) },
|
|
429
|
+
},
|
|
430
|
+
_count: { _all: true },
|
|
420
431
|
});
|
|
421
432
|
// The id-to-key translation read is gone with #5036 D6d.
|
|
422
433
|
expect(prisma.waitlistExperiment.findMany).not.toHaveBeenCalled();
|
|
@@ -885,7 +885,7 @@ describe("a captured draft answers its listicle's Application fields (#7521 AC-2
|
|
|
885
885
|
({ where }: { where: Record<string, unknown> }) =>
|
|
886
886
|
Promise.resolve(flows.filter((f) => matches(f, where))),
|
|
887
887
|
);
|
|
888
|
-
(prisma.waitlistScore.
|
|
888
|
+
(prisma.waitlistScore.groupBy as jest.Mock).mockResolvedValue([]);
|
|
889
889
|
app = Fastify();
|
|
890
890
|
await app.register(startProjectListingRoute);
|
|
891
891
|
await app.register(patchProjectListingDraftRoute);
|
|
@@ -199,6 +199,72 @@ through `metadata.site`.
|
|
|
199
199
|
site, which silences the hub's dunning record for that subscription — the
|
|
200
200
|
same key can already cancel or re-price it outright.
|
|
201
201
|
|
|
202
|
+
## Per-Organization endpoint (#8932)
|
|
203
|
+
|
|
204
|
+
`POST /v1/webhooks/stripe/:connectionToken` (`stripe-offerings.ts`) is the
|
|
205
|
+
receiver for one Organization's own Stripe connection — the Offerings money
|
|
206
|
+
path (spec `docs/specs/8932-the-per-organization-stripe-webhook-spec.md`,
|
|
207
|
+
contract `docs/specs/8879-the-offerings-security-contract-key-spec.md` W1,
|
|
208
|
+
W3–W11, G5, T8). The 43-character token selects one `StripeConnection` row
|
|
209
|
+
(`findStripeConnectionByWebhookToken`, #8907); that row's decrypted secret
|
|
210
|
+
verifies the signature through `verifyWebhookSignatureKeyless` (no env key),
|
|
211
|
+
and its `organizationId` is the only Organization the event can act for.
|
|
212
|
+
Registered beside the legacy route on every build, with its own 100/min
|
|
213
|
+
per-IP bucket; the commerce claim is read at call time.
|
|
214
|
+
|
|
215
|
+
**The ladder** — the first step that answers ends the request; every `200`
|
|
216
|
+
body is `{ received: true }` and the outcome is the log line's only:
|
|
217
|
+
|
|
218
|
+
| # | Step | Answer | Seen |
|
|
219
|
+
| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | -------- |
|
|
220
|
+
| 1 | the token resolves to no row (or is malformed) | `404 not-found`, logs nothing | no |
|
|
221
|
+
| 1a | the token lookup rejects | `503 webhook-unavailable` | no |
|
|
222
|
+
| 2 | the row has no usable secret | `503 webhook-not-configured` | no |
|
|
223
|
+
| 3–6 | signature settled, then the claim (as the legacy route): `undetermined` → `503` + `Retry-After: 300`; `unclaimed` → signed `200 dropped_no_commerce_claim`, unsigned the module-disabled `404`; then the held `400` | as stated | no |
|
|
224
|
+
| 7 | `event.livemode` is not the row's mode | `200 livemode_mismatch` | no |
|
|
225
|
+
| 8 | not `checkout.session.completed` / `.async_payment_succeeded` on a session object | `200 ignored_type` | no |
|
|
226
|
+
| 9–11 | the hub's session record (#8930): rejects / unreadable / none | `503 record-unavailable` / `500 record-unreadable` / `200 unrecorded` | no |
|
|
227
|
+
| 12 | another Organization's record, or `metadata.site` is not the record's workspace | `200 foreign_organization` | no |
|
|
228
|
+
| 13 | the record's mode is not the row's | `200 livemode_mismatch` | no |
|
|
229
|
+
| 14 | already seen for this Organization | `200 duplicate` | — |
|
|
230
|
+
| 15 | a `completed` session neither `paid` nor `no_payment_required` | `200 unpaid` | yes |
|
|
231
|
+
| 16 | the connection's client or its `listLineItems` call fails | `503 stripe-unavailable` (never the env key) | no |
|
|
232
|
+
| 17 | not exactly one line item whose Price id is the record's | `200 offering_mismatch` | yes |
|
|
233
|
+
| 18 | `routeLead` (below) | `200 fulfilled` / `already_fulfilled` / `unfulfillable`, else `503 fulfilment-unavailable` | yes / no |
|
|
234
|
+
|
|
235
|
+
**The lead.** One per session: `routeLead` with the idempotency key
|
|
236
|
+
`"stripe-offerings " + sessionId` — a space, which no `Idempotency-Key`
|
|
237
|
+
header can carry, so no anonymous lead can take the key first — and
|
|
238
|
+
`offeringsCheckout: true`. Its offering is the record's FULL lookup key,
|
|
239
|
+
never `metadata.ailk_offering`. A live session's lead is marked
|
|
240
|
+
`spokeCheckout`, the hold #8793's `releaseHeldCheckoutPlan` releases at the
|
|
241
|
+
buyer's verified first link; no path here writes `Person.planOffering`. A
|
|
242
|
+
test-mode session's lead is written unmarked: no hold, purchase message,
|
|
243
|
+
email or consent grant. A replay or a conflict on that key is
|
|
244
|
+
`already_fulfilled`; a lead the database did not take is a `503`, so Stripe
|
|
245
|
+
retries.
|
|
246
|
+
|
|
247
|
+
**The legacy endpoint steps aside (W8).** `POST /v1/webhooks/stripe` acks
|
|
248
|
+
any event carrying an `ailk_offering` key — in a session's metadata or any
|
|
249
|
+
site-stamp source above — with `200 { received: true }` and
|
|
250
|
+
`outcome: "offerings_path"`, **before** `markSeen`, and writes nothing.
|
|
251
|
+
Every event without the key takes the legacy path unchanged.
|
|
252
|
+
|
|
253
|
+
**Two dedup stores.** The legacy route keeps `hasSeen` / `markSeen`; the
|
|
254
|
+
per-Organization route uses its own `hasSeenForOrganization` /
|
|
255
|
+
`markSeenForOrganization`, keyed `<organizationId>:<eventId>`. Neither
|
|
256
|
+
endpoint's store can swallow the other's event. Both are in-memory per
|
|
257
|
+
instance; the per-Organization route's correctness rests on the lead's
|
|
258
|
+
database key.
|
|
259
|
+
|
|
260
|
+
**Logs.** Each line carries `organizationId`, `eventType`, an 8-character
|
|
261
|
+
`eventIdPrefix` and `outcome` (fewer before an event is verified) — never
|
|
262
|
+
the token, the secret, a full Stripe id, an email, a lookup key, a metadata
|
|
263
|
+
value or Stripe product text. Request and error lines log the path with
|
|
264
|
+
the token replaced whole by `:connectionToken` (`redactLogPath`).
|
|
265
|
+
|
|
266
|
+
**W11.** No billing identity is bound from a connection's event.
|
|
267
|
+
|
|
202
268
|
## Dunning (#6034)
|
|
203
269
|
|
|
204
270
|
Two arms record a per-subscriber lifecycle state so a failed renewal is no
|
|
@@ -179,6 +179,7 @@ describe("AC-1 — completed stamps the row once, guarded, beside the legacy tas
|
|
|
179
179
|
});
|
|
180
180
|
expect(prisma.user.findFirst).toHaveBeenCalledWith({
|
|
181
181
|
where: { billingProvider: "stripe", billingCustomerId: CUSTOMER_ID },
|
|
182
|
+
select: { id: true },
|
|
182
183
|
});
|
|
183
184
|
expect(prisma.user.findUnique).not.toHaveBeenCalled();
|
|
184
185
|
});
|