diff --git a/src/lib/api/adapters/search.adapter.ts b/src/lib/api/adapters/search.adapter.ts index 5cb7043c..a4580abb 100644 --- a/src/lib/api/adapters/search.adapter.ts +++ b/src/lib/api/adapters/search.adapter.ts @@ -229,12 +229,15 @@ export class SearchAdapter extends BaseAdapter { }) // Search endpoints don't use credentials to avoid CORS + // Rails expects params nested under 'search' key + // Per-page is sent via X-Per-Page header return this.request('/search/all', { method: 'POST', - body: JSON.stringify(body), + body: { search: body }, credentials: 'omit', // Cache search results for 5 minutes by default - cacheTTL: params.query ? 300000 : 0 // Don't cache empty searches + cacheTTL: params.query ? 300000 : 0, // Don't cache empty searches + headers: params.per ? { 'X-Per-Page': String(params.per) } : undefined }) } @@ -254,11 +257,14 @@ export class SearchAdapter extends BaseAdapter { extra: true }) + // Rails expects params nested under 'search' key + // Per-page is sent via X-Per-Page header return this.request('/search/weapons', { method: 'POST', - body: JSON.stringify(body), + body: { search: body }, credentials: 'omit', - cacheTTL: params.query ? 300000 : 0 + cacheTTL: params.query ? 300000 : 0, + headers: params.per ? { 'X-Per-Page': String(params.per) } : undefined }) } @@ -279,11 +285,14 @@ export class SearchAdapter extends BaseAdapter { gachaAvailable: true }) + // Rails expects params nested under 'search' key + // Per-page is sent via X-Per-Page header return this.request('/search/characters', { method: 'POST', - body: JSON.stringify(body), + body: { search: body }, credentials: 'omit', - cacheTTL: params.query ? 300000 : 0 + cacheTTL: params.query ? 300000 : 0, + headers: params.per ? { 'X-Per-Page': String(params.per) } : undefined }) } @@ -301,11 +310,14 @@ export class SearchAdapter extends BaseAdapter { subaura: true }) + // Rails expects params nested under 'search' key + // Per-page is sent via X-Per-Page header return this.request('/search/summons', { method: 'POST', - body: JSON.stringify(body), + body: { search: body }, credentials: 'omit', - cacheTTL: params.query ? 300000 : 0 + cacheTTL: params.query ? 300000 : 0, + headers: params.per ? { 'X-Per-Page': String(params.per) } : undefined }) } diff --git a/src/lib/api/queries/search.queries.ts b/src/lib/api/queries/search.queries.ts index 4d6dfc3b..c3beeccd 100644 --- a/src/lib/api/queries/search.queries.ts +++ b/src/lib/api/queries/search.queries.ts @@ -23,6 +23,10 @@ export interface SearchFilters { proficiency2?: number[] subaura?: boolean extra?: boolean + // Character-specific filters + season?: number[] + characterSeries?: number[] + gachaAvailable?: boolean } /** @@ -44,6 +48,9 @@ export interface SearchPageResult { totalPages: number } +/** Default number of results per page for search queries */ +const SEARCH_PER_PAGE = 50 + /** * Builds search parameters from query string and filters */ @@ -51,11 +58,13 @@ function buildSearchParams( query: string, filters: SearchFilters | undefined, page: number, - locale: 'en' | 'ja' = 'en' + locale: 'en' | 'ja' = 'en', + exclude?: string[] ): SearchParams { const params: SearchParams = { page, - locale + locale, + per: SEARCH_PER_PAGE } // Only include query if not empty @@ -63,6 +72,11 @@ function buildSearchParams( params.query = query.trim() } + // Only include exclude if provided + if (exclude && exclude.length > 0) { + params.exclude = exclude + } + // Build filters object with only defined values if (filters) { const apiFilters: NonNullable = {} @@ -85,6 +99,16 @@ function buildSearchParams( if (filters.extra !== undefined) { apiFilters.extra = filters.extra } + // Character-specific filters + if (filters.season && filters.season.length > 0) { + apiFilters.season = filters.season + } + if (filters.characterSeries && filters.characterSeries.length > 0) { + apiFilters.characterSeries = filters.characterSeries + } + if (filters.gachaAvailable !== undefined) { + apiFilters.gachaAvailable = filters.gachaAvailable + } // Only include filters if any were set if (Object.keys(apiFilters).length > 0) { @@ -154,13 +178,21 @@ export const searchQueries = { * @param query - Search query string * @param filters - Optional filter configuration * @param locale - Locale for results (default: 'en') + * @param exclude - Optional array of character IDs to exclude from results + * @param enabled - Whether the query should be enabled (default: true) * @returns Infinite query options for character search */ - characters: (query: string = '', filters?: SearchFilters, locale: 'en' | 'ja' = 'en') => + characters: ( + query: string = '', + filters?: SearchFilters, + locale: 'en' | 'ja' = 'en', + exclude?: string[], + enabled: boolean = true + ) => infiniteQueryOptions({ - queryKey: ['search', 'characters', query, filters, locale] as const, + queryKey: ['search', 'characters', query, filters, locale, exclude] as const, queryFn: async ({ pageParam }): Promise => { - const params = buildSearchParams(query, filters, pageParam, locale) + const params = buildSearchParams(query, filters, pageParam, locale, exclude) const response = await searchAdapter.searchCharacters(params) return { @@ -178,6 +210,7 @@ export const searchQueries = { }, staleTime: 1000 * 60 * 5, // 5 minutes gcTime: 1000 * 60 * 30, // 30 minutes + enabled }), /**