- Add MinIO service to compose.yaml with named volume - Add generic /api/uploads POST+GET route with auth, 5 MB, image/* guards - Add src/lib/minio.ts singleton client with ensureBucket helper - Add garden Drizzle schema: containers, plants, care_logs, care_schedules, species_cache - Register garden module in src/modules/index.ts and src/lib/db.ts - Apply migration 0015_garden_schema.sql - Add MINIO_* and PERENUAL_API_KEY to .env.example - Add task briefs 70-75 for the full garden feature arc - Add uploads.spec.ts E2E test (RED → GREEN on route auth guard)
3.5 KiB
71 — Garden containers
Goal
Implement full CRUD for containers — the grouping layer above individual plants (shelves, terrariums, raised beds, etc.). A container has a name, type, location notes, and an optional cover photo.
Depends on
- 70 (garden infrastructure — schema, MinIO, upload route)
Scope
Server (src/modules/garden/server/)
Add to actions.ts:
createContainer(input: { name, type, locationNotes?, coverImageUrl? })— Zod-validated,getCurrentSession, insert,logActivity,revalidatePath("/garden").updateContainer(input: { id, name?, type?, locationNotes?, coverImageUrl? })— patch, household membership check, logActivity.deleteContainer(input: { id })— household membership check; plants with thiscontainer_idhave it set to null (handled by schemaON DELETE SET NULL); logActivity; revalidatePath.
Add to queries.ts:
listContainers()— returns all containers for the current household with a computedplantCount.getContainer(id)— returns the container + all its plants (with last-watered date derived from care logs — use a lateral join or subquery).
UI (src/modules/garden/components/)
container-list.tsx (server component)
- Grid of cards. Each card: cover photo (placeholder icon if none), name, type badge, plant count.
- "New container" button → sheet/dialog.
container-detail.tsx (server component)
- Header: cover photo, name, type, location notes, edit/delete buttons.
- Plant grid (placeholder for now — task 72 fills in the plant cards).
- "+ Add plant to this container" button — links to
/garden/plants/new?containerId=<id>.
container-form.tsx (client component)
- Fields: name (required), type (select — 7 options), location notes (textarea), cover photo (file input →
POST /api/uploads→ stores returned URL). - Used for both create and edit via an optional
existingprop.
Pages (src/app/garden/)
page.tsx—/gardenroot page: two tabs — "Plants" (placeholder for task 72) and "Containers". Containers tab renders<ContainerList />.containers/[id]/page.tsx— renders<ContainerDetail />.containers/new/page.tsx— renders<ContainerForm />in create mode; redirects to/gardenon success.
Nav
Add /garden to the bottom nav and sidebar. Use the sprout Lucide icon (or nearest available). Register in the garden manifest:
nav: { href: "/garden", label: "Garden", icon: "sprout" }
Activity log
renderActivity for garden.container entity type in the manifest:
create→Created container "${name}"update→Updated container "${name}"delete→Deleted container
Share links
Containers are shareable (read-only). Add loadForShare and renderSharedView to the entity registration so a share link shows the container's name, type, location notes, cover photo, and plant names. Add loadContainerForShare(id) in src/modules/garden/server/share-queries.ts.
Out of scope
- Plant cards inside the container detail (task 72 fills those in).
- Care tracking (tasks 73–74).
Acceptance criteria
- Create / edit / delete containers works end-to-end.
- Cover photo uploads to MinIO and renders on the card and detail page.
- Deleting a container nullifies
container_idon its plants rather than deleting the plants. /gardenpage renders with a Containers tab.garden.containerentity is registered with share support in the manifest.- Activity log entries appear for create / update / delete.