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

Commit 89c4ca8

Browse files
authored
Add creation_ts to list users admin API (#10448)
Signed-off-by: Dirk Klimpel [email protected]
1 parent 38b346a commit 89c4ca8

File tree

6 files changed

+46
-33
lines changed

6 files changed

+46
-33
lines changed

changelog.d/10448.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add `creation_ts` to list users admin API.

docs/admin_api/user_admin_api.md

+7-3
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,8 @@ A response body like the following is returned:
144144
"deactivated": 0,
145145
"shadow_banned": 0,
146146
"displayname": "<User One>",
147-
"avatar_url": null
147+
"avatar_url": null,
148+
"creation_ts": 1560432668000
148149
}, {
149150
"name": "<user_id2>",
150151
"is_guest": 0,
@@ -153,7 +154,8 @@ A response body like the following is returned:
153154
"deactivated": 0,
154155
"shadow_banned": 0,
155156
"displayname": "<User Two>",
156-
"avatar_url": "<avatar_url>"
157+
"avatar_url": "<avatar_url>",
158+
"creation_ts": 1561550621000
157159
}
158160
],
159161
"next_token": "100",
@@ -197,11 +199,12 @@ The following parameters should be set in the URL:
197199
- `shadow_banned` - Users are ordered by `shadow_banned` status.
198200
- `displayname` - Users are ordered alphabetically by `displayname`.
199201
- `avatar_url` - Users are ordered alphabetically by avatar URL.
202+
- `creation_ts` - Users are ordered by when the users was created in ms.
200203

201204
- `dir` - Direction of media order. Either `f` for forwards or `b` for backwards.
202205
Setting this value to `b` will reverse the above sort order. Defaults to `f`.
203206

204-
Caution. The database only has indexes on the columns `name` and `created_ts`.
207+
Caution. The database only has indexes on the columns `name` and `creation_ts`.
205208
This means that if a different sort order is used (`is_guest`, `admin`,
206209
`user_type`, `deactivated`, `shadow_banned`, `avatar_url` or `displayname`),
207210
this can cause a large load on the database, especially for large environments.
@@ -222,6 +225,7 @@ The following fields are returned in the JSON response body:
222225
- `shadow_banned` - bool - Status if that user has been marked as shadow banned.
223226
- `displayname` - string - The user's display name if they have set one.
224227
- `avatar_url` - string - The user's avatar URL if they have set one.
228+
- `creation_ts` - integer - The user's creation timestamp in ms.
225229

226230
- `next_token`: string representing a positive integer - Indication for pagination. See above.
227231
- `total` - integer - Total number of media.

synapse/rest/admin/users.py

+2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class UsersRestServletV2(RestServlet):
6262
The parameter `name` can be used to filter by user id or display name.
6363
The parameter `guests` can be used to exclude guest users.
6464
The parameter `deactivated` can be used to include deactivated users.
65+
The parameter `order_by` can be used to order the result.
6566
"""
6667

6768
def __init__(self, hs: "HomeServer"):
@@ -108,6 +109,7 @@ async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
108109
UserSortOrder.USER_TYPE.value,
109110
UserSortOrder.AVATAR_URL.value,
110111
UserSortOrder.SHADOW_BANNED.value,
112+
UserSortOrder.CREATION_TS.value,
111113
),
112114
)
113115

synapse/storage/databases/main/__init__.py

+7-12
Original file line numberDiff line numberDiff line change
@@ -297,27 +297,22 @@ def get_users_paginate_txn(txn):
297297

298298
where_clause = "WHERE " + " AND ".join(filters) if len(filters) > 0 else ""
299299

300-
sql_base = """
300+
sql_base = f"""
301301
FROM users as u
302302
LEFT JOIN profiles AS p ON u.name = '@' || p.user_id || ':' || ?
303-
{}
304-
""".format(
305-
where_clause
306-
)
303+
{where_clause}
304+
"""
307305
sql = "SELECT COUNT(*) as total_users " + sql_base
308306
txn.execute(sql, args)
309307
count = txn.fetchone()[0]
310308

311-
sql = """
312-
SELECT name, user_type, is_guest, admin, deactivated, shadow_banned, displayname, avatar_url
309+
sql = f"""
310+
SELECT name, user_type, is_guest, admin, deactivated, shadow_banned,
311+
displayname, avatar_url, creation_ts * 1000 as creation_ts
313312
{sql_base}
314313
ORDER BY {order_by_column} {order}, u.name ASC
315314
LIMIT ? OFFSET ?
316-
""".format(
317-
sql_base=sql_base,
318-
order_by_column=order_by_column,
319-
order=order,
320-
)
315+
"""
321316
args += [limit, start]
322317
txn.execute(sql, args)
323318
users = self.db_pool.cursor_to_dict(txn)

synapse/storage/databases/main/stats.py

+2
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class UserSortOrder(Enum):
7575
USER_TYPE = ordered alphabetically by `user_type`
7676
AVATAR_URL = ordered alphabetically by `avatar_url`
7777
SHADOW_BANNED = ordered by `shadow_banned`
78+
CREATION_TS = ordered by `creation_ts`
7879
"""
7980

