mirror of
https://github.com/aljazceru/CTFd.git
synced 2026-01-31 11:54:23 +01:00
Notifications improvements (#2166)
* Improve event `ping`s to actually include data so that they show up in devtools * Improve Event publishers to take an `id` parameter that is sent to the browser * Add a `since_id` parameter to `GET /api/v1/notifications` to get Notifications that have happened since a specific ID * Add `HEAD /api/v1/notifications` to get a count of notifications that have happened. This also includes a `since_id` parameter to allow for a notification cursor.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
from typing import List
|
||||
|
||||
from flask import current_app, request
|
||||
from flask import current_app, make_response, request
|
||||
from flask_restx import Namespace, Resource
|
||||
|
||||
from CTFd.api.v1.helpers.request import validate_args
|
||||
@@ -60,6 +60,7 @@ class NotificantionList(Resource):
|
||||
RawEnum("NotificationFields", {"title": "title", "content": "content"}),
|
||||
None,
|
||||
),
|
||||
"since_id": (int, None),
|
||||
},
|
||||
location="query",
|
||||
)
|
||||
@@ -68,6 +69,10 @@ class NotificantionList(Resource):
|
||||
field = str(query_args.pop("field", None))
|
||||
filters = build_model_filters(model=Notifications, query=q, field=field)
|
||||
|
||||
since_id = query_args.pop("since_id", None)
|
||||
if since_id:
|
||||
filters.append((Notifications.id > since_id))
|
||||
|
||||
notifications = (
|
||||
Notifications.query.filter_by(**query_args).filter(*filters).all()
|
||||
)
|
||||
@@ -77,6 +82,41 @@ class NotificantionList(Resource):
|
||||
return {"success": False, "errors": result.errors}, 400
|
||||
return {"success": True, "data": result.data}
|
||||
|
||||
@notifications_namespace.doc(
|
||||
description="Endpoint to get statistics for notification objects in bulk",
|
||||
responses={200: ("Success", "APISimpleSuccessResponse")},
|
||||
)
|
||||
@validate_args(
|
||||
{
|
||||
"title": (str, None),
|
||||
"content": (str, None),
|
||||
"user_id": (int, None),
|
||||
"team_id": (int, None),
|
||||
"q": (str, None),
|
||||
"field": (
|
||||
RawEnum("NotificationFields", {"title": "title", "content": "content"}),
|
||||
None,
|
||||
),
|
||||
"since_id": (int, None),
|
||||
},
|
||||
location="query",
|
||||
)
|
||||
def head(self, query_args):
|
||||
q = query_args.pop("q", None)
|
||||
field = str(query_args.pop("field", None))
|
||||
filters = build_model_filters(model=Notifications, query=q, field=field)
|
||||
|
||||
since_id = query_args.pop("since_id", None)
|
||||
if since_id:
|
||||
filters.append((Notifications.id > since_id))
|
||||
|
||||
notification_count = (
|
||||
Notifications.query.filter_by(**query_args).filter(*filters).count()
|
||||
)
|
||||
response = make_response()
|
||||
response.headers["Result-Count"] = notification_count
|
||||
return response
|
||||
|
||||
@admins_only
|
||||
@notifications_namespace.doc(
|
||||
description="Endpoint to create a notification object",
|
||||
|
||||
@@ -40,8 +40,8 @@ class EventManager(object):
|
||||
def __init__(self):
|
||||
self.clients = {}
|
||||
|
||||
def publish(self, data, type=None, channel="ctf"):
|
||||
event = ServerSentEvent(data, type=type)
|
||||
def publish(self, data, type=None, id=None, channel="ctf"):
|
||||
event = ServerSentEvent(data, type=type, id=id)
|
||||
message = event.to_dict()
|
||||
for client in list(self.clients.values()):
|
||||
client[channel].put(message)
|
||||
@@ -56,14 +56,14 @@ class EventManager(object):
|
||||
try:
|
||||
# Immediately yield a ping event to force Response headers to be set
|
||||
# or else some reverse proxies will incorrectly buffer SSE
|
||||
yield ServerSentEvent(data="", type="ping")
|
||||
yield ServerSentEvent(data="ping", type="ping")
|
||||
while True:
|
||||
try:
|
||||
with Timeout(5):
|
||||
message = q[channel].get()
|
||||
yield ServerSentEvent(**message)
|
||||
except Timeout:
|
||||
yield ServerSentEvent(data="", type="ping")
|
||||
yield ServerSentEvent(data="ping", type="ping")
|
||||
finally:
|
||||
del self.clients[id(q)]
|
||||
del q
|
||||
@@ -75,8 +75,8 @@ class RedisEventManager(EventManager):
|
||||
self.client = cache.cache._write_client
|
||||
self.clients = {}
|
||||
|
||||
def publish(self, data, type=None, channel="ctf"):
|
||||
event = ServerSentEvent(data, type=type)
|
||||
def publish(self, data, type=None, id=None, channel="ctf"):
|
||||
event = ServerSentEvent(data, type=type, id=id)
|
||||
message = json.dumps(event.to_dict())
|
||||
return self.client.publish(message=message, channel=channel)
|
||||
|
||||
@@ -107,14 +107,14 @@ class RedisEventManager(EventManager):
|
||||
try:
|
||||
# Immediately yield a ping event to force Response headers to be set
|
||||
# or else some reverse proxies will incorrectly buffer SSE
|
||||
yield ServerSentEvent(data="", type="ping")
|
||||
yield ServerSentEvent(data="ping", type="ping")
|
||||
while True:
|
||||
try:
|
||||
with Timeout(5):
|
||||
message = q[channel].get()
|
||||
yield ServerSentEvent(**message)
|
||||
except Timeout:
|
||||
yield ServerSentEvent(data="", type="ping")
|
||||
yield ServerSentEvent(data="ping", type="ping")
|
||||
finally:
|
||||
del self.clients[id(q)]
|
||||
del q
|
||||
|
||||
@@ -36,7 +36,7 @@ def test_event_manager_subscription():
|
||||
events = event_manager.subscribe()
|
||||
message = next(events)
|
||||
assert isinstance(message, ServerSentEvent)
|
||||
assert message.to_dict() == {"data": "", "type": "ping"}
|
||||
assert message.to_dict() == {"data": "ping", "type": "ping"}
|
||||
assert message.__str__().startswith("event:ping")
|
||||
assert len(event_manager.clients) == 1
|
||||
|
||||
@@ -146,7 +146,7 @@ def test_redis_event_manager_subscription():
|
||||
events = event_manager.subscribe()
|
||||
message = next(events)
|
||||
assert isinstance(message, ServerSentEvent)
|
||||
assert message.to_dict() == {"data": "", "type": "ping"}
|
||||
assert message.to_dict() == {"data": "ping", "type": "ping"}
|
||||
assert message.__str__().startswith("event:ping")
|
||||
|
||||
message = next(events)
|
||||
|
||||
Reference in New Issue
Block a user