feat: add garden module infrastructure (task 70)

- 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)
This commit is contained in:
ginnoir
2026-06-01 19:23:19 -05:00
parent 5dcd49d4c9
commit 2fd0677c5f
21 changed files with 1250 additions and 1 deletions
+82
View File
@@ -0,0 +1,82 @@
# 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 this `container_id` have it set to null (handled by schema `ON DELETE SET NULL`); logActivity; revalidatePath.
Add to `queries.ts`:
- `listContainers()` — returns all containers for the current household with a computed `plantCount`.
- `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 `existing` prop.
### Pages (`src/app/garden/`)
- `page.tsx``/garden` root 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 `/garden` on success.
### Nav
Add `/garden` to the bottom nav and sidebar. Use the `sprout` Lucide icon (or nearest available). Register in the garden manifest:
```typescript
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 7374).
## 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_id` on its plants rather than deleting the plants.
- [ ] `/garden` page renders with a Containers tab.
- [ ] `garden.container` entity is registered with share support in the manifest.
- [ ] Activity log entries appear for create / update / delete.