Download backend/src/app.js from CatPtain/web_ppt: direct link, hf CLI and curl.
- Browser
- Download file 43.9 kB
-
https://huggingface.co/spaces/CatPtain/web_ppt/resolve/main/backend/src/app.js
- Command line
-
hf download hf://spaces/CatPtain/web_ppt/backend/src/app.js
-
curl -L -o app.js https://huggingface.co/spaces/CatPtain/web_ppt/resolve/main/backend/src/app.js
43.9 kB
| import express from 'express'; | |
| import cors from 'cors'; | |
| import helmet from 'helmet'; | |
| import rateLimit from 'express-rate-limit'; | |
| import dotenv from 'dotenv'; | |
| import path from 'path'; | |
| import { fileURLToPath } from 'url'; | |
| import axios from 'axios'; | |
| import fs from 'fs'; // 添加ES模块fs导入 | |
| const __filename = fileURLToPath(import.meta.url); | |
| const __dirname = path.dirname(__filename); | |
| // 首先加载环境变量 - 必须在导入服务之前 | |
| dotenv.config({ path: path.join(__dirname, '../../.env') }); | |
| // 验证环境变量是否正确加载 | |
| console.log('=== Environment Variables Check ==='); | |
| console.log('GITHUB_TOKEN configured:', !!process.env.GITHUB_TOKEN); | |
| console.log('GITHUB_TOKEN length:', process.env.GITHUB_TOKEN ? process.env.GITHUB_TOKEN.length : 0); | |
| console.log('GITHUB_REPOS configured:', !!process.env.GITHUB_REPOS); | |
| console.log('GITHUB_REPOS value:', process.env.GITHUB_REPOS); | |
| // 现在导入需要环境变量的模块 | |
| import authRoutes from './routes/auth.js'; | |
| import pptRoutes from './routes/ppt.js'; | |
| import publicRoutes from './routes/public.js'; | |
| import { authenticateToken } from './middleware/auth.js'; | |
| import { errorHandler } from './middleware/errorHandler.js'; | |
| const app = express(); | |
| const PORT = process.env.PORT || 7860; // 修改为7860端口 | |
| // 设置trust proxy用于Huggingface Space | |
| app.set('trust proxy', true); | |
| // 安全中间件 | |
| app.use(helmet({ | |
| contentSecurityPolicy: false, // 为了兼容前端静态文件 | |
| })); | |
| // 修复限流配置 - 针对Huggingface Space生产环境 | |
| const limiter = rateLimit({ | |
| windowMs: 15 * 60 * 1000, // 15分钟 | |
| max: 100, // 每个IP每15分钟最多100个请求 | |
| message: 'Too many requests from this IP, please try again later.', | |
| trustProxy: true, // 与Express trust proxy设置保持一致 | |
| validate: { | |
| trustProxy: false // 禁用trust proxy验证以避免生产环境警告 | |
| }, | |
| standardHeaders: true, | |
| legacyHeaders: false | |
| }); | |
| // 应用限流中间件 | |
| app.use('/api', limiter); | |
| // CORS配置 | |
| app.use(cors({ | |
| origin: process.env.FRONTEND_URL || '*', | |
| credentials: true | |
| })); | |
| app.use(express.json({ limit: '50mb' })); | |
| app.use(express.urlencoded({ extended: true, limit: '50mb' })); | |
| // 提供前端静态文件 - 修复路径配置 | |
| const frontendDistPath = path.join(__dirname, '../../frontend/dist'); | |
| console.log('Frontend dist path:', frontendDistPath); | |
| app.use(express.static(frontendDistPath)); | |
| // 提供数据文件 | |
| app.use('/data', express.static(path.join(__dirname, '../../frontend/public/mocks'))); | |
| // 直接提供测试页面路由 | |
| app.get('/test.html', (req, res) => { | |
| try { | |
| // 检查文件是否存在 | |
| const testFilePath = path.join(__dirname, '../../test.html'); | |
| console.log('Looking for test.html at:', testFilePath); | |
| // 发送测试页面 | |
| res.sendFile(testFilePath, (err) => { | |
| if (err) { | |
| console.error('Error serving test.html:', err); | |
| // 如果文件不存在,返回一个简单的测试页面 | |
| res.send(` | |
| <!DOCTYPE html> | |
| <html> | |
| <head><title>Test Page</title></head> | |
| <body> | |
| <h1>PPTist API Test</h1> | |
| <p>Test file not found at: ${testFilePath}</p> | |
| <button onclick="fetch('/api/health').then(r=>r.json()).then(d=>alert(JSON.stringify(d)))">Test Health</button> | |
| </body> | |
| </html> | |
| `); | |
| } | |
| }); | |
| } catch (error) { | |
| console.error('Test page error:', error); | |
| res.status(500).send('Test page error: ' + error.message); | |
| } | |
| }); | |
| // 添加内置测试页面端点 | |
| app.get('/test', (req, res) => { | |
| res.send(` | |
| <!DOCTYPE html> | |
| <html lang="zh-CN"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>PPTist API 测试页面</title> | |
| <style> | |
| body { font-family: Arial, sans-serif; padding: 20px; background: #f5f5f5; } | |
| .container { max-width: 800px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; } | |
| h1 { color: #333; text-align: center; margin-bottom: 30px; } | |
| .test-section { margin: 20px 0; padding: 15px; background: #f9f9f9; border-radius: 5px; } | |
| .test-button { background: #5b9bd5; color: white; border: none; padding: 10px 20px; margin: 5px; border-radius: 5px; cursor: pointer; } | |
| .test-button:hover { background: #4a8bc2; } | |
| .result { margin: 10px 0; padding: 10px; background: #e8e8e8; border-radius: 3px; font-family: monospace; font-size: 12px; } | |
| .success { background: #d4edda; color: #155724; } | |
| .error { background: #f8d7da; color: #721c24; } | |
| .login-form { margin: 15px 0; } | |
| .login-form input { padding: 8px; margin: 5px; border: 1px solid #ddd; border-radius: 3px; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>🚀 PPTist API 测试控制台</h1> | |
| <div class="test-section"> | |
| <h3>🔗 基础连接测试</h3> | |
| <button class="test-button" onclick="testHealth()">健康检查</button> | |
| <button class="test-button" onclick="testGitHubStatus()">GitHub状态</button> | |
| <button class="test-button" onclick="testGitHubConnection()">GitHub连接</button> | |
| <div id="basic-results"></div> | |
| </div> | |
| <div class="test-section"> | |
| <h3>🔐 用户认证测试</h3> | |
| <div class="login-form"> | |
| <input type="text" id="username" placeholder="用户名" value="PS01"> | |
| <input type="password" id="password" placeholder="密码" value="admin_cybercity2025"> | |
| <button class="test-button" onclick="testLogin()">登录</button> | |
| <button class="test-button" onclick="clearToken()">清除Token</button> | |
| </div> | |
| <div id="token-info">当前Token: 未登录</div> | |
| <button class="test-button" onclick="testVerifyToken()">验证Token</button> | |
| <div id="auth-results"></div> | |
| </div> | |
| <div class="test-section"> | |
| <h3>📄 PPT管理测试</h3> | |
| <button class="test-button" onclick="testPPTRoute()">PPT路由测试</button> | |
| <button class="test-button" onclick="testPPTList()">获取PPT列表</button> | |
| <button class="test-button" onclick="testCreatePPT()">创建PPT</button> | |
| <button class="test-button" onclick="testSavePPT()">保存PPT</button> | |
| <div id="ppt-results"></div> | |
| </div> | |
| <div class="test-section"> | |
| <h3>🌐 公共分享测试</h3> | |
| <button class="test-button" onclick="testGenerateShareLink()">生成分享链接</button> | |
| <div id="share-results"></div> | |
| </div> | |
| </div> | |
| <script> | |
| let currentToken = localStorage.getItem('pptist_test_token') || ''; | |
| let testPptId = ''; | |
| function updateTokenDisplay() { | |
| const display = document.getElementById('token-info'); | |
| if (currentToken) { | |
| display.textContent = '当前Token: ' + currentToken.substring(0, 20) + '...'; | |
| } else { | |
| display.textContent = '当前Token: 未登录'; | |
| } | |
| } | |
| function clearToken() { | |
| currentToken = ''; | |
| localStorage.removeItem('pptist_test_token'); | |
| updateTokenDisplay(); | |
| addResult('auth-results', '✅ Token已清除', 'success'); | |
| } | |
| function addResult(containerId, message, type = '') { | |
| const container = document.getElementById(containerId); | |
| const div = document.createElement('div'); | |
| div.className = 'result ' + type; | |
| div.textContent = '[' + new Date().toLocaleTimeString() + '] ' + message; | |
| container.appendChild(div); | |
| } | |
| async function testHealth() { | |
| try { | |
| const response = await fetch('/api/health'); | |
| const data = await response.json(); | |
| addResult('basic-results', '✅ 健康检查成功: ' + JSON.stringify(data), 'success'); | |
| } catch (error) { | |
| addResult('basic-results', '❌ 健康检查失败: ' + error.message, 'error'); | |
| } | |
| } | |
| async function testGitHubStatus() { | |
| try { | |
| const response = await fetch('/api/github/status'); | |
| const data = await response.json(); | |
| addResult('basic-results', '✅ GitHub状态: ' + JSON.stringify(data, null, 2), 'success'); | |
| } catch (error) { | |
| addResult('basic-results', '❌ GitHub状态失败: ' + error.message, 'error'); | |
| } | |
| } | |
| async function testGitHubConnection() { | |
| try { | |
| const response = await fetch('/api/github/test'); | |
| const data = await response.json(); | |
| addResult('basic-results', '✅ GitHub连接测试: ' + JSON.stringify(data, null, 2), 'success'); | |
| } catch (error) { | |
| addResult('basic-results', '❌ GitHub连接失败: ' + error.message, 'error'); | |
| } | |
| } | |
| async function testLogin() { | |
| const username = document.getElementById('username').value; | |
| const password = document.getElementById('password').value; | |
| try { | |
| const response = await fetch('/api/auth/login', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ username, password }) | |
| }); | |
| const data = await response.json(); | |
| if (response.ok && data.token) { | |
| currentToken = data.token; | |
| localStorage.setItem('pptist_test_token', currentToken); | |
| updateTokenDisplay(); | |
| addResult('auth-results', '✅ 登录成功: ' + data.user.username + ' (' + data.user.role + ')', 'success'); | |
| } else { | |
| addResult('auth-results', '❌ 登录失败: ' + (data.error || '未知错误'), 'error'); | |
| } | |
| } catch (error) { | |
| addResult('auth-results', '❌ 登录请求失败: ' + error.message, 'error'); | |
| } | |
| } | |
| async function testVerifyToken() { | |
| if (!currentToken) { | |
| addResult('auth-results', '❌ 请先登录', 'error'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch('/api/auth/verify', { | |
| headers: { 'Authorization': 'Bearer ' + currentToken } | |
| }); | |
| const data = await response.json(); | |
| if (response.ok) { | |
| addResult('auth-results', '✅ Token验证成功: ' + JSON.stringify(data), 'success'); | |
| } else { | |
| addResult('auth-results', '❌ Token验证失败: ' + data.error, 'error'); | |
| } | |
| } catch (error) { | |
| addResult('auth-results', '❌ Token验证请求失败: ' + error.message, 'error'); | |
| } | |
| } | |
| async function testPPTRoute() { | |
| try { | |
| const response = await fetch('/api/ppt/test'); | |
| const data = await response.json(); | |
| addResult('ppt-results', '✅ PPT路由测试成功: ' + JSON.stringify(data), 'success'); | |
| } catch (error) { | |
| addResult('ppt-results', '❌ PPT路由测试失败: ' + error.message, 'error'); | |
| } | |
| } | |
| async function testPPTList() { | |
| if (!currentToken) { | |
| addResult('ppt-results', '❌ 请先登录', 'error'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch('/api/ppt/list', { | |
| headers: { 'Authorization': 'Bearer ' + currentToken } | |
| }); | |
| const data = await response.json(); | |
| if (response.ok) { | |
| addResult('ppt-results', '✅ PPT列表获取成功: ' + JSON.stringify(data), 'success'); | |
| } else { | |
| addResult('ppt-results', '❌ PPT列表获取失败: ' + data.error, 'error'); | |
| } | |
| } catch (error) { | |
| addResult('ppt-results', '❌ PPT列表请求失败: ' + error.message, 'error'); | |
| } | |
| } | |
| async function testCreatePPT() { | |
| if (!currentToken) { | |
| addResult('ppt-results', '❌ 请先登录', 'error'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch('/api/ppt/create', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'Authorization': 'Bearer ' + currentToken | |
| }, | |
| body: JSON.stringify({ title: '测试PPT - ' + new Date().toLocaleString() }) | |
| }); | |
| const data = await response.json(); | |
| if (response.ok && data.pptId) { | |
| testPptId = data.pptId; | |
| addResult('ppt-results', '✅ PPT创建成功: ' + testPptId, 'success'); | |
| } else { | |
| addResult('ppt-results', '❌ PPT创建失败: ' + (data.error || '未知错误'), 'error'); | |
| } | |
| } catch (error) { | |
| addResult('ppt-results', '❌ PPT创建请求失败: ' + error.message, 'error'); | |
| } | |
| } | |
| async function testSavePPT() { | |
| if (!currentToken) { | |
| addResult('ppt-results', '❌ 请先登录', 'error'); | |
| return; | |
| } | |
| if (!testPptId) { | |
| addResult('ppt-results', '❌ 请先创建PPT', 'error'); | |
| return; | |
| } | |
| const pptData = { | |
| pptId: testPptId, | |
| title: '测试PPT - 已保存', | |
| slides: [{ id: 'slide-1', elements: [], background: { type: 'solid', color: '#ffffff' } }], | |
| theme: { backgroundColor: '#ffffff', themeColor: '#5b9bd5' } | |
| }; | |
| try { | |
| const response = await fetch('/api/ppt/save', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'Authorization': 'Bearer ' + currentToken | |
| }, | |
| body: JSON.stringify(pptData) | |
| }); | |
| const data = await response.json(); | |
| if (response.ok) { | |
| addResult('ppt-results', '✅ PPT保存成功: ' + JSON.stringify(data), 'success'); | |
| } else { | |
| addResult('ppt-results', '❌ PPT保存失败: ' + (data.error || '未知错误'), 'error'); | |
| } | |
| } catch (error) { | |
| addResult('ppt-results', '❌ PPT保存请求失败: ' + error.message, 'error'); | |
| } | |
| } | |
| async function testGenerateShareLink() { | |
| if (!testPptId) { | |
| addResult('share-results', '❌ 请先创建并保存PPT', 'error'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch('/api/public/generate-share-link', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ userId: 'PS01', pptId: testPptId, slideIndex: 0 }) | |
| }); | |
| const data = await response.json(); | |
| if (response.ok) { | |
| addResult('share-results', '✅ 分享链接生成成功: ' + JSON.stringify(data, null, 2), 'success'); | |
| } else { | |
| addResult('share-results', '❌ 分享链接生成失败: ' + (data.error || '未知错误'), 'error'); | |
| } | |
| } catch (error) { | |
| addResult('share-results', '❌ 分享链接生成请求失败: ' + error.message, 'error'); | |
| } | |
| } | |
| // 初始化 | |
| updateTokenDisplay(); | |
| </script> | |
| </body> | |
| </html> | |
| `); | |
| }); | |
| // API路由 | |
| console.log('Registering API routes...'); | |
| // 健康检查 - 需要在其他路由之前 | |
| app.get('/api/health', (req, res) => { | |
| res.json({ status: 'ok', timestamp: new Date().toISOString() }); | |
| }); | |
| // GitHub连接状态检查 | |
| app.get('/api/github/status', async (req, res) => { | |
| try { | |
| const { default: githubService } = await import('./services/githubService.js'); | |
| const validation = await githubService.validateConnection(); | |
| res.json({ | |
| github: validation, | |
| environment: { | |
| tokenConfigured: !!process.env.GITHUB_TOKEN, | |
| tokenPreview: process.env.GITHUB_TOKEN ? `${process.env.GITHUB_TOKEN.substring(0, 8)}...` : 'Not set', | |
| reposConfigured: !!process.env.GITHUB_REPOS, | |
| reposList: process.env.GITHUB_REPOS ? process.env.GITHUB_REPOS.split(',') : [], | |
| nodeEnv: process.env.NODE_ENV | |
| } | |
| }); | |
| } catch (error) { | |
| res.status(500).json({ | |
| error: error.message, | |
| stack: error.stack | |
| }); | |
| } | |
| }); | |
| // 添加GitHub测试路由 | |
| app.get('/api/github/test', async (req, res) => { | |
| try { | |
| console.log('=== GitHub Connection Test ==='); | |
| console.log('GITHUB_TOKEN exists:', !!process.env.GITHUB_TOKEN); | |
| console.log('GITHUB_REPOS:', process.env.GITHUB_REPOS); | |
| const { default: githubService } = await import('./services/githubService.js'); | |
| // 测试基本配置 | |
| const config = { | |
| hasToken: !!githubService.token, | |
| useMemoryStorage: githubService.useMemoryStorage, | |
| repositories: githubService.repositories, | |
| apiUrl: githubService.apiUrl | |
| }; | |
| console.log('GitHub Service Config:', config); | |
| // 如果有token,测试连接 | |
| let connectionTest = null; | |
| if (githubService.token) { | |
| console.log('Testing GitHub API connection...'); | |
| connectionTest = await githubService.validateConnection(); | |
| console.log('Connection test result:', connectionTest); | |
| } | |
| res.json({ | |
| timestamp: new Date().toISOString(), | |
| config, | |
| connectionTest, | |
| environment: { | |
| tokenLength: process.env.GITHUB_TOKEN ? process.env.GITHUB_TOKEN.length : 0, | |
| nodeEnv: process.env.NODE_ENV | |
| } | |
| }); | |
| } catch (error) { | |
| console.error('GitHub test error:', error); | |
| res.status(500).json({ | |
| error: error.message, | |
| stack: process.env.NODE_ENV === 'development' ? error.stack : undefined | |
| }); | |
| } | |
| }); | |
| // 添加GitHub调试路由 | |
| app.get('/api/debug/github', async (req, res) => { | |
| try { | |
| console.log('=== GitHub Debug Information ==='); | |
| const { default: githubService } = await import('./services/githubService.js'); | |
| const debugInfo = { | |
| timestamp: new Date().toISOString(), | |
| environment: { | |
| tokenConfigured: !!process.env.GITHUB_TOKEN, | |
| tokenLength: process.env.GITHUB_TOKEN ? process.env.GITHUB_TOKEN.length : 0, | |
| reposConfigured: !!process.env.GITHUB_REPOS, | |
| reposList: process.env.GITHUB_REPOS ? process.env.GITHUB_REPOS.split(',') : [], | |
| nodeEnv: process.env.NODE_ENV | |
| }, | |
| service: { | |
| hasToken: !!githubService.token, | |
| repositoriesCount: githubService.repositories?.length || 0, | |
| repositories: githubService.repositories || [], | |
| apiUrl: githubService.apiUrl | |
| } | |
| }; | |
| // 测试连接 | |
| try { | |
| const connectionTest = await githubService.validateConnection(); | |
| debugInfo.connectionTest = connectionTest; | |
| } catch (connError) { | |
| debugInfo.connectionError = connError.message; | |
| } | |
| console.log('Debug info:', debugInfo); | |
| res.json(debugInfo); | |
| } catch (error) { | |
| console.error('Debug route error:', error); | |
| res.status(500).json({ | |
| error: error.message, | |
| stack: process.env.NODE_ENV === 'development' ? error.stack : undefined | |
| }); | |
| } | |
| }); | |
| // 添加PPT调试路由 | |
| app.get('/api/debug/ppt/:userId', async (req, res) => { | |
| try { | |
| const { userId } = req.params; | |
| console.log(`=== PPT Debug for User: ${userId} ===`); | |
| const { default: githubService } = await import('./services/githubService.js'); | |
| const debugInfo = { | |
| timestamp: new Date().toISOString(), | |
| userId: userId, | |
| repositories: [] | |
| }; | |
| // 检查每个仓库中用户的文件 | |
| for (let i = 0; i < githubService.repositories.length; i++) { | |
| const repoInfo = { | |
| index: i, | |
| url: githubService.repositories[i], | |
| accessible: false, | |
| userDirectoryExists: false, | |
| files: [] | |
| }; | |
| try { | |
| const { owner, repo } = githubService.parseRepoUrl(githubService.repositories[i]); | |
| // 检查仓库是否可访问 | |
| await axios.get(`https://api.github.com/repos/${owner}/${repo}`, { | |
| headers: { | |
| 'Authorization': `token ${githubService.token}`, | |
| 'Accept': 'application/vnd.github.v3+json' | |
| } | |
| }); | |
| repoInfo.accessible = true; | |
| // 检查用户目录 | |
| try { | |
| const userDirResponse = await axios.get( | |
| `https://api.github.com/repos/${owner}/${repo}/contents/users/${userId}`, | |
| { | |
| headers: { | |
| 'Authorization': `token ${githubService.token}`, | |
| 'Accept': 'application/vnd.github.v3+json' | |
| } | |
| } | |
| ); | |
| repoInfo.userDirectoryExists = true; | |
| repoInfo.files = userDirResponse.data | |
| .filter(item => item.type === 'file' && item.name.endsWith('.json')) | |
| .map(file => ({ | |
| name: file.name, | |
| size: file.size, | |
| sha: file.sha | |
| })); | |
| } catch (userDirError) { | |
| repoInfo.userDirectoryError = userDirError.response?.status === 404 ? 'Directory not found' : userDirError.message; | |
| } | |
| } catch (repoError) { | |
| repoInfo.error = repoError.message; | |
| } | |
| debugInfo.repositories.push(repoInfo); | |
| } | |
| console.log('PPT Debug info:', debugInfo); | |
| res.json(debugInfo); | |
| } catch (error) { | |
| console.error('PPT Debug route error:', error); | |
| res.status(500).json({ | |
| error: error.message, | |
| stack: process.env.NODE_ENV === 'development' ? error.stack : undefined | |
| }); | |
| } | |
| }); | |
| // 添加仓库初始化端点 | |
| app.post('/api/github/initialize', async (req, res) => { | |
| try { | |
| console.log('=== Manual Repository Initialization ==='); | |
| const { default: githubService } = await import('./services/githubService.js'); | |
| if (githubService.useMemoryStorage) { | |
| return res.status(400).json({ | |
| error: 'Cannot initialize repository: using memory storage mode', | |
| reason: 'GitHub token not configured' | |
| }); | |
| } | |
| const { repoIndex = 0 } = req.body; | |
| console.log(`Initializing repository at index: ${repoIndex}`); | |
| const result = await githubService.initializeRepository(repoIndex); | |
| if (result.success) { | |
| res.json({ | |
| success: true, | |
| message: 'Repository initialized successfully', | |
| commit: result.commit, | |
| timestamp: new Date().toISOString() | |
| }); | |
| } else { | |
| res.status(500).json({ | |
| success: false, | |
| error: result.error, | |
| reason: result.reason | |
| }); | |
| } | |
| } catch (error) { | |
| console.error('Repository initialization error:', error); | |
| res.status(500).json({ | |
| error: error.message, | |
| stack: process.env.NODE_ENV === 'development' ? error.stack : undefined | |
| }); | |
| } | |
| }); | |
| // 添加GitHub权限详细检查路由 | |
| app.get('/api/debug/github-permissions', async (req, res) => { | |
| try { | |
| console.log('=== GitHub Token Permissions Check ==='); | |
| const { default: githubService } = await import('./services/githubService.js'); | |
| if (!githubService.token) { | |
| return res.status(400).json({ error: 'No GitHub token configured' }); | |
| } | |
| const debugInfo = { | |
| timestamp: new Date().toISOString(), | |
| tokenInfo: {}, | |
| repositoryTests: [] | |
| }; | |
| // 1. 检查token基本信息和权限 | |
| try { | |
| const userResponse = await axios.get('https://api.github.com/user', { | |
| headers: { | |
| 'Authorization': `token ${githubService.token}`, | |
| 'Accept': 'application/vnd.github.v3+json' | |
| } | |
| }); | |
| debugInfo.tokenInfo = { | |
| login: userResponse.data.login, | |
| id: userResponse.data.id, | |
| type: userResponse.data.type, | |
| company: userResponse.data.company, | |
| publicRepos: userResponse.data.public_repos, | |
| privateRepos: userResponse.data.total_private_repos, | |
| tokenScopes: userResponse.headers['x-oauth-scopes'] || 'Unknown' | |
| }; | |
| console.log('Token info:', debugInfo.tokenInfo); | |
| } catch (tokenError) { | |
| debugInfo.tokenError = { | |
| status: tokenError.response?.status, | |
| message: tokenError.message | |
| }; | |
| } | |
| // 2. 检查每个仓库的详细状态 | |
| for (let i = 0; i < githubService.repositories.length; i++) { | |
| const repoUrl = githubService.repositories[i]; | |
| const repoTest = { | |
| index: i, | |
| url: repoUrl, | |
| tests: {} | |
| }; | |
| try { | |
| const { owner, repo } = githubService.parseRepoUrl(repoUrl); | |
| repoTest.owner = owner; | |
| repoTest.repo = repo; | |
| // 测试1: 基本仓库访问 | |
| try { | |
| const repoResponse = await axios.get(`https://api.github.com/repos/${owner}/${repo}`, { | |
| headers: { | |
| 'Authorization': `token ${githubService.token}`, | |
| 'Accept': 'application/vnd.github.v3+json' | |
| } | |
| }); | |
| repoTest.tests.basicAccess = { | |
| success: true, | |
| repoExists: true, | |
| private: repoResponse.data.private, | |
| permissions: repoResponse.data.permissions, | |
| defaultBranch: repoResponse.data.default_branch, | |
| size: repoResponse.data.size | |
| }; | |
| } catch (repoError) { | |
| repoTest.tests.basicAccess = { | |
| success: false, | |
| status: repoError.response?.status, | |
| message: repoError.message, | |
| details: repoError.response?.data | |
| }; | |
| // 如果是404,检查是否是权限问题还是仓库不存在 | |
| if (repoError.response?.status === 404) { | |
| // 尝试不使用认证访问(如果是公开仓库应该能访问) | |
| try { | |
| await axios.get(`https://api.github.com/repos/${owner}/${repo}`); | |
| repoTest.tests.basicAccess.possibleCause = 'Repository exists but token lacks permission'; | |
| } catch (publicError) { | |
| if (publicError.response?.status === 404) { | |
| repoTest.tests.basicAccess.possibleCause = 'Repository does not exist'; | |
| } | |
| } | |
| } | |
| } | |
| // 测试2: 检查是否能列出用户的仓库 | |
| try { | |
| const userReposResponse = await axios.get(`https://api.github.com/users/${owner}/repos?per_page=100`, { | |
| headers: { | |
| 'Authorization': `token ${githubService.token}`, | |
| 'Accept': 'application/vnd.github.v3+json' | |
| } | |
| }); | |
| const hasRepo = userReposResponse.data.some(r => r.name === repo); | |
| repoTest.tests.userReposList = { | |
| success: true, | |
| totalRepos: userReposResponse.data.length, | |
| targetRepoFound: hasRepo, | |
| repoNames: userReposResponse.data.slice(0, 10).map(r => ({ name: r.name, private: r.private })) | |
| }; | |
| } catch (userReposError) { | |
| repoTest.tests.userReposList = { | |
| success: false, | |
| status: userReposError.response?.status, | |
| message: userReposError.message | |
| }; | |
| } | |
| } catch (parseError) { | |
| repoTest.parseError = parseError.message; | |
| } | |
| debugInfo.repositoryTests.push(repoTest); | |
| } | |
| // 3. 提供修复建议 | |
| const suggestions = []; | |
| if (debugInfo.tokenError) { | |
| suggestions.push('Token authentication failed - check if GITHUB_TOKEN is valid'); | |
| } | |
| debugInfo.repositoryTests.forEach((repoTest, index) => { | |
| if (!repoTest.tests.basicAccess?.success) { | |
| if (repoTest.tests.basicAccess?.status === 404) { | |
| if (repoTest.tests.basicAccess?.possibleCause === 'Repository does not exist') { | |
| suggestions.push(`Repository ${repoTest.url} does not exist - please create it on GitHub`); | |
| } else { | |
| suggestions.push(`Repository ${repoTest.url} exists but token lacks permission - check token scopes`); | |
| } | |
| } else if (repoTest.tests.basicAccess?.status === 403) { | |
| suggestions.push(`Permission denied for ${repoTest.url} - check if token has 'repo' scope`); | |
| } | |
| } | |
| }); | |
| debugInfo.suggestions = suggestions; | |
| console.log('GitHub permissions debug completed'); | |
| res.json(debugInfo); | |
| } catch (error) { | |
| console.error('GitHub permissions check error:', error); | |
| res.status(500).json({ | |
| error: error.message, | |
| stack: process.env.NODE_ENV === 'development' ? error.stack : undefined | |
| }); | |
| } | |
| }); | |
| // 添加大文件处理调试端点 | |
| app.get('/api/debug/large-files/:userId', async (req, res) => { | |
| try { | |
| const { userId } = req.params; | |
| console.log(`=== Large Files Debug for User: ${userId} ===`); | |
| const { default: githubService } = await import('./services/githubService.js'); | |
| const debugInfo = { | |
| timestamp: new Date().toISOString(), | |
| userId: userId, | |
| fileAnalysis: [] | |
| }; | |
| // 检查用户的所有PPT文件 | |
| const pptList = await githubService.getUserPPTList(userId); | |
| for (const ppt of pptList) { | |
| const fileInfo = { | |
| pptId: ppt.name, | |
| title: ppt.title, | |
| fileName: `${ppt.name}.json`, | |
| analysis: {} | |
| }; | |
| try { | |
| // 获取文件内容 | |
| const result = await githubService.getFile(userId, `${ppt.name}.json`, ppt.repoIndex || 0); | |
| if (result && result.content) { | |
| const content = result.content; | |
| const jsonString = JSON.stringify(content); | |
| const fileSize = Buffer.byteLength(jsonString, 'utf8'); | |
| fileInfo.analysis = { | |
| fileSize: fileSize, | |
| fileSizeKB: (fileSize / 1024).toFixed(2), | |
| slidesCount: content.slides?.length || 0, | |
| isChunked: !!content.isChunked, | |
| chunkedInfo: content.isChunked ? { | |
| totalChunks: content.totalChunks, | |
| totalSlides: content.totalSlides | |
| } : null, | |
| wasReassembled: !!result.isReassembled, | |
| metadata: content.metadata || 'No metadata', | |
| status: fileSize > 1024 * 1024 ? 'LARGE' : fileSize > 800 * 1024 ? 'MEDIUM' : 'NORMAL' | |
| }; | |
| // 分析每个slide的大小 | |
| if (content.slides && content.slides.length > 0) { | |
| const slideSizes = content.slides.map((slide, index) => { | |
| const slideJson = JSON.stringify(slide); | |
| const slideSize = Buffer.byteLength(slideJson, 'utf8'); | |
| return { | |
| index: index, | |
| size: slideSize, | |
| sizeKB: (slideSize / 1024).toFixed(2), | |
| elementsCount: slide.elements?.length || 0 | |
| }; | |
| }); | |
| // 找出最大的slides | |
| const largestSlides = slideSizes | |
| .sort((a, b) => b.size - a.size) | |
| .slice(0, 3); | |
| fileInfo.analysis.slideSummary = { | |
| averageSlideSize: (fileSize / content.slides.length).toFixed(0), | |
| largestSlides: largestSlides | |
| }; | |
| } | |
| } else { | |
| fileInfo.analysis = { error: 'Could not read file content' }; | |
| } | |
| } catch (error) { | |
| fileInfo.analysis = { | |
| error: error.message, | |
| errorType: error.name | |
| }; | |
| } | |
| debugInfo.fileAnalysis.push(fileInfo); | |
| } | |
| // 添加统计摘要 | |
| debugInfo.summary = { | |
| totalFiles: debugInfo.fileAnalysis.length, | |
| largeFiles: debugInfo.fileAnalysis.filter(f => f.analysis.status === 'LARGE').length, | |
| chunkedFiles: debugInfo.fileAnalysis.filter(f => f.analysis.isChunked).length, | |
| errors: debugInfo.fileAnalysis.filter(f => f.analysis.error).length | |
| }; | |
| console.log('Large files debug completed'); | |
| res.json(debugInfo); | |
| } catch (error) { | |
| console.error('Large files debug error:', error); | |
| res.status(500).json({ | |
| error: error.message, | |
| stack: process.env.NODE_ENV === 'development' ? error.stack : undefined | |
| }); | |
| } | |
| }); | |
| // 添加分块文件修复端点 | |
| app.post('/api/debug/fix-chunked-file/:userId/:pptId', async (req, res) => { | |
| try { | |
| const { userId, pptId } = req.params; | |
| console.log(`=== Fixing Chunked File: ${userId}/${pptId} ===`); | |
| const { default: githubService } = await import('./services/githubService.js'); | |
| const fileName = `${pptId}.json`; | |
| const result = { | |
| timestamp: new Date().toISOString(), | |
| userId, | |
| pptId, | |
| fileName, | |
| status: 'unknown', | |
| details: {}, | |
| actions: [] | |
| }; | |
| // 1. 检查主文件是否存在 | |
| let mainFile = null; | |
| let mainFileRepo = -1; | |
| for (let i = 0; i < githubService.repositories.length; i++) { | |
| try { | |
| const fileResult = await githubService.getFile(userId, fileName, i); | |
| if (fileResult) { | |
| mainFile = fileResult; | |
| mainFileRepo = i; | |
| result.details.mainFileFound = true; | |
| result.details.mainFileRepo = i; | |
| result.actions.push(`Main file found in repository ${i}`); | |
| break; | |
| } | |
| } catch (error) { | |
| continue; | |
| } | |
| } | |
| if (!mainFile) { | |
| result.status = 'error'; | |
| result.details.error = 'Main file not found in any repository'; | |
| return res.json(result); | |
| } | |
| const content = mainFile.content; | |
| // 2. 检查是否是分块文件 | |
| if (!content.isChunked) { | |
| result.status = 'normal'; | |
| result.details.isChunked = false; | |
| result.details.slideCount = content.slides?.length || 0; | |
| result.actions.push('File is not chunked, no action needed'); | |
| return res.json(result); | |
| } | |
| // 3. 分析分块文件状态 | |
| result.details.isChunked = true; | |
| result.details.totalChunks = content.totalChunks; | |
| result.details.totalSlides = content.totalSlides; | |
| result.details.mainFileSlides = content.slides?.length || 0; | |
| // 4. 检查所有chunk文件 | |
| const chunkStatus = []; | |
| let totalFoundSlides = content.slides?.length || 0; | |
| for (let i = 1; i < content.totalChunks; i++) { | |
| const chunkFileName = fileName.replace('.json', `_chunk_${i}.json`); | |
| const chunkInfo = { | |
| index: i, | |
| fileName: chunkFileName, | |
| found: false, | |
| slides: 0, | |
| error: null | |
| }; | |
| try { | |
| const repoUrl = githubService.repositories[mainFileRepo]; | |
| const { owner, repo } = githubService.parseRepoUrl(repoUrl); | |
| const path = `users/${userId}/${chunkFileName}`; | |
| const response = await axios.get( | |
| `${githubService.apiUrl}/repos/${owner}/${repo}/contents/${path}`, | |
| { | |
| headers: { | |
| 'Authorization': `token ${githubService.token}`, | |
| 'Accept': 'application/vnd.github.v3+json' | |
| }, | |
| timeout: 30000 | |
| } | |
| ); | |
| const chunkContent = Buffer.from(response.data.content, 'base64').toString('utf8'); | |
| const chunkData = JSON.parse(chunkContent); | |
| chunkInfo.found = true; | |
| chunkInfo.slides = chunkData.slides?.length || 0; | |
| totalFoundSlides += chunkInfo.slides; | |
| result.actions.push(`Chunk ${i} found: ${chunkInfo.slides} slides`); | |
| } catch (error) { | |
| chunkInfo.error = error.message; | |
| result.actions.push(`Chunk ${i} missing or error: ${error.message}`); | |
| } | |
| chunkStatus.push(chunkInfo); | |
| } | |
| result.details.chunks = chunkStatus; | |
| result.details.totalFoundSlides = totalFoundSlides; | |
| result.details.missingSlides = content.totalSlides - totalFoundSlides; | |
| // 5. 判断状态和建议修复方案 | |
| const missingChunks = chunkStatus.filter(chunk => !chunk.found); | |
| if (missingChunks.length === 0 && totalFoundSlides === content.totalSlides) { | |
| result.status = 'healthy'; | |
| result.actions.push('All chunks found, file should load correctly'); | |
| } else if (missingChunks.length > 0) { | |
| result.status = 'incomplete'; | |
| result.details.missingChunks = missingChunks.map(c => c.index); | |
| result.actions.push(`Missing chunks: ${missingChunks.map(c => c.index).join(', ')}`); | |
| // 提供修复建议 | |
| if (totalFoundSlides >= content.totalSlides * 0.8) { | |
| result.actions.push('Recommendation: Reassemble available slides into single file'); | |
| result.details.recommendation = 'reassemble'; | |
| } else { | |
| result.actions.push('Recommendation: File may be corrupted, consider restoration from backup'); | |
| result.details.recommendation = 'restore'; | |
| } | |
| } else { | |
| result.status = 'mismatch'; | |
| result.actions.push('Slide count mismatch detected'); | |
| } | |
| console.log('Chunked file analysis completed:', result); | |
| res.json(result); | |
| } catch (error) { | |
| console.error('Chunked file fix error:', error); | |
| res.status(500).json({ | |
| error: error.message, | |
| stack: process.env.NODE_ENV === 'development' ? error.stack : undefined | |
| }); | |
| } | |
| }); | |
| // 添加分块文件重组端点 | |
| app.post('/api/debug/reassemble-chunked-file/:userId/:pptId', async (req, res) => { | |
| try { | |
| const { userId, pptId } = req.params; | |
| console.log(`=== Reassembling Chunked File: ${userId}/${pptId} ===`); | |
| const { default: githubService } = await import('./services/githubService.js'); | |
| const fileName = `${pptId}.json`; | |
| // 强制重新组装文件 | |
| const result = await githubService.getFile(userId, fileName, 0); | |
| if (!result) { | |
| return res.status(404).json({ error: 'File not found' }); | |
| } | |
| if (result.isReassembled) { | |
| res.json({ | |
| success: true, | |
| message: 'File reassembled successfully', | |
| slideCount: result.content.slides?.length || 0, | |
| wasChunked: !!result.content.reassembledInfo, | |
| reassembledInfo: result.content.reassembledInfo | |
| }); | |
| } else { | |
| res.json({ | |
| success: true, | |
| message: 'File was not chunked', | |
| slideCount: result.content.slides?.length || 0, | |
| wasChunked: false | |
| }); | |
| } | |
| } catch (error) { | |
| console.error('File reassembly error:', error); | |
| res.status(500).json({ | |
| error: error.message, | |
| details: error.stack | |
| }); | |
| } | |
| }); | |
| // 添加路由注册日志 | |
| console.log('Importing route modules...'); | |
| console.log('Auth routes imported:', !!authRoutes); | |
| console.log('PPT routes imported:', !!pptRoutes); | |
| console.log('Public routes imported:', !!publicRoutes); | |
| // 认证相关路由(不需要认证) | |
| app.use('/api/auth', authRoutes); | |
| // 公共访问路由(不需要认证) | |
| app.use('/api/public', publicRoutes); | |
| // 添加测试路由来验证 PPT 路由是否工作(不需要认证) | |
| app.get('/api/ppt/test', (req, res) => { | |
| res.json({ message: 'PPT routes are working', timestamp: new Date().toISOString() }); | |
| }); | |
| // PPT管理路由(需要认证) | |
| app.use('/api/ppt', (req, res, next) => { | |
| console.log(`PPT route accessed: ${req.method} ${req.path}`); | |
| next(); | |
| }, authenticateToken, pptRoutes); | |
| console.log('All routes registered successfully'); | |
| // 添加调试中间件 - 处理未匹配的API路由 | |
| app.use('/api/*', (req, res) => { | |
| console.log(`Unmatched API route: ${req.method} ${req.path}`); | |
| res.status(404).json({ error: 'API route not found', path: req.path }); | |
| }); | |
| // 前端路由处理 - 修复ES模块兼容性 | |
| app.get('*', (req, res) => { | |
| const indexPath = path.join(frontendDistPath, 'index.html'); | |
| console.log(`Serving frontend route: ${req.path}, index.html path: ${indexPath}`); | |
| // 使用ES模块的fs检查文件是否存在 | |
| if (fs.existsSync(indexPath)) { | |
| res.sendFile(indexPath); | |
| } else { | |
| console.error('index.html not found at:', indexPath); | |
| res.status(404).send(` | |
| <h1>前端文件未找到</h1> | |
| <p>index.html路径: ${indexPath}</p> | |
| <p>请确保前端已正确构建</p> | |
| <a href="/test">访问API测试页面</a> | |
| `); | |
| } | |
| }); | |
| // 错误处理中间件 | |
| app.use(errorHandler); | |
| app.listen(PORT, '0.0.0.0', () => { | |
| console.log(`Server is running on port ${PORT}`); | |
| console.log(`Environment: ${process.env.NODE_ENV || 'development'}`); | |
| }); |