diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py index b7172f96f3dce964059251b7b75a20d273be2133..5d6e45dc59e00258231b4975370e620c0ec108b7 100644 --- a/server/app/apis/__init__.py +++ b/server/app/apis/__init__.py @@ -43,6 +43,7 @@ def item_response(item, code=codes.OK): from flask_restx import Api from .auth import api as auth_ns +from .codes import api as code_ns from .competitions import api as comp_ns from .components import api as component_ns from .media import api as media_ns @@ -60,5 +61,6 @@ flask_api.add_namespace(auth_ns, path="/api/auth") flask_api.add_namespace(comp_ns, path="/api/competitions") flask_api.add_namespace(slide_ns, path="/api/competitions/<CID>/slides") flask_api.add_namespace(team_ns, path="/api/competitions/<CID>/teams") +flask_api.add_namespace(code_ns, path="/api/competitions/<CID>/codes") flask_api.add_namespace(question_ns, path="/api/competitions/<CID>") flask_api.add_namespace(component_ns, path="/api/competitions/<CID>/slides/<SID>/components/") diff --git a/server/app/apis/auth.py b/server/app/apis/auth.py index 1510df642682f2d00d94bb695af45e7fb1cf24f4..10d820f88d1a5570635a05e54a2ef45492a1c645 100644 --- a/server/app/apis/auth.py +++ b/server/app/apis/auth.py @@ -1,7 +1,8 @@ import app.core.http_codes as codes import app.database.controller as dbc from app.apis import admin_required, item_response, text_response -from app.core.dto import AuthDTO +from app.core.codes import verify_code +from app.core.dto import AuthDTO, CodeDTO from app.core.parsers import create_user_parser, login_parser from app.database.models import User from flask_jwt_extended import ( @@ -69,6 +70,20 @@ class AuthLogin(Resource): return response +@api.route("/login/<code>") +@api.param("code") +class AuthLogin(Resource): + def post(self, code): + if not verify_code(code): + api.abort(codes.BAD_REQUEST, "Invalid code") + + item_code = dbc.get.code_by_code(code) + if not item_code: + api.abort(codes.UNAUTHORIZED, "A presentation with that code does not exist") + + return item_response(CodeDTO.schema.dump(item_code)), codes.OK + + @api.route("/logout") class AuthLogout(Resource): @jwt_required diff --git a/server/app/apis/codes.py b/server/app/apis/codes.py new file mode 100644 index 0000000000000000000000000000000000000000..70cf934166d81d95bd7b2f7f1b8f2ecc5621feb5 --- /dev/null +++ b/server/app/apis/codes.py @@ -0,0 +1,44 @@ +import app.database.controller as dbc +from app.apis import admin_required, item_response, list_response +from app.core import http_codes as codes +from app.core.codes import generate_code +from app.core.dto import CodeDTO +from app.core.parsers import code_parser +from app.database.models import Competition +from flask_jwt_extended import jwt_required +from flask_restx import Resource + +api = CodeDTO.api +schema = CodeDTO.schema +list_schema = CodeDTO.list_schema + + +@api.route("/") +@api.param("CID") +class CodesList(Resource): + @jwt_required + def get(self, CID): + items = dbc.get.code_list(CID) + return list_response(list_schema.dump(items), len(items)), codes.OK + + @jwt_required + def post(self, CID): + args = code_parser.parse_args(strict=True) + item = dbc.add.code(**args) + return item_response(schema.dump(item)), codes.OK + + +@api.route("/<code_id>") +@api.param("CID, code_id") +class Competitions(Resource): + @jwt_required + def put(self, CID, code_id): + item_code = dbc.get.code(code_id) + item_code = dbc.edit.generate_new_code(item_code) + return item_response(schema.dump(item_code)), codes.OK + + # @jwt_required + # def delete(self, CID, code_id): + # item_code = dbc.get.code(code_id) + # dbc.delete.code(item_code) + # return {}, http_codes.NOT_FOUND diff --git a/server/app/core/codes.py b/server/app/core/codes.py new file mode 100644 index 0000000000000000000000000000000000000000..c13fa84e9e1ab8418cabad2743a94148b8554651 --- /dev/null +++ b/server/app/core/codes.py @@ -0,0 +1,15 @@ +import random +import re +import string + +CODE_LENGTH = 6 +ALLOWED_CHARS = string.ascii_uppercase + string.digits +CODE_RE = re.compile(f"^[{ALLOWED_CHARS}]{{{CODE_LENGTH}}}$") + + +def generate_code(): + return "".join(random.choices(ALLOWED_CHARS, k=CODE_LENGTH)) + + +def verify_code(c): + return CODE_RE.search(c.upper()) is not None diff --git a/server/app/core/dto.py b/server/app/core/dto.py index a6899fc7e681d55010ba23b382fd2155954d507d..4533fc0caa874ef2ed20293bec1ada7ce05342fe 100644 --- a/server/app/core/dto.py +++ b/server/app/core/dto.py @@ -37,6 +37,12 @@ class CompetitionDTO: list_schema = schemas.CompetitionSchema(many=True) +class CodeDTO: + api = Namespace("codes") + schema = rich_schemas.CodeSchemaRich(many=False) + list_schema = schemas.CodeSchema(many=True) + + class SlideDTO: api = Namespace("slides") schema = schemas.SlideSchema(many=False) diff --git a/server/app/core/parsers.py b/server/app/core/parsers.py index 54f54239c61f4b81c7f6903d70e6d2c38b9d1ca7..f691ea8d3924a8679068713bf85ca47e2d065376 100644 --- a/server/app/core/parsers.py +++ b/server/app/core/parsers.py @@ -60,6 +60,12 @@ question_parser.add_argument("total_score", type=int, default=None, location="js question_parser.add_argument("type_id", type=int, default=None, location="json") question_parser.add_argument("slide_id", type=int, location="json") +###QUESTION#### +code_parser = reqparse.RequestParser() +code_parser.add_argument("pointer", type=str, default=None, location="json") +code_parser.add_argument("view_type_id", type=int, default=None, location="json") + + ###TEAM#### team_parser = reqparse.RequestParser() team_parser.add_argument("name", type=str, location="json") diff --git a/server/app/core/rich_schemas.py b/server/app/core/rich_schemas.py index 8c220e780e176c4505ebe1c625e6027e25837077..fa6daac9ef59117b76ab9d2244f80af3224dbbe5 100644 --- a/server/app/core/rich_schemas.py +++ b/server/app/core/rich_schemas.py @@ -43,6 +43,16 @@ class TeamSchemaRich(RichSchema): question_answers = fields.Nested(schemas.QuestionAnswerSchema, many=True) +class CodeSchemaRich(RichSchema): + class Meta(RichSchema.Meta): + model = models.Code + + id = ma.auto_field() + code = ma.auto_field() + pointer = ma.auto_field() + view_type = fields.Nested(schemas.ViewTypeSchema, many=False) + + class SlideSchemaRich(RichSchema): class Meta(RichSchema.Meta): model = models.Slide diff --git a/server/app/core/schemas.py b/server/app/core/schemas.py index ee909e4b11392386e1fa806aaf7c0481eb4a313b..4f9646a55e2e64d9498f96589b59886824845a45 100644 --- a/server/app/core/schemas.py +++ b/server/app/core/schemas.py @@ -31,6 +31,16 @@ class ComponentTypeSchema(IdNameSchema): model = models.ComponentType +class CodeSchema(IdNameSchema): + class Meta(BaseSchema.Meta): + model = models.Code + + id = ma.auto_field() + code = ma.auto_field() + pointer = ma.auto_field() + view_type_id = ma.auto_field() + + class ViewTypeSchema(IdNameSchema): class Meta(BaseSchema.Meta): model = models.ViewType diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py index 0e955a9e09a1a26032566ebfabb3133dd0a8359e..07b49fde49e1a7a13704f07ca8ef25555635bdfd 100644 --- a/server/app/database/controller/add.py +++ b/server/app/database/controller/add.py @@ -1,8 +1,10 @@ import app.core.http_codes as codes from app.core import db +from app.core.codes import generate_code from app.database.models import ( Blacklist, City, + Code, Competition, Component, ComponentType, @@ -75,6 +77,11 @@ def team(name, item_competition): return Team(name, item_competition.id) +@db_add +def code(pointer, view_type_id): + return Code(pointer, view_type_id) + + @db_add def mediaType(name): return MediaType(name) diff --git a/server/app/database/controller/edit.py b/server/app/database/controller/edit.py index 3afcb4d45c8ba0c0f9cfdaab83d88b286f566215..7b3ef334427e1bf561a5ab5f4e1bc9f25e9c7c68 100644 --- a/server/app/database/controller/edit.py +++ b/server/app/database/controller/edit.py @@ -1,4 +1,6 @@ from app.core import db +from app.core.codes import generate_code +from app.database.models import Code def switch_order(item1, item2): @@ -109,3 +111,16 @@ def question(item_question, name=None, total_score=None, type_id=None, slide_id= db.session.refresh(item_question) return item_question + + +def generate_new_code(item_code): + + code = generate_code() + while db.session.query(Code).filter(Code.code == code).count(): + code = generate_code() + + item_code.code = code + db.session.commit() + db.session.refresh(item_code) + + return item_code diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py index 1669f0ae3c1fdd2eb9125586a84d070eacb14acc..4e9a6aa5b337a995f2d255a46581c7ec82233e26 100644 --- a/server/app/database/controller/get.py +++ b/server/app/database/controller/get.py @@ -1,6 +1,7 @@ from app.core import db from app.database.models import ( City, + Code, Competition, Component, ComponentType, @@ -11,8 +12,11 @@ from app.database.models import ( Slide, Team, User, + ViewType, ) +team_view_id = ViewType.query.filter(ViewType.name == "Team").one().id + def all(db_type): return db_type.query.all() @@ -34,6 +38,25 @@ def competition(CID, required=True, error_msg=None): return Competition.query.filter(Competition.id == CID).first_extended(required, error_msg) +def code(code_id, required=True, error_msg=None): + return Code.query.filter(Code.id == code_id).first_extended(required, error_msg) + + +def code_by_code(code): + return Code.query.filter(Code.code == code.upper()).first() + + +def code_list(competition_id): + return ( + Code.query.join(Team, (Code.view_type_id == team_view_id) & (Team.id == Code.pointer), isouter=True) + .filter( + ((Code.view_type_id != team_view_id) & (Code.pointer == competition_id)) + | ((Code.view_type_id == team_view_id) & (competition_id == Team.competition_id)) + ) + .all() + ) + + def user(UID, required=True, error_msg=None): return User.query.filter(User.id == UID).first_extended(required, error_msg) diff --git a/server/app/database/models.py b/server/app/database/models.py index 6396e035fb615449ca29940322457d3739dd462e..9479d92cf6909f09341e4432f1c08071418bc94d 100644 --- a/server/app/database/models.py +++ b/server/app/database/models.py @@ -1,6 +1,7 @@ import json from app.core import bcrypt, db +from app.core.codes import generate_code from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property from sqlalchemy.orm import backref from sqlalchemy.types import TypeDecorator @@ -230,7 +231,12 @@ class Code(db.Model): view_type_id = db.Column(db.Integer, db.ForeignKey("view_type.id"), nullable=False) - def __init__(self, code, pointer, view_type_id): + def __init__(self, pointer, view_type_id): + + code = generate_code() + while db.session.query(Code).filter(Code.code == code).count(): + code = generate_code() + self.code = code self.pointer = pointer self.view_type_id = view_type_id