create-ailk 0.15.0 → 0.16.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 +124 -0
- package/dist/add-module.js +52 -7
- package/dist/add-route.js +8 -0
- package/dist/adopt.d.ts +74 -0
- package/dist/adopt.js +314 -0
- package/dist/cli.js +82 -2
- package/dist/derive-architecture.d.ts +22 -0
- package/dist/derive-architecture.js +58 -1
- package/dist/lib/declared-env.d.ts +142 -0
- package/dist/lib/declared-env.js +985 -0
- package/dist/lib/env-catalog.d.ts +17 -0
- package/dist/lib/env-catalog.js +234 -0
- package/dist/lib/extract-module-bundle.d.ts +9 -0
- package/dist/lib/extract-module-bundle.js +18 -1
- package/dist/lib/module-tier.d.ts +2 -0
- package/dist/lib/module-tier.js +4 -0
- package/dist/module-architecture.js +76 -0
- package/dist/parse-args.d.ts +5 -0
- package/dist/parse-args.js +17 -1
- package/dist/programmatic.d.ts +8 -0
- package/dist/programmatic.js +78 -9
- package/dist/seed-flows.d.ts +65 -0
- package/dist/seed-flows.js +350 -0
- package/dist/site-manifest.d.ts +43 -0
- package/dist/site-manifest.js +98 -3
- package/dist/site-roster.d.ts +13 -0
- package/dist/site-roster.js +22 -8
- package/dist/spoke-checkout.d.ts +42 -0
- package/dist/spoke-checkout.js +359 -0
- package/dist/surfaces.js +7 -0
- package/dist/sync-routes.js +23 -8
- package/dist/topology.d.ts +124 -9
- package/dist/topology.js +449 -40
- package/package.json +3 -3
- package/templates/.env.example +10 -0
- package/templates/apps/api/CLAUDE.md +19 -4
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +95 -16
- package/templates/apps/api/src/__tests__/openapi-keyless-hub.test.ts +122 -0
- package/templates/apps/api/src/__tests__/support/hub-sign-in-fake-db.ts +362 -0
- package/templates/apps/api/src/bin/spokes.ts +62 -1
- package/templates/apps/api/src/config/__tests__/hub-require-spoke-key.test.ts +75 -0
- package/templates/apps/api/src/config/index.ts +35 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +89 -0
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +66 -5
- package/templates/apps/api/src/lib/__tests__/hub-sign-in-store.test.ts +209 -0
- package/templates/apps/api/src/lib/__tests__/spoke-store.test.ts +20 -0
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +24 -5
- package/templates/apps/api/src/lib/hub-sign-in-store.ts +419 -0
- package/templates/apps/api/src/lib/spoke-store.ts +25 -3
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +310 -0
- package/templates/apps/api/src/middleware/auth.ts +126 -0
- package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +483 -0
- package/templates/apps/api/src/routes/auth/hub-sign-in.ts +201 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +7 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +105 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +72 -1
- package/templates/apps/api/src/server.ts +105 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +4 -4
- package/templates/apps/api/src/services/__tests__/hub-sign-in.test.ts +854 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +293 -0
- package/templates/apps/api/src/services/__tests__/person.test.ts +553 -0
- package/templates/apps/api/src/services/hub-sign-in.ts +471 -0
- package/templates/apps/api/src/services/lead-routing.ts +131 -2
- package/templates/apps/api/src/services/person.ts +541 -0
- package/templates/apps/web/.env.example +58 -5
- package/templates/apps/web/app/[locale]/(auth)/sign-in/next-path.ts +0 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/hub-mode.test.tsx +206 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +256 -3
- package/templates/apps/web/app/[locale]/(authed)/account/account-data.ts +5 -8
- package/templates/apps/web/app/[locale]/(authed)/account/inbox/message/[id]/file/__tests__/route.test.ts +205 -0
- package/templates/apps/web/app/[locale]/(authed)/account/inbox/message/[id]/file/route.ts +82 -0
- package/templates/apps/web/app/[locale]/(authed)/account/settings/page.tsx +98 -29
- package/templates/apps/web/app/[locale]/(authed)/account/support-actions.ts +6 -3
- package/templates/apps/web/app/[locale]/(authed)/layout.tsx +19 -101
- package/templates/apps/web/app/[locale]/(authed)/signed-out-gate.generated.tsx +90 -0
- package/templates/apps/web/app/[locale]/layout.tsx +28 -15
- package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +295 -0
- package/templates/apps/web/app/api/auth/hub/authorize/route.ts +156 -0
- package/templates/apps/web/app/api/hub-session/__tests__/routes.test.ts +341 -0
- package/templates/apps/web/app/api/hub-session/callback/route.ts +168 -0
- package/templates/apps/web/app/api/hub-session/sign-in/route.ts +76 -0
- package/templates/apps/web/app/api/hub-session/sign-out/route.ts +63 -0
- package/templates/apps/web/app/openapi.json/__tests__/route.test.ts +15 -0
- package/templates/apps/web/app/openapi.json/route.ts +3 -6
- package/templates/apps/web/auth.ts +29 -2
- package/templates/apps/web/components/sign-out-trigger.tsx +20 -1
- package/templates/apps/web/components/signed-in-nav.tsx +17 -3
- package/templates/apps/web/lib/__tests__/account-config.test.ts +5 -2
- package/templates/apps/web/lib/__tests__/listings-config.test.ts +83 -0
- package/templates/apps/web/lib/__tests__/listings.test.tsx +49 -0
- package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +49 -1
- package/templates/apps/web/lib/__tests__/relay.test.ts +27 -0
- package/templates/apps/web/lib/account-config.ts +130 -1
- package/templates/apps/web/lib/api-origin.generated.ts +11 -0
- package/templates/apps/web/lib/hub-session/__tests__/hub-session.test.ts +262 -0
- package/templates/apps/web/lib/hub-session/config.ts +104 -0
- package/templates/apps/web/lib/hub-session/index.ts +23 -0
- package/templates/apps/web/lib/hub-session/seal.ts +82 -0
- package/templates/apps/web/lib/hub-session/session.ts +180 -0
- package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +79 -0
- package/templates/apps/web/lib/listings-config.ts +31 -0
- package/templates/apps/web/lib/listings.tsx +7 -3
- package/templates/apps/web/lib/meeting-slugs.ts +11 -1
- package/templates/apps/web/lib/next-path.ts +35 -0
- package/templates/apps/web/lib/relay.ts +3 -1
- package/templates/apps/web/lib/server-api.ts +11 -1
- package/templates/apps/web/lib/topology.generated.ts +15 -5
- package/templates/apps/web/messages/en.json +32 -3
- package/templates/database/CHANGELOG.md +26 -0
- package/templates/database/__tests__/agents.db.test.ts +25 -5
- package/templates/database/__tests__/fixtures/person-backfill.json +264 -0
- package/templates/database/__tests__/member-area.db.test.ts +245 -0
- package/templates/database/__tests__/person.db.test.ts +584 -0
- package/templates/database/auth/schema.prisma +3 -2
- package/templates/database/config/schema.prisma +13 -0
- package/templates/database/inbox/schema.prisma +98 -0
- package/templates/database/migrations/20261010120000_person/migration.sql +106 -0
- package/templates/database/migrations/20261010121000_person_backfill/migration.sql +132 -0
- package/templates/database/migrations/20261010130000_agent_grants_people_read/migration.sql +20 -0
- package/templates/database/migrations/20261011120000_member_area/migration.sql +158 -0
- package/templates/database/migrations/20261011130000_agent_grants_member_pieces_manage/migration.sql +21 -0
- package/templates/database/package.json +1 -1
- package/templates/database/scripts/person-backfill.sql +132 -0
- package/templates/database/tenancy.ts +53 -0
- package/templates/tools/check-declared-env.ts +37 -0
- package/templates/tools/declared-env.ts +1163 -0
- package/versions.json +35 -35
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HubSignInGate — the signed-out account gate of a spoke that signs in
|
|
3
|
+
* through its hub (#7894 D10).
|
|
4
|
+
*
|
|
5
|
+
* A Server Component, rendered by the `(authed)` layout through the generated
|
|
6
|
+
* gate seam (`signed-out-gate.generated.tsx`, its `signIn: "hub"` render). It
|
|
7
|
+
* offers ONE action: a link to this site's own `/api/hub-session/sign-in`,
|
|
8
|
+
* carrying the requested path as `next`, which starts the hub sign-in. The
|
|
9
|
+
* link is a plain anchor (the `Link` primitive), so nothing prefetches the
|
|
10
|
+
* route and starts a flow the visitor did not ask for.
|
|
11
|
+
*
|
|
12
|
+
* Not configured (`hubSessionConfig()` is `null`: a client id, secret or
|
|
13
|
+
* session secret missing), it renders the account area's existing "sign-in
|
|
14
|
+
* isn't available yet" state, whose one action leads back to the site.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { localeHref } from "@working-theory/ui";
|
|
18
|
+
import { Button, Heading, Link, Text } from "@working-theory/ui/primitives";
|
|
19
|
+
import { getTranslations } from "next-intl/server";
|
|
20
|
+
import type { ReactElement } from "react";
|
|
21
|
+
|
|
22
|
+
import { hubSessionConfig } from "./config";
|
|
23
|
+
import { HUB_SESSION_FALLBACK_PATH } from "./session";
|
|
24
|
+
|
|
25
|
+
/** The route that starts the hub sign-in. */
|
|
26
|
+
const HUB_SIGN_IN_ROUTE = "/api/hub-session/sign-in";
|
|
27
|
+
|
|
28
|
+
export interface HubSignInGateProps {
|
|
29
|
+
locale: string;
|
|
30
|
+
/** The incoming request's headers; `x-pathname` is the page to return to. */
|
|
31
|
+
requestHeaders: Headers;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function HubSignInGate({
|
|
35
|
+
locale,
|
|
36
|
+
requestHeaders,
|
|
37
|
+
}: HubSignInGateProps): Promise<ReactElement> {
|
|
38
|
+
const t = await getTranslations({ locale, namespace: "account.gate" });
|
|
39
|
+
const configured = hubSessionConfig() !== null;
|
|
40
|
+
const next =
|
|
41
|
+
requestHeaders.get("x-pathname") ??
|
|
42
|
+
localeHref(locale, HUB_SESSION_FALLBACK_PATH);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div
|
|
46
|
+
className="flex min-h-screen flex-col items-center justify-center gap-4 py-section text-center"
|
|
47
|
+
data-testid="account-sign-in-gate"
|
|
48
|
+
>
|
|
49
|
+
<Heading level={1} visual={4}>
|
|
50
|
+
{configured ? t("hubHeadline") : t("emptyHeadline")}
|
|
51
|
+
</Heading>
|
|
52
|
+
<Text variant="body-sm" tone="muted" className="max-w-xs">
|
|
53
|
+
{configured ? t("hubDescription") : t("emptyDescription")}
|
|
54
|
+
</Text>
|
|
55
|
+
{configured ? (
|
|
56
|
+
// The primary action: 44px on a phone, 36px from md up.
|
|
57
|
+
<Button asChild className="max-md:min-h-11">
|
|
58
|
+
<Link
|
|
59
|
+
href={`${HUB_SIGN_IN_ROUTE}?next=${encodeURIComponent(next)}`}
|
|
60
|
+
variant="bare"
|
|
61
|
+
data-testid="account-hub-sign-in"
|
|
62
|
+
>
|
|
63
|
+
{t("hubSignIn")}
|
|
64
|
+
</Link>
|
|
65
|
+
</Button>
|
|
66
|
+
) : (
|
|
67
|
+
<Button asChild variant="secondary" className="max-md:min-h-11">
|
|
68
|
+
<Link
|
|
69
|
+
href={localeHref(locale, "/")}
|
|
70
|
+
variant="bare"
|
|
71
|
+
data-testid="account-sign-in-gate-home"
|
|
72
|
+
>
|
|
73
|
+
{t("homeLink")}
|
|
74
|
+
</Link>
|
|
75
|
+
</Button>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The listing site key — the ONE reader (#7872 D8).
|
|
3
|
+
*
|
|
4
|
+
* The listicle directory (`./listings.tsx`, #7521) switches on this key:
|
|
5
|
+
* declared, it renders; absent, it is off and makes no read. Every reader of
|
|
6
|
+
* the key goes through here, so two copies cannot drift.
|
|
7
|
+
*
|
|
8
|
+
* 1. `NEXT_PUBLIC_AILK_LISTING_SITE_KEY` — the name the spoke roadmap fixes
|
|
9
|
+
* (§7.3). The value names a site and grants nothing (#5048 D2), so it is
|
|
10
|
+
* public by design, and a client consumer gets it inlined at build.
|
|
11
|
+
* 2. `AILK_LISTING_PUBLIC_KEY` — the server-only name every site
|
|
12
|
+
* configured since create-ailk 0.12.0 already sets, kept as a fallback
|
|
13
|
+
* so an upgrade does not switch a working directory off.
|
|
14
|
+
*
|
|
15
|
+
* An empty or whitespace value counts as unset. Both reads use dot access, so
|
|
16
|
+
* Next inlines the public name at build: changing or rotating it needs a
|
|
17
|
+
* rebuild, and while it is set a runtime change to the fallback has no effect.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** The site's listing key, trimmed, or `undefined` when neither name holds one. */
|
|
21
|
+
export function listingSiteKey(): string | undefined {
|
|
22
|
+
const declared = process.env.NEXT_PUBLIC_AILK_LISTING_SITE_KEY?.trim();
|
|
23
|
+
if (declared) return declared;
|
|
24
|
+
const fallback = process.env.AILK_LISTING_PUBLIC_KEY?.trim();
|
|
25
|
+
return fallback ? fallback : undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Whether the listicle directory is switched on for this site. */
|
|
29
|
+
export function listingsEnabled(): boolean {
|
|
30
|
+
return listingSiteKey() !== undefined;
|
|
31
|
+
}
|
|
@@ -20,8 +20,11 @@
|
|
|
20
20
|
* BASE TREE, and inert without two things (D7). It ships in every scaffold,
|
|
21
21
|
* Free included, and is never slice-owned — the layout imports it, so owning
|
|
22
22
|
* it would strip every page from a keyless scaffold. Each member answers
|
|
23
|
-
* `null` with NO read when
|
|
24
|
-
* `@working-theory/ui-signal-collectors` package is absent. The
|
|
23
|
+
* `null` with NO read when the site's listing key is unset or the paid
|
|
24
|
+
* `@working-theory/ui-signal-collectors` package is absent. The key is the
|
|
25
|
+
* switch: `./listings-config.ts` is its one reader
|
|
26
|
+
* (`NEXT_PUBLIC_AILK_LISTING_SITE_KEY`, falling back to
|
|
27
|
+
* `AILK_LISTING_PUBLIC_KEY`, #7872 D8). The package is
|
|
25
28
|
* reached the way `src/lib/commerce-sections.ts` reaches its own: through
|
|
26
29
|
* `loadUiSignalCollectors()` in the generated paid-sections registry (#7536),
|
|
27
30
|
* a bundled `import()` where the site carries the package and a stub that
|
|
@@ -64,6 +67,7 @@ import { cache, type ComponentType, type ReactElement, type ReactNode } from "re
|
|
|
64
67
|
|
|
65
68
|
import { resolveBaseUrl } from "../src/lib/routes";
|
|
66
69
|
|
|
70
|
+
import { listingSiteKey } from "./listings-config";
|
|
67
71
|
import { loadUiSignalCollectors } from "./paid-sections.generated";
|
|
68
72
|
|
|
69
73
|
// ── The paid sections, through the generated registry ──────────────────────
|
|
@@ -252,7 +256,7 @@ export function entryJsonLd(entry: ListingEntryPublic, entryType: string, url: s
|
|
|
252
256
|
// ── The extension ───────────────────────────────────────────────────────────
|
|
253
257
|
|
|
254
258
|
export function createListingExtension(deps: ListingExtensionDeps = {}): ListingExtension {
|
|
255
|
-
const siteKey = deps.siteKey ??
|
|
259
|
+
const siteKey = deps.siteKey ?? listingSiteKey;
|
|
256
260
|
const pathname =
|
|
257
261
|
deps.pathname ?? (async () => (await headers()).get("x-pathname"));
|
|
258
262
|
const locales = deps.locales ?? (() => readLocales());
|
|
@@ -17,10 +17,18 @@
|
|
|
17
17
|
* A duplicated inline call in both pages is how the two would drift, and
|
|
18
18
|
* it is also how one of them would forget the fail-closed arm — this is
|
|
19
19
|
* the ONE place that reasoning lives (D5).
|
|
20
|
+
*
|
|
21
|
+
* ON A SPOKE (#7872 D4), `null` means `[]`: the `SCHEDULING_CAL_LINK`
|
|
22
|
+
* fallback is not consulted. The hub books a spoke's slug only when that
|
|
23
|
+
* spoke's workspace has stored it (`spokeMayBook`), so a fallback slug would
|
|
24
|
+
* advertise a meeting whose every booking fails with `event_not_found`. The
|
|
25
|
+
* other arms are the same on both topologies.
|
|
20
26
|
*/
|
|
21
27
|
import { schedule } from "@working-theory/api-client";
|
|
22
28
|
import { MeetingSlugSchema } from "@working-theory/site-config/schema";
|
|
23
29
|
|
|
30
|
+
import { TOPOLOGY } from "./topology.generated";
|
|
31
|
+
|
|
24
32
|
/**
|
|
25
33
|
* The D4 row-1 fallback: the slug half of the env-configured booker's own
|
|
26
34
|
* calLink (`"<owner>/<slug>"`), or `[]` when the env var is unset. This is
|
|
@@ -49,5 +57,7 @@ function derivedFallbackSlugs(): string[] {
|
|
|
49
57
|
export async function enabledMeetingSlugs(): Promise<string[]> {
|
|
50
58
|
const res = await schedule.meetingTypes();
|
|
51
59
|
if (!res.ok) return [];
|
|
52
|
-
|
|
60
|
+
const { slugs } = res.body;
|
|
61
|
+
if (slugs) return slugs;
|
|
62
|
+
return TOPOLOGY === "spoke" ? [] : derivedFallbackSlugs();
|
|
53
63
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `?next=` sanitizer for the /sign-in page (#4984) and the hub-session
|
|
3
|
+
* callback (#7894 D7 — moved here from `app/[locale]/(auth)/sign-in/`, which
|
|
4
|
+
* re-exports it, so a spoke that drops the `(auth)` group keeps it).
|
|
5
|
+
*
|
|
6
|
+
* The post-sign-in `callbackURL` handed to Better Auth is caller-controlled
|
|
7
|
+
* via the `next` search param, so it must be a SAME-ORIGIN, SAME-APP path —
|
|
8
|
+
* never a foreign origin (open redirect), a protocol-relative URL, a
|
|
9
|
+
* backslash trick browsers normalise to `//`, or a control character that
|
|
10
|
+
* could split a header. Anything that fails falls back to `fallback`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// U+0000–U+001F and U+007F — the header-splitting / URL-mangling range.
|
|
14
|
+
// eslint-disable-next-line no-control-regex -- the control range is the thing being rejected
|
|
15
|
+
const CONTROL_CHARS = /[\x00-\x1f\x7f]/;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Returns `raw` when it is a safe in-app path, else `fallback`.
|
|
19
|
+
*
|
|
20
|
+
* Accepts: a string starting with a single "/" (e.g. `/account`,
|
|
21
|
+
* `/en/account?tab=x`, `/waitlist#a`).
|
|
22
|
+
* Rejects: non-strings (incl. Next's `string[]` for a repeated param), "",
|
|
23
|
+
* `//host` (protocol-relative), `/\host` (backslash normalised to `//`),
|
|
24
|
+
* any backslash anywhere, whitespace, control characters, and anything not
|
|
25
|
+
* starting with "/" (`https://…`, `host.tld`).
|
|
26
|
+
*/
|
|
27
|
+
export function resolveNextPath(raw: unknown, fallback: string): string {
|
|
28
|
+
if (typeof raw !== "string") return fallback;
|
|
29
|
+
if (!raw.startsWith("/")) return fallback;
|
|
30
|
+
if (raw.startsWith("//")) return fallback;
|
|
31
|
+
if (raw.includes("\\")) return fallback;
|
|
32
|
+
if (/\s/.test(raw)) return fallback;
|
|
33
|
+
if (CONTROL_CHARS.test(raw)) return fallback;
|
|
34
|
+
return raw;
|
|
35
|
+
}
|
|
@@ -42,6 +42,8 @@ import { isIP } from "node:net";
|
|
|
42
42
|
|
|
43
43
|
import { relayToApi, visitorForwardHeaders } from "@working-theory/api-client";
|
|
44
44
|
|
|
45
|
+
import { browserApiOrigin } from "./api-origin.generated";
|
|
46
|
+
|
|
45
47
|
type RelayMethod = "GET" | "POST";
|
|
46
48
|
|
|
47
49
|
/** One relayed (method, path) pair. */
|
|
@@ -287,7 +289,7 @@ function hubConfigured(): boolean {
|
|
|
287
289
|
if (process.env.NODE_ENV !== "production") return true;
|
|
288
290
|
return Boolean(
|
|
289
291
|
process.env.AILK_API_URL ||
|
|
290
|
-
|
|
292
|
+
browserApiOrigin() ||
|
|
291
293
|
process.env.NEXT_PUBLIC_API_URL,
|
|
292
294
|
);
|
|
293
295
|
}
|
|
@@ -24,6 +24,9 @@
|
|
|
24
24
|
|
|
25
25
|
import { cookies, headers } from "next/headers";
|
|
26
26
|
|
|
27
|
+
import { hubSessionInit } from "./hub-session/session";
|
|
28
|
+
import { SIGN_IN_PROVIDER } from "./topology.generated";
|
|
29
|
+
|
|
27
30
|
/**
|
|
28
31
|
* Structural mirror of `@working-theory/api-client`'s `ApiResult<T>`
|
|
29
32
|
* (`packages/api-client/src/http.ts`) — not imported, because that type is
|
|
@@ -75,10 +78,17 @@ interface ErrorBody {
|
|
|
75
78
|
* pass the check exactly as a browser-originated one would. RSC `GET`s are
|
|
76
79
|
* unaffected: `isOriginAllowed` short-circuits `true` for safe methods before
|
|
77
80
|
* it ever inspects the header.
|
|
81
|
+
*
|
|
82
|
+
* On a spoke that signs in through its hub (`SIGN_IN_PROVIDER === "hub"`,
|
|
83
|
+
* #7894 D12) this is the ONE request-init re-target: it returns the hub
|
|
84
|
+
* credential as `Authorization: Bearer` (`hubSessionInit`), and no header at
|
|
85
|
+
* all signed out — the hub has no cookie session for this site's visitor.
|
|
86
|
+
* Every caller passes the result straight to the api-client unchanged.
|
|
78
87
|
*/
|
|
79
88
|
export async function forwardedCookieInit(): Promise<{
|
|
80
|
-
headers:
|
|
89
|
+
headers: Record<string, string>;
|
|
81
90
|
}> {
|
|
91
|
+
if (SIGN_IN_PROVIDER === "hub") return hubSessionInit();
|
|
82
92
|
const cookieHeader = (await cookies()).toString();
|
|
83
93
|
const origin = (await headers()).get("origin");
|
|
84
94
|
return { headers: { cookie: cookieHeader, ...(origin ? { origin } : {}) } };
|
|
@@ -1,19 +1,29 @@
|
|
|
1
1
|
// topology.generated.ts — how this site reads a session and registers its stores.
|
|
2
2
|
//
|
|
3
3
|
// GENERATED by create-ailk for this site's topology (#7426). Do not edit:
|
|
4
|
-
// every scaffold rewrites it. A hub reads the session through Better Auth
|
|
5
|
-
//
|
|
6
|
-
// so its render reads no session and imports nothing
|
|
7
|
-
|
|
4
|
+
// every scaffold rewrites it. A hub reads the session through Better Auth.
|
|
5
|
+
// A spoke (`--topology spoke`) has no database of its own: by default it
|
|
6
|
+
// has no sign-in, so its render reads no session and imports nothing; with
|
|
7
|
+
// `--hub-sign-in` (#7894) it reads the sealed hub-session cookie.
|
|
8
8
|
|
|
9
9
|
export const TOPOLOGY: "hub" | "spoke" = "hub";
|
|
10
10
|
export const SIGN_IN_ENABLED: boolean = true;
|
|
11
|
+
export const SIGN_IN_PROVIDER: "site" | "hub" | "none" = "site";
|
|
12
|
+
export const SIGN_IN_PATH: string | null = "/sign-in";
|
|
13
|
+
export const SIGN_UP_PATH: string | null = "/sign-up";
|
|
14
|
+
export const SIGN_OUT_ENDPOINT: string | null = null;
|
|
11
15
|
|
|
12
16
|
export type SiteSession = {
|
|
13
|
-
user: {
|
|
17
|
+
user: {
|
|
18
|
+
name?: string | null;
|
|
19
|
+
email: string;
|
|
20
|
+
emailVerified: boolean;
|
|
21
|
+
createdAt?: Date | string | null;
|
|
22
|
+
};
|
|
14
23
|
} | null;
|
|
15
24
|
|
|
16
25
|
export async function readSession(headers: Headers): Promise<SiteSession> {
|
|
26
|
+
const { auth } = await import("../auth");
|
|
17
27
|
return auth.api.getSession({ headers });
|
|
18
28
|
}
|
|
19
29
|
|
|
@@ -303,6 +303,16 @@
|
|
|
303
303
|
"ctaLabel": "Back to services"
|
|
304
304
|
}
|
|
305
305
|
},
|
|
306
|
+
"checkoutThankYou": {
|
|
307
|
+
"title": "Thank you",
|
|
308
|
+
"body": "Your payment went through. We'll email you a receipt and the next steps."
|
|
309
|
+
},
|
|
310
|
+
"checkoutUnavailable": {
|
|
311
|
+
"title": "Checkout isn't available right now",
|
|
312
|
+
"body": "Nothing was charged. Please try again in a few minutes.",
|
|
313
|
+
"ctaHeadline": "Need a hand?",
|
|
314
|
+
"ctaLabel": "Contact us"
|
|
315
|
+
},
|
|
306
316
|
"resources": {
|
|
307
317
|
"heading": "Resources",
|
|
308
318
|
"emptyState": "Resources listing coming soon."
|
|
@@ -649,7 +659,8 @@
|
|
|
649
659
|
"unsaved": "Unsaved changes",
|
|
650
660
|
"clean": "No unsaved changes",
|
|
651
661
|
"nameRequired": "Enter your name.",
|
|
652
|
-
"saveError": "We couldn't save your name. Try again."
|
|
662
|
+
"saveError": "We couldn't save your name. Try again.",
|
|
663
|
+
"manageOnHub": "Manage your account on our main site"
|
|
653
664
|
},
|
|
654
665
|
"security": {
|
|
655
666
|
"title": "Security",
|
|
@@ -660,7 +671,8 @@
|
|
|
660
671
|
"magicLink": "Email sign-in link",
|
|
661
672
|
"password": "Password",
|
|
662
673
|
"available": "Available",
|
|
663
|
-
"none": "No sign-in method is configured."
|
|
674
|
+
"none": "No sign-in method is configured.",
|
|
675
|
+
"viaHub": "You sign in through our main site. Your sign-in methods are managed there."
|
|
664
676
|
},
|
|
665
677
|
"notifications": {
|
|
666
678
|
"title": "Notifications",
|
|
@@ -696,7 +708,10 @@
|
|
|
696
708
|
"emptyHeadline": "Sign-in isn't available yet",
|
|
697
709
|
"emptyDescription": "This site hasn't turned on sign-in for members yet. Check back soon, or contact the site owner.",
|
|
698
710
|
"signInLink": "Go to the sign-in page",
|
|
699
|
-
"homeLink": "Back to the site"
|
|
711
|
+
"homeLink": "Back to the site",
|
|
712
|
+
"hubHeadline": "Sign in to your account",
|
|
713
|
+
"hubDescription": "You sign in on our main site, then come straight back here.",
|
|
714
|
+
"hubSignIn": "Sign in"
|
|
700
715
|
},
|
|
701
716
|
"profile": {
|
|
702
717
|
"email": "Email",
|
|
@@ -718,6 +733,20 @@
|
|
|
718
733
|
"period": "Period",
|
|
719
734
|
"date": "Date"
|
|
720
735
|
},
|
|
736
|
+
"pieces": {
|
|
737
|
+
"heading": "My pieces",
|
|
738
|
+
"type": "Piece",
|
|
739
|
+
"stage": "Stage",
|
|
740
|
+
"createdAt": "Started",
|
|
741
|
+
"updatedAt": "Updated",
|
|
742
|
+
"needsClient": "This piece is waiting on you."
|
|
743
|
+
},
|
|
744
|
+
"messages": {
|
|
745
|
+
"heading": "My messages",
|
|
746
|
+
"sentAt": "Sent",
|
|
747
|
+
"file": "File",
|
|
748
|
+
"download": "Download {filename}"
|
|
749
|
+
},
|
|
721
750
|
"subscription": {
|
|
722
751
|
"headline": "Your payment didn't go through",
|
|
723
752
|
"action": "Update payment method"
|
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @working-theory/database
|
|
2
2
|
|
|
3
|
+
## 0.38.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#8037](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/8037) [`89eba8c`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/89eba8c2bab16766a68284f52c872aae86f59fde) Thanks [@samuelhenry](https://github.com/samuelhenry)! - The member area contract ([#7877](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/7877)): a client's pieces, plan and messages.
|
|
8
|
+
|
|
9
|
+
- `@working-theory/database` — `Piece` and `Interaction` on Person (a composite `(person_id, workspace_id)` foreign key to `people`) and `WorkspacePieceStages`, a workspace's declared piece stages; migration `20261011120000_member_area`.
|
|
10
|
+
- `@working-theory/ids` — the `piece_`, `intx_` and `pstages_` prefixes; `zPieceId` and `zInteractionId`.
|
|
11
|
+
- `@working-theory/validation` — the member-area wire schemas (`pieces.ts`, `member.ts`), the Person timeline's `message` kind, the `people-write` permission (owner, admin; not agent-grantable) and the attachment helpers (`ATTACHABLE_CONTENT_TYPES`, `attachmentFilename`).
|
|
12
|
+
- `@working-theory/api-client` — `member.pieces`, `member.messages` and `member.messageFile` (with `getRaw`), the reads a spoke's server makes for its signed-in client. A spoke that re-serves a file sends its five response headers unchanged.
|
|
13
|
+
- `create-ailk` — the member-area files are co-owned by the `workspace` and `multi-tenant` slices.
|
|
14
|
+
|
|
15
|
+
- [#8049](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/8049) [`6a2a120`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/6a2a120765c423303716d5e3ebf216c9c10e118b) Thanks [@samuelhenry](https://github.com/samuelhenry)! - An agent key may be granted `member-pieces-manage` ([#7878](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/7878)).
|
|
16
|
+
|
|
17
|
+
`@working-theory/validation`: the `member-pieces-manage` permission (owner, admin), the seventh entry of `AGENT_GRANTABLE_PERMISSIONS`. The member area's operator writes — a client's pieces, plan and outbound messages — check it through `allows`, so an agent granted it keeps them in its one workspace; `people-write` stays never-grantable. `@working-theory/database`: the `agents_grants_allowed` CHECK widened to admit it (additive).
|
|
18
|
+
|
|
19
|
+
- [#8004](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/8004) [`d9ba65f`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/d9ba65f6334681b17bd77b64bb7ca9e069b1980f) Thanks [@claudio-planck](https://github.com/claudio-planck)! - Person — the per-workspace client record ([#852](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/852)).
|
|
20
|
+
|
|
21
|
+
`@working-theory/database`: a `Person` model (one client per normalized email per workspace), `personId` on `Lead` and `Appointment`, the schema and set-based backfill migrations, the post-cutover re-run script `scripts/person-backfill.sql`, the agent-grant CHECK widened to admit `people-read`, and the timeline's email-fallback SQL constants on the `tenancy` subpath. `@working-theory/ids`: the `person_` prefix and `zPersonId`. `@working-theory/validation`: the `people-read` permission (owner, admin; agent-grantable) and the People wire schemas. `@working-theory/api-client`: `people.list` and `people.get` for an agent key granted `people-read`.
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- Updated dependencies [[`89eba8c`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/89eba8c2bab16766a68284f52c872aae86f59fde), [`6a2a120`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/6a2a120765c423303716d5e3ebf216c9c10e118b), [`6d38b1a`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/6d38b1a5362b36e56d6bce79fdf96c878020cb18), [`d9ba65f`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/d9ba65f6334681b17bd77b64bb7ca9e069b1980f)]:
|
|
26
|
+
- @working-theory/validation@0.38.0
|
|
27
|
+
- @working-theory/ids@0.11.0
|
|
28
|
+
|
|
3
29
|
## 0.37.0
|
|
4
30
|
|
|
5
31
|
### Minor Changes
|
|
@@ -18,11 +18,12 @@
|
|
|
18
18
|
* 4. Deleting the membership of the person last shown the key sets
|
|
19
19
|
* `key_issued_to_membership_id` null and keeps the agent.
|
|
20
20
|
* 5. Deleting the creator's user row leaves the agent (no relation).
|
|
21
|
-
* 6. `grants` (#7864 D2, AC-2) defaults to empty, takes any of the
|
|
22
|
-
* grantable permissions
|
|
23
|
-
*
|
|
21
|
+
* 6. `grants` (#7864 D2, AC-2) defaults to empty, takes any of the seven
|
|
22
|
+
* grantable permissions (`people-read` since #852 D9,
|
|
23
|
+
* `member-pieces-manage` since #7878), and the `agents_grants_allowed`
|
|
24
|
+
* CHECK refuses anything else.
|
|
24
25
|
*
|
|
25
|
-
* One case needs no database and always runs: the migration's CHECK names
|
|
26
|
+
* One case needs no database and always runs: the latest migration's CHECK names
|
|
26
27
|
* exactly `@working-theory/validation`'s `AGENT_GRANTABLE_PERMISSIONS`, so the
|
|
27
28
|
* SQL and the TypeScript spelling of the list cannot drift (SEC-7864-G).
|
|
28
29
|
*/
|
|
@@ -34,11 +35,12 @@ import { Client } from "pg";
|
|
|
34
35
|
|
|
35
36
|
const describeDb = process.env.DATABASE_URL ? describe : describe.skip;
|
|
36
37
|
|
|
38
|
+
// #7878 — the CHECK as it stands now: the latest migration that re-adds it.
|
|
37
39
|
const GRANTS_MIGRATION = join(
|
|
38
40
|
__dirname,
|
|
39
41
|
"..",
|
|
40
42
|
"migrations",
|
|
41
|
-
"
|
|
43
|
+
"20261011130000_agent_grants_member_pieces_manage",
|
|
42
44
|
"migration.sql",
|
|
43
45
|
);
|
|
44
46
|
|
|
@@ -225,6 +227,24 @@ describeDb("the agents table (#3591, live DB)", () => {
|
|
|
225
227
|
expect(await grantsOf(id)).toEqual(["content-edit", "magnets-manage"]);
|
|
226
228
|
});
|
|
227
229
|
|
|
230
|
+
it("takes people-read (#852 D9)", async () => {
|
|
231
|
+
const id = await insertAgent();
|
|
232
|
+
await db.query(
|
|
233
|
+
`UPDATE "agents" SET "grants" = ARRAY['people-read']::TEXT[] WHERE "id" = $1`,
|
|
234
|
+
[id],
|
|
235
|
+
);
|
|
236
|
+
expect(await grantsOf(id)).toEqual(["people-read"]);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("takes member-pieces-manage (#7878)", async () => {
|
|
240
|
+
const id = await insertAgent();
|
|
241
|
+
await db.query(
|
|
242
|
+
`UPDATE "agents" SET "grants" = ARRAY['member-pieces-manage','people-read']::TEXT[] WHERE "id" = $1`,
|
|
243
|
+
[id],
|
|
244
|
+
);
|
|
245
|
+
expect(await grantsOf(id)).toEqual(["member-pieces-manage", "people-read"]);
|
|
246
|
+
});
|
|
247
|
+
|
|
228
248
|
it("refuses any other permission", async () => {
|
|
229
249
|
const id = await insertAgent();
|
|
230
250
|
await expect(
|