@teamscala/service-boot 0.23.479 → 0.23.492

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.
Files changed (46) hide show
  1. package/package.json +1 -261
  2. package/src/agreement-gate/signing-session.ts +1 -1
  3. package/src/build-client-assets/styles-build.ts +11 -1
  4. package/src/mount-orpc-router.ts +1 -1
  5. package/src/register-page-server-client.ts +1 -15
  6. package/src/register-registry-page-route/region-slots-page-data.ts +2 -2
  7. package/src/register-ws-routes.ts +1 -1
  8. package/src/fn-routers/agreement-certificate.ts +0 -224
  9. package/src/fn-routers/agreement-completion-email.ts +0 -114
  10. package/src/fn-routers/agreement-send-router.ts +0 -303
  11. package/src/fn-routers/agreement-sign-side-effects.ts +0 -192
  12. package/src/fn-routers/agreement-signing-evidence.ts +0 -105
  13. package/src/fn-routers/assessment-report-data.test.ts +0 -404
  14. package/src/fn-routers/assessment-report-data.ts +0 -485
  15. package/src/fn-routers/assessment-report-email.ts +0 -256
  16. package/src/fn-routers/assessment-report-pair.test.ts +0 -107
  17. package/src/fn-routers/assessment-report-pair.ts +0 -155
  18. package/src/fn-routers/assessment-research-report-data.test.ts +0 -262
  19. package/src/fn-routers/assessment-research-report-data.ts +0 -424
  20. package/src/fn-routers/assessment-scoring.ts +0 -105
  21. package/src/fn-routers/auth-router.ts +0 -65
  22. package/src/fn-routers/chat-session-router-shared.ts +0 -122
  23. package/src/fn-routers/chat-session-router.ts +0 -356
  24. package/src/fn-routers/conversation-ownership.ts +0 -24
  25. package/src/fn-routers/email-defaults.ts +0 -28
  26. package/src/fn-routers/email-link-ttl.ts +0 -28
  27. package/src/fn-routers/index.ts +0 -76
  28. package/src/fn-routers/linkedin-identity.test.ts +0 -27
  29. package/src/fn-routers/linkedin-identity.ts +0 -36
  30. package/src/fn-routers/messaging-whatsapp-router.test.ts +0 -121
  31. package/src/fn-routers/messaging-whatsapp-router.ts +0 -200
  32. package/src/fn-routers/panel-data-router.ts +0 -104
  33. package/src/fn-routers/persist-chat-messages.test.ts +0 -86
  34. package/src/fn-routers/persist-chat-messages.ts +0 -98
  35. package/src/fn-routers/proposal-send-router.ts +0 -207
  36. package/src/fn-routers/public-agreement-router.ts +0 -407
  37. package/src/fn-routers/public-assessment-router.test.ts +0 -437
  38. package/src/fn-routers/public-assessment-router.ts +0 -268
  39. package/src/fn-routers/public-gpt-session-router.ts +0 -378
  40. package/src/fn-routers/resend-client.ts +0 -39
  41. package/src/fn-routers/sign-in-flow-router.ts +0 -196
  42. package/src/fn-routers/user-preferences-router.ts +0 -130
  43. package/src/fn-routers/work-items-router.ts +0 -342
  44. package/src/page-server-orpc/audit-revert-router.ts +0 -102
  45. package/src/page-server-orpc/puck-pages-router.ts +0 -170
  46. package/src/page-server-orpc/user-preferences-router.ts +0 -132
