You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A Go rewrite of the Django espn_service + nhl_service. It is a drop-in
replacement for the ESPN service deployed at https://espn-0a381a153f.sheka.xyz
and consumed by the sheka backend (backend/src/domains/prediction/ingestor.ts),
which is tightly coupled to the DRF JSON shape. The Go service reproduces that
shape byte-for-byte for the read endpoints while replacing Celery-beat + worker
with an in-process cron scheduler.
ESPN read API is served under /api/v1/*.
NHL read API is served under /api/v1/nhl/* (deviation from Django, which
served NHL at /api/v1/ — but it has no consumer and ESPN owns /api/v1/teams).
Ingest refresh hooks are POST /api/v1/ingest/*.
Both service groups are gated by SERVICES_ESPN_ENABLED / SERVICES_NHL_ENABLED.
Tech stack
Category
Technology
Language
Go 1.26
HTTP router
chi v5
Database
PostgreSQL via sqlx (read/write split)
Cache
Redis 7
DI
google/wire (compile-time)
Logging
zerolog
Tracing
OpenTelemetry (OTLP gRPC)
Scheduler
robfig/cron v3
Migrations
golang-migrate
API docs
swaggo/swag → Scalar (OpenAPI 3.1)
Request path spans: handler → service → client/repo (OTel scopes handler,
service, espn_client / nhl_client, repository). The HTTP app reports as
service name ${APP_NAME} (default espn); the worker overrides it to
espn_worker in-process so traces are distinguishable.
Running
Local (Go toolchain)
cp .env.example .env # set APP_API_KEY etc.
make migrate.up # apply migrations
make dev # HTTP API with hot reload (air)# or: make run # HTTP API without reload
make worker # ingestion scheduler
Docker Compose (full stack)
cp .env.example .env # set APP_API_KEY
docker compose up -d postgres redis migrate app worker
# optional traces UI:
docker compose up -d jaeger # then set EXTERNAL_OTEL_ENDPOINT=jaeger:4317
The migrate service runs /migrate up once and exits; app and workerdepends_on it completing, so the schema is always in place before they boot.
Ingest — POST /api/v1/ingest/* (gated by SERVICES_ESPN_ENABLED)
Method
Path
POST
/api/v1/ingest/scoreboard
POST
/api/v1/ingest/teams
POST
/api/v1/ingest/news
POST
/api/v1/ingest/injuries
POST
/api/v1/ingest/transactions
Scheduler jobs
The worker process registers cron jobs on boot (ported 1:1 from the Django
CELERY_BEAT_SCHEDULE). Jobs are not run immediately on start; cron fires
them on the first interval boundary. Each job is panic- and error-isolated: a
failure in one league or a panic in one tick never aborts the rest.
ESPN jobs (when SERVICES_ESPN_ENABLED=true)
Job
Cadence
What it does
scoreboards
5 min
Re-ingest every configured league for today + yesterday (UTC).
unstick
10 min
Re-ingest scoreboards for games stuck past kickoff.
news
30 min
Refresh news per league (limit 50).
injuries
4 h
Refresh injuries per league.
transactions
6 h
Refresh transactions per league.
teams
7 d
Refresh team rosters per league.
NHL jobs (when SERVICES_NHL_ENABLED=true)
Job
Cadence
What it does
nhl_teams
7 d
Sync the (near-static) team list.
nhl_rosters
24 h
Sync rosters, fanning out over active teams.
nhl_standings
1 h
Sync standings after game nights.
If neither service is enabled, the worker idles cleanly (stays up, no DB/ESPN
contact) until it receives a signal.
Env-gating behavior summary
SERVICES_ESPN_ENABLED=false → /api/v1/* ESPN routes and /api/v1/ingest/*
are not mounted (404); ESPN scheduler jobs are not registered.
SERVICES_NHL_ENABLED=false → /api/v1/nhl/* returns 404; NHL jobs not registered.
Missing/invalid X-API-Key on any mounted /api/v1 route → 403.