Skip to content

Bulletdev/prostaff-gateway

Repository files navigation

β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—     β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•—    β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—   β–ˆβ–ˆβ•—
β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ•β•β–ˆβ–ˆβ•”β•β•β•    β–ˆβ–ˆβ•”β•β•β•β•β• β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•‘    β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β•
β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘       β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ•‘ β–ˆβ•— β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•
β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘       β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•”β•β•β•  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘  β•šβ–ˆβ–ˆβ•”β•
β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•   β–ˆβ–ˆβ•‘       β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘
β•šβ•β•  β•šβ•β•β•šβ•β• β•šβ•β•β•β•β•β•    β•šβ•β•        β•šβ•β•β•β•β•β• β•šβ•β•  β•šβ•β•   β•šβ•β•   β•šβ•β•β•β•β•β•β• β•šβ•β•β•β•šβ•β•β• β•šβ•β•  β•šβ•β•   β•šβ•β•
                  Riot API Gateway β€” ProStaff Ecosystem

Go Version Redis Docker License: AGPL v3


╔══════════════════════════════════════════════════════════════════════════════╗
β•‘  PROSTAFF RIOT GATEWAY β€” Go 1.23                                             β•‘
╠══════════════════════════════════════════════════════════════════════════════╣
β•‘  Centralized Riot Games API gateway for the ProStaff ecosystem.              β•‘
β•‘  Token bucket rate limiting Β· Two-tier cache Β· Circuit breaker               β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

β–Ά Features (click to expand)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  [β– ] Global App Rate Limiting β€” Single token bucket for the API key         β”‚
β”‚  [β– ] Two-tier Cache           β€” L1 LRU in-process + L2 Redis                β”‚
β”‚  [β– ] Negative Cache           β€” 404s cached in L1 (short TTL per resource)  β”‚
β”‚  [β– ] Circuit Breaker          β€” Per (region, endpoint): match β‰  summoner    β”‚
β”‚  [β– ] Retry with Backoff       β€” 5xx retried 3Γ— (0/100ms/500ms) before open  β”‚ 
β”‚  [β– ] Internal JWT Auth        β€” aud-validated; user tokens rejected         β”‚
β”‚  [β– ] Regional Routing         β€” Auto-resolves Match-V5 routing region       β”‚
β”‚  [β– ] Graceful Degradation     β€” Redis down? L1 cache keeps serving          β”‚
β”‚  [β– ] Request ID Propagation   β€” X-Request-ID for cross-service log correl.  β”‚
β”‚  [β– ] Build Info in /health    β€” version, commit, built_at via ldflags       β”‚
β”‚  [β– ] Structured JSON Logging  β€” slog JSON, compatible with log aggregators  β”‚
β”‚  [β– ] Graceful Shutdown        β€” 5s drain on SIGTERM                         β”‚
β”‚  [β– ] Docker Ready             β€” Multi-stage build, image < 20MB             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Table of Contents

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  01 Β· Quick Start                                    β”‚
β”‚  02 Β· Technology Stack                               β”‚
β”‚  03 Β· Architecture                                   β”‚
β”‚  04 Β· API Endpoints                                  β”‚
β”‚  05 Β· Configuration                                  β”‚
β”‚  06 Β· Cache & Rate Limiting                          β”‚
β”‚  07 Β· Development                                    β”‚
β”‚  08 Β· Integration with ProStaff                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

01 Β· Quick Start

# Copy env and fill in values
cp .env.example .env

# Start gateway + Redis
docker compose up -d

# Check health
curl http://localhost:4444/health

Response:

{
  "status": "ok",
  "redis": "ok",
  "circuit_breakers": {}
}

02 Β· Technology Stack