8081
MEDIA_LENGTH = "media_length"
@@ -88,6 +89,7 @@ class UserSortOrder(Enum):
8889
USER_TYPE = "user_type"
8990
AVATAR_URL = "avatar_url"
9091
SHADOW_BANNED = "shadow_banned"
92+
CREATION_TS = "creation_ts"
9193

9294

9395
class StatsStore(StateDeltasStore):

tests/rest/admin/test_user.py

+27-18
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ def test_no_auth(self):
473473
"""
474474
channel = self.make_request("GET", self.url, b"{}")
475475

476-
self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
476+
self.assertEqual(401, channel.code, msg=channel.json_body)
477477
self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
478478

479479
def test_requester_is_no_admin(self):
@@ -485,7 +485,7 @@ def test_requester_is_no_admin(self):
485485

486486
channel = self.make_request("GET", self.url, access_token=other_user_token)
487487

488-
self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
488+
self.assertEqual(403, channel.code, msg=channel.json_body)
489489
self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
490490

491491
def test_all_users(self):
@@ -497,11 +497,11 @@ def test_all_users(self):
497497
channel = self.make_request(
498498
"GET",
499499
self.url + "?deactivated=true",
500-
b"{}",
500+
{},
501501
access_token=self.admin_user_tok,
502502
)
503503

504-
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
504+
self.assertEqual(200, channel.code, msg=channel.json_body)
505505
self.assertEqual(3, len(channel.json_body["users"]))
506506
self.assertEqual(3, channel.json_body["total"])
507507

@@ -532,7 +532,7 @@ def _search_test(
532532
)
533533
channel = self.make_request(
534534
"GET",
535-
url.encode("ascii"),
535+
url,
536536
access_token=self.admin_user_tok,
537537
)
538538
self.assertEqual(expected_http_code, channel.code, msg=channel.json_body)
@@ -598,7 +598,7 @@ def test_invalid_parameter(self):
598598
access_token=self.admin_user_tok,
599599
)
600600

601-
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
601+
self.assertEqual(400, channel.code, msg=channel.json_body)
602602
self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
603603

604604
# negative from
@@ -608,7 +608,7 @@ def test_invalid_parameter(self):
608608
access_token=self.admin_user_tok,
609609
)
610610

611-
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
611+
self.assertEqual(400, channel.code, msg=channel.json_body)
612612
self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
613613

614614
# invalid guests
@@ -618,7 +618,7 @@ def test_invalid_parameter(self):
618618
access_token=self.admin_user_tok,
619619
)
620620

621-
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
621+
self.assertEqual(400, channel.code, msg=channel.json_body)
622622
self.assertEqual(Codes.UNKNOWN, channel.json_body["errcode"])
623623

624624
# invalid deactivated
@@ -628,7 +628,7 @@ def test_invalid_parameter(self):
628628
access_token=self.admin_user_tok,
629629
)
630630

631-
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
631+
self.assertEqual(400, channel.code, msg=channel.json_body)
632632
self.assertEqual(Codes.UNKNOWN, channel.json_body["errcode"])
633633

634634
# unkown order_by
@@ -648,7 +648,7 @@ def test_invalid_parameter(self):
648648
access_token=self.admin_user_tok,
649649
)
650650

651-
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
651+
self.assertEqual(400, channel.code, msg=channel.json_body)
652652
self.assertEqual(Codes.UNKNOWN, channel.json_body["errcode"])
653653

654654
def test_limit(self):
@@ -666,7 +666,7 @@ def test_limit(self):
666666
access_token=self.admin_user_tok,
667667
)
668668

669-
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
669+
self.assertEqual(200, channel.code, msg=channel.json_body)
670670
self.assertEqual(channel.json_body["total"], number_users)
671671
self.assertEqual(len(channel.json_body["users"]), 5)
672672
self.assertEqual(channel.json_body["next_token"], "5")
@@ -687,7 +687,7 @@ def test_from(self):
687687
access_token=self.admin_user_tok,
688688
)
689689

690-
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
690+
self.assertEqual(200, channel.code, msg=channel.json_body)
691691
self.assertEqual(channel.json_body["total"], number_users)
692692
self.assertEqual(len(channel.json_body["users"]), 15)
693693
self.assertNotIn("next_token", channel.json_body)
@@ -708,7 +708,7 @@ def test_limit_and_from(self):
708708
access_token=self.admin_user_tok,
709709
)
710710

711-
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
711+
self.assertEqual(200, channel.code, msg=channel.json_body)
712712
self.assertEqual(channel.json_body["total"], number_users)
713713
self.assertEqual(channel.json_body["next_token"], "15")
714714
self.assertEqual(len(channel.json_body["users"]), 10)
@@ -731,7 +731,7 @@ def test_next_token(self):
731731
access_token=self.admin_user_tok,
732732
)
733733

734-
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
734+
self.assertEqual(200, channel.code, msg=channel.json_body)
735735
self.assertEqual(channel.json_body["total"], number_users)
736736
self.assertEqual(len(channel.json_body["users"]), number_users)
737737
self.assertNotIn("next_token", channel.json_body)
@@ -744,7 +744,7 @@ def test_next_token(self):
744744
access_token=self.admin_user_tok,
745745
)
746746

747-
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
747+
self.assertEqual(200, channel.code, msg=channel.json_body)
748748
self.assertEqual(channel.json_body["total"], number_users)
749749
self.assertEqual(len(channel.json_body["users"]), number_users)
750750
self.assertNotIn("next_token", channel.json_body)
@@ -757,7 +757,7 @@ def test_next_token(self):
757757
access_token=self.admin_user_tok,
758758
)
759759

760-
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
760+
self.assertEqual(200, channel.code, msg=channel.json_body)
761761
self.assertEqual(channel.json_body["total"], number_users)
762762
self.assertEqual(len(channel.json_body["users"]), 19)
763763
self.assertEqual(channel.json_body["next_token"], "19")
@@ -771,7 +771,7 @@ def test_next_token(self):
771771
access_token=self.admin_user_tok,
772772
)
773773

774-
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
774+
self.assertEqual(200, channel.code, msg=channel.json_body)
775775
self.assertEqual(channel.json_body["total"], number_users)
776776
self.assertEqual(len(channel.json_body["users"]), 1)
777777
self.assertNotIn("next_token", channel.json_body)
@@ -781,7 +781,10 @@ def test_order_by(self):
781781
Testing order list with parameter `order_by`
782782
"""
783783

