issue-ai/src/app/api/tickets/external/route.ts

106 lines
3.5 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.

import { NextRequest, NextResponse } from 'next/server'
import { getDb } from '@/lib/db'
import { initDatabase } from '@/lib/db-schema'
import { verifyApiKey } from '@/lib/auth'
import { writeAuditLog, getClientIP } from '@/lib/audit'
function verifyEnvApiKey(key: string): boolean {
const allowed = process.env.ALLOWED_API_KEYS || ''
if (!allowed) return false
return allowed.split(',').map(k => k.trim()).includes(key)
}
function validateTicketNo(ticketNo: string): string | null {
if (!/^\d{14}$/.test(ticketNo)) return '工单号必须为 14 位纯数字'
const y = parseInt(ticketNo.slice(0, 4))
const m = parseInt(ticketNo.slice(4, 6))
const d = parseInt(ticketNo.slice(6, 8))
const dt = new Date(y, m - 1, d)
if (dt.getFullYear() !== y || dt.getMonth() !== m - 1 || dt.getDate() !== d) {
return '工单号前 8 位必须为合法日期YYYYMMDD'
}
return null
}
export async function POST(request: NextRequest) {
try {
initDatabase()
const authHeader = request.headers.get('authorization')
let authenticated = false
let apiKeyInfo: { id: number; name: string; permissions: string[] } | null = null
if (authHeader?.startsWith('Bearer ak_')) {
const key = authHeader.slice(7)
if (verifyEnvApiKey(key)) {
authenticated = true
} else {
const keyInfo = verifyApiKey(key)
if (keyInfo) {
authenticated = true
apiKeyInfo = keyInfo
}
}
}
if (!authenticated) {
return NextResponse.json({ error: '未授权:需要有效的 API Key' }, { status: 401 })
}
const body = await request.json()
const ticketNo = String(body.ticket_no || '').trim()
if (!ticketNo) {
return NextResponse.json({ error: '缺少必填字段: ticket_no' }, { status: 400 })
}
const validationError = validateTicketNo(ticketNo)
if (validationError) {
return NextResponse.json({ error: validationError }, { status: 400 })
}
const db = getDb()
const ticketId = parseInt(ticketNo)
const existing = db.prepare('SELECT * FROM tickets WHERE id = ?').get(ticketId) as Record<string, unknown> | undefined
if (existing) {
return NextResponse.json({ ticket: existing, created: false })
}
db.prepare(`
INSERT INTO tickets (id, device_ip, device_sn, device_name, content, assign_time,
ticket_type, fault_category, fault_subcategory, responsibility, current_status, counted_in_sla)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(
ticketId,
body.device_ip || null,
body.device_sn || null,
body.device_name || null,
body.content || null,
body.assign_time || new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString().slice(0, 19),
body.ticket_type || null,
body.fault_category || null,
body.fault_subcategory || null,
body.responsibility || null,
'open',
body.counted_in_sla ?? 1,
)
const ticket = db.prepare('SELECT * FROM tickets WHERE id = ?').get(ticketId)
writeAuditLog({
userId: null,
apiKeyId: apiKeyInfo?.id ?? null,
action: 'create',
entityType: 'ticket',
entityId: ticketId,
details: { created: { ticket_no: ticketNo, device_ip: body.device_ip || null } },
ipAddress: getClientIP(request)
})
return NextResponse.json({ ticket, created: true }, { status: 201 })
} catch (e) {
const msg = e instanceof Error ? e.message : '创建失败'
return NextResponse.json({ error: msg }, { status: 500 })
}
}