An async FastAPI backend for idempotent transaction processing, user summaries, and a multi-factor ranking leaderboard.
- Idempotent transactions — Client-provided
idempotency_keyguarantees exactly-once processing; duplicates return the existing record (200) instead of double-counting. - Atomic snapshot updates — Per-user materialised summary rows (
total_earned,total_spent,net_balance, etc.) are updated in the same DB transaction as the transaction insert viaSELECT ... FOR UPDATE. - Multi-factor ranking —
score = earned × 1.0 + spent × 0.5 + tx_count × 10 + bonus_count × 50 − days_since_activity × 0.1. Stored in a materialisedrankingstable; can be refreshed on demand. - Concurrency-safe — Per-user
asyncio.Lockserialises concurrent requests for the same user; idempotency key uniqueness is enforced at the DB level. - Rate limiting — Configurable per-IP sliding window middleware.
- Async throughout — FastAPI async handlers, SQLAlchemy 2.0 async sessions, asyncpg driver.
| Layer | Technology |
|---|---|
| Runtime | Python 3.11+ / FastAPI |
| Database | PostgreSQL 16 (prod) / SQLite (dev/test) |
| ORM | SQLAlchemy 2.0 (async) |
| Migrations | Alembic |
| Validation | Pydantic v2 |
| Testing | pytest + httpx.AsyncClient |
| Deploy | Docker Compose |
git clone https://github.com/SoulByte07/RankGuard.git
cd RankGuard
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txtCreate a .env file:
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/rankgward
RATE_LIMIT_MAX_REQUESTS=60
RATE_LIMIT_WINDOW_SECONDS=60Run with Docker Compose:
docker compose up --buildOr run directly (requires a running PostgreSQL):
alembic upgrade head
uvicorn app.main:app --reloadThe API is available at http://localhost:8000. Interactive docs at http://localhost:8000/docs.
Create a transaction. Returns 201 on first write, 200 on duplicate idempotency key.
{
"idempotency_key": "client-generated-unique-key",
"user_id": "00000000-0000-0000-0000-000000000001",
"type": "earn | spend | bonus",
"amount": 100.50,
"extra_data": {}
}Get a user's aggregated summary and ranking position.
Get the leaderboard, ordered by score descending (and last activity ascending as tiebreaker).
Force-refresh the materialised ranking table. Rankings are automatically computed on the first GET /ranking if empty.
Health check.
app/
├── main.py # FastAPI entrypoint, middleware, router registration
├── config.py # Pydantic settings (env-driven)
├── database.py # Async engine + session factory
├── models.py # SQLAlchemy ORM models (4 tables)
├── schemas.py # Pydantic request/response models
├── middlewares/
│ └── rate_limit.py # Per-IP sliding window rate limiter
├── routers/
│ ├── transaction.py # POST /transaction
│ ├── summary.py # GET /summary/{user_id}
│ └── ranking.py # GET /ranking, POST /ranking/compute
└── services/
├── transaction.py # Core write logic (idempotency, snapshot)
├── summary.py # Snapshot + rank lookup
└── ranking.py # Ranking formula, compute, pagination
tests/
├── conftest.py # Fixtures (SQLite in-memory, async client)
├── test_transaction.py # Create, duplicate, concurrent, validation
├── test_summary.py # Existing/nonexistent user
└── test_ranking.py # Ordering and pagination
pytest -v --asyncio-mode=autoTests use an in-memory SQLite database via aiosqlite — no external dependencies required.
- Single-process locks — Per-user
asyncio.Lockis in-process; horizontal scaling requires Redis or DB-level distributed locking. - Ranking recompute —
POST /ranking/computedoes a full table scan + delete-all. Not suitable for frequent refresh under heavy write load; a materialised view or incremental update would scale better. - In-memory rate limiter — Per-IP counters live in process memory; resets on restart and doesn't work across multiple app instances. Swap to Redis for production multi-replica deployments.
- Idempotency keys persist forever — No TTL or cleanup on the
transactionstable. Under sustained throughput, the uniqueness constraint and index size grow unbounded. - SQLite test DB — Fast and zero-config, but means PostgreSQL-specific features (e.g. partial indexes,
RETURNING,ON CONFLICT) aren't exercised in tests. - No authentication — All endpoints are unauthenticated. Add API key middleware for production use.
MIT