Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Add forgotten status to Room Details API #13503

Merged
merged 9 commits into from
Aug 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/13503.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add forgotten status to Room Details API.
5 changes: 4 additions & 1 deletion docs/admin_api/rooms.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,8 @@ The following fields are possible in the JSON response body:
* `state_events` - Total number of state_events of a room. Complexity of the room.
* `room_type` - The type of the room taken from the room's creation event; for example "m.space" if the room is a space.
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.
Comment on lines +305 to +306
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should note that this was added in Synapse 1.66. I'll add something to this file now while preparing the release.


The API is:

Expand Down Expand Up @@ -330,7 +332,8 @@ A response body like the following is returned:
"guest_access": null,
"history_visibility": "shared",
"state_events": 93534,
"room_type": "m.space"
"room_type": "m.space",
"forgotten": false
}
```

Expand Down
1 change: 1 addition & 0 deletions synapse/rest/admin/rooms.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ async def on_GET(

members = await self.store.get_users_in_room(room_id)
ret["joined_local_devices"] = await self.store.count_devices_by_users(members)
ret["forgotten"] = await self.store.is_locally_forgotten_room(room_id)

return HTTPStatus.OK, ret

Expand Down
24 changes: 24 additions & 0 deletions synapse/storage/databases/main/roommember.py
Original file line number Diff line number Diff line change
Expand Up @@ -1215,6 +1215,30 @@ def _get_forgotten_rooms_for_user_txn(txn: LoggingTransaction) -> Set[str]:
"get_forgotten_rooms_for_user", _get_forgotten_rooms_for_user_txn
)

async def is_locally_forgotten_room(self, room_id: str) -> bool:
"""Returns whether all local users have forgotten this room_id.

Args:
room_id: The room ID to query.

Returns:
Whether the room is forgotten.
"""

sql = """
SELECT count(*) > 0 FROM local_current_membership
INNER JOIN room_memberships USING (room_id, event_id)
WHERE
room_id = ?
AND forgotten = 0;
"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The query plan looks sensible to me


rows = await self.db_pool.execute("is_forgotten_room", None, sql, room_id)

# `count(*)` returns always an integer
# If any rows still exist it means someone has not forgotten this room yet
return not rows[0][0]

async def get_rooms_user_has_been_in(self, user_id: str) -> Set[str]:
"""Get all rooms that the user has ever been in.

Expand Down
1 change: 1 addition & 0 deletions tests/rest/admin/test_room.py
Original file line number Diff line number Diff line change
Expand Up @@ -1633,6 +1633,7 @@ def test_single_room(self) -> None:
self.assertIn("history_visibility", channel.json_body)
self.assertIn("state_events", channel.json_body)
self.assertIn("room_type", channel.json_body)
self.assertIn("forgotten", channel.json_body)
self.assertEqual(room_id_1, channel.json_body["room_id"])

def test_single_room_devices(self) -> None:
Expand Down
70 changes: 70 additions & 0 deletions tests/storage/test_roommember.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

from tests import unittest
from tests.server import TestHomeServer
from tests.test_utils import event_injection


class RoomMemberStoreTestCase(unittest.HomeserverTestCase):
Expand Down Expand Up @@ -157,6 +158,75 @@ def test__null_byte_in_display_name_properly_handled(self) -> None:
# Check that alice's display name is now None
self.assertEqual(row[0]["display_name"], None)

def test_room_is_locally_forgotten(self):
"""Test that when the last local user has forgotten a room it is known as forgotten."""
# join two local and one remote user
self.room = self.helper.create_room_as(self.u_alice, tok=self.t_alice)
self.get_success(
event_injection.inject_member_event(self.hs, self.room, self.u_bob, "join")
)
self.get_success(
event_injection.inject_member_event(
self.hs, self.room, self.u_charlie.to_string(), "join"
)
)
self.assertFalse(
self.get_success(self.store.is_locally_forgotten_room(self.room))
)

# local users leave the room and the room is not forgotten
self.get_success(
event_injection.inject_member_event(
self.hs, self.room, self.u_alice, "leave"
)
)
self.get_success(
event_injection.inject_member_event(self.hs, self.room, self.u_bob, "leave")
)
self.assertFalse(
self.get_success(self.store.is_locally_forgotten_room(self.room))
)

# first user forgets the room, room is not forgotten
self.get_success(self.store.forget(self.u_alice, self.room))
self.assertFalse(
self.get_success(self.store.is_locally_forgotten_room(self.room))
)

# second (last local) user forgets the room and the room is forgotten
self.get_success(self.store.forget(self.u_bob, self.room))
self.assertTrue(
self.get_success(self.store.is_locally_forgotten_room(self.room))
)

def test_join_locally_forgotten_room(self):
"""Tests if a user joins a forgotten room the room is not forgotten anymore."""
self.room = self.helper.create_room_as(self.u_alice, tok=self.t_alice)
self.assertFalse(
self.get_success(self.store.is_locally_forgotten_room(self.room))
)

# after leaving and forget the room, it is forgotten
self.get_success(
event_injection.inject_member_event(
self.hs, self.room, self.u_alice, "leave"
)
)
self.get_success(self.store.forget(self.u_alice, self.room))
self.assertTrue(
self.get_success(self.store.is_locally_forgotten_room(self.room))
)

# after rejoin the room is not forgotten anymore
self.get_success(
event_injection.inject_member_event(
self.hs, self.room, self.u_alice, "join"
)
)
self.assertFalse(
self.get_success(self.store.is_locally_forgotten_room(self.room))
)


class CurrentStateMembershipUpdateTestCase(unittest.HomeserverTestCase):
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
Expand Down