99 lines
3.1 KiB
TypeScript
99 lines
3.1 KiB
TypeScript
/**
|
|
* GET /api/plants/suggestions?q=<term>
|
|
*
|
|
* Returns autocomplete suggestions for the navbar search-as-you-type feature.
|
|
* Queries both plants and diseases from the database and returns an interleaved
|
|
* list with at most 8 suggestions total.
|
|
*
|
|
* Each suggestion includes: type (plant|disease), id, label, subtitle, emoji, href.
|
|
* Plants link to their browse detail page; diseases link to the plant page with
|
|
* a hash anchor to the specific disease card.
|
|
*/
|
|
|
|
import { NextRequest, NextResponse } from "next/server";
|
|
import { like, or, eq } from "drizzle-orm";
|
|
import { getDb } from "@/lib/db/index";
|
|
import { plants, diseases } from "@/lib/db/schema";
|
|
import { getEmojiForCategory } from "@/lib/display-helpers";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
interface SuggestionItem {
|
|
type: "plant" | "disease";
|
|
id: string;
|
|
label: string;
|
|
subtitle: string;
|
|
emoji: string;
|
|
href: string;
|
|
}
|
|
|
|
export async function GET(request: NextRequest) {
|
|
const q = request.nextUrl.searchParams.get("q")?.trim() ?? "";
|
|
|
|
// Empty or very short queries return no suggestions
|
|
if (q.length < 1) {
|
|
return NextResponse.json({ suggestions: [] });
|
|
}
|
|
|
|
const db = getDb();
|
|
const term = `%${q.toLowerCase()}%`;
|
|
|
|
// Fetch matching plants (by common name or scientific name)
|
|
const plantRows = await db
|
|
.select({
|
|
id: plants.id,
|
|
commonName: plants.commonName,
|
|
scientificName: plants.scientificName,
|
|
category: plants.category,
|
|
})
|
|
.from(plants)
|
|
.where(or(like(plants.commonName, term), like(plants.scientificName, term)))
|
|
.limit(5);
|
|
|
|
// Fetch matching diseases (by name or scientific name) with parent plant info
|
|
const diseaseRows = await db
|
|
.select({
|
|
id: diseases.id,
|
|
name: diseases.name,
|
|
plantId: diseases.plantId,
|
|
plantCommonName: plants.commonName,
|
|
plantCategory: plants.category,
|
|
})
|
|
.from(diseases)
|
|
.leftJoin(plants, eq(diseases.plantId, plants.id))
|
|
.where(or(like(diseases.name, term), like(diseases.scientificName, term)))
|
|
.limit(5);
|
|
|
|
const plantSuggestions: SuggestionItem[] = plantRows.map((p) => ({
|
|
type: "plant" as const,
|
|
id: p.id,
|
|
label: p.commonName,
|
|
subtitle: p.scientificName,
|
|
emoji: getEmojiForCategory(p.category),
|
|
href: `/browse/${p.id}`,
|
|
}));
|
|
|
|
const diseaseSuggestions: SuggestionItem[] = diseaseRows.map((d) => ({
|
|
type: "disease" as const,
|
|
id: d.id,
|
|
label: d.name,
|
|
subtitle: `Disease on ${d.plantCommonName ?? "Unknown plant"}`,
|
|
emoji: getEmojiForCategory(d.plantCategory ?? "houseplant"),
|
|
href: `/browse/${d.plantId}#disease-${d.id}`,
|
|
}));
|
|
|
|
// Interleave plant and disease results so the dropdown shows variety
|
|
const interleaved: SuggestionItem[] = [];
|
|
const maxLen = Math.max(plantSuggestions.length, diseaseSuggestions.length);
|
|
for (let i = 0; i < maxLen && interleaved.length < 8; i++) {
|
|
if (i < plantSuggestions.length) {
|
|
interleaved.push(plantSuggestions[i]);
|
|
}
|
|
if (i < diseaseSuggestions.length && interleaved.length < 8) {
|
|
interleaved.push(diseaseSuggestions[i]);
|
|
}
|
|
}
|
|
|
|
return NextResponse.json({ suggestions: interleaved });
|
|
}
|