Browse Source
- popiAssetApi 的 resolve* 直接使用列表项已有的真实地址,不再请求 /api/popi/asset/detail、/api/popi/media/detail - previewImageUrl 统一由 buildPreviewImageUrl 现拼(OSS imageMogr2 参数) - 删除已无引用的 asset/detail、media/detail 代理路由及测试 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>feature-img
4 changed files with 24 additions and 299 deletions
@ -1,64 +0,0 @@ |
|||
import { NextRequest } from "next/server"; |
|||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; |
|||
import { GET } from "../route"; |
|||
|
|||
const originalFetch = global.fetch; |
|||
const originalBaseUrl = process.env.POPIART_API_BASE_URL; |
|||
|
|||
function createRequest(url = "http://localhost/api/popi/asset/detail?id=42"): NextRequest { |
|||
return new NextRequest(url, { |
|||
method: "GET", |
|||
headers: new Headers({ |
|||
Authorization: "Bearer user-token", |
|||
}), |
|||
}); |
|||
} |
|||
|
|||
describe("/api/popi/asset/detail route", () => { |
|||
beforeEach(() => { |
|||
process.env.POPIART_API_BASE_URL = "https://popi.example"; |
|||
}); |
|||
|
|||
afterEach(() => { |
|||
global.fetch = originalFetch; |
|||
process.env.POPIART_API_BASE_URL = originalBaseUrl; |
|||
vi.restoreAllMocks(); |
|||
}); |
|||
|
|||
it("proxies asset detail requests", async () => { |
|||
const data = { |
|||
id: 42, |
|||
title: "Asset", |
|||
images: ["https://cdn.example/full.png"], |
|||
thumbs: ["https://cdn.example/thumb.png"], |
|||
}; |
|||
const fetchMock = vi.fn().mockResolvedValue( |
|||
new Response(JSON.stringify({ status: "0000", message: "ok", data }), { status: 200 }) |
|||
); |
|||
global.fetch = fetchMock; |
|||
|
|||
const response = await GET(createRequest()); |
|||
|
|||
await expect(response.json()).resolves.toEqual({ success: true, data }); |
|||
expect(String(fetchMock.mock.calls[0][0])).toBe("https://popi.example/api_client/anime/asset/detail?id=42"); |
|||
expect(fetchMock.mock.calls[0][1]).toMatchObject({ |
|||
method: "GET", |
|||
headers: { |
|||
Accept: "application/json", |
|||
Authorization: "Bearer user-token", |
|||
token: "user-token", |
|||
}, |
|||
cache: "no-store", |
|||
}); |
|||
}); |
|||
|
|||
it("rejects invalid ids", async () => { |
|||
const response = await GET(createRequest("http://localhost/api/popi/asset/detail?id=abc")); |
|||
|
|||
expect(response.status).toBe(400); |
|||
await expect(response.json()).resolves.toMatchObject({ |
|||
success: false, |
|||
error: "Asset id is invalid.", |
|||
}); |
|||
}); |
|||
}); |
|||
@ -1,69 +0,0 @@ |
|||
import { fetchPopiserverGateway as fetch } from "@/app/api/_popiserverFetch"; |
|||
import { NextRequest, NextResponse } from "next/server"; |
|||
import { requireLogin } from "@/app/api/_auth"; |
|||
import { ApiError } from "@/app/api/_errors"; |
|||
|
|||
const POPI_ASSET_DETAIL_PATH = "/api_client/anime/asset/detail"; |
|||
|
|||
type PopiserverResponse<T> = { |
|||
status?: string; |
|||
message?: string; |
|||
data?: T; |
|||
}; |
|||
|
|||
function getPopiBaseUrl(request: NextRequest): string { |
|||
const baseUrl = |
|||
process.env.POPIART_API_BASE_URL || |
|||
process.env.NEXT_PUBLIC_APP_URL || |
|||
request.nextUrl.origin; |
|||
return baseUrl.replace(/\/+$/, ""); |
|||
} |
|||
|
|||
function readAssetId(request: NextRequest): number { |
|||
const rawId = request.nextUrl.searchParams.get("id"); |
|||
const id = Number(rawId); |
|||
if (!Number.isInteger(id) || id <= 0) { |
|||
throw ApiError.badRequest("Asset id is invalid."); |
|||
} |
|||
return id; |
|||
} |
|||
|
|||
export async function GET(request: NextRequest) { |
|||
try { |
|||
const login = requireLogin(request); |
|||
const id = readAssetId(request); |
|||
const upstreamUrl = new URL(getPopiBaseUrl(request) + POPI_ASSET_DETAIL_PATH); |
|||
upstreamUrl.searchParams.set("id", String(id)); |
|||
|
|||
const upstream = await fetch(upstreamUrl, { |
|||
method: "GET", |
|||
headers: { |
|||
Accept: "application/json", |
|||
Authorization: `Bearer ${login.token}`, |
|||
token: login.token, |
|||
}, |
|||
cache: "no-store", |
|||
}); |
|||
|
|||
const payload = (await upstream.json().catch(() => null)) as PopiserverResponse<unknown> | null; |
|||
if (!upstream.ok) { |
|||
throw ApiError.provider(payload?.message || "Popiserver asset detail failed.", upstream.status); |
|||
} |
|||
if (!payload || payload.status !== "0000") { |
|||
throw ApiError.provider(payload?.message || "Popiserver asset detail returned an error.", 502); |
|||
} |
|||
|
|||
return NextResponse.json({ |
|||
success: true, |
|||
data: payload.data, |
|||
}); |
|||
} catch (error) { |
|||
if (error instanceof ApiError) { |
|||
return NextResponse.json({ success: false, error: error.message }, { status: error.status }); |
|||
} |
|||
return NextResponse.json( |
|||
{ success: false, error: error instanceof Error ? error.message : "Asset detail failed." }, |
|||
{ status: 500 } |
|||
); |
|||
} |
|||
} |
|||
@ -1,69 +0,0 @@ |
|||
import { fetchPopiserverGateway as fetch } from "@/app/api/_popiserverFetch"; |
|||
import { NextRequest, NextResponse } from "next/server"; |
|||
import { requireLogin } from "@/app/api/_auth"; |
|||
import { ApiError } from "@/app/api/_errors"; |
|||
|
|||
const POPI_MEDIA_DETAIL_PATH = "/api_client/media/detail"; |
|||
|
|||
type PopiserverResponse<T> = { |
|||
status?: string; |
|||
message?: string; |
|||
data?: T; |
|||
}; |
|||
|
|||
function getPopiBaseUrl(request: NextRequest): string { |
|||
const baseUrl = |
|||
process.env.POPIART_API_BASE_URL || |
|||
process.env.NEXT_PUBLIC_APP_URL || |
|||
request.nextUrl.origin; |
|||
return baseUrl.replace(/\/+$/, ""); |
|||
} |
|||
|
|||
function readMediaId(request: NextRequest): number { |
|||
const rawId = request.nextUrl.searchParams.get("id"); |
|||
const id = Number(rawId); |
|||
if (!Number.isInteger(id) || id <= 0) { |
|||
throw ApiError.badRequest("Media id is invalid."); |
|||
} |
|||
return id; |
|||
} |
|||
|
|||
export async function GET(request: NextRequest) { |
|||
try { |
|||
const login = requireLogin(request); |
|||
const id = readMediaId(request); |
|||
const upstreamUrl = new URL(getPopiBaseUrl(request) + POPI_MEDIA_DETAIL_PATH); |
|||
upstreamUrl.searchParams.set("id", String(id)); |
|||
|
|||
const upstream = await fetch(upstreamUrl, { |
|||
method: "GET", |
|||
headers: { |
|||
Accept: "application/json", |
|||
Authorization: `Bearer ${login.token}`, |
|||
token: login.token, |
|||
}, |
|||
cache: "no-store", |
|||
}); |
|||
|
|||
const payload = (await upstream.json().catch(() => null)) as PopiserverResponse<unknown> | null; |
|||
if (!upstream.ok) { |
|||
throw ApiError.provider(payload?.message || "Popiserver media detail failed.", upstream.status); |
|||
} |
|||
if (!payload || payload.status !== "0000") { |
|||
throw ApiError.provider(payload?.message || "Popiserver media detail returned an error.", 502); |
|||
} |
|||
|
|||
return NextResponse.json({ |
|||
success: true, |
|||
data: payload.data, |
|||
}); |
|||
} catch (error) { |
|||
if (error instanceof ApiError) { |
|||
return NextResponse.json({ success: false, error: error.message }, { status: error.status }); |
|||
} |
|||
return NextResponse.json( |
|||
{ success: false, error: error instanceof Error ? error.message : "Media detail failed." }, |
|||
{ status: 500 } |
|||
); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue