gitdataai/admin/src/app/api/platform/users/route.ts
ZhenYi 3773fdc780 feat(admin): add structured error logger for all API routes
Replace bare console.error() calls with logError() utility across all
47 API route handlers. logError() prints timestamp + context + message
+ stack trace + extra request data to stderr, and redacts sensitive
fields (password, token, secret, key, etc.) from logged objects.
2026-04-23 09:55:35 +08:00

118 lines
4.0 KiB
TypeScript

import { logError } from "@/lib/logger";
import { NextRequest, NextResponse } from "next/server";
import { query } from "@/lib/db";
import { createAuditLog } from "@/lib/log";
export const runtime = "nodejs";
export async function GET(req: NextRequest) {
try {
const { searchParams } = req.nextUrl;
const page = Math.max(1, parseInt(searchParams.get("page") || "1", 10));
const pageSize = Math.max(1, parseInt(searchParams.get("pageSize") || "20", 10));
const search = searchParams.get("search") || "";
const offset = (page - 1) * pageSize;
let whereClause = "";
const params: unknown[] = [];
if (search) {
whereClause = 'WHERE username ILIKE $1 OR display_name ILIKE $1';
params.push(`%${search}%`);
}
const countResult = await query<{ count: string }>(
`SELECT COUNT(*) FROM "user" ${whereClause}`,
params
);
const total = parseInt(countResult.rows[0].count, 10);
const result = await query(
`SELECT u.uid, u.username, u.display_name, u.avatar_url, u.organization,
u.last_sign_in_at::text as last_sign_in_at, u.created_at::text as created_at,
COALESCE(up.is_active, true) as is_active,
ue.email
FROM "user" u
LEFT JOIN user_password up ON up.user = u.uid
LEFT JOIN user_email ue ON ue.user = u.uid
${whereClause}
ORDER BY u.created_at DESC
LIMIT $1 OFFSET $2`,
params.length > 0
? [...params, pageSize, offset]
: [pageSize, offset]
);
const users = result.rows.map((r: Record<string, unknown>) => ({
uid: r.uid,
username: r.username,
display_name: r.display_name,
avatar_url: r.avatar_url,
organization: r.organization,
last_sign_in_at: r.last_sign_in_at,
created_at: r.created_at,
is_active: r.is_active,
email: r.email,
}));
return NextResponse.json({ users, total, page, pageSize });
} catch (e) {
logError("Platform users error:", e);
return NextResponse.json({ error: "服务器错误" }, { status: 500 });
}
}
export async function PATCH(req: NextRequest) {
try {
const body = await req.json() as {
ids?: string[];
action?: "enable" | "disable";
};
const { ids = [], action } = body;
if (!ids.length) {
return NextResponse.json({ error: "至少选择一个用户" }, { status: 400 });
}
if (!action || !["enable", "disable"].includes(action)) {
return NextResponse.json({ error: "action 必须是 enable 或 disable" }, { status: 400 });
}
const isActive = action === "enable";
// Get user ids from uids
const uidPlaceholders = ids.map((_, i) => `$${i + 1}`).join(", ");
const uidResult = await query<{ uid: string }>(
`SELECT uid FROM "user" WHERE uid IN (${uidPlaceholders})`,
ids
);
const uids = uidResult.rows.map((r) => r.uid);
if (!uids.length) {
return NextResponse.json({ error: "未找到匹配的用户" }, { status: 404 });
}
const uidPlaceholders2 = uids.map((_, i) => `$${i + 1}`).join(", ");
await query(
`UPDATE user_password SET is_active = $${uids.length + 1}, updated_at = NOW() WHERE "user" IN (${uidPlaceholders2})`,
[...uids, isActive]
);
const adminUserId = parseInt(req.headers.get("x-admin-user-id") || "0", 10);
const adminUsername = req.headers.get("x-admin-username") || "unknown";
await createAuditLog({
userId: adminUserId,
username: adminUsername,
action: "update",
resource: "user_batch_status",
resourceId: `batch(${uids.length})`,
requestParams: { uidCount: ids.length, userIdCount: uids.length, action },
ipAddress: req.headers.get("x-forwarded-for") || undefined,
userAgent: req.headers.get("user-agent") || undefined,
});
return NextResponse.json({ success: true, updated: uids.length });
} catch (e) {
logError("Batch update user status error:", e);
return NextResponse.json({ error: "服务器错误" }, { status: 500 });
}
}