784+
# make sure that the users do not have the same timestamps
785+
self.reactor.advance(10)
784786
user1 = self.register_user("user1", "pass1", admin=False, displayname="Name Z")
787+
self.reactor.advance(10)
785788
user2 = self.register_user("user2", "pass2", admin=False, displayname="Name Y")
786789

787790
# Modify user
@@ -841,6 +844,11 @@ def test_order_by(self):
841844
self._order_test([self.admin_user, user2, user1], "avatar_url", "f")
842845
self._order_test([user1, user2, self.admin_user], "avatar_url", "b")
843846

847+
# order by creation_ts
848+
self._order_test([self.admin_user, user1, user2], "creation_ts")
849+
self._order_test([self.admin_user, user1, user2], "creation_ts", "f")
850+
self._order_test([user2, user1, self.admin_user], "creation_ts", "b")
851+
844852
def _order_test(
845853
self,
846854
expected_user_list: List[str],
@@ -863,7 +871,7 @@ def _order_test(
863871
url += "dir=%s" % (dir,)
864872
channel = self.make_request(
865873
"GET",
866-
url.encode("ascii"),
874+
url,
867875
access_token=self.admin_user_tok,
868876
)
869877
self.assertEqual(200, channel.code, msg=channel.json_body)
@@ -887,6 +895,7 @@ def _check_fields(self, content: JsonDict):
887895
self.assertIn("shadow_banned", u)
888896
self.assertIn("displayname", u)
889897
self.assertIn("avatar_url", u)
898+
self.assertIn("creation_ts", u)
890899

891900
def _create_users(self, number_users: int):
892901
"""

0 commit comments

Comments
 (0)