paper-burner/local-proxy/routes/user.js

319 lines
6.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 用户路由
* 复用 server/src/routes/user.js 的逻辑
*/
import express from 'express';
import { prisma } from '../db/client.js';
import { encrypt } from '../db/crypto.js';
const router = express.Router();
// ==================== 用户设置 ====================
// 获取用户设置
router.get('/settings', async (req, res, next) => {
try {
let settings = await prisma.userSettings.findUnique({
where: { userId: req.user.id }
});
if (!settings) {
// 创建默认设置
settings = await prisma.userSettings.create({
data: { userId: req.user.id }
});
}
res.json(settings);
} catch (error) {
next(error);
}
});
// 更新用户设置
router.put('/settings', async (req, res, next) => {
try {
const settings = await prisma.userSettings.upsert({
where: { userId: req.user.id },
update: req.body,
create: {
userId: req.user.id,
...req.body
}
});
res.json(settings);
} catch (error) {
next(error);
}
});
// ==================== API Keys 管理 ====================
// 获取 API Keys
router.get('/api-keys', async (req, res, next) => {
try {
const keys = await prisma.apiKey.findMany({
where: { userId: req.user.id },
orderBy: { order: 'asc' },
select: {
id: true,
provider: true,
remark: true,
status: true,
order: true,
lastUsedAt: true,
createdAt: true
// 不返回 keyValue (已加密)
}
});
res.json(keys);
} catch (error) {
next(error);
}
});
// 添加 API Key
router.post('/api-keys', async (req, res, next) => {
try {
const { provider, keyValue, remark, order } = req.body;
if (!provider || !keyValue) {
return res.status(400).json({ error: 'Provider and keyValue are required' });
}
// 加密 API Key
const encryptedKey = encrypt(keyValue);
const key = await prisma.apiKey.create({
data: {
userId: req.user.id,
provider,
keyValue: encryptedKey,
remark,
order: order || 0
}
});
res.status(201).json({
id: key.id,
provider: key.provider,
remark: key.remark,
status: key.status,
order: key.order
});
} catch (error) {
next(error);
}
});
// 批量保存 API Keys前端发送 { provider, keys: [...] }
router.post('/api-keys/batch', async (req, res, next) => {
try {
const { provider, keys } = req.body;
if (!provider || !Array.isArray(keys)) {
return res.status(400).json({ error: 'Provider and keys array are required' });
}
// 先删除该 provider 的所有旧 keys
await prisma.apiKey.deleteMany({
where: {
userId: req.user.id,
provider
}
});
// 批量创建新的 keys
const createdKeys = [];
for (let i = 0; i < keys.length; i++) {
const keyData = keys[i];
if (keyData.keyValue) {
const encryptedKey = encrypt(keyData.keyValue);
const key = await prisma.apiKey.create({
data: {
userId: req.user.id,
provider,
keyValue: encryptedKey,
remark: keyData.remark || '',
order: i
}
});
createdKeys.push({
id: key.id,
provider: key.provider,
remark: key.remark,
status: key.status,
order: key.order
});
}
}
res.status(201).json(createdKeys);
} catch (error) {
next(error);
}
});
// 更新 API Key 状态
router.patch('/api-keys/:id/status', async (req, res, next) => {
try {
const { status } = req.body;
if (!['VALID', 'INVALID', 'TESTING', 'UNTESTED'].includes(status)) {
return res.status(400).json({ error: 'Invalid status value' });
}
await prisma.apiKey.updateMany({
where: {
id: req.params.id,
userId: req.user.id
},
data: {
status,
lastUsedAt: status === 'VALID' ? new Date() : undefined
}
});
res.json({ success: true });
} catch (error) {
next(error);
}
});
// 删除 API Key
router.delete('/api-keys/:id', async (req, res, next) => {
try {
await prisma.apiKey.deleteMany({
where: {
id: req.params.id,
userId: req.user.id
}
});
res.json({ success: true });
} catch (error) {
next(error);
}
});
// ==================== 已处理文件记录 ====================
// 获取已处理文件列表
router.get('/processed-files', async (req, res, next) => {
try {
const files = await prisma.processedFile.findMany({
where: { userId: req.user.id },
select: {
fileIdentifier: true,
fileName: true,
processedAt: true
}
});
res.json(files);
} catch (error) {
next(error);
}
});
// 标记文件为已处理
router.post('/processed-files', async (req, res, next) => {
try {
const { fileIdentifier, fileName } = req.body;
if (!fileIdentifier || !fileName) {
return res.status(400).json({ error: 'fileIdentifier and fileName are required' });
}
const file = await prisma.processedFile.upsert({
where: {
userId_fileIdentifier: {
userId: req.user.id,
fileIdentifier
}
},
update: {
fileName,
processedAt: new Date()
},
create: {
userId: req.user.id,
fileIdentifier,
fileName
}
});
res.status(201).json(file);
} catch (error) {
next(error);
}
});
// 检查文件是否已处理
router.get('/processed-files/check/:identifier', async (req, res, next) => {
try {
const file = await prisma.processedFile.findUnique({
where: {
userId_fileIdentifier: {
userId: req.user.id,
fileIdentifier: req.params.identifier
}
}
});
res.json({ processed: !!file });
} catch (error) {
next(error);
}
});
// 批量检查文件是否已处理
router.post('/processed-files/check-batch', async (req, res, next) => {
try {
const { identifiers } = req.body;
if (!Array.isArray(identifiers)) {
return res.status(400).json({ error: 'identifiers must be an array' });
}
const files = await prisma.processedFile.findMany({
where: {
userId: req.user.id,
fileIdentifier: {
in: identifiers
}
},
select: {
fileIdentifier: true
}
});
const processedSet = new Set(files.map(f => f.fileIdentifier));
const result = {};
identifiers.forEach(id => {
result[id] = processedSet.has(id);
});
res.json(result);
} catch (error) {
next(error);
}
});
// 清空已处理文件记录
router.delete('/processed-files', async (req, res, next) => {
try {
await prisma.processedFile.deleteMany({
where: { userId: req.user.id }
});
res.json({ success: true });
} catch (error) {
next(error);
}
});
export default router;