- 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)
83 lines
3.5 KiB
Markdown
83 lines
3.5 KiB
Markdown
# 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 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_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.
|