Skip to content

Commit

Permalink
Merge pull request #13 from 2jun0/GDET-24
Browse files Browse the repository at this point in the history
GDET-24: 퀴즈 정답 제출 db 저장
  • Loading branch information
2jun0 authored Jan 11, 2024
2 parents 986344f + 12bbcef commit 50805b8
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 36 deletions.
1 change: 1 addition & 0 deletions backend/pytest.ini
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[pytest]
env_override_existing_values = 1
env_files =
.test.env
5 changes: 5 additions & 0 deletions backend/src/dependency.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import Annotated, Any, AsyncGenerator

from elasticsearch import AsyncElasticsearch
from fastapi import Depends
from sqlmodel.ext.asyncio.session import AsyncSession

Expand All @@ -12,4 +13,8 @@ async def get_session() -> AsyncGenerator[AsyncSession, Any]:
yield session


async def es_client() -> AsyncElasticsearch:
return AsyncElasticsearch(settings.ELASTIC_SEARCH_URL) # type: ignore


SessionDep = Annotated[AsyncSession, Depends(get_session)]
17 changes: 15 additions & 2 deletions backend/src/quiz/dependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,24 @@
from fastapi import Depends

from ..dependency import SessionDep
from .repository import QuizRepository, QuizSubmitRepository
from .service import QuizService


async def get_quiz_service(session: SessionDep) -> QuizService:
return QuizService(session)
async def get_quiz_service(
quiz_repository: "QuizRepositoryDep", quiz_submit_repository: "QuizSubmitRepositoryDep"
) -> QuizService:
return QuizService(quiz_repository=quiz_repository, quiz_submit_repository=quiz_submit_repository)


async def get_quiz_repository(session: SessionDep) -> QuizRepository:
return QuizRepository(session)


async def get_quiz_submit_repository(session: SessionDep) -> QuizSubmitRepository:
return QuizSubmitRepository(session)


QuizRepositoryDep = Annotated[QuizRepository, Depends(get_quiz_repository)]
QuizSubmitRepositoryDep = Annotated[QuizSubmitRepository, Depends(get_quiz_submit_repository)]
QuizServiceDep = Annotated[QuizService, Depends(get_quiz_service)]
1 change: 1 addition & 0 deletions backend/src/quiz/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class QuizSubmit(CreatedAtMixin, UpdatedAtMixin, SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)

answer: str = Field(max_length=64)
correct: bool = Field()

quiz_id: int = Field(foreign_key="quiz.id")
quiz: Quiz = Relationship()
42 changes: 42 additions & 0 deletions backend/src/quiz/repository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from datetime import datetime
from typing import Optional

from sqlalchemy.orm import selectinload
from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession

from ..game.model import GameScreenshot
from ..repository import CRUDMixin, IRepository
from .model import Quiz, QuizSubmit


class QuizRepository(IRepository[Quiz], CRUDMixin):
model = Quiz

def __init__(self, session: AsyncSession) -> None:
self._session = session

async def get_with_game(self, *, id: int) -> Optional[Quiz]:
stmt = (
select(Quiz)
.where(Quiz.id == id)
.options(selectinload(Quiz.screenshots).selectinload(GameScreenshot.game)) # type: ignore
)
rs = await self._session.exec(stmt)
return rs.first()

async def get_by_created_at_interval_with_screenshots(self, *, start_at: datetime, end_at: datetime):
stmts = (
select(Quiz)
.where(Quiz.created_at >= start_at, Quiz.created_at <= end_at)
.options(selectinload(Quiz.screenshots)) # type: ignore
)
rs = await self._session.exec(stmts)
return rs.all()


class QuizSubmitRepository(IRepository[QuizSubmit], CRUDMixin):
model = QuizSubmit

def __init__(self, session: AsyncSession) -> None:
self._session = session
47 changes: 16 additions & 31 deletions backend/src/quiz/service.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,34 @@
from datetime import datetime, time
from typing import Optional, Sequence
from typing import Sequence

from sqlalchemy.orm import selectinload
from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession

from ..game.model import GameScreenshot
from .exception import QuizNotFoundError
from .model import Quiz
from .model import Quiz, QuizSubmit
from .repository import QuizRepository, QuizSubmitRepository


class QuizService:
def __init__(self, session: AsyncSession) -> None:
self._session = session
def __init__(self, *, quiz_repository: QuizRepository, quiz_submit_repository: QuizSubmitRepository) -> None:
self._quiz_repo = quiz_repository
self._quiz_submit_repo = quiz_submit_repository

async def get_today_quizes(self) -> Sequence[Quiz]:
now = datetime.utcnow()
today = now.date()
start_datetime = datetime.combine(today, time.min)
end_datetime = datetime.combine(today, time.max)
start_at = datetime.combine(today, time.min)
end_at = datetime.combine(today, time.max)

