@wopr-network/platform-core 1.69.0 → 1.71.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.
Files changed (44) hide show
  1. package/dist/db/schema/pool-config.d.ts +41 -0
  2. package/dist/db/schema/pool-config.js +5 -0
  3. package/dist/db/schema/pool-instances.d.ts +126 -0
  4. package/dist/db/schema/pool-instances.js +10 -0
  5. package/dist/server/__tests__/container.test.js +4 -1
  6. package/dist/server/container.d.ts +20 -2
  7. package/dist/server/container.js +20 -5
  8. package/dist/server/lifecycle.js +10 -0
  9. package/dist/server/routes/admin.d.ts +18 -0
  10. package/dist/server/routes/admin.js +21 -0
  11. package/dist/server/services/hot-pool-claim.d.ts +30 -0
  12. package/dist/server/services/hot-pool-claim.js +92 -0
  13. package/dist/server/services/hot-pool.d.ts +25 -0
  14. package/dist/server/services/hot-pool.js +129 -0
  15. package/dist/server/services/pool-repository.d.ts +44 -0
  16. package/dist/server/services/pool-repository.js +72 -0
  17. package/dist/server/test-container.js +14 -0
  18. package/dist/trpc/index.d.ts +4 -1
  19. package/dist/trpc/index.js +4 -1
  20. package/dist/trpc/init.d.ts +5 -0
  21. package/dist/trpc/init.js +28 -0
  22. package/dist/trpc/routers/page-context.d.ts +37 -0
  23. package/dist/trpc/routers/page-context.js +41 -0
  24. package/dist/trpc/routers/profile.d.ts +71 -0
  25. package/dist/trpc/routers/profile.js +68 -0
  26. package/dist/trpc/routers/settings.d.ts +85 -0
  27. package/dist/trpc/routers/settings.js +73 -0
  28. package/drizzle/migrations/0025_hot_pool_tables.sql +29 -0
  29. package/package.json +1 -1
  30. package/src/db/schema/pool-config.ts +6 -0
  31. package/src/db/schema/pool-instances.ts +11 -0
  32. package/src/server/__tests__/container.test.ts +4 -1
  33. package/src/server/container.ts +38 -8
  34. package/src/server/lifecycle.ts +10 -0
  35. package/src/server/routes/admin.ts +26 -0
  36. package/src/server/services/hot-pool-claim.ts +130 -0
  37. package/src/server/services/hot-pool.ts +174 -0
  38. package/src/server/services/pool-repository.ts +107 -0
  39. package/src/server/test-container.ts +15 -0
  40. package/src/trpc/index.ts +4 -0
  41. package/src/trpc/init.ts +28 -0
  42. package/src/trpc/routers/page-context.ts +57 -0
  43. package/src/trpc/routers/profile.ts +94 -0
  44. package/src/trpc/routers/settings.ts +97 -0
