refactor(api): extract assistant user admin slice
This commit is contained in:
@@ -31,13 +31,14 @@
|
||||
- the adjacent chargeability/computation read helpers now live in their own domain module, keeping the advanced financial transparency read models out of the monolithic router without changing the assistant contract
|
||||
- the neighboring country and metro-city admin mutations now live in their own domain module, keeping more settings-side CRUD wiring out of the monolithic router without changing the assistant contract
|
||||
- the adjacent management-level, utilization, calculation-rule, effort-rule, and experience-multiplier read helpers now live in their own domain module, further shrinking the monolithic assistant router without changing the assistant contract
|
||||
- the remaining assistant user-admin helper cluster now lives in its own domain module, covering admin listing, user lifecycle mutations, permission overrides, resource linking, and MFA overrides without changing the assistant contract
|
||||
- the authenticated user self-service assistant helpers now live in their own domain module, covering assignable users, dashboard preferences, favorites, column preferences, and MFA self-service without changing the assistant contract
|
||||
|
||||
## Next Up
|
||||
|
||||
Pin the next structural cleanup on the API side:
|
||||
continue splitting `packages/api/src/router/assistant-tools.ts` into domain-oriented tool modules without changing the public tool contract.
|
||||
The next clean slice should stay adjacent to the extracted domains and target one cohesive block such as the remaining admin-user helper cluster around `list_users` and user mutations, or the embedded estimate and project admin helper clusters that are still in the monolithic router.
|
||||
The next clean slice should stay adjacent to the extracted domains and target one cohesive block such as the embedded notification/task helpers, or the remaining estimate and project admin helper clusters that are still in the monolithic router.
|
||||
|
||||
## Remaining Major Themes
|
||||
|
||||
|
||||
@@ -99,6 +99,10 @@ import {
|
||||
createUserSelfServiceExecutors,
|
||||
userSelfServiceToolDefinitions,
|
||||
} from "./assistant-tools/user-self-service.js";
|
||||
import {
|
||||
createUserAdminExecutors,
|
||||
userAdminToolDefinitions,
|
||||
} from "./assistant-tools/user-admin.js";
|
||||
import type { ToolContext, ToolDef, ToolExecutor } from "./assistant-tools/shared.js";
|
||||
import { getCommentToolEntityDescription, getCommentToolScopeSentence } from "../lib/comment-entity-registry.js";
|
||||
|
||||
@@ -2785,170 +2789,8 @@ export const TOOL_DEFINITIONS: ToolDef[] = [
|
||||
},
|
||||
...countryMetroAdminToolDefinitions,
|
||||
...configReadmodelToolDefinitions,
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "list_users",
|
||||
description: "List all system users via the admin user router, including role and MFA state. Admin role required.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
limit: { type: "integer", description: "Max results. Default: 50" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
...userAdminToolDefinitions,
|
||||
...userSelfServiceToolDefinitions,
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "create_user",
|
||||
description: "Create a new system user and auto-link a matching resource by email when possible. Admin role required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
email: { type: "string", description: "User email address." },
|
||||
name: { type: "string", description: "Display name." },
|
||||
systemRole: { type: "string", enum: ["ADMIN", "MANAGER", "CONTROLLER", "USER", "VIEWER"], description: "Initial system role." },
|
||||
password: { type: "string", description: "Initial password, minimum 8 characters." },
|
||||
},
|
||||
required: ["email", "name", "password"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "set_user_password",
|
||||
description: "Reset a user's password. Admin role required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: { type: "string", description: "User ID." },
|
||||
password: { type: "string", description: "New password, minimum 8 characters." },
|
||||
},
|
||||
required: ["userId", "password"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "update_user_role",
|
||||
description: "Change a user's system role. Admin role required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string", description: "User ID." },
|
||||
systemRole: { type: "string", enum: ["ADMIN", "MANAGER", "CONTROLLER", "USER", "VIEWER"] },
|
||||
},
|
||||
required: ["id", "systemRole"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "update_user_name",
|
||||
description: "Rename a user. Admin role required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string", description: "User ID." },
|
||||
name: { type: "string", description: "New display name." },
|
||||
},
|
||||
required: ["id", "name"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "link_user_resource",
|
||||
description: "Link or unlink a user to a resource. Admin role required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: { type: "string", description: "User ID." },
|
||||
resourceId: { type: ["string", "null"], description: "Resource ID or null to unlink." },
|
||||
},
|
||||
required: ["userId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "auto_link_users_by_email",
|
||||
description: "Auto-link all users without a resource to matching resources by email. Admin role required. Always confirm first.",
|
||||
parameters: { type: "object", properties: {} },
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "set_user_permissions",
|
||||
description: "Set explicit permission overrides for a user. Admin role required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: { type: "string", description: "User ID." },
|
||||
overrides: {
|
||||
type: ["object", "null"],
|
||||
properties: {
|
||||
granted: { type: "array", items: { type: "string" } },
|
||||
denied: { type: "array", items: { type: "string" } },
|
||||
chapterIds: { type: "array", items: { type: "string" } },
|
||||
},
|
||||
description: "Permission override object or null to clear.",
|
||||
},
|
||||
},
|
||||
required: ["userId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "reset_user_permissions",
|
||||
description: "Reset a user's permission overrides back to role defaults. Admin role required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: { type: "string", description: "User ID." },
|
||||
},
|
||||
required: ["userId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "get_effective_user_permissions",
|
||||
description: "Get a user's resolved permissions, role, and explicit overrides. Admin role required.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: { type: "string", description: "User ID." },
|
||||
},
|
||||
required: ["userId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "disable_user_totp",
|
||||
description: "Disable MFA TOTP for a user as an admin override. Admin role required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: { type: "string", description: "User ID." },
|
||||
},
|
||||
required: ["userId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
@@ -5335,237 +5177,18 @@ const executors = {
|
||||
createExperienceMultiplierCaller,
|
||||
createScopedCallerContext,
|
||||
}),
|
||||
|
||||
async list_users(params: { limit?: number }, ctx: ToolContext) {
|
||||
const caller = createUserCaller(createScopedCallerContext(ctx));
|
||||
const users = await caller.list();
|
||||
return users.slice(0, Math.min(params.limit ?? 50, 100));
|
||||
},
|
||||
...createUserAdminExecutors({
|
||||
createUserCaller,
|
||||
createScopedCallerContext,
|
||||
toAssistantUserMutationError,
|
||||
toAssistantUserResourceLinkError,
|
||||
}),
|
||||
...createUserSelfServiceExecutors({
|
||||
createUserCaller,
|
||||
createScopedCallerContext,
|
||||
toAssistantTotpEnableError,
|
||||
}),
|
||||
|
||||
async create_user(params: {
|
||||
email: string;
|
||||
name: string;
|
||||
systemRole?: SystemRole;
|
||||
password: string;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = createUserCaller(createScopedCallerContext(ctx));
|
||||
let user;
|
||||
try {
|
||||
user = await caller.create({
|
||||
email: params.email,
|
||||
name: params.name,
|
||||
password: params.password,
|
||||
...(params.systemRole !== undefined ? { systemRole: params.systemRole } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = toAssistantUserMutationError(error, "create");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["user", "resource"],
|
||||
success: true,
|
||||
user,
|
||||
userId: user.id,
|
||||
message: `Created user ${user.name}.`,
|
||||
};
|
||||
},
|
||||
|
||||
async set_user_password(params: { userId: string; password: string }, ctx: ToolContext) {
|
||||
const caller = createUserCaller(createScopedCallerContext(ctx));
|
||||
let result;
|
||||
try {
|
||||
result = await caller.setPassword(params);
|
||||
} catch (error) {
|
||||
const mapped = toAssistantUserMutationError(error, "password");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["user"],
|
||||
...result,
|
||||
message: `Reset password for user ${params.userId}.`,
|
||||
};
|
||||
},
|
||||
|
||||
async update_user_role(params: { id: string; systemRole: SystemRole }, ctx: ToolContext) {
|
||||
const caller = createUserCaller(createScopedCallerContext(ctx));
|
||||
let user;
|
||||
try {
|
||||
user = await caller.updateRole(params);
|
||||
} catch (error) {
|
||||
const mapped = toAssistantUserMutationError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["user"],
|
||||
success: true,
|
||||
user,
|
||||
userId: user.id,
|
||||
message: `Updated role for ${user.name} to ${user.systemRole}.`,
|
||||
};
|
||||
},
|
||||
|
||||
async update_user_name(params: { id: string; name: string }, ctx: ToolContext) {
|
||||
const caller = createUserCaller(createScopedCallerContext(ctx));
|
||||
let user;
|
||||
try {
|
||||
user = await caller.updateName(params);
|
||||
} catch (error) {
|
||||
const mapped = toAssistantUserMutationError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["user"],
|
||||
success: true,
|
||||
user,
|
||||
userId: user.id,
|
||||
message: `Updated user name to ${user.name}.`,
|
||||
};
|
||||
},
|
||||
|
||||
async link_user_resource(params: { userId: string; resourceId?: string | null }, ctx: ToolContext) {
|
||||
const caller = createUserCaller(createScopedCallerContext(ctx));
|
||||
let result;
|
||||
try {
|
||||
result = await caller.linkResource({
|
||||
userId: params.userId,
|
||||
resourceId: params.resourceId ?? null,
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = toAssistantUserResourceLinkError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["user", "resource"],
|
||||
...result,
|
||||
message: params.resourceId ? "Linked user to resource." : "Unlinked user resource.",
|
||||
};
|
||||
},
|
||||
|
||||
async auto_link_users_by_email(_params: Record<string, never>, ctx: ToolContext) {
|
||||
const caller = createUserCaller(createScopedCallerContext(ctx));
|
||||
const result = await caller.autoLinkAllByEmail();
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["user", "resource"],
|
||||
success: true,
|
||||
...result,
|
||||
message: `Auto-linked ${result.linked} user(s) by email.`,
|
||||
};
|
||||
},
|
||||
|
||||
async set_user_permissions(params: {
|
||||
userId: string;
|
||||
overrides?: {
|
||||
granted?: string[];
|
||||
denied?: string[];
|
||||
chapterIds?: string[];
|
||||
} | null;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = createUserCaller(createScopedCallerContext(ctx));
|
||||
let user;
|
||||
try {
|
||||
user = await caller.setPermissions({
|
||||
userId: params.userId,
|
||||
overrides: params.overrides ?? null,
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = toAssistantUserMutationError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["user"],
|
||||
success: true,
|
||||
user,
|
||||
userId: user.id,
|
||||
message: params.overrides ? "Updated user permission overrides." : "Cleared user permission overrides.",
|
||||
};
|
||||
},
|
||||
|
||||
async reset_user_permissions(params: { userId: string }, ctx: ToolContext) {
|
||||
const caller = createUserCaller(createScopedCallerContext(ctx));
|
||||
let user;
|
||||
try {
|
||||
user = await caller.resetPermissions(params);
|
||||
} catch (error) {
|
||||
const mapped = toAssistantUserMutationError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["user"],
|
||||
success: true,
|
||||
user,
|
||||
userId: user.id,
|
||||
message: "Reset user permissions to role defaults.",
|
||||
};
|
||||
},
|
||||
|
||||
async get_effective_user_permissions(params: { userId: string }, ctx: ToolContext) {
|
||||
const caller = createUserCaller(createScopedCallerContext(ctx));
|
||||
try {
|
||||
return await caller.getEffectivePermissions(params);
|
||||
} catch (error) {
|
||||
const mapped = toAssistantUserMutationError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async disable_user_totp(params: { userId: string }, ctx: ToolContext) {
|
||||
const caller = createUserCaller(createScopedCallerContext(ctx));
|
||||
let result;
|
||||
try {
|
||||
result = await caller.disableTotp(params);
|
||||
} catch (error) {
|
||||
const mapped = toAssistantUserMutationError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["user"],
|
||||
success: true,
|
||||
...result,
|
||||
message: `Disabled TOTP for user ${params.userId}.`,
|
||||
};
|
||||
},
|
||||
|
||||
async list_notifications(params: { unreadOnly?: boolean; limit?: number }, ctx: ToolContext) {
|
||||
const caller = createNotificationCaller(createScopedCallerContext(ctx));
|
||||
return caller.list({
|
||||
|
||||
@@ -0,0 +1,446 @@
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import type { TRPCContext } from "../../trpc.js";
|
||||
import type { ToolContext, ToolDef, ToolExecutor } from "./shared.js";
|
||||
|
||||
type AssistantToolErrorResult = { error: string };
|
||||
|
||||
type UserRecord = {
|
||||
id: string;
|
||||
name: string | null;
|
||||
systemRole?: SystemRole | string;
|
||||
};
|
||||
|
||||
type UserAdminDeps = {
|
||||
createUserCaller: (ctx: TRPCContext) => {
|
||||
list: () => Promise<unknown[]>;
|
||||
create: (params: {
|
||||
email: string;
|
||||
name: string;
|
||||
systemRole?: SystemRole;
|
||||
password: string;
|
||||
}) => Promise<UserRecord>;
|
||||
setPassword: (params: { userId: string; password: string }) => Promise<Record<string, unknown>>;
|
||||
updateRole: (params: { id: string; systemRole: SystemRole }) => Promise<UserRecord>;
|
||||
updateName: (params: { id: string; name: string }) => Promise<UserRecord>;
|
||||
linkResource: (
|
||||
params: { userId: string; resourceId: string | null },
|
||||
) => Promise<Record<string, unknown>>;
|
||||
autoLinkAllByEmail: () => Promise<Record<string, unknown> & { linked: number }>;
|
||||
setPermissions: (params: {
|
||||
userId: string;
|
||||
overrides: {
|
||||
granted?: string[];
|
||||
denied?: string[];
|
||||
chapterIds?: string[];
|
||||
} | null;
|
||||
}) => Promise<UserRecord>;
|
||||
resetPermissions: (params: { userId: string }) => Promise<UserRecord>;
|
||||
getEffectivePermissions: (params: { userId: string }) => Promise<unknown>;
|
||||
disableTotp: (params: { userId: string }) => Promise<Record<string, unknown>>;
|
||||
};
|
||||
createScopedCallerContext: (ctx: ToolContext) => TRPCContext;
|
||||
toAssistantUserMutationError: (
|
||||
error: unknown,
|
||||
mode?: "create" | "password",
|
||||
) => AssistantToolErrorResult | null;
|
||||
toAssistantUserResourceLinkError: (
|
||||
error: unknown,
|
||||
) => AssistantToolErrorResult | null;
|
||||
};
|
||||
|
||||
export const userAdminToolDefinitions: ToolDef[] = [
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "list_users",
|
||||
description: "List all system users via the admin user router, including role and MFA state. Admin role required.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
limit: { type: "integer", description: "Max results. Default: 50" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "create_user",
|
||||
description: "Create a new system user and auto-link a matching resource by email when possible. Admin role required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
email: { type: "string", description: "User email address." },
|
||||
name: { type: "string", description: "Display name." },
|
||||
systemRole: { type: "string", enum: ["ADMIN", "MANAGER", "CONTROLLER", "USER", "VIEWER"], description: "Initial system role." },
|
||||
password: { type: "string", description: "Initial password, minimum 8 characters." },
|
||||
},
|
||||
required: ["email", "name", "password"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "set_user_password",
|
||||
description: "Reset a user's password. Admin role required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: { type: "string", description: "User ID." },
|
||||
password: { type: "string", description: "New password, minimum 8 characters." },
|
||||
},
|
||||
required: ["userId", "password"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "update_user_role",
|
||||
description: "Change a user's system role. Admin role required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string", description: "User ID." },
|
||||
systemRole: { type: "string", enum: ["ADMIN", "MANAGER", "CONTROLLER", "USER", "VIEWER"] },
|
||||
},
|
||||
required: ["id", "systemRole"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "update_user_name",
|
||||
description: "Rename a user. Admin role required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string", description: "User ID." },
|
||||
name: { type: "string", description: "New display name." },
|
||||
},
|
||||
required: ["id", "name"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "link_user_resource",
|
||||
description: "Link or unlink a user to a resource. Admin role required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: { type: "string", description: "User ID." },
|
||||
resourceId: { type: ["string", "null"], description: "Resource ID or null to unlink." },
|
||||
},
|
||||
required: ["userId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "auto_link_users_by_email",
|
||||
description: "Auto-link all users without a resource to matching resources by email. Admin role required. Always confirm first.",
|
||||
parameters: { type: "object", properties: {} },
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "set_user_permissions",
|
||||
description: "Set explicit permission overrides for a user. Admin role required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: { type: "string", description: "User ID." },
|
||||
overrides: {
|
||||
type: ["object", "null"],
|
||||
properties: {
|
||||
granted: { type: "array", items: { type: "string" } },
|
||||
denied: { type: "array", items: { type: "string" } },
|
||||
chapterIds: { type: "array", items: { type: "string" } },
|
||||
},
|
||||
description: "Permission override object or null to clear.",
|
||||
},
|
||||
},
|
||||
required: ["userId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "reset_user_permissions",
|
||||
description: "Reset a user's permission overrides back to role defaults. Admin role required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: { type: "string", description: "User ID." },
|
||||
},
|
||||
required: ["userId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "get_effective_user_permissions",
|
||||
description: "Get a user's resolved permissions, role, and explicit overrides. Admin role required.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: { type: "string", description: "User ID." },
|
||||
},
|
||||
required: ["userId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "disable_user_totp",
|
||||
description: "Disable MFA TOTP for a user as an admin override. Admin role required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: { type: "string", description: "User ID." },
|
||||
},
|
||||
required: ["userId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export function createUserAdminExecutors(
|
||||
deps: UserAdminDeps,
|
||||
): Record<string, ToolExecutor> {
|
||||
return {
|
||||
async list_users(params: { limit?: number }, ctx: ToolContext) {
|
||||
const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx));
|
||||
const users = await caller.list();
|
||||
return users.slice(0, Math.min(params.limit ?? 50, 100));
|
||||
},
|
||||
|
||||
async create_user(params: {
|
||||
email: string;
|
||||
name: string;
|
||||
systemRole?: SystemRole;
|
||||
password: string;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx));
|
||||
let user;
|
||||
try {
|
||||
user = await caller.create({
|
||||
email: params.email,
|
||||
name: params.name,
|
||||
password: params.password,
|
||||
...(params.systemRole !== undefined ? { systemRole: params.systemRole } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantUserMutationError(error, "create");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate" as const,
|
||||
scope: ["user", "resource"],
|
||||
success: true,
|
||||
user,
|
||||
userId: user.id,
|
||||
message: `Created user ${user.name}.`,
|
||||
};
|
||||
},
|
||||
|
||||
async set_user_password(params: { userId: string; password: string }, ctx: ToolContext) {
|
||||
const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx));
|
||||
let result;
|
||||
try {
|
||||
result = await caller.setPassword(params);
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantUserMutationError(error, "password");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate" as const,
|
||||
scope: ["user"],
|
||||
...result,
|
||||
message: `Reset password for user ${params.userId}.`,
|
||||
};
|
||||
},
|
||||
|
||||
async update_user_role(params: { id: string; systemRole: SystemRole }, ctx: ToolContext) {
|
||||
const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx));
|
||||
let user;
|
||||
try {
|
||||
user = await caller.updateRole(params);
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantUserMutationError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate" as const,
|
||||
scope: ["user"],
|
||||
success: true,
|
||||
user,
|
||||
userId: user.id,
|
||||
message: `Updated role for ${user.name} to ${user.systemRole}.`,
|
||||
};
|
||||
},
|
||||
|
||||
async update_user_name(params: { id: string; name: string }, ctx: ToolContext) {
|
||||
const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx));
|
||||
let user;
|
||||
try {
|
||||
user = await caller.updateName(params);
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantUserMutationError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate" as const,
|
||||
scope: ["user"],
|
||||
success: true,
|
||||
user,
|
||||
userId: user.id,
|
||||
message: `Updated user name to ${user.name}.`,
|
||||
};
|
||||
},
|
||||
|
||||
async link_user_resource(params: { userId: string; resourceId?: string | null }, ctx: ToolContext) {
|
||||
const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx));
|
||||
let result;
|
||||
try {
|
||||
result = await caller.linkResource({
|
||||
userId: params.userId,
|
||||
resourceId: params.resourceId ?? null,
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantUserResourceLinkError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate" as const,
|
||||
scope: ["user", "resource"],
|
||||
...result,
|
||||
message: params.resourceId ? "Linked user to resource." : "Unlinked user resource.",
|
||||
};
|
||||
},
|
||||
|
||||
async auto_link_users_by_email(_params: Record<string, never>, ctx: ToolContext) {
|
||||
const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx));
|
||||
const result = await caller.autoLinkAllByEmail();
|
||||
return {
|
||||
__action: "invalidate" as const,
|
||||
scope: ["user", "resource"],
|
||||
success: true,
|
||||
...result,
|
||||
message: `Auto-linked ${result.linked} user(s) by email.`,
|
||||
};
|
||||
},
|
||||
|
||||
async set_user_permissions(params: {
|
||||
userId: string;
|
||||
overrides?: {
|
||||
granted?: string[];
|
||||
denied?: string[];
|
||||
chapterIds?: string[];
|
||||
} | null;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx));
|
||||
let user;
|
||||
try {
|
||||
user = await caller.setPermissions({
|
||||
userId: params.userId,
|
||||
overrides: params.overrides ?? null,
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantUserMutationError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate" as const,
|
||||
scope: ["user"],
|
||||
success: true,
|
||||
user,
|
||||
userId: user.id,
|
||||
message: params.overrides ? "Updated user permission overrides." : "Cleared user permission overrides.",
|
||||
};
|
||||
},
|
||||
|
||||
async reset_user_permissions(params: { userId: string }, ctx: ToolContext) {
|
||||
const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx));
|
||||
let user;
|
||||
try {
|
||||
user = await caller.resetPermissions(params);
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantUserMutationError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate" as const,
|
||||
scope: ["user"],
|
||||
success: true,
|
||||
user,
|
||||
userId: user.id,
|
||||
message: "Reset user permissions to role defaults.",
|
||||
};
|
||||
},
|
||||
|
||||
async get_effective_user_permissions(params: { userId: string }, ctx: ToolContext) {
|
||||
const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx));
|
||||
try {
|
||||
return await caller.getEffectivePermissions(params);
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantUserMutationError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async disable_user_totp(params: { userId: string }, ctx: ToolContext) {
|
||||
const caller = deps.createUserCaller(deps.createScopedCallerContext(ctx));
|
||||
let result;
|
||||
try {
|
||||
result = await caller.disableTotp(params);
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantUserMutationError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate" as const,
|
||||
scope: ["user"],
|
||||
success: true,
|
||||
...result,
|
||||
message: `Disabled TOTP for user ${params.userId}.`,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user