Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -26,15 +26,22 @@ export async function GET(
|
||||
return NextResponse.json({ error: "Chapter not found" }, { status: 404 })
|
||||
}
|
||||
|
||||
// Verify the moderator owns the related novel
|
||||
// Verify the moderator owns the related novel (or is an ADMIN)
|
||||
let novelQuery: any = { id: chapter.novelId }
|
||||
if (session.user.role !== "ADMIN") {
|
||||
novelQuery.uploaderId = session.user.id
|
||||
}
|
||||
|
||||
const novel = await prisma.novel.findFirst({
|
||||
where: {
|
||||
id: chapter.novelId,
|
||||
uploaderId: session.user.id
|
||||
}
|
||||
where: novelQuery
|
||||
})
|
||||
|
||||
if (!novel) {
|
||||
console.log("Novel not found or unauthorized:", {
|
||||
chapterNovelId: chapter.novelId,
|
||||
userId: session.user.id,
|
||||
role: session.user.role
|
||||
})
|
||||
return NextResponse.json({ error: "Unauthorized access to this chapter" }, { status: 403 })
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import { NextResponse } from "next/server"
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "@/lib/auth"
|
||||
import connectToMongoDB from "@/lib/mongoose"
|
||||
import { Chapter } from "@/lib/models/chapter"
|
||||
import { prisma } from "@/lib/prisma"
|
||||
|
||||
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 body = await req.json()
|
||||
const { novelId, action = "replace", findText, replaceText, matchCase = false, trashWords = "", preview = false } = body
|
||||
|
||||
if (!novelId) {
|
||||
return NextResponse.json({ error: "novelId is required" }, { status: 400 })
|
||||
}
|
||||
|
||||
// Verify that the novel belongs to the uploader
|
||||
let novelQuery: any = { id: novelId }
|
||||
if (session.user.role !== "ADMIN") {
|
||||
novelQuery.uploaderId = session.user.id
|
||||
}
|
||||
|
||||
const novel = await prisma.novel.findFirst({
|
||||
where: novelQuery,
|
||||
})
|
||||
|
||||
if (!novel) {
|
||||
return NextResponse.json({ error: "Truyện không tồn tại hoặc không đủ quyền" }, { status: 403 })
|
||||
}
|
||||
|
||||
await connectToMongoDB()
|
||||
|
||||
let patterns: { regex: RegExp, replaceWith: string }[] = []
|
||||
|
||||
if (action === "replace") {
|
||||
if (!findText) return NextResponse.json({ error: "findText is required for replace action" }, { status: 400 })
|
||||
const flags = matchCase ? "g" : "gi"
|
||||
const safeFindText = findText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
patterns.push({ regex: new RegExp(safeFindText, flags), replaceWith: replaceText || "" })
|
||||
} else if (action === "trash") {
|
||||
let words: string[] = []
|
||||
if (Array.isArray(trashWords)) {
|
||||
words = trashWords
|
||||
} else if (typeof trashWords === "string") {
|
||||
words = trashWords.split(',').map((w: string) => w.trim()).filter((w: string) => w.length > 0)
|
||||
}
|
||||
|
||||
if (words.length === 0) return NextResponse.json({ error: "No valid words provided" }, { status: 400 })
|
||||
|
||||
words.forEach((word: string) => {
|
||||
const safeWord = word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
patterns.push({ regex: new RegExp(safeWord, 'gi'), replaceWith: "" })
|
||||
})
|
||||
} else {
|
||||
return NextResponse.json({ error: "Invalid action" }, { status: 400 })
|
||||
}
|
||||
|
||||
// Find all chapters for the novel
|
||||
const chapters = await Chapter.find({ novelId }).sort({ number: 1 })
|
||||
let updatedCount = 0
|
||||
let previewResults: any[] = []
|
||||
|
||||
for (const chap of chapters) {
|
||||
let originalContent = chap.content || ""
|
||||
let newContent = originalContent
|
||||
let modified = false
|
||||
|
||||
patterns.forEach(({ regex, replaceWith }) => {
|
||||
if (regex.test(newContent)) {
|
||||
modified = true
|
||||
newContent = newContent.replace(regex, replaceWith)
|
||||
}
|
||||
})
|
||||
|
||||
if (modified) {
|
||||
if (preview && previewResults.length < 5) { // Limit previews to 5 chapters to save payload size
|
||||
// Capture a small text snippet from the first pattern match
|
||||
let snippet = ""
|
||||
if (patterns.length > 0) {
|
||||
const match = patterns[0].regex.exec(originalContent)
|
||||
if (match) {
|
||||
const matchIndex = match.index
|
||||
const start = Math.max(0, matchIndex - 30)
|
||||
const end = Math.min(originalContent.length, matchIndex + match[0].length + 30)
|
||||
snippet = "..." + originalContent.substring(start, end).replace(/\n/g, ' ') + "..."
|
||||
}
|
||||
}
|
||||
|
||||
previewResults.push({
|
||||
chapterId: chap._id,
|
||||
number: chap.number,
|
||||
title: chap.title,
|
||||
snippet
|
||||
})
|
||||
}
|
||||
|
||||
if (!preview) {
|
||||
chap.content = newContent
|
||||
await chap.save()
|
||||
}
|
||||
|
||||
updatedCount++
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
message: preview ? "Preview generated" : "Success",
|
||||
updatedChapters: updatedCount,
|
||||
previews: previewResults
|
||||
}, { status: 200 })
|
||||
|
||||
} catch (error) {
|
||||
console.error("Global Replace Error:", error)
|
||||
return NextResponse.json({ error: "Failed to perform global replacement" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { NextResponse } from "next/server"
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "@/lib/auth"
|
||||
import { prisma } from "@/lib/prisma"
|
||||
|
||||
export async function GET(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const session = await getServerSession(authOptions)
|
||||
if (!session || (session.user.role !== "MOD" && session.user.role !== "ADMIN")) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
||||
}
|
||||
|
||||
const { id: novelId } = await params
|
||||
|
||||
try {
|
||||
const novel = await prisma.novel.findUnique({
|
||||
where: { id: novelId },
|
||||
select: { trashWords: true, uploaderId: true }
|
||||
})
|
||||
|
||||
if (!novel) {
|
||||
return NextResponse.json({ error: "Not found" }, { status: 404 })
|
||||
}
|
||||
|
||||
if (session.user.role !== "ADMIN" && novel.uploaderId !== session.user.id) {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 })
|
||||
}
|
||||
|
||||
return NextResponse.json({ trashWords: novel.trashWords })
|
||||
} catch (error) {
|
||||
console.error("GET Trash Words Error:", error)
|
||||
return NextResponse.json({ error: "Lỗi Server" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const session = await getServerSession(authOptions)
|
||||
if (!session || (session.user.role !== "MOD" && session.user.role !== "ADMIN")) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
||||
}
|
||||
|
||||
const { id: novelId } = await params
|
||||
|
||||
try {
|
||||
const novel = await prisma.novel.findUnique({
|
||||
where: { id: novelId },
|
||||
select: { id: true, uploaderId: true }
|
||||
})
|
||||
|
||||
if (!novel) return NextResponse.json({ error: "Not found" }, { status: 404 })
|
||||
|
||||
if (session.user.role !== "ADMIN" && novel.uploaderId !== session.user.id) {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 })
|
||||
}
|
||||
|
||||
const body = await req.json()
|
||||
const { trashWords } = body
|
||||
|
||||
if (!Array.isArray(trashWords)) {
|
||||
return NextResponse.json({ error: "Mảng từ rác không hợp lệ" }, { status: 400 })
|
||||
}
|
||||
|
||||
const updated = await prisma.novel.update({
|
||||
where: { id: novelId },
|
||||
data: { trashWords }
|
||||
})
|
||||
|
||||
return NextResponse.json({ success: true, trashWords: updated.trashWords })
|
||||
} catch (error) {
|
||||
console.error("PUT Trash Words Error:", error)
|
||||
return NextResponse.json({ error: "Lỗi Server" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
@@ -29,16 +29,19 @@ export async function POST(req: Request) {
|
||||
|
||||
try {
|
||||
const data = await req.json()
|
||||
const { title, authorName, description, genreIds = [] } = data
|
||||
const { title, originalTitle, authorName, originalAuthorName, description, coverUrl, genreIds = [] } = data
|
||||
// Tạo slug từ title
|
||||
const slug = slugify(title)
|
||||
|
||||
const newNovel = await prisma.novel.create({
|
||||
data: {
|
||||
title,
|
||||
originalTitle,
|
||||
slug: slug,
|
||||
authorName,
|
||||
originalAuthorName,
|
||||
description,
|
||||
coverUrl,
|
||||
uploaderId: session.user.id,
|
||||
genres: {
|
||||
create: genreIds.map((id: string) => ({
|
||||
@@ -61,15 +64,18 @@ export async function PUT(req: Request) {
|
||||
|
||||
try {
|
||||
const data = await req.json()
|
||||
const { id, title, authorName, description, status, genreIds } = data
|
||||
const { id, title, originalTitle, authorName, originalAuthorName, description, coverUrl, status, genreIds } = data
|
||||
|
||||
// Update basic info and recreate genre relations
|
||||
const updatedNovel = await prisma.novel.update({
|
||||
where: { id: id, uploaderId: session.user.id }, // Make sure they own it
|
||||
data: {
|
||||
title,
|
||||
originalTitle,
|
||||
authorName,
|
||||
originalAuthorName,
|
||||
description,
|
||||
coverUrl,
|
||||
status,
|
||||
// Replace all existing genres if genreIds is provided
|
||||
...(genreIds !== undefined && {
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { NextResponse } from "next/server"
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "@/lib/auth"
|
||||
import { writeFile } from "fs/promises"
|
||||
import path from "path"
|
||||
import fs from "fs"
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions)
|
||||
if (!session || (session.user.role !== "MOD" && session.user.role !== "ADMIN")) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
||||
}
|
||||
|
||||
const formData = await req.formData()
|
||||
const file = formData.get("file") as File | null
|
||||
|
||||
if (!file) {
|
||||
return NextResponse.json({ error: "No file uploaded" }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!file.type.startsWith("image/")) {
|
||||
return NextResponse.json({ error: "Only image files are allowed" }, { status: 400 })
|
||||
}
|
||||
|
||||
const bytes = await file.arrayBuffer()
|
||||
const buffer = Buffer.from(bytes)
|
||||
|
||||
const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9)
|
||||
const ext = path.extname(file.name) || ".jpg"
|
||||
const filename = `cover-${uniqueSuffix}${ext}`
|
||||
|
||||
const uploadDir = path.join(process.cwd(), "public", "uploads", "covers")
|
||||
|
||||
// Ensure directory exists
|
||||
if (!fs.existsSync(uploadDir)) {
|
||||
fs.mkdirSync(uploadDir, { recursive: true })
|
||||
}
|
||||
|
||||
const filepath = path.join(uploadDir, filename)
|
||||
await writeFile(filepath, buffer)
|
||||
|
||||
return NextResponse.json({ url: `/uploads/covers/${filename}` })
|
||||
} catch (error: any) {
|
||||
console.error("Cover upload error:", error)
|
||||
return NextResponse.json({ error: error.message || "Failed to upload cover" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user