import axios from 'axios'; import { GITHUB_CONFIG } from '../config/users.js'; import memoryStorageService from './memoryStorageService.js'; class GitHubService { constructor() { this.apiUrl = GITHUB_CONFIG.apiUrl; this.token = GITHUB_CONFIG.token; this.repositories = GITHUB_CONFIG.repositories; this.useMemoryStorage = !this.token; // 如果没有token,使用内存存储 console.log('=== GitHub Service Configuration ==='); console.log('Token configured:', !!this.token); console.log('Token preview:', this.token ? `${this.token.substring(0, 8)}...` : 'Not set'); console.log('Repositories:', this.repositories); console.log('Using memory storage:', this.useMemoryStorage); if (!this.token) { console.warn('GitHub token not configured, using memory storage for development'); } } // 验证GitHub连接 async validateConnection() { if (this.useMemoryStorage) { return { valid: false, reason: 'No GitHub token configured' }; } try { // 测试GitHub API连接 const response = await axios.get(`${this.apiUrl}/user`, { headers: { 'Authorization': `token ${this.token}`, 'Accept': 'application/vnd.github.v3+json' } }); console.log('GitHub API connection successful:', response.data.login); // 测试仓库访问 const repoResults = []; for (const repoUrl of this.repositories) { try { const { owner, repo } = this.parseRepoUrl(repoUrl); const repoResponse = await axios.get(`${this.apiUrl}/repos/${owner}/${repo}`, { headers: { 'Authorization': `token ${this.token}`, 'Accept': 'application/vnd.github.v3+json' } }); repoResults.push({ url: repoUrl, accessible: true, name: repoResponse.data.full_name }); } catch (error) { repoResults.push({ url: repoUrl, accessible: false, error: error.message }); } } return { valid: true, user: response.data.login, repositories: repoResults }; } catch (error) { console.error('GitHub connection validation failed:', error.message); return { valid: false, reason: error.message }; } } // 获取仓库信息 parseRepoUrl(repoUrl) { const match = repoUrl.match(/github\.com\/([^\/]+)\/([^\/]+)/); if (!match) throw new Error('Invalid GitHub repository URL'); return { owner: match[1], repo: match[2] }; } // 获取文件内容 async getFile(userId, fileName, repoIndex = 0) { // 如果使用内存存储 if (this.useMemoryStorage) { return await memoryStorageService.getFile(userId, fileName); } // 原有的GitHub逻辑 try { const repoUrl = this.repositories[repoIndex]; const { owner, repo } = this.parseRepoUrl(repoUrl); const path = `users/${userId}/${fileName}`; const response = await axios.get( `${this.apiUrl}/repos/${owner}/${repo}/contents/${path}`, { headers: { 'Authorization': `token ${this.token}`, 'Accept': 'application/vnd.github.v3+json' } } ); const content = Buffer.from(response.data.content, 'base64').toString('utf8'); return { content: JSON.parse(content), sha: response.data.sha }; } catch (error) { if (error.response?.status === 404) { return null; } throw error; } } // 保存文件 async saveFile(userId, fileName, data, repoIndex = 0) { // 如果使用内存存储 if (this.useMemoryStorage) { return await memoryStorageService.saveFile(userId, fileName, data); } // 原有的GitHub逻辑 const repoUrl = this.repositories[repoIndex]; const { owner, repo } = this.parseRepoUrl(repoUrl); const path = `users/${userId}/${fileName}`; // 先尝试获取现有文件的SHA let sha = null; try { const existing = await this.getFile(userId, fileName, repoIndex); if (existing) { sha = existing.sha; } } catch (error) { // 忽略获取SHA的错误 } const content = Buffer.from(JSON.stringify(data, null, 2)).toString('base64'); const payload = { message: `Update ${fileName} for user ${userId}`, content: content, branch: 'main' }; if (sha) { payload.sha = sha; } const response = await axios.put( `${this.apiUrl}/repos/${owner}/${repo}/contents/${path}`, payload, { headers: { 'Authorization': `token ${this.token}`, 'Accept': 'application/vnd.github.v3+json' } } ); return response.data; } // 获取用户的所有PPT列表 async getUserPPTList(userId) { // 如果使用内存存储 if (this.useMemoryStorage) { return await memoryStorageService.getUserPPTList(userId); } // 原有的GitHub逻辑 const results = []; for (let i = 0; i < this.repositories.length; i++) { try { const repoUrl = this.repositories[i]; const { owner, repo } = this.parseRepoUrl(repoUrl); const path = `users/${userId}`; const response = await axios.get( `${this.apiUrl}/repos/${owner}/${repo}/contents/${path}`, { headers: { 'Authorization': `token ${this.token}`, 'Accept': 'application/vnd.github.v3+json' } } ); const files = response.data .filter(item => item.type === 'file' && item.name.endsWith('.json')); // 获取每个PPT文件的实际内容以读取标题 for (const file of files) { try { const pptId = file.name.replace('.json', ''); const fileContent = await this.getFile(userId, file.name, i); if (fileContent && fileContent.content) { results.push({ name: pptId, title: fileContent.content.title || '未命名演示文稿', lastModified: fileContent.content.updatedAt || fileContent.content.createdAt, repoIndex: i, repoUrl: repoUrl }); } } catch (error) { // 如果读取单个文件失败,使用文件名作为标题 console.warn(`Failed to read PPT content for ${file.name}:`, error.message); results.push({ name: file.name.replace('.json', ''), title: file.name.replace('.json', ''), lastModified: new Date().toISOString(), repoIndex: i, repoUrl: repoUrl }); } } } catch (error) { if (error.response?.status !== 404) { console.error(`Error fetching files from repo ${i}:`, error.message); } } } return results; } // 删除文件 async deleteFile(userId, fileName, repoIndex = 0) { // 如果使用内存存储 if (this.useMemoryStorage) { return await memoryStorageService.deleteFile(userId, fileName); } // 原有的GitHub逻辑 const existing = await this.getFile(userId, fileName, repoIndex); if (!existing) { throw new Error('File not found'); } const repoUrl = this.repositories[repoIndex]; const { owner, repo } = this.parseRepoUrl(repoUrl); const path = `users/${userId}/${fileName}`; const response = await axios.delete( `${this.apiUrl}/repos/${owner}/${repo}/contents/${path}`, { data: { message: `Delete ${fileName} for user ${userId}`, sha: existing.sha, branch: 'main' }, headers: { 'Authorization': `token ${this.token}`, 'Accept': 'application/vnd.github.v3+json' } } ); return response.data; } } export default new GitHubService();