Skip to content

rafodelmal/jrafo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Plant Lifecycle Tracker

A comprehensive Spring Boot 4 application for tracking the entire plant lifecycle from germination to fruit/flower. Built with Java 25, PostgreSQL, Flyway, and Docker.

Architecture

src/main/java/com/jrafo/plantlifecycle/
├── PlantLifecycleApplication.java
├── config/          # Security, JWT, OpenAPI configuration
├── controller/      # REST API controllers
├── dto/             # Request/response records
├── exception/       # Global error handling (RFC 7807)
├── mapper/          # Entity ↔ DTO mappers
├── model/           # JPA entities and enums
├── repository/      # Spring Data JPA repositories
└── service/         # Business logic and metrics

Domain Model

Entity Purpose
Strain Cultivar metadata, genetics, ideal environmental ranges
Batch Groups plants from the same strain and sowing date
Plant Individual plant tracked through lifecycle stages
Stage Lifecycle phase with start/end timestamps
EnvironmentReading Timestamped sensor data (temp, humidity, CO2, etc.)
Sensor Physical sensor with calibration and location info
Location Growing room/area
Event Significant lifecycle events (watering, feeding, etc.)
User Basic auth user for ownership and access

Lifecycle Phases

SEED → GERMINATION → SEEDLING → VEGETATIVE → FLOWERING → FRUITING → HARVEST → DRYING → CURING → COMPLETED


Prerequisites

  • Java 25 (JDK)
  • Maven 4 RC5 (or use the included wrapper)
  • Docker and Docker Compose

Quick Start

1. Clone and configure

git clone https://github.com/rafodelmal/jrafo.git
cd jrafo
cp .env.example .env

2. Start with Docker Compose

# Start PostgreSQL only (for local dev)
docker compose up -d postgres

# Or start everything (app + database)
docker compose up -d --build

3. Run locally (without Docker for the app)

# Make sure Postgres is running (via docker compose or locally)
docker compose up -d postgres

# Build and run
./mvnw spring-boot:run -Dspring-boot.run.profiles=dev

The application starts on http://localhost:8080.

4. Access Swagger UI

Open http://localhost:8080/swagger-ui.html for the interactive API documentation.


Build & Test

# Compile
./mvnw compile

# Run unit tests
./mvnw test

# Run integration tests (requires Docker for Testcontainers)
./mvnw verify

# Package
./mvnw package -DskipTests

API Endpoints

Authentication

Method Endpoint Description
POST /api/auth/register Register a new user
POST /api/auth/login Login and get JWT

Strains

Method Endpoint Description
GET /api/strains List all strains
GET /api/strains/{id} Get strain by ID
POST /api/strains Create a strain
PUT /api/strains/{id} Update a strain
DELETE /api/strains/{id} Delete a strain

Plants

Method Endpoint Description
GET /api/plants List all plants
GET /api/plants?batchId={id} Filter by batch
GET /api/plants/{id} Get plant by ID
POST /api/plants Create a plant
DELETE /api/plants/{id} Delete a plant

Batches

Method Endpoint Description
GET /api/batches List all batches
GET /api/batches/{id} Get batch by ID
POST /api/batches Create a batch
DELETE /api/batches/{id} Delete a batch

Sensors

Method Endpoint Description
GET /api/sensors List all sensors
GET /api/sensors/{id} Get sensor by ID
POST /api/sensors Register a sensor
DELETE /api/sensors/{id} Delete a sensor

Environment Readings

Method Endpoint Description
POST /api/readings Ingest single reading
POST /api/readings/bulk Ingest bulk readings
GET /api/readings?plantId={id}&from=...&to=... Query by plant + date range
GET /api/readings?batchId={id}&from=...&to=... Query by batch + date range
GET /api/readings?sensorId={id}&from=...&to=.. Query by sensor + date range

Stages

Method Endpoint Description
POST /api/stages Transition to a new stage
GET /api/stages/plant/{plantId} Get all stages for a plant

Reports

Method Endpoint Description
GET /api/reports/plants/{plantId}/summary Lifecycle summary for a plant

Monitoring

