package storage import ( "encoding/json" "strings" "time" "gardomatic.kleiax.de/internal/platform/validate" ) // PlantModelInterface persists plant instances within a garden boundary. type PlantModelInterface interface { Insert(plant Plant) (Plant, error) Get(gardenID, id int) (Plant, error) GetAllForGarden(gardenID int) ([]Plant, error) Update(gardenID int, plant Plant) (Plant, error) Delete(gardenID, id int) error } // Plant represents a named plant instance managed by a garden. type Plant struct { ID int `json:"id"` GardenID int `json:"garden_id"` SpeciesID *int `json:"species_id,omitempty"` Name string `json:"name"` Notes string `json:"notes"` ImageData string `json:"image_data,omitempty"` ImageID *int `json:"image_id,omitempty"` AcquiredAt *time.Time `json:"acquired_at,omitempty"` Status string `json:"status"` RemovedAt *time.Time `json:"removed_at,omitempty"` Attributes json.RawMessage `json:"attributes"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` Version int `json:"version"` Tags []string `json:"tags,omitempty"` CreatedBy int `json:"created_by"` UpdatedBy int `json:"updated_by"` PlantedBy int `json:"planted_by"` PlantedByName string `json:"planted_by_name,omitempty"` } // ValidatePlant applies persistence-independent plant validation rules. func ValidatePlant(v *validate.Validator, plant Plant) { v.Check(strings.TrimSpace(plant.Name) != "", "name", "must be provided") v.Check(len(plant.Name) <= 500, "name", "must not be more than 500 bytes long") v.Check(len(plant.Notes) <= 10_000, "notes", "must not be more than 10000 bytes long") v.Check(validate.PermittedValue(plant.Status, "alive", "dead", "removed", "infested", "harvested"), "status", "must be alive, dead, removed, infested or harvested") v.Check(len(plant.Attributes) == 0 || json.Valid(plant.Attributes), "attributes", "must be valid JSON") ValidateTags(v, plant.Tags) if plant.SpeciesID != nil { v.Check(*plant.SpeciesID > 0, "species_id", "must be a positive integer") } }