Picker: share PR #218's guild_name field and its no-fallback-after-unlink rule
A server an org explicitly unlinked (unlinked_at) must not fall back to the default org in the picker either, or the founding org could stream into it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
62e6d69bfa
commit
a2c840f186
@@ -83,7 +83,8 @@ async def put_guild_directory(guild_id: str, body: GuildDirectory, _: dict = Dep
|
|||||||
if body.present:
|
if body.present:
|
||||||
data["voice_channels"] = [c.model_dump() for c in body.voice_channels]
|
data["voice_channels"] = [c.model_dump() for c in body.voice_channels]
|
||||||
if body.name:
|
if body.name:
|
||||||
data["name"] = body.name
|
# Same field the /drb link flow writes (feat/213-guild-link, PR #218).
|
||||||
|
data["guild_name"] = body.name
|
||||||
await fstore.doc_set(streams.GUILDS, guild_id, data)
|
await fstore.doc_set(streams.GUILDS, guild_id, data)
|
||||||
return {"ok": True, "guild_id": guild_id, "voice_channels": len(body.voice_channels)}
|
return {"ok": True, "guild_id": guild_id, "voice_channels": len(body.voice_channels)}
|
||||||
|
|
||||||
@@ -109,23 +110,30 @@ async def _node_for_caller(node_id: str, decoded: dict) -> tuple[dict, bool]:
|
|||||||
return node, can
|
return node, can
|
||||||
|
|
||||||
|
|
||||||
|
def _guild_name(g: dict) -> Optional[str]:
|
||||||
|
return g.get("guild_name") or g.get("name")
|
||||||
|
|
||||||
|
|
||||||
def _require_control(can: bool) -> None:
|
def _require_control(can: bool) -> None:
|
||||||
if not can:
|
if not can:
|
||||||
raise HTTPException(403, "Only the organization owner, an admin, or this node's operator can do that.")
|
raise HTTPException(403, "Only the organization owner, an admin, or this node's operator can do that.")
|
||||||
|
|
||||||
|
|
||||||
async def _org_guilds(org_id: str) -> list[dict]:
|
async def _org_guilds(org_id: str) -> list[dict]:
|
||||||
"""``discord_guilds`` docs this org may stream into, bot still present."""
|
"""``discord_guilds`` docs this org may stream into, bot still present.
|
||||||
|
Mirrors streams.resolve_guild_org: explicit link, else the unlinked-server
|
||||||
|
fallback org — except a server an org explicitly unlinked (``unlinked_at``,
|
||||||
|
PR #218), which gets no fallback."""
|
||||||
fallback = settings.discord_unlinked_guild_org_id or None
|
fallback = settings.discord_unlinked_guild_org_id or None
|
||||||
out = []
|
out = []
|
||||||
for g in await fstore.collection_list(streams.GUILDS):
|
for g in await fstore.collection_list(streams.GUILDS):
|
||||||
gid = str(g.get("guild_id") or "")
|
gid = str(g.get("guild_id") or "")
|
||||||
if not gid or g.get("bot_present") is False:
|
if not gid or g.get("bot_present") is False:
|
||||||
continue
|
continue
|
||||||
owner = g.get("org_id") or fallback
|
owner = g.get("org_id") or (None if g.get("unlinked_at") else fallback)
|
||||||
if owner and owner == org_id:
|
if owner and owner == org_id:
|
||||||
out.append(g)
|
out.append(g)
|
||||||
out.sort(key=lambda g: ((g.get("name") or "").lower(), g["guild_id"]))
|
out.sort(key=lambda g: ((_guild_name(g) or "").lower(), g["guild_id"]))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
@@ -183,7 +191,7 @@ async def join_options(node_id: str, decoded: dict = Depends(require_firebase_to
|
|||||||
})
|
})
|
||||||
out_guilds.append({
|
out_guilds.append({
|
||||||
"guild_id": gid,
|
"guild_id": gid,
|
||||||
"name": g.get("name"),
|
"name": _guild_name(g),
|
||||||
"linked": bool(g.get("org_id")),
|
"linked": bool(g.get("org_id")),
|
||||||
"synced": g.get("voice_channels") is not None,
|
"synced": g.get("voice_channels") is not None,
|
||||||
"synced_at": g.get("directory_at"),
|
"synced_at": g.get("directory_at"),
|
||||||
@@ -253,7 +261,7 @@ async def start_node_stream(node_id: str, body: NodeStreamStart, decoded: dict =
|
|||||||
doc = await streams.start_stream(
|
doc = await streams.start_stream(
|
||||||
org_id=org_id, guild_id=guild_id, channel_id=channel_id, system_id=system_id,
|
org_id=org_id, guild_id=guild_id, channel_id=channel_id, system_id=system_id,
|
||||||
node_id=node_id, mode=body.mode,
|
node_id=node_id, mode=body.mode,
|
||||||
channel_name=channel.get("name"), guild_name=guild.get("name"),
|
channel_name=channel.get("name"), guild_name=_guild_name(guild),
|
||||||
created_by=f"web:{decoded.get('uid') or 'unknown'}",
|
created_by=f"web:{decoded.get('uid') or 'unknown'}",
|
||||||
max_streams=settings.discord_guild_max_streams,
|
max_streams=settings.discord_guild_max_streams,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ def test_directory_push_keeps_org_link(fs):
|
|||||||
run(web.put_guild_directory("100", body, {"service": True}))
|
run(web.put_guild_directory("100", body, {"service": True}))
|
||||||
g = fs.get("discord_guilds", "100")
|
g = fs.get("discord_guilds", "100")
|
||||||
assert g["org_id"] == ORG
|
assert g["org_id"] == ORG
|
||||||
assert g["name"] == "Fire Co. 7 (renamed)"
|
assert g["guild_name"] == "Fire Co. 7 (renamed)"
|
||||||
assert [c["channel_id"] for c in g["voice_channels"]] == ["1003"]
|
assert [c["channel_id"] for c in g["voice_channels"]] == ["1003"]
|
||||||
|
|
||||||
|
|
||||||
@@ -112,6 +112,10 @@ def test_options_unlinked_fallback_follows_bot_rule(fs, monkeypatch):
|
|||||||
got = {g["guild_id"]: g for g in run(web.join_options("n1", OWNER))["guilds"]}
|
got = {g["guild_id"]: g for g in run(web.join_options("n1", OWNER))["guilds"]}
|
||||||
assert got["300"]["linked"] is False
|
assert got["300"]["linked"] is False
|
||||||
assert "200" not in got
|
assert "200" not in got
|
||||||
|
# An org that explicitly unlinked its server (PR #218) never falls back.
|
||||||
|
fs.put("discord_guilds", "300", {"guild_id": "300", "org_id": None, "unlinked_at": "2026-10-04T00:00:00+00:00",
|
||||||
|
"guild_name": "Unlinked", "voice_channels": []})
|
||||||
|
assert "300" not in [g["guild_id"] for g in run(web.join_options("n1", OWNER))["guilds"]]
|
||||||
|
|
||||||
|
|
||||||
def test_options_other_org_node_is_404(fs):
|
def test_options_other_org_node_is_404(fs):
|
||||||
|
|||||||
@@ -134,8 +134,8 @@ function DiscordPicker({
|
|||||||
<legend className="text-xs text-ink-muted mb-2">Discord server</legend>
|
<legend className="text-xs text-ink-muted mb-2">Discord server</legend>
|
||||||
{usable.length === 0 ? (
|
{usable.length === 0 ? (
|
||||||
<p className="text-xs text-ink-2 bg-raised border border-line rounded-lg px-3 py-2">
|
<p className="text-xs text-ink-2 bg-raised border border-line rounded-lg px-3 py-2">
|
||||||
No Discord server is linked to your organization yet. Add the DRB bot to your server; a platform
|
No Discord server is linked to your organization yet. Add the DRB bot to your Discord server and
|
||||||
admin then links it to your organization.
|
link it to your organization; it shows up here once the bot reports its channels.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
|
|||||||
Reference in New Issue
Block a user