Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • tddd96-grupp11/teknikattan-scoring-system
1 result
Show changes
Showing
with 474 additions and 404 deletions
......@@ -74,10 +74,12 @@ flask_api.add_namespace(misc_ns, path="/api/misc")
flask_api.add_namespace(user_ns, path="/api/users")
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(alternative_ns, path="/api/competitions/<CID>/slides/<SOrder>/questions/<QID>/alternatives")
flask_api.add_namespace(answer_ns, path="/api/competitions/<CID>/teams/<TID>/answers")
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/<SOrder>/components")
flask_api.add_namespace(slide_ns, path="/api/competitions/<competition_id>/slides")
flask_api.add_namespace(
alternative_ns, path="/api/competitions/<competition_id>/slides/<slide_id>/questions/<question_id>/alternatives"
)
flask_api.add_namespace(answer_ns, path="/api/competitions/<competition_id>/teams/<team_id>/answers")
flask_api.add_namespace(team_ns, path="/api/competitions/<competition_id>/teams")
flask_api.add_namespace(code_ns, path="/api/competitions/<competition_id>/codes")
flask_api.add_namespace(question_ns, path="/api/competitions/<competition_id>")
flask_api.add_namespace(component_ns, path="/api/competitions/<competition_id>/slides/<slide_id>/components")
......@@ -4,8 +4,6 @@ from app.apis import check_jwt, item_response, list_response
from app.core.dto import QuestionAlternativeDTO, QuestionDTO
from app.core.parsers import question_alternative_parser
from app.core.schemas import QuestionAlternativeSchema
from app.database.controller.add import question_alternative
from app.database.controller.get import question_alternatives
from app.database.models import Question, QuestionAlternative
from flask_jwt_extended import jwt_required
from flask_restx import Resource
......@@ -16,32 +14,37 @@ list_schema = QuestionAlternativeDTO.list_schema
@api.route("/")
@api.param("CID, SOrder, QID")
@api.param("competition_id, slide_id, question_id")
class QuestionAlternativeList(Resource):
@check_jwt(editor=True)
def get(self, CID, SOrder, QID):
items = dbc.get.question_alternatives(QID)
def get(self, competition_id, slide_id, question_id):
items = dbc.get.question_alternative_list(competition_id, slide_id, question_id)
return list_response(list_schema.dump(items))
@check_jwt(editor=True)
def post(self, CID, SOrder, QID):
def post(self, competition_id, slide_id, question_id):
args = question_alternative_parser.parse_args(strict=True)
item = dbc.add.question_alternative(**args, question_id=QID)
item = dbc.add.question_alternative(**args, question_id=question_id)
return item_response(schema.dump(item))
@api.route("/<AID>")
@api.param("CID, SOrder, QID, AID")
@api.route("/<alternative_id>")
@api.param("competition_id, slide_id, question_id, alternative_id")
class QuestionAlternatives(Resource):
@check_jwt(editor=True)
def put(self, CID, SOrder, QID, AID):
def get(self, competition_id, slide_id, question_id, alternative_id):
items = dbc.get.question_alternative(competition_id, slide_id, question_id, alternative_id)
return item_response(schema.dump(items))
@check_jwt(editor=True)
def put(self, competition_id, slide_id, question_id, alternative_id):
args = question_alternative_parser.parse_args(strict=True)
item = dbc.get.one(QuestionAlternative, AID)
item = dbc.edit.question_alternative(item, **args)
item = dbc.get.question_alternative(competition_id, slide_id, question_id, alternative_id)
item = dbc.edit.default(item, **args)
return item_response(schema.dump(item))
@check_jwt(editor=True)
def delete(self, CID, SOrder, QID, AID):
item = dbc.get.one(QuestionAlternative, AID)
def delete(self, competition_id, slide_id, question_id, alternative_id):
item = dbc.get.question_alternative(competition_id, slide_id, question_id, alternative_id)
dbc.delete.default(item)
return {}, codes.NO_CONTENT
......@@ -4,8 +4,6 @@ from app.apis import check_jwt, item_response, list_response
from app.core.dto import QuestionAnswerDTO
from app.core.parsers import question_answer_edit_parser, question_answer_parser
from app.core.schemas import QuestionAlternativeSchema
from app.database.controller.add import question_alternative
from app.database.controller.get import question_alternatives
from app.database.models import Question, QuestionAlternative, QuestionAnswer
from flask_jwt_extended import jwt_required
from flask_restx import Resource
......@@ -16,26 +14,31 @@ list_schema = QuestionAnswerDTO.list_schema
@api.route("/")
@api.param("CID, TID")
@api.param("competition_id, team_id")
class QuestionAnswerList(Resource):
@check_jwt(editor=True)
def get(self, CID, TID):
items = dbc.get.question_answers(TID)
def get(self, competition_id, team_id):
items = dbc.get.question_answer_list(competition_id, team_id)
return list_response(list_schema.dump(items))
@check_jwt(editor=True)
def post(self, CID, TID):
def post(self, competition_id, team_id):
args = question_answer_parser.parse_args(strict=True)
item = dbc.add.question_answer(**args, team_id=TID)
item = dbc.add.question_answer(**args, team_id=team_id)
return item_response(schema.dump(item))
@api.route("/<AID>")
@api.param("CID, TID, AID")
@api.route("/<answer_id>")
@api.param("competition_id, team_id, answer_id")
class QuestionAnswers(Resource):
@check_jwt(editor=True)
def put(self, CID, TID, AID):
def get(self, competition_id, team_id, answer_id):
item = dbc.get.question_answer(competition_id, team_id, answer_id)
return item_response(schema.dump(item))
@check_jwt(editor=True)
def put(self, competition_id, team_id, answer_id):
args = question_answer_edit_parser.parse_args(strict=True)
item = dbc.get.one(QuestionAnswer, AID)
item = dbc.edit.question_answer(item, **args)
item = dbc.get.question_answer(competition_id, team_id, answer_id)
item = dbc.edit.default(item, **args)
return item_response(schema.dump(item))
......@@ -58,7 +58,7 @@ class AuthLogin(Resource):
args = login_parser.parse_args(strict=True)
email = args.get("email")
password = args.get("password")
item_user = dbc.get.user_by_email(email, required=False)
item_user = dbc.get.user_by_email(email)
if not item_user or not item_user.is_correct_password(password):
api.abort(codes.UNAUTHORIZED, "Invalid email or password")
......
import app.database.controller as dbc
from app.apis import item_response, list_response
from app.apis import check_jwt, item_response, list_response
from app.core import http_codes as codes
from app.core.dto import CodeDTO
from app.core.parsers import code_parser
from app.database.models import Code, Competition
from flask_jwt_extended import jwt_required
from flask_restx import Resource
from app.apis import check_jwt
api = CodeDTO.api
schema = CodeDTO.schema
......@@ -14,19 +13,19 @@ list_schema = CodeDTO.list_schema
@api.route("/")
@api.param("CID")
@api.param("competition_id")
class CodesList(Resource):
@check_jwt(editor=True)
def get(self, CID):
items = dbc.get.code_list(CID)
def get(self, competition_id):
items = dbc.get.code_list(competition_id)
return list_response(list_schema.dump(items), len(items)), codes.OK
@api.route("/<code_id>")
@api.param("CID, code_id")
@api.param("competition_id, code_id")
class CodesById(Resource):
@check_jwt(editor=False)
def put(self, CID, code_id):
def put(self, competition_id, code_id):
item = dbc.get.one(Code, code_id)
item.code = dbc.utils.generate_unique_code()
dbc.utils.commit_and_refresh(item)
......
......@@ -25,30 +25,30 @@ class CompetitionsList(Resource):
item = dbc.add.competition(**args)
# Add default slide
dbc.add.slide(item)
# dbc.add.slide(item.id)
return item_response(schema.dump(item))
@api.route("/<CID>")
@api.param("CID")
@api.route("/<competition_id>")
@api.param("competition_id")
class Competitions(Resource):
@check_jwt(editor=True)
def get(self, CID):
item = dbc.get.competition(CID)
def get(self, competition_id):
item = dbc.get.competition(competition_id)
return item_response(rich_schema.dump(item))
@check_jwt(editor=True)
def put(self, CID):
def put(self, competition_id):
args = competition_parser.parse_args(strict=True)
item = dbc.get.one(Competition, CID)
item = dbc.edit.competition(item, **args)
item = dbc.get.one(Competition, competition_id)
item = dbc.edit.default(item, **args)
return item_response(schema.dump(item))
@check_jwt(editor=True)
def delete(self, CID):
item = dbc.get.one(Competition, CID)
def delete(self, competition_id):
item = dbc.get.one(Competition, competition_id)
dbc.delete.competition(item)
return "deleted"
......@@ -63,12 +63,12 @@ class CompetitionSearch(Resource):
return list_response(list_schema.dump(items), total)
@api.route("/<CID>/copy")
@api.param("CID")
@api.route("/<competition_id>/copy")
@api.param("competition_id")
class SlidesOrder(Resource):
@check_jwt(editor=True)
def post(self, CID):
item_competition = dbc.get.competition(CID)
def post(self, competition_id):
item_competition = dbc.get.competition(competition_id)
item_competition_copy = dbc.copy.competition(item_competition)
......
......@@ -14,38 +14,37 @@ list_schema = ComponentDTO.list_schema
@api.route("/<component_id>")
@api.param("CID, SOrder, component_id")
@api.param("competition_id, slide_id, component_id")
class ComponentByID(Resource):
@check_jwt(editor=True)
def get(self, CID, SOrder, component_id):
item = dbc.get.one(Component, component_id)
def get(self, competition_id, slide_id, component_id):
item = dbc.get.component(competition_id, slide_id, component_id)
return item_response(schema.dump(item))
@check_jwt(editor=True)
def put(self, CID, SOrder, component_id):
def put(self, competition_id, slide_id, component_id):
args = component_parser.parse_args()
item = dbc.get.one(Component, component_id)
item = dbc.edit.component(item, **args)
item = dbc.get.component(competition_id, slide_id, component_id)
item = dbc.edit.default(item, **args)
return item_response(schema.dump(item))
@check_jwt(editor=True)
def delete(self, CID, SOrder, component_id):
item = dbc.get.one(Component, component_id)
def delete(self, competition_id, slide_id, component_id):
item = dbc.get.component(competition_id, slide_id, component_id)
dbc.delete.component(item)
return {}, codes.NO_CONTENT
@api.route("/")
@api.param("CID, SOrder")
@api.param("competition_id, slide_id")
class ComponentList(Resource):
@check_jwt(editor=True)
def get(self, CID, SOrder):
items = dbc.get.component_list(CID, SOrder)
def get(self, competition_id, slide_id):
items = dbc.get.component_list(competition_id, slide_id)
return list_response(list_schema.dump(items))
@check_jwt(editor=True)
def post(self, CID, SOrder):
def post(self, competition_id, slide_id):
args = component_create_parser.parse_args()
item_slide = dbc.get.slide(CID, SOrder)
item = dbc.add.component(item_slide=item_slide, **args)
item = dbc.add.component(slide_id=slide_id, **args)
return item_response(schema.dump(item))
......@@ -13,47 +13,48 @@ list_schema = QuestionDTO.list_schema
@api.route("/questions")
@api.param("CID")
@api.param("competition_id")
class QuestionList(Resource):
@check_jwt(editor=True)
def get(self, CID):
items = dbc.get.question_list(CID)
def get(self, competition_id):
items = dbc.get.question_list_for_competition(competition_id)
return list_response(list_schema.dump(items))
@api.route("/slides/<SID>/questions")
@api.param("CID, SID")
@api.route("/slides/<slide_id>/questions")
@api.param("competition_id, slide_id")
class QuestionListForSlide(Resource):
@check_jwt(editor=True)
def post(self, SID, CID):
args = question_parser.parse_args(strict=True)
del args["slide_id"]
item_slide = dbc.get.slide(CID, SID)
item = dbc.add.question(item_slide=item_slide, **args)
def get(self, competition_id, slide_id):
items = dbc.get.question_list(competition_id, slide_id)
return list_response(list_schema.dump(items))
@check_jwt(editor=True)
def post(self, competition_id, slide_id):
args = question_parser.parse_args(strict=True)
item = dbc.add.question(slide_id=slide_id, **args)
return item_response(schema.dump(item))
@api.route("/slides/<SID>/questions/<QID>")
@api.param("CID, SID, QID")
@api.route("/slides/<slide_id>/questions/<question_id>")
@api.param("competition_id, slide_id, question_id")
class QuestionById(Resource):
@check_jwt(editor=True)
def get(self, CID, SID, QID):
item_question = dbc.get.question(CID, SID, QID)
def get(self, competition_id, slide_id, question_id):
item_question = dbc.get.question(competition_id, slide_id, question_id)
return item_response(schema.dump(item_question))
@check_jwt(editor=True)
def put(self, CID, SID, QID):
def put(self, competition_id, slide_id, question_id):
args = question_parser.parse_args(strict=True)
item_question = dbc.get.question(CID, SID, QID)
item_question = dbc.edit.question(item_question, **args)
item_question = dbc.get.question(competition_id, slide_id, question_id)
item_question = dbc.edit.default(item_question, **args)
return item_response(schema.dump(item_question))
@check_jwt(editor=True)
def delete(self, CID, SID, QID):
item_question = dbc.get.question(CID, SID, QID)
def delete(self, competition_id, slide_id, question_id):
item_question = dbc.get.question(competition_id, slide_id, question_id)
dbc.delete.question(item_question)
return {}, codes.NO_CONTENT
......@@ -13,84 +13,79 @@ list_schema = SlideDTO.list_schema
@api.route("/")
@api.param("CID")
@api.param("competition_id")
class SlidesList(Resource):
@check_jwt(editor=True)
def get(self, CID):
items = dbc.get.slide_list(CID)
def get(self, competition_id):
items = dbc.get.slide_list(competition_id)
return list_response(list_schema.dump(items))
@check_jwt(editor=True)
def post(self, CID):
item_comp = dbc.get.one(Competition, CID)
item_slide = dbc.add.slide(item_comp)
dbc.add.question(f"Fråga {item_slide.order + 1}", 10, 0, item_slide)
dbc.utils.refresh(item_comp)
return list_response(list_schema.dump(item_comp.slides))
def post(self, competition_id):
item_slide = dbc.add.slide(competition_id)
return item_response(schema.dump(item_slide))
@api.route("/<SOrder>")
@api.param("CID,SOrder")
@api.route("/<slide_id>")
@api.param("competition_id,slide_id")
class Slides(Resource):
@check_jwt(editor=True)
def get(self, CID, SOrder):
item_slide = dbc.get.slide(CID, SOrder)
def get(self, competition_id, slide_id):
item_slide = dbc.get.slide(competition_id, slide_id)
return item_response(schema.dump(item_slide))
@check_jwt(editor=True)
def put(self, CID, SOrder):
def put(self, competition_id, slide_id):
args = slide_parser.parse_args(strict=True)
title = args.get("title")
timer = args.get("timer")
item_slide = dbc.get.slide(CID, SOrder)
item_slide = dbc.edit.slide(item_slide, title, timer)
item_slide = dbc.get.slide(competition_id, slide_id)
item_slide = dbc.edit.default(item_slide, **args)
return item_response(schema.dump(item_slide))
@check_jwt(editor=True)
def delete(self, CID, SOrder):
item_slide = dbc.get.slide(CID, SOrder)
def delete(self, competition_id, slide_id):
item_slide = dbc.get.slide(competition_id, slide_id)
dbc.delete.slide(item_slide)
return {}, codes.NO_CONTENT
@api.route("/<SOrder>/order")
@api.param("CID,SOrder")
class SlidesOrder(Resource):
@api.route("/<slide_id>/order")
@api.param("competition_id,slide_id")
class SlideOrder(Resource):
@check_jwt(editor=True)
def put(self, CID, SOrder):
def put(self, competition_id, slide_id):
args = slide_parser.parse_args(strict=True)
order = args.get("order")
item_slide = dbc.get.slide(CID, SOrder)
item_slide = dbc.get.slide(competition_id, slide_id)
if order == item_slide.order:
return item_response(schema.dump(item_slide))
# clamp order between 0 and max
order_count = dbc.get.slide_count(CID)
order_count = dbc.get.slide_count(competition_id)
if order < 0:
order = 0
elif order >= order_count - 1:
order = order_count - 1
# get slide at the requested order
item_slide_order = dbc.get.slide(CID, order)
item_slide_id = dbc.get.slide(competition_id, order)
# switch place between them
item_slide = dbc.edit.switch_order(item_slide, item_slide_order)
item_slide = dbc.edit.switch_order(item_slide, item_slide_id)
return item_response(schema.dump(item_slide))
@api.route("/<SOrder>/copy")
@api.param("CID,SOrder")
class SlidesOrder(Resource):
@api.route("/<slide_id>/copy")
@api.param("competition_id,slide_id")
class SlideCopy(Resource):
@check_jwt(editor=True)
def post(self, CID, SOrder):
item_slide = dbc.get.slide(CID, SOrder)
def post(self, competition_id, slide_id):
item_slide = dbc.get.slide(competition_id, slide_id)
item_slide_copy = dbc.copy.slide(item_slide)
......
......@@ -13,45 +13,41 @@ list_schema = TeamDTO.list_schema
@api.route("/")
@api.param("CID")
@api.param("competition_id")
class TeamsList(Resource):
@check_jwt(editor=True)
def get(self, CID):
items = dbc.get.team_list(CID)
def get(self, competition_id):
items = dbc.get.team_list(competition_id)
return list_response(list_schema.dump(items))
@check_jwt(editor=True)
def post(self, CID):
def post(self, competition_id):
args = team_parser.parse_args(strict=True)
item_comp = dbc.get.one(Competition, CID)
item_team = dbc.add.team(args["name"], item_comp)
item_team = dbc.add.team(args["name"], competition_id)
return item_response(schema.dump(item_team))
@api.route("/<TID>")
@api.param("CID,TID")
@api.route("/<team_id>")
@api.param("competition_id,team_id")
class Teams(Resource):
@jwt_required
@check_jwt(editor=True)
def get(self, CID, TID):
item = dbc.get.team(CID, TID)
def get(self, competition_id, team_id):
item = dbc.get.team(competition_id, team_id)
return item_response(schema.dump(item))
@jwt_required
@check_jwt(editor=True)
def delete(self, CID, TID):
item_team = dbc.get.team(CID, TID)
def delete(self, competition_id, team_id):
item_team = dbc.get.team(competition_id, team_id)
dbc.delete.team(item_team)
return {}, codes.NO_CONTENT
@jwt_required
@check_jwt(editor=True)
def put(self, CID, TID):
def put(self, competition_id, team_id):
args = team_parser.parse_args(strict=True)
name = args.get("name")
item_team = dbc.get.team(CID, TID)
item_team = dbc.get.team(competition_id, team_id)
item_team = dbc.edit.team(item_team, name=name, competition_id=CID)
item_team = dbc.edit.default(item_team, name=name, competition_id=competition_id)
return item_response(schema.dump(item_team))
......@@ -15,11 +15,16 @@ list_schema = UserDTO.list_schema
def edit_user(item_user, args):
email = args.get("email")
name = args.get("name")
if email:
if User.query.filter(User.email == args["email"]).count() > 0:
if dbc.get.user_exists(email):
api.abort(codes.BAD_REQUEST, "Email is already in use")
return dbc.edit.user(item_user, **args)
if name:
args["name"] = args["name"].title()
return dbc.edit.default(item_user, **args)
@api.route("/")
......
......@@ -58,7 +58,6 @@ question_parser = reqparse.RequestParser()
question_parser.add_argument("name", type=str, default=None, location="json")
question_parser.add_argument("total_score", type=int, default=None, location="json")
question_parser.add_argument("type_id", type=int, default=None, location="json")
question_parser.add_argument("slide_id", type=int, location="json")
###QUESTION ALTERNATIVES####
......
import app.database.controller as dbc
from app.core import db
from app.database.models import Competition, Slide, Team, ViewType
from app.database.models import Competition, Slide, Team, ViewType, Code
from flask.globals import request
from flask_socketio import SocketIO, emit, join_room
import logging
# Presentation is an active competition
logger = logging.getLogger(__name__)
logger.propagate = False
logger.setLevel(logging.INFO)
formatter = logging.Formatter('[%(levelname)s] %(funcName)s: %(message)s')
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler)
sio = SocketIO(cors_allowed_origins="http://localhost:3000")
......@@ -14,7 +21,7 @@ presentations = {}
@sio.on("connect")
def connect():
print(f"[Connected]: {request.sid}")
logger.info(f"Client '{request.sid}' connected")
@sio.on("disconnect")
......@@ -22,23 +29,22 @@ def disconnect():
for competition_id, presentation in presentations.items():
if request.sid in presentation["clients"]:
del presentation["clients"][request.sid]
logger.debug(f"Client '{request.sid}' left presentation '{competition_id}'")
break
if presentations and not presentations[competition_id]["clients"]:
del presentations[competition_id]
logger.info(f"No people left in presentation '{competition_id}', ended presentation")
print(f"{presentations=}")
print(f"[Disconnected]: {request.sid}")
logger.info(f"Client '{request.sid}' disconnected")
@sio.on("start_presentation")
def start_presentation(data):
competition_id = data["competition_id"]
# TODO: Do proper error handling
if competition_id in presentations:
print("THAT PRESENTATION IS ALREADY ACTIVE")
logger.error(f"Client '{request.sid}' failed to start competition '{competition_id}', presentation already active")
return
presentations[competition_id] = {
......@@ -47,40 +53,44 @@ def start_presentation(data):
"timer": {"enabled": False, "start_value": None, "value": None},
}
print(f"{presentations=}")
join_room(competition_id)
print(f"[start_presentation]: {request.sid} -> {competition_id}.")
logger.debug(f"Client '{request.sid}' joined room {competition_id}")
logger.info(f"Client '{request.sid}' started competition '{competition_id}'")
@sio.on("end_presentation")
def end_presentation(data):
competition_id = data["competition_id"]
if competition_id not in presentations:
print("NO PRESENTATION WITH THAT NAME EXISTS")
logger.error(f"Client '{request.sid}' failed to end presentation '{competition_id}', no such presentation exists")
return
if request.sid not in presentations[competition_id]["clients"]:
logger.error(f"Client '{request.sid}' failed to end presentation '{competition_id}', client not in presentation")
return
if presentations[competition_id]["clients"][request.sid]["view_type"] != "Operator":
print("YOU DONT HAVE ACCESS TO DO THAT")
logger.error(f"Client '{request.sid}' failed to end presentation '{competition_id}', client is not operator")
return
del presentations[competition_id]
print(f"{presentations=}")
logger.debug(f"Deleted presentation {competition_id}")
emit("end_presentation", room=competition_id, include_self=True)
logger.debug(f"Emitting event 'end_presentation' to room {competition_id} including self")
logger.info(f"Client '{request.sid}' ended presentation '{competition_id}'")
@sio.on("join_presentation")
def join_presentation(data):
team_view_id = 1
code = data["code"]
item_code = dbc.get.code_by_code(code)
item_code = db.session.query(Code).filter(Code.code == code).first()
# TODO: Do proper error handling
if not item_code:
print("CODE DOES NOT EXIST")
logger.error(f"Client '{request.sid}' failed to join presentation with code '{code}', no such code exists")
return
competition_id = (
......@@ -90,22 +100,22 @@ def join_presentation(data):
)
if competition_id not in presentations:
print("THAT COMPETITION IS CURRENTLY NOT ACTIVE")
logger.error(f"Client '{request.sid}' failed to join presentation '{competition_id}', no such presentation exists")
return
if request.sid in presentations[competition_id]["clients"]:
print("CLIENT ALREADY IN COMPETITION")
logger.error(f"Client '{request.sid}' failed to join presentation '{competition_id}', client already in presentation")
return
# TODO: Write function in database controller to do this
view_type_name = db.session.query(ViewType).filter(ViewType.id == item_code.view_type_id).one().name
presentations[competition_id]["clients"][request.sid] = {"view_type": view_type_name}
join_room(competition_id)
print(f"{presentations=}")
join_room(competition_id)
logger.debug(f"Client '{request.sid}' joined room {competition_id}")
print(f"[Join presentation]: {request.sid} -> {competition_id}. {view_type_name=}")
logger.info(f"Client '{request.sid}' joined competition '{competition_id}'")
@sio.on("set_slide")
......@@ -114,43 +124,52 @@ def set_slide(data):
slide_order = data["slide_order"]
if competition_id not in presentations:
print("CANT SET SLIDE IN NON ACTIVE COMPETITION")
logger.error(f"Client '{request.sid}' failed to set slide in presentation '{competition_id}', no such presentation exists")
return
if request.sid not in presentations[competition_id]["clients"]:
logger.error(f"Client '{request.sid}' failed to set slide in presentation '{competition_id}', client not in presentation")
return
if presentations[competition_id]["clients"][request.sid]["view_type"] != "Operator":
print("YOU DONT HAVE ACCESS TO DO THAT")
logger.error(f"Client '{request.sid}' failed to set slide in presentation '{competition_id}', client is not operator")
return
num_slides = db.session.query(Slide).filter(Slide.competition_id == competition_id).count()
if not (0 <= slide_order < num_slides):
print("CANT CHANGE TO NON EXISTENT SLIDE")
logger.error(f"Client '{request.sid}' failed to set slide in presentation '{competition_id}', slide number {slide_order} does not exist")
return
presentations[competition_id]["slide"] = slide_order
print(f"{presentations=}")
emit("set_slide", {"slide_order": slide_order}, room=competition_id, include_self=True)
print(f"[Set slide]: {slide_order} -> {competition_id}")
logger.debug(f"Emitting event 'set_slide' to room {competition_id} including self")
logger.info(f"Client '{request.sid}' set slide '{slide_order}' in competition '{competition_id}'")
@sio.on("set_timer")
def sync_timer(data):
def set_timer(data):
competition_id = data["competition_id"]
timer = data["timer"]
if competition_id not in presentations:
print("CANT SET TIMER IN NON EXISTENT COMPETITION")
logger.error(f"Client '{request.sid}' failed to set slide in presentation '{competition_id}', no such presentation exists")
return
if request.sid not in presentations[competition_id]["clients"]:
logger.error(f"Client '{request.sid}' failed to set slide in presentation '{competition_id}', client not in presentation")
return
if presentations[competition_id]["clients"][request.sid]["view_type"] != "Operator":
print("YOU DONT HAVE ACCESS TO DO THAT")
logger.error(f"Client '{request.sid}' failed to set slide in presentation '{competition_id}', client is not operator")
return
# TODO: Save timer in presentation, maybe?
print(f"{presentations=}")
emit("set_timer", {"timer": timer}, room=competition_id, include_self=True)
print(f"[Set timer]: {timer=}, {competition_id=}")
logger.debug(f"Emitting event 'set_timer' to room {competition_id} including self")
logger.info(f"Client '{request.sid}' set timer '{timer}' in presentation '{competition_id}'")
......@@ -45,7 +45,7 @@ class Dictionary(TypeDecorator):
def process_bind_param(self, value, dialect):
if value is not None:
value = json.dumps(value).replace("'", '"')
value = json.dumps(value)
return value
......
......@@ -2,6 +2,7 @@
This file contains functionality to add data to the database.
"""
from sqlalchemy.orm.session import sessionmaker
import app.core.http_codes as codes
from app.core import db
from app.database.controller import utils
......@@ -25,6 +26,7 @@ from app.database.models import (
ViewType,
)
from flask_restx import abort
from sqlalchemy import exc
def db_add(item):
......@@ -32,13 +34,18 @@ def db_add(item):
Internal function. Adds item to the database
and handles comitting and refreshing.
"""
db.session.add(item)
db.session.commit()
db.session.refresh(item)
if not item:
abort(codes.BAD_REQUEST, f"Object could not be created")
try:
db.session.add(item)
db.session.commit()
db.session.refresh(item)
except (exc.SQLAlchemyError, exc.DBAPIError):
db.session.rollback()
# SQL errors such as item already exists
abort(codes.INTERNAL_SERVER_ERROR, f"Item of type {type(item)} could not be created")
except:
db.session.rollback()
# Catching other errors
abort(codes.INTERNAL_SERVER_ERROR, f"Something went wrong when creating {type(item)}")
return item
......@@ -85,13 +92,13 @@ def city(name):
return db_add(City(name))
def component(type_id, item_slide, data, x=0, y=0, w=0, h=0):
def component(type_id, slide_id, data, x=0, y=0, w=0, h=0):
"""
Adds a component to the slide at the specified coordinates with the
provided size and data .
"""
return db_add(Component(item_slide.id, type_id, data, x, y, w, h))
return db_add(Component(slide_id, type_id, data, x, y, w, h))
def image(filename, user_id):
......@@ -108,12 +115,12 @@ def user(email, password, role_id, city_id, name=None):
return db_add(User(email, password, role_id, city_id, name))
def question(name, total_score, type_id, item_slide):
def question(name, total_score, type_id, slide_id):
"""
Adds a question to the specified slide using the provided arguments.
"""
return db_add(Question(name, total_score, type_id, item_slide.id))
return db_add(Question(name, total_score, type_id, slide_id))
def question_alternative(text, value, question_id):
......@@ -131,10 +138,10 @@ def code(pointer, view_type_id):
return db_add(Code(code_string, pointer, view_type_id))
def team(name, item_competition):
def team(name, competition_id):
""" Adds a team with the specified name to the provided competition. """
item = db_add(Team(name, item_competition.id))
item = db_add(Team(name, competition_id))
# Add code for the team
code(item.id, 1)
......@@ -142,11 +149,33 @@ def team(name, item_competition):
return item
def slide(item_competition):
def slide(competition_id):
""" Adds a slide to the provided competition. """
# Get the last order from given competition
order = Slide.query.filter(Slide.competition_id == competition_id).count()
# Add slide
item_slide = db_add(Slide(order, competition_id))
# Add default question
question(f"Fråga {item_slide.order + 1}", 10, 0, item_slide.id)
item_slide = utils.refresh(item_slide)
return item_slide
def slide_without_question(competition_id):
""" Adds a slide to the provided competition. """
order = Slide.query.filter(Slide.competition_id == item_competition.id).count() # first element has index 0
return db_add(Slide(order, item_competition.id))
# Get the last order from given competition
order = Slide.query.filter(Slide.competition_id == competition_id).count()
# Add slide
item_slide = db_add(Slide(order, competition_id))
item_slide = utils.refresh(item_slide)
return item_slide
def competition(name, year, city_id):
......@@ -154,18 +183,22 @@ def competition(name, year, city_id):
Adds a competition to the database using the
provided arguments. Also adds slide and codes.
"""
item_competition = db_add(Competition(name, year, city_id))
item_competition = _competition(name, year, city_id)
# Add default slide
slide(item_competition.id)
# Add one slide for the competition
slide(item_competition)
# Add code for Judge view
code(item_competition.id, 2)
# TODO: Add two teams
# Add code for Audience view
code(item_competition.id, 3)
item_competition = utils.refresh(item_competition)
return item_competition
def _competition(name, year, city_id, font=None):
def _competition_no_slides(name, year, city_id, font=None):
"""
Internal function. Adds a competition to the database
using the provided arguments. Also adds codes.
......@@ -181,5 +214,5 @@ def _competition(name, year, city_id, font=None):
# Add code for Audience view
code(item_competition.id, 3)
utils.refresh(item_competition)
item_competition = utils.refresh(item_competition)
return item_competition
......@@ -40,7 +40,7 @@ def _component(item_component, item_slide_new):
add.component(
item_component.type_id,
item_slide_new,
item_slide_new.id,
item_component.data,
item_component.x,
item_component.y,
......@@ -66,7 +66,7 @@ def slide_to_competition(item_slide_old, item_competition):
Does not copy team, question answers.
"""
item_slide_new = add.slide(item_competition)
item_slide_new = add.slide_without_question(item_competition.id)
# Copy all fields
item_slide_new.title = item_slide_old.title
......@@ -98,7 +98,7 @@ def competition(item_competition_old):
print(f"{item_competition[total-1].name}, {total=}")
name = "Kopia av " + item_competition[total - 1].name
item_competition_new = add._competition(
item_competition_new = add._competition_no_slides(
name,
item_competition_old.year,
item_competition_old.city_id,
......
......@@ -2,16 +2,22 @@
This file contains functionality to delete data to the database.
"""
import app.core.http_codes as codes
import app.database.controller as dbc
from app.core import db
from app.database.models import Blacklist, City, Competition, Role, Slide, User
from flask_restx import abort
from sqlalchemy import exc
def default(item):
""" Deletes item and commits. """
db.session.delete(item)
db.session.commit()
try:
db.session.delete(item)
db.session.commit()
except:
db.session.rollback()
abort(codes.INTERNAL_SERVER_ERROR, f"Item of type {type(item)} could not be deleted")
def component(item_component):
......
......@@ -26,130 +26,28 @@ def switch_order(item1, item2):
return item1
def component(item, x, y, w, h, data):
""" Edits position, size and content of the provided component. """
if x:
item.x = x
if y:
item.y = y
if w:
item.w = w
if h:
item.h = h
if data:
item.data = data
db.session.commit()
db.session.refresh(item)
return item
def slide(item, title=None, timer=None):
""" Edits the title and timer of the slide. """
if title:
item.title = title
if timer:
item.timer = timer
db.session.commit()
db.session.refresh(item)
return item
def team(item_team, name=None, competition_id=None):
""" Edits the name and competition of the team. """
if name:
item_team.name = name
if competition_id:
item_team.competition_id = competition_id
db.session.commit()
db.session.refresh(item_team)
return item_team
def competition(item, name=None, year=None, city_id=None):
""" Edits the name and year of the competition. """
if name:
item.name = name
if year:
item.year = year
if city_id:
item.city_id = city_id
db.session.commit()
db.session.refresh(item)
return item
def user(item, name=None, email=None, city_id=None, role_id=None):
""" Edits the name, email, city and role of the user. """
if name:
item.name = name.title()
if email:
item.email = email
if city_id:
item.city_id = city_id
if role_id:
item.role_id = role_id
def default(item, **kwargs):
"""
For every keyword argument, set that attribute on item to the given value.
Raise error if item doesn't already have that attribute. Do nothing if the
value for a given key is None. Works for any type of item.
Example:
>>> user = default(user, name="Karl Karlsson") # Change name
>>> user.name
Karl Karlsson
>>> user = default(user, efternamn="Jönsson") # Try to set attribute that doesn't exist
AttributeError: Item of type <class 'app.database.models.User'> has no attribute 'efternamn'
>>> user = default(user, name=None) # Nothing happens if value is None
>>> user.name
Karl Karlsson
"""
for key, value in kwargs.items():
if not hasattr(item, key):
raise AttributeError(f"Item of type {type(item)} has no attribute '{key}'")
if value is not None:
setattr(item, key, value)
db.session.commit()
db.session.refresh(item)
return item
def question(item_question, name=None, total_score=None, type_id=None, slide_id=None):
""" Edits the name, score, type and slide of the question. """
if name:
item_question.name = name
if total_score:
item_question.total_score = total_score
if type_id:
item_question.type_id = type_id
if slide_id:
item_question.slide_id = slide_id
db.session.commit()
db.session.refresh(item_question)
return item_question
def question_alternative(item, text=None, value=None):
if text:
item.text = text
if value:
item.value = value
db.session.commit()
db.session.refresh(item)
return item
def question_answer(item, data=None, score=None):
if data:
item.data = data
if score:
item.score = score
db.session.commit()
db.session.refresh(item)
return item
......@@ -5,23 +5,17 @@ This file contains functionality to get data from the database.
from app.core import db
from app.core import http_codes as codes
from app.database.models import (
City,
Code,
Competition,
Component,
ComponentType,
MediaType,
Question,
QuestionAlternative,
QuestionAnswer,
QuestionType,
Role,
Slide,
Team,
User,
ViewType,
)
from sqlalchemy.orm import contains_eager, joinedload, subqueryload
from sqlalchemy.orm import joinedload, subqueryload
def all(db_type):
......@@ -30,114 +24,201 @@ def all(db_type):
return db_type.query.all()
def one(db_type, id, required=True, error_msg=None):
def one(db_type, id):
""" Get lazy db-item in the table that has the same id. """
return db_type.query.filter(db_type.id == id).first_extended(required, error_msg)
return db_type.query.filter(db_type.id == id).first_extended()
### Codes ###
def code_by_code(code):
""" Gets the code object associated with the provided code. """
return Code.query.filter(Code.code == code.upper()).first_extended()
def code_list(competition_id):
""" Gets a list of all code objects associated with a the provided competition. """
team_view_id = 1
join_filters = (Code.view_type_id == team_view_id) & (Team.id == Code.pointer)
filters = ((Code.view_type_id != team_view_id) & (Code.pointer == competition_id))(
(Code.view_type_id == team_view_id) & (competition_id == Team.competition_id)
)
return Code.query.join(Team, join_filters, isouter=True).filter(filters).all()
### Users ###
def user_exists(email):
""" Checks if an user has that email. """
return User.query.filter(User.email == email).count() > 0
def code_by_code(code, required=True, error_msg=None):
""" Gets the code object associated with the provided code. """
def user(user_id):
""" Gets the user object associated with the provided id. """
return Code.query.filter(Code.code == code.upper()).first_extended(required, error_msg, codes.UNAUTHORIZED)
return User.query.filter(User.id == user_id).first_extended()
def user(UID, required=True, error_msg=None):
""" Gets the user object associated with the provided id. """
def user_by_email(email):
""" Gets the user object associated with the provided email. """
return User.query.filter(User.email == email).first_extended(error_code=codes.UNAUTHORIZED)
return User.query.filter(User.id == UID).first_extended(required, error_msg)
### Slides ###
def slide(competition_id, slide_id):
""" Gets the slide object associated with the provided id and order. """
join_competition = Competition.id == Slide.competition_id
filters = (Competition.id == competition_id) & (Slide.id == slide_id)
def user_by_email(email, required=True, error_msg=None):
""" Gets the user object associated with the provided email. """
return Slide.query.join(Competition, join_competition).filter(filters).first_extended()
return User.query.filter(User.email == email).first_extended(required, error_msg)
def slide_list(competition_id):
""" Gets a list of all slide objects associated with a the provided competition. """
join_competition = Competition.id == Slide.competition_id
filters = Competition.id == competition_id
def slide(CID, SOrder, required=True, error_msg=None):
""" Gets the slide object associated with the provided id and order. """
return Slide.query.join(Competition, join_competition).filter(filters).all()
filters = (Slide.competition_id == CID) & (Slide.order == SOrder)
return Slide.query.filter(filters).first_extended(required, error_msg)
def slide_count(competition_id):
""" Gets the number of slides in the provided competition. """
return Slide.query.filter(Slide.competition_id == competition_id).count()
def team(CID, TID, required=True, error_msg=None):
### Teams ###
def team(competition_id, team_id):
""" Gets the team object associated with the provided id and competition id. """
join_competition = Competition.id == Team.competition_id
filters = (Competition.id == competition_id) & (Team.id == team_id)
return Team.query.join(Competition, join_competition).filter(filters).first_extended()
def team_list(competition_id):
""" Gets a list of all team objects associated with a the provided competition. """
return Team.query.filter((Team.competition_id == CID) & (Team.id == TID)).first_extended(required, error_msg)
join_competition = Competition.id == Team.competition_id
filters = Competition.id == competition_id
return Team.query.join(Competition, join_competition).filter(filters).all()
def question(CID, SOrder, QID, required=True, error_msg=None):
### Questions ###
def question(competition_id, slide_id, question_id):
""" Gets the question object associated with the provided id, slide order and competition id. """
join_filters = (Slide.competition_id == CID) & (Slide.order == SOrder) & (Slide.id == Question.slide_id)
return Question.query.join(Slide, join_filters).filter(Question.id == QID).first_extended(required, error_msg)
join_competition = Competition.id == Slide.competition_id
join_slide = Slide.id == Question.slide_id
filters = (Competition.id == competition_id) & (Slide.id == slide_id) & (Question.id == question_id)
return Question.query.join(Competition, join_competition).join(Slide, join_slide).filter(filters).first_extended()
def question_alternatives(QID):
# join_filters = (Slide.competition_id == CID) & (Slide.order == SOrder)
return QuestionAlternative.query.filter(QuestionAlternative.question_id == QID).all()
def question_list(competition_id, slide_id):
""" Gets a list of all question objects associated with a the provided competition and slide. """
def question_answers(TID):
# join_filters = (Slide.competition_id == CID) & (Slide.order == SOrder)
return QuestionAnswer.query.filter(QuestionAnswer.team_id == TID).all()
join_competition = Competition.id == Slide.competition_id
join_slide = Slide.id == Question.slide_id
filters = (Competition.id == competition_id) & (Slide.id == slide_id)
return Question.query.join(Competition, join_competition).join(Slide, join_slide).filter(filters).all()
def competition(CID):
""" Get Competition and all it's sub-entities """
""" HOT PATH """
os1 = joinedload(Competition.slides).joinedload(Slide.components)
os2 = joinedload(Competition.slides).joinedload(Slide.questions).joinedload(Question.alternatives)
ot = joinedload(Competition.teams).joinedload(Team.question_answers)
return Competition.query.filter(Competition.id == CID).options(os1).options(os2).options(ot).first()
def question_list_for_competition(competition_id):
""" Gets a list of all question objects associated with a the provided competition. """
join_competition = Competition.id == Slide.competition_id
join_slide = Slide.id == Question.slide_id
filters = Competition.id == competition_id
def code_list(competition_id):
""" Gets a list of all code objects associated with a the provided competition. """
return Question.query.join(Competition, join_competition).join(Slide, join_slide).filter(filters).all()
team_view_id = 1
join_filters = (Code.view_type_id == team_view_id) & (Team.id == Code.pointer)
filters = ((Code.view_type_id != team_view_id) & (Code.pointer == competition_id))(
(Code.view_type_id == team_view_id) & (competition_id == Team.competition_id)
### Question Alternative ###
def question_alternative(competition_id, slide_id, question_id, alternative_id):
""" Get question alternative for a given question based on its competition and slide and ID. """
join_competition = Competition.id == Slide.competition_id
join_slide = Slide.id == Question.slide_id
join_question = Question.id == QuestionAlternative.question_id
filters = (
(Competition.id == competition_id)
& (Slide.id == slide_id)
& (Question.id == question_id)
& (QuestionAlternative.id == alternative_id)
)
return Code.query.join(Team, join_filters, isouter=True).filter(filters).all()
return (
QuestionAlternative.query.join(Competition, join_competition)
.join(Slide, join_slide)
.join(Question, join_question)
.filter(filters)
.first_extended()
)
def question_list(CID):
""" Gets a list of all question objects associated with a the provided competition. """
join_filters = (Slide.competition_id == CID) & (Slide.id == Question.slide_id)
return Question.query.join(Slide, join_filters).all()
def question_alternative_list(competition_id, slide_id, question_id):
""" Get all question alternatives for a given question based on its competition and slide. """
join_competition = Competition.id == Slide.competition_id
join_slide = Slide.id == Question.slide_id
join_question = Question.id == QuestionAlternative.question_id
filters = (Competition.id == competition_id) & (Slide.id == slide_id) & (Question.id == question_id)
return (
QuestionAlternative.query.join(Competition, join_competition)
.join(Slide, join_slide)
.join(Question, join_question)
.filter(filters)
.all()
)
def component_list(CID, SOrder):
""" Gets a list of all component objects associated with a the provided competition id and slide order. """
join_filters = (Slide.competition_id == CID) & (Slide.order == SOrder) & (Component.slide_id == Slide.id)
return Component.query.join(Slide, join_filters).all()
### Question Answers ###
def question_answer(competition_id, team_id, answer_id):
""" Get question answer for a given team based on its competition and ID. """
join_competition = Competition.id == Team.competition_id
join_team = Team.id == QuestionAnswer.team_id
filters = (Competition.id == competition_id) & (Team.id == team_id) & (QuestionAnswer.id == answer_id)
return (
QuestionAnswer.query.join(Competition, join_competition).join(Team, join_team).filter(filters).first_extended()
)
def team_list(CID):
""" Gets a list of all team objects associated with a the provided competition. """
def question_answer_list(competition_id, team_id):
""" Get question answer for a given team based on its competition. """
join_competition = Competition.id == Team.competition_id
join_team = Team.id == QuestionAnswer.team_id
filters = (Competition.id == competition_id) & (Team.id == team_id)
return QuestionAnswer.query.join(Competition, join_competition).join(Team, join_team).filter(filters).all()
return Team.query.filter(Team.competition_id == CID).all()
### Components ###
def component(competition_id, slide_id, component_id):
""" Gets a list of all component objects associated with a the provided competition id and slide order. """
def slide_list(CID):
""" Gets a list of all slide objects associated with a the provided competition. """
join_competition = Competition.id == Slide.competition_id
join_slide = Slide.id == Component.slide_id
filters = (Competition.id == competition_id) & (Slide.id == slide_id) & (Component.id == component_id)
return Component.query.join(Competition, join_competition).join(Slide, join_slide).filter(filters).first_extended()
return Slide.query.filter(Slide.competition_id == CID).all()
def component_list(competition_id, slide_id):
""" Gets a list of all component objects associated with a the provided competition id and slide order. """
def slide_count(CID):
""" Gets the number of slides in the provided competition. """
join_competition = Competition.id == Slide.competition_id
join_slide = Slide.id == Component.slide_id
filters = (Competition.id == competition_id) & (Slide.id == slide_id)
return Component.query.join(Competition, join_competition).join(Slide, join_slide).filter(filters).all()
return Slide.query.filter(Slide.competition_id == CID).count()
### Competitions ###
def competition(competition_id):
""" Get Competition and all it's sub-entities """
os1 = joinedload(Competition.slides).joinedload(Slide.components)
os2 = joinedload(Competition.slides).joinedload(Slide.questions).joinedload(Question.alternatives)
ot = joinedload(Competition.teams).joinedload(Team.question_answers)
return Competition.query.filter(Competition.id == competition_id).options(os1).options(os2).options(ot).first()
......@@ -2,9 +2,32 @@
This file contains some miscellaneous functionality.
"""
import app.core.http_codes as codes
from app.core import db
from app.core.codes import generate_code_string
from app.database.models import Code
from flask_restx import abort
from sqlalchemy import exc
def move_slides(item_competition, start_order, end_order):
slides = item_competition.slides
# Move up
if start_order < end_order:
for i in range(start_order + 1, end_order):
slides[i].order -= 1
# Move down
elif start_order > end_order:
for i in range(end_order, start_order):
slides[i].order += 1
# start = 5, end = 1
# 1->2, 2->3, 4->5
# 5 = 1
slides[start_order].order = end_order
return commit_and_refresh(item_competition)
def generate_unique_code():
......@@ -16,19 +39,27 @@ def generate_unique_code():
return code
def commit_and_refresh(item):
""" Commits and refreshes the provided item. """
db.session.commit()
db.session.refresh(item)
def refresh(item):
""" Refreshes the provided item. """
try:
db.session.refresh(item)
except Exception as e:
abort(codes.INTERNAL_SERVER_ERROR, f"Refresh failed!\n{str(e)}")
db.session.refresh(item)
return item
def commit():
""" Commits. """
db.session.commit()
try:
db.session.commit()
except Exception as e:
db.session.rollback()
abort(codes.INTERNAL_SERVER_ERROR, f"Commit failed!\n{str(e)}")
def commit_and_refresh(item):
""" Commits and refreshes the provided item. """
commit()
return refresh(item)