diff --git a/app/api/search/route.ts b/app/api/search/route.ts new file mode 100644 index 0000000..0d0305d --- /dev/null +++ b/app/api/search/route.ts @@ -0,0 +1,106 @@ +import { NextRequest, NextResponse } from "next/server" +import algoliasearch from "algoliasearch" + +const appId = + process.env.ALGOLIA_APP_ID || process.env.NEXT_PUBLIC_ALGOLIA_APP_ID || "" +const apiKey = + process.env.ALGOLIA_SEARCH_API_KEY || + process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_API_KEY || + "" +const additionalIndexes = ( + process.env.ALGOLIA_ADDITIONAL_INDEXES || + process.env.NEXT_PUBLIC_ALGOLIA_ADDITIONAL_INDEXES || + "" +) + .split(",") + .map((index) => index.trim()) + .filter(Boolean) + +const allIndexes = [...additionalIndexes].filter(Boolean) || [ + "blog", + "projects", +] +const searchClient = appId && apiKey ? algoliasearch(appId, apiKey) : null + +function transformQuery(query: string) { + if (query.toLowerCase().includes("intmax")) { + return query.replace(/intmax/i, '"intmax"') + } + return query +} + +export async function GET(request: NextRequest) { + const searchParams = request.nextUrl.searchParams + const query = searchParams.get("query") || "" + const indexName = searchParams.get("index") || "" + const hitsPerPage = parseInt(searchParams.get("hitsPerPage") || "5", 10) + + if (!query || query.trim() === "") { + return NextResponse.json({ + results: [], + status: "empty", + availableIndexes: allIndexes, + }) + } + + if (!searchClient) { + return NextResponse.json( + { + error: "Search client not initialized - missing Algolia credentials", + availableIndexes: [], + }, + { status: 500 } + ) + } + + try { + const transformedQuery = transformQuery(query) + + // If an index is specified, search only that index + if (indexName && indexName.trim() !== "") { + const index = searchClient.initIndex(indexName) + const response = await index.search(transformedQuery, { hitsPerPage }) + + return NextResponse.json({ + hits: response.hits, + status: "success", + availableIndexes: allIndexes, + }) + } + + // Otherwise search across all configured indexes + const searchPromises = allIndexes.map((idxName) => { + return searchClient! + .initIndex(idxName) + .search(transformedQuery, { hitsPerPage }) + .then((response) => ({ + indexName: idxName, + hits: response.hits, + })) + .catch((err) => { + console.error(`Search error for index ${idxName}:`, err) + return { indexName: idxName, hits: [] } + }) + }) + + const indexResults = await Promise.all(searchPromises) + const nonEmptyResults = indexResults.filter( + (result) => result.hits && result.hits.length > 0 + ) + + return NextResponse.json({ + results: nonEmptyResults, + status: "success", + availableIndexes: allIndexes, + }) + } catch (error: any) { + console.error("Global search error:", error) + return NextResponse.json( + { + error: error.message || "Search failed", + availableIndexes: [], + }, + { status: 500 } + ) + } +} diff --git a/components/search/search-button.tsx b/components/search/search-button.tsx index 8f1791f..b5e0243 100644 --- a/components/search/search-button.tsx +++ b/components/search/search-button.tsx @@ -2,26 +2,13 @@ import { Search } from "lucide-react" import { Button, ButtonProps } from "@/components/ui/button" -import { useEffect, useState } from "react" -import { searchConfig } from "@/hooks/useGlobalSearch" +import { useSearchConfig } from "@/hooks/useGlobalSearch" interface SearchButtonProps extends ButtonProps { onClick: () => void } -const { allIndexes, searchClient } = searchConfig - export function SearchButton({ onClick, ...props }: SearchButtonProps) { - const [disabled, setDisabled] = useState(false) - useEffect(() => { - if (!searchClient || allIndexes.length === 0) { - console.warn( - "Algolia credentials (NEXT_PUBLIC_ALGOLIA_APP_ID, NEXT_PUBLIC_ALGOLIA_SEARCH_API_KEY) or indexes are not configured. Search will not work." - ) - setDisabled(true) - } - }, []) - return (