2026-01-25 09:57:21 +08:00
|
|
|
|
// app/api/book/chapters/route.ts
|
2026-02-06 18:34:02 +08:00
|
|
|
|
// 章节管理API - 使用 Prisma ORM
|
|
|
|
|
|
// 优势:类型安全、防SQL注入、简化查询
|
2026-01-25 09:57:21 +08:00
|
|
|
|
|
|
|
|
|
|
import { NextRequest, NextResponse } from 'next/server'
|
2026-02-06 18:34:02 +08:00
|
|
|
|
import { prisma } from '@/lib/prisma'
|
2026-01-25 09:57:21 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* GET - 获取章节列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function GET(req: NextRequest) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { searchParams } = new URL(req.url)
|
|
|
|
|
|
const partId = searchParams.get('partId')
|
|
|
|
|
|
const status = searchParams.get('status') || 'published'
|
|
|
|
|
|
const page = parseInt(searchParams.get('page') || '1')
|
|
|
|
|
|
const pageSize = parseInt(searchParams.get('pageSize') || '100')
|
|
|
|
|
|
|
2026-02-06 18:34:02 +08:00
|
|
|
|
// 构建 Prisma where 条件
|
|
|
|
|
|
const where: any = {}
|
|
|
|
|
|
if (partId) where.part_id = partId
|
|
|
|
|
|
if (status && status !== 'all') where.status = status as any
|
|
|
|
|
|
|
|
|
|
|
|
// 使用 Prisma 分页查询
|
|
|
|
|
|
const [results, total] = await Promise.all([
|
|
|
|
|
|
prisma.chapters.findMany({
|
|
|
|
|
|
where,
|
|
|
|
|
|
orderBy: { sort_order: 'asc' },
|
|
|
|
|
|
skip: (page - 1) * pageSize,
|
|
|
|
|
|
take: pageSize,
|
|
|
|
|
|
select: {
|
|
|
|
|
|
id: true,
|
|
|
|
|
|
part_id: true,
|
|
|
|
|
|
part_title: true,
|
|
|
|
|
|
chapter_id: true,
|
|
|
|
|
|
chapter_title: true,
|
|
|
|
|
|
section_title: true,
|
|
|
|
|
|
word_count: true,
|
|
|
|
|
|
is_free: true,
|
|
|
|
|
|
price: true,
|
|
|
|
|
|
sort_order: true,
|
|
|
|
|
|
status: true,
|
|
|
|
|
|
created_at: true,
|
|
|
|
|
|
updated_at: true
|
|
|
|
|
|
}
|
|
|
|
|
|
}),
|
|
|
|
|
|
prisma.chapters.count({ where })
|
|
|
|
|
|
])
|
2026-01-25 09:57:21 +08:00
|
|
|
|
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
data: {
|
2026-02-06 18:34:02 +08:00
|
|
|
|
list: results.map(r => ({
|
|
|
|
|
|
...r,
|
|
|
|
|
|
price: Number(r.price)
|
|
|
|
|
|
})),
|
2026-01-25 09:57:21 +08:00
|
|
|
|
total,
|
|
|
|
|
|
page,
|
|
|
|
|
|
pageSize,
|
|
|
|
|
|
totalPages: Math.ceil(total / pageSize)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('[Chapters API] 获取列表失败:', error)
|
|
|
|
|
|
return NextResponse.json(
|
|
|
|
|
|
{ success: false, error: '获取章节列表失败' },
|
|
|
|
|
|
{ status: 500 }
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* POST - 新增章节
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function POST(req: NextRequest) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const body = await req.json()
|
|
|
|
|
|
const {
|
2026-02-06 18:34:02 +08:00
|
|
|
|
id, partId, partTitle, chapterId, chapterTitle, sectionTitle,
|
|
|
|
|
|
content, wordCount, isFree, price, sortOrder, status
|
2026-01-25 09:57:21 +08:00
|
|
|
|
} = body
|
|
|
|
|
|
|
2026-02-06 18:34:02 +08:00
|
|
|
|
if (!id || !partId || !chapterId) {
|
2026-01-25 09:57:21 +08:00
|
|
|
|
return NextResponse.json(
|
2026-02-06 18:34:02 +08:00
|
|
|
|
{ success: false, error: '缺少必要字段' },
|
2026-01-25 09:57:21 +08:00
|
|
|
|
{ status: 400 }
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 18:34:02 +08:00
|
|
|
|
// 使用 Prisma 创建章节
|
|
|
|
|
|
const chapter = await prisma.chapters.create({
|
|
|
|
|
|
data: {
|
|
|
|
|
|
id,
|
|
|
|
|
|
part_id: partId,
|
|
|
|
|
|
part_title: partTitle || '',
|
|
|
|
|
|
chapter_id: chapterId,
|
|
|
|
|
|
chapter_title: chapterTitle || '',
|
|
|
|
|
|
section_title: sectionTitle || '',
|
|
|
|
|
|
content: content || '',
|
|
|
|
|
|
word_count: wordCount || 0,
|
|
|
|
|
|
is_free: isFree || false,
|
|
|
|
|
|
price: price || 1,
|
|
|
|
|
|
sort_order: sortOrder || 0,
|
|
|
|
|
|
status: (status as any) || 'published'
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2026-01-25 09:57:21 +08:00
|
|
|
|
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: '章节创建成功',
|
2026-02-06 18:34:02 +08:00
|
|
|
|
data: { ...chapter, price: Number(chapter.price) }
|
2026-01-25 09:57:21 +08:00
|
|
|
|
})
|
2026-02-06 18:34:02 +08:00
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
console.error('[Chapters API] 创建失败:', error)
|
|
|
|
|
|
if (error.code === 'P2002') {
|
|
|
|
|
|
return NextResponse.json(
|
|
|
|
|
|
{ success: false, error: '章节ID已存在' },
|
|
|
|
|
|
{ status: 400 }
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
2026-01-25 09:57:21 +08:00
|
|
|
|
return NextResponse.json(
|
2026-02-06 18:34:02 +08:00
|
|
|
|
{ success: false, error: '创建章节失败' },
|
2026-01-25 09:57:21 +08:00
|
|
|
|
{ status: 500 }
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-02-06 18:34:02 +08:00
|
|
|
|
* PUT - 更新章节
|
2026-01-25 09:57:21 +08:00
|
|
|
|
*/
|
|
|
|
|
|
export async function PUT(req: NextRequest) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const body = await req.json()
|
2026-02-06 18:34:02 +08:00
|
|
|
|
const { id, ...updateData } = body
|
2026-01-25 09:57:21 +08:00
|
|
|
|
|
|
|
|
|
|
if (!id) {
|
|
|
|
|
|
return NextResponse.json(
|
|
|
|
|
|
{ success: false, error: '缺少章节ID' },
|
|
|
|
|
|
{ status: 400 }
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 18:34:02 +08:00
|
|
|
|
// 构建更新数据
|
|
|
|
|
|
const data: any = { updated_at: new Date() }
|
|
|
|
|
|
if (updateData.partTitle !== undefined) data.part_title = updateData.partTitle
|
|
|
|
|
|
if (updateData.chapterTitle !== undefined) data.chapter_title = updateData.chapterTitle
|
|
|
|
|
|
if (updateData.sectionTitle !== undefined) data.section_title = updateData.sectionTitle
|
|
|
|
|
|
if (updateData.content !== undefined) data.content = updateData.content
|
|
|
|
|
|
if (updateData.wordCount !== undefined) data.word_count = updateData.wordCount
|
|
|
|
|
|
if (updateData.isFree !== undefined) data.is_free = updateData.isFree
|
|
|
|
|
|
if (updateData.price !== undefined) data.price = updateData.price
|
|
|
|
|
|
if (updateData.sortOrder !== undefined) data.sort_order = updateData.sortOrder
|
|
|
|
|
|
if (updateData.status !== undefined) data.status = updateData.status
|
|
|
|
|
|
|
|
|
|
|
|
// 使用 Prisma 更新章节
|
|
|
|
|
|
const chapter = await prisma.chapters.update({
|
|
|
|
|
|
where: { id },
|
|
|
|
|
|
data
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: '章节更新成功',
|
|
|
|
|
|
data: { ...chapter, price: Number(chapter.price) }
|
|
|
|
|
|
})
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
console.error('[Chapters API] 更新失败:', error)
|
|
|
|
|
|
if (error.code === 'P2025') {
|
2026-01-25 09:57:21 +08:00
|
|
|
|
return NextResponse.json(
|
|
|
|
|
|
{ success: false, error: '章节不存在' },
|
|
|
|
|
|
{ status: 404 }
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
return NextResponse.json(
|
|
|
|
|
|
{ success: false, error: '更新章节失败' },
|
|
|
|
|
|
{ status: 500 }
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-02-06 18:34:02 +08:00
|
|
|
|
* DELETE - 删除章节
|
2026-01-25 09:57:21 +08:00
|
|
|
|
*/
|
|
|
|
|
|
export async function DELETE(req: NextRequest) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { searchParams } = new URL(req.url)
|
|
|
|
|
|
const id = searchParams.get('id')
|
|
|
|
|
|
|
|
|
|
|
|
if (!id) {
|
|
|
|
|
|
return NextResponse.json(
|
|
|
|
|
|
{ success: false, error: '缺少章节ID' },
|
|
|
|
|
|
{ status: 400 }
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 18:34:02 +08:00
|
|
|
|
// 使用 Prisma 删除章节
|
|
|
|
|
|
await prisma.chapters.delete({
|
|
|
|
|
|
where: { id }
|
|
|
|
|
|
})
|
2026-01-25 09:57:21 +08:00
|
|
|
|
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: true,
|
2026-02-06 18:34:02 +08:00
|
|
|
|
message: '章节删除成功'
|
2026-01-25 09:57:21 +08:00
|
|
|
|
})
|
2026-02-06 18:34:02 +08:00
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
console.error('[Chapters API] 删除失败:', error)
|
|
|
|
|
|
if (error.code === 'P2025') {
|
|
|
|
|
|
return NextResponse.json(
|
|
|
|
|
|
{ success: false, error: '章节不存在' },
|
|
|
|
|
|
{ status: 404 }
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
2026-01-25 09:57:21 +08:00
|
|
|
|
return NextResponse.json(
|
|
|
|
|
|
{ success: false, error: '删除章节失败' },
|
|
|
|
|
|
{ status: 500 }
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|