feat(admin): auto-migrate admin DB tables on health check (audit_log, user, role, permission)

This commit is contained in:
ZhenYi 2026-04-20 19:32:38 +08:00
parent 33a4a5c6c9
commit 3eeb054452

View File

@ -1,7 +1,138 @@
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { query } from "@/lib/db";
export const runtime = "nodejs"; export const runtime = "nodejs";
export async function GET() { let migrationDone = false;
return NextResponse.json({ status: "ok" }, { status: 200 });
async function ensureTables() {
if (migrationDone) return;
migrationDone = true;
console.log("[Health] Checking database tables...");
// 1. admin_audit_log
await query(`
CREATE TABLE IF NOT EXISTS admin_audit_log (
id BIGSERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
username VARCHAR(255) NOT NULL,
action VARCHAR(50) NOT NULL,
resource VARCHAR(255) NOT NULL,
resource_id VARCHAR(255),
request_params JSONB,
ip_address VARCHAR(255),
user_agent TEXT,
result VARCHAR(20) NOT NULL DEFAULT 'success',
error_message TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
`);
// 2. admin_user
await query(`
CREATE TABLE IF NOT EXISTS admin_user (
id SERIAL PRIMARY KEY,
username VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
`);
// 3. admin_role
await query(`
CREATE TABLE IF NOT EXISTS admin_role (
id SERIAL PRIMARY KEY,
name VARCHAR(255) UNIQUE NOT NULL,
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
`);
// 4. admin_permission
await query(`
CREATE TABLE IF NOT EXISTS admin_permission (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
code VARCHAR(255) UNIQUE NOT NULL,
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
`);
// 5. admin_user_role
await query(`
CREATE TABLE IF NOT EXISTS admin_user_role (
user_id INTEGER NOT NULL REFERENCES admin_user(id) ON DELETE CASCADE,
role_id INTEGER NOT NULL REFERENCES admin_role(id) ON DELETE CASCADE,
PRIMARY KEY (user_id, role_id)
)
`);
// 6. admin_role_permission
await query(`
CREATE TABLE IF NOT EXISTS admin_role_permission (
role_id INTEGER NOT NULL REFERENCES admin_role(id) ON DELETE CASCADE,
permission_id INTEGER NOT NULL REFERENCES admin_permission(id) ON DELETE CASCADE,
PRIMARY KEY (role_id, permission_id)
)
`);
// 索引
await query(`CREATE INDEX IF NOT EXISTS idx_admin_user_username ON admin_user(username)`);
await query(`CREATE INDEX IF NOT EXISTS idx_admin_audit_log_user_id ON admin_audit_log(user_id)`);
await query(`CREATE INDEX IF NOT EXISTS idx_admin_audit_log_created_at ON admin_audit_log(created_at DESC)`);
await query(`CREATE INDEX IF NOT EXISTS idx_admin_audit_log_action ON admin_audit_log(action)`);
await query(`CREATE INDEX IF NOT EXISTS idx_admin_audit_log_resource ON admin_audit_log(resource)`);
// Seed data
await query(`
INSERT INTO admin_permission (name, code, description) VALUES
('用户管理', 'user:read', '查看用户列表'),
('用户创建', 'user:create', '创建管理员用户'),
('用户更新', 'user:update', '更新管理员用户'),
('用户删除', 'user:delete', '删除管理员用户'),
('角色管理', 'role:read', '查看角色'),
('角色创建', 'role:create', '创建角色'),
('角色更新', 'role:update', '更新角色'),
('角色删除', 'role:delete', '删除角色'),
('权限管理', 'permission:read', '查看权限'),
('权限创建', 'permission:create', '创建权限'),
('权限更新', 'permission:update', '更新权限'),
('权限删除', 'permission:delete', '删除权限'),
('日志查看', 'log:read', '查看审计日志'),
('会话管理', 'session:manage', '管理在线用户会话'),
('平台数据', 'platform:read', '查看平台数据'),
('平台管理', 'platform:manage', '管理平台数据')
ON CONFLICT (code) DO NOTHING
`);
await query(`
INSERT INTO admin_role (name, description) VALUES
('超级管理员', '拥有所有权限')
ON CONFLICT (name) DO NOTHING
`);
await query(`
INSERT INTO admin_role_permission (role_id, permission_id)
SELECT r.id, p.id
FROM admin_role r
CROSS JOIN admin_permission p
WHERE r.name = '超级管理员'
ON CONFLICT DO NOTHING
`);
console.log("[Health] Database tables ready");
}
export async function GET() {
try {
await ensureTables();
return NextResponse.json({ status: "ok" }, { status: 200 });
} catch (e) {
console.error("[Health] DB check failed:", e);
return NextResponse.json({ status: "error", reason: String(e) }, { status: 503 });
}
} }