import { NextResponse } from 'next/server' import { cookies } from 'next/headers' import { exec } from 'child_process' import { promisify } from 'util' import { verifySharedJwt } from '@/lib/jwt' import { isLldapAdmin } from '@/lib/ldap' const execAsync = promisify(exec) const INTERNAL_KEY = 'oa-internal-key-tlyq-2026' async function fetchRoles(siteUrl: string): Promise { try { const res = await fetch(`${siteUrl}/api/internal/roles`, { headers: { 'x-internal-key': INTERNAL_KEY }, signal: AbortSignal.timeout(5000), }) const data = await res.json() return (data.roles || []).map((r: { name: string }) => r.name) } catch { return [] } } function queryDb(dbPath: string, sql: string): Promise { return execAsync(`sqlite3 "${dbPath}" "${sql.replace(/"/g, '\\"')}"`, { timeout: 3000 }).then(r => r.stdout).catch(() => '') } async function getSiteUsers(dbPath: string, roles: string[]): Promise<{ username: string; display_name: string; role: string }[]> { const out = await queryDb(dbPath, 'SELECT username, display_name, role FROM users WHERE is_active=1 ORDER BY username;') return out.trim().split('\n').filter(Boolean).map(line => { const [username, display_name, role] = line.split('|') return { username, display_name: display_name || username, role: roles.includes(role) ? role : 'viewer' } }) } async function checkAdmin() { const cookieStore = await cookies() const token = cookieStore.get('tlyq_session')?.value if (!token) return false const session = verifySharedJwt(token) return session ? isLldapAdmin(session.username) : false } // GET — 列出各站点用户及其角色 export async function GET() { if (!(await checkAdmin())) return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) try { const [assetsRoles, issueRoles] = await Promise.all([ fetchRoles('http://assets-ai:3000'), fetchRoles('http://issue-ai:3000'), ]) const [assetsUsers, issueUsers] = await Promise.all([ getSiteUsers(process.env.ASSETS_DB_PATH || '/Users/niuniu/programs/docker/assets-ai/data/assets.db', assetsRoles), getSiteUsers(process.env.ISSUE_DB_PATH || '/Users/niuniu/programs/docker/issue-ai/data/issue.db', issueRoles), ]) // 从 LLDAP 获取所有用户邮箱 let emails: Record = {} try { const { stdout } = await execAsync( `docker exec lldap /bin/sh -c "echo 'SELECT user_id, email FROM users;' | sqlite3 /data/users.db"`, { timeout: 3000 } ) stdout.trim().split('\n').filter(Boolean).forEach(line => { const [uid, e] = line.split('|') emails[uid] = e || '' }) } catch {} return NextResponse.json({ assetsRoles, issueRoles, users: { assets: assetsUsers, issue: issueUsers }, emails, }) } catch (e) { return NextResponse.json({ error: '查询失败' }, { status: 500 }) } } // PUT — 更新用户角色 export async function PUT(request: Request) { if (!(await checkAdmin())) return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) try { const { username, site, role } = await request.json() if (!username || !site || !role) return NextResponse.json({ error: '参数不完整' }, { status: 400 }) if (username === 'admin' || username === 'localadmin') return NextResponse.json({ error: '不能修改系统保留用户角色' }, { status: 400 }) const dbPath = site === 'assets' ? (process.env.ASSETS_DB_PATH || '/Users/niuniu/programs/docker/assets-ai/data/assets.db') : (process.env.ISSUE_DB_PATH || '/Users/niuniu/programs/docker/issue-ai/data/issue.db') // 验证角色合法性 const roles = await fetchRoles(`http://${site}-ai:3000`) if (!roles.includes(role)) return NextResponse.json({ error: '无效的角色' }, { status: 400 }) await execAsync(`sqlite3 "${dbPath}" "UPDATE users SET role='${role}', updated_at=datetime('now', '+8 hours') WHERE username='${username}';"`, { timeout: 3000 }) return NextResponse.json({ success: true }) } catch (e) { return NextResponse.json({ error: '更新失败' }, { status: 500 }) } }