config.js 3.82 KB
/**
 * Config API 路由
 * 读取 / 写入系统配置文件
 */

const express = require('express');
const router = express.Router();
const fs = require('fs');
const path = require('path');
const os = require('os');
const { requireAuth } = require('../middleware/jwt-auth');

// 38 号文档 Phase 1:现状未深入调研是否有公司维度数据,先按最保守的方式处理——只挂登录,
// 后续如发现内部数据需要按公司过滤,再提级到 Phase 2 的归属校验。
router.use(requireAuth);

const ROOT = path.resolve(__dirname, '../..');

/**
 * 配置项定义
 */
const CONFIG_ITEMS = [
  {
    key: 'env',
    label: '环境变量 (.env)',
    icon: '🔐',
    description: 'Feishu / API Key / CORS 等环境变量',
    filePath: path.join(ROOT, '.env'),
    format: 'env',
  },
  {
    key: 'claude-settings',
    label: 'Claude Code Settings',
    icon: '🧠',
    description: 'Claude CLI 全局配置(模型、API、权限)',
    filePath: path.join(os.homedir(), '.claude', 'settings.json'),
    format: 'json',
  },
  {
    key: 'feishu-bot',
    label: 'Feishu Bot',
    icon: '🐦',
    description: '飞书机器人配置脚本',
    filePath: path.join(ROOT, 'skills', 'feishu-bot', 'bot.py'),
    format: 'text',
  },
  {
    key: 'whatsapp-bot',
    label: 'WhatsApp Bot',
    icon: '💬',
    description: 'WhatsApp Bot 配置脚本',
    filePath: path.join(ROOT, 'skills', 'whatsapp-bot', 'bot.js'),
    format: 'text',
  },
  {
    key: 'start-script',
    label: '启动脚本',
    icon: '🚀',
    description: 'start.sh 一键启动配置',
    filePath: path.join(ROOT, 'start.sh'),
    format: 'text',
  },
];

/**
 * GET /api/config/items
 * 返回所有配置项列表
 */
router.get('/items', (req, res) => {
  const items = CONFIG_ITEMS.map(item => ({
    key: item.key,
    label: item.label,
    icon: item.icon,
    description: item.description,
    format: item.format,
    exists: fs.existsSync(item.filePath),
  }));
  res.json({ items });
});

/**
 * GET /api/config/items/:key
 * 读取配置项内容
 */
router.get('/items/:key', (req, res) => {
  const item = CONFIG_ITEMS.find(i => i.key === req.params.key);
  if (!item) return res.status(404).json({ error: 'Config item not found' });

  if (!fs.existsSync(item.filePath)) {
    return res.json({ key: item.key, content: '', exists: false, ...item });
  }

  try {
    let content = fs.readFileSync(item.filePath, 'utf-8');

    // 如果指定了 jsonPath,只返回该字段
    if (item.jsonPath) {
      const json = JSON.parse(content);
      content = JSON.stringify(json[item.jsonPath] || {}, null, 2);
    }

    res.json({
      key: item.key,
      label: item.label,
      icon: item.icon,
      description: item.description,
      format: item.format,
      content,
      exists: true,
    });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

/**
 * PUT /api/config/items/:key
 * 保存配置项内容
 */
router.put('/items/:key', express.json({ limit: '1mb' }), (req, res) => {
  const item = CONFIG_ITEMS.find(i => i.key === req.params.key);
  if (!item) return res.status(404).json({ error: 'Config item not found' });

  const { content } = req.body;
  if (content === undefined) return res.status(400).json({ error: 'Missing content' });

  try {
    // 如果指定了 jsonPath,只更新该字段
    if (item.jsonPath) {
      let json = {};
      if (fs.existsSync(item.filePath)) {
        json = JSON.parse(fs.readFileSync(item.filePath, 'utf-8'));
      }
      json[item.jsonPath] = JSON.parse(content);
      fs.writeFileSync(item.filePath, JSON.stringify(json, null, 2) + '\n');
    } else {
      fs.writeFileSync(item.filePath, content);
    }

    res.json({ success: true, message: 'Config saved' });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

module.exports = router;