Discord: log node-reported stream state; remove a radio channel or a server (#213, #221) #224

Merged
logan merged 2 commits from feat/discord-stream-logging-remove into main 2026-10-04 22:55:29 -04:00
3 changed files with 35 additions and 2 deletions
Showing only changes of commit 25553fbac6 - Show all commits
+9 -2
View File
@@ -374,8 +374,15 @@ async def remove_guild(guild_id: str, by: str) -> dict:
link = await fstore.doc_get(GUILDS, guild_id) or {}
removed = []
for s in await fstore.collection_list(STREAMS, guild_id=guild_id):
if s.get("stream_id") and await delete_stream(s["stream_id"], "server_removed"):
removed.append(s["stream_id"])
try:
if s.get("stream_id") and await delete_stream(s["stream_id"], "server_removed"):
removed.append(s["stream_id"])
except StreamError as e:
# A channel restarted mid-removal: stop here, leave the server
# linked, and tell the caller which channels are already gone so
# the partial removal is still audited.
e.removed = removed
raise
now = _now_iso()
await fstore.doc_set(GUILDS, guild_id, {
"guild_id": guild_id, "org_id": None,
+5
View File
@@ -222,6 +222,11 @@ async def remove_guild(guild_id: str, leave_radio_bots: bool = False,
try:
out = await streams.remove_guild(guild_id, uid)
except streams.StreamError as e:
# Channels deleted before the race are gone for good — audit them.
await write_audit(uid, email, "discord.guild.remove", details={
"guild_id": guild_id, "acting_org_id": acting_org, "outcome": "incomplete", "error": e.code,
"streams_removed": getattr(e, "removed", []), "leave_radio_bots": leave_radio_bots,
})
return _error(e)
left: list = []
if leave_radio_bots:
@@ -278,6 +278,27 @@ async def test_remove_guild_deletes_streams_and_unlinks_without_fallback(fs, mon
assert [_lease(fs, t) for t in toks] == [None, None]
def test_remove_guild_interrupted_by_a_restart_is_still_audited(fs, monkeypatch):
import asyncio
a = asyncio.run(_start("1001"))
b = asyncio.run(_start("1002", node_id="n2"))
real_stop = streams.stop_stream
async def _stop(stream_id, reason="stopped"):
await real_stop(stream_id, reason)
if stream_id == b["stream_id"]:
await _start("1002", node_id="n2") # restarted between stop and delete
monkeypatch.setattr(streams, "stop_stream", _stop)
app.dependency_overrides[require_admin_token] = lambda: ADMIN
r = client.delete("/discord/guilds/100")
assert r.status_code == 409 and r.json()["code"] == "restarted"
(entry,) = _audit(fs, "discord.guild.remove")
assert entry["details"]["outcome"] == "incomplete"
assert entry["details"]["streams_removed"] == [a["stream_id"]]
assert fs.get("discord_guilds", "100")["org_id"] == ORG # left linked: a live channel remains
def test_remove_guild_route_hides_server_and_relink_restores_it(fs):
import asyncio
asyncio.run(_start("1001"))