╔══════════════════════╦════════════════════════════════════════════════════╗
β•‘  LAYER               β•‘  TECHNOLOGY                                        β•‘
╠══════════════════════╬════════════════════════════════════════════════════╣
β•‘  Language            β•‘  Go 1.23                                           β•‘
β•‘  HTTP Router         β•‘  Gorilla Mux v1.8                                  β•‘
β•‘  Authentication      β•‘  JWT HS256 (golang-jwt/jwt v5)                     β•‘
β•‘  Rate Limiting       β•‘  golang.org/x/time/rate (token bucket)             β•‘
β•‘  Cache L1            β•‘  hashicorp/golang-lru v2 (LRU + TTL + GC)          β•‘
β•‘  Cache L2            β•‘  Redis 7 (go-redis/v9)                             β•‘
β•‘  Circuit Breaker     β•‘  Custom 3-state state machine                      β•‘
β•‘  Config              β•‘  godotenv + os.Getenv                              β•‘
β•‘  Logging             β•‘  log/slog (JSON handler, stdlib Go 1.21+)          β•‘
β•‘  Container           β•‘  Docker multi-stage (Alpine, image < 20MB)         β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•©β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

03 Β· Architecture

prostaff-front (vinext) ───────────────────────────┐
prostaff-mobile(vue.js\quasar+capacitor) ───────────
ArenaBR (NextJS)  ──────────────────────────────────
Scrims  (NextJS)  ──────────────────────────────────
                                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  
                                  β–Ό
prostaff-api (Rails) ──────────────────────────────┐
ProStaff-Scraper (Python) ──────────────────────────
                                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                        β–Ό
                          [prostaff-riot-gateway :4444]
                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                         β”‚  JWT InternalAuth    β”‚  aud-validated service identity
                         β”‚  AppLimiter (global) β”‚  single token bucket per API key
                         β”‚  RegionBreakers      β”‚  circuit breaker per (region,endpoint)
                         β”‚  MemoryCache (L1)    β”‚  LRU, max 10k entries, negative cache
                         β”‚  RedisCache  (L2)    β”‚  shared across instances
                         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                     β”‚
                                     β–Ό
                                Riot Games API
                           (br1, na1, euw1, kr, ...)

Request Pipeline

1. Validate JWT              β†’ 401 if missing, invalid, or wrong aud
2. Validate region           β†’ 400 if not in allowed list
3. Check L1 negative cache   β†’ 404 if resource was confirmed absent recently
4. Check L1 cache            β†’ return in < 2ms if hit
5. Check L2 Redis            β†’ populate L1, return if hit
6. Check circuit breaker     β†’ 503 if (region, endpoint) circuit open
7. Acquire rate limiter      β†’ blocks until token available (global)
8. Call Riot API             β†’ 5s timeout, retry 5xx up to 3Γ— with backoff
9. On success                β†’ populate L1 + L2, return 200
10. On 404                   β†’ cache negative in L1, return 404
11. On persistent failure    β†’ trip circuit breaker, return 502

Regional Routing (Match-V5)

Riot Match-V5 uses regional routing instead of server routing. The gateway resolves automatically:

br1, na1, la1, la2  β†’  americas
euw1, eun1, tr1, ru β†’  europe
kr, jp1             β†’  asia
oc1                 β†’  sea

04 Β· API Endpoints

Base URL: http://riot-gateway:4444 Auth header: Authorization: Bearer <internal-jwt> (all /riot/* endpoints)

# Public
GET  /health

# Summoner / Account
GET  /riot/summoner/{region}/by-puuid/{puuid}
GET  /riot/summoner/{region}/by-riot-id/{gameName}/{tagLine}  ← preferred
GET  /riot/summoner/{region}/by-name/{name}                   ← 410 Gone (Riot deprecated 2024)
GET  /riot/account/{region}/{riotId}/{tagline}
GET  /riot/account/{region}/by-puuid/{puuid}

# Ranked
GET  /riot/league/{region}/by-summoner/{summonerId}
GET  /riot/league/{region}/by-puuid/{puuid}

# Matches (auto-resolves routing region)
GET  /riot/matches/{region}/{puuid}/ids?count=20&queue=420&start=0
GET  /riot/match/{region}/{matchId}

# Champion Mastery
GET  /riot/mastery/{region}/{puuid}/top?count=10

Health Response

{
  "status": "ok",
  "version": "v1.0.0",
  "commit": "abc1234",
  "built_at": "2026-04-20T01:00:00Z",
  "redis": "ok",
  "circuit_breakers": {
    "br1:summoner": "closed",
    "americas:match": "open"
  }
}

05 Β· Configuration

