# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://mozilla.org/MPL/2.0/. import json from fastapi import APIRouter, HTTPException from pydantic import BaseModel, validator from classquiz.config import settings, redis from classquiz.db.models import ( PlayGame, GamePlayer, GameAnswer1, GameAnswer2, GameSession, QuizQuestion, RangeQuizAnswer, ABCDQuizAnswer, QuizQuestionType, VotingQuizAnswer, ) from classquiz.auth import check_api_key from classquiz.socket_server import ReturnQuestion, sio, _AnswerDataList settings = settings() router = APIRouter() class _GetLiveDataPlayers(BaseModel): count: int | str class _ABCDQuizAnswer(ABCDQuizAnswer): color_code: str class _QuizQuestion(QuizQuestion): answers: list[ABCDQuizAnswer] | RangeQuizAnswer | list[VotingQuizAnswer] @validator("answers") def answers_not_none_if_abcd_type(cls, v, values): # if values["type"] == QuizQuestionType.ABCD and type(v[0]) != _ABCDQuizAnswer: # print(type(v[0]), values) # raise ValueError("Answers can't be none if type is ABCD") # if values["type"] == QuizQuestionType.RANGE and type(v) != RangeQuizAnswer: # raise ValueError("Answer must be from type RangeQuizAnswer if type is RANGE") # if values["type"] == QuizQuestionType.VOTING and type(v[0]) != VotingQuizAnswer: # raise ValueError("Answer must be from type VotingQuizAnswer if type is VOTING") # return v return v class _PlayGame(PlayGame): questions: list[_QuizQuestion] class _GetLivePlayGame(_PlayGame): total_questions: int class GetLiveDataResponse(BaseModel): quiz: _GetLivePlayGame data: GameSession players: _GetLiveDataPlayers @router.get("/") async def get_live_game_data( game_pin: str, api_key: str, player_count_as_a_string: bool = False, in_array: bool = False, in_human_count: bool = False, ): user_id = await check_api_key(api_key) redis_res = await redis.get(f"game:{game_pin}") if redis_res is None: game_pin = await redis.get(f"game_pin:{user_id}:{game_pin}") redis_res = await redis.get(f"game:{game_pin}") if redis_res is None or user_id is None: raise HTTPException(status_code=404, detail="Game not found or API key not found") game = _PlayGame.parse_raw(redis_res) if game.user_id != user_id: raise HTTPException(status_code=404, detail="Game not found or API key not found") for i, question in enumerate(game.questions): if question.type == QuizQuestionType.ABCD: for o, answer in enumerate(question.answers): color = "#00F007" try: if not answer.right: color = "#FF0000" except AttributeError: game.questions[i].answers[o] = VotingQuizAnswer( answer=answer.answer, color=answer.color, image=answer.image ) continue game.questions[i].answers[o] = _ABCDQuizAnswer( right=answer.right, answer=answer.answer, color=answer.color, color_code=color ) data_redis_res = await redis.get(f"game_session:{game_pin}") if data_redis_res is None: raise HTTPException(status_code=404, detail="Game not found or API key not found") data = GameSession.parse_raw(data_redis_res) for i in range(0, len(game.questions)): res = await redis.get(f"game_session:{game_pin}:{i}") if res is None: break else: res = json.loads(res) ga_1 = GameAnswer1(id=i, answers=[GameAnswer2.parse_obj(i) for i in res]) data.answers.append(ga_1) player_count = await redis.scard(f"game_session:{game_pin}:players") total_questions = len(game.questions) game = _GetLivePlayGame(**{**game.dict(), "total_questions": total_questions}) if in_human_count: player_count += 1 game.current_question += 1 return_obj = None if player_count_as_a_string: return_obj = GetLiveDataResponse(quiz=game, data=data, players=_GetLiveDataPlayers(count=str(player_count))) else: return_obj = GetLiveDataResponse(quiz=game, data=data, players=_GetLiveDataPlayers(count=player_count)) if in_array: return [return_obj] else: return return_obj @router.get("/user_count") async def get_game_user_count(game_pin: str, api_key: str, as_string: bool = False): # if redis_res is None: # raise HTTPException(status_code=404, detail="Game not found") user_id = await check_api_key(api_key) redis_res = await redis.get(f"game_session:{game_pin}") if redis_res is None: game_pin = await redis.get(f"game_pin:{user_id}:{game_pin}") player_count = await redis.scard(f"game_session:{game_pin}:players") if as_string: return {"players": {"count": str(player_count)}} else: return {"players": {"count": player_count}} # class _LivePlayersReturn(BaseModel): # # players: list[GamePlayer | None] # answers: list[GameAnswer1 | None] # players: list[GamePlayer | None] @router.get( "/players", ) async def get_game_session(game_pin: str, api_key: str): user_id = await check_api_key(api_key) redis_res = await redis.get(f"game_session:{game_pin}") if redis_res is None: game_pin = await redis.get(f"game_pin:{user_id}:{game_pin}") redis_res = await redis.get(f"game_session:{game_pin}") if redis_res is None or user_id is None: raise HTTPException(status_code=404, detail="Game not found or API key not found") data = GameSession.parse_raw(redis_res) game = PlayGame.parse_raw(await redis.get(f"game:{game_pin}")) if game.user_id != user_id: raise HTTPException(status_code=404, detail="Game not found or API key not found") for i in range(0, len(game.questions)): res = await redis.get(f"game_session:{game_pin}:{i}") if res is None: break else: res = json.loads(res) ga_1 = GameAnswer1(id=i, answers=[GameAnswer2.parse_obj(i) for i in res]) data.answers.append(ga_1) players = await redis.smembers(f"game_session:{game_pin}:players") player_list = [] for p in players: player_list.append(GamePlayer.parse_raw(p)) return player_list @router.post("/set_question") async def set_next_question(game_pin: str, question_number: int, api_key: str): user_id = await check_api_key(api_key) redis_res = await redis.get(f"game:{game_pin}") if redis_res is None: game_pin = await redis.get(f"game_pin:{user_id}:{game_pin}") redis_res = await redis.get(f"game:{game_pin}") if redis_res is None or user_id is None: raise HTTPException(status_code=404, detail="Game not found or API key not found") game_data = PlayGame.parse_raw(redis_res) if game_data.user_id != user_id: raise HTTPException(status_code=404, detail="Game not found or API key not found") game_data.current_question = question_number await redis.set(f"game:{game_pin}", game_data.json()) await sio.emit( "set_question_number", { "question_index": question_number, "question": ReturnQuestion(**game_data.dict(include={"questions"})["questions"][question_number]).dict(), }, room=game_pin, ) @router.get("/scores") async def get_live_player_scores(game_pin: str, api_key: str): user_id = await check_api_key(api_key) redis_res = await redis.get(f"game:{game_pin}") if redis_res is None: game_pin = await redis.get(f"game_pin:{user_id}:{game_pin}") redis_res = await redis.get(f"game:{game_pin}") if redis_res is None or user_id is None: raise HTTPException(status_code=404, detail="Game not found or API key not found") res = await redis.hgetall(f"game_session:{game_pin}:player_scores") return_arr = [] for username in res: return_arr.append({"username": username, "score": int(res[username])}) return_arr = sorted(return_arr, key=lambda d: d["score"], reverse=True) return return_arr @router.get("/get_question/now") async def too_stupid_to_come_up_with_a_name(game_pin: str, api_key: str, in_human_count: bool = False): user_id = await check_api_key(api_key) redis_res = await redis.get(f"game:{game_pin}") if redis_res is None: game_pin = await redis.get(f"game_pin:{user_id}:{game_pin}") redis_res = await redis.get(f"game:{game_pin}") if redis_res is None or user_id is None: raise HTTPException(status_code=404, detail="Game not found or API key not found") game = PlayGame.parse_raw(redis_res) for i, question in enumerate(game.questions): if question.type == QuizQuestionType.ABCD: for o, answer in enumerate(question.answers): color = "#00F007" if not answer.right: color = "#FF0000" game.questions[i].answers[o] = _ABCDQuizAnswer( right=answer.right, answer=answer.answer, color=answer.color, color_code=color ) if game.current_question >= 0: return [ { **game.questions[game.current_question].dict(), "current_question": game.current_question + 1 if in_human_count else game.current_question, "total_questions": len(game.questions), } ] else: game.current_question += 1 return [{"question": {}, "current_question": game.current_question, "total_questions": len(game.questions)}] @router.get("/voting") async def voting_results(game_pin: str, api_key: str, as_array: bool = False): user_id = await check_api_key(api_key) redis_res = await redis.get(f"game:{game_pin}") if redis_res is None: game_pin = await redis.get(f"game_pin:{user_id}:{game_pin}") redis_res = await redis.get(f"game:{game_pin}") if redis_res is None or user_id is None: raise HTTPException(status_code=404, detail="Game not found or API key not found") game = PlayGame.parse_raw(redis_res) if game.questions[game.current_question].type != QuizQuestionType.VOTING: return answer_data = await redis.get(f"game_session:{game_pin}:{game.current_question}") if answer_data is None: return answer_list = _AnswerDataList.parse_raw(answer_data) answer_dict = {} for answer in game.questions[game.current_question].answers: answer_dict[answer.answer] = 0 for answer in answer_list.__root__: answer_dict[answer.answer] += 1 if as_array: return [answer_dict] else: return answer_dict