ce805adb08
Add server-side EPUB import and integrate Prisma + Mongo for novels/chapters. Introduces a new moderator API route (app/api/mod/epub/route.ts) that parses .epub files, creates a novel record in Prisma, and inserts chapter documents into MongoDB via the Chapter Mongoose model. Frontend: novel management UI now supports EPUB upload (app/mod/truyen/novel-client.tsx) with progress/toasts and preserves the manual 'Add novel' dialog. Convert app pages to fetch real data from Prisma and Mongo (app/page.tsx, app/truyen/[slug]/page.tsx, app/truyen/[slug]/[chapterId]/page.tsx), adapt types/props to use authorName, and adjust chapter/comment IDs to use Mongo _id strings. Minor fixes: TTS player logs playback errors, UI text fixes (e.g. "Chương"), and novel-card/other components updated for authorName. package.json updated with epub2, html-to-text and types; pnpm lock updated. Adds tsconfig.tsbuildinfo.
121 lines
4.2 KiB
TypeScript
121 lines
4.2 KiB
TypeScript
import { NextResponse } from "next/server"
|
|
import { getServerSession } from "next-auth/next"
|
|
import { authOptions } from "@/lib/auth"
|
|
import { prisma } from "@/lib/prisma"
|
|
import connectToMongoDB from "@/lib/mongoose"
|
|
import { Chapter } from "@/lib/models/chapter"
|
|
import path from "path"
|
|
import os from "os"
|
|
import { promises as fs } from "fs"
|
|
import { convert } from "html-to-text"
|
|
|
|
export async function POST(req: Request) {
|
|
const session = await getServerSession(authOptions)
|
|
if (!session || (session.user.role !== "MOD" && session.user.role !== "ADMIN")) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
|
}
|
|
|
|
try {
|
|
const formData = await req.formData()
|
|
const epubFile = formData.get("file") as File
|
|
if (!epubFile) {
|
|
return NextResponse.json({ error: "Thiếu file EPUB" }, { status: 400 })
|
|
}
|
|
|
|
const buffer = Buffer.from(await epubFile.arrayBuffer())
|
|
const tempFilePath = path.join(os.tmpdir(), `upload-${Date.now()}.epub`)
|
|
await fs.writeFile(tempFilePath, buffer)
|
|
|
|
// Phân tích EPUB file
|
|
const parsedData = await new Promise<any>((resolve, reject) => {
|
|
const EPub = require("epub2").EPub || require("epub2")
|
|
const epub = new EPub(tempFilePath, "", "")
|
|
|
|
epub.on("error", (err: any) => reject(err))
|
|
epub.on("end", async () => {
|
|
const metadata = epub.metadata
|
|
const flow = epub.flow // TOC array
|
|
const chapters = []
|
|
|
|
for (let i = 0; i < flow.length; i++) {
|
|
const chapterData = flow[i]
|
|
const text = await new Promise<string>((res) => {
|
|
epub.getChapter(chapterData.id, (err: any, d: string) => {
|
|
if (err) res("")
|
|
else res(d)
|
|
})
|
|
})
|
|
|
|
if (text && text.trim().length > 0) {
|
|
const plainText = convert(text, { wordwrap: false })
|
|
chapters.push({
|
|
title: chapterData.title || `Chương ${i + 1}`,
|
|
content: plainText
|
|
})
|
|
}
|
|
}
|
|
|
|
resolve({ metadata, chapters })
|
|
})
|
|
|
|
epub.parse()
|
|
})
|
|
|
|
// Xóa file tạm
|
|
await fs.unlink(tempFilePath).catch(() => { })
|
|
|
|
const { metadata, chapters } = parsedData
|
|
|
|
let novelTitle = metadata.title || "Truyện chưa đặt tên"
|
|
let novelAuthor = metadata.creator || "Khuyết danh"
|
|
let novelDesc = metadata.description || "Chưa có giới thiệu"
|
|
|
|
// Generate base slug
|
|
const baseSlug = novelTitle
|
|
.toLowerCase()
|
|
.normalize("NFD")
|
|
.replace(/[\u0300-\u036f]/g, "")
|
|
.replace(/[^a-z0-9]+/g, "-")
|
|
.replace(/(^-|-$)+/g, "")
|
|
|
|
let slug = baseSlug
|
|
let slugCounter = 1
|
|
|
|
// Đảm bảo slug là duy nhất
|
|
while (await prisma.novel.findUnique({ where: { slug } })) {
|
|
slug = `${baseSlug}-${slugCounter}`
|
|
slugCounter++
|
|
}
|
|
|
|
const newNovel = await prisma.novel.create({
|
|
data: {
|
|
title: novelTitle,
|
|
slug: slug,
|
|
authorName: novelAuthor,
|
|
description: convert(novelDesc, { wordwrap: false }), // metadata metadata có thể chứa html
|
|
uploaderId: session.user.id,
|
|
totalChapters: chapters.length,
|
|
},
|
|
})
|
|
|
|
// Lưu chapters xuống MongoDB
|
|
await connectToMongoDB()
|
|
const chapterDocs = chapters.map((ch: any, i: number) => ({
|
|
novelId: newNovel.id,
|
|
number: i + 1,
|
|
title: ch.title,
|
|
content: ch.content,
|
|
views: 0
|
|
}))
|
|
|
|
if (chapterDocs.length > 0) {
|
|
await Chapter.insertMany(chapterDocs)
|
|
}
|
|
|
|
return NextResponse.json(newNovel, { status: 201 })
|
|
} catch (error: any) {
|
|
console.error("EPUB upload error:", error)
|
|
return NextResponse.json({ error: "Lỗi xử lý file EPUB", details: error.message }, { status: 500 })
|
|
}
|
|
}
|