search, db integration
This commit is contained in:
98
apps/web/src/app/api/plants/suggestions/route.ts
Normal file
98
apps/web/src/app/api/plants/suggestions/route.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* 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 });
|
||||
}
|
||||
Reference in New Issue
Block a user