68 lines
3.0 KiB
TypeScript
68 lines
3.0 KiB
TypeScript
// POST /api/admin/sync-users — 将用户同步到指定站点(docker exec 写 DB)
|
||
import { NextResponse } from 'next/server'
|
||
import { cookies } from 'next/headers'
|
||
import { execFileSync } from 'child_process'
|
||
import { verifySharedJwt } from '@/lib/jwt'
|
||
import { isLldapAdmin } from '@/lib/ldap'
|
||
import { queryLldap, esc } from '@/lib/lldap-db'
|
||
|
||
const SITES: Record<string, [string, string]> = {
|
||
assets: ['assets-ai', '/app/data/assets.db'],
|
||
issue: ['issue-ai', '/app/data/issue.db'],
|
||
monitor: ['monitor-ai', '/app/data/monitor.db'],
|
||
}
|
||
|
||
function syncToSite(container: string, dbPath: string, username: string, displayName: string, email: string): boolean {
|
||
try {
|
||
const su = esc(username); const sd = esc(displayName); const se = esc(email)
|
||
const d = new Date()
|
||
const now = `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}:${String(d.getSeconds()).padStart(2,'0')}`
|
||
execFileSync('docker', ['exec', '-i', container, 'sqlite3', dbPath], {
|
||
input: `INSERT OR IGNORE INTO users (username, display_name, email, role, is_active, created_at, updated_at) VALUES ('${su}', '${sd}', '${se}', 'viewer', 1, '${now}', '${now}');`,
|
||
timeout: 5000,
|
||
})
|
||
return true
|
||
} catch { return false }
|
||
}
|
||
|
||
export async function POST(request: Request) {
|
||
const cookieStore = await cookies()
|
||
const token = cookieStore.get('tlyq_session')?.value
|
||
if (!token) return NextResponse.json({ error: '未登录' }, { status: 401 })
|
||
const session = verifySharedJwt(token)
|
||
if (!session || !(await isLldapAdmin(session.username))) {
|
||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
|
||
}
|
||
|
||
const { usernames, targetSite } = await request.json()
|
||
if (!usernames || !Array.isArray(usernames) || usernames.length === 0) {
|
||
return NextResponse.json({ error: '请指定要同步的用户' }, { status: 400 })
|
||
}
|
||
|
||
// 从 LLDAP 获取用户信息
|
||
const userInfo: Record<string, { displayName: string; email: string }> = {}
|
||
try {
|
||
const safeNames = usernames.map(u => `'${esc(u)}'`).join(',')
|
||
const out = queryLldap(`SELECT user_id, display_name, email FROM users WHERE user_id IN (${safeNames})`)
|
||
out.split('\n').filter(Boolean).forEach(line => {
|
||
const [uid, dn, em] = line.split('|')
|
||
userInfo[uid] = { displayName: dn || uid, email: em || '' }
|
||
})
|
||
} catch {}
|
||
|
||
const sites = targetSite && SITES[targetSite] ? { [targetSite]: SITES[targetSite] }
|
||
: targetSite ? null : SITES
|
||
if (!sites) return NextResponse.json({ error: '无效的站点' }, { status: 400 })
|
||
|
||
const results: Record<string, Record<string, boolean>> = {}
|
||
for (const [site, [cName, cPath]] of Object.entries(sites)) {
|
||
results[site] = {}
|
||
for (const username of usernames) {
|
||
const info = userInfo[username] || { displayName: username, email: '' }
|
||
results[site][username] = syncToSite(cName, cPath, username, info.displayName, info.email)
|
||
}
|
||
}
|
||
|
||
return NextResponse.json({ success: true, results })
|
||
}
|