Petru Paler fb59ef72a8 Fix FastHTML empty value attribute omission in select options
FastHTML omits the value attribute when value="" (empty string), causing
browsers to use the option's text content as the submitted value. This
made forms send "Keep current" or "No change" text instead of empty
string, failing Pydantic enum validation.

Fixed by using "-" as a sentinel value instead of "" for "no change"
options, and updating route handlers to treat "-" as None.

Affected forms:
- Promote form (sex, repro_status)
- Update attributes form (sex, life_stage, repro_status)
- Outcome form (yield_product_code)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 09:31:11 +00:00
2026-01-05 10:50:16 +00:00
2025-12-31 20:08:20 +00:00

AnimalTrack

Event-sourced animal tracking for poultry farm management. Built with FastHTML, MonsterUI, and SQLite.

Features

  • Egg Collection - Quick capture of daily egg counts by location
  • Feed Tracking - Record purchases and distribution with cost tracking
  • Animal Registry - Track cohorts, movements, and lifecycle events
  • Historical Queries - Point-in-time resolution using interval projections
  • Event Sourcing - Full audit trail with edit/delete support

Development Setup

Prerequisites

  • Nix with flakes enabled
  • direnv (optional but recommended)

Quick Start

# Clone the repo
git clone <repo-url>
cd animaltrack

# Enter dev environment
direnv allow  # or: nix develop

# Run the server
animaltrack serve

The server starts at http://localhost:3366 (3366 = EGG in leetspeak).

CLI Commands

# Run database migrations
animaltrack migrate

# Load seed data (users, locations, species, products, feed types)
animaltrack seed

# Start the web server
animaltrack serve [--port 3366] [--host 0.0.0.0]

# Create a new migration
animaltrack create-migration "add users table"

Running Tests

pytest tests/ -v

Deployment

Docker

Build the Docker image using Nix:

nix build .#dockerImage
docker load < result

Run the container:

docker run -d \
  -p 8080:3366 \
  -v /data/animaltrack:/var/lib/animaltrack \
  -e CSRF_SECRET="your-secret-here" \
  -e TRUSTED_PROXY_IPS="10.0.0.1,10.0.0.2" \
  gitea.v.paler.net/ppetru/animaltrack:latest

Environment Variables

Variable Required Default Description
CSRF_SECRET Yes - Secret for CSRF token generation
DB_PATH No animaltrack.db SQLite database path
PORT No 3366 Server port
AUTH_HEADER_NAME No X-Oidc-Username Header containing username from reverse proxy
TRUSTED_PROXY_IPS No (empty) Comma-separated IPs that can set auth headers
CSRF_COOKIE_NAME No csrf_token CSRF cookie name
SEED_ON_START No false Run seeds on startup
LOG_LEVEL No INFO Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
METRICS_ENABLED No true Enable Prometheus metrics at /metrics
DEV_MODE No false Bypasses auth, sets default user for development
BASE_PATH No / URL base path for reverse proxy setups

Reverse Proxy Configuration

AnimalTrack expects authentication to be handled by a reverse proxy (e.g., Authelia, Authentik, oauth2-proxy). The proxy should:

  1. Authenticate users via OIDC/OAuth2
  2. Forward the username in X-Oidc-Username header (configurable via AUTH_HEADER_NAME)
  3. Be listed in TRUSTED_PROXY_IPS to be trusted

Example Caddy configuration:

animaltrack.example.com {
    forward_auth authelia:9091 {
        uri /api/verify?rd=https://auth.example.com
        copy_headers Remote-User Remote-Groups Remote-Email
        header_up X-Oidc-Username {http.auth.resp.Remote-User}
    }
    reverse_proxy animaltrack:3366
}

Data Persistence

Mount a volume to /var/lib/animaltrack for the SQLite database:

-v /path/on/host:/var/lib/animaltrack

The database file will be created at $DB_PATH (default: /var/lib/animaltrack/animaltrack.db in Docker).

Health Check

  • GET /healthz - Returns 200 if database is writable, 503 otherwise
  • GET /metrics - Prometheus metrics (when METRICS_ENABLED=true)

Architecture

  • Event Sourcing - All state changes are events. Events are immutable.
  • Projections - Materialized views updated synchronously in transactions.
  • Interval Tables - Track animal locations, tags, and attributes over time.
  • ULID IDs - Time-ordered, sortable, unique identifiers.
  • Integer Timestamps - Milliseconds since Unix epoch for all timestamps.
  • Integer Money - All prices stored as cents for precision.

License

Proprietary - All rights reserved.

Description
No description provided
Readme 1.3 MiB
Languages
Python 99.3%
Nix 0.6%
JavaScript 0.1%