clean up seed data and plant info setup

This commit is contained in:
2026-06-12 17:55:05 +00:00
parent 8725a387bf
commit a0348f701e
5 changed files with 14 additions and 377 deletions
-1
View File
@@ -553,7 +553,6 @@ export function App() {
plants={plants} plants={plants}
onCompleteBulkTasks={(tasks) => void completeBulkTasks(tasks)} onCompleteBulkTasks={(tasks) => void completeBulkTasks(tasks)}
onCompleteTask={(task) => void completeTask(task)} onCompleteTask={(task) => void completeTask(task)}
onNewPlant={startAddingPlant}
onOpenPlant={openPlantDetail} onOpenPlant={openPlantDetail}
/> />
)} )}
@@ -1,5 +1,5 @@
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { CalendarCheck, Plus } from 'lucide-react'; import { CalendarCheck } from 'lucide-react';
import type { CareTask, Plant, PlantGroup } from '../domain'; import type { CareTask, Plant, PlantGroup } from '../domain';
import { PlantCard } from './PlantCard'; import { PlantCard } from './PlantCard';
@@ -12,7 +12,6 @@ type HomeViewProps = {
plants: Plant[]; plants: Plant[];
onCompleteBulkTasks: (tasks: CareTask[]) => void; onCompleteBulkTasks: (tasks: CareTask[]) => void;
onCompleteTask: (task: CareTask) => void; onCompleteTask: (task: CareTask) => void;
onNewPlant: () => void;
onOpenPlant: (plant: Plant) => void; onOpenPlant: (plant: Plant) => void;
}; };
@@ -25,7 +24,6 @@ export function HomeView({
plants, plants,
onCompleteBulkTasks, onCompleteBulkTasks,
onCompleteTask, onCompleteTask,
onNewPlant,
onOpenPlant, onOpenPlant,
}: HomeViewProps) { }: HomeViewProps) {
const dueTaskGroups = groupDueTasks(careTasks); const dueTaskGroups = groupDueTasks(careTasks);
@@ -192,9 +190,6 @@ export function HomeView({
<section className="section" aria-labelledby="plants-heading"> <section className="section" aria-labelledby="plants-heading">
<div className="section-heading"> <div className="section-heading">
<h2 id="plants-heading">My Plants</h2> <h2 id="plants-heading">My Plants</h2>
<button className="icon-button compact" type="button" aria-label="Add plant" onClick={onNewPlant}>
<Plus size={18} />
</button>
</div> </div>
<div className="plant-grid"> <div className="plant-grid">
-369
View File
@@ -1,369 +0,0 @@
using Microsoft.EntityFrameworkCore;
using plant_manager.Data.Models;
namespace plant_manager.Data
{
public static class DatabaseSeeder
{
private static readonly CareAction[] StarterCareActions =
[
new() { Name = "Water", Description = "Hydrate a plant or refresh its reservoir." },
new() { Name = "Repot", Description = "Move a plant into a new container or medium." },
new() { Name = "Fertilize", Description = "Apply nutrients or plant food." },
new() { Name = "Prune", Description = "Trim leaves, stems, roots, or spent growth." },
new() { Name = "Inspect", Description = "Check for pests, health changes, or growth." }
];
private static readonly ActionResource[] StarterResources =
[
new() { Name = "Water", Notes = "Plain watering resource." },
new() { Name = "Potting Mix", Notes = "General purpose houseplant medium." },
new() { Name = "Orchid Bark", Notes = "Chunky amendment for airflow and drainage." },
new() { Name = "Perlite", Notes = "Lightweight amendment for drainage and aeration." },
new() { Name = "Fertilizer", Notes = "General plant nutrient." },
new() { Name = "Nursery Pot", Notes = "Basic plastic grow pot." },
new() { Name = "Neem Oil", Notes = "Common pest treatment." },
new() { Name = "Pruners", Notes = "Cutting tool for pruning or cleanup." }
];
private static readonly StarterCareActivity[] StarterCareActivities =
[
new("Water", [new("Water", ["Water"])]),
new("Repot with Potting Mix", [new("Repot", ["Potting Mix"])]),
new("Fertilize", [new("Fertilize", ["Fertilizer"])]),
new("Prune", [new("Prune", ["Pruners"])]),
new("Inspect", [new("Inspect", [])])
];
private static readonly PlantFlagDefinition[] StarterFlagDefinitions =
[
new() { Name = "Spider mites", Color = "#ffe4e1" },
new() { Name = "Fungus gnats", Color = "#fff4cc" },
new() { Name = "Dying", Color = "#ffd6d6" },
new() { Name = "Quarantine", Color = "#e8eef8" },
new() { Name = "Needs repotting", Color = "#e9f5e7" },
new() { Name = "Watch closely", Color = "#eeeeee" }
];
private static readonly StarterTaxon[] StarterTaxa =
[
new("Chinese Money Plant", "Pilea", "peperomioides"),
new("Croton Petra", "Codiaeum", "variegatum"),
new("Parallel Peperomia", "Peperomia", "tetragona"),
new("Marble Peperomia", "Peperomia", "obtusifolia"),
new("Silver Squill", "Ledebouria", "socialis"),
new("Fiddle-leaf Fig", "Ficus", "lyrata"),
new("Ficus Audrey", "Ficus", "benghalensis"),
new("Dumbcane", "Dieffenbachia", "seguine"),
new("Kris Plant", "Alocasia", "sanderiana"),
new("Lucky Bamboo", "Dracaena", "sanderiana"),
new("Common Ivy", "Hedera", "helix"),
new("Peacock Plant", "Goeppertia", "makoyana"),
new("False Shamrock", "Oxalis", "triangularis"),
new("Pinstripe Plant", "Goeppertia", "ornata"),
new("Monstera Thai Constellation", "Monstera", "deliciosa"),
new("Pothos", "Epipremnum", "aureum"),
new("Moth orchid", "Phalaenopsis", "hybrid"),
new("Money Tree", "Pachira", "aquatica")
];
private static readonly StarterPlant[] StarterPlants =
[
new("Chinese Money Plant 1", "Chinese Money Plant", "Plant cart"),
new("Chinese Money Plant 2", "Chinese Money Plant", "Plant cart"),
new("Chinese Money Plant 3", "Chinese Money Plant", "Plant cart"),
new("Croton Petra", "Croton Petra", "Plant cart"),
new("Parallel Peperomia", "Parallel Peperomia", "Plant cart"),
new("Marble Peperomia", "Marble Peperomia", "Plant cart"),
new("Silver Squill", "Silver Squill", "Plant cart"),
new("Fiddle-leaf Fig", "Fiddle-leaf Fig", "Living room"),
new("Ficus Audrey", "Ficus Audrey", "Plant cart"),
new("Dumbcane 1", "Dumbcane", "Plant cart"),
new("Dumbcane 2", "Dumbcane", "Plant cart"),
new("Kris Plant", "Kris Plant", "Plant cart"),
new("Lucky Bamboo", "Lucky Bamboo", "Plant cart"),
new("Common Ivy 1", "Common Ivy", "Plant cart"),
new("Common Ivy 2", "Common Ivy", "Plant cart"),
new("Peacock Plant 1", "Peacock Plant", "Plant cart"),
new("Peacock Plant 2", "Peacock Plant", "Plant cart"),
new("False Shamrock", "False Shamrock", "Plant cart"),
new("Pinstripe Plant", "Pinstripe Plant", "Computer desk"),
new("Monstera Thai Constellation", "Monstera Thai Constellation", "Plant cart"),
new("Pothos", "Pothos", "Plant cart"),
new("Moth orchid", "Moth orchid", "Plant cart"),
new("Money Tree", "Money Tree", "Computer desk")
];
public static void Seed(ApplicationDbContext db)
{
SeedCareActions(db);
SeedActionResources(db);
SeedCareActivities(db);
SeedPlantFlags(db);
SeedStarterTaxa(db);
SeedPlantLocations(db);
SeedStarterPlants(db);
}
private static void SeedCareActions(ApplicationDbContext db)
{
var existingActionNames = db.CareActions
.Select(action => action.Name)
.ToList();
var missingActions = StarterCareActions
.Where(starterAction => !existingActionNames.Any(existingName =>
string.Equals(existingName, starterAction.Name, StringComparison.OrdinalIgnoreCase)))
.Select(starterAction => new CareAction
{
Name = starterAction.Name,
Description = starterAction.Description
})
.ToList();
if (missingActions.Count == 0)
{
return;
}
db.CareActions.AddRange(missingActions);
db.SaveChanges();
}
private static void SeedActionResources(ApplicationDbContext db)
{
var existingResourceNames = db.ActionResources
.Select(resource => resource.Name)
.ToList();
var missingResources = StarterResources
.Where(starterResource => !existingResourceNames.Any(existingName =>
string.Equals(existingName, starterResource.Name, StringComparison.OrdinalIgnoreCase)))
.Select(starterResource => new ActionResource
{
Name = starterResource.Name,
Notes = starterResource.Notes
})
.ToList();
if (missingResources.Count == 0)
{
return;
}
db.ActionResources.AddRange(missingResources);
db.SaveChanges();
}
private static void SeedCareActivities(ApplicationDbContext db)
{
var existingActivityNames = db.CareActivities
.Select(activity => activity.Name)
.ToList();
var actionsByName = db.CareActions.ToDictionary(action => action.Name, StringComparer.OrdinalIgnoreCase);
var resourcesByName = db.ActionResources.ToDictionary(resource => resource.Name, StringComparer.OrdinalIgnoreCase);
var missingActivities = StarterCareActivities
.Where(starterActivity => !existingActivityNames.Any(existingName =>
string.Equals(existingName, starterActivity.Name, StringComparison.OrdinalIgnoreCase)))
.Select(starterActivity =>
{
var actions = starterActivity.Actions
.Select((starterAction, index) => actionsByName.TryGetValue(starterAction.Name, out var action)
? new CareActivityAction
{
CareActionId = action.Id,
CareAction = action,
SortOrder = index,
Resources = starterAction.ResourceNames
.Select(resourceName => resourcesByName.TryGetValue(resourceName, out var resource)
? new CareActivityActionResource
{
ActionResourceId = resource.Id,
ActionResource = resource
}
: null)
.OfType<CareActivityActionResource>()
.ToList()
}
: null)
.OfType<CareActivityAction>()
.ToList();
if (actions.Count != starterActivity.Actions.Count
|| actions.Zip(starterActivity.Actions).Any(pair => pair.First.Resources.Count != pair.Second.ResourceNames.Count))
{
return null;
}
return new CareActivity
{
Name = starterActivity.Name,
Actions = actions
};
})
.OfType<CareActivity>()
.ToList();
if (missingActivities.Count == 0)
{
return;
}
db.CareActivities.AddRange(missingActivities);
db.SaveChanges();
}
private static void SeedPlantFlags(ApplicationDbContext db)
{
var existingFlagNames = db.PlantFlagDefinitions
.Select(flag => flag.Name)
.ToList();
var missingFlags = StarterFlagDefinitions
.Where(starterFlag => !existingFlagNames.Any(existingName =>
string.Equals(existingName, starterFlag.Name, StringComparison.OrdinalIgnoreCase)))
.Select(starterFlag => new PlantFlagDefinition
{
Name = starterFlag.Name,
Color = starterFlag.Color
})
.ToList();
if (missingFlags.Count == 0)
{
return;
}
db.PlantFlagDefinitions.AddRange(missingFlags);
db.SaveChanges();
}
private static void SeedStarterTaxa(ApplicationDbContext db)
{
var existingTaxa = db.PlantTaxa.ToList();
var missingTaxa = StarterTaxa
.Where(starterTaxon => !existingTaxa.Any(existingTaxon =>
string.Equals(existingTaxon.Name, starterTaxon.Name, StringComparison.OrdinalIgnoreCase)
&& string.Equals(existingTaxon.Genus, starterTaxon.Genus, StringComparison.OrdinalIgnoreCase)
&& string.Equals(existingTaxon.Species, starterTaxon.Species, StringComparison.OrdinalIgnoreCase)))
.Select(starterTaxon => new PlantTaxon
{
Name = starterTaxon.Name,
Genus = starterTaxon.Genus,
Species = starterTaxon.Species
})
.ToList();
if (missingTaxa.Count == 0)
{
return;
}
db.PlantTaxa.AddRange(missingTaxa);
db.SaveChanges();
}
private static void SeedStarterPlants(ApplicationDbContext db)
{
if (db.Plants.Any())
{
return;
}
var taxaByName = db.PlantTaxa.ToDictionary(taxon => taxon.Name, StringComparer.OrdinalIgnoreCase);
var locationsByName = db.PlantLocations.ToDictionary(location => location.Name, StringComparer.OrdinalIgnoreCase);
var flagsByName = db.PlantFlagDefinitions.ToDictionary(flag => flag.Name, StringComparer.OrdinalIgnoreCase);
PlantTaxon Taxon(string name) => taxaByName[name];
PlantLocation Location(string name) => locationsByName[name];
var starterPlants = StarterPlants
.Select(starterPlant => new Plant
{
Nickname = starterPlant.Nickname,
Taxon = Taxon(starterPlant.TaxonName),
Location = Location(starterPlant.Location)
})
.ToList();
db.Plants.AddRange(starterPlants);
db.SaveChanges();
var today = DateOnly.FromDateTime(DateTime.UtcNow);
var starterFlags = new List<PlantFlag>();
void AddFlag(string plantNickname, string flagName)
{
if (!flagsByName.TryGetValue(flagName, out var flag))
{
return;
}
var plant = starterPlants.FirstOrDefault(item =>
string.Equals(item.Nickname, plantNickname, StringComparison.OrdinalIgnoreCase));
if (plant is null)
{
return;
}
starterFlags.Add(new PlantFlag
{
PlantId = plant.Id,
PlantFlagDefinitionId = flag.Id,
StartedOn = today
});
}
AddFlag("Pinstripe Plant", "Dying");
AddFlag("Pinstripe Plant", "Spider mites");
AddFlag("Pinstripe Plant", "Quarantine");
AddFlag("Chinese Money Plant 1", "Quarantine");
AddFlag("Chinese Money Plant 2", "Quarantine");
AddFlag("Chinese Money Plant 3", "Quarantine");
AddFlag("Ficus Audrey", "Needs repotting");
db.PlantFlags.AddRange(starterFlags);
db.SaveChanges();
}
private static void SeedPlantLocations(ApplicationDbContext db)
{
var starterLocationNames = StarterPlants
.Select(plant => plant.Location)
.Append("Unassigned")
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
var existingLocationNames = db.PlantLocations
.Select(location => location.Name)
.ToList();
var missingLocations = starterLocationNames
.Where(starterLocation => !existingLocationNames.Any(existingName =>
string.Equals(existingName, starterLocation, StringComparison.OrdinalIgnoreCase)))
.Select(starterLocation => new PlantLocation
{
Name = starterLocation
})
.ToList();
if (missingLocations.Count == 0)
{
return;
}
db.PlantLocations.AddRange(missingLocations);
db.SaveChanges();
}
private sealed record StarterTaxon(string Name, string Genus, string Species);
private sealed record StarterCareActivity(string Name, IReadOnlyList<StarterCareActivityAction> Actions);
private sealed record StarterCareActivityAction(string Name, IReadOnlyList<string> ResourceNames);
private sealed record StarterPlant(
string Nickname,
string TaxonName,
string Location);
}
}
-1
View File
@@ -42,7 +42,6 @@ using (var scope = app.Services.CreateScope())
{ {
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
db.Database.Migrate(); db.Database.Migrate();
DatabaseSeeder.Seed(db);
var plantInfoDb = scope.ServiceProvider.GetRequiredService<PlantInfoDbContext>(); var plantInfoDb = scope.ServiceProvider.GetRequiredService<PlantInfoDbContext>();
await plantInfoDb.EnsureSearchSchemaAsync(); await plantInfoDb.EnsureSearchSchemaAsync();
+13
View File
@@ -6,6 +6,11 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
SOURCE_DB="$REPO_ROOT/data/build/plant-info.db" SOURCE_DB="$REPO_ROOT/data/build/plant-info.db"
TARGET_DB="$REPO_ROOT/plant-manager/App_Data/plant-info.db" TARGET_DB="$REPO_ROOT/plant-manager/App_Data/plant-info.db"
if pgrep -f "dotnet run --project plant-manager|plant-manager.dll" >/dev/null; then
echo "Plant-Man backend appears to be running. Stop it before installing plant-info.db." >&2
exit 1
fi
if [[ ! -f "$SOURCE_DB" ]]; then if [[ ! -f "$SOURCE_DB" ]]; then
echo "Missing generated plant info database: $SOURCE_DB" >&2 echo "Missing generated plant info database: $SOURCE_DB" >&2
echo "Build it first with:" >&2 echo "Build it first with:" >&2
@@ -13,7 +18,15 @@ if [[ ! -f "$SOURCE_DB" ]]; then
exit 1 exit 1
fi fi
SOURCE_COUNT="$(sqlite3 "$SOURCE_DB" "SELECT COUNT(*) FROM PlantInfoRecords;" 2>/dev/null || echo 0)"
if [[ "$SOURCE_COUNT" == "0" ]]; then
echo "Generated plant info database has no PlantInfoRecords: $SOURCE_DB" >&2
exit 1
fi
mkdir -p "$(dirname "$TARGET_DB")" mkdir -p "$(dirname "$TARGET_DB")"
rm -f "$TARGET_DB-shm" "$TARGET_DB-wal"
cp -f "$SOURCE_DB" "$TARGET_DB" cp -f "$SOURCE_DB" "$TARGET_DB"
echo "Installed $SOURCE_DB -> $TARGET_DB" echo "Installed $SOURCE_DB -> $TARGET_DB"
echo "Plant info records: $SOURCE_COUNT"