nyk
feat(refactor): ready for manual QA after main sync (#274)
b6ecafa unverified
import { NextRequest, NextResponse } from 'next/server'
import { requireRole } from '@/lib/auth'
import { getDatabase } from '@/lib/db'
import { getDetectedGatewayPort, getDetectedGatewayToken } from '@/lib/gateway-runtime'
interface GatewayEntry {
id: number
name: string
host: string
port: number
token: string
is_primary: number
status: string
last_seen: number | null
latency: number | null
sessions_count: number
agents_count: number
created_at: number
updated_at: number
}
function ensureTable(db: ReturnType<typeof getDatabase>) {
db.exec(`
CREATE TABLE IF NOT EXISTS gateways (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
host TEXT NOT NULL DEFAULT '127.0.0.1',
port INTEGER NOT NULL DEFAULT 18789,
token TEXT NOT NULL DEFAULT '',
is_primary INTEGER NOT NULL DEFAULT 0,
status TEXT NOT NULL DEFAULT 'unknown',
last_seen INTEGER,
latency INTEGER,
sessions_count INTEGER NOT NULL DEFAULT 0,
agents_count INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
)
`)
}
/**
* GET /api/gateways - List all registered gateways
*/
export async function GET(request: NextRequest) {
const auth = requireRole(request, 'viewer')
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
const db = getDatabase()
ensureTable(db)
const gateways = db.prepare('SELECT * FROM gateways ORDER BY is_primary DESC, name ASC').all() as GatewayEntry[]
// If no gateways exist, seed defaults from environment
if (gateways.length === 0) {
const name = String(process.env.MC_DEFAULT_GATEWAY_NAME || 'primary')
const host = String(process.env.OPENCLAW_GATEWAY_HOST || '127.0.0.1')
const mainPort = getDetectedGatewayPort() || parseInt(process.env.NEXT_PUBLIC_GATEWAY_PORT || '18789')
const mainToken = getDetectedGatewayToken()
db.prepare(`
INSERT INTO gateways (name, host, port, token, is_primary) VALUES (?, ?, ?, ?, 1)
`).run(name, host, mainPort, mainToken)
const seeded = db.prepare('SELECT * FROM gateways ORDER BY is_primary DESC, name ASC').all() as GatewayEntry[]
return NextResponse.json({ gateways: redactTokens(seeded) })
}
return NextResponse.json({ gateways: redactTokens(gateways) })
}
/**
* POST /api/gateways - Add a new gateway
*/
export async function POST(request: NextRequest) {
const auth = requireRole(request, 'admin')
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
const db = getDatabase()
ensureTable(db)
const body = await request.json()
const { name, host, port, token, is_primary } = body
if (!name || !host || !port) {
return NextResponse.json({ error: 'name, host, and port are required' }, { status: 400 })
}
try {
// If marking as primary, unset other primaries
if (is_primary) {
db.prepare('UPDATE gateways SET is_primary = 0').run()
}
const result = db.prepare(`
INSERT INTO gateways (name, host, port, token, is_primary) VALUES (?, ?, ?, ?, ?)
`).run(name, host, port, token || '', is_primary ? 1 : 0)
try {
db.prepare('INSERT INTO audit_log (action, actor, detail) VALUES (?, ?, ?)').run(
'gateway_added', auth.user?.username || 'system', `Added gateway: ${name} (${host}:${port})`
)
} catch { /* audit might not exist */ }
const gw = db.prepare('SELECT * FROM gateways WHERE id = ?').get(result.lastInsertRowid) as GatewayEntry
return NextResponse.json({ gateway: redactToken(gw) }, { status: 201 })
} catch (err: any) {
if (err.message?.includes('UNIQUE')) {
return NextResponse.json({ error: 'A gateway with that name already exists' }, { status: 409 })
}
return NextResponse.json({ error: err.message || 'Failed to add gateway' }, { status: 500 })
}
}
/**
* PUT /api/gateways - Update a gateway
*/
export async function PUT(request: NextRequest) {
const auth = requireRole(request, 'admin')
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
const db = getDatabase()
ensureTable(db)
const body = await request.json()
const { id, ...updates } = body
if (!id) return NextResponse.json({ error: 'id is required' }, { status: 400 })
const existing = db.prepare('SELECT * FROM gateways WHERE id = ?').get(id) as GatewayEntry | undefined
if (!existing) return NextResponse.json({ error: 'Gateway not found' }, { status: 404 })
// If setting as primary, unset others
if (updates.is_primary) {
db.prepare('UPDATE gateways SET is_primary = 0').run()
}
const allowed = ['name', 'host', 'port', 'token', 'is_primary', 'status', 'last_seen', 'latency', 'sessions_count', 'agents_count']
const sets: string[] = []
const values: any[] = []
for (const key of allowed) {
if (key in updates) {
sets.push(`${key} = ?`)
values.push(updates[key])
}
}
if (sets.length === 0) return NextResponse.json({ error: 'No valid fields to update' }, { status: 400 })
sets.push('updated_at = (unixepoch())')
values.push(id)
db.prepare(`UPDATE gateways SET ${sets.join(', ')} WHERE id = ?`).run(...values)
const updated = db.prepare('SELECT * FROM gateways WHERE id = ?').get(id) as GatewayEntry
return NextResponse.json({ gateway: redactToken(updated) })
}
/**
* DELETE /api/gateways - Remove a gateway
*/
export async function DELETE(request: NextRequest) {
const auth = requireRole(request, 'admin')
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
const db = getDatabase()
ensureTable(db)
const body = await request.json()
const { id } = body
if (!id) return NextResponse.json({ error: 'id is required' }, { status: 400 })
const gw = db.prepare('SELECT * FROM gateways WHERE id = ?').get(id) as GatewayEntry | undefined
if (gw?.is_primary) {
return NextResponse.json({ error: 'Cannot delete the primary gateway' }, { status: 400 })
}
const result = db.prepare('DELETE FROM gateways WHERE id = ?').run(id)
try {
db.prepare('INSERT INTO audit_log (action, actor, detail) VALUES (?, ?, ?)').run(
'gateway_removed', auth.user?.username || 'system', `Removed gateway: ${gw?.name}`
)
} catch { /* audit might not exist */ }
return NextResponse.json({ deleted: result.changes > 0 })
}
function redactToken(gw: GatewayEntry): GatewayEntry & { token_set: boolean } {
return { ...gw, token: gw.token ? '--------' : '', token_set: !!gw.token }
}
function redactTokens(gws: GatewayEntry[]) {
return gws.map(redactToken)
}