201 lines
5.8 KiB
TypeScript
201 lines
5.8 KiB
TypeScript
import { NextResponse } from "next/server"
|
|
import { getServerSession } from "next-auth/next"
|
|
import { authOptions } from "@/lib/auth"
|
|
import { prisma } from "@/lib/prisma"
|
|
import { slugify } from "@/lib/utils"
|
|
|
|
function normalizeText(value: any): string {
|
|
return typeof value === "string" ? value.trim() : ""
|
|
}
|
|
|
|
async function resolveEditableSeries(
|
|
id: string,
|
|
session: { user: { role: "USER" | "MOD" | "ADMIN"; id: string } }
|
|
) {
|
|
return prisma.series.findFirst({
|
|
where: session.user.role === "ADMIN"
|
|
? { id }
|
|
: {
|
|
id,
|
|
OR: [
|
|
{ novels: { some: { uploaderId: session.user.id } } },
|
|
{ novels: { some: { uploaderId: null } } },
|
|
{ novels: { none: {} } },
|
|
],
|
|
},
|
|
select: { id: true },
|
|
})
|
|
}
|
|
|
|
export async function GET() {
|
|
const session = await getServerSession(authOptions)
|
|
if (!session || (session.user.role !== "MOD" && session.user.role !== "ADMIN")) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
|
}
|
|
|
|
try {
|
|
const series = await prisma.series.findMany({
|
|
where: session.user.role === "ADMIN"
|
|
? undefined
|
|
: {
|
|
OR: [
|
|
{ novels: { some: { uploaderId: session.user.id } } },
|
|
{ novels: { some: { uploaderId: null } } },
|
|
{ novels: { none: {} } },
|
|
],
|
|
},
|
|
orderBy: { updatedAt: "desc" },
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
slug: true,
|
|
description: true,
|
|
_count: { select: { novels: true } },
|
|
},
|
|
})
|
|
|
|
return NextResponse.json(series)
|
|
} catch {
|
|
return NextResponse.json({ error: "Failed to fetch series" }, { status: 500 })
|
|
}
|
|
}
|
|
|
|
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 name = normalizeText(body?.name)
|
|
const description = normalizeText(body?.description)
|
|
|
|
if (!name) {
|
|
return NextResponse.json({ error: "Tên series không được để trống" }, { status: 400 })
|
|
}
|
|
|
|
const existing = await prisma.series.findFirst({
|
|
where: { name: { equals: name, mode: "insensitive" } },
|
|
select: { id: true, name: true, slug: true, description: true },
|
|
})
|
|
|
|
if (existing) {
|
|
return NextResponse.json(existing)
|
|
}
|
|
|
|
const baseSlug = slugify(name)
|
|
let slug = baseSlug
|
|
let counter = 1
|
|
|
|
while (await prisma.series.findUnique({ where: { slug } })) {
|
|
slug = `${baseSlug}-${counter}`
|
|
counter += 1
|
|
}
|
|
|
|
const created = await prisma.series.create({
|
|
data: { name, slug, description: description || null },
|
|
select: { id: true, name: true, slug: true, description: true },
|
|
})
|
|
|
|
return NextResponse.json(created, { status: 201 })
|
|
} catch {
|
|
return NextResponse.json({ error: "Failed to create series" }, { status: 500 })
|
|
}
|
|
}
|
|
|
|
export async function PUT(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 id = normalizeText(body?.id)
|
|
const name = normalizeText(body?.name)
|
|
const description = normalizeText(body?.description)
|
|
|
|
if (!id || !name) {
|
|
return NextResponse.json({ error: "Thiếu thông tin series" }, { status: 400 })
|
|
}
|
|
|
|
const target = await resolveEditableSeries(id, session as any)
|
|
if (!target) {
|
|
return NextResponse.json({ error: "Không tìm thấy series hoặc không đủ quyền" }, { status: 404 })
|
|
}
|
|
|
|
const duplicated = await prisma.series.findFirst({
|
|
where: {
|
|
id: { not: id },
|
|
name: { equals: name, mode: "insensitive" },
|
|
},
|
|
select: { id: true },
|
|
})
|
|
|
|
if (duplicated) {
|
|
return NextResponse.json({ error: "Tên series đã tồn tại" }, { status: 409 })
|
|
}
|
|
|
|
const baseSlug = slugify(name)
|
|
let slug = baseSlug
|
|
let counter = 1
|
|
|
|
while (await prisma.series.findFirst({ where: { slug, id: { not: id } }, select: { id: true } })) {
|
|
slug = `${baseSlug}-${counter}`
|
|
counter += 1
|
|
}
|
|
|
|
const updated = await prisma.series.update({
|
|
where: { id },
|
|
data: {
|
|
name,
|
|
slug,
|
|
description: description || null,
|
|
},
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
slug: true,
|
|
description: true,
|
|
_count: { select: { novels: true } },
|
|
},
|
|
})
|
|
|
|
return NextResponse.json(updated)
|
|
} catch {
|
|
return NextResponse.json({ error: "Failed to update series" }, { status: 500 })
|
|
}
|
|
}
|
|
|
|
export async function DELETE(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 url = new URL(req.url)
|
|
const id = normalizeText(url.searchParams.get("id"))
|
|
|
|
if (!id) {
|
|
return NextResponse.json({ error: "Thiếu id series" }, { status: 400 })
|
|
}
|
|
|
|
const target = await resolveEditableSeries(id, session as any)
|
|
if (!target) {
|
|
return NextResponse.json({ error: "Không tìm thấy series hoặc không đủ quyền" }, { status: 404 })
|
|
}
|
|
|
|
const usedCount = await prisma.novel.count({ where: { seriesId: id } })
|
|
if (usedCount > 0) {
|
|
return NextResponse.json({ error: "Series đang chứa truyện, không thể xóa" }, { status: 409 })
|
|
}
|
|
|
|
await prisma.series.delete({ where: { id } })
|
|
return NextResponse.json({ success: true })
|
|
} catch {
|
|
return NextResponse.json({ error: "Failed to delete series" }, { status: 500 })
|
|
}
|
|
}
|