From b3345f0d2a905b727358dfaf75244b18a3e6febc Mon Sep 17 00:00:00 2001 From: Kevin Chung Date: Fri, 19 Jun 2020 19:05:35 -0400 Subject: [PATCH] Add pagination to endpoints. Avoiding scoreboard for now --- CTFd/api/v1/challenges.py | 26 +++++++++++++++++++++----- CTFd/api/v1/files.py | 19 ++++++++++++++++--- CTFd/api/v1/flags.py | 19 ++++++++++++++++--- CTFd/api/v1/hints.py | 19 ++++++++++++++++--- CTFd/api/v1/notifications.py | 19 ++++++++++++++++--- CTFd/api/v1/pages.py | 19 ++++++++++++++++--- CTFd/api/v1/submissions.py | 29 +++++++++++++++++++++++++---- CTFd/api/v1/tags.py | 19 ++++++++++++++++--- CTFd/api/v1/teams.py | 23 +++++++++++++++++++---- CTFd/api/v1/tokens.py | 19 ++++++++++++++++--- CTFd/api/v1/unlocks.py | 19 ++++++++++++++++--- CTFd/api/v1/users.py | 4 ++-- 12 files changed, 195 insertions(+), 39 deletions(-) diff --git a/CTFd/api/v1/challenges.py b/CTFd/api/v1/challenges.py index 2ccd9598..104aedaa 100644 --- a/CTFd/api/v1/challenges.py +++ b/CTFd/api/v1/challenges.py @@ -49,15 +49,18 @@ class ChallengeList(Resource): # Admins can request to see everything if is_admin() and request.args.get("view") == "admin": - challenges = Challenges.query.order_by(Challenges.value).all() - solve_ids = set([challenge.id for challenge in challenges]) + challenges = Challenges.query.order_by(Challenges.value).paginate( + max_per_page=100 + ) + # As far as prerequisites go, admins should be able to see/attempt on any challenge + solve_ids = set([challenge.id for challenge in Challenges.query.with_entities(Challenges.id).all()]) else: challenges = ( Challenges.query.filter( and_(Challenges.state != "hidden", Challenges.state != "locked") ) .order_by(Challenges.value) - .all() + .paginate(max_per_page=100) ) if user: @@ -80,7 +83,7 @@ class ChallengeList(Resource): response = [] tag_schema = TagSchema(view="user", many=True) - for challenge in challenges: + for challenge in challenges.items: if challenge.requirements: requirements = challenge.requirements.get("prerequisites", []) anonymize = challenge.requirements.get("anonymize") @@ -119,7 +122,20 @@ class ChallengeList(Resource): ) db.session.close() - return {"success": True, "data": response} + return { + "meta": { + "pagination": { + "page": challenges.page, + "next": challenges.next_num, + "prev": challenges.prev_num, + "pages": challenges.pages, + "per_page": challenges.per_page, + "total": challenges.total, + } + }, + "success": True, + "data": response, + } @admins_only def post(self): diff --git a/CTFd/api/v1/files.py b/CTFd/api/v1/files.py index b0e783f1..cc6826c9 100644 --- a/CTFd/api/v1/files.py +++ b/CTFd/api/v1/files.py @@ -14,14 +14,27 @@ class FilesList(Resource): @admins_only def get(self): file_type = request.args.get("type") - files = Files.query.filter_by(type=file_type).all() + files = Files.query.filter_by(type=file_type).paginate(max_per_page=100) schema = FileSchema(many=True) - response = schema.dump(files) + response = schema.dump(files.items) if response.errors: return {"success": False, "errors": response.errors}, 400 - return {"success": True, "data": response.data} + return { + "meta": { + "pagination": { + "page": files.page, + "next": files.next_num, + "prev": files.prev_num, + "pages": files.pages, + "per_page": files.per_page, + "total": files.total, + } + }, + "success": True, + "data": response.data, + } @admins_only def post(self): diff --git a/CTFd/api/v1/flags.py b/CTFd/api/v1/flags.py index 08fe2785..bdf5a243 100644 --- a/CTFd/api/v1/flags.py +++ b/CTFd/api/v1/flags.py @@ -13,13 +13,26 @@ flags_namespace = Namespace("flags", description="Endpoint to retrieve Flags") class FlagList(Resource): @admins_only def get(self): - flags = Flags.query.all() + flags = Flags.query.paginate(max_per_page=100) schema = FlagSchema(many=True) - response = schema.dump(flags) + response = schema.dump(flags.items) if response.errors: return {"success": False, "errors": response.errors}, 400 - return {"success": True, "data": response.data} + return { + "meta": { + "pagination": { + "page": flags.page, + "next": flags.next_num, + "prev": flags.prev_num, + "pages": flags.pages, + "per_page": flags.per_page, + "total": flags.total, + } + }, + "success": True, + "data": response.data, + } @admins_only def post(self): diff --git a/CTFd/api/v1/hints.py b/CTFd/api/v1/hints.py index 5acea7f0..cdd59572 100644 --- a/CTFd/api/v1/hints.py +++ b/CTFd/api/v1/hints.py @@ -13,13 +13,26 @@ hints_namespace = Namespace("hints", description="Endpoint to retrieve Hints") class HintList(Resource): @admins_only def get(self): - hints = Hints.query.all() - response = HintSchema(many=True).dump(hints) + hints = Hints.query.paginate(max_per_page=100) + response = HintSchema(many=True).dump(hints.items) if response.errors: return {"success": False, "errors": response.errors}, 400 - return {"success": True, "data": response.data} + return { + "meta": { + "pagination": { + "page": hints.page, + "next": hints.next_num, + "prev": hints.prev_num, + "pages": hints.pages, + "per_page": hints.per_page, + "total": hints.total, + } + }, + "success": True, + "data": response.data, + } @admins_only def post(self): diff --git a/CTFd/api/v1/notifications.py b/CTFd/api/v1/notifications.py index 0cf63a74..6dd51faa 100644 --- a/CTFd/api/v1/notifications.py +++ b/CTFd/api/v1/notifications.py @@ -13,12 +13,25 @@ notifications_namespace = Namespace( @notifications_namespace.route("") class NotificantionList(Resource): def get(self): - notifications = Notifications.query.all() + notifications = Notifications.query.paginate(max_per_page=100) schema = NotificationSchema(many=True) - result = schema.dump(notifications) + result = schema.dump(notifications.items) if result.errors: return {"success": False, "errors": result.errors}, 400 - return {"success": True, "data": result.data} + return { + "meta": { + "pagination": { + "page": notifications.page, + "next": notifications.next_num, + "prev": notifications.prev_num, + "pages": notifications.pages, + "per_page": notifications.per_page, + "total": notifications.total, + } + }, + "success": True, + "data": result.data, + } @admins_only def post(self): diff --git a/CTFd/api/v1/pages.py b/CTFd/api/v1/pages.py index b97bef81..3cceb689 100644 --- a/CTFd/api/v1/pages.py +++ b/CTFd/api/v1/pages.py @@ -13,13 +13,26 @@ pages_namespace = Namespace("pages", description="Endpoint to retrieve Pages") class PageList(Resource): @admins_only def get(self): - pages = Pages.query.all() + pages = Pages.query.paginate(max_per_page=100) schema = PageSchema(exclude=["content"], many=True) - response = schema.dump(pages) + response = schema.dump(pages.items) if response.errors: return {"success": False, "errors": response.errors}, 400 - return {"success": True, "data": response.data} + return { + "meta": { + "pagination": { + "page": pages.page, + "next": pages.next_num, + "prev": pages.prev_num, + "pages": pages.pages, + "per_page": pages.per_page, + "total": pages.total, + } + }, + "success": True, + "data": response.data, + } @admins_only def post(self): diff --git a/CTFd/api/v1/submissions.py b/CTFd/api/v1/submissions.py index 7a2e5e68..23998002 100644 --- a/CTFd/api/v1/submissions.py +++ b/CTFd/api/v1/submissions.py @@ -17,17 +17,38 @@ class SubmissionsList(Resource): def get(self): args = request.args.to_dict() schema = SubmissionSchema(many=True) + pagination_args = { + "per_page": int(args.pop("per_page", 50)), + "page": int(args.pop("page", 1)), + } if args: - submissions = Submissions.query.filter_by(**args).all() + submissions = Submissions.query.filter_by(**args).paginate( + **pagination_args, max_per_page=100 + ) else: - submissions = Submissions.query.all() + submissions = Submissions.query.paginate( + **pagination_args, max_per_page=100 + ) - response = schema.dump(submissions) + response = schema.dump(submissions.items) if response.errors: return {"success": False, "errors": response.errors}, 400 - return {"success": True, "data": response.data} + return { + "meta": { + "pagination": { + "page": submissions.page, + "next": submissions.next_num, + "prev": submissions.prev_num, + "pages": submissions.pages, + "per_page": submissions.per_page, + "total": submissions.total, + } + }, + "success": True, + "data": response.data, + } @admins_only def post(self): diff --git a/CTFd/api/v1/tags.py b/CTFd/api/v1/tags.py index 2134178a..7241eb8a 100644 --- a/CTFd/api/v1/tags.py +++ b/CTFd/api/v1/tags.py @@ -13,14 +13,27 @@ class TagList(Resource): @admins_only def get(self): # TODO: Filter by challenge_id - tags = Tags.query.all() + tags = Tags.query.paginate(max_per_page=100) schema = TagSchema(many=True) - response = schema.dump(tags) + response = schema.dump(tags.items) if response.errors: return {"success": False, "errors": response.errors}, 400 - return {"success": True, "data": response.data} + return { + "meta": { + "pagination": { + "page": tags.page, + "next": tags.next_num, + "prev": tags.prev_num, + "pages": tags.pages, + "per_page": tags.per_page, + "total": tags.total, + } + }, + "success": True, + "data": response.data, + } @admins_only def post(self): diff --git a/CTFd/api/v1/teams.py b/CTFd/api/v1/teams.py index cba196b1..10abbdc2 100644 --- a/CTFd/api/v1/teams.py +++ b/CTFd/api/v1/teams.py @@ -23,19 +23,34 @@ class TeamList(Resource): @check_account_visibility def get(self): if is_admin() and request.args.get("view") == "admin": - teams = Teams.query.filter_by() + teams = Teams.query.filter_by().paginate(per_page=50, max_per_page=100) else: - teams = Teams.query.filter_by(hidden=False, banned=False) + teams = Teams.query.filter_by(hidden=False, banned=False).paginate( + per_page=50, max_per_page=100 + ) user_type = get_current_user_type(fallback="user") view = copy.deepcopy(TeamSchema.views.get(user_type)) view.remove("members") - response = TeamSchema(view=view, many=True).dump(teams) + response = TeamSchema(view=view, many=True).dump(teams.items) if response.errors: return {"success": False, "errors": response.errors}, 400 - return {"success": True, "data": response.data} + return { + "meta": { + "pagination": { + "page": teams.page, + "next": teams.next_num, + "prev": teams.prev_num, + "pages": teams.pages, + "per_page": teams.per_page, + "total": teams.total, + } + }, + "success": True, + "data": response.data, + } @admins_only def post(self): diff --git a/CTFd/api/v1/tokens.py b/CTFd/api/v1/tokens.py index c8eaffa9..9a98f8de 100644 --- a/CTFd/api/v1/tokens.py +++ b/CTFd/api/v1/tokens.py @@ -18,15 +18,28 @@ class TokenList(Resource): @authed_only def get(self): user = get_current_user() - tokens = Tokens.query.filter_by(user_id=user.id) + tokens = Tokens.query.filter_by(user_id=user.id).paginate(max_per_page=100) response = TokenSchema(view=["id", "type", "expiration"], many=True).dump( - tokens + tokens.items ) if response.errors: return {"success": False, "errors": response.errors}, 400 - return {"success": True, "data": response.data} + return { + "meta": { + "pagination": { + "page": tokens.page, + "next": tokens.next_num, + "prev": tokens.prev_num, + "pages": tokens.pages, + "per_page": tokens.per_page, + "total": tokens.total, + } + }, + "success": True, + "data": response.data, + } @require_verified_emails @authed_only diff --git a/CTFd/api/v1/unlocks.py b/CTFd/api/v1/unlocks.py index b1499be1..38e993a6 100644 --- a/CTFd/api/v1/unlocks.py +++ b/CTFd/api/v1/unlocks.py @@ -20,14 +20,27 @@ unlocks_namespace = Namespace("unlocks", description="Endpoint to retrieve Unloc class UnlockList(Resource): @admins_only def get(self): - hints = Unlocks.query.all() + unlocks = Unlocks.query.paginate(max_per_page=100) schema = UnlockSchema() - response = schema.dump(hints) + response = schema.dump(unlocks.items) if response.errors: return {"success": False, "errors": response.errors}, 400 - return {"success": True, "data": response.data} + return { + "meta": { + "pagination": { + "page": unlocks.page, + "next": unlocks.next_num, + "prev": unlocks.prev_num, + "pages": unlocks.pages, + "per_page": unlocks.per_page, + "total": unlocks.total, + } + }, + "success": True, + "data": response.data, + } @during_ctf_time_only @require_verified_emails diff --git a/CTFd/api/v1/users.py b/CTFd/api/v1/users.py index 496b4bc2..b497b459 100644 --- a/CTFd/api/v1/users.py +++ b/CTFd/api/v1/users.py @@ -33,10 +33,10 @@ class UserList(Resource): @check_account_visibility def get(self): if is_admin() and request.args.get("view") == "admin": - users = Users.query.filter_by().paginate(max_per_page=50) + users = Users.query.filter_by().paginate(max_per_page=100) else: users = Users.query.filter_by(banned=False, hidden=False).paginate( - max_per_page=50 + max_per_page=100 ) response = UserSchema(view="user", many=True).dump(users.items)