Files
soul/app/api/db/book/route.ts

476 lines
14 KiB
TypeScript
Raw Normal View History

/**
* API
* CRUD操作 - ///
*
*/
import { NextRequest, NextResponse } from 'next/server'
import { query } from '@/lib/db'
import fs from 'fs'
import path from 'path'
import { bookData } from '@/lib/book-data'
// 获取章节内容(从数据库或文件系统)
async function getSectionContent(id: string): Promise<{content: string, source: 'db' | 'file'} | null> {
try {
// 先从数据库查询
const results = await query(
'SELECT content, section_title FROM chapters WHERE id = ?',
[id]
) as any[]
if (results.length > 0 && results[0].content) {
return { content: results[0].content, source: 'db' }
}
} catch (e) {
console.log('[Book API] 数据库查询失败,尝试从文件读取:', e)
}
// 从文件系统读取
const filePath = findSectionFilePath(id)
if (filePath && fs.existsSync(filePath)) {
try {
const content = fs.readFileSync(filePath, 'utf-8')
return { content, source: 'file' }
} catch (e) {
console.error('[Book API] 读取文件失败:', e)
}
}
return null
}
// 根据section ID查找对应的文件路径
function findSectionFilePath(id: string): string | null {
for (const part of bookData) {
for (const chapter of part.chapters) {
const section = chapter.sections.find(s => s.id === id)
if (section?.filePath) {
return path.join(process.cwd(), section.filePath)
}
}
}
return null
}
// 获取section的完整信息
function getSectionInfo(id: string) {
for (const part of bookData) {
for (const chapter of part.chapters) {
const section = chapter.sections.find(s => s.id === id)
if (section) {
return {
section,
chapter,
part,
partId: part.id,
chapterId: chapter.id,
partTitle: part.title,
chapterTitle: chapter.title
}
}
}
}
return null
}
/**
* GET -
* :
* - id: 章节ID
* - action: 'read' | 'export' | 'list'
*/
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const action = searchParams.get('action') || 'read'
const id = searchParams.get('id')
try {
// 读取单个章节
if (action === 'read' && id) {
const result = await getSectionContent(id)
const sectionInfo = getSectionInfo(id)
if (result) {
return NextResponse.json({
success: true,
section: {
id,
content: result.content,
source: result.source,
title: sectionInfo?.section.title || '',
price: sectionInfo?.section.price || 1,
partTitle: sectionInfo?.partTitle,
chapterTitle: sectionInfo?.chapterTitle
}
})
} else {
return NextResponse.json({
success: false,
error: '章节不存在或无法读取'
}, { status: 404 })
}
}
// 导出所有章节
if (action === 'export') {
const sections: any[] = []
for (const part of bookData) {
for (const chapter of part.chapters) {
for (const section of chapter.sections) {
const content = await getSectionContent(section.id)
sections.push({
id: section.id,
title: section.title,
price: section.price,
isFree: section.isFree,
partId: part.id,
partTitle: part.title,
chapterId: chapter.id,
chapterTitle: chapter.title,
content: content?.content || '',
source: content?.source || 'none'
})
}
}
}
const blob = JSON.stringify(sections, null, 2)
return new NextResponse(blob, {
headers: {
'Content-Type': 'application/json',
'Content-Disposition': `attachment; filename="book_sections_${new Date().toISOString().split('T')[0]}.json"`
}
})
}
// 列出所有章节(不含内容)
// 优先从数据库读取,确保新建章节能立即显示
if (action === 'list') {
const sectionsFromDb = new Map<string, any>()
try {
const rows = await query(`
SELECT id, part_id, part_title, chapter_id, chapter_title, section_title,
price, is_free, content
FROM chapters ORDER BY part_id, chapter_id, id
`) as any[]
if (rows && rows.length > 0) {
for (const r of rows) {
sectionsFromDb.set(r.id, {
id: r.id,
title: r.section_title || '',
price: r.price ?? 1,
isFree: !!r.is_free,
partId: r.part_id || 'part-1',
partTitle: r.part_title || '',
chapterId: r.chapter_id || 'chapter-1',
chapterTitle: r.chapter_title || '',
filePath: ''
})
}
}
} catch (e) {
console.log('[Book API] list 从数据库读取失败,回退到 bookData:', (e as Error).message)
}
// 合并:以数据库为准,数据库没有的用 bookData 补
const sections: any[] = []
for (const part of bookData) {
for (const chapter of part.chapters) {
for (const section of chapter.sections) {
const dbRow = sectionsFromDb.get(section.id)
sections.push(dbRow || {
id: section.id,
title: section.title,
price: section.price,
isFree: section.isFree,
partId: part.id,
partTitle: part.title,
chapterId: chapter.id,
chapterTitle: chapter.title,
filePath: section.filePath
})
sectionsFromDb.delete(section.id)
}
}
}
// 数据库有但 bookData 没有的(新建章节)
for (const [, v] of sectionsFromDb) {
sections.push(v)
}
// 按 id 去重,避免数据库重复或合并逻辑导致同一文章出现多次
const seen = new Set<string>()
const deduped = sections.filter((s) => {
if (seen.has(s.id)) return false
seen.add(s.id)
return true
})
return NextResponse.json({
success: true,
sections: deduped,
total: deduped.length
})
}
return NextResponse.json({
success: false,
error: '无效的操作或缺少参数'
}, { status: 400 })
} catch (error) {
console.error('[Book API] GET错误:', error)
return NextResponse.json({
success: false,
error: '获取章节失败: ' + (error as Error).message
}, { status: 500 })
}
}
/**
* POST - /
* action:
* - sync: 同步文件系统到数据库
* - import:
*/
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const { action, data } = body
// 同步到数据库
if (action === 'sync') {
let synced = 0
let failed = 0
for (const part of bookData) {
for (const chapter of part.chapters) {
for (const section of chapter.sections) {
try {
const filePath = path.join(process.cwd(), section.filePath)
let content = ''
if (fs.existsSync(filePath)) {
content = fs.readFileSync(filePath, 'utf-8')
}
// 插入或更新到数据库
await query(`
INSERT INTO chapters (id, part_id, part_title, chapter_id, chapter_title, section_title, content, word_count, is_free, price, sort_order, status)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'published')
ON DUPLICATE KEY UPDATE
part_title = VALUES(part_title),
chapter_title = VALUES(chapter_title),
section_title = VALUES(section_title),
content = VALUES(content),
word_count = VALUES(word_count),
is_free = VALUES(is_free),
price = VALUES(price),
updated_at = CURRENT_TIMESTAMP
`, [
section.id,
part.id,
part.title,
chapter.id,
chapter.title,
section.title,
content,
content.length,
section.isFree,
section.price,
synced
])
synced++
} catch (e) {
console.error(`[Book API] 同步章节${section.id}失败:`, e)
failed++
}
}
}
}
return NextResponse.json({
success: true,
message: `同步完成:成功 ${synced} 个章节,失败 ${failed}`,
synced,
failed
})
}
// 导入数据
if (action === 'import' && data) {
let imported = 0
let failed = 0
for (const item of data) {
try {
await query(`
INSERT INTO chapters (id, part_id, part_title, chapter_id, chapter_title, section_title, content, word_count, is_free, price, status)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'published')
ON DUPLICATE KEY UPDATE
section_title = VALUES(section_title),
content = VALUES(content),
word_count = VALUES(word_count),
is_free = VALUES(is_free),
price = VALUES(price),
updated_at = CURRENT_TIMESTAMP
`, [
item.id,
item.partId || 'part-1',
item.partTitle || '未分类',
item.chapterId || 'chapter-1',
item.chapterTitle || '未分类',
item.title,
item.content || '',
(item.content || '').length,
item.is_free || false,
item.price || 1
])
imported++
} catch (e) {
console.error(`[Book API] 导入章节${item.id}失败:`, e)
failed++
}
}
return NextResponse.json({
success: true,
message: `导入完成:成功 ${imported} 个章节,失败 ${failed}`,
imported,
failed
})
}
return NextResponse.json({
success: false,
error: '无效的操作'
}, { status: 400 })
} catch (error) {
console.error('[Book API] POST错误:', error)
return NextResponse.json({
success: false,
error: '操作失败: ' + (error as Error).message
}, { status: 500 })
}
}
/**
* PUT -
*
*/
export async function PUT(request: NextRequest) {
try {
const body = await request.json()
const { id, title, content, price, saveToFile = true, partId, chapterId, partTitle, chapterTitle, isFree } = body
if (!id) {
return NextResponse.json({
success: false,
error: '章节ID不能为空'
}, { status: 400 })
}
const sectionInfo = getSectionInfo(id)
const finalPartId = partId || sectionInfo?.partId || 'part-1'
const finalPartTitle = partTitle || sectionInfo?.partTitle || '未分类'
const finalChapterId = chapterId || sectionInfo?.chapterId || 'chapter-1'
const finalChapterTitle = chapterTitle || sectionInfo?.chapterTitle || '未分类'
const finalPrice = price ?? sectionInfo?.section?.price ?? 1
const finalIsFree = isFree ?? sectionInfo?.section?.isFree ?? false
// 更新数据库(含新建章节)
try {
await query(`
INSERT INTO chapters (id, part_id, part_title, chapter_id, chapter_title, section_title, content, word_count, is_free, price, status)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'published')
ON DUPLICATE KEY UPDATE
part_id = VALUES(part_id),
part_title = VALUES(part_title),
chapter_id = VALUES(chapter_id),
chapter_title = VALUES(chapter_title),
section_title = VALUES(section_title),
content = VALUES(content),
word_count = VALUES(word_count),
is_free = VALUES(is_free),
price = VALUES(price),
updated_at = CURRENT_TIMESTAMP
`, [
id,
finalPartId,
finalPartTitle,
finalChapterId,
finalChapterTitle,
title || sectionInfo?.section?.title || '',
content || '',
(content || '').length,
finalIsFree,
finalPrice
])
} catch (e) {
console.error('[Book API] 更新数据库失败:', e)
}
// 同时保存到文件系统
if (saveToFile && sectionInfo?.section.filePath) {
const filePath = path.join(process.cwd(), sectionInfo.section.filePath)
try {
// 确保目录存在
const dir = path.dirname(filePath)
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
}
fs.writeFileSync(filePath, content || '', 'utf-8')
} catch (e) {
console.error('[Book API] 保存文件失败:', e)
}
}
return NextResponse.json({
success: true,
message: '章节更新成功',
id
})
} catch (error) {
console.error('[Book API] PUT错误:', error)
return NextResponse.json({
success: false,
error: '更新章节失败: ' + (error as Error).message
}, { status: 500 })
}
}
/**
* DELETE -
*/
export async function DELETE(request: NextRequest) {
const { searchParams } = new URL(request.url)
const id = searchParams.get('id')
if (!id) {
return NextResponse.json({
success: false,
error: '章节ID不能为空'
}, { status: 400 })
}
try {
// 从数据库删除
await query('DELETE FROM chapters WHERE id = ?', [id])
return NextResponse.json({
success: true,
message: '章节删除成功',
id
})
} catch (error) {
console.error('[Book API] DELETE错误:', error)
return NextResponse.json({
success: false,
error: '删除章节失败: ' + (error as Error).message
}, { status: 500 })
}
}