Endpoint Description
/actuator/health Health check
/actuator/prometheus Prometheus metrics
/actuator/info Application info

Example Walkthrough

The seed data (Flyway V2) pre-populates sample strains, a batch, a plant, and readings. Here's how to interact with the API from scratch:

Step 1: Register and login

# Register
curl -s -X POST http://localhost:8080/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "username": "grower1",
    "password": "securepass123",
    "email": "grower1@example.com",
    "fullName": "Green Thumb"
  }'

# Login (get JWT)
TOKEN=$(curl -s -X POST http://localhost:8080/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username": "grower1", "password": "securepass123"}' | jq -r '.token')

Step 2: Create a strain

curl -s -X POST http://localhost:8080/api/strains \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "name": "San Marzano Tomato",
    "genetics": "heirloom",
    "origin": "Italy",
    "description": "Classic Italian paste tomato",
    "expectedGerminationDays": 7,
    "expectedSeedlingDays": 21,
    "expectedVegetativeDays": 35,
    "expectedFloweringDays": 14,
    "expectedFruitingDays": 60,
    "idealTempMinCelsius": 18.0,
    "idealTempMaxCelsius": 30.0,
    "idealHumidityMinPercent": 45.0,
    "idealHumidityMaxPercent": 75.0,
    "idealPhMin": 6.0,
    "idealPhMax": 6.8,
    "idealEcMinMsCm": 2.0,
    "idealEcMaxMsCm": 5.0,
    "idealCo2MinPpm": 400.0,
    "idealCo2MaxPpm": 1200.0,
    "idealLightMinPpfd": 200.0,
    "idealLightMaxPpfd": 800.0,
    "idealSoilMoistureMinPercent": 40.0,
    "idealSoilMoistureMaxPercent": 70.0
  }' | jq .

Expected response:

{
  "id": "<uuid>",
  "name": "San Marzano Tomato",
  "genetics": "heirloom",
  "origin": "Italy",
  "idealTempMinCelsius": 18.0,
  "idealTempMaxCelsius": 30.0,
  "createdAt": "2026-02-06T...",
  "updatedAt": "2026-02-06T..."
}

Step 3: Create a batch

STRAIN_ID="<strain-id-from-above>"

curl -s -X POST http://localhost:8080/api/batches \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d "{
    \"name\": \"Spring 2026 San Marzano\",
    \"strainId\": \"$STRAIN_ID\",
    \"sowDate\": \"2026-02-06\",
    \"plantCount\": 4,
    \"notes\": \"Started indoors under grow lights\"
  }" | jq .

Step 4: Create a plant

BATCH_ID="<batch-id-from-above>"

curl -s -X POST http://localhost:8080/api/plants \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d "{
    \"name\": \"SM Plant #1\",
    \"strainId\": \"$STRAIN_ID\",
    \"batchId\": \"$BATCH_ID\",
    \"sowDate\": \"2026-02-06\"
  }" | jq .

Step 5: Transition stage

PLANT_ID="<plant-id-from-above>"

curl -s -X POST http://localhost:8080/api/stages \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d "{
    \"plantId\": \"$PLANT_ID\",
    \"phase\": \"GERMINATION\",
    \"notes\": \"Radicle emerged\"
  }" | jq .

Step 6: Ingest bulk environment readings

curl -s -X POST http://localhost:8080/api/readings/bulk \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d "{
    \"readings\": [
      {
        \"recordedAt\": \"2026-02-06T08:00:00Z\",
        \"plantId\": \"$PLANT_ID\",
        \"batchId\": \"$BATCH_ID\",
        \"temperatureCelsius\": 24.5,
        \"humidityPercent\": 62.0,
        \"co2Ppm\": 800,
        \"lightPpfd\": 350,
        \"ph\": 6.3,
        \"ecMsCm\": 2.5,
        \"soilMoisturePercent\": 55.0
      },
      {
        \"recordedAt\": \"2026-02-06T14:00:00Z\",
        \"plantId\": \"$PLANT_ID\",
        \"batchId\": \"$BATCH_ID\",
        \"temperatureCelsius\": 27.0,
        \"humidityPercent\": 55.0,
        \"co2Ppm\": 900,
        \"lightPpfd\": 500,
        \"ph\": 6.5,
        \"ecMsCm\": 2.8,
        \"soilMoisturePercent\": 48.0
      },
      {
        \"recordedAt\": \"2026-02-06T20:00:00Z\",
        \"plantId\": \"$PLANT_ID\",
        \"batchId\": \"$BATCH_ID\",
        \"temperatureCelsius\": 21.0,
        \"humidityPercent\": 68.0,
        \"co2Ppm\": 750,
        \"lightPpfd\": 0,
        \"ph\": 6.2,
        \"ecMsCm\": 2.3,
        \"soilMoisturePercent\": 60.0
      }
    ]
  }" | jq .

