pagination fix: OC MemberCollection has NO cursor pageInfo — use offset+totalCount pagination (verified against the live API); the cursor query 400'd, taking the sweep and org endpoints down
This commit is contained in:
1 parent
42c664c076
commit
e4da4da2c6
1 file changed
+18
-18
+18
-18
@@ -646,26 +646,25 @@ def compute_max_budget(allowance: float, credit_balance: float, team_spend: floa
|
||||
|
||||
|
||||
async def fetch_all_member_nodes() -> list[dict]:
|
||||
"""Every member node of the collective, following OC's cursor pagination.
|
||||
"""Every member node of the collective, via OC's offset pagination.
|
||||
|
||||
The members connection caps at 100 per page: an unpaginated query silently
|
||||
hides everyone past the first page — the same failure class as Cloudron's
|
||||
25-per-page user list. Raises on ANY page failure so callers can apply
|
||||
their own fail-safes; never returns a partial list (a partial member list
|
||||
would make the sweep deactivate everyone missing from it).
|
||||
The members connection has NO cursor/pageInfo pagination (older-style API)
|
||||
and caps at 100 per page: an unpaginated query silently hides everyone
|
||||
past the first page — the same failure class as Cloudron's 25-per-page
|
||||
user list. Raises on ANY page failure so callers can apply their own
|
||||
fail-safes; never returns a partial list (a partial member list would make
|
||||
the sweep deactivate everyone missing from it).
|
||||
"""
|
||||
if not OC_PERSONAL_TOKEN:
|
||||
raise RuntimeError("OC_PERSONAL_TOKEN not set; cannot fetch members")
|
||||
nodes: list[dict] = []
|
||||
after: str | None = None
|
||||
offset = 0
|
||||
async with httpx.AsyncClient() as client:
|
||||
for _ in range(20): # hard cap: 20 pages × 100 = 1000 members
|
||||
after_q = f', after: "{after}"' if after else ""
|
||||
for _ in range(20): # hard cap: 20 pages × 100 = 2000 members
|
||||
query = (
|
||||
'{ collective(slug: "%s") { members(limit: 100%s) { nodes '
|
||||
'{ role tier { name } account { name slug type ... on Individual { email } } } '
|
||||
'pageInfo { hasNextPage endCursor } } } }'
|
||||
% (OC_COLLECTIVE_SLUG, after_q)
|
||||
'{ collective(slug: "%s") { members(limit: 100, offset: %d) { '
|
||||
"totalCount nodes { role tier { name } account { name slug type ... on Individual { email } } } } } }"
|
||||
% (OC_COLLECTIVE_SLUG, offset)
|
||||
)
|
||||
r = await client.post(
|
||||
OC_GRAPHQL_URL,
|
||||
@@ -674,12 +673,13 @@ async def fetch_all_member_nodes() -> list[dict]:
|
||||
)
|
||||
if r.status_code != 200:
|
||||
raise RuntimeError(f"OC member fetch failed: HTTP {r.status_code}")
|
||||
data = ((r.json().get("data") or {}).get("collective") or {}).get("members") or {}
|
||||
nodes.extend(data.get("nodes") or [])
|
||||
info = data.get("pageInfo") or {}
|
||||
if not info.get("hasNextPage") or not info.get("endCursor"):
|
||||
members = ((r.json().get("data") or {}).get("collective") or {}).get("members") or {}
|
||||
total = members.get("totalCount") or 0
|
||||
page = members.get("nodes") or []
|
||||
nodes.extend(page)
|
||||
offset += len(page)
|
||||
if not page or offset >= total:
|
||||
return nodes
|
||||
after = info["endCursor"]
|
||||
raise RuntimeError("OC member pagination exceeded 20 pages")
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user