-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
hailsDotGO is a single Go binary serving server-rendered HTML pages that are hydrated by TypeScript bundles. The backend uses the chi router with MySQL for persistence; game data from external sources lives in an in-memory store that background goroutines refresh on a schedule. This page is the big-picture map for contributors: how a request flows through the stack, how templates and assets are served, and where everything lives in the repository.
| Layer | Technology |
|---|---|
| HTTP server | Go net/http with chi v5 |
| Persistence | MySQL (users, sessions, raids, trust, store, translations) |
| Templates | Go html/template, one base layout plus one file per page |
| Frontend | TypeScript compiled with esbuild into static/js/ bundles |
| CSRF | gorilla/csrf |
| Passwords | bcrypt (cost 13) |
| Game data | In-memory pogodata.Store, refreshed in the background (see Data Sources) |
The app is designed as a single instance: raid matchmaking is serialized with an in-process mutex (raidMu in internal/handlers), and caches (sprites, weather, geocoding, game data) are process-local.
| Path | Contents |
|---|---|
main.go |
Entry point: env vars, DB open, locale registration, CSRF key, graceful shutdown |
internal/auth/ |
Session create/lookup/delete, the User struct, role predicates (IsMod, IsAdmin, IsSuperAdmin, IsTester, IsTranslator, HasAPIAccess) |
internal/db/ |
MySQL connection setup |
internal/handlers/ |
All HTTP handlers: pages, JSON APIs, admin tools, raid lobbies, trust, awards, store, PayPal, weather, sprites, translations |
internal/i18n/ |
Embedded locale JSON plus runtime override loading (see Localization) |
internal/pogodata/ |
The game data store, external fetchers, disk cache, embedded fallback JSON, event page scraper |
internal/server/ |
Router wiring (server.go), security headers, the per-IP bandwidth limiter (bwlimit.go) |
templates/ |
base.html layout plus one template per page (raids.html, admin.html, ...) |
ts/ |
TypeScript sources, bundled by esbuild into static/js/
|
static/ |
CSS, compiled JS, bundled sprite assets |
schema.sql |
Complete current schema for fresh installs (reflects all migrations; run this once on a new database) |
migrate.sql |
Numbered migration sections for upgrading an existing install to the current schema |
migrate_tags.sql |
Standalone convenience script creating the tags and user_tags tables (useful for operators applying only that change) |
-
Router:
server.Newbuilds a chi router withmiddleware.Logger,middleware.Recoverer,middleware.Compress(5), and asecurityHeaderswrapper applied globally. -
CSRF group: every route except
POST /api/store/webhook(PayPal server-to-server, verified by webhook signature instead) and the/static/*file server lives inside acsrf.Protectgroup. The CSRF cookie isSecurewithSameSite=Strict; failures are logged with a diagnostic handler and answered with 403. -
Auth middleware: routes are wrapped per-route with the guard they need (table below). The session is read from the
hgo_sessioncookie and looked up in thesessionstable joined tousers; disabled accounts resolve to "not logged in". -
Handler: page handlers gather data and call
h.render; API handlers write JSON.
| Wrapper | Requirement | Failure behavior |
|---|---|---|
RequireAuth |
Logged in | Redirect to /login?next=...
|
RequireAuthAPI |
Logged in | 401 JSON |
RequireMod |
Moderator, admin, or superadmin | 403 |
RequireAdmin |
Admin or superadmin | 403 |
RequireSuperAdmin |
The SUPERADMIN_USER account only |
403 |
RequireTranslator |
Translator flag or superadmin | 403 |
RequireAPIAccess |
Superadmin, or admin with the api_access flag |
403 JSON |
See Accounts and Roles for what each role means.
Templates are parsed once at startup into a map of page name to template set (each set is base.html plus the page file). Per request, render:
- Checks the page's maintenance toggle in
site_settings; if disabled it serves the maintenance template with HTTP 503 instead (see Admin Guide). - Resolves the current user and bumps
users.last_seen_at(this drives the "online" dot in the Trainer Directory). - Detects the language: the user's saved preference, else the
langcookie, elseen. Translators previewing a locale get their own pending edits overlaid (see Translator Workspace). -
Clones the template set and injects a per-request
Ttranslation func viaFuncs. The clone is what makes per-request language injection safe under concurrency. - Builds
PageDataand executes thebasetemplate.
PageData is the root data for every page:
| Field | Meaning |
|---|---|
User |
*auth.User, nil when not logged in |
Data |
Page-specific payload (any type) |
CSRFToken |
Token rendered into the page; the frontend echoes it back in the X-CSRF-Token header |
StoreEnabled |
Whether the Store is switched on |
Maintenance |
Per-page enable flags |
Lang |
Active locale code |
Langs |
Locales shown in the public language switcher |
AssetVersion |
Cache-busting hash (below) |
computeAssetVersion (in internal/handlers/handlers.go) hashes the contents of every file under static/css and static/js with SHA-256 at startup and keeps the first 8 hex characters. Templates append it to every asset URL as ?v=<hash>, so a deploy that changes any bundle changes every asset URL.
The cache headers are intentionally split:
| Response | Cache-Control |
|---|---|
| HTML pages (including the maintenance page) | no-cache |
/static/* files |
public, max-age=31536000 |
| Public JSON data endpoints | public, max-age=300 |
Trainer sprite proxy (/api/trainer-sprite/{slug}) |
public, max-age=2592000, immutable |
HTML is revalidated every time, so the browser always sees fresh ?v= URLs; the static files behind those URLs can then be cached for a year because a content change produces a new URL.
-
Headers (set on every response):
X-Frame-Options: SAMEORIGINandContent-Security-Policy: frame-ancestors 'self'(SAMEORIGIN rather than DENY so the translator preview iframe can embed site pages),X-Content-Type-Options: nosniff,Referrer-Policy: strict-origin-when-cross-origin,Strict-Transport-Security: max-age=31536000; includeSubDomains. -
CSRF: gorilla/csrf on everything except the PayPal webhook and static files. Browser mutations must send the
X-CSRF-Tokenheader. -
Sessions: 64-character hex tokens (32 random bytes) stored server-side in the
sessionstable with a 30-day expiry; the cookie isHttpOnly,Secure,SameSite=Lax. - Passwords: bcrypt with cost 13.
-
Rate limiting: per-IP request limits on auth and public data endpoints plus a per-IP bandwidth cap on the public API (
internal/server/bwlimit.go). Details in the API Reference. - Scraped content: LeekDuck event HTML is sanitized with bluemonday before storage (see Data Sources).
-
Game data refreshers:
pogodata.Store.Start()loads embedded fallback data, overlays the disk cache, then refreshes from the network on schedules described in Data Sources. -
Raid sweeper: a goroutine started from
server.Newruns every 15 seconds to enforce raid lobby and queue timers for clients that stopped polling (see Raid Finder).
Getting Started covers running the app locally, Configuration lists the environment variables, Building and Development covers the TypeScript build, and the Frontend Guide explains the bundle structure.
Repository | Live site | hailsDotGO is a fan-made project, not affiliated with, endorsed by, or connected to Niantic or The Pokémon Company. Game data comes from community sources credited on the Data Sources page.
Start Here
Features
- Raids and Counters
- DPS Calculator
- IV Calculator
- PvP IV Ranker
- Events
- Shiny Tracking
- Trainer Directory
- Raid Finder
- Social Features
- Trust and Awards
- Bug Reports
- Player Reports
- Store
- Accounts and Roles
- Admin Guide
Self-Hosting
Development
Translations