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

Admin API to list, filter and sort rooms #6720

Merged
merged 23 commits into from
Jan 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4f685c6
Admin API to list, filter and sort rooms
anoadragon453 Jan 5, 2020
6761961
Merge branch 'develop' of github.com:matrix-org/synapse into anoa/adm…
anoadragon453 Jan 16, 2020
908775c
Add changelog
anoadragon453 Jan 16, 2020
df32276
Add alias to subquery for postgres
anoadragon453 Jan 17, 2020
5aa30d8
Add documentation
anoadragon453 Jan 17, 2020
5552f97
Remove potentially confusing quotation marks
anoadragon453 Jan 17, 2020
a5a7e2e
Minor fixups
anoadragon453 Jan 17, 2020
9d30f4a
Fix LIMIT differences in Postgres vs. SQLite
anoadragon453 Jan 17, 2020
ad5edaa
debug
anoadragon453 Jan 17, 2020
973651d
more debugging
anoadragon453 Jan 17, 2020
ed5457c
Fix postgres-specific %-related bug
anoadragon453 Jan 17, 2020
9ca3bd0
Address review comments
anoadragon453 Jan 20, 2020
5205820
Address review comments. Return total rooms
anoadragon453 Jan 20, 2020
b449efd
Small docs clarification
anoadragon453 Jan 20, 2020
980cbd3
Indentation
anoadragon453 Jan 20, 2020
1cfd5e1
admin_api creation method refactor
anoadragon453 Jan 20, 2020
f91e692
Fix docstring
anoadragon453 Jan 20, 2020
93b2212
Add 'total_rooms' key to tests
anoadragon453 Jan 20, 2020
1c5ff45
Ensure pagination test returns rooms in a predicatable order
anoadragon453 Jan 20, 2020
6bebc12
Can't use regex pattern in method type definition
anoadragon453 Jan 20, 2020
8374cb1
Add offset parameter to the response
anoadragon453 Jan 21, 2020
7be87aa
Add prev_batch. next_token -> next_batch
anoadragon453 Jan 21, 2020
8c7f668
Don't raise exception on incorrect order_by value
anoadragon453 Jan 21, 2020
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/6720.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a new admin API to list and filter rooms on the server.
173 changes: 173 additions & 0 deletions docs/admin_api/rooms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# List Room API

The List Room admin API allows server admins to get a list of rooms on their
server. There are various parameters available that allow for filtering and
sorting the returned list. This API supports pagination.

## Parameters

The following query parameters are available:

* `from` - Offset in the returned list. Defaults to `0`.
* `limit` - Maximum amount of rooms to return. Defaults to `100`.
* `order_by` - The method in which to sort the returned list of rooms. Valid values are:
- `alphabetical` - Rooms are ordered alphabetically by room name. This is the default.
- `size` - Rooms are ordered by the number of members. Largest to smallest.
* `dir` - Direction of room order. Either `f` for forwards or `b` for backwards. Setting
this value to `b` will reverse the above sort order. Defaults to `f`.
* `search_term` - Filter rooms by their room name. Search term can be contained in any
part of the room name. Defaults to no filtering.

The following fields are possible in the JSON response body:

* `rooms` - An array of objects, each containing information about a room.
- Room objects contain the following fields:
- `room_id` - The ID of the room.
- `name` - The name of the room.
- `canonical_alias` - The canonical (main) alias address of the room.
- `joined_members` - How many users are currently in the room.
* `offset` - The current pagination offset in rooms. This parameter should be
used instead of `next_token` for room offset as `next_token` is
not intended to be parsed.
* `total_rooms` - The total number of rooms this query can return. Using this
and `offset`, you have enough information to know the current
progression through the list.
* `next_batch` - If this field is present, we know that there are potentially
more rooms on the server that did not all fit into this response.
We can use `next_batch` to get the "next page" of results. To do
so, simply repeat your request, setting the `from` parameter to
the value of `next_batch`.
* `prev_batch` - If this field is present, it is possible to paginate backwards.
Use `prev_batch` for the `from` value in the next request to
get the "previous page" of results.

