103 lines
2.7 KiB
TypeScript
103 lines
2.7 KiB
TypeScript
/**
|
|
* Browse API — fetches plants with disease counts from the Turso DB
|
|
* for the browse page. Runs server-side only.
|
|
*/
|
|
|
|
import { sql, eq } from "drizzle-orm";
|
|
import { getDb } from "@/lib/db/index";
|
|
import { plants, diseases, plantViews } from "@/lib/db/schema";
|
|
import type { PlantCardData } from "@/components/PlantCard";
|
|
|
|
export type { PlantCardData };
|
|
|
|
/**
|
|
* Get all plants with their disease counts for the browse page.
|
|
*/
|
|
export async function getBrowsePlants(): Promise<PlantCardData[]> {
|
|
const db = getDb();
|
|
|
|
// LEFT JOIN to include plants with zero diseases
|
|
const rows = await db
|
|
.select({
|
|
id: plants.id,
|
|
commonName: plants.commonName,
|
|
scientificName: plants.scientificName,
|
|
family: plants.family,
|
|
category: plants.category,
|
|
imageUrl: plants.imageUrl,
|
|
updatedAt: plants.updatedAt,
|
|
viewCount: sql<number>`COALESCE(${plantViews.viewCount}, 0)`,
|
|
diseaseCount: sql<number>`COUNT(${diseases.id})`,
|
|
})
|
|
.from(plants)
|
|
.leftJoin(diseases, eq(diseases.plantId, plants.id))
|
|
.leftJoin(plantViews, eq(plantViews.plantId, plants.id))
|
|
.groupBy(plants.id)
|
|
.orderBy(plants.commonName);
|
|
|
|
return rows.map((r) => ({
|
|
id: r.id,
|
|
commonName: r.commonName,
|
|
scientificName: r.scientificName,
|
|
family: r.family,
|
|
category: r.category,
|
|
imageUrl: r.imageUrl,
|
|
updatedAt: r.updatedAt,
|
|
viewCount: r.viewCount,
|
|
diseaseCount: r.diseaseCount,
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Get a single plant with disease count (for detail page lookups).
|
|
*/
|
|
export async function getBrowsePlant(id: string): Promise<PlantCardData | null> {
|
|
const db = getDb();
|
|
const rows = await db
|
|
.select({
|
|
id: plants.id,
|
|
commonName: plants.commonName,
|
|
scientificName: plants.scientificName,
|
|
family: plants.family,
|
|
category: plants.category,
|
|
imageUrl: plants.imageUrl,
|
|
diseaseCount: sql<number>`COUNT(${diseases.id})`,
|
|
})
|
|
.from(plants)
|
|
.leftJoin(diseases, eq(diseases.plantId, plants.id))
|
|
.where(eq(plants.id, id))
|
|
.groupBy(plants.id)
|
|
.limit(1);
|
|
|
|
return rows[0] ?? null;
|
|
}
|
|
|
|
/**
|
|
* Get featured plants for the homepage (subset).
|
|
*/
|
|
const FEATURED_IDS = [
|
|
"tomato",
|
|
"basil",
|
|
"rose",
|
|
"monstera",
|
|
"snake-plant",
|
|
"pepper",
|
|
"apple",
|
|
"corn",
|
|
"wheat",
|
|
"strawberry",
|
|
"blueberry",
|
|
"lettuce",
|
|
];
|
|
|
|
export async function getFeaturedPlants(): Promise<PlantCardData[]> {
|
|
const all = await getBrowsePlants();
|
|
const featured = all.filter((p) => FEATURED_IDS.includes(p.id));
|
|
// If fewer than expected are found, pad with first available plants
|
|
if (featured.length < 6) {
|
|
const rest = all.filter((p) => !FEATURED_IDS.includes(p.id));
|
|
return [...featured, ...rest].slice(0, 12);
|
|
}
|
|
return featured.slice(0, 12);
|
|
}
|