Skip to content

cryptice/verdant

Repository files navigation

Verdant

A garden planning and plant lifecycle tracking system with three components: a Quarkus backend, an Android app, and a React admin UI.

Components

Backend (backend/)

Quarkus + Kotlin REST API with PostgreSQL.

  • Plant lifecycle: Sow seeds (in beds or portable trays), pot up, plant out, harvest, recover, discard — each as a tracked event
  • Species management: System-wide and user-created species with Swedish/English names, growing conditions, photos, and seed provider links
  • Garden structure: Gardens with geo-located beds and boundary polygons
  • Seed inventory: Track seed batches with collection/expiration dates, auto-decrement on sowing
  • Scheduled tasks: Recurring garden activities with deadlines and progress tracking
  • AI integration: Gemini-powered extraction of species info from seed packet photos
  • Storage: Google Cloud Storage for images
  • Auth: JWT-based authentication with Google OAuth for the app and email/password for admin

Android App (android/)

Kotlin Android app with Jetpack Compose.

  • My World: Dashboard with gardens, tray plant summary, and harvest stats
  • Plants: Species-grouped view with current locations, batch actions (pot up, plant out, harvest, recover, discard) via modal
  • Tasks: Scheduled activities with species-specific workflows
  • Sowing: Select species, choose bed or portable tray, auto-creates individual plants
  • Gardens: Map-based garden/bed creation with boundary drawing, inline editing
  • Seed inventory: Track and manage seed batches with FAB to add
  • Swedish-first: All UI strings localized, Swedish names shown as primary

Admin UI (admin/)

React + TypeScript + Tailwind CSS (Notion-inspired design).

  • Species CRUD: Create, edit, delete species with image uploads, AI extraction, provider linking
  • Users/Gardens: View and manage registered users and their gardens
  • Providers: Manage seed providers (Impecta, Florea, Wexthuset, etc.)
  • Import/Export: JSON export/import of species data including provider info
  • Bundled with backend: Built into the Quarkus JAR and served as static files at /

Development

Prerequisites

  • JDK 21
  • Node.js 22+
  • Android Studio (for the Android app)
  • Docker (for local PostgreSQL and deployment builds)

Alternatively, use the Dev Container (see below) — it bundles JDK 21, Node 22, and Claude Code CLI, and wires PostgreSQL automatically.

Dev Container (optional)

The repo ships a Dev Container (mcr.microsoft.com/devcontainers/java:21-bookworm + Node 22 + Claude Code CLI + Android SDK platform-35) that runs the backend, admin UI, and Android build alongside a PostgreSQL container.

VS Code: open the folder and choose "Reopen in Container". The workspace mounts at /workspaces/verdant and ports 8081 (backend) and 5174 (admin) are auto-forwarded.

Plain Docker:

Start the containers:

docker compose -f docker-compose.yml -f .devcontainer/docker-compose.yml up -d

Open a shell inside the dev container (workspace is already at /workspaces/verdant):

docker compose -f docker-compose.yml -f .devcontainer/docker-compose.yml exec dev bash

Then, inside the container, start the backend:

cd backend && ./gradlew quarkusDev

Rebuilding after a Dockerfile change (e.g. picking up a new bundled Claude Code CLI):

docker compose -f docker-compose.yml -f .devcontainer/docker-compose.yml rm -sf dev
docker compose -f docker-compose.yml -f .devcontainer/docker-compose.yml build --no-cache dev
docker compose -f docker-compose.yml -f .devcontainer/docker-compose.yml up -d dev
docker compose -f docker-compose.yml -f .devcontainer/docker-compose.yml exec dev bash

With the official devcontainer CLI (npm i -g @devcontainers/cli) the same flow is two commands:

devcontainer up --workspace-folder . --remove-existing-container
devcontainer exec --workspace-folder . bash

The host's ~/.claude and ~/.config/gcloud are bind-mounted into the container so Claude Code auth/memory/plugins and gcloud/cloud-sql-proxy credentials follow you in (no re-auth required). Gradle and npm caches live in named volumes (gradle-cache, node-modules-cache) so they survive container rebuilds. The backend reaches PostgreSQL at postgres:5432 inside the network; the host can reach it on localhost:5433.

