码桶

发现社区成员的开源项目

data.js3.3 KB
/**
 * Vercel Serverless Function - Upstash Redis 数据读写
 * GET  /api/data → 公开读取域名列表 + 网站设置
 * POST /api/data → 需认证,保存数据到 Redis
 *
 * 环境变量(通过 Vercel 安装 Upstash 后自动注入):
 *   KV_REST_API_URL / KV_REST_API_TOKEN
 *   或 UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN
 */

let redis = null;
try {
  const { Redis } = require('@upstash/redis');
  const url = process.env.UPSTASH_REDIS_REST_URL || process.env.KV_REST_API_URL;
  const token = process.env.UPSTASH_REDIS_REST_TOKEN || process.env.KV_REST_API_TOKEN;
  if (url && token) {
    redis = new Redis({ url, token });
  }
} catch (e) {
  // 本地开发时包可能未安装
}

// 认证检查(与 auth.js 的 token 格式一致)
function checkAuth(req) {
  const auth = req.headers.authorization;
  if (!auth || !auth.startsWith('Bearer ')) return false;
  const token = auth.slice(7);
  const adminPassword = process.env.ADMIN_PASSWORD || '';
  if (!adminPassword) return false;

  // 当天 token
  const today = new Date().toISOString().slice(0, 10);
  const expectedToken = Buffer.from(`${adminPassword}:${today}`).toString('base64');
  if (token === expectedToken) return true;

  // 昨天的 token(跨午夜宽限)
  const yesterday = new Date(Date.now() - 86400000).toISOString().slice(0, 10);
  const yToken = Buffer.from(`${adminPassword}:${yesterday}`).toString('base64');
  return token === yToken;
}

module.exports = async (req, res) => {
  // CORS
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');

  if (req.method === 'OPTIONS') {
    return res.status(200).end();
  }

  // 检查 Redis 是否可用
  if (!redis) {
    return res.status(503).json({ error: 'REDIS_NOT_CONFIGURED' });
  }

  // GET — 公开读取(前台访客可调用)
  if (req.method === 'GET') {
    try {
      const [domains, config] = await Promise.all([
        redis.get('domains'),
        redis.get('config')
      ]);
      return res.status(200).json({
        domains: domains !== null ? domains : null,
        config: config !== null ? config : null
      });
    } catch (e) {
      console.error('Redis GET error:', e);
      return res.status(500).json({ error: e.message });
    }
  }

  // POST — 需认证,保存数据
  if (req.method === 'POST') {
    if (!checkAuth(req)) {
      return res.status(401).json({ error: 'Unauthorized' });
    }

    let body = req.body;
    if (typeof body === 'string') {
      try {
        body = JSON.parse(body);
      } catch (e) {
        return res.status(400).json({ error: 'Invalid JSON' });
      }
    }

    const { key, value } = body;
    if (!key || !['domains', 'config'].includes(key)) {
      return res.status(400).json({ error: 'Invalid key, must be "domains" or "config"' });
    }

    try {
      await redis.set(key, value);
      return res.status(200).json({ success: true });
    } catch (e) {
      console.error('Redis SET error:', e);
      return res.status(500).json({ error: e.message });
    }
  }

  return res.status(405).json({ error: 'Method Not Allowed' });
};