Skip to content

feat(pokestop): consume Golbat /api/pokestop/available with endpoint-authoritative fallback#1227

Closed
jfberry wants to merge 9 commits into
WatWowMap:developfrom
jfberry:feat/pokestop-available-consumer
Closed

feat(pokestop): consume Golbat /api/pokestop/available with endpoint-authoritative fallback#1227
jfberry wants to merge 9 commits into
WatWowMap:developfrom
jfberry:feat/pokestop-available-consumer

Conversation

@jfberry

@jfberry jfberry commented Jul 14, 2026

Copy link
Copy Markdown

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 Golbat endpoint now fetches the pokestop filter/available list from Golbat instead of running ~30 DISTINCT/GROUP BY SQL queries every 15 minutes — mirroring how Pokemon.getAvailable already 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 to this.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 rewards q/d/p/m/c/x/u + pokémon, invasions i/b, confirmed rocket a, lures l, showcases f/h, plus per-reward conditions). Its process() helper is byte-identical to the SQL path's.
  • Pokestop.getAvailable wiring — an if (mem) endpoint branch that fetches, validates the response shape, and maps it. On failure (503 / fort_in_memory off, 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's Promise.allSettled.
  • Dual endpoint+DB sources (DbManager) — a schema carrying both endpoint and 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 read mem; 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émon a-keys (from state.event.invasions[*].encounters, gated by fallbackRocketPokemonFiltering, default on) are emitted from one helper used by both the SQL and endpoint paths, so those keys are identical by construction.
  • TypesAvailablePokestops (+ members); ApiEndpoint.httpAuth typed; DbConnection gains optional endpoint/secret/httpAuth for the dual shape.

Configuring & testing

Requires the Golbat side up: [general] fort_in_memory = true and the API secret matching ReactMap's. Sanity-check Golbat directly:

curl -H "X-Golbat-Secret: <secret>" http://<golbat-host>:<port>/api/pokestop/available   # 200 + {quests,invasions,lures,showcases}

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 whose useFor includes pokestop):

// config/local.json → database.schemas[]  (your existing scanner DB source)
{
  "host": "127.0.0.1", "port": 3306,
  "username": "...", "password": "...", "database": "golbat_db",
  "endpoint": "http://{golbat_address}:{golbat_port}",   // ← add (same as your pokemon golbat source)
  "secret": "<golbat api secret>",                       // ← add
  "useFor": ["gym", "pokestop", "spawnpoint", "weather", "station", "..."]
}

That's the whole change — the source becomes dual. Only pokestop consumes the endpoint (getAvailable); gym/station/etc. don't check mem, so they keep using the DB; and pokestop markers/popups/search/submissions fall back to this.query() on the same DB. Your pure-endpoint pokémon source is untouched. (If you'd rather scope it tighter, split a useFor: ["pokestop"]-only copy of the DB source with the endpoint added and remove pokestop from the original — but it isn't necessary.)

Behavior:

  • pokestop filter options ← Golbat /api/pokestop/available (offloads the ~30-query, 15-min SQL)
  • pokestop markers / popups / search / submissions ← the bound DB (unchanged)
  • Golbat 503 → getAvailable falls back to SQL on the DB

Verify: 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:

ℹ [POKESTOPS] [POKESTOP] loaded available from Golbat endpoint http://<golbat>:<port>/api/pokestop/available — 245 filter keys (245 quests, 6 invasions, 11 lures, 3 showcases), 215 reward conditions

Note the pre-existing [DB] Querying available for Pokestop line is emitted by the DbManager fan-out before any source picks endpoint-vs-SQL, so it does not indicate which path ran — the [POKESTOPS] loaded … from Golbat endpoint line 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 -form rule, the u-set, i/b, showcases, lures, with_ar merge 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 by DbManager.getAvailable + filters/builder/pokestop.js), and eslint/prettier clean (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 SQL getAvailable on the same data: form_id === 0 → bare <id>; type-20 real data (m<id> vs u20); a-key slot 2/3 when fallbackRocketPokemonFiltering is 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)

  • Extract a shared evalQuery util — Pokestop.evalQuery is a ~47-line copy of Pokemon.evalQuery (task-scoping artifact).
  • Optionally migrate 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.
  • fetchJson logs the request payload (incl. base64 auth header) on every 503 — pre-existing, worth quieting.

