From d3f3d9c91ab270cc1c51083219fabf60dfd22fca Mon Sep 17 00:00:00 2001 From: virtus Date: Mon, 30 Mar 2026 14:28:40 +0700 Subject: [PATCH] Implement user API proxying and enhance missing fields handling --- app/api/user/[...path]/route.ts | 61 +++++++++++++++++++ .../thieu-thong-tin/missing-fields-client.tsx | 27 +++++++- next.config.mjs | 4 -- 3 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 app/api/user/[...path]/route.ts diff --git a/app/api/user/[...path]/route.ts b/app/api/user/[...path]/route.ts new file mode 100644 index 0000000..7d8adac --- /dev/null +++ b/app/api/user/[...path]/route.ts @@ -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) +} \ No newline at end of file diff --git a/app/mod/thieu-thong-tin/missing-fields-client.tsx b/app/mod/thieu-thong-tin/missing-fields-client.tsx index 1091e43..840f3db 100644 --- a/app/mod/thieu-thong-tin/missing-fields-client.tsx +++ b/app/mod/thieu-thong-tin/missing-fields-client.tsx @@ -49,12 +49,30 @@ const missingKeyLabel: Record = { const allMissingKeys: MissingKey[] = ["author", "cover", "description", "genres"] +function isBlank(value: unknown): boolean { + return typeof value !== "string" || value.trim() === "" +} + +function normalizeMissingFlags(row: any): Record { + 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 { + const safeGenres = Array.isArray(novel.genres) ? novel.genres : [] + return { authorName: novel.authorName || "", coverUrl: novel.coverUrl || "", 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 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) setSelectedNovelIds((prev) => prev.filter((id) => rows.some((row) => row.id === id))) diff --git a/next.config.mjs b/next.config.mjs index d0d8a02..040796a 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -28,10 +28,6 @@ const nextConfig = { source: "/api/chapters/:path*", destination: `${readerApiOrigin}/api/chapters/:path*`, }, - { - source: "/api/user/:path*", - destination: `${readerApiOrigin}/api/user/:path*`, - }, { source: "/api/auth/mobile-login", destination: `${readerApiOrigin}/api/auth/mobile-login`,