feat: complete user management workflow

This commit is contained in:
2026-07-02 01:18:52 -07:00
parent e3e7b0d8e0
commit 99bcfb1038
12 changed files with 663 additions and 46 deletions

View File

@@ -1,10 +1,10 @@
import { and, eq } from "drizzle-orm";
import { jsonSuccess } from "@/modules/core/server/api";
import { jsonFailure, jsonSuccess, readJsonBody } from "@/modules/core/server/api";
import { hashPassword, normalizeEmail, requirePermission, toPublicAccount } from "@/modules/core/server/auth";
import { jsonFailure, readJsonBody } from "@/modules/core/server/api";
import { recordAuditLog } from "@/modules/core/server/audit";
import { getDatabase } from "@/modules/core/server/db";
import { hasPermission } from "@/modules/core/server/permissions";
import { accounts, memberships, organizations, roles } from "@/modules/core/server/schema";
import { readData } from "@/modules/core/server/store";
@@ -28,8 +28,21 @@ export async function GET(): Promise<Response> {
}
const data = await readData();
const canReadAllAccounts = hasPermission(auth.context.permissions, "platform:manage");
const organizationId = auth.context.organization?.id;
const scopedAccountIds =
organizationId && !canReadAllAccounts
? new Set(
data.memberships
.filter((membership) => membership.organizationId === organizationId)
.map((membership) => membership.accountId),
)
: null;
return jsonSuccess("账号列表已加载", {
accounts: data.accounts.map((account) => toPublicAccount(account)),
accounts: data.accounts
.filter((account) => !scopedAccountIds || scopedAccountIds.has(account.id))
.map((account) => toPublicAccount(account)),
});
}
@@ -60,7 +73,18 @@ export async function POST(request: Request): Promise<Response> {
const database = getDatabase();
const passwordHash = hashPassword(password);
const created = await database.transaction(async (transaction) => {
try {
const created = await database.transaction(async (transaction) => {
const normalizedEmail = normalizeEmail(email);
const existingAccountRows = await transaction
.select({ id: accounts.id })
.from(accounts)
.where(eq(accounts.email, normalizedEmail))
.limit(1);
if (existingAccountRows.length > 0) {
throw new Error("账号已存在");
}
const organizationRows = await transaction.select().from(organizations).where(eq(organizations.id, organizationId)).limit(1);
const organization = organizationRows[0];
if (!organization) {
@@ -81,7 +105,7 @@ export async function POST(request: Request): Promise<Response> {
.insert(accounts)
.values({
name,
email: normalizeEmail(email),
email: normalizedEmail,
passwordHash: passwordHash.hash,
passwordSalt: passwordHash.salt,
status: "active",
@@ -99,27 +123,31 @@ export async function POST(request: Request): Promise<Response> {
status: "active",
});
return { account, organization, role };
});
return { account, organization, role };
});
await recordAuditLog({
actor: auth.context.account,
organizationId,
action: "account.create",
targetType: "account",
targetId: created.account.id,
result: "success",
reason: `创建机构用户:${created.account.email}`,
});
await recordAuditLog({
actor: auth.context.account,
organizationId,
action: "account.create",
targetType: "account",
targetId: created.account.id,
result: "success",
reason: `创建机构用户:${created.account.email}`,
});
return jsonSuccess("账号已创建", {
account: toPublicAccount(created.account, created.role.key, {
id: created.organization.id,
name: created.organization.name,
slug: created.organization.slug,
status: created.organization.status,
createdAt: created.organization.createdAt.toISOString(),
updatedAt: created.organization.updatedAt.toISOString(),
}),
}, 201);
return jsonSuccess("账号已创建", {
account: toPublicAccount(created.account, created.role.key, {
id: created.organization.id,
name: created.organization.name,
slug: created.organization.slug,
status: created.organization.status,
createdAt: created.organization.createdAt.toISOString(),
updatedAt: created.organization.updatedAt.toISOString(),
}),
}, 201);
} catch (error) {
const reason = error instanceof Error ? error.message : "账号创建失败";
return jsonFailure(reason, reason === "账号已存在" ? 409 : 400);
}
}