Python-first implementation of an RPS assignment with a proper backend, persistent storage, robust sync, explicit data validation decisions, and a built-in web dashboard frontend.
- FastAPI (REST + SSE proxy endpoint + server-rendered frontend)
- SQLAlchemy + SQLite (persistent local storage)
- httpx (async upstream API client)
- Pydantic (input validation/sanitation)
app/clients/rps_client.py: upstream API integration, retries for429+5xx,Retry-Aftersupport.app/services/sync_service.py: history ingestion, deduplication (game_idunique), cursor-based backfill state.app/services/leaderboard_service.py: in-process aggregation for player stats.app/api/routes.py: thin API layer for history/leaderboards/latest/live/manual sync.app/templates/index.html+app/static/*: dashboard UI for fans/stakeholders, including a Top 5 wins mini chart.app/main.py: startup bootstrap sync + continuous background syncing loop.
- Backend + persistence + frontend: includes both a real backend and a UI layer, avoiding one-sided implementations.
- Rate-limit/error handling: automatic retry with backoff for
429/500/502/503/504. - Validation/sanitation:
- strict move enum:
ROCK | PAPER | SCISSORS - timestamp validity guard
- player query sanitation against wildcard widening (
%,_,\) - date parsing for
YYYY-MM-DD
- strict move enum:
- Separation of concerns: routes, services, client, models, and utility modules are isolated.
- Continuous sync: startup backfill + periodic fetches.
GET /api/matches/latest?take=50GET /api/matches/history?player=&date=&startDate=&endDate=&take=GET /api/leaderboard/todayGET /api/leaderboard/history?date=&startDate=&endDate=POST /api/matches/sync?pages=20GET /api/live(SSE stream proxied from upstream)
Frontend route:
GET /(dashboard)
- A single
date=YYYY-MM-DDis interpreted with Helsinki-day semantics (UTC+2 boundary), mapped to a UTC range. - Range filters (
startDate,endDate) follow the same conversion logic.
- Create virtual env and install dependencies:
cd redo-website/github/RPS-league-app-python
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt- Configure environment:
cp .env.example .env
# set RPS_TOKEN in .env
# set RPS_BASE_URL to your assignment provider endpoint- Run server:
uvicorn app.main:app --reload --port 4000- Open docs:
http://localhost:4000/docshttp://localhost:4000/(dashboard UI)
- Run smoke tests (optional but recommended before demo/interview):
./scripts/smoke_test.sh
# or custom base URL:
./scripts/smoke_test.sh http://127.0.0.1:4000- Invalid records are rejected during validation and not persisted.
- Duplicate
gameIdrecords are ignored via DB uniqueness + insert error handling. - Draws are tracked explicitly (
winner = "DRAW"). - Live endpoint is included but treated as best-effort bonus.
- Design: clear module boundaries (
clients,services,api,templates/static) and explicit data contracts. - Implementation: defensive coding for upstream/API failures and empty states in both API and UI.
- Verification:
- Static checks:
python3 -m compileall app - Repeatable smoke checks:
./scripts/smoke_test.sh - CI gate: GitHub Actions workflow at
.github/workflows/smoke.ymlruns smoke checks on push/PR. - Manual system checks:
GET /renders dashboard- filter actions update both tables and chart
/api/liveremains connected without aggressive reconnect loops/docsand key endpoints return 200
- Static checks:
- Documentation: assumptions, tradeoffs, and extension ideas are tracked in this README.
- Operations: no hardcoded secrets, configurable
.env, local persistence for deterministic reads.
- Add automated tests (unit + integration against a mocked upstream API).
- Add structured logging and metrics.
- Add DB migrations and indexes tuned for large historical datasets.
- Add auth for manual sync endpoint in shared deployments.
AI tooling was used to speed up scaffolding and documentation quality. Design decisions, validation rules, and architecture boundaries were reviewed and adjusted manually.