Files
reader/app/api/mod/series/route.ts
T

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 })
}
}