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