diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3380f4e..f086238 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -55,6 +55,15 @@ jobs: cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache-new + - name: Copy Docker Compose file via SCP + uses: appleboy/scp-action@v0.1.7 + with: + host: ${{ secrets.GCE_HOST }} + username: ${{ secrets.GCE_SSH_USER }} + key: ${{ secrets.GCE_SSH_KEY }} + source: "docker-compose.prod.yml" + target: "~/app-path" + - name: Deploy to GCE via SSH uses: appleboy/ssh-action@v1.0.0 with: @@ -62,6 +71,7 @@ jobs: username: ${{ secrets.GCE_SSH_USER }} key: ${{ secrets.GCE_SSH_KEY }} script: | + mkdir -p ~/app-path cd ~/app-path cat << 'EOF' > .env @@ -70,7 +80,9 @@ jobs: gcloud auth configure-docker ${{ env.ARTIFACT_REGISTRY }} --quiet - docker-compose -f docker-compose.prod.yml down - docker-compose -f docker-compose.prod.yml pull - docker-compose -f docker-compose.prod.yml run --rm -e MODE=prod fastapi alembic upgrade head - docker-compose -f docker-compose.prod.yml up -d \ No newline at end of file + docker compose -f docker-compose.prod.yml down + docker compose -f docker-compose.prod.yml pull + + docker compose -f docker-compose.prod.yml run --rm -e MODE=prod fastapi alembic upgrade head + + docker compose -f docker-compose.prod.yml up -d \ No newline at end of file diff --git a/api/v1/auth/auth_router.py b/api/v1/auth/auth_router.py index dcd9e82..a5efebc 100644 --- a/api/v1/auth/auth_router.py +++ b/api/v1/auth/auth_router.py @@ -41,12 +41,12 @@ async def sign_up(request: SignUpRequest, db: AsyncSession = Depends(get_db)): print(info.values()) user_dto = UserCreateDTO( - **request.model_dump(exclude={"kakao_token"}), - **info + **{**request.model_dump(exclude={"kakao_token"}), **info} ) user = await SignUpUseCase(db).execute(user_dto) - token_data = TokenUseCase.generate_tokens(user_id=user.id) + token_uc = TokenUseCase() + token_data = token_uc.generate_tokens(user_id=user.id) return created(data=token_data, message="회원가입 성공") diff --git a/api/v1/test/test_router.py b/api/v1/test/test_router.py index 47aaefa..387b711 100644 --- a/api/v1/test/test_router.py +++ b/api/v1/test/test_router.py @@ -41,6 +41,7 @@ from app.test.dto.request.create_dummy_data_request import CreateDummyDataRequest #시험모드 문제 리스트 출력 from app.test.dto.response.get_certificates_exam_list_response import GetCertificatesExamListResponse +from app.test.usecase.test_usecase import get_exam_list_by_certificate_id_usecase from app.test.usecase.exam_usecase import get_certificates_exam_list_usecase from app.test.usecase.exam_usecase import create_exam_usecase from app.test.usecase.exam_usecase import get_exam_info_usecase @@ -171,6 +172,10 @@ async def get_questions_by_exam_id( result = await get_questions_by_exam_id_usecase(exam_id, db) return ok(data=[item.dict() for item in result], message="문제 목록 조회 성공") +@router.get("/list/{certificate_id}") +async def get_exam_list(certificate_id: int = Path(...), db: AsyncSession = Depends(get_db)): + return await get_exam_list_by_certificate_id_usecase(certificate_id, db) + @router.post("/dummy-data", summary="유형별 Dummy Data 생성 API") async def create_dummy_data( request: CreateDummyDataRequest, # ← 여기에 request 추가 필요! diff --git a/app/review/dto/response/review_note_list_response.py b/app/review/dto/response/review_note_list_response.py index 28f50c2..7ce9e40 100644 --- a/app/review/dto/response/review_note_list_response.py +++ b/app/review/dto/response/review_note_list_response.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional from pydantic import BaseModel @@ -7,5 +7,5 @@ class ReviewNoteListResponseDto(BaseModel): category: List[str] - selected_category: str + selected_category: Optional[str] = None exams: List[ExamItemInfo] \ No newline at end of file diff --git a/app/test/dto/response/exam_response_dto.py b/app/test/dto/response/exam_response_dto.py new file mode 100644 index 0000000..87b1464 --- /dev/null +++ b/app/test/dto/response/exam_response_dto.py @@ -0,0 +1,14 @@ +from pydantic import BaseModel + +class ExamResponseDTO(BaseModel): + id: int + name: str + year: int + month: int + trial: int | None + time: int + pass_rate: float | None + question_count: int + + class Config: + from_attributes = True \ No newline at end of file diff --git a/app/test/usecase/test_usecase.py b/app/test/usecase/test_usecase.py index 2fc4a47..86f867e 100644 --- a/app/test/usecase/test_usecase.py +++ b/app/test/usecase/test_usecase.py @@ -1,10 +1,13 @@ -from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import func from sqlalchemy.future import select from domain.test.entity.exam import Exam from domain.test.entity.question import Question from domain.user.entity.user import User +from app.utils.dto.success import ok +from sqlalchemy.ext.asyncio import AsyncSession +from app.test.dto.response.exam_response_dto import ExamResponseDTO +from domain.test.repository.test_repository import TestRepository from exception.client_exception import NotFoundException -from app.utils.dto.success import created, ok async def get_test_mode_usecase( exam_id: int, @@ -44,3 +47,31 @@ async def get_test_mode_usecase( }, message="시험 조회 성공" ) + +async def get_exam_list_by_certificate_id_usecase(certificate_id: int, db: AsyncSession): + repo = TestRepository(db) + exams = await repo.get_exams_by_certificate_id(certificate_id) + + if not exams: + raise NotFoundException("해당 자격증에 대한 시험이 존재하지 않습니다.") + + exam_list = [] + + for exam in exams: + stmt = select(func.count()).where(Question.exam_id == exam.id) + result = await db.execute(stmt) + question_count = result.scalar_one() + + dto = ExamResponseDTO( + id=exam.id, + name=exam.name, + year=exam.year, + month=exam.month, + trial=exam.trial, + time=exam.time, + pass_rate=exam.pass_rate, + question_count=question_count + ) + exam_list.append(dto.model_dump()) + + return ok(data={"exams": exam_list}, message="시험 리스트 조회 성공") diff --git a/domain/review/service/review_note_test_service.py b/domain/review/service/review_note_test_service.py index 53d76af..038e59e 100644 --- a/domain/review/service/review_note_test_service.py +++ b/domain/review/service/review_note_test_service.py @@ -69,7 +69,7 @@ async def list_review_notes( return ReviewNoteListResponseDto( category = categories, - selected_category = selected, + selected_category = selected or "", exams = exams ) diff --git a/domain/test/repository/test_repository.py b/domain/test/repository/test_repository.py index a415b80..4eb0160 100644 --- a/domain/test/repository/test_repository.py +++ b/domain/test/repository/test_repository.py @@ -161,6 +161,11 @@ async def get_question_count_by_exam_id(self, exam_id: int) -> int: ) return result.scalar() + async def get_exams_by_certificate_id(self, certificate_id: int) -> list[Exam]: + stmt = select(Exam).where(Exam.certificate_id == certificate_id) + result = await self.db.execute(stmt) + return result.scalars().all() + async def get_exams_by_certificate_ids(self, certificate_ids: list[int]) -> list[Exam]: result = await self.db.execute( select(Exam) @@ -179,4 +184,5 @@ async def get_exam_by_id(self, exam_id: int) -> Exam | None: result = await self.db.execute( select(Exam).where(Exam.id == exam_id) ) - return result.scalars().first() \ No newline at end of file + return result.scalars().first() + diff --git a/domain/test/service/test_service.py b/domain/test/service/test_service.py index 7ac3d4e..1947c28 100644 --- a/domain/test/service/test_service.py +++ b/domain/test/service/test_service.py @@ -35,10 +35,17 @@ async def check_user_submit( if is_correct: correct_count += 1 + raw_list = question.option_explanations or [] + option_explanations_dict: dict[str, str] = { + key: val + for item in raw_list + for key, val in item.items() + } + # 문제별 해설 DTO info_list.append(AnswerInfoDto( answer=question.answer, - option_explanations=question.option_explanations + option_explanations=option_explanations_dict )) # 문제별 사용자 응답 기록 diff --git a/requirements.txt b/requirements.txt index a422f17..2e91558 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,7 +21,6 @@ boto3~=1.38.32 python-jose[cryptography]~=3.5.0 requests-oauthlib requests~=2.32.3 -starlette>=0.49.1 # test pytest