feat: implement serve command with dev mode support

- 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 <noreply@anthropic.com>
This commit is contained in:
2025-12-29 21:00:00 +00:00
parent 352fa387af
commit 85b5e81e35
6 changed files with 48 additions and 3 deletions

2
bin/animaltrack Executable file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env bash
exec python -m animaltrack.cli "$@"

2
bin/serve-dev Executable file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env bash
exec env CSRF_SECRET="dev-secret" DEV_MODE=true python -m animaltrack.cli serve "$@"

View File

@@ -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"
'';

View File

@@ -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__":

View File

@@ -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]:

View File

@@ -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)