oa-ai/src/app/api/admin/sync-users/route.ts

68 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 })
}