diff --git a/src/app/v2/answers/router.py b/src/app/v2/answers/router.py index 6afeed7..94612b0 100644 --- a/src/app/v2/answers/router.py +++ b/src/app/v2/answers/router.py @@ -2,6 +2,8 @@ from app.v2.answers.services.answer_service import AnswerService +from app.v2.levels.services.level_service import LevelService + router = APIRouter(prefix="/answer", tags=["Test용"]) @@ -11,3 +13,10 @@ async def get_answer_record_view(): user_id = "180a4e40-62f8-46be-b1eb-e7e3dd91cddf" record_dto = await AnswerService.get_answer_record(user_id) return {"user_id": user_id, "consecutive_answer_days": record_dto.count} + + +@router.get("/level-up") +async def level_up_handler(): + user_id = "180a4e40-62f8-46be-b1eb-e7e3dd91cddf" + result = await LevelService.level_up(user_id=user_id) + return result diff --git a/src/app/v2/badges/dtos/badge_dto.py b/src/app/v2/badges/dtos/badge_dto.py index 0d3d0e0..8026267 100644 --- a/src/app/v2/badges/dtos/badge_dto.py +++ b/src/app/v2/badges/dtos/badge_dto.py @@ -4,6 +4,10 @@ class BadgeCodeDTO(BaseModel): badgeCode: str + @classmethod + def builder(cls, badge_raw: dict) -> "BadgeCodeDTO": + return cls(badgeCode=badge_raw.get("badge_code")) + class BadgeDTO(BaseModel): badgeCode: str diff --git a/src/app/v2/badges/services/badge_service.py b/src/app/v2/badges/services/badge_service.py new file mode 100644 index 0000000..042e996 --- /dev/null +++ b/src/app/v2/badges/services/badge_service.py @@ -0,0 +1,9 @@ +from app.v2.badges.dtos.badge_dto import BadgeCodeDTO +from app.v2.badges.models.badge import Badge + + +class BadgeService: + @classmethod + async def get_badges(cls, user_id: str) -> list[BadgeCodeDTO]: + badges_raw = await Badge.get_badge_codes_by_user_id(user_id=user_id) + return [BadgeCodeDTO.builder(badge) for badge in badges_raw] diff --git a/src/app/v2/cheese_managers/dtos/__init__.py b/src/app/v2/cheese_managers/dtos/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/app/v2/cheese_managers/dtos/cheese_dto.py b/src/app/v2/cheese_managers/dtos/cheese_dto.py new file mode 100644 index 0000000..5e7d44a --- /dev/null +++ b/src/app/v2/cheese_managers/dtos/cheese_dto.py @@ -0,0 +1,9 @@ +from pydantic import BaseModel + + +class CheeseResponseDTO(BaseModel): + cheeseBalance: int + + @classmethod + def builder(cls, cheese_balance: int) -> "CheeseResponseDTO": + return cls(cheeseBalance=cheese_balance) diff --git a/src/app/v2/cheese_managers/router.py b/src/app/v2/cheese_managers/router.py new file mode 100644 index 0000000..f75d0f9 --- /dev/null +++ b/src/app/v2/cheese_managers/router.py @@ -0,0 +1,16 @@ +from fastapi import APIRouter, status + +from app.v2.cheese_managers.dtos.cheese_dto import CheeseResponseDTO +from app.v2.cheese_managers.services.cheese_service import CheeseService +from app.v2.users.services.user_service import UserService + +router = APIRouter(prefix="/cheese", tags=["Cheese"]) + + +@router.get("", response_model=CheeseResponseDTO, status_code=status.HTTP_200_OK) +async def get_cheese_handler(): + user_id = "180a4e40-62f8-46be-b1eb-e7e3dd91cddf" + user = await UserService.get_user_info(user_id=user_id) + cheese_amount = await CheeseService.get_cheese_balance(user["cheese_manager_id"]) + + return CheeseResponseDTO.builder(cheese_balance=cheese_amount) diff --git a/src/app/v2/cheese_managers/services/__init__.py b/src/app/v2/cheese_managers/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/app/v2/cheese_managers/services/cheese_service.py b/src/app/v2/cheese_managers/services/cheese_service.py new file mode 100644 index 0000000..a7a2f58 --- /dev/null +++ b/src/app/v2/cheese_managers/services/cheese_service.py @@ -0,0 +1,10 @@ +from app.v2.cheese_managers.models.cheese_manager import CheeseManager + + +class CheeseService: + + @classmethod + async def get_cheese_balance(cls, cheese_manager_id: str) -> int: + return await CheeseManager.get_total_cheese_amount_by_manager( + cheese_manager_id=cheese_manager_id + ) diff --git a/src/app/v2/colors/dtos/color_dto.py b/src/app/v2/colors/dtos/color_dto.py index 241f00c..e153cc0 100644 --- a/src/app/v2/colors/dtos/color_dto.py +++ b/src/app/v2/colors/dtos/color_dto.py @@ -3,3 +3,7 @@ class ColorCodeDTO(BaseModel): colorCode: str + + @classmethod + def builder(cls, color_raw: dict) -> "ColorCodeDTO": + return cls(colorCode=color_raw.get("color_code")) diff --git a/src/app/v2/colors/services/color_service.py b/src/app/v2/colors/services/color_service.py new file mode 100644 index 0000000..120b67f --- /dev/null +++ b/src/app/v2/colors/services/color_service.py @@ -0,0 +1,9 @@ +from app.v2.colors.dtos.color_dto import ColorCodeDTO +from app.v2.colors.models.color import Color + + +class ColorService: + @classmethod + async def get_colors(cls, user_id: str) -> list[ColorCodeDTO]: + colors_raw = await Color.get_color_codes_by_user_id(user_id=user_id) + return [ColorCodeDTO.builder(color) for color in colors_raw] diff --git a/src/app/v2/levels/dtos/level_dto.py b/src/app/v2/levels/dtos/level_dto.py index 7d8aebf..f512950 100644 --- a/src/app/v2/levels/dtos/level_dto.py +++ b/src/app/v2/levels/dtos/level_dto.py @@ -4,3 +4,7 @@ class LevelDTO(BaseModel): level: int current_exp: int + + @classmethod + def builder(cls, level: int, current_exp: int) -> "LevelDTO": + return cls(level=level, current_exp=current_exp) diff --git a/src/app/v2/levels/models/level.py b/src/app/v2/levels/models/level.py index 2bf1705..b19cf08 100644 --- a/src/app/v2/levels/models/level.py +++ b/src/app/v2/levels/models/level.py @@ -3,6 +3,8 @@ from app.v2.levels.querys.level_query import ( SELECT_USER_LEVEL_AND_EXP_BY_USER_UUID_QUERY, + SELECT_USER_EXP_QUERY, + UPDATE_USER_LEVEL_AND_EXP_QUERY, ) from common.utils.query_executor import QueryExecutor @@ -22,3 +24,19 @@ async def get_level_info_by_user_id(cls, user_id: str) -> dict | None: return await QueryExecutor.execute_query( query, values=value, fetch_type="single" ) + + @classmethod + async def get_required_exp_by_user_id(cls, user_id: str) -> dict | None: + query = SELECT_USER_EXP_QUERY + value = user_id + return await QueryExecutor.execute_query( + query, values=(value,), fetch_type="single" + ) + + @classmethod + async def update_level_and_exp( + cls, user_id: str, new_level: int, new_exp: int + ) -> None: + query = UPDATE_USER_LEVEL_AND_EXP_QUERY + values = (new_level, new_exp, user_id) + await QueryExecutor.execute_query(query, values=values, fetch_type="single") diff --git a/src/app/v2/levels/querys/level_query.py b/src/app/v2/levels/querys/level_query.py index 279b231..33d04fb 100644 --- a/src/app/v2/levels/querys/level_query.py +++ b/src/app/v2/levels/querys/level_query.py @@ -10,3 +10,19 @@ level l ON u.level_id = l.level_id WHERE {USER_ID_QUERY} """ + +SELECT_USER_EXP_QUERY = f""" +SELECT li.required_exp +FROM user u +JOIN level l ON u.level_id = l.level_id +JOIN level_inventory li ON l.user_level = li.level +WHERE {USER_ID_QUERY} +LIMIT 1; +""" + +UPDATE_USER_LEVEL_AND_EXP_QUERY = f""" + UPDATE level l + JOIN user u ON u.level_id = l.level_id + SET l.user_level = %s, l.user_exp = %s + WHERE {USER_ID_QUERY}; +""" diff --git a/src/app/v2/levels/services/level_service.py b/src/app/v2/levels/services/level_service.py new file mode 100644 index 0000000..3ae204e --- /dev/null +++ b/src/app/v2/levels/services/level_service.py @@ -0,0 +1,52 @@ +import asyncio + +from app.v2.levels.dtos.level_dto import LevelDTO +from app.v2.levels.models.level import Level + + +class LevelService: + @classmethod + async def get_level_info(cls, user_id: str) -> LevelDTO: + # 레벨 정보를 조회하는 로직 + level_info_raw = await Level.get_level_info_by_user_id(user_id=user_id) + return LevelDTO.builder( + level=level_info_raw.get("level_level"), + current_exp=level_info_raw.get("level_exp"), + ) + + @classmethod + async def level_up(cls, user_id: str) -> dict: + """ + 유저가 레벨업 가능한지 확인 후, 레벨업 처리 + """ + level_dto, required_exp_raw = await asyncio.gather( + cls.get_level_info(user_id=user_id), + Level.get_required_exp_by_user_id(user_id=user_id), + ) + + level = level_dto.level + current_exp = level_dto.current_exp + required_exp = required_exp_raw["required_exp"] + + if current_exp >= required_exp: + new_exp = current_exp - required_exp + new_level = level + 1 + + await Level.update_level_and_exp( + user_id=user_id, new_level=new_level, new_exp=new_exp + ) + + return { + "status": "success", + "message": "레벨업 성공", + "new_level": new_level, + "remaining_exp": new_exp, + } + + return { + "status": "failure", + "message": "레벨업에 필요한 경험치가 부족합니다", + "current_level": level, + "current_exp": current_exp, + "required_exp": required_exp, + } diff --git a/src/app/v2/mobiles/router.py b/src/app/v2/mobiles/router.py index 7f2df3f..85faa70 100644 --- a/src/app/v2/mobiles/router.py +++ b/src/app/v2/mobiles/router.py @@ -3,24 +3,28 @@ from fastapi import APIRouter, status, HTTPException from app.v2.answers.models.answer import Answer -from app.v2.badges.dtos.badge_dto import BadgeCodeDTO + from app.v2.badges.models.badge import Badge +from app.v2.badges.services.badge_service import BadgeService from app.v2.cheese_managers.models.cheese_manager import CheeseManager -from app.v2.colors.dtos.color_dto import ColorCodeDTO -from app.v2.colors.models.color import Color +from app.v2.cheese_managers.services.cheese_service import CheeseService + +from app.v2.colors.services.color_service import ColorService from app.v2.levels.dtos.level_dto import LevelDTO from app.v2.levels.models.level import Level +from app.v2.levels.services.level_service import LevelService from app.v2.mobiles.dtos.mypage_response import ( UserProfileWithLevel, MyPageResponseDTO, ) from app.v2.mobiles.dtos.teller_card_response import DataDTO, TellerCardResponseDTO -from app.v2.teller_cards.dtos.teller_card_dto import TellerCardDTO -from app.v2.teller_cards.models.teller_card import TellerCard + +from app.v2.teller_cards.services.teller_card_service import TellerCardService from app.v2.users.dtos.user_info_dto import UserInfoDTO from app.v2.users.dtos.user_profile_dto import UserProfileDTO from app.v2.users.models.user import User +from app.v2.users.services.user_service import UserService router = APIRouter(prefix="/mobiles", tags=["모바일 화면용 컨트롤러"]) @@ -30,50 +34,33 @@ async def mobile_main_handler(): pass -@router.get("/tellercard") +@router.get( + "/tellercard", + response_model=TellerCardResponseDTO, + status_code=status.HTTP_200_OK, +) async def mobile_teller_card_handler(): user_id = "180a4e40-62f8-46be-b1eb-e7e3dd91cddf" try: - badges_raw, colors_raw, level_info_raw, teller_cards_raw, user_raw = ( - await asyncio.gather( - Badge.get_badge_codes_by_user_id(user_id=user_id), - Color.get_color_codes_by_user_id(user_id=user_id), - Level.get_level_info_by_user_id(user_id=user_id), - TellerCard.get_teller_card_info_by_user_id(user_id=user_id), - User.get_user_info_by_user_id(user_id=user_id), - ) + badges_task = BadgeService.get_badges(user_id) + colors_task = ColorService.get_colors(user_id) + level_info_task = LevelService.get_level_info(user_id) # LevelService 추가 + teller_card_task = TellerCardService.get_teller_card(user_id) + user_info_task = UserService.get_user_info(user_id) + + badges, colors, level_info, teller_card, user_raw = await asyncio.gather( + badges_task, colors_task, level_info_task, teller_card_task, user_info_task ) except Exception as e: raise HTTPException(status_code=500, detail="내부 서버 오류") - cheese_amount = await CheeseManager.get_total_cheese_amount_by_manager( - cheese_manager_id=user_raw["cheese_manager_id"] - ) - - badges: list[BadgeCodeDTO] = [ - BadgeCodeDTO(badgeCode=badge.get("badge_code")) for badge in badges_raw - ] - colors: list[ColorCodeDTO] = [ - ColorCodeDTO(colorCode=color.get("color_code")) for color in colors_raw - ] - - teller_card = TellerCardDTO( - badgeCode=teller_cards_raw.get("activate_badge_code"), - badgeName=teller_cards_raw.get("badge_name"), - badgeMiddleName=teller_cards_raw.get("badge_middle_name"), - colorCode=teller_cards_raw.get("activate_color_code"), - ) - - user_info = UserInfoDTO( - nickname=user_raw.get("nickname"), - cheeseBalance=cheese_amount, - tellerCard=teller_card, + cheese_amount = await CheeseService.get_cheese_balance( + user_raw["cheese_manager_id"] ) - level_info: LevelDTO = LevelDTO( - level=level_info_raw.get("level_level"), - current_exp=level_info_raw.get("level_exp"), + user_info = UserInfoDTO.builder( + user_raw, cheeseBalance=cheese_amount, tellerCard=teller_card ) data = DataDTO(badges=badges, colors=colors, userInfo=user_info, level=level_info) diff --git a/src/app/v2/teller_cards/dtos/teller_card_dto.py b/src/app/v2/teller_cards/dtos/teller_card_dto.py index 4b4c10b..d29463b 100644 --- a/src/app/v2/teller_cards/dtos/teller_card_dto.py +++ b/src/app/v2/teller_cards/dtos/teller_card_dto.py @@ -6,3 +6,12 @@ class TellerCardDTO(BaseModel): badgeName: str badgeMiddleName: str colorCode: str + + @classmethod + def builder(cls, teller_card_raw: dict) -> "TellerCardDTO": + return cls( + badgeCode=teller_card_raw.get("activate_badge_code"), + badgeName=teller_card_raw.get("badge_name"), + badgeMiddleName=teller_card_raw.get("badge_middle_name"), + colorCode=teller_card_raw.get("activate_color_code"), + ) diff --git a/src/app/v2/teller_cards/services/teller_card_service.py b/src/app/v2/teller_cards/services/teller_card_service.py new file mode 100644 index 0000000..2d9de44 --- /dev/null +++ b/src/app/v2/teller_cards/services/teller_card_service.py @@ -0,0 +1,11 @@ +from app.v2.teller_cards.dtos.teller_card_dto import TellerCardDTO +from app.v2.teller_cards.models.teller_card import TellerCard + + +class TellerCardService: + @classmethod + async def get_teller_card(cls, user_id: str) -> TellerCardDTO: + teller_cards_raw = await TellerCard.get_teller_card_info_by_user_id( + user_id=user_id + ) + return TellerCardDTO.builder(teller_cards_raw) diff --git a/src/app/v2/users/dtos/user_info_dto.py b/src/app/v2/users/dtos/user_info_dto.py index f60bae4..1866a1a 100644 --- a/src/app/v2/users/dtos/user_info_dto.py +++ b/src/app/v2/users/dtos/user_info_dto.py @@ -1,5 +1,4 @@ from pydantic import BaseModel - from app.v2.teller_cards.dtos.teller_card_dto import TellerCardDTO @@ -7,3 +6,13 @@ class UserInfoDTO(BaseModel): nickname: str cheeseBalance: int tellerCard: TellerCardDTO + + @classmethod + def builder( + cls, user_raw: dict, cheeseBalance: int, tellerCard: TellerCardDTO + ) -> "UserInfoDTO": + return cls( + nickname=user_raw.get("nickname"), + cheeseBalance=cheeseBalance, + tellerCard=tellerCard, + ) diff --git a/src/app/v2/users/services/user_service.py b/src/app/v2/users/services/user_service.py new file mode 100644 index 0000000..2d77ae0 --- /dev/null +++ b/src/app/v2/users/services/user_service.py @@ -0,0 +1,14 @@ +from app.v2.cheese_managers.models.cheese_manager import CheeseManager +from app.v2.users.models.user import User + + +class UserService: + @classmethod + async def get_user_info(cls, user_id: str) -> dict: + return await User.get_user_info_by_user_id(user_id=user_id) + + @classmethod + async def get_cheese_balance(cls, cheese_manager_id: str) -> int: + return await CheeseManager.get_total_cheese_amount_by_manager( + cheese_manager_id=cheese_manager_id + ) diff --git a/src/common/handlers/router_handler.py b/src/common/handlers/router_handler.py index dd2f34b..aceeff5 100644 --- a/src/common/handlers/router_handler.py +++ b/src/common/handlers/router_handler.py @@ -5,6 +5,7 @@ from app.v2.payments.router import router as payment_router from app.v2.missions.router import router as mission_router from app.v2.answers.router import router as answer_router +from app.v2.cheese_managers.router import router as cheese_router def attach_router_handlers(app): @@ -14,4 +15,5 @@ def attach_router_handlers(app): app.include_router(router=teller_card_router, prefix="/api/v2") app.include_router(router=payment_router, prefix="/api/v2") app.include_router(router=mission_router, prefix="/api/v2") + app.include_router(router=cheese_router, prefix="/api/v2") app.include_router(router=answer_router, prefix="/test")