feat(pokestop): consume Golbat /api/pokestop/available with endpoint-authoritative fallback (draft)#1227
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pure, dependency-free mapper that reproduces Pokestop.js's SQL-derived
{ available, conditions } filter-key shape from the structured tuples
returned by Golbat's GET /api/pokestop/available, so a future endpoint
consumer can replace the SQL block key-for-key.
…tardust Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wires Pokestop.getAvailable() to Golbat's GET /api/pokestop/available
when a source has `mem` set (an endpoint source), using the Task 2
mapper to build the same {available, conditions} shape the SQL path
produces. Falls back to the existing SQL block unchanged on a
non-2xx response, a network/timeout error, or any thrown error;
MAD/DB sources (mem: '') skip the branch entirely and always run SQL.
Extracts the config-gated `fallbackRocketPokemonFiltering` a${id}-${form}
backfill (previously inline in the SQL rocketPokemon case) into a shared
applyRocketPokemonFallback() helper so both the endpoint and SQL paths
stay byte-identical. Adds a Pokestop.evalQuery static mirroring
Pokemon.evalQuery, since Pokestop extends Model directly and had no
endpoint-fetch helper of its own.
… SQL fallback path)
Endpoint sources (mem truthy) have no bound knex, so on
/api/pokestop/available failure the previous fallthrough to the SQL
block threw on this.query(), was swallowed by Promise.allSettled, and
silently contributed zero filter keys. Endpoint sources are now
endpoint-authoritative: on failure they return a clean empty
{ available: [], conditions: {} } instead of falling through, matching
Pokemon.getAvailable. The SQL block is reached only for mem:'' (DB/MAD)
sources.
|
Status of this branch: does not operate in its current form as only the get available branch is ready; examining whether we should implement other api calls for pokestops here, or whether to allow a hybrid option |
A source with both a Golbat endpoint and DB creds now registers the endpoint AND builds a knex connection: migrated queries (Pokestop getAvailable) use the endpoint, un-migrated ones (getAll/getOne/search/ submissions) fall back to this.query() on the bound DB — so a Golbat pokestop endpoint no longer breaks map markers/popups/search. - DbManager: keep the knex connection for endpoint schemas that also carry DB creds; overlay mem/secret/httpAuth onto the schemaChecked context so isMad/has* flags survive. - Pokestop.getAvailable: on endpoint failure fall through to the SQL block (a dual source runs it on the bound knex; a pure-endpoint source throws and is dropped by allSettled) instead of returning empty. - types: ApiEndpoint.httpAuth typed; DbConnection gains optional endpoint/secret/httpAuth for the dual shape. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
output: |
How to configure ReactMap to use thisThe pokestop available/filter list is served by Golbat's Golbat side
ReactMap sideMost instances already run Golbat for pokémon: a scanner DB source serving {
"host": "127.0.0.1", "port": 3306,
"username": "…", "password": "…", "database": "golbat_db",
"endpoint": "http://{golbat_address}:{golbat_port}", // ← add (same URL as your pokemon golbat source)
"secret": "<golbat api secret>", // ← add
"useFor": ["gym", "pokestop", "spawnpoint", "weather", "station", "…"]
}That single change makes the source dual:
Your existing pure-endpoint pokémon source is untouched. Restart ReactMap after the config change. (If you prefer tighter scoping, instead split off a Verify (optional golden check)Filter options should populate and markers/popups/search should still work. To confirm the endpoint-derived filter list matches the SQL one on the same forts: cd ~/ReactMap
curl -sS -H "X-Golbat-Secret: <secret>" http://<golbat>:<port>/api/pokestop/available > /tmp/avail.json
# available.json only flushes on graceful shutdown — restart ReactMap first so it's a fresh SQL snapshot
node -e '
const fs=require("fs");
const {mapAvailablePokestops}=require("./server/src/models/pokestopAvailableMapper");
const sql=new Set(JSON.parse(fs.readFileSync("server/.cache/available.json","utf8")).pokestops);
const ep =new Set(mapAvailablePokestops(require("/tmp/avail.json"),{invasions:{}}).available);
const noA=s=>new Set([...s].filter(k=>k[0]!=="a")); // a-keys need real event config; skip
const S=noA(sql), E=noA(ep);
console.log("SQL",S.size," endpoint",E.size);
console.log("only in SQL :",[...S].filter(k=>!E.has(k)).sort().join(" ")||"(none)");
console.log("only in endpt :",[...E].filter(k=>!S.has(k)).sort().join(" ")||"(none)");
'On live production data this matched byte-for-byte (245/245, no diffs). Any remaining diffs after a fresh restart are just time-sensitive Requires Golbat UnownHash/Golbat#383 (the |
…the Golbat endpoint The DbManager fan-out logs "[DB] Querying available for Pokestop" before any source picks endpoint-vs-SQL, so it can't confirm the endpoint path ran. Add an [POKESTOPS] info line on the endpoint success path with the endpoint URL and per-category counts, so operators can positively confirm the pokestop available list came from Golbat (and by its absence, that it fell back to SQL). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
This is working in my production environment |
|
This is now ready for review @Mygod - I'm building a second PR which does a DNF lookup of the gyms / pokestops / stations but this first PR solves a real problem with the 15-minute entire database scan so I think worth landing first if you are comfortable with this approach |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b07316de5b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
The SQL getAvailable gates quests by map.misc.questLayerMode (shouldIncludeBaseQuests/shouldIncludeAltQuests), but the endpoint mapper processed both with_ar:true (AR) and with_ar:false (non-AR) tuples unconditionally — so with the default without_ar it advertised AR-layer quest filters that match no displayed stops. The mem branch now computes the layer selection via resolveQuestLayerSelection and passes includeBaseQuests/ includeAltQuests into the mapper, which skips the excluded layer. Addresses the Codex review on pokestopAvailableMapper.js. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Adds a ReactMap consumer for Golbat's new
GET /api/pokestop/available(Golbat draft PR UnownHash/Golbat#383). A scanner source that has a Golbatendpointnow fetches the pokestop filter/available list from Golbat instead of running ~30DISTINCT/GROUP BYSQL queries every 15 minutes — mirroring howPokemon.getAvailablealready consumes/api/pokemon/available. Everything else about pokestops (map markers, popups, search, submissions) is unchanged.A source can now be dual (
endpoint+ DB creds): the migrated query (getAvailable) uses the endpoint, while un-migrated queries fall back tothis.query()on the bound DB — so a Golbat pokestop endpoint is safe to enable without touching those features.How it works
server/src/models/pokestopAvailableMapper.js(new) — a pure mapper reproducing the SQL path's filter-key formulas exactly (quest rewardsq/d/p/m/c/x/u+ pokémon, invasionsi/b, confirmed rocketa, luresl, showcasesf/h, plus per-rewardconditions). Itsprocess()helper is byte-identical to the SQL path's.Pokestop.getAvailablewiring — anif (mem)endpoint branch that fetches, validates the response shape, and maps it. On failure (503 /fort_in_memoryoff, network error) it falls through to the SQL block: a dual source runs the SQL fallback on its bound knex; a pure-endpoint source has no knex, so it's dropped by the caller'sPromise.allSettled.DbManager) — a schema carrying bothendpointand DB creds registers the endpoint and builds a knex connection; the endpoint context (mem/secret/httpAuth) is overlaid onto the schema-checked DB context. Migrated methods readmem; un-migrated ones use the bound DB. Pure-endpoint and pure-DB schemas behave exactly as before.applyRocketPokemonFallback(shared helper) — the config-derived rocket-pokémona-keys (fromstate.event.invasions[*].encounters, gated byfallbackRocketPokemonFiltering, default on) are emitted from one helper used by both the SQL and endpoint paths, so those keys are identical by construction.AvailablePokestops(+ members);ApiEndpoint.httpAuthtyped;DbConnectiongains optionalendpoint/secret/httpAuthfor the dual shape.Configuring & testing
Requires the Golbat side up:
[general] fort_in_memory = trueand the APIsecretmatching ReactMap's. Sanity-check Golbat directly:Most instances already run Golbat for pokémon: a scanner DB source serving
["gym","pokestop","spawnpoint",...]plus a separate pure-endpoint golbat source for["pokemon","device"]. To route the pokestop available list through Golbat while keeping pokestop markers/popups/search on the DB, just add the two endpoint fields to your existing scanner DB source (the one whoseuseForincludespokestop):That's the whole change — the source becomes dual. Only pokestop consumes the endpoint (
getAvailable); gym/station/etc. don't checkmem, so they keep using the DB; and pokestop markers/popups/search/submissions fall back tothis.query()on the same DB. Your pure-endpoint pokémon source is untouched. (If you'd rather scope it tighter, split auseFor: ["pokestop"]-only copy of the DB source with the endpoint added and removepokestopfrom the original — but it isn't necessary.)Behavior:
/api/pokestop/available(offloads the ~30-query, 15-min SQL)getAvailablefalls back to SQL on the DBVerify: filter options populate and markers/popups/search still work; cross-check the filter list against your pre-change list (the
form_id 0/ type-20 golden check, on live data).Confirming the endpoint is live (logs): on each ~15-min refresh, ReactMap logs a genuine source-aware line when the pokestop available list comes from Golbat:
Note the pre-existing
[DB] Querying available for Pokestopline is emitted by theDbManagerfan-out before any source picks endpoint-vs-SQL, so it does not indicate which path ran — the[POKESTOPS] loaded … from Golbat endpointline does. If the endpoint is down, you instead get a[POKESTOPS] [POKESTOP] /api/pokestop/available unavailable/error …warn and it falls back to SQL. (A pure-DB source stays silent, as before.)Verification
Per maintainer preference, no test suite was added (this repo has none). Correctness was established by a line-by-line golden comparison of the mapper against the SQL key-building (all reward-type formulas, the
-formrule, theu-set,i/b, showcases, lures,with_armerge confirmed to match), throwaway sanity scripts, an end-to-end output-shape check (mapper output is structurally identical to the SQL return and consumed identically byDbManager.getAvailable+filters/builder/pokestop.js), andeslint/prettierclean (adding the dual-source types reduced the pre-existing tsc error count by one).Residual golden check (before enabling in prod)
Diff
mapAvailablePokestops(liveGolbatResponse)vs the SQLgetAvailableon the same data:form_id === 0→ bare<id>; type-20 real data (m<id>vsu20);a-key slot 2/3 whenfallbackRocketPokemonFilteringis off. If any diverge, the cleaner fix is Golbat-side (convey null form / a type-20 sentinel) — coordinate via #383.Follow-ups (deferred, non-blocking)
evalQueryutil —Pokestop.evalQueryis a ~47-line copy ofPokemon.evalQuery(task-scoping artifact).getOne(simple, endpoint exists) /getAll(complex, needs incidents added to Golbat's scan) to shave DB load — not needed for correctness thanks to the dual-source DB fallback.fetchJsonlogs the request payload (incl. base64 auth header) on every 503 — pre-existing, worth quieting.Depends on
Golbat UnownHash/Golbat#383 (the
/api/pokestop/availableendpoint). Draft until that lands and the golden check is run.🤖 Generated with Claude Code