Step 7: Fetch lifecycle summary

curl -s http://localhost:8080/api/reports/plants/$PLANT_ID/summary | jq .

Expected response:

{
  "plantId": "<uuid>",
  "plantName": "SM Plant #1",
  "strainName": "San Marzano Tomato",
  "currentPhase": "GERMINATION",
  "sowDate": "2026-02-06",
  "active": true,
  "stages": [
    {
      "phase": "GERMINATION",
      "startedAt": "2026-02-06T...",
      "notes": "Radicle emerged"
    }
  ],
  "overallMetrics": {
    "avgTemperature": 24.17,
    "avgHumidity": 61.67,
    "avgCo2": 816.67,
    "avgLight": 283.33,
    "avgPh": 6.33,
    "avgEc": 2.53,
    "avgSoilMoisture": 54.33,
    "minTemperature": 21.0,
    "maxTemperature": 27.0,
    "readingCount": 3,
    "alerts": []
  },
  "recentEvents": [
    {
      "eventType": "STAGE_TRANSITION",
      "title": "Stage transition to GERMINATION"
    }
  ],
  "generatedAt": "2026-02-06T..."
}

Using the pre-seeded data

The seed data includes a ready-to-use plant. Fetch the summary for it:

curl -s http://localhost:8080/api/reports/plants/e0000000-0000-0000-0000-000000000001/summary | jq .

Configuration

Environment Variables

Variable Default Description
POSTGRES_HOST localhost Database host
POSTGRES_PORT 5432 Database port
POSTGRES_DB plantlifecycle Database name
POSTGRES_USER plantuser Database user
POSTGRES_PASSWORD plantpass Database password
JWT_SECRET (see .env.example) JWT signing key
JWT_EXPIRATION_MS 86400000 JWT TTL (24h)
SPRING_PROFILES_ACTIVE dev Spring profile
SERVER_PORT 8080 Application port

Spring Profiles

  • default: Production-ready settings
  • dev: Debug logging, SQL output enabled

Project Structure

jrafo/
├── pom.xml                         # Maven 4 RC5 configuration
├── Dockerfile                      # Multi-stage build
├── docker-compose.yml              # Postgres + App services
├── .env.example                    # Environment template
├── .mvn/
│   ├── maven.config
│   └── wrapper/maven-wrapper.properties
├── src/main/
│   ├── java/com/jrafo/plantlifecycle/
│   │   ├── config/                 # Security, JWT, OpenAPI
│   │   ├── controller/             # REST controllers
│   │   ├── dto/                    # Request/Response records
│   │   ├── exception/              # Error handling
│   │   ├── mapper/                 # Entity ↔ DTO mappers
│   │   ├── model/                  # JPA entities + enums
│   │   ├── repository/             # Spring Data repos
│   │   └── service/                # Business logic
│   └── resources/
│       ├── application.yml
│       ├── application-dev.yml
│       └── db/migration/
│           ├── V1__create_schema.sql
│           └── V2__seed_data.sql
└── src/test/
    └── java/com/jrafo/plantlifecycle/
        ├── integration/            # Full-stack integration tests
        ├── mapper/                 # Mapper unit tests
        ├── repository/             # Repository integration tests
        └── service/                # Service unit tests

License

MIT Java Software Rules by Rafo

About

Java Software Rules by Rafo

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors