# 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).