383 lines
11 KiB
JavaScript
383 lines
11 KiB
JavaScript
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 { forceRefresh } = req.query
|
|
const stats = await repositoryService.getStats(forceRefresh === 'true')
|
|
res.json(ResponseUtil.success(stats))
|
|
} catch (error) {
|
|
res.status(500).json(ResponseUtil.error(error.message))
|
|
}
|
|
})
|
|
|
|
// 清除磁盘使用情况缓存
|
|
router.post('/stats/clear-cache', async (req, res) => {
|
|
try {
|
|
const result = await repositoryService.clearDiskUsageCache()
|
|
res.json(ResponseUtil.success(result))
|
|
} 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
|