Scaffold FastAPI + MySQL + Keycloak service with devcontainer
Sets up the project skeleton: - FastAPI app factory with lifespan, request-id middleware, and RFC 9457 problem+json error handlers - Async SQLAlchemy 2.0 over MySQL (asyncmy), with a constraint naming convention in place before the first migration and async Alembic - Keycloak as a pure resource server: OIDC discovery, cached JWKS with rotation-aware refresh, and require_roles dependencies - Devcontainer running MySQL 8.4 and Keycloak 26.7 as compose siblings, with the realm (clients, roles, test users) imported on first boot - Test suite covering the endpoints plus the token validator itself, exercised against a locally generated RSA keypair - uv packaging, ruff, mypy --strict, pre-commit, Gitea CI, prod Dockerfile Two Keycloak-in-containers traps are handled explicitly and documented in the README: the issuer/internal-URL split (the browser sees localhost:8080, the API sees keycloak:8080) and the audience mapper that stops Keycloak issuing tokens with aud=account. The devices resource is a placeholder proving the routing -> auth -> ORM -> migration path end to end; replace it with the real domain. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
commit
0526d34e42
52 files changed
+4433
No files matched your search
@@ -0,0 +1,60 @@
|
||||
.DEFAULT_GOAL := help
|
||||
.PHONY: help install dev migrate revision downgrade test lint fmt typecheck check shell db up down reset kc-export
|
||||
|
||||
help: ## Show this help
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
|
||||
| awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-12s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
install: ## Sync the virtualenv from uv.lock
|
||||
uv sync --frozen
|
||||
|
||||
dev: ## Run the API with autoreload on :8000
|
||||
uv run uvicorn v2x_server.main:app --host 0.0.0.0 --port 8000 --reload
|
||||
|
||||
migrate: ## Apply migrations up to head
|
||||
uv run alembic upgrade head
|
||||
|
||||
revision: ## Autogenerate a migration: make revision m="add widgets"
|
||||
@test -n "$(m)" || (echo 'Usage: make revision m="description"' && exit 1)
|
||||
uv run alembic revision --autogenerate -m "$(m)"
|
||||
|
||||
downgrade: ## Roll back one migration
|
||||
uv run alembic downgrade -1
|
||||
|
||||
test: ## Run the test suite
|
||||
uv run pytest
|
||||
|
||||
lint: ## Lint (no changes written)
|
||||
uv run ruff check .
|
||||
uv run ruff format --check .
|
||||
|
||||
fmt: ## Format and autofix
|
||||
uv run ruff check --fix .
|
||||
uv run ruff format .
|
||||
|
||||
typecheck: ## Static type check
|
||||
uv run mypy
|
||||
|
||||
check: lint typecheck test ## Everything CI runs
|
||||
|
||||
shell: ## Python REPL with the app importable
|
||||
uv run python
|
||||
|
||||
db: ## Open a MySQL shell on the dev database
|
||||
mysql -h mysql -u v2x -pv2xpassword v2x
|
||||
|
||||
up: ## Start the backing services (outside the devcontainer)
|
||||
docker compose up -d
|
||||
|
||||
down: ## Stop the backing services
|
||||
docker compose down
|
||||
|
||||
reset: ## Destroy all data and start clean
|
||||
docker compose down -v
|
||||
docker compose up -d
|
||||
|
||||
kc-export: ## Re-export the realm after editing it in the Keycloak console
|
||||
docker compose exec keycloak /opt/keycloak/bin/kc.sh export \
|
||||
--realm v2x --file /tmp/realm-v2x.json --users realm_file
|
||||
docker compose cp keycloak:/tmp/realm-v2x.json ./docker/keycloak/realm-v2x.json
|
||||
@echo "Exported. Review the diff before committing -- exports include volatile fields."
|
||||
Reference in new issue
Block a user