@@ -1,170 +0,0 @@
1
- /**
2
- * @system page-server
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * ORPC procedures for loading Puck pages. These replace the direct Prisma calls
7
- * in load-puck-page-by-slug.ts with proper ORPC procedures that receive
8
- * context.prisma from the router.
9
- */
10
-
11
- import { os } from "@orpc/server";
12
- import * as v from "valibot";
13
- import { createCache } from "@teamscala/cache/create-cache";
14
-
15
- const procedure = os;
16
-
17
- const _WebsiteShellSettingsSchema = v.object({
18
- website_type: v.nullable(v.string()),
19
- layout_settings: v.nullable(v.record(v.string(), v.unknown())),
20
- header_settings: v.nullable(v.record(v.string(), v.unknown())),
21
- footer_settings: v.nullable(v.record(v.string(), v.unknown())),
22
- });
23
-
24
- const PuckPageRowSchema = v.object({
25
- id: v.string(),
26
- path: v.nullable(v.string()),
27
- puckData: v.unknown(),
28
- mode: v.nullable(v.string()),
29
- render_mode: v.nullable(v.string()),
30
- website_slug: v.string(),
31
- organisation_id: v.nullable(v.string()),
32
- meta_title: v.nullable(v.string()),
33
- meta_description: v.nullable(v.string()),
34
- website: v.object({
35
- slug: v.string(),
36
- name: v.string(),
37
- organisation_id: v.nullable(v.string()),
38
- website_type: v.nullable(v.string()),
39
- layout_settings: v.nullable(v.record(v.string(), v.unknown())),
40
- header_settings: v.nullable(v.record(v.string(), v.unknown())),
41
- footer_settings: v.nullable(v.record(v.string(), v.unknown())),
42
- }),
43
- });
44
-
45
- type PuckPageRow = v.InferOutput<typeof PuckPageRowSchema>;
46
-
47
- /**
48
- * Right-panel / region pages (AI chat, audit, comments, …) are STATIC website
49
- * config — one page row + its website row, no per-user data — so they are safe
50
- * to cache across users keyed by website + path. Each cold open otherwise pays
51
- * TWO dependent cross-internet DB round-trips (~230ms each against the dev host's
52
- * proxied Railway Postgres); caching makes a repeat panel open near-instant.
53
- * SWR: fresh 60s, stale-served up to 5min while ONE background refresh runs
54
- * (stampede-safe), so a page edit appears within the window without every open
55
- * re-querying. No user identity in the key or value — no cross-user leak.
56
- */
57
- const pageCache = createCache<PuckPageRow | null>(
58
- "page-server:load-from-website",
59
- { ttlMs: 60_000, staleTtlMs: 5 * 60_000, maxSize: 200, stampedeProtection: true },
60
- );
61
-
62
- export const puckPagesRouter = {
63
- loadFromWebsite: procedure
64
- .input(v.object({ websiteSlug: v.string(), path: v.string() }))
65
- .output(v.nullable(PuckPageRowSchema))
66
- .handler(async ({ input, context }) => {
67
- const db = (context as { prisma?: unknown }).prisma;
68
- if (!db)
69
- throw new Error("puckPages.loadFromWebsite requires context.prisma");
70
-
71
- // Normalize the path: builda_pages stores paths WITH a leading slash
72
- // ('/chat', '/audit', …), but a panel_view.page_slug + a region
73
- // `?right=`/`?panel=` slug conventionally OMIT it ('chat', 'audit').
74
- // The SSR panel resolver (resolve-page-puck-data.ts) already prepends
75
- // '/' (`/${activePanel}`); match that contract here so a client-side
76
- // panel/region load by slug resolves the SAME row a deep-link SSR does.
77
- // Without this, regions.loadPage({ path: "chat" }) queried
78
- // `WHERE path = 'chat'` → no match → null → the panel dock opened blank
79
- // (portal Chat/Audit/Notifications/Utility + the fleet-dashboard region
80
- // swap — any page-kind panel opened by click rather than deep link).
81
- const path = input.path.startsWith("/") ? input.path : `/${input.path}`;
82
-
83
- return pageCache.fetchOrCompute(
84
- `${input.websiteSlug}::${path}`,
85
- async () => {
86
- const site = (await (
87
- db as {
88
- builda_websites: { findFirst: (args: unknown) => Promise<unknown> };
89
- }
90
- ).builda_websites.findFirst({
91
- // websiteSlug may be a website SLUG or a website ID. The Utility shell
92
- // carries the serving website's id (loaderData.*.website_id) but not its
93
- // slug, so its region panels pass the id; UUIDs and kebab slugs cannot
94
- // collide, so OR-matching both is unambiguous.
95
- where: {
96
- OR: [{ slug: input.websiteSlug }, { id: input.websiteSlug }],
97
- status: { not: "archived" },
98
- },
99
- select: {
100
- id: true,
101
- name: true,
102
- slug: true,
103
- organisation_id: true,
104
- website_type: true,
105
- layout_settings: true,
106
- header_settings: true,
107
- footer_settings: true,
108
- },
109
- })) as {
110
- id: string;
111
- name: string;
112
- slug: string;
113
- organisation_id: string | null;
114
- website_type: string | null;
115
- layout_settings: Record<string, unknown> | null;
116
- header_settings: Record<string, unknown> | null;
117
- footer_settings: Record<string, unknown> | null;
118
- } | null;
119
-
120
- if (!site) return null;
121
-
122
- const row = (await (
123
- db as {
124
- builda_pages: { findFirst: (args: unknown) => Promise<unknown> };
125
- }
126
- ).builda_pages.findFirst({
127
- where: { path, is_active: true, website_id: site.id },
128
- select: {
129
- id: true,
130
- path: true,
131
- puck_data: true,
132
- meta_title: true,
133
- meta_description: true,
134
- },
135
- })) as {
136
- id: string;
137
- path: string | null;
138
- puck_data: unknown;
139
- meta_title: string | null;
140
- meta_description: string | null;
141
- } | null;
142
-
143
- if (!row) return null;
144
-
145
- return {
146
- id: row.id,
147
- path: row.path,
148
- puckData: row.puck_data,
149
- mode: null,
150
- render_mode: null,
151
- website_slug: site.slug,
152
- organisation_id: site.organisation_id,
153
- meta_title: row.meta_title,
154
- meta_description: row.meta_description,
155
- website: {
156
- slug: site.slug,
157
- name: site.name,
158
- organisation_id: site.organisation_id,
159
- website_type: site.website_type,
160
- layout_settings: site.layout_settings,
161
- header_settings: site.header_settings,
162
- footer_settings: site.footer_settings,
163
- },
164
- };
165
- });
166
- }),
167
-
168
- };
169
-
170
- export type PuckPagesRouter = typeof puckPagesRouter;
@@ -1,132 +0,0 @@
1
- /**
2
- * @system page-server
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * ORPC procedures for per-user portal UI preferences (sidebar collapse,
7
- * panel sizes, active right panel, conversation ID, brand cache).
8
- * Reads and writes the `portal` key inside `user.preferences` JSONB.
9
- *
10
- * The SSR page loads initial state via getPortalPreferences; mutations
11
- * from usePortalActionHelpers call updatePortalPreferences. This replaces
12
- * the injected localStorage pattern with DB-backed persistence.
13
- */
14
-
15
- import { os } from "@orpc/server";
16
- import type { PrismaClient } from "@teamscala/db/client";
17
- import * as v from "valibot";
18
-
19
- const procedure = os;
20
-
21
- // Typed portal UI preferences shape (the `portal` key inside user.preferences).
22
- // Owned here because the page-server is the sole reader/writer of this key, and
23
- // db-validation's generated user-preferences schema is a loose `v.record` that
24
- // can't express this typed all-optional ("partial") shape. Used for both the
25
- // mutation input and the merged-result re-validation below.
26
- const portalPreferencesPartialSchema = v.object({
27
- // Per-user dark/light choice. Read server-side into the shell's initialTheme
28
- // (so a refresh keeps the user's choice instead of resetting to the org brand
29
- // default); written on the toolbar theme toggle.
30
- theme: v.optional(v.nullable(v.picklist(["light", "dark"]))),
31
- paneLeftWidth: v.optional(v.number()),
32
- paneRightWidth: v.optional(v.number()),
33
- sidebarCollapsed: v.optional(v.boolean()),
34
- activePanel: v.optional(v.nullable(v.string())),
35
- panelSizes: v.optional(
36
- v.object({ main: v.optional(v.number()), right: v.optional(v.number()) }),
37
- ),
38
- conversationId: v.optional(v.nullable(v.string())),
39
- brandCache: v.optional(
40
- v.nullable(
41
- v.object({
42
- logo: v.object({
43
- light: v.optional(v.nullable(v.string())),
44
- dark: v.optional(v.nullable(v.string())),
45
- brand: v.optional(v.nullable(v.string())),
46
- }),
47
- primaryColor: v.optional(v.string()),
48
- }),
49
- ),
50
- ),
51
- });
52
-
53
- type C = { prisma?: unknown; user?: { id?: string } | null };
54
-
55
- function getPrisma(c: unknown) {
56
- const db = (c as C).prisma;
57
- if (!db) throw new Error("userPreferences: context.prisma is required");
58
- return db as PrismaClient;
59
- }
60
-
61
- export const userPreferencesRouter = {
62
- /**
63
- * Read the current user's portal preferences from user.preferences.portal.
64
- * Returns null portal if no preferences exist yet.
65
- */
66
- getPortalPreferences: procedure.handler(async ({ context }) => {
67
- const prisma = getPrisma(context);
68
- const userId = (context as C).user?.id;
69
- if (!userId) return { portal: null };
70
-
71
- const user = await prisma.user.findUnique({
72
- where: { id: userId },
73
- select: { preferences: true },
74
- });
75
-
76
- const prefs = (user?.preferences as Record<string, unknown> | null) ?? {};
77
- const portal = (prefs.portal as Record<string, unknown> | undefined) ?? null;
78
- if (!portal) return { portal: null };
79
-
80
- return { portal };
81
- }),
82
-
83
- /**
84
- * Merge and persist a patch to the user's portal preferences.
85
- * The patch is a deep partial of the portal object; missing keys
86
- * are preserved from the existing value.
87
- */
88
- updatePortalPreferences: procedure
89
- .input(v.object({ patch: portalPreferencesPartialSchema }))
90
- .handler(async ({ context, input }) => {
91
- const prisma = getPrisma(context);
92
- const userId = (context as C).user?.id;
93
- if (!userId) throw new Error("updatePortalPreferences: no user id in context");
94
-
95
- const user = await prisma.user.findUnique({
96
- where: { id: userId },
97
- select: { preferences: true },
98
- });
99
-
100
- const current = ((user?.preferences as Record<string, unknown>) ?? {}) as Record<
101
- string,
102
- unknown
103
- >;
104
- const existingPortal = ((current.portal as Record<string, unknown>) ?? {}) as Record<
105
- string,
106
- unknown
107
- >;
108
-
109
- const mergedPortal = {
110
- ...existingPortal,
111
- ...input.patch,
112
- panelSizes: input.patch.panelSizes
113
- ? { ...(existingPortal.panelSizes as Record<string, unknown>), ...input.patch.panelSizes }
114
- : existingPortal.panelSizes,
115
- brandCache:
116
- input.patch.brandCache !== undefined
117
- ? input.patch.brandCache
118
- : existingPortal.brandCache,
119
- };
120
-
121
- v.parse(portalPreferencesPartialSchema, mergedPortal);
122
-
123
- await prisma.user.update({
124
- where: { id: userId },
125
- data: { preferences: JSON.parse(JSON.stringify({ ...current, portal: mergedPortal })) },
126
- });
127
-
128
- return { portal: mergedPortal };
129
- }),
130
- };
131
-
132
- export type UserPreferencesRouter = typeof userPreferencesRouter;