stmts = (
select(Quiz)
.where(Quiz.created_at >= start_datetime, Quiz.created_at <= end_datetime)
.options(selectinload(Quiz.screenshots)) # type: ignore
)
rs = await self._session.exec(stmts)
return rs.all()
return await self._quiz_repo.get_by_created_at_interval_with_screenshots(start_at=start_at, end_at=end_at)

async def submit_answer(self, *, quiz_id: int, answer: str) -> bool:
"""
퀴즈에 대한 정답 여부를 반환하는 함수
"""

# TODO: 제출 기록을 저장해야 함
quiz = await self._get_quiz_by_id_with_game(quiz_id)
"""퀴즈에 대한 정답 여부를 반환하는 함수"""
quiz = await self._quiz_repo.get_with_game(id=quiz_id)

if quiz is None:
raise QuizNotFoundError

return quiz.game.name == answer
correct = quiz.game.name == answer
quiz_submit = QuizSubmit(answer=answer, correct=correct, quiz_id=quiz_id)

await self._quiz_submit_repo.create(model=quiz_submit)

async def _get_quiz_by_id_with_game(self, id: int) -> Optional[Quiz]:
stmt = (
select(Quiz).where(Quiz.id == id).options(selectinload(Quiz.screenshots).selectinload(GameScreenshot.game)) # type: ignore
)
rs = await self._session.exec(stmt)
return rs.first()
return correct
36 changes: 36 additions & 0 deletions backend/src/repository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from typing import Generic, Protocol, Type, TypeVar

from sqlalchemy import exc
from sqlmodel import SQLModel, exists, select
from sqlmodel.ext.asyncio.session import AsyncSession

ModelTypeT = TypeVar("ModelTypeT", bound=SQLModel)
ModelTypeS = TypeVar("ModelTypeS", bound=SQLModel)


class IRepository(Protocol, Generic[ModelTypeT]):
_session: AsyncSession
model: Type[ModelTypeT]


class CRUDMixin:
async def get(self: IRepository[ModelTypeS], *, id: int) -> ModelTypeS | None:
stmt = select(self.model).where(self.model.id == id)
rs = await self._session.exec(stmt)
return rs.first()

async def exists(self: IRepository[ModelTypeS], *, id: int) -> bool:
stmt = select(exists().where(self.model.id == id))
rs = await self._session.exec(stmt)
return rs.one()

async def create(self: IRepository[ModelTypeS], *, model: ModelTypeS) -> ModelTypeS:
try:
self._session.add(model)
await self._session.commit()
except exc.IntegrityError:
await self._session.rollback()
raise

await self._session.refresh(model)
return model
12 changes: 11 additions & 1 deletion backend/tests/integration/test_quiz_submit_answer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from fastapi.testclient import TestClient
from sqlmodel import Session

from tests.utils.quiz import create_random_quiz
from tests.utils.quiz import create_random_quiz, get_quiz_submit


def test_post_submit_true_answer(client: TestClient, session: Session):
Expand All @@ -14,6 +14,11 @@ def test_post_submit_true_answer(client: TestClient, session: Session):
res_json = res.json()
assert res_json["correct"] is True

# check db
submit = get_quiz_submit(session, quiz_id=saved_quiz.id)
assert submit is not None
assert submit.correct is True


def test_post_submit_false_answer(client: TestClient, session: Session):
saved_quiz = create_random_quiz(session)
Expand All @@ -24,6 +29,11 @@ def test_post_submit_false_answer(client: TestClient, session: Session):
res_json = res.json()
assert res_json["correct"] is False

# check db
submit = get_quiz_submit(session, quiz_id=saved_quiz.id)
assert submit is not None
assert submit.correct is False


def test_post_submit_answer_with_invalid_quiz_id(client: TestClient):
res = client.post("/quiz/submit_answer", json={"quiz_id": -1, "answer": "아무거나 빙빙바리바리구"})
Expand Down
18 changes: 16 additions & 2 deletions backend/tests/utils/quiz.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from sqlmodel import Session
from typing import Optional

from sqlmodel import Session, select

from src.game.model import GameScreenshot
from src.quiz.model import Quiz
from src.quiz.model import Quiz, QuizSubmit

from .game import create_random_game
from .screenshot import create_random_game_screenshot
Expand All @@ -25,3 +27,15 @@ def create_random_quiz(session: Session, *, screenshots: list[GameScreenshot] |
session.refresh(quiz)

return quiz


def get_quiz_submit(
session: Session, *, quiz_id: int | None = None, answer: int | None = None
) -> Optional[QuizSubmit]:
stmt = select(QuizSubmit)
if quiz_id:
stmt = stmt.where(QuizSubmit.quiz_id == quiz_id)
if answer:
stmt = stmt.where(QuizSubmit.answer == answer)

return session.exec(stmt).first()

0 comments on commit 50805b8

Please sign in to comment.