Skip to content

Commit 87864f3

Browse files
committed
Return info_updates from room-modifying endpoints
This works similarly to returning the seqno from post-modifying endpoints, but for room metadata (fields, pinned messages, mods, etc.). This allows a client to track when the expected change should have been applied to the room when there might be concurrent room polling that could race with a pre-update response.
1 parent 6f1cb2c commit 87864f3

File tree

6 files changed

+147
-42
lines changed

6 files changed

+147
-42
lines changed

sogs/model/room.py

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1374,9 +1374,8 @@ def set_moderator(self, user: User, *, added_by: User, admin=False, visible=True
13741374
admin=admin,
13751375
visible=visible,
13761376
)
1377-
if u.id in self._perm_cache:
1378-
del self._perm_cache[u.id]
13791377

1378+
self._refresh()
13801379
if u.id in self._perm_cache:
13811380
del self._perm_cache[u.id]
13821381

@@ -1395,19 +1394,21 @@ def remove_moderator(self, user: User, *, removed_by: User, remove_admin_only: b
13951394
if not self.check_admin(removed_by):
13961395
raise BadPermission()
13971396

1398-
query(
1399-
f"""
1400-
UPDATE user_permission_overrides
1401-
SET admin = FALSE
1402-
{', moderator = FALSE, visible_mod = TRUE' if not remove_admin_only else ''}
1403-
WHERE room = :r AND "user" = :u
1404-
""",
1405-
r=self.id,
1406-
u=user.id,
1407-
)
1397+
with db.transaction():
1398+
query(
1399+
f"""
1400+
UPDATE user_permission_overrides
1401+
SET admin = FALSE
1402+
{', moderator = FALSE, visible_mod = TRUE' if not remove_admin_only else ''}
1403+
WHERE room = :r AND "user" = :u
1404+
""",
1405+
r=self.id,
1406+
u=user.id,
1407+
)
14081408

1409-
if user.id in self._perm_cache:
1410-
del self._perm_cache[user.id]
1409+
self._refresh()
1410+
if user.id in self._perm_cache:
1411+
del self._perm_cache[user.id]
14111412

14121413
app.logger.info(f"{removed_by} removed {user} as mod/admin of {self}")
14131414

@@ -1826,7 +1827,7 @@ def pin(self, msg_id: int, admin: User):
18261827
a=admin.id,
18271828
now=time.time(),
18281829
)
1829-
self._pinned = None
1830+
self._refresh()
18301831

18311832
def unpin_all(self, admin: User):
18321833
"""
@@ -1855,8 +1856,9 @@ def unpin_all(self, admin: User):
18551856
if unpinned_files:
18561857
File.reset_expiries(unpinned_files)
18571858

1858-
if count != 0:
1859-
self._pinned = None
1859+
if count != 0:
1860+
self._refresh()
1861+
18601862
return count
18611863

18621864
def unpin(self, msg_id: int, admin: User):
@@ -1884,8 +1886,9 @@ def unpin(self, msg_id: int, admin: User):
18841886
if unpinned_files:
18851887
File.reset_expiries(unpinned_files)
18861888

1887-
if count != 0:
1888-
self._pinned = None
1889+
if count != 0:
1890+
self._refresh()
1891+
18891892
return count
18901893

18911894
@property

sogs/routes/messages.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,10 @@ def message_pin(room, msg_id):
473473
474474
# Return value
475475
476-
On success returns a 200 status code and returns an empty JSON object as response.
476+
On success returns a 200 status code and returns a JSON object as response containing keys:
477+
478+
- `info_updates` -- the new info_updates value of the room; a client can use this to avoid
479+
race conditions with room info polling that might not yet include the updated value(s).
477480
478481
# Error status codes
479482
@@ -483,7 +486,7 @@ def message_pin(room, msg_id):
483486
pinning (e.g. a whisper or deleted post).
484487
"""
485488
room.pin(msg_id, g.user)
486-
return jsonify({})
489+
return jsonify({"info_updates": room.info_updates})
487490

488491

489492
@messages.post("/room/<Room:room>/unpin/<int:msg_id>")
@@ -504,14 +507,20 @@ def message_unpin(room, msg_id):
504507
505508
# Return value
506509
507-
On success returns a 200 status code and returns an empty JSON object as response body.
510+
On success returns a 200 status code and returns an JSON object as response body containing
511+
keys:
512+
513+
- `unpinned` - boolean value indicating whether the message was pinned and has now been unpinned
514+
(true), or was already unpinned (false).
515+
- `info_updates` - the new info_updates value for the room. This value will only change if the
516+
given message was actually pinned (i.e. it does not increment when `unpinned` is false).
508517
509518
# Error status codes
510519
511520
- 403 Forbidden — returned if the invoking user does not have admin permission in this room.
512521
"""
513-
room.unpin(msg_id, g.user)
514-
return jsonify({})
522+
count = room.unpin(msg_id, g.user)
523+
return jsonify({"unpinned": count > 0, "info_updates": room.info_updates})
515524

516525

517526
@messages.post("/room/<Room:room>/unpin/all")
@@ -527,15 +536,18 @@ def message_unpin_all(room):
527536
528537
# Return value
529538
530-
On success returns a 200 status code with an empty JSON object as response body. All pinned
531-
messages have been removed.
539+
On success returns a 200 status code with an JSON object as response body containing keys:
540+
541+
- `unpinned` - count of how many pinned messages were removed.
542+
- `info_updates` - new `info_updates` property for the room. This value is only incremented by
543+
this operation if at least one message was found and unpinned (i.e. if `unpinned > 0`).
532544
533545
# Error status codes
534546
535547
- 403 Forbidden — returned if the invoking user does not have admin permission in this room.
536548
"""
537-
room.unpin_all(g.user)
538-
return jsonify({})
549+
count = room.unpin_all(g.user)
550+
return jsonify({"unpinned": count, "info_updates": room.info_updates})
539551

