mirror of
https://mau.dev/maunium/synapse.git
synced 2024-10-01 01:36:05 -04:00
Filter added to Admin-API GET /rooms (#17276)
This commit is contained in:
parent
a412a5829d
commit
9104a9f0d0
1
changelog.d/17276.feature
Normal file
1
changelog.d/17276.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Filter for public and empty rooms added to Admin-API [List Room API](https://element-hq.github.io/synapse/latest/admin_api/rooms.html#list-room-api).
|
@ -36,6 +36,10 @@ The following query parameters are available:
|
|||||||
- the room's name,
|
- the room's name,
|
||||||
- the local part of the room's canonical alias, or
|
- the local part of the room's canonical alias, or
|
||||||
- the complete (local and server part) room's id (case sensitive).
|
- the complete (local and server part) room's id (case sensitive).
|
||||||
|
* `public_rooms` - Optional flag to filter public rooms. If `true`, only public rooms are queried. If `false`, public rooms are excluded from
|
||||||
|
the query. When the flag is absent (the default), **both** public and non-public rooms are included in the search results.
|
||||||
|
* `empty_rooms` - Optional flag to filter empty rooms. A room is empty if joined_members is zero. If `true`, only empty rooms are queried. If `false`, empty rooms are excluded from
|
||||||
|
the query. When the flag is absent (the default), **both** empty and non-empty rooms are included in the search results.
|
||||||
|
|
||||||
Defaults to no filtering.
|
Defaults to no filtering.
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ from synapse.http.servlet import (
|
|||||||
ResolveRoomIdMixin,
|
ResolveRoomIdMixin,
|
||||||
RestServlet,
|
RestServlet,
|
||||||
assert_params_in_dict,
|
assert_params_in_dict,
|
||||||
|
parse_boolean,
|
||||||
parse_enum,
|
parse_enum,
|
||||||
parse_integer,
|
parse_integer,
|
||||||
parse_json,
|
parse_json,
|
||||||
@ -242,13 +243,23 @@ class ListRoomRestServlet(RestServlet):
|
|||||||
errcode=Codes.INVALID_PARAM,
|
errcode=Codes.INVALID_PARAM,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
public_rooms = parse_boolean(request, "public_rooms")
|
||||||
|
empty_rooms = parse_boolean(request, "empty_rooms")
|
||||||
|
|
||||||
direction = parse_enum(request, "dir", Direction, default=Direction.FORWARDS)
|
direction = parse_enum(request, "dir", Direction, default=Direction.FORWARDS)
|
||||||
reverse_order = True if direction == Direction.BACKWARDS else False
|
reverse_order = True if direction == Direction.BACKWARDS else False
|
||||||
|
|
||||||
# Return list of rooms according to parameters
|
# Return list of rooms according to parameters
|
||||||
rooms, total_rooms = await self.store.get_rooms_paginate(
|
rooms, total_rooms = await self.store.get_rooms_paginate(
|
||||||
start, limit, order_by, reverse_order, search_term
|
start,
|
||||||
|
limit,
|
||||||
|
order_by,
|
||||||
|
reverse_order,
|
||||||
|
search_term,
|
||||||
|
public_rooms,
|
||||||
|
empty_rooms,
|
||||||
)
|
)
|
||||||
|
|
||||||
response = {
|
response = {
|
||||||
# next_token should be opaque, so return a value the client can parse
|
# next_token should be opaque, so return a value the client can parse
|
||||||
"offset": start,
|
"offset": start,
|
||||||
|
@ -606,6 +606,8 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
|||||||
order_by: str,
|
order_by: str,
|
||||||
reverse_order: bool,
|
reverse_order: bool,
|
||||||
search_term: Optional[str],
|
search_term: Optional[str],
|
||||||
|
public_rooms: Optional[bool],
|
||||||
|
empty_rooms: Optional[bool],
|
||||||
) -> Tuple[List[Dict[str, Any]], int]:
|
) -> Tuple[List[Dict[str, Any]], int]:
|
||||||
"""Function to retrieve a paginated list of rooms as json.
|
"""Function to retrieve a paginated list of rooms as json.
|
||||||
|
|
||||||
@ -617,30 +619,49 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
|||||||
search_term: a string to filter room names,
|
search_term: a string to filter room names,
|
||||||
canonical alias and room ids by.
|
canonical alias and room ids by.
|
||||||
Room ID must match exactly. Canonical alias must match a substring of the local part.
|
Room ID must match exactly. Canonical alias must match a substring of the local part.
|
||||||
|
public_rooms: Optional flag to filter public and non-public rooms. If true, public rooms are queried.
|
||||||
|
if false, public rooms are excluded from the query. When it is
|
||||||
|
none (the default), both public rooms and none-public-rooms are queried.
|
||||||
|
empty_rooms: Optional flag to filter empty and non-empty rooms.
|
||||||
|
A room is empty if joined_members is zero.
|
||||||
|
If true, empty rooms are queried.
|
||||||
|
if false, empty rooms are excluded from the query. When it is
|
||||||
|
none (the default), both empty rooms and none-empty rooms are queried.
|
||||||
Returns:
|
Returns:
|
||||||
A list of room dicts and an integer representing the total number of
|
A list of room dicts and an integer representing the total number of
|
||||||
rooms that exist given this query
|
rooms that exist given this query
|
||||||
"""
|
"""
|
||||||
# Filter room names by a string
|
# Filter room names by a string
|
||||||
where_statement = ""
|
filter_ = []
|
||||||
search_pattern: List[object] = []
|
where_args = []
|
||||||
if search_term:
|
if search_term:
|
||||||
where_statement = """
|
filter_ = [
|
||||||
WHERE LOWER(state.name) LIKE ?
|
"LOWER(state.name) LIKE ? OR "
|
||||||
OR LOWER(state.canonical_alias) LIKE ?
|
"LOWER(state.canonical_alias) LIKE ? OR "
|
||||||
OR state.room_id = ?
|
"state.room_id = ?"
|
||||||
"""
|
]
|
||||||
|
|
||||||
# Our postgres db driver converts ? -> %s in SQL strings as that's the
|
# Our postgres db driver converts ? -> %s in SQL strings as that's the
|
||||||
# placeholder for postgres.
|
# placeholder for postgres.
|
||||||
# HOWEVER, if you put a % into your SQL then everything goes wibbly.
|
# HOWEVER, if you put a % into your SQL then everything goes wibbly.
|
||||||
# To get around this, we're going to surround search_term with %'s
|
# To get around this, we're going to surround search_term with %'s
|
||||||
# before giving it to the database in python instead
|
# before giving it to the database in python instead
|
||||||
search_pattern = [
|
where_args = [
|
||||||
"%" + search_term.lower() + "%",
|
f"%{search_term.lower()}%",
|
||||||
"#%" + search_term.lower() + "%:%",
|
f"#%{search_term.lower()}%:%",
|
||||||
search_term,
|
search_term,
|
||||||
]
|
]
|
||||||
|
if public_rooms is not None:
|
||||||
|
filter_arg = "1" if public_rooms else "0"
|
||||||
|
filter_.append(f"rooms.is_public = '{filter_arg}'")
|
||||||
|
|
||||||
|
if empty_rooms is not None:
|
||||||
|
if empty_rooms:
|
||||||
|
filter_.append("curr.joined_members = 0")
|
||||||
|
else:
|
||||||
|
filter_.append("curr.joined_members <> 0")
|
||||||
|
|
||||||
|
where_clause = "WHERE " + " AND ".join(filter_) if len(filter_) > 0 else ""
|
||||||
|
|
||||||
# Set ordering
|
# Set ordering
|
||||||
if RoomSortOrder(order_by) == RoomSortOrder.SIZE:
|
if RoomSortOrder(order_by) == RoomSortOrder.SIZE:
|
||||||
@ -717,7 +738,7 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
|||||||
LIMIT ?
|
LIMIT ?
|
||||||
OFFSET ?
|
OFFSET ?
|
||||||
""".format(
|
""".format(
|
||||||
where=where_statement,
|
where=where_clause,
|
||||||
order_by=order_by_column,
|
order_by=order_by_column,
|
||||||
direction="ASC" if order_by_asc else "DESC",
|
direction="ASC" if order_by_asc else "DESC",
|
||||||
)
|
)
|
||||||
@ -726,10 +747,12 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
|||||||
count_sql = """
|
count_sql = """
|
||||||
SELECT count(*) FROM (
|
SELECT count(*) FROM (
|
||||||
SELECT room_id FROM room_stats_state state
|
SELECT room_id FROM room_stats_state state
|
||||||
|
INNER JOIN room_stats_current curr USING (room_id)
|
||||||
|
INNER JOIN rooms USING (room_id)
|
||||||
{where}
|
{where}
|
||||||
) AS get_room_ids
|
) AS get_room_ids
|
||||||
""".format(
|
""".format(
|
||||||
where=where_statement,
|
where=where_clause,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_rooms_paginate_txn(
|
def _get_rooms_paginate_txn(
|
||||||
@ -737,7 +760,7 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
|||||||
) -> Tuple[List[Dict[str, Any]], int]:
|
) -> Tuple[List[Dict[str, Any]], int]:
|
||||||
# Add the search term into the WHERE clause
|
# Add the search term into the WHERE clause
|
||||||
# and execute the data query
|
# and execute the data query
|
||||||
txn.execute(info_sql, search_pattern + [limit, start])
|
txn.execute(info_sql, where_args + [limit, start])
|
||||||
|
|
||||||
# Refactor room query data into a structured dictionary
|
# Refactor room query data into a structured dictionary
|
||||||
rooms = []
|
rooms = []
|
||||||
@ -767,7 +790,7 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
|||||||
# Execute the count query
|
# Execute the count query
|
||||||
|
|
||||||
# Add the search term into the WHERE clause if present
|
# Add the search term into the WHERE clause if present
|
||||||
txn.execute(count_sql, search_pattern)
|
txn.execute(count_sql, where_args)
|
||||||
|
|
||||||
room_count = cast(Tuple[int], txn.fetchone())
|
room_count = cast(Tuple[int], txn.fetchone())
|
||||||
return rooms, room_count[0]
|
return rooms, room_count[0]
|
||||||
|
@ -1795,6 +1795,83 @@ class RoomTestCase(unittest.HomeserverTestCase):
|
|||||||
self.assertEqual(room_id, channel.json_body["rooms"][0].get("room_id"))
|
self.assertEqual(room_id, channel.json_body["rooms"][0].get("room_id"))
|
||||||
self.assertEqual("ж", channel.json_body["rooms"][0].get("name"))
|
self.assertEqual("ж", channel.json_body["rooms"][0].get("name"))
|
||||||
|
|
||||||
|
def test_filter_public_rooms(self) -> None:
|
||||||
|
self.helper.create_room_as(
|
||||||
|
self.admin_user, tok=self.admin_user_tok, is_public=True
|
||||||
|
)
|
||||||
|
self.helper.create_room_as(
|
||||||
|
self.admin_user, tok=self.admin_user_tok, is_public=True
|
||||||
|
)
|
||||||
|
self.helper.create_room_as(
|
||||||
|
self.admin_user, tok=self.admin_user_tok, is_public=False
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.make_request(
|
||||||
|
"GET",
|
||||||
|
"/_synapse/admin/v1/rooms",
|
||||||
|
access_token=self.admin_user_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(200, response.code, msg=response.json_body)
|
||||||
|
self.assertEqual(3, response.json_body["total_rooms"])
|
||||||
|
self.assertEqual(3, len(response.json_body["rooms"]))
|
||||||
|
|
||||||
|
response = self.make_request(
|
||||||
|
"GET",
|
||||||
|
"/_synapse/admin/v1/rooms?public_rooms=true",
|
||||||
|
access_token=self.admin_user_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(200, response.code, msg=response.json_body)
|
||||||
|
self.assertEqual(2, response.json_body["total_rooms"])
|
||||||
|
self.assertEqual(2, len(response.json_body["rooms"]))
|
||||||
|
|
||||||
|
response = self.make_request(
|
||||||
|
"GET",
|
||||||
|
"/_synapse/admin/v1/rooms?public_rooms=false",
|
||||||
|
access_token=self.admin_user_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(200, response.code, msg=response.json_body)
|
||||||
|
self.assertEqual(1, response.json_body["total_rooms"])
|
||||||
|
self.assertEqual(1, len(response.json_body["rooms"]))
|
||||||
|
|
||||||
|
def test_filter_empty_rooms(self) -> None:
|
||||||
|
self.helper.create_room_as(
|
||||||
|
self.admin_user, tok=self.admin_user_tok, is_public=True
|
||||||
|
)
|
||||||
|
self.helper.create_room_as(
|
||||||
|
self.admin_user, tok=self.admin_user_tok, is_public=True
|
||||||
|
)
|
||||||
|
room_id = self.helper.create_room_as(
|
||||||
|
self.admin_user, tok=self.admin_user_tok, is_public=False
|
||||||
|
)
|
||||||
|
self.helper.leave(room_id, self.admin_user, tok=self.admin_user_tok)
|
||||||
|
|
||||||
|
response = self.make_request(
|
||||||
|
"GET",
|
||||||
|
"/_synapse/admin/v1/rooms",
|
||||||
|
access_token=self.admin_user_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(200, response.code, msg=response.json_body)
|
||||||
|
self.assertEqual(3, response.json_body["total_rooms"])
|
||||||
|
self.assertEqual(3, len(response.json_body["rooms"]))
|
||||||
|
|
||||||
|
response = self.make_request(
|
||||||
|
"GET",
|
||||||
|
"/_synapse/admin/v1/rooms?empty_rooms=false",
|
||||||
|
access_token=self.admin_user_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(200, response.code, msg=response.json_body)
|
||||||
|
self.assertEqual(2, response.json_body["total_rooms"])
|
||||||
|
self.assertEqual(2, len(response.json_body["rooms"]))
|
||||||
|
|
||||||
|
response = self.make_request(
|
||||||
|
"GET",
|
||||||
|
"/_synapse/admin/v1/rooms?empty_rooms=true",
|
||||||
|
access_token=self.admin_user_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(200, response.code, msg=response.json_body)
|
||||||
|
self.assertEqual(1, response.json_body["total_rooms"])
|
||||||
|
self.assertEqual(1, len(response.json_body["rooms"]))
|
||||||
|
|
||||||
def test_single_room(self) -> None:
|
def test_single_room(self) -> None:
|
||||||
"""Test that a single room can be requested correctly"""
|
"""Test that a single room can be requested correctly"""
|
||||||
# Create two test rooms
|
# Create two test rooms
|
||||||
|
Loading…
Reference in New Issue
Block a user