The Android app compiles, tests, and lints inside the containercd android && ./gradlew :app:compileDebugKotlin (or :app:assembleDebug, :app:testDebugUnitTest, :app:lintDebug) all work against the bundled SDK at /opt/android-sdk. The container resolves the SDK via ANDROID_HOME rather than writing local.properties, so host-side Android Studio builds keep working without manual cleanup. android/settings.gradle.kts auto-deletes a stale local.properties whose sdk.dir points at a directory that doesn't exist in the current environment — so opening the project in Android Studio on the host (which regenerates the file with the host's SDK path), and then jumping into the container, just works. For CLI-only host builds, set ANDROID_HOME=$HOME/Library/Android/sdk in your shell profile.

Interactive debugging on a device or emulator still needs the host: Docker Desktop on macOS doesn't expose KVM or USB. To adb into a host-side emulator/device from inside the container, run adb tcpip 5555 on the host once, then adb connect host.docker.internal:5555 inside the container. For running the app interactively, set the Android app's .env.yaml API host to your laptop IP and the forwarded :8081 port.

Backend

cd backend
cp .env.yaml.template .env.yaml  # edit with your keys
./gradlew quarkusDev              # starts on port 8081, auto-provisions PostgreSQL

Admin UI

cd admin
npm install
npm run dev    # starts on port 5174, proxies /api to backend

Android App

cd android
cp .env.yaml.template .env.yaml  # set your laptop IP and API keys

Open in Android Studio and run on device/emulator.

Sample Data

To populate the database with 3 seasons of realistic flower production data, first log in with Google to create your account, then:

# Local development
curl -X POST http://localhost:8081/api/dev/seed

# Production
curl -X POST https://verdantplanner.com/api/dev/seed

# For a different user
curl -X POST https://verdantplanner.com/api/dev/seed?email=someone@example.com

Defaults to erik@l2c.se. Creates 16 cut flower species, 5 customers, 6 beds, ~45 plants across 2024-2026 with full harvest data, succession schedules, production targets, variety trials, bouquet recipes, and pest/disease logs.

Database

The schema is managed by Flyway with a single migration (V1__schema.sql). Flyway runs automatically on startup (quarkus.flyway.migrate-at-start=true).

cd backend
./gradlew dbBackup                # backup to db-backups/
./gradlew dbRestore               # restore latest backup

Connecting to the production database

  1. Start the Cloud SQL Auth Proxy:

    cloud-sql-proxy verdant-planner-staging:europe-north1:verdant-staging --port 5433
  2. Fetch the password from Secret Manager:

    gcloud secrets versions access latest --secret=verdant-db-password --project=verdant-planner-staging
  3. Connect with psql:

    PGPASSWORD=$(gcloud secrets versions access latest --secret=verdant-db-password --project=verdant-planner-staging) \
      psql -h localhost -p 5433 -U verdant -d verdant

Install the proxy with brew install cloud-sql-proxy if needed.

Resetting the schema

Drop and recreate the public schema to force Flyway to re-run all migrations on next startup. This destroys all data.

# Local
PGPASSWORD=verdant psql -h localhost -p 5433 -U verdant -d verdant \
  -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"

# Production (with proxy running)
PGPASSWORD=$(gcloud secrets versions access latest --secret=verdant-db-password --project=verdant-planner-staging) \
  psql -h localhost -p 5433 -U verdant -d verdant \
  -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"

Deployment

Deployed to Google Cloud Run with Cloud SQL (PostgreSQL).

# One-time setup
./deploy/setup-gcp.sh <PROJECT_ID> <REGION>

# Store secrets
gcloud secrets create verdant-gemini-key --data-file=<(echo -n 'KEY')
gcloud secrets create verdant-admin-password --data-file=<(echo -n 'PASSWORD')

# Configure Docker auth
gcloud auth configure-docker <REGION>-docker.pkg.dev

# Build and deploy (backend + admin UI in one container)
./deploy/deploy.sh <PROJECT_ID> <REGION>

Configuration

Each module has its own .env.yaml (not checked in):

  • backend/.env.yaml — Gemini API key, GCS credentials, admin password, database connection
  • android/.env.yaml — Backend API URL, Google OAuth client ID, Maps API key

Deployed at verdantplanner.com

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors