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 type { SearchResponse, SearchSort } from "@/lib/search/types"
|
||||||
|
|
||||||
|
import { httpJson } from "./http"
|
||||||
|
|
||||||
export interface SearchCatalogParams {
|
export interface SearchCatalogParams {
|
||||||
q?: string
|
q?: string
|
||||||
selectedGames?: string[]
|
selectedGames?: string[]
|
||||||
@@ -19,26 +22,30 @@ export async function searchCatalog(params: SearchCatalogParams): Promise<Search
|
|||||||
if (params.q) searchParams.set("q", params.q)
|
if (params.q) searchParams.set("q", params.q)
|
||||||
|
|
||||||
for (const game of params.selectedGames ?? []) {
|
for (const game of params.selectedGames ?? []) {
|
||||||
searchParams.append("game", game)
|
searchParams.append("selectedGames", game)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.min) searchParams.set("min", params.min)
|
if (params.min) searchParams.set("min", params.min)
|
||||||
if (params.max) searchParams.set("max", params.max)
|
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.minRating && params.minRating !== "0") searchParams.set("minRating", params.minRating)
|
||||||
if (params.sort && params.sort !== "composite") searchParams.set("sort", params.sort)
|
if (params.sort && params.sort !== "composite") searchParams.set("sort", params.sort)
|
||||||
|
|
||||||
if (params.limit !== undefined) searchParams.set("limit", String(params.limit))
|
if (params.limit !== undefined) searchParams.set("limit", String(params.limit))
|
||||||
if (params.offset !== undefined) searchParams.set("offset", String(params.offset))
|
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",
|
cache: "no-store",
|
||||||
signal: params.signal,
|
signal: params.signal,
|
||||||
})
|
})
|
||||||
|
} catch (error) {
|
||||||
if (!res.ok) {
|
if (error instanceof Error && error.message === "UNAUTHORIZED") {
|
||||||
throw new Error(`Search API request failed: ${res.status} ${res.statusText}`)
|
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