Depends on

Golbat UnownHash/Golbat#383 (the /api/pokestop/available endpoint). Draft until that lands and the golden check is run.

🤖 Generated with Claude Code

jfberry and others added 6 commits July 14, 2026 17:32
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.
@jfberry

jfberry commented Jul 14, 2026

Copy link
Copy Markdown
Author

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>
@jfberry

jfberry commented Jul 15, 2026

Copy link
Copy Markdown
Author
cd ~/ReactMap
# sanity: confirm the file + a pokestops array (find it if the path differs)
jq '.pokestops | length' server/.cache/available.json    # or: find . -name available.json

# fresh endpoint snapshot (so both are close in time)
curl -sS -H "X-Golbat-Secret: whatever" http://localhost:9001/api/pokestop/available > /tmp/avail.json

# diff: SQL (available.json) vs endpoint (mapper)
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 here
  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)");
'

output:

SQL 245  endpoint 245
only in SQL   : (none)
only in endpt : (none)

@jfberry

jfberry commented Jul 15, 2026

Copy link
Copy Markdown
Author

How to configure ReactMap to use this

The pokestop available/filter list is served by Golbat's GET /api/pokestop/available; everything else about pokestops (map markers, popups, search, submission cells) still runs on the DB. So the scanner source needs both a Golbat endpoint and DB creds — a "dual" source (supported by the DbManager change in this PR).