@@ -0,0 +1,57 @@
1
+ /**
2
+ * tRPC page-context router — stores and retrieves per-user page context.
3
+ *
4
+ * Pure platform-core — no product-specific imports.
5
+ */
6
+
7
+ import { TRPCError } from "@trpc/server";
8
+ import { z } from "zod";
9
+ import type { IPageContextRepository } from "../../fleet/page-context-repository.js";
10
+ import { protectedProcedure, router } from "../init.js";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Deps
14
+ // ---------------------------------------------------------------------------
15
+
16
+ export interface PageContextRouterDeps {
17
+ repo: IPageContextRepository;
18
+ }
19
+
20
+ let _deps: PageContextRouterDeps | null = null;
21
+
22
+ export function setPageContextRouterDeps(deps: PageContextRouterDeps): void {
23
+ _deps = deps;
24
+ }
25
+
26
+ function deps(): PageContextRouterDeps {
27
+ if (!_deps) throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Page context not initialized" });
28
+ return _deps;
29
+ }
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Schema
33
+ // ---------------------------------------------------------------------------
34
+
35
+ const updatePageContextSchema = z.object({
36
+ currentPage: z.string().min(1).max(500),
37
+ pagePrompt: z.string().max(2000).nullable(),
38
+ });
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Router
42
+ // ---------------------------------------------------------------------------
43
+
44
+ export const pageContextRouter = router({
45
+ /** Update the page context for the current user. Called on route change. */
46
+ update: protectedProcedure.input(updatePageContextSchema).mutation(async ({ ctx, input }) => {
47
+ await deps().repo.set(ctx.user.id, input.currentPage, input.pagePrompt);
48
+ return { ok: true as const };
49
+ }),
50
+
51
+ /** Get the current page context for the authenticated user. */
52
+ current: protectedProcedure.query(async ({ ctx }) => {
53
+ const pc = await deps().repo.get(ctx.user.id);
54
+ if (!pc) return null;
55
+ return { currentPage: pc.currentPage, pagePrompt: pc.pagePrompt };
56
+ }),
57
+ });
@@ -0,0 +1,94 @@
1
+ /**
2
+ * tRPC profile router — get/update user profile and change password.
3
+ *
4
+ * Pure platform-core — no product-specific imports.
5
+ */
6
+
7
+ import { TRPCError } from "@trpc/server";
8
+ import { z } from "zod";
9
+ import { protectedProcedure, router } from "../init.js";
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Deps
13
+ // ---------------------------------------------------------------------------
14
+
15
+ export interface ProfileRouterDeps {
16
+ getUser: (
17
+ userId: string,
18
+ ) => Promise<{ id: string; name: string; email: string; image: string | null; twoFactorEnabled: boolean } | null>;
19
+ updateUser: (
20
+ userId: string,
21
+ data: { name?: string; image?: string | null },
22
+ ) => Promise<{ id: string; name: string; email: string; image: string | null; twoFactorEnabled: boolean }>;
23
+ changePassword: (userId: string, currentPassword: string, newPassword: string) => Promise<boolean>;
24
+ }
25
+
26
+ let _deps: ProfileRouterDeps | null = null;
27
+
28
+ export function setProfileRouterDeps(deps: ProfileRouterDeps): void {
29
+ _deps = deps;
30
+ }
31
+
32
+ function deps(): ProfileRouterDeps {
33
+ if (!_deps) throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Profile router not initialized" });
34
+ return _deps;
35
+ }
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Router
39
+ // ---------------------------------------------------------------------------
40
+
41
+ export const profileRouter = router({
42
+ /** Get the authenticated user's profile. */
43
+ getProfile: protectedProcedure.query(async ({ ctx }) => {
44
+ const user = await deps().getUser(ctx.user.id);
45
+ if (!user) {
46
+ throw new TRPCError({ code: "NOT_FOUND", message: "User not found" });
47
+ }
48
+ return {
49
+ id: user.id,
50
+ name: user.name,
51
+ email: user.email,
52
+ image: user.image,
53
+ twoFactorEnabled: user.twoFactorEnabled,
54
+ };
55
+ }),
56
+
57
+ /** Update the authenticated user's display name and/or avatar. */
58
+ updateProfile: protectedProcedure
59
+ .input(
60
+ z.object({
61
+ name: z.string().min(1).max(128).optional(),
62
+ image: z.string().url().max(2048).nullable().optional(),
63
+ }),
64
+ )
65
+ .mutation(async ({ input, ctx }) => {
66
+ const updated = await deps().updateUser(ctx.user.id, {
67
+ ...(input.name !== undefined && { name: input.name }),
68
+ ...(input.image !== undefined && { image: input.image }),
69
+ });
70
+ return {
71
+ id: updated.id,
72
+ name: updated.name,
73
+ email: updated.email,
74
+ image: updated.image,
75
+ twoFactorEnabled: updated.twoFactorEnabled,
76
+ };
77
+ }),
78
+
79
+ /** Change the authenticated user's password. */
80
+ changePassword: protectedProcedure
81
+ .input(
82
+ z.object({
83
+ currentPassword: z.string().min(1),
84
+ newPassword: z.string().min(8, "Password must be at least 8 characters"),
85
+ }),
86
+ )
87
+ .mutation(async ({ input, ctx }) => {
88
+ const ok = await deps().changePassword(ctx.user.id, input.currentPassword, input.newPassword);
89
+ if (!ok) {
90
+ throw new TRPCError({ code: "BAD_REQUEST", message: "Current password is incorrect" });
91
+ }
92
+ return { ok: true as const };
93
+ }),
94
+ });
@@ -0,0 +1,97 @@
1
+ /**
2
+ * tRPC settings router — tenant config, preferences, health.
3
+ *
4
+ * Pure platform-core — service name parameterized via deps.
5
+ */
6
+
7
+ import { TRPCError } from "@trpc/server";
8
+ import { z } from "zod";
9
+ import type { INotificationPreferencesRepository } from "../../email/index.js";
10
+ import { publicProcedure, router, tenantProcedure } from "../init.js";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Deps
14
+ // ---------------------------------------------------------------------------
15
+
16
+ export interface SettingsRouterDeps {
17
+ /** Service name returned by health endpoint (e.g. "paperclip-platform"). */
18
+ serviceName: string;
19
+ getNotificationPrefsStore: () => INotificationPreferencesRepository;
20
+ testProvider?: (provider: string, tenantId: string) => Promise<{ ok: boolean; latencyMs?: number; error?: string }>;
21
+ }
22
+
23
+ let _deps: SettingsRouterDeps | null = null;
24
+
25
+ export function setSettingsRouterDeps(deps: SettingsRouterDeps): void {
26
+ _deps = deps;
27
+ }
28
+
29
+ function deps(): SettingsRouterDeps {
30
+ if (!_deps) throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Settings not initialized" });
31
+ return _deps;
32
+ }
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Router
36
+ // ---------------------------------------------------------------------------
37
+
38
+ export const settingsRouter = router({
39
+ /** Health check — publicly accessible. */
40
+ health: publicProcedure.query(() => {
41
+ return { status: "ok" as const, service: deps().serviceName };
42
+ }),
43
+
44
+ /** Get tenant configuration summary. */
45
+ tenantConfig: tenantProcedure.query(({ ctx }) => {
46
+ return {
47
+ tenantId: ctx.tenantId,
48
+ configured: true,
49
+ };
50
+ }),
51
+
52
+ /** Ping — verify auth and tenant context. */
53
+ ping: tenantProcedure.query(({ ctx }) => {
54
+ return {
55
+ ok: true as const,
56
+ tenantId: ctx.tenantId,
57
+ userId: ctx.user.id,
58
+ timestamp: Date.now(),
59
+ };
60
+ }),
61
+
62
+ /** Get notification preferences for the authenticated tenant. */
63
+ notificationPreferences: tenantProcedure.query(({ ctx }) => {
64
+ const store = deps().getNotificationPrefsStore();
65
+ return store.get(ctx.tenantId);
66
+ }),
67
+
68
+ /** Test connectivity to a provider. */
69
+ testProvider: tenantProcedure
70
+ .input(z.object({ provider: z.string().min(1).max(64) }))
71
+ .mutation(async ({ input, ctx }) => {
72
+ const testFn = deps().testProvider;
73
+ if (!testFn) {
74
+ return { ok: false as const, error: "Provider testing not configured" };
75
+ }
76
+ return testFn(input.provider, ctx.tenantId);
77
+ }),
78
+
79
+ /** Update notification preferences for the authenticated tenant. */
80
+ updateNotificationPreferences: tenantProcedure
81
+ .input(
82
+ z.object({
83
+ billing_low_balance: z.boolean().optional(),
84
+ billing_receipts: z.boolean().optional(),
85
+ billing_auto_topup: z.boolean().optional(),
86
+ agent_channel_disconnect: z.boolean().optional(),
87
+ agent_status_changes: z.boolean().optional(),
88
+ account_role_changes: z.boolean().optional(),
89
+ account_team_invites: z.boolean().optional(),
90
+ }),
91
+ )
92
+ .mutation(async ({ input, ctx }) => {
93
+ const store = deps().getNotificationPrefsStore();
94
+ await store.update(ctx.tenantId, input);
95
+ return store.get(ctx.tenantId);
96
+ }),
97
+ });