Compare commits
1 Commits
master
...
feat/datab
| Author | SHA1 | Date |
|---|---|---|
|
|
ce324d8da3 |
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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.");
|
||||||
}
|
}
|
||||||
|
|
||||||
data = await getResultFromDB(id);
|
// 使用 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);
|
||||||
|
}
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -509,8 +509,12 @@ async function triggerReprocess(includeTranslation) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存到 IndexedDB
|
// 保存到数据库
|
||||||
await saveResultToDB(window.data);
|
if (window.storageAdapter && typeof window.storageAdapter.saveResultToDB === 'function') {
|
||||||
|
await window.storageAdapter.saveResultToDB(window.data);
|
||||||
|
} else if (typeof saveResultToDB === 'function') {
|
||||||
|
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('数据已保存到数据库');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
|
||||||
|
|
@ -67,8 +67,33 @@ 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 {
|
||||||
// 回退到本地存储的方法
|
// 回退到本地存储的方法
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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
|
||||||
|
};
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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');
|
||||||
|
}
|
||||||
|
|
@ -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 };
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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:
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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")
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -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)} ║
|
||||||
╚═══════════════════════════════════════════════════════╝
|
╚═══════════════════════════════════════════════════════╝
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
@ -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
Loading…
Reference in New Issue