Discord admin overview: no fallback org for explicitly unlinked servers

PR #218 (server-26#213 item 1) unlinks a server by clearing org_id and
stamping unlinked_at, and resolve_guild_org then refuses the fallback org.
The overview showed such a server as "Unlinked · fallback org" and offered it
in Add radio channel, where start_stream would 403. Mirror the rule here
(forward-compatible: unlinked_at does not exist on main yet). Also key each
stream row by the resolved guild id, and drop panel copy that pointed at a
"Needed" figure the page doesn't show / misstated the invite permissions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Logan Cusano
2026-10-04 21:35:48 -04:00
co-authored by Claude Opus 5.5
parent d3277c13e1
commit 31ca63b2a0
3 changed files with 22 additions and 6 deletions
+8 -4
View File
@@ -202,7 +202,10 @@ async def admin_overview(_: dict = Depends(require_admin_token)):
guilds: dict[str, dict] = {}
for gid, link in links.items():
guilds[gid] = {"guild_id": gid, "guild_name": link.get("guild_name"), "linked": bool(link.get("org_id")),
"org_id": link.get("org_id"), "linked_at": link.get("linked_at")}
"org_id": link.get("org_id"), "linked_at": link.get("linked_at"),
# Explicitly unlinked servers get no fallback org (mirrors
# streams.resolve_guild_org once server-26#218 lands).
"_no_fallback": bool(link.get("unlinked_at"))}
rows = []
for s in all_streams:
gid = str(s.get("guild_id") or leases.guild_of(s.get("stream_id") or ""))
@@ -212,14 +215,15 @@ async def admin_overview(_: dict = Depends(require_admin_token)):
g["guild_name"] = s["guild_name"]
node = nodes.get(s.get("node_id")) or {}
tok = tokens.get(s.get("token_id")) or {}
rows.append({**_public_stream(s), "org_id": s.get("org_id"), "guild_name": s.get("guild_name"),
rows.append({**_public_stream(s), "guild_id": gid, "org_id": s.get("org_id"), "guild_name": s.get("guild_name"),
"created_at": s.get("created_at"), "node_name": node.get("name"),
"node_status": node.get("status"), "token_name": tok.get("name")})
for g in guilds.values():
acting = g["org_id"] if g["linked"] else fallback
no_fallback = g.pop("_no_fallback", False)
acting = g["org_id"] if g["linked"] else (None if no_fallback else fallback)
g["acting_org_id"] = acting
g["acting_org_name"] = (orgs.get(acting) or {}).get("name") if acting else None
g["live_streams"] = sum(1 for r in rows if r["guild_id"] == g["guild_id"] and r.get("desired") == "on")
g["live_streams"] = sum(1 for r in rows if r.get("guild_id") == g["guild_id"] and r.get("desired") == "on")
rows.sort(key=lambda r: (0 if r.get("desired") == "on" else 1, r.get("guild_name") or r.get("guild_id") or "",
r.get("channel_name") or r.get("channel_id") or ""))
+12
View File
@@ -115,3 +115,15 @@ def test_relinking_a_server_to_another_org_stops_its_live_channels(fs, monkeypat
# moved to a different org: its live channels are stopped
r = client.put("/discord/guilds/111/link", json={"org_id": "founding"}).json()
assert r["stopped"] == ["111:900"] and calls == [("111", "server linked to another organization")]
def test_explicitly_unlinked_server_gets_no_fallback_org(fs):
# server-26#218 unlinks by clearing org_id and stamping unlinked_at;
# resolve_guild_org then refuses the fallback, so the page must not offer it.
_seed(fs)
fs.put("discord_guilds", "444", {"guild_id": "444", "org_id": None, "unlinked_at": "2026-10-02T00:00:00+00:00"})
app.dependency_overrides[require_admin_token] = lambda: ADMIN
guilds = {g["guild_id"]: g for g in client.get("/discord/admin/overview").json()["guilds"]}
assert guilds["444"]["linked"] is False and guilds["444"]["acting_org_id"] is None
assert "_no_fallback" not in guilds["444"]
assert guilds["222"]["acting_org_id"] == "founding" # never-linked server keeps the fallback