Fix Loomio sync: use portal DB (OC returns email:null); add manual sync endpoint
This commit is contained in:
1 parent
5490eeb2fc
commit
5d3ace8fe8
1 file changed
+30
-19
+30
-19
@@ -182,25 +182,20 @@ async def is_active_member(slug: str) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
async def get_active_member_emails() -> list[str]:
|
async def get_active_member_emails() -> list[str]:
|
||||||
"""Return the list of active financial contributor emails from Open Collective."""
|
"""Return the list of active member emails from the portal's own database.
|
||||||
query = (
|
|
||||||
'{ collective(slug: "%s") { members(limit: 100) { nodes { role account { email slug } } } } }'
|
We use the local `members` table (populated during OAuth, which captures the
|
||||||
% OC_COLLECTIVE_SLUG
|
member's email) rather than Open Collective's GraphQL, because OC returns
|
||||||
)
|
`email: null` for privacy — the email field is only exposed via OAuth with
|
||||||
async with httpx.AsyncClient() as client:
|
the user's consent. The local table is the authoritative source of member
|
||||||
r = await client.post(OC_GRAPHQL_URL, json={"query": query})
|
emails.
|
||||||
if r.status_code != 200:
|
"""
|
||||||
logger.warning("OC member list fetch failed: %s", r.status_code)
|
conn = get_db()
|
||||||
return []
|
rows = conn.execute(
|
||||||
nodes = r.json().get("data", {}).get("collective", {}).get("members", {}).get("nodes", [])
|
"SELECT email FROM members WHERE active = 1"
|
||||||
emails = []
|
).fetchall()
|
||||||
for n in nodes:
|
conn.close()
|
||||||
role = n.get("role", "")
|
return [r[0] for r in rows]
|
||||||
acct = n.get("account", {}) or {}
|
|
||||||
email = acct.get("email")
|
|
||||||
if role in ("BACKER", "ADMIN") and email:
|
|
||||||
emails.append(email)
|
|
||||||
return emails
|
|
||||||
|
|
||||||
|
|
||||||
async def sync_loomio_memberships() -> None:
|
async def sync_loomio_memberships() -> None:
|
||||||
@@ -739,6 +734,22 @@ async def health():
|
|||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/admin/sync-loomio/{token}")
|
||||||
|
async def admin_sync_loomio(token: str):
|
||||||
|
"""Manually trigger a Loomio membership sync.
|
||||||
|
|
||||||
|
Protected by the same secret token as the Open Collective webhook. Useful
|
||||||
|
for reconciling pre-existing members (accounts created before the sync
|
||||||
|
existed) or recovering from a missed webhook. Idempotent — safe to call
|
||||||
|
repeatedly.
|
||||||
|
"""
|
||||||
|
if not WEBHOOK_TOKEN or not secrets.compare_digest(token, WEBHOOK_TOKEN):
|
||||||
|
raise HTTPException(401, "Invalid token")
|
||||||
|
|
||||||
|
await sync_loomio_memberships()
|
||||||
|
return {"status": "synced"}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def index():
|
async def index():
|
||||||
return {"service": "Inference Cooperative Member Portal", "version": "0.1.0"}
|
return {"service": "Inference Cooperative Member Portal", "version": "0.1.0"}
|
||||||
Reference in new issue
Block a user