src/lib/garden/knowledge.ts holds hand-curated zone-4b (~May 15 frost) knowledge for 28 crops/fruits/flowers: monthly care timing, companion friends/foes, rotation families. Pure matchers knowledgeFor/tipsForMonth use whole-word stem matching (tested — 'Pear' must not hit 'Pea'). The calendar gains a "Good to do this month" list matched to owned plants, deduped against existing task titles, with one-click Remind me → YEARLY task via /api/v1/tasks. Plant type pages gain a "Growing in zone 4b" card (timeline, companions, rotation). Tips cite UW Extension via site search URLs — no fabricated article links. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
145 lines
8.1 KiB
Markdown
145 lines
8.1 KiB
Markdown
# Moon Base — Project Notes
|
||
|
||
Property management for the Moon homestead (Eau Claire, WI). Next.js 14 + Prisma +
|
||
Postgres. Same conventions as FMB (`/Users/bonnamoon/Projects/fullmoonbakehouse`).
|
||
Repo on Gitea: **`bonna61/Moonbase`**. Live at NAS:8053. See `README.md` for the
|
||
user-facing overview.
|
||
|
||
## Modules (all live)
|
||
|
||
- **Garden** — Plant registry + care/harvest logs, plant **groups**, reusable
|
||
**PlantType** records, Wikipedia photos. Plants sit on the shared location tree.
|
||
Harvest logs carry structured `amount`+`unit` (legacy freeform `quantity` kept
|
||
for old rows — don't regress); a harvest can simultaneously create a pantry
|
||
item (`addPlantLog`'s `pantry` input → GARDEN-source consumable). PlantTypes
|
||
can link a UW–Madison Extension article (`uwExtensionUrl/Title`), searched via
|
||
`/api/v1/plant-types/uw-search` (proxy to hort.extension.wisc.edu's WP REST
|
||
API; helper `src/lib/garden/uw-extension.ts`).
|
||
- **Inventory** — durable **Item**s that nest (`parentItemId`: a toolbox is an
|
||
Item that holds Items, with a cycle guard), with **Label**s (M:N via
|
||
`LabelOnItem`), `ItemAttachment` (PHOTO/MANUAL/RECEIPT/WARRANTY), and `ItemLog`.
|
||
Items carry value/purchase/warranty (incl. `lifetimeWarranty`, `insured`),
|
||
`serial`/`modelNumber`/`brand`, a **sold** lifecycle, and freeform `customFields`
|
||
(JSON, e.g. VIN). Maintenance = a `Task` linked via `Task.itemId`. **HomeBox CSV
|
||
import** at `/api/v1/items/import` (pure parser `src/lib/inventory/homebox-import.ts`).
|
||
`kind` is DURABLE|CONSUMABLE. Shares the Location spine.
|
||
- **Pantry** (Kitchen → Pantry & Freezer) — CONSUMABLE Items with categories,
|
||
price paid, tags (shared `Label` table), and expiry. Pulls go through
|
||
`/api/v1/pantry/[id]/use` or `/bulk-use` and write `ItemLog` type `USED` with a
|
||
structured `amount` (consumption is queryable — don't regress to NOTE strings).
|
||
Service `src/lib/services/pantry.ts`; pure logic + tests in `src/lib/pantry/core.ts`.
|
||
Rides the `items:*` scopes (pantry items ARE Items).
|
||
- **Animals** — `Animal` + `AnimalLog` (pets/livestock); placed on the Location
|
||
tree, and `Task`s can link to an animal (`Task.animalId`).
|
||
- **Locations** — `Location` tree (self-ref `parentId`) shared by plants, items, animals.
|
||
- **Reminders** — `Task` + `TaskCompletion`; recurrence in `src/lib/tasks/schedule.ts`
|
||
(incl. `occurrencesInRange` projection). **/calendar** renders a month view of
|
||
all tasks (garden + house) plus PlantType bloom/harvest windows
|
||
(`src/lib/garden/season.ts`) and garden-brain suggestions.
|
||
- **Garden brain** — `src/lib/garden/knowledge.ts`: hand-curated zone-4b
|
||
(~May 15 frost) care timing, companions/foes, rotation families for ~28
|
||
crops; pure matchers `knowledgeFor`/`tipsForMonth` (whole-word stem match —
|
||
beware "Pear"/"Pea"-style false hits, tested). Surfaced as calendar
|
||
"Good to do this month" (one-click YEARLY task via /api/v1/tasks) and a
|
||
"Growing in zone 4b" card on type pages. Tips cite UW Extension via site
|
||
search URLs — never fabricate article URLs.
|
||
- **REST API** — `/api/v1/*` authed by `ApiToken` (per-scope; logic in `src/lib/api/`).
|
||
- **Suggestions** — `@otm/account-panel` (Gitea npm registry) → files issues to the repo.
|
||
- **Backup & Restore** — `/api/admin/backup` + `/api/admin/restore` (admin only).
|
||
- **Self-update** — admin "Pull & rebuild" (see Deploy).
|
||
|
||
## Data model (the one rule)
|
||
|
||
**Two nesting worlds, crossed once.** *Places* (`Location`, self-ref `parentId`,
|
||
unlimited depth — `LocationKind` is an icon hint, never a constraint) nest in
|
||
places. *Items* (things you own) nest in items, and an Item lives in a Place **or**
|
||
inside another Item. You never put a Place inside an Item. **Plants** and
|
||
**Animals** are leaves in a Place (own logs, don't nest). HomeBox + Grocy + farmOS
|
||
collapse into: **Places · Plants/Animals (leaves) · Items (durable + consumable,
|
||
nesting)**. Logs are per-domain tables (`PlantLog`/`ItemLog`/`AnimalLog`), NOT a
|
||
polymorphic table; `Task` carries nullable `plantId`/`itemId`/`animalId`.
|
||
|
||
## Architecture conventions
|
||
|
||
- **API-first.** New modules (Inventory, Animals) are built on `/api/v1/*` and the
|
||
UI calls those same endpoints — no separate UI routes. Garden/Tasks predate this
|
||
and still use `/api/plants` etc.
|
||
- **Service layer** `src/lib/services/*` does DB work + AuditEvent, taking
|
||
`(ctx: AuthContext, input)`. Routes are thin: `authenticateRequest(req, scope)` →
|
||
service → `handleApiError`. The MCP server (unbuilt) will reuse these services.
|
||
- **Auth helper** `src/lib/api/authenticate.ts` accepts EITHER a NextAuth cookie
|
||
session (full scope `["*"]`) OR a Bearer PAT (limited to its `scopes`; write
|
||
implies read). Scope constants in `src/lib/api/scopes.ts` (client-safe).
|
||
- **Decimals** for money/quantity (`@db.Decimal`) — convert to `Number()` before
|
||
passing to client components (Prisma Decimal isn't serializable across the boundary).
|
||
|
||
## Blocked on a domain + HTTPS (deferred)
|
||
|
||
Camera scanning/photo-capture (`getUserMedia` needs a secure context), the **PWA**
|
||
(service worker + install need HTTPS), and the **MCP server** (Claude needs a public
|
||
HTTPS URL) all wait until there's a domain + reverse-proxy/tunnel. The app is
|
||
LAN-only over `http://NAS:8053` today. Pantry/consumables and other pure web work
|
||
can ship before then.
|
||
|
||
## Local dev
|
||
|
||
- DB: `postgresql://tonym@localhost:5432/moonbase_dev`; copy `.env.example` → `.env`.
|
||
- `npm install`, `npm run dev` (3000). After schema edits: `npx prisma generate`.
|
||
- `npx prisma migrate deploy` provisions Bonna + Tony. `npm run db:seed` for demo
|
||
data (`SEED_MODE=minimal` = accounts only).
|
||
- Backfills for the location/plant-type refactors: `npm run db:backfill-locations`,
|
||
`npm run db:backfill-plant-types`.
|
||
|
||
## Deploy — git checkout on the NAS (NOT rsync)
|
||
|
||
> Working without this file (e.g. a Claude on another machine)? The self-contained
|
||
> version is `docs/git-deploy-guide.md`.
|
||
|
||
The NAS appdata (`/mnt/user/appdata/moonbase`) is a real **git checkout** tracking
|
||
`origin/master` (remote URL carries the token). Deploy by:
|
||
|
||
```sh
|
||
git push origin master
|
||
ssh root@192.168.0.5 'cd /mnt/user/appdata/moonbase && git pull && docker compose up -d --build'
|
||
```
|
||
|
||
Or admins click **Settings → Updates → Pull & rebuild**, which signals the
|
||
privileged `updater` sidecar (docker socket + adnanh/webhook) to run the same
|
||
`git pull && compose up -d --build app` detached; the UI polls `/api/version` and
|
||
reloads. **Never rsync over the appdata dir** — it fights the checkout.
|
||
|
||
- **Secrets** (`NEXTAUTH_SECRET`, `GITEA_TOKEN`, `UPDATER_SECRET`) live in a
|
||
gitignored host `.env` next to the compose file, interpolated in — never committed.
|
||
- **Docker runtime** mirrors FMB: `node:20-bookworm-slim` + openssl, runs as root,
|
||
full `node_modules`, `next start`. Migrations run on container start via the
|
||
entrypoint (waits for Postgres first). Image gotcha: don't let `npx prisma`
|
||
resolve at runtime — with no local prisma on PATH it pulls Prisma 7, which
|
||
rejects `url` in the datasource.
|
||
|
||
## Conventions (same as FMB)
|
||
|
||
- **Every commit bumps APP_VERSION** in `src/lib/version.ts` + a plain-English
|
||
entry atop `src/lib/changelog.ts` (the footer shows it + an "update available"
|
||
badge from `src/lib/update-check.ts`). Minor for features/migrations, patch for fixes.
|
||
**Fetch first**: parallel sessions push to master — check
|
||
`git show origin/master:src/lib/version.ts` before picking a number
|
||
(a v0.22.0 collision has already happened once).
|
||
- **AuditEvent on every mutation** — append-only.
|
||
- **Soft-delete** via `active=false`.
|
||
- **Pure core + colocated test** — logic in `src/lib/...` with a `.test.ts`; routes thin.
|
||
|
||
## Auth
|
||
|
||
- Username OR email login. Accounts `bonna` / `tony`, role `ADMIN`.
|
||
- `getSession()` / `requireAuth()` / `isAdmin()` in `src/lib/auth.ts`.
|
||
- **Forced password change is wired** (provisioned accounts ship `moonbase!` +
|
||
`mustChangePassword=true`): the dashboard layout redirects to `/change-password`
|
||
(outside the layout group; backed by `/api/account/change-password`). Don't
|
||
remove that redirect.
|
||
- `NEXTAUTH_SECRET` is asserted at runtime in production (skipped during build).
|
||
|
||
## Color palette
|
||
|
||
Forest / earth theme: `--canopy` (sidebar), `--leaf` (accent), `--soil` (text),
|
||
`--dew` (bg), `--ember` (destructive).
|