const express = require('express') const path = require('path') const fs = require('fs').promises const RepositoryService = require('../services/repository') const ResponseUtil = require('../utils/response') const router = express.Router() const repositoryService = new RepositoryService() // 初始化仓库 router.post('/initialize', async (req, res) => { try { const result = await repositoryService.initialize() res.json(ResponseUtil.success(result)) } catch (error) { res.status(500).json(ResponseUtil.error(error.message)) } }) // 获取仓库配置 router.get('/config', async (req, res) => { try { const config = await repositoryService.getConfig() res.json(ResponseUtil.success(config)) } catch (error) { res.status(500).json(ResponseUtil.error(error.message)) } }) // 更新仓库配置 router.put('/config', async (req, res) => { try { const result = await repositoryService.updateConfig(req.body) res.json(ResponseUtil.success(result)) } catch (error) { res.status(500).json(ResponseUtil.error(error.message)) } }) // 重置仓库配置为默认值 router.post('/config/reset', async (req, res) => { try { const result = await repositoryService.resetConfig() res.json(ResponseUtil.success(result)) } catch (error) { res.status(500).json(ResponseUtil.error(error.message)) } }) // 获取仓库统计信息 router.get('/stats', async (req, res) => { try { const stats = await repositoryService.getStats() res.json(ResponseUtil.success(stats)) } catch (error) { res.status(500).json(ResponseUtil.error(error.message)) } }) // 获取所有作者列表 router.get('/artists', async (req, res) => { try { const { offset = 0, limit = 50 } = req.query const artists = await repositoryService.getArtists(parseInt(offset), parseInt(limit)) res.json(ResponseUtil.success(artists)) } catch (error) { res.status(500).json(ResponseUtil.error(error.message)) } }) // 获取作者作品列表 router.get('/artists/:artistName/artworks', async (req, res) => { try { const { artistName } = req.params const { offset = 0, limit = 50 } = req.query const artworks = await repositoryService.getArtworksByArtist( artistName, parseInt(offset), parseInt(limit) ) res.json(ResponseUtil.success(artworks)) } catch (error) { res.status(500).json(ResponseUtil.error(error.message)) } }) // 获取所有作品列表 router.get('/artworks', async (req, res) => { try { const { offset = 0, limit = 50 } = req.query const results = await repositoryService.searchArtworks('', parseInt(offset), parseInt(limit)) res.json(ResponseUtil.success(results)) } catch (error) { res.status(500).json(ResponseUtil.error(error.message)) } }) // 搜索作品 router.get('/search', async (req, res) => { try { const { q, offset = 0, limit = 50 } = req.query if (!q) { return res.status(400).json(ResponseUtil.error('搜索关键词不能为空')) } const results = await repositoryService.searchArtworks(q, parseInt(offset), parseInt(limit)) res.json(ResponseUtil.success(results)) } catch (error) { res.status(500).json(ResponseUtil.error(error.message)) } }) // 获取作品详情 router.get('/artworks/:artworkId', async (req, res) => { try { const { artworkId } = req.params const artwork = await repositoryService.findArtworkById(artworkId) if (!artwork) { return res.status(404).json(ResponseUtil.error('作品不存在')) } res.json(ResponseUtil.success(artwork)) } catch (error) { res.status(500).json(ResponseUtil.error(error.message)) } }) // 删除作品 router.delete('/artworks/:artworkId', async (req, res) => { try { const { artworkId } = req.params const result = await repositoryService.deleteArtwork(artworkId) res.json(ResponseUtil.success(result)) } catch (error) { res.status(500).json(ResponseUtil.error(error.message)) } }) // 自动迁移旧项目 router.post('/migrate', async (req, res) => { try { const { sourceDir } = req.body if (!sourceDir) { return res.status(400).json(ResponseUtil.error('源目录不能为空')) } const result = await repositoryService.migrateOldProjects(sourceDir) res.json(ResponseUtil.success(result)) } catch (error) { res.status(500).json(ResponseUtil.error(error.message)) } }) // 文件预览代理 router.get('/preview', async (req, res) => { try { const { path: filePath } = req.query if (!filePath) { return res.status(400).json(ResponseUtil.error('文件路径不能为空')) } const currentBaseDir = repositoryService.getCurrentBaseDir() const fullPath = path.join(currentBaseDir, filePath) // 安全检查:确保文件在仓库目录内 const relativePath = path.relative(currentBaseDir, fullPath) if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) { return res.status(403).json(ResponseUtil.error('访问被拒绝')) } // 检查文件是否存在 try { await fs.access(fullPath) } catch (error) { return res.status(404).json(ResponseUtil.error('文件不存在')) } // 获取文件信息 const stats = await fs.stat(fullPath) const ext = path.extname(fullPath).toLowerCase() // 设置响应头 res.setHeader('Content-Type', getContentType(ext)) res.setHeader('Content-Length', stats.size) res.setHeader('Cache-Control', 'public, max-age=3600') // 流式传输文件 const fileStream = require('fs').createReadStream(fullPath) fileStream.pipe(res) } catch (error) { res.status(500).json(ResponseUtil.error(error.message)) } }) // 获取文件信息 router.get('/file-info', async (req, res) => { try { const { path: filePath } = req.query if (!filePath) { return res.status(400).json(ResponseUtil.error('文件路径不能为空')) } const currentBaseDir = repositoryService.getCurrentBaseDir() const fullPath = path.join(currentBaseDir, filePath) // 安全检查 const relativePath = path.relative(currentBaseDir, fullPath) if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) { return res.status(403).json(ResponseUtil.error('访问被拒绝')) } const stats = await fs.stat(fullPath) const ext = path.extname(fullPath).toLowerCase() res.json(ResponseUtil.success({ name: path.basename(fullPath), path: filePath, size: stats.size, extension: ext, modifiedAt: stats.mtime, createdAt: stats.birthtime, contentType: getContentType(ext), previewUrl: `/api/repository/preview?path=${encodeURIComponent(filePath)}` })) } catch (error) { res.status(500).json(ResponseUtil.error(error.message)) } }) /** * 获取目录结构 * GET /api/repository/directory */ router.get('/directory', async (req, res) => { try { const { path: dirPath = '' } = req.query const currentBaseDir = repositoryService.getCurrentBaseDir() const fullPath = path.join(currentBaseDir, dirPath) // 安全检查 const relativePath = path.relative(currentBaseDir, fullPath) if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) { return res.status(403).json(ResponseUtil.error('访问被拒绝')) } const entries = await fs.readdir(fullPath, { withFileTypes: true }) const items = [] for (const entry of entries) { const itemPath = path.join(dirPath, entry.name) const fullItemPath = path.join(fullPath, entry.name) if (entry.isDirectory()) { items.push({ type: 'directory', name: entry.name, path: itemPath }) } else { const ext = path.extname(entry.name).toLowerCase() if (repositoryService.config.allowedExtensions.includes(ext)) { const stats = await fs.stat(fullItemPath) items.push({ type: 'file', name: entry.name, path: itemPath, size: stats.size, extension: ext, modifiedAt: stats.mtime }) } } } res.json(ResponseUtil.success({ path: dirPath, items: items.sort((a, b) => { // 目录在前,文件在后 if (a.type !== b.type) { return a.type === 'directory' ? -1 : 1 } return a.name.localeCompare(b.name) }) })) } catch (error) { res.status(500).json(ResponseUtil.error(error.message)) } }) /** * 检查作品是否已下载 * GET /api/repository/check-downloaded/:artworkId */ router.get('/check-downloaded/:artworkId', async (req, res) => { try { const { artworkId } = req.params if (!artworkId || isNaN(parseInt(artworkId))) { return res.status(400).json(ResponseUtil.error('无效的作品ID')) } const isDownloaded = await repositoryService.isArtworkDownloaded(parseInt(artworkId)) res.json(ResponseUtil.success({ artwork_id: parseInt(artworkId), is_downloaded: isDownloaded })) } catch (error) { res.status(500).json(ResponseUtil.error(error.message)) } }) /** * 检查目录是否存在 * GET /api/repository/check-directory */ router.get('/check-directory', async (req, res) => { try { const { path: dirPath } = req.query if (!dirPath) { return res.status(400).json(ResponseUtil.error('目录路径不能为空')) } const exists = await repositoryService.checkDirectoryExists(dirPath) res.json(ResponseUtil.success({ path: dirPath, exists: exists })) } catch (error) { res.status(500).json(ResponseUtil.error(error.message)) } }) /** * 从旧目录迁移到新目录 * POST /api/repository/migrate-old-to-new */ router.post('/migrate-old-to-new', async (req, res) => { try { const { oldDir, newDir } = req.body if (!oldDir || !newDir) { return res.status(400).json(ResponseUtil.error('旧目录和新目录路径都不能为空')) } const result = await repositoryService.migrateFromOldToNew(oldDir, newDir) res.json(ResponseUtil.success(result)) } catch (error) { res.status(500).json(ResponseUtil.error(error.message)) } }) // 获取文件信息 function getContentType(extension) { const contentTypes = { '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.gif': 'image/gif', '.webp': 'image/webp', '.bmp': 'image/bmp', '.svg': 'image/svg+xml' } return contentTypes[extension.toLowerCase()] || 'application/octet-stream' } module.exports = router