@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.
- package/dist/server/boot-config.d.ts +11 -1
- package/dist/server/container.d.ts +2 -0
- package/dist/server/container.js +21 -11
- package/dist/server/services/__tests__/hot-pool.test.d.ts +6 -0
- package/dist/server/services/__tests__/hot-pool.test.js +26 -0
- package/dist/server/services/__tests__/in-memory-pool-repository.d.ts +21 -0
- package/dist/server/services/__tests__/in-memory-pool-repository.js +57 -0
- package/dist/server/services/__tests__/pool-repository.test.d.ts +7 -0
- package/dist/server/services/__tests__/pool-repository.test.js +108 -0
- package/dist/server/test-container.js +14 -0
- package/dist/trpc/index.d.ts +4 -1
- package/dist/trpc/index.js +4 -1
- package/dist/trpc/init.d.ts +5 -0
- package/dist/trpc/init.js +28 -0
- package/dist/trpc/routers/page-context.d.ts +37 -0
- package/dist/trpc/routers/page-context.js +41 -0
- package/dist/trpc/routers/profile.d.ts +71 -0
- package/dist/trpc/routers/profile.js +68 -0
- package/dist/trpc/routers/settings.d.ts +85 -0
- package/dist/trpc/routers/settings.js +73 -0
- package/package.json +1 -1
- package/src/server/boot-config.ts +12 -1
- package/src/server/container.ts +22 -12
- package/src/server/services/__tests__/hot-pool.test.ts +30 -0
- package/src/server/services/__tests__/in-memory-pool-repository.ts +66 -0
- package/src/server/services/__tests__/pool-repository.test.ts +135 -0
- package/src/server/test-container.ts +15 -0
- package/src/trpc/index.ts +4 -0
- package/src/trpc/init.ts +28 -0
- package/src/trpc/routers/page-context.ts +57 -0
- package/src/trpc/routers/profile.ts +94 -0
- 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
|
+
});
|