From 2e7019ebc8df9b3439ca8fbda37a175a41c77d3e Mon Sep 17 00:00:00 2001 From: Noah Markert Date: Thu, 30 Apr 2026 14:37:40 +0200 Subject: [PATCH] Expose tombstone status in room details (#19737) Exposes `tombstoned` and `replacement_room` in room details on admin API endpoint `GET /_synapse/admin/v1/rooms/`. Resolves #18347 --- changelog.d/19737.feature | 1 + docs/admin_api/rooms.md | 7 ++++++- synapse/rest/admin/rooms.py | 10 ++++++++++ tests/rest/admin/test_room.py | 4 ++++ 4 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 changelog.d/19737.feature diff --git a/changelog.d/19737.feature b/changelog.d/19737.feature new file mode 100644 index 0000000000..13bf2405df --- /dev/null +++ b/changelog.d/19737.feature @@ -0,0 +1 @@ +Exposes `tombstoned` and `replacement_room` in room details on admin API endpoint `GET /_synapse/admin/v1/rooms/`. Contributed by Noah Markert. diff --git a/docs/admin_api/rooms.md b/docs/admin_api/rooms.md index 11e787c236..c7544033e8 100644 --- a/docs/admin_api/rooms.md +++ b/docs/admin_api/rooms.md @@ -308,6 +308,9 @@ The following fields are possible in the JSON response body: If the room does not define a type, the value will be `null`. * `forgotten` - Whether all local users have [forgotten](https://spec.matrix.org/latest/client-server-api/#leaving-rooms) the room. +* `tombstoned` - Whether the room has been tombstoned (permanently closed). +* `replacement_room` - The room ID of the new room that users should join instead, if this room was tombstoned. Will be + `null` if the room has not been tombstoned, or if it was tombstoned without designating a successor room. The API is: @@ -337,7 +340,9 @@ A response body like the following is returned: "history_visibility": "shared", "state_events": 93534, "room_type": "m.space", - "forgotten": false + "forgotten": false, + "tombstoned": false, + "replacement_room": null } ``` diff --git a/synapse/rest/admin/rooms.py b/synapse/rest/admin/rooms.py index 61511b9360..3783211a92 100644 --- a/synapse/rest/admin/rooms.py +++ b/synapse/rest/admin/rooms.py @@ -367,6 +367,7 @@ class RoomRestServlet(RestServlet): self.store = hs.get_datastores().main self.room_shutdown_handler = hs.get_room_shutdown_handler() self.pagination_handler = hs.get_pagination_handler() + self._storage_controllers = hs.get_storage_controllers() async def on_GET( self, request: SynapseRequest, room_id: str @@ -383,6 +384,15 @@ class RoomRestServlet(RestServlet): members ) result["forgotten"] = await self.store.is_locally_forgotten_room(room_id) + tombstone_event = await self._storage_controllers.state.get_current_state_event( + room_id, + EventTypes.Tombstone, + "", + ) + result["tombstoned"] = tombstone_event is not None + result["replacement_room"] = ( + tombstone_event.content.get("replacement_room") if tombstone_event else None + ) return HTTPStatus.OK, result diff --git a/tests/rest/admin/test_room.py b/tests/rest/admin/test_room.py index b32665eb73..507cf10c5d 100644 --- a/tests/rest/admin/test_room.py +++ b/tests/rest/admin/test_room.py @@ -2311,10 +2311,14 @@ class RoomTestCase(unittest.HomeserverTestCase): self.assertIn("state_events", channel.json_body) self.assertIn("room_type", channel.json_body) self.assertIn("forgotten", channel.json_body) + self.assertIn("tombstoned", channel.json_body) + self.assertIn("replacement_room", channel.json_body) self.assertEqual(room_id_1, channel.json_body["room_id"]) self.assertIs(True, channel.json_body["federatable"]) self.assertIs(True, channel.json_body["public"]) + self.assertIs(False, channel.json_body["tombstoned"]) + self.assertIs(None, channel.json_body["replacement_room"]) def test_single_room_devices(self) -> None: """Test that `joined_local_devices` can be requested correctly"""