From 8861fcb1cecd10cf695d1f8ac94a87f93e458214 Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Fri, 21 Mar 2025 16:14:43 +0900 Subject: [PATCH 01/26] =?UTF-8?q?Chore:=20Add=20dnspython=20=EB=B0=8F=20py?= =?UTF-8?q?mongo=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poetry.lock | 103 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 81b7933..fc71d4c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -114,6 +114,27 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "dnspython" +version = "2.7.0" +description = "DNS toolkit" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, + {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, +] + +[package.extras] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.16.0)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "quart-trio (>=0.11.0)", "sphinx (>=7.2.0)", "sphinx-rtd-theme (>=2.0.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=43)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] +doq = ["aioquic (>=1.0.0)"] +idna = ["idna (>=3.7)"] +trio = ["trio (>=0.23)"] +wmi = ["wmi (>=1.5.1)"] + [[package]] name = "dotenv" version = "0.9.9" @@ -409,6 +430,86 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pymongo" +version = "4.11.3" +description = "Python driver for MongoDB " +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pymongo-4.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:78f19598246dd61ba2a4fc4dddfa6a4f9af704fff7d81cb4fe0d02c7b17b1f68"}, + {file = "pymongo-4.11.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c9cbe81184ec81ad8c76ccedbf5b743639448008d68f51f9a3c8a9abe6d9a46"}, + {file = "pymongo-4.11.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9047ecb3bc47c43ada7d6f98baf8060c637b1e880c803a2bbd1dc63b49d2f92"}, + {file = "pymongo-4.11.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1a16ec731b42f6b2b4f1aa3a94e74ff2722aacf691922a2e8e607b7f6b8d9f1"}, + {file = "pymongo-4.11.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9120e25ac468fda3e3a1749695e0c5e52ff2294334fcc81e70ccb65c897bb58"}, + {file = "pymongo-4.11.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f618bd6ed5c3c08b350b157b1d9066d3d389785b7359d2b7b7d82ca4083595d3"}, + {file = "pymongo-4.11.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:98017f006e047f5ed6c99c2cb1cac71534f0e11862beeff4d0bc9227189bedcd"}, + {file = "pymongo-4.11.3-cp310-cp310-win32.whl", hash = "sha256:84b9300ed411fef776c60feab40f3ee03db5d0ac8921285c6e03a3e27efa2c20"}, + {file = "pymongo-4.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:07231d0bac54e32503507777719dd05ca63bc68896e64ea852edde2f1986b868"}, + {file = "pymongo-4.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31b5ad4ce148b201fa8426d0767517dc68424c3380ef4a981038d4d4350f10ee"}, + {file = "pymongo-4.11.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:505fb3facf54623b45c96e8e6ad6516f58bb8069f9456e1d7c0abdfdb6929c21"}, + {file = "pymongo-4.11.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3f20467d695f49ce4c2d6cb87de458ebb3d098cbc951834a74f36a2e992a6bb"}, + {file = "pymongo-4.11.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65e8a397b03156880a099d55067daa1580a5333aaf4da3b0313bd7e1731e408f"}, + {file = "pymongo-4.11.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0992917ed259f5ca3506ec8009e7c82d398737a4230a607bf44d102cae31e1d6"}, + {file = "pymongo-4.11.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f2f0c3ab8284e0e2674367fa47774411212c86482bbbe78e8ae9fb223b8f6ee"}, + {file = "pymongo-4.11.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2240126683f55160f83f587d76955ad1e419a72d5c09539a509bd9d1e20bd53"}, + {file = "pymongo-4.11.3-cp311-cp311-win32.whl", hash = "sha256:be89776c5b8272437a85c904d45e0f1bbc0f21bf11688341938380843dd7fe5f"}, + {file = "pymongo-4.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:c237780760f891cae79abbfc52fda55b584492d5d9452762040aadb2c64ac691"}, + {file = "pymongo-4.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5f48b7faf4064e5f484989608a59503b11b7f134ca344635e416b1b12e7dc255"}, + {file = "pymongo-4.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:722f22bf18d208aa752591bde93e018065641711594e7a2fef0432da429264e8"}, + {file = "pymongo-4.11.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5be1b35c4897626327c4e8bae14655807c2bc710504fa790bc19a72403142264"}, + {file = "pymongo-4.11.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14f9e4d2172545798738d27bc6293b972c4f1f98cce248aa56e1e62c4c258ca7"}, + {file = "pymongo-4.11.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd3f7bafe441135f58d2b91a312714f423e15fed5afe3854880c8c61ad78d3ce"}, + {file = "pymongo-4.11.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73de1b9f416a2662ba95b4b49edc963d47b93760a7e2b561b932c8099d160151"}, + {file = "pymongo-4.11.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e24268e2d7ae96eab12161985b39e75a75185393134fc671f4bb1a16f50bf6f4"}, + {file = "pymongo-4.11.3-cp312-cp312-win32.whl", hash = "sha256:33a936d3c1828e4f52bed3dad6191a3618cc28ab056e2770390aec88d9e9f9ea"}, + {file = "pymongo-4.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:c4673d8ef0c8ef712491a750adf64f7998202a82abd72be5be749749275b3edb"}, + {file = "pymongo-4.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5e53b98c9700bb69f33a322b648d028bfe223ad135fb04ec48c0226998b80d0e"}, + {file = "pymongo-4.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8464aff011208cf86eae28f4a3624ebc4a40783634e119b2b35852252b901ef3"}, + {file = "pymongo-4.11.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3742ffc1951bec1450a5a6a02cfd40ddd4b1c9416b36c70ae439a532e8be0e05"}, + {file = "pymongo-4.11.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a29294b508975a5dfd384f4b902cd121dc2b6e5d55ea2be2debffd2a63461cd9"}, + {file = "pymongo-4.11.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:051c741586ab6efafe72e027504ac4e5f01c88eceec579e4e1a438a369a61b0c"}, + {file = "pymongo-4.11.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b05e03a327cdef28ec2bb72c974d412d308f5cf867a472ef17f9ac95d18ec05"}, + {file = "pymongo-4.11.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dafeddf1db51df19effd0828ae75492b15d60c7faec388da08f1fe9593c88e7a"}, + {file = "pymongo-4.11.3-cp313-cp313-win32.whl", hash = "sha256:40c55afb34788ae6a6b8c175421fa46a37cfc45de41fe4669d762c3b1bbda48e"}, + {file = "pymongo-4.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:a5b8b7ba9614a081d1f932724b7a6a20847f6c9629420ae81ce827db3b599af2"}, + {file = "pymongo-4.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0f23f849693e829655f667ea18b87bf34e1395237eb45084f3495317d455beb2"}, + {file = "pymongo-4.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:62bcfa88deb4a6152a7c93bedd1a808497f6c2881424ca54c3c81964a51c5040"}, + {file = "pymongo-4.11.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2eaa0233858f72074bf0319f5034018092b43f19202bd7ecb822980c35bfd623"}, + {file = "pymongo-4.11.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a434e081017be360595237cd1aeac3d047dd38e8785c549be80748608c1d4ca"}, + {file = "pymongo-4.11.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e8aa65a9e4a989245198c249816d86cb240221861b748db92b8b3a5356bd6f1"}, + {file = "pymongo-4.11.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0a91004029d1fc9e66a800e6da4170afaa9b93bcf41299e4b5951b837b3467a"}, + {file = "pymongo-4.11.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b992904ac78cb712b42c4b7348974ba1739137c1692cdf8bf75c3eeb22881a4"}, + {file = "pymongo-4.11.3-cp313-cp313t-win32.whl", hash = "sha256:45e18bda802d95a2aed88e487f06becc3bd0b22286a25aeca8c46b8c64980dbb"}, + {file = "pymongo-4.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:07d40b831590bc458b624f421849c2b09ad2b9110b956f658b583fe01fe01c01"}, + {file = "pymongo-4.11.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4a1c241d8424c0e5d66a1710ff2b691f361b5fd354754a086ddea99ee19cc2d3"}, + {file = "pymongo-4.11.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b1aaccbcb4a5aaaa3acaabc59b30edd047c38c6cdfc97eb64e0611b6882a6d6"}, + {file = "pymongo-4.11.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be60f63a310d0d2824e9fb2ef0f821bb45d23e73446af6d50bddda32564f285d"}, + {file = "pymongo-4.11.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1b943d1b13f1232cb92762c82a5154f02b01234db8d632ea9525ab042bd7619"}, + {file = "pymongo-4.11.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afc7d1d2bd1997bb42fdba8a5a104198e4ff7990f096ac90353dcb87c69bb57f"}, + {file = "pymongo-4.11.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:730fe9a6c432669fa69af0905a7a4835e5a3752363b2ae3b34007919003394cd"}, + {file = "pymongo-4.11.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0633536b31980a8af7262edb03a20df88d8aa0ad803e48c49609b6408a33486d"}, + {file = "pymongo-4.11.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e88e99f33a89e8f58f7401201e79e29f98b2da21d4082ba50eeae0828bb35451"}, + {file = "pymongo-4.11.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a30f1b9bf79f53f995198ed42bc9b675fc38e6ec30d8f6f7e53094085b5eb803"}, + {file = "pymongo-4.11.3-cp39-cp39-win32.whl", hash = "sha256:e1872a33f1d4266c14fae1dc4744b955d0ef5d6fad87cc72141d04d8c97245dc"}, + {file = "pymongo-4.11.3-cp39-cp39-win_amd64.whl", hash = "sha256:a19f186455e4b3af1e11ee877346418d18303800ecc688ef732b5725c2795f13"}, + {file = "pymongo-4.11.3.tar.gz", hash = "sha256:b6f24aec7c0cfcf0ea9f89e92b7d40ba18a1e18c134815758f111ecb0122e61c"}, +] + +[package.dependencies] +dnspython = ">=1.16.0,<3.0.0" + +[package.extras] +aws = ["pymongo-auth-aws (>=1.1.0,<2.0.0)"] +docs = ["furo (==2024.8.6)", "readthedocs-sphinx-search (>=0.3,<1.0)", "sphinx (>=5.3,<9)", "sphinx-autobuild (>=2020.9.1)", "sphinx-rtd-theme (>=2,<4)", "sphinxcontrib-shellcheck (>=1,<2)"] +encryption = ["certifi", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.12.0,<2.0.0)"] +gssapi = ["pykerberos", "winkerberos (>=0.5.0)"] +ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] +snappy = ["python-snappy"] +test = ["pytest (>=8.2)", "pytest-asyncio (>=0.24.0)"] +zstd = ["zstandard"] + [[package]] name = "pytest" version = "8.3.5" @@ -572,4 +673,4 @@ standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", [metadata] lock-version = "2.1" python-versions = "^3.12" -content-hash = "09f6a01e0688827869ed10b5aaea9352c8d02c71c404ad67a24b3aad9502087b" +content-hash = "d74e83d5f195da6bfc3f4673248a32382a4db74d881df9c782e0d9f0d7481f09" diff --git a/pyproject.toml b/pyproject.toml index f90231e..a4f2b7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ boto3 = "^1.37.16" botocore = "^1.37.16" dotenv = "^0.9.9" httpx = "^0.28.1" +pymongo = "^4.11.3" [build-system] requires = ["poetry-core"] From f8837129e460f7de190757d8eb80c3194eccf4ab Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Fri, 21 Mar 2025 16:17:47 +0900 Subject: [PATCH 02/26] =?UTF-8?q?Feat:=20MongoDB=20=ED=81=B4=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EC=96=B8=ED=8A=B8=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/config/__init__.py | 0 src/main/config/mongodb.py | 16 ++++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 src/main/config/__init__.py create mode 100644 src/main/config/mongodb.py diff --git a/src/main/config/__init__.py b/src/main/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/main/config/mongodb.py b/src/main/config/mongodb.py new file mode 100644 index 0000000..097829a --- /dev/null +++ b/src/main/config/mongodb.py @@ -0,0 +1,16 @@ +import os +from dotenv import load_dotenv +from pymongo import MongoClient + +load_dotenv() + +MONGO_URI = os.getenv("MONGODB_URI") +MONGO_PORT = os.getenv("MONGODB_PORT") +MONGO_DB = os.getenv("MONGODB_DB") +MONGO_USER = os.getenv("MONGODB_USER") +MONGO_PASSWORD = os.getenv("MONGODB_PASSWORD") + + +def get_mongo_client(): + client = MongoClient("mongodb://" + MONGO_USER + ":" + MONGO_PASSWORD + "@" + MONGO_URI + ":" + MONGO_PORT + "/" + MONGO_DB + "?retryWrites=true") + return client From 1c6fd827f852e9713378ef5050bdc8bc83b11cba Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Fri, 21 Mar 2025 22:20:57 +0900 Subject: [PATCH 03/26] =?UTF-8?q?Chore:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=EC=A1=B0=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/ai/__init__.py | 0 src/main/ai/data/__init__.py | 0 src/main/ai/di/__init__.py | 0 src/main/ai/models/__init__.py | 1 + src/main/ai/router/__init__.py | 0 src/main/ai/service/__init__.py | 0 src/main/auth/__init__.py | 0 7 files changed, 1 insertion(+) create mode 100644 src/main/ai/__init__.py create mode 100644 src/main/ai/data/__init__.py create mode 100644 src/main/ai/di/__init__.py create mode 100644 src/main/ai/models/__init__.py create mode 100644 src/main/ai/router/__init__.py create mode 100644 src/main/ai/service/__init__.py create mode 100644 src/main/auth/__init__.py diff --git a/src/main/ai/__init__.py b/src/main/ai/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/main/ai/data/__init__.py b/src/main/ai/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/main/ai/di/__init__.py b/src/main/ai/di/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/main/ai/models/__init__.py b/src/main/ai/models/__init__.py new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/src/main/ai/models/__init__.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/ai/router/__init__.py b/src/main/ai/router/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/main/ai/service/__init__.py b/src/main/ai/service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/main/auth/__init__.py b/src/main/auth/__init__.py new file mode 100644 index 0000000..e69de29 From 27eb83995fca0e40362b8d4abce86ff7228e44e5 Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Fri, 21 Mar 2025 22:21:29 +0900 Subject: [PATCH 04/26] Refactor: Move to `src/main/auth` --- src/{ => main}/auth/dependencies.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{ => main}/auth/dependencies.py (100%) diff --git a/src/auth/dependencies.py b/src/main/auth/dependencies.py similarity index 100% rename from src/auth/dependencies.py rename to src/main/auth/dependencies.py From 8473b62472007201510d194ab185cfd0533a188b Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Fri, 21 Mar 2025 22:21:35 +0900 Subject: [PATCH 05/26] =?UTF-8?q?Chore:=20.gitignore=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EC=97=90=20.doc/=20=EB=B0=8F=20.idea/=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c571e8b..b6eff0b 100644 --- a/.gitignore +++ b/.gitignore @@ -173,4 +173,6 @@ cython_debug/ # PyPI configuration file .pypirc -.DS_Store \ No newline at end of file +.DS_Store +.doc/ +.idea/ \ No newline at end of file From 994af44632561bb5de5d0a8a602a2c437e43245b Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Sat, 22 Mar 2025 00:36:31 +0900 Subject: [PATCH 06/26] =?UTF-8?q?Fix:=20MongoDB=20=ED=81=B4=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EC=96=B8=ED=8A=B8=20URI=EB=A5=BC=20SRV=20=ED=98=95?= =?UTF-8?q?=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/config/mongodb.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/config/mongodb.py b/src/main/config/mongodb.py index 097829a..f998808 100644 --- a/src/main/config/mongodb.py +++ b/src/main/config/mongodb.py @@ -5,12 +5,11 @@ load_dotenv() MONGO_URI = os.getenv("MONGODB_URI") -MONGO_PORT = os.getenv("MONGODB_PORT") MONGO_DB = os.getenv("MONGODB_DB") MONGO_USER = os.getenv("MONGODB_USER") MONGO_PASSWORD = os.getenv("MONGODB_PASSWORD") def get_mongo_client(): - client = MongoClient("mongodb://" + MONGO_USER + ":" + MONGO_PASSWORD + "@" + MONGO_URI + ":" + MONGO_PORT + "/" + MONGO_DB + "?retryWrites=true") + client = MongoClient("mongodb+srv://" + MONGO_USER + ":" + MONGO_PASSWORD + "@" + MONGO_URI + "/" + MONGO_DB + "?retryWrites=true") return client From bd4140086c7b2612747882b233a82dd0a538c3fb Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Sat, 22 Mar 2025 00:36:41 +0900 Subject: [PATCH 07/26] =?UTF-8?q?Feat:=20=EC=9A=94=EC=B2=AD=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=ED=95=B8=EB=93=A4=EB=9F=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/app.py b/src/app.py index db07f36..63ffeb1 100644 --- a/src/app.py +++ b/src/app.py @@ -1,5 +1,7 @@ -from fastapi import FastAPI +from fastapi import FastAPI, Request, status from fastapi.middleware.cors import CORSMiddleware +from fastapi.exceptions import RequestValidationError +from fastapi.responses import JSONResponse from dotenv import load_dotenv from src.router import router @@ -26,4 +28,17 @@ allow_headers=["*"], ) + +@app.exception_handler(RequestValidationError) +async def validation_exception_handler(request: Request, exc: RequestValidationError): + return JSONResponse( + status_code=status.HTTP_400_BAD_REQUEST, + content={ + "status": 400, + "message": "잘못된 요청입니다.", + "detail": "명세에 맞지 않은 요청입니다." + } + ) + + app.include_router(router) \ No newline at end of file From e3e683f5adcc8c1581bbb63e1c5412be3238a961 Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Sat, 22 Mar 2025 00:36:55 +0900 Subject: [PATCH 08/26] =?UTF-8?q?Feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EC=B2=9C=20=ED=81=90=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20SQS=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EC=A0=84=EC=86=A1=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/data/CategoryRecommendationQueue.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/main/ai/data/CategoryRecommendationQueue.py diff --git a/src/main/ai/data/CategoryRecommendationQueue.py b/src/main/ai/data/CategoryRecommendationQueue.py new file mode 100644 index 0000000..5db9dcd --- /dev/null +++ b/src/main/ai/data/CategoryRecommendationQueue.py @@ -0,0 +1,35 @@ +import os +import json +import boto3 +from dotenv import load_dotenv + +load_dotenv() + + +class CategoryRecommendationQueue: + def __init__(self, sqs_client: boto3.client, queue_url: str): + self.sqs = sqs_client + self.queue_url = queue_url + + def send_message(self, request_id: str, title: str, user_id: str): + try: + message_body = { + 'request_type': 'category_recommendation', + 'request_id': request_id, + 'user_id': str(user_id), + 'payload': { + 'title': title + } + } + + response = self.sqs.send_message( + QueueUrl=self.queue_url, + MessageGroupId=str(user_id), + MessageDeduplicationId=str(request_id), + MessageBody=json.dumps(message_body) + ) + + return response + except Exception as e: + print(f"Error sending message to SQS: {e}") + raise \ No newline at end of file From dfdca2765cc640e6d0973f1fb7501396ba1fee14 Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Sat, 22 Mar 2025 00:37:00 +0900 Subject: [PATCH 09/26] =?UTF-8?q?Feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EC=B2=9C=20=EC=9A=94=EC=B2=AD=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EB=A6=AC=ED=8F=AC=EC=A7=80=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/CategoryRecommendationRepository.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/main/ai/data/CategoryRecommendationRepository.py diff --git a/src/main/ai/data/CategoryRecommendationRepository.py b/src/main/ai/data/CategoryRecommendationRepository.py new file mode 100644 index 0000000..1813f95 --- /dev/null +++ b/src/main/ai/data/CategoryRecommendationRepository.py @@ -0,0 +1,56 @@ +from typing import Optional +from pymongo.collection import Collection +from pymongo import MongoClient +from bson import ObjectId +from datetime import datetime, timezone + + +class CategoryRecommendationRepository: + def __init__(self, client: MongoClient): + self.db = client.get_database() + self.collection: Collection = self.db.get_collection('category_recommendations') + + def create_recommendation_request(self, title: str, user_id: str) -> dict: + document = { + "title": title, + "user_id": user_id, + "is_completed": False, + "created_at": self.get_current_time() + } + result = self.collection.insert_one(document) + document["_id"] = result.inserted_id + return document + + def get_recommendation_by_id(self, request_id: str, user_id: str) -> Optional[dict]: + try: + object_id = ObjectId(request_id) + return self.collection.find_one({ + "_id": object_id, + "user_id": user_id + }) + except: + return None + + def update_recommendation_result(self, request_id: str, predicted_category: str) -> Optional[dict]: + try: + object_id = ObjectId(request_id) + result = self.collection.update_one( + {"_id": object_id}, + { + "$set": { + "is_completed": True, + "predicted_category": predicted_category, + "updated_at": self.get_current_time() + } + } + ) + + if result.modified_count == 0: + return None + + return self.collection.find_one({"_id": object_id}) + except: + return None + + def get_current_time(self): + return datetime.now(timezone.utc) \ No newline at end of file From 9525c460e6bd679d55207ecf652ff3a82a1a66b0 Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Sat, 22 Mar 2025 00:37:08 +0900 Subject: [PATCH 10/26] =?UTF-8?q?Feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EC=B2=9C=20=EB=AA=A8=EB=8D=B8=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/ai/models/CategoryRecommendation.py | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/main/ai/models/CategoryRecommendation.py diff --git a/src/main/ai/models/CategoryRecommendation.py b/src/main/ai/models/CategoryRecommendation.py new file mode 100644 index 0000000..06b8c91 --- /dev/null +++ b/src/main/ai/models/CategoryRecommendation.py @@ -0,0 +1,21 @@ +from pydantic import BaseModel, Field +import uuid +from typing import Optional + + +class CategoryRecommendationRequest(BaseModel): + title: str + + +class CategoryRecommendationResponse(BaseModel): + request_id: str = Field(default_factory=lambda: str(uuid.uuid4())) + + +class CategoryRecommendationStatusResponse(BaseModel): + request_id: str + is_completed: bool + predicted_category: Optional[str] = None + + +class CategoryRecommendationResultRequest(BaseModel): + predicted_category: str \ No newline at end of file From 6d81417a322b6614542d3eb13495cff25dc22924 Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Sat, 22 Mar 2025 00:37:16 +0900 Subject: [PATCH 11/26] =?UTF-8?q?Feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EC=B2=9C=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=EC=B6=94=EC=B2=9C=20=EC=9A=94=EC=B2=AD=20=EC=83=9D=EC=84=B1,?= =?UTF-8?q?=20=EC=83=81=ED=83=9C=20=EC=A1=B0=ED=9A=8C,=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CategoryRecommendationService.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/main/ai/service/CategoryRecommendationService.py diff --git a/src/main/ai/service/CategoryRecommendationService.py b/src/main/ai/service/CategoryRecommendationService.py new file mode 100644 index 0000000..9638859 --- /dev/null +++ b/src/main/ai/service/CategoryRecommendationService.py @@ -0,0 +1,64 @@ +from typing import Optional +import uuid +from bson import ObjectId + +from src.main.ai.data.CategoryRecommendationRepository import CategoryRecommendationRepository +from src.main.ai.data.CategoryRecommendationQueue import CategoryRecommendationQueue +from src.main.ai.models.CategoryRecommendation import ( + CategoryRecommendationRequest, + CategoryRecommendationResponse, + CategoryRecommendationStatusResponse, + CategoryRecommendationResultRequest +) + + +class CategoryRecommendationService: + def __init__(self, repository: CategoryRecommendationRepository, queue: CategoryRecommendationQueue): + self.repository = repository + self.queue = queue + + def create_recommendation_request(self, request: CategoryRecommendationRequest, user_id: uuid.UUID) -> CategoryRecommendationResponse: + # MongoDB에 저장 - ObjectId 자동 생성 + document = self.repository.create_recommendation_request( + title=request.title, + user_id=str(user_id) + ) + + # request_id는 MongoDB의 _id를 문자열로 변환 + request_id = str(document["_id"]) + + # 메시지 발행 + self.queue.send_message( + request_id=request_id, + title=request.title, + user_id=str(user_id) + ) + + return CategoryRecommendationResponse(request_id=request_id) + + def get_recommendation_status(self, request_id: str, user_id: uuid.UUID) -> Optional[CategoryRecommendationStatusResponse]: + result = self.repository.get_recommendation_by_id(request_id, str(user_id)) + + if not result: + return None + + return CategoryRecommendationStatusResponse( + request_id=str(result["_id"]), + is_completed=result["is_completed"], + predicted_category=result.get("predicted_category") + ) + + def update_recommendation_result(self, request_id: str, result: CategoryRecommendationResultRequest) -> Optional[CategoryRecommendationStatusResponse]: + updated = self.repository.update_recommendation_result( + request_id=request_id, + predicted_category=result.predicted_category + ) + + if not updated: + return None + + return CategoryRecommendationStatusResponse( + request_id=str(updated["_id"]), + is_completed=updated["is_completed"], + predicted_category=updated.get("predicted_category") + ) \ No newline at end of file From fbe93e27a9d147c93cc92ec41a0a8a11ffde4216 Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Sat, 22 Mar 2025 00:37:22 +0900 Subject: [PATCH 12/26] =?UTF-8?q?Feat:=20AI=20=EB=82=B4=EB=B6=80=20API=20?= =?UTF-8?q?=EB=9D=BC=EC=9A=B0=ED=84=B0=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EC=B6=94=EC=B2=9C=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/ai/router/AIInternalAPIRouter.py | 36 +++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/main/ai/router/AIInternalAPIRouter.py diff --git a/src/main/ai/router/AIInternalAPIRouter.py b/src/main/ai/router/AIInternalAPIRouter.py new file mode 100644 index 0000000..1c1d078 --- /dev/null +++ b/src/main/ai/router/AIInternalAPIRouter.py @@ -0,0 +1,36 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from fastapi.responses import JSONResponse + +from src.main.ai.di.dependencies import get_category_recommendation_service +from src.main.ai.models.CategoryRecommendation import CategoryRecommendationResultRequest, CategoryRecommendationStatusResponse +from src.main.ai.service.CategoryRecommendationService import CategoryRecommendationService + + +router = APIRouter( + prefix="/ai-proxy", + tags=["AI", "Internal"] +) + + +@router.post("/category-recommendations-results/{request_id}", response_model=CategoryRecommendationStatusResponse) +async def update_category_recommendation_result( + request_id: str, + request: CategoryRecommendationResultRequest, + service: CategoryRecommendationService = Depends(get_category_recommendation_service) +): + """ + (내부용) 자료 제목에 따른 추천 카테고리 조회 결과 저장 + """ + result = service.update_recommendation_result(request_id, request) + + if not result: + return JSONResponse( + status_code=status.HTTP_404_NOT_FOUND, + content={ + "status": 404, + "message": "요청을 찾을 수 없습니다.", + "detail": "존재하지 않는 ID입니다." + } + ) + + return result From efe0f618a851a0730a31499096ccc331dcbfad3e Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Sat, 22 Mar 2025 00:37:26 +0900 Subject: [PATCH 13/26] =?UTF-8?q?Feat:=20AI=20=EA=B3=B5=EA=B0=9C=20API=20?= =?UTF-8?q?=EB=9D=BC=EC=9A=B0=ED=84=B0=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EC=B6=94=EC=B2=9C=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EB=B0=8F=20=EC=83=81=ED=83=9C=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/ai/router/AIPublicAPIRouter.py | 45 +++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/main/ai/router/AIPublicAPIRouter.py diff --git a/src/main/ai/router/AIPublicAPIRouter.py b/src/main/ai/router/AIPublicAPIRouter.py new file mode 100644 index 0000000..7c4cdf4 --- /dev/null +++ b/src/main/ai/router/AIPublicAPIRouter.py @@ -0,0 +1,45 @@ +from fastapi import APIRouter, Depends, HTTPException, status +import uuid + +from src.main.auth.dependencies import get_current_user +from src.main.ai.di.dependencies import get_category_recommendation_service +from src.main.ai.models.CategoryRecommendation import CategoryRecommendationRequest, CategoryRecommendationResponse, CategoryRecommendationStatusResponse +from src.main.ai.service.CategoryRecommendationService import CategoryRecommendationService + + +router = APIRouter( + prefix="/ai", + tags=["AI", "Public"] +) + + +@router.post("/category-recommendations", response_model=CategoryRecommendationResponse) +async def create_category_recommendation_request( + request: CategoryRecommendationRequest, + user_id: uuid.UUID = Depends(get_current_user), + service: CategoryRecommendationService = Depends(get_category_recommendation_service) +): + """ + 자료 제목에 따른 추천 카테고리 요청 + """ + return service.create_recommendation_request(request, user_id) + + +@router.get("/category-recommendations/{request_id}", response_model=CategoryRecommendationStatusResponse) +async def get_category_recommendation_status( + request_id: str, + user_id: uuid.UUID = Depends(get_current_user), + service: CategoryRecommendationService = Depends(get_category_recommendation_service) +): + """ + 자료 제목에 따른 추천 카테고리 조회 + """ + result = service.get_recommendation_status(request_id, user_id) + + if not result: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="요청을 찾을 수 없습니다. 존재하지 않는 ID입니다." + ) + + return result From 655cec7dfb0ec2f6b7bb0083dab67d75d5bf5b62 Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Sat, 22 Mar 2025 00:37:35 +0900 Subject: [PATCH 14/26] =?UTF-8?q?Feat:=20AI=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=EB=9D=BC=EC=9A=B0=ED=84=B0=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/ai/di/dependencies.py | 33 +++++++++++++++++++++++++++++++++ src/router.py | 6 +++++- 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 src/main/ai/di/dependencies.py diff --git a/src/main/ai/di/dependencies.py b/src/main/ai/di/dependencies.py new file mode 100644 index 0000000..b6a9c67 --- /dev/null +++ b/src/main/ai/di/dependencies.py @@ -0,0 +1,33 @@ +import os +import boto3 +from dotenv import load_dotenv + +from src.main.ai.data.CategoryRecommendationRepository import CategoryRecommendationRepository +from src.main.ai.service.CategoryRecommendationService import CategoryRecommendationService +from src.main.ai.data.CategoryRecommendationQueue import CategoryRecommendationQueue +from src.main.config.mongodb import get_mongo_client + +load_dotenv() + + +def get_category_recommendation_repository(): + client = get_mongo_client() + return CategoryRecommendationRepository(client) + + +def get_category_recommendation_queue(): + if os.getenv('ENV') == 'local': + aws_profile = os.getenv('AWS_PROFILE', 'default') + session = boto3.Session(profile_name=aws_profile) + sqs_client = session.client('sqs') + else: + sqs_client = boto3.client('sqs', region_name=os.getenv('AWS_REGION')) + + queue_url = os.getenv('SQS_REQUEST_QUEUE_URL') + return CategoryRecommendationQueue(sqs_client, queue_url) + + +def get_category_recommendation_service(): + repository = get_category_recommendation_repository() + queue = get_category_recommendation_queue() + return CategoryRecommendationService(repository, queue) diff --git a/src/router.py b/src/router.py index 9172beb..8985743 100644 --- a/src/router.py +++ b/src/router.py @@ -1,10 +1,14 @@ from fastapi import APIRouter from src.main.health.router import HealthAPIRouter +from src.main.ai.router.AIPublicAPIRouter import router as ai_public_router +from src.main.ai.router.AIInternalAPIRouter import router as ai_internal_router router = APIRouter( prefix="", ) -router.include_router(HealthAPIRouter.router) \ No newline at end of file +router.include_router(HealthAPIRouter.router) +router.include_router(ai_public_router) +router.include_router(ai_internal_router) \ No newline at end of file From 7db4223d8f5502fece1cb42e8983c05354c02856 Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Sat, 22 Mar 2025 00:41:24 +0900 Subject: [PATCH 15/26] =?UTF-8?q?Fix:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EC=B2=9C=20=EA=B2=B0=EA=B3=BC=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EC=97=94=EB=93=9C=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=EC=9D=98=20=EA=B2=BD=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/ai/router/AIInternalAPIRouter.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/ai/router/AIInternalAPIRouter.py b/src/main/ai/router/AIInternalAPIRouter.py index 1c1d078..59f7578 100644 --- a/src/main/ai/router/AIInternalAPIRouter.py +++ b/src/main/ai/router/AIInternalAPIRouter.py @@ -12,15 +12,12 @@ ) -@router.post("/category-recommendations-results/{request_id}", response_model=CategoryRecommendationStatusResponse) +@router.post("/category-recommendation-results/{request_id}", response_model=CategoryRecommendationStatusResponse) async def update_category_recommendation_result( request_id: str, request: CategoryRecommendationResultRequest, service: CategoryRecommendationService = Depends(get_category_recommendation_service) ): - """ - (내부용) 자료 제목에 따른 추천 카테고리 조회 결과 저장 - """ result = service.update_recommendation_result(request_id, request) if not result: From f7fc56085d94b37ecb1d96472fb64b7272160cbc Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Sat, 22 Mar 2025 01:19:44 +0900 Subject: [PATCH 16/26] =?UTF-8?q?Test:=20AI=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EC=B4=88=EA=B8=B0=ED=99=94=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tests/ai/__init__.py | 0 src/tests/ai/data/__init__.py | 0 src/tests/ai/models/__init__.py | 0 src/tests/ai/router/__init__.py | 0 src/tests/ai/service/__init__.py | 0 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/tests/ai/__init__.py create mode 100644 src/tests/ai/data/__init__.py create mode 100644 src/tests/ai/models/__init__.py create mode 100644 src/tests/ai/router/__init__.py create mode 100644 src/tests/ai/service/__init__.py diff --git a/src/tests/ai/__init__.py b/src/tests/ai/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/ai/data/__init__.py b/src/tests/ai/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/ai/models/__init__.py b/src/tests/ai/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/ai/router/__init__.py b/src/tests/ai/router/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/ai/service/__init__.py b/src/tests/ai/service/__init__.py new file mode 100644 index 0000000..e69de29 From 973bc26127c1b0f4bd189517944ba3444193966c Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Sat, 22 Mar 2025 01:19:52 +0900 Subject: [PATCH 17/26] =?UTF-8?q?Test:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EC=B2=9C=20=ED=81=90=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EC=9D=98=20SQS=20=EB=A9=94=EC=8B=9C=EC=A7=80=20?= =?UTF-8?q?=EC=A0=84=EC=86=A1=20=EA=B8=B0=EB=8A=A5=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test_category_recommendation_queue.py | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/tests/ai/data/test_category_recommendation_queue.py diff --git a/src/tests/ai/data/test_category_recommendation_queue.py b/src/tests/ai/data/test_category_recommendation_queue.py new file mode 100644 index 0000000..a6dafef --- /dev/null +++ b/src/tests/ai/data/test_category_recommendation_queue.py @@ -0,0 +1,76 @@ +import pytest +import json +from unittest.mock import MagicMock, patch +from src.main.ai.data.CategoryRecommendationQueue import CategoryRecommendationQueue + + +class TestCategoryRecommendationQueue: + def setup_method(self): + # 목업 SQS 클라이언트 생성 + self.mock_sqs_client = MagicMock() + self.queue_url = "https://example.com/queue" + + # 테스트 대상 큐 생성 + self.queue = CategoryRecommendationQueue(self.mock_sqs_client, self.queue_url) + + def test_send_message_success(self): + # given + request_id = "test-request-id" + title = "테스트 제목" + user_id = "test-user-id" + + # SQS 응답 설정 + expected_response = {"MessageId": "1234567890"} + self.mock_sqs_client.send_message.return_value = expected_response + + # when + result = self.queue.send_message(request_id, title, user_id) + + # then + expected_message_body = { + 'request_type': 'category_recommendation', + 'request_id': request_id, + 'user_id': user_id, + 'payload': { + 'title': title + } + } + + self.mock_sqs_client.send_message.assert_called_once_with( + QueueUrl=self.queue_url, + MessageGroupId=user_id, + MessageDeduplicationId=request_id, + MessageBody=json.dumps(expected_message_body) + ) + assert result == expected_response + + def test_send_message_with_exception(self): + # given + request_id = "test-request-id" + title = "테스트 제목" + user_id = "test-user-id" + + # SQS 예외 발생 설정 + self.mock_sqs_client.send_message.side_effect = Exception("SQS error") + + # when/then + with pytest.raises(Exception) as e: + self.queue.send_message(request_id, title, user_id) + + assert "SQS error" in str(e.value) + + expected_message_body = { + 'request_type': 'category_recommendation', + 'request_id': request_id, + 'user_id': user_id, + 'payload': { + 'title': title + } + } + + self.mock_sqs_client.send_message.assert_called_once_with( + QueueUrl=self.queue_url, + MessageGroupId=user_id, + MessageDeduplicationId=request_id, + MessageBody=json.dumps(expected_message_body) + ) \ No newline at end of file From 6fc069aabd6b27d50c950eb56aceac3b9092548d Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Sat, 22 Mar 2025 01:19:56 +0900 Subject: [PATCH 18/26] =?UTF-8?q?Test:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EC=B2=9C=20=EB=A6=AC=ED=8F=AC=EC=A7=80?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=97=90=20=EB=8C=80=ED=95=9C=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...test_category_recommendation_repository.py | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 src/tests/ai/data/test_category_recommendation_repository.py diff --git a/src/tests/ai/data/test_category_recommendation_repository.py b/src/tests/ai/data/test_category_recommendation_repository.py new file mode 100644 index 0000000..651b0a3 --- /dev/null +++ b/src/tests/ai/data/test_category_recommendation_repository.py @@ -0,0 +1,158 @@ +import pytest +from unittest.mock import MagicMock, patch, ANY +from bson import ObjectId +from datetime import datetime, timezone +from src.main.ai.data.CategoryRecommendationRepository import CategoryRecommendationRepository + + +class TestCategoryRecommendationRepository: + def setup_method(self): + # 목업 MongoDB 클라이언트 생성 + self.mock_client = MagicMock() + self.mock_db = MagicMock() + self.mock_collection = MagicMock() + + # 클라이언트에서 DB와 컬렉션 반환하도록 설정 + self.mock_client.get_database.return_value = self.mock_db + self.mock_db.get_collection.return_value = self.mock_collection + + # 테스트 대상 리포지토리 생성 + self.repository = CategoryRecommendationRepository(self.mock_client) + + # 현재 시간을 일정하게 만들기 위한 패치 + self.time_patcher = patch.object( + self.repository, 'get_current_time', + return_value=datetime(2023, 1, 1, tzinfo=timezone.utc) + ) + self.mock_time = self.time_patcher.start() + + def teardown_method(self): + # 패치 제거 + self.time_patcher.stop() + + def test_create_recommendation_request(self): + # given + title = "테스트 제목" + user_id = "test-user-id" + mock_id = ObjectId("6123456789abcdef01234567") + + # MongoDB insert_one의 응답 설정 + self.mock_collection.insert_one.return_value = MagicMock(inserted_id=mock_id) + + # when + result = self.repository.create_recommendation_request(title, user_id) + + # then + expected_doc = { + "title": title, + "user_id": user_id, + "is_completed": False, + "created_at": datetime(2023, 1, 1, tzinfo=timezone.utc), + "_id": mock_id + } + + # ANY를 사용하여 document의 내용을 확인하지 않고 호출 자체만 확인 + self.mock_collection.insert_one.assert_called_once() + assert result == expected_doc + + def test_get_recommendation_by_id(self): + # given + request_id = "6123456789abcdef01234567" + user_id = "test-user-id" + expected_result = { + "_id": ObjectId(request_id), + "title": "테스트 제목", + "user_id": user_id, + "is_completed": False, + "created_at": datetime(2023, 1, 1, tzinfo=timezone.utc) + } + + self.mock_collection.find_one.return_value = expected_result + + # when + result = self.repository.get_recommendation_by_id(request_id, user_id) + + # then + self.mock_collection.find_one.assert_called_once_with({ + "_id": ObjectId(request_id), + "user_id": user_id + }) + assert result == expected_result + + def test_get_recommendation_by_id_invalid_id(self): + # given + request_id = "invalid-id" + user_id = "test-user-id" + + # when + result = self.repository.get_recommendation_by_id(request_id, user_id) + + # then + assert result is None + self.mock_collection.find_one.assert_not_called() + + def test_update_recommendation_result(self): + # given + request_id = "6123456789abcdef01234567" + predicted_category = "기술" + + # MongoDB 응답 설정 + update_result = MagicMock(modified_count=1) + self.mock_collection.update_one.return_value = update_result + + expected_doc = { + "_id": ObjectId(request_id), + "title": "테스트 제목", + "user_id": "test-user-id", + "is_completed": True, + "predicted_category": predicted_category, + "updated_at": datetime(2023, 1, 1, tzinfo=timezone.utc) + } + self.mock_collection.find_one.return_value = expected_doc + + # when + result = self.repository.update_recommendation_result(request_id, predicted_category) + + # then + self.mock_collection.update_one.assert_called_once_with( + {"_id": ObjectId(request_id)}, + { + "$set": { + "is_completed": True, + "predicted_category": predicted_category, + "updated_at": datetime(2023, 1, 1, tzinfo=timezone.utc) + } + } + ) + self.mock_collection.find_one.assert_called_once_with({"_id": ObjectId(request_id)}) + assert result == expected_doc + + def test_update_recommendation_result_invalid_id(self): + # given + request_id = "invalid-id" + predicted_category = "기술" + + # when + result = self.repository.update_recommendation_result(request_id, predicted_category) + + # then + assert result is None + self.mock_collection.update_one.assert_not_called() + self.mock_collection.find_one.assert_not_called() + + def test_update_recommendation_result_not_found(self): + # given + request_id = "6123456789abcdef01234567" + predicted_category = "기술" + + # MongoDB 응답 설정 - modified_count가 0이면 업데이트된 문서가 없음 + update_result = MagicMock(modified_count=0) + self.mock_collection.update_one.return_value = update_result + + # when + result = self.repository.update_recommendation_result(request_id, predicted_category) + + # then + self.mock_collection.update_one.assert_called_once() + self.mock_collection.find_one.assert_not_called() + assert result is None \ No newline at end of file From c0258803dfa3bc2d44befbb05ce6847e073ba762 Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Sat, 22 Mar 2025 01:20:01 +0900 Subject: [PATCH 19/26] =?UTF-8?q?Test:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EC=B2=9C=20=EB=AA=A8=EB=8D=B8=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test_category_recommendation_models.py | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 src/tests/ai/models/test_category_recommendation_models.py diff --git a/src/tests/ai/models/test_category_recommendation_models.py b/src/tests/ai/models/test_category_recommendation_models.py new file mode 100644 index 0000000..43ec33e --- /dev/null +++ b/src/tests/ai/models/test_category_recommendation_models.py @@ -0,0 +1,93 @@ +import pytest +import uuid +from pydantic import ValidationError + +from src.main.ai.models.CategoryRecommendation import ( + CategoryRecommendationRequest, + CategoryRecommendationResponse, + CategoryRecommendationStatusResponse, + CategoryRecommendationResultRequest +) + + +class TestCategoryRecommendationModels: + def test_category_recommendation_request_valid(self): + # given + title = "테스트 제목" + + # when + model = CategoryRecommendationRequest(title=title) + + # then + assert model.title == title + + def test_category_recommendation_request_invalid(self): + # when/then - 제목이 없는 경우 검증 오류 + with pytest.raises(ValidationError): + CategoryRecommendationRequest() + + def test_category_recommendation_response(self): + # given + request_id = "test-request-id" + + # when + model = CategoryRecommendationResponse(request_id=request_id) + + # then + assert model.request_id == request_id + + def test_category_recommendation_response_default_id(self): + # when + model = CategoryRecommendationResponse() + + # then + assert model.request_id is not None + # UUID 형식으로 변환 가능한지 확인 + uuid.UUID(model.request_id) + + def test_category_recommendation_status_response_incomplete(self): + # given + request_id = "test-request-id" + + # when + model = CategoryRecommendationStatusResponse( + request_id=request_id, + is_completed=False + ) + + # then + assert model.request_id == request_id + assert model.is_completed is False + assert model.predicted_category is None + + def test_category_recommendation_status_response_complete(self): + # given + request_id = "test-request-id" + category = "기술" + + # when + model = CategoryRecommendationStatusResponse( + request_id=request_id, + is_completed=True, + predicted_category=category + ) + + # then + assert model.request_id == request_id + assert model.is_completed is True + assert model.predicted_category == category + + def test_category_recommendation_result_request_valid(self): + # given + category = "기술" + + # when + model = CategoryRecommendationResultRequest(predicted_category=category) + + # then + assert model.predicted_category == category + + def test_category_recommendation_result_request_invalid(self): + # when/then - 카테고리가 없는 경우 검증 오류 + with pytest.raises(ValidationError): + CategoryRecommendationResultRequest() \ No newline at end of file From ed2327fc198833071633a9bb42813e724d36f1d2 Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Sat, 22 Mar 2025 01:20:06 +0900 Subject: [PATCH 20/26] =?UTF-8?q?Test:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EC=B2=9C=20=EC=84=9C=EB=B9=84=EC=8A=A4?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=EB=8B=A8=EC=9C=84=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test_category_recommendation_service.py | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 src/tests/ai/service/test_category_recommendation_service.py diff --git a/src/tests/ai/service/test_category_recommendation_service.py b/src/tests/ai/service/test_category_recommendation_service.py new file mode 100644 index 0000000..47c75dd --- /dev/null +++ b/src/tests/ai/service/test_category_recommendation_service.py @@ -0,0 +1,176 @@ +import pytest +import uuid +from unittest.mock import MagicMock, patch +from bson import ObjectId +from datetime import datetime, timezone + +from src.main.ai.service.CategoryRecommendationService import CategoryRecommendationService +from src.main.ai.models.CategoryRecommendation import ( + CategoryRecommendationRequest, + CategoryRecommendationResponse, + CategoryRecommendationStatusResponse, + CategoryRecommendationResultRequest +) + + +class TestCategoryRecommendationService: + def setup_method(self): + # 목업 리포지토리 및 큐 생성 + self.mock_repository = MagicMock() + self.mock_queue = MagicMock() + + # 테스트 대상 서비스 생성 + self.service = CategoryRecommendationService(self.mock_repository, self.mock_queue) + + # 테스트 공통 데이터 + self.test_title = "테스트 제목" + self.test_user_id = uuid.UUID("12345678-1234-5678-1234-567812345678") + self.test_request_id = "6123456789abcdef01234567" + self.test_object_id = ObjectId(self.test_request_id) + + def test_create_recommendation_request(self): + # given + request = CategoryRecommendationRequest(title=self.test_title) + + # 리포지토리 응답 설정 + mongo_document = { + "_id": self.test_object_id, + "title": self.test_title, + "user_id": str(self.test_user_id), + "is_completed": False, + "created_at": datetime(2023, 1, 1, tzinfo=timezone.utc) + } + self.mock_repository.create_recommendation_request.return_value = mongo_document + + # when + result = self.service.create_recommendation_request(request, self.test_user_id) + + # then + self.mock_repository.create_recommendation_request.assert_called_once_with( + title=self.test_title, + user_id=str(self.test_user_id) + ) + + self.mock_queue.send_message.assert_called_once_with( + request_id=self.test_request_id, + title=self.test_title, + user_id=str(self.test_user_id) + ) + + assert isinstance(result, CategoryRecommendationResponse) + assert result.request_id == self.test_request_id + + def test_get_recommendation_status_exists(self): + # given + # 리포지토리 응답 설정 - 완료되지 않은 추천 + mongo_document = { + "_id": self.test_object_id, + "title": self.test_title, + "user_id": str(self.test_user_id), + "is_completed": False, + "created_at": datetime(2023, 1, 1, tzinfo=timezone.utc) + } + self.mock_repository.get_recommendation_by_id.return_value = mongo_document + + # when + result = self.service.get_recommendation_status(self.test_request_id, self.test_user_id) + + # then + self.mock_repository.get_recommendation_by_id.assert_called_once_with( + self.test_request_id, str(self.test_user_id) + ) + + assert isinstance(result, CategoryRecommendationStatusResponse) + assert result.request_id == self.test_request_id + assert result.is_completed is False + assert result.predicted_category is None + + def test_get_recommendation_status_completed(self): + # given + # 리포지토리 응답 설정 - 완료된 추천 + mongo_document = { + "_id": self.test_object_id, + "title": self.test_title, + "user_id": str(self.test_user_id), + "is_completed": True, + "predicted_category": "기술", + "created_at": datetime(2023, 1, 1, tzinfo=timezone.utc), + "updated_at": datetime(2023, 1, 2, tzinfo=timezone.utc) + } + self.mock_repository.get_recommendation_by_id.return_value = mongo_document + + # when + result = self.service.get_recommendation_status(self.test_request_id, self.test_user_id) + + # then + self.mock_repository.get_recommendation_by_id.assert_called_once_with( + self.test_request_id, str(self.test_user_id) + ) + + assert isinstance(result, CategoryRecommendationStatusResponse) + assert result.request_id == self.test_request_id + assert result.is_completed is True + assert result.predicted_category == "기술" + + def test_get_recommendation_status_not_found(self): + # given + # 리포지토리 응답 설정 - 문서 없음 + self.mock_repository.get_recommendation_by_id.return_value = None + + # when + result = self.service.get_recommendation_status(self.test_request_id, self.test_user_id) + + # then + self.mock_repository.get_recommendation_by_id.assert_called_once_with( + self.test_request_id, str(self.test_user_id) + ) + + assert result is None + + def test_update_recommendation_result_success(self): + # given + request = CategoryRecommendationResultRequest(predicted_category="기술") + + # 리포지토리 응답 설정 + updated_document = { + "_id": self.test_object_id, + "title": self.test_title, + "user_id": str(self.test_user_id), + "is_completed": True, + "predicted_category": "기술", + "created_at": datetime(2023, 1, 1, tzinfo=timezone.utc), + "updated_at": datetime(2023, 1, 2, tzinfo=timezone.utc) + } + self.mock_repository.update_recommendation_result.return_value = updated_document + + # when + result = self.service.update_recommendation_result(self.test_request_id, request) + + # then + self.mock_repository.update_recommendation_result.assert_called_once_with( + request_id=self.test_request_id, + predicted_category=request.predicted_category + ) + + assert isinstance(result, CategoryRecommendationStatusResponse) + assert result.request_id == self.test_request_id + assert result.is_completed is True + assert result.predicted_category == "기술" + + def test_update_recommendation_result_not_found(self): + # given + request = CategoryRecommendationResultRequest(predicted_category="기술") + + # 리포지토리 응답 설정 - 업데이트 실패 + self.mock_repository.update_recommendation_result.return_value = None + + # when + result = self.service.update_recommendation_result(self.test_request_id, request) + + # then + self.mock_repository.update_recommendation_result.assert_called_once_with( + request_id=self.test_request_id, + predicted_category=request.predicted_category + ) + + assert result is None \ No newline at end of file From 0f63c39ecf797dd3b1f399200eb6a43b797c459c Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Sat, 22 Mar 2025 01:20:11 +0900 Subject: [PATCH 21/26] =?UTF-8?q?Test:=20AI=20=EB=82=B4=EB=B6=80=20API=20?= =?UTF-8?q?=EB=9D=BC=EC=9A=B0=ED=84=B0=EC=9D=98=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=B6=94=EC=B2=9C=20=EA=B2=B0=EA=B3=BC=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=EB=8B=A8=EC=9C=84=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/router/test_ai_internal_api_router.py | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/tests/ai/router/test_ai_internal_api_router.py diff --git a/src/tests/ai/router/test_ai_internal_api_router.py b/src/tests/ai/router/test_ai_internal_api_router.py new file mode 100644 index 0000000..fd48baa --- /dev/null +++ b/src/tests/ai/router/test_ai_internal_api_router.py @@ -0,0 +1,77 @@ +import pytest +from unittest.mock import MagicMock, patch +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from src.main.ai.models.CategoryRecommendation import CategoryRecommendationStatusResponse +from src.main.ai.router.AIInternalAPIRouter import router as internal_router + + +# 테스트용 앱 생성 +app = FastAPI() +app.include_router(internal_router) + +@pytest.fixture +def client(): + return TestClient(app) + + +class TestAIInternalAPIRouter: + def setup_method(self): + # 테스트 공통 데이터 + self.test_request_id = "6123456789abcdef01234567" + self.test_category = "기술" + + def test_update_category_recommendation_result_success(self, client): + # given + request_data = { + "predicted_category": self.test_category + } + + # 서비스 응답 모의 설정 + with patch('src.main.ai.service.CategoryRecommendationService.CategoryRecommendationService.update_recommendation_result') as mock_service: + # 서비스 응답 설정 + mock_service.return_value = CategoryRecommendationStatusResponse( + request_id=self.test_request_id, + is_completed=True, + predicted_category=self.test_category + ) + + # when + response = client.post(f"/ai-proxy/category-recommendation-results/{self.test_request_id}", json=request_data) + + # then + assert response.status_code == 200 + assert response.json() == { + "request_id": self.test_request_id, + "is_completed": True, + "predicted_category": self.test_category + } + + # 서비스가 한 번 호출됐는지 확인 + mock_service.assert_called_once() + + def test_update_category_recommendation_result_not_found(self, client): + # given + request_data = { + "predicted_category": self.test_category + } + + # 서비스 응답 모의 설정 - 업데이트 실패 + with patch('src.main.ai.service.CategoryRecommendationService.CategoryRecommendationService.update_recommendation_result') as mock_service: + # 서비스 응답 설정 + mock_service.return_value = None + + # when + response = client.post(f"/ai-proxy/category-recommendation-results/{self.test_request_id}", json=request_data) + + # then + assert response.status_code == 404 + assert response.json() == { + "status": 404, + "message": "요청을 찾을 수 없습니다.", + "detail": "존재하지 않는 ID입니다." + } + + # 서비스가 한 번 호출됐는지 확인 + mock_service.assert_called_once() \ No newline at end of file From d24529d1bdf4d631f1a7d073e251881b83260b18 Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Sat, 22 Mar 2025 01:20:17 +0900 Subject: [PATCH 22/26] =?UTF-8?q?Test:=20AI=20=EA=B3=B5=EA=B0=9C=20API=20?= =?UTF-8?q?=EB=9D=BC=EC=9A=B0=ED=84=B0=EC=9D=98=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=B6=94=EC=B2=9C=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EB=B0=8F=20=EC=83=81=ED=83=9C=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=EC=97=90=20=EB=8C=80=ED=95=9C=20=EB=8B=A8=EC=9C=84=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/router/test_ai_public_api_router.py | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 src/tests/ai/router/test_ai_public_api_router.py diff --git a/src/tests/ai/router/test_ai_public_api_router.py b/src/tests/ai/router/test_ai_public_api_router.py new file mode 100644 index 0000000..cc8e247 --- /dev/null +++ b/src/tests/ai/router/test_ai_public_api_router.py @@ -0,0 +1,96 @@ +import pytest +import uuid +from unittest.mock import MagicMock, patch +from fastapi import FastAPI, Depends +from fastapi.testclient import TestClient + +from src.main.ai.models.CategoryRecommendation import ( + CategoryRecommendationRequest, + CategoryRecommendationResponse, + CategoryRecommendationStatusResponse +) +from src.main.ai.router.AIPublicAPIRouter import router as public_router +from src.main.auth.dependencies import get_current_user + + +# 테스트용 앱 생성 +app = FastAPI() +app.include_router(public_router) + +# 고정된 사용자 ID를 반환하는 의존성 함수 +async def mock_get_current_user(): + return uuid.UUID("12345678-1234-5678-1234-567812345678") + +# 실제 의존성 대신 모의 의존성 사용 +app.dependency_overrides[get_current_user] = mock_get_current_user + +# 테스트용 클라이언트 +@pytest.fixture +def client(): + return TestClient(app) + + +class TestAIPublicAPIRouter: + def setup_method(self): + # 테스트 공통 데이터 + self.test_title = "테스트 제목" + self.test_user_id = uuid.UUID("12345678-1234-5678-1234-567812345678") + self.test_request_id = "6123456789abcdef01234567" + + def test_create_category_recommendation_request(self, client): + # given + request_data = { + "title": self.test_title + } + + # 서비스 응답 모의 설정 + with patch('src.main.ai.service.CategoryRecommendationService.CategoryRecommendationService.create_recommendation_request') as mock_service: + # 서비스 응답 설정 + mock_service.return_value = CategoryRecommendationResponse(request_id=self.test_request_id) + + # when + response = client.post("/ai/category-recommendations", json=request_data) + + # then + assert response.status_code == 200 + assert response.json() == {"request_id": self.test_request_id} + + # 서비스가 한 번 호출됐는지 확인 + mock_service.assert_called_once() + + def test_get_category_recommendation_status_exists(self, client): + # given + with patch('src.main.ai.service.CategoryRecommendationService.CategoryRecommendationService.get_recommendation_status') as mock_service: + # 서비스 응답 설정 - 완료된 추천 + mock_service.return_value = CategoryRecommendationStatusResponse( + request_id=self.test_request_id, + is_completed=True, + predicted_category="기술" + ) + + # when + response = client.get(f"/ai/category-recommendations/{self.test_request_id}") + + # then + assert response.status_code == 200 + assert response.json() == { + "request_id": self.test_request_id, + "is_completed": True, + "predicted_category": "기술" + } + + # 서비스가 한 번 호출됐는지 확인 + mock_service.assert_called_once() + + def test_get_category_recommendation_status_not_found(self, client): + # given + with patch('src.main.ai.service.CategoryRecommendationService.CategoryRecommendationService.get_recommendation_status') as mock_service: + # 서비스 응답 설정 - 요청 없음 + mock_service.return_value = None + + # when + response = client.get(f"/ai/category-recommendations/{self.test_request_id}") + + # then + assert response.status_code == 404 + assert "요청을 찾을 수 없습니다" in response.json().get("detail", "") \ No newline at end of file From bf9abc6877b51178b6f73f5ca1abcebaeea46aa7 Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Sat, 22 Mar 2025 01:31:05 +0900 Subject: [PATCH 23/26] =?UTF-8?q?Refactor:=20MongoDB=20=EC=97=B0=EA=B2=B0?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=EC=9D=84=20=EC=9C=84=ED=95=9C=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=20=EB=B3=80=EC=88=98=EB=A5=BC=20MONGODB=5FURL?= =?UTF-8?q?=EB=A1=9C=20=EB=8B=A8=EC=9D=BC=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/config/mongodb.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/config/mongodb.py b/src/main/config/mongodb.py index f998808..0b7f00f 100644 --- a/src/main/config/mongodb.py +++ b/src/main/config/mongodb.py @@ -4,12 +4,8 @@ load_dotenv() -MONGO_URI = os.getenv("MONGODB_URI") -MONGO_DB = os.getenv("MONGODB_DB") -MONGO_USER = os.getenv("MONGODB_USER") -MONGO_PASSWORD = os.getenv("MONGODB_PASSWORD") - +MONGODB_URL = os.getenv("MONGODB_URL") def get_mongo_client(): - client = MongoClient("mongodb+srv://" + MONGO_USER + ":" + MONGO_PASSWORD + "@" + MONGO_URI + "/" + MONGO_DB + "?retryWrites=true") + client = MongoClient(MONGODB_URL + "?retryWrites=true") return client From b542630a600909b17f49bbe2008a0d5c46f58272 Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Sat, 22 Mar 2025 01:31:34 +0900 Subject: [PATCH 24/26] =?UTF-8?q?Refactor:=20=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EC=97=90=20TEST=5FMONGODB=5FURL=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20Docker=20=EC=8B=A4=ED=96=89=20=EC=8B=9C?= =?UTF-8?q?=20MONGODB=5FURL=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd-ecs.yml | 2 ++ .github/workflows/ci-pytest.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/cd-ecs.yml b/.github/workflows/cd-ecs.yml index 6464832..69bcb86 100644 --- a/.github/workflows/cd-ecs.yml +++ b/.github/workflows/cd-ecs.yml @@ -13,6 +13,7 @@ env: ECS_TASK_DEFINITION: ${{ vars.ECS_TASK_DEFINITION }} CONTAINER_NAME: ${{ vars.CONTAINER_NAME }} TEST_IMAGE_NAME: xrpedia-ai-proxy-test + TEST_MONGODB_URL: ${{ secrets.TEST_MONGODB_URL }} permissions: contents: read id-token: write @@ -49,6 +50,7 @@ jobs: id: run-test run: | docker run --rm \ + -e MONGODB_URL=${{ env.TEST_MONGODB_URL }} \ ${{ env.TEST_IMAGE_NAME }} - name: Build, tag, and push image to Amazon ECR diff --git a/.github/workflows/ci-pytest.yml b/.github/workflows/ci-pytest.yml index 580243e..a56e9e4 100644 --- a/.github/workflows/ci-pytest.yml +++ b/.github/workflows/ci-pytest.yml @@ -6,6 +6,7 @@ on: env: TEST_IMAGE_NAME: xrpedia-ai-proxy-test + TEST_MONGODB_URL: ${{ secrets.TEST_MONGODB_URL }} jobs: build: @@ -22,4 +23,5 @@ jobs: id: run-test run: | docker run --rm \ + -e MONGODB_URL=${{ env.TEST_MONGODB_URL }} \ ${{ env.TEST_IMAGE_NAME }} From d5059e3064e8c8fe3aa849a3d87e2251a92aa576 Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Sat, 22 Mar 2025 01:31:49 +0900 Subject: [PATCH 25/26] =?UTF-8?q?Refactor:=20=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EC=97=90=20MONGODB=5FURL=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80:=20AWS=20Secrets=20Manager=EC=97=90=EC=84=9C=20MongoD?= =?UTF-8?q?B=20URL=EC=9D=84=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .aws/ecs-task-definition.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.aws/ecs-task-definition.json b/.aws/ecs-task-definition.json index 0f98200..bbe6412 100644 --- a/.aws/ecs-task-definition.json +++ b/.aws/ecs-task-definition.json @@ -20,6 +20,10 @@ "mountPoints": [], "volumesFrom": [], "secrets": [ + { + "name": "MONGODB_URL", + "valueFrom": "arn:aws:secretsmanager:ap-northeast-2:864981757354:secret:xrpedia/credentials-UAy9x0:xrpedia-mongodb-url::" + } ], "ulimits": [], "logConfiguration": { From cce65ea8d14724c60074f681281c8b391ca095b0 Mon Sep 17 00:00:00 2001 From: Coldot <41678750+Coldot@users.noreply.github.com> Date: Sat, 22 Mar 2025 01:39:59 +0900 Subject: [PATCH 26/26] =?UTF-8?q?Fix:=20=ED=99=98=EA=B2=BD=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=EC=97=90=20AWS=5FREGION=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd-ecs.yml | 1 + .github/workflows/ci-pytest.yml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/cd-ecs.yml b/.github/workflows/cd-ecs.yml index 69bcb86..79e4376 100644 --- a/.github/workflows/cd-ecs.yml +++ b/.github/workflows/cd-ecs.yml @@ -51,6 +51,7 @@ jobs: run: | docker run --rm \ -e MONGODB_URL=${{ env.TEST_MONGODB_URL }} \ + -e AWS_REGION=${{ env.AWS_REGION }} \ ${{ env.TEST_IMAGE_NAME }} - name: Build, tag, and push image to Amazon ECR diff --git a/.github/workflows/ci-pytest.yml b/.github/workflows/ci-pytest.yml index a56e9e4..059e05b 100644 --- a/.github/workflows/ci-pytest.yml +++ b/.github/workflows/ci-pytest.yml @@ -7,6 +7,7 @@ on: env: TEST_IMAGE_NAME: xrpedia-ai-proxy-test TEST_MONGODB_URL: ${{ secrets.TEST_MONGODB_URL }} + AWS_REGION: ${{ secrets.AWS_REGION }} jobs: build: @@ -23,5 +24,6 @@ jobs: id: run-test run: | docker run --rm \ + -e AWS_REGION=${{ env.AWS_REGION }} \ -e MONGODB_URL=${{ env.TEST_MONGODB_URL }} \ ${{ env.TEST_IMAGE_NAME }}