Files

131 lines
4.1 KiB
JavaScript

import { NextResponse } from 'next/server';
import fs from 'fs/promises';
import path from 'path';
// 数据持久化 API — 将所有用户数据存储到本地文件系统
// 每个用户有独立的目录(通过 userId cookie 隔离)
const DATA_ROOT = process.env.DATA_DIR || path.join(process.cwd(), 'data');
// 从请求中提取或创建用户ID
function getUserId(request) {
// 优先从 cookie 读取
const cookies = request.headers.get('cookie') || '';
const match = cookies.match(/author-uid=([a-zA-Z0-9_-]+)/);
if (match) return match[1];
return null;
}
// 确保目录存在
async function ensureDir(dirPath) {
try {
await fs.mkdir(dirPath, { recursive: true });
} catch (e) {
if (e.code !== 'EEXIST') throw e;
}
}
// key → 文件路径映射(防止路径穿越)
function resolveFilePath(userId, key) {
// 仅允许字母、数字、连字符、下划线、斜杠、点
const safeKey = key.replace(/[^a-zA-Z0-9\-_./]/g, '');
if (!safeKey || safeKey.includes('..')) {
throw new Error('Invalid storage key');
}
const userDir = path.join(DATA_ROOT, userId);
const filePath = path.join(userDir, safeKey + '.json');
// 安全检查:确保路径在用户目录内
const resolvedPath = path.resolve(filePath);
const resolvedUserDir = path.resolve(userDir);
if (!resolvedPath.startsWith(resolvedUserDir)) {
throw new Error('Path traversal detected');
}
return filePath;
}
// GET /api/storage?key=xxx — 读取数据
export async function GET(request) {
try {
const userId = getUserId(request);
if (!userId) {
return NextResponse.json({ error: 'No user ID' }, { status: 401 });
}
const { searchParams } = new URL(request.url);
const key = searchParams.get('key');
if (!key) {
return NextResponse.json({ error: 'Missing key parameter' }, { status: 400 });
}
const filePath = resolveFilePath(userId, key);
try {
const content = await fs.readFile(filePath, 'utf-8');
return NextResponse.json({ data: JSON.parse(content) });
} catch (e) {
if (e.code === 'ENOENT') {
return NextResponse.json({ data: null });
}
throw e;
}
} catch (error) {
console.error('Storage GET error:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
// POST /api/storage — 写入数据 { key, value }
export async function POST(request) {
try {
const userId = getUserId(request);
if (!userId) {
return NextResponse.json({ error: 'No user ID' }, { status: 401 });
}
const { key, value } = await request.json();
if (!key) {
return NextResponse.json({ error: 'Missing key' }, { status: 400 });
}
const filePath = resolveFilePath(userId, key);
await ensureDir(path.dirname(filePath));
await fs.writeFile(filePath, JSON.stringify(value, null, 2), 'utf-8');
return NextResponse.json({ ok: true });
} catch (error) {
console.error('Storage POST error:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
// DELETE /api/storage?key=xxx — 删除数据
export async function DELETE(request) {
try {
const userId = getUserId(request);
if (!userId) {
return NextResponse.json({ error: 'No user ID' }, { status: 401 });
}
const { searchParams } = new URL(request.url);
const key = searchParams.get('key');
if (!key) {
return NextResponse.json({ error: 'Missing key parameter' }, { status: 400 });
}
const filePath = resolveFilePath(userId, key);
try {
await fs.unlink(filePath);
} catch (e) {
if (e.code !== 'ENOENT') throw e;
}
return NextResponse.json({ ok: true });
} catch (error) {
console.error('Storage DELETE error:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}