From 42b446d9ccf5ef1602683a3f1e16c7bc2e394158 Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Sun, 1 Jun 2025 15:13:23 +0900 Subject: [PATCH 01/27] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20app=20?= =?UTF-8?q?=ED=8F=B4=EB=8D=94=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- Dockerfile | 59 ++++--------------- src/main.py => app/__init__.py | 10 ++-- {src/app => app/apis}/__init__.py | 0 {src/app => app/apis}/v2/__init__.py | 0 {src/app => app/apis}/v2/answers/__init__.py | 0 .../apis}/v2/answers/dtos/__init__.py | 0 .../apis}/v2/answers/dtos/answer_dto.py | 0 .../apis}/v2/answers/models/__init__.py | 0 .../apis}/v2/answers/models/answer.py | 6 +- .../apis}/v2/answers/querys/__init__.py | 0 .../apis}/v2/answers/querys/answer_query.py | 2 +- {src/app => app/apis}/v2/answers/router.py | 5 +- .../apis}/v2/answers/services/__init__.py | 0 .../v2/answers/services/answer_service.py | 2 +- {src/app => app/apis}/v2/badges/__init__.py | 0 .../apis}/v2/badges/dtos/__init__.py | 0 .../apis}/v2/badges/dtos/badge_dto.py | 0 app/apis/v2/badges/dtos/response.py | 6 ++ .../apis}/v2/badges/models/__init__.py | 0 .../apis}/v2/badges/models/badge.py | 6 +- .../apis}/v2/badges/querys/__init__.py | 0 .../apis}/v2/badges/querys/badge_query.py | 2 +- {src/app => app/apis}/v2/badges/router.py | 4 +- .../apis}/v2/badges/services/__init__.py | 0 .../apis}/v2/badges/services/badge_service.py | 4 +- .../apis}/v2/cheese_managers/__init__.py | 0 .../apis}/v2/cheese_managers/dtos/__init__.py | 0 .../v2/cheese_managers/dtos/cheese_dto.py | 2 +- .../v2/cheese_managers/models/__init__.py | 0 .../cheese_managers/models/cheese_manager.py | 2 +- .../cheese_managers/models/cheese_status.py | 0 .../v2/cheese_managers/querys/__init__.py | 0 .../apis}/v2/cheese_managers/router.py | 6 +- .../v2/cheese_managers/services/__init__.py | 0 .../services/cheese_service.py | 2 +- {src/app => app/apis}/v2/colors/__init__.py | 0 .../apis}/v2/colors/dtos/__init__.py | 0 .../apis}/v2/colors/dtos/color_dto.py | 0 app/apis/v2/colors/dtos/response.py | 6 ++ .../apis}/v2/colors/models/__init__.py | 0 .../apis}/v2/colors/models/color.py | 6 +- .../apis}/v2/colors/querys/__init__.py | 0 .../apis}/v2/colors/querys/color_query.py | 2 +- {src/app => app/apis}/v2/colors/router.py | 4 +- .../apis}/v2/colors/services/__init__.py | 0 .../apis}/v2/colors/services/color_service.py | 6 +- {src/app => app/apis}/v2/emotions/__init__.py | 0 .../apis}/v2/emotions/dtos/__init__.py | 0 .../apis}/v2/emotions/dtos/response.py | 2 +- .../apis}/v2/emotions/models/__init__.py | 0 .../apis}/v2/emotions/models/emotion.py | 6 +- .../apis}/v2/emotions/querys/__init__.py | 0 .../apis}/v2/emotions/querys/emotion_query.py | 2 +- {src/app => app/apis}/v2/emotions/router.py | 4 +- .../apis}/v2/emotions/services/__init__.py | 0 .../v2/emotions/services/emotion_service.py | 6 +- {src/app => app/apis}/v2/items/__init__.py | 0 .../apis}/v2/items/dtos/__init__.py | 0 .../apis}/v2/items/dtos/item_dto.py | 0 .../apis}/v2/items/models/__init__.py | 0 {src/app => app/apis}/v2/items/models/item.py | 0 .../apis}/v2/items/repositorys/__init__.py | 0 {src/app => app/apis}/v2/items/router.py | 0 .../apis}/v2/items/services/__init__.py | 0 {src/app => app/apis}/v2/levels/__init__.py | 0 .../apis}/v2/levels/dtos/__init__.py | 0 .../apis}/v2/levels/dtos/level_dto.py | 0 .../apis}/v2/levels/models/__init__.py | 0 .../apis}/v2/levels/models/level.py | 7 ++- .../apis}/v2/levels/querys/__init__.py | 0 .../apis}/v2/levels/querys/level_query.py | 2 +- {src/app => app/apis}/v2/levels/router.py | 0 .../apis}/v2/levels/services/__init__.py | 0 .../apis}/v2/levels/services/level_service.py | 6 +- {src/app => app/apis}/v2/likes/__init__.py | 0 .../apis}/v2/likes/models/__init__.py | 0 {src/app => app/apis}/v2/likes/models/like.py | 8 +-- .../apis}/v2/likes/querys/__init__.py | 0 .../apis}/v2/likes/querys/like_query.py | 0 {src/app => app/apis}/v2/missions/__init__.py | 0 .../apis}/v2/missions/dtos/__init__.py | 0 .../apis}/v2/missions/dtos/mission_dto.py | 0 .../apis}/v2/missions/dtos/request.py | 0 .../apis}/v2/missions/dtos/response.py | 0 .../apis}/v2/missions/dtos/reward_dto.py | 2 +- .../apis}/v2/missions/models/__init__.py | 0 .../apis}/v2/missions/models/mission.py | 6 +- .../apis}/v2/missions/querys/__init__.py | 0 .../apis}/v2/missions/querys/mission_query.py | 2 - {src/app => app/apis}/v2/missions/router.py | 4 +- .../apis}/v2/missions/services/__init__.py | 0 .../v2/missions/services/mission_service.py | 28 ++++----- {src/app => app/apis}/v2/mobiles/__init__.py | 0 .../apis}/v2/mobiles/dtos/__init__.py | 0 .../apis}/v2/mobiles/dtos/mypage_response.py | 6 +- .../v2/mobiles/dtos/teller_card_response.py | 12 ++-- {src/app => app/apis}/v2/mobiles/router.py | 26 ++++---- {src/app => app/apis}/v2/notices/__init__.py | 0 .../apis}/v2/notices/dtos/__init__.py | 0 .../apis}/v2/notices/models/__init__.py | 0 .../apis}/v2/notices/models/notice.py | 4 +- .../apis}/v2/notices/services/__init__.py | 0 .../v2/notices/services/notice_service.py | 3 +- {src/app => app/apis}/v2/payments/__init__.py | 0 .../apis}/v2/payments/dtos/__init__.py | 0 .../apis}/v2/payments/dtos/request.py | 0 .../apis}/v2/payments/dtos/response.py | 2 +- .../apis}/v2/payments/models/__init__.py | 0 .../apis}/v2/payments/querys/__init__.py | 0 {src/app => app/apis}/v2/payments/router.py | 8 +-- .../apis}/v2/payments/services/__init__.py | 0 .../v2/payments/services/payment_service.py | 17 +++--- .../app => app/apis}/v2/purchases/__init__.py | 0 .../apis}/v2/purchases/dtos/__init__.py | 0 .../apis}/v2/purchases/dtos/purchase_dto.py | 3 +- .../apis}/v2/purchases/dtos/requests.py | 0 .../apis}/v2/purchases/models/__init__.py | 0 .../v2/purchases/models/purchase_history.py | 4 +- .../v2/purchases/models/purchase_status.py | 0 .../v2/purchases/repositorys/__init__.py | 0 {src/app => app/apis}/v2/purchases/router.py | 6 +- .../apis}/v2/purchases/services/__init__.py | 0 .../v2/purchases/services/purchase_service.py | 18 +++--- .../app => app/apis}/v2/questions/__init__.py | 0 .../apis}/v2/questions/dtos/__init__.py | 0 .../apis}/v2/questions/dtos/responses.py | 0 .../apis}/v2/questions/models/__init__.py | 0 .../apis}/v2/questions/models/question.py | 0 .../v2/questions/repositorys/__init__.py | 0 {src/app => app/apis}/v2/questions/router.py | 4 +- .../apis}/v2/questions/services/__init__.py | 0 .../apis}/v2/teller_cards/__init__.py | 0 .../apis}/v2/teller_cards/dtos/__init__.py | 0 .../apis}/v2/teller_cards/dtos/request.py | 0 .../apis}/v2/teller_cards/dtos/response.py | 4 +- .../v2/teller_cards/dtos/teller_card_dto.py | 2 - .../apis}/v2/teller_cards/models/__init__.py | 0 .../v2/teller_cards/models/teller_card.py | 4 +- .../apis}/v2/teller_cards/querys/__init__.py | 0 .../teller_cards/querys/teller_card_query.py | 2 +- .../apis}/v2/teller_cards/router.py | 6 +- .../v2/teller_cards/services/__init__.py | 0 .../services/teller_card_service.py | 8 +-- {src/app => app/apis}/v2/users/__init__.py | 0 .../apis}/v2/users/dtos/__init__.py | 0 .../apis}/v2/users/dtos/user_dto.py | 0 .../apis}/v2/users/dtos/user_info_dto.py | 2 +- .../apis}/v2/users/dtos/user_profile_dto.py | 0 .../apis}/v2/users/models/__init__.py | 0 .../apis}/v2/users/models/refresh_token.py | 0 {src/app => app/apis}/v2/users/models/user.py | 12 ++-- .../apis}/v2/users/querys/__init__.py | 0 .../apis}/v2/users/querys/user_query.py | 0 .../apis}/v2/users/services/__init__.py | 0 .../apis}/v2/users/services/user_service.py | 5 +- {src => app}/celery_worker.py | 0 {src => app}/common/__init__.py | 0 {src => app}/common/base_models/__init__.py | 0 .../common/base_models/base_dtos/__init__.py | 0 .../base_models/base_dtos/base_response.py | 0 .../base_models/custom_fields/__init__.py | 0 {src => app}/common/exceptions/__init__.py | 0 .../common/exceptions/custom_exception.py | 2 +- {src => app}/common/exceptions/error_code.py | 0 {src => app}/common/handlers/__init__.py | 0 .../common/handlers/exception_handler.py | 2 +- .../common/handlers/router_handler.py | 22 +++---- app/common/post_construct.py | 13 ++++ {src => app}/common/tasks/__init__.py | 0 {src => app}/common/tasks/mission_task.py | 2 +- .../common/tasks/renew_subscription_task.py | 2 +- {src => app}/common/utils/__init__.py | 0 {src => app}/common/utils/get_user_id.py | 0 {src => app}/common/utils/query_executor.py | 3 +- {src => app}/common/utils/query_formatter.py | 0 {src => app}/common/utils/scheduler.py | 2 +- {src => app}/core/__init__.py | 0 {src => app}/core/configs/__init__.py | 2 +- {src => app}/core/configs/base_settings.py | 0 {src => app}/core/configs/celery_settings.py | 8 +-- {src => app}/core/database/__init__.py | 0 .../core/database/database_settings.py | 30 +++++----- app/dtos/__init__.py | 0 app/log/__init__.py | 0 app/models/__init__.py | 0 app/services/__init__.py | 0 app/tests/__init__.py | 0 poetry.lock | 31 +++++++++- pyproject.toml | 39 ++++++++---- scripts/deploy-prod.sh | 2 +- scripts/start_app.sh | 2 +- src/app/v2/badges/dtos/response.py | 6 -- src/app/v2/colors/dtos/response.py | 6 -- src/common/post_construct.py | 13 ---- test.sh | 8 +-- 196 files changed, 290 insertions(+), 292 deletions(-) rename src/main.py => app/__init__.py (68%) rename {src/app => app/apis}/__init__.py (100%) rename {src/app => app/apis}/v2/__init__.py (100%) rename {src/app => app/apis}/v2/answers/__init__.py (100%) rename {src/app => app/apis}/v2/answers/dtos/__init__.py (100%) rename {src/app => app/apis}/v2/answers/dtos/answer_dto.py (100%) rename {src/app => app/apis}/v2/answers/models/__init__.py (100%) rename {src/app => app/apis}/v2/answers/models/answer.py (93%) rename {src/app => app/apis}/v2/answers/querys/__init__.py (100%) rename {src/app => app/apis}/v2/answers/querys/answer_query.py (90%) rename {src/app => app/apis}/v2/answers/router.py (83%) rename {src/app => app/apis}/v2/answers/services/__init__.py (100%) rename {src/app => app/apis}/v2/answers/services/answer_service.py (97%) rename {src/app => app/apis}/v2/badges/__init__.py (100%) rename {src/app => app/apis}/v2/badges/dtos/__init__.py (100%) rename {src/app => app/apis}/v2/badges/dtos/badge_dto.py (100%) create mode 100644 app/apis/v2/badges/dtos/response.py rename {src/app => app/apis}/v2/badges/models/__init__.py (100%) rename {src/app => app/apis}/v2/badges/models/badge.py (92%) rename {src/app => app/apis}/v2/badges/querys/__init__.py (100%) rename {src/app => app/apis}/v2/badges/querys/badge_query.py (91%) rename {src/app => app/apis}/v2/badges/router.py (78%) rename {src/app => app/apis}/v2/badges/services/__init__.py (100%) rename {src/app => app/apis}/v2/badges/services/badge_service.py (89%) rename {src/app => app/apis}/v2/cheese_managers/__init__.py (100%) rename {src/app => app/apis}/v2/cheese_managers/dtos/__init__.py (100%) rename {src/app => app/apis}/v2/cheese_managers/dtos/cheese_dto.py (89%) rename {src/app => app/apis}/v2/cheese_managers/models/__init__.py (100%) rename {src/app => app/apis}/v2/cheese_managers/models/cheese_manager.py (97%) rename {src/app => app/apis}/v2/cheese_managers/models/cheese_status.py (100%) rename {src/app => app/apis}/v2/cheese_managers/querys/__init__.py (100%) rename {src/app => app/apis}/v2/cheese_managers/router.py (69%) rename {src/app => app/apis}/v2/cheese_managers/services/__init__.py (100%) rename {src/app => app/apis}/v2/cheese_managers/services/cheese_service.py (84%) rename {src/app => app/apis}/v2/colors/__init__.py (100%) rename {src/app => app/apis}/v2/colors/dtos/__init__.py (100%) rename {src/app => app/apis}/v2/colors/dtos/color_dto.py (100%) create mode 100644 app/apis/v2/colors/dtos/response.py rename {src/app => app/apis}/v2/colors/models/__init__.py (100%) rename {src/app => app/apis}/v2/colors/models/color.py (92%) rename {src/app => app/apis}/v2/colors/querys/__init__.py (100%) rename {src/app => app/apis}/v2/colors/querys/color_query.py (88%) rename {src/app => app/apis}/v2/colors/router.py (78%) rename {src/app => app/apis}/v2/colors/services/__init__.py (100%) rename {src/app => app/apis}/v2/colors/services/color_service.py (83%) rename {src/app => app/apis}/v2/emotions/__init__.py (100%) rename {src/app => app/apis}/v2/emotions/dtos/__init__.py (100%) rename {src/app => app/apis}/v2/emotions/dtos/response.py (79%) rename {src/app => app/apis}/v2/emotions/models/__init__.py (100%) rename {src/app => app/apis}/v2/emotions/models/emotion.py (89%) rename {src/app => app/apis}/v2/emotions/querys/__init__.py (100%) rename {src/app => app/apis}/v2/emotions/querys/emotion_query.py (82%) rename {src/app => app/apis}/v2/emotions/router.py (76%) rename {src/app => app/apis}/v2/emotions/services/__init__.py (100%) rename {src/app => app/apis}/v2/emotions/services/emotion_service.py (87%) rename {src/app => app/apis}/v2/items/__init__.py (100%) rename {src/app => app/apis}/v2/items/dtos/__init__.py (100%) rename {src/app => app/apis}/v2/items/dtos/item_dto.py (100%) rename {src/app => app/apis}/v2/items/models/__init__.py (100%) rename {src/app => app/apis}/v2/items/models/item.py (100%) rename {src/app => app/apis}/v2/items/repositorys/__init__.py (100%) rename {src/app => app/apis}/v2/items/router.py (100%) rename {src/app => app/apis}/v2/items/services/__init__.py (100%) rename {src/app => app/apis}/v2/levels/__init__.py (100%) rename {src/app => app/apis}/v2/levels/dtos/__init__.py (100%) rename {src/app => app/apis}/v2/levels/dtos/level_dto.py (100%) rename {src/app => app/apis}/v2/levels/models/__init__.py (100%) rename {src/app => app/apis}/v2/levels/models/level.py (83%) rename {src/app => app/apis}/v2/levels/querys/__init__.py (100%) rename {src/app => app/apis}/v2/levels/querys/level_query.py (94%) rename {src/app => app/apis}/v2/levels/router.py (100%) rename {src/app => app/apis}/v2/levels/services/__init__.py (100%) rename {src/app => app/apis}/v2/levels/services/level_service.py (93%) rename {src/app => app/apis}/v2/likes/__init__.py (100%) rename {src/app => app/apis}/v2/likes/models/__init__.py (100%) rename {src/app => app/apis}/v2/likes/models/like.py (81%) rename {src/app => app/apis}/v2/likes/querys/__init__.py (100%) rename {src/app => app/apis}/v2/likes/querys/like_query.py (100%) rename {src/app => app/apis}/v2/missions/__init__.py (100%) rename {src/app => app/apis}/v2/missions/dtos/__init__.py (100%) rename {src/app => app/apis}/v2/missions/dtos/mission_dto.py (100%) rename {src/app => app/apis}/v2/missions/dtos/request.py (100%) rename {src/app => app/apis}/v2/missions/dtos/response.py (100%) rename {src/app => app/apis}/v2/missions/dtos/reward_dto.py (94%) rename {src/app => app/apis}/v2/missions/models/__init__.py (100%) rename {src/app => app/apis}/v2/missions/models/mission.py (88%) rename {src/app => app/apis}/v2/missions/querys/__init__.py (100%) rename {src/app => app/apis}/v2/missions/querys/mission_query.py (87%) rename {src/app => app/apis}/v2/missions/router.py (78%) rename {src/app => app/apis}/v2/missions/services/__init__.py (100%) rename {src/app => app/apis}/v2/missions/services/mission_service.py (94%) rename {src/app => app/apis}/v2/mobiles/__init__.py (100%) rename {src/app => app/apis}/v2/mobiles/dtos/__init__.py (100%) rename {src/app => app/apis}/v2/mobiles/dtos/mypage_response.py (68%) rename {src/app => app/apis}/v2/mobiles/dtos/teller_card_response.py (68%) rename {src/app => app/apis}/v2/mobiles/router.py (76%) rename {src/app => app/apis}/v2/notices/__init__.py (100%) rename {src/app => app/apis}/v2/notices/dtos/__init__.py (100%) rename {src/app => app/apis}/v2/notices/models/__init__.py (100%) rename {src/app => app/apis}/v2/notices/models/notice.py (94%) rename {src/app => app/apis}/v2/notices/services/__init__.py (100%) rename {src/app => app/apis}/v2/notices/services/notice_service.py (96%) rename {src/app => app/apis}/v2/payments/__init__.py (100%) rename {src/app => app/apis}/v2/payments/dtos/__init__.py (100%) rename {src/app => app/apis}/v2/payments/dtos/request.py (100%) rename {src/app => app/apis}/v2/payments/dtos/response.py (84%) rename {src/app => app/apis}/v2/payments/models/__init__.py (100%) rename {src/app => app/apis}/v2/payments/querys/__init__.py (100%) rename {src/app => app/apis}/v2/payments/router.py (77%) rename {src/app => app/apis}/v2/payments/services/__init__.py (100%) rename {src/app => app/apis}/v2/payments/services/payment_service.py (82%) rename {src/app => app/apis}/v2/purchases/__init__.py (100%) rename {src/app => app/apis}/v2/purchases/dtos/__init__.py (100%) rename {src/app => app/apis}/v2/purchases/dtos/purchase_dto.py (93%) rename {src/app => app/apis}/v2/purchases/dtos/requests.py (100%) rename {src/app => app/apis}/v2/purchases/models/__init__.py (100%) rename {src/app => app/apis}/v2/purchases/models/purchase_history.py (98%) rename {src/app => app/apis}/v2/purchases/models/purchase_status.py (100%) rename {src/app => app/apis}/v2/purchases/repositorys/__init__.py (100%) rename {src/app => app/apis}/v2/purchases/router.py (85%) rename {src/app => app/apis}/v2/purchases/services/__init__.py (100%) rename {src/app => app/apis}/v2/purchases/services/purchase_service.py (94%) rename {src/app => app/apis}/v2/questions/__init__.py (100%) rename {src/app => app/apis}/v2/questions/dtos/__init__.py (100%) rename {src/app => app/apis}/v2/questions/dtos/responses.py (100%) rename {src/app => app/apis}/v2/questions/models/__init__.py (100%) rename {src/app => app/apis}/v2/questions/models/question.py (100%) rename {src/app => app/apis}/v2/questions/repositorys/__init__.py (100%) rename {src/app => app/apis}/v2/questions/router.py (76%) rename {src/app => app/apis}/v2/questions/services/__init__.py (100%) rename {src/app => app/apis}/v2/teller_cards/__init__.py (100%) rename {src/app => app/apis}/v2/teller_cards/dtos/__init__.py (100%) rename {src/app => app/apis}/v2/teller_cards/dtos/request.py (100%) rename {src/app => app/apis}/v2/teller_cards/dtos/response.py (73%) rename {src/app => app/apis}/v2/teller_cards/dtos/teller_card_dto.py (95%) rename {src/app => app/apis}/v2/teller_cards/models/__init__.py (100%) rename {src/app => app/apis}/v2/teller_cards/models/teller_card.py (89%) rename {src/app => app/apis}/v2/teller_cards/querys/__init__.py (100%) rename {src/app => app/apis}/v2/teller_cards/querys/teller_card_query.py (94%) rename {src/app => app/apis}/v2/teller_cards/router.py (76%) rename {src/app => app/apis}/v2/teller_cards/services/__init__.py (100%) rename {src/app => app/apis}/v2/teller_cards/services/teller_card_service.py (82%) rename {src/app => app/apis}/v2/users/__init__.py (100%) rename {src/app => app/apis}/v2/users/dtos/__init__.py (100%) rename {src/app => app/apis}/v2/users/dtos/user_dto.py (100%) rename {src/app => app/apis}/v2/users/dtos/user_info_dto.py (85%) rename {src/app => app/apis}/v2/users/dtos/user_profile_dto.py (100%) rename {src/app => app/apis}/v2/users/models/__init__.py (100%) rename {src/app => app/apis}/v2/users/models/refresh_token.py (100%) rename {src/app => app/apis}/v2/users/models/user.py (90%) rename {src/app => app/apis}/v2/users/querys/__init__.py (100%) rename {src/app => app/apis}/v2/users/querys/user_query.py (100%) rename {src/app => app/apis}/v2/users/services/__init__.py (100%) rename {src/app => app/apis}/v2/users/services/user_service.py (76%) rename {src => app}/celery_worker.py (100%) rename {src => app}/common/__init__.py (100%) rename {src => app}/common/base_models/__init__.py (100%) rename {src => app}/common/base_models/base_dtos/__init__.py (100%) rename {src => app}/common/base_models/base_dtos/base_response.py (100%) rename {src => app}/common/base_models/custom_fields/__init__.py (100%) rename {src => app}/common/exceptions/__init__.py (100%) rename {src => app}/common/exceptions/custom_exception.py (87%) rename {src => app}/common/exceptions/error_code.py (100%) rename {src => app}/common/handlers/__init__.py (100%) rename {src => app}/common/handlers/exception_handler.py (92%) rename {src => app}/common/handlers/router_handler.py (52%) create mode 100644 app/common/post_construct.py rename {src => app}/common/tasks/__init__.py (100%) rename {src => app}/common/tasks/mission_task.py (81%) rename {src => app}/common/tasks/renew_subscription_task.py (79%) rename {src => app}/common/utils/__init__.py (100%) rename {src => app}/common/utils/get_user_id.py (100%) rename {src => app}/common/utils/query_executor.py (95%) rename {src => app}/common/utils/query_formatter.py (100%) rename {src => app}/common/utils/scheduler.py (92%) rename {src => app}/core/__init__.py (100%) rename {src => app}/core/configs/__init__.py (61%) rename {src => app}/core/configs/base_settings.py (100%) rename {src => app}/core/configs/celery_settings.py (87%) rename {src => app}/core/database/__init__.py (100%) rename {src => app}/core/database/database_settings.py (66%) create mode 100644 app/dtos/__init__.py create mode 100644 app/log/__init__.py create mode 100644 app/models/__init__.py create mode 100644 app/services/__init__.py create mode 100644 app/tests/__init__.py delete mode 100644 src/app/v2/badges/dtos/response.py delete mode 100644 src/app/v2/colors/dtos/response.py delete mode 100644 src/common/post_construct.py diff --git a/.gitignore b/.gitignore index 2733f73..7048d74 100644 --- a/.gitignore +++ b/.gitignore @@ -122,7 +122,7 @@ celerybeat.pid *.sage.py # Environments -src/.env.local +app/.env.local .venv env/ venv/ diff --git a/Dockerfile b/Dockerfile index ff0034c..126f0d4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,52 +1,17 @@ -# 1. 기본 Python 이미지를 지정합니다. -FROM python:3.12-slim +# Redis 공식 이미지 사용 +FROM redis:7.2-alpine -# 2. 작업 디렉토리를 설정합니다. -WORKDIR /app -ENV PYTHONPATH="/app/src" +# 포트 오픈 +EXPOSE 6379 -# 3. 필수 패키지를 설치합니다. -RUN apt-get update && apt-get install -y \ - curl \ - git \ - build-essential \ - libssl-dev \ - zlib1g-dev \ - libbz2-dev \ - libreadline-dev \ - libsqlite3-dev \ - wget \ - llvm \ - libncurses5-dev \ - libncursesw5-dev \ - xz-utils \ - tk-dev \ - libffi-dev \ - liblzma-dev \ - python3-openssl \ - default-libmysqlclient-dev \ - libmariadb-dev-compat \ - pkg-config \ - && rm -rf /var/lib/apt/lists/* -# 4. Poetry 설치 -RUN curl -sSL https://install.python-poetry.org | python3 - +# 기본 명령 실행 (기본 설정 사용 시) +CMD ["redis-server"] +# 커스텀 설정 사용 시 아래 명령어 사용 +# CMD ["redis-server", "/usr/local/etc/redis/redis.conf"] -# 5. Poetry 환경 설정 -ENV PATH="/root/.local/bin:$PATH" +# 빌드 명령어 +# docker build -t my-redis . -# 6. 프로젝트 파일 복사 및 의존성 설치 -COPY pyproject.toml poetry.lock /app/ -RUN /bin/bash -c "source ~/.bashrc" -RUN /bin/bash -c "poetry config virtualenvs.create false" -RUN /bin/bash -c "poetry install --no-root" - -# 7. 프로젝트 소스 코드 복사 -COPY . /app - -# 8. ENTRYPOINT 설정 -RUN chmod +x ./scripts/start_app.sh -ENTRYPOINT ["/bin/bash", "./scripts/start_app.sh"] - -# 9. Gunicorn이 8000 포트에서 수신하도록 EXPOSE -EXPOSE 8000 \ No newline at end of file +# 컨테이너 실행 명령어 +# docker run -d -p 6379:6379 --name redis-server my-redis diff --git a/src/main.py b/app/__init__.py similarity index 68% rename from src/main.py rename to app/__init__.py index 7c23be5..e7edade 100644 --- a/src/main.py +++ b/app/__init__.py @@ -2,7 +2,7 @@ from fastapi import FastAPI -from common.post_construct import post_construct +from app.common.post_construct import post_construct logging.basicConfig(level=logging.DEBUG) @@ -18,7 +18,7 @@ def health_check() -> dict[str, str]: return {"message": "Hello World"} -if __name__ == "__main__": - import uvicorn - - uvicorn.run(app, host="0.0.0.0", port=8000) +# if __name__ == "__main__": +# import uvicorn +# +# uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/src/app/__init__.py b/app/apis/__init__.py similarity index 100% rename from src/app/__init__.py rename to app/apis/__init__.py diff --git a/src/app/v2/__init__.py b/app/apis/v2/__init__.py similarity index 100% rename from src/app/v2/__init__.py rename to app/apis/v2/__init__.py diff --git a/src/app/v2/answers/__init__.py b/app/apis/v2/answers/__init__.py similarity index 100% rename from src/app/v2/answers/__init__.py rename to app/apis/v2/answers/__init__.py diff --git a/src/app/v2/answers/dtos/__init__.py b/app/apis/v2/answers/dtos/__init__.py similarity index 100% rename from src/app/v2/answers/dtos/__init__.py rename to app/apis/v2/answers/dtos/__init__.py diff --git a/src/app/v2/answers/dtos/answer_dto.py b/app/apis/v2/answers/dtos/answer_dto.py similarity index 100% rename from src/app/v2/answers/dtos/answer_dto.py rename to app/apis/v2/answers/dtos/answer_dto.py diff --git a/src/app/v2/answers/models/__init__.py b/app/apis/v2/answers/models/__init__.py similarity index 100% rename from src/app/v2/answers/models/__init__.py rename to app/apis/v2/answers/models/__init__.py diff --git a/src/app/v2/answers/models/answer.py b/app/apis/v2/answers/models/answer.py similarity index 93% rename from src/app/v2/answers/models/answer.py rename to app/apis/v2/answers/models/answer.py index 98188f3..3e892d3 100644 --- a/src/app/v2/answers/models/answer.py +++ b/app/apis/v2/answers/models/answer.py @@ -5,14 +5,14 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model -from app.v2.answers.querys.answer_query import ( +from app.apis.v2.answers.querys.answer_query import ( SELECT_ANSWER_BY_USER_UUID_QUERY, SELECT_ANSWER_COUNT_BY_USER_UUID_QUERY, SELECT_ANSWER_COUNT_BY_USER_UUID_QUERY_V2, SELECT_MOST_RECENT_ANSWER_BY_USER_UUID_QUERY, ) -from app.v2.users.models.user import User -from common.utils.query_executor import QueryExecutor +from app.apis.v2.users.models.user import User +from app.common.utils.query_executor import QueryExecutor class Answer(Model): diff --git a/src/app/v2/answers/querys/__init__.py b/app/apis/v2/answers/querys/__init__.py similarity index 100% rename from src/app/v2/answers/querys/__init__.py rename to app/apis/v2/answers/querys/__init__.py diff --git a/src/app/v2/answers/querys/answer_query.py b/app/apis/v2/answers/querys/answer_query.py similarity index 90% rename from src/app/v2/answers/querys/answer_query.py rename to app/apis/v2/answers/querys/answer_query.py index ff5c6f8..e76c1d7 100644 --- a/src/app/v2/answers/querys/answer_query.py +++ b/app/apis/v2/answers/querys/answer_query.py @@ -1,4 +1,4 @@ -from app.v2.users.querys.user_query import USER_ID_QUERY +from app.apis.v2.users.querys.user_query import USER_ID_QUERY SELECT_ANSWER_COUNT_BY_USER_UUID_QUERY = f"SELECT COUNT(*) as answer_count FROM answer WHERE {USER_ID_QUERY}" diff --git a/src/app/v2/answers/router.py b/app/apis/v2/answers/router.py similarity index 83% rename from src/app/v2/answers/router.py rename to app/apis/v2/answers/router.py index 821fb95..b8c4f75 100644 --- a/src/app/v2/answers/router.py +++ b/app/apis/v2/answers/router.py @@ -1,13 +1,10 @@ from fastapi import APIRouter -from app.v2.levels.services.level_service import LevelService +from app.apis.v2.levels.services.level_service import LevelService router = APIRouter(prefix="/answer", tags=["Test용"]) -# FastAPI 비동기 뷰 - - @router.get("/level-up") async def level_up_handler() -> int: user_id = "180a4e40-62f8-46be-b1eb-e7e3dd91cddf" diff --git a/src/app/v2/answers/services/__init__.py b/app/apis/v2/answers/services/__init__.py similarity index 100% rename from src/app/v2/answers/services/__init__.py rename to app/apis/v2/answers/services/__init__.py diff --git a/src/app/v2/answers/services/answer_service.py b/app/apis/v2/answers/services/answer_service.py similarity index 97% rename from src/app/v2/answers/services/answer_service.py rename to app/apis/v2/answers/services/answer_service.py index 2d30db9..ca5aaef 100644 --- a/src/app/v2/answers/services/answer_service.py +++ b/app/apis/v2/answers/services/answer_service.py @@ -3,7 +3,7 @@ import pytz -from app.v2.answers.models.answer import Answer +from app.apis.v2.answers.models.answer import Answer class AnswerService: diff --git a/src/app/v2/badges/__init__.py b/app/apis/v2/badges/__init__.py similarity index 100% rename from src/app/v2/badges/__init__.py rename to app/apis/v2/badges/__init__.py diff --git a/src/app/v2/badges/dtos/__init__.py b/app/apis/v2/badges/dtos/__init__.py similarity index 100% rename from src/app/v2/badges/dtos/__init__.py rename to app/apis/v2/badges/dtos/__init__.py diff --git a/src/app/v2/badges/dtos/badge_dto.py b/app/apis/v2/badges/dtos/badge_dto.py similarity index 100% rename from src/app/v2/badges/dtos/badge_dto.py rename to app/apis/v2/badges/dtos/badge_dto.py diff --git a/app/apis/v2/badges/dtos/response.py b/app/apis/v2/badges/dtos/response.py new file mode 100644 index 0000000..ac98ae0 --- /dev/null +++ b/app/apis/v2/badges/dtos/response.py @@ -0,0 +1,6 @@ +from app.apis.v2.badges.dtos.badge_dto import BadgeDTO +from app.common.base_models.base_dtos.base_response import BaseResponseDTO + + +class BadgeListResponseDTO(BaseResponseDTO): + data: list[BadgeDTO] diff --git a/src/app/v2/badges/models/__init__.py b/app/apis/v2/badges/models/__init__.py similarity index 100% rename from src/app/v2/badges/models/__init__.py rename to app/apis/v2/badges/models/__init__.py diff --git a/src/app/v2/badges/models/badge.py b/app/apis/v2/badges/models/badge.py similarity index 92% rename from src/app/v2/badges/models/badge.py rename to app/apis/v2/badges/models/badge.py index 3de92a8..3ab436e 100644 --- a/src/app/v2/badges/models/badge.py +++ b/app/apis/v2/badges/models/badge.py @@ -4,14 +4,14 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model -from app.v2.badges.querys.badge_query import ( +from app.apis.v2.badges.querys.badge_query import ( INSERT_BADGE_CODE_FOR_USER_QUERY, SELECT_BADGE_BY_USER_UUID_QUERY, SELECT_BADGE_CODE_BY_USER_UUID_QUERY, SELECT_BADGE_COUNT_BY_USER_UUID_QUERY, ) -from app.v2.users.models.user import User -from common.utils.query_executor import QueryExecutor +from app.apis.v2.users.models.user import User +from app.common.utils.query_executor import QueryExecutor class Badge(Model): diff --git a/src/app/v2/badges/querys/__init__.py b/app/apis/v2/badges/querys/__init__.py similarity index 100% rename from src/app/v2/badges/querys/__init__.py rename to app/apis/v2/badges/querys/__init__.py diff --git a/src/app/v2/badges/querys/badge_query.py b/app/apis/v2/badges/querys/badge_query.py similarity index 91% rename from src/app/v2/badges/querys/badge_query.py rename to app/apis/v2/badges/querys/badge_query.py index 2dae63e..d337e90 100644 --- a/src/app/v2/badges/querys/badge_query.py +++ b/app/apis/v2/badges/querys/badge_query.py @@ -1,4 +1,4 @@ -from app.v2.users.querys.user_query import USER_ID_QUERY +from app.apis.v2.users.querys.user_query import USER_ID_QUERY SELECT_BADGE_COUNT_BY_USER_UUID_QUERY = f""" SELECT COUNT(*) as badge_count diff --git a/src/app/v2/badges/router.py b/app/apis/v2/badges/router.py similarity index 78% rename from src/app/v2/badges/router.py rename to app/apis/v2/badges/router.py index a3ec584..409c19c 100644 --- a/src/app/v2/badges/router.py +++ b/app/apis/v2/badges/router.py @@ -1,7 +1,7 @@ from fastapi import APIRouter, status -from app.v2.badges.dtos.response import BadgeListResponseDTO -from app.v2.badges.services.badge_service import BadgeService +from app.apis.v2.badges.dtos.response import BadgeListResponseDTO +from app.apis.v2.badges.services.badge_service import BadgeService router = APIRouter(prefix="/user/badge", tags=["Badge"]) diff --git a/src/app/v2/badges/services/__init__.py b/app/apis/v2/badges/services/__init__.py similarity index 100% rename from src/app/v2/badges/services/__init__.py rename to app/apis/v2/badges/services/__init__.py diff --git a/src/app/v2/badges/services/badge_service.py b/app/apis/v2/badges/services/badge_service.py similarity index 89% rename from src/app/v2/badges/services/badge_service.py rename to app/apis/v2/badges/services/badge_service.py index e611757..be7a00e 100644 --- a/src/app/v2/badges/services/badge_service.py +++ b/app/apis/v2/badges/services/badge_service.py @@ -1,5 +1,5 @@ -from app.v2.badges.dtos.badge_dto import BadgeCodeDTO, BadgeDTO -from app.v2.badges.models.badge import Badge, BadgeInventory +from app.apis.v2.badges.dtos.badge_dto import BadgeCodeDTO, BadgeDTO +from app.apis.v2.badges.models.badge import Badge, BadgeInventory class BadgeService: diff --git a/src/app/v2/cheese_managers/__init__.py b/app/apis/v2/cheese_managers/__init__.py similarity index 100% rename from src/app/v2/cheese_managers/__init__.py rename to app/apis/v2/cheese_managers/__init__.py diff --git a/src/app/v2/cheese_managers/dtos/__init__.py b/app/apis/v2/cheese_managers/dtos/__init__.py similarity index 100% rename from src/app/v2/cheese_managers/dtos/__init__.py rename to app/apis/v2/cheese_managers/dtos/__init__.py diff --git a/src/app/v2/cheese_managers/dtos/cheese_dto.py b/app/apis/v2/cheese_managers/dtos/cheese_dto.py similarity index 89% rename from src/app/v2/cheese_managers/dtos/cheese_dto.py rename to app/apis/v2/cheese_managers/dtos/cheese_dto.py index 83dc866..87e7aa7 100644 --- a/src/app/v2/cheese_managers/dtos/cheese_dto.py +++ b/app/apis/v2/cheese_managers/dtos/cheese_dto.py @@ -2,7 +2,7 @@ from pydantic import BaseModel -from common.base_models.base_dtos.base_response import BaseResponseDTO +from app.common.base_models.base_dtos.base_response import BaseResponseDTO class CheeseAmountResult(TypedDict): diff --git a/src/app/v2/cheese_managers/models/__init__.py b/app/apis/v2/cheese_managers/models/__init__.py similarity index 100% rename from src/app/v2/cheese_managers/models/__init__.py rename to app/apis/v2/cheese_managers/models/__init__.py diff --git a/src/app/v2/cheese_managers/models/cheese_manager.py b/app/apis/v2/cheese_managers/models/cheese_manager.py similarity index 97% rename from src/app/v2/cheese_managers/models/cheese_manager.py rename to app/apis/v2/cheese_managers/models/cheese_manager.py index ea7dbfd..641d61a 100644 --- a/src/app/v2/cheese_managers/models/cheese_manager.py +++ b/app/apis/v2/cheese_managers/models/cheese_manager.py @@ -6,7 +6,7 @@ from tortoise.functions import Sum from tortoise.models import Model -from app.v2.cheese_managers.models.cheese_status import CheeseStatus +from app.apis.v2.cheese_managers.models.cheese_status import CheeseStatus class CheeseManager(Model): diff --git a/src/app/v2/cheese_managers/models/cheese_status.py b/app/apis/v2/cheese_managers/models/cheese_status.py similarity index 100% rename from src/app/v2/cheese_managers/models/cheese_status.py rename to app/apis/v2/cheese_managers/models/cheese_status.py diff --git a/src/app/v2/cheese_managers/querys/__init__.py b/app/apis/v2/cheese_managers/querys/__init__.py similarity index 100% rename from src/app/v2/cheese_managers/querys/__init__.py rename to app/apis/v2/cheese_managers/querys/__init__.py diff --git a/src/app/v2/cheese_managers/router.py b/app/apis/v2/cheese_managers/router.py similarity index 69% rename from src/app/v2/cheese_managers/router.py rename to app/apis/v2/cheese_managers/router.py index 2589c81..f39fd09 100644 --- a/src/app/v2/cheese_managers/router.py +++ b/app/apis/v2/cheese_managers/router.py @@ -1,8 +1,8 @@ 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 +from app.apis.v2.cheese_managers.dtos.cheese_dto import CheeseResponseDTO +from app.apis.v2.cheese_managers.services.cheese_service import CheeseService +from app.apis.v2.users.services.user_service import UserService router = APIRouter(prefix="/cheese", tags=["Cheese"]) diff --git a/src/app/v2/cheese_managers/services/__init__.py b/app/apis/v2/cheese_managers/services/__init__.py similarity index 100% rename from src/app/v2/cheese_managers/services/__init__.py rename to app/apis/v2/cheese_managers/services/__init__.py diff --git a/src/app/v2/cheese_managers/services/cheese_service.py b/app/apis/v2/cheese_managers/services/cheese_service.py similarity index 84% rename from src/app/v2/cheese_managers/services/cheese_service.py rename to app/apis/v2/cheese_managers/services/cheese_service.py index d846548..f44f4f6 100644 --- a/src/app/v2/cheese_managers/services/cheese_service.py +++ b/app/apis/v2/cheese_managers/services/cheese_service.py @@ -1,4 +1,4 @@ -from app.v2.cheese_managers.models.cheese_manager import CheeseManager +from app.apis.v2.cheese_managers.models.cheese_manager import CheeseManager class CheeseService: diff --git a/src/app/v2/colors/__init__.py b/app/apis/v2/colors/__init__.py similarity index 100% rename from src/app/v2/colors/__init__.py rename to app/apis/v2/colors/__init__.py diff --git a/src/app/v2/colors/dtos/__init__.py b/app/apis/v2/colors/dtos/__init__.py similarity index 100% rename from src/app/v2/colors/dtos/__init__.py rename to app/apis/v2/colors/dtos/__init__.py diff --git a/src/app/v2/colors/dtos/color_dto.py b/app/apis/v2/colors/dtos/color_dto.py similarity index 100% rename from src/app/v2/colors/dtos/color_dto.py rename to app/apis/v2/colors/dtos/color_dto.py diff --git a/app/apis/v2/colors/dtos/response.py b/app/apis/v2/colors/dtos/response.py new file mode 100644 index 0000000..5dd1429 --- /dev/null +++ b/app/apis/v2/colors/dtos/response.py @@ -0,0 +1,6 @@ +from app.apis.v2.colors.dtos.color_dto import ColorDTO +from app.common.base_models.base_dtos.base_response import BaseResponseDTO + + +class ColorListResponseDTO(BaseResponseDTO): + data: list[ColorDTO] diff --git a/src/app/v2/colors/models/__init__.py b/app/apis/v2/colors/models/__init__.py similarity index 100% rename from src/app/v2/colors/models/__init__.py rename to app/apis/v2/colors/models/__init__.py diff --git a/src/app/v2/colors/models/color.py b/app/apis/v2/colors/models/color.py similarity index 92% rename from src/app/v2/colors/models/color.py rename to app/apis/v2/colors/models/color.py index acf8bc2..c56f763 100644 --- a/src/app/v2/colors/models/color.py +++ b/app/apis/v2/colors/models/color.py @@ -4,13 +4,13 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model -from app.v2.colors.querys.color_query import ( +from app.apis.v2.colors.querys.color_query import ( INSERT_COLOR_CODE_FOR_USER_QUERY, SELECT_COLOR_BY_USER_UUID_QUERY, SELECT_COLOR_CODE_BY_USER_UUID_QUERY, ) -from app.v2.users.models.user import User -from common.utils.query_executor import QueryExecutor +from app.apis.v2.users.models.user import User +from app.common.utils.query_executor import QueryExecutor class Color(Model): diff --git a/src/app/v2/colors/querys/__init__.py b/app/apis/v2/colors/querys/__init__.py similarity index 100% rename from src/app/v2/colors/querys/__init__.py rename to app/apis/v2/colors/querys/__init__.py diff --git a/src/app/v2/colors/querys/color_query.py b/app/apis/v2/colors/querys/color_query.py similarity index 88% rename from src/app/v2/colors/querys/color_query.py rename to app/apis/v2/colors/querys/color_query.py index 0121d8d..4b02c0f 100644 --- a/src/app/v2/colors/querys/color_query.py +++ b/app/apis/v2/colors/querys/color_query.py @@ -1,4 +1,4 @@ -from app.v2.users.querys.user_query import USER_ID_QUERY +from app.apis.v2.users.querys.user_query import USER_ID_QUERY SELECT_COLOR_CODE_BY_USER_UUID_QUERY = f""" SELECT color_code diff --git a/src/app/v2/colors/router.py b/app/apis/v2/colors/router.py similarity index 78% rename from src/app/v2/colors/router.py rename to app/apis/v2/colors/router.py index 0b11aa3..02106d6 100644 --- a/src/app/v2/colors/router.py +++ b/app/apis/v2/colors/router.py @@ -1,7 +1,7 @@ from fastapi import APIRouter, status -from app.v2.colors.dtos.response import ColorListResponseDTO -from app.v2.colors.services.color_service import ColorService +from app.apis.v2.colors.dtos.response import ColorListResponseDTO +from app.apis.v2.colors.services.color_service import ColorService router = APIRouter(prefix="/user/color", tags=["Color"]) diff --git a/src/app/v2/colors/services/__init__.py b/app/apis/v2/colors/services/__init__.py similarity index 100% rename from src/app/v2/colors/services/__init__.py rename to app/apis/v2/colors/services/__init__.py diff --git a/src/app/v2/colors/services/color_service.py b/app/apis/v2/colors/services/color_service.py similarity index 83% rename from src/app/v2/colors/services/color_service.py rename to app/apis/v2/colors/services/color_service.py index 5b03137..5d202bf 100644 --- a/src/app/v2/colors/services/color_service.py +++ b/app/apis/v2/colors/services/color_service.py @@ -1,6 +1,6 @@ -from app.v2.colors.dtos.color_dto import ColorCodeDTO, ColorDTO -from app.v2.colors.models.color import Color, ColorInventory -from app.v2.users.services.user_service import UserService +from app.apis.v2.colors.dtos.color_dto import ColorCodeDTO, ColorDTO +from app.apis.v2.colors.models.color import Color, ColorInventory +from app.apis.v2.users.services.user_service import UserService class ColorService: diff --git a/src/app/v2/emotions/__init__.py b/app/apis/v2/emotions/__init__.py similarity index 100% rename from src/app/v2/emotions/__init__.py rename to app/apis/v2/emotions/__init__.py diff --git a/src/app/v2/emotions/dtos/__init__.py b/app/apis/v2/emotions/dtos/__init__.py similarity index 100% rename from src/app/v2/emotions/dtos/__init__.py rename to app/apis/v2/emotions/dtos/__init__.py diff --git a/src/app/v2/emotions/dtos/response.py b/app/apis/v2/emotions/dtos/response.py similarity index 79% rename from src/app/v2/emotions/dtos/response.py rename to app/apis/v2/emotions/dtos/response.py index 3fcac1e..6acc2c3 100644 --- a/src/app/v2/emotions/dtos/response.py +++ b/app/apis/v2/emotions/dtos/response.py @@ -1,6 +1,6 @@ from pydantic import BaseModel -from common.base_models.base_dtos.base_response import BaseResponseDTO +from app.common.base_models.base_dtos.base_response import BaseResponseDTO class EmotionDTO(BaseModel): diff --git a/src/app/v2/emotions/models/__init__.py b/app/apis/v2/emotions/models/__init__.py similarity index 100% rename from src/app/v2/emotions/models/__init__.py rename to app/apis/v2/emotions/models/__init__.py diff --git a/src/app/v2/emotions/models/emotion.py b/app/apis/v2/emotions/models/emotion.py similarity index 89% rename from src/app/v2/emotions/models/emotion.py rename to app/apis/v2/emotions/models/emotion.py index 58732fd..345f6cf 100644 --- a/src/app/v2/emotions/models/emotion.py +++ b/app/apis/v2/emotions/models/emotion.py @@ -3,12 +3,12 @@ from tortoise import fields, models from tortoise.fields import ForeignKeyRelation -from app.v2.emotions.querys.emotion_query import ( +from app.apis.v2.emotions.querys.emotion_query import ( INSERT_EMOTION_CODE_FOR_USER_QUERY, SELECT_EMOTION_CODE_BY_USER_UUID_QUERY, ) -from app.v2.users.models.user import User -from common.utils.query_executor import QueryExecutor +from app.apis.v2.users.models.user import User +from app.common.utils.query_executor import QueryExecutor class Emotion(models.Model): diff --git a/src/app/v2/emotions/querys/__init__.py b/app/apis/v2/emotions/querys/__init__.py similarity index 100% rename from src/app/v2/emotions/querys/__init__.py rename to app/apis/v2/emotions/querys/__init__.py diff --git a/src/app/v2/emotions/querys/emotion_query.py b/app/apis/v2/emotions/querys/emotion_query.py similarity index 82% rename from src/app/v2/emotions/querys/emotion_query.py rename to app/apis/v2/emotions/querys/emotion_query.py index 813d727..40595e7 100644 --- a/src/app/v2/emotions/querys/emotion_query.py +++ b/app/apis/v2/emotions/querys/emotion_query.py @@ -1,4 +1,4 @@ -from app.v2.users.querys.user_query import USER_ID_QUERY +from app.apis.v2.users.querys.user_query import USER_ID_QUERY SELECT_EMOTION_CODE_BY_USER_UUID_QUERY = f""" SELECT emotion_code diff --git a/src/app/v2/emotions/router.py b/app/apis/v2/emotions/router.py similarity index 76% rename from src/app/v2/emotions/router.py rename to app/apis/v2/emotions/router.py index 3fea988..0a64a68 100644 --- a/src/app/v2/emotions/router.py +++ b/app/apis/v2/emotions/router.py @@ -1,7 +1,7 @@ from fastapi import APIRouter, status -from app.v2.emotions.dtos.response import EmotionListResponseDTO -from app.v2.emotions.services.emotion_service import EmotionService +from app.apis.v2.emotions.dtos.response import EmotionListResponseDTO +from app.apis.v2.emotions.services.emotion_service import EmotionService router = APIRouter(prefix="/user/emotion", tags=["Emotion"]) diff --git a/src/app/v2/emotions/services/__init__.py b/app/apis/v2/emotions/services/__init__.py similarity index 100% rename from src/app/v2/emotions/services/__init__.py rename to app/apis/v2/emotions/services/__init__.py diff --git a/src/app/v2/emotions/services/emotion_service.py b/app/apis/v2/emotions/services/emotion_service.py similarity index 87% rename from src/app/v2/emotions/services/emotion_service.py rename to app/apis/v2/emotions/services/emotion_service.py index bdcc9a6..835d08f 100644 --- a/src/app/v2/emotions/services/emotion_service.py +++ b/app/apis/v2/emotions/services/emotion_service.py @@ -1,8 +1,8 @@ from typing import Any -from app.v2.emotions.dtos.response import EmotionDTO, EmotionListResponseDTO -from app.v2.emotions.models.emotion import Emotion, EmotionInventory -from app.v2.users.services.user_service import UserService +from app.apis.v2.emotions.dtos.response import EmotionDTO +from app.apis.v2.emotions.models.emotion import Emotion, EmotionInventory +from app.apis.v2.users.services.user_service import UserService emotion_mapping = { "EM_HAPPY": 1, diff --git a/src/app/v2/items/__init__.py b/app/apis/v2/items/__init__.py similarity index 100% rename from src/app/v2/items/__init__.py rename to app/apis/v2/items/__init__.py diff --git a/src/app/v2/items/dtos/__init__.py b/app/apis/v2/items/dtos/__init__.py similarity index 100% rename from src/app/v2/items/dtos/__init__.py rename to app/apis/v2/items/dtos/__init__.py diff --git a/src/app/v2/items/dtos/item_dto.py b/app/apis/v2/items/dtos/item_dto.py similarity index 100% rename from src/app/v2/items/dtos/item_dto.py rename to app/apis/v2/items/dtos/item_dto.py diff --git a/src/app/v2/items/models/__init__.py b/app/apis/v2/items/models/__init__.py similarity index 100% rename from src/app/v2/items/models/__init__.py rename to app/apis/v2/items/models/__init__.py diff --git a/src/app/v2/items/models/item.py b/app/apis/v2/items/models/item.py similarity index 100% rename from src/app/v2/items/models/item.py rename to app/apis/v2/items/models/item.py diff --git a/src/app/v2/items/repositorys/__init__.py b/app/apis/v2/items/repositorys/__init__.py similarity index 100% rename from src/app/v2/items/repositorys/__init__.py rename to app/apis/v2/items/repositorys/__init__.py diff --git a/src/app/v2/items/router.py b/app/apis/v2/items/router.py similarity index 100% rename from src/app/v2/items/router.py rename to app/apis/v2/items/router.py diff --git a/src/app/v2/items/services/__init__.py b/app/apis/v2/items/services/__init__.py similarity index 100% rename from src/app/v2/items/services/__init__.py rename to app/apis/v2/items/services/__init__.py diff --git a/src/app/v2/levels/__init__.py b/app/apis/v2/levels/__init__.py similarity index 100% rename from src/app/v2/levels/__init__.py rename to app/apis/v2/levels/__init__.py diff --git a/src/app/v2/levels/dtos/__init__.py b/app/apis/v2/levels/dtos/__init__.py similarity index 100% rename from src/app/v2/levels/dtos/__init__.py rename to app/apis/v2/levels/dtos/__init__.py diff --git a/src/app/v2/levels/dtos/level_dto.py b/app/apis/v2/levels/dtos/level_dto.py similarity index 100% rename from src/app/v2/levels/dtos/level_dto.py rename to app/apis/v2/levels/dtos/level_dto.py diff --git a/src/app/v2/levels/models/__init__.py b/app/apis/v2/levels/models/__init__.py similarity index 100% rename from src/app/v2/levels/models/__init__.py rename to app/apis/v2/levels/models/__init__.py diff --git a/src/app/v2/levels/models/level.py b/app/apis/v2/levels/models/level.py similarity index 83% rename from src/app/v2/levels/models/level.py rename to app/apis/v2/levels/models/level.py index 8081d0f..f30fc0e 100644 --- a/src/app/v2/levels/models/level.py +++ b/app/apis/v2/levels/models/level.py @@ -3,8 +3,11 @@ from tortoise import fields from tortoise.models import Model -from app.v2.levels.querys.level_query import SELECT_USER_LEVEL_AND_REQUIRED_EXP_QUERY, UPDATE_USER_LEVEL_AND_EXP_QUERY -from common.utils.query_executor import QueryExecutor +from app.apis.v2.levels.querys.level_query import ( + SELECT_USER_LEVEL_AND_REQUIRED_EXP_QUERY, + UPDATE_USER_LEVEL_AND_EXP_QUERY, +) +from app.common.utils.query_executor import QueryExecutor class Level(Model): diff --git a/src/app/v2/levels/querys/__init__.py b/app/apis/v2/levels/querys/__init__.py similarity index 100% rename from src/app/v2/levels/querys/__init__.py rename to app/apis/v2/levels/querys/__init__.py diff --git a/src/app/v2/levels/querys/level_query.py b/app/apis/v2/levels/querys/level_query.py similarity index 94% rename from src/app/v2/levels/querys/level_query.py rename to app/apis/v2/levels/querys/level_query.py index e92b0fe..562e460 100644 --- a/src/app/v2/levels/querys/level_query.py +++ b/app/apis/v2/levels/querys/level_query.py @@ -1,4 +1,4 @@ -from app.v2.users.querys.user_query import USER_ID_QUERY +from app.apis.v2.users.querys.user_query import USER_ID_QUERY SELECT_USER_LEVEL_AND_EXP_BY_USER_UUID_QUERY = f""" SELECT diff --git a/src/app/v2/levels/router.py b/app/apis/v2/levels/router.py similarity index 100% rename from src/app/v2/levels/router.py rename to app/apis/v2/levels/router.py diff --git a/src/app/v2/levels/services/__init__.py b/app/apis/v2/levels/services/__init__.py similarity index 100% rename from src/app/v2/levels/services/__init__.py rename to app/apis/v2/levels/services/__init__.py diff --git a/src/app/v2/levels/services/level_service.py b/app/apis/v2/levels/services/level_service.py similarity index 93% rename from src/app/v2/levels/services/level_service.py rename to app/apis/v2/levels/services/level_service.py index a723d00..0bf8dab 100644 --- a/src/app/v2/levels/services/level_service.py +++ b/app/apis/v2/levels/services/level_service.py @@ -1,8 +1,8 @@ from fastapi import HTTPException -from app.v2.answers.services.answer_service import AnswerService -from app.v2.levels.dtos.level_dto import LevelDTO, LevelInfoDTO -from app.v2.levels.models.level import Level +from app.apis.v2.answers.services.answer_service import AnswerService +from app.apis.v2.levels.dtos.level_dto import LevelDTO, LevelInfoDTO +from app.apis.v2.levels.models.level import Level class LevelService: diff --git a/src/app/v2/likes/__init__.py b/app/apis/v2/likes/__init__.py similarity index 100% rename from src/app/v2/likes/__init__.py rename to app/apis/v2/likes/__init__.py diff --git a/src/app/v2/likes/models/__init__.py b/app/apis/v2/likes/models/__init__.py similarity index 100% rename from src/app/v2/likes/models/__init__.py rename to app/apis/v2/likes/models/__init__.py diff --git a/src/app/v2/likes/models/like.py b/app/apis/v2/likes/models/like.py similarity index 81% rename from src/app/v2/likes/models/like.py rename to app/apis/v2/likes/models/like.py index 8369c8c..bbbd06a 100644 --- a/src/app/v2/likes/models/like.py +++ b/app/apis/v2/likes/models/like.py @@ -4,10 +4,10 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model -from app.v2.answers.models.answer import Answer -from app.v2.likes.querys.like_query import SELECT_UNIQUE_LIKES_COUNT_BY_USER_TODAY_QUERY -from app.v2.users.models.user import User -from common.utils.query_executor import QueryExecutor +from app.apis.v2.answers.models.answer import Answer +from app.apis.v2.likes.querys.like_query import SELECT_UNIQUE_LIKES_COUNT_BY_USER_TODAY_QUERY +from app.apis.v2.users.models.user import User +from app.common.utils.query_executor import QueryExecutor class Like(Model): diff --git a/src/app/v2/likes/querys/__init__.py b/app/apis/v2/likes/querys/__init__.py similarity index 100% rename from src/app/v2/likes/querys/__init__.py rename to app/apis/v2/likes/querys/__init__.py diff --git a/src/app/v2/likes/querys/like_query.py b/app/apis/v2/likes/querys/like_query.py similarity index 100% rename from src/app/v2/likes/querys/like_query.py rename to app/apis/v2/likes/querys/like_query.py diff --git a/src/app/v2/missions/__init__.py b/app/apis/v2/missions/__init__.py similarity index 100% rename from src/app/v2/missions/__init__.py rename to app/apis/v2/missions/__init__.py diff --git a/src/app/v2/missions/dtos/__init__.py b/app/apis/v2/missions/dtos/__init__.py similarity index 100% rename from src/app/v2/missions/dtos/__init__.py rename to app/apis/v2/missions/dtos/__init__.py diff --git a/src/app/v2/missions/dtos/mission_dto.py b/app/apis/v2/missions/dtos/mission_dto.py similarity index 100% rename from src/app/v2/missions/dtos/mission_dto.py rename to app/apis/v2/missions/dtos/mission_dto.py diff --git a/src/app/v2/missions/dtos/request.py b/app/apis/v2/missions/dtos/request.py similarity index 100% rename from src/app/v2/missions/dtos/request.py rename to app/apis/v2/missions/dtos/request.py diff --git a/src/app/v2/missions/dtos/response.py b/app/apis/v2/missions/dtos/response.py similarity index 100% rename from src/app/v2/missions/dtos/response.py rename to app/apis/v2/missions/dtos/response.py diff --git a/src/app/v2/missions/dtos/reward_dto.py b/app/apis/v2/missions/dtos/reward_dto.py similarity index 94% rename from src/app/v2/missions/dtos/reward_dto.py rename to app/apis/v2/missions/dtos/reward_dto.py index c0f46f9..008accb 100644 --- a/src/app/v2/missions/dtos/reward_dto.py +++ b/app/apis/v2/missions/dtos/reward_dto.py @@ -1,4 +1,4 @@ -from typing import Any, Optional +from typing import Optional from pydantic import BaseModel diff --git a/src/app/v2/missions/models/__init__.py b/app/apis/v2/missions/models/__init__.py similarity index 100% rename from src/app/v2/missions/models/__init__.py rename to app/apis/v2/missions/models/__init__.py diff --git a/src/app/v2/missions/models/mission.py b/app/apis/v2/missions/models/mission.py similarity index 88% rename from src/app/v2/missions/models/mission.py rename to app/apis/v2/missions/models/mission.py index 94f281f..c8dbccf 100644 --- a/src/app/v2/missions/models/mission.py +++ b/app/apis/v2/missions/models/mission.py @@ -4,9 +4,9 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model -from app.v2.missions.querys.mission_query import SELECT_USER_MISSIONS_QUERY, UPDATE_USER_MISSION_PROGRESS_QUERY -from app.v2.users.models.user import User -from common.utils.query_executor import QueryExecutor +from app.apis.v2.missions.querys.mission_query import SELECT_USER_MISSIONS_QUERY, UPDATE_USER_MISSION_PROGRESS_QUERY +from app.apis.v2.users.models.user import User +from app.common.utils.query_executor import QueryExecutor class UserMission(Model): diff --git a/src/app/v2/missions/querys/__init__.py b/app/apis/v2/missions/querys/__init__.py similarity index 100% rename from src/app/v2/missions/querys/__init__.py rename to app/apis/v2/missions/querys/__init__.py diff --git a/src/app/v2/missions/querys/mission_query.py b/app/apis/v2/missions/querys/mission_query.py similarity index 87% rename from src/app/v2/missions/querys/mission_query.py rename to app/apis/v2/missions/querys/mission_query.py index 40f5671..1a57e43 100644 --- a/src/app/v2/missions/querys/mission_query.py +++ b/app/apis/v2/missions/querys/mission_query.py @@ -1,5 +1,3 @@ -from app.v2.users.querys.user_query import USER_ID_QUERY - SELECT_USER_MISSIONS_QUERY = """ SELECT um.* FROM user_mission um diff --git a/src/app/v2/missions/router.py b/app/apis/v2/missions/router.py similarity index 78% rename from src/app/v2/missions/router.py rename to app/apis/v2/missions/router.py index da468cd..db92de4 100644 --- a/src/app/v2/missions/router.py +++ b/app/apis/v2/missions/router.py @@ -2,8 +2,8 @@ from fastapi import APIRouter, Depends -from app.v2.missions.services.mission_service import MissionService -from core.configs.celery_settings import process_mission_in_background +from app.apis.v2.missions.services.mission_service import MissionService +from app.core.configs.celery_settings import process_mission_in_background router = APIRouter(prefix="/mission", tags=["Mission"]) diff --git a/src/app/v2/missions/services/__init__.py b/app/apis/v2/missions/services/__init__.py similarity index 100% rename from src/app/v2/missions/services/__init__.py rename to app/apis/v2/missions/services/__init__.py diff --git a/src/app/v2/missions/services/mission_service.py b/app/apis/v2/missions/services/mission_service.py similarity index 94% rename from src/app/v2/missions/services/mission_service.py rename to app/apis/v2/missions/services/mission_service.py index 039df56..7469d89 100644 --- a/src/app/v2/missions/services/mission_service.py +++ b/app/apis/v2/missions/services/mission_service.py @@ -1,5 +1,5 @@ import asyncio -from datetime import date, datetime, timedelta, timezone +from datetime import datetime, timedelta, timezone from typing import Optional import pytz @@ -7,18 +7,18 @@ from tortoise.exceptions import DoesNotExist from tortoise.transactions import atomic -from app.v2.answers.services.answer_service import AnswerService -from app.v2.badges.services.badge_service import BadgeService -from app.v2.cheese_managers.services.cheese_service import CheeseService -from app.v2.colors.services.color_service import ColorService -from app.v2.items.models.item import ItemInventory, ItemInventoryRewardInventory, RewardInventory -from app.v2.levels.services.level_service import LevelService -from app.v2.likes.models.like import Like -from app.v2.missions.dtos.mission_dto import UserMissionDTO -from app.v2.missions.dtos.reward_dto import RewardDTO -from app.v2.missions.models.mission import MissionInventory, UserMission -from app.v2.notices.services.notice_service import NoticeService -from app.v2.users.services.user_service import UserService +from app.apis.v2.answers.services.answer_service import AnswerService +from app.apis.v2.badges.services.badge_service import BadgeService +from app.apis.v2.cheese_managers.services.cheese_service import CheeseService +from app.apis.v2.colors.services.color_service import ColorService +from app.apis.v2.items.models.item import ItemInventory, ItemInventoryRewardInventory, RewardInventory +from app.apis.v2.levels.services.level_service import LevelService +from app.apis.v2.likes.models.like import Like +from app.apis.v2.missions.dtos.mission_dto import UserMissionDTO +from app.apis.v2.missions.dtos.reward_dto import RewardDTO +from app.apis.v2.missions.models.mission import MissionInventory, UserMission +from app.apis.v2.notices.services.notice_service import NoticeService +from app.apis.v2.users.services.user_service import UserService class MissionService: @@ -140,7 +140,7 @@ async def evaluate_mission_condition(self, user_id: str, mission_code: str) -> i return 1 elif mission_code == "MS_DAILY_LIKE_3_PER_DAY" and await self.check_three_likes_different_posts(user_id): return 1 - elif mission_code == f"MS_LV_UP" and await LevelService.level_up(user_id=user_id): + elif mission_code == "MS_LV_UP" and await LevelService.level_up(user_id=user_id): return 1 elif mission_code == "MS_DAILY_POST_GENERAL" and await self.check_daily_post(user_id): return 1 diff --git a/src/app/v2/mobiles/__init__.py b/app/apis/v2/mobiles/__init__.py similarity index 100% rename from src/app/v2/mobiles/__init__.py rename to app/apis/v2/mobiles/__init__.py diff --git a/src/app/v2/mobiles/dtos/__init__.py b/app/apis/v2/mobiles/dtos/__init__.py similarity index 100% rename from src/app/v2/mobiles/dtos/__init__.py rename to app/apis/v2/mobiles/dtos/__init__.py diff --git a/src/app/v2/mobiles/dtos/mypage_response.py b/app/apis/v2/mobiles/dtos/mypage_response.py similarity index 68% rename from src/app/v2/mobiles/dtos/mypage_response.py rename to app/apis/v2/mobiles/dtos/mypage_response.py index 4d492aa..f1ed53b 100644 --- a/src/app/v2/mobiles/dtos/mypage_response.py +++ b/app/apis/v2/mobiles/dtos/mypage_response.py @@ -1,8 +1,8 @@ from pydantic import BaseModel -from app.v2.levels.dtos.level_dto import LevelInfoDTO -from app.v2.users.dtos.user_profile_dto import UserProfileDTO -from common.base_models.base_dtos.base_response import BaseResponseDTO +from app.apis.v2.levels.dtos.level_dto import LevelInfoDTO +from app.apis.v2.users.dtos.user_profile_dto import UserProfileDTO +from app.common.base_models.base_dtos.base_response import BaseResponseDTO class UserProfileWithLevel(BaseModel): diff --git a/src/app/v2/mobiles/dtos/teller_card_response.py b/app/apis/v2/mobiles/dtos/teller_card_response.py similarity index 68% rename from src/app/v2/mobiles/dtos/teller_card_response.py rename to app/apis/v2/mobiles/dtos/teller_card_response.py index 29f8349..bd1b15c 100644 --- a/src/app/v2/mobiles/dtos/teller_card_response.py +++ b/app/apis/v2/mobiles/dtos/teller_card_response.py @@ -1,12 +1,12 @@ -from typing import List, Optional +from typing import Optional from pydantic import BaseModel -from app.v2.badges.dtos.badge_dto import BadgeDTO -from app.v2.colors.dtos.color_dto import ColorDTO -from app.v2.levels.dtos.level_dto import LevelDTO, LevelInfoDTO -from app.v2.users.dtos.user_info_dto import UserInfoDTO -from common.base_models.base_dtos.base_response import BaseResponseDTO +from app.apis.v2.badges.dtos.badge_dto import BadgeDTO +from app.apis.v2.colors.dtos.color_dto import ColorDTO +from app.apis.v2.levels.dtos.level_dto import LevelInfoDTO +from app.apis.v2.users.dtos.user_info_dto import UserInfoDTO +from app.common.base_models.base_dtos.base_response import BaseResponseDTO class DataDTO(BaseModel): diff --git a/src/app/v2/mobiles/router.py b/app/apis/v2/mobiles/router.py similarity index 76% rename from src/app/v2/mobiles/router.py rename to app/apis/v2/mobiles/router.py index 1e2c9fa..71845a1 100644 --- a/src/app/v2/mobiles/router.py +++ b/app/apis/v2/mobiles/router.py @@ -1,18 +1,18 @@ import asyncio -from fastapi import APIRouter, HTTPException, status - -from app.v2.answers.services.answer_service import AnswerService -from app.v2.badges.services.badge_service import BadgeService -from app.v2.cheese_managers.services.cheese_service import CheeseService -from app.v2.colors.services.color_service import ColorService -from app.v2.levels.services.level_service import LevelService -from app.v2.mobiles.dtos.mypage_response import MyPageResponseDTO, UserProfileWithLevel -from app.v2.mobiles.dtos.teller_card_response import DataDTO, TellerCardResponseDTO -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.services.user_service import UserService +from fastapi import APIRouter, status + +from app.apis.v2.answers.services.answer_service import AnswerService +from app.apis.v2.badges.services.badge_service import BadgeService +from app.apis.v2.cheese_managers.services.cheese_service import CheeseService +from app.apis.v2.colors.services.color_service import ColorService +from app.apis.v2.levels.services.level_service import LevelService +from app.apis.v2.mobiles.dtos.mypage_response import MyPageResponseDTO, UserProfileWithLevel +from app.apis.v2.mobiles.dtos.teller_card_response import DataDTO, TellerCardResponseDTO +from app.apis.v2.teller_cards.services.teller_card_service import TellerCardService +from app.apis.v2.users.dtos.user_info_dto import UserInfoDTO +from app.apis.v2.users.dtos.user_profile_dto import UserProfileDTO +from app.apis.v2.users.services.user_service import UserService router = APIRouter(prefix="/mobiles", tags=["모바일 화면용 컨트롤러"]) diff --git a/src/app/v2/notices/__init__.py b/app/apis/v2/notices/__init__.py similarity index 100% rename from src/app/v2/notices/__init__.py rename to app/apis/v2/notices/__init__.py diff --git a/src/app/v2/notices/dtos/__init__.py b/app/apis/v2/notices/dtos/__init__.py similarity index 100% rename from src/app/v2/notices/dtos/__init__.py rename to app/apis/v2/notices/dtos/__init__.py diff --git a/src/app/v2/notices/models/__init__.py b/app/apis/v2/notices/models/__init__.py similarity index 100% rename from src/app/v2/notices/models/__init__.py rename to app/apis/v2/notices/models/__init__.py diff --git a/src/app/v2/notices/models/notice.py b/app/apis/v2/notices/models/notice.py similarity index 94% rename from src/app/v2/notices/models/notice.py rename to app/apis/v2/notices/models/notice.py index f189219..8c95267 100644 --- a/src/app/v2/notices/models/notice.py +++ b/app/apis/v2/notices/models/notice.py @@ -4,8 +4,8 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model -from app.v2.users.models.user import User -from common.utils.query_executor import QueryExecutor +from app.apis.v2.users.models.user import User +from app.common.utils.query_executor import QueryExecutor class Notice(Model): diff --git a/src/app/v2/notices/services/__init__.py b/app/apis/v2/notices/services/__init__.py similarity index 100% rename from src/app/v2/notices/services/__init__.py rename to app/apis/v2/notices/services/__init__.py diff --git a/src/app/v2/notices/services/notice_service.py b/app/apis/v2/notices/services/notice_service.py similarity index 96% rename from src/app/v2/notices/services/notice_service.py rename to app/apis/v2/notices/services/notice_service.py index 5c94902..f175954 100644 --- a/src/app/v2/notices/services/notice_service.py +++ b/app/apis/v2/notices/services/notice_service.py @@ -1,7 +1,6 @@ from typing import Optional -from app.v2.badges.models.badge import BadgeInventory -from app.v2.notices.models.notice import Notice +from app.apis.v2.notices.models.notice import Notice class NoticeService: diff --git a/src/app/v2/payments/__init__.py b/app/apis/v2/payments/__init__.py similarity index 100% rename from src/app/v2/payments/__init__.py rename to app/apis/v2/payments/__init__.py diff --git a/src/app/v2/payments/dtos/__init__.py b/app/apis/v2/payments/dtos/__init__.py similarity index 100% rename from src/app/v2/payments/dtos/__init__.py rename to app/apis/v2/payments/dtos/__init__.py diff --git a/src/app/v2/payments/dtos/request.py b/app/apis/v2/payments/dtos/request.py similarity index 100% rename from src/app/v2/payments/dtos/request.py rename to app/apis/v2/payments/dtos/request.py diff --git a/src/app/v2/payments/dtos/response.py b/app/apis/v2/payments/dtos/response.py similarity index 84% rename from src/app/v2/payments/dtos/response.py rename to app/apis/v2/payments/dtos/response.py index 490deb4..711cddd 100644 --- a/src/app/v2/payments/dtos/response.py +++ b/app/apis/v2/payments/dtos/response.py @@ -1,6 +1,6 @@ from pydantic import BaseModel -from common.base_models.base_dtos.base_response import BaseResponseDTO +from app.common.base_models.base_dtos.base_response import BaseResponseDTO class ProductDTO(BaseModel): diff --git a/src/app/v2/payments/models/__init__.py b/app/apis/v2/payments/models/__init__.py similarity index 100% rename from src/app/v2/payments/models/__init__.py rename to app/apis/v2/payments/models/__init__.py diff --git a/src/app/v2/payments/querys/__init__.py b/app/apis/v2/payments/querys/__init__.py similarity index 100% rename from src/app/v2/payments/querys/__init__.py rename to app/apis/v2/payments/querys/__init__.py diff --git a/src/app/v2/payments/router.py b/app/apis/v2/payments/router.py similarity index 77% rename from src/app/v2/payments/router.py rename to app/apis/v2/payments/router.py index bb3d71c..eb25d4e 100644 --- a/src/app/v2/payments/router.py +++ b/app/apis/v2/payments/router.py @@ -1,9 +1,9 @@ from fastapi import APIRouter, HTTPException, status -from app.v2.payments.dtos.request import PaymentRequestDTO -from app.v2.payments.dtos.response import PaymentResponseDTO -from app.v2.payments.services.payment_service import PaymentService -from app.v2.users.services.user_service import UserService +from app.apis.v2.payments.dtos.request import PaymentRequestDTO +from app.apis.v2.payments.dtos.response import PaymentResponseDTO +from app.apis.v2.payments.services.payment_service import PaymentService +from app.apis.v2.users.services.user_service import UserService router = APIRouter(prefix="/payment", tags=["Payment"]) diff --git a/src/app/v2/payments/services/__init__.py b/app/apis/v2/payments/services/__init__.py similarity index 100% rename from src/app/v2/payments/services/__init__.py rename to app/apis/v2/payments/services/__init__.py diff --git a/src/app/v2/payments/services/payment_service.py b/app/apis/v2/payments/services/payment_service.py similarity index 82% rename from src/app/v2/payments/services/payment_service.py rename to app/apis/v2/payments/services/payment_service.py index b03d68c..29c9322 100644 --- a/src/app/v2/payments/services/payment_service.py +++ b/app/apis/v2/payments/services/payment_service.py @@ -1,14 +1,13 @@ -from fastapi import HTTPException from tortoise.exceptions import DoesNotExist, IntegrityError from tortoise.transactions import atomic -from app.v2.badges.services.badge_service import BadgeService -from app.v2.cheese_managers.models.cheese_manager import CheeseManager -from app.v2.colors.services.color_service import ColorService -from app.v2.emotions.services.emotion_service import EmotionService -from app.v2.items.models.item import ItemInventory, ItemInventoryProductInventory, ProductInventory -from common.exceptions.custom_exception import CustomException -from common.exceptions.error_code import ErrorCode +from app.apis.v2.badges.services.badge_service import BadgeService +from app.apis.v2.cheese_managers.models.cheese_manager import CheeseManager +from app.apis.v2.colors.services.color_service import ColorService +from app.apis.v2.emotions.services.emotion_service import EmotionService +from app.apis.v2.items.models.item import ItemInventory, ItemInventoryProductInventory, ProductInventory +from app.common.exceptions.custom_exception import CustomException +from app.common.exceptions.error_code import ErrorCode class PaymentService: @@ -52,7 +51,7 @@ async def process_cheese_payment( try: await CheeseManager.use_cheese(cheese_manager_id, int(total_required_cheese)) - except ValueError as e: + except ValueError: raise CustomException(ErrorCode.NOT_ENOUGH_CHEESE) try: diff --git a/src/app/v2/purchases/__init__.py b/app/apis/v2/purchases/__init__.py similarity index 100% rename from src/app/v2/purchases/__init__.py rename to app/apis/v2/purchases/__init__.py diff --git a/src/app/v2/purchases/dtos/__init__.py b/app/apis/v2/purchases/dtos/__init__.py similarity index 100% rename from src/app/v2/purchases/dtos/__init__.py rename to app/apis/v2/purchases/dtos/__init__.py diff --git a/src/app/v2/purchases/dtos/purchase_dto.py b/app/apis/v2/purchases/dtos/purchase_dto.py similarity index 93% rename from src/app/v2/purchases/dtos/purchase_dto.py rename to app/apis/v2/purchases/dtos/purchase_dto.py index a03b463..06eed14 100644 --- a/src/app/v2/purchases/dtos/purchase_dto.py +++ b/app/apis/v2/purchases/dtos/purchase_dto.py @@ -2,8 +2,7 @@ from pydantic import BaseModel -from app.v2.purchases.models.purchase_status import purchase_mapping -from common.base_models.base_dtos.base_response import BaseResponseDTO +from app.apis.v2.purchases.models.purchase_status import purchase_mapping class ReceiptInfoDTO(BaseModel): diff --git a/src/app/v2/purchases/dtos/requests.py b/app/apis/v2/purchases/dtos/requests.py similarity index 100% rename from src/app/v2/purchases/dtos/requests.py rename to app/apis/v2/purchases/dtos/requests.py diff --git a/src/app/v2/purchases/models/__init__.py b/app/apis/v2/purchases/models/__init__.py similarity index 100% rename from src/app/v2/purchases/models/__init__.py rename to app/apis/v2/purchases/models/__init__.py diff --git a/src/app/v2/purchases/models/purchase_history.py b/app/apis/v2/purchases/models/purchase_history.py similarity index 98% rename from src/app/v2/purchases/models/purchase_history.py rename to app/apis/v2/purchases/models/purchase_history.py index c5206b9..490dfce 100644 --- a/src/app/v2/purchases/models/purchase_history.py +++ b/app/apis/v2/purchases/models/purchase_history.py @@ -5,8 +5,8 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model -from app.v2.users.models.user import User -from common.utils.query_executor import QueryExecutor +from app.apis.v2.users.models.user import User +from app.common.utils.query_executor import QueryExecutor class Subscription(Model): diff --git a/src/app/v2/purchases/models/purchase_status.py b/app/apis/v2/purchases/models/purchase_status.py similarity index 100% rename from src/app/v2/purchases/models/purchase_status.py rename to app/apis/v2/purchases/models/purchase_status.py diff --git a/src/app/v2/purchases/repositorys/__init__.py b/app/apis/v2/purchases/repositorys/__init__.py similarity index 100% rename from src/app/v2/purchases/repositorys/__init__.py rename to app/apis/v2/purchases/repositorys/__init__.py diff --git a/src/app/v2/purchases/router.py b/app/apis/v2/purchases/router.py similarity index 85% rename from src/app/v2/purchases/router.py rename to app/apis/v2/purchases/router.py index 7cd7ba6..d551845 100644 --- a/src/app/v2/purchases/router.py +++ b/app/apis/v2/purchases/router.py @@ -2,9 +2,9 @@ from fastapi import APIRouter, Depends, status -from app.v2.purchases.dtos.purchase_dto import PurchaseResponseDTO -from app.v2.purchases.dtos.requests import ReceiptRequestDTO -from app.v2.purchases.services.purchase_service import PurchaseService +from app.apis.v2.purchases.dtos.purchase_dto import PurchaseResponseDTO +from app.apis.v2.purchases.dtos.requests import ReceiptRequestDTO +from app.apis.v2.purchases.services.purchase_service import PurchaseService router = APIRouter(prefix="/purchase", tags=["Purchase"]) diff --git a/src/app/v2/purchases/services/__init__.py b/app/apis/v2/purchases/services/__init__.py similarity index 100% rename from src/app/v2/purchases/services/__init__.py rename to app/apis/v2/purchases/services/__init__.py diff --git a/src/app/v2/purchases/services/purchase_service.py b/app/apis/v2/purchases/services/purchase_service.py similarity index 94% rename from src/app/v2/purchases/services/purchase_service.py rename to app/apis/v2/purchases/services/purchase_service.py index 6420521..55b3d4b 100644 --- a/src/app/v2/purchases/services/purchase_service.py +++ b/app/apis/v2/purchases/services/purchase_service.py @@ -8,15 +8,15 @@ from tortoise.exceptions import DoesNotExist from tortoise.transactions import atomic -from app.v2.items.models.item import ItemInventory, ItemInventoryProductInventory, ProductInventory -from app.v2.purchases.dtos.purchase_dto import PurchaseResponseDTO, ReceiptInfoDTO -from app.v2.purchases.models.purchase_history import PurchaseHistory, Subscription -from app.v2.purchases.models.purchase_status import PurchaseStatus, SubscriptionStatus -from app.v2.users.models.user import User -from app.v2.users.services.user_service import UserService -from common.exceptions.custom_exception import CustomException -from common.exceptions.error_code import ErrorCode -from core.configs import settings +from app.apis.v2.items.models.item import ItemInventory, ItemInventoryProductInventory, ProductInventory +from app.apis.v2.purchases.dtos.purchase_dto import PurchaseResponseDTO, ReceiptInfoDTO +from app.apis.v2.purchases.models.purchase_history import PurchaseHistory, Subscription +from app.apis.v2.purchases.models.purchase_status import PurchaseStatus, SubscriptionStatus +from app.apis.v2.users.models.user import User +from app.apis.v2.users.services.user_service import UserService +from app.common.exceptions.custom_exception import CustomException +from app.common.exceptions.error_code import ErrorCode +from app.core.configs import settings class PurchaseService: diff --git a/src/app/v2/questions/__init__.py b/app/apis/v2/questions/__init__.py similarity index 100% rename from src/app/v2/questions/__init__.py rename to app/apis/v2/questions/__init__.py diff --git a/src/app/v2/questions/dtos/__init__.py b/app/apis/v2/questions/dtos/__init__.py similarity index 100% rename from src/app/v2/questions/dtos/__init__.py rename to app/apis/v2/questions/dtos/__init__.py diff --git a/src/app/v2/questions/dtos/responses.py b/app/apis/v2/questions/dtos/responses.py similarity index 100% rename from src/app/v2/questions/dtos/responses.py rename to app/apis/v2/questions/dtos/responses.py diff --git a/src/app/v2/questions/models/__init__.py b/app/apis/v2/questions/models/__init__.py similarity index 100% rename from src/app/v2/questions/models/__init__.py rename to app/apis/v2/questions/models/__init__.py diff --git a/src/app/v2/questions/models/question.py b/app/apis/v2/questions/models/question.py similarity index 100% rename from src/app/v2/questions/models/question.py rename to app/apis/v2/questions/models/question.py diff --git a/src/app/v2/questions/repositorys/__init__.py b/app/apis/v2/questions/repositorys/__init__.py similarity index 100% rename from src/app/v2/questions/repositorys/__init__.py rename to app/apis/v2/questions/repositorys/__init__.py diff --git a/src/app/v2/questions/router.py b/app/apis/v2/questions/router.py similarity index 76% rename from src/app/v2/questions/router.py rename to app/apis/v2/questions/router.py index 0f789d9..9deda2e 100644 --- a/src/app/v2/questions/router.py +++ b/app/apis/v2/questions/router.py @@ -1,6 +1,4 @@ -from fastapi import APIRouter, HTTPException - -from app.v2.questions.models.question import Question +from fastapi import APIRouter router = APIRouter(prefix="/question", tags=["Question"]) diff --git a/src/app/v2/questions/services/__init__.py b/app/apis/v2/questions/services/__init__.py similarity index 100% rename from src/app/v2/questions/services/__init__.py rename to app/apis/v2/questions/services/__init__.py diff --git a/src/app/v2/teller_cards/__init__.py b/app/apis/v2/teller_cards/__init__.py similarity index 100% rename from src/app/v2/teller_cards/__init__.py rename to app/apis/v2/teller_cards/__init__.py diff --git a/src/app/v2/teller_cards/dtos/__init__.py b/app/apis/v2/teller_cards/dtos/__init__.py similarity index 100% rename from src/app/v2/teller_cards/dtos/__init__.py rename to app/apis/v2/teller_cards/dtos/__init__.py diff --git a/src/app/v2/teller_cards/dtos/request.py b/app/apis/v2/teller_cards/dtos/request.py similarity index 100% rename from src/app/v2/teller_cards/dtos/request.py rename to app/apis/v2/teller_cards/dtos/request.py diff --git a/src/app/v2/teller_cards/dtos/response.py b/app/apis/v2/teller_cards/dtos/response.py similarity index 73% rename from src/app/v2/teller_cards/dtos/response.py rename to app/apis/v2/teller_cards/dtos/response.py index 4df7b3e..386ebf1 100644 --- a/src/app/v2/teller_cards/dtos/response.py +++ b/app/apis/v2/teller_cards/dtos/response.py @@ -1,7 +1,7 @@ from pydantic import BaseModel -from app.v2.teller_cards.dtos.teller_card_dto import TellerCardDTO as TellerCardLogicDTO -from common.base_models.base_dtos.base_response import BaseResponseDTO +from app.apis.v2.teller_cards.dtos.teller_card_dto import TellerCardDTO as TellerCardLogicDTO +from app.common.base_models.base_dtos.base_response import BaseResponseDTO class TellerCardDTO(BaseModel): diff --git a/src/app/v2/teller_cards/dtos/teller_card_dto.py b/app/apis/v2/teller_cards/dtos/teller_card_dto.py similarity index 95% rename from src/app/v2/teller_cards/dtos/teller_card_dto.py rename to app/apis/v2/teller_cards/dtos/teller_card_dto.py index a38e524..529dcb4 100644 --- a/src/app/v2/teller_cards/dtos/teller_card_dto.py +++ b/app/apis/v2/teller_cards/dtos/teller_card_dto.py @@ -1,5 +1,3 @@ -from typing import Any - from pydantic import BaseModel diff --git a/src/app/v2/teller_cards/models/__init__.py b/app/apis/v2/teller_cards/models/__init__.py similarity index 100% rename from src/app/v2/teller_cards/models/__init__.py rename to app/apis/v2/teller_cards/models/__init__.py diff --git a/src/app/v2/teller_cards/models/teller_card.py b/app/apis/v2/teller_cards/models/teller_card.py similarity index 89% rename from src/app/v2/teller_cards/models/teller_card.py rename to app/apis/v2/teller_cards/models/teller_card.py index 7314021..75e1560 100644 --- a/src/app/v2/teller_cards/models/teller_card.py +++ b/app/apis/v2/teller_cards/models/teller_card.py @@ -3,11 +3,11 @@ from tortoise import fields from tortoise.models import Model -from app.v2.teller_cards.querys.teller_card_query import ( +from app.apis.v2.teller_cards.querys.teller_card_query import ( PATCH_TELLER_CARD_QUERY, SELECT_TELLER_CARD_INFO_BY_USER_UUID_QUERY, ) -from common.utils.query_executor import QueryExecutor +from app.common.utils.query_executor import QueryExecutor class TellerCard(Model): diff --git a/src/app/v2/teller_cards/querys/__init__.py b/app/apis/v2/teller_cards/querys/__init__.py similarity index 100% rename from src/app/v2/teller_cards/querys/__init__.py rename to app/apis/v2/teller_cards/querys/__init__.py diff --git a/src/app/v2/teller_cards/querys/teller_card_query.py b/app/apis/v2/teller_cards/querys/teller_card_query.py similarity index 94% rename from src/app/v2/teller_cards/querys/teller_card_query.py rename to app/apis/v2/teller_cards/querys/teller_card_query.py index 7e64673..cc47cba 100644 --- a/src/app/v2/teller_cards/querys/teller_card_query.py +++ b/app/apis/v2/teller_cards/querys/teller_card_query.py @@ -1,4 +1,4 @@ -from app.v2.users.querys.user_query import USER_ID_QUERY +from app.apis.v2.users.querys.user_query import USER_ID_QUERY SELECT_TELLER_CARD_INFO_BY_USER_UUID_QUERY = f""" SELECT diff --git a/src/app/v2/teller_cards/router.py b/app/apis/v2/teller_cards/router.py similarity index 76% rename from src/app/v2/teller_cards/router.py rename to app/apis/v2/teller_cards/router.py index a66c981..68935bd 100644 --- a/src/app/v2/teller_cards/router.py +++ b/app/apis/v2/teller_cards/router.py @@ -1,8 +1,8 @@ from fastapi import APIRouter, status -from app.v2.teller_cards.dtos.request import TellerCardRequestDTO -from app.v2.teller_cards.dtos.response import TellerCardResponseDTO -from app.v2.teller_cards.services.teller_card_service import TellerCardService +from app.apis.v2.teller_cards.dtos.request import TellerCardRequestDTO +from app.apis.v2.teller_cards.dtos.response import TellerCardResponseDTO +from app.apis.v2.teller_cards.services.teller_card_service import TellerCardService router = APIRouter(prefix="/tellercard", tags=["TellerCard"]) diff --git a/src/app/v2/teller_cards/services/__init__.py b/app/apis/v2/teller_cards/services/__init__.py similarity index 100% rename from src/app/v2/teller_cards/services/__init__.py rename to app/apis/v2/teller_cards/services/__init__.py diff --git a/src/app/v2/teller_cards/services/teller_card_service.py b/app/apis/v2/teller_cards/services/teller_card_service.py similarity index 82% rename from src/app/v2/teller_cards/services/teller_card_service.py rename to app/apis/v2/teller_cards/services/teller_card_service.py index 7e34904..f707b46 100644 --- a/src/app/v2/teller_cards/services/teller_card_service.py +++ b/app/apis/v2/teller_cards/services/teller_card_service.py @@ -1,9 +1,9 @@ from typing import Optional -from app.v2.badges.models.badge import BadgeInventory -from app.v2.colors.models.color import ColorInventory -from app.v2.teller_cards.dtos.teller_card_dto import TellerCardDTO -from app.v2.teller_cards.models.teller_card import TellerCard +from app.apis.v2.badges.models.badge import BadgeInventory +from app.apis.v2.colors.models.color import ColorInventory +from app.apis.v2.teller_cards.dtos.teller_card_dto import TellerCardDTO +from app.apis.v2.teller_cards.models.teller_card import TellerCard class TellerCardService: diff --git a/src/app/v2/users/__init__.py b/app/apis/v2/users/__init__.py similarity index 100% rename from src/app/v2/users/__init__.py rename to app/apis/v2/users/__init__.py diff --git a/src/app/v2/users/dtos/__init__.py b/app/apis/v2/users/dtos/__init__.py similarity index 100% rename from src/app/v2/users/dtos/__init__.py rename to app/apis/v2/users/dtos/__init__.py diff --git a/src/app/v2/users/dtos/user_dto.py b/app/apis/v2/users/dtos/user_dto.py similarity index 100% rename from src/app/v2/users/dtos/user_dto.py rename to app/apis/v2/users/dtos/user_dto.py diff --git a/src/app/v2/users/dtos/user_info_dto.py b/app/apis/v2/users/dtos/user_info_dto.py similarity index 85% rename from src/app/v2/users/dtos/user_info_dto.py rename to app/apis/v2/users/dtos/user_info_dto.py index 4bc245d..3cf46d8 100644 --- a/src/app/v2/users/dtos/user_info_dto.py +++ b/app/apis/v2/users/dtos/user_info_dto.py @@ -1,6 +1,6 @@ from pydantic import BaseModel -from app.v2.teller_cards.dtos.teller_card_dto import TellerCardDTO +from app.apis.v2.teller_cards.dtos.teller_card_dto import TellerCardDTO class UserInfoDTO(BaseModel): diff --git a/src/app/v2/users/dtos/user_profile_dto.py b/app/apis/v2/users/dtos/user_profile_dto.py similarity index 100% rename from src/app/v2/users/dtos/user_profile_dto.py rename to app/apis/v2/users/dtos/user_profile_dto.py diff --git a/src/app/v2/users/models/__init__.py b/app/apis/v2/users/models/__init__.py similarity index 100% rename from src/app/v2/users/models/__init__.py rename to app/apis/v2/users/models/__init__.py diff --git a/src/app/v2/users/models/refresh_token.py b/app/apis/v2/users/models/refresh_token.py similarity index 100% rename from src/app/v2/users/models/refresh_token.py rename to app/apis/v2/users/models/refresh_token.py diff --git a/src/app/v2/users/models/user.py b/app/apis/v2/users/models/user.py similarity index 90% rename from src/app/v2/users/models/user.py rename to app/apis/v2/users/models/user.py index 72212a9..28a1a70 100644 --- a/src/app/v2/users/models/user.py +++ b/app/apis/v2/users/models/user.py @@ -6,16 +6,16 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model -from app.v2.cheese_managers.models.cheese_manager import CheeseManager -from app.v2.levels.models.level import Level -from app.v2.teller_cards.models.teller_card import TellerCard -from app.v2.users.models.refresh_token import RefreshToken -from app.v2.users.querys.user_query import ( +from app.apis.v2.cheese_managers.models.cheese_manager import CheeseManager +from app.apis.v2.levels.models.level import Level +from app.apis.v2.teller_cards.models.teller_card import TellerCard +from app.apis.v2.users.models.refresh_token import RefreshToken +from app.apis.v2.users.querys.user_query import ( SELECT_USER_INFO_BY_USER_UUID_QUERY, SELECT_USER_PROFILE_BY_USER_ID_QUERY, UPDATE_PREMIUM_STATUS_QUERY, ) -from common.utils.query_executor import QueryExecutor +from app.common.utils.query_executor import QueryExecutor class User(Model): diff --git a/src/app/v2/users/querys/__init__.py b/app/apis/v2/users/querys/__init__.py similarity index 100% rename from src/app/v2/users/querys/__init__.py rename to app/apis/v2/users/querys/__init__.py diff --git a/src/app/v2/users/querys/user_query.py b/app/apis/v2/users/querys/user_query.py similarity index 100% rename from src/app/v2/users/querys/user_query.py rename to app/apis/v2/users/querys/user_query.py diff --git a/src/app/v2/users/services/__init__.py b/app/apis/v2/users/services/__init__.py similarity index 100% rename from src/app/v2/users/services/__init__.py rename to app/apis/v2/users/services/__init__.py diff --git a/src/app/v2/users/services/user_service.py b/app/apis/v2/users/services/user_service.py similarity index 76% rename from src/app/v2/users/services/user_service.py rename to app/apis/v2/users/services/user_service.py index 95c3fb9..0b3ce60 100644 --- a/src/app/v2/users/services/user_service.py +++ b/app/apis/v2/users/services/user_service.py @@ -1,8 +1,7 @@ from typing import Any -from app.v2.cheese_managers.models.cheese_manager import CheeseManager -from app.v2.users.dtos.user_dto import UserDTO -from app.v2.users.models.user import User +from app.apis.v2.users.dtos.user_dto import UserDTO +from app.apis.v2.users.models.user import User class UserService: diff --git a/src/celery_worker.py b/app/celery_worker.py similarity index 100% rename from src/celery_worker.py rename to app/celery_worker.py diff --git a/src/common/__init__.py b/app/common/__init__.py similarity index 100% rename from src/common/__init__.py rename to app/common/__init__.py diff --git a/src/common/base_models/__init__.py b/app/common/base_models/__init__.py similarity index 100% rename from src/common/base_models/__init__.py rename to app/common/base_models/__init__.py diff --git a/src/common/base_models/base_dtos/__init__.py b/app/common/base_models/base_dtos/__init__.py similarity index 100% rename from src/common/base_models/base_dtos/__init__.py rename to app/common/base_models/base_dtos/__init__.py diff --git a/src/common/base_models/base_dtos/base_response.py b/app/common/base_models/base_dtos/base_response.py similarity index 100% rename from src/common/base_models/base_dtos/base_response.py rename to app/common/base_models/base_dtos/base_response.py diff --git a/src/common/base_models/custom_fields/__init__.py b/app/common/base_models/custom_fields/__init__.py similarity index 100% rename from src/common/base_models/custom_fields/__init__.py rename to app/common/base_models/custom_fields/__init__.py diff --git a/src/common/exceptions/__init__.py b/app/common/exceptions/__init__.py similarity index 100% rename from src/common/exceptions/__init__.py rename to app/common/exceptions/__init__.py diff --git a/src/common/exceptions/custom_exception.py b/app/common/exceptions/custom_exception.py similarity index 87% rename from src/common/exceptions/custom_exception.py rename to app/common/exceptions/custom_exception.py index 48e6fb3..c3a5070 100644 --- a/src/common/exceptions/custom_exception.py +++ b/app/common/exceptions/custom_exception.py @@ -1,6 +1,6 @@ from typing import Any -from common.exceptions.error_code import ErrorCode +from app.common.exceptions.error_code import ErrorCode class CustomException(Exception): diff --git a/src/common/exceptions/error_code.py b/app/common/exceptions/error_code.py similarity index 100% rename from src/common/exceptions/error_code.py rename to app/common/exceptions/error_code.py diff --git a/src/common/handlers/__init__.py b/app/common/handlers/__init__.py similarity index 100% rename from src/common/handlers/__init__.py rename to app/common/handlers/__init__.py diff --git a/src/common/handlers/exception_handler.py b/app/common/handlers/exception_handler.py similarity index 92% rename from src/common/handlers/exception_handler.py rename to app/common/handlers/exception_handler.py index 84313d0..d8f13e2 100644 --- a/src/common/handlers/exception_handler.py +++ b/app/common/handlers/exception_handler.py @@ -1,7 +1,7 @@ from fastapi import FastAPI, Request from starlette.responses import JSONResponse -from common.exceptions.custom_exception import CustomException +from app.common.exceptions.custom_exception import CustomException def attach_exception_handlers(app: FastAPI) -> None: diff --git a/src/common/handlers/router_handler.py b/app/common/handlers/router_handler.py similarity index 52% rename from src/common/handlers/router_handler.py rename to app/common/handlers/router_handler.py index ff6b67e..c137510 100644 --- a/src/common/handlers/router_handler.py +++ b/app/common/handlers/router_handler.py @@ -1,16 +1,16 @@ from fastapi import FastAPI -from app.v2.answers.router import router as answer_router -from app.v2.badges.router import router as badge_router -from app.v2.cheese_managers.router import router as cheese_router -from app.v2.colors.router import router as color_router -from app.v2.emotions.router import router as emotion_router -from app.v2.missions.router import router as mission_router -from app.v2.mobiles.router import router as mobile_router -from app.v2.payments.router import router as payment_router -from app.v2.purchases.router import router as purchase_router -from app.v2.questions.router import router as question_router -from app.v2.teller_cards.router import router as teller_card_router +from app.apis.v2.answers.router import router as answer_router +from app.apis.v2.badges.router import router as badge_router +from app.apis.v2.cheese_managers.router import router as cheese_router +from app.apis.v2.colors.router import router as color_router +from app.apis.v2.emotions.router import router as emotion_router +from app.apis.v2.missions.router import router as mission_router +from app.apis.v2.mobiles.router import router as mobile_router +from app.apis.v2.payments.router import router as payment_router +from app.apis.v2.purchases.router import router as purchase_router +from app.apis.v2.questions.router import router as question_router +from app.apis.v2.teller_cards.router import router as teller_card_router def attach_router_handlers(app: FastAPI) -> None: diff --git a/app/common/post_construct.py b/app/common/post_construct.py new file mode 100644 index 0000000..2ba7540 --- /dev/null +++ b/app/common/post_construct.py @@ -0,0 +1,13 @@ +from fastapi import FastAPI + +from app.common.handlers.exception_handler import attach_exception_handlers +from app.common.handlers.router_handler import attach_router_handlers +from app.common.utils.scheduler import start_scheduler +from app.core.database.database_settings import database_initialize + + +def post_construct(app: FastAPI) -> None: + attach_router_handlers(app) + attach_exception_handlers(app) + database_initialize(app) + start_scheduler() diff --git a/src/common/tasks/__init__.py b/app/common/tasks/__init__.py similarity index 100% rename from src/common/tasks/__init__.py rename to app/common/tasks/__init__.py diff --git a/src/common/tasks/mission_task.py b/app/common/tasks/mission_task.py similarity index 81% rename from src/common/tasks/mission_task.py rename to app/common/tasks/mission_task.py index 8e3ed33..f7da282 100644 --- a/src/common/tasks/mission_task.py +++ b/app/common/tasks/mission_task.py @@ -1,6 +1,6 @@ import asyncio -from app.v2.missions.models.mission import UserMission +from app.apis.v2.missions.models.mission import UserMission async def mission_reset_task() -> None: diff --git a/src/common/tasks/renew_subscription_task.py b/app/common/tasks/renew_subscription_task.py similarity index 79% rename from src/common/tasks/renew_subscription_task.py rename to app/common/tasks/renew_subscription_task.py index eb71fb9..daf0035 100644 --- a/src/common/tasks/renew_subscription_task.py +++ b/app/common/tasks/renew_subscription_task.py @@ -1,4 +1,4 @@ -from app.v2.purchases.services.purchase_service import PurchaseService +from app.apis.v2.purchases.services.purchase_service import PurchaseService async def renew_subscription_task() -> None: diff --git a/src/common/utils/__init__.py b/app/common/utils/__init__.py similarity index 100% rename from src/common/utils/__init__.py rename to app/common/utils/__init__.py diff --git a/src/common/utils/get_user_id.py b/app/common/utils/get_user_id.py similarity index 100% rename from src/common/utils/get_user_id.py rename to app/common/utils/get_user_id.py diff --git a/src/common/utils/query_executor.py b/app/common/utils/query_executor.py similarity index 95% rename from src/common/utils/query_executor.py rename to app/common/utils/query_executor.py index cb10261..0e88658 100644 --- a/src/common/utils/query_executor.py +++ b/app/common/utils/query_executor.py @@ -1,5 +1,4 @@ -from datetime import datetime -from typing import Any, Union +from typing import Any from tortoise import Tortoise diff --git a/src/common/utils/query_formatter.py b/app/common/utils/query_formatter.py similarity index 100% rename from src/common/utils/query_formatter.py rename to app/common/utils/query_formatter.py diff --git a/src/common/utils/scheduler.py b/app/common/utils/scheduler.py similarity index 92% rename from src/common/utils/scheduler.py rename to app/common/utils/scheduler.py index 130a969..a94fb5c 100644 --- a/src/common/utils/scheduler.py +++ b/app/common/utils/scheduler.py @@ -3,7 +3,7 @@ from apscheduler.jobstores.redis import RedisJobStore # type: ignore from apscheduler.schedulers.background import BackgroundScheduler # type: ignore -from core.configs.celery_settings import celery_app +from app.core.configs.celery_settings import celery_app logger = logging.getLogger(__name__) diff --git a/src/core/__init__.py b/app/core/__init__.py similarity index 100% rename from src/core/__init__.py rename to app/core/__init__.py diff --git a/src/core/configs/__init__.py b/app/core/configs/__init__.py similarity index 61% rename from src/core/configs/__init__.py rename to app/core/configs/__init__.py index 8110387..3a65399 100644 --- a/src/core/configs/__init__.py +++ b/app/core/configs/__init__.py @@ -1,4 +1,4 @@ -from core.configs.base_settings import Settings +from app.core.configs.base_settings import Settings def get_settings() -> Settings: diff --git a/src/core/configs/base_settings.py b/app/core/configs/base_settings.py similarity index 100% rename from src/core/configs/base_settings.py rename to app/core/configs/base_settings.py diff --git a/src/core/configs/celery_settings.py b/app/core/configs/celery_settings.py similarity index 87% rename from src/core/configs/celery_settings.py rename to app/core/configs/celery_settings.py index cf66aaa..89c6958 100644 --- a/src/core/configs/celery_settings.py +++ b/app/core/configs/celery_settings.py @@ -4,9 +4,9 @@ from celery import Celery from tortoise import Tortoise -from app.v2.missions.services.mission_service import MissionService -from common.tasks.mission_task import mission_reset_task -from core.database.database_settings import TORTOISE_ORM +from app.apis.v2.missions.services.mission_service import MissionService +from app.common.tasks.mission_task import mission_reset_task +from app.core.database.database_settings import TORTOISE_ORM celery_app = Celery( "telling-me-celery", @@ -53,7 +53,7 @@ async def execute_async_mission_task(user_id: str) -> None: async def initialize_celery() -> None: logger = logging.getLogger(__name__) - logger.info(f"Current path: 여기") + logger.info("Current path: 여기") logging.basicConfig(level=logging.DEBUG) db_client_logger = logging.getLogger("tortoise.db_client") db_client_logger.setLevel(logging.DEBUG) diff --git a/src/core/database/__init__.py b/app/core/database/__init__.py similarity index 100% rename from src/core/database/__init__.py rename to app/core/database/__init__.py diff --git a/src/core/database/database_settings.py b/app/core/database/database_settings.py similarity index 66% rename from src/core/database/database_settings.py rename to app/core/database/database_settings.py index 98477c4..edabab0 100644 --- a/src/core/database/database_settings.py +++ b/app/core/database/database_settings.py @@ -2,23 +2,23 @@ from tortoise import Tortoise from tortoise.contrib.fastapi import register_tortoise -from core.configs import settings +from app.core.configs import settings TORTOISE_APP_MODELS = [ - "app.v2.questions.models.question", - "app.v2.users.models.user", - "app.v2.users.models.refresh_token", - "app.v2.badges.models.badge", - "app.v2.colors.models.color", - "app.v2.answers.models.answer", - "app.v2.teller_cards.models.teller_card", - "app.v2.levels.models.level", - "app.v2.cheese_managers.models.cheese_manager", - "app.v2.items.models.item", - "app.v2.missions.models.mission", - "app.v2.likes.models.like", - "app.v2.emotions.models.emotion", - "app.v2.purchases.models.purchase_history", + "app.apis.v2.questions.models.question", + "app.apis.v2.users.models.user", + "app.apis.v2.users.models.refresh_token", + "app.apis.v2.badges.models.badge", + "app.apis.v2.colors.models.color", + "app.apis.v2.answers.models.answer", + "app.apis.v2.teller_cards.models.teller_card", + "app.apis.v2.levels.models.level", + "app.apis.v2.cheese_managers.models.cheese_manager", + "app.apis.v2.items.models.item", + "app.apis.v2.missions.models.mission", + "app.apis.v2.likes.models.like", + "app.apis.v2.emotions.models.emotion", + "app.apis.v2.purchases.models.purchase_history", ] TORTOISE_ORM = { diff --git a/app/dtos/__init__.py b/app/dtos/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/log/__init__.py b/app/log/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/models/__init__.py b/app/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/services/__init__.py b/app/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/tests/__init__.py b/app/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/poetry.lock b/poetry.lock index f522317..51f04a0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "aiomysql" @@ -1519,6 +1519,33 @@ pygments = ">=2.13.0,<3.0.0" [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] +[[package]] +name = "ruff" +version = "0.11.12" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.11.12-py3-none-linux_armv6l.whl", hash = "sha256:c7680aa2f0d4c4f43353d1e72123955c7a2159b8646cd43402de6d4a3a25d7cc"}, + {file = "ruff-0.11.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2cad64843da9f134565c20bcc430642de897b8ea02e2e79e6e02a76b8dcad7c3"}, + {file = "ruff-0.11.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9b6886b524a1c659cee1758140138455d3c029783d1b9e643f3624a5ee0cb0aa"}, + {file = "ruff-0.11.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc3a3690aad6e86c1958d3ec3c38c4594b6ecec75c1f531e84160bd827b2012"}, + {file = "ruff-0.11.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f97fdbc2549f456c65b3b0048560d44ddd540db1f27c778a938371424b49fe4a"}, + {file = "ruff-0.11.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74adf84960236961090e2d1348c1a67d940fd12e811a33fb3d107df61eef8fc7"}, + {file = "ruff-0.11.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b56697e5b8bcf1d61293ccfe63873aba08fdbcbbba839fc046ec5926bdb25a3a"}, + {file = "ruff-0.11.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d47afa45e7b0eaf5e5969c6b39cbd108be83910b5c74626247e366fd7a36a13"}, + {file = "ruff-0.11.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bf9603fe1bf949de8b09a2da896f05c01ed7a187f4a386cdba6760e7f61be"}, + {file = "ruff-0.11.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08033320e979df3b20dba567c62f69c45e01df708b0f9c83912d7abd3e0801cd"}, + {file = "ruff-0.11.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:929b7706584f5bfd61d67d5070f399057d07c70585fa8c4491d78ada452d3bef"}, + {file = "ruff-0.11.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7de4a73205dc5756b8e09ee3ed67c38312dce1aa28972b93150f5751199981b5"}, + {file = "ruff-0.11.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2635c2a90ac1b8ca9e93b70af59dfd1dd2026a40e2d6eebaa3efb0465dd9cf02"}, + {file = "ruff-0.11.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d05d6a78a89166f03f03a198ecc9d18779076ad0eec476819467acb401028c0c"}, + {file = "ruff-0.11.12-py3-none-win32.whl", hash = "sha256:f5a07f49767c4be4772d161bfc049c1f242db0cfe1bd976e0f0886732a4765d6"}, + {file = "ruff-0.11.12-py3-none-win_amd64.whl", hash = "sha256:5a4d9f8030d8c3a45df201d7fb3ed38d0219bccd7955268e863ee4a115fa0832"}, + {file = "ruff-0.11.12-py3-none-win_arm64.whl", hash = "sha256:65194e37853158d368e333ba282217941029a28ea90913c67e558c611d04daa5"}, + {file = "ruff-0.11.12.tar.gz", hash = "sha256:43cf7f69c7d7c7d7513b9d59c5d8cafd704e05944f978614aa9faff6ac202603"}, +] + [[package]] name = "shellingham" version = "1.5.4" @@ -2031,4 +2058,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "42b1f2c02091e30e76d806b1415db897e5feb02b3daf41250bbc809f76e05ea2" +content-hash = "3c5afc1504f5de31998ff92cafdb4bb5d46321d7deb8b977f71eed6a96471e93" diff --git a/pyproject.toml b/pyproject.toml index 02211f6..225c3be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,25 +30,42 @@ pytest-asyncio = "^0.24.0" mypy-extensions = "^1.0.0" coverage = "^7.6.8" celery-stubs = "^0.1.3" - -[tool.mypy] -files = "src" -strict = true - +ruff = "^0.11.12" #disallow_untyped_calls = true # 타입이 없는 함수 호출 금지 #disallow_untyped_defs = true # 타입이 없는 함수 정의 금지 #ignore_missing_imports = true # 누락된 import 무시 -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +[tool.mypy] +python_version = "3.12" +strict = true +plugins = [ + "pydantic.mypy", + "sqlalchemy.ext.mypy.plugin" +] [tool.black] line-length = 120 +[tool.ruff] +line-length = 120 + +[tool.ruff.lint] +select = ["E", "F", "W", "I"] +ignore = [ + "E501", # Line too long +] + +[tool.pytest.ini_options] +asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "session" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" -[tool.isort] -profile = "black" -line_length = 120 +[tool.coverage.run] +concurrency = ["greenlet"] +[tool.coverage.report] +fail_under = 92 diff --git a/scripts/deploy-prod.sh b/scripts/deploy-prod.sh index a2ccfbb..706a602 100644 --- a/scripts/deploy-prod.sh +++ b/scripts/deploy-prod.sh @@ -1,7 +1,7 @@ cat deploy-prod.sh #!/bin/bash -# src 디렉토리로 이동 +# apis 디렉토리로 이동 cd "$(dirname "$0")/../src" || exit # .env.prod 파일 확인 및 로드 diff --git a/scripts/start_app.sh b/scripts/start_app.sh index 87ae737..7b51ca6 100755 --- a/scripts/start_app.sh +++ b/scripts/start_app.sh @@ -7,6 +7,6 @@ poetry install --no-root # -k uvicorn.workers.UvicornWorker: Uvicorn 워커를 사용하여 FastAPI를 실행 # -w: 워커 수를 지정 (CPU 코어 수에 맞춰 조정) # -b: 바인딩할 주소 및 포트 -# src.main:app -> src 디렉토리 내의 main.py 파일에서 "app" 객체를 가리킴 (FastAPI 인스턴스) +# apis.main:apis -> apis 디렉토리 내의 __init__.py 파일에서 "apis" 객체를 가리킴 (FastAPI 인스턴스) exec gunicorn -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 main:app \ No newline at end of file diff --git a/src/app/v2/badges/dtos/response.py b/src/app/v2/badges/dtos/response.py deleted file mode 100644 index efba691..0000000 --- a/src/app/v2/badges/dtos/response.py +++ /dev/null @@ -1,6 +0,0 @@ -from app.v2.badges.dtos.badge_dto import BadgeDTO -from common.base_models.base_dtos.base_response import BaseResponseDTO - - -class BadgeListResponseDTO(BaseResponseDTO): - data: list[BadgeDTO] diff --git a/src/app/v2/colors/dtos/response.py b/src/app/v2/colors/dtos/response.py deleted file mode 100644 index 7a0459e..0000000 --- a/src/app/v2/colors/dtos/response.py +++ /dev/null @@ -1,6 +0,0 @@ -from app.v2.colors.dtos.color_dto import ColorDTO -from common.base_models.base_dtos.base_response import BaseResponseDTO - - -class ColorListResponseDTO(BaseResponseDTO): - data: list[ColorDTO] diff --git a/src/common/post_construct.py b/src/common/post_construct.py deleted file mode 100644 index f72dfb4..0000000 --- a/src/common/post_construct.py +++ /dev/null @@ -1,13 +0,0 @@ -from fastapi import FastAPI - -from common.handlers.exception_handler import attach_exception_handlers -from common.handlers.router_handler import attach_router_handlers -from common.utils.scheduler import start_scheduler -from core.database.database_settings import database_initialize - - -def post_construct(app: FastAPI) -> None: - attach_router_handlers(app) - attach_exception_handlers(app) - database_initialize(app) - start_scheduler() diff --git a/test.sh b/test.sh index 780ed8a..3c24a41 100755 --- a/test.sh +++ b/test.sh @@ -7,12 +7,12 @@ echo "Starting black" poetry run black . echo "OK" -echo "Starting isort" -poetry run isort . +echo "Starting ruff" +poetry run ruff check . --fix echo "OK" echo "Starting mypy" -poetry run mypy . +poetry run dmypy run -- . echo "OK" echo "Starting pytest with coverage" @@ -20,4 +20,4 @@ poetry run coverage run -m pytest poetry run coverage report -m poetry run coverage html -echo "${COLOR_GREEN}All tests passed successfully!${COLOR_NC}" \ No newline at end of file +echo "${COLOR_GREEN}All tests passed successfully!${COLOR_NC}" From d1f095709b952396bb78ddb1d5bcec9b6e2047eb Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Sun, 1 Jun 2025 15:48:15 +0900 Subject: [PATCH 02/27] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20app=20?= =?UTF-8?q?=ED=8F=B4=EB=8D=94=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{answers/router.py => answer_router.py} | 8 ++--- app/apis/v2/answers/dtos/answer_dto.py | 0 .../v2/{badges/router.py => badge_router.py} | 8 ++--- app/apis/v2/cheese_managers/router.py | 17 --------- .../v2/cheese_managers/services/__init__.py | 0 app/apis/v2/cheese_router.py | 17 +++++++++ .../v2/{colors/router.py => color_router.py} | 8 ++--- app/apis/v2/colors/__init__.py | 0 app/apis/v2/colors/dtos/__init__.py | 0 app/apis/v2/colors/models/__init__.py | 0 app/apis/v2/colors/querys/__init__.py | 0 app/apis/v2/colors/services/__init__.py | 0 .../{emotions/router.py => emotion_router.py} | 8 ++--- app/apis/v2/emotions/__init__.py | 0 app/apis/v2/emotions/dtos/__init__.py | 0 app/apis/v2/emotions/models/__init__.py | 0 app/apis/v2/emotions/querys/__init__.py | 0 app/apis/v2/emotions/services/__init__.py | 0 app/apis/v2/items/__init__.py | 0 app/apis/v2/items/dtos/__init__.py | 0 app/apis/v2/items/models/__init__.py | 0 app/apis/v2/items/repositorys/__init__.py | 0 app/apis/v2/items/router.py | 0 app/apis/v2/items/services/__init__.py | 0 app/apis/v2/levels/__init__.py | 0 app/apis/v2/levels/dtos/__init__.py | 0 app/apis/v2/levels/models/__init__.py | 0 app/apis/v2/levels/querys/__init__.py | 0 app/apis/v2/levels/router.py | 0 app/apis/v2/levels/services/__init__.py | 0 app/apis/v2/likes/__init__.py | 0 app/apis/v2/likes/models/__init__.py | 0 app/apis/v2/likes/querys/__init__.py | 0 .../{missions/router.py => mission_router.py} | 8 ++--- app/apis/v2/missions/__init__.py | 0 app/apis/v2/missions/dtos/__init__.py | 0 app/apis/v2/missions/models/__init__.py | 0 app/apis/v2/missions/querys/__init__.py | 0 app/apis/v2/missions/services/__init__.py | 0 .../{mobiles/router.py => mobile_router.py} | 36 +++++++++---------- app/apis/v2/mobiles/__init__.py | 0 app/apis/v2/mobiles/dtos/__init__.py | 0 app/apis/v2/notices/__init__.py | 0 app/apis/v2/notices/dtos/__init__.py | 0 app/apis/v2/notices/models/__init__.py | 0 app/apis/v2/notices/services/__init__.py | 0 .../{payments/router.py => payment_router.py} | 12 +++---- app/apis/v2/payments/__init__.py | 0 app/apis/v2/payments/dtos/__init__.py | 0 app/apis/v2/payments/models/__init__.py | 0 app/apis/v2/payments/querys/__init__.py | 0 app/apis/v2/payments/services/__init__.py | 0 .../router.py => purchase_router.py} | 16 ++++----- app/apis/v2/purchases/__init__.py | 0 app/apis/v2/purchases/dtos/__init__.py | 0 app/apis/v2/purchases/models/__init__.py | 0 app/apis/v2/purchases/repositorys/__init__.py | 0 app/apis/v2/purchases/services/__init__.py | 0 app/apis/v2/questions/__init__.py | 0 app/apis/v2/questions/dtos/__init__.py | 0 app/apis/v2/questions/models/__init__.py | 0 app/apis/v2/questions/repositorys/__init__.py | 0 app/apis/v2/questions/router.py | 11 ------ app/apis/v2/questions/services/__init__.py | 0 .../router.py => teller_card_router.py} | 10 +++--- app/apis/v2/teller_cards/__init__.py | 0 app/apis/v2/teller_cards/dtos/__init__.py | 0 app/apis/v2/teller_cards/models/__init__.py | 0 app/apis/v2/teller_cards/querys/__init__.py | 0 app/apis/v2/teller_cards/services/__init__.py | 0 app/apis/v2/users/__init__.py | 0 app/apis/v2/users/dtos/__init__.py | 0 app/apis/v2/users/models/__init__.py | 0 app/apis/v2/users/querys/__init__.py | 0 app/apis/v2/users/services/__init__.py | 0 app/celery_worker.py | 2 +- app/common/handlers/router_handler.py | 22 ++++++------ app/common/tasks/mission_task.py | 2 +- app/common/tasks/renew_subscription_task.py | 2 +- app/core/configs/celery_settings.py | 2 +- app/core/database/database_settings.py | 28 +++++++-------- .../v2/answers => dtos/badge}/__init__.py | 0 .../badges/dtos => dtos/badge}/badge_dto.py | 0 .../v2/badges/dtos => dtos/badge}/response.py | 2 +- .../answers/dtos => dtos/cheese}/__init__.py | 0 .../dtos => dtos/cheese}/cheese_dto.py | 0 .../answers/models => dtos/color}/__init__.py | 0 .../colors/dtos => dtos/color}/color_dto.py | 0 .../v2/colors/dtos => dtos/color}/response.py | 2 +- .../querys => dtos/emotion}/__init__.py | 0 .../dtos => dtos/emotion}/response.py | 0 .../services => dtos/item}/__init__.py | 0 .../v2/items/dtos => dtos/item}/item_dto.py | 0 .../v2/badges => dtos/level}/__init__.py | 0 .../levels/dtos => dtos/level}/level_dto.py | 0 .../badges/dtos => dtos/mission}/__init__.py | 0 .../dtos => dtos/mission}/mission_dto.py | 0 .../missions/dtos => dtos/mission}/request.py | 0 .../dtos => dtos/mission}/response.py | 0 .../dtos => dtos/mission}/reward_dto.py | 0 .../badges/models => dtos/mobile}/__init__.py | 0 .../dtos => dtos/mobile}/mypage_response.py | 4 +-- .../mobile}/teller_card_response.py | 8 ++--- .../querys => dtos/payment}/__init__.py | 0 .../payments/dtos => dtos/payment}/request.py | 0 .../dtos => dtos/payment}/response.py | 0 .../services => dtos/purchase}/__init__.py | 0 .../dtos => dtos/purchase}/purchase_dto.py | 2 +- .../dtos => dtos/purchase}/requests.py | 0 .../question}/__init__.py | 0 .../dtos => dtos/question}/responses.py | 0 .../dtos => dtos/teller_card}/__init__.py | 0 .../dtos => dtos/teller_card}/request.py | 0 .../dtos => dtos/teller_card}/response.py | 2 +- .../teller_card}/teller_card_dto.py | 0 .../models => dtos/user}/__init__.py | 0 .../v2/users/dtos => dtos/user}/user_dto.py | 0 .../users/dtos => dtos/user}/user_info_dto.py | 2 +- .../dtos => dtos/user}/user_profile_dto.py | 0 app/{apis/v2/answers => }/models/answer.py | 4 +-- app/{apis/v2/badges => }/models/badge.py | 4 +-- .../models/cheese_manager.py | 2 +- .../models/cheese_status.py | 0 app/{apis/v2/colors => }/models/color.py | 4 +-- app/{apis/v2/emotions => }/models/emotion.py | 4 +-- app/{apis/v2/items => }/models/item.py | 0 app/{apis/v2/levels => }/models/level.py | 2 +- app/{apis/v2/likes => }/models/like.py | 6 ++-- app/{apis/v2/missions => }/models/mission.py | 4 +-- app/{apis/v2/notices => }/models/notice.py | 2 +- .../purchases => }/models/purchase_history.py | 10 +++--- .../purchases => }/models/purchase_status.py | 0 .../v2/questions => }/models/question.py | 0 .../v2/users => }/models/refresh_token.py | 0 .../teller_cards => }/models/teller_card.py | 2 +- app/{apis/v2/users => }/models/user.py | 10 +++--- .../querys => queries}/__init__.py | 0 .../querys => queries}/answer_query.py | 6 ++-- .../badges/querys => queries}/badge_query.py | 8 ++--- .../colors/querys => queries}/color_query.py | 8 ++--- .../querys => queries}/emotion_query.py | 6 ++-- .../levels/querys => queries}/level_query.py | 20 +++++------ .../v2/likes/querys => queries}/like_query.py | 0 .../querys => queries}/mission_query.py | 0 .../querys => queries}/teller_card_query.py | 12 +++---- .../v2/users/querys => queries}/user_query.py | 6 ++-- .../answers => }/services/answer_service.py | 2 +- .../v2/badges => }/services/badge_service.py | 4 +-- .../services/cheese_service.py | 2 +- .../v2/colors => }/services/color_service.py | 6 ++-- .../emotions => }/services/emotion_service.py | 6 ++-- .../v2/levels => }/services/level_service.py | 6 ++-- .../missions => }/services/mission_service.py | 24 ++++++------- .../notices => }/services/notice_service.py | 2 +- .../payments => }/services/payment_service.py | 10 +++--- .../services/purchase_service.py | 14 ++++---- .../services/teller_card_service.py | 8 ++--- .../v2/users => }/services/user_service.py | 4 +-- 158 files changed, 217 insertions(+), 230 deletions(-) rename app/apis/v2/{answers/router.py => answer_router.py} (66%) delete mode 100644 app/apis/v2/answers/dtos/answer_dto.py rename app/apis/v2/{badges/router.py => badge_router.py} (67%) delete mode 100644 app/apis/v2/cheese_managers/router.py delete mode 100644 app/apis/v2/cheese_managers/services/__init__.py create mode 100644 app/apis/v2/cheese_router.py rename app/apis/v2/{colors/router.py => color_router.py} (67%) delete mode 100644 app/apis/v2/colors/__init__.py delete mode 100644 app/apis/v2/colors/dtos/__init__.py delete mode 100644 app/apis/v2/colors/models/__init__.py delete mode 100644 app/apis/v2/colors/querys/__init__.py delete mode 100644 app/apis/v2/colors/services/__init__.py rename app/apis/v2/{emotions/router.py => emotion_router.py} (64%) delete mode 100644 app/apis/v2/emotions/__init__.py delete mode 100644 app/apis/v2/emotions/dtos/__init__.py delete mode 100644 app/apis/v2/emotions/models/__init__.py delete mode 100644 app/apis/v2/emotions/querys/__init__.py delete mode 100644 app/apis/v2/emotions/services/__init__.py delete mode 100644 app/apis/v2/items/__init__.py delete mode 100644 app/apis/v2/items/dtos/__init__.py delete mode 100644 app/apis/v2/items/models/__init__.py delete mode 100644 app/apis/v2/items/repositorys/__init__.py delete mode 100644 app/apis/v2/items/router.py delete mode 100644 app/apis/v2/items/services/__init__.py delete mode 100644 app/apis/v2/levels/__init__.py delete mode 100644 app/apis/v2/levels/dtos/__init__.py delete mode 100644 app/apis/v2/levels/models/__init__.py delete mode 100644 app/apis/v2/levels/querys/__init__.py delete mode 100644 app/apis/v2/levels/router.py delete mode 100644 app/apis/v2/levels/services/__init__.py delete mode 100644 app/apis/v2/likes/__init__.py delete mode 100644 app/apis/v2/likes/models/__init__.py delete mode 100644 app/apis/v2/likes/querys/__init__.py rename app/apis/v2/{missions/router.py => mission_router.py} (74%) delete mode 100644 app/apis/v2/missions/__init__.py delete mode 100644 app/apis/v2/missions/dtos/__init__.py delete mode 100644 app/apis/v2/missions/models/__init__.py delete mode 100644 app/apis/v2/missions/querys/__init__.py delete mode 100644 app/apis/v2/missions/services/__init__.py rename app/apis/v2/{mobiles/router.py => mobile_router.py} (73%) delete mode 100644 app/apis/v2/mobiles/__init__.py delete mode 100644 app/apis/v2/mobiles/dtos/__init__.py delete mode 100644 app/apis/v2/notices/__init__.py delete mode 100644 app/apis/v2/notices/dtos/__init__.py delete mode 100644 app/apis/v2/notices/models/__init__.py delete mode 100644 app/apis/v2/notices/services/__init__.py rename app/apis/v2/{payments/router.py => payment_router.py} (70%) delete mode 100644 app/apis/v2/payments/__init__.py delete mode 100644 app/apis/v2/payments/dtos/__init__.py delete mode 100644 app/apis/v2/payments/models/__init__.py delete mode 100644 app/apis/v2/payments/querys/__init__.py delete mode 100644 app/apis/v2/payments/services/__init__.py rename app/apis/v2/{purchases/router.py => purchase_router.py} (74%) delete mode 100644 app/apis/v2/purchases/__init__.py delete mode 100644 app/apis/v2/purchases/dtos/__init__.py delete mode 100644 app/apis/v2/purchases/models/__init__.py delete mode 100644 app/apis/v2/purchases/repositorys/__init__.py delete mode 100644 app/apis/v2/purchases/services/__init__.py delete mode 100644 app/apis/v2/questions/__init__.py delete mode 100644 app/apis/v2/questions/dtos/__init__.py delete mode 100644 app/apis/v2/questions/models/__init__.py delete mode 100644 app/apis/v2/questions/repositorys/__init__.py delete mode 100644 app/apis/v2/questions/router.py delete mode 100644 app/apis/v2/questions/services/__init__.py rename app/apis/v2/{teller_cards/router.py => teller_card_router.py} (68%) delete mode 100644 app/apis/v2/teller_cards/__init__.py delete mode 100644 app/apis/v2/teller_cards/dtos/__init__.py delete mode 100644 app/apis/v2/teller_cards/models/__init__.py delete mode 100644 app/apis/v2/teller_cards/querys/__init__.py delete mode 100644 app/apis/v2/teller_cards/services/__init__.py delete mode 100644 app/apis/v2/users/__init__.py delete mode 100644 app/apis/v2/users/dtos/__init__.py delete mode 100644 app/apis/v2/users/models/__init__.py delete mode 100644 app/apis/v2/users/querys/__init__.py delete mode 100644 app/apis/v2/users/services/__init__.py rename app/{apis/v2/answers => dtos/badge}/__init__.py (100%) rename app/{apis/v2/badges/dtos => dtos/badge}/badge_dto.py (100%) rename app/{apis/v2/badges/dtos => dtos/badge}/response.py (72%) rename app/{apis/v2/answers/dtos => dtos/cheese}/__init__.py (100%) rename app/{apis/v2/cheese_managers/dtos => dtos/cheese}/cheese_dto.py (100%) rename app/{apis/v2/answers/models => dtos/color}/__init__.py (100%) rename app/{apis/v2/colors/dtos => dtos/color}/color_dto.py (100%) rename app/{apis/v2/colors/dtos => dtos/color}/response.py (72%) rename app/{apis/v2/answers/querys => dtos/emotion}/__init__.py (100%) rename app/{apis/v2/emotions/dtos => dtos/emotion}/response.py (100%) rename app/{apis/v2/answers/services => dtos/item}/__init__.py (100%) rename app/{apis/v2/items/dtos => dtos/item}/item_dto.py (100%) rename app/{apis/v2/badges => dtos/level}/__init__.py (100%) rename app/{apis/v2/levels/dtos => dtos/level}/level_dto.py (100%) rename app/{apis/v2/badges/dtos => dtos/mission}/__init__.py (100%) rename app/{apis/v2/missions/dtos => dtos/mission}/mission_dto.py (100%) rename app/{apis/v2/missions/dtos => dtos/mission}/request.py (100%) rename app/{apis/v2/missions/dtos => dtos/mission}/response.py (100%) rename app/{apis/v2/missions/dtos => dtos/mission}/reward_dto.py (100%) rename app/{apis/v2/badges/models => dtos/mobile}/__init__.py (100%) rename app/{apis/v2/mobiles/dtos => dtos/mobile}/mypage_response.py (80%) rename app/{apis/v2/mobiles/dtos => dtos/mobile}/teller_card_response.py (78%) rename app/{apis/v2/badges/querys => dtos/payment}/__init__.py (100%) rename app/{apis/v2/payments/dtos => dtos/payment}/request.py (100%) rename app/{apis/v2/payments/dtos => dtos/payment}/response.py (100%) rename app/{apis/v2/badges/services => dtos/purchase}/__init__.py (100%) rename app/{apis/v2/purchases/dtos => dtos/purchase}/purchase_dto.py (96%) rename app/{apis/v2/purchases/dtos => dtos/purchase}/requests.py (100%) rename app/{apis/v2/cheese_managers => dtos/question}/__init__.py (100%) rename app/{apis/v2/questions/dtos => dtos/question}/responses.py (100%) rename app/{apis/v2/cheese_managers/dtos => dtos/teller_card}/__init__.py (100%) rename app/{apis/v2/teller_cards/dtos => dtos/teller_card}/request.py (100%) rename app/{apis/v2/teller_cards/dtos => dtos/teller_card}/response.py (84%) rename app/{apis/v2/teller_cards/dtos => dtos/teller_card}/teller_card_dto.py (100%) rename app/{apis/v2/cheese_managers/models => dtos/user}/__init__.py (100%) rename app/{apis/v2/users/dtos => dtos/user}/user_dto.py (100%) rename app/{apis/v2/users/dtos => dtos/user}/user_info_dto.py (85%) rename app/{apis/v2/users/dtos => dtos/user}/user_profile_dto.py (100%) rename app/{apis/v2/answers => }/models/answer.py (96%) rename app/{apis/v2/badges => }/models/badge.py (95%) rename app/{apis/v2/cheese_managers => }/models/cheese_manager.py (97%) rename app/{apis/v2/cheese_managers => }/models/cheese_status.py (100%) rename app/{apis/v2/colors => }/models/color.py (95%) rename app/{apis/v2/emotions => }/models/emotion.py (93%) rename app/{apis/v2/items => }/models/item.py (100%) rename app/{apis/v2/levels => }/models/level.py (95%) rename app/{apis/v2/likes => }/models/like.py (85%) rename app/{apis/v2/missions => }/models/mission.py (91%) rename app/{apis/v2/notices => }/models/notice.py (97%) rename app/{apis/v2/purchases => }/models/purchase_history.py (97%) rename app/{apis/v2/purchases => }/models/purchase_status.py (100%) rename app/{apis/v2/questions => }/models/question.py (100%) rename app/{apis/v2/users => }/models/refresh_token.py (100%) rename app/{apis/v2/teller_cards => }/models/teller_card.py (94%) rename app/{apis/v2/users => }/models/user.py (92%) rename app/{apis/v2/cheese_managers/querys => queries}/__init__.py (100%) rename app/{apis/v2/answers/querys => queries}/answer_query.py (82%) rename app/{apis/v2/badges/querys => queries}/badge_query.py (84%) rename app/{apis/v2/colors/querys => queries}/color_query.py (79%) rename app/{apis/v2/emotions/querys => queries}/emotion_query.py (69%) rename app/{apis/v2/levels/querys => queries}/level_query.py (82%) rename app/{apis/v2/likes/querys => queries}/like_query.py (100%) rename app/{apis/v2/missions/querys => queries}/mission_query.py (100%) rename app/{apis/v2/teller_cards/querys => queries}/teller_card_query.py (87%) rename app/{apis/v2/users/querys => queries}/user_query.py (94%) rename app/{apis/v2/answers => }/services/answer_service.py (97%) rename app/{apis/v2/badges => }/services/badge_service.py (89%) rename app/{apis/v2/cheese_managers => }/services/cheese_service.py (84%) rename app/{apis/v2/colors => }/services/color_service.py (83%) rename app/{apis/v2/emotions => }/services/emotion_service.py (88%) rename app/{apis/v2/levels => }/services/level_service.py (93%) rename app/{apis/v2/missions => }/services/mission_service.py (94%) rename app/{apis/v2/notices => }/services/notice_service.py (98%) rename app/{apis/v2/payments => }/services/payment_service.py (87%) rename app/{apis/v2/purchases => }/services/purchase_service.py (95%) rename app/{apis/v2/teller_cards => }/services/teller_card_service.py (82%) rename app/{apis/v2/users => }/services/user_service.py (83%) diff --git a/app/apis/v2/answers/router.py b/app/apis/v2/answer_router.py similarity index 66% rename from app/apis/v2/answers/router.py rename to app/apis/v2/answer_router.py index b8c4f75..25b35dd 100644 --- a/app/apis/v2/answers/router.py +++ b/app/apis/v2/answer_router.py @@ -1,18 +1,18 @@ from fastapi import APIRouter -from app.apis.v2.levels.services.level_service import LevelService +from app.services.level_service import LevelService -router = APIRouter(prefix="/answer", tags=["Test용"]) +answer_router = APIRouter(prefix="/answer", tags=["Test용"]) -@router.get("/level-up") +@answer_router.get("/level-up") async def level_up_handler() -> int: user_id = "180a4e40-62f8-46be-b1eb-e7e3dd91cddf" result = await LevelService.level_up(user_id=user_id) return result -@router.get("/add-exp") +@answer_router.get("/add-exp") async def add_exp_handler() -> None: user_id = "180a4e40-62f8-46be-b1eb-e7e3dd91cddf" await LevelService.add_exp(user_id=user_id, exp=100) diff --git a/app/apis/v2/answers/dtos/answer_dto.py b/app/apis/v2/answers/dtos/answer_dto.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/badges/router.py b/app/apis/v2/badge_router.py similarity index 67% rename from app/apis/v2/badges/router.py rename to app/apis/v2/badge_router.py index 409c19c..a198ccf 100644 --- a/app/apis/v2/badges/router.py +++ b/app/apis/v2/badge_router.py @@ -1,12 +1,12 @@ from fastapi import APIRouter, status -from app.apis.v2.badges.dtos.response import BadgeListResponseDTO -from app.apis.v2.badges.services.badge_service import BadgeService +from app.dtos.badge.response import BadgeListResponseDTO +from app.services.badge_service import BadgeService -router = APIRouter(prefix="/user/badge", tags=["Badge"]) +badge_router = APIRouter(prefix="/user/badge", tags=["Badge"]) -@router.get( +@badge_router.get( "", response_model=BadgeListResponseDTO, status_code=status.HTTP_200_OK, diff --git a/app/apis/v2/cheese_managers/router.py b/app/apis/v2/cheese_managers/router.py deleted file mode 100644 index f39fd09..0000000 --- a/app/apis/v2/cheese_managers/router.py +++ /dev/null @@ -1,17 +0,0 @@ -from fastapi import APIRouter, status - -from app.apis.v2.cheese_managers.dtos.cheese_dto import CheeseResponseDTO -from app.apis.v2.cheese_managers.services.cheese_service import CheeseService -from app.apis.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: str) -> CheeseResponseDTO: - - user = await UserService.get_user_info(user_id=user_id) - cheese_amount = await CheeseService.get_cheese_balance(user["cheese_manager_id"]) - print(cheese_amount) - - return CheeseResponseDTO.builder(cheese_balance=cheese_amount) diff --git a/app/apis/v2/cheese_managers/services/__init__.py b/app/apis/v2/cheese_managers/services/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/cheese_router.py b/app/apis/v2/cheese_router.py new file mode 100644 index 0000000..a99a620 --- /dev/null +++ b/app/apis/v2/cheese_router.py @@ -0,0 +1,17 @@ +from fastapi import APIRouter, status + +from app.dtos.cheese.cheese_dto import CheeseResponseDTO +from app.services.cheese_service import CheeseService +from app.services.user_service import UserService + +cheese_router = APIRouter(prefix="/cheese", tags=["Cheese"]) + + +@cheese_router.get("", response_model=CheeseResponseDTO, status_code=status.HTTP_200_OK) +async def get_cheese_handler(user_id: str) -> CheeseResponseDTO: + + user = await UserService.get_user_info(user_id=user_id) + cheese_amount = await CheeseService.get_cheese_balance(user["cheese_manager_id"]) + print(cheese_amount) + + return CheeseResponseDTO.builder(cheese_balance=cheese_amount) diff --git a/app/apis/v2/colors/router.py b/app/apis/v2/color_router.py similarity index 67% rename from app/apis/v2/colors/router.py rename to app/apis/v2/color_router.py index 02106d6..ae3ab4a 100644 --- a/app/apis/v2/colors/router.py +++ b/app/apis/v2/color_router.py @@ -1,12 +1,12 @@ from fastapi import APIRouter, status -from app.apis.v2.colors.dtos.response import ColorListResponseDTO -from app.apis.v2.colors.services.color_service import ColorService +from app.dtos.color.response import ColorListResponseDTO +from app.services.color_service import ColorService -router = APIRouter(prefix="/user/color", tags=["Color"]) +color_router = APIRouter(prefix="/user/color", tags=["Color"]) -@router.get( +@color_router.get( "", response_model=ColorListResponseDTO, status_code=status.HTTP_200_OK, diff --git a/app/apis/v2/colors/__init__.py b/app/apis/v2/colors/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/colors/dtos/__init__.py b/app/apis/v2/colors/dtos/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/colors/models/__init__.py b/app/apis/v2/colors/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/colors/querys/__init__.py b/app/apis/v2/colors/querys/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/colors/services/__init__.py b/app/apis/v2/colors/services/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/emotions/router.py b/app/apis/v2/emotion_router.py similarity index 64% rename from app/apis/v2/emotions/router.py rename to app/apis/v2/emotion_router.py index 0a64a68..3d27cee 100644 --- a/app/apis/v2/emotions/router.py +++ b/app/apis/v2/emotion_router.py @@ -1,12 +1,12 @@ from fastapi import APIRouter, status -from app.apis.v2.emotions.dtos.response import EmotionListResponseDTO -from app.apis.v2.emotions.services.emotion_service import EmotionService +from app.dtos.emotion.response import EmotionListResponseDTO +from app.services.emotion_service import EmotionService -router = APIRouter(prefix="/user/emotion", tags=["Emotion"]) +emotion_router = APIRouter(prefix="/user/emotion", tags=["Emotion"]) -@router.get( +@emotion_router.get( "", response_model=EmotionListResponseDTO, status_code=status.HTTP_200_OK, diff --git a/app/apis/v2/emotions/__init__.py b/app/apis/v2/emotions/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/emotions/dtos/__init__.py b/app/apis/v2/emotions/dtos/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/emotions/models/__init__.py b/app/apis/v2/emotions/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/emotions/querys/__init__.py b/app/apis/v2/emotions/querys/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/emotions/services/__init__.py b/app/apis/v2/emotions/services/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/items/__init__.py b/app/apis/v2/items/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/items/dtos/__init__.py b/app/apis/v2/items/dtos/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/items/models/__init__.py b/app/apis/v2/items/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/items/repositorys/__init__.py b/app/apis/v2/items/repositorys/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/items/router.py b/app/apis/v2/items/router.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/items/services/__init__.py b/app/apis/v2/items/services/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/levels/__init__.py b/app/apis/v2/levels/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/levels/dtos/__init__.py b/app/apis/v2/levels/dtos/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/levels/models/__init__.py b/app/apis/v2/levels/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/levels/querys/__init__.py b/app/apis/v2/levels/querys/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/levels/router.py b/app/apis/v2/levels/router.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/levels/services/__init__.py b/app/apis/v2/levels/services/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/likes/__init__.py b/app/apis/v2/likes/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/likes/models/__init__.py b/app/apis/v2/likes/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/likes/querys/__init__.py b/app/apis/v2/likes/querys/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/missions/router.py b/app/apis/v2/mission_router.py similarity index 74% rename from app/apis/v2/missions/router.py rename to app/apis/v2/mission_router.py index db92de4..5276bc7 100644 --- a/app/apis/v2/missions/router.py +++ b/app/apis/v2/mission_router.py @@ -2,18 +2,18 @@ from fastapi import APIRouter, Depends -from app.apis.v2.missions.services.mission_service import MissionService +from app.services.mission_service import MissionService from app.core.configs.celery_settings import process_mission_in_background -router = APIRouter(prefix="/mission", tags=["Mission"]) +mission_router = APIRouter(prefix="/mission", tags=["Mission"]) -@router.get("") +@mission_router.get("") async def mission_handler(user_id: str) -> None: process_mission_in_background.delay(user_id) -@router.get("/direct") +@mission_router.get("/direct") async def mission_handler_direct( user_id: str, mission_service: MissionService = Depends(), diff --git a/app/apis/v2/missions/__init__.py b/app/apis/v2/missions/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/missions/dtos/__init__.py b/app/apis/v2/missions/dtos/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/missions/models/__init__.py b/app/apis/v2/missions/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/missions/querys/__init__.py b/app/apis/v2/missions/querys/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/missions/services/__init__.py b/app/apis/v2/missions/services/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/mobiles/router.py b/app/apis/v2/mobile_router.py similarity index 73% rename from app/apis/v2/mobiles/router.py rename to app/apis/v2/mobile_router.py index 71845a1..12219c2 100644 --- a/app/apis/v2/mobiles/router.py +++ b/app/apis/v2/mobile_router.py @@ -2,27 +2,27 @@ from fastapi import APIRouter, status -from app.apis.v2.answers.services.answer_service import AnswerService -from app.apis.v2.badges.services.badge_service import BadgeService -from app.apis.v2.cheese_managers.services.cheese_service import CheeseService -from app.apis.v2.colors.services.color_service import ColorService -from app.apis.v2.levels.services.level_service import LevelService -from app.apis.v2.mobiles.dtos.mypage_response import MyPageResponseDTO, UserProfileWithLevel -from app.apis.v2.mobiles.dtos.teller_card_response import DataDTO, TellerCardResponseDTO -from app.apis.v2.teller_cards.services.teller_card_service import TellerCardService -from app.apis.v2.users.dtos.user_info_dto import UserInfoDTO -from app.apis.v2.users.dtos.user_profile_dto import UserProfileDTO -from app.apis.v2.users.services.user_service import UserService - -router = APIRouter(prefix="/mobiles", tags=["모바일 화면용 컨트롤러"]) - - -@router.post("/main") +from app.services.answer_service import AnswerService +from app.services.badge_service import BadgeService +from app.services.cheese_service import CheeseService +from app.services.color_service import ColorService +from app.services.level_service import LevelService +from app.dtos.mobile.mypage_response import MyPageResponseDTO, UserProfileWithLevel +from app.dtos.mobile.teller_card_response import DataDTO, TellerCardResponseDTO +from app.services.teller_card_service import TellerCardService +from app.dtos.user.user_info_dto import UserInfoDTO +from app.dtos.user.user_profile_dto import UserProfileDTO +from app.services.user_service import UserService + +mobile_router = APIRouter(prefix="/mobiles", tags=["모바일 화면용 컨트롤러"]) + + +@mobile_router.post("/main") async def mobile_main_handler() -> None: pass -@router.get( +@mobile_router.get( "/tellercard", response_model=TellerCardResponseDTO, status_code=status.HTTP_200_OK, @@ -55,7 +55,7 @@ async def mobile_teller_card_handler(user_id: str) -> TellerCardResponseDTO: ) -@router.get( +@mobile_router.get( "/mypage", response_model=MyPageResponseDTO, status_code=status.HTTP_200_OK, diff --git a/app/apis/v2/mobiles/__init__.py b/app/apis/v2/mobiles/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/mobiles/dtos/__init__.py b/app/apis/v2/mobiles/dtos/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/notices/__init__.py b/app/apis/v2/notices/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/notices/dtos/__init__.py b/app/apis/v2/notices/dtos/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/notices/models/__init__.py b/app/apis/v2/notices/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/notices/services/__init__.py b/app/apis/v2/notices/services/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/payments/router.py b/app/apis/v2/payment_router.py similarity index 70% rename from app/apis/v2/payments/router.py rename to app/apis/v2/payment_router.py index eb25d4e..371bdf3 100644 --- a/app/apis/v2/payments/router.py +++ b/app/apis/v2/payment_router.py @@ -1,14 +1,14 @@ from fastapi import APIRouter, HTTPException, status -from app.apis.v2.payments.dtos.request import PaymentRequestDTO -from app.apis.v2.payments.dtos.response import PaymentResponseDTO -from app.apis.v2.payments.services.payment_service import PaymentService -from app.apis.v2.users.services.user_service import UserService +from app.dtos.payment.request import PaymentRequestDTO +from app.dtos.payment.response import PaymentResponseDTO +from app.services.payment_service import PaymentService +from app.services.user_service import UserService -router = APIRouter(prefix="/payment", tags=["Payment"]) +payment_router = APIRouter(prefix="/payment", tags=["Payment"]) -@router.post( +@payment_router.post( "", response_model=PaymentResponseDTO, status_code=status.HTTP_200_OK, diff --git a/app/apis/v2/payments/__init__.py b/app/apis/v2/payments/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/payments/dtos/__init__.py b/app/apis/v2/payments/dtos/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/payments/models/__init__.py b/app/apis/v2/payments/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/payments/querys/__init__.py b/app/apis/v2/payments/querys/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/payments/services/__init__.py b/app/apis/v2/payments/services/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/purchases/router.py b/app/apis/v2/purchase_router.py similarity index 74% rename from app/apis/v2/purchases/router.py rename to app/apis/v2/purchase_router.py index d551845..f015560 100644 --- a/app/apis/v2/purchases/router.py +++ b/app/apis/v2/purchase_router.py @@ -2,14 +2,14 @@ from fastapi import APIRouter, Depends, status -from app.apis.v2.purchases.dtos.purchase_dto import PurchaseResponseDTO -from app.apis.v2.purchases.dtos.requests import ReceiptRequestDTO -from app.apis.v2.purchases.services.purchase_service import PurchaseService +from app.dtos.purchase.purchase_dto import PurchaseResponseDTO +from app.dtos.purchase.requests import ReceiptRequestDTO +from app.services.purchase_service import PurchaseService -router = APIRouter(prefix="/purchase", tags=["Purchase"]) +purchase_router = APIRouter(prefix="/purchase", tags=["Purchase"]) -@router.post( +@purchase_router.post( "/apple", status_code=status.HTTP_200_OK, response_model=PurchaseResponseDTO, @@ -23,7 +23,7 @@ async def process_receipt( return await purchase_service.process_apple_purchase(receipt_data=receipt.receiptData, user_id=receipt.user_id) -@router.post("/receipt-test") +@purchase_router.post("/receipt-test") async def receipt_test( receipt: ReceiptRequestDTO, purchase_service: PurchaseService = Depends(), @@ -36,14 +36,14 @@ async def receipt_test( } -@router.get("/renew-test") +@purchase_router.get("/renew-test") async def renew_test( purchase_service: PurchaseService = Depends(), ) -> None: return await purchase_service.process_subscriptions_renewal() -@router.get("/expired-test") +@purchase_router.get("/expired-test") async def expired_test( purchase_service: PurchaseService = Depends(), ) -> None: diff --git a/app/apis/v2/purchases/__init__.py b/app/apis/v2/purchases/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/purchases/dtos/__init__.py b/app/apis/v2/purchases/dtos/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/purchases/models/__init__.py b/app/apis/v2/purchases/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/purchases/repositorys/__init__.py b/app/apis/v2/purchases/repositorys/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/purchases/services/__init__.py b/app/apis/v2/purchases/services/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/questions/__init__.py b/app/apis/v2/questions/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/questions/dtos/__init__.py b/app/apis/v2/questions/dtos/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/questions/models/__init__.py b/app/apis/v2/questions/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/questions/repositorys/__init__.py b/app/apis/v2/questions/repositorys/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/questions/router.py b/app/apis/v2/questions/router.py deleted file mode 100644 index 9deda2e..0000000 --- a/app/apis/v2/questions/router.py +++ /dev/null @@ -1,11 +0,0 @@ -from fastapi import APIRouter - -router = APIRouter(prefix="/question", tags=["Question"]) - - -# @router.get("/questions/{date}") -# async def get_question_by_date(date: str): -# question = await Question.get_or_none(date=date) -# if question is None: -# raise HTTPException(status_code=404, detail="Question not found") -# return question diff --git a/app/apis/v2/questions/services/__init__.py b/app/apis/v2/questions/services/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/teller_cards/router.py b/app/apis/v2/teller_card_router.py similarity index 68% rename from app/apis/v2/teller_cards/router.py rename to app/apis/v2/teller_card_router.py index 68935bd..e8bfcac 100644 --- a/app/apis/v2/teller_cards/router.py +++ b/app/apis/v2/teller_card_router.py @@ -1,13 +1,13 @@ from fastapi import APIRouter, status -from app.apis.v2.teller_cards.dtos.request import TellerCardRequestDTO -from app.apis.v2.teller_cards.dtos.response import TellerCardResponseDTO -from app.apis.v2.teller_cards.services.teller_card_service import TellerCardService +from app.dtos.teller_card.request import TellerCardRequestDTO +from app.dtos.teller_card.response import TellerCardResponseDTO +from app.services.teller_card_service import TellerCardService -router = APIRouter(prefix="/tellercard", tags=["TellerCard"]) +teller_card_router = APIRouter(prefix="/tellercard", tags=["TellerCard"]) -@router.post( +@teller_card_router.post( "", response_model=TellerCardResponseDTO, status_code=status.HTTP_200_OK, diff --git a/app/apis/v2/teller_cards/__init__.py b/app/apis/v2/teller_cards/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/teller_cards/dtos/__init__.py b/app/apis/v2/teller_cards/dtos/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/teller_cards/models/__init__.py b/app/apis/v2/teller_cards/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/teller_cards/querys/__init__.py b/app/apis/v2/teller_cards/querys/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/teller_cards/services/__init__.py b/app/apis/v2/teller_cards/services/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/users/__init__.py b/app/apis/v2/users/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/users/dtos/__init__.py b/app/apis/v2/users/dtos/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/users/models/__init__.py b/app/apis/v2/users/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/users/querys/__init__.py b/app/apis/v2/users/querys/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/apis/v2/users/services/__init__.py b/app/apis/v2/users/services/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/celery_worker.py b/app/celery_worker.py index 7eeeeb6..7972d69 100644 --- a/app/celery_worker.py +++ b/app/celery_worker.py @@ -1,3 +1,3 @@ -from core.configs.celery_settings import celery_app +from app.core.configs.celery_settings import celery_app __all__ = ("celery_app",) diff --git a/app/common/handlers/router_handler.py b/app/common/handlers/router_handler.py index c137510..faab157 100644 --- a/app/common/handlers/router_handler.py +++ b/app/common/handlers/router_handler.py @@ -1,23 +1,21 @@ from fastapi import FastAPI -from app.apis.v2.answers.router import router as answer_router -from app.apis.v2.badges.router import router as badge_router -from app.apis.v2.cheese_managers.router import router as cheese_router -from app.apis.v2.colors.router import router as color_router -from app.apis.v2.emotions.router import router as emotion_router -from app.apis.v2.missions.router import router as mission_router -from app.apis.v2.mobiles.router import router as mobile_router -from app.apis.v2.payments.router import router as payment_router -from app.apis.v2.purchases.router import router as purchase_router -from app.apis.v2.questions.router import router as question_router -from app.apis.v2.teller_cards.router import router as teller_card_router +from app.apis.v2.answer_router import answer_router +from app.apis.v2.badge_router import badge_router as badge_router +from app.apis.v2.cheese_router import cheese_router as cheese_router +from app.apis.v2.color_router import color_router as color_router +from app.apis.v2.emotion_router import emotion_router as emotion_router +from app.apis.v2.mission_router import mission_router as mission_router +from app.apis.v2.mobile_router import mobile_router as mobile_router +from app.apis.v2.payment_router import payment_router as payment_router +from app.apis.v2.purchase_router import purchase_router as purchase_router +from app.apis.v2.teller_card_router import teller_card_router as teller_card_router def attach_router_handlers(app: FastAPI) -> None: app.include_router(router=mobile_router, prefix="/api/v2") app.include_router(router=badge_router, prefix="/api/v2") app.include_router(router=color_router, prefix="/api/v2") - app.include_router(router=question_router, prefix="/api/v2") app.include_router(router=teller_card_router, prefix="/api/v2") app.include_router(router=payment_router, prefix="/api/v2") app.include_router(router=purchase_router, prefix="/api/v2") diff --git a/app/common/tasks/mission_task.py b/app/common/tasks/mission_task.py index f7da282..ea0efcd 100644 --- a/app/common/tasks/mission_task.py +++ b/app/common/tasks/mission_task.py @@ -1,6 +1,6 @@ import asyncio -from app.apis.v2.missions.models.mission import UserMission +from app.models.mission import UserMission async def mission_reset_task() -> None: diff --git a/app/common/tasks/renew_subscription_task.py b/app/common/tasks/renew_subscription_task.py index daf0035..746f2e3 100644 --- a/app/common/tasks/renew_subscription_task.py +++ b/app/common/tasks/renew_subscription_task.py @@ -1,4 +1,4 @@ -from app.apis.v2.purchases.services.purchase_service import PurchaseService +from app.services.purchase_service import PurchaseService async def renew_subscription_task() -> None: diff --git a/app/core/configs/celery_settings.py b/app/core/configs/celery_settings.py index 89c6958..0fa76b0 100644 --- a/app/core/configs/celery_settings.py +++ b/app/core/configs/celery_settings.py @@ -4,7 +4,7 @@ from celery import Celery from tortoise import Tortoise -from app.apis.v2.missions.services.mission_service import MissionService +from app.services.mission_service import MissionService from app.common.tasks.mission_task import mission_reset_task from app.core.database.database_settings import TORTOISE_ORM diff --git a/app/core/database/database_settings.py b/app/core/database/database_settings.py index edabab0..f3c57a4 100644 --- a/app/core/database/database_settings.py +++ b/app/core/database/database_settings.py @@ -5,20 +5,20 @@ from app.core.configs import settings TORTOISE_APP_MODELS = [ - "app.apis.v2.questions.models.question", - "app.apis.v2.users.models.user", - "app.apis.v2.users.models.refresh_token", - "app.apis.v2.badges.models.badge", - "app.apis.v2.colors.models.color", - "app.apis.v2.answers.models.answer", - "app.apis.v2.teller_cards.models.teller_card", - "app.apis.v2.levels.models.level", - "app.apis.v2.cheese_managers.models.cheese_manager", - "app.apis.v2.items.models.item", - "app.apis.v2.missions.models.mission", - "app.apis.v2.likes.models.like", - "app.apis.v2.emotions.models.emotion", - "app.apis.v2.purchases.models.purchase_history", + "app.models.question", + "app.models.user", + "app.models.refresh_token", + "app.models.badge", + "app.models.color", + "app.models.answer", + "app.models.teller_card", + "app.models.level", + "app.models.cheese_manager", + "app.models.item", + "app.models.mission", + "app.models.like", + "app.models.emotion", + "app.models.purchase_history", ] TORTOISE_ORM = { diff --git a/app/apis/v2/answers/__init__.py b/app/dtos/badge/__init__.py similarity index 100% rename from app/apis/v2/answers/__init__.py rename to app/dtos/badge/__init__.py diff --git a/app/apis/v2/badges/dtos/badge_dto.py b/app/dtos/badge/badge_dto.py similarity index 100% rename from app/apis/v2/badges/dtos/badge_dto.py rename to app/dtos/badge/badge_dto.py diff --git a/app/apis/v2/badges/dtos/response.py b/app/dtos/badge/response.py similarity index 72% rename from app/apis/v2/badges/dtos/response.py rename to app/dtos/badge/response.py index ac98ae0..7a77474 100644 --- a/app/apis/v2/badges/dtos/response.py +++ b/app/dtos/badge/response.py @@ -1,4 +1,4 @@ -from app.apis.v2.badges.dtos.badge_dto import BadgeDTO +from app.dtos.badge.badge_dto import BadgeDTO from app.common.base_models.base_dtos.base_response import BaseResponseDTO diff --git a/app/apis/v2/answers/dtos/__init__.py b/app/dtos/cheese/__init__.py similarity index 100% rename from app/apis/v2/answers/dtos/__init__.py rename to app/dtos/cheese/__init__.py diff --git a/app/apis/v2/cheese_managers/dtos/cheese_dto.py b/app/dtos/cheese/cheese_dto.py similarity index 100% rename from app/apis/v2/cheese_managers/dtos/cheese_dto.py rename to app/dtos/cheese/cheese_dto.py diff --git a/app/apis/v2/answers/models/__init__.py b/app/dtos/color/__init__.py similarity index 100% rename from app/apis/v2/answers/models/__init__.py rename to app/dtos/color/__init__.py diff --git a/app/apis/v2/colors/dtos/color_dto.py b/app/dtos/color/color_dto.py similarity index 100% rename from app/apis/v2/colors/dtos/color_dto.py rename to app/dtos/color/color_dto.py diff --git a/app/apis/v2/colors/dtos/response.py b/app/dtos/color/response.py similarity index 72% rename from app/apis/v2/colors/dtos/response.py rename to app/dtos/color/response.py index 5dd1429..2f6a3e7 100644 --- a/app/apis/v2/colors/dtos/response.py +++ b/app/dtos/color/response.py @@ -1,4 +1,4 @@ -from app.apis.v2.colors.dtos.color_dto import ColorDTO +from app.dtos.color.color_dto import ColorDTO from app.common.base_models.base_dtos.base_response import BaseResponseDTO diff --git a/app/apis/v2/answers/querys/__init__.py b/app/dtos/emotion/__init__.py similarity index 100% rename from app/apis/v2/answers/querys/__init__.py rename to app/dtos/emotion/__init__.py diff --git a/app/apis/v2/emotions/dtos/response.py b/app/dtos/emotion/response.py similarity index 100% rename from app/apis/v2/emotions/dtos/response.py rename to app/dtos/emotion/response.py diff --git a/app/apis/v2/answers/services/__init__.py b/app/dtos/item/__init__.py similarity index 100% rename from app/apis/v2/answers/services/__init__.py rename to app/dtos/item/__init__.py diff --git a/app/apis/v2/items/dtos/item_dto.py b/app/dtos/item/item_dto.py similarity index 100% rename from app/apis/v2/items/dtos/item_dto.py rename to app/dtos/item/item_dto.py diff --git a/app/apis/v2/badges/__init__.py b/app/dtos/level/__init__.py similarity index 100% rename from app/apis/v2/badges/__init__.py rename to app/dtos/level/__init__.py diff --git a/app/apis/v2/levels/dtos/level_dto.py b/app/dtos/level/level_dto.py similarity index 100% rename from app/apis/v2/levels/dtos/level_dto.py rename to app/dtos/level/level_dto.py diff --git a/app/apis/v2/badges/dtos/__init__.py b/app/dtos/mission/__init__.py similarity index 100% rename from app/apis/v2/badges/dtos/__init__.py rename to app/dtos/mission/__init__.py diff --git a/app/apis/v2/missions/dtos/mission_dto.py b/app/dtos/mission/mission_dto.py similarity index 100% rename from app/apis/v2/missions/dtos/mission_dto.py rename to app/dtos/mission/mission_dto.py diff --git a/app/apis/v2/missions/dtos/request.py b/app/dtos/mission/request.py similarity index 100% rename from app/apis/v2/missions/dtos/request.py rename to app/dtos/mission/request.py diff --git a/app/apis/v2/missions/dtos/response.py b/app/dtos/mission/response.py similarity index 100% rename from app/apis/v2/missions/dtos/response.py rename to app/dtos/mission/response.py diff --git a/app/apis/v2/missions/dtos/reward_dto.py b/app/dtos/mission/reward_dto.py similarity index 100% rename from app/apis/v2/missions/dtos/reward_dto.py rename to app/dtos/mission/reward_dto.py diff --git a/app/apis/v2/badges/models/__init__.py b/app/dtos/mobile/__init__.py similarity index 100% rename from app/apis/v2/badges/models/__init__.py rename to app/dtos/mobile/__init__.py diff --git a/app/apis/v2/mobiles/dtos/mypage_response.py b/app/dtos/mobile/mypage_response.py similarity index 80% rename from app/apis/v2/mobiles/dtos/mypage_response.py rename to app/dtos/mobile/mypage_response.py index f1ed53b..ebe8316 100644 --- a/app/apis/v2/mobiles/dtos/mypage_response.py +++ b/app/dtos/mobile/mypage_response.py @@ -1,7 +1,7 @@ from pydantic import BaseModel -from app.apis.v2.levels.dtos.level_dto import LevelInfoDTO -from app.apis.v2.users.dtos.user_profile_dto import UserProfileDTO +from app.dtos.level.level_dto import LevelInfoDTO +from app.dtos.user.user_profile_dto import UserProfileDTO from app.common.base_models.base_dtos.base_response import BaseResponseDTO diff --git a/app/apis/v2/mobiles/dtos/teller_card_response.py b/app/dtos/mobile/teller_card_response.py similarity index 78% rename from app/apis/v2/mobiles/dtos/teller_card_response.py rename to app/dtos/mobile/teller_card_response.py index bd1b15c..0389cf1 100644 --- a/app/apis/v2/mobiles/dtos/teller_card_response.py +++ b/app/dtos/mobile/teller_card_response.py @@ -2,10 +2,10 @@ from pydantic import BaseModel -from app.apis.v2.badges.dtos.badge_dto import BadgeDTO -from app.apis.v2.colors.dtos.color_dto import ColorDTO -from app.apis.v2.levels.dtos.level_dto import LevelInfoDTO -from app.apis.v2.users.dtos.user_info_dto import UserInfoDTO +from app.dtos.badge.badge_dto import BadgeDTO +from app.dtos.color.color_dto import ColorDTO +from app.dtos.level.level_dto import LevelInfoDTO +from app.dtos.user.user_info_dto import UserInfoDTO from app.common.base_models.base_dtos.base_response import BaseResponseDTO diff --git a/app/apis/v2/badges/querys/__init__.py b/app/dtos/payment/__init__.py similarity index 100% rename from app/apis/v2/badges/querys/__init__.py rename to app/dtos/payment/__init__.py diff --git a/app/apis/v2/payments/dtos/request.py b/app/dtos/payment/request.py similarity index 100% rename from app/apis/v2/payments/dtos/request.py rename to app/dtos/payment/request.py diff --git a/app/apis/v2/payments/dtos/response.py b/app/dtos/payment/response.py similarity index 100% rename from app/apis/v2/payments/dtos/response.py rename to app/dtos/payment/response.py diff --git a/app/apis/v2/badges/services/__init__.py b/app/dtos/purchase/__init__.py similarity index 100% rename from app/apis/v2/badges/services/__init__.py rename to app/dtos/purchase/__init__.py diff --git a/app/apis/v2/purchases/dtos/purchase_dto.py b/app/dtos/purchase/purchase_dto.py similarity index 96% rename from app/apis/v2/purchases/dtos/purchase_dto.py rename to app/dtos/purchase/purchase_dto.py index 06eed14..f98c11d 100644 --- a/app/apis/v2/purchases/dtos/purchase_dto.py +++ b/app/dtos/purchase/purchase_dto.py @@ -2,7 +2,7 @@ from pydantic import BaseModel -from app.apis.v2.purchases.models.purchase_status import purchase_mapping +from app.models.purchase_status import purchase_mapping class ReceiptInfoDTO(BaseModel): diff --git a/app/apis/v2/purchases/dtos/requests.py b/app/dtos/purchase/requests.py similarity index 100% rename from app/apis/v2/purchases/dtos/requests.py rename to app/dtos/purchase/requests.py diff --git a/app/apis/v2/cheese_managers/__init__.py b/app/dtos/question/__init__.py similarity index 100% rename from app/apis/v2/cheese_managers/__init__.py rename to app/dtos/question/__init__.py diff --git a/app/apis/v2/questions/dtos/responses.py b/app/dtos/question/responses.py similarity index 100% rename from app/apis/v2/questions/dtos/responses.py rename to app/dtos/question/responses.py diff --git a/app/apis/v2/cheese_managers/dtos/__init__.py b/app/dtos/teller_card/__init__.py similarity index 100% rename from app/apis/v2/cheese_managers/dtos/__init__.py rename to app/dtos/teller_card/__init__.py diff --git a/app/apis/v2/teller_cards/dtos/request.py b/app/dtos/teller_card/request.py similarity index 100% rename from app/apis/v2/teller_cards/dtos/request.py rename to app/dtos/teller_card/request.py diff --git a/app/apis/v2/teller_cards/dtos/response.py b/app/dtos/teller_card/response.py similarity index 84% rename from app/apis/v2/teller_cards/dtos/response.py rename to app/dtos/teller_card/response.py index 386ebf1..67f509e 100644 --- a/app/apis/v2/teller_cards/dtos/response.py +++ b/app/dtos/teller_card/response.py @@ -1,6 +1,6 @@ from pydantic import BaseModel -from app.apis.v2.teller_cards.dtos.teller_card_dto import TellerCardDTO as TellerCardLogicDTO +from app.dtos.teller_card.teller_card_dto import TellerCardDTO as TellerCardLogicDTO from app.common.base_models.base_dtos.base_response import BaseResponseDTO diff --git a/app/apis/v2/teller_cards/dtos/teller_card_dto.py b/app/dtos/teller_card/teller_card_dto.py similarity index 100% rename from app/apis/v2/teller_cards/dtos/teller_card_dto.py rename to app/dtos/teller_card/teller_card_dto.py diff --git a/app/apis/v2/cheese_managers/models/__init__.py b/app/dtos/user/__init__.py similarity index 100% rename from app/apis/v2/cheese_managers/models/__init__.py rename to app/dtos/user/__init__.py diff --git a/app/apis/v2/users/dtos/user_dto.py b/app/dtos/user/user_dto.py similarity index 100% rename from app/apis/v2/users/dtos/user_dto.py rename to app/dtos/user/user_dto.py diff --git a/app/apis/v2/users/dtos/user_info_dto.py b/app/dtos/user/user_info_dto.py similarity index 85% rename from app/apis/v2/users/dtos/user_info_dto.py rename to app/dtos/user/user_info_dto.py index 3cf46d8..e9400ba 100644 --- a/app/apis/v2/users/dtos/user_info_dto.py +++ b/app/dtos/user/user_info_dto.py @@ -1,6 +1,6 @@ from pydantic import BaseModel -from app.apis.v2.teller_cards.dtos.teller_card_dto import TellerCardDTO +from app.dtos.teller_card.response import TellerCardDTO class UserInfoDTO(BaseModel): diff --git a/app/apis/v2/users/dtos/user_profile_dto.py b/app/dtos/user/user_profile_dto.py similarity index 100% rename from app/apis/v2/users/dtos/user_profile_dto.py rename to app/dtos/user/user_profile_dto.py diff --git a/app/apis/v2/answers/models/answer.py b/app/models/answer.py similarity index 96% rename from app/apis/v2/answers/models/answer.py rename to app/models/answer.py index 3e892d3..8970544 100644 --- a/app/apis/v2/answers/models/answer.py +++ b/app/models/answer.py @@ -5,13 +5,13 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model -from app.apis.v2.answers.querys.answer_query import ( +from app.queries.answer_query import ( SELECT_ANSWER_BY_USER_UUID_QUERY, SELECT_ANSWER_COUNT_BY_USER_UUID_QUERY, SELECT_ANSWER_COUNT_BY_USER_UUID_QUERY_V2, SELECT_MOST_RECENT_ANSWER_BY_USER_UUID_QUERY, ) -from app.apis.v2.users.models.user import User +from app.models.user import User from app.common.utils.query_executor import QueryExecutor diff --git a/app/apis/v2/badges/models/badge.py b/app/models/badge.py similarity index 95% rename from app/apis/v2/badges/models/badge.py rename to app/models/badge.py index 3ab436e..a4378cf 100644 --- a/app/apis/v2/badges/models/badge.py +++ b/app/models/badge.py @@ -4,13 +4,13 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model -from app.apis.v2.badges.querys.badge_query import ( +from app.queries.badge_query import ( INSERT_BADGE_CODE_FOR_USER_QUERY, SELECT_BADGE_BY_USER_UUID_QUERY, SELECT_BADGE_CODE_BY_USER_UUID_QUERY, SELECT_BADGE_COUNT_BY_USER_UUID_QUERY, ) -from app.apis.v2.users.models.user import User +from app.models.user import User from app.common.utils.query_executor import QueryExecutor diff --git a/app/apis/v2/cheese_managers/models/cheese_manager.py b/app/models/cheese_manager.py similarity index 97% rename from app/apis/v2/cheese_managers/models/cheese_manager.py rename to app/models/cheese_manager.py index 641d61a..134e0fd 100644 --- a/app/apis/v2/cheese_managers/models/cheese_manager.py +++ b/app/models/cheese_manager.py @@ -6,7 +6,7 @@ from tortoise.functions import Sum from tortoise.models import Model -from app.apis.v2.cheese_managers.models.cheese_status import CheeseStatus +from app.models.cheese_status import CheeseStatus class CheeseManager(Model): diff --git a/app/apis/v2/cheese_managers/models/cheese_status.py b/app/models/cheese_status.py similarity index 100% rename from app/apis/v2/cheese_managers/models/cheese_status.py rename to app/models/cheese_status.py diff --git a/app/apis/v2/colors/models/color.py b/app/models/color.py similarity index 95% rename from app/apis/v2/colors/models/color.py rename to app/models/color.py index c56f763..6834172 100644 --- a/app/apis/v2/colors/models/color.py +++ b/app/models/color.py @@ -4,12 +4,12 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model -from app.apis.v2.colors.querys.color_query import ( +from app.queries.color_query import ( INSERT_COLOR_CODE_FOR_USER_QUERY, SELECT_COLOR_BY_USER_UUID_QUERY, SELECT_COLOR_CODE_BY_USER_UUID_QUERY, ) -from app.apis.v2.users.models.user import User +from app.models.user import User from app.common.utils.query_executor import QueryExecutor diff --git a/app/apis/v2/emotions/models/emotion.py b/app/models/emotion.py similarity index 93% rename from app/apis/v2/emotions/models/emotion.py rename to app/models/emotion.py index 345f6cf..18b7b0e 100644 --- a/app/apis/v2/emotions/models/emotion.py +++ b/app/models/emotion.py @@ -3,11 +3,11 @@ from tortoise import fields, models from tortoise.fields import ForeignKeyRelation -from app.apis.v2.emotions.querys.emotion_query import ( +from app.queries.emotion_query import ( INSERT_EMOTION_CODE_FOR_USER_QUERY, SELECT_EMOTION_CODE_BY_USER_UUID_QUERY, ) -from app.apis.v2.users.models.user import User +from app.models.user import User from app.common.utils.query_executor import QueryExecutor diff --git a/app/apis/v2/items/models/item.py b/app/models/item.py similarity index 100% rename from app/apis/v2/items/models/item.py rename to app/models/item.py diff --git a/app/apis/v2/levels/models/level.py b/app/models/level.py similarity index 95% rename from app/apis/v2/levels/models/level.py rename to app/models/level.py index f30fc0e..30806ad 100644 --- a/app/apis/v2/levels/models/level.py +++ b/app/models/level.py @@ -3,7 +3,7 @@ from tortoise import fields from tortoise.models import Model -from app.apis.v2.levels.querys.level_query import ( +from app.queries.level_query import ( SELECT_USER_LEVEL_AND_REQUIRED_EXP_QUERY, UPDATE_USER_LEVEL_AND_EXP_QUERY, ) diff --git a/app/apis/v2/likes/models/like.py b/app/models/like.py similarity index 85% rename from app/apis/v2/likes/models/like.py rename to app/models/like.py index bbbd06a..b3441dd 100644 --- a/app/apis/v2/likes/models/like.py +++ b/app/models/like.py @@ -4,9 +4,9 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model -from app.apis.v2.answers.models.answer import Answer -from app.apis.v2.likes.querys.like_query import SELECT_UNIQUE_LIKES_COUNT_BY_USER_TODAY_QUERY -from app.apis.v2.users.models.user import User +from app.models.answer import Answer +from app.queries.like_query import SELECT_UNIQUE_LIKES_COUNT_BY_USER_TODAY_QUERY +from app.models.user import User from app.common.utils.query_executor import QueryExecutor diff --git a/app/apis/v2/missions/models/mission.py b/app/models/mission.py similarity index 91% rename from app/apis/v2/missions/models/mission.py rename to app/models/mission.py index c8dbccf..05af298 100644 --- a/app/apis/v2/missions/models/mission.py +++ b/app/models/mission.py @@ -4,8 +4,8 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model -from app.apis.v2.missions.querys.mission_query import SELECT_USER_MISSIONS_QUERY, UPDATE_USER_MISSION_PROGRESS_QUERY -from app.apis.v2.users.models.user import User +from app.queries.mission_query import SELECT_USER_MISSIONS_QUERY, UPDATE_USER_MISSION_PROGRESS_QUERY +from app.models.user import User from app.common.utils.query_executor import QueryExecutor diff --git a/app/apis/v2/notices/models/notice.py b/app/models/notice.py similarity index 97% rename from app/apis/v2/notices/models/notice.py rename to app/models/notice.py index 8c95267..ae12edd 100644 --- a/app/apis/v2/notices/models/notice.py +++ b/app/models/notice.py @@ -4,7 +4,7 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model -from app.apis.v2.users.models.user import User +from app.models.user import User from app.common.utils.query_executor import QueryExecutor diff --git a/app/apis/v2/purchases/models/purchase_history.py b/app/models/purchase_history.py similarity index 97% rename from app/apis/v2/purchases/models/purchase_history.py rename to app/models/purchase_history.py index 490dfce..f20fd70 100644 --- a/app/apis/v2/purchases/models/purchase_history.py +++ b/app/models/purchase_history.py @@ -5,7 +5,7 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model -from app.apis.v2.users.models.user import User +from app.models.user import User from app.common.utils.query_executor import QueryExecutor @@ -139,13 +139,13 @@ async def create_purchase_history( ) -> None: query = """ INSERT INTO purchase_history ( - user_id, subscription_id, product_code, transaction_id, - original_transaction_id, status, expires_date, purchase_date, + user_id, subscription_id, product_code, transaction_id, + original_transaction_id, status, expires_date, purchase_date, quantity, receipt_data, created_at, updated_at ) VALUES ( - UNHEX(REPLACE(%s, '-', '')), %s, %s, %s, - %s, %s, FROM_UNIXTIME(%s / 1000), FROM_UNIXTIME(%s / 1000), + UNHEX(REPLACE(%s, '-', '')), %s, %s, %s, + %s, %s, FROM_UNIXTIME(%s / 1000), FROM_UNIXTIME(%s / 1000), %s, %s, NOW(), NOW() ); """ diff --git a/app/apis/v2/purchases/models/purchase_status.py b/app/models/purchase_status.py similarity index 100% rename from app/apis/v2/purchases/models/purchase_status.py rename to app/models/purchase_status.py diff --git a/app/apis/v2/questions/models/question.py b/app/models/question.py similarity index 100% rename from app/apis/v2/questions/models/question.py rename to app/models/question.py diff --git a/app/apis/v2/users/models/refresh_token.py b/app/models/refresh_token.py similarity index 100% rename from app/apis/v2/users/models/refresh_token.py rename to app/models/refresh_token.py diff --git a/app/apis/v2/teller_cards/models/teller_card.py b/app/models/teller_card.py similarity index 94% rename from app/apis/v2/teller_cards/models/teller_card.py rename to app/models/teller_card.py index 75e1560..9e14049 100644 --- a/app/apis/v2/teller_cards/models/teller_card.py +++ b/app/models/teller_card.py @@ -3,7 +3,7 @@ from tortoise import fields from tortoise.models import Model -from app.apis.v2.teller_cards.querys.teller_card_query import ( +from app.queries.teller_card_query import ( PATCH_TELLER_CARD_QUERY, SELECT_TELLER_CARD_INFO_BY_USER_UUID_QUERY, ) diff --git a/app/apis/v2/users/models/user.py b/app/models/user.py similarity index 92% rename from app/apis/v2/users/models/user.py rename to app/models/user.py index 28a1a70..6974193 100644 --- a/app/apis/v2/users/models/user.py +++ b/app/models/user.py @@ -6,11 +6,11 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model -from app.apis.v2.cheese_managers.models.cheese_manager import CheeseManager -from app.apis.v2.levels.models.level import Level -from app.apis.v2.teller_cards.models.teller_card import TellerCard -from app.apis.v2.users.models.refresh_token import RefreshToken -from app.apis.v2.users.querys.user_query import ( +from app.models.cheese_manager import CheeseManager +from app.models.level import Level +from app.models.teller_card import TellerCard +from app.models.refresh_token import RefreshToken +from app.queries.user_query import ( SELECT_USER_INFO_BY_USER_UUID_QUERY, SELECT_USER_PROFILE_BY_USER_ID_QUERY, UPDATE_PREMIUM_STATUS_QUERY, diff --git a/app/apis/v2/cheese_managers/querys/__init__.py b/app/queries/__init__.py similarity index 100% rename from app/apis/v2/cheese_managers/querys/__init__.py rename to app/queries/__init__.py diff --git a/app/apis/v2/answers/querys/answer_query.py b/app/queries/answer_query.py similarity index 82% rename from app/apis/v2/answers/querys/answer_query.py rename to app/queries/answer_query.py index e76c1d7..5f5b138 100644 --- a/app/apis/v2/answers/querys/answer_query.py +++ b/app/queries/answer_query.py @@ -1,10 +1,10 @@ -from app.apis.v2.users.querys.user_query import USER_ID_QUERY +from app.queries.user_query import USER_ID_QUERY SELECT_ANSWER_COUNT_BY_USER_UUID_QUERY = f"SELECT COUNT(*) as answer_count FROM answer WHERE {USER_ID_QUERY}" SELECT_ANSWER_COUNT_BY_USER_UUID_QUERY_V2 = f""" - SELECT COUNT(*) as answer_count - FROM answer + SELECT COUNT(*) as answer_count + FROM answer WHERE {USER_ID_QUERY} AND created_time >= '2024-12-16 00:00:00' """ diff --git a/app/apis/v2/badges/querys/badge_query.py b/app/queries/badge_query.py similarity index 84% rename from app/apis/v2/badges/querys/badge_query.py rename to app/queries/badge_query.py index d337e90..6bc5951 100644 --- a/app/apis/v2/badges/querys/badge_query.py +++ b/app/queries/badge_query.py @@ -1,4 +1,4 @@ -from app.apis.v2.users.querys.user_query import USER_ID_QUERY +from app.queries.user_query import USER_ID_QUERY SELECT_BADGE_COUNT_BY_USER_UUID_QUERY = f""" SELECT COUNT(*) as badge_count @@ -7,7 +7,7 @@ """ SELECT_BADGE_BY_USER_UUID_QUERY = f""" - SELECT + SELECT b.badge_code, bi.badge_name, bi.badge_condition, @@ -18,8 +18,8 @@ """ SELECT_BADGE_CODE_BY_USER_UUID_QUERY = f""" - SELECT badge_code - FROM badge + SELECT badge_code + FROM badge WHERE {USER_ID_QUERY} """ INSERT_BADGE_CODE_FOR_USER_QUERY = f""" diff --git a/app/apis/v2/colors/querys/color_query.py b/app/queries/color_query.py similarity index 79% rename from app/apis/v2/colors/querys/color_query.py rename to app/queries/color_query.py index 4b02c0f..bd425dd 100644 --- a/app/apis/v2/colors/querys/color_query.py +++ b/app/queries/color_query.py @@ -1,8 +1,8 @@ -from app.apis.v2.users.querys.user_query import USER_ID_QUERY +from app.queries.user_query import USER_ID_QUERY SELECT_COLOR_CODE_BY_USER_UUID_QUERY = f""" - SELECT color_code - FROM color + SELECT color_code + FROM color WHERE {USER_ID_QUERY} """ @@ -14,7 +14,7 @@ """ SELECT_COLOR_BY_USER_UUID_QUERY = f""" - SELECT + SELECT c.color_code, ci.color_name, ci.color_hex_code diff --git a/app/apis/v2/emotions/querys/emotion_query.py b/app/queries/emotion_query.py similarity index 69% rename from app/apis/v2/emotions/querys/emotion_query.py rename to app/queries/emotion_query.py index 40595e7..3bb84d3 100644 --- a/app/apis/v2/emotions/querys/emotion_query.py +++ b/app/queries/emotion_query.py @@ -1,8 +1,8 @@ -from app.apis.v2.users.querys.user_query import USER_ID_QUERY +from app.queries.user_query import USER_ID_QUERY SELECT_EMOTION_CODE_BY_USER_UUID_QUERY = f""" - SELECT emotion_code - FROM emotion + SELECT emotion_code + FROM emotion WHERE {USER_ID_QUERY} """ diff --git a/app/apis/v2/levels/querys/level_query.py b/app/queries/level_query.py similarity index 82% rename from app/apis/v2/levels/querys/level_query.py rename to app/queries/level_query.py index 562e460..28d4d91 100644 --- a/app/apis/v2/levels/querys/level_query.py +++ b/app/queries/level_query.py @@ -1,12 +1,12 @@ -from app.apis.v2.users.querys.user_query import USER_ID_QUERY +from app.queries.user_query import USER_ID_QUERY SELECT_USER_LEVEL_AND_EXP_BY_USER_UUID_QUERY = f""" - SELECT - l.user_exp AS level_exp, + SELECT + l.user_exp AS level_exp, l.user_level AS level_level - FROM + FROM user u - JOIN + JOIN level l ON u.level_id = l.level_id WHERE {USER_ID_QUERY} """ @@ -28,17 +28,17 @@ """ SELECT_USER_LEVEL_AND_REQUIRED_EXP_QUERY = f""" - SELECT + SELECT l.user_exp AS level_exp, l.user_level AS level_level, li.required_exp AS required_exp - FROM + FROM user u - JOIN + JOIN level l ON u.level_id = l.level_id - JOIN + JOIN level_inventory li ON l.user_level = li.level - WHERE + WHERE {USER_ID_QUERY} LIMIT 1; """ diff --git a/app/apis/v2/likes/querys/like_query.py b/app/queries/like_query.py similarity index 100% rename from app/apis/v2/likes/querys/like_query.py rename to app/queries/like_query.py diff --git a/app/apis/v2/missions/querys/mission_query.py b/app/queries/mission_query.py similarity index 100% rename from app/apis/v2/missions/querys/mission_query.py rename to app/queries/mission_query.py diff --git a/app/apis/v2/teller_cards/querys/teller_card_query.py b/app/queries/teller_card_query.py similarity index 87% rename from app/apis/v2/teller_cards/querys/teller_card_query.py rename to app/queries/teller_card_query.py index cc47cba..74e5001 100644 --- a/app/apis/v2/teller_cards/querys/teller_card_query.py +++ b/app/queries/teller_card_query.py @@ -1,7 +1,7 @@ -from app.apis.v2.users.querys.user_query import USER_ID_QUERY +from app.queries.user_query import USER_ID_QUERY SELECT_TELLER_CARD_INFO_BY_USER_UUID_QUERY = f""" - SELECT + SELECT tc.activate_badge_code, bi.badge_name, bi.badge_middle_name, @@ -9,16 +9,16 @@ FROM teller_card tc JOIN badge_inventory bi ON tc.activate_badge_code = bi.badge_code WHERE tc.teller_card_id = ( - SELECT u.teller_card_id - FROM user u + SELECT u.teller_card_id + FROM user u WHERE {USER_ID_QUERY} ) """ PATCH_TELLER_CARD_QUERY = """ UPDATE teller_card - SET - activate_badge_code = COALESCE(%s, activate_badge_code), + SET + activate_badge_code = COALESCE(%s, activate_badge_code), activate_color_code = COALESCE(%s, activate_color_code) WHERE teller_card_id = ( SELECT u.teller_card_id diff --git a/app/apis/v2/users/querys/user_query.py b/app/queries/user_query.py similarity index 94% rename from app/apis/v2/users/querys/user_query.py rename to app/queries/user_query.py index 9966e95..a3f7ed5 100644 --- a/app/apis/v2/users/querys/user_query.py +++ b/app/queries/user_query.py @@ -4,7 +4,7 @@ SELECT_USER_PROFILE_BY_USER_ID_QUERY = f""" - SELECT + SELECT u.nickname, u.is_premium, u.cheese_manager_id, @@ -14,7 +14,7 @@ """ SELECT_USER_INFO_BY_USER_UUID_QUERY = f""" - SELECT + SELECT u.nickname, u.cheese_manager_id FROM user u @@ -29,7 +29,7 @@ """ UPDATE_PREMIUM_STATUS_QUERY = f""" - UPDATE `user` + UPDATE `user` SET `is_premium` = %s, `premium_started_at` = %s WHERE {USER_ID_QUERY} diff --git a/app/apis/v2/answers/services/answer_service.py b/app/services/answer_service.py similarity index 97% rename from app/apis/v2/answers/services/answer_service.py rename to app/services/answer_service.py index ca5aaef..0ed5eff 100644 --- a/app/apis/v2/answers/services/answer_service.py +++ b/app/services/answer_service.py @@ -3,7 +3,7 @@ import pytz -from app.apis.v2.answers.models.answer import Answer +from app.models.answer import Answer class AnswerService: diff --git a/app/apis/v2/badges/services/badge_service.py b/app/services/badge_service.py similarity index 89% rename from app/apis/v2/badges/services/badge_service.py rename to app/services/badge_service.py index be7a00e..6c11ca3 100644 --- a/app/apis/v2/badges/services/badge_service.py +++ b/app/services/badge_service.py @@ -1,5 +1,5 @@ -from app.apis.v2.badges.dtos.badge_dto import BadgeCodeDTO, BadgeDTO -from app.apis.v2.badges.models.badge import Badge, BadgeInventory +from app.dtos.badge.badge_dto import BadgeCodeDTO, BadgeDTO +from app.models.badge import Badge, BadgeInventory class BadgeService: diff --git a/app/apis/v2/cheese_managers/services/cheese_service.py b/app/services/cheese_service.py similarity index 84% rename from app/apis/v2/cheese_managers/services/cheese_service.py rename to app/services/cheese_service.py index f44f4f6..fc4518e 100644 --- a/app/apis/v2/cheese_managers/services/cheese_service.py +++ b/app/services/cheese_service.py @@ -1,4 +1,4 @@ -from app.apis.v2.cheese_managers.models.cheese_manager import CheeseManager +from app.models.cheese_manager import CheeseManager class CheeseService: diff --git a/app/apis/v2/colors/services/color_service.py b/app/services/color_service.py similarity index 83% rename from app/apis/v2/colors/services/color_service.py rename to app/services/color_service.py index 5d202bf..35d38ba 100644 --- a/app/apis/v2/colors/services/color_service.py +++ b/app/services/color_service.py @@ -1,6 +1,6 @@ -from app.apis.v2.colors.dtos.color_dto import ColorCodeDTO, ColorDTO -from app.apis.v2.colors.models.color import Color, ColorInventory -from app.apis.v2.users.services.user_service import UserService +from app.dtos.color.color_dto import ColorCodeDTO, ColorDTO +from app.models.color import Color, ColorInventory +from app.services.user_service import UserService class ColorService: diff --git a/app/apis/v2/emotions/services/emotion_service.py b/app/services/emotion_service.py similarity index 88% rename from app/apis/v2/emotions/services/emotion_service.py rename to app/services/emotion_service.py index 835d08f..1785f13 100644 --- a/app/apis/v2/emotions/services/emotion_service.py +++ b/app/services/emotion_service.py @@ -1,8 +1,8 @@ from typing import Any -from app.apis.v2.emotions.dtos.response import EmotionDTO -from app.apis.v2.emotions.models.emotion import Emotion, EmotionInventory -from app.apis.v2.users.services.user_service import UserService +from app.dtos.emotion.response import EmotionDTO +from app.models.emotion import Emotion, EmotionInventory +from app.services.user_service import UserService emotion_mapping = { "EM_HAPPY": 1, diff --git a/app/apis/v2/levels/services/level_service.py b/app/services/level_service.py similarity index 93% rename from app/apis/v2/levels/services/level_service.py rename to app/services/level_service.py index 0bf8dab..ce7e56e 100644 --- a/app/apis/v2/levels/services/level_service.py +++ b/app/services/level_service.py @@ -1,8 +1,8 @@ from fastapi import HTTPException -from app.apis.v2.answers.services.answer_service import AnswerService -from app.apis.v2.levels.dtos.level_dto import LevelDTO, LevelInfoDTO -from app.apis.v2.levels.models.level import Level +from app.services.answer_service import AnswerService +from app.dtos.level.level_dto import LevelDTO, LevelInfoDTO +from app.models.level import Level class LevelService: diff --git a/app/apis/v2/missions/services/mission_service.py b/app/services/mission_service.py similarity index 94% rename from app/apis/v2/missions/services/mission_service.py rename to app/services/mission_service.py index 7469d89..f29d60b 100644 --- a/app/apis/v2/missions/services/mission_service.py +++ b/app/services/mission_service.py @@ -7,18 +7,18 @@ from tortoise.exceptions import DoesNotExist from tortoise.transactions import atomic -from app.apis.v2.answers.services.answer_service import AnswerService -from app.apis.v2.badges.services.badge_service import BadgeService -from app.apis.v2.cheese_managers.services.cheese_service import CheeseService -from app.apis.v2.colors.services.color_service import ColorService -from app.apis.v2.items.models.item import ItemInventory, ItemInventoryRewardInventory, RewardInventory -from app.apis.v2.levels.services.level_service import LevelService -from app.apis.v2.likes.models.like import Like -from app.apis.v2.missions.dtos.mission_dto import UserMissionDTO -from app.apis.v2.missions.dtos.reward_dto import RewardDTO -from app.apis.v2.missions.models.mission import MissionInventory, UserMission -from app.apis.v2.notices.services.notice_service import NoticeService -from app.apis.v2.users.services.user_service import UserService +from app.services.answer_service import AnswerService +from app.services.badge_service import BadgeService +from app.services.cheese_service import CheeseService +from app.services.color_service import ColorService +from app.models.item import ItemInventory, ItemInventoryRewardInventory, RewardInventory +from app.services.level_service import LevelService +from app.models.like import Like +from app.dtos.mission.mission_dto import UserMissionDTO +from app.dtos.mission.reward_dto import RewardDTO +from app.models.mission import MissionInventory, UserMission +from app.services.notice_service import NoticeService +from app.services.user_service import UserService class MissionService: diff --git a/app/apis/v2/notices/services/notice_service.py b/app/services/notice_service.py similarity index 98% rename from app/apis/v2/notices/services/notice_service.py rename to app/services/notice_service.py index f175954..74fe793 100644 --- a/app/apis/v2/notices/services/notice_service.py +++ b/app/services/notice_service.py @@ -1,6 +1,6 @@ from typing import Optional -from app.apis.v2.notices.models.notice import Notice +from app.models.notice import Notice class NoticeService: diff --git a/app/apis/v2/payments/services/payment_service.py b/app/services/payment_service.py similarity index 87% rename from app/apis/v2/payments/services/payment_service.py rename to app/services/payment_service.py index 29c9322..ddc80f3 100644 --- a/app/apis/v2/payments/services/payment_service.py +++ b/app/services/payment_service.py @@ -1,11 +1,11 @@ from tortoise.exceptions import DoesNotExist, IntegrityError from tortoise.transactions import atomic -from app.apis.v2.badges.services.badge_service import BadgeService -from app.apis.v2.cheese_managers.models.cheese_manager import CheeseManager -from app.apis.v2.colors.services.color_service import ColorService -from app.apis.v2.emotions.services.emotion_service import EmotionService -from app.apis.v2.items.models.item import ItemInventory, ItemInventoryProductInventory, ProductInventory +from app.services.badge_service import BadgeService +from app.models.cheese_manager import CheeseManager +from app.services.color_service import ColorService +from app.services.emotion_service import EmotionService +from app.models.item import ItemInventory, ItemInventoryProductInventory, ProductInventory from app.common.exceptions.custom_exception import CustomException from app.common.exceptions.error_code import ErrorCode diff --git a/app/apis/v2/purchases/services/purchase_service.py b/app/services/purchase_service.py similarity index 95% rename from app/apis/v2/purchases/services/purchase_service.py rename to app/services/purchase_service.py index 55b3d4b..0ea51a3 100644 --- a/app/apis/v2/purchases/services/purchase_service.py +++ b/app/services/purchase_service.py @@ -8,12 +8,12 @@ from tortoise.exceptions import DoesNotExist from tortoise.transactions import atomic -from app.apis.v2.items.models.item import ItemInventory, ItemInventoryProductInventory, ProductInventory -from app.apis.v2.purchases.dtos.purchase_dto import PurchaseResponseDTO, ReceiptInfoDTO -from app.apis.v2.purchases.models.purchase_history import PurchaseHistory, Subscription -from app.apis.v2.purchases.models.purchase_status import PurchaseStatus, SubscriptionStatus -from app.apis.v2.users.models.user import User -from app.apis.v2.users.services.user_service import UserService +from app.models.item import ItemInventory, ItemInventoryProductInventory, ProductInventory +from app.dtos.purchase.purchase_dto import PurchaseResponseDTO, ReceiptInfoDTO +from app.models.purchase_history import PurchaseHistory, Subscription +from app.models.purchase_status import PurchaseStatus, SubscriptionStatus +from app.models.user import User +from app.services.user_service import UserService from app.common.exceptions.custom_exception import CustomException from app.common.exceptions.error_code import ErrorCode from app.core.configs import settings @@ -188,7 +188,7 @@ async def _process_purchase( ) -> None: for item_inventory_product in item_inventory_products: item: ItemInventory = await item_inventory_product.item_inventory - quantity = item_inventory_product.quantity + # quantity = item_inventory_product.quantity if item.item_category == "SUBSCRIPTION": if status == SubscriptionStatus.ACTIVE.value: diff --git a/app/apis/v2/teller_cards/services/teller_card_service.py b/app/services/teller_card_service.py similarity index 82% rename from app/apis/v2/teller_cards/services/teller_card_service.py rename to app/services/teller_card_service.py index f707b46..792a234 100644 --- a/app/apis/v2/teller_cards/services/teller_card_service.py +++ b/app/services/teller_card_service.py @@ -1,9 +1,9 @@ from typing import Optional -from app.apis.v2.badges.models.badge import BadgeInventory -from app.apis.v2.colors.models.color import ColorInventory -from app.apis.v2.teller_cards.dtos.teller_card_dto import TellerCardDTO -from app.apis.v2.teller_cards.models.teller_card import TellerCard +from app.models.badge import BadgeInventory +from app.models.color import ColorInventory +from app.dtos.teller_card.teller_card_dto import TellerCardDTO +from app.models.teller_card import TellerCard class TellerCardService: diff --git a/app/apis/v2/users/services/user_service.py b/app/services/user_service.py similarity index 83% rename from app/apis/v2/users/services/user_service.py rename to app/services/user_service.py index 0b3ce60..7f87020 100644 --- a/app/apis/v2/users/services/user_service.py +++ b/app/services/user_service.py @@ -1,7 +1,7 @@ from typing import Any -from app.apis.v2.users.dtos.user_dto import UserDTO -from app.apis.v2.users.models.user import User +from app.dtos.user.user_dto import UserDTO +from app.models.user import User class UserService: From 2de1cd66cd8afd4dfc466610b31a84265a0840d0 Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Sun, 1 Jun 2025 16:51:14 +0900 Subject: [PATCH 03/27] =?UTF-8?q?=E2=9C=A8=20refactor:=20alchemy=20&=20tor?= =?UTF-8?q?toise=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 198 +++++++++++++++++- app/__init__.py | 1 + app/common/post_construct.py | 2 +- app/core/configs/base_settings.py | 4 + app/core/configs/celery_settings.py | 2 +- ...tings.py => tortoise_database_settings.py} | 0 6 files changed, 201 insertions(+), 6 deletions(-) rename app/core/database/{database_settings.py => tortoise_database_settings.py} (100%) diff --git a/.gitignore b/.gitignore index 7048d74..249a9a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,151 @@ +/etc +/htmlcov +/uploads + +# Ignore .env.local files +.env +.env.* + +# Created by https://www.toptal.com/developers/gitignore/api/python,pycharm+all,macos,windows,linux +# Edit at https://www.toptal.com/developers/gitignore?templates=python,pycharm+all,macos,windows,linux + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### PyCharm+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### PyCharm+all Patch ### +# Ignore everything but code style settings and run configurations +# that are supposed to be shared within teams. + +.idea/* + +!.idea/codeStyles +!.idea/runConfigurations + +### Python ### # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -57,6 +205,7 @@ cover/ # Django stuff: *.log +*.log.* local_settings.py db.sqlite3 db.sqlite3-journal @@ -106,10 +255,8 @@ ipython_config.py #pdm.lock # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it # in version control. -# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +# https://pdm.fming.dev/#use-with-ide .pdm.toml -.pdm-python -.pdm-build/ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ @@ -122,7 +269,7 @@ celerybeat.pid *.sage.py # Environments -app/.env.local +.env.* .venv env/ venv/ @@ -159,4 +306,47 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/python,pycharm+all,macos,windows,linux +/app/.env.local + +# JetBrains 관련 설정 파일 무시 .idea/ +/app/migrations/ diff --git a/app/__init__.py b/app/__init__.py index e7edade..4afe45c 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -4,6 +4,7 @@ from app.common.post_construct import post_construct + logging.basicConfig(level=logging.DEBUG) db_client_logger = logging.getLogger("tortoise.db_client") diff --git a/app/common/post_construct.py b/app/common/post_construct.py index 2ba7540..9c3dce5 100644 --- a/app/common/post_construct.py +++ b/app/common/post_construct.py @@ -3,7 +3,7 @@ from app.common.handlers.exception_handler import attach_exception_handlers from app.common.handlers.router_handler import attach_router_handlers from app.common.utils.scheduler import start_scheduler -from app.core.database.database_settings import database_initialize +from app.core.database.tortoise_database_settings import database_initialize def post_construct(app: FastAPI) -> None: diff --git a/app/core/configs/base_settings.py b/app/core/configs/base_settings.py index 247b705..1a0c982 100644 --- a/app/core/configs/base_settings.py +++ b/app/core/configs/base_settings.py @@ -25,3 +25,7 @@ class Settings(BaseSettings): class Config: env_file = f".env.{os.getenv('ENV', 'local')}" env_file_encoding = "utf-8" + + @property + def database_url(self) -> str: + return f"mysql+asyncmy://{self.DB_USER}:{self.DB_PASSWORD}" f"@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}" \ No newline at end of file diff --git a/app/core/configs/celery_settings.py b/app/core/configs/celery_settings.py index 0fa76b0..efed109 100644 --- a/app/core/configs/celery_settings.py +++ b/app/core/configs/celery_settings.py @@ -6,7 +6,7 @@ from app.services.mission_service import MissionService from app.common.tasks.mission_task import mission_reset_task -from app.core.database.database_settings import TORTOISE_ORM +from app.core.database.tortoise_database_settings import TORTOISE_ORM celery_app = Celery( "telling-me-celery", diff --git a/app/core/database/database_settings.py b/app/core/database/tortoise_database_settings.py similarity index 100% rename from app/core/database/database_settings.py rename to app/core/database/tortoise_database_settings.py From 742875c85130bf852d964075fef04b4af5a1f396 Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Tue, 3 Jun 2025 15:13:06 +0900 Subject: [PATCH 04/27] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20test=20:=20test=20se?= =?UTF-8?q?tting=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/__init__.py | 7 ---- app/apis/v2/answer_router.py | 18 ---------- app/apis/v2/badge_router.py | 2 -- app/apis/v2/mission_router.py | 2 +- app/apis/v2/mobile_router.py | 13 +++----- app/common/base_models/__init__.py | 0 app/common/base_models/base_dtos/__init__.py | 0 .../base_models/custom_fields/__init__.py | 0 app/common/handlers/router_handler.py | 2 -- app/core/configs/base_settings.py | 2 +- app/core/configs/celery_settings.py | 2 +- app/core/database/sql_alchemy_db_settings.py | 19 +++++++++++ .../database/tortoise_database_settings.py | 2 +- app/dtos/badge/badge_dto.py | 4 ++- app/dtos/badge/response.py | 5 ++- .../base_dtos => dtos}/base_response.py | 4 +++ app/dtos/cheese/cheese_dto.py | 2 +- app/dtos/color/response.py | 2 +- app/dtos/emotion/response.py | 2 +- app/dtos/frozen_config.py | 3 ++ app/dtos/mission/reward_dto.py | 5 ++- app/dtos/mobile/mypage_response.py | 2 +- app/dtos/mobile/teller_card_response.py | 2 +- app/dtos/payment/response.py | 2 +- app/dtos/teller_card/response.py | 2 +- app/dtos/user/user_info_dto.py | 2 +- app/models/answer.py | 6 ++-- app/models/badge.py | 6 ++-- app/models/cheese_manager.py | 4 +-- app/models/color.py | 6 ++-- app/models/emotion.py | 8 ++--- app/models/item.py | 10 +++--- app/models/level.py | 6 ++-- app/models/like.py | 6 ++-- app/models/mission.py | 8 ++--- app/models/notice.py | 4 +-- app/models/purchase_history.py | 6 ++-- app/models/question.py | 2 +- app/models/refresh_token.py | 2 +- app/models/teller_card.py | 4 +-- app/models/user.py | 6 ++-- app/services/answer_service.py | 5 ++- app/services/level_service.py | 2 +- app/services/mission_service.py | 14 ++++---- app/services/payment_service.py | 8 ++--- app/services/purchase_service.py | 8 ++--- app/services/teller_card_service.py | 2 +- app/tests/fixtures.py | 33 +++++++++++++++++++ app/tests/test_simple.py | 2 ++ asgi.py | 6 ++++ conftest.py | 22 +++++++++++++ 51 files changed, 175 insertions(+), 117 deletions(-) delete mode 100644 app/apis/v2/answer_router.py delete mode 100644 app/common/base_models/__init__.py delete mode 100644 app/common/base_models/base_dtos/__init__.py delete mode 100644 app/common/base_models/custom_fields/__init__.py create mode 100644 app/core/database/sql_alchemy_db_settings.py rename app/{common/base_models/base_dtos => dtos}/base_response.py (69%) create mode 100644 app/dtos/frozen_config.py create mode 100644 app/tests/fixtures.py create mode 100644 app/tests/test_simple.py create mode 100644 asgi.py create mode 100644 conftest.py diff --git a/app/__init__.py b/app/__init__.py index 4afe45c..b1d6d96 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -4,7 +4,6 @@ from app.common.post_construct import post_construct - logging.basicConfig(level=logging.DEBUG) db_client_logger = logging.getLogger("tortoise.db_client") @@ -17,9 +16,3 @@ @app.get("/health_check") def health_check() -> dict[str, str]: return {"message": "Hello World"} - - -# if __name__ == "__main__": -# import uvicorn -# -# uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/app/apis/v2/answer_router.py b/app/apis/v2/answer_router.py deleted file mode 100644 index 25b35dd..0000000 --- a/app/apis/v2/answer_router.py +++ /dev/null @@ -1,18 +0,0 @@ -from fastapi import APIRouter - -from app.services.level_service import LevelService - -answer_router = APIRouter(prefix="/answer", tags=["Test용"]) - - -@answer_router.get("/level-up") -async def level_up_handler() -> int: - user_id = "180a4e40-62f8-46be-b1eb-e7e3dd91cddf" - result = await LevelService.level_up(user_id=user_id) - return result - - -@answer_router.get("/add-exp") -async def add_exp_handler() -> None: - user_id = "180a4e40-62f8-46be-b1eb-e7e3dd91cddf" - await LevelService.add_exp(user_id=user_id, exp=100) diff --git a/app/apis/v2/badge_router.py b/app/apis/v2/badge_router.py index a198ccf..0a56ca9 100644 --- a/app/apis/v2/badge_router.py +++ b/app/apis/v2/badge_router.py @@ -12,9 +12,7 @@ status_code=status.HTTP_200_OK, ) async def get_user_badge_handler(user_id: str) -> BadgeListResponseDTO: - badges = await BadgeService.get_badges_with_details_by_user_id(user_id) - return BadgeListResponseDTO( code=status.HTTP_200_OK, message="보유 뱃지 정보 조회", diff --git a/app/apis/v2/mission_router.py b/app/apis/v2/mission_router.py index 5276bc7..6781ba7 100644 --- a/app/apis/v2/mission_router.py +++ b/app/apis/v2/mission_router.py @@ -2,8 +2,8 @@ from fastapi import APIRouter, Depends -from app.services.mission_service import MissionService from app.core.configs.celery_settings import process_mission_in_background +from app.services.mission_service import MissionService mission_router = APIRouter(prefix="/mission", tags=["Mission"]) diff --git a/app/apis/v2/mobile_router.py b/app/apis/v2/mobile_router.py index 12219c2..1451559 100644 --- a/app/apis/v2/mobile_router.py +++ b/app/apis/v2/mobile_router.py @@ -2,26 +2,21 @@ from fastapi import APIRouter, status +from app.dtos.mobile.mypage_response import MyPageResponseDTO, UserProfileWithLevel +from app.dtos.mobile.teller_card_response import DataDTO, TellerCardResponseDTO +from app.dtos.user.user_info_dto import UserInfoDTO +from app.dtos.user.user_profile_dto import UserProfileDTO from app.services.answer_service import AnswerService from app.services.badge_service import BadgeService from app.services.cheese_service import CheeseService from app.services.color_service import ColorService from app.services.level_service import LevelService -from app.dtos.mobile.mypage_response import MyPageResponseDTO, UserProfileWithLevel -from app.dtos.mobile.teller_card_response import DataDTO, TellerCardResponseDTO from app.services.teller_card_service import TellerCardService -from app.dtos.user.user_info_dto import UserInfoDTO -from app.dtos.user.user_profile_dto import UserProfileDTO from app.services.user_service import UserService mobile_router = APIRouter(prefix="/mobiles", tags=["모바일 화면용 컨트롤러"]) -@mobile_router.post("/main") -async def mobile_main_handler() -> None: - pass - - @mobile_router.get( "/tellercard", response_model=TellerCardResponseDTO, diff --git a/app/common/base_models/__init__.py b/app/common/base_models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/common/base_models/base_dtos/__init__.py b/app/common/base_models/base_dtos/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/common/base_models/custom_fields/__init__.py b/app/common/base_models/custom_fields/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/common/handlers/router_handler.py b/app/common/handlers/router_handler.py index faab157..adee726 100644 --- a/app/common/handlers/router_handler.py +++ b/app/common/handlers/router_handler.py @@ -1,6 +1,5 @@ from fastapi import FastAPI -from app.apis.v2.answer_router import answer_router from app.apis.v2.badge_router import badge_router as badge_router from app.apis.v2.cheese_router import cheese_router as cheese_router from app.apis.v2.color_router import color_router as color_router @@ -21,5 +20,4 @@ def attach_router_handlers(app: FastAPI) -> None: app.include_router(router=purchase_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") app.include_router(router=emotion_router, prefix="/api/v2") diff --git a/app/core/configs/base_settings.py b/app/core/configs/base_settings.py index 1a0c982..bfb1d7f 100644 --- a/app/core/configs/base_settings.py +++ b/app/core/configs/base_settings.py @@ -28,4 +28,4 @@ class Config: @property def database_url(self) -> str: - return f"mysql+asyncmy://{self.DB_USER}:{self.DB_PASSWORD}" f"@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}" \ No newline at end of file + return f"mysql+asyncmy://{self.DB_USER}:{self.DB_PASSWORD}" f"@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}" diff --git a/app/core/configs/celery_settings.py b/app/core/configs/celery_settings.py index efed109..b5d8df5 100644 --- a/app/core/configs/celery_settings.py +++ b/app/core/configs/celery_settings.py @@ -4,9 +4,9 @@ from celery import Celery from tortoise import Tortoise -from app.services.mission_service import MissionService from app.common.tasks.mission_task import mission_reset_task from app.core.database.tortoise_database_settings import TORTOISE_ORM +from app.services.mission_service import MissionService celery_app = Celery( "telling-me-celery", diff --git a/app/core/database/sql_alchemy_db_settings.py b/app/core/database/sql_alchemy_db_settings.py new file mode 100644 index 0000000..a8ed00c --- /dev/null +++ b/app/core/database/sql_alchemy_db_settings.py @@ -0,0 +1,19 @@ +from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine + +from app.core.configs import settings + +_async_engine = create_async_engine( + settings.database_url, + pool_recycle=3600, + echo=False, +) +_AsyncSessionFactory = async_sessionmaker( + autocommit=False, + autoflush=False, + expire_on_commit=False, + bind=_async_engine, +) + + +def get_async_session_with_alchemy() -> AsyncSession: + return _AsyncSessionFactory() diff --git a/app/core/database/tortoise_database_settings.py b/app/core/database/tortoise_database_settings.py index f3c57a4..11227a6 100644 --- a/app/core/database/tortoise_database_settings.py +++ b/app/core/database/tortoise_database_settings.py @@ -52,7 +52,7 @@ def database_initialize(app: FastAPI) -> None: Tortoise.init_models(TORTOISE_APP_MODELS, "models") register_tortoise( - app, + app=app, config=TORTOISE_ORM, generate_schemas=False, add_exception_handlers=True, diff --git a/app/dtos/badge/badge_dto.py b/app/dtos/badge/badge_dto.py index 30772de..2bbb4bc 100644 --- a/app/dtos/badge/badge_dto.py +++ b/app/dtos/badge/badge_dto.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from pydantic import BaseModel @@ -5,7 +7,7 @@ class BadgeCodeDTO(BaseModel): badgeCode: str @classmethod - def builder(cls, badge_raw: dict[str, str]) -> "BadgeCodeDTO": + def builder(cls, badge_raw: dict[str, str]) -> BadgeCodeDTO: return cls(badgeCode=badge_raw.get("badge_code", "")) diff --git a/app/dtos/badge/response.py b/app/dtos/badge/response.py index 7a77474..d2704d9 100644 --- a/app/dtos/badge/response.py +++ b/app/dtos/badge/response.py @@ -1,6 +1,9 @@ +from app.dtos.base_response import BaseResponseDTO from app.dtos.badge.badge_dto import BadgeDTO -from app.common.base_models.base_dtos.base_response import BaseResponseDTO +from app.dtos.frozen_config import FROZEN_CONFIG class BadgeListResponseDTO(BaseResponseDTO): + model_config = FROZEN_CONFIG + data: list[BadgeDTO] diff --git a/app/common/base_models/base_dtos/base_response.py b/app/dtos/base_response.py similarity index 69% rename from app/common/base_models/base_dtos/base_response.py rename to app/dtos/base_response.py index 13d2463..6c27126 100644 --- a/app/common/base_models/base_dtos/base_response.py +++ b/app/dtos/base_response.py @@ -2,9 +2,13 @@ from pydantic import BaseModel +from app.dtos.frozen_config import FROZEN_CONFIG + # 공통 응답 모델 정의 class BaseResponseDTO(BaseModel): + model_config = FROZEN_CONFIG + code: int message: str data: Optional[Any] = None diff --git a/app/dtos/cheese/cheese_dto.py b/app/dtos/cheese/cheese_dto.py index 87e7aa7..c83748b 100644 --- a/app/dtos/cheese/cheese_dto.py +++ b/app/dtos/cheese/cheese_dto.py @@ -2,7 +2,7 @@ from pydantic import BaseModel -from app.common.base_models.base_dtos.base_response import BaseResponseDTO +from app.dtos.base_response import BaseResponseDTO class CheeseAmountResult(TypedDict): diff --git a/app/dtos/color/response.py b/app/dtos/color/response.py index 2f6a3e7..3a76f21 100644 --- a/app/dtos/color/response.py +++ b/app/dtos/color/response.py @@ -1,5 +1,5 @@ +from app.dtos.base_response import BaseResponseDTO from app.dtos.color.color_dto import ColorDTO -from app.common.base_models.base_dtos.base_response import BaseResponseDTO class ColorListResponseDTO(BaseResponseDTO): diff --git a/app/dtos/emotion/response.py b/app/dtos/emotion/response.py index 6acc2c3..9d32342 100644 --- a/app/dtos/emotion/response.py +++ b/app/dtos/emotion/response.py @@ -1,6 +1,6 @@ from pydantic import BaseModel -from app.common.base_models.base_dtos.base_response import BaseResponseDTO +from app.dtos.base_response import BaseResponseDTO class EmotionDTO(BaseModel): diff --git a/app/dtos/frozen_config.py b/app/dtos/frozen_config.py new file mode 100644 index 0000000..cebe046 --- /dev/null +++ b/app/dtos/frozen_config.py @@ -0,0 +1,3 @@ +from pydantic import ConfigDict + +FROZEN_CONFIG = ConfigDict(frozen=True) diff --git a/app/dtos/mission/reward_dto.py b/app/dtos/mission/reward_dto.py index 008accb..ebe3bc3 100644 --- a/app/dtos/mission/reward_dto.py +++ b/app/dtos/mission/reward_dto.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict class RewardDTO(BaseModel): @@ -9,8 +9,7 @@ class RewardDTO(BaseModel): badge_code: Optional[str] = None badge_full_name: Optional[str] = None - class META: - orm_mode = True + model_config = ConfigDict(from_attributes=True) @classmethod async def build( diff --git a/app/dtos/mobile/mypage_response.py b/app/dtos/mobile/mypage_response.py index ebe8316..e3dd6e2 100644 --- a/app/dtos/mobile/mypage_response.py +++ b/app/dtos/mobile/mypage_response.py @@ -1,8 +1,8 @@ from pydantic import BaseModel +from app.dtos.base_response import BaseResponseDTO from app.dtos.level.level_dto import LevelInfoDTO from app.dtos.user.user_profile_dto import UserProfileDTO -from app.common.base_models.base_dtos.base_response import BaseResponseDTO class UserProfileWithLevel(BaseModel): diff --git a/app/dtos/mobile/teller_card_response.py b/app/dtos/mobile/teller_card_response.py index 0389cf1..7604dde 100644 --- a/app/dtos/mobile/teller_card_response.py +++ b/app/dtos/mobile/teller_card_response.py @@ -2,11 +2,11 @@ from pydantic import BaseModel +from app.dtos.base_response import BaseResponseDTO from app.dtos.badge.badge_dto import BadgeDTO from app.dtos.color.color_dto import ColorDTO from app.dtos.level.level_dto import LevelInfoDTO from app.dtos.user.user_info_dto import UserInfoDTO -from app.common.base_models.base_dtos.base_response import BaseResponseDTO class DataDTO(BaseModel): diff --git a/app/dtos/payment/response.py b/app/dtos/payment/response.py index 711cddd..04b1e18 100644 --- a/app/dtos/payment/response.py +++ b/app/dtos/payment/response.py @@ -1,6 +1,6 @@ from pydantic import BaseModel -from app.common.base_models.base_dtos.base_response import BaseResponseDTO +from app.dtos.base_response import BaseResponseDTO class ProductDTO(BaseModel): diff --git a/app/dtos/teller_card/response.py b/app/dtos/teller_card/response.py index 67f509e..677e98d 100644 --- a/app/dtos/teller_card/response.py +++ b/app/dtos/teller_card/response.py @@ -1,7 +1,7 @@ from pydantic import BaseModel +from app.dtos.base_response import BaseResponseDTO from app.dtos.teller_card.teller_card_dto import TellerCardDTO as TellerCardLogicDTO -from app.common.base_models.base_dtos.base_response import BaseResponseDTO class TellerCardDTO(BaseModel): diff --git a/app/dtos/user/user_info_dto.py b/app/dtos/user/user_info_dto.py index e9400ba..f0ada54 100644 --- a/app/dtos/user/user_info_dto.py +++ b/app/dtos/user/user_info_dto.py @@ -1,6 +1,6 @@ from pydantic import BaseModel -from app.dtos.teller_card.response import TellerCardDTO +from app.dtos.teller_card.teller_card_dto import TellerCardDTO class UserInfoDTO(BaseModel): diff --git a/app/models/answer.py b/app/models/answer.py index 8970544..2c41c2d 100644 --- a/app/models/answer.py +++ b/app/models/answer.py @@ -5,18 +5,18 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model +from app.common.utils.query_executor import QueryExecutor +from app.models.user import User from app.queries.answer_query import ( SELECT_ANSWER_BY_USER_UUID_QUERY, SELECT_ANSWER_COUNT_BY_USER_UUID_QUERY, SELECT_ANSWER_COUNT_BY_USER_UUID_QUERY_V2, SELECT_MOST_RECENT_ANSWER_BY_USER_UUID_QUERY, ) -from app.models.user import User -from app.common.utils.query_executor import QueryExecutor class Answer(Model): - answer_id = fields.BigIntField(pk=True) + answer_id = fields.BigIntField(primary_key=True) content = fields.TextField(null=False) created_time = fields.DatetimeField(null=True) date = fields.DateField(null=False) diff --git a/app/models/badge.py b/app/models/badge.py index a4378cf..6467ce5 100644 --- a/app/models/badge.py +++ b/app/models/badge.py @@ -4,18 +4,18 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model +from app.common.utils.query_executor import QueryExecutor +from app.models.user import User from app.queries.badge_query import ( INSERT_BADGE_CODE_FOR_USER_QUERY, SELECT_BADGE_BY_USER_UUID_QUERY, SELECT_BADGE_CODE_BY_USER_UUID_QUERY, SELECT_BADGE_COUNT_BY_USER_UUID_QUERY, ) -from app.models.user import User -from app.common.utils.query_executor import QueryExecutor class Badge(Model): - badge_id = fields.BigIntField(pk=True) + badge_id = fields.BigIntField(primary_key=True) badge_code = fields.CharField(max_length=255) user: ForeignKeyRelation[User] = fields.ForeignKeyField("models.User", related_name="badges") diff --git a/app/models/cheese_manager.py b/app/models/cheese_manager.py index 134e0fd..3cfd574 100644 --- a/app/models/cheese_manager.py +++ b/app/models/cheese_manager.py @@ -10,7 +10,7 @@ class CheeseManager(Model): - cheese_manager_id = fields.BigIntField(pk=True) # BIGINT auto_increment equivalent + cheese_manager_id = fields.BigIntField(primary_key=True) # BIGINT auto_increment equivalent class Meta: table = "cheese_manager" # Database table name @@ -82,7 +82,7 @@ async def add_cheese(cheese_manager_id: int, amount: int) -> None: class CheeseHistory(Model): - cheese_history_id = fields.BigIntField(pk=True) + cheese_history_id = fields.BigIntField(primary_key=True) status = fields.CharEnumField(CheeseStatus, max_length=50, null=True) # Enum Field current_amount = fields.IntField() starting_amount = fields.IntField() diff --git a/app/models/color.py b/app/models/color.py index 6834172..9934755 100644 --- a/app/models/color.py +++ b/app/models/color.py @@ -4,17 +4,17 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model +from app.common.utils.query_executor import QueryExecutor +from app.models.user import User from app.queries.color_query import ( INSERT_COLOR_CODE_FOR_USER_QUERY, SELECT_COLOR_BY_USER_UUID_QUERY, SELECT_COLOR_CODE_BY_USER_UUID_QUERY, ) -from app.models.user import User -from app.common.utils.query_executor import QueryExecutor class Color(Model): - color_id = fields.BigIntField(pk=True) + color_id = fields.BigIntField(primary_key=True) color_code = fields.CharField(max_length=255, null=True) user: ForeignKeyRelation[User] = fields.ForeignKeyField( "models.User", related_name="colors", on_delete=fields.CASCADE diff --git a/app/models/emotion.py b/app/models/emotion.py index 18b7b0e..2f032d0 100644 --- a/app/models/emotion.py +++ b/app/models/emotion.py @@ -3,16 +3,16 @@ from tortoise import fields, models from tortoise.fields import ForeignKeyRelation +from app.common.utils.query_executor import QueryExecutor +from app.models.user import User from app.queries.emotion_query import ( INSERT_EMOTION_CODE_FOR_USER_QUERY, SELECT_EMOTION_CODE_BY_USER_UUID_QUERY, ) -from app.models.user import User -from app.common.utils.query_executor import QueryExecutor class Emotion(models.Model): - emotion_id = fields.BigIntField(pk=True) + emotion_id = fields.BigIntField(primary_key=True) emotion_code = fields.CharField(max_length=255, unique=True) user: ForeignKeyRelation[User] = fields.ForeignKeyField("models.User", related_name="emotions") @@ -33,7 +33,7 @@ async def add_emotion(cls, user_id: str, emotion_code: str) -> None: class EmotionInventory(models.Model): - emotion_inventory_id = fields.BigIntField(pk=True) + emotion_inventory_id = fields.BigIntField(primary_key=True) emotion_code = fields.CharField(max_length=255, unique=True) emotion_name = fields.CharField(max_length=255) diff --git a/app/models/item.py b/app/models/item.py index 5717827..1a14285 100644 --- a/app/models/item.py +++ b/app/models/item.py @@ -3,7 +3,7 @@ class ItemInventory(models.Model): - item_id = fields.BigIntField(pk=True) + item_id = fields.BigIntField(primary_key=True) item_category = fields.CharField(max_length=255, null=True) item_code = fields.CharField(max_length=255, null=True) @@ -12,7 +12,7 @@ class Meta: class ProductInventory(models.Model): - product_id = fields.BigIntField(pk=True) + product_id = fields.BigIntField(primary_key=True) price = fields.FloatField(null=True) product_category = fields.CharField(max_length=255, null=True) product_code = fields.CharField(max_length=255, null=True) @@ -23,7 +23,7 @@ class Meta: class ItemInventoryProductInventory(models.Model): - item_inventory_product_inventory_id = fields.BigIntField(pk=True) + item_inventory_product_inventory_id = fields.BigIntField(primary_key=True) quantity = fields.IntField() item_inventory: ForeignKeyRelation[ItemInventory] = fields.ForeignKeyField( "models.ItemInventory", related_name="product_inventories" @@ -38,7 +38,7 @@ class Meta: class RewardInventory(models.Model): - reward_inventory_id = fields.BigIntField(pk=True) + reward_inventory_id = fields.BigIntField(primary_key=True) item_code = fields.CharField(max_length=255, null=True) reward_code = fields.CharField(max_length=255, null=True) reward_description = fields.CharField(max_length=255, null=True) @@ -51,7 +51,7 @@ class Meta: class ItemInventoryRewardInventory(models.Model): - item_inventory_reward_invnetory_id = fields.BigIntField(pk=True) + item_inventory_reward_invnetory_id = fields.BigIntField(primary_key=True) quantity = fields.IntField() item_inventory: ForeignKeyRelation[ItemInventory] = fields.ForeignKeyField( "models.ItemInventory", diff --git a/app/models/level.py b/app/models/level.py index 30806ad..ba6ff59 100644 --- a/app/models/level.py +++ b/app/models/level.py @@ -3,15 +3,15 @@ from tortoise import fields from tortoise.models import Model +from app.common.utils.query_executor import QueryExecutor from app.queries.level_query import ( SELECT_USER_LEVEL_AND_REQUIRED_EXP_QUERY, UPDATE_USER_LEVEL_AND_EXP_QUERY, ) -from app.common.utils.query_executor import QueryExecutor class Level(Model): - level_id = fields.BigIntField(pk=True) + level_id = fields.BigIntField(primary_key=True) user_exp = fields.IntField() user_level = fields.IntField() @@ -32,6 +32,6 @@ async def update_level_and_exp(cls, user_id: str, new_level: int, new_exp: int) class LevelInventory(Model): - level_inventory_id = fields.BigIntField(pk=True) + level_inventory_id = fields.BigIntField(primary_key=True) level = fields.IntField(null=True) required_exp = fields.IntField(null=True) diff --git a/app/models/like.py b/app/models/like.py index b3441dd..a182445 100644 --- a/app/models/like.py +++ b/app/models/like.py @@ -4,14 +4,14 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model +from app.common.utils.query_executor import QueryExecutor from app.models.answer import Answer -from app.queries.like_query import SELECT_UNIQUE_LIKES_COUNT_BY_USER_TODAY_QUERY from app.models.user import User -from app.common.utils.query_executor import QueryExecutor +from app.queries.like_query import SELECT_UNIQUE_LIKES_COUNT_BY_USER_TODAY_QUERY class Like(Model): - likes_id = fields.BigIntField(pk=True) + likes_id = fields.BigIntField(primary_key=True) answer: ForeignKeyRelation[Answer] = fields.ForeignKeyField( "models.Answer", related_name="likes", on_delete=fields.CASCADE ) diff --git a/app/models/mission.py b/app/models/mission.py index 05af298..13cfd98 100644 --- a/app/models/mission.py +++ b/app/models/mission.py @@ -4,13 +4,13 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model -from app.queries.mission_query import SELECT_USER_MISSIONS_QUERY, UPDATE_USER_MISSION_PROGRESS_QUERY -from app.models.user import User from app.common.utils.query_executor import QueryExecutor +from app.models.user import User +from app.queries.mission_query import SELECT_USER_MISSIONS_QUERY, UPDATE_USER_MISSION_PROGRESS_QUERY class UserMission(Model): - user_mission_id = fields.BigIntField(pk=True) + user_mission_id = fields.BigIntField(primary_key=True) is_completed = fields.BooleanField(default=False) mission_code = fields.CharField(max_length=255) progress_count = fields.IntField(default=0) @@ -39,7 +39,7 @@ async def update_user_mission_progress( class MissionInventory(Model): - mission_inventory_id = fields.BigIntField(pk=True) + mission_inventory_id = fields.BigIntField(primary_key=True) condition_type = fields.CharField(max_length=255) mission_code = fields.CharField(max_length=255) mission_description = fields.CharField(max_length=255) diff --git a/app/models/notice.py b/app/models/notice.py index ae12edd..2bec5a4 100644 --- a/app/models/notice.py +++ b/app/models/notice.py @@ -4,12 +4,12 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model -from app.models.user import User from app.common.utils.query_executor import QueryExecutor +from app.models.user import User class Notice(Model): - notice_id = fields.BigIntField(pk=True) + notice_id = fields.BigIntField(primary_key=True) title = fields.CharField(max_length=255, null=False) content = fields.TextField(null=True) is_read = fields.BooleanField(default=False) diff --git a/app/models/purchase_history.py b/app/models/purchase_history.py index f20fd70..1a1422f 100644 --- a/app/models/purchase_history.py +++ b/app/models/purchase_history.py @@ -5,12 +5,12 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model -from app.models.user import User from app.common.utils.query_executor import QueryExecutor +from app.models.user import User class Subscription(Model): - subscription_id = fields.BigIntField(pk=True, description="Primary key for the Subscription") + subscription_id = fields.BigIntField(primary_key=True, description="Primary key for the Subscription") product_code = fields.CharField(max_length=255, null=False, description="Product code of the subscription") status = fields.CharField(max_length=255, null=False, description="Status of the subscription") current_transaction_id = fields.CharField(max_length=255, null=False, description="Current transaction ID") @@ -93,7 +93,7 @@ async def update_subscription( class PurchaseHistory(Model): - purchase_history_id = fields.BigIntField(pk=True, description="Primary key for the Purchase History") + purchase_history_id = fields.BigIntField(primary_key=True, description="Primary key for the Purchase History") product_code = fields.CharField(max_length=255, null=False, description="Product code of the purchase") transaction_id = fields.CharField(max_length=255, unique=True, null=False, description="Transaction ID") original_transaction_id = fields.CharField(max_length=255, null=True, description="Original transaction ID") diff --git a/app/models/question.py b/app/models/question.py index f3571d5..b446a9c 100644 --- a/app/models/question.py +++ b/app/models/question.py @@ -3,7 +3,7 @@ class Question(Model): - date = fields.DateField(pk=True) # 기본 키로 설정된 날짜 필드 + date = fields.DateField(primary_key=True) # 기본 키로 설정된 날짜 필드 phrase = fields.CharField(max_length=255) title = fields.CharField(max_length=255) spare_phrase = fields.CharField(max_length=255) diff --git a/app/models/refresh_token.py b/app/models/refresh_token.py index 15d5e03..f312c69 100644 --- a/app/models/refresh_token.py +++ b/app/models/refresh_token.py @@ -3,7 +3,7 @@ class RefreshToken(Model): - refresh_token_id = fields.BigIntField(pk=True) + refresh_token_id = fields.BigIntField(primary_key=True) access_token = fields.CharField(max_length=255) refresh_token = fields.CharField(max_length=255) user_id = fields.BinaryField(max_length=16) # foreign key로 사용되지는 않지만 binary field로 선언 diff --git a/app/models/teller_card.py b/app/models/teller_card.py index 9e14049..bae12d9 100644 --- a/app/models/teller_card.py +++ b/app/models/teller_card.py @@ -3,15 +3,15 @@ from tortoise import fields from tortoise.models import Model +from app.common.utils.query_executor import QueryExecutor from app.queries.teller_card_query import ( PATCH_TELLER_CARD_QUERY, SELECT_TELLER_CARD_INFO_BY_USER_UUID_QUERY, ) -from app.common.utils.query_executor import QueryExecutor class TellerCard(Model): - teller_card_id = fields.BigIntField(pk=True) + teller_card_id = fields.BigIntField(primary_key=True) activate_badge_code = fields.CharField(max_length=255, null=True) activate_color_code = fields.CharField(max_length=255, null=True) diff --git a/app/models/user.py b/app/models/user.py index 6974193..15415c2 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -6,20 +6,20 @@ from tortoise.fields import ForeignKeyRelation from tortoise.models import Model +from app.common.utils.query_executor import QueryExecutor from app.models.cheese_manager import CheeseManager from app.models.level import Level -from app.models.teller_card import TellerCard from app.models.refresh_token import RefreshToken +from app.models.teller_card import TellerCard from app.queries.user_query import ( SELECT_USER_INFO_BY_USER_UUID_QUERY, SELECT_USER_PROFILE_BY_USER_ID_QUERY, UPDATE_PREMIUM_STATUS_QUERY, ) -from app.common.utils.query_executor import QueryExecutor class User(Model): - user_id = fields.CharField(max_length=255, pk=True, description="Primary key for the User") + user_id = fields.CharField(max_length=255, primary_key=True, description="Primary key for the User") allow_notification = fields.BooleanField(null=True) birth_date = fields.CharField(max_length=8, null=True) created_time = fields.DatetimeField(auto_now_add=True) diff --git a/app/services/answer_service.py b/app/services/answer_service.py index 0ed5eff..9746ebd 100644 --- a/app/services/answer_service.py +++ b/app/services/answer_service.py @@ -1,7 +1,6 @@ from datetime import datetime, timedelta from typing import Any - -import pytz +from zoneinfo import ZoneInfo from app.models.answer import Answer @@ -30,7 +29,7 @@ async def get_answer_count_v2(cls, user_id: str) -> int: @classmethod async def get_answer_record(cls, user_id: str) -> int: - seoul_tz = pytz.timezone("Asia/Seoul") + seoul_tz = ZoneInfo("Asia/Seoul") now = datetime.now(seoul_tz) if now.hour < 6: diff --git a/app/services/level_service.py b/app/services/level_service.py index ce7e56e..8652ab5 100644 --- a/app/services/level_service.py +++ b/app/services/level_service.py @@ -1,8 +1,8 @@ from fastapi import HTTPException -from app.services.answer_service import AnswerService from app.dtos.level.level_dto import LevelDTO, LevelInfoDTO from app.models.level import Level +from app.services.answer_service import AnswerService class LevelService: diff --git a/app/services/mission_service.py b/app/services/mission_service.py index f29d60b..bfe0867 100644 --- a/app/services/mission_service.py +++ b/app/services/mission_service.py @@ -1,22 +1,22 @@ import asyncio from datetime import datetime, timedelta, timezone from typing import Optional +from zoneinfo import ZoneInfo -import pytz from fastapi import HTTPException from tortoise.exceptions import DoesNotExist from tortoise.transactions import atomic +from app.dtos.mission.mission_dto import UserMissionDTO +from app.dtos.mission.reward_dto import RewardDTO +from app.models.item import ItemInventory, ItemInventoryRewardInventory, RewardInventory +from app.models.like import Like +from app.models.mission import MissionInventory, UserMission from app.services.answer_service import AnswerService from app.services.badge_service import BadgeService from app.services.cheese_service import CheeseService from app.services.color_service import ColorService -from app.models.item import ItemInventory, ItemInventoryRewardInventory, RewardInventory from app.services.level_service import LevelService -from app.models.like import Like -from app.dtos.mission.mission_dto import UserMissionDTO -from app.dtos.mission.reward_dto import RewardDTO -from app.models.mission import MissionInventory, UserMission from app.services.notice_service import NoticeService from app.services.user_service import UserService @@ -201,7 +201,7 @@ async def check_three_likes_different_posts(user_id: str) -> bool: @staticmethod async def check_daily_post(user_id: str) -> bool: - seoul_tz = pytz.timezone("Asia/Seoul") + seoul_tz = ZoneInfo("Asia/Seoul") now = datetime.now(seoul_tz) current_date = (now - timedelta(days=1)).date() if now.hour < 6 else now.date() diff --git a/app/services/payment_service.py b/app/services/payment_service.py index ddc80f3..ae38b3b 100644 --- a/app/services/payment_service.py +++ b/app/services/payment_service.py @@ -1,13 +1,13 @@ from tortoise.exceptions import DoesNotExist, IntegrityError from tortoise.transactions import atomic -from app.services.badge_service import BadgeService +from app.common.exceptions.custom_exception import CustomException +from app.common.exceptions.error_code import ErrorCode from app.models.cheese_manager import CheeseManager +from app.models.item import ItemInventory, ItemInventoryProductInventory, ProductInventory +from app.services.badge_service import BadgeService from app.services.color_service import ColorService from app.services.emotion_service import EmotionService -from app.models.item import ItemInventory, ItemInventoryProductInventory, ProductInventory -from app.common.exceptions.custom_exception import CustomException -from app.common.exceptions.error_code import ErrorCode class PaymentService: diff --git a/app/services/purchase_service.py b/app/services/purchase_service.py index 0ea51a3..579d3b9 100644 --- a/app/services/purchase_service.py +++ b/app/services/purchase_service.py @@ -8,15 +8,15 @@ from tortoise.exceptions import DoesNotExist from tortoise.transactions import atomic -from app.models.item import ItemInventory, ItemInventoryProductInventory, ProductInventory +from app.common.exceptions.custom_exception import CustomException +from app.common.exceptions.error_code import ErrorCode +from app.core.configs import settings from app.dtos.purchase.purchase_dto import PurchaseResponseDTO, ReceiptInfoDTO +from app.models.item import ItemInventory, ItemInventoryProductInventory, ProductInventory from app.models.purchase_history import PurchaseHistory, Subscription from app.models.purchase_status import PurchaseStatus, SubscriptionStatus from app.models.user import User from app.services.user_service import UserService -from app.common.exceptions.custom_exception import CustomException -from app.common.exceptions.error_code import ErrorCode -from app.core.configs import settings class PurchaseService: diff --git a/app/services/teller_card_service.py b/app/services/teller_card_service.py index 792a234..d5a4e32 100644 --- a/app/services/teller_card_service.py +++ b/app/services/teller_card_service.py @@ -1,8 +1,8 @@ from typing import Optional +from app.dtos.teller_card.teller_card_dto import TellerCardDTO from app.models.badge import BadgeInventory from app.models.color import ColorInventory -from app.dtos.teller_card.teller_card_dto import TellerCardDTO from app.models.teller_card import TellerCard diff --git a/app/tests/fixtures.py b/app/tests/fixtures.py new file mode 100644 index 0000000..be96819 --- /dev/null +++ b/app/tests/fixtures.py @@ -0,0 +1,33 @@ +from typing import Any +from unittest.mock import Mock, patch + +import pytest +from pytest import FixtureRequest +from tortoise.backends.base.config_generator import generate_config +from tortoise.contrib.test import finalizer, initializer + +from app.core.configs import settings +from app.core.database.tortoise_database_settings import TORTOISE_APP_MODELS + +TEST_BASE_URL = "http://test" +TEST_DB_LABEL = "models" +TEST_DB_TZ = "Asia/Seoul" + + +def get_test_db_config() -> Any: + config = generate_config( + db_url=f"mysql://{settings.DB_USER}:{settings.DB_PASSWORD}@{settings.DB_HOST}:{settings.DB_PORT}/test", + app_modules={TEST_DB_LABEL: TORTOISE_APP_MODELS}, + connection_label=TEST_DB_LABEL, + testing=True, + ) + config["timezone"] = TEST_DB_TZ + + return config + + +@pytest.fixture(scope="session", autouse=True) +def initialize(request: FixtureRequest) -> None: + with patch("tortoise.contrib.test.getDBConfig", Mock(return_value=get_test_db_config())): + initializer(modules=TORTOISE_APP_MODELS) + request.addfinalizer(finalizer) diff --git a/app/tests/test_simple.py b/app/tests/test_simple.py new file mode 100644 index 0000000..b9df410 --- /dev/null +++ b/app/tests/test_simple.py @@ -0,0 +1,2 @@ +def test_simple() -> None: + assert True diff --git a/asgi.py b/asgi.py new file mode 100644 index 0000000..3919967 --- /dev/null +++ b/asgi.py @@ -0,0 +1,6 @@ +from app import app + +if __name__ == "__main__": + import uvicorn + + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..89ecd25 --- /dev/null +++ b/conftest.py @@ -0,0 +1,22 @@ +from typing import Any + +import pytest +from pytest_asyncio import is_async_test + +pytest_plugins = ("app.tests.fixtures",) + + +def pytest_collection_modifyitems(items: Any) -> None: + """ + https://pytest-asyncio.readthedocs.io/en/stable/how-to-guides/run_session_tests_in_same_loop.html + pyproject.toml 의 + [tool.pytest.ini_options] + asyncio_default_fixture_loop_scope = "session" + + 설정은 fixture 의 scope 만 변경한다...! 이 설정만 하면 다른 모든 test 함수는 session scope 의 loop 를 쓰지 않는다. + 이 함수야말로 모든 테스트가 같은 루프를 쓰게 해준다. (아니 왜 이렇게 만들었나...) + """ + pytest_asyncio_tests = (item for item in items if is_async_test(item)) + session_scope_marker = pytest.mark.asyncio(loop_scope="session") + for async_test in pytest_asyncio_tests: + async_test.add_marker(session_scope_marker, append=False) From cadcaf226c8b807804f05fdaf1d757a2ca827812 Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Tue, 3 Jun 2025 15:26:09 +0900 Subject: [PATCH 05/27] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20badge=20?= =?UTF-8?q?router=20DTO=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/apis/v2/badge_router.py | 8 ++++---- app/dtos/badge/badge_code_dto.py | 11 +++++++++++ app/dtos/badge/badge_data.py | 9 +++++++++ app/dtos/badge/badge_dto.py | 17 ----------------- .../badge/{response.py => badges_response.py} | 2 +- app/models/badge.py | 6 ++++-- app/services/badge_service.py | 15 ++++++++++++--- 7 files changed, 41 insertions(+), 27 deletions(-) create mode 100644 app/dtos/badge/badge_code_dto.py create mode 100644 app/dtos/badge/badge_data.py rename app/dtos/badge/{response.py => badges_response.py} (82%) diff --git a/app/apis/v2/badge_router.py b/app/apis/v2/badge_router.py index 0a56ca9..9e76bd1 100644 --- a/app/apis/v2/badge_router.py +++ b/app/apis/v2/badge_router.py @@ -1,6 +1,6 @@ from fastapi import APIRouter, status -from app.dtos.badge.response import BadgeListResponseDTO +from app.dtos.badge.badges_response import BadgesResponse from app.services.badge_service import BadgeService badge_router = APIRouter(prefix="/user/badge", tags=["Badge"]) @@ -8,12 +8,12 @@ @badge_router.get( "", - response_model=BadgeListResponseDTO, + response_model=BadgesResponse, status_code=status.HTTP_200_OK, ) -async def get_user_badge_handler(user_id: str) -> BadgeListResponseDTO: +async def get_user_badge_handler(user_id: str) -> BadgesResponse: badges = await BadgeService.get_badges_with_details_by_user_id(user_id) - return BadgeListResponseDTO( + return BadgesResponse( code=status.HTTP_200_OK, message="보유 뱃지 정보 조회", data=badges, diff --git a/app/dtos/badge/badge_code_dto.py b/app/dtos/badge/badge_code_dto.py new file mode 100644 index 0000000..2c98edb --- /dev/null +++ b/app/dtos/badge/badge_code_dto.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from pydantic import BaseModel + + +class BadgeCodeDTO(BaseModel): + badgeCode: str + + @classmethod + def builder(cls, badge_raw: dict[str, str]) -> BadgeCodeDTO: + return cls(badgeCode=badge_raw.get("badge_code", "")) diff --git a/app/dtos/badge/badge_data.py b/app/dtos/badge/badge_data.py new file mode 100644 index 0000000..5731095 --- /dev/null +++ b/app/dtos/badge/badge_data.py @@ -0,0 +1,9 @@ +import dataclasses + + +@dataclasses.dataclass(frozen=True) +class BadgeData: + badge_code: str + badge_name: str + badge_condition: str + badge_middle_name: str diff --git a/app/dtos/badge/badge_dto.py b/app/dtos/badge/badge_dto.py index 2bbb4bc..6634af7 100644 --- a/app/dtos/badge/badge_dto.py +++ b/app/dtos/badge/badge_dto.py @@ -3,25 +3,8 @@ from pydantic import BaseModel -class BadgeCodeDTO(BaseModel): - badgeCode: str - - @classmethod - def builder(cls, badge_raw: dict[str, str]) -> BadgeCodeDTO: - return cls(badgeCode=badge_raw.get("badge_code", "")) - - class BadgeDTO(BaseModel): badgeCode: str badgeName: str badgeMiddleName: str badgeCondition: str - - @classmethod - def builder(cls, badge_raw: dict[str, str]) -> "BadgeDTO": - return cls( - badgeCode=badge_raw.get("badge_code", ""), - badgeName=badge_raw.get("badge_name", ""), - badgeMiddleName=badge_raw.get("badge_middle_name", ""), - badgeCondition=badge_raw.get("badge_condition", ""), - ) diff --git a/app/dtos/badge/response.py b/app/dtos/badge/badges_response.py similarity index 82% rename from app/dtos/badge/response.py rename to app/dtos/badge/badges_response.py index d2704d9..ccea426 100644 --- a/app/dtos/badge/response.py +++ b/app/dtos/badge/badges_response.py @@ -3,7 +3,7 @@ from app.dtos.frozen_config import FROZEN_CONFIG -class BadgeListResponseDTO(BaseResponseDTO): +class BadgesResponse(BaseResponseDTO): model_config = FROZEN_CONFIG data: list[BadgeDTO] diff --git a/app/models/badge.py b/app/models/badge.py index 6467ce5..1b0be24 100644 --- a/app/models/badge.py +++ b/app/models/badge.py @@ -5,6 +5,7 @@ from tortoise.models import Model from app.common.utils.query_executor import QueryExecutor +from app.dtos.badge.badge_data import BadgeData from app.models.user import User from app.queries.badge_query import ( INSERT_BADGE_CODE_FOR_USER_QUERY, @@ -29,10 +30,11 @@ async def get_badge_count_by_user_id(cls, user_id: str) -> Any: return await QueryExecutor.execute_query(query, values=value, fetch_type="single") @classmethod - async def get_badges_with_details_by_user_id(cls, user_id: str) -> Any: + async def get_badges_with_details_by_user_id(cls, user_id: str) -> list[BadgeData]: query = SELECT_BADGE_BY_USER_UUID_QUERY value = user_id - return await QueryExecutor.execute_query(query, values=value, fetch_type="multiple") + result = await QueryExecutor.execute_query(query, values=value, fetch_type="multiple") + return [BadgeData(**row) for row in result] @classmethod async def get_badge_codes_by_user_id(cls, user_id: str) -> Any: diff --git a/app/services/badge_service.py b/app/services/badge_service.py index 6c11ca3..f211b1a 100644 --- a/app/services/badge_service.py +++ b/app/services/badge_service.py @@ -1,4 +1,5 @@ -from app.dtos.badge.badge_dto import BadgeCodeDTO, BadgeDTO +from app.dtos.badge.badge_dto import BadgeDTO +from app.dtos.badge.badge_code_dto import BadgeCodeDTO from app.models.badge import Badge, BadgeInventory @@ -14,8 +15,16 @@ async def add_badge(cls, user_id: str, badge_code: str) -> None: @classmethod async def get_badges_with_details_by_user_id(cls, user_id: str) -> list[BadgeDTO]: - badges_raw = await Badge.get_badges_with_details_by_user_id(user_id=user_id) - return [BadgeDTO.builder(badge) for badge in badges_raw] + badges = await Badge.get_badges_with_details_by_user_id(user_id=user_id) + return [ + BadgeDTO( + badgeCode=badge.badge_code, + badgeName=badge.badge_name, + badgeCondition=badge.badge_condition, + badgeMiddleName=badge.badge_middle_name, + ) + for badge in badges + ] @classmethod async def get_badge_count(cls, user_id: str) -> int: From 100e3b2fde89e3d6af387687e28efbe8ff7d3de6 Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Tue, 3 Jun 2025 15:45:08 +0900 Subject: [PATCH 06/27] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20cheese?= =?UTF-8?q?=20router=20DTO=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/apis/v2/cheese_router.py | 17 +++++++++-------- app/apis/v2/mobile_router.py | 4 ++-- app/dtos/cheese/cheese_dto.py | 29 ----------------------------- app/dtos/cheese/cheese_response.py | 11 +++++++++++ app/dtos/user/user_data.py | 7 +++++++ app/services/mission_service.py | 2 +- app/services/user_service.py | 6 ++++-- 7 files changed, 34 insertions(+), 42 deletions(-) delete mode 100644 app/dtos/cheese/cheese_dto.py create mode 100644 app/dtos/cheese/cheese_response.py create mode 100644 app/dtos/user/user_data.py diff --git a/app/apis/v2/cheese_router.py b/app/apis/v2/cheese_router.py index a99a620..8ff78d0 100644 --- a/app/apis/v2/cheese_router.py +++ b/app/apis/v2/cheese_router.py @@ -1,17 +1,18 @@ from fastapi import APIRouter, status -from app.dtos.cheese.cheese_dto import CheeseResponseDTO +from app.dtos.cheese.cheese_response import CheeseResponse, TotalCheeseAmount from app.services.cheese_service import CheeseService from app.services.user_service import UserService cheese_router = APIRouter(prefix="/cheese", tags=["Cheese"]) -@cheese_router.get("", response_model=CheeseResponseDTO, status_code=status.HTTP_200_OK) -async def get_cheese_handler(user_id: str) -> CheeseResponseDTO: - +@cheese_router.get("", response_model=CheeseResponse, status_code=status.HTTP_200_OK) +async def get_cheese_handler(user_id: str) -> CheeseResponse: user = await UserService.get_user_info(user_id=user_id) - cheese_amount = await CheeseService.get_cheese_balance(user["cheese_manager_id"]) - print(cheese_amount) - - return CheeseResponseDTO.builder(cheese_balance=cheese_amount) + cheese_amount = await CheeseService.get_cheese_balance(user.cheese_manager_id) + return CheeseResponse( + code=status.HTTP_200_OK, + message="총 치즈 갯수 조회", + data=TotalCheeseAmount(cheeseBalance=cheese_amount), + ) diff --git a/app/apis/v2/mobile_router.py b/app/apis/v2/mobile_router.py index 1451559..570a166 100644 --- a/app/apis/v2/mobile_router.py +++ b/app/apis/v2/mobile_router.py @@ -35,9 +35,9 @@ async def mobile_teller_card_handler(user_id: str) -> TellerCardResponseDTO: badges_task, colors_task, level_info_task, teller_card_task, user_info_task, record_answer_task ) - cheese_amount = await CheeseService.get_cheese_balance(user_raw["cheese_manager_id"]) + cheese_amount = await CheeseService.get_cheese_balance(user_raw.cheese_manager_id) - user_info = UserInfoDTO.builder(user_raw, cheeseBalance=cheese_amount, tellerCard=teller_card) + user_info = UserInfoDTO(nickname=user_raw.nickname, cheeseBalance=cheese_amount, tellerCard=teller_card) data = DataDTO.builder( badges=badges, colors=colors, userInfo=user_info, levelInfo=level_info, recordCount=record_count diff --git a/app/dtos/cheese/cheese_dto.py b/app/dtos/cheese/cheese_dto.py deleted file mode 100644 index c83748b..0000000 --- a/app/dtos/cheese/cheese_dto.py +++ /dev/null @@ -1,29 +0,0 @@ -from typing import Optional, TypedDict - -from pydantic import BaseModel - -from app.dtos.base_response import BaseResponseDTO - - -class CheeseAmountResult(TypedDict): - total_cheese_amount: Optional[int] - - -class CheeseDTO(BaseModel): - cheeseBalance: int - - @classmethod - def builder(cls, cheese_balance: int) -> "CheeseDTO": - return cls(cheeseBalance=cheese_balance) - - -class CheeseResponseDTO(BaseResponseDTO): - data: CheeseDTO - - @classmethod - def builder(cls, cheese_balance: int) -> "CheeseResponseDTO": - return cls( - code=200, - message="success", - data=CheeseDTO.builder(cheese_balance=cheese_balance), - ) diff --git a/app/dtos/cheese/cheese_response.py b/app/dtos/cheese/cheese_response.py new file mode 100644 index 0000000..178d52e --- /dev/null +++ b/app/dtos/cheese/cheese_response.py @@ -0,0 +1,11 @@ +from pydantic import BaseModel + +from app.dtos.base_response import BaseResponseDTO + + +class TotalCheeseAmount(BaseModel): + cheeseBalance: int + + +class CheeseResponse(BaseResponseDTO): + data: TotalCheeseAmount diff --git a/app/dtos/user/user_data.py b/app/dtos/user/user_data.py new file mode 100644 index 0000000..f247983 --- /dev/null +++ b/app/dtos/user/user_data.py @@ -0,0 +1,7 @@ +import dataclasses + + +@dataclasses.dataclass(frozen=True) +class UserData: + nickname: str + cheese_manager_id: int diff --git a/app/services/mission_service.py b/app/services/mission_service.py index bfe0867..c164404 100644 --- a/app/services/mission_service.py +++ b/app/services/mission_service.py @@ -179,7 +179,7 @@ async def check_early_morning_posts(user_id: str) -> bool: @staticmethod async def check_cheese_total(user_id: str) -> bool: user = await UserService.get_user_info(user_id=user_id) - cheese_amount = await CheeseService.get_cheese_balance(user["cheese_manager_id"]) + cheese_amount = await CheeseService.get_cheese_balance(user.cheese_manager_id) return cheese_amount >= 50 diff --git a/app/services/user_service.py b/app/services/user_service.py index 7f87020..0fdf50e 100644 --- a/app/services/user_service.py +++ b/app/services/user_service.py @@ -1,13 +1,15 @@ from typing import Any +from app.dtos.user.user_data import UserData from app.dtos.user.user_dto import UserDTO from app.models.user import User class UserService: @staticmethod - async def get_user_info(user_id: str) -> Any: - return await User.get_user_info_by_user_id(user_id=user_id) + async def get_user_info(user_id: str) -> UserData: + result = await User.get_user_info_by_user_id(user_id=user_id) + return UserData(**result) @classmethod async def get_user_profile(cls, user_id: str) -> UserDTO: From cea8f7b2506a19e6c2213c579b153f05fbd03627 Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Tue, 3 Jun 2025 16:16:47 +0900 Subject: [PATCH 07/27] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20color=20?= =?UTF-8?q?router=20DTO=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/apis/v2/badge_router.py | 2 +- app/apis/v2/cheese_router.py | 2 +- app/apis/v2/color_router.py | 10 +++---- .../database/tortoise_database_settings.py | 1 + app/dtos/color/color_code_dto.py | 9 ++++++ app/dtos/color/color_data.py | 8 ++++++ app/dtos/color/color_dto.py | 20 ++----------- .../color/{response.py => colors_response.py} | 2 +- app/dtos/user/user_dto.py | 14 ++++++++++ app/models/color.py | 19 +++---------- app/models/color_inventory.py | 17 +++++++++++ app/models/user.py | 16 +++++++++-- app/queries/user_query.py | 4 +++ app/services/color_service.py | 28 +++++++++++-------- app/services/teller_card_service.py | 2 +- app/services/user_service.py | 6 ++-- 16 files changed, 101 insertions(+), 59 deletions(-) create mode 100644 app/dtos/color/color_code_dto.py create mode 100644 app/dtos/color/color_data.py rename app/dtos/color/{response.py => colors_response.py} (73%) create mode 100644 app/models/color_inventory.py diff --git a/app/apis/v2/badge_router.py b/app/apis/v2/badge_router.py index 9e76bd1..98bb42f 100644 --- a/app/apis/v2/badge_router.py +++ b/app/apis/v2/badge_router.py @@ -11,7 +11,7 @@ response_model=BadgesResponse, status_code=status.HTTP_200_OK, ) -async def get_user_badge_handler(user_id: str) -> BadgesResponse: +async def api_get_user_badges(user_id: str) -> BadgesResponse: badges = await BadgeService.get_badges_with_details_by_user_id(user_id) return BadgesResponse( code=status.HTTP_200_OK, diff --git a/app/apis/v2/cheese_router.py b/app/apis/v2/cheese_router.py index 8ff78d0..6682027 100644 --- a/app/apis/v2/cheese_router.py +++ b/app/apis/v2/cheese_router.py @@ -8,7 +8,7 @@ @cheese_router.get("", response_model=CheeseResponse, status_code=status.HTTP_200_OK) -async def get_cheese_handler(user_id: str) -> CheeseResponse: +async def api_get_cheese_balance(user_id: str) -> CheeseResponse: user = await UserService.get_user_info(user_id=user_id) cheese_amount = await CheeseService.get_cheese_balance(user.cheese_manager_id) return CheeseResponse( diff --git a/app/apis/v2/color_router.py b/app/apis/v2/color_router.py index ae3ab4a..2481c03 100644 --- a/app/apis/v2/color_router.py +++ b/app/apis/v2/color_router.py @@ -1,6 +1,6 @@ from fastapi import APIRouter, status -from app.dtos.color.response import ColorListResponseDTO +from app.dtos.color.colors_response import ColorsResponse from app.services.color_service import ColorService color_router = APIRouter(prefix="/user/color", tags=["Color"]) @@ -8,14 +8,12 @@ @color_router.get( "", - response_model=ColorListResponseDTO, + response_model=ColorsResponse, status_code=status.HTTP_200_OK, ) -async def get_user_color_handler(user_id: str) -> ColorListResponseDTO: - +async def api_get_user_colors(user_id: str) -> ColorsResponse: colors = await ColorService.get_colors_with_details_by_user_id(user_id=user_id) - - return ColorListResponseDTO( + return ColorsResponse( code=status.HTTP_200_OK, message="보유 색상 정보 조회", data=colors, diff --git a/app/core/database/tortoise_database_settings.py b/app/core/database/tortoise_database_settings.py index 11227a6..9b6c237 100644 --- a/app/core/database/tortoise_database_settings.py +++ b/app/core/database/tortoise_database_settings.py @@ -10,6 +10,7 @@ "app.models.refresh_token", "app.models.badge", "app.models.color", + "app.models.color_inventory", "app.models.answer", "app.models.teller_card", "app.models.level", diff --git a/app/dtos/color/color_code_dto.py b/app/dtos/color/color_code_dto.py new file mode 100644 index 0000000..c0d6672 --- /dev/null +++ b/app/dtos/color/color_code_dto.py @@ -0,0 +1,9 @@ +from pydantic import BaseModel + + +class ColorCodeDTO(BaseModel): + colorCode: str + + @classmethod + def builder(cls, color_raw: dict[str, str]) -> "ColorCodeDTO": + return cls(colorCode=color_raw.get("color_code", "")) diff --git a/app/dtos/color/color_data.py b/app/dtos/color/color_data.py new file mode 100644 index 0000000..a58c37e --- /dev/null +++ b/app/dtos/color/color_data.py @@ -0,0 +1,8 @@ +import dataclasses + + +@dataclasses.dataclass(frozen=True) +class ColorData: + color_code: str + color_name: str + color_hex_code: str diff --git a/app/dtos/color/color_dto.py b/app/dtos/color/color_dto.py index df1a390..34f0732 100644 --- a/app/dtos/color/color_dto.py +++ b/app/dtos/color/color_dto.py @@ -1,23 +1,7 @@ from pydantic import BaseModel -class ColorCodeDTO(BaseModel): - colorCode: str - - @classmethod - def builder(cls, color_raw: dict[str, str]) -> "ColorCodeDTO": - return cls(colorCode=color_raw.get("color_code", "")) - - class ColorDTO(BaseModel): colorCode: str - # colorName: str - # colorHexCode: str - - @classmethod - def builder(cls, color_raw: dict[str, str]) -> "ColorDTO": - return cls( - colorCode=color_raw.get("color_code", ""), - # colorName=color_raw.get("color_name", ""), - # colorHexCode=color_raw.get("color_hex_code", ""), - ) + colorName: str + colorHexCode: str diff --git a/app/dtos/color/response.py b/app/dtos/color/colors_response.py similarity index 73% rename from app/dtos/color/response.py rename to app/dtos/color/colors_response.py index 3a76f21..e00f3e6 100644 --- a/app/dtos/color/response.py +++ b/app/dtos/color/colors_response.py @@ -2,5 +2,5 @@ from app.dtos.color.color_dto import ColorDTO -class ColorListResponseDTO(BaseResponseDTO): +class ColorsResponse(BaseResponseDTO): data: list[ColorDTO] diff --git a/app/dtos/user/user_dto.py b/app/dtos/user/user_dto.py index 4da00de..607e101 100644 --- a/app/dtos/user/user_dto.py +++ b/app/dtos/user/user_dto.py @@ -1,8 +1,22 @@ +import dataclasses from typing import Any, Optional from pydantic import BaseModel +@dataclasses.dataclass(frozen=True) +class UserProfileData: + user_id: str + nickname: str + profile_url: str + is_premium: bool + user_status: bool + cheese_manager_id: int + teller_card_id: int + level_id: int + allow_notification: bool + + class UserDTO(BaseModel): user_id: Optional[str] = None nickname: Optional[str] = None diff --git a/app/models/color.py b/app/models/color.py index 9934755..5762de9 100644 --- a/app/models/color.py +++ b/app/models/color.py @@ -5,6 +5,7 @@ from tortoise.models import Model from app.common.utils.query_executor import QueryExecutor +from app.dtos.color.color_data import ColorData from app.models.user import User from app.queries.color_query import ( INSERT_COLOR_CODE_FOR_USER_QUERY, @@ -36,20 +37,8 @@ async def add_color_code_for_user(cls, user_id: str, color_code: str) -> Any: return await QueryExecutor.execute_query(query, values=values, fetch_type="single") @classmethod - async def get_colors_with_details_by_user_id(cls, user_id: str) -> Any: + async def get_colors_with_details_by_user_id(cls, user_id: str) -> list[ColorData]: query = SELECT_COLOR_BY_USER_UUID_QUERY value = user_id - return await QueryExecutor.execute_query(query, values=value, fetch_type="multiple") - - -class ColorInventory(Model): - color_code = fields.CharField(max_length=255, primary_key=True) - color_name = fields.CharField(max_length=255, null=True) - color_hex_code = fields.CharField(max_length=255, null=True) - - class Meta: - table = "color_inventory" # 테이블 이름을 명시 - - @classmethod - async def get_color_inventory(cls) -> list[dict[str, str]]: - return await cls.all().values("color_code", "color_name", "color_hex_code") + result = await QueryExecutor.execute_query(query, values=value, fetch_type="multiple") + return [ColorData(**row) for row in result] diff --git a/app/models/color_inventory.py b/app/models/color_inventory.py new file mode 100644 index 0000000..f536a5d --- /dev/null +++ b/app/models/color_inventory.py @@ -0,0 +1,17 @@ +from tortoise import Model, fields + +from app.dtos.color.color_data import ColorData + + +class ColorInventory(Model): + color_code = fields.CharField(max_length=255, primary_key=True) + color_name = fields.CharField(max_length=255, null=True) + color_hex_code = fields.CharField(max_length=255, null=True) + + class Meta: + table = "color_inventory" # 테이블 이름을 명시 + + @classmethod + async def get_color_inventory(cls) -> list[ColorData]: + result = await cls.all().values("color_code", "color_name", "color_hex_code") + return [ColorData(**row) for row in result] diff --git a/app/models/user.py b/app/models/user.py index 15415c2..a415937 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -7,6 +7,7 @@ from tortoise.models import Model from app.common.utils.query_executor import QueryExecutor +from app.dtos.user.user_dto import UserProfileData from app.models.cheese_manager import CheeseManager from app.models.level import Level from app.models.refresh_token import RefreshToken @@ -65,10 +66,21 @@ class Meta: table = "user" @classmethod - async def get_user_profile_by_user_id(cls, user_id: str) -> Any: + async def get_user_profile_by_user_id(cls, user_id: str) -> UserProfileData: query = SELECT_USER_PROFILE_BY_USER_ID_QUERY value = user_id - return await QueryExecutor.execute_query(query, values=value, fetch_type="single") + result = await QueryExecutor.execute_query(query, values=value, fetch_type="single") + return UserProfileData( + user_id=result.get("user_id", ""), + nickname=result.get("nickname", ""), + profile_url=result.get("profile_url", ""), + is_premium=result.get("is_premium") != b"\x00", + user_status=result.get("user_status") != b"\x00", + cheese_manager_id=result.get("cheese_manager_id", 0), + teller_card_id=result.get("teller_card_id", 0), + level_id=result.get("level_id", 0), + allow_notification=result.get("allow_notification") != b"\x00", + ) @classmethod async def get_user_info_by_user_id(cls, user_id: str) -> Any: diff --git a/app/queries/user_query.py b/app/queries/user_query.py index a3f7ed5..904c991 100644 --- a/app/queries/user_query.py +++ b/app/queries/user_query.py @@ -6,8 +6,12 @@ SELECT_USER_PROFILE_BY_USER_ID_QUERY = f""" SELECT u.nickname, + u.profile_url, u.is_premium, + u.user_status, u.cheese_manager_id, + u.teller_card_id, + u.level_id, u.allow_notification FROM user u WHERE {USER_ID_QUERY} diff --git a/app/services/color_service.py b/app/services/color_service.py index 35d38ba..2aeaf6a 100644 --- a/app/services/color_service.py +++ b/app/services/color_service.py @@ -1,6 +1,8 @@ -from app.dtos.color.color_dto import ColorCodeDTO, ColorDTO -from app.models.color import Color, ColorInventory -from app.services.user_service import UserService +from app.dtos.color.color_dto import ColorDTO +from app.dtos.color.color_code_dto import ColorCodeDTO +from app.models.color import Color +from app.models.color_inventory import ColorInventory +from app.models.user import User class ColorService: @@ -15,14 +17,18 @@ async def add_color(cls, user_id: str, color_code: str) -> None: @classmethod async def get_colors_with_details_by_user_id(cls, user_id: str) -> list[ColorDTO]: - user = await UserService.get_user_profile(user_id=user_id) + user = await User.get_user_profile_by_user_id(user_id=user_id) + if user.is_premium: - colors_raw = await ColorInventory.get_color_inventory() + colors = await ColorInventory.get_color_inventory() else: - colors_raw = await Color.get_colors_with_details_by_user_id(user_id=user_id) - - return [ColorDTO.builder(color) for color in colors_raw] + colors = await Color.get_colors_with_details_by_user_id(user_id=user_id) - @classmethod - async def get_color_inventory(cls) -> list[dict[str, str]]: - return await ColorInventory.get_color_inventory() + return [ + ColorDTO( + colorCode=color.color_code, + colorName=color.color_name, + colorHexCode=color.color_hex_code, + ) + for color in colors + ] diff --git a/app/services/teller_card_service.py b/app/services/teller_card_service.py index d5a4e32..9f44558 100644 --- a/app/services/teller_card_service.py +++ b/app/services/teller_card_service.py @@ -2,7 +2,7 @@ from app.dtos.teller_card.teller_card_dto import TellerCardDTO from app.models.badge import BadgeInventory -from app.models.color import ColorInventory +from app.models.color_inventory import ColorInventory from app.models.teller_card import TellerCard diff --git a/app/services/user_service.py b/app/services/user_service.py index 0fdf50e..b7f3989 100644 --- a/app/services/user_service.py +++ b/app/services/user_service.py @@ -1,7 +1,7 @@ from typing import Any from app.dtos.user.user_data import UserData -from app.dtos.user.user_dto import UserDTO +from app.dtos.user.user_dto import UserDTO, UserProfileData from app.models.user import User @@ -12,8 +12,8 @@ async def get_user_info(user_id: str) -> UserData: return UserData(**result) @classmethod - async def get_user_profile(cls, user_id: str) -> UserDTO: - return UserDTO.build(await User.get_user_profile_by_user_id(user_id=user_id)) + async def get_user_profile(cls, user_id: str) -> UserProfileData: + return await User.get_user_profile_by_user_id(user_id=user_id) @staticmethod async def set_is_premium(user_id: str, is_premium: bool) -> None: From 3c5479b57402be868fc68fbc48618da5b6d5344c Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Tue, 3 Jun 2025 16:35:04 +0900 Subject: [PATCH 08/27] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20emotion?= =?UTF-8?q?=20router=20DTO=20=EA=B5=AC=EC=A1=B0=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/apis/v2/badge_router.py | 3 +- app/apis/v2/color_router.py | 3 +- app/apis/v2/emotion_router.py | 8 +++--- app/common/constants/__init__.py | 0 app/common/constants/emotion_dict.py | 14 +++++++++ app/dtos/emotion/emotion_data.py | 7 +++++ app/dtos/emotion/emotion_dto.py | 5 ++++ app/dtos/emotion/emotions_response.py | 6 ++++ app/dtos/emotion/response.py | 15 ---------- app/models/emotion.py | 11 ++++--- app/queries/emotion_query.py | 6 ++-- app/services/emotion_service.py | 41 +++++++-------------------- app/services/mission_service.py | 5 ++-- app/services/purchase_service.py | 2 +- 14 files changed, 63 insertions(+), 63 deletions(-) create mode 100644 app/common/constants/__init__.py create mode 100644 app/common/constants/emotion_dict.py create mode 100644 app/dtos/emotion/emotion_data.py create mode 100644 app/dtos/emotion/emotion_dto.py create mode 100644 app/dtos/emotion/emotions_response.py delete mode 100644 app/dtos/emotion/response.py diff --git a/app/apis/v2/badge_router.py b/app/apis/v2/badge_router.py index 98bb42f..a2ce83d 100644 --- a/app/apis/v2/badge_router.py +++ b/app/apis/v2/badge_router.py @@ -12,9 +12,8 @@ status_code=status.HTTP_200_OK, ) async def api_get_user_badges(user_id: str) -> BadgesResponse: - badges = await BadgeService.get_badges_with_details_by_user_id(user_id) return BadgesResponse( code=status.HTTP_200_OK, message="보유 뱃지 정보 조회", - data=badges, + data=await BadgeService.get_badges_with_details_by_user_id(user_id), ) diff --git a/app/apis/v2/color_router.py b/app/apis/v2/color_router.py index 2481c03..a4f1aaf 100644 --- a/app/apis/v2/color_router.py +++ b/app/apis/v2/color_router.py @@ -12,9 +12,8 @@ status_code=status.HTTP_200_OK, ) async def api_get_user_colors(user_id: str) -> ColorsResponse: - colors = await ColorService.get_colors_with_details_by_user_id(user_id=user_id) return ColorsResponse( code=status.HTTP_200_OK, message="보유 색상 정보 조회", - data=colors, + data=await ColorService.get_colors_with_details_by_user_id(user_id=user_id), ) diff --git a/app/apis/v2/emotion_router.py b/app/apis/v2/emotion_router.py index 3d27cee..b0e609d 100644 --- a/app/apis/v2/emotion_router.py +++ b/app/apis/v2/emotion_router.py @@ -1,6 +1,6 @@ from fastapi import APIRouter, status -from app.dtos.emotion.response import EmotionListResponseDTO +from app.dtos.emotion.emotions_response import EmotionsResponse from app.services.emotion_service import EmotionService emotion_router = APIRouter(prefix="/user/emotion", tags=["Emotion"]) @@ -8,11 +8,11 @@ @emotion_router.get( "", - response_model=EmotionListResponseDTO, + response_model=EmotionsResponse, status_code=status.HTTP_200_OK, ) -async def get_user_emotion_handler(user_id: str) -> EmotionListResponseDTO: - return EmotionListResponseDTO( +async def api_get_user_emotions(user_id: str) -> EmotionsResponse: + return EmotionsResponse( data=await EmotionService.mapping_emotion_list(user_id=user_id), code=status.HTTP_200_OK, message="보유 감정 정보 조회", diff --git a/app/common/constants/__init__.py b/app/common/constants/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/common/constants/emotion_dict.py b/app/common/constants/emotion_dict.py new file mode 100644 index 0000000..3ca72a0 --- /dev/null +++ b/app/common/constants/emotion_dict.py @@ -0,0 +1,14 @@ +EMOTION_DICT = { + "EM_HAPPY": 1, + "EM_PROUD": 2, + "EM_OKAY": 3, + "EM_TIRED": 4, + "EM_SAD": 5, + "EM_ANGRY": 6, + "EM_EXCITED": 7, + "EM_FUN": 8, + "EM_RELAXED": 9, + "EM_APATHETIC": 10, + "EM_LONELY": 11, + "EM_COMPLEX": 12, +} diff --git a/app/dtos/emotion/emotion_data.py b/app/dtos/emotion/emotion_data.py new file mode 100644 index 0000000..178eeb6 --- /dev/null +++ b/app/dtos/emotion/emotion_data.py @@ -0,0 +1,7 @@ +import dataclasses + + +@dataclasses.dataclass(frozen=True) +class EmotionData: + emotion_code: str + emotion_name: str diff --git a/app/dtos/emotion/emotion_dto.py b/app/dtos/emotion/emotion_dto.py new file mode 100644 index 0000000..c537095 --- /dev/null +++ b/app/dtos/emotion/emotion_dto.py @@ -0,0 +1,5 @@ +from pydantic import BaseModel + + +class EmotionDTO(BaseModel): + emotionList: list[int] diff --git a/app/dtos/emotion/emotions_response.py b/app/dtos/emotion/emotions_response.py new file mode 100644 index 0000000..fe87c88 --- /dev/null +++ b/app/dtos/emotion/emotions_response.py @@ -0,0 +1,6 @@ +from app.dtos.base_response import BaseResponseDTO +from app.dtos.emotion.emotion_dto import EmotionDTO + + +class EmotionsResponse(BaseResponseDTO): + data: EmotionDTO diff --git a/app/dtos/emotion/response.py b/app/dtos/emotion/response.py deleted file mode 100644 index 9d32342..0000000 --- a/app/dtos/emotion/response.py +++ /dev/null @@ -1,15 +0,0 @@ -from pydantic import BaseModel - -from app.dtos.base_response import BaseResponseDTO - - -class EmotionDTO(BaseModel): - emotionList: list[int] - - @classmethod - def build(cls, emotion_list: list[int]) -> "EmotionDTO": - return cls(emotionList=emotion_list) - - -class EmotionListResponseDTO(BaseResponseDTO): - data: EmotionDTO diff --git a/app/models/emotion.py b/app/models/emotion.py index 2f032d0..a54707b 100644 --- a/app/models/emotion.py +++ b/app/models/emotion.py @@ -4,6 +4,7 @@ from tortoise.fields import ForeignKeyRelation from app.common.utils.query_executor import QueryExecutor +from app.dtos.emotion.emotion_data import EmotionData from app.models.user import User from app.queries.emotion_query import ( INSERT_EMOTION_CODE_FOR_USER_QUERY, @@ -20,10 +21,11 @@ class Meta: table = "emotion" @classmethod - async def get_emotions_with_details_by_user_id(cls, user_id: str) -> Any: + async def get_emotions_with_details_by_user_id(cls, user_id: str) -> list[EmotionData]: query = SELECT_EMOTION_CODE_BY_USER_UUID_QUERY values = user_id - return await QueryExecutor.execute_query(query, values=values, fetch_type="multiple") + result = await QueryExecutor.execute_query(query, values=values, fetch_type="multiple") + return [EmotionData(**row) for row in result] @classmethod async def add_emotion(cls, user_id: str, emotion_code: str) -> None: @@ -41,5 +43,6 @@ class Meta: table = "emotion_inventory" @classmethod - async def get_emotion_inventory(cls) -> list[dict[str, str]]: - return await cls.all().values("emotion_code", "emotion_name") + async def get_emotion_inventory(cls) -> list[EmotionData]: + result = await cls.all().values("emotion_code", "emotion_name") + return [EmotionData(**row) for row in result] diff --git a/app/queries/emotion_query.py b/app/queries/emotion_query.py index 3bb84d3..5dc14a0 100644 --- a/app/queries/emotion_query.py +++ b/app/queries/emotion_query.py @@ -1,8 +1,10 @@ from app.queries.user_query import USER_ID_QUERY SELECT_EMOTION_CODE_BY_USER_UUID_QUERY = f""" - SELECT emotion_code - FROM emotion + SELECT e.emotion_code, ei.emotion_name + FROM emotion e + JOIN emotion_inventory ei + ON e.emotion_code = ei.emotion_code WHERE {USER_ID_QUERY} """ diff --git a/app/services/emotion_service.py b/app/services/emotion_service.py index 1785f13..36e3165 100644 --- a/app/services/emotion_service.py +++ b/app/services/emotion_service.py @@ -1,53 +1,32 @@ from typing import Any -from app.dtos.emotion.response import EmotionDTO +from app.common.constants.emotion_dict import EMOTION_DICT +from app.dtos.emotion.emotion_data import EmotionData +from app.dtos.emotion.emotion_dto import EmotionDTO from app.models.emotion import Emotion, EmotionInventory +from app.models.user import User from app.services.user_service import UserService -emotion_mapping = { - "EM_HAPPY": 1, - "EM_PROUD": 2, - "EM_OKAY": 3, - "EM_TIRED": 4, - "EM_SAD": 5, - "EM_ANGRY": 6, - "EM_EXCITED": 7, - "EM_FUN": 8, - "EM_RELAXED": 9, - "EM_APATHETIC": 10, - "EM_LONELY": 11, - "EM_COMPLEX": 12, -} - class EmotionService: - @classmethod - async def get_emotions(cls, user_id: str) -> Any: - return await Emotion.get_emotions_with_details_by_user_id(user_id=user_id) @classmethod async def add_emotion(cls, user_id: str, emotion_code: str) -> None: await Emotion.add_emotion(user_id=user_id, emotion_code=emotion_code) - @classmethod - async def get_emotion_inventory(cls) -> list[dict[str, str]]: - return await EmotionInventory.get_emotion_inventory() - @classmethod async def mapping_emotion_list(cls, user_id: str) -> EmotionDTO: - user = await UserService.get_user_profile(user_id=user_id) + user = await User.get_user_profile_by_user_id(user_id=user_id) if user.is_premium: - emotions = await cls.get_emotion_inventory() + emotions = await EmotionInventory.get_emotion_inventory() else: - emotions = await cls.get_emotions(user_id=user_id) + emotions = await Emotion.get_emotions_with_details_by_user_id(user_id=user_id) - return EmotionDTO.build(emotion_list=await cls.get_mapped_emotions(emotions)) + return EmotionDTO(emotionList=await cls.get_mapped_emotions(emotions)) @classmethod - async def get_mapped_emotions(cls, emotions: list[dict[str, str]]) -> list[int]: + async def get_mapped_emotions(cls, emotions: list[EmotionData]) -> list[int]: return [ - value - for value in (emotion_mapping.get(emotion["emotion_code"]) for emotion in emotions) - if value is not None + value for value in (EMOTION_DICT.get(emotion.emotion_code) for emotion in emotions) if value is not None ] diff --git a/app/services/mission_service.py b/app/services/mission_service.py index c164404..ddd20da 100644 --- a/app/services/mission_service.py +++ b/app/services/mission_service.py @@ -12,6 +12,7 @@ from app.models.item import ItemInventory, ItemInventoryRewardInventory, RewardInventory from app.models.like import Like from app.models.mission import MissionInventory, UserMission +from app.models.user import User from app.services.answer_service import AnswerService from app.services.badge_service import BadgeService from app.services.cheese_service import CheeseService @@ -361,9 +362,9 @@ async def reward_level_up_mission(self, user_id: str, cheese_manager_id: int, re cheese_manager_id=cheese_manager_id, ) level_info = await LevelService.get_level_info_add_answer_days(user_id) - user_info = await UserService.get_user_profile(user_id=user_id) + user_profile = await User.get_user_profile_by_user_id(user_id=user_id) - nickname = user_info.nickname + nickname = user_profile.nickname level = level_info.levelDto.level await self._create_reward_notice( diff --git a/app/services/purchase_service.py b/app/services/purchase_service.py index 579d3b9..ffed052 100644 --- a/app/services/purchase_service.py +++ b/app/services/purchase_service.py @@ -54,7 +54,7 @@ async def process_apple_purchase(self, receipt_data: str, user_id: str) -> Purch status=subscription_status, ) - user = await UserService.get_user_profile(user_id=user_id) + user = await User.get_user_profile_by_user_id(user_id=user_id) return PurchaseResponseDTO.build(is_premium=user.is_premium, product_code=receipt_info.product_code_two) # type: ignore From 00a712b02f6dd5a8c3f3db855b1d1b9401499523 Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Tue, 3 Jun 2025 17:38:58 +0900 Subject: [PATCH 09/27] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20payment?= =?UTF-8?q?=20router=20=EC=97=AD=ED=95=A0=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/apis/v2/cheese_router.py | 5 +- app/apis/v2/mobile_router.py | 11 ++- app/apis/v2/payment_router.py | 34 +++---- app/common/constants/item_category.py | 7 ++ .../{request.py => payment_request.py} | 2 +- app/dtos/payment/payment_response.py | 11 +++ app/dtos/payment/response.py | 21 ---- app/models/badge.py | 2 +- app/models/cheese_manager.py | 99 ++++++++++--------- app/models/color.py | 2 +- app/models/emotion.py | 2 +- app/models/user.py | 6 +- app/services/badge_service.py | 4 - app/services/cheese_service.py | 12 --- app/services/color_service.py | 4 - app/services/emotion_service.py | 4 - app/services/mission_service.py | 20 ++-- app/services/payment_service.py | 41 +++++--- app/services/user_service.py | 3 +- 19 files changed, 136 insertions(+), 154 deletions(-) create mode 100644 app/common/constants/item_category.py rename app/dtos/payment/{request.py => payment_request.py} (66%) create mode 100644 app/dtos/payment/payment_response.py delete mode 100644 app/dtos/payment/response.py delete mode 100644 app/services/cheese_service.py diff --git a/app/apis/v2/cheese_router.py b/app/apis/v2/cheese_router.py index 6682027..74c359f 100644 --- a/app/apis/v2/cheese_router.py +++ b/app/apis/v2/cheese_router.py @@ -1,7 +1,8 @@ from fastapi import APIRouter, status from app.dtos.cheese.cheese_response import CheeseResponse, TotalCheeseAmount -from app.services.cheese_service import CheeseService +from app.models.cheese_manager import CheeseManager + from app.services.user_service import UserService cheese_router = APIRouter(prefix="/cheese", tags=["Cheese"]) @@ -10,7 +11,7 @@ @cheese_router.get("", response_model=CheeseResponse, status_code=status.HTTP_200_OK) async def api_get_cheese_balance(user_id: str) -> CheeseResponse: user = await UserService.get_user_info(user_id=user_id) - cheese_amount = await CheeseService.get_cheese_balance(user.cheese_manager_id) + cheese_amount = await CheeseManager.get_total_cheese_amount_by_manager(cheese_manager_id=user.cheese_manager_id) return CheeseResponse( code=status.HTTP_200_OK, message="총 치즈 갯수 조회", diff --git a/app/apis/v2/mobile_router.py b/app/apis/v2/mobile_router.py index 570a166..9b4a3f0 100644 --- a/app/apis/v2/mobile_router.py +++ b/app/apis/v2/mobile_router.py @@ -6,9 +6,10 @@ from app.dtos.mobile.teller_card_response import DataDTO, TellerCardResponseDTO from app.dtos.user.user_info_dto import UserInfoDTO from app.dtos.user.user_profile_dto import UserProfileDTO +from app.models.cheese_manager import CheeseManager from app.services.answer_service import AnswerService from app.services.badge_service import BadgeService -from app.services.cheese_service import CheeseService + from app.services.color_service import ColorService from app.services.level_service import LevelService from app.services.teller_card_service import TellerCardService @@ -31,13 +32,13 @@ async def mobile_teller_card_handler(user_id: str) -> TellerCardResponseDTO: user_info_task = UserService.get_user_info(user_id) record_answer_task = AnswerService.get_answer_record(user_id=user_id) - badges, colors, level_info, teller_card, user_raw, record_count = await asyncio.gather( + badges, colors, level_info, teller_card, user, record_count = await asyncio.gather( badges_task, colors_task, level_info_task, teller_card_task, user_info_task, record_answer_task ) - cheese_amount = await CheeseService.get_cheese_balance(user_raw.cheese_manager_id) + cheese_amount = await CheeseManager.get_total_cheese_amount_by_manager(cheese_manager_id=user.cheese_manager_id) - user_info = UserInfoDTO(nickname=user_raw.nickname, cheeseBalance=cheese_amount, tellerCard=teller_card) + user_info = UserInfoDTO(nickname=user.nickname, cheeseBalance=cheese_amount, tellerCard=teller_card) data = DataDTO.builder( badges=badges, colors=colors, userInfo=user_info, levelInfo=level_info, recordCount=record_count @@ -65,7 +66,7 @@ async def mobile_my_page_handler(user_id: str) -> MyPageResponseDTO: LevelService.get_level_info_add_answer_days(user_id), ) - cheese_amount = await CheeseService.get_cheese_balance(cheese_manager_id=user.cheese_manager_id) # type: ignore + cheese_amount = await CheeseManager.get_total_cheese_amount_by_manager(cheese_manager_id=user.cheese_manager_id) user_profile_data = UserProfileWithLevel.builder( userProfile=UserProfileDTO.builder( diff --git a/app/apis/v2/payment_router.py b/app/apis/v2/payment_router.py index 371bdf3..d5b4256 100644 --- a/app/apis/v2/payment_router.py +++ b/app/apis/v2/payment_router.py @@ -1,7 +1,7 @@ from fastapi import APIRouter, HTTPException, status -from app.dtos.payment.request import PaymentRequestDTO -from app.dtos.payment.response import PaymentResponseDTO +from app.dtos.payment.payment_request import PaymentRequest +from app.dtos.payment.payment_response import PaymentResponse, ProductDTO from app.services.payment_service import PaymentService from app.services.user_service import UserService @@ -10,24 +10,16 @@ @payment_router.post( "", - response_model=PaymentResponseDTO, + response_model=PaymentResponse, status_code=status.HTTP_200_OK, ) -async def process_payment(request: PaymentRequestDTO) -> PaymentResponseDTO: - try: - user_id = request.user_id - product_code = request.productCode - - product, item_inventory_products = await PaymentService.validate_payment(product_code) - - user = await UserService.get_user_info(user_id=user_id) - - await PaymentService.process_cheese_payment( - product, item_inventory_products, user_id, user["cheese_manager_id"] - ) - return PaymentResponseDTO.builder(product_code=product.product_code) - - except HTTPException as e: - raise e - except ValueError as e: - raise HTTPException(status_code=400, detail=str(e)) +async def process_payment(payment_request: PaymentRequest) -> PaymentResponse: + return PaymentResponse( + code=status.HTTP_200_OK, + data=ProductDTO( + product_code=await PaymentService.process_cheese_payment( + product_code=payment_request.productCode, user_id=payment_request.user_id + ), + ), + message="Payment successful", + ) diff --git a/app/common/constants/item_category.py b/app/common/constants/item_category.py new file mode 100644 index 0000000..8ad044f --- /dev/null +++ b/app/common/constants/item_category.py @@ -0,0 +1,7 @@ +from enum import Enum + + +class ItemCategory(str, Enum): + BADGE = "BADGE" + COLOR = "COLOR" + EMOTION = "EMOTION" diff --git a/app/dtos/payment/request.py b/app/dtos/payment/payment_request.py similarity index 66% rename from app/dtos/payment/request.py rename to app/dtos/payment/payment_request.py index fb55b02..0ef40de 100644 --- a/app/dtos/payment/request.py +++ b/app/dtos/payment/payment_request.py @@ -1,6 +1,6 @@ from pydantic import BaseModel -class PaymentRequestDTO(BaseModel): +class PaymentRequest(BaseModel): user_id: str productCode: str diff --git a/app/dtos/payment/payment_response.py b/app/dtos/payment/payment_response.py new file mode 100644 index 0000000..14066fe --- /dev/null +++ b/app/dtos/payment/payment_response.py @@ -0,0 +1,11 @@ +from pydantic import BaseModel + +from app.dtos.base_response import BaseResponseDTO + + +class ProductDTO(BaseModel): + product_code: str + + +class PaymentResponse(BaseResponseDTO): + data: ProductDTO diff --git a/app/dtos/payment/response.py b/app/dtos/payment/response.py deleted file mode 100644 index 04b1e18..0000000 --- a/app/dtos/payment/response.py +++ /dev/null @@ -1,21 +0,0 @@ -from pydantic import BaseModel - -from app.dtos.base_response import BaseResponseDTO - - -class ProductDTO(BaseModel): - product_code: str - - -class PaymentResponseDTO(BaseResponseDTO): - data: ProductDTO - - @classmethod - def builder(cls, product_code: str) -> "PaymentResponseDTO": - return cls( - code=200, - message="Payment successful", - data=ProductDTO( - product_code=product_code, - ), - ) diff --git a/app/models/badge.py b/app/models/badge.py index 1b0be24..dfe4551 100644 --- a/app/models/badge.py +++ b/app/models/badge.py @@ -43,7 +43,7 @@ async def get_badge_codes_by_user_id(cls, user_id: str) -> Any: return await QueryExecutor.execute_query(query, values=value, fetch_type="multiple") @classmethod - async def add_badge(cls, user_id: str, badge_code: str) -> None: + async def create_by_user_id(cls, user_id: str, badge_code: str) -> None: query = INSERT_BADGE_CODE_FOR_USER_QUERY values = (badge_code, user_id) await QueryExecutor.execute_query(query, values=values) diff --git a/app/models/cheese_manager.py b/app/models/cheese_manager.py index 3cfd574..35e36d1 100644 --- a/app/models/cheese_manager.py +++ b/app/models/cheese_manager.py @@ -1,75 +1,58 @@ -from typing import Any - from tortoise import fields from tortoise.expressions import Q from tortoise.fields import ForeignKeyRelation from tortoise.functions import Sum from tortoise.models import Model + from app.models.cheese_status import CheeseStatus class CheeseManager(Model): - cheese_manager_id = fields.BigIntField(primary_key=True) # BIGINT auto_increment equivalent + """ + Cheese Manager 치즈 관련 작업을 하는 모델 겸 서비스 + """ + + cheese_manager_id = fields.BigIntField(primary_key=True) class Meta: - table = "cheese_manager" # Database table name + table = "cheese_manager" @staticmethod async def get_total_cheese_amount_by_manager(cheese_manager_id: int) -> int: - result: list[dict[str, Any]] = ( - await CheeseHistory.filter( - Q(status=CheeseStatus.CAN_USE) | Q(status=CheeseStatus.USING), - cheese_manager_id=cheese_manager_id, - ) - .annotate(total_cheese_amount=Sum("current_amount")) - .values("total_cheese_amount") - ) - if not result or result[0].get("total_cheese_amount") is None: - return 0 - - total_cheese_amount = result[0].get("total_cheese_amount") - return int(total_cheese_amount) if total_cheese_amount is not None else 0 + return await CheeseHistory.get_total_amount_by_manager(cheese_manager_id) - @staticmethod - async def use_cheese(cheese_manager_id: int, amount: int) -> None: - using_cheese = await CheeseHistory.filter( - status=CheeseStatus.USING, cheese_manager_id=cheese_manager_id - ).order_by("cheese_history_id") + @classmethod + async def use_cheese(cls, cheese_manager_id: int, amount: int) -> None: + remaining = amount - remaining_amount = amount - - for cheese in using_cheese: - if cheese.current_amount >= remaining_amount: - cheese.current_amount -= remaining_amount + using_cheeses = await CheeseHistory.get_using_cheeses(cheese_manager_id) + for cheese in using_cheeses: + if cheese.current_amount >= remaining: + cheese.current_amount -= remaining if cheese.current_amount == 0: cheese.status = CheeseStatus.ALREADY_USED await cheese.save() return + else: + remaining -= cheese.current_amount + cheese.current_amount = 0 + cheese.status = CheeseStatus.ALREADY_USED + await cheese.save() - remaining_amount -= cheese.current_amount - cheese.current_amount = 0 - cheese.status = CheeseStatus.ALREADY_USED - await cheese.save() - - can_use_cheese = await CheeseHistory.filter( - status=CheeseStatus.CAN_USE, cheese_manager_id=cheese_manager_id - ).order_by("cheese_history_id") + can_use_cheeses = await CheeseHistory.get_can_use_cheeses(cheese_manager_id) - for cheese in can_use_cheese: - if cheese.current_amount >= remaining_amount: - cheese.current_amount -= remaining_amount - cheese.status = CheeseStatus.USING + for cheese in can_use_cheeses: + if cheese.current_amount >= remaining: + cheese.current_amount -= remaining + cheese.status = CheeseStatus.USING if cheese.current_amount > 0 else CheeseStatus.ALREADY_USED await cheese.save() return - - remaining_amount -= cheese.current_amount - cheese.current_amount = 0 - cheese.status = CheeseStatus.ALREADY_USED - await cheese.save() - - if remaining_amount > 0: - raise ValueError("Not enough cheese to complete the transaction") + else: + remaining -= cheese.current_amount + cheese.current_amount = 0 + cheese.status = CheeseStatus.ALREADY_USED + await cheese.save() @staticmethod async def add_cheese(cheese_manager_id: int, amount: int) -> None: @@ -83,7 +66,7 @@ async def add_cheese(cheese_manager_id: int, amount: int) -> None: class CheeseHistory(Model): cheese_history_id = fields.BigIntField(primary_key=True) - status = fields.CharEnumField(CheeseStatus, max_length=50, null=True) # Enum Field + status = fields.CharEnumField(CheeseStatus, max_length=50, null=True) current_amount = fields.IntField() starting_amount = fields.IntField() cheese_manager: ForeignKeyRelation[CheeseManager] = fields.ForeignKeyField( @@ -94,3 +77,23 @@ class CheeseHistory(Model): class Meta: table = "cheese_history" + + @classmethod + async def get_total_amount_by_manager(cls, manager_id: int) -> int: + result = await ( + cls.filter( + Q(status=CheeseStatus.CAN_USE) | Q(status=CheeseStatus.USING), + cheese_manager_id=manager_id, + ) + .annotate(total=Sum("current_amount")) + .values_list("total", flat=True) + ) + return int(result[0]) if result and result[0] is not None else 0 + + @classmethod + async def get_using_cheeses(cls, manager_id: int) -> list["CheeseHistory"]: + return await cls.filter(status=CheeseStatus.USING, cheese_manager_id=manager_id).order_by("cheese_history_id") + + @classmethod + async def get_can_use_cheeses(cls, manager_id: int) -> list["CheeseHistory"]: + return await cls.filter(status=CheeseStatus.CAN_USE, cheese_manager_id=manager_id).order_by("cheese_history_id") diff --git a/app/models/color.py b/app/models/color.py index 5762de9..469d7b6 100644 --- a/app/models/color.py +++ b/app/models/color.py @@ -31,7 +31,7 @@ async def get_color_codes_by_user_id(cls, user_id: str) -> Any: return await QueryExecutor.execute_query(query, values=value, fetch_type="multiple") @classmethod - async def add_color_code_for_user(cls, user_id: str, color_code: str) -> Any: + async def create_by_user_id(cls, user_id: str, color_code: str) -> Any: query = INSERT_COLOR_CODE_FOR_USER_QUERY values = (color_code, user_id) return await QueryExecutor.execute_query(query, values=values, fetch_type="single") diff --git a/app/models/emotion.py b/app/models/emotion.py index a54707b..c2b64a0 100644 --- a/app/models/emotion.py +++ b/app/models/emotion.py @@ -28,7 +28,7 @@ async def get_emotions_with_details_by_user_id(cls, user_id: str) -> list[Emotio return [EmotionData(**row) for row in result] @classmethod - async def add_emotion(cls, user_id: str, emotion_code: str) -> None: + async def create_by_user_id(cls, user_id: str, emotion_code: str) -> None: query = INSERT_EMOTION_CODE_FOR_USER_QUERY values = (emotion_code, user_id) await QueryExecutor.execute_query(query, values=values) diff --git a/app/models/user.py b/app/models/user.py index a415937..6b835b0 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -7,6 +7,7 @@ from tortoise.models import Model from app.common.utils.query_executor import QueryExecutor +from app.dtos.user.user_data import UserData from app.dtos.user.user_dto import UserProfileData from app.models.cheese_manager import CheeseManager from app.models.level import Level @@ -83,10 +84,11 @@ async def get_user_profile_by_user_id(cls, user_id: str) -> UserProfileData: ) @classmethod - async def get_user_info_by_user_id(cls, user_id: str) -> Any: + async def get_user_info_by_user_id(cls, user_id: str) -> UserData: query = SELECT_USER_INFO_BY_USER_UUID_QUERY value = user_id - return await QueryExecutor.execute_query(query, values=value, fetch_type="single") + result = await QueryExecutor.execute_query(query, values=value, fetch_type="single") + return UserData(**result) @classmethod async def set_is_premium(cls, user_id: str, is_premium: bool) -> Any: diff --git a/app/services/badge_service.py b/app/services/badge_service.py index f211b1a..a354613 100644 --- a/app/services/badge_service.py +++ b/app/services/badge_service.py @@ -9,10 +9,6 @@ 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] - @classmethod - async def add_badge(cls, user_id: str, badge_code: str) -> None: - await Badge.add_badge(user_id=user_id, badge_code=badge_code) - @classmethod async def get_badges_with_details_by_user_id(cls, user_id: str) -> list[BadgeDTO]: badges = await Badge.get_badges_with_details_by_user_id(user_id=user_id) diff --git a/app/services/cheese_service.py b/app/services/cheese_service.py deleted file mode 100644 index fc4518e..0000000 --- a/app/services/cheese_service.py +++ /dev/null @@ -1,12 +0,0 @@ -from app.models.cheese_manager import CheeseManager - - -class CheeseService: - - @classmethod - async def get_cheese_balance(cls, cheese_manager_id: int) -> int: - return await CheeseManager.get_total_cheese_amount_by_manager(cheese_manager_id=cheese_manager_id) or 0 - - @classmethod - async def add_cheese(cls, cheese_manager_id: int, amount: int) -> None: - await CheeseManager.add_cheese(cheese_manager_id=cheese_manager_id, amount=amount) diff --git a/app/services/color_service.py b/app/services/color_service.py index 2aeaf6a..b5667f2 100644 --- a/app/services/color_service.py +++ b/app/services/color_service.py @@ -11,10 +11,6 @@ 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] - @classmethod - async def add_color(cls, user_id: str, color_code: str) -> None: - await Color.add_color_code_for_user(user_id=user_id, color_code=color_code) - @classmethod async def get_colors_with_details_by_user_id(cls, user_id: str) -> list[ColorDTO]: user = await User.get_user_profile_by_user_id(user_id=user_id) diff --git a/app/services/emotion_service.py b/app/services/emotion_service.py index 36e3165..038bf88 100644 --- a/app/services/emotion_service.py +++ b/app/services/emotion_service.py @@ -10,10 +10,6 @@ class EmotionService: - @classmethod - async def add_emotion(cls, user_id: str, emotion_code: str) -> None: - await Emotion.add_emotion(user_id=user_id, emotion_code=emotion_code) - @classmethod async def mapping_emotion_list(cls, user_id: str) -> EmotionDTO: user = await User.get_user_profile_by_user_id(user_id=user_id) diff --git a/app/services/mission_service.py b/app/services/mission_service.py index ddd20da..cbf523c 100644 --- a/app/services/mission_service.py +++ b/app/services/mission_service.py @@ -9,17 +9,19 @@ from app.dtos.mission.mission_dto import UserMissionDTO from app.dtos.mission.reward_dto import RewardDTO +from app.models.badge import Badge +from app.models.cheese_manager import CheeseManager +from app.models.color import Color from app.models.item import ItemInventory, ItemInventoryRewardInventory, RewardInventory from app.models.like import Like from app.models.mission import MissionInventory, UserMission from app.models.user import User from app.services.answer_service import AnswerService from app.services.badge_service import BadgeService -from app.services.cheese_service import CheeseService + from app.services.color_service import ColorService from app.services.level_service import LevelService from app.services.notice_service import NoticeService -from app.services.user_service import UserService class MissionService: @@ -46,7 +48,7 @@ async def _update_user_mission_progress( async def update_mission_progress(self, user_id: str) -> None: user, user_missions, missions = await asyncio.gather( - UserService.get_user_info(user_id=user_id), + User.get_user_info_by_user_id(user_id=user_id), self.get_user_missions(user_id=user_id), MissionInventory.all(), ) @@ -179,8 +181,8 @@ async def check_early_morning_posts(user_id: str) -> bool: @staticmethod async def check_cheese_total(user_id: str) -> bool: - user = await UserService.get_user_info(user_id=user_id) - cheese_amount = await CheeseService.get_cheese_balance(user.cheese_manager_id) + user = await User.get_user_info_by_user_id(user_id=user_id) + cheese_amount = await CheeseManager.get_total_cheese_amount_by_manager(cheese_manager_id=user.cheese_manager_id) return cheese_amount >= 50 @@ -243,16 +245,16 @@ async def process_reward( if item.item_category == "BADGE": for _ in range(quantity): - await BadgeService.add_badge(user_id=user_id, badge_code=item.item_code) + await Badge.create_by_user_id(user_id=user_id, badge_code=item.item_code) badge = await BadgeService.get_badge_info_by_badge_code(badge_code=item.item_code) badge_info.append(badge) elif item.item_category == "COLOR": for _ in range(quantity): - await ColorService.add_color(user_id=user_id, color_code=item.item_code) + await Color.create_by_user_id(user_id=user_id, color_code=item.item_code) elif item.item_category == "CHEESE": total_cheese += quantity - await CheeseService.add_cheese(cheese_manager_id=cheese_manager_id, amount=quantity) + await CheeseManager.add_cheese(cheese_manager_id=cheese_manager_id, amount=quantity) elif item.item_category == "POINT": total_exp += quantity await LevelService.add_exp(user_id=user_id, exp=quantity) @@ -328,7 +330,7 @@ async def _add_exp_and_cheese(user_id: str, cheese_manager_id: int, exp: int, ch await LevelService.add_exp(user_id=user_id, exp=exp) # 치즈 추가 - await CheeseService.add_cheese(cheese_manager_id=cheese_manager_id, amount=cheese) + await CheeseManager.add_cheese(cheese_manager_id=cheese_manager_id, amount=cheese) @staticmethod async def _create_reward_notice( diff --git a/app/services/payment_service.py b/app/services/payment_service.py index ae38b3b..ebf0836 100644 --- a/app/services/payment_service.py +++ b/app/services/payment_service.py @@ -1,10 +1,15 @@ from tortoise.exceptions import DoesNotExist, IntegrityError from tortoise.transactions import atomic +from app.common.constants.item_category import ItemCategory from app.common.exceptions.custom_exception import CustomException from app.common.exceptions.error_code import ErrorCode +from app.models.badge import Badge from app.models.cheese_manager import CheeseManager +from app.models.color import Color +from app.models.emotion import Emotion from app.models.item import ItemInventory, ItemInventoryProductInventory, ProductInventory +from app.models.user import User from app.services.badge_service import BadgeService from app.services.color_service import ColorService from app.services.emotion_service import EmotionService @@ -12,7 +17,7 @@ class PaymentService: @staticmethod - async def validate_payment( + async def _validate_payment( product_code: str, ) -> tuple[ProductInventory, list[ItemInventoryProductInventory]]: try: @@ -37,38 +42,42 @@ async def validate_payment( @atomic() async def process_cheese_payment( cls, - product: ProductInventory, - item_inventory_products: list[ItemInventoryProductInventory], + product_code: str, user_id: str, - cheese_manager_id: int, - ) -> None: - total_cheese = await CheeseManager.get_total_cheese_amount_by_manager(cheese_manager_id=cheese_manager_id) + ) -> str: + # 1. 제품 코드 검증 + product, item_inventory_products = await cls._validate_payment(product_code) + + # 2. 유저 정보 조회 및 치즈 잔액 조회 + user = await User.get_user_info_by_user_id(user_id=user_id) + total_cheese = await CheeseManager.get_total_cheese_amount_by_manager(cheese_manager_id=user.cheese_manager_id) + # 3. 치즈 결제 진행 total_required_cheese = product.price if total_cheese < total_required_cheese: raise CustomException(ErrorCode.NOT_ENOUGH_CHEESE) - try: - await CheeseManager.use_cheese(cheese_manager_id, int(total_required_cheese)) - except ValueError: - raise CustomException(ErrorCode.NOT_ENOUGH_CHEESE) + await CheeseManager.use_cheese(user.cheese_manager_id, int(total_required_cheese)) + # 4. 아이템 부여 try: for item_inventory_product in item_inventory_products: item: ItemInventory = await item_inventory_product.item_inventory quantity = item_inventory_product.quantity - if item.item_category == "BADGE": + if item.item_category == ItemCategory.BADGE: for _ in range(quantity): - await BadgeService.add_badge(user_id=user_id, badge_code=item.item_code) - elif item.item_category == "COLOR": + await Badge.create_by_user_id(user_id=user_id, badge_code=item.item_code) + elif item.item_category == ItemCategory.COLOR: for _ in range(quantity): - await ColorService.add_color(user_id=user_id, color_code=item.item_code) - elif item.item_category == "EMOTION": + await Color.create_by_user_id(user_id=user_id, color_code=item.item_code) + elif item.item_category == ItemCategory.EMOTION: for _ in range(quantity): - await EmotionService.add_emotion(user_id=user_id, emotion_code=item.item_code) + await Emotion.create_by_user_id(user_id=user_id, emotion_code=item.item_code) else: raise CustomException(ErrorCode.INVALID_ITEM_CATEGORY) + + return product_code except IntegrityError: raise CustomException(ErrorCode.DUPLICATE_PURCHASE) diff --git a/app/services/user_service.py b/app/services/user_service.py index b7f3989..6934e12 100644 --- a/app/services/user_service.py +++ b/app/services/user_service.py @@ -8,8 +8,7 @@ class UserService: @staticmethod async def get_user_info(user_id: str) -> UserData: - result = await User.get_user_info_by_user_id(user_id=user_id) - return UserData(**result) + return await User.get_user_info_by_user_id(user_id=user_id) @classmethod async def get_user_profile(cls, user_id: str) -> UserProfileData: From 17a8812beb961a7b8c47e862628a0c4b1604d454 Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Tue, 10 Jun 2025 18:05:49 +0900 Subject: [PATCH 10/27] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20teller?= =?UTF-8?q?=20card=20router=20DTO=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/apis/v2/teller_card_router.py | 33 ++++++++++---------- app/dtos/badge/badge_dto.py | 4 +++ app/dtos/cheese/cheese_response.py | 5 +++ app/dtos/color/color_dto.py | 4 +++ app/dtos/color/colors_response.py | 3 ++ app/dtos/emotion/emotion_dto.py | 4 +++ app/dtos/emotion/emotions_response.py | 3 ++ app/dtos/payment/payment_request.py | 4 +++ app/dtos/payment/payment_response.py | 5 +++ app/dtos/teller_card/request.py | 9 ------ app/dtos/teller_card/response.py | 21 ------------- app/dtos/teller_card/teller_card_data.py | 9 ++++++ app/dtos/teller_card/teller_card_dto.py | 15 +++------ app/dtos/teller_card/teller_card_request.py | 13 ++++++++ app/dtos/teller_card/teller_card_response.py | 8 +++++ app/models/teller_card.py | 8 +++-- app/services/teller_card_service.py | 19 ++++++++--- 17 files changed, 103 insertions(+), 64 deletions(-) delete mode 100644 app/dtos/teller_card/request.py delete mode 100644 app/dtos/teller_card/response.py create mode 100644 app/dtos/teller_card/teller_card_data.py create mode 100644 app/dtos/teller_card/teller_card_request.py create mode 100644 app/dtos/teller_card/teller_card_response.py diff --git a/app/apis/v2/teller_card_router.py b/app/apis/v2/teller_card_router.py index e8bfcac..1007fcc 100644 --- a/app/apis/v2/teller_card_router.py +++ b/app/apis/v2/teller_card_router.py @@ -1,7 +1,7 @@ from fastapi import APIRouter, status -from app.dtos.teller_card.request import TellerCardRequestDTO -from app.dtos.teller_card.response import TellerCardResponseDTO +from app.dtos.teller_card.teller_card_request import TellerCardRequest +from app.dtos.teller_card.teller_card_response import TellerCardResponse from app.services.teller_card_service import TellerCardService teller_card_router = APIRouter(prefix="/tellercard", tags=["TellerCard"]) @@ -9,20 +9,21 @@ @teller_card_router.post( "", - response_model=TellerCardResponseDTO, + response_model=TellerCardResponse, status_code=status.HTTP_200_OK, ) async def patch_teller_card_handler( - body: TellerCardRequestDTO, -) -> TellerCardResponseDTO: - user_id = body.user_id - badge_code = body.badgeCode - color_code = body.colorCode - - await TellerCardService.validate_teller_card(badge_code=badge_code, color_code=color_code) - - await TellerCardService.patch_teller_card(user_id=user_id, badge_code=badge_code, color_code=color_code) - - teller_card = await TellerCardService.get_teller_card(user_id=user_id) - - return TellerCardResponseDTO.builder(teller_card=teller_card) + teller_card_request: TellerCardRequest, +) -> TellerCardResponse: + return TellerCardResponse( + code=status.HTTP_200_OK, + message="success", + data=await TellerCardService.patch_teller_card( + user_id=teller_card_request.user_id, + badge_code=teller_card_request.badgeCode, + color_code=teller_card_request.colorCode, + ), + ) + + +# todo : teller card, mission, mobile refactoring 및 purchase 삭제 및 테스트 코드 작성 diff --git a/app/dtos/badge/badge_dto.py b/app/dtos/badge/badge_dto.py index 6634af7..c51a00e 100644 --- a/app/dtos/badge/badge_dto.py +++ b/app/dtos/badge/badge_dto.py @@ -2,8 +2,12 @@ from pydantic import BaseModel +from app.dtos.frozen_config import FROZEN_CONFIG + class BadgeDTO(BaseModel): + model_config = FROZEN_CONFIG + badgeCode: str badgeName: str badgeMiddleName: str diff --git a/app/dtos/cheese/cheese_response.py b/app/dtos/cheese/cheese_response.py index 178d52e..3d3b2a8 100644 --- a/app/dtos/cheese/cheese_response.py +++ b/app/dtos/cheese/cheese_response.py @@ -1,11 +1,16 @@ from pydantic import BaseModel from app.dtos.base_response import BaseResponseDTO +from app.dtos.frozen_config import FROZEN_CONFIG class TotalCheeseAmount(BaseModel): + model_config = FROZEN_CONFIG + cheeseBalance: int class CheeseResponse(BaseResponseDTO): + model_config = FROZEN_CONFIG + data: TotalCheeseAmount diff --git a/app/dtos/color/color_dto.py b/app/dtos/color/color_dto.py index 34f0732..1adfc41 100644 --- a/app/dtos/color/color_dto.py +++ b/app/dtos/color/color_dto.py @@ -1,7 +1,11 @@ from pydantic import BaseModel +from app.dtos.frozen_config import FROZEN_CONFIG + class ColorDTO(BaseModel): + model_config = FROZEN_CONFIG + colorCode: str colorName: str colorHexCode: str diff --git a/app/dtos/color/colors_response.py b/app/dtos/color/colors_response.py index e00f3e6..8538905 100644 --- a/app/dtos/color/colors_response.py +++ b/app/dtos/color/colors_response.py @@ -1,6 +1,9 @@ from app.dtos.base_response import BaseResponseDTO from app.dtos.color.color_dto import ColorDTO +from app.dtos.frozen_config import FROZEN_CONFIG class ColorsResponse(BaseResponseDTO): + model_config = FROZEN_CONFIG + data: list[ColorDTO] diff --git a/app/dtos/emotion/emotion_dto.py b/app/dtos/emotion/emotion_dto.py index c537095..207fa83 100644 --- a/app/dtos/emotion/emotion_dto.py +++ b/app/dtos/emotion/emotion_dto.py @@ -1,5 +1,9 @@ from pydantic import BaseModel +from app.dtos.frozen_config import FROZEN_CONFIG + class EmotionDTO(BaseModel): + model_config = FROZEN_CONFIG + emotionList: list[int] diff --git a/app/dtos/emotion/emotions_response.py b/app/dtos/emotion/emotions_response.py index fe87c88..fc31fb0 100644 --- a/app/dtos/emotion/emotions_response.py +++ b/app/dtos/emotion/emotions_response.py @@ -1,6 +1,9 @@ from app.dtos.base_response import BaseResponseDTO from app.dtos.emotion.emotion_dto import EmotionDTO +from app.dtos.frozen_config import FROZEN_CONFIG class EmotionsResponse(BaseResponseDTO): + model_config = FROZEN_CONFIG + data: EmotionDTO diff --git a/app/dtos/payment/payment_request.py b/app/dtos/payment/payment_request.py index 0ef40de..4fa3ec6 100644 --- a/app/dtos/payment/payment_request.py +++ b/app/dtos/payment/payment_request.py @@ -1,6 +1,10 @@ from pydantic import BaseModel +from app.dtos.frozen_config import FROZEN_CONFIG + class PaymentRequest(BaseModel): + model_config = FROZEN_CONFIG + user_id: str productCode: str diff --git a/app/dtos/payment/payment_response.py b/app/dtos/payment/payment_response.py index 14066fe..9c2bd24 100644 --- a/app/dtos/payment/payment_response.py +++ b/app/dtos/payment/payment_response.py @@ -1,11 +1,16 @@ from pydantic import BaseModel from app.dtos.base_response import BaseResponseDTO +from app.dtos.frozen_config import FROZEN_CONFIG class ProductDTO(BaseModel): + model_config = FROZEN_CONFIG + product_code: str class PaymentResponse(BaseResponseDTO): + model_config = FROZEN_CONFIG + data: ProductDTO diff --git a/app/dtos/teller_card/request.py b/app/dtos/teller_card/request.py deleted file mode 100644 index fecc72e..0000000 --- a/app/dtos/teller_card/request.py +++ /dev/null @@ -1,9 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel - - -class TellerCardRequestDTO(BaseModel): - user_id: str - colorCode: Optional[str] = None - badgeCode: Optional[str] = None diff --git a/app/dtos/teller_card/response.py b/app/dtos/teller_card/response.py deleted file mode 100644 index 677e98d..0000000 --- a/app/dtos/teller_card/response.py +++ /dev/null @@ -1,21 +0,0 @@ -from pydantic import BaseModel - -from app.dtos.base_response import BaseResponseDTO -from app.dtos.teller_card.teller_card_dto import TellerCardDTO as TellerCardLogicDTO - - -class TellerCardDTO(BaseModel): - colorCode: str - badgeCode: str - - -class TellerCardResponseDTO(BaseResponseDTO): - data: TellerCardDTO - - @classmethod - def builder(cls, teller_card: TellerCardLogicDTO) -> "TellerCardResponseDTO": - return cls( - code=200, - message="success", - data=TellerCardDTO(colorCode=teller_card.colorCode, badgeCode=teller_card.badgeCode), - ) diff --git a/app/dtos/teller_card/teller_card_data.py b/app/dtos/teller_card/teller_card_data.py new file mode 100644 index 0000000..dafa8ba --- /dev/null +++ b/app/dtos/teller_card/teller_card_data.py @@ -0,0 +1,9 @@ +import dataclasses + + +@dataclasses.dataclass(frozen=True) +class TellerCardData: + activate_color_code: str + activate_badge_code: str + badge_name: str + badge_middle_name: str diff --git a/app/dtos/teller_card/teller_card_dto.py b/app/dtos/teller_card/teller_card_dto.py index 529dcb4..bd8c53e 100644 --- a/app/dtos/teller_card/teller_card_dto.py +++ b/app/dtos/teller_card/teller_card_dto.py @@ -1,17 +1,12 @@ from pydantic import BaseModel +from app.dtos.frozen_config import FROZEN_CONFIG + class TellerCardDTO(BaseModel): + model_config = FROZEN_CONFIG + + colorCode: str badgeCode: str badgeName: str badgeMiddleName: str - colorCode: str - - @classmethod - def builder(cls, teller_card_raw: dict[str, str]) -> "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/app/dtos/teller_card/teller_card_request.py b/app/dtos/teller_card/teller_card_request.py new file mode 100644 index 0000000..b3c4746 --- /dev/null +++ b/app/dtos/teller_card/teller_card_request.py @@ -0,0 +1,13 @@ +from typing import Optional + +from pydantic import BaseModel + +from app.dtos.frozen_config import FROZEN_CONFIG + + +class TellerCardRequest(BaseModel): + model_config = FROZEN_CONFIG + + user_id: str + colorCode: str | None = None + badgeCode: str | None = None diff --git a/app/dtos/teller_card/teller_card_response.py b/app/dtos/teller_card/teller_card_response.py new file mode 100644 index 0000000..b5326ad --- /dev/null +++ b/app/dtos/teller_card/teller_card_response.py @@ -0,0 +1,8 @@ +from pydantic import BaseModel + +from app.dtos.base_response import BaseResponseDTO +from app.dtos.teller_card.teller_card_dto import TellerCardDTO + + +class TellerCardResponse(BaseResponseDTO): + data: TellerCardDTO diff --git a/app/models/teller_card.py b/app/models/teller_card.py index bae12d9..3c070b8 100644 --- a/app/models/teller_card.py +++ b/app/models/teller_card.py @@ -4,6 +4,7 @@ from tortoise.models import Model from app.common.utils.query_executor import QueryExecutor +from app.dtos.teller_card.teller_card_data import TellerCardData from app.queries.teller_card_query import ( PATCH_TELLER_CARD_QUERY, SELECT_TELLER_CARD_INFO_BY_USER_UUID_QUERY, @@ -19,14 +20,15 @@ class Meta: table = "teller_card" @classmethod - async def get_teller_card_info_by_user_id(cls, user_id: str) -> Any: # type ignore + async def get_teller_card_info_by_user_id(cls, user_id: str) -> TellerCardData: # type ignore query = SELECT_TELLER_CARD_INFO_BY_USER_UUID_QUERY value = user_id - return await QueryExecutor.execute_query(query, values=value, fetch_type="single") # type ignore + result = await QueryExecutor.execute_query(query, values=value, fetch_type="single") # type ignore + return TellerCardData(**result) @classmethod async def patch_teller_card_info_by_user_id( - cls, user_id: str, badge_code: Optional[str] = None, color_code: Optional[str] = None + cls, user_id: str, badge_code: str | None = None, color_code: str | None = None ) -> None: query = PATCH_TELLER_CARD_QUERY values = (badge_code, color_code, user_id) diff --git a/app/services/teller_card_service.py b/app/services/teller_card_service.py index 9f44558..38f0fb2 100644 --- a/app/services/teller_card_service.py +++ b/app/services/teller_card_service.py @@ -9,19 +9,28 @@ class TellerCardService: @classmethod async def get_teller_card(cls, user_id: str) -> TellerCardDTO: - teller_cards_raw: dict[str, str] = await TellerCard.get_teller_card_info_by_user_id(user_id=user_id) - return TellerCardDTO.builder(teller_cards_raw) + teller_card = await TellerCard.get_teller_card_info_by_user_id(user_id=user_id) + return TellerCardDTO( + badgeCode=teller_card.activate_badge_code, + badgeName=teller_card.badge_name, + badgeMiddleName=teller_card.badge_middle_name, + colorCode=teller_card.activate_color_code, + ) @classmethod async def patch_teller_card( - cls, user_id: str, badge_code: Optional[str] = None, color_code: Optional[str] = None - ) -> None: + cls, user_id: str, badge_code: str | None = None, color_code: str | None = None + ) -> TellerCardDTO: + await cls._validate_teller_card(badge_code=badge_code, color_code=color_code) + await TellerCard.patch_teller_card_info_by_user_id( user_id=user_id, badge_code=badge_code, color_code=color_code ) + return await cls.get_teller_card(user_id=user_id) + @classmethod - async def validate_teller_card(cls, badge_code: Optional[str], color_code: Optional[str]) -> None: + async def _validate_teller_card(cls, badge_code: str | None, color_code: str | None) -> None: badge_code_list = await BadgeInventory.all().values("badge_code") color_code_list = await ColorInventory.all().values("color_code") badge_codes = [badge["badge_code"] for badge in badge_code_list] From 1cef705453555b00282fe4613ac561b8d1bfb95f Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Tue, 10 Jun 2025 19:00:49 +0900 Subject: [PATCH 11/27] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20mobile?= =?UTF-8?q?=20router=20DTO=20=EA=B5=AC=EC=A1=B0=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/apis/v2/cheese_router.py | 1 - app/apis/v2/mission_router.py | 3 ++ app/apis/v2/mobile_router.py | 37 ++++++++---------- app/apis/v2/payment_router.py | 3 +- app/apis/v2/teller_card_router.py | 3 -- app/core/configs/base_settings.py | 5 +++ app/dtos/answer/__init__.py | 0 app/dtos/answer/answer_data.py | 20 ++++++++++ app/dtos/badge/badges_response.py | 2 +- app/dtos/level/level_data.py | 8 ++++ app/dtos/level/level_dto.py | 28 +++---------- app/dtos/level/level_info_dto.py | 11 ++++++ app/dtos/mobile/data_dto.py | 17 ++++++++ app/dtos/mobile/mypage_response.py | 26 +++---------- app/dtos/mobile/teller_card_response.py | 39 +++---------------- .../mobile/user_profile_with_level_dto.py | 12 ++++++ app/dtos/teller_card/teller_card_request.py | 2 - app/dtos/teller_card/teller_card_response.py | 5 ++- app/dtos/user/user_info_dto.py | 11 ++---- app/dtos/user/user_profile_dto.py | 25 ++---------- app/models/answer.py | 6 ++- app/models/cheese_manager.py | 23 ++++++----- app/models/emotion.py | 2 - app/models/level.py | 8 ++-- app/models/teller_card.py | 2 - app/services/answer_service.py | 10 ++--- app/services/badge_service.py | 2 +- app/services/color_service.py | 2 +- app/services/emotion_service.py | 3 -- app/services/level_service.py | 29 ++++++-------- app/services/mission_service.py | 9 ++--- app/services/payment_service.py | 3 -- app/services/purchase_service.py | 2 +- app/services/teller_card_service.py | 2 - app/services/user_service.py | 4 +- 35 files changed, 165 insertions(+), 200 deletions(-) create mode 100644 app/dtos/answer/__init__.py create mode 100644 app/dtos/answer/answer_data.py create mode 100644 app/dtos/level/level_data.py create mode 100644 app/dtos/level/level_info_dto.py create mode 100644 app/dtos/mobile/data_dto.py create mode 100644 app/dtos/mobile/user_profile_with_level_dto.py diff --git a/app/apis/v2/cheese_router.py b/app/apis/v2/cheese_router.py index 74c359f..b7922eb 100644 --- a/app/apis/v2/cheese_router.py +++ b/app/apis/v2/cheese_router.py @@ -2,7 +2,6 @@ from app.dtos.cheese.cheese_response import CheeseResponse, TotalCheeseAmount from app.models.cheese_manager import CheeseManager - from app.services.user_service import UserService cheese_router = APIRouter(prefix="/cheese", tags=["Cheese"]) diff --git a/app/apis/v2/mission_router.py b/app/apis/v2/mission_router.py index 6781ba7..db8465d 100644 --- a/app/apis/v2/mission_router.py +++ b/app/apis/v2/mission_router.py @@ -24,3 +24,6 @@ async def mission_handler_direct( "message": "success", "data": True, } + + +# todo : mission refactoring 및 purchase 삭제 및 테스트 코드 작성 diff --git a/app/apis/v2/mobile_router.py b/app/apis/v2/mobile_router.py index 9b4a3f0..a069e29 100644 --- a/app/apis/v2/mobile_router.py +++ b/app/apis/v2/mobile_router.py @@ -2,14 +2,15 @@ from fastapi import APIRouter, status -from app.dtos.mobile.mypage_response import MyPageResponseDTO, UserProfileWithLevel -from app.dtos.mobile.teller_card_response import DataDTO, TellerCardResponseDTO +from app.dtos.mobile.data_dto import DataDTO +from app.dtos.mobile.mypage_response import MyPageResponse +from app.dtos.mobile.teller_card_response import TellerCardResponse +from app.dtos.mobile.user_profile_with_level_dto import UserProfileWithLevelDTO from app.dtos.user.user_info_dto import UserInfoDTO from app.dtos.user.user_profile_dto import UserProfileDTO from app.models.cheese_manager import CheeseManager from app.services.answer_service import AnswerService from app.services.badge_service import BadgeService - from app.services.color_service import ColorService from app.services.level_service import LevelService from app.services.teller_card_service import TellerCardService @@ -20,31 +21,27 @@ @mobile_router.get( "/tellercard", - response_model=TellerCardResponseDTO, + response_model=TellerCardResponse, status_code=status.HTTP_200_OK, ) -async def mobile_teller_card_handler(user_id: str) -> TellerCardResponseDTO: +async def mobile_teller_card_handler(user_id: str) -> TellerCardResponse: badges_task = BadgeService.get_badges_with_details_by_user_id(user_id) colors_task = ColorService.get_colors_with_details_by_user_id(user_id) level_info_task = LevelService.get_level_info_add_answer_days(user_id) teller_card_task = TellerCardService.get_teller_card(user_id) user_info_task = UserService.get_user_info(user_id) - record_answer_task = AnswerService.get_answer_record(user_id=user_id) + record_answer_task = AnswerService.get_answer_record(user_id) badges, colors, level_info, teller_card, user, record_count = await asyncio.gather( badges_task, colors_task, level_info_task, teller_card_task, user_info_task, record_answer_task ) - cheese_amount = await CheeseManager.get_total_cheese_amount_by_manager(cheese_manager_id=user.cheese_manager_id) - user_info = UserInfoDTO(nickname=user.nickname, cheeseBalance=cheese_amount, tellerCard=teller_card) - data = DataDTO.builder( - badges=badges, colors=colors, userInfo=user_info, levelInfo=level_info, recordCount=record_count - ) + data = DataDTO(badges=badges, colors=colors, userInfo=user_info, levelInfo=level_info, recordCount=record_count) - return TellerCardResponseDTO( + return TellerCardResponse( code=status.HTTP_200_OK, data=data, message="teller_card ui page", @@ -53,10 +50,10 @@ async def mobile_teller_card_handler(user_id: str) -> TellerCardResponseDTO: @mobile_router.get( "/mypage", - response_model=MyPageResponseDTO, + response_model=MyPageResponse, status_code=status.HTTP_200_OK, ) -async def mobile_my_page_handler(user_id: str) -> MyPageResponseDTO: +async def mobile_my_page_handler(user_id: str) -> MyPageResponse: user, answer_count, badge_count, teller_card, level = await asyncio.gather( UserService.get_user_profile(user_id=user_id), @@ -68,20 +65,20 @@ async def mobile_my_page_handler(user_id: str) -> MyPageResponseDTO: cheese_amount = await CheeseManager.get_total_cheese_amount_by_manager(cheese_manager_id=user.cheese_manager_id) - user_profile_data = UserProfileWithLevel.builder( - userProfile=UserProfileDTO.builder( - nickname=user.nickname, # type: ignore + user_profile_data = UserProfileWithLevelDTO( + userProfile=UserProfileDTO( + nickname=user.nickname, cheeseBalance=cheese_amount, badgeCode=teller_card.badgeCode, badgeCount=badge_count, answerCount=answer_count, - premium=user.is_premium, # type: ignore - allow_notification=user.allow_notification, # type: ignore + premium=user.is_premium, + allowNotification=user.allow_notification, ), level=level, ) - return MyPageResponseDTO( + return MyPageResponse( code=status.HTTP_200_OK, message="정상처리되었습니다", data=user_profile_data, diff --git a/app/apis/v2/payment_router.py b/app/apis/v2/payment_router.py index d5b4256..a6196c9 100644 --- a/app/apis/v2/payment_router.py +++ b/app/apis/v2/payment_router.py @@ -1,9 +1,8 @@ -from fastapi import APIRouter, HTTPException, status +from fastapi import APIRouter, status from app.dtos.payment.payment_request import PaymentRequest from app.dtos.payment.payment_response import PaymentResponse, ProductDTO from app.services.payment_service import PaymentService -from app.services.user_service import UserService payment_router = APIRouter(prefix="/payment", tags=["Payment"]) diff --git a/app/apis/v2/teller_card_router.py b/app/apis/v2/teller_card_router.py index 1007fcc..9d72985 100644 --- a/app/apis/v2/teller_card_router.py +++ b/app/apis/v2/teller_card_router.py @@ -24,6 +24,3 @@ async def patch_teller_card_handler( color_code=teller_card_request.colorCode, ), ) - - -# todo : teller card, mission, mobile refactoring 및 purchase 삭제 및 테스트 코드 작성 diff --git a/app/core/configs/base_settings.py b/app/core/configs/base_settings.py index bfb1d7f..e31b7c0 100644 --- a/app/core/configs/base_settings.py +++ b/app/core/configs/base_settings.py @@ -1,5 +1,6 @@ import os from enum import StrEnum +from zoneinfo import ZoneInfo from pydantic_settings import BaseSettings @@ -29,3 +30,7 @@ class Config: @property def database_url(self) -> str: return f"mysql+asyncmy://{self.DB_USER}:{self.DB_PASSWORD}" f"@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}" + + @property + def db_zoneinfo(self) -> ZoneInfo: + return ZoneInfo(self.DB_TIMEZONE) diff --git a/app/dtos/answer/__init__.py b/app/dtos/answer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/dtos/answer/answer_data.py b/app/dtos/answer/answer_data.py new file mode 100644 index 0000000..eaf5ed3 --- /dev/null +++ b/app/dtos/answer/answer_data.py @@ -0,0 +1,20 @@ +import dataclasses +from datetime import date, datetime + + +@dataclasses.dataclass(frozen=True) +class AnswerData: + answer_id: int + content: str + created_time: datetime + date: date + emotion: int + is_premium: bool + is_public: bool + modified_time: datetime + user_id: bytes + is_blind: bool + like_count: int + is_spare: bool + blind_ended_at: datetime | None = None + blind_started_at: datetime | None = None diff --git a/app/dtos/badge/badges_response.py b/app/dtos/badge/badges_response.py index ccea426..55252ab 100644 --- a/app/dtos/badge/badges_response.py +++ b/app/dtos/badge/badges_response.py @@ -1,5 +1,5 @@ -from app.dtos.base_response import BaseResponseDTO from app.dtos.badge.badge_dto import BadgeDTO +from app.dtos.base_response import BaseResponseDTO from app.dtos.frozen_config import FROZEN_CONFIG diff --git a/app/dtos/level/level_data.py b/app/dtos/level/level_data.py new file mode 100644 index 0000000..ead8b79 --- /dev/null +++ b/app/dtos/level/level_data.py @@ -0,0 +1,8 @@ +import dataclasses + + +@dataclasses.dataclass(frozen=True) +class LevelData: + level_exp: int + level_level: int + required_exp: int diff --git a/app/dtos/level/level_dto.py b/app/dtos/level/level_dto.py index b9e8e25..c099628 100644 --- a/app/dtos/level/level_dto.py +++ b/app/dtos/level/level_dto.py @@ -1,29 +1,11 @@ -from typing import Any - from pydantic import BaseModel +from app.dtos.frozen_config import FROZEN_CONFIG + class LevelDTO(BaseModel): + model_config = FROZEN_CONFIG + level: int currentExp: int - requiredExp: int | None = None - - @classmethod - def builder(cls, level: dict[str, Any]) -> "LevelDTO": - return cls( - level=level["level_level"], - currentExp=level["level_exp"], - requiredExp=level["required_exp"], - ) - - -class LevelInfoDTO(BaseModel): - levelDto: LevelDTO - daysToLevelUp: int - - @classmethod - def builder(cls, level_dto: LevelDTO, days_to_level_up: int) -> "LevelInfoDTO": - return cls( - levelDto=level_dto, - daysToLevelUp=days_to_level_up, - ) + requiredExp: int diff --git a/app/dtos/level/level_info_dto.py b/app/dtos/level/level_info_dto.py new file mode 100644 index 0000000..79e79bb --- /dev/null +++ b/app/dtos/level/level_info_dto.py @@ -0,0 +1,11 @@ +from pydantic import BaseModel + +from app.dtos.frozen_config import FROZEN_CONFIG +from app.dtos.level.level_dto import LevelDTO + + +class LevelInfoDTO(BaseModel): + model_config = FROZEN_CONFIG + + levelDto: LevelDTO + daysToLevelUp: int diff --git a/app/dtos/mobile/data_dto.py b/app/dtos/mobile/data_dto.py new file mode 100644 index 0000000..0eed6fb --- /dev/null +++ b/app/dtos/mobile/data_dto.py @@ -0,0 +1,17 @@ +from pydantic import BaseModel + +from app.dtos.badge.badge_dto import BadgeDTO +from app.dtos.color.color_dto import ColorDTO +from app.dtos.frozen_config import FROZEN_CONFIG +from app.dtos.level.level_info_dto import LevelInfoDTO +from app.dtos.user.user_info_dto import UserInfoDTO + + +class DataDTO(BaseModel): + model_config = FROZEN_CONFIG + + badges: list[BadgeDTO] + colors: list[ColorDTO] + userInfo: UserInfoDTO + levelInfo: LevelInfoDTO + recordCount: int = 0 diff --git a/app/dtos/mobile/mypage_response.py b/app/dtos/mobile/mypage_response.py index e3dd6e2..dfdfb19 100644 --- a/app/dtos/mobile/mypage_response.py +++ b/app/dtos/mobile/mypage_response.py @@ -1,25 +1,9 @@ -from pydantic import BaseModel - from app.dtos.base_response import BaseResponseDTO -from app.dtos.level.level_dto import LevelInfoDTO -from app.dtos.user.user_profile_dto import UserProfileDTO - - -class UserProfileWithLevel(BaseModel): - userProfile: UserProfileDTO - level: LevelInfoDTO +from app.dtos.frozen_config import FROZEN_CONFIG +from app.dtos.mobile.user_profile_with_level_dto import UserProfileWithLevelDTO - @classmethod - def builder( - cls, - userProfile: UserProfileDTO, - level: LevelInfoDTO, - ) -> "UserProfileWithLevel": - return cls( - userProfile=userProfile, - level=level, - ) +class MyPageResponse(BaseResponseDTO): + model_config = FROZEN_CONFIG -class MyPageResponseDTO(BaseResponseDTO): - data: UserProfileWithLevel + data: UserProfileWithLevelDTO diff --git a/app/dtos/mobile/teller_card_response.py b/app/dtos/mobile/teller_card_response.py index 7604dde..f1e3b5b 100644 --- a/app/dtos/mobile/teller_card_response.py +++ b/app/dtos/mobile/teller_card_response.py @@ -1,39 +1,10 @@ -from typing import Optional - -from pydantic import BaseModel - from app.dtos.base_response import BaseResponseDTO -from app.dtos.badge.badge_dto import BadgeDTO -from app.dtos.color.color_dto import ColorDTO -from app.dtos.level.level_dto import LevelInfoDTO -from app.dtos.user.user_info_dto import UserInfoDTO - - -class DataDTO(BaseModel): - badges: list[BadgeDTO] - colors: list[ColorDTO] - userInfo: UserInfoDTO - levelInfo: LevelInfoDTO - recordCount: int = 0 - - @classmethod - def builder( - cls, - badges: list[BadgeDTO], - colors: list[ColorDTO], - userInfo: UserInfoDTO, - levelInfo: LevelInfoDTO, - recordCount: Optional[int] = None, - ) -> "DataDTO": - return cls( - badges=badges, - colors=colors, - userInfo=userInfo, - levelInfo=levelInfo, - recordCount=recordCount if recordCount is not None else 0, - ) +from app.dtos.frozen_config import FROZEN_CONFIG +from app.dtos.mobile.data_dto import DataDTO # 최종 응답 DTO -class TellerCardResponseDTO(BaseResponseDTO): +class TellerCardResponse(BaseResponseDTO): + model_config = FROZEN_CONFIG + data: DataDTO diff --git a/app/dtos/mobile/user_profile_with_level_dto.py b/app/dtos/mobile/user_profile_with_level_dto.py new file mode 100644 index 0000000..38da123 --- /dev/null +++ b/app/dtos/mobile/user_profile_with_level_dto.py @@ -0,0 +1,12 @@ +from pydantic import BaseModel + +from app.dtos.frozen_config import FROZEN_CONFIG +from app.dtos.level.level_info_dto import LevelInfoDTO +from app.dtos.user.user_profile_dto import UserProfileDTO + + +class UserProfileWithLevelDTO(BaseModel): + model_config = FROZEN_CONFIG + + userProfile: UserProfileDTO + level: LevelInfoDTO diff --git a/app/dtos/teller_card/teller_card_request.py b/app/dtos/teller_card/teller_card_request.py index b3c4746..f431891 100644 --- a/app/dtos/teller_card/teller_card_request.py +++ b/app/dtos/teller_card/teller_card_request.py @@ -1,5 +1,3 @@ -from typing import Optional - from pydantic import BaseModel from app.dtos.frozen_config import FROZEN_CONFIG diff --git a/app/dtos/teller_card/teller_card_response.py b/app/dtos/teller_card/teller_card_response.py index b5326ad..a757d75 100644 --- a/app/dtos/teller_card/teller_card_response.py +++ b/app/dtos/teller_card/teller_card_response.py @@ -1,8 +1,9 @@ -from pydantic import BaseModel - from app.dtos.base_response import BaseResponseDTO +from app.dtos.frozen_config import FROZEN_CONFIG from app.dtos.teller_card.teller_card_dto import TellerCardDTO class TellerCardResponse(BaseResponseDTO): + model_config = FROZEN_CONFIG + data: TellerCardDTO diff --git a/app/dtos/user/user_info_dto.py b/app/dtos/user/user_info_dto.py index f0ada54..7de8b18 100644 --- a/app/dtos/user/user_info_dto.py +++ b/app/dtos/user/user_info_dto.py @@ -1,17 +1,12 @@ from pydantic import BaseModel +from app.dtos.frozen_config import FROZEN_CONFIG from app.dtos.teller_card.teller_card_dto import TellerCardDTO class UserInfoDTO(BaseModel): + model_config = FROZEN_CONFIG + nickname: str cheeseBalance: int tellerCard: TellerCardDTO - - @classmethod - def builder(cls, user_raw: dict[str, str], cheeseBalance: int, tellerCard: TellerCardDTO) -> "UserInfoDTO": - return cls( - nickname=user_raw.get("nickname", ""), - cheeseBalance=cheeseBalance, - tellerCard=tellerCard, - ) diff --git a/app/dtos/user/user_profile_dto.py b/app/dtos/user/user_profile_dto.py index 0aca6e3..0bf60b4 100644 --- a/app/dtos/user/user_profile_dto.py +++ b/app/dtos/user/user_profile_dto.py @@ -1,7 +1,11 @@ from pydantic import BaseModel +from app.dtos.frozen_config import FROZEN_CONFIG + class UserProfileDTO(BaseModel): + model_config = FROZEN_CONFIG + nickname: str badgeCode: str cheeseBalance: int @@ -9,24 +13,3 @@ class UserProfileDTO(BaseModel): answerCount: int premium: bool allowNotification: bool - - @classmethod - def builder( - cls, - nickname: str, - badgeCode: str, - cheeseBalance: int, - badgeCount: int, - answerCount: int, - premium: bool, - allow_notification: bool, - ) -> "UserProfileDTO": - return cls( - nickname=nickname, - badgeCode=badgeCode, - cheeseBalance=cheeseBalance, - badgeCount=badgeCount, - answerCount=answerCount, - premium=premium, - allowNotification=allow_notification, - ) diff --git a/app/models/answer.py b/app/models/answer.py index 2c41c2d..5b26a47 100644 --- a/app/models/answer.py +++ b/app/models/answer.py @@ -6,6 +6,7 @@ from tortoise.models import Model from app.common.utils.query_executor import QueryExecutor +from app.dtos.answer.answer_data import AnswerData from app.models.user import User from app.queries.answer_query import ( SELECT_ANSWER_BY_USER_UUID_QUERY, @@ -53,10 +54,11 @@ async def get_answer_count_by_user_id_v2(cls, user_id: str) -> Any: return await QueryExecutor.execute_query(query, values=value, fetch_type="single") @classmethod - async def find_all_by_user(cls, user_id: str, start_date: datetime, end_date: datetime) -> Any: + async def get_all_by_user_id(cls, user_id: str, start_date: datetime, end_date: datetime) -> list[AnswerData]: query = SELECT_ANSWER_BY_USER_UUID_QUERY values = (user_id, start_date, end_date) - return await QueryExecutor.execute_query(query, values=values, fetch_type="multiple") + results = await QueryExecutor.execute_query(query, values=values, fetch_type="multiple") + return [AnswerData(**row) for row in results] @classmethod async def get_most_recent_answer_by_user_id(cls, user_id: str) -> Any: diff --git a/app/models/cheese_manager.py b/app/models/cheese_manager.py index 35e36d1..72ed97a 100644 --- a/app/models/cheese_manager.py +++ b/app/models/cheese_manager.py @@ -1,10 +1,11 @@ +from typing import cast + from tortoise import fields from tortoise.expressions import Q from tortoise.fields import ForeignKeyRelation from tortoise.functions import Sum from tortoise.models import Model - from app.models.cheese_status import CheeseStatus @@ -80,15 +81,19 @@ class Meta: @classmethod async def get_total_amount_by_manager(cls, manager_id: int) -> int: - result = await ( - cls.filter( - Q(status=CheeseStatus.CAN_USE) | Q(status=CheeseStatus.USING), - cheese_manager_id=manager_id, - ) - .annotate(total=Sum("current_amount")) - .values_list("total", flat=True) + result = cast( + list[int | None], + await ( + cls.filter( + Q(status=CheeseStatus.CAN_USE) | Q(status=CheeseStatus.USING), + cheese_manager_id=manager_id, + ) + .annotate(total=Sum("current_amount")) + .values_list("total", flat=True) + ), ) - return int(result[0]) if result and result[0] is not None else 0 + + return result[0] if result and result[0] is not None else 0 @classmethod async def get_using_cheeses(cls, manager_id: int) -> list["CheeseHistory"]: diff --git a/app/models/emotion.py b/app/models/emotion.py index c2b64a0..7c0020e 100644 --- a/app/models/emotion.py +++ b/app/models/emotion.py @@ -1,5 +1,3 @@ -from typing import Any - from tortoise import fields, models from tortoise.fields import ForeignKeyRelation diff --git a/app/models/level.py b/app/models/level.py index ba6ff59..2d16661 100644 --- a/app/models/level.py +++ b/app/models/level.py @@ -1,9 +1,8 @@ -from typing import Any - from tortoise import fields from tortoise.models import Model from app.common.utils.query_executor import QueryExecutor +from app.dtos.level.level_data import LevelData from app.queries.level_query import ( SELECT_USER_LEVEL_AND_REQUIRED_EXP_QUERY, UPDATE_USER_LEVEL_AND_EXP_QUERY, @@ -19,10 +18,11 @@ class Meta: table = "level" @classmethod - async def get_level_info(cls, user_id: str) -> Any: + async def get_level_info(cls, user_id: str) -> LevelData: query = SELECT_USER_LEVEL_AND_REQUIRED_EXP_QUERY value = user_id - return await QueryExecutor.execute_query(query, values=(value,), fetch_type="single") + result = await QueryExecutor.execute_query(query, values=(value,), fetch_type="single") + return LevelData(**result) @classmethod async def update_level_and_exp(cls, user_id: str, new_level: int, new_exp: int) -> None: diff --git a/app/models/teller_card.py b/app/models/teller_card.py index 3c070b8..66e39af 100644 --- a/app/models/teller_card.py +++ b/app/models/teller_card.py @@ -1,5 +1,3 @@ -from typing import Any, Optional - from tortoise import fields from tortoise.models import Model diff --git a/app/services/answer_service.py b/app/services/answer_service.py index 9746ebd..dcc1f10 100644 --- a/app/services/answer_service.py +++ b/app/services/answer_service.py @@ -1,7 +1,7 @@ from datetime import datetime, timedelta from typing import Any -from zoneinfo import ZoneInfo +from app.core.configs import settings from app.models.answer import Answer @@ -28,9 +28,7 @@ async def get_answer_count_v2(cls, user_id: str) -> int: @classmethod async def get_answer_record(cls, user_id: str) -> int: - - seoul_tz = ZoneInfo("Asia/Seoul") - now = datetime.now(seoul_tz) + now = datetime.now(settings.db_zoneinfo) if now.hour < 6: now -= timedelta(days=1) @@ -38,14 +36,14 @@ async def get_answer_record(cls, user_id: str) -> int: end_date = now start_date = end_date - timedelta(days=100) - all_answers = await Answer.find_all_by_user(user_id, start_date, end_date) + all_answers = await Answer.get_all_by_user_id(user_id, start_date, end_date) record = 0 target_date = end_date if all_answers: for answer in all_answers: - answer_date = answer["date"] + answer_date = answer.date if answer_date == target_date.date(): # 날짜만 비교 record += 1 diff --git a/app/services/badge_service.py b/app/services/badge_service.py index a354613..8722646 100644 --- a/app/services/badge_service.py +++ b/app/services/badge_service.py @@ -1,5 +1,5 @@ -from app.dtos.badge.badge_dto import BadgeDTO from app.dtos.badge.badge_code_dto import BadgeCodeDTO +from app.dtos.badge.badge_dto import BadgeDTO from app.models.badge import Badge, BadgeInventory diff --git a/app/services/color_service.py b/app/services/color_service.py index b5667f2..c8ac22f 100644 --- a/app/services/color_service.py +++ b/app/services/color_service.py @@ -1,5 +1,5 @@ -from app.dtos.color.color_dto import ColorDTO from app.dtos.color.color_code_dto import ColorCodeDTO +from app.dtos.color.color_dto import ColorDTO from app.models.color import Color from app.models.color_inventory import ColorInventory from app.models.user import User diff --git a/app/services/emotion_service.py b/app/services/emotion_service.py index 038bf88..8544365 100644 --- a/app/services/emotion_service.py +++ b/app/services/emotion_service.py @@ -1,11 +1,8 @@ -from typing import Any - from app.common.constants.emotion_dict import EMOTION_DICT from app.dtos.emotion.emotion_data import EmotionData from app.dtos.emotion.emotion_dto import EmotionDTO from app.models.emotion import Emotion, EmotionInventory from app.models.user import User -from app.services.user_service import UserService class EmotionService: diff --git a/app/services/level_service.py b/app/services/level_service.py index 8652ab5..3120d75 100644 --- a/app/services/level_service.py +++ b/app/services/level_service.py @@ -1,38 +1,33 @@ -from fastapi import HTTPException - -from app.dtos.level.level_dto import LevelDTO, LevelInfoDTO +from app.dtos.level.level_dto import LevelDTO +from app.dtos.level.level_info_dto import LevelInfoDTO from app.models.level import Level from app.services.answer_service import AnswerService class LevelService: @classmethod - async def get_level_info(cls, user_id: str) -> LevelDTO: - level_data = await Level.get_level_info(user_id=user_id) - if level_data is None: - raise HTTPException(status_code=404, detail="Level info not found") - return LevelDTO.builder(level=level_data) + async def get_level(cls, user_id: str) -> LevelDTO: + level = await Level.get_level_info(user_id=user_id) + return LevelDTO(level=level.level_level, requiredExp=level.required_exp, currentExp=level.level_exp) @classmethod async def get_level_info_add_answer_days(cls, user_id: str) -> LevelInfoDTO: - level_dto = await cls.get_level_info(user_id=user_id) - - if level_dto.requiredExp is None: - raise ValueError("Required experience cannot be None") + level_dto = await cls.get_level(user_id=user_id) needs_to_level_up = await cls.calculate_days_to_level_up( user_id=user_id, current_exp=level_dto.currentExp, required_exp=level_dto.requiredExp, ) - return LevelInfoDTO.builder( - level_dto=await cls.get_level_info(user_id=user_id), - days_to_level_up=needs_to_level_up, + + return LevelInfoDTO( + levelDto=await cls.get_level(user_id=user_id), + daysToLevelUp=needs_to_level_up, ) @classmethod async def level_up(cls, user_id: str) -> int: - level_dto = await cls.get_level_info(user_id=user_id) + level_dto = await cls.get_level(user_id=user_id) level = level_dto.level current_exp = level_dto.currentExp @@ -51,7 +46,7 @@ async def level_up(cls, user_id: str) -> int: @classmethod async def add_exp(cls, user_id: str, exp: int) -> None: - level_dto = await cls.get_level_info(user_id=user_id) + level_dto = await cls.get_level(user_id=user_id) current_exp = level_dto.currentExp new_exp = current_exp + exp diff --git a/app/services/mission_service.py b/app/services/mission_service.py index cbf523c..f124e24 100644 --- a/app/services/mission_service.py +++ b/app/services/mission_service.py @@ -1,12 +1,12 @@ import asyncio from datetime import datetime, timedelta, timezone from typing import Optional -from zoneinfo import ZoneInfo from fastapi import HTTPException from tortoise.exceptions import DoesNotExist from tortoise.transactions import atomic +from app.core.configs import settings from app.dtos.mission.mission_dto import UserMissionDTO from app.dtos.mission.reward_dto import RewardDTO from app.models.badge import Badge @@ -18,8 +18,6 @@ from app.models.user import User from app.services.answer_service import AnswerService from app.services.badge_service import BadgeService - -from app.services.color_service import ColorService from app.services.level_service import LevelService from app.services.notice_service import NoticeService @@ -53,7 +51,7 @@ async def update_mission_progress(self, user_id: str) -> None: MissionInventory.all(), ) - cheese_manager_id: int = user["cheese_manager_id"] + cheese_manager_id: int = user.cheese_manager_id mission_dict = {mission.mission_code: mission for mission in missions} badge_missions, lv_up_mission, daily_missions = await self._classify_missions(user_missions) @@ -204,8 +202,7 @@ async def check_three_likes_different_posts(user_id: str) -> bool: @staticmethod async def check_daily_post(user_id: str) -> bool: - seoul_tz = ZoneInfo("Asia/Seoul") - now = datetime.now(seoul_tz) + now = datetime.now(settings.db_zoneinfo) current_date = (now - timedelta(days=1)).date() if now.hour < 6 else now.date() diff --git a/app/services/payment_service.py b/app/services/payment_service.py index ebf0836..445ed40 100644 --- a/app/services/payment_service.py +++ b/app/services/payment_service.py @@ -10,9 +10,6 @@ from app.models.emotion import Emotion from app.models.item import ItemInventory, ItemInventoryProductInventory, ProductInventory from app.models.user import User -from app.services.badge_service import BadgeService -from app.services.color_service import ColorService -from app.services.emotion_service import EmotionService class PaymentService: diff --git a/app/services/purchase_service.py b/app/services/purchase_service.py index ffed052..8831dc2 100644 --- a/app/services/purchase_service.py +++ b/app/services/purchase_service.py @@ -56,7 +56,7 @@ async def process_apple_purchase(self, receipt_data: str, user_id: str) -> Purch user = await User.get_user_profile_by_user_id(user_id=user_id) - return PurchaseResponseDTO.build(is_premium=user.is_premium, product_code=receipt_info.product_code_two) # type: ignore + return PurchaseResponseDTO.build(is_premium=user.is_premium, product_code=receipt_info.product_code_two) @staticmethod def _extract_latest_receipt_info(response: dict[str, Any]) -> dict[str, Any] | None: diff --git a/app/services/teller_card_service.py b/app/services/teller_card_service.py index 38f0fb2..aaa2bd6 100644 --- a/app/services/teller_card_service.py +++ b/app/services/teller_card_service.py @@ -1,5 +1,3 @@ -from typing import Optional - from app.dtos.teller_card.teller_card_dto import TellerCardDTO from app.models.badge import BadgeInventory from app.models.color_inventory import ColorInventory diff --git a/app/services/user_service.py b/app/services/user_service.py index 6934e12..61e8fda 100644 --- a/app/services/user_service.py +++ b/app/services/user_service.py @@ -1,7 +1,5 @@ -from typing import Any - from app.dtos.user.user_data import UserData -from app.dtos.user.user_dto import UserDTO, UserProfileData +from app.dtos.user.user_dto import UserProfileData from app.models.user import User From a0817e4833bf77cd51d7f4aeed9fbef5a0afcb08 Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Wed, 11 Jun 2025 17:35:44 +0900 Subject: [PATCH 12/27] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20mission?= =?UTF-8?q?=20router=20DTO=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/apis/v2/purchase_router.py | 100 +-- .../constants}/cheese_status.py | 0 app/common/constants/item_category.py | 3 + app/common/constants/mission_condition.py | 17 + app/common/constants/purchase_status.py | 21 + app/common/constants/reward_type.py | 7 + app/common/handlers/router_handler.py | 2 - app/common/tasks/renew_subscription_task.py | 22 +- .../database/tortoise_database_settings.py | 8 +- app/dtos/badge/badge_code_dto.py | 11 - app/dtos/badge/badge_dto.py | 2 - app/dtos/base_response.py | 4 +- app/dtos/color/color_code_dto.py | 9 - app/dtos/item/__init__.py | 0 app/dtos/item/item_dto.py | 23 - app/dtos/mission/mission_data.py | 15 + app/dtos/mission/mission_dto.py | 18 +- app/dtos/mission/request.py | 6 - app/dtos/mission/response.py | 22 - app/dtos/mission/reward_dto.py | 27 +- app/dtos/purchase/purchase_dto.py | 128 ++-- app/dtos/purchase/requests.py | 22 +- app/dtos/question/__init__.py | 0 app/dtos/question/responses.py | 0 app/dtos/user/user_dto.py | 31 - app/models/answer.py | 16 +- app/models/badge.py | 28 +- app/models/badge_inventory.py | 21 + app/models/cheese_manager.py | 2 +- app/models/color.py | 7 - app/models/emotion.py | 14 - app/models/emotion_inventory.py | 17 + app/models/level.py | 6 - app/models/level_inventory.py | 7 + app/models/like.py | 9 +- app/models/mission.py | 29 +- app/models/mission_inventory.py | 14 + app/models/purchase_history.py | 332 +++++----- app/models/purchase_status.py | 21 - app/models/question.py | 13 - app/models/teller_card.py | 4 +- app/queries/badge_query.py | 5 - app/services/answer_service.py | 23 +- app/services/badge_service.py | 16 +- app/services/color_service.py | 5 - app/services/emotion_service.py | 3 +- app/services/level_service.py | 3 +- app/services/mission_service.py | 132 ++-- app/services/notice_service.py | 24 +- app/services/purchase_service.py | 618 +++++++++--------- app/services/teller_card_service.py | 2 +- 51 files changed, 868 insertions(+), 1001 deletions(-) rename app/{models => common/constants}/cheese_status.py (100%) create mode 100644 app/common/constants/mission_condition.py create mode 100644 app/common/constants/purchase_status.py create mode 100644 app/common/constants/reward_type.py delete mode 100644 app/dtos/badge/badge_code_dto.py delete mode 100644 app/dtos/color/color_code_dto.py delete mode 100644 app/dtos/item/__init__.py delete mode 100644 app/dtos/item/item_dto.py create mode 100644 app/dtos/mission/mission_data.py delete mode 100644 app/dtos/mission/request.py delete mode 100644 app/dtos/mission/response.py delete mode 100644 app/dtos/question/__init__.py delete mode 100644 app/dtos/question/responses.py create mode 100644 app/models/badge_inventory.py create mode 100644 app/models/emotion_inventory.py create mode 100644 app/models/level_inventory.py create mode 100644 app/models/mission_inventory.py delete mode 100644 app/models/purchase_status.py delete mode 100644 app/models/question.py diff --git a/app/apis/v2/purchase_router.py b/app/apis/v2/purchase_router.py index f015560..0e5e188 100644 --- a/app/apis/v2/purchase_router.py +++ b/app/apis/v2/purchase_router.py @@ -1,50 +1,50 @@ -from typing import Any - -from fastapi import APIRouter, Depends, status - -from app.dtos.purchase.purchase_dto import PurchaseResponseDTO -from app.dtos.purchase.requests import ReceiptRequestDTO -from app.services.purchase_service import PurchaseService - -purchase_router = APIRouter(prefix="/purchase", tags=["Purchase"]) - - -@purchase_router.post( - "/apple", - status_code=status.HTTP_200_OK, - response_model=PurchaseResponseDTO, - summary="apple 결제 api", - description="apple 결제 api", -) -async def process_receipt( - receipt: ReceiptRequestDTO, - purchase_service: PurchaseService = Depends(), -) -> PurchaseResponseDTO: - return await purchase_service.process_apple_purchase(receipt_data=receipt.receiptData, user_id=receipt.user_id) - - -@purchase_router.post("/receipt-test") -async def receipt_test( - receipt: ReceiptRequestDTO, - purchase_service: PurchaseService = Depends(), -) -> dict[str, Any]: - data = await purchase_service._validate_apple_receipt(receipt_data=receipt.receiptData) - return { - "code": 200, - "data": data, - "message": "정상처리되었습니다", - } - - -@purchase_router.get("/renew-test") -async def renew_test( - purchase_service: PurchaseService = Depends(), -) -> None: - return await purchase_service.process_subscriptions_renewal() - - -@purchase_router.get("/expired-test") -async def expired_test( - purchase_service: PurchaseService = Depends(), -) -> None: - await purchase_service.expire_subscriptions() +# from typing import Any +# +# from fastapi import APIRouter, Depends, status +# +# from app.dtos.purchase.purchase_dto import PurchaseResponseDTO +# from app.dtos.purchase.requests import ReceiptRequestDTO +# from app.services.purchase_service import PurchaseService +# +# purchase_router = APIRouter(prefix="/purchase", tags=["Purchase"]) +# +# +# @purchase_router.post( +# "/apple", +# status_code=status.HTTP_200_OK, +# response_model=PurchaseResponseDTO, +# summary="apple 결제 api", +# description="apple 결제 api", +# ) +# async def process_receipt( +# receipt: ReceiptRequestDTO, +# purchase_service: PurchaseService = Depends(), +# ) -> PurchaseResponseDTO: +# return await purchase_service.process_apple_purchase(receipt_data=receipt.receiptData, user_id=receipt.user_id) +# +# +# @purchase_router.post("/receipt-test") +# async def receipt_test( +# receipt: ReceiptRequestDTO, +# purchase_service: PurchaseService = Depends(), +# ) -> dict[str, Any]: +# data = await purchase_service._validate_apple_receipt(receipt_data=receipt.receiptData) +# return { +# "code": 200, +# "data": data, +# "message": "정상처리되었습니다", +# } +# +# +# @purchase_router.get("/renew-test") +# async def renew_test( +# purchase_service: PurchaseService = Depends(), +# ) -> None: +# return await purchase_service.process_subscriptions_renewal() +# +# +# @purchase_router.get("/expired-test") +# async def expired_test( +# purchase_service: PurchaseService = Depends(), +# ) -> None: +# await purchase_service.expire_subscriptions() diff --git a/app/models/cheese_status.py b/app/common/constants/cheese_status.py similarity index 100% rename from app/models/cheese_status.py rename to app/common/constants/cheese_status.py diff --git a/app/common/constants/item_category.py b/app/common/constants/item_category.py index 8ad044f..eafea5c 100644 --- a/app/common/constants/item_category.py +++ b/app/common/constants/item_category.py @@ -5,3 +5,6 @@ class ItemCategory(str, Enum): BADGE = "BADGE" COLOR = "COLOR" EMOTION = "EMOTION" + SUBSCRIPTION = "SUBSCRIPTION" + CHEESE = "CHEESE" + POINT = "POINT" diff --git a/app/common/constants/mission_condition.py b/app/common/constants/mission_condition.py new file mode 100644 index 0000000..991839a --- /dev/null +++ b/app/common/constants/mission_condition.py @@ -0,0 +1,17 @@ +from enum import Enum + + +class MS(str, Enum): + BADGE = "MS_BADGE" + BADGE_POST_FIRST = "MS_BADGE_POST_FIRST" + BADGE_POST_280_CHAR = "MS_BADGE_POST_280_CHAR" + BADGE_POST_CONSECUTIVE_7 = "MS_BADGE_POST_CONSECUTIVE_7" + BADGE_POST_EARLY_3 = "MS_BADGE_POST_EARLY_3" + BADGE_CHEESE_TOTAL_50 = "MS_BADGE_CHEESE_TOTAL_50" + BADGE_CHRISTMAS = "MS_BADGE_CHRISTMAS" + + DAILY = "MS_DAILY" + DAILY_LIKE_3_PER_DAY = "MS_DAILY_LIKE_3_PER_DAY" + DAILY_POST_GENERAL = "MS_DAILY_POST_GENERAL" + + LV_UP = "MS_LV_UP" diff --git a/app/common/constants/purchase_status.py b/app/common/constants/purchase_status.py new file mode 100644 index 0000000..3f071e4 --- /dev/null +++ b/app/common/constants/purchase_status.py @@ -0,0 +1,21 @@ +# from enum import Enum +# +# +# class PurchaseStatus(Enum): +# AVAILABLE = "AVAILABLE" +# CONSUMED = "CONSUMED" +# EXPIRED = "EXPIRED" +# REFUNDED = "REFUNDED" +# CANCELED = "CANCELED" +# +# +# class SubscriptionStatus(Enum): +# ACTIVE = "ACTIVE" +# EXPIRED = "EXPIRED" +# CANCELED = "CANCELED" +# +# +# purchase_mapping = { +# "tellingme.plus.oneMonth": "PD_PLUS_MONTH_1_KR", +# "tellingme.plus.oneYear": "PD_PLUS_YEAR_1_KR", +# } diff --git a/app/common/constants/reward_type.py b/app/common/constants/reward_type.py new file mode 100644 index 0000000..abb8e0d --- /dev/null +++ b/app/common/constants/reward_type.py @@ -0,0 +1,7 @@ +from enum import Enum + + +class RewardType(str, Enum): + DAILY_MISSION = "DAILY_MISSION" + LEVEL_UP = "LEVEL_UP" + BADGE_MISSION = "BADGE_MISSION" diff --git a/app/common/handlers/router_handler.py b/app/common/handlers/router_handler.py index adee726..c9d5fd6 100644 --- a/app/common/handlers/router_handler.py +++ b/app/common/handlers/router_handler.py @@ -7,7 +7,6 @@ from app.apis.v2.mission_router import mission_router as mission_router from app.apis.v2.mobile_router import mobile_router as mobile_router from app.apis.v2.payment_router import payment_router as payment_router -from app.apis.v2.purchase_router import purchase_router as purchase_router from app.apis.v2.teller_card_router import teller_card_router as teller_card_router @@ -17,7 +16,6 @@ def attach_router_handlers(app: FastAPI) -> None: app.include_router(router=color_router, prefix="/api/v2") app.include_router(router=teller_card_router, prefix="/api/v2") app.include_router(router=payment_router, prefix="/api/v2") - app.include_router(router=purchase_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=emotion_router, prefix="/api/v2") diff --git a/app/common/tasks/renew_subscription_task.py b/app/common/tasks/renew_subscription_task.py index 746f2e3..dfec321 100644 --- a/app/common/tasks/renew_subscription_task.py +++ b/app/common/tasks/renew_subscription_task.py @@ -1,11 +1,11 @@ -from app.services.purchase_service import PurchaseService - - -async def renew_subscription_task() -> None: - purchase_service = PurchaseService() - await purchase_service.process_subscriptions_renewal() - - -async def expire_subscription_task() -> None: - purchase_service = PurchaseService() - await purchase_service.expire_subscriptions() +# from app.services.purchase_service import PurchaseService +# +# +# async def renew_subscription_task() -> None: +# purchase_service = PurchaseService() +# await purchase_service.process_subscriptions_renewal() +# +# +# async def expire_subscription_task() -> None: +# purchase_service = PurchaseService() +# await purchase_service.expire_subscriptions() diff --git a/app/core/database/tortoise_database_settings.py b/app/core/database/tortoise_database_settings.py index 9b6c237..e6bb75c 100644 --- a/app/core/database/tortoise_database_settings.py +++ b/app/core/database/tortoise_database_settings.py @@ -5,21 +5,23 @@ from app.core.configs import settings TORTOISE_APP_MODELS = [ - "app.models.question", "app.models.user", "app.models.refresh_token", "app.models.badge", + "app.models.badge_inventory", "app.models.color", "app.models.color_inventory", + "app.models.emotion", + "app.models.emotion_inventory", "app.models.answer", "app.models.teller_card", "app.models.level", + "app.models.level_inventory", "app.models.cheese_manager", "app.models.item", "app.models.mission", + "app.models.mission_inventory", "app.models.like", - "app.models.emotion", - "app.models.purchase_history", ] TORTOISE_ORM = { diff --git a/app/dtos/badge/badge_code_dto.py b/app/dtos/badge/badge_code_dto.py deleted file mode 100644 index 2c98edb..0000000 --- a/app/dtos/badge/badge_code_dto.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -from pydantic import BaseModel - - -class BadgeCodeDTO(BaseModel): - badgeCode: str - - @classmethod - def builder(cls, badge_raw: dict[str, str]) -> BadgeCodeDTO: - return cls(badgeCode=badge_raw.get("badge_code", "")) diff --git a/app/dtos/badge/badge_dto.py b/app/dtos/badge/badge_dto.py index c51a00e..9554766 100644 --- a/app/dtos/badge/badge_dto.py +++ b/app/dtos/badge/badge_dto.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from pydantic import BaseModel from app.dtos.frozen_config import FROZEN_CONFIG diff --git a/app/dtos/base_response.py b/app/dtos/base_response.py index 6c27126..0e0b986 100644 --- a/app/dtos/base_response.py +++ b/app/dtos/base_response.py @@ -1,4 +1,4 @@ -from typing import Any, Optional +from typing import Any from pydantic import BaseModel @@ -11,4 +11,4 @@ class BaseResponseDTO(BaseModel): code: int message: str - data: Optional[Any] = None + data: Any | None = None diff --git a/app/dtos/color/color_code_dto.py b/app/dtos/color/color_code_dto.py deleted file mode 100644 index c0d6672..0000000 --- a/app/dtos/color/color_code_dto.py +++ /dev/null @@ -1,9 +0,0 @@ -from pydantic import BaseModel - - -class ColorCodeDTO(BaseModel): - colorCode: str - - @classmethod - def builder(cls, color_raw: dict[str, str]) -> "ColorCodeDTO": - return cls(colorCode=color_raw.get("color_code", "")) diff --git a/app/dtos/item/__init__.py b/app/dtos/item/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/dtos/item/item_dto.py b/app/dtos/item/item_dto.py deleted file mode 100644 index 554b2be..0000000 --- a/app/dtos/item/item_dto.py +++ /dev/null @@ -1,23 +0,0 @@ -# schemas.py -from typing import Optional - -from pydantic import BaseModel - - -class ItemInventorySchema(BaseModel): - item_category: Optional[str] - item_code: Optional[str] - - -class ProductInventorySchema(BaseModel): - price: Optional[float] - product_category: Optional[str] - product_code: Optional[str] - transaction_currency: Optional[str] - - -class ItemInventoryProductInventorySchema(BaseModel): - quantity: int - item_measurement: Optional[str] - item_inventory_id: int - product_inventory_id: int diff --git a/app/dtos/mission/mission_data.py b/app/dtos/mission/mission_data.py new file mode 100644 index 0000000..1a45c30 --- /dev/null +++ b/app/dtos/mission/mission_data.py @@ -0,0 +1,15 @@ +import dataclasses + + +@dataclasses.dataclass(frozen=True) +class MissionData: + user_mission_id: int + is_completed: bool + mission_code: str + progress_count: int + + @staticmethod + def to_bool(val: str) -> bool: + if isinstance(val, bytes): + return bool(int.from_bytes(val, byteorder="big")) + return bool(val) diff --git a/app/dtos/mission/mission_dto.py b/app/dtos/mission/mission_dto.py index 4d73b7a..1968431 100644 --- a/app/dtos/mission/mission_dto.py +++ b/app/dtos/mission/mission_dto.py @@ -1,25 +1,9 @@ -from typing import Any - from pydantic import BaseModel class UserMissionDTO(BaseModel): + user_mission_id: int is_completed: bool mission_code: str progress_count: int - - @classmethod - def builder(cls, user_mission: dict[str, Any]) -> "UserMissionDTO": - is_completed_raw = user_mission.get("is_completed") - is_completed = ( - bool(int.from_bytes(is_completed_raw, byteorder="big")) - if isinstance(is_completed_raw, bytes) and is_completed_raw is not None - else bool(is_completed_raw) if is_completed_raw is not None else False - ) - return cls( - user_mission_id=user_mission.get("user_mission_id", 0), # 기본값 0 설정 - is_completed=is_completed, - mission_code=user_mission.get("mission_code", ""), # 기본값 빈 문자열 설정 - progress_count=user_mission.get("progress_count", 0), # 기본값 0 설정 - ) diff --git a/app/dtos/mission/request.py b/app/dtos/mission/request.py deleted file mode 100644 index 026bed1..0000000 --- a/app/dtos/mission/request.py +++ /dev/null @@ -1,6 +0,0 @@ -from pydantic import BaseModel - - -class MissionProgressRequest(BaseModel): - mission_code: str - progress_count: int diff --git a/app/dtos/mission/response.py b/app/dtos/mission/response.py deleted file mode 100644 index 46ed7df..0000000 --- a/app/dtos/mission/response.py +++ /dev/null @@ -1,22 +0,0 @@ -# 응답 모델 정의 -from pydantic import BaseModel - - -class MissionProgressResponse(BaseModel): - mission_code: str - progress_count: int - is_completed: bool - mission_name: str - mission_description: str - target_count: int - - -class UserLevelResponse(BaseModel): - user_level: int - user_exp: int - level_up: bool - - -class ApiResponse(BaseModel): - mission_progress: MissionProgressResponse - user_level_info: UserLevelResponse diff --git a/app/dtos/mission/reward_dto.py b/app/dtos/mission/reward_dto.py index ebe3bc3..c3f8415 100644 --- a/app/dtos/mission/reward_dto.py +++ b/app/dtos/mission/reward_dto.py @@ -1,27 +1,12 @@ -from typing import Optional +from pydantic import BaseModel -from pydantic import BaseModel, ConfigDict +from app.dtos.frozen_config import FROZEN_CONFIG class RewardDTO(BaseModel): + model_config = FROZEN_CONFIG + total_cheese: int total_exp: int - badge_code: Optional[str] = None - badge_full_name: Optional[str] = None - - model_config = ConfigDict(from_attributes=True) - - @classmethod - async def build( - cls, - total_cheese: int, - total_exp: int, - badge_code: Optional[str] = None, - badge_full_name: Optional[str] = None, - ) -> "RewardDTO": - return cls( - total_cheese=total_cheese, - total_exp=total_exp, - badge_code=badge_code, - badge_full_name=badge_full_name, - ) + badge_code: str | None = None + badge_full_name: str | None = None diff --git a/app/dtos/purchase/purchase_dto.py b/app/dtos/purchase/purchase_dto.py index f98c11d..63747a7 100644 --- a/app/dtos/purchase/purchase_dto.py +++ b/app/dtos/purchase/purchase_dto.py @@ -1,64 +1,64 @@ -from typing import Any, Optional - -from pydantic import BaseModel - -from app.models.purchase_status import purchase_mapping - - -class ReceiptInfoDTO(BaseModel): - transaction_id: str - original_transaction_id: str - expires_date_ms: int - purchase_date_ms: int - product_code: str - product_code_two: str - quantity: int - cancellation_date_ms: Optional[int] = None - - @classmethod - def build(cls, latest_receipt_info: dict[str, Any]) -> "ReceiptInfoDTO": - transaction_id = latest_receipt_info["transaction_id"] - original_transaction_id = latest_receipt_info["original_transaction_id"] - expires_date_ms = int(latest_receipt_info.get("expires_date_ms", 0)) - purchase_date_ms = int(latest_receipt_info.get("purchase_date_ms", 0)) - product_code = purchase_mapping.get(latest_receipt_info["product_id"], latest_receipt_info["product_id"]) - product_code_two = latest_receipt_info["product_id"] - quantity = int(latest_receipt_info.get("quantity", 1)) - cancellation_date_ms = latest_receipt_info.get("cancellation_date_ms") # 환불일 (밀리초) - - return cls( - transaction_id=transaction_id, - original_transaction_id=original_transaction_id, - expires_date_ms=expires_date_ms, - purchase_date_ms=purchase_date_ms, - product_code=product_code, - product_code_two=product_code_two, - quantity=quantity, - cancellation_date_ms=cancellation_date_ms, - ) - - -class PurchaseDTO(BaseModel): - productCode: str - isPremium: bool - - @classmethod - def build(cls, product_code: str, is_premium: bool) -> "PurchaseDTO": - return cls( - productCode=product_code, - isPremium=is_premium, - ) - - -class PurchaseResponseDTO(BaseModel): - message: str - data: PurchaseDTO - code: int - - @classmethod - def build(cls, is_premium: bool, product_code: str) -> "PurchaseResponseDTO": - return cls( - code=200, - message="Purchase successful.", - data=PurchaseDTO.build(product_code=product_code, is_premium=is_premium), - ) +# from typing import Any, Optional +# +# from pydantic import BaseModel +# +# from app.models.purchase_status import purchase_mapping +# +# +# class ReceiptInfoDTO(BaseModel): +# transaction_id: str +# original_transaction_id: str +# expires_date_ms: int +# purchase_date_ms: int +# product_code: str +# product_code_two: str +# quantity: int +# cancellation_date_ms: Optional[int] = None +# +# @classmethod +# def build(cls, latest_receipt_info: dict[str, Any]) -> "ReceiptInfoDTO": +# transaction_id = latest_receipt_info["transaction_id"] +# original_transaction_id = latest_receipt_info["original_transaction_id"] +# expires_date_ms = int(latest_receipt_info.get("expires_date_ms", 0)) +# purchase_date_ms = int(latest_receipt_info.get("purchase_date_ms", 0)) +# product_code = purchase_mapping.get(latest_receipt_info["product_id"], latest_receipt_info["product_id"]) +# product_code_two = latest_receipt_info["product_id"] +# quantity = int(latest_receipt_info.get("quantity", 1)) +# cancellation_date_ms = latest_receipt_info.get("cancellation_date_ms") # 환불일 (밀리초) +# +# return cls( +# transaction_id=transaction_id, +# original_transaction_id=original_transaction_id, +# expires_date_ms=expires_date_ms, +# purchase_date_ms=purchase_date_ms, +# product_code=product_code, +# product_code_two=product_code_two, +# quantity=quantity, +# cancellation_date_ms=cancellation_date_ms, +# ) +# +# +# class PurchaseDTO(BaseModel): +# productCode: str +# isPremium: bool +# +# @classmethod +# def build(cls, product_code: str, is_premium: bool) -> "PurchaseDTO": +# return cls( +# productCode=product_code, +# isPremium=is_premium, +# ) +# +# +# class PurchaseResponseDTO(BaseModel): +# message: str +# data: PurchaseDTO +# code: int +# +# @classmethod +# def build(cls, is_premium: bool, product_code: str) -> "PurchaseResponseDTO": +# return cls( +# code=200, +# message="Purchase successful.", +# data=PurchaseDTO.build(product_code=product_code, is_premium=is_premium), +# ) diff --git a/app/dtos/purchase/requests.py b/app/dtos/purchase/requests.py index a2f87fc..0a7c927 100644 --- a/app/dtos/purchase/requests.py +++ b/app/dtos/purchase/requests.py @@ -1,11 +1,11 @@ -from pydantic import BaseModel - - -class ReceiptRequestDTO(BaseModel): - receiptData: str - user_id: str - - -class PurchaseRequest(BaseModel): - user_id: str - product_code: str +# from pydantic import BaseModel +# +# +# class ReceiptRequestDTO(BaseModel): +# receiptData: str +# user_id: str +# +# +# class PurchaseRequest(BaseModel): +# user_id: str +# product_code: str diff --git a/app/dtos/question/__init__.py b/app/dtos/question/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/dtos/question/responses.py b/app/dtos/question/responses.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/dtos/user/user_dto.py b/app/dtos/user/user_dto.py index 607e101..0df2955 100644 --- a/app/dtos/user/user_dto.py +++ b/app/dtos/user/user_dto.py @@ -1,7 +1,4 @@ import dataclasses -from typing import Any, Optional - -from pydantic import BaseModel @dataclasses.dataclass(frozen=True) @@ -15,31 +12,3 @@ class UserProfileData: teller_card_id: int level_id: int allow_notification: bool - - -class UserDTO(BaseModel): - user_id: Optional[str] = None - nickname: Optional[str] = None - profile_url: Optional[str] = None - is_premium: Optional[bool] = None - user_status: Optional[bool] = None - cheese_manager_id: Optional[int] = None - teller_card_id: Optional[int] = None - level_id: Optional[int] = None - allow_notification: Optional[bool] = None - - @classmethod - def build(cls, user: dict[str, Any]) -> "UserDTO": - is_premium = user.get("is_premium") != b"\x00" - allow_notification = user.get("allow_notification") != b"\x00" - return cls( - user_id=user.get("user_id", None), - nickname=user.get("nickname", None), - profile_url=user.get("profile_url", None), - is_premium=is_premium, - user_status=user.get("user_status", None), - cheese_manager_id=user.get("cheese_manager_id", None), - teller_card_id=user.get("teller_card_id", None), - level_id=user.get("level_id", None), - allow_notification=allow_notification, - ) diff --git a/app/models/answer.py b/app/models/answer.py index 5b26a47..7a25d7e 100644 --- a/app/models/answer.py +++ b/app/models/answer.py @@ -1,5 +1,4 @@ from datetime import datetime -from typing import Any from tortoise import fields from tortoise.fields import ForeignKeyRelation @@ -42,16 +41,18 @@ class Meta: # 기존 get_answer_count_by_user_id 메서드 @classmethod - async def get_answer_count_by_user_id(cls, user_id: str) -> Any: + async def get_answer_count_by_user_id(cls, user_id: str) -> int: query = SELECT_ANSWER_COUNT_BY_USER_UUID_QUERY value = user_id - return await QueryExecutor.execute_query(query, values=value, fetch_type="single") + result = await QueryExecutor.execute_query(query, values=value, fetch_type="single") + return int(result.get("answer_count", 0) if result else 0) @classmethod - async def get_answer_count_by_user_id_v2(cls, user_id: str) -> Any: + async def get_answer_count_by_user_id_v2(cls, user_id: str) -> int: query = SELECT_ANSWER_COUNT_BY_USER_UUID_QUERY_V2 value = user_id - return await QueryExecutor.execute_query(query, values=value, fetch_type="single") + result = await QueryExecutor.execute_query(query, values=value, fetch_type="single") + return int(result.get("answer_count", 0) if result else 0) @classmethod async def get_all_by_user_id(cls, user_id: str, start_date: datetime, end_date: datetime) -> list[AnswerData]: @@ -61,7 +62,8 @@ async def get_all_by_user_id(cls, user_id: str, start_date: datetime, end_date: return [AnswerData(**row) for row in results] @classmethod - async def get_most_recent_answer_by_user_id(cls, user_id: str) -> Any: + async def get_most_recent_answer_by_user_id(cls, user_id: str) -> AnswerData | None: query = SELECT_MOST_RECENT_ANSWER_BY_USER_UUID_QUERY value = user_id - return await QueryExecutor.execute_query(query, values=value, fetch_type="single") + result = await QueryExecutor.execute_query(query, values=value, fetch_type="single") + return AnswerData(**result) if result else None diff --git a/app/models/badge.py b/app/models/badge.py index dfe4551..662f161 100644 --- a/app/models/badge.py +++ b/app/models/badge.py @@ -1,4 +1,4 @@ -from typing import Any +from __future__ import annotations from tortoise import fields from tortoise.fields import ForeignKeyRelation @@ -10,7 +10,6 @@ from app.queries.badge_query import ( INSERT_BADGE_CODE_FOR_USER_QUERY, SELECT_BADGE_BY_USER_UUID_QUERY, - SELECT_BADGE_CODE_BY_USER_UUID_QUERY, SELECT_BADGE_COUNT_BY_USER_UUID_QUERY, ) @@ -24,10 +23,11 @@ class Meta: table = "badge" @classmethod - async def get_badge_count_by_user_id(cls, user_id: str) -> Any: + async def get_badge_count_by_user_id(cls, user_id: str) -> int: query = SELECT_BADGE_COUNT_BY_USER_UUID_QUERY value = user_id - return await QueryExecutor.execute_query(query, values=value, fetch_type="single") + result = await QueryExecutor.execute_query(query, values=value, fetch_type="single") + return int(result.get("badge_count", 0) if result else 0) @classmethod async def get_badges_with_details_by_user_id(cls, user_id: str) -> list[BadgeData]: @@ -36,28 +36,8 @@ async def get_badges_with_details_by_user_id(cls, user_id: str) -> list[BadgeDat result = await QueryExecutor.execute_query(query, values=value, fetch_type="multiple") return [BadgeData(**row) for row in result] - @classmethod - async def get_badge_codes_by_user_id(cls, user_id: str) -> Any: - query = SELECT_BADGE_CODE_BY_USER_UUID_QUERY - value = user_id - return await QueryExecutor.execute_query(query, values=value, fetch_type="multiple") - @classmethod async def create_by_user_id(cls, user_id: str, badge_code: str) -> None: query = INSERT_BADGE_CODE_FOR_USER_QUERY values = (badge_code, user_id) await QueryExecutor.execute_query(query, values=values) - - -class BadgeInventory(Model): - badge_code = fields.CharField(max_length=255, primary_key=True) - badge_name = fields.CharField(max_length=255, null=True) - badge_condition = fields.CharField(max_length=255, null=True) - badge_middle_name = fields.CharField(max_length=255, null=True) - - class Meta: - table = "badge_inventory" - - @property - def badge_full_name(self) -> str: - return f"{self.badge_middle_name} {self.badge_name}" diff --git a/app/models/badge_inventory.py b/app/models/badge_inventory.py new file mode 100644 index 0000000..a5c7c27 --- /dev/null +++ b/app/models/badge_inventory.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +from tortoise import Model, fields + + +class BadgeInventory(Model): + badge_code = fields.CharField(max_length=255, primary_key=True) + badge_name = fields.CharField(max_length=255, null=True) + badge_condition = fields.CharField(max_length=255, null=True) + badge_middle_name = fields.CharField(max_length=255, null=True) + + class Meta: + table = "badge_inventory" + + @property + def badge_full_name(self) -> str: + return f"{self.badge_middle_name} {self.badge_name}" + + @classmethod + async def get_by_badge_code(cls, badge_code: str) -> BadgeInventory: + return await cls.get(badge_code=badge_code) diff --git a/app/models/cheese_manager.py b/app/models/cheese_manager.py index 72ed97a..7e36e41 100644 --- a/app/models/cheese_manager.py +++ b/app/models/cheese_manager.py @@ -6,7 +6,7 @@ from tortoise.functions import Sum from tortoise.models import Model -from app.models.cheese_status import CheeseStatus +from app.common.constants.cheese_status import CheeseStatus class CheeseManager(Model): diff --git a/app/models/color.py b/app/models/color.py index 469d7b6..699d8d2 100644 --- a/app/models/color.py +++ b/app/models/color.py @@ -10,7 +10,6 @@ from app.queries.color_query import ( INSERT_COLOR_CODE_FOR_USER_QUERY, SELECT_COLOR_BY_USER_UUID_QUERY, - SELECT_COLOR_CODE_BY_USER_UUID_QUERY, ) @@ -24,12 +23,6 @@ class Color(Model): class Meta: table = "color" - @classmethod - async def get_color_codes_by_user_id(cls, user_id: str) -> Any: - query = SELECT_COLOR_CODE_BY_USER_UUID_QUERY - value = user_id - return await QueryExecutor.execute_query(query, values=value, fetch_type="multiple") - @classmethod async def create_by_user_id(cls, user_id: str, color_code: str) -> Any: query = INSERT_COLOR_CODE_FOR_USER_QUERY diff --git a/app/models/emotion.py b/app/models/emotion.py index 7c0020e..9753764 100644 --- a/app/models/emotion.py +++ b/app/models/emotion.py @@ -30,17 +30,3 @@ async def create_by_user_id(cls, user_id: str, emotion_code: str) -> None: query = INSERT_EMOTION_CODE_FOR_USER_QUERY values = (emotion_code, user_id) await QueryExecutor.execute_query(query, values=values) - - -class EmotionInventory(models.Model): - emotion_inventory_id = fields.BigIntField(primary_key=True) - emotion_code = fields.CharField(max_length=255, unique=True) - emotion_name = fields.CharField(max_length=255) - - class Meta: - table = "emotion_inventory" - - @classmethod - async def get_emotion_inventory(cls) -> list[EmotionData]: - result = await cls.all().values("emotion_code", "emotion_name") - return [EmotionData(**row) for row in result] diff --git a/app/models/emotion_inventory.py b/app/models/emotion_inventory.py new file mode 100644 index 0000000..ca3716c --- /dev/null +++ b/app/models/emotion_inventory.py @@ -0,0 +1,17 @@ +from tortoise import fields, models + +from app.dtos.emotion.emotion_data import EmotionData + + +class EmotionInventory(models.Model): + emotion_inventory_id = fields.BigIntField(primary_key=True) + emotion_code = fields.CharField(max_length=255, unique=True) + emotion_name = fields.CharField(max_length=255) + + class Meta: + table = "emotion_inventory" + + @classmethod + async def get_emotion_inventory(cls) -> list[EmotionData]: + result = await cls.all().values("emotion_code", "emotion_name") + return [EmotionData(**row) for row in result] diff --git a/app/models/level.py b/app/models/level.py index 2d16661..54612ee 100644 --- a/app/models/level.py +++ b/app/models/level.py @@ -29,9 +29,3 @@ async def update_level_and_exp(cls, user_id: str, new_level: int, new_exp: int) query = UPDATE_USER_LEVEL_AND_EXP_QUERY values = (new_level, new_exp, user_id) await QueryExecutor.execute_query(query, values=values, fetch_type="single") - - -class LevelInventory(Model): - level_inventory_id = fields.BigIntField(primary_key=True) - level = fields.IntField(null=True) - required_exp = fields.IntField(null=True) diff --git a/app/models/level_inventory.py b/app/models/level_inventory.py new file mode 100644 index 0000000..e7a1293 --- /dev/null +++ b/app/models/level_inventory.py @@ -0,0 +1,7 @@ +from tortoise import Model, fields + + +class LevelInventory(Model): + level_inventory_id = fields.BigIntField(primary_key=True) + level = fields.IntField(null=True) + required_exp = fields.IntField(null=True) diff --git a/app/models/like.py b/app/models/like.py index a182445..404bd1f 100644 --- a/app/models/like.py +++ b/app/models/like.py @@ -1,5 +1,3 @@ -from typing import Any - from tortoise import fields from tortoise.fields import ForeignKeyRelation from tortoise.models import Model @@ -31,7 +29,8 @@ class Meta: ] @staticmethod - async def get_unique_likes_today(user_id: str) -> Any: + async def get_unique_likes_today(user_id: str) -> int: query = SELECT_UNIQUE_LIKES_COUNT_BY_USER_TODAY_QUERY - values = (user_id,) - return await QueryExecutor.execute_query(query, values=values, fetch_type="single") + value = user_id + result = await QueryExecutor.execute_query(query, values=value, fetch_type="single") + return int(result.get("unique_likes", 0) if result else 0) diff --git a/app/models/mission.py b/app/models/mission.py index 13cfd98..29577bf 100644 --- a/app/models/mission.py +++ b/app/models/mission.py @@ -1,10 +1,9 @@ -from typing import Any - from tortoise import fields from tortoise.fields import ForeignKeyRelation from tortoise.models import Model from app.common.utils.query_executor import QueryExecutor +from app.dtos.mission.mission_data import MissionData from app.models.user import User from app.queries.mission_query import SELECT_USER_MISSIONS_QUERY, UPDATE_USER_MISSION_PROGRESS_QUERY @@ -20,10 +19,19 @@ class Meta: table = "user_mission" @classmethod - async def get_user_missions_by_condition_type(cls, user_id: str) -> Any: + async def get_user_missions_by_condition_type(cls, user_id: str) -> list[MissionData]: query = SELECT_USER_MISSIONS_QUERY values = (user_id,) - return await QueryExecutor.execute_query(query, values=values, fetch_type="multiple") + results = await QueryExecutor.execute_query(query, values=values, fetch_type="multiple") + return [ + MissionData( + user_mission_id=row.get("user_mission_id", 0), + mission_code=row.get("mission_code", ""), + progress_count=row.get("progress_count", 0), + is_completed=MissionData.to_bool(row.get("is_completed")), + ) + for row in results + ] @classmethod async def update_user_mission_progress( @@ -36,16 +44,3 @@ async def update_user_mission_progress( query = UPDATE_USER_MISSION_PROGRESS_QUERY values = (new_progress_count, int(is_completed), user_id, mission_code) await QueryExecutor.execute_query(query, values=values, fetch_type="single") - - -class MissionInventory(Model): - mission_inventory_id = fields.BigIntField(primary_key=True) - condition_type = fields.CharField(max_length=255) - mission_code = fields.CharField(max_length=255) - mission_description = fields.CharField(max_length=255) - mission_name = fields.CharField(max_length=255) - reward_code = fields.CharField(max_length=255) - target_count = fields.IntField() - - class Meta: - table = "mission_inventory" diff --git a/app/models/mission_inventory.py b/app/models/mission_inventory.py new file mode 100644 index 0000000..7e1c21a --- /dev/null +++ b/app/models/mission_inventory.py @@ -0,0 +1,14 @@ +from tortoise import Model, fields + + +class MissionInventory(Model): + mission_inventory_id = fields.BigIntField(primary_key=True) + condition_type = fields.CharField(max_length=255) + mission_code = fields.CharField(max_length=255) + mission_description = fields.CharField(max_length=255) + mission_name = fields.CharField(max_length=255) + reward_code = fields.CharField(max_length=255) + target_count = fields.IntField() + + class Meta: + table = "mission_inventory" diff --git a/app/models/purchase_history.py b/app/models/purchase_history.py index 1a1422f..4f78435 100644 --- a/app/models/purchase_history.py +++ b/app/models/purchase_history.py @@ -1,166 +1,166 @@ -from datetime import datetime -from typing import Optional - -from tortoise import fields -from tortoise.fields import ForeignKeyRelation -from tortoise.models import Model - -from app.common.utils.query_executor import QueryExecutor -from app.models.user import User - - -class Subscription(Model): - subscription_id = fields.BigIntField(primary_key=True, description="Primary key for the Subscription") - product_code = fields.CharField(max_length=255, null=False, description="Product code of the subscription") - status = fields.CharField(max_length=255, null=False, description="Status of the subscription") - current_transaction_id = fields.CharField(max_length=255, null=False, description="Current transaction ID") - expires_date = fields.DatetimeField(null=False, description="Expiration date of the subscription") - created_at = fields.DatetimeField(auto_now_add=True, description="When the subscription was created") - updated_at = fields.DatetimeField(auto_now=True, description="Last updated timestamp") - - user: ForeignKeyRelation["User"] = fields.ForeignKeyField( - "models.User", - related_name="subscriptions", - on_delete=fields.CASCADE, - description="User linked to the subscription", - ) - purchase_histories = fields.ReverseRelation["PurchaseHistory"] - - class Meta: - table = "subscription" - - @classmethod - async def get_subscription_by_user_id_and_product_code( - cls, user_id: str, product_code: str - ) -> Optional["Subscription"]: - query = """ - SELECT * FROM subscription - WHERE user_id = UNHEX(REPLACE(%s, '-', '')) AND product_code = %s - LIMIT 1; - """ - values = (user_id, product_code) - - result = await QueryExecutor.execute_query(query, values=values, fetch_type="single") - - if result: - return cls(**result) - return None - - @classmethod - async def create_or_update_subscription( - cls, - user_id: str, - product_code: str, - transaction_id: str, - expires_date_ms: int, - status: str, - ) -> "Subscription": - query = """ - INSERT INTO subscription (user_id, product_code, status, current_transaction_id, expires_date) - VALUES (UNHEX(REPLACE(%s, '-', '')), %s, %s, %s, FROM_UNIXTIME(%s / 1000)) - ON DUPLICATE KEY UPDATE - current_transaction_id = VALUES(current_transaction_id), - expires_date = VALUES(expires_date), - status = VALUES(status); - """ - values = (user_id, product_code, status, transaction_id, expires_date_ms) - - await QueryExecutor.execute_query(query, values=values, fetch_type="none") - - return cls( - user_id=user_id, - product_code=product_code, - status=status, - current_transaction_id=transaction_id, - expires_date=datetime.fromtimestamp(expires_date_ms / 1000), - ) - - @classmethod - async def update_subscription( - cls, user_id: str, product_code: str, transaction_id: str, expires_date_ms: int - ) -> None: - query = """ - UPDATE subscription - SET current_transaction_id = %s, - expires_date = FROM_UNIXTIME(%s / 1000), - status = %s - WHERE user_id = UNHEX(REPLACE(%s, '-', '')) - AND product_code = %s; - """ - values = (transaction_id, expires_date_ms, "active", user_id, product_code) - - await QueryExecutor.execute_query(query, values=values, fetch_type="single") - - -class PurchaseHistory(Model): - purchase_history_id = fields.BigIntField(primary_key=True, description="Primary key for the Purchase History") - product_code = fields.CharField(max_length=255, null=False, description="Product code of the purchase") - transaction_id = fields.CharField(max_length=255, unique=True, null=False, description="Transaction ID") - original_transaction_id = fields.CharField(max_length=255, null=True, description="Original transaction ID") - status = fields.CharField(max_length=255, null=False, description="Purchase status") - expires_date = fields.DatetimeField(null=True, description="Expiration date of the purchase") - purchase_date = fields.DatetimeField(null=False, description="Date of the purchase") - quantity = fields.IntField(default=1, description="Quantity of items purchased") - receipt_data = fields.TextField(null=True, description="Raw receipt data from Apple") - created_at = fields.DatetimeField(auto_now_add=True, description="When the purchase was made") - updated_at = fields.DatetimeField(auto_now=True, description="Last updated timestamp") - - user: ForeignKeyRelation["User"] = fields.ForeignKeyField( - "models.User", - related_name="purchase_histories", - on_delete=fields.CASCADE, - description="User linked to the purchase", - ) - - subscription: Optional[ForeignKeyRelation["Subscription"]] = fields.ForeignKeyField( - "models.Subscription", - related_name="purchase_histories", - null=True, - on_delete=fields.SET_NULL, - description="Linked subscription", - ) - - class Meta: - table = "purchase_history" - - @classmethod - async def create_purchase_history( - cls, - user_id: str, - subscription_id: Optional[int], - product_code: str, - transaction_id: str, - original_transaction_id: str, - status: str, - expires_date_ms: Optional[int], - purchase_date_ms: int, - receipt_data: str, - quantity: int = 1, - ) -> None: - query = """ - INSERT INTO purchase_history ( - user_id, subscription_id, product_code, transaction_id, - original_transaction_id, status, expires_date, purchase_date, - quantity, receipt_data, created_at, updated_at - ) - VALUES ( - UNHEX(REPLACE(%s, '-', '')), %s, %s, %s, - %s, %s, FROM_UNIXTIME(%s / 1000), FROM_UNIXTIME(%s / 1000), - %s, %s, NOW(), NOW() - ); - """ - - values = ( - user_id, - subscription_id, - product_code, - transaction_id, - original_transaction_id, - status, - expires_date_ms, - purchase_date_ms, - quantity, - receipt_data, - ) - - await QueryExecutor.execute_query(query, values=values, fetch_type="single") +# from datetime import datetime +# from typing import Optional +# +# from tortoise import fields +# from tortoise.fields import ForeignKeyRelation +# from tortoise.models import Model +# +# from app.common.utils.query_executor import QueryExecutor +# from app.models.user import User +# +# +# class Subscription(Model): +# subscription_id = fields.BigIntField(primary_key=True, description="Primary key for the Subscription") +# product_code = fields.CharField(max_length=255, null=False, description="Product code of the subscription") +# status = fields.CharField(max_length=255, null=False, description="Status of the subscription") +# current_transaction_id = fields.CharField(max_length=255, null=False, description="Current transaction ID") +# expires_date = fields.DatetimeField(null=False, description="Expiration date of the subscription") +# created_at = fields.DatetimeField(auto_now_add=True, description="When the subscription was created") +# updated_at = fields.DatetimeField(auto_now=True, description="Last updated timestamp") +# +# user: ForeignKeyRelation["User"] = fields.ForeignKeyField( +# "models.User", +# related_name="subscriptions", +# on_delete=fields.CASCADE, +# description="User linked to the subscription", +# ) +# purchase_histories = fields.ReverseRelation["PurchaseHistory"] +# +# class Meta: +# table = "subscription" +# +# @classmethod +# async def get_subscription_by_user_id_and_product_code( +# cls, user_id: str, product_code: str +# ) -> Optional["Subscription"]: +# query = """ +# SELECT * FROM subscription +# WHERE user_id = UNHEX(REPLACE(%s, '-', '')) AND product_code = %s +# LIMIT 1; +# """ +# values = (user_id, product_code) +# +# result = await QueryExecutor.execute_query(query, values=values, fetch_type="single") +# +# if result: +# return cls(**result) +# return None +# +# @classmethod +# async def create_or_update_subscription( +# cls, +# user_id: str, +# product_code: str, +# transaction_id: str, +# expires_date_ms: int, +# status: str, +# ) -> "Subscription": +# query = """ +# INSERT INTO subscription (user_id, product_code, status, current_transaction_id, expires_date) +# VALUES (UNHEX(REPLACE(%s, '-', '')), %s, %s, %s, FROM_UNIXTIME(%s / 1000)) +# ON DUPLICATE KEY UPDATE +# current_transaction_id = VALUES(current_transaction_id), +# expires_date = VALUES(expires_date), +# status = VALUES(status); +# """ +# values = (user_id, product_code, status, transaction_id, expires_date_ms) +# +# await QueryExecutor.execute_query(query, values=values, fetch_type="none") +# +# return cls( +# user_id=user_id, +# product_code=product_code, +# status=status, +# current_transaction_id=transaction_id, +# expires_date=datetime.fromtimestamp(expires_date_ms / 1000), +# ) +# +# @classmethod +# async def update_subscription( +# cls, user_id: str, product_code: str, transaction_id: str, expires_date_ms: int +# ) -> None: +# query = """ +# UPDATE subscription +# SET current_transaction_id = %s, +# expires_date = FROM_UNIXTIME(%s / 1000), +# status = %s +# WHERE user_id = UNHEX(REPLACE(%s, '-', '')) +# AND product_code = %s; +# """ +# values = (transaction_id, expires_date_ms, "active", user_id, product_code) +# +# await QueryExecutor.execute_query(query, values=values, fetch_type="single") +# +# +# class PurchaseHistory(Model): +# purchase_history_id = fields.BigIntField(primary_key=True, description="Primary key for the Purchase History") +# product_code = fields.CharField(max_length=255, null=False, description="Product code of the purchase") +# transaction_id = fields.CharField(max_length=255, unique=True, null=False, description="Transaction ID") +# original_transaction_id = fields.CharField(max_length=255, null=True, description="Original transaction ID") +# status = fields.CharField(max_length=255, null=False, description="Purchase status") +# expires_date = fields.DatetimeField(null=True, description="Expiration date of the purchase") +# purchase_date = fields.DatetimeField(null=False, description="Date of the purchase") +# quantity = fields.IntField(default=1, description="Quantity of items purchased") +# receipt_data = fields.TextField(null=True, description="Raw receipt data from Apple") +# created_at = fields.DatetimeField(auto_now_add=True, description="When the purchase was made") +# updated_at = fields.DatetimeField(auto_now=True, description="Last updated timestamp") +# +# user: ForeignKeyRelation["User"] = fields.ForeignKeyField( +# "models.User", +# related_name="purchase_histories", +# on_delete=fields.CASCADE, +# description="User linked to the purchase", +# ) +# +# subscription: Optional[ForeignKeyRelation["Subscription"]] = fields.ForeignKeyField( +# "models.Subscription", +# related_name="purchase_histories", +# null=True, +# on_delete=fields.SET_NULL, +# description="Linked subscription", +# ) +# +# class Meta: +# table = "purchase_history" +# +# @classmethod +# async def create_purchase_history( +# cls, +# user_id: str, +# subscription_id: Optional[int], +# product_code: str, +# transaction_id: str, +# original_transaction_id: str, +# status: str, +# expires_date_ms: Optional[int], +# purchase_date_ms: int, +# receipt_data: str, +# quantity: int = 1, +# ) -> None: +# query = """ +# INSERT INTO purchase_history ( +# user_id, subscription_id, product_code, transaction_id, +# original_transaction_id, status, expires_date, purchase_date, +# quantity, receipt_data, created_at, updated_at +# ) +# VALUES ( +# UNHEX(REPLACE(%s, '-', '')), %s, %s, %s, +# %s, %s, FROM_UNIXTIME(%s / 1000), FROM_UNIXTIME(%s / 1000), +# %s, %s, NOW(), NOW() +# ); +# """ +# +# values = ( +# user_id, +# subscription_id, +# product_code, +# transaction_id, +# original_transaction_id, +# status, +# expires_date_ms, +# purchase_date_ms, +# quantity, +# receipt_data, +# ) +# +# await QueryExecutor.execute_query(query, values=values, fetch_type="single") diff --git a/app/models/purchase_status.py b/app/models/purchase_status.py deleted file mode 100644 index f8789f7..0000000 --- a/app/models/purchase_status.py +++ /dev/null @@ -1,21 +0,0 @@ -from enum import Enum - - -class PurchaseStatus(Enum): - AVAILABLE = "AVAILABLE" - CONSUMED = "CONSUMED" - EXPIRED = "EXPIRED" - REFUNDED = "REFUNDED" - CANCELED = "CANCELED" - - -class SubscriptionStatus(Enum): - ACTIVE = "ACTIVE" - EXPIRED = "EXPIRED" - CANCELED = "CANCELED" - - -purchase_mapping = { - "tellingme.plus.oneMonth": "PD_PLUS_MONTH_1_KR", - "tellingme.plus.oneYear": "PD_PLUS_YEAR_1_KR", -} diff --git a/app/models/question.py b/app/models/question.py deleted file mode 100644 index b446a9c..0000000 --- a/app/models/question.py +++ /dev/null @@ -1,13 +0,0 @@ -from tortoise import fields -from tortoise.models import Model - - -class Question(Model): - date = fields.DateField(primary_key=True) # 기본 키로 설정된 날짜 필드 - phrase = fields.CharField(max_length=255) - title = fields.CharField(max_length=255) - spare_phrase = fields.CharField(max_length=255) - spare_title = fields.CharField(max_length=255) - - class Meta: - table = "question" # 데이터베이스에서 매핑할 테이블 이름 diff --git a/app/models/teller_card.py b/app/models/teller_card.py index 66e39af..767a48d 100644 --- a/app/models/teller_card.py +++ b/app/models/teller_card.py @@ -18,10 +18,10 @@ class Meta: table = "teller_card" @classmethod - async def get_teller_card_info_by_user_id(cls, user_id: str) -> TellerCardData: # type ignore + async def get_teller_card_info_by_user_id(cls, user_id: str) -> TellerCardData: query = SELECT_TELLER_CARD_INFO_BY_USER_UUID_QUERY value = user_id - result = await QueryExecutor.execute_query(query, values=value, fetch_type="single") # type ignore + result = await QueryExecutor.execute_query(query, values=value, fetch_type="single") return TellerCardData(**result) @classmethod diff --git a/app/queries/badge_query.py b/app/queries/badge_query.py index 6bc5951..ae3364d 100644 --- a/app/queries/badge_query.py +++ b/app/queries/badge_query.py @@ -17,11 +17,6 @@ WHERE {USER_ID_QUERY} """ -SELECT_BADGE_CODE_BY_USER_UUID_QUERY = f""" - SELECT badge_code - FROM badge - WHERE {USER_ID_QUERY} -""" INSERT_BADGE_CODE_FOR_USER_QUERY = f""" INSERT INTO badge (badge_code, user_id) SELECT %s, user_id diff --git a/app/services/answer_service.py b/app/services/answer_service.py index dcc1f10..8aa7cb9 100644 --- a/app/services/answer_service.py +++ b/app/services/answer_service.py @@ -1,5 +1,4 @@ from datetime import datetime, timedelta -from typing import Any from app.core.configs import settings from app.models.answer import Answer @@ -11,20 +10,7 @@ async def get_answer_count(cls, user_id: str) -> int: """ 과거부터 현재까지 총 답변 수 """ - answer_count_raw = await Answer.get_answer_count_by_user_id(user_id=user_id) - if answer_count_raw is None: - return 0 - return int(answer_count_raw.get("answer_count", 0)) - - @classmethod - async def get_answer_count_v2(cls, user_id: str) -> int: - """ - v2 이후 총 답변 수 - """ - answer_count_raw = await Answer.get_answer_count_by_user_id_v2(user_id=user_id) - if answer_count_raw is None: - return 0 - return int(answer_count_raw.get("answer_count", 0)) + return await Answer.get_answer_count_by_user_id(user_id=user_id) @classmethod async def get_answer_record(cls, user_id: str) -> int: @@ -56,10 +42,3 @@ async def get_answer_record(cls, user_id: str) -> int: @classmethod async def calculate_consecutive_answer_points(cls, user_id: str) -> int: return min(await cls.get_answer_record(user_id=user_id), 10) - - @classmethod - async def get_most_recent_answer(cls, user_id: str) -> Any: - answer = await Answer.get_most_recent_answer_by_user_id(user_id=user_id) - if answer == 0: - return {} - return answer diff --git a/app/services/badge_service.py b/app/services/badge_service.py index 8722646..4c07e50 100644 --- a/app/services/badge_service.py +++ b/app/services/badge_service.py @@ -1,13 +1,8 @@ -from app.dtos.badge.badge_code_dto import BadgeCodeDTO from app.dtos.badge.badge_dto import BadgeDTO -from app.models.badge import Badge, BadgeInventory +from app.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] @classmethod async def get_badges_with_details_by_user_id(cls, user_id: str) -> list[BadgeDTO]: @@ -24,11 +19,4 @@ async def get_badges_with_details_by_user_id(cls, user_id: str) -> list[BadgeDTO @classmethod async def get_badge_count(cls, user_id: str) -> int: - badge_count_raw = await Badge.get_badge_count_by_user_id(user_id=user_id) - if badge_count_raw is None: - return 0 - return int(badge_count_raw.get("badge_count", 0)) - - @classmethod - async def get_badge_info_by_badge_code(cls, badge_code: str) -> BadgeInventory: - return await BadgeInventory.get(badge_code=badge_code) + return await Badge.get_badge_count_by_user_id(user_id=user_id) diff --git a/app/services/color_service.py b/app/services/color_service.py index c8ac22f..7ba6208 100644 --- a/app/services/color_service.py +++ b/app/services/color_service.py @@ -1,4 +1,3 @@ -from app.dtos.color.color_code_dto import ColorCodeDTO from app.dtos.color.color_dto import ColorDTO from app.models.color import Color from app.models.color_inventory import ColorInventory @@ -6,10 +5,6 @@ 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] @classmethod async def get_colors_with_details_by_user_id(cls, user_id: str) -> list[ColorDTO]: diff --git a/app/services/emotion_service.py b/app/services/emotion_service.py index 8544365..5666079 100644 --- a/app/services/emotion_service.py +++ b/app/services/emotion_service.py @@ -1,7 +1,8 @@ from app.common.constants.emotion_dict import EMOTION_DICT from app.dtos.emotion.emotion_data import EmotionData from app.dtos.emotion.emotion_dto import EmotionDTO -from app.models.emotion import Emotion, EmotionInventory +from app.models.emotion import Emotion +from app.models.emotion_inventory import EmotionInventory from app.models.user import User diff --git a/app/services/level_service.py b/app/services/level_service.py index 3120d75..a6babdb 100644 --- a/app/services/level_service.py +++ b/app/services/level_service.py @@ -1,5 +1,6 @@ from app.dtos.level.level_dto import LevelDTO from app.dtos.level.level_info_dto import LevelInfoDTO +from app.models.answer import Answer from app.models.level import Level from app.services.answer_service import AnswerService @@ -58,7 +59,7 @@ async def calculate_days_to_level_up(cls, user_id: str, current_exp: int, requir remaining_exp = required_exp - current_exp days_needed = 0 - answer_count = await AnswerService.get_answer_count_v2(user_id=user_id) + 1 + answer_count = await Answer.get_answer_count_by_user_id_v2(user_id=user_id) + 1 bonus_points = await AnswerService.calculate_consecutive_answer_points(user_id=user_id) while remaining_exp > 0: diff --git a/app/services/mission_service.py b/app/services/mission_service.py index f124e24..1c0ba9c 100644 --- a/app/services/mission_service.py +++ b/app/services/mission_service.py @@ -1,23 +1,27 @@ import asyncio from datetime import datetime, timedelta, timezone -from typing import Optional -from fastapi import HTTPException +from fastapi import HTTPException, status from tortoise.exceptions import DoesNotExist from tortoise.transactions import atomic +from app.common.constants.item_category import ItemCategory +from app.common.constants.mission_condition import MS +from app.common.constants.reward_type import RewardType from app.core.configs import settings from app.dtos.mission.mission_dto import UserMissionDTO from app.dtos.mission.reward_dto import RewardDTO +from app.models.answer import Answer from app.models.badge import Badge +from app.models.badge_inventory import BadgeInventory from app.models.cheese_manager import CheeseManager from app.models.color import Color from app.models.item import ItemInventory, ItemInventoryRewardInventory, RewardInventory from app.models.like import Like -from app.models.mission import MissionInventory, UserMission +from app.models.mission import UserMission +from app.models.mission_inventory import MissionInventory from app.models.user import User from app.services.answer_service import AnswerService -from app.services.badge_service import BadgeService from app.services.level_service import LevelService from app.services.notice_service import NoticeService @@ -25,8 +29,16 @@ class MissionService: @staticmethod async def get_user_missions(user_id: str) -> list[UserMissionDTO]: - user_mission_raw = await UserMission.get_user_missions_by_condition_type(user_id) - return [UserMissionDTO.builder(user_mission) for user_mission in user_mission_raw] + user_missions = await UserMission.get_user_missions_by_condition_type(user_id) + return [ + UserMissionDTO( + user_mission_id=user_mission.user_mission_id, + is_completed=user_mission.is_completed, + mission_code=user_mission.mission_code, + progress_count=user_mission.progress_count, + ) + for user_mission in user_missions + ] @staticmethod async def _update_user_mission_progress( @@ -51,7 +63,7 @@ async def update_mission_progress(self, user_id: str) -> None: MissionInventory.all(), ) - cheese_manager_id: int = user.cheese_manager_id + cheese_manager_id = user.cheese_manager_id mission_dict = {mission.mission_code: mission for mission in missions} badge_missions, lv_up_mission, daily_missions = await self._classify_missions(user_missions) @@ -64,14 +76,6 @@ async def update_mission_progress(self, user_id: str) -> None: if lv_up_mission[0]: await self._process_mission(lv_up_mission[0], mission_dict, cheese_manager_id, user_id) - async def _classify_missions( - self, user_missions: list[UserMissionDTO] - ) -> tuple[list[UserMissionDTO], list[UserMissionDTO], list[UserMissionDTO]]: - badge_missions = [mission for mission in user_missions if mission.mission_code.startswith("MS_BADGE")] - lv_up_mission = [mission for mission in user_missions if mission.mission_code == "MS_LV_UP"] - daily_missions = [mission for mission in user_missions if mission.mission_code.startswith("MS_DAILY")] - return badge_missions, lv_up_mission, daily_missions - async def _process_mission( self, user_mission: UserMissionDTO, @@ -109,13 +113,13 @@ async def _handle_mission_reward( reward_code: str, cheese_manager_id: int, ) -> None: - if mission_code == "MS_DAILY_POST_GENERAL": + if mission_code == MS.DAILY_POST_GENERAL: await self.reward_daily_post(user_id=user_id, cheese_manager_id=cheese_manager_id) - elif mission_code == "MS_LV_UP": + elif mission_code == MS.LV_UP: await self.reward_level_up_mission( user_id=user_id, cheese_manager_id=cheese_manager_id, reward_code=reward_code ) - elif mission_code.startswith("MS_BADGE"): + elif mission_code.startswith(MS.BADGE): await self.reward_badge_mission( user_id=user_id, cheese_manager_id=cheese_manager_id, reward_code=reward_code ) @@ -127,33 +131,42 @@ async def _handle_mission_reward( ) async def evaluate_mission_condition(self, user_id: str, mission_code: str) -> int: - if mission_code == "MS_BADGE_POST_FIRST" and await self.check_first_post(user_id): + if mission_code == MS.BADGE_POST_FIRST and await self.check_first_post(user_id): return 1 - elif mission_code == "MS_BADGE_POST_280_CHAR" and await self.check_long_answer(user_id): + elif mission_code == MS.BADGE_POST_280_CHAR and await self.check_long_answer(user_id): return 1 - elif mission_code == "MS_BADGE_POST_CONSECUTIVE_7" and await self.check_consecutive_days(user_id): + elif mission_code == MS.BADGE_POST_CONSECUTIVE_7 and await self.check_consecutive_days(user_id): return 1 - elif mission_code == "MS_BADGE_POST_EARLY_3" and await self.check_early_morning_posts(user_id): + elif mission_code == MS.BADGE_POST_EARLY_3 and await self.check_early_morning_posts(user_id): return 1 - elif mission_code == "MS_BADGE_CHEESE_TOTAL_50" and await self.check_cheese_total(user_id): + elif mission_code == MS.BADGE_CHEESE_TOTAL_50 and await self.check_cheese_total(user_id): return 1 - elif mission_code == "MS_BADGE_CHRISTMAS" and await self.check_christmas_period(): + elif mission_code == MS.BADGE_CHRISTMAS and await self.check_christmas_period(): return 1 - elif mission_code == "MS_DAILY_LIKE_3_PER_DAY" and await self.check_three_likes_different_posts(user_id): + elif mission_code == MS.DAILY_LIKE_3_PER_DAY and await self.check_three_likes_different_posts(user_id): return 1 - elif mission_code == "MS_LV_UP" and await LevelService.level_up(user_id=user_id): + elif mission_code == MS.DAILY_POST_GENERAL and await self.check_daily_post(user_id): return 1 - elif mission_code == "MS_DAILY_POST_GENERAL" and await self.check_daily_post(user_id): + elif mission_code == MS.LV_UP and await LevelService.level_up(user_id=user_id): return 1 return 0 + @staticmethod + async def _classify_missions( + user_missions: list[UserMissionDTO], + ) -> tuple[list[UserMissionDTO], list[UserMissionDTO], list[UserMissionDTO]]: + badge_missions = [mission for mission in user_missions if mission.mission_code.startswith(MS.BADGE)] + lv_up_mission = [mission for mission in user_missions if mission.mission_code == MS.LV_UP] + daily_missions = [mission for mission in user_missions if mission.mission_code.startswith(MS.DAILY)] + return badge_missions, lv_up_mission, daily_missions + @staticmethod async def check_first_post(user_id: str) -> bool: - return await AnswerService.get_answer_count_v2(user_id=user_id) > 0 + return await Answer.get_answer_count_by_user_id_v2(user_id=user_id) > 0 @staticmethod async def get_answer_count(user_id: str) -> int: - return await AnswerService.get_answer_count_v2(user_id=user_id) + return await Answer.get_answer_count_by_user_id_v2(user_id=user_id) @staticmethod async def check_post_count_range(answer_count: int, min_count: int, max_count: int) -> bool: @@ -161,8 +174,8 @@ async def check_post_count_range(answer_count: int, min_count: int, max_count: i @staticmethod async def check_long_answer(user_id: str) -> bool: - recent_answer = await AnswerService.get_most_recent_answer(user_id=user_id) - return len(recent_answer["content"]) >= 280 if recent_answer else False + recent_answer = await Answer.get_most_recent_answer_by_user_id(user_id=user_id) + return len(recent_answer.content) >= 280 if recent_answer else False @staticmethod async def check_consecutive_days(user_id: str) -> bool: @@ -171,11 +184,8 @@ async def check_consecutive_days(user_id: str) -> bool: @staticmethod async def check_early_morning_posts(user_id: str) -> bool: - recent_answer = await AnswerService.get_most_recent_answer(user_id=user_id) - if recent_answer: - answer_time = recent_answer.get("created_time") - return 0 <= answer_time.hour <= 5 if isinstance(answer_time, datetime) else False - return False + recent_answer = await Answer.get_most_recent_answer_by_user_id(user_id=user_id) + return 0 <= recent_answer.created_time.hour <= 5 if recent_answer else False @staticmethod async def check_cheese_total(user_id: str) -> bool: @@ -196,20 +206,15 @@ async def check_christmas_period() -> bool: @staticmethod async def check_three_likes_different_posts(user_id: str) -> bool: - like_raw = await Like.get_unique_likes_today(user_id) - like_count: int = like_raw.get("unique_likes", 0) + like_count = await Like.get_unique_likes_today(user_id) return like_count >= 3 @staticmethod async def check_daily_post(user_id: str) -> bool: now = datetime.now(settings.db_zoneinfo) - current_date = (now - timedelta(days=1)).date() if now.hour < 6 else now.date() - - answer = await AnswerService.get_most_recent_answer(user_id=user_id) - answer_date = answer.get("date") - - return answer_date == current_date # type: ignore + answer = await Answer.get_most_recent_answer_by_user_id(user_id=user_id) + return answer.date == current_date if answer else False @staticmethod async def validate_reward(reward_code: str): # type: ignore @@ -217,14 +222,14 @@ async def validate_reward(reward_code: str): # type: ignore reward = await RewardInventory.filter(reward_code=reward_code).prefetch_related("item_inventories").first() if not reward: - raise HTTPException(status_code=404, detail="Reward not found.") + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Reward not found.") item_inventory_rewards = reward.item_inventories return item_inventory_rewards except DoesNotExist: - raise HTTPException(status_code=404, detail="Reward not found.") + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Reward not found.") async def process_reward( self, @@ -240,19 +245,19 @@ async def process_reward( item: ItemInventory = await item_inventory_reward.item_inventory quantity = item_inventory_reward.quantity - if item.item_category == "BADGE": + if item.item_category == ItemCategory.BADGE: for _ in range(quantity): await Badge.create_by_user_id(user_id=user_id, badge_code=item.item_code) - badge = await BadgeService.get_badge_info_by_badge_code(badge_code=item.item_code) + badge = await BadgeInventory.get_by_badge_code(badge_code=item.item_code) badge_info.append(badge) - elif item.item_category == "COLOR": + elif item.item_category == ItemCategory.COLOR: for _ in range(quantity): await Color.create_by_user_id(user_id=user_id, color_code=item.item_code) - elif item.item_category == "CHEESE": + elif item.item_category == ItemCategory.CHEESE: total_cheese += quantity await CheeseManager.add_cheese(cheese_manager_id=cheese_manager_id, amount=quantity) - elif item.item_category == "POINT": + elif item.item_category == ItemCategory.POINT: total_exp += quantity await LevelService.add_exp(user_id=user_id, exp=quantity) else: @@ -261,8 +266,11 @@ async def process_reward( badge_full_name = badge_info[0].badge_full_name if badge_info else None badge_code = badge_info[0].badge_code if badge_info else None - return await RewardDTO.build( - total_cheese=total_cheese, total_exp=total_exp, badge_full_name=badge_full_name, badge_code=badge_code + return RewardDTO( + total_cheese=total_cheese, + total_exp=total_exp, + badge_full_name=badge_full_name, + badge_code=badge_code, ) async def reward_daily_post(self, user_id: str, cheese_manager_id: int) -> None: @@ -275,7 +283,7 @@ async def reward_daily_post(self, user_id: str, cheese_manager_id: int) -> None: # 3. 보상 알림 생성 await self._create_reward_notice( user_id=user_id, - reward_type="DAILY_MISSION", + reward_type=RewardType.DAILY_MISSION, total_exp=exp, total_cheese=cheese, ) @@ -335,11 +343,11 @@ async def _create_reward_notice( reward_type: str, total_exp: int, total_cheese: int, - badge_full_name: Optional[str] = None, - badge_code: Optional[str] = None, - level_up: Optional[bool] = False, - nickname: Optional[str] = None, - new_level: Optional[int] = None, + badge_full_name: str | None = None, + badge_code: str | None = None, + level_up: bool = False, + nickname: str | None = None, + new_level: int | None = None, ) -> None: await NoticeService.create_reward_notice( user_id=user_id, @@ -368,7 +376,7 @@ async def reward_level_up_mission(self, user_id: str, cheese_manager_id: int, re await self._create_reward_notice( user_id=user_id, - reward_type="LEVEL_UP", + reward_type=RewardType.LEVEL_UP, total_exp=reward_dto.total_exp, total_cheese=reward_dto.total_cheese, level_up=True, @@ -385,7 +393,7 @@ async def reward_badge_mission(self, user_id: str, cheese_manager_id: int, rewar ) await self._create_reward_notice( user_id=user_id, - reward_type="BADGE_MISSION", + reward_type=RewardType.BADGE_MISSION, total_exp=reward_dto.total_exp, total_cheese=reward_dto.total_cheese, badge_code=reward_dto.badge_code, @@ -401,7 +409,7 @@ async def reward_mission(self, user_id: str, cheese_manager_id: int, reward_code ) await self._create_reward_notice( user_id=user_id, - reward_type="DAILY_MISSION", + reward_type=RewardType.DAILY_MISSION, total_exp=reward_dto.total_exp, total_cheese=reward_dto.total_cheese, ) diff --git a/app/services/notice_service.py b/app/services/notice_service.py index 74fe793..df65264 100644 --- a/app/services/notice_service.py +++ b/app/services/notice_service.py @@ -1,5 +1,3 @@ -from typing import Optional - from app.models.notice import Notice @@ -12,7 +10,7 @@ async def create_notice( title: str, reward_type: str, content: str, - badge_code: Optional[str] = None, + badge_code: str | None = None, ) -> None: return await Notice.create_notice( title=title, @@ -31,11 +29,11 @@ async def create_reward_notice( reward_type: str, total_cheese: int = 0, total_exp: int = 0, - badge_full_name: Optional[str] = None, - badge_code: Optional[str] = None, - level_up: Optional[bool] = False, - nickname: Optional[str] = None, - new_level: Optional[int] = None, + badge_full_name: str | None = None, + badge_code: str | None = None, + level_up: bool = False, + nickname: str | None = None, + new_level: int | None = None, ) -> None: if not badge_code and not level_up and total_cheese == 0 and total_exp == 0: return @@ -66,10 +64,10 @@ async def create_reward_notice( @classmethod def create_title( cls, - badge_full_name: Optional[str] = None, - level_up: Optional[bool] = False, - nickname: Optional[str] = None, - new_level: Optional[int] = None, + level_up: bool = False, + badge_full_name: str | None = None, + nickname: str | None = None, + new_level: int | None = None, ) -> str: if level_up and nickname and new_level is not None: return f"{nickname} LV{new_level}로 레벨업!" @@ -79,7 +77,7 @@ def create_title( @classmethod def create_reward_message( - cls, total_cheese: int, total_exp: int, badge_full_name: Optional[str] = None, level_up: Optional[bool] = False + cls, total_cheese: int, total_exp: int, badge_full_name: str | None = None, level_up: bool = False ) -> str: if level_up: return f"선물로 치즈 {total_cheese}개를 드릴게요!" if total_cheese > 0 else "레벨업을 축하드립니다!" diff --git a/app/services/purchase_service.py b/app/services/purchase_service.py index 8831dc2..dc9c642 100644 --- a/app/services/purchase_service.py +++ b/app/services/purchase_service.py @@ -1,309 +1,309 @@ -import time -import uuid -from datetime import date, datetime, timedelta, timezone -from typing import Any, Optional, cast - -import httpx -from fastapi import HTTPException -from tortoise.exceptions import DoesNotExist -from tortoise.transactions import atomic - -from app.common.exceptions.custom_exception import CustomException -from app.common.exceptions.error_code import ErrorCode -from app.core.configs import settings -from app.dtos.purchase.purchase_dto import PurchaseResponseDTO, ReceiptInfoDTO -from app.models.item import ItemInventory, ItemInventoryProductInventory, ProductInventory -from app.models.purchase_history import PurchaseHistory, Subscription -from app.models.purchase_status import PurchaseStatus, SubscriptionStatus -from app.models.user import User -from app.services.user_service import UserService - - -class PurchaseService: - @atomic() - async def process_apple_purchase(self, receipt_data: str, user_id: str) -> PurchaseResponseDTO: - response = await self._validate_apple_receipt(receipt_data=receipt_data) - - latest_receipt_info = self._extract_latest_receipt_info(response) - - if latest_receipt_info is None: - raise CustomException(ErrorCode.NO_VALID_RECEIPT) - - receipt_info = await self._parse_receipt_info(latest_receipt_info) - - subscription_status = self.get_subscription_status(receipt_info) - - await self._create_or_update_subscription( - user_id=user_id, - product_code=receipt_info.product_code, - transaction_id=receipt_info.transaction_id, - expires_date_ms=receipt_info.expires_date_ms, - status=subscription_status, - ) - - subscription = await self._get_subscription(user_id, receipt_info.product_code) - - if subscription is None: - raise DoesNotExist("Subscription not found") - - item_inventory_products = await self._validate_purchase(product_code=receipt_info.product_code) - - await self._process_purchase( - user_id=user_id, - item_inventory_products=item_inventory_products, - status=subscription_status, - ) - - user = await User.get_user_profile_by_user_id(user_id=user_id) - - return PurchaseResponseDTO.build(is_premium=user.is_premium, product_code=receipt_info.product_code_two) - - @staticmethod - def _extract_latest_receipt_info(response: dict[str, Any]) -> dict[str, Any] | None: - latest_receipt_info = response.get("latest_receipt_info") - - if isinstance(latest_receipt_info, list) and latest_receipt_info: - return latest_receipt_info[0] or {} - return None - - async def _validate_apple_receipt(self, receipt_data: str) -> dict[str, Any]: - url = settings.APPLE_URL - payload = self.create_receipt_validation_payload(receipt_data) - response = await self.send_receipt_validation_request(url, payload) - return await self.parse_apple_response(response) - - @staticmethod - def create_receipt_validation_payload(receipt_data: str) -> dict[str, Any]: - return { - "receipt-data": receipt_data, - "password": settings.APPLE_SHARED_SECRET, - } - - @staticmethod - async def send_receipt_validation_request(url: str, payload: dict[str, Any]) -> httpx.Response: - async with httpx.AsyncClient() as client: - response = await client.post(url, json=payload) - return response - - @staticmethod - async def parse_apple_response(response: httpx.Response) -> dict[str, Any]: - if response.status_code == 200: - return cast(dict[str, Any], response.json()) - else: - raise HTTPException(status_code=500, detail="Failed to connect to Apple server") - - @staticmethod - async def _create_or_update_subscription( - user_id: str, - product_code: str, - transaction_id: str, - expires_date_ms: int, - status: str, - ) -> None: - await Subscription.create_or_update_subscription( - user_id=user_id, - product_code=product_code, - transaction_id=transaction_id, - expires_date_ms=expires_date_ms, - status=status, - ) - - @staticmethod - def get_subscription_status(receipt: ReceiptInfoDTO) -> str: - current_time_ms = int(time.time() * 1000) - - if receipt.cancellation_date_ms: - return SubscriptionStatus.CANCELED.value - - if receipt.expires_date_ms < current_time_ms: - return SubscriptionStatus.EXPIRED.value - - return SubscriptionStatus.ACTIVE.value - - @staticmethod - def get_purchase_status(cancellation_date_ms: Optional[int]) -> str: - if cancellation_date_ms: - return PurchaseStatus.REFUNDED.value - return PurchaseStatus.AVAILABLE.value - - @staticmethod - async def _get_subscription(user_id: str, product_code: str) -> Subscription | None: - return await Subscription.get_subscription_by_user_id_and_product_code( - user_id=user_id, product_code=product_code - ) - - @staticmethod - async def _create_purchase_history( - user_id: str, - subscription: Subscription, - product_code: str, - transaction_id: str, - original_transaction_id: str, - status: str, - expires_date_ms: int, - purchase_date_ms: int, - quantity: int, - receipt_data: str, - ) -> None: - await PurchaseHistory.create_purchase_history( - user_id=user_id, - subscription_id=subscription.subscription_id if subscription else None, - product_code=product_code, - transaction_id=transaction_id, - original_transaction_id=original_transaction_id, - status=status, - expires_date_ms=expires_date_ms, - purchase_date_ms=purchase_date_ms, - quantity=quantity, - receipt_data=receipt_data, - ) - - @staticmethod - async def _validate_purchase( - product_code: str, - ) -> list[ItemInventoryProductInventory]: - try: - product = await ProductInventory.get(product_code=product_code) - - if product.transaction_currency not in ["KRW", "CHEESE"]: - raise HTTPException(status_code=400, detail="Invalid transaction currency for purchase.") - - item_inventory_products = await ItemInventoryProductInventory.filter( - product_inventory_id=product.product_id - ).all() - - if not item_inventory_products: - raise HTTPException(status_code=404, detail="No inventory found for this product.") - return item_inventory_products - except DoesNotExist: - raise HTTPException(status_code=404, detail="Product not found.") - - @classmethod - async def _process_purchase( - cls, - item_inventory_products: list[ItemInventoryProductInventory], - user_id: str, - status: str = "ACTIVE", - # cheese_manager_id: int, - ) -> None: - for item_inventory_product in item_inventory_products: - item: ItemInventory = await item_inventory_product.item_inventory - # quantity = item_inventory_product.quantity - - if item.item_category == "SUBSCRIPTION": - if status == SubscriptionStatus.ACTIVE.value: - await UserService.set_is_premium(user_id=user_id, is_premium=True) - if status == SubscriptionStatus.CANCELED.value or status == SubscriptionStatus.EXPIRED.value: - await UserService.set_is_premium(user_id=user_id, is_premium=False) - # elif item.item_category == "CHEESE": - # await CheeseService.add_cheese(cheese_manager_id=cheese_manager_id, amount=quantity) - else: - raise ValueError(f"Invalid item category for purchase: {item.item_category}") - - async def renew_subscription(self, subscription: Subscription) -> None: - - purchase_history = await PurchaseHistory.filter(transaction_id=subscription.current_transaction_id).first() - - if not purchase_history: - return - - response = await self._validate_apple_receipt(receipt_data=purchase_history.receipt_data) - latest_receipt_info = self._extract_latest_receipt_info(response) - - if latest_receipt_info is None: - raise CustomException(ErrorCode.NO_VALID_RECEIPT) - - receipt_data = await self._parse_receipt_info(latest_receipt_info) - - purchase_status = self.get_purchase_status(receipt_data.cancellation_date_ms) - - if not await self._check_auto_renewal(response.get("pending_renewal_info", [])): - return - - await self._update_subscription_expiration( - subscription=subscription, - expires_date_ms=receipt_data.expires_date_ms, - transaction_id=receipt_data.transaction_id, - ) - - await self._create_purchase_history( - user_id=str(uuid.UUID(bytes=subscription.user.user_id)), # type: ignore - subscription=subscription, - product_code=receipt_data.product_code, - transaction_id=receipt_data.transaction_id, - original_transaction_id=receipt_data.original_transaction_id, - status=purchase_status, - expires_date_ms=receipt_data.expires_date_ms, - purchase_date_ms=receipt_data.purchase_date_ms, - quantity=receipt_data.quantity, - receipt_data=purchase_history.receipt_data, - ) - - @staticmethod - async def get_subscriptions_to_renew(today: datetime) -> list[Subscription]: - return ( - await Subscription.filter(expires_date__lte=today + timedelta(days=1), status="ACTIVE") - .select_related("user") - .all() - ) - - @atomic() - async def process_subscriptions_renewal(self) -> None: - today = datetime.now(timezone.utc) + timedelta(hours=9) - subscriptions_to_renew = await self.get_subscriptions_to_renew(today) - - for subscription in subscriptions_to_renew: - await self.renew_subscription(subscription) - - @staticmethod - async def _update_subscription_expiration( - subscription: Subscription, expires_date_ms: int, transaction_id: str - ) -> None: - new_expires_date = datetime.fromtimestamp(expires_date_ms / 1000) - subscription.expires_date = new_expires_date - subscription.current_transaction_id = transaction_id - await subscription.save() - - @staticmethod - async def _parse_receipt_info(latest_receipt_info: dict[str, Any]) -> ReceiptInfoDTO: - return ReceiptInfoDTO.build(latest_receipt_info) - - @staticmethod - async def _check_auto_renewal(pending_renewal_info: list[dict[str, Any]]) -> bool: - if pending_renewal_info: - auto_renew_status = pending_renewal_info[0].get("auto_renew_status") - expiration_intent = pending_renewal_info[0].get("expiration_intent") - - if auto_renew_status == "0" or expiration_intent == "1": - return False - return True - - @staticmethod - async def get_expired_subscriptions(today: date) -> list[Subscription]: - return ( - await Subscription.filter(status=SubscriptionStatus.ACTIVE.value, expires_date__lt=today) - .select_related("user") - .all() - ) - - @staticmethod - async def update_subscription_status(expired_subscriptions: list[Subscription]) -> None: - for subscription in expired_subscriptions: - subscription.status = SubscriptionStatus.EXPIRED.value - await Subscription.bulk_update(expired_subscriptions, fields=["status"]) - - @staticmethod - async def update_user_premium_status(expired_subscriptions: list[Subscription]) -> None: - user_ids = [subscription.user.user_id for subscription in expired_subscriptions] - if user_ids: - await User.bulk_update_is_premium(user_ids) # type: ignore - - @atomic() - async def expire_subscriptions(self) -> None: - today = date.today() - - expired_subscriptions = await self.get_expired_subscriptions(today) - - if expired_subscriptions: - await self.update_subscription_status(expired_subscriptions) - await self.update_user_premium_status(expired_subscriptions) +# import time +# import uuid +# from datetime import date, datetime, timedelta, timezone +# from typing import Any, Optional, cast +# +# import httpx +# from fastapi import HTTPException +# from tortoise.exceptions import DoesNotExist +# from tortoise.transactions import atomic +# +# from app.common.exceptions.custom_exception import CustomException +# from app.common.exceptions.error_code import ErrorCode +# from app.core.configs import settings +# from app.dtos.purchase.purchase_dto import PurchaseResponseDTO, ReceiptInfoDTO +# from app.models.item import ItemInventory, ItemInventoryProductInventory, ProductInventory +# from app.models.purchase_history import PurchaseHistory, Subscription +# from app.models.purchase_status import PurchaseStatus, SubscriptionStatus +# from app.models.user import User +# from app.services.user_service import UserService +# +# +# class PurchaseService: +# @atomic() +# async def process_apple_purchase(self, receipt_data: str, user_id: str) -> PurchaseResponseDTO: +# response = await self._validate_apple_receipt(receipt_data=receipt_data) +# +# latest_receipt_info = self._extract_latest_receipt_info(response) +# +# if latest_receipt_info is None: +# raise CustomException(ErrorCode.NO_VALID_RECEIPT) +# +# receipt_info = await self._parse_receipt_info(latest_receipt_info) +# +# subscription_status = self.get_subscription_status(receipt_info) +# +# await self._create_or_update_subscription( +# user_id=user_id, +# product_code=receipt_info.product_code, +# transaction_id=receipt_info.transaction_id, +# expires_date_ms=receipt_info.expires_date_ms, +# status=subscription_status, +# ) +# +# subscription = await self._get_subscription(user_id, receipt_info.product_code) +# +# if subscription is None: +# raise DoesNotExist("Subscription not found") +# +# item_inventory_products = await self._validate_purchase(product_code=receipt_info.product_code) +# +# await self._process_purchase( +# user_id=user_id, +# item_inventory_products=item_inventory_products, +# status=subscription_status, +# ) +# +# user = await User.get_user_profile_by_user_id(user_id=user_id) +# +# return PurchaseResponseDTO.build(is_premium=user.is_premium, product_code=receipt_info.product_code_two) +# +# @staticmethod +# def _extract_latest_receipt_info(response: dict[str, Any]) -> dict[str, Any] | None: +# latest_receipt_info = response.get("latest_receipt_info") +# +# if isinstance(latest_receipt_info, list) and latest_receipt_info: +# return latest_receipt_info[0] or {} +# return None +# +# async def _validate_apple_receipt(self, receipt_data: str) -> dict[str, Any]: +# url = settings.APPLE_URL +# payload = self.create_receipt_validation_payload(receipt_data) +# response = await self.send_receipt_validation_request(url, payload) +# return await self.parse_apple_response(response) +# +# @staticmethod +# def create_receipt_validation_payload(receipt_data: str) -> dict[str, Any]: +# return { +# "receipt-data": receipt_data, +# "password": settings.APPLE_SHARED_SECRET, +# } +# +# @staticmethod +# async def send_receipt_validation_request(url: str, payload: dict[str, Any]) -> httpx.Response: +# async with httpx.AsyncClient() as client: +# response = await client.post(url, json=payload) +# return response +# +# @staticmethod +# async def parse_apple_response(response: httpx.Response) -> dict[str, Any]: +# if response.status_code == 200: +# return cast(dict[str, Any], response.json()) +# else: +# raise HTTPException(status_code=500, detail="Failed to connect to Apple server") +# +# @staticmethod +# async def _create_or_update_subscription( +# user_id: str, +# product_code: str, +# transaction_id: str, +# expires_date_ms: int, +# status: str, +# ) -> None: +# await Subscription.create_or_update_subscription( +# user_id=user_id, +# product_code=product_code, +# transaction_id=transaction_id, +# expires_date_ms=expires_date_ms, +# status=status, +# ) +# +# @staticmethod +# def get_subscription_status(receipt: ReceiptInfoDTO) -> str: +# current_time_ms = int(time.time() * 1000) +# +# if receipt.cancellation_date_ms: +# return SubscriptionStatus.CANCELED.value +# +# if receipt.expires_date_ms < current_time_ms: +# return SubscriptionStatus.EXPIRED.value +# +# return SubscriptionStatus.ACTIVE.value +# +# @staticmethod +# def get_purchase_status(cancellation_date_ms: Optional[int]) -> str: +# if cancellation_date_ms: +# return PurchaseStatus.REFUNDED.value +# return PurchaseStatus.AVAILABLE.value +# +# @staticmethod +# async def _get_subscription(user_id: str, product_code: str) -> Subscription | None: +# return await Subscription.get_subscription_by_user_id_and_product_code( +# user_id=user_id, product_code=product_code +# ) +# +# @staticmethod +# async def _create_purchase_history( +# user_id: str, +# subscription: Subscription, +# product_code: str, +# transaction_id: str, +# original_transaction_id: str, +# status: str, +# expires_date_ms: int, +# purchase_date_ms: int, +# quantity: int, +# receipt_data: str, +# ) -> None: +# await PurchaseHistory.create_purchase_history( +# user_id=user_id, +# subscription_id=subscription.subscription_id if subscription else None, +# product_code=product_code, +# transaction_id=transaction_id, +# original_transaction_id=original_transaction_id, +# status=status, +# expires_date_ms=expires_date_ms, +# purchase_date_ms=purchase_date_ms, +# quantity=quantity, +# receipt_data=receipt_data, +# ) +# +# @staticmethod +# async def _validate_purchase( +# product_code: str, +# ) -> list[ItemInventoryProductInventory]: +# try: +# product = await ProductInventory.get(product_code=product_code) +# +# if product.transaction_currency not in ["KRW", "CHEESE"]: +# raise HTTPException(status_code=400, detail="Invalid transaction currency for purchase.") +# +# item_inventory_products = await ItemInventoryProductInventory.filter( +# product_inventory_id=product.product_id +# ).all() +# +# if not item_inventory_products: +# raise HTTPException(status_code=404, detail="No inventory found for this product.") +# return item_inventory_products +# except DoesNotExist: +# raise HTTPException(status_code=404, detail="Product not found.") +# +# @classmethod +# async def _process_purchase( +# cls, +# item_inventory_products: list[ItemInventoryProductInventory], +# user_id: str, +# status: str = "ACTIVE", +# # cheese_manager_id: int, +# ) -> None: +# for item_inventory_product in item_inventory_products: +# item: ItemInventory = await item_inventory_product.item_inventory +# # quantity = item_inventory_product.quantity +# +# if item.item_category == "SUBSCRIPTION": +# if status == SubscriptionStatus.ACTIVE.value: +# await UserService.set_is_premium(user_id=user_id, is_premium=True) +# if status == SubscriptionStatus.CANCELED.value or status == SubscriptionStatus.EXPIRED.value: +# await UserService.set_is_premium(user_id=user_id, is_premium=False) +# # elif item.item_category == "CHEESE": +# # await CheeseService.add_cheese(cheese_manager_id=cheese_manager_id, amount=quantity) +# else: +# raise ValueError(f"Invalid item category for purchase: {item.item_category}") +# +# async def renew_subscription(self, subscription: Subscription) -> None: +# +# purchase_history = await PurchaseHistory.filter(transaction_id=subscription.current_transaction_id).first() +# +# if not purchase_history: +# return +# +# response = await self._validate_apple_receipt(receipt_data=purchase_history.receipt_data) +# latest_receipt_info = self._extract_latest_receipt_info(response) +# +# if latest_receipt_info is None: +# raise CustomException(ErrorCode.NO_VALID_RECEIPT) +# +# receipt_data = await self._parse_receipt_info(latest_receipt_info) +# +# purchase_status = self.get_purchase_status(receipt_data.cancellation_date_ms) +# +# if not await self._check_auto_renewal(response.get("pending_renewal_info", [])): +# return +# +# await self._update_subscription_expiration( +# subscription=subscription, +# expires_date_ms=receipt_data.expires_date_ms, +# transaction_id=receipt_data.transaction_id, +# ) +# +# await self._create_purchase_history( +# user_id=str(uuid.UUID(bytes=subscription.user.user_id)), # type: ignore +# subscription=subscription, +# product_code=receipt_data.product_code, +# transaction_id=receipt_data.transaction_id, +# original_transaction_id=receipt_data.original_transaction_id, +# status=purchase_status, +# expires_date_ms=receipt_data.expires_date_ms, +# purchase_date_ms=receipt_data.purchase_date_ms, +# quantity=receipt_data.quantity, +# receipt_data=purchase_history.receipt_data, +# ) +# +# @staticmethod +# async def get_subscriptions_to_renew(today: datetime) -> list[Subscription]: +# return ( +# await Subscription.filter(expires_date__lte=today + timedelta(days=1), status="ACTIVE") +# .select_related("user") +# .all() +# ) +# +# @atomic() +# async def process_subscriptions_renewal(self) -> None: +# today = datetime.now(timezone.utc) + timedelta(hours=9) +# subscriptions_to_renew = await self.get_subscriptions_to_renew(today) +# +# for subscription in subscriptions_to_renew: +# await self.renew_subscription(subscription) +# +# @staticmethod +# async def _update_subscription_expiration( +# subscription: Subscription, expires_date_ms: int, transaction_id: str +# ) -> None: +# new_expires_date = datetime.fromtimestamp(expires_date_ms / 1000) +# subscription.expires_date = new_expires_date +# subscription.current_transaction_id = transaction_id +# await subscription.save() +# +# @staticmethod +# async def _parse_receipt_info(latest_receipt_info: dict[str, Any]) -> ReceiptInfoDTO: +# return ReceiptInfoDTO.build(latest_receipt_info) +# +# @staticmethod +# async def _check_auto_renewal(pending_renewal_info: list[dict[str, Any]]) -> bool: +# if pending_renewal_info: +# auto_renew_status = pending_renewal_info[0].get("auto_renew_status") +# expiration_intent = pending_renewal_info[0].get("expiration_intent") +# +# if auto_renew_status == "0" or expiration_intent == "1": +# return False +# return True +# +# @staticmethod +# async def get_expired_subscriptions(today: date) -> list[Subscription]: +# return ( +# await Subscription.filter(status=SubscriptionStatus.ACTIVE.value, expires_date__lt=today) +# .select_related("user") +# .all() +# ) +# +# @staticmethod +# async def update_subscription_status(expired_subscriptions: list[Subscription]) -> None: +# for subscription in expired_subscriptions: +# subscription.status = SubscriptionStatus.EXPIRED.value +# await Subscription.bulk_update(expired_subscriptions, fields=["status"]) +# +# @staticmethod +# async def update_user_premium_status(expired_subscriptions: list[Subscription]) -> None: +# user_ids = [subscription.user.user_id for subscription in expired_subscriptions] +# if user_ids: +# await User.bulk_update_is_premium(user_ids) # type: ignore +# +# @atomic() +# async def expire_subscriptions(self) -> None: +# today = date.today() +# +# expired_subscriptions = await self.get_expired_subscriptions(today) +# +# if expired_subscriptions: +# await self.update_subscription_status(expired_subscriptions) +# await self.update_user_premium_status(expired_subscriptions) diff --git a/app/services/teller_card_service.py b/app/services/teller_card_service.py index aaa2bd6..20e9cb3 100644 --- a/app/services/teller_card_service.py +++ b/app/services/teller_card_service.py @@ -1,5 +1,5 @@ from app.dtos.teller_card.teller_card_dto import TellerCardDTO -from app.models.badge import BadgeInventory +from app.models.badge_inventory import BadgeInventory from app.models.color_inventory import ColorInventory from app.models.teller_card import TellerCard From d04d640afb0650a9c5a3d699cdf8e52109aec24e Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Wed, 11 Jun 2025 17:47:14 +0900 Subject: [PATCH 13/27] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20CI=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/checks.yml | 140 +++++++++++++++++++++++++++++------ app/models/cheese_manager.py | 6 +- app/models/color.py | 4 +- app/models/user.py | 2 +- pyproject.toml | 4 - 5 files changed, 125 insertions(+), 31 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 09d40a6..7b80496 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -1,41 +1,137 @@ -name: Code Quality Checks +name: CI on: push: - branches: - - develop - pull_request: -jobs: - ci: - runs-on: ubuntu-latest +jobs: + static-analysis: # mypy, black, ruff 등 정적 분석 + runs-on: ubuntu-22.04 # 실제 프로덕션에서는 모든 버전을 고정하는 것이 좋다. + # 예기치 못하게 버전이 올라가서 장애나는 것을 막기 위해 steps: - - name: Checkout code - uses: actions/checkout@v3 + - name: Check out the codes + uses: actions/checkout@v2 + + - name: Setup python environment + id: setup-python + uses: actions/setup-python@v2 + with: + python-version: "3.12" - - name: Set up Python - uses: actions/setup-python@v4 + - name: Cache Poetry + id: cache-poetry + uses: actions/cache@v4 with: - python-version: '3.12' + key: poetry-1.8.5 + path: ~/.local/ # poetry 는 ~/.local 에 설치되므로, 이 디렉터리를 통째로 캐시할 것입니다. - name: Install Poetry + if: steps.cache-poetry.outputs.cache-hit != 'true' + run: | + curl -sSL https://install.python-poetry.org | python3 - --version 1.8.5 + + - name: Register Poetry bin + run: echo "${HOME}/.poetry/bin" >> $GITHUB_PATH + + - name: Cache dependencies + id: cache-venv + uses: actions/cache@v4 + with: + key: python-${{ steps.setup-python.outputs.python-version }}-poetry-lock-${{ hashFiles('poetry.lock') }}-toml-${{ hashFiles('pyproject.toml') }}-poetry-1.8.5 + path: /home/runner/.cache/pypoetry/virtualenvs/ + + - name: Install dependencies + if: steps.cache-venv.outputs.cache-hit != 'true' + run: poetry install --no-root + + - name: Run Black + run: poetry run black . --check + + - name: Run Ruff run: | - curl -sSL https://install.python-poetry.org | python3 - - echo "${HOME}/.local/bin" >> $GITHUB_PATH + poetry run ruff check --select I + poetry run ruff check + + - name: Run Mypy + run: poetry run mypy . + + test: # 전체 테스트 실행한다. + runs-on: ubuntu-22.04 + env: + MYSQL_HOST: 127.0.0.1 + MYSQL_PORT: 3306 + MYSQL_USER: root + MYSQL_PASSWORD: 1234 + MYSQL_DATABASE: db_telling_me_dev - - name: Install Packages & Libraries + steps: + - name: Check out the codes + uses: actions/checkout@v2 + + - name: Setup python environment + id: setup-python + uses: actions/setup-python@v2 + with: + python-version: "3.12" + + - name: Cache Poetry + id: cache-poetry + uses: actions/cache@v4 + with: + key: poetry-1.8.5 + path: ~/.local/ # poetry 는 ~/.local 에 설치되므로, 이 디렉터리를 통째로 캐시할 것입니다. + + - name: Install Poetry + if: steps.cache-poetry.outputs.cache-hit != 'true' run: | - poetry install + curl -sSL https://install.python-poetry.org | python3 - --version 1.8.5 - - name: Run isort (Import sorting) + - name: Register Poetry bin + run: echo "${HOME}/.poetry/bin" >> $GITHUB_PATH + + - name: Cache dependencies + id: cache-venv + uses: actions/cache@v4 + with: + key: python-${{ steps.setup-python.outputs.python-version }}-poetry-lock-${{ hashFiles('poetry.lock') }}-toml-${{ hashFiles('pyproject.toml') }}-poetry-1.8.5 + path: /home/runner/.cache/pypoetry/virtualenvs/ + + - name: Install dependencies + if: steps.cache-venv.outputs.cache-hit != 'true' + run: poetry install --no-root + + - name: Set timezone to KST run: | - poetry run isort . --check --diff + sudo rm /etc/localtime + sudo ln -s /usr/share/zoneinfo/Asia/Seoul /etc/localtime - - name: Run black (Code formatting) + - name: Start Mysql run: | - poetry run black . --check + sudo systemctl start mysql + mysql -e "use mysql; FLUSH PRIVILEGES; ALTER USER '${{ env.MYSQL_USER }}'@'localhost' IDENTIFIED BY '${{ env.MYSQL_PASSWORD }}';" -uroot -proot + mysql -e 'CREATE DATABASE ${{ env.MYSQL_DATABASE }};' -u${{ env.MYSQL_USER }} -p${{ env.MYSQL_PASSWORD }} - - name: Run Mypy + - name: Run tests run: | - poetry run mypy . + poetry run coverage run -m pytest . + poetry run coverage report -m + + +# deploy: +# runs-on: ubuntu-24.04 +# needs: [test, static-analysis] +# if: github.ref == 'refs/heads/main' +# steps: +# - name: Check out the codes +# uses: actions/checkout@v3 +# +# - name: deploy staging +# env: +# PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY_STAGING }} +# HOSTNAME: ${{ secrets.SSH_HOST_STAGING }} +# USER_NAME: ${{ secrets.USER_NAME_STAGING }} +# +# # staging 서버의 .bashrc 에 gunicorn_reload 가 정의되어 있습니다. gunicorn master 에게 HUP 를 줘서 worker 를 재시작합니다. +# run: | +# echo "$PRIVATE_KEY" > private_key && chmod 600 private_key +# ssh -o StrictHostKeyChecking=no -t -i private_key ${USER_NAME}@${HOSTNAME} "bash -i -c 'gunicorn_reload'" \ No newline at end of file diff --git a/app/models/cheese_manager.py b/app/models/cheese_manager.py index 7e36e41..9a3ee47 100644 --- a/app/models/cheese_manager.py +++ b/app/models/cheese_manager.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import cast from tortoise import fields @@ -96,9 +98,9 @@ async def get_total_amount_by_manager(cls, manager_id: int) -> int: return result[0] if result and result[0] is not None else 0 @classmethod - async def get_using_cheeses(cls, manager_id: int) -> list["CheeseHistory"]: + async def get_using_cheeses(cls, manager_id: int) -> list[CheeseHistory]: return await cls.filter(status=CheeseStatus.USING, cheese_manager_id=manager_id).order_by("cheese_history_id") @classmethod - async def get_can_use_cheeses(cls, manager_id: int) -> list["CheeseHistory"]: + async def get_can_use_cheeses(cls, manager_id: int) -> list[CheeseHistory]: return await cls.filter(status=CheeseStatus.CAN_USE, cheese_manager_id=manager_id).order_by("cheese_history_id") diff --git a/app/models/color.py b/app/models/color.py index 699d8d2..029fdb3 100644 --- a/app/models/color.py +++ b/app/models/color.py @@ -24,10 +24,10 @@ class Meta: table = "color" @classmethod - async def create_by_user_id(cls, user_id: str, color_code: str) -> Any: + async def create_by_user_id(cls, user_id: str, color_code: str) -> None: query = INSERT_COLOR_CODE_FOR_USER_QUERY values = (color_code, user_id) - return await QueryExecutor.execute_query(query, values=values, fetch_type="single") + await QueryExecutor.execute_query(query, values=values, fetch_type="single") @classmethod async def get_colors_with_details_by_user_id(cls, user_id: str) -> list[ColorData]: diff --git a/app/models/user.py b/app/models/user.py index 6b835b0..9d257a0 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -91,7 +91,7 @@ async def get_user_info_by_user_id(cls, user_id: str) -> UserData: return UserData(**result) @classmethod - async def set_is_premium(cls, user_id: str, is_premium: bool) -> Any: + async def set_is_premium(cls, user_id: str, is_premium: bool) -> None: query = UPDATE_PREMIUM_STATUS_QUERY current_time = datetime.now() values = (int(is_premium), current_time, user_id) diff --git a/pyproject.toml b/pyproject.toml index 225c3be..552f065 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,10 +32,6 @@ coverage = "^7.6.8" celery-stubs = "^0.1.3" ruff = "^0.11.12" -#disallow_untyped_calls = true # 타입이 없는 함수 호출 금지 -#disallow_untyped_defs = true # 타입이 없는 함수 정의 금지 -#ignore_missing_imports = true # 누락된 import 무시 - [tool.mypy] python_version = "3.12" strict = true From 0fcf0c1d4c8c807848141bae23e7d96b7c74c800 Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Thu, 12 Jun 2025 17:07:47 +0900 Subject: [PATCH 14/27] =?UTF-8?q?=E2=9C=85=20test:=20fixture=20tortoise=20?= =?UTF-8?q?connection=20error=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/utils/query_executor.py | 1 + app/core/configs/base_settings.py | 4 +-- app/models/color.py | 2 -- app/models/user.py | 2 +- app/tests/apis/__init__.py | 0 app/tests/apis/v2/__init__.py | 0 app/tests/apis/v2/test_badge_router.py | 6 +++++ app/tests/fixtures.py | 34 ++++++++++++++++++++++---- app/tests/mothers/__init__.py | 0 app/tests/telling_me_client.py | 27 ++++++++++++++++++++ 10 files changed, 66 insertions(+), 10 deletions(-) create mode 100644 app/tests/apis/__init__.py create mode 100644 app/tests/apis/v2/__init__.py create mode 100644 app/tests/apis/v2/test_badge_router.py create mode 100644 app/tests/mothers/__init__.py create mode 100644 app/tests/telling_me_client.py diff --git a/app/common/utils/query_executor.py b/app/common/utils/query_executor.py index 0e88658..cd55899 100644 --- a/app/common/utils/query_executor.py +++ b/app/common/utils/query_executor.py @@ -19,6 +19,7 @@ async def execute_query( :param fetch_type: "single"일 경우 단일 값을 반환하고, "multiple"일 경우 여러 값을 반환 :return: 단일 값 또는 여러 값(딕셔너리 리스트) """ + connection = Tortoise.get_connection("default") if isinstance(values, tuple): diff --git a/app/core/configs/base_settings.py b/app/core/configs/base_settings.py index e31b7c0..3189aa8 100644 --- a/app/core/configs/base_settings.py +++ b/app/core/configs/base_settings.py @@ -14,10 +14,10 @@ class Env(StrEnum): class Settings(BaseSettings): ENV: Env = Env.LOCAL DB_HOST: str = "localhost" - DB_PORT: int = 3306 + DB_PORT: int = 3307 DB_USER: str = "root" DB_PASSWORD: str = "password" - DB_NAME: str = "database_name" + DB_NAME: str = "tellingme_local" DB_TIMEZONE: str = "Asia/Seoul" DB_CHARSET: str = "utf8mb4" APPLE_URL: str = "https://sandbox.itunes.apple.com/verifyReceipt" diff --git a/app/models/color.py b/app/models/color.py index 029fdb3..a36bb89 100644 --- a/app/models/color.py +++ b/app/models/color.py @@ -1,5 +1,3 @@ -from typing import Any - from tortoise import fields from tortoise.fields import ForeignKeyRelation from tortoise.models import Model diff --git a/app/models/user.py b/app/models/user.py index 9d257a0..fabfa5d 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -1,6 +1,6 @@ import uuid from datetime import datetime -from typing import Any, Optional +from typing import Optional from tortoise import Tortoise, fields from tortoise.fields import ForeignKeyRelation diff --git a/app/tests/apis/__init__.py b/app/tests/apis/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/tests/apis/v2/__init__.py b/app/tests/apis/v2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/tests/apis/v2/test_badge_router.py b/app/tests/apis/v2/test_badge_router.py new file mode 100644 index 0000000..6c46487 --- /dev/null +++ b/app/tests/apis/v2/test_badge_router.py @@ -0,0 +1,6 @@ +from app.tests.telling_me_client import TellingMeClient + + +async def test_get_badges(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: + response = await telling_me_client.get_badges(user_id="1234") + print(response.json()) diff --git a/app/tests/fixtures.py b/app/tests/fixtures.py index be96819..3ae4d91 100644 --- a/app/tests/fixtures.py +++ b/app/tests/fixtures.py @@ -1,20 +1,24 @@ -from typing import Any +from typing import Any, AsyncGenerator from unittest.mock import Mock, patch +import httpx import pytest -from pytest import FixtureRequest +from _pytest.fixtures import FixtureRequest +from tortoise import Tortoise from tortoise.backends.base.config_generator import generate_config from tortoise.contrib.test import finalizer, initializer +from app import app from app.core.configs import settings from app.core.database.tortoise_database_settings import TORTOISE_APP_MODELS +from app.tests.telling_me_client import TellingMeClient TEST_BASE_URL = "http://test" TEST_DB_LABEL = "models" TEST_DB_TZ = "Asia/Seoul" -def get_test_db_config() -> Any: +def get_test_db_config() -> dict[Any, Any]: config = generate_config( db_url=f"mysql://{settings.DB_USER}:{settings.DB_PASSWORD}@{settings.DB_HOST}:{settings.DB_PORT}/test", app_modules={TEST_DB_LABEL: TORTOISE_APP_MODELS}, @@ -22,12 +26,32 @@ def get_test_db_config() -> Any: testing=True, ) config["timezone"] = TEST_DB_TZ - return config @pytest.fixture(scope="session", autouse=True) def initialize(request: FixtureRequest) -> None: with patch("tortoise.contrib.test.getDBConfig", Mock(return_value=get_test_db_config())): - initializer(modules=TORTOISE_APP_MODELS) + initializer(modules=TORTOISE_APP_MODELS, loop=None) request.addfinalizer(finalizer) + + +@pytest.fixture() +async def init_tortoise_connection() -> AsyncGenerator[None, None]: + """ + tortoise orm 테스트 시 initializer에서는 테이블 생성 후 connection을 전부 삭제한다. + Tortoise.get_connection("default") or atomic()을 사용 시 연결을 찾을 수 없어 에러 발생 + 이런 오류를 해결하기 위해서 직접 connection을 생성 + """ + await Tortoise.init( + db_url=f"mysql://{settings.DB_USER}:{settings.DB_PASSWORD}@{settings.DB_HOST}:{settings.DB_PORT}/test", + modules={"models": TORTOISE_APP_MODELS}, + ) + yield + await Tortoise.close_connections() + + +@pytest.fixture() +async def telling_me_client() -> AsyncGenerator[TellingMeClient, None]: + async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url=TEST_BASE_URL) as client: + yield TellingMeClient(client) diff --git a/app/tests/mothers/__init__.py b/app/tests/mothers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/tests/telling_me_client.py b/app/tests/telling_me_client.py new file mode 100644 index 0000000..d17c285 --- /dev/null +++ b/app/tests/telling_me_client.py @@ -0,0 +1,27 @@ +import httpx + + +class TellingMeClient: + def __init__(self, httpx_client: httpx.AsyncClient): + """ + 테스트 중 API 호출은 본 클래스를 통합니다. + 모든 public 메소드들은 알파벳 순으로 정렬합시다. + """ + self._client = httpx_client + + async def get_badges(self, user_id: str) -> httpx.Response: + return await self._client.get( + "/api/v2/user/badge", + params={ + key: value + for key, value in { + "user_id": user_id, + }.items() + if value is not None + }, + ) + + # async def create_product(self, token: str, create_product_request: CreateProductRequest) -> httpx.Response: + # return await self._client.post( + # "/v1/products/admin", json=create_product_request.model_dump(), headers={"Authorization": f"Bearer {token}"} + # ) From d239a90881f8577f8341c93cf55a19a6e571bfed Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Thu, 12 Jun 2025 19:42:57 +0900 Subject: [PATCH 15/27] =?UTF-8?q?=E2=9C=85=20test:=20add=20badge=20router?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/constants/badge_code_list.py | 11 +++ app/common/utils/query_executor.py | 11 +++ app/models/answer.py | 6 -- app/models/badge.py | 4 +- app/models/badge_inventory.py | 50 ++++++++++++ app/models/cheese_manager.py | 11 +++ app/models/color.py | 26 +++++-- app/models/emotion.py | 18 ++++- app/models/like.py | 15 +--- app/models/mission.py | 31 +++++++- app/models/notice.py | 7 +- app/models/refresh_token.py | 2 +- app/models/user.py | 99 +++++++++++++++++++----- app/services/badge_service.py | 9 +++ app/services/user_service.py | 27 +++++++ app/tests/apis/v2/test_badge_router.py | 30 ++++++- app/tests/apis/v2/test_color_mother.py | 9 +++ app/tests/apis/v2/test_emotion_mother.py | 1 + app/tests/mothers/badge_mother.py | 14 ++++ app/tests/mothers/user_mother.py | 10 +++ app/tests/telling_me_client.py | 12 +++ 21 files changed, 342 insertions(+), 61 deletions(-) create mode 100644 app/common/constants/badge_code_list.py create mode 100644 app/tests/apis/v2/test_color_mother.py create mode 100644 app/tests/apis/v2/test_emotion_mother.py create mode 100644 app/tests/mothers/badge_mother.py create mode 100644 app/tests/mothers/user_mother.py diff --git a/app/common/constants/badge_code_list.py b/app/common/constants/badge_code_list.py new file mode 100644 index 0000000..68828da --- /dev/null +++ b/app/common/constants/badge_code_list.py @@ -0,0 +1,11 @@ +from enum import Enum + + +class BadgeCodeList(str, Enum): + AGAIN_001 = "BG_AGAIN_001" + CHRISTMAS_2024 = "BG_CHRISTMAS_2024" + FIRST = "BG_FIRST" + MUCH_001 = "BG_MUCH_001" + NEW = "BG_NEW" + NIGHT_001 = "BG_NIGHT_001" + SAVE_001 = "BG_SAVE_001" diff --git a/app/common/utils/query_executor.py b/app/common/utils/query_executor.py index cd55899..653d579 100644 --- a/app/common/utils/query_executor.py +++ b/app/common/utils/query_executor.py @@ -35,3 +35,14 @@ async def execute_query( elif fetch_type == "multiple": return result return 0 if fetch_type == "single" else [] + + @staticmethod + async def execute_write_query(query: str, values: Any = ()) -> None: + connection = Tortoise.get_connection("default") + + if isinstance(values, tuple): + processed_values = tuple(v[0] if isinstance(v, tuple) else v for v in values) + else: + processed_values = (values,) + + await connection.execute_query(query, processed_values) # type: ignore diff --git a/app/models/answer.py b/app/models/answer.py index 7a25d7e..e011470 100644 --- a/app/models/answer.py +++ b/app/models/answer.py @@ -1,12 +1,10 @@ from datetime import datetime from tortoise import fields -from tortoise.fields import ForeignKeyRelation from tortoise.models import Model from app.common.utils.query_executor import QueryExecutor from app.dtos.answer.answer_data import AnswerData -from app.models.user import User from app.queries.answer_query import ( SELECT_ANSWER_BY_USER_UUID_QUERY, SELECT_ANSWER_COUNT_BY_USER_UUID_QUERY, @@ -32,10 +30,6 @@ class Answer(Model): created_at = fields.DatetimeField(auto_now_add=True) updated_at = fields.DatetimeField(auto_now=True) - user: ForeignKeyRelation[User] = fields.ForeignKeyField( - "models.User", related_name="answers", on_delete=fields.CASCADE - ) - class Meta: table = "answer" diff --git a/app/models/badge.py b/app/models/badge.py index 662f161..25ec452 100644 --- a/app/models/badge.py +++ b/app/models/badge.py @@ -1,12 +1,10 @@ from __future__ import annotations from tortoise import fields -from tortoise.fields import ForeignKeyRelation from tortoise.models import Model from app.common.utils.query_executor import QueryExecutor from app.dtos.badge.badge_data import BadgeData -from app.models.user import User from app.queries.badge_query import ( INSERT_BADGE_CODE_FOR_USER_QUERY, SELECT_BADGE_BY_USER_UUID_QUERY, @@ -17,7 +15,7 @@ class Badge(Model): badge_id = fields.BigIntField(primary_key=True) badge_code = fields.CharField(max_length=255) - user: ForeignKeyRelation[User] = fields.ForeignKeyField("models.User", related_name="badges") + user_id = fields.BinaryField(max_length=16, null=True) class Meta: table = "badge" diff --git a/app/models/badge_inventory.py b/app/models/badge_inventory.py index a5c7c27..184ac75 100644 --- a/app/models/badge_inventory.py +++ b/app/models/badge_inventory.py @@ -1,6 +1,7 @@ from __future__ import annotations from tortoise import Model, fields +from tortoise.transactions import in_transaction class BadgeInventory(Model): @@ -16,6 +17,55 @@ class Meta: def badge_full_name(self) -> str: return f"{self.badge_middle_name} {self.badge_name}" + @classmethod + async def create_bulk(cls) -> None: + badges = [ + cls( + badge_code="BG_AGAIN_001", + badge_condition="연속으로 7일 작성했어요!", + badge_middle_name="또 오셨네요,", + badge_name="단골 텔러", + ), + cls( + badge_code="BG_CHRISTMAS_2024", + badge_condition="2024 크리스마스 한정판 배지예요!", + badge_middle_name="흰 눈 사이에서,", + badge_name="화이트 크리스마스", + ), + cls( + badge_code="BG_FIRST", + badge_condition="첫 글을 작성했어요!", + badge_middle_name="낯선 길에 첫 발자국,", + badge_name="탐험가 텔러", + ), + cls( + badge_code="BG_MUCH_001", + badge_condition="280자 이상 글을 1회 작성했어요!", + badge_middle_name="내 이야길 들어봐,", + badge_name="투머치 토커", + ), + cls( + badge_code="BG_NEW", + badge_condition="회원가입 시 기본 제공", + badge_middle_name="아직은 낯설어요,", + badge_name="미스터리 방문객", + ), + cls( + badge_code="BG_NIGHT_001", + badge_condition="새벽 시간에 글 3회 작성했어요!", + badge_middle_name="다들 꿈꿀 때 글을 썼지,", + badge_name="올빼미 텔러", + ), + cls( + badge_code="BG_SAVE_001", + badge_condition="치즈 50개를 모았어요!", + badge_middle_name="치즈를 모아모아,", + badge_name="나는야 저축왕", + ), + ] + async with in_transaction(): + await cls.bulk_create(badges) + @classmethod async def get_by_badge_code(cls, badge_code: str) -> BadgeInventory: return await cls.get(badge_code=badge_code) diff --git a/app/models/cheese_manager.py b/app/models/cheese_manager.py index 9a3ee47..fc6d71a 100644 --- a/app/models/cheese_manager.py +++ b/app/models/cheese_manager.py @@ -9,6 +9,7 @@ from tortoise.models import Model from app.common.constants.cheese_status import CheeseStatus +from app.common.utils.query_executor import QueryExecutor class CheeseManager(Model): @@ -21,6 +22,16 @@ class CheeseManager(Model): class Meta: table = "cheese_manager" + @classmethod + async def create_cheese_manager(cls) -> CheeseManager: + query = "INSERT INTO cheese_manager VALUES (DEFAULT);" + await QueryExecutor.execute_write_query(query) + + result = await cls.all().order_by("-cheese_manager_id").first() + if result is None: + raise RuntimeError("CheeseManager creation failed unexpectedly.") + return result + @staticmethod async def get_total_cheese_amount_by_manager(cheese_manager_id: int) -> int: return await CheeseHistory.get_total_amount_by_manager(cheese_manager_id) diff --git a/app/models/color.py b/app/models/color.py index a36bb89..a215320 100644 --- a/app/models/color.py +++ b/app/models/color.py @@ -1,10 +1,10 @@ +from datetime import datetime, timedelta, timezone + from tortoise import fields -from tortoise.fields import ForeignKeyRelation from tortoise.models import Model from app.common.utils.query_executor import QueryExecutor from app.dtos.color.color_data import ColorData -from app.models.user import User from app.queries.color_query import ( INSERT_COLOR_CODE_FOR_USER_QUERY, SELECT_COLOR_BY_USER_UUID_QUERY, @@ -14,13 +14,29 @@ class Color(Model): color_id = fields.BigIntField(primary_key=True) color_code = fields.CharField(max_length=255, null=True) - user: ForeignKeyRelation[User] = fields.ForeignKeyField( - "models.User", related_name="colors", on_delete=fields.CASCADE - ) + user_id = fields.BinaryField(max_length=16, null=True) class Meta: table = "color" + @classmethod + async def create_default_by_user_id(cls, user_id: str) -> None: + color_codes = ["CL_DEFAULT", "CL_BLUE_001", "CL_ORANGE_001"] + now_kst = datetime.now(timezone(timedelta(hours=9))) + + if now_kst >= datetime(2024, 12, 28, 6, 0, 0, tzinfo=timezone(timedelta(hours=9))): + color_codes.append("CL_RED_001") + + values_placeholders = ", ".join(["(%s, UNHEX(REPLACE(%s, '-', '')))"] * len(color_codes)) + values = [] + + for code in color_codes: + values.extend([code, user_id]) + + query = f"INSERT INTO color (color_code, user_id) VALUES {values_placeholders}" + + await QueryExecutor.execute_write_query(query, tuple(values)) + @classmethod async def create_by_user_id(cls, user_id: str, color_code: str) -> None: query = INSERT_COLOR_CODE_FOR_USER_QUERY diff --git a/app/models/emotion.py b/app/models/emotion.py index 9753764..44613fd 100644 --- a/app/models/emotion.py +++ b/app/models/emotion.py @@ -1,9 +1,7 @@ from tortoise import fields, models -from tortoise.fields import ForeignKeyRelation from app.common.utils.query_executor import QueryExecutor from app.dtos.emotion.emotion_data import EmotionData -from app.models.user import User from app.queries.emotion_query import ( INSERT_EMOTION_CODE_FOR_USER_QUERY, SELECT_EMOTION_CODE_BY_USER_UUID_QUERY, @@ -13,11 +11,25 @@ class Emotion(models.Model): emotion_id = fields.BigIntField(primary_key=True) emotion_code = fields.CharField(max_length=255, unique=True) - user: ForeignKeyRelation[User] = fields.ForeignKeyField("models.User", related_name="emotions") + user_id = fields.BinaryField(max_length=16, null=True) class Meta: table = "emotion" + @classmethod + async def create_default_emotions_by_user_id(cls, user_id: str) -> None: + emotion_codes = ["EM_HAPPY", "EM_PROUD", "EM_OKAY", "EM_TIRED", "EM_SAD", "EM_ANGRY"] + + values_placeholders = ", ".join(["(%s, UNHEX(REPLACE(%s, '-', '')))"] * len(emotion_codes)) + values = [] + + for code in emotion_codes: + values.extend([code, user_id]) + + query = f"INSERT INTO emotion (emotion_code, user_id) VALUES {values_placeholders}" + + await QueryExecutor.execute_write_query(query, tuple(values)) + @classmethod async def get_emotions_with_details_by_user_id(cls, user_id: str) -> list[EmotionData]: query = SELECT_EMOTION_CODE_BY_USER_UUID_QUERY diff --git a/app/models/like.py b/app/models/like.py index 404bd1f..9ba8c44 100644 --- a/app/models/like.py +++ b/app/models/like.py @@ -1,21 +1,14 @@ from tortoise import fields -from tortoise.fields import ForeignKeyRelation from tortoise.models import Model from app.common.utils.query_executor import QueryExecutor -from app.models.answer import Answer -from app.models.user import User from app.queries.like_query import SELECT_UNIQUE_LIKES_COUNT_BY_USER_TODAY_QUERY class Like(Model): likes_id = fields.BigIntField(primary_key=True) - answer: ForeignKeyRelation[Answer] = fields.ForeignKeyField( - "models.Answer", related_name="likes", on_delete=fields.CASCADE - ) - user: ForeignKeyRelation[User] = fields.ForeignKeyField( - "models.User", related_name="likes", on_delete=fields.CASCADE - ) + answer_id = fields.BinaryField(null=True) + user_id = fields.BinaryField(max_length=16, null=True) created_time = fields.DatetimeField(null=True) modified_time = fields.DatetimeField(null=True) created_at = fields.DatetimeField(auto_now_add=True) @@ -23,10 +16,6 @@ class Like(Model): class Meta: table = "likes" - indexes = [ - ("answer_id",), - ("user_id",), - ] @staticmethod async def get_unique_likes_today(user_id: str) -> int: diff --git a/app/models/mission.py b/app/models/mission.py index 29577bf..57223dd 100644 --- a/app/models/mission.py +++ b/app/models/mission.py @@ -1,10 +1,8 @@ from tortoise import fields -from tortoise.fields import ForeignKeyRelation from tortoise.models import Model from app.common.utils.query_executor import QueryExecutor from app.dtos.mission.mission_data import MissionData -from app.models.user import User from app.queries.mission_query import SELECT_USER_MISSIONS_QUERY, UPDATE_USER_MISSION_PROGRESS_QUERY @@ -13,11 +11,38 @@ class UserMission(Model): is_completed = fields.BooleanField(default=False) mission_code = fields.CharField(max_length=255) progress_count = fields.IntField(default=0) - user: ForeignKeyRelation[User] = fields.ForeignKeyField("models.User", related_name="missions") + user_id = fields.BinaryField(max_length=16) class Meta: table = "user_mission" + @classmethod + async def create_default_missions_by_user_id(cls, user_id: str) -> None: + mission_codes = [ + "MS_LV_UP", + "MS_BADGE_POST_FIRST", + "MS_BADGE_POST_280_CHAR", + "MS_BADGE_POST_CONSECUTIVE_7", + "MS_BADGE_POST_EARLY_3", + "MS_DAILY_LIKE_3_PER_DAY", + "MS_BADGE_CHEESE_TOTAL_50", + "MS_BADGE_CHRISTMAS", + "MS_DAILY_POST_GENERAL", + ] + + values_placeholders = ", ".join(["(%s, UNHEX(REPLACE(%s, '-', '')), %s, %s)"] * len(mission_codes)) + values = [] + + for code in mission_codes: + values.extend([code, user_id, False, 0]) + + query = f""" + INSERT INTO user_mission (mission_code, user_id, is_completed, progress_count) + VALUES {values_placeholders} + """ + + await QueryExecutor.execute_write_query(query, tuple(values)) + @classmethod async def get_user_missions_by_condition_type(cls, user_id: str) -> list[MissionData]: query = SELECT_USER_MISSIONS_QUERY diff --git a/app/models/notice.py b/app/models/notice.py index 2bec5a4..7fac677 100644 --- a/app/models/notice.py +++ b/app/models/notice.py @@ -1,11 +1,9 @@ from typing import Optional from tortoise import fields -from tortoise.fields import ForeignKeyRelation from tortoise.models import Model from app.common.utils.query_executor import QueryExecutor -from app.models.user import User class Notice(Model): @@ -17,13 +15,10 @@ class Notice(Model): link = fields.CharField(max_length=255, null=True) is_internal = fields.BooleanField(default=False) answer_id = fields.BigIntField(null=True) + user_id = fields.BinaryField(max_length=16, null=True) date = fields.DateField(null=True) reward_type = fields.CharField(max_length=255, null=True) - user: ForeignKeyRelation[User] = fields.ForeignKeyField( - "models.User", related_name="notices", on_delete=fields.CASCADE - ) - class Meta: table = "notice" diff --git a/app/models/refresh_token.py b/app/models/refresh_token.py index f312c69..133a4da 100644 --- a/app/models/refresh_token.py +++ b/app/models/refresh_token.py @@ -6,7 +6,7 @@ class RefreshToken(Model): refresh_token_id = fields.BigIntField(primary_key=True) access_token = fields.CharField(max_length=255) refresh_token = fields.CharField(max_length=255) - user_id = fields.BinaryField(max_length=16) # foreign key로 사용되지는 않지만 binary field로 선언 + user_id = fields.BinaryField(max_length=16) class Meta: table = "refresh_token" diff --git a/app/models/user.py b/app/models/user.py index fabfa5d..bf39c38 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import uuid from datetime import datetime from typing import Optional @@ -7,12 +9,10 @@ from tortoise.models import Model from app.common.utils.query_executor import QueryExecutor +from app.core.configs import settings from app.dtos.user.user_data import UserData from app.dtos.user.user_dto import UserProfileData -from app.models.cheese_manager import CheeseManager -from app.models.level import Level from app.models.refresh_token import RefreshToken -from app.models.teller_card import TellerCard from app.queries.user_query import ( SELECT_USER_INFO_BY_USER_UUID_QUERY, SELECT_USER_PROFILE_BY_USER_ID_QUERY, @@ -21,7 +21,8 @@ class User(Model): - user_id = fields.CharField(max_length=255, primary_key=True, description="Primary key for the User") + id = fields.BigIntField(primary_key=True) # Auto Increment Primary Key + user_id = fields.BinaryField(max_length=16, description="UUID PK in binary form") allow_notification = fields.BooleanField(null=True) birth_date = fields.CharField(max_length=8, null=True) created_time = fields.DatetimeField(auto_now_add=True) @@ -47,25 +48,77 @@ class User(Model): default="https://miro.medium.com/v2/resize:fit:1400/format:webp/1*dh7Xy5tFvRj7n2wf1UweAw.png", ) premium_started_at = fields.DatetimeField(null=True) - cheese_manager: ForeignKeyRelation[CheeseManager] = fields.ForeignKeyField( - "models.CheeseManager", - related_name="users", - db_column="cheese_manager_id", - ) - teller_card: ForeignKeyRelation[TellerCard] = fields.ForeignKeyField( - "models.TellerCard", - related_name="users", - db_column="teller_card_id", - ) - level: ForeignKeyRelation[Level] = fields.ForeignKeyField( - "models.Level", - related_name="users", - db_column="level_id", - ) + cheese_manager_id = fields.BigIntField(null=True) + teller_card_id = fields.BigIntField(null=True) + level_id = fields.BigIntField(null=True) class Meta: table = "user" + @classmethod + async def create_user( + cls, + social_id: str, + social_login_type: str, + nickname: str, + purpose: str, + job: int, + cheese_manager_id: int, + teller_card_id: int, + level_id: int, + allow_notification: bool = True, + birth_date: str | None = None, + gender: str = "female", + mbti: str | None = None, + push_token: str | None = None, + refresh_token: RefreshToken | None = None, + is_premium: bool = False, + profile_url: str | None = "", + ) -> str: + + user_id = str(uuid.uuid4()) + created_time = datetime.now(settings.db_zoneinfo).strftime("%Y-%m-%d %H:%M:%S") + + query = """ + INSERT INTO user ( + user_id, social_id, social_login_type, nickname, purpose, job, + cheese_manager_id, teller_card_id, level_id, + allow_notification, birth_date, gender, mbti, + push_token, refresh_token_id, is_premium, profile_url, user_status, created_time + ) + VALUES ( + UNHEX(REPLACE(%s, '-', '')), %s, %s, %s, %s, %s, + %s, %s, %s, + %s, %s, %s, %s, + %s, %s, %s, %s, TRUE, %s + ); + """ + + await QueryExecutor.execute_write_query( + query, + ( + user_id, + social_id, + social_login_type, + nickname, + purpose, + job, + cheese_manager_id, + teller_card_id, + level_id, + allow_notification, + birth_date, + gender, + mbti, + push_token, + refresh_token, + is_premium, + profile_url, + created_time, + ), + ) + return user_id + @classmethod async def get_user_profile_by_user_id(cls, user_id: str) -> UserProfileData: query = SELECT_USER_PROFILE_BY_USER_ID_QUERY @@ -113,3 +166,11 @@ async def bulk_update_is_premium(cls, user_ids: list[bytes]) -> None: WHERE user_id IN ({cls.format_user_ids(user_ids)}); """ await Tortoise.get_connection("default").execute_query(query) + + @staticmethod + def uuid_str_to_bytes(s: str) -> bytes: + return uuid.UUID(s).bytes + + @staticmethod + def uuid_bytes_to_str(b: bytes) -> str: + return str(uuid.UUID(bytes=b)) diff --git a/app/services/badge_service.py b/app/services/badge_service.py index 4c07e50..1ab1f62 100644 --- a/app/services/badge_service.py +++ b/app/services/badge_service.py @@ -1,9 +1,18 @@ from app.dtos.badge.badge_dto import BadgeDTO from app.models.badge import Badge +from app.models.badge_inventory import BadgeInventory class BadgeService: + @classmethod + async def create_badge(cls, user_id: str, badge_code: str) -> None: + await Badge.create_by_user_id(user_id=user_id, badge_code=badge_code) + + @classmethod + async def create_badge_inventory(cls) -> None: + await BadgeInventory.create_bulk() + @classmethod async def get_badges_with_details_by_user_id(cls, user_id: str) -> list[BadgeDTO]: badges = await Badge.get_badges_with_details_by_user_id(user_id=user_id) diff --git a/app/services/user_service.py b/app/services/user_service.py index 61e8fda..91a7685 100644 --- a/app/services/user_service.py +++ b/app/services/user_service.py @@ -1,9 +1,36 @@ from app.dtos.user.user_data import UserData from app.dtos.user.user_dto import UserProfileData +from app.models.cheese_manager import CheeseManager +from app.models.color import Color +from app.models.emotion import Emotion +from app.models.level import Level +from app.models.mission import UserMission +from app.models.teller_card import TellerCard from app.models.user import User class UserService: + + @staticmethod + async def create_user() -> str: + cheese_manager = await CheeseManager.create_cheese_manager() + teller_card = await TellerCard.create(activate_badge_code="BG_NEW", activate_color_code="CL_DEFAULT") + level = await Level.create(user_exp=0, user_level=1) + user_id = await User.create_user( + social_id="kakao_456", + social_login_type="kakao", + nickname="test_user", + purpose="test", + job=1, + cheese_manager_id=cheese_manager.cheese_manager_id, + teller_card_id=teller_card.teller_card_id, + level_id=level.level_id, + ) + await Color.create_default_by_user_id(user_id=user_id) + await Emotion.create_default_emotions_by_user_id(user_id=user_id) + await UserMission.create_default_missions_by_user_id(user_id=user_id) + return user_id + @staticmethod async def get_user_info(user_id: str) -> UserData: return await User.get_user_info_by_user_id(user_id=user_id) diff --git a/app/tests/apis/v2/test_badge_router.py b/app/tests/apis/v2/test_badge_router.py index 6c46487..dcebee6 100644 --- a/app/tests/apis/v2/test_badge_router.py +++ b/app/tests/apis/v2/test_badge_router.py @@ -1,6 +1,32 @@ +from fastapi import status + +from app.common.constants.badge_code_list import BadgeCodeList +from app.tests.mothers.badge_mother import BadgeMother +from app.tests.mothers.user_mother import UserMother from app.tests.telling_me_client import TellingMeClient async def test_get_badges(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: - response = await telling_me_client.get_badges(user_id="1234") - print(response.json()) + # Given + user_mother = UserMother() + badge_mother = BadgeMother() + + user_id = await user_mother.create_user() + await badge_mother.create_badge_inventory() + await badge_mother.create_badge(user_id=user_id, badge_code=BadgeCodeList.NEW) + + # When + response = await telling_me_client.get_badges(user_id=user_id) + code = response.json()["code"] + data = response.json()["data"] + message = response.json()["message"] + + # Then + assert response.status_code == status.HTTP_200_OK + + assert code == response.status_code + assert message == "보유 뱃지 정보 조회" + assert data[0]["badgeCode"] == BadgeCodeList.NEW + assert data[0]["badgeName"] == "미스터리 방문객" + assert data[0]["badgeMiddleName"] == "아직은 낯설어요," + assert data[0]["badgeCondition"] == "회원가입 시 기본 제공" diff --git a/app/tests/apis/v2/test_color_mother.py b/app/tests/apis/v2/test_color_mother.py new file mode 100644 index 0000000..281b41c --- /dev/null +++ b/app/tests/apis/v2/test_color_mother.py @@ -0,0 +1,9 @@ +# from app.tests.mothers.user_mother import UserMother +# from app.tests.telling_me_client import TellingMeClient +# +# +# async def test_get_colors(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: +# user_mother = UserMother() +# user_id = await user_mother.create_user() +# response = await telling_me_client.get_colors(user_id=user_id) +# print(response.json()) diff --git a/app/tests/apis/v2/test_emotion_mother.py b/app/tests/apis/v2/test_emotion_mother.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/app/tests/apis/v2/test_emotion_mother.py @@ -0,0 +1 @@ + diff --git a/app/tests/mothers/badge_mother.py b/app/tests/mothers/badge_mother.py new file mode 100644 index 0000000..40ec1d9 --- /dev/null +++ b/app/tests/mothers/badge_mother.py @@ -0,0 +1,14 @@ +from app.services.badge_service import BadgeService + + +class BadgeMother: + + @staticmethod + async def create_badge(user_id: str, badge_code: str) -> None: + badge_service = BadgeService() + await badge_service.create_badge(user_id=user_id, badge_code=badge_code) + + @staticmethod + async def create_badge_inventory() -> None: + badge_service = BadgeService() + await badge_service.create_badge_inventory() diff --git a/app/tests/mothers/user_mother.py b/app/tests/mothers/user_mother.py new file mode 100644 index 0000000..3352a55 --- /dev/null +++ b/app/tests/mothers/user_mother.py @@ -0,0 +1,10 @@ +from app.services.user_service import UserService + + +class UserMother: + + @staticmethod + async def create_user() -> str: + user_service = UserService() + new_user_id = await user_service.create_user() + return new_user_id diff --git a/app/tests/telling_me_client.py b/app/tests/telling_me_client.py index d17c285..1de40c9 100644 --- a/app/tests/telling_me_client.py +++ b/app/tests/telling_me_client.py @@ -21,6 +21,18 @@ async def get_badges(self, user_id: str) -> httpx.Response: }, ) + async def get_colors(self, user_id: str) -> httpx.Response: + return await self._client.get( + "/api/v2/user/color", + params={ + key: value + for key, value in { + "user_id": user_id, + }.items() + if value is not None + }, + ) + # async def create_product(self, token: str, create_product_request: CreateProductRequest) -> httpx.Response: # return await self._client.post( # "/v1/products/admin", json=create_product_request.model_dump(), headers={"Authorization": f"Bearer {token}"} From a15a545954c3d5ff42f2ec8efa75de29b7d7293f Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Thu, 12 Jun 2025 21:44:00 +0900 Subject: [PATCH 16/27] =?UTF-8?q?=E2=9C=85=20test:=20add=20color=20router?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/constants/color_code_list.py | 13 +++++++ app/models/answer.py | 1 + app/models/color_inventory.py | 17 ++++++++++ app/models/emotion.py | 2 +- app/models/level_inventory.py | 34 +++++++++++++++++++ app/models/user.py | 19 +++++++---- app/services/color_service.py | 8 +++++ app/services/level_service.py | 5 +++ app/services/user_service.py | 3 +- app/tests/apis/v2/test_color_mother.py | 45 ++++++++++++++++++++----- app/tests/mothers/color_mother.py | 14 ++++++++ app/tests/mothers/user_mother.py | 12 +++++-- app/tests/telling_me_client.py | 12 +++++++ 13 files changed, 165 insertions(+), 20 deletions(-) create mode 100644 app/common/constants/color_code_list.py create mode 100644 app/tests/mothers/color_mother.py diff --git a/app/common/constants/color_code_list.py b/app/common/constants/color_code_list.py new file mode 100644 index 0000000..a50b1e1 --- /dev/null +++ b/app/common/constants/color_code_list.py @@ -0,0 +1,13 @@ +from enum import Enum + + +class ColorCodeList(str, Enum): + CL_BLUE_001 = "CL_BLUE_001" + CL_DEFAULT = "CL_DEFAULT" + CL_GREEN_001 = "CL_GREEN_001" + CL_NAVY_001 = "CL_NAVY_001" + CL_ORANGE_001 = "CL_ORANGE_001" + CL_PINK_001 = "CL_PINK_001" + CL_PURPLE_001 = "CL_PURPLE_001" + CL_RED_001 = "CL_RED_001" + CL_YELLOW_001 = "CL_YELLOW_001" diff --git a/app/models/answer.py b/app/models/answer.py index e011470..d580d24 100644 --- a/app/models/answer.py +++ b/app/models/answer.py @@ -15,6 +15,7 @@ class Answer(Model): answer_id = fields.BigIntField(primary_key=True) + user_id = fields.BinaryField(max_length=16, null=True) content = fields.TextField(null=False) created_time = fields.DatetimeField(null=True) date = fields.DateField(null=False) diff --git a/app/models/color_inventory.py b/app/models/color_inventory.py index f536a5d..0f57c07 100644 --- a/app/models/color_inventory.py +++ b/app/models/color_inventory.py @@ -1,4 +1,5 @@ from tortoise import Model, fields +from tortoise.transactions import in_transaction from app.dtos.color.color_data import ColorData @@ -11,6 +12,22 @@ class ColorInventory(Model): class Meta: table = "color_inventory" # 테이블 이름을 명시 + @classmethod + async def create_bulk(cls) -> None: + colors = [ + cls(color_code="CL_BLUE_001", color_hex_code="#229DF6", color_name="Blue_1"), + cls(color_code="CL_DEFAULT", color_hex_code="#1EDCC5", color_name="Default"), + cls(color_code="CL_GREEN_001", color_hex_code="#80E252", color_name="Green_1"), + cls(color_code="CL_NAVY_001", color_hex_code="#7075FF", color_name="Navy_1"), + cls(color_code="CL_ORANGE_001", color_hex_code="#FFA216", color_name="Orange_1"), + cls(color_code="CL_PINK_001", color_hex_code="#FC6CA0", color_name="Pink_1"), + cls(color_code="CL_PURPLE_001", color_hex_code="#8C56FF", color_name="Purple_1"), + cls(color_code="CL_RED_001", color_hex_code="#ED3639", color_name="Red_1"), + cls(color_code="CL_YELLOW_001", color_hex_code="#FFC543", color_name="Yellow_1"), + ] + async with in_transaction(): + await cls.bulk_create(colors) + @classmethod async def get_color_inventory(cls) -> list[ColorData]: result = await cls.all().values("color_code", "color_name", "color_hex_code") diff --git a/app/models/emotion.py b/app/models/emotion.py index 44613fd..f57fefd 100644 --- a/app/models/emotion.py +++ b/app/models/emotion.py @@ -10,7 +10,7 @@ class Emotion(models.Model): emotion_id = fields.BigIntField(primary_key=True) - emotion_code = fields.CharField(max_length=255, unique=True) + emotion_code = fields.CharField(max_length=255) user_id = fields.BinaryField(max_length=16, null=True) class Meta: diff --git a/app/models/level_inventory.py b/app/models/level_inventory.py index e7a1293..79d295d 100644 --- a/app/models/level_inventory.py +++ b/app/models/level_inventory.py @@ -1,7 +1,41 @@ from tortoise import Model, fields +from tortoise.transactions import in_transaction class LevelInventory(Model): level_inventory_id = fields.BigIntField(primary_key=True) level = fields.IntField(null=True) required_exp = fields.IntField(null=True) + + class Meta: + table = "level_inventory" + + @classmethod + async def create_bulk(cls) -> None: + levels = [ + cls(level=1, required_exp=15), + cls(level=2, required_exp=20), + cls(level=3, required_exp=20), + cls(level=4, required_exp=20), + cls(level=5, required_exp=20), + cls(level=6, required_exp=30), + cls(level=7, required_exp=30), + cls(level=8, required_exp=30), + cls(level=9, required_exp=30), + cls(level=10, required_exp=30), + cls(level=11, required_exp=30), + cls(level=12, required_exp=30), + cls(level=13, required_exp=30), + cls(level=14, required_exp=30), + cls(level=15, required_exp=30), + cls(level=16, required_exp=30), + cls(level=17, required_exp=30), + cls(level=18, required_exp=30), + cls(level=19, required_exp=30), + cls(level=20, required_exp=30), + cls(level=21, required_exp=30), + cls(level=22, required_exp=30), + ] + + async with in_transaction(): + await cls.bulk_create(levels) diff --git a/app/models/user.py b/app/models/user.py index bf39c38..1655a36 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -23,7 +23,7 @@ class User(Model): id = fields.BigIntField(primary_key=True) # Auto Increment Primary Key user_id = fields.BinaryField(max_length=16, description="UUID PK in binary form") - allow_notification = fields.BooleanField(null=True) + allow_notification = fields.BinaryField(null=True) birth_date = fields.CharField(max_length=8, null=True) created_time = fields.DatetimeField(auto_now_add=True) gender = fields.CharField(max_length=16, null=True) @@ -34,7 +34,7 @@ class User(Model): push_token = fields.CharField(max_length=255, null=True) social_id = fields.CharField(max_length=255) social_login_type = fields.CharField(max_length=16) - user_status = fields.BooleanField() + user_status = fields.BinaryField() withdraw_period = fields.DatetimeField(null=True) refresh_token: Optional[ForeignKeyRelation[RefreshToken]] = fields.ForeignKeyField( "models.RefreshToken", @@ -42,7 +42,7 @@ class User(Model): db_column="refresh_token_id", null=True, ) - is_premium = fields.BooleanField() + is_premium = fields.BinaryField() profile_url = fields.CharField( max_length=255, default="https://miro.medium.com/v2/resize:fit:1400/format:webp/1*dh7Xy5tFvRj7n2wf1UweAw.png", @@ -66,7 +66,7 @@ async def create_user( cheese_manager_id: int, teller_card_id: int, level_id: int, - allow_notification: bool = True, + allow_notification: bool = False, birth_date: str | None = None, gender: str = "female", mbti: str | None = None, @@ -78,6 +78,9 @@ async def create_user( user_id = str(uuid.uuid4()) created_time = datetime.now(settings.db_zoneinfo).strftime("%Y-%m-%d %H:%M:%S") + allow_notification_byte = b"\x01" if allow_notification else b"\x00" + is_premium_byte = b"\x01" if is_premium else b"\x00" + user_status_byte = b"\x01" # 항상 TRUE로 설정한 부분 query = """ INSERT INTO user ( @@ -90,7 +93,7 @@ async def create_user( UNHEX(REPLACE(%s, '-', '')), %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, - %s, %s, %s, %s, TRUE, %s + %s, %s, %s, %s, %s, %s ); """ @@ -106,14 +109,15 @@ async def create_user( cheese_manager_id, teller_card_id, level_id, - allow_notification, + allow_notification_byte, birth_date, gender, mbti, push_token, refresh_token, - is_premium, + is_premium_byte, profile_url, + user_status_byte, created_time, ), ) @@ -124,6 +128,7 @@ async def get_user_profile_by_user_id(cls, user_id: str) -> UserProfileData: query = SELECT_USER_PROFILE_BY_USER_ID_QUERY value = user_id result = await QueryExecutor.execute_query(query, values=value, fetch_type="single") + print(result) return UserProfileData( user_id=result.get("user_id", ""), nickname=result.get("nickname", ""), diff --git a/app/services/color_service.py b/app/services/color_service.py index 7ba6208..e8df28c 100644 --- a/app/services/color_service.py +++ b/app/services/color_service.py @@ -6,6 +6,14 @@ class ColorService: + @classmethod + async def create_color(cls, user_id: str, color_code: str) -> None: + await Color.create_by_user_id(user_id=user_id, color_code=color_code) + + @classmethod + async def create_color_inventory(cls) -> None: + await ColorInventory.create_bulk() + @classmethod async def get_colors_with_details_by_user_id(cls, user_id: str) -> list[ColorDTO]: user = await User.get_user_profile_by_user_id(user_id=user_id) diff --git a/app/services/level_service.py b/app/services/level_service.py index a6babdb..0580a40 100644 --- a/app/services/level_service.py +++ b/app/services/level_service.py @@ -2,10 +2,15 @@ from app.dtos.level.level_info_dto import LevelInfoDTO from app.models.answer import Answer from app.models.level import Level +from app.models.level_inventory import LevelInventory from app.services.answer_service import AnswerService class LevelService: + @classmethod + async def create_level_inventory(cls) -> None: + await LevelInventory.create_bulk() + @classmethod async def get_level(cls, user_id: str) -> LevelDTO: level = await Level.get_level_info(user_id=user_id) diff --git a/app/services/user_service.py b/app/services/user_service.py index 91a7685..994d788 100644 --- a/app/services/user_service.py +++ b/app/services/user_service.py @@ -12,7 +12,7 @@ class UserService: @staticmethod - async def create_user() -> str: + async def create_user(is_premium: bool = False) -> str: cheese_manager = await CheeseManager.create_cheese_manager() teller_card = await TellerCard.create(activate_badge_code="BG_NEW", activate_color_code="CL_DEFAULT") level = await Level.create(user_exp=0, user_level=1) @@ -25,6 +25,7 @@ async def create_user() -> str: cheese_manager_id=cheese_manager.cheese_manager_id, teller_card_id=teller_card.teller_card_id, level_id=level.level_id, + is_premium=is_premium, ) await Color.create_default_by_user_id(user_id=user_id) await Emotion.create_default_emotions_by_user_id(user_id=user_id) diff --git a/app/tests/apis/v2/test_color_mother.py b/app/tests/apis/v2/test_color_mother.py index 281b41c..301f8a0 100644 --- a/app/tests/apis/v2/test_color_mother.py +++ b/app/tests/apis/v2/test_color_mother.py @@ -1,9 +1,36 @@ -# from app.tests.mothers.user_mother import UserMother -# from app.tests.telling_me_client import TellingMeClient -# -# -# async def test_get_colors(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: -# user_mother = UserMother() -# user_id = await user_mother.create_user() -# response = await telling_me_client.get_colors(user_id=user_id) -# print(response.json()) +from fastapi import status + +from app.common.constants.color_code_list import ColorCodeList +from app.tests.mothers.color_mother import ColorMother +from app.tests.mothers.user_mother import UserMother +from app.tests.telling_me_client import TellingMeClient + + +async def test_get_colors(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: + # Given + user_mother = UserMother() + color_mother = ColorMother() + + user_id = await user_mother.create_user() + await color_mother.create_color_inventory() + + # 기본 색상 + expected_colors = [ + {"colorCode": ColorCodeList.CL_BLUE_001, "colorName": "Blue_1", "colorHexCode": "#229DF6"}, + {"colorCode": ColorCodeList.CL_DEFAULT, "colorName": "Default", "colorHexCode": "#1EDCC5"}, + {"colorCode": ColorCodeList.CL_ORANGE_001, "colorName": "Orange_1", "colorHexCode": "#FFA216"}, + {"colorCode": ColorCodeList.CL_RED_001, "colorName": "Red_1", "colorHexCode": "#ED3639"}, + ] + + # When + response = await telling_me_client.get_colors(user_id=user_id) + code = response.json()["code"] + data = response.json()["data"] + message = response.json()["message"] + + # Then + assert response.status_code == status.HTTP_200_OK + + assert code == response.status_code + assert message == "보유 색상 정보 조회" + assert sorted(data, key=lambda x: x["colorCode"]) == sorted(expected_colors, key=lambda x: x["colorCode"]) diff --git a/app/tests/mothers/color_mother.py b/app/tests/mothers/color_mother.py new file mode 100644 index 0000000..67ab688 --- /dev/null +++ b/app/tests/mothers/color_mother.py @@ -0,0 +1,14 @@ +from app.services.color_service import ColorService + + +class ColorMother: + + @staticmethod + async def create_color(user_id: str, color_code: str) -> None: + color_service = ColorService() + await color_service.create_color(user_id=user_id, color_code=color_code) + + @staticmethod + async def create_color_inventory() -> None: + color_service = ColorService() + await color_service.create_color_inventory() diff --git a/app/tests/mothers/user_mother.py b/app/tests/mothers/user_mother.py index 3352a55..275cbfa 100644 --- a/app/tests/mothers/user_mother.py +++ b/app/tests/mothers/user_mother.py @@ -1,10 +1,18 @@ +from app.services.level_service import LevelService from app.services.user_service import UserService class UserMother: @staticmethod - async def create_user() -> str: + async def create_user( + is_premium: bool = False, + ) -> str: user_service = UserService() - new_user_id = await user_service.create_user() + new_user_id = await user_service.create_user(is_premium=is_premium) return new_user_id + + @staticmethod + async def create_level_inventory() -> None: + level_service = LevelService() + await level_service.create_level_inventory() diff --git a/app/tests/telling_me_client.py b/app/tests/telling_me_client.py index 1de40c9..53f5849 100644 --- a/app/tests/telling_me_client.py +++ b/app/tests/telling_me_client.py @@ -9,6 +9,18 @@ def __init__(self, httpx_client: httpx.AsyncClient): """ self._client = httpx_client + async def get_mobile_my_page(self, user_id: str) -> httpx.Response: + return await self._client.get( + "/api/v2/mobiles/mypage", + params={ + key: value + for key, value in { + "user_id": user_id, + }.items() + if value is not None + }, + ) + async def get_badges(self, user_id: str) -> httpx.Response: return await self._client.get( "/api/v2/user/badge", From 564034d88142298a2c73c68f6d9480234b0d81e0 Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Thu, 12 Jun 2025 21:56:13 +0900 Subject: [PATCH 17/27] =?UTF-8?q?=E2=9C=85=20test:=20add=20emotion=20route?= =?UTF-8?q?r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/emotion.py | 12 ++++---- app/models/emotion_inventory.py | 20 +++++++++++++ app/models/user.py | 1 - app/services/emotion_service.py | 8 +++++ app/tests/apis/v2/test_emotion_mother.py | 37 ++++++++++++++++++++++++ app/tests/mothers/emotion_mother.py | 14 +++++++++ app/tests/telling_me_client.py | 12 ++++++++ 7 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 app/tests/mothers/emotion_mother.py diff --git a/app/models/emotion.py b/app/models/emotion.py index f57fefd..748fb99 100644 --- a/app/models/emotion.py +++ b/app/models/emotion.py @@ -30,15 +30,15 @@ async def create_default_emotions_by_user_id(cls, user_id: str) -> None: await QueryExecutor.execute_write_query(query, tuple(values)) + @classmethod + async def create_by_user_id(cls, user_id: str, emotion_code: str) -> None: + query = INSERT_EMOTION_CODE_FOR_USER_QUERY + values = (emotion_code, user_id) + await QueryExecutor.execute_query(query, values=values) + @classmethod async def get_emotions_with_details_by_user_id(cls, user_id: str) -> list[EmotionData]: query = SELECT_EMOTION_CODE_BY_USER_UUID_QUERY values = user_id result = await QueryExecutor.execute_query(query, values=values, fetch_type="multiple") return [EmotionData(**row) for row in result] - - @classmethod - async def create_by_user_id(cls, user_id: str, emotion_code: str) -> None: - query = INSERT_EMOTION_CODE_FOR_USER_QUERY - values = (emotion_code, user_id) - await QueryExecutor.execute_query(query, values=values) diff --git a/app/models/emotion_inventory.py b/app/models/emotion_inventory.py index ca3716c..3f3f149 100644 --- a/app/models/emotion_inventory.py +++ b/app/models/emotion_inventory.py @@ -1,4 +1,5 @@ from tortoise import fields, models +from tortoise.transactions import in_transaction from app.dtos.emotion.emotion_data import EmotionData @@ -11,6 +12,25 @@ class EmotionInventory(models.Model): class Meta: table = "emotion_inventory" + @classmethod + async def create_bulk(cls) -> None: + emotions = [ + cls(emotion_code="EM_HAPPY", emotion_name="행복해요"), + cls(emotion_code="EM_PROUD", emotion_name="뿌듯해요"), + cls(emotion_code="EM_OKAY", emotion_name="그저 그래요"), + cls(emotion_code="EM_TIRED", emotion_name="피곤해요"), + cls(emotion_code="EM_SAD", emotion_name="슬퍼요"), + cls(emotion_code="EM_ANGRY", emotion_name="화나요"), + cls(emotion_code="EM_EXCITED", emotion_name="설레요"), + cls(emotion_code="EM_FUN", emotion_name="신나요"), + cls(emotion_code="EM_RELAXED", emotion_name="편안해요"), + cls(emotion_code="EM_APATHETIC", emotion_name="무기력해요"), + cls(emotion_code="EM_LONELY", emotion_name="외로워요"), + cls(emotion_code="EM_COMPLEX", emotion_name="복잡해요"), + ] + async with in_transaction(): + await cls.bulk_create(emotions) + @classmethod async def get_emotion_inventory(cls) -> list[EmotionData]: result = await cls.all().values("emotion_code", "emotion_name") diff --git a/app/models/user.py b/app/models/user.py index 1655a36..1d75a26 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -128,7 +128,6 @@ async def get_user_profile_by_user_id(cls, user_id: str) -> UserProfileData: query = SELECT_USER_PROFILE_BY_USER_ID_QUERY value = user_id result = await QueryExecutor.execute_query(query, values=value, fetch_type="single") - print(result) return UserProfileData( user_id=result.get("user_id", ""), nickname=result.get("nickname", ""), diff --git a/app/services/emotion_service.py b/app/services/emotion_service.py index 5666079..13c1a3b 100644 --- a/app/services/emotion_service.py +++ b/app/services/emotion_service.py @@ -8,6 +8,14 @@ class EmotionService: + @classmethod + async def create_emotion(cls, user_id: str, emotion_code: str) -> None: + await Emotion.create_by_user_id(user_id=user_id, emotion_code=emotion_code) + + @classmethod + async def create_emotion_inventory(cls) -> None: + await EmotionInventory.create_bulk() + @classmethod async def mapping_emotion_list(cls, user_id: str) -> EmotionDTO: user = await User.get_user_profile_by_user_id(user_id=user_id) diff --git a/app/tests/apis/v2/test_emotion_mother.py b/app/tests/apis/v2/test_emotion_mother.py index 8b13789..89f848b 100644 --- a/app/tests/apis/v2/test_emotion_mother.py +++ b/app/tests/apis/v2/test_emotion_mother.py @@ -1 +1,38 @@ +from fastapi import status +from app.common.constants.emotion_dict import EMOTION_DICT +from app.tests.mothers.emotion_mother import EmotionMother +from app.tests.mothers.user_mother import UserMother +from app.tests.telling_me_client import TellingMeClient + + +async def test_get_emotions(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: + # Given + user_mother = UserMother() + emotion_mother = EmotionMother() + + user_id = await user_mother.create_user() + await emotion_mother.create_emotion_inventory() + + # 기본 감정 + expected_emotion_ids = [ + EMOTION_DICT["EM_HAPPY"], + EMOTION_DICT["EM_PROUD"], + EMOTION_DICT["EM_OKAY"], + EMOTION_DICT["EM_TIRED"], + EMOTION_DICT["EM_SAD"], + EMOTION_DICT["EM_ANGRY"], + ] + + # When + response = await telling_me_client.get_emotions(user_id=user_id) + code = response.json()["code"] + data = response.json()["data"] + message = response.json()["message"] + + # Then + assert response.status_code == status.HTTP_200_OK + + assert code == response.status_code + assert message == "보유 감정 정보 조회" + assert sorted(data["emotionList"]) == sorted(expected_emotion_ids) diff --git a/app/tests/mothers/emotion_mother.py b/app/tests/mothers/emotion_mother.py new file mode 100644 index 0000000..721fc85 --- /dev/null +++ b/app/tests/mothers/emotion_mother.py @@ -0,0 +1,14 @@ +from app.services.emotion_service import EmotionService + + +class EmotionMother: + + @staticmethod + async def create_emotion(user_id: str, emotion_code: str) -> None: + emotion_service = EmotionService() + await emotion_service.create_emotion(user_id=user_id, emotion_code=emotion_code) + + @staticmethod + async def create_emotion_inventory() -> None: + emotion_service = EmotionService() + await emotion_service.create_emotion_inventory() diff --git a/app/tests/telling_me_client.py b/app/tests/telling_me_client.py index 53f5849..151d8fa 100644 --- a/app/tests/telling_me_client.py +++ b/app/tests/telling_me_client.py @@ -45,6 +45,18 @@ async def get_colors(self, user_id: str) -> httpx.Response: }, ) + async def get_emotions(self, user_id: str) -> httpx.Response: + return await self._client.get( + "/api/v2/user/emotion", + params={ + key: value + for key, value in { + "user_id": user_id, + }.items() + if value is not None + }, + ) + # async def create_product(self, token: str, create_product_request: CreateProductRequest) -> httpx.Response: # return await self._client.post( # "/v1/products/admin", json=create_product_request.model_dump(), headers={"Authorization": f"Bearer {token}"} From c0616b0a7f241c57fea55cae4bb6178e3b79e2bb Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Thu, 12 Jun 2025 22:05:31 +0900 Subject: [PATCH 18/27] =?UTF-8?q?=E2=9C=85=20test:=20add=20cheese=20router?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/tests/apis/v2/test_cheese_router.py | 25 +++++++++++++++++++++++++ app/tests/mothers/user_mother.py | 6 ++++++ app/tests/telling_me_client.py | 12 ++++++++++++ 3 files changed, 43 insertions(+) create mode 100644 app/tests/apis/v2/test_cheese_router.py diff --git a/app/tests/apis/v2/test_cheese_router.py b/app/tests/apis/v2/test_cheese_router.py new file mode 100644 index 0000000..29f2809 --- /dev/null +++ b/app/tests/apis/v2/test_cheese_router.py @@ -0,0 +1,25 @@ +from fastapi import status + +from app.tests.mothers.user_mother import UserMother +from app.tests.telling_me_client import TellingMeClient + + +async def test_get_cheese_amount(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: + # Given + user_mother = UserMother() + + user_id = await user_mother.create_user() + await user_mother.add_cheese(user_id=user_id, amount=(cheese_amount := 100)) + + # When + response = await telling_me_client.get_cheese_amount(user_id=user_id) + code = response.json()["code"] + data = response.json()["data"] + message = response.json()["message"] + + # Then + assert response.status_code == status.HTTP_200_OK + + assert code == response.status_code + assert message == "총 치즈 갯수 조회" + assert data["cheeseBalance"] == cheese_amount diff --git a/app/tests/mothers/user_mother.py b/app/tests/mothers/user_mother.py index 275cbfa..9fe44ad 100644 --- a/app/tests/mothers/user_mother.py +++ b/app/tests/mothers/user_mother.py @@ -1,3 +1,4 @@ +from app.models.cheese_manager import CheeseManager from app.services.level_service import LevelService from app.services.user_service import UserService @@ -12,6 +13,11 @@ async def create_user( new_user_id = await user_service.create_user(is_premium=is_premium) return new_user_id + @staticmethod + async def add_cheese(user_id: str, amount: int) -> None: + user = await UserService.get_user_info(user_id=user_id) + await CheeseManager.add_cheese(cheese_manager_id=user.cheese_manager_id, amount=amount) + @staticmethod async def create_level_inventory() -> None: level_service = LevelService() diff --git a/app/tests/telling_me_client.py b/app/tests/telling_me_client.py index 151d8fa..5945c38 100644 --- a/app/tests/telling_me_client.py +++ b/app/tests/telling_me_client.py @@ -57,6 +57,18 @@ async def get_emotions(self, user_id: str) -> httpx.Response: }, ) + async def get_cheese_amount(self, user_id: str) -> httpx.Response: + return await self._client.get( + "/api/v2/cheese", + params={ + key: value + for key, value in { + "user_id": user_id, + }.items() + if value is not None + }, + ) + # async def create_product(self, token: str, create_product_request: CreateProductRequest) -> httpx.Response: # return await self._client.post( # "/v1/products/admin", json=create_product_request.model_dump(), headers={"Authorization": f"Bearer {token}"} From 55bad81b038cc7f390bd0850acdc0c2fc51e122a Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Thu, 12 Jun 2025 22:12:35 +0900 Subject: [PATCH 19/27] =?UTF-8?q?=E2=9C=85=20test:=20add=20color,emotion?= =?UTF-8?q?=20router=20premium=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/tests/apis/v2/test_color_mother.py | 36 ---------- app/tests/apis/v2/test_color_router.py | 71 +++++++++++++++++++ ...otion_mother.py => test_emotion_router.py} | 30 +++++++- 3 files changed, 100 insertions(+), 37 deletions(-) delete mode 100644 app/tests/apis/v2/test_color_mother.py create mode 100644 app/tests/apis/v2/test_color_router.py rename app/tests/apis/v2/{test_emotion_mother.py => test_emotion_router.py} (53%) diff --git a/app/tests/apis/v2/test_color_mother.py b/app/tests/apis/v2/test_color_mother.py deleted file mode 100644 index 301f8a0..0000000 --- a/app/tests/apis/v2/test_color_mother.py +++ /dev/null @@ -1,36 +0,0 @@ -from fastapi import status - -from app.common.constants.color_code_list import ColorCodeList -from app.tests.mothers.color_mother import ColorMother -from app.tests.mothers.user_mother import UserMother -from app.tests.telling_me_client import TellingMeClient - - -async def test_get_colors(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: - # Given - user_mother = UserMother() - color_mother = ColorMother() - - user_id = await user_mother.create_user() - await color_mother.create_color_inventory() - - # 기본 색상 - expected_colors = [ - {"colorCode": ColorCodeList.CL_BLUE_001, "colorName": "Blue_1", "colorHexCode": "#229DF6"}, - {"colorCode": ColorCodeList.CL_DEFAULT, "colorName": "Default", "colorHexCode": "#1EDCC5"}, - {"colorCode": ColorCodeList.CL_ORANGE_001, "colorName": "Orange_1", "colorHexCode": "#FFA216"}, - {"colorCode": ColorCodeList.CL_RED_001, "colorName": "Red_1", "colorHexCode": "#ED3639"}, - ] - - # When - response = await telling_me_client.get_colors(user_id=user_id) - code = response.json()["code"] - data = response.json()["data"] - message = response.json()["message"] - - # Then - assert response.status_code == status.HTTP_200_OK - - assert code == response.status_code - assert message == "보유 색상 정보 조회" - assert sorted(data, key=lambda x: x["colorCode"]) == sorted(expected_colors, key=lambda x: x["colorCode"]) diff --git a/app/tests/apis/v2/test_color_router.py b/app/tests/apis/v2/test_color_router.py new file mode 100644 index 0000000..c2d6140 --- /dev/null +++ b/app/tests/apis/v2/test_color_router.py @@ -0,0 +1,71 @@ +from fastapi import status + +from app.common.constants.color_code_list import ColorCodeList +from app.tests.mothers.color_mother import ColorMother +from app.tests.mothers.user_mother import UserMother +from app.tests.telling_me_client import TellingMeClient + + +async def test_get_colors_with_normal_user(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: + # Given + user_mother = UserMother() + color_mother = ColorMother() + + user_id = await user_mother.create_user() + await color_mother.create_color_inventory() + + # 기본 색상 + expected_colors = [ + {"colorCode": ColorCodeList.CL_BLUE_001, "colorName": "Blue_1", "colorHexCode": "#229DF6"}, + {"colorCode": ColorCodeList.CL_DEFAULT, "colorName": "Default", "colorHexCode": "#1EDCC5"}, + {"colorCode": ColorCodeList.CL_ORANGE_001, "colorName": "Orange_1", "colorHexCode": "#FFA216"}, + {"colorCode": ColorCodeList.CL_RED_001, "colorName": "Red_1", "colorHexCode": "#ED3639"}, + ] + + # When + response = await telling_me_client.get_colors(user_id=user_id) + code = response.json()["code"] + data = response.json()["data"] + message = response.json()["message"] + + # Then + assert response.status_code == status.HTTP_200_OK + + assert code == response.status_code + assert message == "보유 색상 정보 조회" + assert sorted(data, key=lambda x: x["colorCode"]) == sorted(expected_colors, key=lambda x: x["colorCode"]) + + +async def test_get_colors_with_premium_user(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: + # Given + user_mother = UserMother() + color_mother = ColorMother() + + user_id = await user_mother.create_user(is_premium=True) + await color_mother.create_color_inventory() + + # 기본 색상 + expected_colors = [ + {"colorCode": "CL_BLUE_001", "colorName": "Blue_1", "colorHexCode": "#229DF6"}, + {"colorCode": "CL_DEFAULT", "colorName": "Default", "colorHexCode": "#1EDCC5"}, + {"colorCode": "CL_GREEN_001", "colorName": "Green_1", "colorHexCode": "#80E252"}, + {"colorCode": "CL_NAVY_001", "colorName": "Navy_1", "colorHexCode": "#7075FF"}, + {"colorCode": "CL_ORANGE_001", "colorName": "Orange_1", "colorHexCode": "#FFA216"}, + {"colorCode": "CL_PINK_001", "colorName": "Pink_1", "colorHexCode": "#FC6CA0"}, + {"colorCode": "CL_PURPLE_001", "colorName": "Purple_1", "colorHexCode": "#8C56FF"}, + {"colorCode": "CL_RED_001", "colorName": "Red_1", "colorHexCode": "#ED3639"}, + {"colorCode": "CL_YELLOW_001", "colorName": "Yellow_1", "colorHexCode": "#FFC543"}, + ] + + # When + response = await telling_me_client.get_colors(user_id=user_id) + code = response.json()["code"] + data = response.json()["data"] + message = response.json()["message"] + + # Then + assert response.status_code == status.HTTP_200_OK + + assert code == response.status_code + assert message == "보유 색상 정보 조회" + assert sorted(data, key=lambda x: x["colorCode"]) == sorted(expected_colors, key=lambda x: x["colorCode"]) diff --git a/app/tests/apis/v2/test_emotion_mother.py b/app/tests/apis/v2/test_emotion_router.py similarity index 53% rename from app/tests/apis/v2/test_emotion_mother.py rename to app/tests/apis/v2/test_emotion_router.py index 89f848b..36aedf7 100644 --- a/app/tests/apis/v2/test_emotion_mother.py +++ b/app/tests/apis/v2/test_emotion_router.py @@ -6,7 +6,9 @@ from app.tests.telling_me_client import TellingMeClient -async def test_get_emotions(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: +async def test_get_emotions_with_normal_user( + telling_me_client: TellingMeClient, init_tortoise_connection: None +) -> None: # Given user_mother = UserMother() emotion_mother = EmotionMother() @@ -36,3 +38,29 @@ async def test_get_emotions(telling_me_client: TellingMeClient, init_tortoise_co assert code == response.status_code assert message == "보유 감정 정보 조회" assert sorted(data["emotionList"]) == sorted(expected_emotion_ids) + + +async def test_get_emotions_with_premium_user( + telling_me_client: TellingMeClient, init_tortoise_connection: None +) -> None: + # Given + user_mother = UserMother() + emotion_mother = EmotionMother() + + user_id = await user_mother.create_user(is_premium=True) + await emotion_mother.create_emotion_inventory() + + # 기본 감정 + expected_emotion_ids = list(EMOTION_DICT.values()) + # When + response = await telling_me_client.get_emotions(user_id=user_id) + code = response.json()["code"] + data = response.json()["data"] + message = response.json()["message"] + + # Then + assert response.status_code == status.HTTP_200_OK + + assert code == response.status_code + assert message == "보유 감정 정보 조회" + assert sorted(data["emotionList"]) == sorted(expected_emotion_ids) From c5de4ad7b80bd02dab71cbc50a0b4a2c8e64323d Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Thu, 12 Jun 2025 22:28:08 +0900 Subject: [PATCH 20/27] =?UTF-8?q?=E2=9C=85=20test:=20add=20inventory=20db?= =?UTF-8?q?=20clean=20up=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/tests/fixtures.py | 21 ++++----------------- app/tests/utils/__init__.py | 0 app/tests/utils/db_utils.py | 13 +++++++++++++ app/tests/utils/test_db_config.py | 21 +++++++++++++++++++++ 4 files changed, 38 insertions(+), 17 deletions(-) create mode 100644 app/tests/utils/__init__.py create mode 100644 app/tests/utils/db_utils.py create mode 100644 app/tests/utils/test_db_config.py diff --git a/app/tests/fixtures.py b/app/tests/fixtures.py index 3ae4d91..3e48389 100644 --- a/app/tests/fixtures.py +++ b/app/tests/fixtures.py @@ -1,32 +1,18 @@ -from typing import Any, AsyncGenerator +from typing import AsyncGenerator from unittest.mock import Mock, patch import httpx import pytest from _pytest.fixtures import FixtureRequest from tortoise import Tortoise -from tortoise.backends.base.config_generator import generate_config from tortoise.contrib.test import finalizer, initializer from app import app from app.core.configs import settings from app.core.database.tortoise_database_settings import TORTOISE_APP_MODELS from app.tests.telling_me_client import TellingMeClient - -TEST_BASE_URL = "http://test" -TEST_DB_LABEL = "models" -TEST_DB_TZ = "Asia/Seoul" - - -def get_test_db_config() -> dict[Any, Any]: - config = generate_config( - db_url=f"mysql://{settings.DB_USER}:{settings.DB_PASSWORD}@{settings.DB_HOST}:{settings.DB_PORT}/test", - app_modules={TEST_DB_LABEL: TORTOISE_APP_MODELS}, - connection_label=TEST_DB_LABEL, - testing=True, - ) - config["timezone"] = TEST_DB_TZ - return config +from app.tests.utils.db_utils import reset_inventory_tables +from app.tests.utils.test_db_config import TEST_BASE_URL, get_test_db_config @pytest.fixture(scope="session", autouse=True) @@ -47,6 +33,7 @@ async def init_tortoise_connection() -> AsyncGenerator[None, None]: db_url=f"mysql://{settings.DB_USER}:{settings.DB_PASSWORD}@{settings.DB_HOST}:{settings.DB_PORT}/test", modules={"models": TORTOISE_APP_MODELS}, ) + await reset_inventory_tables() yield await Tortoise.close_connections() diff --git a/app/tests/utils/__init__.py b/app/tests/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/tests/utils/db_utils.py b/app/tests/utils/db_utils.py new file mode 100644 index 0000000..29e283b --- /dev/null +++ b/app/tests/utils/db_utils.py @@ -0,0 +1,13 @@ +from app.models.badge_inventory import BadgeInventory +from app.models.color_inventory import ColorInventory +from app.models.emotion_inventory import EmotionInventory + + +async def reset_inventory_tables() -> None: + """ + 인벤토리 관련 테이블 초기화 함수 + 테스트 시 중복 데이터로 인한 충돌 방지를 위해 사용 + """ + await BadgeInventory.all().delete() + await ColorInventory.all().delete() + await EmotionInventory.all().delete() diff --git a/app/tests/utils/test_db_config.py b/app/tests/utils/test_db_config.py new file mode 100644 index 0000000..5918f7d --- /dev/null +++ b/app/tests/utils/test_db_config.py @@ -0,0 +1,21 @@ +from typing import Any + +from tortoise.backends.base.config_generator import generate_config + +from app.core.configs import settings +from app.core.database.tortoise_database_settings import TORTOISE_APP_MODELS + +TEST_BASE_URL = "http://test" +TEST_DB_LABEL = "models" +TEST_DB_TZ = "Asia/Seoul" + + +def get_test_db_config() -> dict[Any, Any]: + config = generate_config( + db_url=f"mysql://{settings.DB_USER}:{settings.DB_PASSWORD}@{settings.DB_HOST}:{settings.DB_PORT}/test", + app_modules={TEST_DB_LABEL: TORTOISE_APP_MODELS}, + connection_label=TEST_DB_LABEL, + testing=True, + ) + config["timezone"] = TEST_DB_TZ + return config From 96f3c33af2e176fe13aa969aeed04c41b969e291 Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Thu, 12 Jun 2025 23:01:42 +0900 Subject: [PATCH 21/27] =?UTF-8?q?=E2=9C=85=20test:=20add=20teller=20card?= =?UTF-8?q?=20router?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/exceptions/error_code.py | 5 +- app/services/teller_card_service.py | 8 +- app/tests/apis/v2/test_teller_card_router.py | 89 ++++++++++++++++++++ app/tests/telling_me_client.py | 20 ++++- app/tests/utils/db_utils.py | 2 + 5 files changed, 116 insertions(+), 8 deletions(-) create mode 100644 app/tests/apis/v2/test_teller_card_router.py diff --git a/app/common/exceptions/error_code.py b/app/common/exceptions/error_code.py index 22afa5a..b5a8242 100644 --- a/app/common/exceptions/error_code.py +++ b/app/common/exceptions/error_code.py @@ -2,10 +2,13 @@ class ErrorCode(Enum): + # 400 Bad Request + INVALID_TRANSACTION_CURRENCY = (4001, "결제에 유효하지 않은 거래 통화입니다.") NOT_ENOUGH_CHEESE = (4003, "치즈가 부족하여 구매를 진행할 수 없습니다.") INVALID_ITEM_CATEGORY = (4004, "치즈 결제에 유효하지 않은 아이템 카테고리입니다.") - INVALID_TRANSACTION_CURRENCY = (4001, "결제에 유효하지 않은 거래 통화입니다.") DUPLICATE_PURCHASE = (4005, "이미 소유한 제품입니다.") + INVALID_BADGE_CODE = (4006, "유효하지 않은 뱃지 코드입니다") + INVALID_COLOR_CODE = (4006, "유효하지 않은 컬러 코드입니다") # 404 Not Found NO_INVENTORY_FOR_PRODUCT = (4041, "이 상품에 대한 재고가 없습니다.") diff --git a/app/services/teller_card_service.py b/app/services/teller_card_service.py index 20e9cb3..028266b 100644 --- a/app/services/teller_card_service.py +++ b/app/services/teller_card_service.py @@ -1,3 +1,6 @@ + +from app.common.exceptions.custom_exception import CustomException +from app.common.exceptions.error_code import ErrorCode from app.dtos.teller_card.teller_card_dto import TellerCardDTO from app.models.badge_inventory import BadgeInventory from app.models.color_inventory import ColorInventory @@ -35,7 +38,6 @@ async def _validate_teller_card(cls, badge_code: str | None, color_code: str | N color_codes = [color["color_code"] for color in color_code_list] if badge_code and badge_code not in badge_codes: - raise ValueError("Invalid badge code") - + raise CustomException(ErrorCode.INVALID_BADGE_CODE) if color_code and color_code not in color_codes: - raise ValueError("Invalid color code") + raise CustomException(ErrorCode.INVALID_COLOR_CODE) diff --git a/app/tests/apis/v2/test_teller_card_router.py b/app/tests/apis/v2/test_teller_card_router.py new file mode 100644 index 0000000..3367536 --- /dev/null +++ b/app/tests/apis/v2/test_teller_card_router.py @@ -0,0 +1,89 @@ +from fastapi import status + +from app.common.constants.badge_code_list import BadgeCodeList +from app.common.constants.color_code_list import ColorCodeList +from app.dtos.teller_card.teller_card_request import TellerCardRequest +from app.tests.mothers.badge_mother import BadgeMother +from app.tests.mothers.color_mother import ColorMother +from app.tests.mothers.user_mother import UserMother +from app.tests.telling_me_client import TellingMeClient + + +async def test_update_teller_card(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: + # Given + user_mother = UserMother() + badge_mother = BadgeMother() + color_mother = ColorMother() + + user_id = await user_mother.create_user() + await user_mother.create_level_inventory() + await badge_mother.create_badge_inventory() + await color_mother.create_color_inventory() + await badge_mother.create_badge(user_id=user_id, badge_code=BadgeCodeList.FIRST) + + teller_card_request = TellerCardRequest( + user_id=user_id, + badgeCode=BadgeCodeList.FIRST, + colorCode=ColorCodeList.CL_RED_001, + ) + + # When + before_update_response = await telling_me_client.get_mobile_teller_card(user_id=user_id) + before_user_data = before_update_response.json()["data"]["userInfo"]["tellerCard"] + + update_response = await telling_me_client.update_teller_card(teller_card_request=teller_card_request) + + after_update_response = await telling_me_client.get_mobile_teller_card(user_id=user_id) + after_user_data = after_update_response.json()["data"]["userInfo"]["tellerCard"] + + # Then + assert before_update_response.status_code == status.HTTP_200_OK + assert update_response.status_code == status.HTTP_200_OK + assert after_update_response.status_code == status.HTTP_200_OK + + assert before_user_data["colorCode"] == ColorCodeList.CL_DEFAULT + assert before_user_data["badgeCode"] == BadgeCodeList.NEW + + assert after_user_data["colorCode"] == ColorCodeList.CL_RED_001 + assert after_user_data["badgeCode"] == BadgeCodeList.FIRST + + +async def test_update_teller_card_with_invalid_code( + telling_me_client: TellingMeClient, init_tortoise_connection: None +) -> None: + # Given + user_mother = UserMother() + badge_mother = BadgeMother() + color_mother = ColorMother() + + user_id = await user_mother.create_user() + await user_mother.create_level_inventory() + await badge_mother.create_badge_inventory() + await color_mother.create_color_inventory() + await badge_mother.create_badge(user_id=user_id, badge_code=BadgeCodeList.FIRST) + + invalid_badge_code_request = TellerCardRequest( + user_id=user_id, + badgeCode="invalid_badge_code", + colorCode=ColorCodeList.CL_RED_001, + ) + invalid_color_code_request = TellerCardRequest( + user_id=user_id, + badgeCode=BadgeCodeList.FIRST, + colorCode="invalid_color_code", + ) + + # When + invalid_badge_code_response = await telling_me_client.update_teller_card( + teller_card_request=invalid_badge_code_request + ) + invalid_color_code_response = await telling_me_client.update_teller_card( + teller_card_request=invalid_color_code_request + ) + + # Then + assert invalid_badge_code_response.status_code == status.HTTP_400_BAD_REQUEST + assert invalid_color_code_response.status_code == status.HTTP_400_BAD_REQUEST + + assert invalid_badge_code_response.json()["message"] == "유효하지 않은 뱃지 코드입니다" + assert invalid_color_code_response.json()["message"] == "유효하지 않은 컬러 코드입니다" diff --git a/app/tests/telling_me_client.py b/app/tests/telling_me_client.py index 5945c38..72fd41c 100644 --- a/app/tests/telling_me_client.py +++ b/app/tests/telling_me_client.py @@ -1,5 +1,7 @@ import httpx +from app.dtos.teller_card.teller_card_request import TellerCardRequest + class TellingMeClient: def __init__(self, httpx_client: httpx.AsyncClient): @@ -21,6 +23,18 @@ async def get_mobile_my_page(self, user_id: str) -> httpx.Response: }, ) + async def get_mobile_teller_card(self, user_id: str) -> httpx.Response: + return await self._client.get( + "/api/v2/mobiles/tellercard", + params={ + key: value + for key, value in { + "user_id": user_id, + }.items() + if value is not None + }, + ) + async def get_badges(self, user_id: str) -> httpx.Response: return await self._client.get( "/api/v2/user/badge", @@ -69,7 +83,5 @@ async def get_cheese_amount(self, user_id: str) -> httpx.Response: }, ) - # async def create_product(self, token: str, create_product_request: CreateProductRequest) -> httpx.Response: - # return await self._client.post( - # "/v1/products/admin", json=create_product_request.model_dump(), headers={"Authorization": f"Bearer {token}"} - # ) + async def update_teller_card(self, teller_card_request: TellerCardRequest) -> httpx.Response: + return await self._client.post("/api/v2/tellercard", json=teller_card_request.model_dump()) diff --git a/app/tests/utils/db_utils.py b/app/tests/utils/db_utils.py index 29e283b..5fe9ce3 100644 --- a/app/tests/utils/db_utils.py +++ b/app/tests/utils/db_utils.py @@ -1,6 +1,7 @@ from app.models.badge_inventory import BadgeInventory from app.models.color_inventory import ColorInventory from app.models.emotion_inventory import EmotionInventory +from app.models.level_inventory import LevelInventory async def reset_inventory_tables() -> None: @@ -11,3 +12,4 @@ async def reset_inventory_tables() -> None: await BadgeInventory.all().delete() await ColorInventory.all().delete() await EmotionInventory.all().delete() + await LevelInventory.all().delete() From 368f2ae0709964a72661bf20230c1eb88ecd4d9e Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Fri, 13 Jun 2025 00:53:26 +0900 Subject: [PATCH 22/27] =?UTF-8?q?=20=E2=9C=85=20test:=20add=20mobile=20rou?= =?UTF-8?q?ter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/apis/v2/mobile_router.py | 2 +- app/models/answer.py | 36 ++++- app/models/user.py | 8 -- app/queries/user_query.py | 7 - app/services/answer_service.py | 14 ++ app/services/color_service.py | 4 - app/services/emotion_service.py | 4 - app/services/teller_card_service.py | 1 - app/services/user_service.py | 8 +- app/tests/apis/v2/test_badge_router.py | 7 +- app/tests/apis/v2/test_mobile_router.py | 132 +++++++++++++++++++ app/tests/apis/v2/test_teller_card_router.py | 23 ++-- app/tests/mothers/answer_mother.py | 13 ++ app/tests/mothers/color_mother.py | 5 - app/tests/mothers/emotion_mother.py | 5 - app/tests/mothers/user_mother.py | 3 +- 16 files changed, 218 insertions(+), 54 deletions(-) create mode 100644 app/tests/apis/v2/test_mobile_router.py create mode 100644 app/tests/mothers/answer_mother.py diff --git a/app/apis/v2/mobile_router.py b/app/apis/v2/mobile_router.py index a069e29..5cded0d 100644 --- a/app/apis/v2/mobile_router.py +++ b/app/apis/v2/mobile_router.py @@ -80,6 +80,6 @@ async def mobile_my_page_handler(user_id: str) -> MyPageResponse: return MyPageResponse( code=status.HTTP_200_OK, - message="정상처리되었습니다", + message="mypage ui page", data=user_profile_data, ) diff --git a/app/models/answer.py b/app/models/answer.py index d580d24..7ff386e 100644 --- a/app/models/answer.py +++ b/app/models/answer.py @@ -4,6 +4,7 @@ from tortoise.models import Model from app.common.utils.query_executor import QueryExecutor +from app.core.configs import settings from app.dtos.answer.answer_data import AnswerData from app.queries.answer_query import ( SELECT_ANSWER_BY_USER_UUID_QUERY, @@ -28,12 +29,43 @@ class Answer(Model): blind_started_at = fields.DatetimeField(null=True) like_count = fields.IntField(null=False, default=0) is_spare = fields.BooleanField(null=False) - created_at = fields.DatetimeField(auto_now_add=True) - updated_at = fields.DatetimeField(auto_now=True) class Meta: table = "answer" + @classmethod + async def create_answer( + cls, + user_id: str, + content: str, + date: str, + emotion: int = 1, + like_count: int = 0, + is_premium: bool = False, + is_public: bool = False, + is_blind: bool = False, + is_spare: bool = False, + ) -> None: + created_time = datetime.now(settings.db_zoneinfo).strftime("%Y-%m-%d %H:%M:%S") + + query = """ + INSERT INTO answer ( + user_id, content, date, emotion, + is_premium, is_public, is_blind, is_spare, + created_time, like_count + ) + VALUES ( + UNHEX(REPLACE(%s, '-', '')), %s, %s, %s, + %s, %s, %s, %s, + %s, %s + ); + """ + + await QueryExecutor.execute_write_query( + query, + (user_id, content, date, emotion, is_premium, is_public, is_blind, is_spare, created_time, like_count), + ) + # 기존 get_answer_count_by_user_id 메서드 @classmethod async def get_answer_count_by_user_id(cls, user_id: str) -> int: diff --git a/app/models/user.py b/app/models/user.py index 1d75a26..3c53b9d 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -16,7 +16,6 @@ from app.queries.user_query import ( SELECT_USER_INFO_BY_USER_UUID_QUERY, SELECT_USER_PROFILE_BY_USER_ID_QUERY, - UPDATE_PREMIUM_STATUS_QUERY, ) @@ -147,13 +146,6 @@ async def get_user_info_by_user_id(cls, user_id: str) -> UserData: result = await QueryExecutor.execute_query(query, values=value, fetch_type="single") return UserData(**result) - @classmethod - async def set_is_premium(cls, user_id: str, is_premium: bool) -> None: - query = UPDATE_PREMIUM_STATUS_QUERY - current_time = datetime.now() - values = (int(is_premium), current_time, user_id) - await QueryExecutor.execute_query(query, values=values, fetch_type="single") - @classmethod def format_user_id(cls, user_id_bytes: bytes) -> str: return str(uuid.UUID(bytes=user_id_bytes)) diff --git a/app/queries/user_query.py b/app/queries/user_query.py index 904c991..18d0ce8 100644 --- a/app/queries/user_query.py +++ b/app/queries/user_query.py @@ -31,10 +31,3 @@ FROM user u WHERE {USER_ID_QUERY} """ - -UPDATE_PREMIUM_STATUS_QUERY = f""" - UPDATE `user` - SET `is_premium` = %s, - `premium_started_at` = %s - WHERE {USER_ID_QUERY} - """ diff --git a/app/services/answer_service.py b/app/services/answer_service.py index 8aa7cb9..9bcb983 100644 --- a/app/services/answer_service.py +++ b/app/services/answer_service.py @@ -5,6 +5,20 @@ class AnswerService: + + @classmethod + async def create_answer( + cls, + user_id: str, + content: str, + date: str, + ) -> None: + await Answer.create_answer( + user_id=user_id, + content=content, + date=date, + ) + @classmethod async def get_answer_count(cls, user_id: str) -> int: """ diff --git a/app/services/color_service.py b/app/services/color_service.py index e8df28c..8f865ba 100644 --- a/app/services/color_service.py +++ b/app/services/color_service.py @@ -6,10 +6,6 @@ class ColorService: - @classmethod - async def create_color(cls, user_id: str, color_code: str) -> None: - await Color.create_by_user_id(user_id=user_id, color_code=color_code) - @classmethod async def create_color_inventory(cls) -> None: await ColorInventory.create_bulk() diff --git a/app/services/emotion_service.py b/app/services/emotion_service.py index 13c1a3b..7ec593b 100644 --- a/app/services/emotion_service.py +++ b/app/services/emotion_service.py @@ -8,10 +8,6 @@ class EmotionService: - @classmethod - async def create_emotion(cls, user_id: str, emotion_code: str) -> None: - await Emotion.create_by_user_id(user_id=user_id, emotion_code=emotion_code) - @classmethod async def create_emotion_inventory(cls) -> None: await EmotionInventory.create_bulk() diff --git a/app/services/teller_card_service.py b/app/services/teller_card_service.py index 028266b..8c5b5e0 100644 --- a/app/services/teller_card_service.py +++ b/app/services/teller_card_service.py @@ -1,4 +1,3 @@ - from app.common.exceptions.custom_exception import CustomException from app.common.exceptions.error_code import ErrorCode from app.dtos.teller_card.teller_card_dto import TellerCardDTO diff --git a/app/services/user_service.py b/app/services/user_service.py index 994d788..c305d31 100644 --- a/app/services/user_service.py +++ b/app/services/user_service.py @@ -12,14 +12,14 @@ class UserService: @staticmethod - async def create_user(is_premium: bool = False) -> str: + async def create_user(user_name: str, is_premium: bool = False) -> str: cheese_manager = await CheeseManager.create_cheese_manager() teller_card = await TellerCard.create(activate_badge_code="BG_NEW", activate_color_code="CL_DEFAULT") level = await Level.create(user_exp=0, user_level=1) user_id = await User.create_user( social_id="kakao_456", social_login_type="kakao", - nickname="test_user", + nickname=user_name, purpose="test", job=1, cheese_manager_id=cheese_manager.cheese_manager_id, @@ -39,7 +39,3 @@ async def get_user_info(user_id: str) -> UserData: @classmethod async def get_user_profile(cls, user_id: str) -> UserProfileData: return await User.get_user_profile_by_user_id(user_id=user_id) - - @staticmethod - async def set_is_premium(user_id: str, is_premium: bool) -> None: - await User.set_is_premium(user_id=user_id, is_premium=is_premium) diff --git a/app/tests/apis/v2/test_badge_router.py b/app/tests/apis/v2/test_badge_router.py index dcebee6..8650787 100644 --- a/app/tests/apis/v2/test_badge_router.py +++ b/app/tests/apis/v2/test_badge_router.py @@ -1,3 +1,5 @@ +import asyncio + from fastapi import status from app.common.constants.badge_code_list import BadgeCodeList @@ -12,8 +14,9 @@ async def test_get_badges(telling_me_client: TellingMeClient, init_tortoise_conn badge_mother = BadgeMother() user_id = await user_mother.create_user() - await badge_mother.create_badge_inventory() - await badge_mother.create_badge(user_id=user_id, badge_code=BadgeCodeList.NEW) + await asyncio.gather( + badge_mother.create_badge_inventory(), badge_mother.create_badge(user_id=user_id, badge_code=BadgeCodeList.NEW) + ) # When response = await telling_me_client.get_badges(user_id=user_id) diff --git a/app/tests/apis/v2/test_mobile_router.py b/app/tests/apis/v2/test_mobile_router.py new file mode 100644 index 0000000..e355819 --- /dev/null +++ b/app/tests/apis/v2/test_mobile_router.py @@ -0,0 +1,132 @@ +import asyncio +from datetime import datetime + +import time_machine +from fastapi import status + +from app.common.constants.badge_code_list import BadgeCodeList +from app.common.constants.color_code_list import ColorCodeList +from app.core.configs import settings +from app.tests.mothers.answer_mother import AnswerMother +from app.tests.mothers.badge_mother import BadgeMother +from app.tests.mothers.color_mother import ColorMother +from app.tests.mothers.user_mother import UserMother +from app.tests.telling_me_client import TellingMeClient + + +async def test_get_mobile_teller_card(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: + # Given + user_mother = UserMother() + answer_mother = AnswerMother() + badge_mother = BadgeMother() + color_mother = ColorMother() + + user_id = await user_mother.create_user(user_name=(user_name := "telling me user")) + + await asyncio.gather( + user_mother.create_level_inventory(), + badge_mother.create_badge_inventory(), + color_mother.create_color_inventory(), + answer_mother.create_answer(user_id=user_id, content="-", date="2025-06-10"), + answer_mother.create_answer(user_id=user_id, content="-", date="2025-06-12"), + answer_mother.create_answer(user_id=user_id, content="-", date="2025-06-13"), + user_mother.add_cheese(user_id=user_id, amount=(cheese_amount := 100)), + badge_mother.create_badge(user_id=user_id, badge_code=BadgeCodeList.FIRST), + ) + + excepted_badges = [ + { + "badgeCode": BadgeCodeList.FIRST, + "badgeName": "탐험가 텔러", + "badgeMiddleName": "낯선 길에 첫 발자국,", + "badgeCondition": "첫 글을 작성했어요!", + } + ] + expected_colors = [ + {"colorCode": ColorCodeList.CL_BLUE_001, "colorName": "Blue_1", "colorHexCode": "#229DF6"}, + {"colorCode": ColorCodeList.CL_DEFAULT, "colorName": "Default", "colorHexCode": "#1EDCC5"}, + {"colorCode": ColorCodeList.CL_ORANGE_001, "colorName": "Orange_1", "colorHexCode": "#FFA216"}, + {"colorCode": ColorCodeList.CL_RED_001, "colorName": "Red_1", "colorHexCode": "#ED3639"}, + ] + + # When + with time_machine.travel(datetime(2025, 6, 13, 1, 0, 0, tzinfo=settings.db_zoneinfo), tick=False): + response_before_6am = await telling_me_client.get_mobile_teller_card(user_id=user_id) + with time_machine.travel(datetime(2025, 6, 13, 10, 0, 0, tzinfo=settings.db_zoneinfo), tick=False): + # record는 연속 작성일을 의미한다. 6시 이전은 전날로 인식하여 time machine으로 시간 고정 + response = await telling_me_client.get_mobile_teller_card(user_id=user_id) + + code = response.json()["code"] + message = response.json()["message"] + data = response.json()["data"] + badges = data["badges"] + colors = data["colors"] + user_info = data["userInfo"] + level_info = data["levelInfo"] + after_6am_record_count = data["recordCount"] + before_6am_record_count = response_before_6am.json()["data"]["recordCount"] + + # Then user 정보 및 보유 색상 및 뱃지 조회 + assert response.status_code == status.HTTP_200_OK + + assert code == response.status_code + assert message == "teller_card ui page" + + assert user_info["nickname"] == user_name + assert user_info["cheeseBalance"] == cheese_amount + assert user_info["tellerCard"]["colorCode"] == ColorCodeList.CL_DEFAULT + assert user_info["tellerCard"]["badgeCode"] == BadgeCodeList.NEW + + assert level_info["levelDto"]["level"] == 1 + assert level_info["levelDto"]["currentExp"] == 0 + assert level_info["levelDto"]["requiredExp"] == 15 + assert after_6am_record_count == 2 + assert before_6am_record_count == 1 + + assert sorted(badges, key=lambda x: x["badgeCode"]) == sorted(excepted_badges, key=lambda x: x["badgeCode"]) + assert sorted(colors, key=lambda x: x["colorCode"]) == sorted(expected_colors, key=lambda x: x["colorCode"]) + + +async def test_get_mobile_mypage(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: + # Given + user_mother = UserMother() + badge_mother = BadgeMother() + color_mother = ColorMother() + + user_id = await user_mother.create_user(user_name=(user_name := "telling me user"), is_premium=(is_premium := True)) + + await asyncio.gather( + user_mother.create_level_inventory(), + badge_mother.create_badge_inventory(), + color_mother.create_color_inventory(), + user_mother.add_cheese(user_id=user_id, amount=(cheese_amount := 100)), + badge_mother.create_badge(user_id=user_id, badge_code=BadgeCodeList.FIRST), + ) + + # When + + response = await telling_me_client.get_mobile_my_page(user_id=user_id) + code = response.json()["code"] + message = response.json()["message"] + data = response.json()["data"] + + user_profile = data["userProfile"] + level = data["level"]["levelDto"] + + # Then user 정보 및 보유 색상 및 뱃지 조회 + assert response.status_code == status.HTTP_200_OK + + assert code == response.status_code + assert message == "mypage ui page" + + assert user_profile["nickname"] == user_name + assert user_profile["cheeseBalance"] == cheese_amount + assert user_profile["badgeCode"] == BadgeCodeList.NEW + assert user_profile["badgeCount"] == 1 + assert user_profile["answerCount"] == 0 + assert user_profile["premium"] == is_premium + assert not user_profile["allowNotification"] + + assert level["level"] == 1 + assert level["currentExp"] == 0 + assert level["requiredExp"] == 15 diff --git a/app/tests/apis/v2/test_teller_card_router.py b/app/tests/apis/v2/test_teller_card_router.py index 3367536..dddbaf0 100644 --- a/app/tests/apis/v2/test_teller_card_router.py +++ b/app/tests/apis/v2/test_teller_card_router.py @@ -1,3 +1,5 @@ +import asyncio + from fastapi import status from app.common.constants.badge_code_list import BadgeCodeList @@ -16,10 +18,13 @@ async def test_update_teller_card(telling_me_client: TellingMeClient, init_torto color_mother = ColorMother() user_id = await user_mother.create_user() - await user_mother.create_level_inventory() - await badge_mother.create_badge_inventory() - await color_mother.create_color_inventory() - await badge_mother.create_badge(user_id=user_id, badge_code=BadgeCodeList.FIRST) + + await asyncio.gather( + user_mother.create_level_inventory(), + badge_mother.create_badge_inventory(), + color_mother.create_color_inventory(), + badge_mother.create_badge(user_id=user_id, badge_code=BadgeCodeList.FIRST), + ) teller_card_request = TellerCardRequest( user_id=user_id, @@ -57,10 +62,12 @@ async def test_update_teller_card_with_invalid_code( color_mother = ColorMother() user_id = await user_mother.create_user() - await user_mother.create_level_inventory() - await badge_mother.create_badge_inventory() - await color_mother.create_color_inventory() - await badge_mother.create_badge(user_id=user_id, badge_code=BadgeCodeList.FIRST) + await asyncio.gather( + user_mother.create_level_inventory(), + badge_mother.create_badge_inventory(), + color_mother.create_color_inventory(), + badge_mother.create_badge(user_id=user_id, badge_code=BadgeCodeList.FIRST), + ) invalid_badge_code_request = TellerCardRequest( user_id=user_id, diff --git a/app/tests/mothers/answer_mother.py b/app/tests/mothers/answer_mother.py new file mode 100644 index 0000000..bfd9c30 --- /dev/null +++ b/app/tests/mothers/answer_mother.py @@ -0,0 +1,13 @@ +from app.services.answer_service import AnswerService + + +class AnswerMother: + + @staticmethod + async def create_answer( + user_id: str, + date: str, + content: str = "-", + ) -> None: + answer_service = AnswerService() + await answer_service.create_answer(user_id=user_id, content=content, date=date) diff --git a/app/tests/mothers/color_mother.py b/app/tests/mothers/color_mother.py index 67ab688..c764aab 100644 --- a/app/tests/mothers/color_mother.py +++ b/app/tests/mothers/color_mother.py @@ -3,11 +3,6 @@ class ColorMother: - @staticmethod - async def create_color(user_id: str, color_code: str) -> None: - color_service = ColorService() - await color_service.create_color(user_id=user_id, color_code=color_code) - @staticmethod async def create_color_inventory() -> None: color_service = ColorService() diff --git a/app/tests/mothers/emotion_mother.py b/app/tests/mothers/emotion_mother.py index 721fc85..afe1120 100644 --- a/app/tests/mothers/emotion_mother.py +++ b/app/tests/mothers/emotion_mother.py @@ -3,11 +3,6 @@ class EmotionMother: - @staticmethod - async def create_emotion(user_id: str, emotion_code: str) -> None: - emotion_service = EmotionService() - await emotion_service.create_emotion(user_id=user_id, emotion_code=emotion_code) - @staticmethod async def create_emotion_inventory() -> None: emotion_service = EmotionService() diff --git a/app/tests/mothers/user_mother.py b/app/tests/mothers/user_mother.py index 9fe44ad..0ce94ff 100644 --- a/app/tests/mothers/user_mother.py +++ b/app/tests/mothers/user_mother.py @@ -7,10 +7,11 @@ class UserMother: @staticmethod async def create_user( + user_name: str = "test_user", is_premium: bool = False, ) -> str: user_service = UserService() - new_user_id = await user_service.create_user(is_premium=is_premium) + new_user_id = await user_service.create_user(user_name=user_name, is_premium=is_premium) return new_user_id @staticmethod From cfe10c47b9a1b34fe7401f2ef266d1e0bc34fdf0 Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Fri, 13 Jun 2025 00:55:01 +0900 Subject: [PATCH 23/27] =?UTF-8?q?=20=F0=9F=92=A1=20chore:=20add=20time=20m?= =?UTF-8?q?achine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poetry.lock | 70 +++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 51f04a0..cf8cca2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1691,6 +1691,74 @@ anyio = ">=3.4.0,<5" [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] +[[package]] +name = "time-machine" +version = "2.16.0" +description = "Travel through time in your tests." +optional = false +python-versions = ">=3.9" +files = [ + {file = "time_machine-2.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:09531af59fdfb39bfd24d28bd1e837eff5a5d98318509a31b6cfd57d27801e52"}, + {file = "time_machine-2.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:92d0b0f3c49f34dd76eb462f0afdc61ed1cb318c06c46d03e99b44ebb489bdad"}, + {file = "time_machine-2.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c29616e18e2349a8766d5b6817920fc74e39c00fa375d202231e9d525a1b882"}, + {file = "time_machine-2.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1ceb6035a64cb00650e3ab203cf3faffac18576a3f3125c24df468b784077c7"}, + {file = "time_machine-2.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64c205ea37b8c4ba232645335fc3b75bc2d03ce30f0a34649e36cae85652ee96"}, + {file = "time_machine-2.16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dfe92412bd11104c4f0fb2da68653e6c45b41f7217319a83a8b66ed4f20148b3"}, + {file = "time_machine-2.16.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d5fe7a6284e3dce87ae13a25029c53542dd27a28d151f3ef362ec4dd9c3e45fd"}, + {file = "time_machine-2.16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c0fca3025266d88d1b48be162a43b7c2d91c81cc5b3bee9f01194678ffb9969a"}, + {file = "time_machine-2.16.0-cp310-cp310-win32.whl", hash = "sha256:4149e17018af07a5756a1df84aea71e6e178598c358c860c6bfec42170fa7970"}, + {file = "time_machine-2.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:01bc257e9418980a4922de94775be42a966e1a082fb01a1635917f9afc7b84ca"}, + {file = "time_machine-2.16.0-cp310-cp310-win_arm64.whl", hash = "sha256:6895e3e84119594ab12847c928f619d40ae9cedd0755515dc154a5b5dc6edd9f"}, + {file = "time_machine-2.16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8f936566ef9f09136a3d5db305961ef6d897b76b240c9ff4199144aed6dd4fe5"}, + {file = "time_machine-2.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5886e23ede3478ca2a3e0a641f5d09dd784dfa9e48c96e8e5e31fc4fe77b6dc0"}, + {file = "time_machine-2.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c76caf539fa4941e1817b7c482c87c65c52a1903fea761e84525955c6106fafb"}, + {file = "time_machine-2.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:298aa423e07c8b21b991782f01d7749c871c792319c2af3e9755f9ab49033212"}, + {file = "time_machine-2.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391ae9c484736850bb44ef125cbad52fe2d1b69e42c95dc88c43af8ead2cc7"}, + {file = "time_machine-2.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:503e7ff507c2089699d91885fc5b9c8ff16774a7b6aff48b4dcee0c0a0685b61"}, + {file = "time_machine-2.16.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eee7b0fc4fbab2c6585ea17606c6548be83919c70deea0865409fe9fc2d8cdce"}, + {file = "time_machine-2.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9db5e5b3ccdadaafa5730c2f9db44c38b013234c9ad01f87738907e19bdba268"}, + {file = "time_machine-2.16.0-cp311-cp311-win32.whl", hash = "sha256:2552f0767bc10c9d668f108fef9b487809cdeb772439ce932e74136365c69baf"}, + {file = "time_machine-2.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:12474fcdbc475aa6fe5275fe7224e685c5b9777f5939647f35980e9614ae7558"}, + {file = "time_machine-2.16.0-cp311-cp311-win_arm64.whl", hash = "sha256:ac2df0fa564356384515ed62cb6679f33f1f529435b16b0ec0f88414635dbe39"}, + {file = "time_machine-2.16.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:84788f4d62a8b1bf5e499bb9b0e23ceceea21c415ad6030be6267ce3d639842f"}, + {file = "time_machine-2.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:15ec236b6571730236a193d9d6c11d472432fc6ab54e85eac1c16d98ddcd71bf"}, + {file = "time_machine-2.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cedc989717c8b44a3881ac3d68ab5a95820448796c550de6a2149ed1525157f0"}, + {file = "time_machine-2.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d26d79de1c63a8c6586c75967e09b0ff306aa7e944a1eaddb74595c9b1839ca"}, + {file = "time_machine-2.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:317b68b56a9c3731e0cf8886e0f94230727159e375988b36c60edce0ddbcb44a"}, + {file = "time_machine-2.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:43e1e18279759897be3293a255d53e6b1cb0364b69d9591d0b80c51e461c94b0"}, + {file = "time_machine-2.16.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e43adb22def972a29d2b147999b56897116085777a0fea182fd93ee45730611e"}, + {file = "time_machine-2.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0c766bea27a0600e36806d628ebc4b47178b12fcdfb6c24dc0a566a9c06bfe7f"}, + {file = "time_machine-2.16.0-cp312-cp312-win32.whl", hash = "sha256:6dae82ab647d107817e013db82223e20a9853fa88543fec853ae326382d03c2e"}, + {file = "time_machine-2.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:265462c77dc9576267c3c7f20707780a171a9fdbac93ac22e608c309efd68c33"}, + {file = "time_machine-2.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:ef768e14768eebe3bb1196c0dece8e14c1c6991605721214a0c3c68cf77eb216"}, + {file = "time_machine-2.16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7751bf745d54e9e8b358c0afa332815da9b8a6194b26d0fd62876ab6c4d5c9c0"}, + {file = "time_machine-2.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1784edf173ca840ba154de6eed000b5727f65ab92972c2f88cec5c4d6349c5f2"}, + {file = "time_machine-2.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f5876a5682ce1f517e55d7ace2383432627889f6f7e338b961f99d684fd9e8d"}, + {file = "time_machine-2.16.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:806672529a2e255cd901f244c9033767dc1fa53466d0d3e3e49565a1572a64fe"}, + {file = "time_machine-2.16.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:667b150fedb54acdca2a4bea5bf6da837b43e6dd12857301b48191f8803ba93f"}, + {file = "time_machine-2.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:da3ae1028af240c0c46c79adf9c1acffecc6ed1701f2863b8132f5ceae6ae4b5"}, + {file = "time_machine-2.16.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:520a814ea1b2706c89ab260a54023033d3015abef25c77873b83e3d7c1fafbb2"}, + {file = "time_machine-2.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8243664438bb468408b29c6865958662d75e51f79c91842d2794fa22629eb697"}, + {file = "time_machine-2.16.0-cp313-cp313-win32.whl", hash = "sha256:32d445ce20d25c60ab92153c073942b0bac9815bfbfd152ce3dcc225d15ce988"}, + {file = "time_machine-2.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:f6927dda86425f97ffda36131f297b1a601c64a6ee6838bfa0e6d3149c2f0d9f"}, + {file = "time_machine-2.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:4d3843143c46dddca6491a954bbd0abfd435681512ac343169560e9bab504129"}, + {file = "time_machine-2.16.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:23c5283c01b4f80b7dfbc88f3d8088c06c301b94b7c35366be498c2d7b308549"}, + {file = "time_machine-2.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ac95ae4529d7d85b251f9cf0f961a8a408ba285875811268f469d824a3b0b15a"}, + {file = "time_machine-2.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfb76674db946a74f0ca6e3b81caa8265e35dafe9b7005c7d2b8dd5bbd3825cf"}, + {file = "time_machine-2.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0b6ff3ccde9b16bbc694a2b5facf2d8890554f3135ff626ed1429e270e3cc4f"}, + {file = "time_machine-2.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1906ec6e26e6b803cd6aab28d420c87285b9c209ff2a69f82d12f82278f78bb"}, + {file = "time_machine-2.16.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e46bd09c944ec7a20868abd2b83d7d7abdaf427775e9df3089b9226a122b340f"}, + {file = "time_machine-2.16.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cac3e2b4101db296b150cb665e5461c03621e6ede6117fc9d5048c0ec96d6e7c"}, + {file = "time_machine-2.16.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e0dcc97cfec12ae306e3036746e7631cc7ef65c31889f7264c25217d4938367"}, + {file = "time_machine-2.16.0-cp39-cp39-win32.whl", hash = "sha256:c761d32d0c5d1fe5b71ac502e1bd5edec4598a7fc6f607b9b906b98e911148ce"}, + {file = "time_machine-2.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:ddfab1c622342f2945942c5c2d6be327656980e8f2d2b2ce0c022d0aa3711361"}, + {file = "time_machine-2.16.0-cp39-cp39-win_arm64.whl", hash = "sha256:2e08a4015d5d1aab2cb46c780e85b33efcd5cbe880bb363b282a6972e617b8bb"}, + {file = "time_machine-2.16.0.tar.gz", hash = "sha256:4a99acc273d2f98add23a89b94d4dd9e14969c01214c8514bfa78e4e9364c7e2"}, +] + +[package.dependencies] +python-dateutil = "*" + [[package]] name = "tortoise-orm" version = "0.21.7" @@ -2058,4 +2126,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "3c5afc1504f5de31998ff92cafdb4bb5d46321d7deb8b977f71eed6a96471e93" +content-hash = "ee677ff6b453248aadd43bea161451ce9d18a7889e3ecf490ff38a6a291c7749" diff --git a/pyproject.toml b/pyproject.toml index 552f065..e1dc5ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ mypy-extensions = "^1.0.0" coverage = "^7.6.8" celery-stubs = "^0.1.3" ruff = "^0.11.12" +time-machine = "^2.16.0" [tool.mypy] python_version = "3.12" From 98701c098fbc61f7847270bb308758fcc11e46fb Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Fri, 13 Jun 2025 15:42:02 +0900 Subject: [PATCH 24/27] =?UTF-8?q?=E2=9C=85=20test:=20add=20payment=20route?= =?UTF-8?q?r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/constants/product_code_list.py | 21 ++ app/common/exceptions/error_code.py | 5 +- app/models/item.py | 129 +++++++++- app/services/item_service.py | 15 ++ app/services/payment_service.py | 2 - app/tests/apis/v2/test_mission_router.py | 1 + app/tests/apis/v2/test_payment_router.py | 256 +++++++++++++++++++ app/tests/apis/v2/test_teller_card_router.py | 5 +- app/tests/mothers/item_mother.py | 15 ++ app/tests/telling_me_client.py | 4 + app/tests/utils/db_utils.py | 5 + 11 files changed, 443 insertions(+), 15 deletions(-) create mode 100644 app/common/constants/product_code_list.py create mode 100644 app/services/item_service.py create mode 100644 app/tests/apis/v2/test_mission_router.py create mode 100644 app/tests/apis/v2/test_payment_router.py create mode 100644 app/tests/mothers/item_mother.py diff --git a/app/common/constants/product_code_list.py b/app/common/constants/product_code_list.py new file mode 100644 index 0000000..504da32 --- /dev/null +++ b/app/common/constants/product_code_list.py @@ -0,0 +1,21 @@ +from enum import Enum + + +class ProductCodeList(str, Enum): + PD_CL_PURPLE_001 = "PD_CL_PURPLE_001" + PD_CL_NAVY_001 = "PD_CL_NAVY_001" + PD_CL_PINK_001 = "PD_CL_PINK_001" + PD_CL_YELLOW_001 = "PD_CL_YELLOW_001" + PD_CL_GREEN_001 = "PD_CL_GREEN_001" + PD_EM_EXCITED = "PD_EM_EXCITED" + PD_EM_FUN = "PD_EM_FUN" + PD_EM_RELAXED = "PD_EM_RELAXED" + PD_EM_APATHETIC = "PD_EM_APATHETIC" + PD_EM_LONELY = "PD_EM_LONELY" + PD_EM_COMPLEX = "PD_EM_COMPLEX" + PD_PLUS_MONTH_1_KR = "PD_PLUS_MONTH_1_KR" + PD_PLUS_YEAR_1_KR = "PD_PLUS_YEAR_1_KR" + + # 뱃지 구매 테스트용 + PD_BG_CHRISTMAS_2024 = "PD_BG_CHRISTMAS_2024" + PD_TEST = "PD_TEST" diff --git a/app/common/exceptions/error_code.py b/app/common/exceptions/error_code.py index b5a8242..72f2690 100644 --- a/app/common/exceptions/error_code.py +++ b/app/common/exceptions/error_code.py @@ -11,11 +11,12 @@ class ErrorCode(Enum): INVALID_COLOR_CODE = (4006, "유효하지 않은 컬러 코드입니다") # 404 Not Found - NO_INVENTORY_FOR_PRODUCT = (4041, "이 상품에 대한 재고가 없습니다.") PRODUCT_NOT_FOUND = (4042, "해당 상품을 찾을 수 없습니다.") - NO_VALID_RECEIPT = (4006, "유효한 영수증이 없습니다.") + # 500 server Error + NO_INVENTORY_FOR_PRODUCT = (5001, "데이터베이스에 해당 상품에 대한 재고가 없습니다.") + def __init__(self, code: int, message: str) -> None: self._code = code self._message = message diff --git a/app/models/item.py b/app/models/item.py index 1a14285..4bc68f6 100644 --- a/app/models/item.py +++ b/app/models/item.py @@ -1,8 +1,10 @@ -from tortoise import fields, models +from tortoise import fields from tortoise.fields import ForeignKeyRelation +from tortoise.models import Model +from tortoise.transactions import in_transaction -class ItemInventory(models.Model): +class ItemInventory(Model): item_id = fields.BigIntField(primary_key=True) item_category = fields.CharField(max_length=255, null=True) item_code = fields.CharField(max_length=255, null=True) @@ -10,8 +12,50 @@ class ItemInventory(models.Model): class Meta: table = "item_inventory" - -class ProductInventory(models.Model): + @classmethod + async def create_bulk(cls) -> None: + items_data = [ + (1, "BADGE", "BG_AGAIN_001"), + (2, "BADGE", "BG_CHRISTMAS_2024"), + (3, "BADGE", "BG_FIRST"), + (4, "BADGE", "BG_MUCH_001"), + (5, "BADGE", "BG_NEW"), + (6, "BADGE", "BG_NIGHT_001"), + (7, "BADGE", "BG_SAVE_001"), + (16, "COLOR", "CL_BLUE_001"), + (17, "COLOR", "CL_DEFAULT"), + (18, "COLOR", "CL_GREEN_001"), + (19, "COLOR", "CL_NAVY_001"), + (20, "COLOR", "CL_ORANGE_001"), + (21, "COLOR", "CL_PINK_001"), + (22, "COLOR", "CL_PURPLE_001"), + (23, "COLOR", "CL_RED_001"), + (24, "COLOR", "CL_YELLOW_001"), + (25, "EMOTION", "EM_HAPPY"), + (26, "EMOTION", "EM_PROUD"), + (27, "EMOTION", "EM_OKAY"), + (28, "EMOTION", "EM_TIRED"), + (29, "EMOTION", "EM_SAD"), + (30, "EMOTION", "EM_ANGRY"), + (31, "EMOTION", "EM_EXCITED"), + (32, "EMOTION", "EM_FUN"), + (33, "EMOTION", "EM_RELAXED"), + (34, "EMOTION", "EM_APATHETIC"), + (35, "EMOTION", "EM_LONELY"), + (36, "EMOTION", "EM_COMPLEX"), + (37, "SUBSCRIPTION", "PLUS_MONTH_1"), + (38, "SUBSCRIPTION", "PLUS_YEAR_1"), + (39, "CHEESE", "CHEESE"), + (40, "POINT", "POINT"), + ] + + async with in_transaction(): + await cls.bulk_create( + [cls(item_id=item_id, item_category=category, item_code=code) for item_id, category, code in items_data] + ) + + +class ProductInventory(Model): product_id = fields.BigIntField(primary_key=True) price = fields.FloatField(null=True) product_category = fields.CharField(max_length=255, null=True) @@ -21,8 +65,42 @@ class ProductInventory(models.Model): class Meta: table = "product_inventory" - -class ItemInventoryProductInventory(models.Model): + @classmethod + async def create_bulk(cls) -> None: + products_data = [ + (1, 20, "CONSUMABLE", "PD_CL_PURPLE_001", "CHEESE"), + (2, 20, "CONSUMABLE", "PD_CL_NAVY_001", "CHEESE"), + (3, 20, "CONSUMABLE", "PD_CL_PINK_001", "CHEESE"), + (4, 20, "CONSUMABLE", "PD_CL_YELLOW_001", "CHEESE"), + (5, 20, "CONSUMABLE", "PD_CL_GREEN_001", "CHEESE"), + (6, 33, "CONSUMABLE", "PD_EM_EXCITED", "CHEESE"), + (7, 33, "CONSUMABLE", "PD_EM_FUN", "CHEESE"), + (8, 33, "CONSUMABLE", "PD_EM_RELAXED", "CHEESE"), + (9, 33, "CONSUMABLE", "PD_EM_APATHETIC", "CHEESE"), + (10, 33, "CONSUMABLE", "PD_EM_LONELY", "CHEESE"), + (11, 33, "CONSUMABLE", "PD_EM_COMPLEX", "CHEESE"), + (12, 990, "SUBSCRIPTION", "PD_PLUS_MONTH_1_KR", "KRW"), + (13, 9900, "SUBSCRIPTION", "PD_PLUS_YEAR_1_KR", "KRW"), + (14, 33, "CONSUMABLE", "PD_BG_CHRISTMAS_2024", "CHEESE"), + (15, 0, "CONSUMABLE", "PD_TEST", "CHEESE"), + ] + + async with in_transaction(): + await cls.bulk_create( + [ + cls( + product_id=pid, + price=price, + product_category=category, + product_code=code, + transaction_currency=currency, + ) + for pid, price, category, code, currency in products_data + ] + ) + + +class ItemInventoryProductInventory(Model): item_inventory_product_inventory_id = fields.BigIntField(primary_key=True) quantity = fields.IntField() item_inventory: ForeignKeyRelation[ItemInventory] = fields.ForeignKeyField( @@ -36,8 +114,41 @@ class ItemInventoryProductInventory(models.Model): class Meta: table = "item_inventory_product_inventory" - -class RewardInventory(models.Model): + @classmethod + async def create_bulk(cls) -> None: + mapping_data = [ + (1, 1, 22, 1, "UNIT"), + (2, 1, 19, 2, "UNIT"), + (3, 1, 21, 3, "UNIT"), + (4, 1, 24, 4, "UNIT"), + (5, 1, 18, 5, "UNIT"), + (6, 1, 31, 6, "UNIT"), + (7, 1, 32, 7, "UNIT"), + (8, 1, 33, 8, "UNIT"), + (9, 1, 34, 9, "UNIT"), + (10, 1, 35, 10, "UNIT"), + (11, 1, 36, 11, "UNIT"), + (12, 1, 37, 12, "MONTH"), + (13, 1, 38, 13, "YEAR"), + (14, 1, 2, 14, "UNIT"), + ] + + async with in_transaction(): + await cls.bulk_create( + [ + cls( + item_inventory_product_inventory_id=id_, + quantity=quantity, + item_inventory_id=item_id, + product_inventory_id=product_id, + item_measurement=measurement, + ) + for id_, quantity, item_id, product_id, measurement in mapping_data + ] + ) + + +class RewardInventory(Model): reward_inventory_id = fields.BigIntField(primary_key=True) item_code = fields.CharField(max_length=255, null=True) reward_code = fields.CharField(max_length=255, null=True) @@ -50,7 +161,7 @@ class Meta: table = "reward_inventory" -class ItemInventoryRewardInventory(models.Model): +class ItemInventoryRewardInventory(Model): item_inventory_reward_invnetory_id = fields.BigIntField(primary_key=True) quantity = fields.IntField() item_inventory: ForeignKeyRelation[ItemInventory] = fields.ForeignKeyField( diff --git a/app/services/item_service.py b/app/services/item_service.py new file mode 100644 index 0000000..fade043 --- /dev/null +++ b/app/services/item_service.py @@ -0,0 +1,15 @@ +from app.models.item import ItemInventory, ItemInventoryProductInventory, ProductInventory + + +class ItemService: + @classmethod + async def create_item_inventory(cls) -> None: + await ItemInventory.create_bulk() + + @classmethod + async def create_product_inventory(cls) -> None: + await ProductInventory.create_bulk() + + @classmethod + async def create_link_item_product(cls) -> None: + await ItemInventoryProductInventory.create_bulk() diff --git a/app/services/payment_service.py b/app/services/payment_service.py index 445ed40..485889f 100644 --- a/app/services/payment_service.py +++ b/app/services/payment_service.py @@ -72,8 +72,6 @@ async def process_cheese_payment( elif item.item_category == ItemCategory.EMOTION: for _ in range(quantity): await Emotion.create_by_user_id(user_id=user_id, emotion_code=item.item_code) - else: - raise CustomException(ErrorCode.INVALID_ITEM_CATEGORY) return product_code except IntegrityError: diff --git a/app/tests/apis/v2/test_mission_router.py b/app/tests/apis/v2/test_mission_router.py new file mode 100644 index 0000000..d889cea --- /dev/null +++ b/app/tests/apis/v2/test_mission_router.py @@ -0,0 +1 @@ +# todo : mission router 테스트 코드 작성 diff --git a/app/tests/apis/v2/test_payment_router.py b/app/tests/apis/v2/test_payment_router.py new file mode 100644 index 0000000..9c3e172 --- /dev/null +++ b/app/tests/apis/v2/test_payment_router.py @@ -0,0 +1,256 @@ +# todo : payment router 테스트코드 작성 +import asyncio +from unittest.mock import patch + +from fastapi import status +from tortoise.exceptions import IntegrityError + +from app.common.constants.product_code_list import ProductCodeList +from app.common.exceptions.error_code import ErrorCode +from app.dtos.payment.payment_request import PaymentRequest +from app.tests.mothers.badge_mother import BadgeMother +from app.tests.mothers.color_mother import ColorMother +from app.tests.mothers.emotion_mother import EmotionMother +from app.tests.mothers.item_mother import ItemMother +from app.tests.mothers.user_mother import UserMother +from app.tests.telling_me_client import TellingMeClient + + +async def test_payment_happy_case(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: + # Given + user_mother = UserMother() + color_mother = ColorMother() + badge_mother = BadgeMother() + emotion_mother = EmotionMother() + item_mother = ItemMother() + + user_id = await user_mother.create_user(user_name="telling me user") + + await asyncio.gather( + user_mother.create_level_inventory(), + badge_mother.create_badge_inventory(), + color_mother.create_color_inventory(), + emotion_mother.create_emotion_inventory(), + item_mother.create_item_inventory_and_product_inventory(), + user_mother.add_cheese(user_id=user_id, amount=100), + ) + + payment_badge = PaymentRequest(user_id=user_id, productCode=ProductCodeList.PD_BG_CHRISTMAS_2024) + payment_color = PaymentRequest(user_id=user_id, productCode=ProductCodeList.PD_CL_GREEN_001) + payment_emotion = PaymentRequest(user_id=user_id, productCode=ProductCodeList.PD_EM_LONELY) + + # When + payment_badge_response, payment_color_response, payment_emotion_response = await asyncio.gather( + telling_me_client.payment_product(payment_request=payment_badge), + telling_me_client.payment_product(payment_request=payment_color), + telling_me_client.payment_product(payment_request=payment_emotion), + ) + + badge_response_code = payment_badge_response.json()["code"] + badge_response_data = payment_badge_response.json()["data"] + badge_response_message = payment_badge_response.json()["message"] + + color_response_code = payment_color_response.json()["code"] + color_response_data = payment_color_response.json()["data"] + color_response_message = payment_color_response.json()["message"] + + emotion_response_code = payment_emotion_response.json()["code"] + emotion_response_data = payment_emotion_response.json()["data"] + emotion_response_message = payment_emotion_response.json()["message"] + + # Then + assert payment_badge_response.status_code == status.HTTP_200_OK + assert payment_color_response.status_code == status.HTTP_200_OK + assert payment_emotion_response.status_code == status.HTTP_200_OK + + assert badge_response_code == payment_badge_response.status_code + assert color_response_code == payment_color_response.status_code + assert emotion_response_code == payment_emotion_response.status_code + + assert badge_response_message == "Payment successful" + assert color_response_message == "Payment successful" + assert emotion_response_message == "Payment successful" + + assert badge_response_data["product_code"] == ProductCodeList.PD_BG_CHRISTMAS_2024 + assert color_response_data["product_code"] == ProductCodeList.PD_CL_GREEN_001 + assert emotion_response_data["product_code"] == ProductCodeList.PD_EM_LONELY + + +async def test_duplicate_payment_case(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: + # Given + user_mother = UserMother() + color_mother = ColorMother() + badge_mother = BadgeMother() + emotion_mother = EmotionMother() + item_mother = ItemMother() + + user_id = await user_mother.create_user(user_name="telling me user") + + await asyncio.gather( + user_mother.create_level_inventory(), + badge_mother.create_badge_inventory(), + color_mother.create_color_inventory(), + emotion_mother.create_emotion_inventory(), + item_mother.create_item_inventory_and_product_inventory(), + user_mother.add_cheese(user_id=user_id, amount=100), + ) + + payment_request = PaymentRequest(user_id=user_id, productCode=ProductCodeList.PD_BG_CHRISTMAS_2024) + with patch("app.models.badge.Badge.create_by_user_id", side_effect=IntegrityError("mock integrity error")): + response = await telling_me_client.payment_product(payment_request=payment_request) + + code = response.json()["code"] + data = response.json()["data"] + message = response.json()["message"] + + # Then + assert response.status_code == status.HTTP_400_BAD_REQUEST + + assert code == ErrorCode.DUPLICATE_PURCHASE.code + assert message == ErrorCode.DUPLICATE_PURCHASE.message + assert data is None + + +async def test_payment_when_cheese_is_insufficient( + telling_me_client: TellingMeClient, init_tortoise_connection: None +) -> None: + # Given + user_mother = UserMother() + color_mother = ColorMother() + badge_mother = BadgeMother() + emotion_mother = EmotionMother() + item_mother = ItemMother() + + user_id = await user_mother.create_user(user_name="telling me user") + + await asyncio.gather( + user_mother.create_level_inventory(), + badge_mother.create_badge_inventory(), + color_mother.create_color_inventory(), + emotion_mother.create_emotion_inventory(), + item_mother.create_item_inventory_and_product_inventory(), + user_mother.add_cheese(user_id=user_id, amount=0), + ) + + payment_request = PaymentRequest(user_id=user_id, productCode=ProductCodeList.PD_BG_CHRISTMAS_2024) + response = await telling_me_client.payment_product(payment_request=payment_request) + + code = response.json()["code"] + data = response.json()["data"] + message = response.json()["message"] + + # Then + assert response.status_code == status.HTTP_400_BAD_REQUEST + + assert code == ErrorCode.NOT_ENOUGH_CHEESE.code + assert message == ErrorCode.NOT_ENOUGH_CHEESE.message + assert data is None + + +async def test_payment_when_not_cheese_payment( + telling_me_client: TellingMeClient, init_tortoise_connection: None +) -> None: + # Given + user_mother = UserMother() + color_mother = ColorMother() + badge_mother = BadgeMother() + emotion_mother = EmotionMother() + item_mother = ItemMother() + + user_id = await user_mother.create_user(user_name="telling me user") + + await asyncio.gather( + user_mother.create_level_inventory(), + badge_mother.create_badge_inventory(), + color_mother.create_color_inventory(), + emotion_mother.create_emotion_inventory(), + item_mother.create_item_inventory_and_product_inventory(), + user_mother.add_cheese(user_id=user_id, amount=100), + ) + + # 현금 구매 제품 + payment_request = PaymentRequest(user_id=user_id, productCode=ProductCodeList.PD_PLUS_MONTH_1_KR) + response = await telling_me_client.payment_product(payment_request=payment_request) + + code = response.json()["code"] + data = response.json()["data"] + message = response.json()["message"] + + # Then + assert response.status_code == status.HTTP_400_BAD_REQUEST + + assert code == ErrorCode.INVALID_TRANSACTION_CURRENCY.code + assert message == ErrorCode.INVALID_TRANSACTION_CURRENCY.message + assert data is None + + +async def test_payment_invalid_product_code(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: + # Given + user_mother = UserMother() + color_mother = ColorMother() + badge_mother = BadgeMother() + emotion_mother = EmotionMother() + item_mother = ItemMother() + + user_id = await user_mother.create_user(user_name="telling me user") + + await asyncio.gather( + user_mother.create_level_inventory(), + badge_mother.create_badge_inventory(), + color_mother.create_color_inventory(), + emotion_mother.create_emotion_inventory(), + item_mother.create_item_inventory_and_product_inventory(), + user_mother.add_cheese(user_id=user_id, amount=100), + ) + + # 현금 구매 제품 + payment_request = PaymentRequest(user_id=user_id, productCode="invalid_product_code") + response = await telling_me_client.payment_product(payment_request=payment_request) + + code = response.json()["code"] + data = response.json()["data"] + message = response.json()["message"] + + # Then + assert response.status_code == status.HTTP_404_NOT_FOUND + + assert code == ErrorCode.PRODUCT_NOT_FOUND.code + assert message == ErrorCode.PRODUCT_NOT_FOUND.message + assert data is None + + +async def test_payment_invalid_item_category( + telling_me_client: TellingMeClient, init_tortoise_connection: None +) -> None: + # Given + user_mother = UserMother() + color_mother = ColorMother() + badge_mother = BadgeMother() + emotion_mother = EmotionMother() + item_mother = ItemMother() + + user_id = await user_mother.create_user(user_name="telling me user") + + await asyncio.gather( + user_mother.create_level_inventory(), + badge_mother.create_badge_inventory(), + color_mother.create_color_inventory(), + emotion_mother.create_emotion_inventory(), + item_mother.create_item_inventory_and_product_inventory(), + user_mother.add_cheese(user_id=user_id, amount=100), + ) + + # 현금 구매 제품 + payment_request = PaymentRequest(user_id=user_id, productCode=ProductCodeList.PD_TEST) + response = await telling_me_client.payment_product(payment_request=payment_request) + + code = response.json()["code"] + data = response.json()["data"] + message = response.json()["message"] + + # Then + assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR + + assert code == ErrorCode.NO_INVENTORY_FOR_PRODUCT.code + assert message == ErrorCode.NO_INVENTORY_FOR_PRODUCT.message + assert data is None diff --git a/app/tests/apis/v2/test_teller_card_router.py b/app/tests/apis/v2/test_teller_card_router.py index dddbaf0..c344144 100644 --- a/app/tests/apis/v2/test_teller_card_router.py +++ b/app/tests/apis/v2/test_teller_card_router.py @@ -4,6 +4,7 @@ from app.common.constants.badge_code_list import BadgeCodeList from app.common.constants.color_code_list import ColorCodeList +from app.common.exceptions.error_code import ErrorCode from app.dtos.teller_card.teller_card_request import TellerCardRequest from app.tests.mothers.badge_mother import BadgeMother from app.tests.mothers.color_mother import ColorMother @@ -92,5 +93,5 @@ async def test_update_teller_card_with_invalid_code( assert invalid_badge_code_response.status_code == status.HTTP_400_BAD_REQUEST assert invalid_color_code_response.status_code == status.HTTP_400_BAD_REQUEST - assert invalid_badge_code_response.json()["message"] == "유효하지 않은 뱃지 코드입니다" - assert invalid_color_code_response.json()["message"] == "유효하지 않은 컬러 코드입니다" + assert invalid_badge_code_response.json()["message"] == ErrorCode.INVALID_BADGE_CODE.message + assert invalid_color_code_response.json()["message"] == ErrorCode.INVALID_COLOR_CODE.message diff --git a/app/tests/mothers/item_mother.py b/app/tests/mothers/item_mother.py new file mode 100644 index 0000000..76e5ce5 --- /dev/null +++ b/app/tests/mothers/item_mother.py @@ -0,0 +1,15 @@ +import asyncio + +from app.services.item_service import ItemService + + +class ItemMother: + + @staticmethod + async def create_item_inventory_and_product_inventory() -> None: + item_service = ItemService() + await asyncio.gather( + item_service.create_item_inventory(), + item_service.create_product_inventory(), + ) + await item_service.create_link_item_product() diff --git a/app/tests/telling_me_client.py b/app/tests/telling_me_client.py index 72fd41c..5767281 100644 --- a/app/tests/telling_me_client.py +++ b/app/tests/telling_me_client.py @@ -1,5 +1,6 @@ import httpx +from app.dtos.payment.payment_request import PaymentRequest from app.dtos.teller_card.teller_card_request import TellerCardRequest @@ -85,3 +86,6 @@ async def get_cheese_amount(self, user_id: str) -> httpx.Response: async def update_teller_card(self, teller_card_request: TellerCardRequest) -> httpx.Response: return await self._client.post("/api/v2/tellercard", json=teller_card_request.model_dump()) + + async def payment_product(self, payment_request: PaymentRequest) -> httpx.Response: + return await self._client.post("/api/v2/payment", json=payment_request.model_dump()) diff --git a/app/tests/utils/db_utils.py b/app/tests/utils/db_utils.py index 5fe9ce3..be5b9c5 100644 --- a/app/tests/utils/db_utils.py +++ b/app/tests/utils/db_utils.py @@ -1,6 +1,7 @@ from app.models.badge_inventory import BadgeInventory from app.models.color_inventory import ColorInventory from app.models.emotion_inventory import EmotionInventory +from app.models.item import ItemInventory, ItemInventoryProductInventory, ProductInventory from app.models.level_inventory import LevelInventory @@ -13,3 +14,7 @@ async def reset_inventory_tables() -> None: await ColorInventory.all().delete() await EmotionInventory.all().delete() await LevelInventory.all().delete() + + await ItemInventoryProductInventory.all().delete() + await ItemInventory.all().delete() + await ProductInventory.all().delete() From a7c068ac9556b6194c889ca5f29cb9669bd75a54 Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Fri, 13 Jun 2025 18:17:46 +0900 Subject: [PATCH 25/27] =?UTF-8?q?=E2=9C=85=20test:=20mission=20router?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/apis/v2/mission_router.py | 2 +- app/common/tasks/mission_task.py | 11 +- .../database/tortoise_database_settings.py | 1 + app/models/item.py | 65 ++++ app/models/mission_inventory.py | 57 ++++ app/models/notice.py | 9 +- app/services/answer_service.py | 2 + app/services/item_service.py | 16 +- app/services/level_service.py | 3 - app/services/mission_service.py | 38 +-- app/services/notice_service.py | 3 - app/tests/apis/v2/test_mission_router.py | 287 +++++++++++++++++- app/tests/apis/v2/test_payment_router.py | 13 +- app/tests/mothers/answer_mother.py | 3 +- app/tests/mothers/item_mother.py | 9 + app/tests/mothers/mission_mother.py | 14 + app/tests/telling_me_client.py | 12 + app/tests/utils/db_utils.py | 13 +- 18 files changed, 506 insertions(+), 52 deletions(-) create mode 100644 app/tests/mothers/mission_mother.py diff --git a/app/apis/v2/mission_router.py b/app/apis/v2/mission_router.py index db8465d..fb4648f 100644 --- a/app/apis/v2/mission_router.py +++ b/app/apis/v2/mission_router.py @@ -13,7 +13,7 @@ async def mission_handler(user_id: str) -> None: process_mission_in_background.delay(user_id) -@mission_router.get("/direct") +@mission_router.get("/check") async def mission_handler_direct( user_id: str, mission_service: MissionService = Depends(), diff --git a/app/common/tasks/mission_task.py b/app/common/tasks/mission_task.py index ea0efcd..a443ddb 100644 --- a/app/common/tasks/mission_task.py +++ b/app/common/tasks/mission_task.py @@ -1,11 +1,6 @@ -import asyncio - -from app.models.mission import UserMission +from app.services.mission_service import MissionService async def mission_reset_task() -> None: - await asyncio.gather( - UserMission.filter(mission_code__in=["MS_LV_UP", "MS_DAILY_LIKE_3_PER_DAY", "MS_DAILY_POST_GENERAL"]).update( - is_completed=False, progress_count=0 - ), - ) + mission_service = MissionService() + await mission_service.reset_mission() diff --git a/app/core/database/tortoise_database_settings.py b/app/core/database/tortoise_database_settings.py index e6bb75c..dca05a6 100644 --- a/app/core/database/tortoise_database_settings.py +++ b/app/core/database/tortoise_database_settings.py @@ -22,6 +22,7 @@ "app.models.mission", "app.models.mission_inventory", "app.models.like", + "app.models.notice", ] TORTOISE_ORM = { diff --git a/app/models/item.py b/app/models/item.py index 4bc68f6..0bb0f82 100644 --- a/app/models/item.py +++ b/app/models/item.py @@ -160,6 +160,36 @@ class RewardInventory(Model): class Meta: table = "reward_inventory" + @classmethod + async def create_bulk(cls) -> None: + rewards_data = [ + (1, "RW_LV_000", "레벨업 보상"), + (2, "RW_FIRST_POST", "첫 글 작성 보상"), + (3, "RW_LONG_POST", "280자 이상의 글을 작성 보상"), + (4, "RW_CONSECUTIVE_7", "연속 7일 글 작성 보상"), + (5, "RW_EARLY_MORNING", "오전 12시~6시에 3개의 글 작성 보상"), + (6, "RW_LIKE_3_DAY", "하루 좋아요 3개"), + (7, "RW_CHEESE_50", "누적 치즈 50개를 획득하세요"), + (8, "RW_REGISTRATION", "회원가입 보상"), + (9, "RW_CHRISTMAS", "크리스마스 시즌에 접속하여 뱃지를 받으세요"), + (10, "RW_POST_2_5", "글 작성 보상 2~5개"), + (11, "RW_POST_GENERAL", "글 작성 보상"), + ] + + async with in_transaction(): + await cls.bulk_create( + [ + cls( + reward_inventory_id=reward_id, + reward_code=code, + reward_description=desc, + reward_name=None, + item_code=None, + ) + for reward_id, code, desc in rewards_data + ] + ) + class ItemInventoryRewardInventory(Model): item_inventory_reward_invnetory_id = fields.BigIntField(primary_key=True) @@ -180,3 +210,38 @@ class ItemInventoryRewardInventory(Model): class Meta: table = "item_inventory_reward_inventory" + + @classmethod + async def create_bulk(cls) -> None: + data = [ + (1, 1, 39, 1, "UNIT"), + (2, 1, 39, 2, "UNIT"), + (3, 1, 3, 2, "UNIT"), + (4, 1, 4, 3, "UNIT"), + (5, 1, 1, 4, "UNIT"), + (6, 1, 6, 5, "UNIT"), + (7, 1, 40, 6, "UNIT"), + (8, 1, 7, 7, "UNIT"), + (9, 1, 5, 8, "UNIT"), + (10, 1, 2, 9, "UNIT"), + (11, 1, 23, 9, "UNIT"), + (12, 5, 40, 10, "UNIT"), + (13, 5, 39, 3, "UNIT"), + (14, 5, 39, 4, "UNIT"), + (15, 5, 39, 5, "UNIT"), + (16, 10, 39, 7, "UNIT"), + ] + + async with in_transaction(): + await cls.bulk_create( + [ + cls( + item_inventory_reward_invnetory_id=pk, + quantity=qty, + item_inventory_id=item_id, + reward_inventory_id=reward_id, + item_measurement=measure, + ) + for pk, qty, item_id, reward_id, measure in data + ] + ) diff --git a/app/models/mission_inventory.py b/app/models/mission_inventory.py index 7e1c21a..ea32c6e 100644 --- a/app/models/mission_inventory.py +++ b/app/models/mission_inventory.py @@ -1,4 +1,5 @@ from tortoise import Model, fields +from tortoise.transactions import in_transaction class MissionInventory(Model): @@ -12,3 +13,59 @@ class MissionInventory(Model): class Meta: table = "mission_inventory" + + @classmethod + async def create_bulk(cls) -> None: + data = [ + (1, "하", "MS_LV_UP", "XP를 누적하여 다음 레벨에 도달했어요!", "레벨 업 달성", "RW_LV_000", 1), + (2, "하", "MS_BADGE_POST_FIRST", "첫 글을 첫 글을 작성했어요!", "첫 글 작성", "RW_FIRST_POST", 1), + (3, "하", "MS_BADGE_POST_280_CHAR", "280자 이상 글을 1회 작성했어요!", "긴 글 작성", "RW_LONG_POST", 1), + ( + 4, + "하", + "MS_BADGE_POST_CONSECUTIVE_7", + "연속으로 7일 작성했어요!", + "연속 7일 글 작성", + "RW_CONSECUTIVE_7", + 1, + ), + ( + 5, + "하", + "MS_BADGE_POST_EARLY_3", + "새벽 시간에 글 3회 작성했어요!", + "이른 아침 작가", + "RW_EARLY_MORNING", + 3, + ), + ( + 6, + "하", + "MS_DAILY_LIKE_3_PER_DAY", + "하루에 다른 글 3개에 좋아요를 누르세요", + "하루 좋아요 3개", + "RW_LIKE_3_DAY", + 1, + ), + (7, "중", "MS_BADGE_CHEESE_TOTAL_50", "치즈 50개를 모았어요!", "치즈 수집가", "RW_CHEESE_50", 1), + (8, "하", "MS_BADGE_REGISTRATION", "회원가입 보상이에요!", "환영 뱃지", "RW_REGISTRATION", 1), + (9, "하", "MS_BADGE_CHRISTMAS", "2024 크리스마스 한정판 배지예요!", "크리스마스 뱃지", "RW_CHRISTMAS", 1), + (10, "하", "MS_SINGLE_POST_2_5", "2~5회의 기록을 작성했어요!", "2~5 글 작성", "RW_POST_2_5", 1), + (11, "하", "MS_DAILY_POST_GENERAL", "기록을 작성했어요!", "일반 작성 보상", "RW_POST_GENERAL", 1), + ] + + async with in_transaction(): + await cls.bulk_create( + [ + cls( + mission_inventory_id=mid, + condition_type=ctype, + mission_code=mcode, + mission_description=desc, + mission_name=name, + reward_code=rcode, + target_count=target, + ) + for mid, ctype, mcode, desc, name, rcode, target in data + ] + ) diff --git a/app/models/notice.py b/app/models/notice.py index 7fac677..7be0a48 100644 --- a/app/models/notice.py +++ b/app/models/notice.py @@ -8,15 +8,16 @@ class Notice(Model): notice_id = fields.BigIntField(primary_key=True) - title = fields.CharField(max_length=255, null=False) + answer_id = fields.BigIntField(null=True) content = fields.TextField(null=True) - is_read = fields.BooleanField(default=False) created_at = fields.DatetimeField(auto_now_add=True) - link = fields.CharField(max_length=255, null=True) is_internal = fields.BooleanField(default=False) - answer_id = fields.BigIntField(null=True) + is_read = fields.BooleanField(default=False) + link = fields.CharField(max_length=255, null=True) + title = fields.CharField(max_length=255, null=False) user_id = fields.BinaryField(max_length=16, null=True) date = fields.DateField(null=True) + badge_code = fields.CharField(max_length=255, null=True) reward_type = fields.CharField(max_length=255, null=True) class Meta: diff --git a/app/services/answer_service.py b/app/services/answer_service.py index 9bcb983..d73adab 100644 --- a/app/services/answer_service.py +++ b/app/services/answer_service.py @@ -12,11 +12,13 @@ async def create_answer( user_id: str, content: str, date: str, + like_count: int = 0, ) -> None: await Answer.create_answer( user_id=user_id, content=content, date=date, + like_count=like_count, ) @classmethod diff --git a/app/services/item_service.py b/app/services/item_service.py index fade043..b06523d 100644 --- a/app/services/item_service.py +++ b/app/services/item_service.py @@ -1,4 +1,10 @@ -from app.models.item import ItemInventory, ItemInventoryProductInventory, ProductInventory +from app.models.item import ( + ItemInventory, + ItemInventoryProductInventory, + ItemInventoryRewardInventory, + ProductInventory, + RewardInventory, +) class ItemService: @@ -13,3 +19,11 @@ async def create_product_inventory(cls) -> None: @classmethod async def create_link_item_product(cls) -> None: await ItemInventoryProductInventory.create_bulk() + + @classmethod + async def create_reward_inventory(cls) -> None: + await RewardInventory.create_bulk() + + @classmethod + async def create_link_item_reward(cls) -> None: + await ItemInventoryRewardInventory.create_bulk() diff --git a/app/services/level_service.py b/app/services/level_service.py index 0580a40..26357ba 100644 --- a/app/services/level_service.py +++ b/app/services/level_service.py @@ -39,9 +39,6 @@ async def level_up(cls, user_id: str) -> int: current_exp = level_dto.currentExp required_exp = level_dto.requiredExp - if current_exp is None or required_exp is None: - raise ValueError("Experience values cannot be None") - if current_exp >= required_exp: new_exp = current_exp - required_exp new_level = level + 1 diff --git a/app/services/mission_service.py b/app/services/mission_service.py index 1c0ba9c..0ffedc3 100644 --- a/app/services/mission_service.py +++ b/app/services/mission_service.py @@ -27,6 +27,17 @@ class MissionService: + + @staticmethod + async def create_mission_inventory() -> None: + await MissionInventory.create_bulk() + + @staticmethod + async def reset_mission() -> None: + await UserMission.filter( + mission_code__in=["MS_LV_UP", "MS_DAILY_LIKE_3_PER_DAY", "MS_DAILY_POST_GENERAL"] + ).update(is_completed=False, progress_count=0) + @staticmethod async def get_user_missions(user_id: str) -> list[UserMissionDTO]: user_missions = await UserMission.get_user_missions_by_condition_type(user_id) @@ -123,12 +134,6 @@ async def _handle_mission_reward( await self.reward_badge_mission( user_id=user_id, cheese_manager_id=cheese_manager_id, reward_code=reward_code ) - else: - await self.reward_mission( - user_id=user_id, - cheese_manager_id=cheese_manager_id, - reward_code=reward_code, - ) async def evaluate_mission_condition(self, user_id: str, mission_code: str) -> int: if mission_code == MS.BADGE_POST_FIRST and await self.check_first_post(user_id): @@ -185,7 +190,7 @@ async def check_consecutive_days(user_id: str) -> bool: @staticmethod async def check_early_morning_posts(user_id: str) -> bool: recent_answer = await Answer.get_most_recent_answer_by_user_id(user_id=user_id) - return 0 <= recent_answer.created_time.hour <= 5 if recent_answer else False + return 0 <= recent_answer.created_time.hour <= 6 if recent_answer else False @staticmethod async def check_cheese_total(user_id: str) -> bool: @@ -257,11 +262,6 @@ async def process_reward( elif item.item_category == ItemCategory.CHEESE: total_cheese += quantity await CheeseManager.add_cheese(cheese_manager_id=cheese_manager_id, amount=quantity) - elif item.item_category == ItemCategory.POINT: - total_exp += quantity - await LevelService.add_exp(user_id=user_id, exp=quantity) - else: - raise ValueError(f"Invalid item category for reward: {item.item_category}") badge_full_name = badge_info[0].badge_full_name if badge_info else None badge_code = badge_info[0].badge_code if badge_info else None @@ -399,17 +399,3 @@ async def reward_badge_mission(self, user_id: str, cheese_manager_id: int, rewar badge_code=reward_dto.badge_code, badge_full_name=reward_dto.badge_full_name, ) - - async def reward_mission(self, user_id: str, cheese_manager_id: int, reward_code: str) -> None: - item_inventory_rewards = await self.validate_reward(reward_code=reward_code) - reward_dto = await self.process_reward( - item_inventory_rewards=item_inventory_rewards, - user_id=user_id, - cheese_manager_id=cheese_manager_id, - ) - await self._create_reward_notice( - user_id=user_id, - reward_type=RewardType.DAILY_MISSION, - total_exp=reward_dto.total_exp, - total_cheese=reward_dto.total_cheese, - ) diff --git a/app/services/notice_service.py b/app/services/notice_service.py index df65264..f244f2a 100644 --- a/app/services/notice_service.py +++ b/app/services/notice_service.py @@ -35,9 +35,6 @@ async def create_reward_notice( nickname: str | None = None, new_level: int | None = None, ) -> None: - if not badge_code and not level_up and total_cheese == 0 and total_exp == 0: - return - # 1. 제목 생성 title = cls.create_title( badge_full_name=badge_full_name, diff --git a/app/tests/apis/v2/test_mission_router.py b/app/tests/apis/v2/test_mission_router.py index d889cea..8b3a342 100644 --- a/app/tests/apis/v2/test_mission_router.py +++ b/app/tests/apis/v2/test_mission_router.py @@ -1 +1,286 @@ -# todo : mission router 테스트 코드 작성 +import asyncio +from datetime import datetime + +import time_machine + +from app.common.constants.badge_code_list import BadgeCodeList +from app.common.constants.color_code_list import ColorCodeList +from app.core.configs import settings +from app.tests.mothers.answer_mother import AnswerMother +from app.tests.mothers.badge_mother import BadgeMother +from app.tests.mothers.color_mother import ColorMother +from app.tests.mothers.emotion_mother import EmotionMother +from app.tests.mothers.item_mother import ItemMother +from app.tests.mothers.mission_mother import MissionMother +from app.tests.mothers.user_mother import UserMother +from app.tests.telling_me_client import TellingMeClient + + +async def test_mission_check_first_post(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: + # Given + user_mother = UserMother() + answer_mother = AnswerMother() + color_mother = ColorMother() + badge_mother = BadgeMother() + emotion_mother = EmotionMother() + item_mother = ItemMother() + mission_mother = MissionMother() + + user_id = await user_mother.create_user(user_name="telling me user") + + await asyncio.gather( + user_mother.create_level_inventory(), + badge_mother.create_badge_inventory(), + color_mother.create_color_inventory(), + emotion_mother.create_emotion_inventory(), + mission_mother.create_mission_inventory(), + item_mother.create_item_inventory_and_reward_inventory(), + user_mother.add_cheese(user_id=user_id, amount=0), + answer_mother.create_answer(user_id=user_id, content="-", date="2025-06-13"), + ) + + # When + with time_machine.travel(datetime(2025, 6, 13, 10, 0, 0, tzinfo=settings.db_zoneinfo), tick=False): + await telling_me_client.check_mission(user_id=user_id) + + response = await telling_me_client.get_mobile_teller_card(user_id=user_id) + badge_codes = [badge["badgeCode"] for badge in response.json()["data"]["badges"]] + level_info = response.json()["data"]["levelInfo"]["levelDto"] + + # Then 첫 글 작성 시 Badge code First + 경험치 11 ( 첫 글 작성 10 + 기본 1 ) + assert BadgeCodeList.FIRST in badge_codes + assert level_info["currentExp"] == 11 + + +async def test_mission_check_long_post(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: + # Given + user_mother = UserMother() + answer_mother = AnswerMother() + color_mother = ColorMother() + badge_mother = BadgeMother() + emotion_mother = EmotionMother() + item_mother = ItemMother() + mission_mother = MissionMother() + + user_id = await user_mother.create_user(user_name="telling me user") + + await asyncio.gather( + user_mother.create_level_inventory(), + badge_mother.create_badge_inventory(), + color_mother.create_color_inventory(), + emotion_mother.create_emotion_inventory(), + mission_mother.create_mission_inventory(), + item_mother.create_item_inventory_and_reward_inventory(), + user_mother.add_cheese(user_id=user_id, amount=0), + answer_mother.create_answer( + user_id=user_id, content="이것은 텔러가 작성한 280자 이상의 긴 답변입니다. " * 10, date="2025-06-13" + ), + ) + + # When + with time_machine.travel(datetime(2025, 6, 13, 10, 0, 0, tzinfo=settings.db_zoneinfo), tick=False): + await telling_me_client.check_mission(user_id=user_id) + + response = await telling_me_client.get_mobile_teller_card(user_id=user_id) + badge_codes = [badge["badgeCode"] for badge in response.json()["data"]["badges"]] + + # Then 280자 이상 글 작성 시 뱃지 지급 + assert BadgeCodeList.MUCH_001 in badge_codes + + +async def test_mission_check_consecutive_7_days( + telling_me_client: TellingMeClient, init_tortoise_connection: None +) -> None: + # Given + user_mother = UserMother() + answer_mother = AnswerMother() + color_mother = ColorMother() + badge_mother = BadgeMother() + emotion_mother = EmotionMother() + item_mother = ItemMother() + mission_mother = MissionMother() + + user_id = await user_mother.create_user(user_name="telling me user") + + await asyncio.gather( + user_mother.create_level_inventory(), + badge_mother.create_badge_inventory(), + color_mother.create_color_inventory(), + emotion_mother.create_emotion_inventory(), + mission_mother.create_mission_inventory(), + item_mother.create_item_inventory_and_reward_inventory(), + user_mother.add_cheese(user_id=user_id, amount=0), + answer_mother.create_answer(user_id=user_id, content="-", date="2025-06-07"), + answer_mother.create_answer(user_id=user_id, content="-", date="2025-06-08"), + answer_mother.create_answer(user_id=user_id, content="-", date="2025-06-09"), + answer_mother.create_answer(user_id=user_id, content="-", date="2025-06-10"), + answer_mother.create_answer(user_id=user_id, content="-", date="2025-06-11"), + answer_mother.create_answer(user_id=user_id, content="-", date="2025-06-12"), + answer_mother.create_answer(user_id=user_id, content="-", date="2025-06-13"), + ) + + # When + with time_machine.travel(datetime(2025, 6, 13, 10, 0, 0, tzinfo=settings.db_zoneinfo), tick=False): + await telling_me_client.check_mission(user_id=user_id) + + response = await telling_me_client.get_mobile_teller_card(user_id=user_id) + badge_codes = [badge["badgeCode"] for badge in response.json()["data"]["badges"]] + + # Then 연속 7일 글 작성 시 Badge code AGAIN + assert BadgeCodeList.AGAIN_001 in badge_codes + + +async def test_mission_check_cheese_50(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: + # Given + user_mother = UserMother() + color_mother = ColorMother() + badge_mother = BadgeMother() + emotion_mother = EmotionMother() + item_mother = ItemMother() + mission_mother = MissionMother() + + user_id = await user_mother.create_user(user_name="telling me user") + + await asyncio.gather( + user_mother.create_level_inventory(), + badge_mother.create_badge_inventory(), + color_mother.create_color_inventory(), + emotion_mother.create_emotion_inventory(), + mission_mother.create_mission_inventory(), + item_mother.create_item_inventory_and_reward_inventory(), + user_mother.add_cheese(user_id=user_id, amount=50), + ) + + # When + with time_machine.travel(datetime(2025, 6, 13, 10, 0, 0, tzinfo=settings.db_zoneinfo), tick=False): + await telling_me_client.check_mission(user_id=user_id) + + response = await telling_me_client.get_mobile_teller_card(user_id=user_id) + badge_codes = [badge["badgeCode"] for badge in response.json()["data"]["badges"]] + + # Then 총 치즈 누적량 50 이상 + assert BadgeCodeList.SAVE_001 in badge_codes + + +async def test_mission_christmas(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: + # Given + user_mother = UserMother() + color_mother = ColorMother() + badge_mother = BadgeMother() + emotion_mother = EmotionMother() + item_mother = ItemMother() + mission_mother = MissionMother() + + with time_machine.travel(datetime(2024, 12, 25, 10, 0, 0, tzinfo=settings.db_zoneinfo), tick=False): + user_id = await user_mother.create_user(user_name="telling me user") + + await asyncio.gather( + user_mother.create_level_inventory(), + badge_mother.create_badge_inventory(), + color_mother.create_color_inventory(), + emotion_mother.create_emotion_inventory(), + mission_mother.create_mission_inventory(), + item_mother.create_item_inventory_and_reward_inventory(), + user_mother.add_cheese(user_id=user_id, amount=50), + ) + + # When + with time_machine.travel(datetime(2024, 12, 25, 10, 0, 0, tzinfo=settings.db_zoneinfo), tick=False): + await telling_me_client.check_mission(user_id=user_id) + + response = await telling_me_client.get_mobile_teller_card(user_id=user_id) + badge_codes = [badge["badgeCode"] for badge in response.json()["data"]["badges"]] + color_codes = [badge["colorCode"] for badge in response.json()["data"]["colors"]] + + # Then 2024년 크리스마스 기간에 접속 했을 시 뱃지 및 색상 지급 + assert BadgeCodeList.CHRISTMAS_2024 in badge_codes + assert ColorCodeList.CL_RED_001 in color_codes + + +async def test_mission_post_2_5(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: + # Given + user_mother = UserMother() + answer_mother = AnswerMother() + color_mother = ColorMother() + badge_mother = BadgeMother() + emotion_mother = EmotionMother() + item_mother = ItemMother() + mission_mother = MissionMother() + + user_id = await user_mother.create_user(user_name="telling me user") + + await asyncio.gather( + user_mother.create_level_inventory(), + badge_mother.create_badge_inventory(), + color_mother.create_color_inventory(), + emotion_mother.create_emotion_inventory(), + mission_mother.create_mission_inventory(), + item_mother.create_item_inventory_and_reward_inventory(), + user_mother.add_cheese(user_id=user_id, amount=50), + ) + + # When + with time_machine.travel(datetime(2025, 6, 11, 10, 0, 0, tzinfo=settings.db_zoneinfo), tick=False): + await answer_mother.create_answer(user_id=user_id, content="-", date="2025-06-11") + await telling_me_client.check_mission(user_id=user_id) + await mission_mother.reset_mission() + + with time_machine.travel(datetime(2025, 6, 12, 10, 0, 0, tzinfo=settings.db_zoneinfo), tick=False): + await answer_mother.create_answer(user_id=user_id, content="-", date="2025-06-12") + await telling_me_client.check_mission(user_id=user_id) + await mission_mother.reset_mission() + + with time_machine.travel(datetime(2025, 6, 13, 10, 0, 0, tzinfo=settings.db_zoneinfo), tick=False): + await answer_mother.create_answer(user_id=user_id, content="-", date="2025-06-13") + await telling_me_client.check_mission(user_id=user_id) + await mission_mother.reset_mission() + + response = await telling_me_client.get_mobile_teller_card(user_id=user_id) + level_info = response.json()["data"]["levelInfo"]["levelDto"] + + # Then 첫 날 경험치 11 ( 첫 글 작성 10 + 기본 1 ) + 둘쨰날 경험치 7 ( 2~5 작성 시 5pt + 연속 작성 2pt ) + 셋째날 경험치 8 ( 2~5 작성 시 5pt + 연속 작성 3pt ) + assert level_info["level"] == 2 + assert level_info["currentExp"] == 11 + + +async def test_mission_level_up(telling_me_client: TellingMeClient, init_tortoise_connection: None) -> None: + # Given + user_mother = UserMother() + answer_mother = AnswerMother() + color_mother = ColorMother() + badge_mother = BadgeMother() + emotion_mother = EmotionMother() + item_mother = ItemMother() + mission_mother = MissionMother() + + user_id = await user_mother.create_user(user_name="telling me user") + + await asyncio.gather( + user_mother.create_level_inventory(), + badge_mother.create_badge_inventory(), + color_mother.create_color_inventory(), + emotion_mother.create_emotion_inventory(), + mission_mother.create_mission_inventory(), + item_mother.create_item_inventory_and_reward_inventory(), + user_mother.add_cheese(user_id=user_id, amount=0), + ) + + # When + with time_machine.travel(datetime(2025, 6, 12, 10, 0, 0, tzinfo=settings.db_zoneinfo), tick=False): + await answer_mother.create_answer(user_id=user_id, content="-", date="2025-06-12") + await telling_me_client.check_mission(user_id=user_id) + await mission_mother.reset_mission() + + with time_machine.travel(datetime(2025, 6, 13, 10, 0, 0, tzinfo=settings.db_zoneinfo), tick=False): + await answer_mother.create_answer(user_id=user_id, content="-", date="2025-06-13") + await telling_me_client.check_mission(user_id=user_id) + await mission_mother.reset_mission() + + response = await telling_me_client.get_mobile_teller_card(user_id=user_id) + cheese_balance = response.json()["data"]["userInfo"]["cheeseBalance"] + level_info = response.json()["data"]["levelInfo"]["levelDto"] + + # Then 첫 날 경험치 11 ( 첫 글 작성 10 + 기본 1 ) + 둘쨰날 경험치 7 ( 2~5 작성 시 5pt + 연속 작성 2pt ) + assert level_info["level"] == 2 + assert level_info["currentExp"] == 3 + assert cheese_balance == 1 + 2 # 첫 글 보상 치즈 10 + 레벨업 보상 치즈 1 + 연속 작성 2일 보상 치즈 2개 diff --git a/app/tests/apis/v2/test_payment_router.py b/app/tests/apis/v2/test_payment_router.py index 9c3e172..692d29a 100644 --- a/app/tests/apis/v2/test_payment_router.py +++ b/app/tests/apis/v2/test_payment_router.py @@ -1,4 +1,3 @@ -# todo : payment router 테스트코드 작성 import asyncio from unittest.mock import patch @@ -96,6 +95,8 @@ async def test_duplicate_payment_case(telling_me_client: TellingMeClient, init_t ) payment_request = PaymentRequest(user_id=user_id, productCode=ProductCodeList.PD_BG_CHRISTMAS_2024) + + # When with patch("app.models.badge.Badge.create_by_user_id", side_effect=IntegrityError("mock integrity error")): response = await telling_me_client.payment_product(payment_request=payment_request) @@ -133,6 +134,8 @@ async def test_payment_when_cheese_is_insufficient( ) payment_request = PaymentRequest(user_id=user_id, productCode=ProductCodeList.PD_BG_CHRISTMAS_2024) + + # When response = await telling_me_client.payment_product(payment_request=payment_request) code = response.json()["code"] @@ -170,6 +173,8 @@ async def test_payment_when_not_cheese_payment( # 현금 구매 제품 payment_request = PaymentRequest(user_id=user_id, productCode=ProductCodeList.PD_PLUS_MONTH_1_KR) + + # When response = await telling_me_client.payment_product(payment_request=payment_request) code = response.json()["code"] @@ -203,8 +208,9 @@ async def test_payment_invalid_product_code(telling_me_client: TellingMeClient, user_mother.add_cheese(user_id=user_id, amount=100), ) - # 현금 구매 제품 payment_request = PaymentRequest(user_id=user_id, productCode="invalid_product_code") + + # When response = await telling_me_client.payment_product(payment_request=payment_request) code = response.json()["code"] @@ -240,8 +246,9 @@ async def test_payment_invalid_item_category( user_mother.add_cheese(user_id=user_id, amount=100), ) - # 현금 구매 제품 payment_request = PaymentRequest(user_id=user_id, productCode=ProductCodeList.PD_TEST) + + # When response = await telling_me_client.payment_product(payment_request=payment_request) code = response.json()["code"] diff --git a/app/tests/mothers/answer_mother.py b/app/tests/mothers/answer_mother.py index bfd9c30..5bcff63 100644 --- a/app/tests/mothers/answer_mother.py +++ b/app/tests/mothers/answer_mother.py @@ -8,6 +8,7 @@ async def create_answer( user_id: str, date: str, content: str = "-", + likes: int = 0, ) -> None: answer_service = AnswerService() - await answer_service.create_answer(user_id=user_id, content=content, date=date) + await answer_service.create_answer(user_id=user_id, content=content, date=date, like_count=likes) diff --git a/app/tests/mothers/item_mother.py b/app/tests/mothers/item_mother.py index 76e5ce5..23cfd91 100644 --- a/app/tests/mothers/item_mother.py +++ b/app/tests/mothers/item_mother.py @@ -13,3 +13,12 @@ async def create_item_inventory_and_product_inventory() -> None: item_service.create_product_inventory(), ) await item_service.create_link_item_product() + + @staticmethod + async def create_item_inventory_and_reward_inventory() -> None: + item_service = ItemService() + await asyncio.gather( + item_service.create_item_inventory(), + item_service.create_reward_inventory(), + ) + await item_service.create_link_item_reward() diff --git a/app/tests/mothers/mission_mother.py b/app/tests/mothers/mission_mother.py new file mode 100644 index 0000000..63bff79 --- /dev/null +++ b/app/tests/mothers/mission_mother.py @@ -0,0 +1,14 @@ +from app.services.mission_service import MissionService + + +class MissionMother: + + @staticmethod + async def create_mission_inventory() -> None: + mission_service = MissionService() + await mission_service.create_mission_inventory() + + @staticmethod + async def reset_mission() -> None: + mission_service = MissionService() + await mission_service.reset_mission() diff --git a/app/tests/telling_me_client.py b/app/tests/telling_me_client.py index 5767281..d4d1763 100644 --- a/app/tests/telling_me_client.py +++ b/app/tests/telling_me_client.py @@ -84,6 +84,18 @@ async def get_cheese_amount(self, user_id: str) -> httpx.Response: }, ) + async def check_mission(self, user_id: str) -> httpx.Response: + return await self._client.get( + "/api/v2/mission/check", + params={ + key: value + for key, value in { + "user_id": user_id, + }.items() + if value is not None + }, + ) + async def update_teller_card(self, teller_card_request: TellerCardRequest) -> httpx.Response: return await self._client.post("/api/v2/tellercard", json=teller_card_request.model_dump()) diff --git a/app/tests/utils/db_utils.py b/app/tests/utils/db_utils.py index be5b9c5..4e73c63 100644 --- a/app/tests/utils/db_utils.py +++ b/app/tests/utils/db_utils.py @@ -1,8 +1,15 @@ from app.models.badge_inventory import BadgeInventory from app.models.color_inventory import ColorInventory from app.models.emotion_inventory import EmotionInventory -from app.models.item import ItemInventory, ItemInventoryProductInventory, ProductInventory +from app.models.item import ( + ItemInventory, + ItemInventoryProductInventory, + ItemInventoryRewardInventory, + ProductInventory, + RewardInventory, +) from app.models.level_inventory import LevelInventory +from app.models.mission_inventory import MissionInventory async def reset_inventory_tables() -> None: @@ -14,7 +21,11 @@ async def reset_inventory_tables() -> None: await ColorInventory.all().delete() await EmotionInventory.all().delete() await LevelInventory.all().delete() + await MissionInventory.all().delete() await ItemInventoryProductInventory.all().delete() + await ItemInventoryRewardInventory.all().delete() + await ItemInventory.all().delete() await ProductInventory.all().delete() + await RewardInventory.all().delete() From 3cbe9198fa43a43778fb192b46b095e971a4ee5f Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Fri, 13 Jun 2025 18:27:31 +0900 Subject: [PATCH 26/27] =?UTF-8?q?=F0=9F=92=A1=20chore:=20CI=20add=20redis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/checks.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 7b80496..0b48ca4 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -57,12 +57,24 @@ jobs: test: # 전체 테스트 실행한다. runs-on: ubuntu-22.04 + + services: + redis: + image: redis:7.2-alpine + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval 5s + --health-timeout 5s + --health-retries 5 env: MYSQL_HOST: 127.0.0.1 MYSQL_PORT: 3306 MYSQL_USER: root MYSQL_PASSWORD: 1234 MYSQL_DATABASE: db_telling_me_dev + REDIS_HOST: localhost steps: - name: Check out the codes @@ -134,4 +146,6 @@ jobs: # # staging 서버의 .bashrc 에 gunicorn_reload 가 정의되어 있습니다. gunicorn master 에게 HUP 를 줘서 worker 를 재시작합니다. # run: | # echo "$PRIVATE_KEY" > private_key && chmod 600 private_key -# ssh -o StrictHostKeyChecking=no -t -i private_key ${USER_NAME}@${HOSTNAME} "bash -i -c 'gunicorn_reload'" \ No newline at end of file +# ssh -o StrictHostKeyChecking=no -t -i private_key ${USER_NAME}@${HOSTNAME} "bash -i -c 'gunicorn_reload'" + +# todo : CD 작성하기 \ No newline at end of file From 1f80dd2b8b6ae620868c2215778ffd1177a5725f Mon Sep 17 00:00:00 2001 From: taewoo-dev Date: Fri, 13 Jun 2025 18:32:20 +0900 Subject: [PATCH 27/27] =?UTF-8?q?=F0=9F=92=A1=20chore:=20CI=20fix=20env=20?= =?UTF-8?q?settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/checks.yml | 4 ++-- app/core/configs/base_settings.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 0b48ca4..f804cf3 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -72,8 +72,8 @@ jobs: MYSQL_HOST: 127.0.0.1 MYSQL_PORT: 3306 MYSQL_USER: root - MYSQL_PASSWORD: 1234 - MYSQL_DATABASE: db_telling_me_dev + MYSQL_PASSWORD: password + MYSQL_DATABASE: tellingme_local REDIS_HOST: localhost steps: diff --git a/app/core/configs/base_settings.py b/app/core/configs/base_settings.py index 3189aa8..aa97238 100644 --- a/app/core/configs/base_settings.py +++ b/app/core/configs/base_settings.py @@ -14,7 +14,7 @@ class Env(StrEnum): class Settings(BaseSettings): ENV: Env = Env.LOCAL DB_HOST: str = "localhost" - DB_PORT: int = 3307 + DB_PORT: int = 3306 DB_USER: str = "root" DB_PASSWORD: str = "password" DB_NAME: str = "tellingme_local"