import { and, eq } from "drizzle-orm"; import { jsonFailure, jsonSuccess, readJsonBody } from "@/modules/core/server/api"; import { recordAuditLog } from "@/modules/core/server/audit"; import { requirePermission, toPublicAccount } from "@/modules/core/server/auth"; import { getDatabase } from "@/modules/core/server/db"; import { hasPermission } from "@/modules/core/server/permissions"; import { accounts, memberships, organizations, roles, sessions } from "@/modules/core/server/schema"; import type { AccountStatus, Organization } from "@/modules/core/types"; type RouteContext = { params: Promise<{ id: string; }>; }; const ACCOUNT_STATUSES: AccountStatus[] = ["active", "disabled", "pending"]; function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } function readString(source: Record, key: string): string { const value = source[key]; return typeof value === "string" ? value.trim() : ""; } function readStatus(value: unknown): AccountStatus | null { if (typeof value !== "string") { return null; } return ACCOUNT_STATUSES.find((status) => status === value) ?? null; } function toOrganization(row: typeof organizations.$inferSelect): Organization { return { id: row.id, name: row.name, slug: row.slug, status: row.status, registrationEnabled: row.registrationEnabled, oidcEnabled: row.oidcEnabled, oidcIssuerUrl: row.oidcIssuerUrl, oidcClientId: row.oidcClientId, oidcHasClientSecret: row.oidcClientSecret.length > 0, oidcScopes: row.oidcScopes, oidcRedirectUri: row.oidcRedirectUri, oidcAutoProvision: row.oidcAutoProvision, createdAt: row.createdAt.toISOString(), updatedAt: row.updatedAt.toISOString(), }; } export async function PATCH(request: Request, context: RouteContext): Promise { const { id } = await context.params; const auth = await requirePermission("account:manage", { action: "account.update", targetType: "account", targetId: id, }); if (!auth.success) { return auth.response; } const body = await readJsonBody(request); if (!isRecord(body)) { return jsonFailure("请求数据格式无效"); } const name = readString(body, "name"); const status = "status" in body ? readStatus(body.status) : null; const roleId = readString(body, "roleId"); const requestedOrganizationId = readString(body, "organizationId"); const organizationId = requestedOrganizationId || auth.context.organization?.id; if ("status" in body && !status) { return jsonFailure("账号状态无效"); } if (!name && !status && !roleId) { return jsonFailure("请至少填写一个要更新的字段"); } if (id === auth.context.account.id && status === "disabled") { return jsonFailure("不能停用当前登录账号", 400); } const canManagePlatform = hasPermission(auth.context.permissions, "platform:manage"); if (!canManagePlatform && requestedOrganizationId && requestedOrganizationId !== auth.context.organization?.id) { return jsonFailure("权限不足", 403); } const database = getDatabase(); const targetAccountRows = await database.select().from(accounts).where(eq(accounts.id, id)).limit(1); const targetAccount = targetAccountRows[0]; if (!targetAccount) { return jsonFailure("账号不存在", 404); } if (roleId && !organizationId) { return jsonFailure("请选择机构后分配角色", 400); } const organizationRows = organizationId ? await database.select().from(organizations).where(eq(organizations.id, organizationId)).limit(1) : []; const organization = organizationRows[0]; if (roleId && !organization) { return jsonFailure("机构不存在", 404); } try { await database.transaction(async (transaction) => { if (name || status) { const updateValues: { name?: string; status?: AccountStatus; updatedAt: Date; } = { updatedAt: new Date(), }; if (name) { updateValues.name = name; } if (status) { updateValues.status = status; } await transaction.update(accounts).set(updateValues).where(eq(accounts.id, id)); if (status === "disabled") { await transaction.delete(sessions).where(eq(sessions.accountId, id)); } } if (roleId && organizationId) { const roleRows = await transaction .select() .from(roles) .where(and(eq(roles.id, roleId), eq(roles.organizationId, organizationId), eq(roles.isEnabled, true))) .limit(1); const role = roleRows[0]; if (!role) { throw new Error("角色不存在"); } const membershipRows = await transaction .select() .from(memberships) .where(and(eq(memberships.accountId, id), eq(memberships.organizationId, organizationId))) .limit(1); const membership = membershipRows[0]; if (membership) { await transaction .update(memberships) .set({ roleId, status: "active", updatedAt: new Date() }) .where(eq(memberships.id, membership.id)); } else { await transaction.insert(memberships).values({ accountId: id, organizationId, roleId, status: "active", }); } } }); } catch (error) { const reason = error instanceof Error ? error.message : "账号更新失败"; return jsonFailure(reason, reason === "角色不存在" ? 404 : 400); } const updatedAccountRows = await database.select().from(accounts).where(eq(accounts.id, id)).limit(1); const updatedAccount = updatedAccountRows[0]; if (!updatedAccount) { return jsonFailure("账号不存在", 404); } const membershipRows = organizationId ? await database .select({ membership: memberships, role: roles }) .from(memberships) .innerJoin(roles, eq(roles.id, memberships.roleId)) .where(and(eq(memberships.accountId, id), eq(memberships.organizationId, organizationId))) .limit(1) : []; const membership = membershipRows[0]; const publicAccount = toPublicAccount( updatedAccount, membership?.role.key, organization ? toOrganization(organization) : undefined, ); await recordAuditLog({ actor: auth.context.account, organizationId, action: "account.update", targetType: "account", targetId: updatedAccount.id, result: "success", reason: `更新账号:${updatedAccount.email}`, }); return jsonSuccess("账号已更新", { account: publicAccount }); } export async function DELETE(_request: Request, context: RouteContext): Promise { const { id } = await context.params; const auth = await requirePermission("account:manage", { action: "account.disable", targetType: "account", targetId: id, }); if (!auth.success) { return auth.response; } if (id === auth.context.account.id) { return jsonFailure("不能停用当前登录账号", 400); } const database = getDatabase(); const rows = await database .update(accounts) .set({ status: "disabled", updatedAt: new Date() }) .where(eq(accounts.id, id)) .returning(); const account = rows[0]; if (!account) { return jsonFailure("账号不存在", 404); } await database.delete(sessions).where(eq(sessions.accountId, id)); await recordAuditLog({ actor: auth.context.account, organizationId: auth.context.organization?.id, action: "account.disable", targetType: "account", targetId: account.id, result: "success", reason: `停用账号:${account.email}`, }); return jsonSuccess("账号已停用", { account: toPublicAccount(account) }); }