mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-19 06:54:20 +01:00
Add API searching and filtering (#1515)
* Works on #1318 * Adds searching and filtering to most of the bulk API endpoints * Adds documentation on the GET parameters used to conduct searches
This commit is contained in:
@@ -3,9 +3,12 @@ from typing import List
|
|||||||
from flask import request
|
from flask import request
|
||||||
from flask_restx import Namespace, Resource
|
from flask_restx import Namespace, Resource
|
||||||
|
|
||||||
|
from CTFd.api.v1.helpers.models import build_model_filters
|
||||||
|
from CTFd.api.v1.helpers.request import validate_args
|
||||||
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
||||||
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
||||||
from CTFd.cache import clear_standings
|
from CTFd.cache import clear_standings
|
||||||
|
from CTFd.constants import RawEnum
|
||||||
from CTFd.models import Awards, Users, db
|
from CTFd.models import Awards, Users, db
|
||||||
from CTFd.schemas.awards import AwardSchema
|
from CTFd.schemas.awards import AwardSchema
|
||||||
from CTFd.utils.config import is_teams_mode
|
from CTFd.utils.config import is_teams_mode
|
||||||
@@ -35,6 +38,55 @@ awards_namespace.schema_model(
|
|||||||
|
|
||||||
@awards_namespace.route("")
|
@awards_namespace.route("")
|
||||||
class AwardList(Resource):
|
class AwardList(Resource):
|
||||||
|
@admins_only
|
||||||
|
@awards_namespace.doc(
|
||||||
|
description="Endpoint to list Award objects in bulk",
|
||||||
|
responses={
|
||||||
|
200: ("Success", "AwardListSuccessResponse"),
|
||||||
|
400: (
|
||||||
|
"An error occured processing the provided or stored data",
|
||||||
|
"APISimpleErrorResponse",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
@validate_args(
|
||||||
|
{
|
||||||
|
"user_id": (int, None),
|
||||||
|
"team_id": (int, None),
|
||||||
|
"type": (str, None),
|
||||||
|
"value": (int, None),
|
||||||
|
"category": (int, None),
|
||||||
|
"icon": (int, None),
|
||||||
|
"q": (str, None),
|
||||||
|
"field": (
|
||||||
|
RawEnum(
|
||||||
|
"AwardFields",
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"description": "description",
|
||||||
|
"category": "category",
|
||||||
|
"icon": "icon",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
location="query",
|
||||||
|
)
|
||||||
|
def get(self, query_args):
|
||||||
|
q = query_args.pop("q", None)
|
||||||
|
field = str(query_args.pop("field", None))
|
||||||
|
filters = build_model_filters(model=Awards, query=q, field=field)
|
||||||
|
|
||||||
|
awards = Awards.query.filter_by(**query_args).filter(*filters).all()
|
||||||
|
schema = AwardSchema(many=True)
|
||||||
|
response = schema.dump(awards)
|
||||||
|
|
||||||
|
if response.errors:
|
||||||
|
return {"success": False, "errors": response.errors}, 400
|
||||||
|
|
||||||
|
return {"success": True, "data": response.data}
|
||||||
|
|
||||||
@admins_only
|
@admins_only
|
||||||
@awards_namespace.doc(
|
@awards_namespace.doc(
|
||||||
description="Endpoint to create an Award object",
|
description="Endpoint to create an Award object",
|
||||||
|
|||||||
@@ -5,9 +5,12 @@ from flask import abort, render_template, request, url_for
|
|||||||
from flask_restx import Namespace, Resource
|
from flask_restx import Namespace, Resource
|
||||||
from sqlalchemy.sql import and_
|
from sqlalchemy.sql import and_
|
||||||
|
|
||||||
|
from CTFd.api.v1.helpers.models import build_model_filters
|
||||||
|
from CTFd.api.v1.helpers.request import validate_args
|
||||||
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
||||||
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
||||||
from CTFd.cache import clear_standings
|
from CTFd.cache import clear_standings
|
||||||
|
from CTFd.constants import RawEnum
|
||||||
from CTFd.models import ChallengeFiles as ChallengeFilesModel
|
from CTFd.models import ChallengeFiles as ChallengeFilesModel
|
||||||
from CTFd.models import (
|
from CTFd.models import (
|
||||||
Challenges,
|
Challenges,
|
||||||
@@ -86,19 +89,56 @@ class ChallengeList(Resource):
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def get(self):
|
@validate_args(
|
||||||
|
{
|
||||||
|
"name": (str, None),
|
||||||
|
"max_attempts": (int, None),
|
||||||
|
"value": (int, None),
|
||||||
|
"category": (str, None),
|
||||||
|
"type": (str, None),
|
||||||
|
"state": (str, None),
|
||||||
|
"q": (str, None),
|
||||||
|
"field": (
|
||||||
|
RawEnum(
|
||||||
|
"ChallengeFields",
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"description": "description",
|
||||||
|
"category": "category",
|
||||||
|
"type": "type",
|
||||||
|
"state": "state",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
location="query",
|
||||||
|
)
|
||||||
|
def get(self, query_args):
|
||||||
|
# Build filtering queries
|
||||||
|
q = query_args.pop("q", None)
|
||||||
|
field = str(query_args.pop("field", None))
|
||||||
|
filters = build_model_filters(model=Challenges, query=q, field=field)
|
||||||
|
|
||||||
# This can return None (unauth) if visibility is set to public
|
# This can return None (unauth) if visibility is set to public
|
||||||
user = get_current_user()
|
user = get_current_user()
|
||||||
|
|
||||||
# Admins can request to see everything
|
# Admins can request to see everything
|
||||||
if is_admin() and request.args.get("view") == "admin":
|
if is_admin() and request.args.get("view") == "admin":
|
||||||
challenges = Challenges.query.order_by(Challenges.value).all()
|
challenges = (
|
||||||
|
Challenges.query.filter_by(**query_args)
|
||||||
|
.filter(*filters)
|
||||||
|
.order_by(Challenges.value)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
solve_ids = set([challenge.id for challenge in challenges])
|
solve_ids = set([challenge.id for challenge in challenges])
|
||||||
else:
|
else:
|
||||||
challenges = (
|
challenges = (
|
||||||
Challenges.query.filter(
|
Challenges.query.filter(
|
||||||
and_(Challenges.state != "hidden", Challenges.state != "locked")
|
and_(Challenges.state != "hidden", Challenges.state != "locked")
|
||||||
)
|
)
|
||||||
|
.filter_by(**query_args)
|
||||||
|
.filter(*filters)
|
||||||
.order_by(Challenges.value)
|
.order_by(Challenges.value)
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ from typing import List
|
|||||||
from flask import request
|
from flask import request
|
||||||
from flask_restx import Namespace, Resource
|
from flask_restx import Namespace, Resource
|
||||||
|
|
||||||
|
from CTFd.api.v1.helpers.models import build_model_filters
|
||||||
|
from CTFd.api.v1.helpers.request import validate_args
|
||||||
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
||||||
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
||||||
from CTFd.cache import clear_config, clear_standings
|
from CTFd.cache import clear_config, clear_standings
|
||||||
|
from CTFd.constants import RawEnum
|
||||||
from CTFd.models import Configs, db
|
from CTFd.models import Configs, db
|
||||||
from CTFd.schemas.config import ConfigSchema
|
from CTFd.schemas.config import ConfigSchema
|
||||||
from CTFd.utils import get_config, set_config
|
from CTFd.utils import get_config, set_config
|
||||||
@@ -46,8 +49,21 @@ class ConfigList(Resource):
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def get(self):
|
@validate_args(
|
||||||
configs = Configs.query.all()
|
{
|
||||||
|
"key": (str, None),
|
||||||
|
"value": (str, None),
|
||||||
|
"q": (str, None),
|
||||||
|
"field": (RawEnum("ConfigFields", {"key": "key", "value": "value"}), None),
|
||||||
|
},
|
||||||
|
location="query",
|
||||||
|
)
|
||||||
|
def get(self, query_args):
|
||||||
|
q = query_args.pop("q", None)
|
||||||
|
field = str(query_args.pop("field", None))
|
||||||
|
filters = build_model_filters(model=Configs, query=q, field=field)
|
||||||
|
|
||||||
|
configs = Configs.query.filter_by(**query_args).filter(*filters).all()
|
||||||
schema = ConfigSchema(many=True)
|
schema = ConfigSchema(many=True)
|
||||||
response = schema.dump(configs)
|
response = schema.dump(configs)
|
||||||
if response.errors:
|
if response.errors:
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ from typing import List
|
|||||||
from flask import request
|
from flask import request
|
||||||
from flask_restx import Namespace, Resource
|
from flask_restx import Namespace, Resource
|
||||||
|
|
||||||
|
from CTFd.api.v1.helpers.models import build_model_filters
|
||||||
|
from CTFd.api.v1.helpers.request import validate_args
|
||||||
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
||||||
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
||||||
|
from CTFd.constants import RawEnum
|
||||||
from CTFd.models import Files, db
|
from CTFd.models import Files, db
|
||||||
from CTFd.schemas.files import FileSchema
|
from CTFd.schemas.files import FileSchema
|
||||||
from CTFd.utils import uploads
|
from CTFd.utils import uploads
|
||||||
@@ -45,9 +48,24 @@ class FilesList(Resource):
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def get(self):
|
@validate_args(
|
||||||
file_type = request.args.get("type")
|
{
|
||||||
files = Files.query.filter_by(type=file_type).all()
|
"type": (str, None),
|
||||||
|
"location": (str, None),
|
||||||
|
"q": (str, None),
|
||||||
|
"field": (
|
||||||
|
RawEnum("FileFields", {"type": "type", "location": "location"}),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
location="query",
|
||||||
|
)
|
||||||
|
def get(self, query_args):
|
||||||
|
q = query_args.pop("q", None)
|
||||||
|
field = str(query_args.pop("field", None))
|
||||||
|
filters = build_model_filters(model=Files, query=q, field=field)
|
||||||
|
|
||||||
|
files = Files.query.filter_by(**query_args).filter(*filters).all()
|
||||||
schema = FileSchema(many=True)
|
schema = FileSchema(many=True)
|
||||||
response = schema.dump(files)
|
response = schema.dump(files)
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ from typing import List
|
|||||||
from flask import request
|
from flask import request
|
||||||
from flask_restx import Namespace, Resource
|
from flask_restx import Namespace, Resource
|
||||||
|
|
||||||
|
from CTFd.api.v1.helpers.models import build_model_filters
|
||||||
|
from CTFd.api.v1.helpers.request import validate_args
|
||||||
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
||||||
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
||||||
|
from CTFd.constants import RawEnum
|
||||||
from CTFd.models import Flags, db
|
from CTFd.models import Flags, db
|
||||||
from CTFd.plugins.flags import FLAG_CLASSES, get_flag_class
|
from CTFd.plugins.flags import FLAG_CLASSES, get_flag_class
|
||||||
from CTFd.schemas.flags import FlagSchema
|
from CTFd.schemas.flags import FlagSchema
|
||||||
@@ -45,8 +48,28 @@ class FlagList(Resource):
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def get(self):
|
@validate_args(
|
||||||
flags = Flags.query.all()
|
{
|
||||||
|
"challenge_id": (int, None),
|
||||||
|
"type": (str, None),
|
||||||
|
"content": (str, None),
|
||||||
|
"data": (str, None),
|
||||||
|
"q": (str, None),
|
||||||
|
"field": (
|
||||||
|
RawEnum(
|
||||||
|
"FlagFields", {"type": "type", "content": "content", "data": "data"}
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
location="query",
|
||||||
|
)
|
||||||
|
def get(self, query_args):
|
||||||
|
q = query_args.pop("q", None)
|
||||||
|
field = str(query_args.pop("field", None))
|
||||||
|
filters = build_model_filters(model=Flags, query=q, field=field)
|
||||||
|
|
||||||
|
flags = Flags.query.filter_by(**query_args).filter(*filters).all()
|
||||||
schema = FlagSchema(many=True)
|
schema = FlagSchema(many=True)
|
||||||
response = schema.dump(flags)
|
response = schema.dump(flags)
|
||||||
if response.errors:
|
if response.errors:
|
||||||
|
|||||||
7
CTFd/api/v1/helpers/models.py
Normal file
7
CTFd/api/v1/helpers/models.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
def build_model_filters(model, query, field):
|
||||||
|
filters = []
|
||||||
|
if query:
|
||||||
|
# The field exists as an exposed column
|
||||||
|
if model.__mapper__.has_property(field):
|
||||||
|
filters.append(getattr(model, field).like("%{}%".format(query)))
|
||||||
|
return filters
|
||||||
@@ -3,8 +3,11 @@ from typing import List
|
|||||||
from flask import request
|
from flask import request
|
||||||
from flask_restx import Namespace, Resource
|
from flask_restx import Namespace, Resource
|
||||||
|
|
||||||
|
from CTFd.api.v1.helpers.models import build_model_filters
|
||||||
|
from CTFd.api.v1.helpers.request import validate_args
|
||||||
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
||||||
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
||||||
|
from CTFd.constants import RawEnum
|
||||||
from CTFd.models import Hints, HintUnlocks, db
|
from CTFd.models import Hints, HintUnlocks, db
|
||||||
from CTFd.schemas.hints import HintSchema
|
from CTFd.schemas.hints import HintSchema
|
||||||
from CTFd.utils.decorators import admins_only, authed_only, during_ctf_time_only
|
from CTFd.utils.decorators import admins_only, authed_only, during_ctf_time_only
|
||||||
@@ -45,8 +48,26 @@ class HintList(Resource):
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def get(self):
|
@validate_args(
|
||||||
hints = Hints.query.all()
|
{
|
||||||
|
"type": (str, None),
|
||||||
|
"challenge_id": (int, None),
|
||||||
|
"content": (str, None),
|
||||||
|
"cost": (int, None),
|
||||||
|
"q": (str, None),
|
||||||
|
"field": (
|
||||||
|
RawEnum("HintFields", {"type": "type", "content": "content"}),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
location="query",
|
||||||
|
)
|
||||||
|
def get(self, query_args):
|
||||||
|
q = query_args.pop("q", None)
|
||||||
|
field = str(query_args.pop("field", None))
|
||||||
|
filters = build_model_filters(model=Hints, query=q, field=field)
|
||||||
|
|
||||||
|
hints = Hints.query.filter_by(**query_args).filter(*filters).all()
|
||||||
response = HintSchema(many=True).dump(hints)
|
response = HintSchema(many=True).dump(hints)
|
||||||
|
|
||||||
if response.errors:
|
if response.errors:
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ from typing import List
|
|||||||
from flask import current_app, request
|
from flask import current_app, request
|
||||||
from flask_restx import Namespace, Resource
|
from flask_restx import Namespace, Resource
|
||||||
|
|
||||||
|
from CTFd.api.v1.helpers.models import build_model_filters
|
||||||
|
from CTFd.api.v1.helpers.request import validate_args
|
||||||
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
||||||
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
||||||
|
from CTFd.constants import RawEnum
|
||||||
from CTFd.models import Notifications, db
|
from CTFd.models import Notifications, db
|
||||||
from CTFd.schemas.notifications import NotificationSchema
|
from CTFd.schemas.notifications import NotificationSchema
|
||||||
from CTFd.utils.decorators import admins_only
|
from CTFd.utils.decorators import admins_only
|
||||||
@@ -46,8 +49,28 @@ class NotificantionList(Resource):
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def get(self):
|
@validate_args(
|
||||||
notifications = Notifications.query.all()
|
{
|
||||||
|
"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,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
location="query",
|
||||||
|
)
|
||||||
|
def get(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)
|
||||||
|
|
||||||
|
notifications = (
|
||||||
|
Notifications.query.filter_by(**query_args).filter(*filters).all()
|
||||||
|
)
|
||||||
schema = NotificationSchema(many=True)
|
schema = NotificationSchema(many=True)
|
||||||
result = schema.dump(notifications)
|
result = schema.dump(notifications)
|
||||||
if result.errors:
|
if result.errors:
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ from typing import List
|
|||||||
from flask import request
|
from flask import request
|
||||||
from flask_restx import Namespace, Resource
|
from flask_restx import Namespace, Resource
|
||||||
|
|
||||||
|
from CTFd.api.v1.helpers.models import build_model_filters
|
||||||
from CTFd.api.v1.helpers.request import validate_args
|
from CTFd.api.v1.helpers.request import validate_args
|
||||||
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
||||||
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
||||||
from CTFd.cache import clear_pages
|
from CTFd.cache import clear_pages
|
||||||
|
from CTFd.constants import RawEnum
|
||||||
from CTFd.models import Pages, db
|
from CTFd.models import Pages, db
|
||||||
from CTFd.schemas.pages import PageSchema
|
from CTFd.schemas.pages import PageSchema
|
||||||
from CTFd.utils.decorators import admins_only
|
from CTFd.utils.decorators import admins_only
|
||||||
@@ -59,11 +61,23 @@ class PageList(Resource):
|
|||||||
"draft": (bool, None),
|
"draft": (bool, None),
|
||||||
"hidden": (bool, None),
|
"hidden": (bool, None),
|
||||||
"auth_required": (bool, None),
|
"auth_required": (bool, None),
|
||||||
|
"q": (str, None),
|
||||||
|
"field": (
|
||||||
|
RawEnum(
|
||||||
|
"PageFields",
|
||||||
|
{"title": "title", "route": "route", "content": "content"},
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
location="query",
|
location="query",
|
||||||
)
|
)
|
||||||
def get(self, query):
|
def get(self, query_args):
|
||||||
pages = Pages.query.filter_by(**query).all()
|
q = query_args.pop("q", None)
|
||||||
|
field = str(query_args.pop("field", None))
|
||||||
|
filters = build_model_filters(model=Pages, query=q, field=field)
|
||||||
|
|
||||||
|
pages = Pages.query.filter_by(**query_args).filter(*filters).all()
|
||||||
schema = PageSchema(exclude=["content"], many=True)
|
schema = PageSchema(exclude=["content"], many=True)
|
||||||
response = schema.dump(pages)
|
response = schema.dump(pages)
|
||||||
if response.errors:
|
if response.errors:
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from flask import request
|
|
||||||
from flask_restx import Namespace, Resource
|
from flask_restx import Namespace, Resource
|
||||||
|
|
||||||
|
from CTFd.api.v1.helpers.models import build_model_filters
|
||||||
from CTFd.api.v1.helpers.request import validate_args
|
from CTFd.api.v1.helpers.request import validate_args
|
||||||
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
||||||
from CTFd.api.v1.schemas import (
|
from CTFd.api.v1.schemas import (
|
||||||
@@ -10,6 +10,7 @@ from CTFd.api.v1.schemas import (
|
|||||||
PaginatedAPIListSuccessResponse,
|
PaginatedAPIListSuccessResponse,
|
||||||
)
|
)
|
||||||
from CTFd.cache import clear_standings
|
from CTFd.cache import clear_standings
|
||||||
|
from CTFd.constants import RawEnum
|
||||||
from CTFd.models import Submissions, db
|
from CTFd.models import Submissions, db
|
||||||
from CTFd.schemas.submissions import SubmissionSchema
|
from CTFd.schemas.submissions import SubmissionSchema
|
||||||
from CTFd.utils.decorators import admins_only
|
from CTFd.utils.decorators import admins_only
|
||||||
@@ -52,21 +53,45 @@ class SubmissionsList(Resource):
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def get(self):
|
@validate_args(
|
||||||
args = request.args.to_dict()
|
{
|
||||||
|
"challenge_id": (int, None),
|
||||||
|
"user_id": (int, None),
|
||||||
|
"team_id": (int, None),
|
||||||
|
"ip": (str, None),
|
||||||
|
"provided": (str, None),
|
||||||
|
"type": (str, None),
|
||||||
|
"q": (str, None),
|
||||||
|
"field": (
|
||||||
|
RawEnum(
|
||||||
|
"SubmissionFields",
|
||||||
|
{
|
||||||
|
"challenge_id": "challenge_id",
|
||||||
|
"user_id": "user_id",
|
||||||
|
"team_id": "team_id",
|
||||||
|
"ip": "ip",
|
||||||
|
"provided": "provided",
|
||||||
|
"type": "type",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
location="query",
|
||||||
|
)
|
||||||
|
def get(self, query_args):
|
||||||
|
q = query_args.pop("q", None)
|
||||||
|
field = str(query_args.pop("field", None))
|
||||||
|
filters = build_model_filters(model=Submissions, query=q, field=field)
|
||||||
|
|
||||||
|
args = query_args
|
||||||
schema = SubmissionSchema(many=True)
|
schema = SubmissionSchema(many=True)
|
||||||
pagination_args = {
|
|
||||||
"per_page": int(args.pop("per_page", 50)),
|
submissions = (
|
||||||
"page": int(args.pop("page", 1)),
|
Submissions.query.filter_by(**args)
|
||||||
}
|
.filter(*filters)
|
||||||
if args:
|
.paginate(max_per_page=100)
|
||||||
submissions = Submissions.query.filter_by(**args).paginate(
|
)
|
||||||
**pagination_args, max_per_page=100
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
submissions = Submissions.query.paginate(
|
|
||||||
**pagination_args, max_per_page=100
|
|
||||||
)
|
|
||||||
|
|
||||||
response = schema.dump(submissions.items)
|
response = schema.dump(submissions.items)
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ from typing import List
|
|||||||
from flask import request
|
from flask import request
|
||||||
from flask_restx import Namespace, Resource
|
from flask_restx import Namespace, Resource
|
||||||
|
|
||||||
|
from CTFd.api.v1.helpers.models import build_model_filters
|
||||||
|
from CTFd.api.v1.helpers.request import validate_args
|
||||||
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
||||||
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
||||||
|
from CTFd.constants import RawEnum
|
||||||
from CTFd.models import Tags, db
|
from CTFd.models import Tags, db
|
||||||
from CTFd.schemas.tags import TagSchema
|
from CTFd.schemas.tags import TagSchema
|
||||||
from CTFd.utils.decorators import admins_only
|
from CTFd.utils.decorators import admins_only
|
||||||
@@ -42,9 +45,26 @@ class TagList(Resource):
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def get(self):
|
@validate_args(
|
||||||
# TODO: Filter by challenge_id
|
{
|
||||||
tags = Tags.query.all()
|
"challenge_id": (int, None),
|
||||||
|
"value": (str, None),
|
||||||
|
"q": (str, None),
|
||||||
|
"field": (
|
||||||
|
RawEnum(
|
||||||
|
"TagFields", {"challenge_id": "challenge_id", "value": "value"}
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
location="query",
|
||||||
|
)
|
||||||
|
def get(self, query_args):
|
||||||
|
q = query_args.pop("q", None)
|
||||||
|
field = str(query_args.pop("field", None))
|
||||||
|
filters = build_model_filters(model=Tags, query=q, field=field)
|
||||||
|
|
||||||
|
tags = Tags.query.filter_by(**query_args).filter(*filters).all()
|
||||||
schema = TagSchema(many=True)
|
schema = TagSchema(many=True)
|
||||||
response = schema.dump(tags)
|
response = schema.dump(tags)
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,15 @@ from typing import List
|
|||||||
from flask import abort, request, session
|
from flask import abort, request, session
|
||||||
from flask_restx import Namespace, Resource
|
from flask_restx import Namespace, Resource
|
||||||
|
|
||||||
|
from CTFd.api.v1.helpers.models import build_model_filters
|
||||||
|
from CTFd.api.v1.helpers.request import validate_args
|
||||||
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
||||||
from CTFd.api.v1.schemas import (
|
from CTFd.api.v1.schemas import (
|
||||||
APIDetailedSuccessResponse,
|
APIDetailedSuccessResponse,
|
||||||
PaginatedAPIListSuccessResponse,
|
PaginatedAPIListSuccessResponse,
|
||||||
)
|
)
|
||||||
from CTFd.cache import clear_standings, clear_team_session, clear_user_session
|
from CTFd.cache import clear_standings, clear_team_session, clear_user_session
|
||||||
|
from CTFd.constants import RawEnum
|
||||||
from CTFd.models import Awards, Submissions, Teams, Unlocks, Users, db
|
from CTFd.models import Awards, Submissions, Teams, Unlocks, Users, db
|
||||||
from CTFd.schemas.awards import AwardSchema
|
from CTFd.schemas.awards import AwardSchema
|
||||||
from CTFd.schemas.submissions import SubmissionSchema
|
from CTFd.schemas.submissions import SubmissionSchema
|
||||||
@@ -57,12 +60,44 @@ class TeamList(Resource):
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def get(self):
|
@validate_args(
|
||||||
|
{
|
||||||
|
"affiliation": (str, None),
|
||||||
|
"country": (str, None),
|
||||||
|
"bracket": (str, None),
|
||||||
|
"q": (str, None),
|
||||||
|
"field": (
|
||||||
|
RawEnum(
|
||||||
|
"TeamFields",
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"website": "website",
|
||||||
|
"country": "country",
|
||||||
|
"bracket": "bracket",
|
||||||
|
"affiliation": "affiliation",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
location="query",
|
||||||
|
)
|
||||||
|
def get(self, query_args):
|
||||||
|
q = query_args.pop("q", None)
|
||||||
|
field = str(query_args.pop("field", None))
|
||||||
|
filters = build_model_filters(model=Teams, query=q, field=field)
|
||||||
|
|
||||||
if is_admin() and request.args.get("view") == "admin":
|
if is_admin() and request.args.get("view") == "admin":
|
||||||
teams = Teams.query.filter_by().paginate(per_page=50, max_per_page=100)
|
teams = (
|
||||||
|
Teams.query.filter_by(**query_args)
|
||||||
|
.filter(*filters)
|
||||||
|
.paginate(per_page=50, max_per_page=100)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
teams = Teams.query.filter_by(hidden=False, banned=False).paginate(
|
teams = (
|
||||||
per_page=50, max_per_page=100
|
Teams.query.filter_by(hidden=False, banned=False, **query_args)
|
||||||
|
.filter(*filters)
|
||||||
|
.paginate(per_page=50, max_per_page=100)
|
||||||
)
|
)
|
||||||
|
|
||||||
user_type = get_current_user_type(fallback="user")
|
user_type = get_current_user_type(fallback="user")
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ from typing import List
|
|||||||
from flask import request
|
from flask import request
|
||||||
from flask_restx import Namespace, Resource
|
from flask_restx import Namespace, Resource
|
||||||
|
|
||||||
|
from CTFd.api.v1.helpers.models import build_model_filters
|
||||||
|
from CTFd.api.v1.helpers.request import validate_args
|
||||||
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
||||||
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
||||||
from CTFd.cache import clear_standings
|
from CTFd.cache import clear_standings
|
||||||
|
from CTFd.constants import RawEnum
|
||||||
from CTFd.models import Unlocks, db, get_class_by_tablename
|
from CTFd.models import Unlocks, db, get_class_by_tablename
|
||||||
from CTFd.schemas.awards import AwardSchema
|
from CTFd.schemas.awards import AwardSchema
|
||||||
from CTFd.schemas.unlocks import UnlockSchema
|
from CTFd.schemas.unlocks import UnlockSchema
|
||||||
@@ -53,10 +56,28 @@ class UnlockList(Resource):
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def get(self):
|
@validate_args(
|
||||||
hints = Unlocks.query.all()
|
{
|
||||||
|
"user_id": (int, None),
|
||||||
|
"team_id": (int, None),
|
||||||
|
"target": (int, None),
|
||||||
|
"type": (str, None),
|
||||||
|
"q": (str, None),
|
||||||
|
"field": (
|
||||||
|
RawEnum("UnlockFields", {"target": "target", "type": "type"}),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
location="query",
|
||||||
|
)
|
||||||
|
def get(self, query_args):
|
||||||
|
q = query_args.pop("q", None)
|
||||||
|
field = str(query_args.pop("field", None))
|
||||||
|
filters = build_model_filters(model=Unlocks, query=q, field=field)
|
||||||
|
|
||||||
|
unlocks = Unlocks.query.filter_by(**query_args).filter(*filters).all()
|
||||||
schema = UnlockSchema()
|
schema = UnlockSchema()
|
||||||
response = schema.dump(hints)
|
response = schema.dump(unlocks)
|
||||||
|
|
||||||
if response.errors:
|
if response.errors:
|
||||||
return {"success": False, "errors": response.errors}, 400
|
return {"success": False, "errors": response.errors}, 400
|
||||||
|
|||||||
@@ -3,12 +3,15 @@ from typing import List
|
|||||||
from flask import abort, request
|
from flask import abort, request
|
||||||
from flask_restx import Namespace, Resource
|
from flask_restx import Namespace, Resource
|
||||||
|
|
||||||
|
from CTFd.api.v1.helpers.models import build_model_filters
|
||||||
|
from CTFd.api.v1.helpers.request import validate_args
|
||||||
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
||||||
from CTFd.api.v1.schemas import (
|
from CTFd.api.v1.schemas import (
|
||||||
APIDetailedSuccessResponse,
|
APIDetailedSuccessResponse,
|
||||||
PaginatedAPIListSuccessResponse,
|
PaginatedAPIListSuccessResponse,
|
||||||
)
|
)
|
||||||
from CTFd.cache import clear_standings, clear_user_session
|
from CTFd.cache import clear_standings, clear_user_session
|
||||||
|
from CTFd.constants import RawEnum
|
||||||
from CTFd.models import (
|
from CTFd.models import (
|
||||||
Awards,
|
Awards,
|
||||||
Notifications,
|
Notifications,
|
||||||
@@ -69,12 +72,44 @@ class UserList(Resource):
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def get(self):
|
@validate_args(
|
||||||
|
{
|
||||||
|
"affiliation": (str, None),
|
||||||
|
"country": (str, None),
|
||||||
|
"bracket": (str, None),
|
||||||
|
"q": (str, None),
|
||||||
|
"field": (
|
||||||
|
RawEnum(
|
||||||
|
"UserFields",
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"website": "website",
|
||||||
|
"country": "country",
|
||||||
|
"bracket": "bracket",
|
||||||
|
"affiliation": "affiliation",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
location="query",
|
||||||
|
)
|
||||||
|
def get(self, query_args):
|
||||||
|
q = query_args.pop("q", None)
|
||||||
|
field = str(query_args.pop("field", None))
|
||||||
|
filters = build_model_filters(model=Users, query=q, field=field)
|
||||||
|
|
||||||
if is_admin() and request.args.get("view") == "admin":
|
if is_admin() and request.args.get("view") == "admin":
|
||||||
users = Users.query.filter_by().paginate(per_page=50, max_per_page=100)
|
users = (
|
||||||
|
Users.query.filter_by(**query_args)
|
||||||
|
.filter(*filters)
|
||||||
|
.paginate(per_page=50, max_per_page=100)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
users = Users.query.filter_by(banned=False, hidden=False).paginate(
|
users = (
|
||||||
per_page=50, max_per_page=100
|
Users.query.filter_by(banned=False, hidden=False, **query_args)
|
||||||
|
.filter(*filters)
|
||||||
|
.paginate(per_page=50, max_per_page=100)
|
||||||
)
|
)
|
||||||
|
|
||||||
response = UserSchema(view="user", many=True).dump(users.items)
|
response = UserSchema(view="user", many=True).dump(users.items)
|
||||||
|
|||||||
Reference in New Issue
Block a user