## Usage

A standard request with no filtering:

```
GET /_synapse/admin/rooms

{}
```

Response:

```
{
"rooms": [
{
"room_id": "!OGEhHVWSdvArJzumhm:matrix.org",
"name": "Matrix HQ",
"canonical_alias": "#matrix:matrix.org",
"joined_members": 8326
},
... (8 hidden items) ...
{
"room_id": "!xYvNcQPhnkrdUmYczI:matrix.org",
"name": "This Week In Matrix (TWIM)",
"canonical_alias": "#twim:matrix.org",
"joined_members": 314
}
],
"offset": 0,
"total_rooms": 10
}
```

Filtering by room name:

```
GET /_synapse/admin/rooms?search_term=TWIM

{}
```

Response:

```
{
"rooms": [
{
"room_id": "!xYvNcQPhnkrdUmYczI:matrix.org",
"name": "This Week In Matrix (TWIM)",
"canonical_alias": "#twim:matrix.org",
"joined_members": 314
}
],
"offset": 0,
"total_rooms": 1
}
```

Paginating through a list of rooms:

```
GET /_synapse/admin/rooms?order_by=size

{}
```

Response:

```
{
"rooms": [
{
"room_id": "!OGEhHVWSdvArJzumhm:matrix.org",
"name": "Matrix HQ",
"canonical_alias": "#matrix:matrix.org",
"joined_members": 8326
},
... (98 hidden items) ...
{
"room_id": "!xYvNcQPhnkrdUmYczI:matrix.org",
"name": "This Week In Matrix (TWIM)",
"canonical_alias": "#twim:matrix.org",
"joined_members": 314
}
],
"offset": 0,
"total_rooms": 150
"next_token": 100
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
}
```

The presence of the `next_token` parameter tells us that there are more rooms
than returned in this request, and we need to make another request to get them.
To get the next batch of room results, we repeat our request, setting the `from`
parameter to the value of `next_token`.

```
GET /_synapse/admin/rooms?order_by=size&from=100

{}
```

Response:

```
{
"rooms": [
{
"room_id": "!mscvqgqpHYjBGDxNym:matrix.org",
"name": "Music Theory",
"canonical_alias": "#musictheory:matrix.org",
"joined_members": 127
},
... (48 hidden items) ...
{
"room_id": "!twcBhHVdZlQWuuxBhN:termina.org.uk",
"name": "weechat-matrix",
"canonical_alias": "#weechat-matrix:termina.org.uk",
"joined_members": 137
}
],
"offset": 100,
"prev_batch": 0,
"total_rooms": 150
}
```

