Compare commits

...

1 Commits

Author SHA1 Message Date
肖应宇 ce324d8da3 feat: init database; 暂时只有历史正常显示; 2026-03-24 17:58:57 +08:00
286 changed files with 4929 additions and 76813 deletions

View File

@ -45,8 +45,16 @@ document.addEventListener('DOMContentLoaded', function() {
if (!quickListEl) return; if (!quickListEl) return;
try { try {
// 假设 getAllResultsFromDB 是全局可用的 (在 storage.js 中定义) // 使用 storageAdapter 获取数据(支持后端模式)
const results = await window.getAllResultsFromDB(); let results;
if (window.storageAdapter && typeof window.storageAdapter.getAllResultsFromDB === 'function') {
results = await window.storageAdapter.getAllResultsFromDB();
} else if (typeof getAllResultsFromDB === 'function') {
results = await getAllResultsFromDB();
} else {
console.error('No storage method available');
return;
}
if (!results || !Array.isArray(results) || results.length === 0) { if (!results || !Array.isArray(results) || results.length === 0) {
quickListEl.innerHTML = '<div class="px-3 py-2 text-xs text-slate-400 text-center">暂无记录</div>'; quickListEl.innerHTML = '<div class="px-3 py-2 text-xs text-slate-400 text-center">暂无记录</div>';
return; return;
@ -509,7 +517,16 @@ document.addEventListener('DOMContentLoaded', function() {
historySearchInput.value = historyUIState.searchQuery; historySearchInput.value = historyUIState.searchQuery;
} }
const results = await getAllResultsFromDB(); // 使用 storageAdapter 获取数据(支持后端模式)
let results;
if (window.storageAdapter && typeof window.storageAdapter.getAllResultsFromDB === 'function') {
results = await window.storageAdapter.getAllResultsFromDB();
} else if (typeof getAllResultsFromDB === 'function') {
results = await getAllResultsFromDB();
} else {
console.error('No storage method available');
results = [];
}
const assignments = loadFolderAssignments(); const assignments = loadFolderAssignments();
const userFolders = loadUserFolders(); const userFolders = loadUserFolders();
@ -2232,7 +2249,12 @@ document.addEventListener('DOMContentLoaded', function() {
* @returns {Promise<void>} ZIP 文件准备好并开始下载时解决或在发生错误时提前返回 * @returns {Promise<void>} ZIP 文件准备好并开始下载时解决或在发生错误时提前返回
*/ */
window.downloadHistoryRecord = async function(id) { window.downloadHistoryRecord = async function(id) {
const r = await getResultFromDB(id); let r;
if (window.storageAdapter && typeof window.storageAdapter.getResultFromDB === 'function') {
r = await window.storageAdapter.getResultFromDB(id);
} else if (typeof getResultFromDB === 'function') {
r = await getResultFromDB(id);
}
if (!r) return; if (!r) return;
if (typeof JSZip === 'undefined') { if (typeof JSZip === 'undefined') {
alert('JSZip 加载失败,无法打包下载'); alert('JSZip 加载失败,无法打包下载');
@ -2418,7 +2440,12 @@ document.addEventListener('DOMContentLoaded', function() {
if (!ctx) return; if (!ctx) return;
_setBusy(id, true, '处理中...'); _setBusy(id, true, '处理中...');
try { try {
const record = await getResultFromDB(id); let record;
if (window.storageAdapter && typeof window.storageAdapter.getResultFromDB === 'function') {
record = await window.storageAdapter.getResultFromDB(id);
} else if (typeof getResultFromDB === 'function') {
record = await getResultFromDB(id);
}
if (!record) { if (!record) {
showNotification && showNotification('未找到历史记录。', 'error'); showNotification && showNotification('未找到历史记录。', 'error');
return; return;

View File

@ -38,7 +38,13 @@ async function renderDetail() {
console.error("DockLogic not available or init function missing."); console.error("DockLogic not available or init function missing.");
} }
// 使用 storageAdapter 获取数据(支持后端模式)
let data;
if (window.storageAdapter && typeof window.storageAdapter.getResultFromDB === 'function') {
data = await window.storageAdapter.getResultFromDB(id);
} else if (typeof getResultFromDB === 'function') {
data = await getResultFromDB(id); data = await getResultFromDB(id);
}
window.data = data; // for debugging window.data = data; // for debugging
const fileMetaTimeEl = document.getElementById('fileMetaTime'); const fileMetaTimeEl = document.getElementById('fileMetaTime');
const fileMetaImagesEl = document.getElementById('fileMetaImages'); const fileMetaImagesEl = document.getElementById('fileMetaImages');
@ -76,7 +82,12 @@ async function renderDetail() {
// ========== 确保批注数据在渲染前加载 ========== // ========== 确保批注数据在渲染前加载 ==========
if (id) { // 确保我们有文档 ID if (id) { // 确保我们有文档 ID
try { try {
const annotations = await getAnnotationsForDocFromDB(id); let annotations;
if (window.storageAdapter && typeof window.storageAdapter.getAnnotationsForDocFromDB === 'function') {
annotations = await window.storageAdapter.getAnnotationsForDocFromDB(id);
} else if (typeof getAnnotationsForDocFromDB === 'function') {
annotations = await getAnnotationsForDocFromDB(id);
}
console.log(`Annotations for docId '${id}' (loaded in renderDetail):`, annotations); console.log(`Annotations for docId '${id}' (loaded in renderDetail):`, annotations);
data.annotations = annotations || []; // 存储到 data 对象,确保是数组 data.annotations = annotations || []; // 存储到 data 对象,确保是数组
// updateAnnotationSummary(); // Handled by updateAllDockStats via showTab // updateAnnotationSummary(); // Handled by updateAllDockStats via showTab

View File

@ -509,8 +509,12 @@ async function triggerReprocess(includeTranslation) {
} }
} }
// 保存到 IndexedDB // 保存到数据库
if (window.storageAdapter && typeof window.storageAdapter.saveResultToDB === 'function') {
await window.storageAdapter.saveResultToDB(window.data);
} else if (typeof saveResultToDB === 'function') {
await saveResultToDB(window.data); await saveResultToDB(window.data);
}
// 刷新页面显示 // 刷新页面显示
if (typeof renderDetail === 'function') { if (typeof renderDetail === 'function') {
@ -761,8 +765,10 @@ async function triggerReprocessWithMinerU() {
Object.assign(window.data.metadata, result.metadata); Object.assign(window.data.metadata, result.metadata);
} }
// 保存到 IndexedDB // 保存到数据库
if (typeof saveResultToDB === 'function') { if (window.storageAdapter && typeof window.storageAdapter.saveResultToDB === 'function') {
await window.storageAdapter.saveResultToDB(window.data);
} else if (typeof saveResultToDB === 'function') {
await saveResultToDB(window.data); await saveResultToDB(window.data);
} }
@ -972,7 +978,10 @@ async function executeMinerUStructuredTranslation() {
window.data = dataObj; window.data = dataObj;
// 保存到数据库 // 保存到数据库
if (typeof saveResultToDB === 'function') { if (window.storageAdapter && typeof window.storageAdapter.saveResultToDB === 'function') {
await window.storageAdapter.saveResultToDB(dataObj);
addLog('数据已保存到数据库');
} else if (typeof saveResultToDB === 'function') {
await saveResultToDB(dataObj); await saveResultToDB(dataObj);
addLog('数据已保存到数据库'); addLog('数据已保存到数据库');
} }

View File

@ -240,8 +240,9 @@ function showTabImmediate(tab) {
const bytes = new Uint8Array(raw.length); const bytes = new Uint8Array(raw.length);
for (let i = 0; i < raw.length; i++) bytes[i] = raw.charCodeAt(i); for (let i = 0; i < raw.length; i++) bytes[i] = raw.charCodeAt(i);
// 加载 viewer.html // 统一走后端暴露的 /pdfjs 静态资源,避免 Vite 在开发环境下
const viewerBase = '../../public/pdfjs/web/viewer.html'; // 将 PDF.js viewer 的脚本按模块脚本处理,导致 viewer.js 加载报错。
const viewerBase = '/pdfjs/web/viewer.html';
const iframe = document.getElementById('pdf-viewer-iframe'); const iframe = document.getElementById('pdf-viewer-iframe');
const loading = document.getElementById('pdf-viewer-loading'); const loading = document.getElementById('pdf-viewer-loading');

View File

@ -67,7 +67,32 @@ class AuthManager {
? { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } ? { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }
: { 'Content-Type': 'application/json' }; : { 'Content-Type': 'application/json' };
} }
/**
* URL query 参数中提取 token 并存储
* 支持 ?token=xxx ?auth_token=xxx
*/
static initTokenFromURL() {
try {
const p = new URLSearchParams(window.location.search);
const token = p.get('token') || p.get('auth_token');
if (token) {
this.setToken(token);
console.log('[Auth] Token initialized from URL parameter');
// 清理 URL 中的 token 参数(安全考虑)
const cleanUrl = new URL(window.location.href);
cleanUrl.searchParams.delete('token');
cleanUrl.searchParams.delete('auth_token');
window.history.replaceState({}, '', cleanUrl.toString());
} }
} catch (e) {
console.warn('[Auth] Failed to init token from URL:', e);
}
}
}
// 页面加载时自动从 URL 获取 token
AuthManager.initTokenFromURL();
// ---------------- 后端存储实现 ---------------- // ---------------- 后端存储实现 ----------------
class BackendStorage { class BackendStorage {

View File

@ -33,3 +33,27 @@ MISTRAL_API_KEY=
# 阿里云百炼平台 API Key从 https://bailian.console.aliyun.com 获取) # 阿里云百炼平台 API Key从 https://bailian.console.aliyun.com 获取)
# 推荐模型qwen-plus、qwen-turbo、qwen-max # 推荐模型qwen-plus、qwen-turbo、qwen-max
DASHSCOPE_API_KEY= DASHSCOPE_API_KEY=
# ==================== 数据库配置 ====================
# PostgreSQL 数据库连接字符串
# 本地开发可以用 Docker 快速启动:
# docker run -d --name paperburner-postgres \
# -e POSTGRES_USER=paperburner \
# -e POSTGRES_PASSWORD=paperburner123 \
# -e POSTGRES_DB=paperburner \
# -p 5432:5432 \
# -v paperburner-pgdata:/var/lib/postgresql/data \
# postgres:15-alpine
DATABASE_URL=postgresql://paperburner:paperburner123@localhost:5432/paperburner
# ==================== 外部认证配置 ====================
# 外部 Token 验证 API用于验证用户身份
AUTH_CHECK_URL=https://sxwz.xueai.art/api/auth/check/checkTokenRn
# 禁用认证(开发/测试模式,设为 true 跳过 Token 验证)
# AUTH_DISABLED=true
# ==================== 加密配置 ====================
# 用于加密 API Keys 等敏感数据32字符以上
ENCRYPTION_SECRET=your-encryption-secret-change-in-production
ENCRYPTION_SALT=your-encryption-salt-change-in-production

100
local-proxy/app.js Normal file
View File

@ -0,0 +1,100 @@
/**
* Express 应用入口
* 处理持久化 API 路由
*/
import express from 'express';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { prisma } from './db/client.js';
import { requireAuth } from './auth/middleware.js';
// 导入路由(稍后创建)
import documentsRouter from './routes/documents.js';
import userRouter from './routes/user.js';
import glossaryRouter from './routes/glossary.js';
import chatRouter from './routes/chat.js';
import referencesRouter from './routes/references.js';
import promptPoolRouter from './routes/prompt-pool.js';
const app = express();
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const projectRoot = join(__dirname, '..');
// ==================== 中间件配置 ====================
// JSON 解析
app.use(express.json({ limit: '50mb' }));
app.use(express.urlencoded({ extended: true, limit: '50mb' }));
// CORS
app.use((req, res, next) => {
const origin = req.headers.origin || '*';
res.header('Access-Control-Allow-Origin', origin);
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, x-api-key');
res.header('Access-Control-Expose-Headers', 'Content-Length, Content-Range');
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
// 请求日志(开发环境)
if (process.env.NODE_ENV !== 'production') {
app.use((req, res, next) => {
console.log(`[Express] ${req.method} ${req.path}`);
next();
});
}
// 开发模式下让 Vite 代理过来的 PDF.js 静态资源可用。
app.use('/pdfjs', express.static(join(projectRoot, 'pdfjs')));
// ==================== 路由注册 ====================
// 健康检查(无需认证)
app.get('/health', async (req, res) => {
try {
// 测试数据库连接
await prisma.$queryRaw`SELECT 1`;
res.json({
status: 'ok',
database: 'connected',
timestamp: Date.now()
});
} catch (error) {
res.json({
status: 'ok',
database: 'disconnected',
timestamp: Date.now()
});
}
});
// 持久化路由(需要认证)
app.use('/api/documents', requireAuth, documentsRouter);
app.use('/api/user', requireAuth, userRouter);
app.use('/api/glossary', requireAuth, glossaryRouter);
app.use('/api/chat', requireAuth, chatRouter);
app.use('/api/references', requireAuth, referencesRouter);
app.use('/api/prompt-pool', requireAuth, promptPoolRouter);
// ==================== 错误处理 ====================
// 404
app.use((req, res) => {
res.status(404).json({ error: 'Not Found' });
});
// 统一错误处理
app.use((err, req, res, next) => {
console.error('[Express] Error:', err.message);
res.status(err.status || 500).json({
error: err.message || 'Internal Server Error'
});
});
export default app;

View File

@ -0,0 +1,202 @@
/**
* 认证中间件
* 通过外部 API 验证 Token
*/
import fetch from 'node-fetch';
import { prisma } from '../db/client.js';
// 是否禁用认证(开发/测试模式)
const AUTH_DISABLED = process.env.AUTH_DISABLED === 'true' || process.env.NODE_ENV === 'test';
// 默认认证 API URL
const DEFAULT_AUTH_CHECK_URL = 'https://sxwz.xueai.art/api/auth/check/checkTokenRn';
// Token 缓存(减少外部 API 调用)
const tokenCache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 分钟
// 测试用户(认证禁用时使用)
const TEST_USER = {
id: 'test-user-001',
username: 'testuser',
nickname: '测试用户'
};
/**
* 验证 Token
* @param {string} token - JWT Token
* @returns {Promise<Object|null>} 用户信息或 null
*/
export async function verifyToken(token) {
if (!token) return null;
// 1. 检查缓存
const cached = tokenCache.get(token);
if (cached && cached.expiresAt > Date.now()) {
return cached.user;
}
// 2. 调用外部 API 验证
const authCheckUrl = process.env.AUTH_CHECK_URL || DEFAULT_AUTH_CHECK_URL;
const checkUrl = `${authCheckUrl}/${token}`;
try {
const response = await fetch(checkUrl, {
method: 'GET',
headers: {
'Accept': 'application/json',
'User-Agent': 'PaperBurner-LocalProxy/1.0'
},
signal: AbortSignal.timeout(5000) // 5 秒超时
});
if (!response.ok) {
console.warn('[Auth] Token verification failed:', response.status);
return null;
}
const data = await response.json();
// 检查响应格式
if (data.code !== '0' && data.success !== true) {
console.warn('[Auth] Token verification returned error:', data.msg || data.message);
return null;
}
const user = data.data;
if (!user || !user.id) {
console.warn('[Auth] Invalid user data in response');
return null;
}
// 3. 缓存结果
tokenCache.set(token, {
user: {
id: String(user.id),
username: user.username,
nickname: user.nickname || user.username
},
expiresAt: Date.now() + CACHE_TTL
});
return user;
} catch (error) {
console.error('[Auth] Token verification error:', error.message);
return null;
}
}
/**
* 确保用户存在首次访问时自动创建
* @param {string} userId - 用户 ID
* @param {string} name - 用户名称
*/
export async function ensureUserExists(userId, name) {
try {
await prisma.user.upsert({
where: { id: userId },
update: {}, // 不更新任何字段
create: {
id: userId,
name: name || `用户${userId.slice(-6)}`
}
});
} catch (error) {
console.error('[Auth] Failed to ensure user exists:', error.message);
}
}
/**
* 认证中间件
* 验证 Token 并自动创建用户
*/
export async function requireAuth(req, res, next) {
// 认证禁用模式(开发/测试)
if (AUTH_DISABLED) {
await ensureUserExists(TEST_USER.id, TEST_USER.nickname);
req.user = TEST_USER;
if (typeof next === 'function') {
next();
}
return;
}
const authHeader = req.headers.authorization;
const token = authHeader?.split(' ')[1];
if (!token) {
res.writeHead(401, {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': req.headers.origin || '*'
});
res.end(JSON.stringify({ error: 'Token required' }));
return;
}
const user = await verifyToken(token);
if (!user) {
res.writeHead(401, {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': req.headers.origin || '*'
});
res.end(JSON.stringify({ error: 'Invalid or expired token' }));
return;
}
// 自动创建用户(首次访问)
await ensureUserExists(user.id, user.nickname || user.username);
// 将用户信息附加到请求对象
req.user = user;
// 调用下一个处理器
if (typeof next === 'function') {
next();
}
}
/**
* 可选认证中间件
* 如果有 Token 则验证没有则跳过
*/
export async function optionalAuth(req, res, next) {
const authHeader = req.headers.authorization;
const token = authHeader?.split(' ')[1];
if (token) {
const user = await verifyToken(token);
if (user) {
await ensureUserExists(user.id, user.nickname || user.username);
req.user = user;
}
}
if (typeof next === 'function') {
next();
}
}
/**
* 清理过期的 Token 缓存
*/
export function cleanupTokenCache() {
const now = Date.now();
for (const [token, data] of tokenCache.entries()) {
if (data.expiresAt < now) {
tokenCache.delete(token);
}
}
}
// 每 10 分钟清理一次缓存
setInterval(cleanupTokenCache, 10 * 60 * 1000);
export default {
verifyToken,
ensureUserExists,
requireAuth,
optionalAuth
};

50
local-proxy/db/client.js Normal file
View File

@ -0,0 +1,50 @@
/**
* Prisma 客户端单例
* 避免多个实例导致数据库连接泄漏
*/
import pkg from '@prisma/client';
const { PrismaClient } = pkg;
let prismaInstance = null;
/**
* 获取 PrismaClient 单例
* @returns {PrismaClient} PrismaClient 实例
*/
export function getPrisma() {
if (!prismaInstance) {
prismaInstance = new PrismaClient({
log: process.env.NODE_ENV === 'development'
? ['query', 'error', 'warn']
: ['error'],
});
// 优雅关闭处理
process.on('beforeExit', async () => {
await prismaInstance.$disconnect();
});
}
return prismaInstance;
}
// 导出单例实例
export const prisma = getPrisma();
/**
* 初始化数据库连接
* @returns {Promise<boolean>} 是否成功连接
*/
export async function initDatabase() {
try {
await prisma.$connect();
console.log('[Prisma] Database connected');
return true;
} catch (error) {
console.error('[Prisma] Database connection failed:', error.message);
return false;
}
}
export default prisma;

118
local-proxy/db/crypto.js Normal file
View File

@ -0,0 +1,118 @@
/**
* 加密工具模块
* 用于安全地加密和解密敏感数据 API Keys
*/
import crypto from 'crypto';
// 加密常量
const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 16;
const TAG_LENGTH = 16;
const KEY_LENGTH = 32;
const PBKDF2_ITERATIONS = 100000;
/**
* 获取加密 salt
*/
function getEncryptionSalt() {
if (process.env.ENCRYPTION_SALT) {
return process.env.ENCRYPTION_SALT;
}
if (process.env.NODE_ENV === 'production') {
throw new Error('ENCRYPTION_SALT must be set in production environment');
}
console.warn('⚠️ Using default encryption salt for development. Set ENCRYPTION_SALT for production.');
return 'dev-salt-fixed-for-development';
}
/**
* 从环境变量获取加密密钥
*/
function getEncryptionKey() {
const secret = process.env.ENCRYPTION_SECRET || process.env.ENCRYPTION_KEY || 'default-encryption-key-change-in-production';
return crypto.pbkdf2Sync(secret, getEncryptionSalt(), PBKDF2_ITERATIONS, KEY_LENGTH, 'sha256');
}
/**
* 加密文本
* @param {string} text - 要加密的明文
* @returns {string} 加密后的数据Base64 编码
*/
export function encrypt(text) {
if (!text) return text;
try {
const key = getEncryptionKey();
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
const tag = cipher.getAuthTag();
// 组合 IV + 加密数据 + 认证标签
const combined = Buffer.concat([
iv,
Buffer.from(encrypted, 'hex'),
tag
]);
return combined.toString('base64');
} catch (error) {
console.error('Encryption error:', error);
throw new Error('Failed to encrypt data');
}
}
/**
* 解密文本
* @param {string} encryptedData - 加密的数据Base64 编码
* @returns {string} 解密后的明文
*/
export function decrypt(encryptedData) {
if (!encryptedData) return encryptedData;
try {
const key = getEncryptionKey();
const combined = Buffer.from(encryptedData, 'base64');
// 提取 IV、加密数据和认证标签
const iv = combined.subarray(0, IV_LENGTH);
const tag = combined.subarray(combined.length - TAG_LENGTH);
const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
decipher.setAuthTag(tag);
let decrypted = decipher.update(encrypted, undefined, 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
} catch (error) {
console.error('Decryption error:', error);
throw new Error('Failed to decrypt data');
}
}
/**
* 生成随机加密密钥用于初始化
* @returns {string} Base64 编码的随机密钥
*/
export function generateEncryptionSecret() {
return crypto.randomBytes(32).toString('base64');
}
/**
* 哈希敏感数据单向用于比较
* @param {string} data - 要哈希的数据
* @returns {string} 哈希值
*/
export function hash(data) {
return crypto.createHash('sha256').update(data).digest('hex');
}

51
local-proxy/db/init.js Normal file
View File

@ -0,0 +1,51 @@
/**
* 数据库初始化脚本
*/
import { prisma } from './client.js';
/**
* 测试数据库连接
* @returns {Promise<boolean>} 连接是否成功
*/
export async function testConnection() {
try {
await prisma.$queryRaw`SELECT 1`;
console.log('[DB] Database connection successful');
return true;
} catch (error) {
console.error('[DB] Database connection failed:', error.message);
return false;
}
}
/**
* 初始化数据库
* - 测试连接
* - 可选创建默认数据
*/
export async function initDatabase() {
console.log('[DB] Initializing database...');
const connected = await testConnection();
if (!connected) {
throw new Error('Failed to connect to database');
}
console.log('[DB] Database initialized successfully');
return true;
}
/**
* 优雅关闭数据库连接
*/
export async function closeDatabase() {
try {
await prisma.$disconnect();
console.log('[DB] Database connection closed');
} catch (error) {
console.error('[DB] Error closing database connection:', error.message);
}
}
export default { initDatabase, testConnection, closeDatabase };

54
local-proxy/deploy.sh Executable file
View File

@ -0,0 +1,54 @@
#!/bin/bash
# Paper Burner Local Proxy 一键部署脚本
set -e
cd "$(dirname "$0")"
echo "🚀 Paper Burner Local Proxy 部署脚本"
echo "======================================"
# 检查 Docker
if ! command -v docker &> /dev/null; then
echo "❌ Docker 未安装,请先安装 Docker"
exit 1
fi
if ! docker info &> /dev/null; then
echo "❌ Docker 未运行,请启动 Docker"
exit 1
fi
echo "✅ Docker 可用"
# 启动 PostgreSQL
echo ""
echo "📦 启动 PostgreSQL..."
docker compose up -d
# 等待数据库就绪
echo "⏳ 等待数据库就绪..."
for i in {1..30}; do
if docker exec paperburner-postgres pg_isready -U paperburner &> /dev/null; then
echo "✅ PostgreSQL 已就绪"
break
fi
sleep 1
done
# 运行数据库迁移
echo ""
echo "🔧 运行数据库迁移..."
npx prisma@5 migrate deploy
echo ""
echo "✅ 部署完成!"
echo ""
echo "数据库连接信息:"
echo " Host: localhost:15432"
echo " User: paperburner"
echo " Password: paperburner123"
echo " Database: paperburner"
echo ""
echo "启动服务器: npm start"
echo "停止数据库: docker compose down"

View File

@ -0,0 +1,21 @@
services:
postgres:
image: postgres:15-alpine
container_name: paperburner-postgres
restart: unless-stopped
environment:
POSTGRES_USER: paperburner
POSTGRES_PASSWORD: paperburner123
POSTGRES_DB: paperburner
ports:
- "15432:5432"
volumes:
- paperburner-pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U paperburner"]
interval: 5s
timeout: 5s
retries: 5
volumes:
paperburner-pgdata:

View File

@ -9,17 +9,103 @@
"version": "1.0.0", "version": "1.0.0",
"license": "GPL-2.0", "license": "GPL-2.0",
"dependencies": { "dependencies": {
"@prisma/client": "^5.22.0",
"ali-oss": "^6.23.0", "ali-oss": "^6.23.0",
"axios": "^1.13.6", "axios": "^1.13.6",
"dotenv": "^17.3.1", "dotenv": "^17.3.1",
"express": "^4.21.1",
"form-data": "^4.0.5", "form-data": "^4.0.5",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
"require": "^2.4.20" "require": "^2.4.20"
}, },
"devDependencies": {
"prisma": "^5.22.0"
},
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
} }
}, },
"node_modules/@prisma/client": {
"version": "5.22.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@prisma/client/-/client-5.22.0.tgz",
"integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==",
"hasInstallScript": true,
"license": "Apache-2.0",
"engines": {
"node": ">=16.13"
},
"peerDependencies": {
"prisma": "*"
},
"peerDependenciesMeta": {
"prisma": {
"optional": true
}
}
},
"node_modules/@prisma/debug": {
"version": "5.22.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@prisma/debug/-/debug-5.22.0.tgz",
"integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/@prisma/engines": {
"version": "5.22.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@prisma/engines/-/engines-5.22.0.tgz",
"integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==",
"devOptional": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "5.22.0",
"@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
"@prisma/fetch-engine": "5.22.0",
"@prisma/get-platform": "5.22.0"
}
},
"node_modules/@prisma/engines-version": {
"version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
"resolved": "https://mirrors.cloud.tencent.com/npm/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz",
"integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/@prisma/fetch-engine": {
"version": "5.22.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz",
"integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "5.22.0",
"@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
"@prisma/get-platform": "5.22.0"
}
},
"node_modules/@prisma/get-platform": {
"version": "5.22.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@prisma/get-platform/-/get-platform-5.22.0.tgz",
"integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "5.22.0"
}
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://mirrors.cloud.tencent.com/npm/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"license": "MIT",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/address": { "node_modules/address": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmmirror.com/address/-/address-1.2.2.tgz", "resolved": "https://registry.npmmirror.com/address/-/address-1.2.2.tgz",
@ -92,6 +178,12 @@
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://mirrors.cloud.tencent.com/npm/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
"node_modules/async": { "node_modules/async": {
"version": "0.2.10", "version": "0.2.10",
"resolved": "https://registry.npmmirror.com/async/-/async-0.2.10.tgz", "resolved": "https://registry.npmmirror.com/async/-/async-0.2.10.tgz",
@ -114,6 +206,72 @@
"proxy-from-env": "^1.1.0" "proxy-from-env": "^1.1.0"
} }
}, },
"node_modules/body-parser": {
"version": "1.20.4",
"resolved": "https://mirrors.cloud.tencent.com/npm/body-parser/-/body-parser-1.20.4.tgz",
"integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "~1.2.0",
"http-errors": "~2.0.1",
"iconv-lite": "~0.4.24",
"on-finished": "~2.4.1",
"qs": "~6.14.0",
"raw-body": "~2.5.3",
"type-is": "~1.6.18",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/body-parser/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://mirrors.cloud.tencent.com/npm/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/body-parser/node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://mirrors.cloud.tencent.com/npm/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/body-parser/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/body-parser/node_modules/qs": {
"version": "6.14.2",
"resolved": "https://mirrors.cloud.tencent.com/npm/qs/-/qs-6.14.2.tgz",
"integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/bowser": { "node_modules/bowser": {
"version": "1.9.4", "version": "1.9.4",
"resolved": "https://registry.npmmirror.com/bowser/-/bowser-1.9.4.tgz", "resolved": "https://registry.npmmirror.com/bowser/-/bowser-1.9.4.tgz",
@ -126,6 +284,15 @@
"integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://mirrors.cloud.tencent.com/npm/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind-apply-helpers": { "node_modules/call-bind-apply-helpers": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
@ -167,6 +334,38 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://mirrors.cloud.tencent.com/npm/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"license": "MIT",
"dependencies": {
"safe-buffer": "5.2.1"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/content-disposition/node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://mirrors.cloud.tencent.com/npm/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/content-type": { "node_modules/content-type": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz",
@ -176,6 +375,21 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://mirrors.cloud.tencent.com/npm/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.0.7",
"resolved": "https://mirrors.cloud.tencent.com/npm/cookie-signature/-/cookie-signature-1.0.7.tgz",
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
"license": "MIT"
},
"node_modules/copy-to": { "node_modules/copy-to": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmmirror.com/copy-to/-/copy-to-2.0.1.tgz", "resolved": "https://registry.npmmirror.com/copy-to/-/copy-to-2.0.1.tgz",
@ -244,6 +458,15 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/destroy": { "node_modules/destroy": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz", "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz",
@ -295,6 +518,15 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/end-of-stream": { "node_modules/end-of-stream": {
"version": "1.4.5", "version": "1.4.5",
"resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz", "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz",
@ -364,6 +596,120 @@
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://mirrors.cloud.tencent.com/npm/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express": {
"version": "4.22.1",
"resolved": "https://mirrors.cloud.tencent.com/npm/express/-/express-4.22.1.tgz",
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "~1.20.3",
"content-disposition": "~0.5.4",
"content-type": "~1.0.4",
"cookie": "~0.7.1",
"cookie-signature": "~1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "~1.3.1",
"fresh": "~0.5.2",
"http-errors": "~2.0.0",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "~2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "~0.1.12",
"proxy-addr": "~2.0.7",
"qs": "~6.14.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "~0.19.0",
"serve-static": "~1.16.2",
"setprototypeof": "1.2.0",
"statuses": "~2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/express/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://mirrors.cloud.tencent.com/npm/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/express/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/express/node_modules/qs": {
"version": "6.14.2",
"resolved": "https://mirrors.cloud.tencent.com/npm/qs/-/qs-6.14.2.tgz",
"integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/express/node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://mirrors.cloud.tencent.com/npm/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/express/node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://mirrors.cloud.tencent.com/npm/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/extend-shallow": { "node_modules/extend-shallow": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz",
@ -399,6 +745,48 @@
"node": "^12.20 || >= 14.13" "node": "^12.20 || >= 14.13"
} }
}, },
"node_modules/finalhandler": {
"version": "1.3.2",
"resolved": "https://mirrors.cloud.tencent.com/npm/finalhandler/-/finalhandler-1.3.2.tgz",
"integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"on-finished": "~2.4.1",
"parseurl": "~1.3.3",
"statuses": "~2.0.2",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/finalhandler/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://mirrors.cloud.tencent.com/npm/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/finalhandler/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/finalhandler/node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://mirrors.cloud.tencent.com/npm/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.11", "version": "1.15.11",
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz",
@ -459,6 +847,38 @@
"pause-stream": "~0.0.11" "pause-stream": "~0.0.11"
} }
}, },
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://mirrors.cloud.tencent.com/npm/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://mirrors.cloud.tencent.com/npm/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": { "node_modules/function-bind": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
@ -562,6 +982,35 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://mirrors.cloud.tencent.com/npm/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"license": "MIT",
"dependencies": {
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/http-errors/node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://mirrors.cloud.tencent.com/npm/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/humanize-ms": { "node_modules/humanize-ms": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmmirror.com/humanize-ms/-/humanize-ms-1.2.1.tgz", "resolved": "https://registry.npmmirror.com/humanize-ms/-/humanize-ms-1.2.1.tgz",
@ -589,6 +1038,15 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://mirrors.cloud.tencent.com/npm/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/is-class-hotfix": { "node_modules/is-class-hotfix": {
"version": "0.0.6", "version": "0.0.6",
"resolved": "https://registry.npmmirror.com/is-class-hotfix/-/is-class-hotfix-0.0.6.tgz", "resolved": "https://registry.npmmirror.com/is-class-hotfix/-/is-class-hotfix-0.0.6.tgz",
@ -654,6 +1112,15 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/merge-descriptors": { "node_modules/merge-descriptors": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz", "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
@ -663,6 +1130,15 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://mirrors.cloud.tencent.com/npm/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime": { "node_modules/mime": {
"version": "2.6.0", "version": "2.6.0",
"resolved": "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz", "resolved": "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz",
@ -734,6 +1210,15 @@
"thenify-all": "^1.0.0" "thenify-all": "^1.0.0"
} }
}, },
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://mirrors.cloud.tencent.com/npm/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/node-domexception": { "node_modules/node-domexception": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
@ -802,6 +1287,18 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://mirrors.cloud.tencent.com/npm/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/once": { "node_modules/once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
@ -851,6 +1348,21 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://mirrors.cloud.tencent.com/npm/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-to-regexp": {
"version": "0.1.12",
"resolved": "https://mirrors.cloud.tencent.com/npm/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT"
},
"node_modules/pause-stream": { "node_modules/pause-stream": {
"version": "0.0.11", "version": "0.0.11",
"resolved": "https://registry.npmmirror.com/pause-stream/-/pause-stream-0.0.11.tgz", "resolved": "https://registry.npmmirror.com/pause-stream/-/pause-stream-0.0.11.tgz",
@ -869,12 +1381,45 @@
"integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/prisma": {
"version": "5.22.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/prisma/-/prisma-5.22.0.tgz",
"integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==",
"devOptional": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/engines": "5.22.0"
},
"bin": {
"prisma": "build/index.js"
},
"engines": {
"node": ">=16.13"
},
"optionalDependencies": {
"fsevents": "2.3.3"
}
},
"node_modules/process-nextick-args": { "node_modules/process-nextick-args": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://mirrors.cloud.tencent.com/npm/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"license": "MIT",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/proxy-from-env": { "node_modules/proxy-from-env": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@ -906,6 +1451,42 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://mirrors.cloud.tencent.com/npm/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "2.5.3",
"resolved": "https://mirrors.cloud.tencent.com/npm/raw-body/-/raw-body-2.5.3.tgz",
"integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"http-errors": "~2.0.1",
"iconv-lite": "~0.4.24",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/raw-body/node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://mirrors.cloud.tencent.com/npm/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/readable-stream": { "node_modules/readable-stream": {
"version": "2.3.8", "version": "2.3.8",
"resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz",
@ -976,6 +1557,87 @@
"semver": "bin/semver" "semver": "bin/semver"
} }
}, },
"node_modules/send": {
"version": "0.19.2",
"resolved": "https://mirrors.cloud.tencent.com/npm/send/-/send-0.19.2.tgz",
"integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "~0.5.2",
"http-errors": "~2.0.1",
"mime": "1.6.0",
"ms": "2.1.3",
"on-finished": "~2.4.1",
"range-parser": "~1.2.1",
"statuses": "~2.0.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/send/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://mirrors.cloud.tencent.com/npm/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/send/node_modules/debug/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/send/node_modules/mime": {
"version": "1.6.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"license": "MIT",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/send/node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://mirrors.cloud.tencent.com/npm/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/serve-static": {
"version": "1.16.3",
"resolved": "https://mirrors.cloud.tencent.com/npm/serve-static/-/serve-static-1.16.3.tgz",
"integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
"license": "MIT",
"dependencies": {
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "~0.19.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/side-channel": { "node_modules/side-channel": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz",
@ -1140,6 +1802,28 @@
"integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==", "integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://mirrors.cloud.tencent.com/npm/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://mirrors.cloud.tencent.com/npm/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"license": "MIT",
"dependencies": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/uglify-js": { "node_modules/uglify-js": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmmirror.com/uglify-js/-/uglify-js-2.3.0.tgz", "resolved": "https://registry.npmmirror.com/uglify-js/-/uglify-js-2.3.0.tgz",
@ -1168,6 +1852,15 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/urllib": { "node_modules/urllib": {
"version": "2.44.0", "version": "2.44.0",
"resolved": "https://registry.npmmirror.com/urllib/-/urllib-2.44.0.tgz", "resolved": "https://registry.npmmirror.com/urllib/-/urllib-2.44.0.tgz",
@ -1221,6 +1914,24 @@
"node": ">= 0.12.0" "node": ">= 0.12.0"
} }
}, },
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://mirrors.cloud.tencent.com/npm/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"license": "MIT",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://mirrors.cloud.tencent.com/npm/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/web-streams-polyfill": { "node_modules/web-streams-polyfill": {
"version": "3.3.3", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",

View File

@ -16,13 +16,18 @@
], ],
"license": "GPL-2.0", "license": "GPL-2.0",
"dependencies": { "dependencies": {
"@prisma/client": "^5.22.0",
"ali-oss": "^6.23.0", "ali-oss": "^6.23.0",
"axios": "^1.13.6", "axios": "^1.13.6",
"dotenv": "^17.3.1", "dotenv": "^17.3.1",
"express": "^4.21.1",
"form-data": "^4.0.5", "form-data": "^4.0.5",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
"require": "^2.4.20" "require": "^2.4.20"
}, },
"devDependencies": {
"prisma": "^5.22.0"
},
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
} }

View File

@ -0,0 +1,345 @@
-- CreateEnum
CREATE TYPE "KeyStatus" AS ENUM ('UNTESTED', 'VALID', 'INVALID', 'TESTING');
-- CreateEnum
CREATE TYPE "DocStatus" AS ENUM ('PENDING', 'PROCESSING', 'OCR_COMPLETED', 'TRANSLATION_COMPLETED', 'COMPLETED', 'FAILED');
-- CreateTable
CREATE TABLE "users" (
"id" TEXT NOT NULL,
"name" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "user_settings" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"ocrProvider" TEXT,
"ocrApiKey" TEXT,
"translationModel" TEXT,
"targetLanguage" TEXT NOT NULL DEFAULT 'chinese',
"customTargetLanguageName" TEXT,
"maxTokensPerChunk" INTEGER NOT NULL DEFAULT 2000,
"translationConcurrency" INTEGER NOT NULL DEFAULT 15,
"defaultSystemPrompt" TEXT,
"defaultUserPromptTemplate" TEXT,
"useCustomPrompts" BOOLEAN NOT NULL DEFAULT false,
"enableGlossary" BOOLEAN NOT NULL DEFAULT false,
"batchModeEnabled" BOOLEAN NOT NULL DEFAULT false,
"batchModeTemplate" TEXT,
"batchModeFormats" TEXT[],
"batchModeZipEnabled" BOOLEAN NOT NULL DEFAULT false,
"ocrConfig" JSONB,
"academicSearchConfig" JSONB,
"uiLayoutConfig" JSONB,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "user_settings_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "api_keys" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"provider" TEXT NOT NULL,
"keyValue" TEXT NOT NULL,
"remark" TEXT,
"status" "KeyStatus" NOT NULL DEFAULT 'UNTESTED',
"order" INTEGER NOT NULL DEFAULT 0,
"lastUsedAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "api_keys_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "custom_source_sites" (
"id" TEXT NOT NULL,
"userId" TEXT,
"displayName" TEXT NOT NULL,
"apiBaseUrl" TEXT NOT NULL,
"modelId" TEXT,
"availableModels" TEXT[],
"requestFormat" TEXT NOT NULL DEFAULT 'openai',
"temperature" DOUBLE PRECISION NOT NULL DEFAULT 0.5,
"maxTokens" INTEGER NOT NULL DEFAULT 8000,
"endpointMode" TEXT NOT NULL DEFAULT 'auto',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "custom_source_sites_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "documents" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"fileName" TEXT NOT NULL,
"fileSize" INTEGER,
"fileType" TEXT NOT NULL,
"filePath" TEXT,
"status" "DocStatus" NOT NULL DEFAULT 'PENDING',
"ocrProvider" TEXT,
"ocrText" TEXT,
"ocrMetadata" JSONB,
"translationModel" TEXT,
"translatedText" TEXT,
"translationMetadata" JSONB,
"summary" TEXT,
"toc" JSONB,
"metadata" JSONB,
"processingTime" INTEGER,
"errorMessage" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "documents_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "annotations" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"documentId" TEXT NOT NULL,
"type" TEXT NOT NULL,
"color" TEXT,
"startIndex" INTEGER NOT NULL,
"endIndex" INTEGER NOT NULL,
"text" TEXT NOT NULL,
"note" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "annotations_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "semantic_groups" (
"id" TEXT NOT NULL,
"documentId" TEXT NOT NULL,
"groups" JSONB NOT NULL,
"version" TEXT,
"source" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "semantic_groups_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "glossaries" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"entries" JSONB NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "glossaries_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "system_config" (
"id" TEXT NOT NULL,
"key" TEXT NOT NULL,
"value" TEXT NOT NULL,
"description" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "system_config_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "processed_files" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"fileIdentifier" TEXT NOT NULL,
"fileName" TEXT NOT NULL,
"processedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "processed_files_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "user_quotas" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"maxDocumentsPerDay" INTEGER NOT NULL DEFAULT -1,
"maxDocumentsPerMonth" INTEGER NOT NULL DEFAULT -1,
"maxStorageSize" INTEGER NOT NULL DEFAULT -1,
"maxApiKeysCount" INTEGER NOT NULL DEFAULT -1,
"documentsThisMonth" INTEGER NOT NULL DEFAULT 0,
"currentStorageUsed" INTEGER NOT NULL DEFAULT 0,
"lastMonthlyReset" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "user_quotas_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "usage_logs" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"action" TEXT NOT NULL,
"resourceId" TEXT,
"metadata" JSONB,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "usage_logs_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "chat_messages" (
"id" TEXT NOT NULL,
"documentId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"role" TEXT NOT NULL,
"content" TEXT NOT NULL,
"timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"metadata" JSONB,
CONSTRAINT "chat_messages_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "references" (
"id" TEXT NOT NULL,
"documentId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"citationKey" TEXT NOT NULL,
"doi" TEXT,
"title" TEXT,
"authors" JSONB,
"year" INTEGER,
"journal" TEXT,
"volume" TEXT,
"pages" TEXT,
"url" TEXT,
"metadata" JSONB,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "references_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "prompt_pool" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"prompts" JSONB NOT NULL,
"healthConfig" JSONB,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "prompt_pool_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "users_id_idx" ON "users"("id");
-- CreateIndex
CREATE UNIQUE INDEX "user_settings_userId_key" ON "user_settings"("userId");
-- CreateIndex
CREATE INDEX "api_keys_userId_provider_idx" ON "api_keys"("userId", "provider");
-- CreateIndex
CREATE INDEX "custom_source_sites_userId_idx" ON "custom_source_sites"("userId");
-- CreateIndex
CREATE INDEX "documents_userId_createdAt_idx" ON "documents"("userId", "createdAt");
-- CreateIndex
CREATE INDEX "documents_status_idx" ON "documents"("status");
-- CreateIndex
CREATE INDEX "annotations_documentId_idx" ON "annotations"("documentId");
-- CreateIndex
CREATE INDEX "annotations_userId_idx" ON "annotations"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "semantic_groups_documentId_key" ON "semantic_groups"("documentId");
-- CreateIndex
CREATE INDEX "glossaries_userId_idx" ON "glossaries"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "system_config_key_key" ON "system_config"("key");
-- CreateIndex
CREATE INDEX "processed_files_userId_idx" ON "processed_files"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "processed_files_userId_fileIdentifier_key" ON "processed_files"("userId", "fileIdentifier");
-- CreateIndex
CREATE UNIQUE INDEX "user_quotas_userId_key" ON "user_quotas"("userId");
-- CreateIndex
CREATE INDEX "usage_logs_userId_createdAt_idx" ON "usage_logs"("userId", "createdAt");
-- CreateIndex
CREATE INDEX "usage_logs_action_createdAt_idx" ON "usage_logs"("action", "createdAt");
-- CreateIndex
CREATE INDEX "chat_messages_documentId_timestamp_idx" ON "chat_messages"("documentId", "timestamp");
-- CreateIndex
CREATE INDEX "references_documentId_idx" ON "references"("documentId");
-- CreateIndex
CREATE UNIQUE INDEX "references_documentId_citationKey_key" ON "references"("documentId", "citationKey");
-- CreateIndex
CREATE UNIQUE INDEX "prompt_pool_userId_key" ON "prompt_pool"("userId");
-- AddForeignKey
ALTER TABLE "user_settings" ADD CONSTRAINT "user_settings_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "api_keys" ADD CONSTRAINT "api_keys_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "documents" ADD CONSTRAINT "documents_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "annotations" ADD CONSTRAINT "annotations_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "annotations" ADD CONSTRAINT "annotations_documentId_fkey" FOREIGN KEY ("documentId") REFERENCES "documents"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "semantic_groups" ADD CONSTRAINT "semantic_groups_documentId_fkey" FOREIGN KEY ("documentId") REFERENCES "documents"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "glossaries" ADD CONSTRAINT "glossaries_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "processed_files" ADD CONSTRAINT "processed_files_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "user_quotas" ADD CONSTRAINT "user_quotas_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "chat_messages" ADD CONSTRAINT "chat_messages_documentId_fkey" FOREIGN KEY ("documentId") REFERENCES "documents"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "chat_messages" ADD CONSTRAINT "chat_messages_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "references" ADD CONSTRAINT "references_documentId_fkey" FOREIGN KEY ("documentId") REFERENCES "documents"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "references" ADD CONSTRAINT "references_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "prompt_pool" ADD CONSTRAINT "prompt_pool_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"

View File

@ -0,0 +1,379 @@
// Prisma Schema for Paper Burner X Local Proxy
// 适配外部认证系统User.id 为 String 类型,存储外部系统的用户 ID
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ==================== 用户与认证 ====================
// 注意:认证由外部系统完成,此处仅存储用户关联数据
model User {
id String @id // 外部系统的用户 ID (如 "773932955797029230")
name String?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// 关联
settings UserSettings?
documents Document[]
apiKeys ApiKey[]
glossaries Glossary[]
annotations Annotation[]
processedFiles ProcessedFile[]
chatMessages ChatMessage[]
references Reference[]
promptPool PromptPool?
quota UserQuota?
@@index([id])
@@map("users")
}
// ==================== 用户设置 ====================
model UserSettings {
id String @id @default(uuid())
userId String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
// OCR 设置
ocrProvider String? // 'mineru' | 'doc2x'
ocrApiKey String? @db.Text
// 翻译设置
translationModel String? // 'deepseek' | 'gemini' | 'claude' | 'custom'
targetLanguage String @default("chinese")
customTargetLanguageName String?
maxTokensPerChunk Int @default(2000)
translationConcurrency Int @default(15)
// 提示词设置
defaultSystemPrompt String? @db.Text
defaultUserPromptTemplate String? @db.Text
useCustomPrompts Boolean @default(false)
// 其他设置
enableGlossary Boolean @default(false)
batchModeEnabled Boolean @default(false)
batchModeTemplate String?
batchModeFormats String[] // ['original', 'markdown', 'pdf']
batchModeZipEnabled Boolean @default(false)
// 扩展配置JSON 字段)
ocrConfig Json? // OCR 引擎和详细配置
academicSearchConfig Json? // 学术搜索配置
uiLayoutConfig Json? // UI 布局配置
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("user_settings")
}
// ==================== API Keys 管理 ====================
model ApiKey {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
provider String // 'mistral' | 'deepseek' | 'gemini' | 'claude' | 'tongyi' | 'volcano' | 'custom_source_xxx'
keyValue String @db.Text // 加密存储
remark String?
status KeyStatus @default(UNTESTED)
order Int @default(0)
lastUsedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId, provider])
@@map("api_keys")
}
enum KeyStatus {
UNTESTED
VALID
INVALID
TESTING
}
// ==================== 自定义模型源站配置 ====================
model CustomSourceSite {
id String @id @default(uuid())
userId String? // null = 管理员全局配置
displayName String
apiBaseUrl String
modelId String?
availableModels String[] // JSON array
requestFormat String @default("openai") // 'openai' | 'anthropic' | 'custom'
temperature Float @default(0.5)
maxTokens Int @default(8000)
endpointMode String @default("auto")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@map("custom_source_sites")
}
// ==================== 文档处理历史 ====================
model Document {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
// 基本信息
fileName String
fileSize Int?
fileType String
filePath String? // 如果启用文件上传存储
// 处理状态
status DocStatus @default(PENDING)
// OCR 结果
ocrProvider String?
ocrText String? @db.Text
ocrMetadata Json?
// 翻译结果
translationModel String?
translatedText String? @db.Text
translationMetadata Json?
// 其他数据
summary String? @db.Text
toc Json? // Table of Contents
metadata Json? // 其他元数据
processingTime Int? // 处理时间(毫秒)
errorMessage String? @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// 关联
annotations Annotation[]
semanticGroups SemanticGroup[]
chatMessages ChatMessage[]
references Reference[]
@@index([userId, createdAt])
@@index([status])
@@map("documents")
}
enum DocStatus {
PENDING
PROCESSING
OCR_COMPLETED
TRANSLATION_COMPLETED
COMPLETED
FAILED
}
// ==================== 高亮与标注 ====================
model Annotation {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
documentId String
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
type String // 'highlight' | 'note'
color String?
startIndex Int
endIndex Int
text String @db.Text
note String? @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([documentId])
@@index([userId])
@@map("annotations")
}
// ==================== 意群数据 ====================
model SemanticGroup {
id String @id @default(uuid())
documentId String
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
groups Json // 存储意群数组
version String?
source String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([documentId])
@@map("semantic_groups")
}
// ==================== 翻译术语库 ====================
model Glossary {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
name String
enabled Boolean @default(true)
entries Json // 存储术语条目数组
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@map("glossaries")
}
// ==================== 系统配置(管理员)====================
model SystemConfig {
id String @id @default(uuid())
key String @unique
value String @db.Text
description String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("system_config")
}
// ==================== 已处理文件记录 ====================
model ProcessedFile {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
fileIdentifier String // 文件标识符 (name + size + lastModified)
fileName String
processedAt DateTime @default(now())
@@unique([userId, fileIdentifier])
@@index([userId])
@@map("processed_files")
}
// ==================== 用户配额管理 ====================
model UserQuota {
id String @id @default(uuid())
userId String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
// 配额限制 (-1 表示无限制)
maxDocumentsPerDay Int @default(-1)
maxDocumentsPerMonth Int @default(-1)
maxStorageSize Int @default(-1) // MB
maxApiKeysCount Int @default(-1)
// 当前使用量
documentsThisMonth Int @default(0)
currentStorageUsed Int @default(0) // MB
// 重置时间
lastMonthlyReset DateTime @default(now())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("user_quotas")
}
// ==================== 使用量日志 ====================
model UsageLog {
id String @id @default(uuid())
userId String
action String // 'ocr', 'translate', 'document_create', etc.
resourceId String? // 关联的资源ID如文档ID
metadata Json? // 额外元数据
createdAt DateTime @default(now())
@@index([userId, createdAt])
@@index([action, createdAt])
@@map("usage_logs")
}
// ==================== 聊天消息 ====================
model ChatMessage {
id String @id @default(uuid())
documentId String
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
role String // 'user' | 'assistant'
content String @db.Text
timestamp DateTime @default(now())
metadata Json? // 扩展信息模型、token 等)
@@index([documentId, timestamp])
@@map("chat_messages")
}
// ==================== 文献引用 ====================
model Reference {
id String @id @default(uuid())
documentId String
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
citationKey String // 引用标识符(如 "[1]"
doi String?
title String?
authors Json? // [{name, affiliation}]
year Int?
journal String?
volume String?
pages String?
url String?
metadata Json? // 完整元数据
createdAt DateTime @default(now())
@@unique([documentId, citationKey])
@@index([documentId])
@@map("references")
}
// ==================== 提示词池 ====================
model PromptPool {
id String @id @default(uuid())
userId String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
prompts Json // 提示词数组
healthConfig Json? // 健康检查配置
updatedAt DateTime @default(now()) @updatedAt
@@map("prompt_pool")
}

242
local-proxy/routes/chat.js Normal file
View File

@ -0,0 +1,242 @@
/**
* 聊天历史路由
* 复用 server/src/routes/chat.js 的逻辑
*/
import express from 'express';
import { prisma } from '../db/client.js';
const router = express.Router();
// 允许的聊天角色
const ALLOWED_ROLES = ['user', 'assistant'];
// UUID 验证
function isValidUUID(id) {
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id);
}
// 获取文档的聊天历史
router.get('/:documentId/history', async (req, res, next) => {
try {
const { documentId } = req.params;
const { limit = 100, before } = req.query;
// 验证 UUID 格式
if (!isValidUUID(documentId)) {
return res.status(400).json({ error: 'Invalid document ID format' });
}
// 验证和规范化参数
const limitNum = Math.min(Math.max(parseInt(limit) || 100, 1), 1000);
// 验证文档所有权
const document = await prisma.document.findFirst({
where: {
id: documentId,
userId: req.user.id
}
});
if (!document) {
return res.status(404).json({ error: 'Document not found' });
}
// 构建查询条件
const where = {
documentId,
userId: req.user.id
};
if (before) {
where.timestamp = { lt: new Date(before) };
}
// 获取消息
const messages = await prisma.chatMessage.findMany({
where,
orderBy: { timestamp: 'desc' },
take: limitNum,
select: {
id: true,
role: true,
content: true,
timestamp: true,
metadata: true
}
});
// 反转顺序,使最早的消息在前
const sortedMessages = messages.reverse();
res.json({
messages: sortedMessages,
hasMore: messages.length === limitNum
});
} catch (error) {
next(error);
}
});
// 添加聊天消息
router.post('/:documentId/history', async (req, res, next) => {
try {
const { documentId } = req.params;
const { role, content, metadata } = req.body;
// 验证 UUID 格式
if (!isValidUUID(documentId)) {
return res.status(400).json({ error: 'Invalid document ID format' });
}
// 输入验证
if (!role || typeof role !== 'string') {
return res.status(400).json({ error: 'Role is required' });
}
if (!ALLOWED_ROLES.includes(role)) {
return res.status(400).json({ error: `Role must be one of: ${ALLOWED_ROLES.join(', ')}` });
}
if (!content || typeof content !== 'string') {
return res.status(400).json({ error: 'Content is required' });
}
// 限制内容长度
const maxContentLength = 100000;
if (content.length > maxContentLength) {
return res.status(400).json({ error: `Content too long (max ${maxContentLength} characters)` });
}
// 验证文档所有权
const document = await prisma.document.findFirst({
where: {
id: documentId,
userId: req.user.id
}
});
if (!document) {
return res.status(404).json({ error: 'Document not found' });
}
// 创建消息
const message = await prisma.chatMessage.create({
data: {
documentId,
userId: req.user.id,
role,
content,
metadata
}
});
res.status(201).json(message);
} catch (error) {
next(error);
}
});
// 批量添加聊天消息
router.post('/:documentId/history/batch', async (req, res, next) => {
try {
const { documentId } = req.params;
const { messages } = req.body;
// 验证 UUID 格式
if (!isValidUUID(documentId)) {
return res.status(400).json({ error: 'Invalid document ID format' });
}
// 输入验证
if (!Array.isArray(messages)) {
return res.status(400).json({ error: 'Messages must be an array' });
}
// 限制批量大小
const maxBatchSize = 1000;
if (messages.length > maxBatchSize) {
return res.status(400).json({ error: `Batch size too large (max ${maxBatchSize} messages)` });
}
// 验证文档所有权
const document = await prisma.document.findFirst({
where: {
id: documentId,
userId: req.user.id
}
});
if (!document) {
return res.status(404).json({ error: 'Document not found' });
}
// 验证每条消息的基本格式
const validMessages = messages.filter(msg => {
return msg && typeof msg === 'object' &&
ALLOWED_ROLES.includes(msg.role) &&
typeof msg.content === 'string';
});
if (validMessages.length !== messages.length) {
return res.status(400).json({ error: 'Some messages have invalid format' });
}
// 批量创建消息
const createdMessages = await prisma.chatMessage.createMany({
data: validMessages.map(msg => ({
documentId,
userId: req.user.id,
role: msg.role,
content: msg.content,
timestamp: msg.timestamp ? new Date(msg.timestamp) : undefined,
metadata: msg.metadata
})),
skipDuplicates: true
});
res.status(201).json({
success: true,
count: createdMessages.count
});
} catch (error) {
next(error);
}
});
// 清空文档的聊天历史
router.delete('/:documentId/history', async (req, res, next) => {
try {
const { documentId } = req.params;
// 验证 UUID 格式
if (!isValidUUID(documentId)) {
return res.status(400).json({ error: 'Invalid document ID format' });
}
// 验证文档所有权
const document = await prisma.document.findFirst({
where: {
id: documentId,
userId: req.user.id
}
});
if (!document) {
return res.status(404).json({ error: 'Document not found' });
}
await prisma.chatMessage.deleteMany({
where: {
documentId,
userId: req.user.id
}
});
res.json({ success: true });
} catch (error) {
next(error);
}
});
export default router;

View File

@ -0,0 +1,404 @@
/**
* 文档路由
* 复用 server/src/routes/document.js 的逻辑
*/
import express from 'express';
import { prisma } from '../db/client.js';
const router = express.Router();
// 允许的状态值白名单
const ALLOWED_STATUSES = ['PENDING', 'PROCESSING', 'COMPLETED', 'FAILED'];
// ==================== 文档 CRUD ====================
// 获取文档列表
router.get('/', async (req, res, next) => {
try {
const { page = 1, limit = 20, status } = req.query;
const where = {
userId: req.user.id,
...(status && ALLOWED_STATUSES.includes(status) && { status })
};
const pageNum = Math.max(parseInt(page) || 1, 1);
const limitNum = Math.min(Math.max(parseInt(limit) || 20, 1), 100);
const documents = await prisma.document.findMany({
where,
orderBy: { createdAt: 'desc' },
skip: (pageNum - 1) * limitNum,
take: limitNum,
select: {
id: true,
fileName: true,
fileSize: true,
fileType: true,
status: true,
ocrProvider: true,
ocrText: true,
translationModel: true,
translatedText: true,
processingTime: true,
createdAt: true,
updatedAt: true,
metadata: true,
ocrMetadata: true,
translationMetadata: true,
summary: true,
toc: true
}
});
const total = await prisma.document.count({ where });
// 字段映射:适配前端期望的字段名,并从 metadata 中提取嵌套字段
const mappedDocs = documents.map(doc => {
const meta = doc.metadata || {};
return {
...doc,
name: doc.fileName,
size: doc.fileSize,
time: doc.createdAt,
ocr: doc.ocrText,
translation: doc.translatedText,
ocrEngine: doc.ocrProvider,
translationModelName: doc.translationModel,
// 从 metadata 中提取的字段
ocrChunks: meta.ocrChunks || [],
translatedChunks: meta.translatedChunks || [],
images: meta.images || [],
metadata: meta
};
});
res.json({
documents: mappedDocs,
pagination: {
page: pageNum,
limit: limitNum,
total,
totalPages: Math.ceil(total / limitNum)
}
});
} catch (error) {
next(error);
}
});
// 获取单个文档详情
router.get('/:id', async (req, res, next) => {
try {
const { id } = req.params;
const document = await prisma.document.findFirst({
where: {
id,
userId: req.user.id
},
include: {
annotations: true,
semanticGroups: true
}
});
if (!document) {
return res.status(404).json({ error: 'Document not found' });
}
// 字段映射:适配前端期望的字段名,并从 metadata 中提取嵌套字段
const meta = document.metadata || {};
const mappedDoc = {
...document,
name: document.fileName,
size: document.fileSize,
time: document.createdAt,
ocr: document.ocrText,
translation: document.translatedText,
ocrEngine: document.ocrProvider,
translationModelName: document.translationModel,
// 从 metadata 中提取的字段
ocrChunks: meta.ocrChunks || [],
translatedChunks: meta.translatedChunks || [],
images: meta.images || [],
metadata: meta
};
res.json(mappedDoc);
} catch (error) {
next(error);
}
});
// 创建文档记录
router.post('/', async (req, res, next) => {
try {
const body = { ...req.body };
// 前端字段映射name -> fileName, size -> fileSize
if (body.name && !body.fileName) {
body.fileName = body.name;
}
if (body.size && !body.fileSize) {
body.fileSize = body.size;
}
// Schema 中定义的字段
const schemaFields = {
fileName: body.fileName || body.name,
fileSize: body.fileSize || body.size,
fileType: body.fileType,
filePath: body.filePath,
status: body.status || 'PENDING',
ocrProvider: body.ocrProvider || body.ocrEngine,
ocrText: body.ocrText || body.ocr,
ocrMetadata: body.ocrMetadata,
translationModel: body.translationModel || body.translationModelName,
translatedText: body.translatedText || body.translation,
translationMetadata: body.translationMetadata,
summary: body.summary,
toc: body.toc,
processingTime: body.processingTime,
errorMessage: body.errorMessage
};
// 其他字段保存到 metadata
const metadataFields = {};
const knownFields = [
'fileName', 'name', 'fileSize', 'size', 'fileType', 'filePath', 'status',
'ocrProvider', 'ocrText', 'ocr', 'ocrMetadata', 'ocrEngine', 'ocrSource',
'translationModel', 'translatedText', 'translation', 'translationMetadata',
'translationModelName', 'summary', 'toc', 'processingTime', 'errorMessage',
'id', 'userId', 'createdAt', 'updatedAt'
];
for (const [key, value] of Object.entries(body)) {
if (!knownFields.includes(key) && value !== undefined) {
metadataFields[key] = value;
}
}
// 合并原有 metadata
if (body.metadata && typeof body.metadata === 'object') {
Object.assign(metadataFields, body.metadata);
}
schemaFields.metadata = Object.keys(metadataFields).length > 0 ? metadataFields : body.metadata;
// 移除 undefined 字段
const cleanData = {};
for (const [key, value] of Object.entries(schemaFields)) {
if (value !== undefined) {
cleanData[key] = value;
}
}
const document = await prisma.document.create({
data: {
userId: req.user.id,
...cleanData
}
});
// 返回时添加前端需要的字段
const responseData = {
...document,
name: document.fileName,
size: document.fileSize
};
res.status(201).json(responseData);
} catch (error) {
next(error);
}
});
// 更新文档
router.put('/:id', async (req, res, next) => {
try {
const { id } = req.params;
await prisma.document.updateMany({
where: {
id,
userId: req.user.id
},
data: req.body
});
res.json({ success: true });
} catch (error) {
next(error);
}
});
// 删除文档
router.delete('/:id', async (req, res, next) => {
try {
const { id } = req.params;
const document = await prisma.document.findFirst({
where: {
id,
userId: req.user.id
}
});
if (document) {
await prisma.document.delete({
where: { id }
});
}
res.json({ success: true });
} catch (error) {
next(error);
}
});
// ==================== 标注管理 ====================
// 保存标注
router.post('/:id/annotations', async (req, res, next) => {
try {
const { id } = req.params;
const annotation = await prisma.annotation.create({
data: {
userId: req.user.id,
documentId: id,
...req.body
}
});
res.status(201).json(annotation);
} catch (error) {
next(error);
}
});
// 获取文档的所有标注
router.get('/:id/annotations', async (req, res, next) => {
try {
const { id } = req.params;
const annotations = await prisma.annotation.findMany({
where: {
documentId: id,
userId: req.user.id
}
});
res.json(annotations);
} catch (error) {
next(error);
}
});
// 更新标注
router.put('/:documentId/annotations/:annotationId', async (req, res, next) => {
try {
const { documentId, annotationId } = req.params;
await prisma.annotation.updateMany({
where: {
id: annotationId,
documentId,
userId: req.user.id
},
data: req.body
});
res.json({ success: true });
} catch (error) {
next(error);
}
});
// 删除标注
router.delete('/:documentId/annotations/:annotationId', async (req, res, next) => {
try {
const { documentId, annotationId } = req.params;
await prisma.annotation.deleteMany({
where: {
id: annotationId,
documentId,
userId: req.user.id
}
});
res.json({ success: true });
} catch (error) {
next(error);
}
});
// ==================== 意群数据 ====================
// 保存意群数据
router.post('/:id/semantic-groups', async (req, res, next) => {
try {
const { id } = req.params;
const { groups, version, source } = req.body;
// 验证文档所有权
const document = await prisma.document.findFirst({
where: {
id,
userId: req.user.id
}
});
if (!document) {
return res.status(404).json({ error: 'Document not found' });
}
const semanticGroup = await prisma.semanticGroup.upsert({
where: { documentId: id },
update: { groups, version, source },
create: {
documentId: id,
groups,
version,
source
}
});
res.json(semanticGroup);
} catch (error) {
next(error);
}
});
// 获取意群数据
router.get('/:id/semantic-groups', async (req, res, next) => {
try {
const { id } = req.params;
// 验证文档所有权
const document = await prisma.document.findFirst({
where: {
id,
userId: req.user.id
}
});
if (!document) {
return res.status(404).json({ error: 'Document not found' });
}
const semanticGroup = await prisma.semanticGroup.findUnique({
where: { documentId: id }
});
if (!semanticGroup) {
return res.status(404).json({ error: 'Semantic groups not found' });
}
res.json(semanticGroup);
} catch (error) {
next(error);
}
});
export default router;

View File

@ -0,0 +1,134 @@
/**
* 术语库路由
* 使用数据库存储而非文件系统
*/
import express from 'express';
import { prisma } from '../db/client.js';
const router = express.Router();
// 获取用户的所有术语库
router.get('/', async (req, res, next) => {
try {
const glossaries = await prisma.glossary.findMany({
where: { userId: req.user.id },
orderBy: { createdAt: 'desc' }
});
res.json(glossaries);
} catch (error) {
next(error);
}
});
// 获取单个术语库
router.get('/:id', async (req, res, next) => {
try {
const glossary = await prisma.glossary.findFirst({
where: {
id: req.params.id,
userId: req.user.id
}
});
if (!glossary) {
return res.status(404).json({ error: 'Glossary not found' });
}
res.json(glossary);
} catch (error) {
next(error);
}
});
// 创建术语库
router.post('/', async (req, res, next) => {
try {
const { name, enabled, entries } = req.body;
if (!name) {
return res.status(400).json({ error: 'Name is required' });
}
const glossary = await prisma.glossary.create({
data: {
userId: req.user.id,
name,
enabled: enabled !== undefined ? enabled : true,
entries: entries || []
}
});
res.status(201).json(glossary);
} catch (error) {
next(error);
}
});
// 更新术语库
router.put('/:id', async (req, res, next) => {
try {
const { id } = req.params;
const { name, enabled, entries } = req.body;
// 构建更新数据
const updateData = {};
if (name !== undefined) updateData.name = name;
if (enabled !== undefined) updateData.enabled = enabled;
if (entries !== undefined) updateData.entries = entries;
await prisma.glossary.updateMany({
where: {
id,
userId: req.user.id
},
data: updateData
});
res.json({ success: true });
} catch (error) {
next(error);
}
});
// 删除术语库
router.delete('/:id', async (req, res, next) => {
try {
await prisma.glossary.deleteMany({
where: {
id: req.params.id,
userId: req.user.id
}
});
res.json({ success: true });
} catch (error) {
next(error);
}
});
// 批量更新术语库状态
router.patch('/batch/status', async (req, res, next) => {
try {
const { ids, enabled } = req.body;
if (!Array.isArray(ids)) {
return res.status(400).json({ error: 'ids must be an array' });
}
await prisma.glossary.updateMany({
where: {
id: { in: ids },
userId: req.user.id
},
data: { enabled }
});
res.json({ success: true });
} catch (error) {
next(error);
}
});
export default router;

View File

@ -0,0 +1,172 @@
/**
* 提示词池路由
* 复用 server/src/routes/prompt-pool.js 的逻辑
*/
import express from 'express';
import { prisma } from '../db/client.js';
const router = express.Router();
// 限制数组大小
const MAX_PROMPTS_ARRAY_SIZE = 1000;
// 获取用户的 Prompt Pool
router.get('/', async (req, res, next) => {
try {
const promptPool = await prisma.promptPool.findUnique({
where: { userId: req.user.id }
});
if (!promptPool) {
// 返回默认空结构
return res.json({
prompts: [],
healthConfig: null
});
}
res.json({
prompts: promptPool.prompts,
healthConfig: promptPool.healthConfig
});
} catch (error) {
next(error);
}
});
// 更新 Prompt Pool全量
router.put('/', async (req, res, next) => {
try {
const { prompts, healthConfig } = req.body;
// 输入验证
if (!Array.isArray(prompts)) {
return res.status(400).json({ error: 'prompts must be an array' });
}
// 限制数组大小
if (prompts.length > MAX_PROMPTS_ARRAY_SIZE) {
return res.status(400).json({ error: `Too many prompts (max ${MAX_PROMPTS_ARRAY_SIZE})` });
}
// 验证每个 prompt 的基本格式
const validPrompts = prompts.filter(prompt => {
return prompt && typeof prompt === 'object';
});
if (validPrompts.length !== prompts.length) {
return res.status(400).json({ error: 'Some prompts have invalid format' });
}
const promptPool = await prisma.promptPool.upsert({
where: { userId: req.user.id },
update: {
prompts: validPrompts,
healthConfig
},
create: {
userId: req.user.id,
prompts: validPrompts,
healthConfig
}
});
res.json({
prompts: promptPool.prompts,
healthConfig: promptPool.healthConfig
});
} catch (error) {
next(error);
}
});
// 添加单个 Prompt
router.post('/prompts', async (req, res, next) => {
try {
const newPrompt = req.body;
// 输入验证
if (!newPrompt || typeof newPrompt !== 'object') {
return res.status(400).json({ error: 'Prompt must be an object' });
}
// 获取当前 Prompt Pool
let promptPool = await prisma.promptPool.findUnique({
where: { userId: req.user.id }
});
let prompts = promptPool ? (promptPool.prompts || []) : [];
// 限制数组大小
if (prompts.length >= MAX_PROMPTS_ARRAY_SIZE) {
return res.status(400).json({ error: `Maximum number of prompts reached (${MAX_PROMPTS_ARRAY_SIZE})` });
}
// 添加新 Prompt
prompts.push(newPrompt);
// 更新
promptPool = await prisma.promptPool.upsert({
where: { userId: req.user.id },
update: { prompts },
create: {
userId: req.user.id,
prompts
}
});
res.status(201).json({
prompts: promptPool.prompts,
healthConfig: promptPool.healthConfig
});
} catch (error) {
next(error);
}
});
// 删除指定 Prompt根据索引或 ID
router.delete('/prompts/:identifier', async (req, res, next) => {
try {
const { identifier } = req.params;
const promptPool = await prisma.promptPool.findUnique({
where: { userId: req.user.id }
});
if (!promptPool) {
return res.status(404).json({ error: 'Prompt pool not found' });
}
let prompts = promptPool.prompts || [];
// 尝试作为索引解析
const index = parseInt(identifier);
if (!isNaN(index) && index >= 0 && index < prompts.length) {
prompts.splice(index, 1);
} else {
// 尝试作为 ID 查找
const initialLength = prompts.length;
prompts = prompts.filter(p => p.id !== identifier);
if (prompts.length === initialLength) {
return res.status(404).json({ error: 'Prompt not found' });
}
}
// 更新
const updated = await prisma.promptPool.update({
where: { userId: req.user.id },
data: { prompts }
});
res.json({
prompts: updated.prompts,
healthConfig: updated.healthConfig
});
} catch (error) {
next(error);
}
});
export default router;

View File

@ -0,0 +1,186 @@
/**
* 文献引用路由
* 用于管理文档的参考文献
*/
import express from 'express';
import { prisma } from '../db/client.js';
const router = express.Router();
// 获取文档的所有引用
router.get('/:documentId', async (req, res, next) => {
try {
const { documentId } = req.params;
// 验证文档所有权
const document = await prisma.document.findFirst({
where: {
id: documentId,
userId: req.user.id
}
});
if (!document) {
return res.status(404).json({ error: 'Document not found' });
}
const references = await prisma.reference.findMany({
where: {
documentId,
userId: req.user.id
},
orderBy: { createdAt: 'desc' }
});
res.json(references);
} catch (error) {
next(error);
}
});
// 添加引用
router.post('/:documentId', async (req, res, next) => {
try {
const { documentId } = req.params;
const { title, authors, doi, url, source, year, notes, metadata } = req.body;
// 验证文档所有权
const document = await prisma.document.findFirst({
where: {
id: documentId,
userId: req.user.id
}
});
if (!document) {
return res.status(404).json({ error: 'Document not found' });
}
const reference = await prisma.reference.create({
data: {
documentId,
userId: req.user.id,
title,
authors,
doi,
url,
source,
year,
notes,
metadata
}
});
res.status(201).json(reference);
} catch (error) {
next(error);
}
});
// 批量添加引用
router.post('/:documentId/batch', async (req, res, next) => {
try {
const { documentId } = req.params;
const { references } = req.body;
if (!Array.isArray(references)) {
return res.status(400).json({ error: 'references must be an array' });
}
// 验证文档所有权
const document = await prisma.document.findFirst({
where: {
id: documentId,
userId: req.user.id
}
});
if (!document) {
return res.status(404).json({ error: 'Document not found' });
}
// 批量创建
const createdReferences = await prisma.reference.createMany({
data: references.map(ref => ({
documentId,
userId: req.user.id,
title: ref.title,
authors: ref.authors,
doi: ref.doi,
url: ref.url,
source: ref.source,
year: ref.year,
notes: ref.notes,
metadata: ref.metadata
})),
skipDuplicates: true
});
res.status(201).json({
success: true,
count: createdReferences.count
});
} catch (error) {
next(error);
}
});
// 更新引用
router.put('/:documentId/:referenceId', async (req, res, next) => {
try {
const { documentId, referenceId } = req.params;
await prisma.reference.updateMany({
where: {
id: referenceId,
documentId,
userId: req.user.id
},
data: req.body
});
res.json({ success: true });
} catch (error) {
next(error);
}
});
// 删除引用
router.delete('/:documentId/:referenceId', async (req, res, next) => {
try {
const { documentId, referenceId } = req.params;
await prisma.reference.deleteMany({
where: {
id: referenceId,
documentId,
userId: req.user.id
}
});
res.json({ success: true });
} catch (error) {
next(error);
}
});
// 清空文档的所有引用
router.delete('/:documentId', async (req, res, next) => {
try {
const { documentId } = req.params;
await prisma.reference.deleteMany({
where: {
documentId,
userId: req.user.id
}
});
res.json({ success: true });
} catch (error) {
next(error);
}
});
export default router;

319
local-proxy/routes/user.js Normal file
View File

@ -0,0 +1,319 @@
/**
* 用户路由
* 复用 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;

123
local-proxy/routes/utils.js Normal file
View File

@ -0,0 +1,123 @@
/**
* 路由辅助工具
* 用于简化 HTTP 路由处理
*/
import { URL } from 'url';
/**
* JSON 响应辅助函数
*/
export function jsonResponse(res, data, status = 200, origin = '*') {
const headers = {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Methods': 'GET, HEAD, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
};
res.writeHead(status, headers);
res.end(JSON.stringify(data));
}
/**
* 读取请求体
*/
export async function readBody(req) {
return new Promise((resolve, reject) => {
const chunks = [];
req.on('data', chunk => chunks.push(chunk));
req.on('end', () => resolve(Buffer.concat(chunks)));
req.on('error', reject);
});
}
/**
* 解析 JSON 请求体
*/
export async function readJsonBody(req) {
const body = await readBody(req);
if (body.length === 0) return {};
try {
return JSON.parse(body.toString());
} catch {
throw new Error('Invalid JSON');
}
}
/**
* 解析 URL 路径参数
* @param {string} pathname - 请求路径
* @param {string} pattern - 路由模式 '/api/documents/:id'
* @returns {Object|null} 解析出的参数或 null
*/
export function parsePathParams(pathname, pattern) {
const patternParts = pattern.split('/');
const pathnameParts = pathname.split('/');
if (patternParts.length !== pathnameParts.length) {
return null;
}
const params = {};
for (let i = 0; i < patternParts.length; i++) {
if (patternParts[i].startsWith(':')) {
params[patternParts[i].slice(1)] = pathnameParts[i];
} else if (patternParts[i] !== pathnameParts[i]) {
return null;
}
}
return params;
}
/**
* 检查路径是否匹配
* @param {string} pathname - 请求路径
* @param {string} prefix - 路径前缀
* @returns {boolean}
*/
export function pathStartsWith(pathname, prefix) {
return pathname.startsWith(prefix);
}
/**
* 获取路径剩余部分
* @param {string} pathname - 请求路径
* @param {string} prefix - 路径前缀
* @returns {string}
*/
export function getPathRemainder(pathname, prefix) {
return pathname.slice(prefix.length);
}
/**
* 解析查询参数
* @param {string} searchParams - URL 查询字符串
* @returns {Object}
*/
export function parseQuery(searchParams) {
const params = new URLSearchParams(searchParams);
const result = {};
for (const [key, value] of params) {
result[key] = value;
}
return result;
}
/**
* 验证 UUID 格式
*/
export function isValidUUID(id) {
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id);
}
/**
* 生成 UUID
*/
export function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}

View File

@ -25,6 +25,12 @@ import { fileURLToPath } from 'url';
import { Readable } from 'stream'; import { Readable } from 'stream';
import OSS from 'ali-oss'; import OSS from 'ali-oss';
// Express 应用(持久化 API
import app from './app.js';
// 数据库初始化
import { initDatabase, prisma } from './db/client.js';
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
// ==================== 配置加载 ==================== // ==================== 配置加载 ====================
@ -67,6 +73,22 @@ const PORT = parseInt(process.env.PORT || '3456', 10);
const MINERU_BASE_URL = 'https://mineru.net/api/v4'; const MINERU_BASE_URL = 'https://mineru.net/api/v4';
const DOC2X_BASE_URL = 'https://v2.doc2x.noedgeai.com'; const DOC2X_BASE_URL = 'https://v2.doc2x.noedgeai.com';
// ==================== 数据库初始化 ====================
let dbConnected = false;
initDatabase()
.then(connected => {
dbConnected = connected;
if (connected) {
console.log('[Database] PostgreSQL connected successfully');
globalThis.__prismaConnected = true;
} else {
console.warn('[Database] PostgreSQL not configured or connection failed');
}
})
.catch(err => {
console.error('[Database] Initialization error:', err.message);
});
// ==================== OSS 配置 ==================== // ==================== OSS 配置 ====================
let ossClient = null; let ossClient = null;
function initOssClient() { function initOssClient() {
@ -1068,12 +1090,13 @@ const server = http.createServer(async (req, res) => {
return await handleProxyDownload(req, res, pdfUrl, origin); return await handleProxyDownload(req, res, pdfUrl, origin);
} }
// ===== 健康检查 ===== // ===== 健康检查(兼容两个路径)=====
if (pathname === '/health') { if (pathname === '/health' || pathname === '/api/health') {
return jsonResponse(res, { return jsonResponse(res, {
status: 'ok', status: 'ok',
timestamp: Date.now(), timestamp: Date.now(),
version: '1.0.0', version: '1.0.0',
database: dbConnected ? 'connected' : 'not_configured',
services: { services: {
ocr: { ocr: {
mineru: { enabled: true, hasToken: !!process.env.MINERU_API_TOKEN }, mineru: { enabled: true, hasToken: !!process.env.MINERU_API_TOKEN },
@ -1090,6 +1113,22 @@ const server = http.createServer(async (req, res) => {
}, 200, origin); }, 200, origin);
} }
// ===== 持久化 API交给 Express 处理)=====
// 匹配 /api/documents, /api/user, /api/glossary, /api/chat, /api/references, /api/prompt-pool
const persistentApiPrefixes = [
'/api/documents',
'/api/user',
'/api/glossary',
'/api/chat',
'/api/references',
'/api/prompt-pool'
];
if (persistentApiPrefixes.some(prefix => pathname.startsWith(prefix))) {
// 交给 Express 应用处理
return app(req, res);
}
// 404 // 404
if (!res.headersSent) { if (!res.headersSent) {
jsonResponse(res, { error: 'Not Found' }, 404, origin); jsonResponse(res, { error: 'Not Found' }, 404, origin);
@ -1110,7 +1149,10 @@ server.timeout = 300000;
server.keepAliveTimeout = 120000; server.keepAliveTimeout = 120000;
server.headersTimeout = 120000; server.headersTimeout = 120000;
server.listen(PORT, () => { server.listen(PORT, async () => {
// 等待数据库初始化完成
await new Promise(resolve => setTimeout(resolve, 500));
console.log(` console.log(`
Paper Burner Local Proxy Server Paper Burner Local Proxy Server
@ -1118,6 +1160,9 @@ server.listen(PORT, () => {
Port: ${PORT.toString().padEnd(47)} Port: ${PORT.toString().padEnd(47)}
URL: http://localhost:${PORT.toString().padEnd(30)} ║ URL: http://localhost:${PORT.toString().padEnd(30)} ║
Database: ${(dbConnected ? '✓ PostgreSQL Connected' : '✗ Not configured').padEnd(38)}
Auth: ${(process.env.AUTH_CHECK_URL ? '✓ External' : '✓ Default (xueai.art)').padEnd(38)}
LLM Providers: LLM Providers:
Zhipu AI: ${(process.env.ZHIPU_API_KEY ? '✓ ' + process.env.ZHIPU_API_KEY.substring(0,6) + '***' : '✗ Not set').padEnd(36)} Zhipu AI: ${(process.env.ZHIPU_API_KEY ? '✓ ' + process.env.ZHIPU_API_KEY.substring(0,6) + '***' : '✗ Not set').padEnd(36)}
Aliyun: ${(process.env.DASHSCOPE_API_KEY ? '✓ ' + process.env.DASHSCOPE_API_KEY.substring(0,6) + '***' : '✗ Not set').padEnd(36)} Aliyun: ${(process.env.DASHSCOPE_API_KEY ? '✓ ' + process.env.DASHSCOPE_API_KEY.substring(0,6) + '***' : '✗ Not set').padEnd(36)}
@ -1134,6 +1179,10 @@ server.listen(PORT, () => {
OSS Upload: OSS Upload:
Configured: ${((process.env.OSS_BUCKET || process.env.OSS_BUCKET_NAME) && process.env.OSS_ACCESS_KEY_ID ? '✓ Yes' : '✗ No').padEnd(36)} Configured: ${((process.env.OSS_BUCKET || process.env.OSS_BUCKET_NAME) && process.env.OSS_ACCESS_KEY_ID ? '✓ Yes' : '✗ No').padEnd(36)}
Persistent API:
/api/documents, /api/user, /api/glossary
/api/chat, /api/references, /api/prompt-pool
Paper Burner 中设置代理地址为: Paper Burner 中设置代理地址为:
http://localhost:${PORT.toString().padEnd(38)} ║ http://localhost:${PORT.toString().padEnd(38)} ║

110
local-proxy/test-db.js Normal file
View File

@ -0,0 +1,110 @@
/**
* 数据库功能测试脚本
* 直接测试 Prisma 操作绕过认证
*/
import { prisma } from './db/client.js';
async function testDatabase() {
console.log('🧪 测试数据库功能\n');
const testUserId = 'test-user-' + Date.now();
try {
// 1. 创建用户
console.log('1⃣ 创建测试用户...');
const user = await prisma.user.create({
data: {
id: testUserId,
name: '测试用户'
}
});
console.log(' ✅ 用户创建成功:', user.id);
// 2. 创建用户设置
console.log('\n2⃣ 创建用户设置...');
const settings = await prisma.userSettings.create({
data: {
userId: testUserId,
targetLanguage: 'chinese',
translationConcurrency: 15
}
});
console.log(' ✅ 设置创建成功:', settings.id);
// 3. 创建文档
console.log('\n3⃣ 创建文档...');
const document = await prisma.document.create({
data: {
userId: testUserId,
fileName: 'test-paper.pdf',
fileType: 'pdf',
status: 'PENDING'
}
});
console.log(' ✅ 文档创建成功:', document.id);
// 4. 创建术语库
console.log('\n4⃣ 创建术语库...');
const glossary = await prisma.glossary.create({
data: {
userId: testUserId,
name: '测试术语库',
entries: [
{ source: 'AI', target: '人工智能' },
{ source: 'ML', target: '机器学习' }
]
}
});
console.log(' ✅ 术语库创建成功:', glossary.id);
// 5. 创建聊天消息
console.log('\n5⃣ 创建聊天消息...');
const chatMessage = await prisma.chatMessage.create({
data: {
documentId: document.id,
userId: testUserId,
role: 'user',
content: '这篇论文的主要贡献是什么?'
}
});
console.log(' ✅ 聊天消息创建成功:', chatMessage.id);
// 6. 查询测试
console.log('\n6⃣ 查询测试...');
const userWithRelations = await prisma.user.findUnique({
where: { id: testUserId },
include: {
settings: true,
documents: true,
glossaries: true,
chatMessages: true
}
});
console.log(' ✅ 查询结果:');
console.log(' - 设置:', userWithRelations.settings ? '有' : '无');
console.log(' - 文档数:', userWithRelations.documents.length);
console.log(' - 术语库数:', userWithRelations.glossaries.length);
console.log(' - 聊天消息数:', userWithRelations.chatMessages.length);
// 7. 清理测试数据
console.log('\n7⃣ 清理测试数据...');
await prisma.chatMessage.deleteMany({ where: { userId: testUserId } });
await prisma.document.deleteMany({ where: { userId: testUserId } });
await prisma.glossary.deleteMany({ where: { userId: testUserId } });
await prisma.userSettings.deleteMany({ where: { userId: testUserId } });
await prisma.user.delete({ where: { id: testUserId } });
console.log(' ✅ 测试数据已清理');
console.log('\n✅ 所有数据库测试通过!\n');
} catch (error) {
console.error('\n❌ 测试失败:', error.message);
console.error(error);
process.exit(1);
} finally {
await prisma.$disconnect();
}
}
testDatabase();

986
package-lock.json generated Normal file
View File

@ -0,0 +1,986 @@
{
"name": "paper-burner-root",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "paper-burner-root",
"hasInstallScript": true,
"devDependencies": {
"vite": "^5.4.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz",
"integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz",
"integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz",
"integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz",
"integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz",
"integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz",
"integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz",
"integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz",
"integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz",
"integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz",
"integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz",
"integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz",
"integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz",
"integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz",
"integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz",
"integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz",
"integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz",
"integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz",
"integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz",
"integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz",
"integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz",
"integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz",
"integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz",
"integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz",
"integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz",
"integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://mirrors.cloud.tencent.com/npm/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"dev": true,
"license": "MIT"
},
"node_modules/esbuild": {
"version": "0.21.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/esbuild/-/esbuild-0.21.5.tgz",
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.21.5",
"@esbuild/android-arm": "0.21.5",
"@esbuild/android-arm64": "0.21.5",
"@esbuild/android-x64": "0.21.5",
"@esbuild/darwin-arm64": "0.21.5",
"@esbuild/darwin-x64": "0.21.5",
"@esbuild/freebsd-arm64": "0.21.5",
"@esbuild/freebsd-x64": "0.21.5",
"@esbuild/linux-arm": "0.21.5",
"@esbuild/linux-arm64": "0.21.5",
"@esbuild/linux-ia32": "0.21.5",
"@esbuild/linux-loong64": "0.21.5",
"@esbuild/linux-mips64el": "0.21.5",
"@esbuild/linux-ppc64": "0.21.5",
"@esbuild/linux-riscv64": "0.21.5",
"@esbuild/linux-s390x": "0.21.5",
"@esbuild/linux-x64": "0.21.5",
"@esbuild/netbsd-x64": "0.21.5",
"@esbuild/openbsd-x64": "0.21.5",
"@esbuild/sunos-x64": "0.21.5",
"@esbuild/win32-arm64": "0.21.5",
"@esbuild/win32-ia32": "0.21.5",
"@esbuild/win32-x64": "0.21.5"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://mirrors.cloud.tencent.com/npm/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://mirrors.cloud.tencent.com/npm/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://mirrors.cloud.tencent.com/npm/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC"
},
"node_modules/postcss": {
"version": "8.5.8",
"resolved": "https://mirrors.cloud.tencent.com/npm/postcss/-/postcss-8.5.8.tgz",
"integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/rollup": {
"version": "4.60.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/rollup/-/rollup-4.60.0.tgz",
"integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.8"
},
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.60.0",
"@rollup/rollup-android-arm64": "4.60.0",
"@rollup/rollup-darwin-arm64": "4.60.0",
"@rollup/rollup-darwin-x64": "4.60.0",
"@rollup/rollup-freebsd-arm64": "4.60.0",
"@rollup/rollup-freebsd-x64": "4.60.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.60.0",
"@rollup/rollup-linux-arm-musleabihf": "4.60.0",
"@rollup/rollup-linux-arm64-gnu": "4.60.0",
"@rollup/rollup-linux-arm64-musl": "4.60.0",
"@rollup/rollup-linux-loong64-gnu": "4.60.0",
"@rollup/rollup-linux-loong64-musl": "4.60.0",
"@rollup/rollup-linux-ppc64-gnu": "4.60.0",
"@rollup/rollup-linux-ppc64-musl": "4.60.0",
"@rollup/rollup-linux-riscv64-gnu": "4.60.0",
"@rollup/rollup-linux-riscv64-musl": "4.60.0",
"@rollup/rollup-linux-s390x-gnu": "4.60.0",
"@rollup/rollup-linux-x64-gnu": "4.60.0",
"@rollup/rollup-linux-x64-musl": "4.60.0",
"@rollup/rollup-openbsd-x64": "4.60.0",
"@rollup/rollup-openharmony-arm64": "4.60.0",
"@rollup/rollup-win32-arm64-msvc": "4.60.0",
"@rollup/rollup-win32-ia32-msvc": "4.60.0",
"@rollup/rollup-win32-x64-gnu": "4.60.0",
"@rollup/rollup-win32-x64-msvc": "4.60.0",
"fsevents": "~2.3.2"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://mirrors.cloud.tencent.com/npm/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/vite": {
"version": "5.4.21",
"resolved": "https://mirrors.cloud.tencent.com/npm/vite/-/vite-5.4.21.tgz",
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
"rollup": "^4.20.0"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^18.0.0 || >=20.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^18.0.0 || >=20.0.0",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More