All configuration via environment variables (.env):

# Gateway
PORT=4444
# Must be different from prostaff-api user JWT secret β€” generate with: openssl rand -hex 32
# Tokens must include aud: "prostaff-riot-gateway"
INTERNAL_JWT_SECRET=<dedicated-gateway-secret>

# Riot API
RIOT_API_KEY=RGAPI-...
RIOT_API_TIMEOUT=5s

# Rate Limiting (Riot dev key: 20/s, 100/2min β€” single global bucket)
RIOT_RATE_LIMIT_PER_SECOND=20
RIOT_RATE_LIMIT_BURST=20
RIOT_RATE_LIMIT_PER_2MIN=100

# Cache L1 (in-process LRU)
CACHE_L1_MAX_SIZE=10000          # max entries before LRU eviction

# Cache L2 (Redis)
REDIS_URL=redis://redis:6379/1   # db 1, separate from prostaff-api db 0
CACHE_ENABLED=true

# Circuit Breaker
CIRCUIT_BREAKER_THRESHOLD=5      # consecutive failures to open circuit
CIRCUIT_BREAKER_TIMEOUT=60       # failure counting window (seconds)
CIRCUIT_BREAKER_COOLDOWN=30      # seconds before half-open probe

# Logging
LOG_LEVEL=info                   # debug | info | warn | error

06 Β· Cache TTLs

╔═══════════════════════╦══════════╦══════════╦══════════════╗
β•‘  Resource             β•‘  L1 TTL  β•‘  L2 TTL  β•‘  404 (L1)    β•‘
╠═══════════════════════╬══════════╬══════════╬══════════════╣
β•‘  summoner by riot-id  β•‘  10 min  β•‘  10 min  β•‘  30s         β•‘
β•‘  summoner by PUUID    β•‘  10 min  β•‘  10 min  β•‘  2 min       β•‘
β•‘  account (riot ID)    β•‘   1 h    β•‘   1 h    β•‘  2 min       β•‘
β•‘  league entries       β•‘   5 min  β•‘   5 min  β•‘  30s         β•‘
β•‘  match IDs list       β•‘   5 min  β•‘   5 min  β•‘  30s         β•‘
β•‘  match detail         β•‘   1 h    β•‘   24 h   β•‘  5 min       β•‘
β•‘  champion mastery     β•‘  30 min  β•‘   1 h    β•‘  30s         β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•©β•β•β•β•β•β•β•β•β•β•β•©β•β•β•β•β•β•β•β•β•β•β•©β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

Match detail has a 24h L2 TTL because match data is immutable once the game ends. 404s are cached only in L1 β€” they're short-lived and should not persist across instances.


07 Β· Development

# Build (Go not required locally, uses Docker)
docker run --rm -v $(pwd):/app -w /app golang:1.23-alpine go build ./cmd/server/

# Lint (staticcheck)
docker run --rm -v $(pwd):/app -w /app golang:1.23-alpine sh -c \
  "go install honnef.co/go/tools/cmd/staticcheck@latest 2>/dev/null && staticcheck ./..."

# go vet
docker run --rm -v $(pwd):/app -w /app golang:1.23-alpine go vet ./...

# Run tests
docker run --rm -v $(pwd):/app -w /app golang:1.23-alpine go test ./...

# Start with docker compose
docker compose up -d

# View logs
docker compose logs -f gateway

# Check health
curl http://localhost:4444/health

Project Structure

