Files
Moonbase/prisma/schema.prisma
tonym 98cabc514d v0.9.1 — Self-update: 403 on trigger mismatch + reliability
webhook returns 200 even when a trigger rule isn't satisfied, which would let
the app's res.ok check falsely report success if the secret drifted. Set
trigger-rule-mismatch-http-response-code: 403 so unauthorized triggers are
clearly rejected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 22:44:36 -05:00

222 lines
7.4 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Moon Base — property management for the Moon homestead.
// Tracks the food forest (plants, care logs, harvests), home maintenance,
// and equipment. v0.1 ships the garden module; home + equipment come next.
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ---------------------------------------------------------------------------
// Auth
// ---------------------------------------------------------------------------
enum UserRole {
ADMIN // full access — add/edit/delete anything
MEMBER // can log observations and care, but not delete records
}
model User {
id String @id @default(cuid())
email String @unique
username String? @unique
name String
passwordHash String
role UserRole @default(MEMBER)
active Boolean @default(true)
mustChangePassword Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ---------------------------------------------------------------------------
// Audit log — append-only, every mutation lands here
// ---------------------------------------------------------------------------
model AuditEvent {
id String @id @default(cuid())
action String // e.g. "plant.create", "plant.log.add"
entityId String?
actorId String?
payload Json?
createdAt DateTime @default(now())
@@index([entityId])
@@index([createdAt])
}
// ---------------------------------------------------------------------------
// Location — shared hierarchical place tree (garden areas, buildings, rooms,
// storage bins). The spine for Garden, Inventory, and Pantry. Plants/items/
// stock all hang off a Location instead of a freeform string.
// ---------------------------------------------------------------------------
enum LocationKind {
AREA // outdoor garden zone / farm field ("Back fence guild")
BUILDING // house, garage, barn, shed
ROOM // kitchen, basement, pantry room
STORAGE // shelf, bin, cabinet, drawer, freezer
OTHER
}
model Location {
id String @id @default(cuid())
name String
kind LocationKind @default(AREA)
parentId String?
parent Location? @relation("LocationTree", fields: [parentId], references: [id], onDelete: SetNull)
children Location[] @relation("LocationTree")
path String? // denormalized "House Basement Shelf B" for breadcrumbs
notes String?
qrSlug String? @unique // short slug printed on a QR label → resolves here
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
plants Plant[]
@@index([parentId])
@@index([kind])
@@index([active])
}
// ---------------------------------------------------------------------------
// Garden — food forest plant registry
// ---------------------------------------------------------------------------
enum PlantCategory {
CANOPY_TREE // apple, pear, cherry, walnut — the tall ones
UNDERSTORY_TREE // serviceberry, elderberry, pawpaw
LARGE_SHRUB // hazelnuts, currants, gooseberries
SHRUB // roses, raspberries, blueberries
HERB // comfrey, yarrow, mint, chives
GROUNDCOVER // strawberries, clover, creeping thyme
VINE // grapes, hops, kiwi
ANNUAL_VEGETABLE // tomatoes, squash, beans, peppers — seasonal veg
ANNUAL // legacy, treated as annual vegetable
ANNUAL_FLOWER // zinnias, marigolds, nasturtiums, cosmos
PERENNIAL // asparagus, rhubarb, perennial veg
PERENNIAL_FLOWER // coneflower, bee balm, black-eyed susan
BULB // garlic, onions, tulips, daffodils
MUSHROOM // shiitake log, oyster bed, etc.
OTHER
}
model PlantGroup {
id String @id @default(cuid())
name String @unique
notes String?
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
plants Plant[]
@@index([active])
}
model Plant {
id String @id @default(cuid())
commonName String
species String? // scientific name (optional)
variety String? // cultivar / variety name
category PlantCategory
zone String? // legacy freeform location — kept during the Location migration (dropped in Release B)
locationId String? // shared Location spine
location Location? @relation(fields: [locationId], references: [id], onDelete: SetNull)
plantedAt DateTime? // when it went in the ground
source String? // where it came from ("Jung's Nursery", "from seed", "division from Mom's")
notes String? // general notes about this plant
groupId String? // optional: belongs to a PlantGroup (e.g. "Irises")
group PlantGroup? @relation(fields: [groupId], references: [id])
imageUrl String? // thumbnail fetched from Wikipedia on add/edit
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
logs PlantLog[]
tasks Task[]
@@index([category])
@@index([zone])
@@index([locationId])
@@index([active])
@@index([groupId])
}
enum PlantLogType {
OBSERVATION // general note — "looking healthy", "saw first flowers"
CARE // watering, mulching, pruning, fertilizing, chop-and-drop
HARVEST // picked fruit, veg, herb, flower
TREATMENT // pest or disease treatment
TRANSPLANT // moved to a different spot
DIED // plant died or was removed
}
// ---------------------------------------------------------------------------
// Tasks / reminders
// ---------------------------------------------------------------------------
enum TaskCategory {
GARDEN
HOME
EQUIPMENT
OTHER
}
enum RecurrenceType {
ONCE // one-time task, no repeat
CUSTOM // every N days (intervalDays)
MONTHLY // same day every month (uses nextDue day-of-month)
YEARLY // same month every year (month field)
}
model Task {
id String @id @default(cuid())
title String
notes String?
category TaskCategory @default(GARDEN)
plantId String?
plant Plant? @relation(fields: [plantId], references: [id])
recurrence RecurrenceType @default(YEARLY)
intervalDays Int? // for CUSTOM: every N days
month Int? // 112: for YEARLY, which month
nextDue DateTime
lastDone DateTime?
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
completions TaskCompletion[]
@@index([nextDue])
@@index([plantId])
@@index([active])
}
model TaskCompletion {
id String @id @default(cuid())
taskId String
task Task @relation(fields: [taskId], references: [id])
doneAt DateTime @default(now())
notes String?
actorId String?
@@index([taskId])
}
model PlantLog {
id String @id @default(cuid())
plantId String
plant Plant @relation(fields: [plantId], references: [id])
type PlantLogType
date DateTime @default(now())
notes String?
quantity String? // freeform for harvests: "2 lbs", "1 gallon", "a big handful"
actorId String?
createdAt DateTime @default(now())
@@index([plantId])
@@index([date])
}