@wopr-network/platform-core 1.70.0 → 1.72.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 (32) hide show
  1. package/dist/server/boot-config.d.ts +11 -1
  2. package/dist/server/container.d.ts +2 -0
  3. package/dist/server/container.js +21 -11
  4. package/dist/server/services/__tests__/hot-pool.test.d.ts +6 -0
  5. package/dist/server/services/__tests__/hot-pool.test.js +26 -0
  6. package/dist/server/services/__tests__/in-memory-pool-repository.d.ts +21 -0
  7. package/dist/server/services/__tests__/in-memory-pool-repository.js +57 -0
  8. package/dist/server/services/__tests__/pool-repository.test.d.ts +7 -0
  9. package/dist/server/services/__tests__/pool-repository.test.js +108 -0
  10. package/dist/server/test-container.js +14 -0
  11. package/dist/trpc/index.d.ts +4 -1
  12. package/dist/trpc/index.js +4 -1
  13. package/dist/trpc/init.d.ts +5 -0
  14. package/dist/trpc/init.js +28 -0
  15. package/dist/trpc/routers/page-context.d.ts +37 -0
  16. package/dist/trpc/routers/page-context.js +41 -0
  17. package/dist/trpc/routers/profile.d.ts +71 -0
  18. package/dist/trpc/routers/profile.js +68 -0
  19. package/dist/trpc/routers/settings.d.ts +85 -0
  20. package/dist/trpc/routers/settings.js +73 -0
  21. package/package.json +1 -1
  22. package/src/server/boot-config.ts +12 -1
  23. package/src/server/container.ts +22 -12
  24. package/src/server/services/__tests__/hot-pool.test.ts +30 -0
  25. package/src/server/services/__tests__/in-memory-pool-repository.ts +66 -0
  26. package/src/server/services/__tests__/pool-repository.test.ts +135 -0
  27. package/src/server/test-container.ts +15 -0
  28. package/src/trpc/index.ts +4 -0
  29. package/src/trpc/init.ts +28 -0
  30. package/src/trpc/routers/page-context.ts +57 -0
  31. package/src/trpc/routers/profile.ts +94 -0
  32. package/src/trpc/routers/settings.ts +97 -0
@@ -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
+ });