540552

541553
@messages.put("/room/<Room:room>/reaction/<int:msg_id>/<path:reaction>")

sogs/routes/rooms.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,10 @@ def update_room(room):
203203
204204
# Return value
205205
206-
On success this endpoint returns a 200 status code and an empty json object (`{}`) as the body.
206+
On success this endpoint returns a 200 status code and a json object containing keys:
207+
208+
- `info_updates` -- the new info_updates value of the room; a client can use this to avoid
209+
race conditions with room info polling that might not yet include the updated value(s).
207210
208211
# Error status codes
209212
@@ -276,7 +279,7 @@ def update_room(room):
276279
app.logger.warning("Room update: must include at least one field to update")
277280
abort(http.BAD_REQUEST)
278281

279-
return jsonify({})
282+
return jsonify({"info_updates": room.info_updates})
280283

281284

282285
def addExtraPermInfo(perms):

sogs/routes/users.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,15 @@ def set_mod(sid):
9494
- `rooms` — List of one or more room tokens to which the moderator status should be applied. The
9595
invoking user must be an admin of all of the given rooms.
9696
97-
This may be set to the single-element list ["*"] to add or remove the moderator from all
97+
This may be set to the single-element list `["*"]` to add or remove the moderator from all
9898
rooms in which the current user has admin permissions (the call will succeed if the calling
99-
user is an admin in at least one channel).
99+
user is an admin in at least one room).
100100
101101
Exclusive of `global`.
102102
103103
- `global` — boolean value: if true then apply the change at the server-wide global level: the
104-
user will be added/removed as a global moderator. The invoking user must be a global admin in
105-
order to control global mods/admins.
104+
user will be added/removed as a global moderator/admin. The invoking user must be a global
105+
admin in order to control global mods/admins.
106106
107107
Exclusive of `rooms`.
108108
@@ -156,7 +156,16 @@ def set_mod(sid):
156156
157157
# Return value
158158
159-
On success returns a 200 status code with an empty JSON object as body.
159+
On success returns a 200 status code with JSON object as body containing keys:
160+
161+
- "info_updates": this is an object where each key is a room token, and each value is that
162+
room's new `info_updates` value. For a request making changes to room-level mods (i.e. using
163+
the `rooms` parameter) this will be the new `info_updates` value for each of the given rooms.
164+
For global moderator changes this will contain the new info_updates value of *all* rooms on
165+
the server (because all rooms are updated when a global mod is added/removed).
166+
167+
These values can be useful to track whether possibly-concurrent room polling requests are
168+
expected to have the moderator changes applied yet.
160169
161170
# Error status codes
162171
@@ -205,6 +214,8 @@ def set_mod(sid):
205214
if visible is None:
206215
visible = True
207216

217+
info_updates = {}
218+
208219
with db.transaction():
209220
for room in rooms:
210221
if (admin, mod) in ((True, None), (None, True)):
@@ -220,6 +231,8 @@ def set_mod(sid):
220231
app.logger.error("Internal error: unhandled mod/admin room case")
221232
raise RuntimeError("Internal error: unhandled mod/admin room case")
222233

234+
info_updates[room.token] = room.info_updates
235+
223236
else: # global mod
224237
if visible is None:
225238
visible = False
@@ -234,8 +247,9 @@ def set_mod(sid):
234247
with db.transaction():
235248
user.remove_moderator(removed_by=g.user, remove_admin_only=True)
236249
user.set_moderator(added_by=g.user, admin=bool(admin), visible=visible)
250+
info_updates = {room.token: room.info_updates for room in mroom.get_rooms()}
237251

238-
return jsonify({})
252+
return jsonify({"info_updates": info_updates})
239253

240254

241255
@users.post("/user/<AnySessionID:sid>/ban")

tests/test_room_routes.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,8 @@ def test_polling(client, room, user, user2, mod, admin, global_mod, global_admin
466466
assert img.expiry == from_now.hours(1)
467467
r = sogs_put(client, f'/room/{room.token}', {'image': img.id}, admin)
468468
assert r.status_code == 200
469-
assert r.json == dict()
469+
assert r.json == {"info_updates": info_up + 1}
470+
470471
img = File(id=img.id)
471472
assert img.expiry is None
472473

0 commit comments

Comments
 (0)