Once the `next_token` parameter is no longer present, we know we've reached the
end of the list.
3 changes: 2 additions & 1 deletion synapse/rest/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from synapse.rest.admin.groups import DeleteGroupAdminRestServlet
from synapse.rest.admin.media import ListMediaInRoom, register_servlets_for_media_repo
from synapse.rest.admin.purge_room_servlet import PurgeRoomServlet
from synapse.rest.admin.rooms import ShutdownRoomRestServlet
from synapse.rest.admin.rooms import ListRoomRestServlet, ShutdownRoomRestServlet
from synapse.rest.admin.server_notice_servlet import SendServerNoticeServlet
from synapse.rest.admin.users import (
AccountValidityRenewServlet,
Expand Down Expand Up @@ -188,6 +188,7 @@ def register_servlets(hs, http_server):
Register all the admin servlets.
"""
register_servlets_for_client_rest_resource(hs, http_server)
ListRoomRestServlet(hs).register(http_server)
PurgeRoomServlet(hs).register(http_server)
SendServerNoticeServlet(hs).register(http_server)
VersionServlet(hs).register(http_server)
Expand Down
15 changes: 15 additions & 0 deletions synapse/rest/admin/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,21 @@ def historical_admin_path_patterns(path_regex):
)


def admin_patterns(path_regex: str):
"""Returns the list of patterns for an admin endpoint

Args:
path_regex: The regex string to match. This should NOT have a ^
as this will be prefixed.

Returns:
A list of regex patterns.
"""
admin_prefix = "^/_synapse/admin/v1"
patterns = [re.compile(admin_prefix + path_regex)]
return patterns


async def assert_requester_is_admin(auth, request):
"""Verify that the requester is an admin user

Expand Down
82 changes: 82 additions & 0 deletions synapse/rest/admin/rooms.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,20 @@
import logging

from synapse.api.constants import Membership
from synapse.api.errors import Codes, SynapseError
from synapse.http.servlet import (
RestServlet,
assert_params_in_dict,
parse_integer,
parse_json_object_from_request,
parse_string,
)
from synapse.rest.admin._base import (
admin_patterns,
assert_user_is_admin,
historical_admin_path_patterns,
)
from synapse.storage.data_stores.main.room import RoomSortOrder
from synapse.types import create_requester
from synapse.util.async_helpers import maybe_awaitable

Expand Down Expand Up @@ -155,3 +160,80 @@ async def on_POST(self, request, room_id):
"new_room_id": new_room_id,
},
)


class ListRoomRestServlet(RestServlet):
"""
List all rooms that are known to the homeserver. Results are returned
in a dictionary containing room information. Supports pagination.
"""

PATTERNS = admin_patterns("/rooms")

def __init__(self, hs):
self.store = hs.get_datastore()
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
self.auth = hs.get_auth()
self.admin_handler = hs.get_handlers().admin_handler

async def on_GET(self, request):
requester = await self.auth.get_user_by_req(request)
await assert_user_is_admin(self.auth, requester.user)

# Extract query parameters
start = parse_integer(request, "from", default=0)
limit = parse_integer(request, "limit", default=100)
order_by = parse_string(request, "order_by", default="alphabetical")
if order_by not in (
RoomSortOrder.ALPHABETICAL.value,
RoomSortOrder.SIZE.value,
):
raise SynapseError(
400,
"Unknown value for order_by: %s" % (order_by,),
errcode=Codes.INVALID_PARAM,
)

search_term = parse_string(request, "search_term")
if search_term == "":
raise SynapseError(
400,
"search_term cannot be an empty string",
errcode=Codes.INVALID_PARAM,
)

direction = parse_string(request, "dir", default="f")
if direction not in ("f", "b"):
raise SynapseError(
400, "Unknown direction: %s" % (direction,), errcode=Codes.INVALID_PARAM
)

reverse_order = True if direction == "b" else False

# Return list of rooms according to parameters
rooms, total_rooms = await self.store.get_rooms_paginate(
start, limit, order_by, reverse_order, search_term
)
response = {
# next_token should be opaque, so return a value the client can parse
"offset": start,
"rooms": rooms,
"total_rooms": total_rooms,
}

# Are there more rooms to paginate through after this?
if (start + limit) < total_rooms:
# There are. Calculate where the query should start from next time
# to get the next part of the list
response["next_batch"] = start + limit

# Is it possible to paginate backwards? Check if we currently have an
# offset
if start > 0:
if start > limit:
# Going back one iteration won't take us to the start.
# Calculate new offset
response["prev_batch"] = start - limit
else:
response["prev_batch"] = 0

return 200, response
2 changes: 1 addition & 1 deletion synapse/rest/client/v2_alpha/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def client_patterns(path_regex, releases=(0,), unstable=True, v1=False):

Args:
path_regex (str): The regex string to match. This should NOT have a ^
as this will be prefixed.
as this will be prefixed.
Returns:
SRE_Pattern
"""
Expand Down
Loading