Pantry now runs on the v1 API like Inventory/Animals: service layer in src/lib/services/pantry.ts, pure logic + tests in src/lib/pantry/core.ts, thin routes at /api/v1/pantry/* riding the items:* scopes. Pulls write ItemLog type USED with a structured amount (queryable consumption). Bulk-use runs its reads inside the transaction, accumulates repeated pulls, and reports skipped items instead of silently dropping them — the Pull dialog now surfaces those by name. Old /api/pantry routes deleted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
7.0 KiB
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.
- Inventory — durable Items that nest (
parentItemId: a toolbox is an Item that holds Items, with a cycle guard), with Labels (M:N viaLabelOnItem),ItemAttachment(PHOTO/MANUAL/RECEIPT/WARRANTY), andItemLog. Items carry value/purchase/warranty (incl.lifetimeWarranty,insured),serial/modelNumber/brand, a sold lifecycle, and freeformcustomFields(JSON, e.g. VIN). Maintenance = aTasklinked viaTask.itemId. HomeBox CSV import at/api/v1/items/import(pure parsersrc/lib/inventory/homebox-import.ts).kindis DURABLE|CONSUMABLE. Shares the Location spine. - Pantry (Kitchen → Pantry & Freezer) — CONSUMABLE Items with categories,
price paid, tags (shared
Labeltable), and expiry. Pulls go through/api/v1/pantry/[id]/useor/bulk-useand writeItemLogtypeUSEDwith a structuredamount(consumption is queryable — don't regress to NOTE strings). Servicesrc/lib/services/pantry.ts; pure logic + tests insrc/lib/pantry/core.ts. Rides theitems:*scopes (pantry items ARE Items). - Animals —
Animal+AnimalLog(pets/livestock); placed on the Location tree, andTasks can link to an animal (Task.animalId). - Locations —
Locationtree (self-refparentId) shared by plants, items, animals. - Reminders —
Task+TaskCompletion; recurrence insrc/lib/tasks/schedule.ts. - REST API —
/api/v1/*authed byApiToken(per-scope; logic insrc/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/plantsetc. - 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.tsaccepts EITHER a NextAuth cookie session (full scope["*"]) OR a Bearer PAT (limited to itsscopes; write implies read). Scope constants insrc/lib/api/scopes.ts(client-safe). - Decimals for money/quantity (
@db.Decimal) — convert toNumber()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 deployprovisions Bonna + Tony.npm run db:seedfor 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:
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.envnext to the compose file, interpolated in — never committed. - Docker runtime mirrors FMB:
node:20-bookworm-slim+ openssl, runs as root, fullnode_modules,next start. Migrations run on container start via the entrypoint (waits for Postgres first). Image gotcha: don't letnpx prismaresolve at runtime — with no local prisma on PATH it pulls Prisma 7, which rejectsurlin the datasource.
Conventions (same as FMB)
- Every commit bumps APP_VERSION in
src/lib/version.ts+ a plain-English entry atopsrc/lib/changelog.ts(the footer shows it + an "update available" badge fromsrc/lib/update-check.ts). Minor for features/migrations, patch for fixes. Fetch first: parallel sessions push to master — checkgit show origin/master:src/lib/version.tsbefore 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, roleADMIN. getSession()/requireAuth()/isAdmin()insrc/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_SECRETis asserted at runtime in production (skipped during build).
Color palette
Forest / earth theme: --canopy (sidebar), --leaf (accent), --soil (text),
--dew (bg), --ember (destructive).