Golbat side

  • [general] fort_in_memory = true (otherwise the endpoint returns 503)
  • API secret matching what ReactMap sends — the same one you already use for /api/pokemon/*
  • Sanity check:
    curl -H "X-Golbat-Secret: <secret>" http://<golbat-host>:<port>/api/pokestop/available
    # 200 + {"quests":[…],"invasions":[…],"lures":[…],"showcases":[…]}

ReactMap side

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, just add the two endpoint fields to your existing scanner DB source (the one whose useFor includes pokestop), in config/local.json → database.schemas[]:

{
  "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:

  • pokestop filter options ← Golbat /api/pokestop/available (offloads the ~30-query, 15-min SQL)
  • pokestop markers / popups / search / submissions ← the bound DB (unchanged)
  • gym/station/spawnpoint/etc. ignore the endpoint (their queries don't check mem) → keep using the DB
  • Golbat 503 (fort_in_memory off) → getAvailable falls back to SQL on the same DB

Your existing pure-endpoint pokémon source is untouched. Restart ReactMap after the config change.

(If you prefer tighter scoping, instead split off a useFor: ["pokestop"]-only copy of the DB source with the endpoint added and drop pokestop from the original — not necessary, since gym/station ignore mem.)

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 i/l keys between the two pulls.

Requires Golbat UnownHash/Golbat#383 (the /api/pokestop/available endpoint) deployed with fort_in_memory on.

…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>
@jfberry

jfberry commented Jul 15, 2026

Copy link
Copy Markdown
Author

This is working in my production environment

@jfberry

jfberry commented Jul 16, 2026

Copy link
Copy Markdown
Author

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

@jfberry
jfberry marked this pull request as ready for review July 16, 2026 13:52

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread server/src/models/pokestopAvailableMapper.js
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>
@jfberry jfberry changed the title feat(pokestop): consume Golbat /api/pokestop/available with endpoint-authoritative fallback (draft) feat(pokestop): consume Golbat /api/pokestop/available with endpoint-authoritative fallback Jul 17, 2026
@Mygod
Mygod requested a review from Copilot July 20, 2026 03:08

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the ReactMap backend to consume Golbat’s new GET /api/pokestop/available endpoint for Pokéstop “available filter key” generation, with a dual-source design that falls back to the existing SQL path when an endpoint is unavailable.

Changes:

  • Adds a pure mapper to translate Golbat /api/pokestop/available tuples into the existing { available, conditions } shape used by the UI.
  • Updates Pokestop.getAvailable to prefer the Golbat endpoint when mem is configured, with SQL fallback behavior.
  • Extends DbManager and server type definitions to support dual endpoint+DB schemas (including optional HTTP basic auth).

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
server/src/services/DbManager.js Allows a single schema to carry both endpoint config and DB creds, binding knex while also surfacing mem/secret/httpAuth for migrated endpoint-backed queries.
server/src/models/pokestopAvailableMapper.js New pure mapper that recreates Pokéstop filter-key/conditions derivation from Golbat’s available response tuples.
server/src/models/Pokestop.js Adds endpoint-backed getAvailable path, shared rocket fallback helper, and a model-local evalQuery mirroring Pokémon’s endpoint querying behavior.
packages/types/lib/server.d.ts Adds AvailablePokestops tuple types and optional httpAuth plumbing to support the new endpoint consumption path.
docs/superpowers/plans/2026-07-14-pokestop-available-consumer-reactmap.md Documents the implementation plan and intended behavior for the endpoint consumer + fallback.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +69 to +88
// Add all potential first slot rewards
if (invasionInfo.firstReward && invasionInfo.encounters.first) {
invasionInfo.encounters.first.forEach((poke) => {
availableSet.add(`a${poke.id}-${poke.form}`)
})
}

// Add all potential second slot rewards
if (invasionInfo.secondReward && invasionInfo.encounters.second) {
invasionInfo.encounters.second.forEach((poke) => {
availableSet.add(`a${poke.id}-${poke.form}`)
})
}

// Add all potential third slot rewards
if (invasionInfo.thirdReward && invasionInfo.encounters.third) {
invasionInfo.encounters.third.forEach((poke) => {
availableSet.add(`a${poke.id}-${poke.form}`)
})
}
Comment on lines +1424 to +1427
log.warn(
TAGS.pokestops,
'[POKESTOP] /api/pokestop/available unavailable (e.g. fort_in_memory off) — returning empty available for this endpoint source',
)
Comment on lines +1428 to +1433
} catch (e) {
log.warn(
TAGS.pokestops,
`[POKESTOP] /api/pokestop/available error — returning empty available for this endpoint source: ${e}`,
)
}
Comment on lines +67 to +72
* `reward_type` values are cross-checked against the SQL query definitions
* that feed that switch (not just its `questTypes.filter` bookkeeping,
* which references 9/12 in a way that looks swapped at a glance but is
* self-correcting because both branches always run together — see
* task-2-report.md): `candy` filters `quest_reward_type === 4`, `xlCandy`
* filters `=== 9`, `mega` filters `=== MEGA_RESOURCE_REWARD_TYPE` (`12`).
@Mygod

Mygod commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

The endpoint mapper advertises keys that the existing map-data filtering path cannot honor for populated type-20 rewards, and it loses confirmed rocket rewards outside slot 1 under a supported configuration. These cause user-visible missing or nonfunctional filters.

Full review comments:

  • [P1] Normalize populated type-20 rewards across every query path — server/src/models/pokestopAvailableMapper.js:100-104
    When Golbat returns a populated type-20 reward with pokemon_id and amount, this advertises an m{id}-{amount} filter, but Pokestop.getAll still matches m filters only against reward type 12 and parseRdmRewards leaves populated type 20 as u20. Selecting the newly advertised filter therefore returns no quest; normalize type 20 in the SQL filtering and display paths as well, or keep it as u20 until those paths support it.

  • [P2] Preserve confirmed slot-2 and slot-3 reward keys — server/src/models/pokestopAvailableMapper.js:181-187
    When fallbackRocketPokemonFiltering is disabled and a confirmed grunt can reward its second or third Pokémon, the endpoint path can emit only slot 1 because its tuple and mapper contain no slot-2/slot-3 fields. The SQL path adds a... keys for every reward-enabled slot, so confirmed encounter filters for those slots disappear from the available list; extend the endpoint contract and mapper to process all reward slots.

@Mygod

Mygod commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Superseded by #1228.

@Mygod Mygod closed this Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants