gitdataai/admin/src/app/api/platform/audit-logs/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

129 lines
4.6 KiB
TypeScript

import { logError } from "@/lib/logger";
import { NextRequest, NextResponse } from "next/server";
import { query } from "@/lib/db";
export const runtime = "nodejs";
interface AuditLog {
source: "user_activity" | "project_audit";
id: number;
actor_uid: string | null;
action: string;
resource: string | null;
ip_address: string | null;
user_agent: string | null;
created_at: string;
}
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 source = searchParams.get("source") || "all";
const action = searchParams.get("action") || "";
const offset = (page - 1) * pageSize;
const actionPattern = action ? `%${action}%` : null;
const limitOffsetParams: unknown[] = [pageSize, offset];
let userQuery = "";
let projectQuery = "";
let userParams: unknown[] = [];
let projectParams: unknown[] = [];
// Build user_activity_log query
if (source !== "project") {
if (action) {
userParams = [actionPattern, ...limitOffsetParams];
userQuery = `SELECT 'user_activity' as source, id,
COALESCE(user_uid::text, '') as actor_uid,
action, NULL::text as resource,
ip_address, user_agent, created_at::text as created_at
FROM user_activity_log
WHERE action ILIKE $1
ORDER BY created_at DESC
LIMIT $2 OFFSET $3`;
} else {
userParams = limitOffsetParams;
userQuery = `SELECT 'user_activity' as source, id,
COALESCE(user_uid::text, '') as actor_uid,
action, NULL::text as resource,
ip_address, user_agent, created_at::text as created_at
FROM user_activity_log
ORDER BY created_at DESC
LIMIT $1 OFFSET $2`;
}
}
// Build project_audit_log query
if (source !== "user") {
if (action) {
projectParams = [actionPattern, ...limitOffsetParams];
projectQuery = `SELECT 'project_audit' as source, id,
COALESCE(actor::text, '') as actor_uid,
action, details as resource, ip_address,
NULL as user_agent, created_at::text as created_at
FROM project_audit_log
WHERE action ILIKE $1
ORDER BY created_at DESC
LIMIT $2 OFFSET $3`;
} else {
projectParams = limitOffsetParams;
projectQuery = `SELECT 'project_audit' as source, id,
COALESCE(actor::text, '') as actor_uid,
action, details as resource, ip_address,
NULL as user_agent, created_at::text as created_at
FROM project_audit_log
ORDER BY created_at DESC
LIMIT $1 OFFSET $2`;
}
}
const [userLogs, projectLogs] = await Promise.all([
userQuery ? query<AuditLog>(userQuery, userParams) : Promise.resolve({ rows: [] as AuditLog[] }),
projectQuery ? query<AuditLog>(projectQuery, projectParams) : Promise.resolve({ rows: [] as AuditLog[] }),
]);
// 合并并排序
const combined = [...userLogs.rows, ...projectLogs.rows].sort(
(a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
);
// 总数
const [userCountRes, projectCountRes] = await Promise.all([
userCountQuery(userParams, action),
projectCountQuery(projectParams, action),
]);
const total = parseInt(String(userCountRes.rows[0]?.count || "0"), 10) +
parseInt(String(projectCountRes.rows[0]?.count || "0"), 10);
return NextResponse.json({ logs: combined.slice(0, pageSize), total, page, pageSize });
} catch (e) {
logError("Platform audit logs error:", e);
return NextResponse.json({ error: "服务器错误" }, { status: 500 });
}
}
async function userCountQuery(params: unknown[], action: string | null) {
if (!params.length) return { rows: [] as { count: string }[] };
if (action) {
return query<{ count: string }>(
`SELECT COUNT(*) FROM user_activity_log WHERE action ILIKE $1`,
[params[0]]
);
}
return query<{ count: string }>(`SELECT COUNT(*) FROM user_activity_log`);
}
async function projectCountQuery(params: unknown[], action: string | null) {
if (!params.length) return { rows: [] as { count: string }[] };
if (action) {
return query<{ count: string }>(
`SELECT COUNT(*) FROM project_audit_log WHERE action ILIKE $1`,
[params[0]]
);
}
return query<{ count: string }>(`SELECT COUNT(*) FROM project_audit_log`);
}