From 85b5e81e35b89ace084d651f357eb969c3731d72 Mon Sep 17 00:00:00 2001 From: Petru Paler Date: Mon, 29 Dec 2025 21:00:00 +0000 Subject: [PATCH] feat: implement serve command with dev mode support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CLI serve command now runs uvicorn with migrations - Add dev_mode setting to bypass auth with default admin user - Add bin/animaltrack wrapper for Nix environment - Add bin/serve-dev for quick local development - Update flake.nix shellHook for PYTHONPATH and bin PATH 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- bin/animaltrack | 2 ++ bin/serve-dev | 2 ++ flake.nix | 2 ++ src/animaltrack/cli.py | 30 +++++++++++++++++++++++++++--- src/animaltrack/config.py | 1 + src/animaltrack/web/middleware.py | 14 ++++++++++++++ 6 files changed, 48 insertions(+), 3 deletions(-) create mode 100755 bin/animaltrack create mode 100755 bin/serve-dev diff --git a/bin/animaltrack b/bin/animaltrack new file mode 100755 index 0000000..3fa9518 --- /dev/null +++ b/bin/animaltrack @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec python -m animaltrack.cli "$@" diff --git a/bin/serve-dev b/bin/serve-dev new file mode 100755 index 0000000..6f872a5 --- /dev/null +++ b/bin/serve-dev @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec env CSRF_SECRET="dev-secret" DEV_MODE=true python -m animaltrack.cli serve "$@" diff --git a/flake.nix b/flake.nix index f9e998f..2cb9b29 100644 --- a/flake.nix +++ b/flake.nix @@ -80,6 +80,8 @@ ]; shellHook = '' + export PYTHONPATH="$PWD/src:$PYTHONPATH" + export PATH="$PWD/bin:$PATH" echo "AnimalTrack development environment ready!" echo "Run 'animaltrack serve' to start the app" ''; diff --git a/src/animaltrack/cli.py b/src/animaltrack/cli.py index b85f3c3..4757acf 100644 --- a/src/animaltrack/cli.py +++ b/src/animaltrack/cli.py @@ -26,7 +26,7 @@ def main(): # serve command serve_parser = subparsers.add_parser("serve", help="Start the web server") - serve_parser.add_argument("--port", type=int, default=5000, help="Port to listen on") + serve_parser.add_argument("--port", type=int, default=3366, help="Port to listen on") serve_parser.add_argument("--host", type=str, default="0.0.0.0", help="Host to bind to") args = parser.parse_args() @@ -86,9 +86,33 @@ def main(): run_seeds(db) print("Seed data loaded successfully") elif args.command == "serve": + import uvicorn + + from animaltrack.config import Settings + from animaltrack.db import get_db + from animaltrack.migrations import run_migrations + from animaltrack.web.app import create_app + + settings = Settings() + + # Run migrations first + print("Running migrations...") + success = run_migrations( + db_path=settings.db_path, + migrations_dir="migrations", + verbose=False, + ) + if not success: + print("Migration failed", file=sys.stderr) + sys.exit(1) + + # Create app + db = get_db(settings.db_path) + app, _ = create_app(settings=settings, db=db) + + # Start server print(f"Starting server on {args.host}:{args.port}...") - # TODO: Implement server - print("Server not yet implemented") + uvicorn.run(app, host=args.host, port=args.port) if __name__ == "__main__": diff --git a/src/animaltrack/config.py b/src/animaltrack/config.py index 538412b..4134b19 100644 --- a/src/animaltrack/config.py +++ b/src/animaltrack/config.py @@ -37,6 +37,7 @@ class Settings(BaseSettings): seed_on_start: bool = False log_level: str = "INFO" metrics_enabled: bool = True + dev_mode: bool = False # Bypasses auth, sets default user @cached_property def trusted_proxy_ips(self) -> list[str]: diff --git a/src/animaltrack/web/middleware.py b/src/animaltrack/web/middleware.py index 0144866..49660c4 100644 --- a/src/animaltrack/web/middleware.py +++ b/src/animaltrack/web/middleware.py @@ -10,6 +10,7 @@ from starlette.responses import PlainTextResponse, Response from animaltrack.config import Settings from animaltrack.id_gen import generate_id +from animaltrack.models.reference import User, UserRole from animaltrack.repositories.users import UserRepository # Safe HTTP methods that don't require CSRF protection @@ -170,6 +171,8 @@ def auth_before(req: Request, settings: Settings, db) -> Response | None: - Auth header is present - User exists and is active in database + In dev_mode, bypasses all checks and uses a default admin user. + Args: req: The Starlette request object. settings: Application settings. @@ -178,6 +181,17 @@ def auth_before(req: Request, settings: Settings, db) -> Response | None: Returns: None to continue processing, or Response to short-circuit. """ + # Dev mode: bypass auth entirely + if settings.dev_mode: + req.scope["auth"] = User( + username="dev", + role=UserRole.ADMIN, + active=True, + created_at_utc=0, + updated_at_utc=0, + ) + return None + # Check trusted proxy if not is_trusted_proxy(req, settings): return PlainTextResponse("Forbidden: Request not from trusted proxy", status_code=403)