Files
Moonbase/prisma/seed.ts
tonym cdb21ed58a v0.11.0 — Plant types: the reusable "kind" record
A rich-but-all-optional reference record per kind of plant (care, sun, water,
bloom & harvest windows, toxic-to-pets, edible parts, spacing), distinct from a
specific planting or a group.

- PlantType model + optional Plant.plantTypeId; migration + idempotent backfill
  that seeds 247 kinds from the built-in plant library and links plantings by
  species/common name (wired into the deploy entrypoint)
- /api/plant-types CRUD; new "Plant types" section (browse by category, edit a
  kind, see every spot you've planted it)
- New plants auto-link to their type on create; plant detail shows "More about…"
- PlantType added to backup/restore; bump APP_VERSION 0.11.0 + changelog

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 23:46:07 -05:00

101 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { PrismaClient, PlantCategory } from "@prisma/client";
import { hashSync } from "bcryptjs";
import { syncPlantTypesFromLibrary } from "../src/lib/garden/plant-types";
const db = new PrismaClient();
async function main() {
console.log("Seeding Moon Base...");
// Admin accounts — only created if they don't exist yet.
const password = hashSync("moonbase!", 10);
await db.user.upsert({
where: { username: "bonna" },
update: { passwordHash: password },
create: {
id: "cluser0000000bonna",
email: "bonna@moonbase.local",
username: "bonna",
name: "Bonna Moon",
passwordHash: password,
role: "ADMIN",
mustChangePassword: true,
},
});
await db.user.upsert({
where: { username: "tony" },
update: { passwordHash: password },
create: {
id: "cluser0000000tony0",
email: "tony@moonbase.local",
username: "tony",
name: "Tony Moon",
passwordHash: password,
role: "ADMIN",
mustChangePassword: true,
},
});
if (process.env.SEED_MODE === "minimal") {
console.log("Minimal seed complete (admin accounts only).");
return;
}
// Sample plants for dev — a small starter food forest
const plants = [
{ commonName: "Apple", variety: "Honeycrisp", category: PlantCategory.CANOPY_TREE, zone: "Back yard, north fence", source: "Jung's Nursery", plantedAt: new Date("2024-04-15") },
{ commonName: "Apple", variety: "Liberty", category: PlantCategory.CANOPY_TREE, zone: "Back yard, north fence", source: "Jung's Nursery", plantedAt: new Date("2024-04-15") },
{ commonName: "Pear", variety: "Bartlett", category: PlantCategory.CANOPY_TREE, zone: "Back yard, east side", source: "Jung's Nursery", plantedAt: new Date("2024-04-20") },
{ commonName: "Elderberry", variety: "Bob Gordon", category: PlantCategory.UNDERSTORY_TREE, zone: "Back fence, south", source: "Raintree Nursery", plantedAt: new Date("2024-05-01") },
{ commonName: "Serviceberry", species: "Amelanchier canadensis", category: PlantCategory.UNDERSTORY_TREE, zone: "Front yard", source: "local nursery", plantedAt: new Date("2023-04-10") },
{ commonName: "Hazelnut", variety: "American", category: PlantCategory.LARGE_SHRUB, zone: "Back yard, west fence", source: "Oikos Tree Crops", plantedAt: new Date("2024-04-15") },
{ commonName: "Comfrey", species: "Symphytum officinale", category: PlantCategory.HERB, zone: "Under apple trees", source: "from seed", plantedAt: new Date("2024-05-15"), notes: "Chop-and-drop around the apples. Cut before it flowers to prevent spreading." },
{ commonName: "Raspberry", variety: "Heritage", category: PlantCategory.SHRUB, zone: "Back fence, east", source: "division", plantedAt: new Date("2023-05-01") },
{ commonName: "Strawberry", variety: "Ozark Beauty", category: PlantCategory.GROUNDCOVER, zone: "Under pear tree", source: "Jung's Nursery", plantedAt: new Date("2024-05-10") },
{ commonName: "Garlic", variety: "Music", category: PlantCategory.BULB, zone: "Raised bed #1", source: "Keene Organics", plantedAt: new Date("2024-10-10") },
];
// Starter Location tree — a root property node with the garden areas under it.
const root = await db.location.upsert({
where: { id: "seed-loc-root" },
update: {},
create: { id: "seed-loc-root", name: "Moon Homestead", kind: "OTHER", path: "Moon Homestead" },
});
const zoneNames = Array.from(new Set(plants.map((p) => p.zone).filter(Boolean) as string[]));
const zoneToLocId = new Map<string, string>();
for (const z of zoneNames) {
const id = `seed-loc-${z.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
await db.location.upsert({
where: { id },
update: {},
create: { id, name: z, kind: "AREA", parentId: root.id, path: `Moon Homestead ${z}` },
});
zoneToLocId.set(z, id);
}
for (const p of plants) {
await db.plant.upsert({
where: { id: `seed-${p.commonName.toLowerCase().replace(/\s/g, "-")}-${p.variety?.toLowerCase().replace(/\s/g, "-") ?? "0"}` },
update: {},
create: {
id: `seed-${p.commonName.toLowerCase().replace(/\s/g, "-")}-${p.variety?.toLowerCase().replace(/\s/g, "-") ?? "0"}`,
...p,
locationId: p.zone ? zoneToLocId.get(p.zone) : undefined,
},
});
}
// Seed the PlantType library and link the sample plants to their kind.
const pt = await syncPlantTypesFromLibrary(db);
console.log(`Seeded ${pt.types} plant types; linked ${pt.linked} plantings.`);
console.log("Demo seed complete.");
}
main()
.catch(console.error)
.finally(() => db.$disconnect());