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,使用内存存储 if (!this.token) { console.warn('GitHub token not configured, using memory storage for development'); } } // 获取仓库信息 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();