feat(search): migrate to backend endpoint
This commit is contained in:
@@ -1,62 +0,0 @@
|
||||
import { mockPlayers, mockServices, mockShops } from "@/lib/mock"
|
||||
import { searchCatalog } from "@/lib/search/search-catalog"
|
||||
import type { SearchSort } from "@/lib/search/types"
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
export const dynamic = "force-dynamic"
|
||||
|
||||
const SEARCH_SORTS: ReadonlySet<SearchSort> = new Set([
|
||||
"composite",
|
||||
"rating",
|
||||
"orders",
|
||||
"price_asc",
|
||||
"price_desc",
|
||||
])
|
||||
|
||||
function numberParam(value: string | null, fallback: number) {
|
||||
if (value === null) return fallback
|
||||
const parsed = Number(value)
|
||||
return Number.isFinite(parsed) ? parsed : fallback
|
||||
}
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
|
||||
const q = searchParams.get("q") ?? undefined
|
||||
const selectedGames = searchParams.getAll("game")
|
||||
const min = searchParams.get("min") ?? ""
|
||||
const max = searchParams.get("max") ?? ""
|
||||
const onlyOnline = (searchParams.get("online") ?? "0") === "1"
|
||||
const minRating = searchParams.get("minRating") ?? "0"
|
||||
|
||||
const sortParam = (searchParams.get("sort") ?? "composite") as SearchSort
|
||||
const sort: SearchSort = SEARCH_SORTS.has(sortParam) ? sortParam : "composite"
|
||||
|
||||
const limit = numberParam(searchParams.get("limit"), 12)
|
||||
const offset = numberParam(searchParams.get("offset"), 0)
|
||||
|
||||
const response = searchCatalog(
|
||||
{
|
||||
q,
|
||||
selectedGames,
|
||||
min,
|
||||
max,
|
||||
onlyOnline,
|
||||
minRating,
|
||||
sort,
|
||||
limit,
|
||||
offset,
|
||||
},
|
||||
{
|
||||
players: mockPlayers,
|
||||
shops: mockShops,
|
||||
services: mockServices,
|
||||
},
|
||||
)
|
||||
|
||||
return NextResponse.json(response, {
|
||||
headers: {
|
||||
"Cache-Control": "no-store",
|
||||
},
|
||||
})
|
||||
}
|
||||
+15
-8
@@ -1,5 +1,8 @@
|
||||
import { isApiError } from "@/lib/errors"
|
||||
import type { SearchResponse, SearchSort } from "@/lib/search/types"
|
||||
|
||||
import { httpJson } from "./http"
|
||||
|
||||
export interface SearchCatalogParams {
|
||||
q?: string
|
||||
selectedGames?: string[]
|
||||
@@ -19,26 +22,30 @@ export async function searchCatalog(params: SearchCatalogParams): Promise<Search
|
||||
if (params.q) searchParams.set("q", params.q)
|
||||
|
||||
for (const game of params.selectedGames ?? []) {
|
||||
searchParams.append("game", game)
|
||||
searchParams.append("selectedGames", game)
|
||||
}
|
||||
|
||||
if (params.min) searchParams.set("min", params.min)
|
||||
if (params.max) searchParams.set("max", params.max)
|
||||
if (params.onlyOnline) searchParams.set("online", "1")
|
||||
if (params.onlyOnline !== undefined) searchParams.set("onlyOnline", String(params.onlyOnline))
|
||||
if (params.minRating && params.minRating !== "0") searchParams.set("minRating", params.minRating)
|
||||
if (params.sort && params.sort !== "composite") searchParams.set("sort", params.sort)
|
||||
|
||||
if (params.limit !== undefined) searchParams.set("limit", String(params.limit))
|
||||
if (params.offset !== undefined) searchParams.set("offset", String(params.offset))
|
||||
|
||||
const res = await fetch(`/api/search?${searchParams.toString()}`, {
|
||||
try {
|
||||
return await httpJson<SearchResponse>(`/api/v1/search?${searchParams.toString()}`, {
|
||||
cache: "no-store",
|
||||
signal: params.signal,
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Search API request failed: ${res.status} ${res.statusText}`)
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === "UNAUTHORIZED") {
|
||||
throw error
|
||||
}
|
||||
if (isApiError(error)) {
|
||||
throw new Error(`Search API request failed: ${error.code} ${error.msg}`)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
|
||||
return (await res.json()) as SearchResponse
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import { GET } from "@/app/api/search/route"
|
||||
import { describe, expect, it } from "vitest"
|
||||
|
||||
describe("GET /api/search", () => {
|
||||
it("returns 200 with items and meta", async () => {
|
||||
const request = new Request("http://x/api/search")
|
||||
const response = await GET(request)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
const json = await response.json()
|
||||
expect(Array.isArray(json.items)).toBe(true)
|
||||
expect(json.meta).toHaveProperty("total")
|
||||
expect(json.meta).toHaveProperty("offset")
|
||||
expect(json.meta).toHaveProperty("limit")
|
||||
})
|
||||
|
||||
it("sets Cache-Control to no-store", async () => {
|
||||
const request = new Request("http://x/api/search")
|
||||
const response = await GET(request)
|
||||
|
||||
expect(response.headers.get("Cache-Control")).toBe("no-store")
|
||||
})
|
||||
|
||||
it("filters by q param", async () => {
|
||||
const request = new Request("http://x/api/search?q=winter")
|
||||
const response = await GET(request)
|
||||
const json = await response.json()
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(json.items.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it("respects limit and offset", async () => {
|
||||
const request = new Request("http://x/api/search?limit=2&offset=1")
|
||||
const response = await GET(request)
|
||||
const json = await response.json()
|
||||
|
||||
expect(json.items.length).toBeLessThanOrEqual(2)
|
||||
expect(json.meta.limit).toBe(2)
|
||||
expect(json.meta.offset).toBe(1)
|
||||
})
|
||||
|
||||
it("handles game filter", async () => {
|
||||
const request = new Request("http://x/api/search?game=CS2")
|
||||
const response = await GET(request)
|
||||
const json = await response.json()
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(json.items.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user