prostaff-riot-gateway/
β”œβ”€β”€ cmd/server/main.go          β€” entry point, router wiring, graceful shutdown
β”œβ”€β”€ internal/
β”‚   β”œβ”€β”€ auth/
β”‚   β”‚   β”œβ”€β”€ jwt.go              β€” ServiceClaims, ValidateServiceToken
β”‚   β”‚   └── middleware.go       β€” InternalAuth JWT middleware
β”‚   β”œβ”€β”€ cache/
β”‚   β”‚   β”œβ”€β”€ memory.go           β€” L1: hashicorp/golang-lru + TTL + negative cache
β”‚   β”‚   β”œβ”€β”€ redis.go            β€” L2: go-redis/v9, graceful fallback
β”‚   β”‚   └── ttl.go              β€” TTL + negative TTL constants per resource type
β”‚   β”œβ”€β”€ circuit/
β”‚   β”‚   └── breaker.go          β€” 3-state machine, RegionBreakers per (region,endpoint)
β”‚   β”œβ”€β”€ config/
β”‚   β”‚   └── config.go           β€” typed config, godotenv + os.Getenv
β”‚   β”œβ”€β”€ handlers/
β”‚   β”‚   β”œβ”€β”€ base.go             β€” shared fetch pipeline (neg cacheβ†’L1β†’L2β†’riot)
β”‚   β”‚   β”œβ”€β”€ health.go           β€” GET /health with version + circuit breaker states
β”‚   β”‚   β”œβ”€β”€ summoner.go         β€” summoner (by-riot-id, by-puuid) + account endpoints
β”‚   β”‚   β”œβ”€β”€ league.go           β€” ranked/league endpoints
β”‚   β”‚   β”œβ”€β”€ matches.go          β€” Match-V5 IDs + detail
β”‚   β”‚   └── mastery.go          β€” champion mastery
β”‚   β”œβ”€β”€ middleware/
β”‚   β”‚   └── requestid.go        β€” X-Request-ID propagation (UUID v4 fallback)
β”‚   β”œβ”€β”€ ratelimit/
β”‚   β”‚   └── limiter.go          β€” global AppLimiter (1s + 2min), region validation
β”‚   β”œβ”€β”€ riot/
β”‚   β”‚   └── client.go           β€” HTTP client, retry backoff, circuit integration
β”‚   └── webutils/
β”‚       └── json_helpers.go     β€” WriteJSON, RawJSON, ErrorJSON
β”œβ”€β”€ Dockerfile                  β€” multi-stage, Alpine final image
β”œβ”€β”€ docker-compose.yml          β€” gateway + Redis
β”œβ”€β”€ .env.example
└── go.mod

08 Β· Integration with ProStaff

Adding the gateway to prostaff-api docker-compose

# docker-compose.yml (prostaff-api)
riot-gateway:
  image: prostaff-riot-gateway:latest
  build:
    context: ../prostaff-riot-gateway
    dockerfile: Dockerfile
  ports:
    - "4444:4444"
  environment:
    - INTERNAL_JWT_SECRET=${RIOT_GATEWAY_JWT_SECRET}   # dedicated secret, not the user JWT
    - RIOT_API_KEY=${RIOT_API_KEY}
    - REDIS_URL=redis://redis:6379/1
    - PORT=4444
  depends_on:
    redis:
      condition: service_healthy
  networks:
    - prostaff_network
  restart: unless-stopped

Migrating riot_api_service.rb

# Before
BASE_URL = "https://#{region}.api.riotgames.com"
headers = { "X-Riot-Token" => ENV["RIOT_API_KEY"] }

# After
GATEWAY_URL = ENV.fetch("RIOT_GATEWAY_URL", "http://riot-gateway:4444")
headers = { "Authorization" => "Bearer #{internal_jwt}" }

def internal_jwt
  payload = {
    iss: "prostaff-api",
    aud: "prostaff-riot-gateway",
    sub: "service-account",
    service: "prostaff-api",
    exp: 1.hour.from_now.to_i
  }
  JWT.encode(payload, ENV.fetch("RIOT_GATEWAY_JWT_SECRET"), "HS256")
end

Migrating ProStaff-Scraper (Python)

GATEWAY_URL = os.getenv("RIOT_GATEWAY_URL", "http://riot-gateway:4444")
headers = {"Authorization": f"Bearer {generate_internal_jwt()}"}

response = requests.get(
    f"{GATEWAY_URL}/riot/match/americas/{match_id}",
    headers=headers,
    timeout=10
)

# generate_internal_jwt must include aud: "prostaff-riot-gateway"

Last Updated: 2026-04-20 Go Version: 1.23 Cache Strategy: L1 LRU (hashicorp/golang-lru) + L2 Redis + negative cache Rate Limit: Global token bucket per API key (20 req/s / 100 req/2min, configurable)

About

Riot API Gateway - ProStaff Ecosystem

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors