Implement user API proxying and enhance missing fields handling
This commit is contained in:
@@ -0,0 +1,61 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server"
|
||||||
|
import { getToken } from "next-auth/jwt"
|
||||||
|
|
||||||
|
export const runtime = "nodejs"
|
||||||
|
export const dynamic = "force-dynamic"
|
||||||
|
|
||||||
|
const readerApiOrigin = (process.env.READER_API_ORIGIN || "http://localhost:8000").replace(/\/+$/, "")
|
||||||
|
|
||||||
|
async function proxyToReaderApi(req: NextRequest, path: string[]) {
|
||||||
|
const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET })
|
||||||
|
const accessToken = typeof (token as any)?.accessToken === "string" ? (token as any).accessToken : null
|
||||||
|
|
||||||
|
const url = new URL(req.url)
|
||||||
|
const query = url.search || ""
|
||||||
|
const targetUrl = `${readerApiOrigin}/api/user/${path.join("/")}${query}`
|
||||||
|
|
||||||
|
const headers = new Headers(req.headers)
|
||||||
|
headers.delete("host")
|
||||||
|
if (accessToken) {
|
||||||
|
headers.set("authorization", `Bearer ${accessToken}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isBodyMethod = req.method !== "GET" && req.method !== "HEAD"
|
||||||
|
const upstream = await fetch(targetUrl, {
|
||||||
|
method: req.method,
|
||||||
|
headers,
|
||||||
|
body: isBodyMethod ? req.body : undefined,
|
||||||
|
cache: "no-store",
|
||||||
|
duplex: "half",
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
return new NextResponse(upstream.body, {
|
||||||
|
status: upstream.status,
|
||||||
|
headers: upstream.headers,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
|
||||||
|
const { path } = await ctx.params
|
||||||
|
return proxyToReaderApi(req, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
|
||||||
|
const { path } = await ctx.params
|
||||||
|
return proxyToReaderApi(req, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function PUT(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
|
||||||
|
const { path } = await ctx.params
|
||||||
|
return proxyToReaderApi(req, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function PATCH(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
|
||||||
|
const { path } = await ctx.params
|
||||||
|
return proxyToReaderApi(req, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function DELETE(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
|
||||||
|
const { path } = await ctx.params
|
||||||
|
return proxyToReaderApi(req, path)
|
||||||
|
}
|
||||||
@@ -49,12 +49,30 @@ const missingKeyLabel: Record<MissingKey, string> = {
|
|||||||
|
|
||||||
const allMissingKeys: MissingKey[] = ["author", "cover", "description", "genres"]
|
const allMissingKeys: MissingKey[] = ["author", "cover", "description", "genres"]
|
||||||
|
|
||||||
|
function isBlank(value: unknown): boolean {
|
||||||
|
return typeof value !== "string" || value.trim() === ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeMissingFlags(row: any): Record<MissingKey, boolean> {
|
||||||
|
const safeGenres = Array.isArray(row?.genres) ? row.genres : []
|
||||||
|
const incoming = row?.missing && typeof row.missing === "object" ? row.missing : {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
author: typeof incoming.author === "boolean" ? incoming.author : isBlank(row?.authorName),
|
||||||
|
cover: typeof incoming.cover === "boolean" ? incoming.cover : isBlank(row?.coverUrl),
|
||||||
|
description: typeof incoming.description === "boolean" ? incoming.description : isBlank(row?.description),
|
||||||
|
genres: typeof incoming.genres === "boolean" ? incoming.genres : safeGenres.length === 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function toDraft(novel: MissingNovel): RowDraft {
|
function toDraft(novel: MissingNovel): RowDraft {
|
||||||
|
const safeGenres = Array.isArray(novel.genres) ? novel.genres : []
|
||||||
|
|
||||||
return {
|
return {
|
||||||
authorName: novel.authorName || "",
|
authorName: novel.authorName || "",
|
||||||
coverUrl: novel.coverUrl || "",
|
coverUrl: novel.coverUrl || "",
|
||||||
description: novel.description || "",
|
description: novel.description || "",
|
||||||
genreIds: novel.genres.map((genre) => genre.id),
|
genreIds: safeGenres.map((genre) => genre.id),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,7 +330,12 @@ export function MissingFieldsClient() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
const rows: MissingNovel[] = Array.isArray(data?.items) ? data.items : []
|
const rawRows: any[] = Array.isArray(data?.items) ? data.items : []
|
||||||
|
const rows: MissingNovel[] = rawRows.map((row) => ({
|
||||||
|
...row,
|
||||||
|
genres: Array.isArray(row?.genres) ? row.genres : [],
|
||||||
|
missing: normalizeMissingFlags(row),
|
||||||
|
}))
|
||||||
|
|
||||||
setItems(rows)
|
setItems(rows)
|
||||||
setSelectedNovelIds((prev) => prev.filter((id) => rows.some((row) => row.id === id)))
|
setSelectedNovelIds((prev) => prev.filter((id) => rows.some((row) => row.id === id)))
|
||||||
|
|||||||
@@ -28,10 +28,6 @@ const nextConfig = {
|
|||||||
source: "/api/chapters/:path*",
|
source: "/api/chapters/:path*",
|
||||||
destination: `${readerApiOrigin}/api/chapters/:path*`,
|
destination: `${readerApiOrigin}/api/chapters/:path*`,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
source: "/api/user/:path*",
|
|
||||||
destination: `${readerApiOrigin}/api/user/:path*`,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
source: "/api/auth/mobile-login",
|
source: "/api/auth/mobile-login",
|
||||||
destination: `${readerApiOrigin}/api/auth/mobile-login`,
|
destination: `${readerApiOrigin}/api/auth/mobile-login`,
|
||||||
|
|||||||
Reference in New Issue
Block a user