import { NextRequest, NextResponse } from 'next/server' import { ConfigManager, reconnectAllPlatforms } from '@/remote-control/config/manager' export async function GET() { const configManager = ConfigManager.getInstance() await configManager.ensureLoaded() const config = configManager.getMasked() return NextResponse.json(config) } export async function PUT(request: NextRequest) { try { const body = await request.json() const configManager = ConfigManager.getInstance() await configManager.ensureLoaded() // Replace masked values with existing real values so we don't overwrite secrets const merged = stripMaskedValues(body, configManager.get()) const validationError = validateConfig(merged) if (validationError) { return NextResponse.json( { success: false, error: validationError }, { status: 400 } ) } // skipEmit: lifecycle is managed explicitly below, avoid double-triggering await configManager.update(merged, { skipEmit: true }) // After saving, explicitly manage bot lifecycle: // - Stop all disabled bots // - Reconnect (stop + start) all enabled bots const config = configManager.get() const errors: string[] = [] await reconnectAllPlatforms(config, errors) return NextResponse.json({ success: true, message: errors.length > 0 ? `配置已保存,部分渠道连接异常: ${errors.join('; ')}` : '配置已保存并生效', }) } catch (error) { return NextResponse.json( { success: false, error: '保存配置失败' }, { status: 500 } ) } } const MASK_PREFIX = '••••' /** * If the frontend sends back a masked value (e.g. "••••pbYI"), replace it * with the real value from the current config so we never overwrite secrets. */ function stripMaskedValues( incoming: Record>, current: Record>, ): Record> { const result: Record> = {} for (const platform of Object.keys(incoming)) { const incomingPlatform = incoming[platform] const currentPlatform = current[platform] ?? {} const merged: Record = {} for (const key of Object.keys(incomingPlatform)) { const val = incomingPlatform[key] if (typeof val === 'string' && val.startsWith(MASK_PREFIX)) { // Keep the real value merged[key] = currentPlatform[key] ?? '' } else { merged[key] = val } } result[platform] = merged } return result } function validateConfig(config: Record): string | null { const discord = config.discord as Record | undefined const dingtalk = config.dingtalk as Record | undefined const lark = config.lark as Record | undefined if (discord?.enabled && !discord?.botToken) { return 'Discord Bot Token 不能为空' } if (dingtalk?.enabled && (!dingtalk?.clientId || !dingtalk?.clientSecret)) { return '钉钉 Client ID 和 Client Secret 不能为空' } if (lark?.enabled) { if (!lark?.appId || !lark?.appSecret) { return '飞书 App ID 和 App Secret 不能为空' } } const telegram = config.telegram as Record | undefined const slack = config.slack as Record | undefined if (telegram?.enabled && !telegram?.botToken) { return 'Telegram Bot Token 不能为空' } if (slack?.enabled && (!slack?.botToken || !slack?.appToken)) { return 'Slack Bot Token 和 App Token 不能为空' } return null }