Refactor API routes and remove unused endpoints
- Deleted the following API routes: - `upload-cover` - `truyen/[id]/chapters` - `truyen/[id]/comments` - `truyen/[id]/rate` - `truyen/suggest` - `user/bookmarks` - `user/recommendations` - `user/settings` - Updated `chapter-client.tsx` and `novel-client.tsx` to handle error messages more gracefully. - Adjusted pagination logic in `tim-kiem/page.tsx` to improve user experience. - Added support for additional search parameters in the search functionality. - Introduced API rewrites in `next.config.mjs` to route requests to a reader API.
This commit is contained in:
@@ -1,181 +0,0 @@
|
||||
import { NextResponse } from "next/server"
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "@/lib/auth"
|
||||
import { prisma } from "@/lib/prisma"
|
||||
|
||||
function toUTCDateOnly(value: Date): Date {
|
||||
return new Date(Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate()))
|
||||
}
|
||||
|
||||
async function upsertDailyNovelView(novelId: string, day: Date) {
|
||||
const delegate = (prisma as any).novelViewDaily
|
||||
if (!delegate || typeof delegate.upsert !== "function") return
|
||||
|
||||
await delegate.upsert({
|
||||
where: {
|
||||
novelId_day: {
|
||||
novelId,
|
||||
day,
|
||||
},
|
||||
},
|
||||
update: {
|
||||
views: { increment: 1 },
|
||||
},
|
||||
create: {
|
||||
novelId,
|
||||
day,
|
||||
views: 1,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Lấy danh sách bookmark
|
||||
export async function GET(req: Request) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions)
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
||||
}
|
||||
|
||||
const bookmarks = await prisma.bookmark.findMany({
|
||||
where: { userId: session.user.id },
|
||||
include: { novel: true },
|
||||
orderBy: { createdAt: "desc" }
|
||||
})
|
||||
|
||||
return NextResponse.json(bookmarks)
|
||||
} catch (error) {
|
||||
console.error("GET Bookmarks Error", error)
|
||||
return NextResponse.json({ error: "Internal Server Error" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// Thêm, cập nhật hoặc xóa bookmark
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions)
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await req.json()
|
||||
const { action, novelId, lastChapterId, lastChapterNumber } = body
|
||||
|
||||
if (!novelId || !action) {
|
||||
return NextResponse.json({ error: "Bad Request" }, { status: 400 })
|
||||
}
|
||||
|
||||
if (action === "toggle") {
|
||||
const existing = await prisma.bookmark.findUnique({
|
||||
where: {
|
||||
userId_novelId: {
|
||||
userId: session.user.id,
|
||||
novelId,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (existing) {
|
||||
// Xoá
|
||||
await prisma.$transaction([
|
||||
prisma.bookmark.delete({ where: { id: existing.id } }),
|
||||
prisma.novel.update({ where: { id: novelId }, data: { bookmarkCount: { decrement: 1 } } })
|
||||
])
|
||||
return NextResponse.json({ status: "removed" })
|
||||
} else {
|
||||
// Thêm mới
|
||||
const newBookmark = await prisma.$transaction(async (tx) => {
|
||||
const b = await tx.bookmark.create({
|
||||
data: {
|
||||
userId: session.user.id,
|
||||
novelId,
|
||||
lastChapterId,
|
||||
lastChapterNumber
|
||||
}
|
||||
})
|
||||
await tx.novel.update({ where: { id: novelId }, data: { bookmarkCount: { increment: 1 } } })
|
||||
return b
|
||||
})
|
||||
return NextResponse.json({ status: "added", bookmark: newBookmark })
|
||||
}
|
||||
} else if (action === "updateProgress") {
|
||||
// Cập nhật tiến độ lưu trang
|
||||
if (!lastChapterId || !lastChapterNumber) {
|
||||
return NextResponse.json({ error: "Missing chapter info" }, { status: 400 })
|
||||
}
|
||||
|
||||
// Lấy bookmark cũ (nếu có)
|
||||
const existingBookmark = await prisma.bookmark.findUnique({
|
||||
where: {
|
||||
userId_novelId: {
|
||||
userId: session.user.id,
|
||||
novelId,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let newReadChapters: number[] = []
|
||||
let newHasCountedView = false
|
||||
let shouldIncrementNovelView = false
|
||||
|
||||
if (existingBookmark) {
|
||||
newReadChapters = existingBookmark.readChapters || []
|
||||
newHasCountedView = existingBookmark.hasCountedView
|
||||
|
||||
// Nếu chương này chưa đọc, thêm vào mảng
|
||||
if (!newReadChapters.includes(lastChapterNumber)) {
|
||||
newReadChapters.push(lastChapterNumber)
|
||||
}
|
||||
|
||||
// Nếu đọc đủ 5 chương và chưa từng đếm view
|
||||
if (newReadChapters.length >= 5 && !newHasCountedView) {
|
||||
newHasCountedView = true
|
||||
shouldIncrementNovelView = true
|
||||
}
|
||||
} else {
|
||||
newReadChapters = [lastChapterNumber]
|
||||
// Chưa đủ 5 chương ngay từ lần đầu tạo
|
||||
}
|
||||
|
||||
const bookmark = await prisma.bookmark.upsert({
|
||||
where: {
|
||||
userId_novelId: {
|
||||
userId: session.user.id,
|
||||
novelId,
|
||||
}
|
||||
},
|
||||
update: {
|
||||
lastChapterId,
|
||||
lastChapterNumber,
|
||||
readChapters: newReadChapters,
|
||||
hasCountedView: newHasCountedView
|
||||
},
|
||||
create: {
|
||||
userId: session.user.id,
|
||||
novelId,
|
||||
lastChapterId,
|
||||
lastChapterNumber,
|
||||
readChapters: newReadChapters,
|
||||
hasCountedView: newHasCountedView
|
||||
}
|
||||
})
|
||||
|
||||
if (shouldIncrementNovelView) {
|
||||
const day = toUTCDateOnly(new Date())
|
||||
await prisma.novel.update({
|
||||
where: { id: novelId },
|
||||
data: { views: { increment: 1 } }
|
||||
})
|
||||
await upsertDailyNovelView(novelId, day)
|
||||
}
|
||||
|
||||
return NextResponse.json({ status: "updated", bookmark })
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: "Invalid action" }, { status: 400 })
|
||||
|
||||
} catch (error) {
|
||||
console.error("POST Bookmarks Error", error)
|
||||
return NextResponse.json({ error: "Internal Server Error" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
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 { UserRecommendation } from "@/lib/models/user-recommendation"
|
||||
|
||||
function normalizeText(value: unknown): string {
|
||||
return typeof value === "string" ? value.trim() : ""
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const session = await getServerSession(authOptions)
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
||||
}
|
||||
|
||||
await connectToMongoDB()
|
||||
|
||||
const docs = (await UserRecommendation.find({ userId: session.user.id })
|
||||
.sort({ createdAt: -1 })
|
||||
.limit(1000)
|
||||
.lean()) as Array<{
|
||||
_id: any
|
||||
novelId: string
|
||||
createdAt?: Date
|
||||
}>
|
||||
|
||||
const novelIds = Array.from(new Set(docs.map((doc) => doc.novelId).filter(Boolean)))
|
||||
const novels = novelIds.length
|
||||
? await prisma.novel.findMany({
|
||||
where: { id: { in: novelIds } },
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
slug: true,
|
||||
authorName: true,
|
||||
coverUrl: true,
|
||||
status: true,
|
||||
totalChapters: true,
|
||||
},
|
||||
})
|
||||
: []
|
||||
|
||||
const novelMap = new Map(novels.map((novel) => [novel.id, novel]))
|
||||
|
||||
const items = docs
|
||||
.map((doc) => {
|
||||
const novel = novelMap.get(doc.novelId)
|
||||
if (!novel) return null
|
||||
|
||||
return {
|
||||
id: String(doc._id),
|
||||
novelId: doc.novelId,
|
||||
createdAt: doc.createdAt || null,
|
||||
novel,
|
||||
}
|
||||
})
|
||||
.filter((item): item is NonNullable<typeof item> => Boolean(item))
|
||||
|
||||
return NextResponse.json(items)
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch user recommendations", error)
|
||||
return NextResponse.json({ error: "Failed to fetch recommendations" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions)
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await req.json()
|
||||
const novelId = normalizeText(body?.novelId)
|
||||
|
||||
if (!novelId) {
|
||||
return NextResponse.json({ error: "Thiếu ID truyện" }, { status: 400 })
|
||||
}
|
||||
|
||||
const novel = await prisma.novel.findUnique({ where: { id: novelId }, select: { id: true } })
|
||||
if (!novel) {
|
||||
return NextResponse.json({ error: "Truyện không tồn tại" }, { status: 404 })
|
||||
}
|
||||
|
||||
await connectToMongoDB()
|
||||
|
||||
try {
|
||||
const existing = (await UserRecommendation.findOne({
|
||||
userId: session.user.id,
|
||||
novelId,
|
||||
})
|
||||
.select({ _id: 1 })
|
||||
.lean()) as { _id: any } | null
|
||||
|
||||
if (existing) {
|
||||
return NextResponse.json({ error: "Bạn đã đề cử truyện này rồi" }, { status: 409 })
|
||||
}
|
||||
|
||||
const created = await UserRecommendation.create({
|
||||
userId: session.user.id,
|
||||
novelId,
|
||||
})
|
||||
|
||||
await prisma.novel.update({
|
||||
where: { id: novelId },
|
||||
data: { bookmarkCount: { increment: 1 } },
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
id: String(created._id),
|
||||
novelId,
|
||||
},
|
||||
{ status: 201 }
|
||||
)
|
||||
} catch (error: any) {
|
||||
if (error?.code === 11000) {
|
||||
return NextResponse.json({ error: "Bạn đã đề cử truyện này rồi" }, { status: 409 })
|
||||
}
|
||||
throw error
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to create user recommendation", error)
|
||||
return NextResponse.json({ error: "Failed to create recommendation" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(req: Request) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions)
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
||||
}
|
||||
|
||||
const url = new URL(req.url)
|
||||
const novelId = normalizeText(url.searchParams.get("novelId"))
|
||||
|
||||
if (!novelId) {
|
||||
return NextResponse.json({ error: "Thiếu ID truyện" }, { status: 400 })
|
||||
}
|
||||
|
||||
await connectToMongoDB()
|
||||
|
||||
const existing = (await UserRecommendation.findOne({
|
||||
userId: session.user.id,
|
||||
novelId,
|
||||
})
|
||||
.select({ _id: 1 })
|
||||
.lean()) as { _id: any } | null
|
||||
|
||||
if (!existing) {
|
||||
return NextResponse.json({ error: "Bạn chưa đề cử truyện này" }, { status: 404 })
|
||||
}
|
||||
|
||||
await UserRecommendation.deleteOne({ _id: existing._id })
|
||||
|
||||
await prisma.novel.update({
|
||||
where: { id: novelId },
|
||||
data: { bookmarkCount: { decrement: 1 } },
|
||||
})
|
||||
|
||||
return NextResponse.json({ success: true })
|
||||
} catch (error) {
|
||||
console.error("Failed to delete user recommendation", error)
|
||||
return NextResponse.json({ error: "Failed to delete recommendation" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
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() {
|
||||
const session = await getServerSession(authOptions)
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const settings = await prisma.userSetting.findUnique({
|
||||
where: { userId: session.user.id }
|
||||
})
|
||||
|
||||
return NextResponse.json(settings || {})
|
||||
} catch (error) {
|
||||
console.error("GET User Settings Error", error)
|
||||
return NextResponse.json({ error: "Internal Server Error" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const session = await getServerSession(authOptions)
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await req.json()
|
||||
const { fontSize, lineHeight, letterSpacing, fontFamily } = body
|
||||
|
||||
const updateData: any = {}
|
||||
if (fontSize !== undefined) updateData.fontSize = Number(fontSize)
|
||||
if (lineHeight !== undefined) updateData.lineHeight = Number(lineHeight)
|
||||
if (letterSpacing !== undefined) updateData.letterSpacing = Number(letterSpacing)
|
||||
if (fontFamily !== undefined) updateData.fontFamily = String(fontFamily)
|
||||
|
||||
const createData = {
|
||||
userId: session.user.id,
|
||||
fontSize: fontSize !== undefined ? Number(fontSize) : 18,
|
||||
lineHeight: lineHeight !== undefined ? Number(lineHeight) : 1.8,
|
||||
letterSpacing: letterSpacing !== undefined ? Number(letterSpacing) : 0,
|
||||
fontFamily: fontFamily !== undefined ? String(fontFamily) : "font-serif",
|
||||
}
|
||||
|
||||
const settings = await prisma.userSetting.upsert({
|
||||
where: { userId: session.user.id },
|
||||
update: updateData,
|
||||
create: createData
|
||||
})
|
||||
|
||||
return NextResponse.json(settings)
|
||||
} catch (error) {
|
||||
console.error("POST User Settings Error", error)
|
||||
return NextResponse.json({ error: "Internal Server Error" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user