From d829e9311c07c79cfaaa89d958e91c2e0eb80e27 Mon Sep 17 00:00:00 2001 From: seoyeon0103 Date: Fri, 21 Mar 2025 13:20:51 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20=ED=8B=80=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ec5d198 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "devon-aide.resourcePath": "c:\\Users\\user\\.vscode\\extensions\\lgcns-devon-aide.devon-aide-0.0.1\\resource" +} \ No newline at end of file From f503d01d5c5b62d3ede07322934b1657f408e628 Mon Sep 17 00:00:00 2001 From: seoyeon0103 Date: Fri, 21 Mar 2025 17:24:47 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20dto=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/nft/nft_Info_dto.py | 30 ++++++++++++++++++++++++++++++ src/main/nft/nft_issue_service.py | 13 +++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 src/main/nft/nft_Info_dto.py create mode 100644 src/main/nft/nft_issue_service.py diff --git a/src/main/nft/nft_Info_dto.py b/src/main/nft/nft_Info_dto.py new file mode 100644 index 0000000..f7e8aa3 --- /dev/null +++ b/src/main/nft/nft_Info_dto.py @@ -0,0 +1,30 @@ +from pydantic import BaseModel +from typing import Dict + +# 필요한 필드들(Response) +# 1. user-wallet id +# 2. nft-id +# 3. nft-grade +# 4. transaction_hash #nft 발급 트ㅐㄴ잭션이 성공적으로 기록 되어있는지 확인 가능한 정보 +#(블록체인 익스플로어 에서 트ㅐㄴ잭션을 조회 가능함) +# 5. nft 메타 데이터 +# 6. 발급일, 만료일 +class NftResponseDTO(BaseModel): + user_wallet_id: str + point: int + nft_id: str + nft_grade: str + transaction_hash: str + nft_metadata: Dict[str, str] + issued_at: datetime + expired_at: datetime + + +# db 저자에 필요한 필드 +# 1. nft-id +# 2. nft-grade +# 3. transacation_hash +# 4. meta_uri +# nft의 상세 정보(이미지, 설명, 속성 등) 외부 저장소(s3)에 보관 +# 5. 발급, 만료일 + diff --git a/src/main/nft/nft_issue_service.py b/src/main/nft/nft_issue_service.py new file mode 100644 index 0000000..eec16bd --- /dev/null +++ b/src/main/nft/nft_issue_service.py @@ -0,0 +1,13 @@ +#1. 포인트를 기준으로 사용자 나열하기 +# 조건) 500포인트 이상인 사람만 걸러서 나열할 것 + +#2. 포인트에 따른 nft 등급 매핑 + +#3.nft 발급 대상자 선정 +# 1) 전체 상위 3퍼센트 + 1500포가 넘는지 checking(저장) -> nft 발급 +# 2) 전체 상위 10퍼 + 상위 3퍼센트 제외 + 1000포가 넘는지 checking(저장) -> nft 발급급 +# 3) 전체 상위 40퍼 + 상위 3 + 상위 10 제외 + 500포 이상 chekcing(저장) -> ntt 발급 + +#4. nft 발급 구현(병렬 처리) +#5. db에 저장 +#6. return 값은 void로 하는데 checking 용으로 해보기 From c7bb98549a3b2e148aa3df3828d08d9faeca15d0 Mon Sep 17 00:00:00 2001 From: seoyeon0103 Date: Sat, 22 Mar 2025 17:57:04 +0900 Subject: [PATCH 03/10] =?UTF-8?q?Feat:=20user=20=EB=8D=94=EB=AF=B8=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A1=9C=20nft=20=EB=B0=9C=EA=B8=89?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poetry.lock | 490 +++++++++++++++++++++++++++++- pyproject.toml | 2 + src/main/nft/nft_Info_dto.py | 14 +- src/main/nft/nft_controller.py | 25 ++ src/main/nft/nft_issue_service.py | 256 ++++++++++++++++ src/main/nft/nft_model.py | 18 ++ src/main/nft/nft_repository.py | 12 + src/router.py | 5 +- 8 files changed, 808 insertions(+), 14 deletions(-) create mode 100644 src/main/nft/nft_controller.py create mode 100644 src/main/nft/nft_model.py create mode 100644 src/main/nft/nft_repository.py diff --git a/poetry.lock b/poetry.lock index fc71d4c..1704898 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -31,9 +31,24 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] trio = ["trio (>=0.26.1)"] +[[package]] +name = "base58" +version = "2.1.1" +description = "Base58 and Base58Check implementation." +optional = false +python-versions = ">=3.5" +groups = ["main"] +files = [ + {file = "base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2"}, + {file = "base58-2.1.1.tar.gz", hash = "sha256:c5d0cb3f5b6e81e8e35da5754388ddcc6d0d14b6c6a132cb93d69ed580a7278c"}, +] + +[package.extras] +tests = ["PyHamcrest (>=2.0.2)", "mypy", "pytest (>=4.6)", "pytest-benchmark", "pytest-cov", "pytest-flake8"] + [[package]] name = "boto3" version = "1.37.16" @@ -114,6 +129,24 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "deprecated" +version = "1.2.18" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +groups = ["main"] +files = [ + {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"}, + {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"}, +] + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"] + [[package]] name = "dnspython" version = "2.7.0" @@ -149,6 +182,18 @@ files = [ [package.dependencies] python-dotenv = "*" +[[package]] +name = "ecpy" +version = "1.2.5" +description = "Pure Pyhton Elliptic Curve Library" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "ECPy-1.2.5-py3-none-any.whl", hash = "sha256:559c92e42406d9d1a6b2b8fc26e6ad7bc985f33903b72f426a56cb1073a25ce3"}, + {file = "ECPy-1.2.5.tar.gz", hash = "sha256:9635cffb9b6ecf7fd7f72aea1665829ac74a1d272006d0057d45a621aae20228"}, +] + [[package]] name = "fastapi" version = "0.115.11" @@ -170,6 +215,94 @@ typing-extensions = ">=4.8.0" all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] +[[package]] +name = "greenlet" +version = "3.1.1" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")" +files = [ + {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6"}, + {file = "greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80"}, + {file = "greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395"}, + {file = "greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39"}, + {file = "greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942"}, + {file = "greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01"}, + {file = "greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c"}, + {file = "greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de"}, + {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa"}, + {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af"}, + {file = "greenlet-3.1.1-cp37-cp37m-win32.whl", hash = "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798"}, + {file = "greenlet-3.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef"}, + {file = "greenlet-3.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1"}, + {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd"}, + {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7"}, + {file = "greenlet-3.1.1-cp38-cp38-win32.whl", hash = "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef"}, + {file = "greenlet-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d"}, + {file = "greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e"}, + {file = "greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c"}, + {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"}, + {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] + [[package]] name = "h11" version = "0.14.0" @@ -223,7 +356,7 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -296,6 +429,45 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pycryptodome" +version = "3.22.0" +description = "Cryptographic library for Python" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main"] +files = [ + {file = "pycryptodome-3.22.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:96e73527c9185a3d9b4c6d1cfb4494f6ced418573150be170f6580cb975a7f5a"}, + {file = "pycryptodome-3.22.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:9e1bb165ea1dc83a11e5dbbe00ef2c378d148f3a2d3834fb5ba4e0f6fd0afe4b"}, + {file = "pycryptodome-3.22.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:d4d1174677855c266eed5c4b4e25daa4225ad0c9ffe7584bb1816767892545d0"}, + {file = "pycryptodome-3.22.0-cp27-cp27m-win32.whl", hash = "sha256:9dbb749cef71c28271484cbef684f9b5b19962153487735411e1020ca3f59cb1"}, + {file = "pycryptodome-3.22.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f1ae7beb64d4fc4903a6a6cca80f1f448e7a8a95b77d106f8a29f2eb44d17547"}, + {file = "pycryptodome-3.22.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:a26bcfee1293b7257c83b0bd13235a4ee58165352be4f8c45db851ba46996dc6"}, + {file = "pycryptodome-3.22.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:009e1c80eea42401a5bd5983c4bab8d516aef22e014a4705622e24e6d9d703c6"}, + {file = "pycryptodome-3.22.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3b76fa80daeff9519d7e9f6d9e40708f2fce36b9295a847f00624a08293f4f00"}, + {file = "pycryptodome-3.22.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a31fa5914b255ab62aac9265654292ce0404f6b66540a065f538466474baedbc"}, + {file = "pycryptodome-3.22.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0092fd476701eeeb04df5cc509d8b739fa381583cda6a46ff0a60639b7cd70d"}, + {file = "pycryptodome-3.22.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d5b0ddc7cf69231736d778bd3ae2b3efb681ae33b64b0c92fb4626bb48bb89"}, + {file = "pycryptodome-3.22.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f6cf6aa36fcf463e622d2165a5ad9963b2762bebae2f632d719dfb8544903cf5"}, + {file = "pycryptodome-3.22.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:aec7b40a7ea5af7c40f8837adf20a137d5e11a6eb202cde7e588a48fb2d871a8"}, + {file = "pycryptodome-3.22.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d21c1eda2f42211f18a25db4eaf8056c94a8563cd39da3683f89fe0d881fb772"}, + {file = "pycryptodome-3.22.0-cp37-abi3-win32.whl", hash = "sha256:f02baa9f5e35934c6e8dcec91fcde96612bdefef6e442813b8ea34e82c84bbfb"}, + {file = "pycryptodome-3.22.0-cp37-abi3-win_amd64.whl", hash = "sha256:d086aed307e96d40c23c42418cbbca22ecc0ab4a8a0e24f87932eeab26c08627"}, + {file = "pycryptodome-3.22.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:98fd9da809d5675f3a65dcd9ed384b9dc67edab6a4cda150c5870a8122ec961d"}, + {file = "pycryptodome-3.22.0-pp27-pypy_73-win32.whl", hash = "sha256:37ddcd18284e6b36b0a71ea495a4c4dca35bb09ccc9bfd5b91bfaf2321f131c1"}, + {file = "pycryptodome-3.22.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b4bdce34af16c1dcc7f8c66185684be15f5818afd2a82b75a4ce6b55f9783e13"}, + {file = "pycryptodome-3.22.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2988ffcd5137dc2d27eb51cd18c0f0f68e5b009d5fec56fbccb638f90934f333"}, + {file = "pycryptodome-3.22.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e653519dedcd1532788547f00eeb6108cc7ce9efdf5cc9996abce0d53f95d5a9"}, + {file = "pycryptodome-3.22.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5810bc7494e4ac12a4afef5a32218129e7d3890ce3f2b5ec520cc69eb1102ad"}, + {file = "pycryptodome-3.22.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e7514a1aebee8e85802d154fdb261381f1cb9b7c5a54594545145b8ec3056ae6"}, + {file = "pycryptodome-3.22.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:56c6f9342fcb6c74e205fbd2fee568ec4cdbdaa6165c8fde55dbc4ba5f584464"}, + {file = "pycryptodome-3.22.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87a88dc543b62b5c669895caf6c5a958ac7abc8863919e94b7a6cafd2f64064f"}, + {file = "pycryptodome-3.22.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7a683bc9fa585c0dfec7fa4801c96a48d30b30b096e3297f9374f40c2fedafc"}, + {file = "pycryptodome-3.22.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f4f6f47a7f411f2c157e77bbbda289e0c9f9e1e9944caa73c1c2e33f3f92d6e"}, + {file = "pycryptodome-3.22.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6cf9553b29624961cab0785a3177a333e09e37ba62ad22314ebdbb01ca79840"}, + {file = "pycryptodome-3.22.0.tar.gz", hash = "sha256:fd7ab568b3ad7b77c908d7c3f7e167ec5a8f035c64ff74f10d47a4edd043d723"}, +] + [[package]] name = "pydantic" version = "2.10.6" @@ -315,7 +487,7 @@ typing-extensions = ">=4.12.2" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] name = "pydantic-core" @@ -503,9 +675,9 @@ 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)"] +encryption = ["certifi ; os_name == \"nt\" or sys_platform == \"darwin\"", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.12.0,<2.0.0)"] +gssapi = ["pykerberos ; os_name != \"nt\"", "winkerberos (>=0.5.0) ; os_name == \"nt\""] +ocsp = ["certifi ; os_name == \"nt\" or sys_platform == \"darwin\"", "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"] @@ -603,6 +775,102 @@ files = [ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] +[[package]] +name = "sqlalchemy" +version = "2.0.39" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "SQLAlchemy-2.0.39-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:66a40003bc244e4ad86b72abb9965d304726d05a939e8c09ce844d27af9e6d37"}, + {file = "SQLAlchemy-2.0.39-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67de057fbcb04a066171bd9ee6bcb58738d89378ee3cabff0bffbf343ae1c787"}, + {file = "SQLAlchemy-2.0.39-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:533e0f66c32093a987a30df3ad6ed21170db9d581d0b38e71396c49718fbb1ca"}, + {file = "SQLAlchemy-2.0.39-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7399d45b62d755e9ebba94eb89437f80512c08edde8c63716552a3aade61eb42"}, + {file = "SQLAlchemy-2.0.39-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:788b6ff6728072b313802be13e88113c33696a9a1f2f6d634a97c20f7ef5ccce"}, + {file = "SQLAlchemy-2.0.39-cp37-cp37m-win32.whl", hash = "sha256:01da15490c9df352fbc29859d3c7ba9cd1377791faeeb47c100832004c99472c"}, + {file = "SQLAlchemy-2.0.39-cp37-cp37m-win_amd64.whl", hash = "sha256:f2bcb085faffcacf9319b1b1445a7e1cfdc6fb46c03f2dce7bc2d9a4b3c1cdc5"}, + {file = "SQLAlchemy-2.0.39-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b761a6847f96fdc2d002e29e9e9ac2439c13b919adfd64e8ef49e75f6355c548"}, + {file = "SQLAlchemy-2.0.39-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0d7e3866eb52d914aea50c9be74184a0feb86f9af8aaaa4daefe52b69378db0b"}, + {file = "SQLAlchemy-2.0.39-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:995c2bacdddcb640c2ca558e6760383dcdd68830160af92b5c6e6928ffd259b4"}, + {file = "SQLAlchemy-2.0.39-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:344cd1ec2b3c6bdd5dfde7ba7e3b879e0f8dd44181f16b895940be9b842fd2b6"}, + {file = "SQLAlchemy-2.0.39-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:5dfbc543578058c340360f851ddcecd7a1e26b0d9b5b69259b526da9edfa8875"}, + {file = "SQLAlchemy-2.0.39-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3395e7ed89c6d264d38bea3bfb22ffe868f906a7985d03546ec7dc30221ea980"}, + {file = "SQLAlchemy-2.0.39-cp38-cp38-win32.whl", hash = "sha256:bf555f3e25ac3a70c67807b2949bfe15f377a40df84b71ab2c58d8593a1e036e"}, + {file = "SQLAlchemy-2.0.39-cp38-cp38-win_amd64.whl", hash = "sha256:463ecfb907b256e94bfe7bcb31a6d8c7bc96eca7cbe39803e448a58bb9fcad02"}, + {file = "sqlalchemy-2.0.39-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6827f8c1b2f13f1420545bd6d5b3f9e0b85fe750388425be53d23c760dcf176b"}, + {file = "sqlalchemy-2.0.39-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9f119e7736967c0ea03aff91ac7d04555ee038caf89bb855d93bbd04ae85b41"}, + {file = "sqlalchemy-2.0.39-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4600c7a659d381146e1160235918826c50c80994e07c5b26946a3e7ec6c99249"}, + {file = "sqlalchemy-2.0.39-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a06e6c8e31c98ddc770734c63903e39f1947c9e3e5e4bef515c5491b7737dde"}, + {file = "sqlalchemy-2.0.39-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4c433f78c2908ae352848f56589c02b982d0e741b7905228fad628999799de4"}, + {file = "sqlalchemy-2.0.39-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7bd5c5ee1448b6408734eaa29c0d820d061ae18cb17232ce37848376dcfa3e92"}, + {file = "sqlalchemy-2.0.39-cp310-cp310-win32.whl", hash = "sha256:87a1ce1f5e5dc4b6f4e0aac34e7bb535cb23bd4f5d9c799ed1633b65c2bcad8c"}, + {file = "sqlalchemy-2.0.39-cp310-cp310-win_amd64.whl", hash = "sha256:871f55e478b5a648c08dd24af44345406d0e636ffe021d64c9b57a4a11518304"}, + {file = "sqlalchemy-2.0.39-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a28f9c238f1e143ff42ab3ba27990dfb964e5d413c0eb001b88794c5c4a528a9"}, + {file = "sqlalchemy-2.0.39-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:08cf721bbd4391a0e765fe0fe8816e81d9f43cece54fdb5ac465c56efafecb3d"}, + {file = "sqlalchemy-2.0.39-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a8517b6d4005facdbd7eb4e8cf54797dbca100a7df459fdaff4c5123265c1cd"}, + {file = "sqlalchemy-2.0.39-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b2de1523d46e7016afc7e42db239bd41f2163316935de7c84d0e19af7e69538"}, + {file = "sqlalchemy-2.0.39-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:412c6c126369ddae171c13987b38df5122cb92015cba6f9ee1193b867f3f1530"}, + {file = "sqlalchemy-2.0.39-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b35e07f1d57b79b86a7de8ecdcefb78485dab9851b9638c2c793c50203b2ae8"}, + {file = "sqlalchemy-2.0.39-cp311-cp311-win32.whl", hash = "sha256:3eb14ba1a9d07c88669b7faf8f589be67871d6409305e73e036321d89f1d904e"}, + {file = "sqlalchemy-2.0.39-cp311-cp311-win_amd64.whl", hash = "sha256:78f1b79132a69fe8bd6b5d91ef433c8eb40688ba782b26f8c9f3d2d9ca23626f"}, + {file = "sqlalchemy-2.0.39-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c457a38351fb6234781d054260c60e531047e4d07beca1889b558ff73dc2014b"}, + {file = "sqlalchemy-2.0.39-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:018ee97c558b499b58935c5a152aeabf6d36b3d55d91656abeb6d93d663c0c4c"}, + {file = "sqlalchemy-2.0.39-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5493a8120d6fc185f60e7254fc056a6742f1db68c0f849cfc9ab46163c21df47"}, + {file = "sqlalchemy-2.0.39-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2cf5b5ddb69142511d5559c427ff00ec8c0919a1e6c09486e9c32636ea2b9dd"}, + {file = "sqlalchemy-2.0.39-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f03143f8f851dd8de6b0c10784363712058f38209e926723c80654c1b40327a"}, + {file = "sqlalchemy-2.0.39-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06205eb98cb3dd52133ca6818bf5542397f1dd1b69f7ea28aa84413897380b06"}, + {file = "sqlalchemy-2.0.39-cp312-cp312-win32.whl", hash = "sha256:7f5243357e6da9a90c56282f64b50d29cba2ee1f745381174caacc50d501b109"}, + {file = "sqlalchemy-2.0.39-cp312-cp312-win_amd64.whl", hash = "sha256:2ed107331d188a286611cea9022de0afc437dd2d3c168e368169f27aa0f61338"}, + {file = "sqlalchemy-2.0.39-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fe193d3ae297c423e0e567e240b4324d6b6c280a048e64c77a3ea6886cc2aa87"}, + {file = "sqlalchemy-2.0.39-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:79f4f502125a41b1b3b34449e747a6abfd52a709d539ea7769101696bdca6716"}, + {file = "sqlalchemy-2.0.39-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a10ca7f8a1ea0fd5630f02feb055b0f5cdfcd07bb3715fc1b6f8cb72bf114e4"}, + {file = "sqlalchemy-2.0.39-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6b0a1c7ed54a5361aaebb910c1fa864bae34273662bb4ff788a527eafd6e14d"}, + {file = "sqlalchemy-2.0.39-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52607d0ebea43cf214e2ee84a6a76bc774176f97c5a774ce33277514875a718e"}, + {file = "sqlalchemy-2.0.39-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c08a972cbac2a14810463aec3a47ff218bb00c1a607e6689b531a7c589c50723"}, + {file = "sqlalchemy-2.0.39-cp313-cp313-win32.whl", hash = "sha256:23c5aa33c01bd898f879db158537d7e7568b503b15aad60ea0c8da8109adf3e7"}, + {file = "sqlalchemy-2.0.39-cp313-cp313-win_amd64.whl", hash = "sha256:4dabd775fd66cf17f31f8625fc0e4cfc5765f7982f94dc09b9e5868182cb71c0"}, + {file = "sqlalchemy-2.0.39-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2600a50d590c22d99c424c394236899ba72f849a02b10e65b4c70149606408b5"}, + {file = "sqlalchemy-2.0.39-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4eff9c270afd23e2746e921e80182872058a7a592017b2713f33f96cc5f82e32"}, + {file = "sqlalchemy-2.0.39-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7332868ce891eda48896131991f7f2be572d65b41a4050957242f8e935d5d7"}, + {file = "sqlalchemy-2.0.39-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:125a7763b263218a80759ad9ae2f3610aaf2c2fbbd78fff088d584edf81f3782"}, + {file = "sqlalchemy-2.0.39-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:04545042969833cb92e13b0a3019549d284fd2423f318b6ba10e7aa687690a3c"}, + {file = "sqlalchemy-2.0.39-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:805cb481474e111ee3687c9047c5f3286e62496f09c0e82e8853338aaaa348f8"}, + {file = "sqlalchemy-2.0.39-cp39-cp39-win32.whl", hash = "sha256:34d5c49f18778a3665d707e6286545a30339ad545950773d43977e504815fa70"}, + {file = "sqlalchemy-2.0.39-cp39-cp39-win_amd64.whl", hash = "sha256:35e72518615aa5384ef4fae828e3af1b43102458b74a8c481f69af8abf7e802a"}, + {file = "sqlalchemy-2.0.39-py3-none-any.whl", hash = "sha256:a1c6b0a5e3e326a466d809b651c63f278b1256146a377a528b6938a279da334f"}, + {file = "sqlalchemy-2.0.39.tar.gz", hash = "sha256:5d2d1fe548def3267b4c70a8568f108d1fed7cbbeccb9cc166e05af2abc25c22"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} +typing-extensions = ">=4.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + [[package]] name = "starlette" version = "0.46.1" @@ -621,6 +889,18 @@ anyio = ">=3.6.2,<5" [package.extras] full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] +[[package]] +name = "types-deprecated" +version = "1.2.15.20250304" +description = "Typing stubs for Deprecated" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "types_deprecated-1.2.15.20250304-py3-none-any.whl", hash = "sha256:86a65aa550ea8acf49f27e226b8953288cd851de887970fbbdf2239c116c3107"}, + {file = "types_deprecated-1.2.15.20250304.tar.gz", hash = "sha256:c329030553029de5cc6cb30f269c11f4e00e598c4241290179f63cda7d33f719"}, +] + [[package]] name = "typing-extensions" version = "4.12.2" @@ -646,7 +926,7 @@ files = [ ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -668,9 +948,199 @@ click = ">=7.0" h11 = ">=0.8" [package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] +standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "websockets" +version = "15.0.1" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b"}, + {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205"}, + {file = "websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a"}, + {file = "websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e"}, + {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf"}, + {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb"}, + {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d"}, + {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9"}, + {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c"}, + {file = "websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256"}, + {file = "websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41"}, + {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431"}, + {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57"}, + {file = "websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905"}, + {file = "websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562"}, + {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792"}, + {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413"}, + {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8"}, + {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3"}, + {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf"}, + {file = "websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85"}, + {file = "websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065"}, + {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3"}, + {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665"}, + {file = "websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2"}, + {file = "websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215"}, + {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5"}, + {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65"}, + {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe"}, + {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4"}, + {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597"}, + {file = "websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9"}, + {file = "websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7"}, + {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931"}, + {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675"}, + {file = "websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151"}, + {file = "websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22"}, + {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f"}, + {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8"}, + {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375"}, + {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d"}, + {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4"}, + {file = "websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa"}, + {file = "websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561"}, + {file = "websockets-15.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f4c04ead5aed67c8a1a20491d54cdfba5884507a48dd798ecaf13c74c4489f5"}, + {file = "websockets-15.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abdc0c6c8c648b4805c5eacd131910d2a7f6455dfd3becab248ef108e89ab16a"}, + {file = "websockets-15.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a625e06551975f4b7ea7102bc43895b90742746797e2e14b70ed61c43a90f09b"}, + {file = "websockets-15.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d591f8de75824cbb7acad4e05d2d710484f15f29d4a915092675ad3456f11770"}, + {file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47819cea040f31d670cc8d324bb6435c6f133b8c7a19ec3d61634e62f8d8f9eb"}, + {file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac017dd64572e5c3bd01939121e4d16cf30e5d7e110a119399cf3133b63ad054"}, + {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4a9fac8e469d04ce6c25bb2610dc535235bd4aa14996b4e6dbebf5e007eba5ee"}, + {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363c6f671b761efcb30608d24925a382497c12c506b51661883c3e22337265ed"}, + {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2034693ad3097d5355bfdacfffcbd3ef5694f9718ab7f29c29689a9eae841880"}, + {file = "websockets-15.0.1-cp39-cp39-win32.whl", hash = "sha256:3b1ac0d3e594bf121308112697cf4b32be538fb1444468fb0a6ae4feebc83411"}, + {file = "websockets-15.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7643a03db5c95c799b89b31c036d5f27eeb4d259c798e878d6937d71832b1e4"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7f493881579c90fc262d9cdbaa05a6b54b3811c2f300766748db79f098db9940"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:47b099e1f4fbc95b701b6e85768e1fcdaf1630f3cbe4765fa216596f12310e2e"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f2b6de947f8c757db2db9c71527933ad0019737ec374a8a6be9a956786aaf9"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d08eb4c2b7d6c41da6ca0600c077e93f5adcfd979cd777d747e9ee624556da4b"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b826973a4a2ae47ba357e4e82fa44a463b8f168e1ca775ac64521442b19e87f"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:21c1fa28a6a7e3cbdc171c694398b6df4744613ce9b36b1a498e816787e28123"}, + {file = "websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f"}, + {file = "websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee"}, +] + +[[package]] +name = "wrapt" +version = "1.17.2" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, + {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, + {file = "wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62"}, + {file = "wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563"}, + {file = "wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72"}, + {file = "wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317"}, + {file = "wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9"}, + {file = "wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9"}, + {file = "wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504"}, + {file = "wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a"}, + {file = "wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f"}, + {file = "wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555"}, + {file = "wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c803c401ea1c1c18de70a06a6f79fcc9c5acfc79133e9869e730ad7f8ad8ef9"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f917c1180fdb8623c2b75a99192f4025e412597c50b2ac870f156de8fb101119"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ecc840861360ba9d176d413a5489b9a0aff6d6303d7e733e2c4623cfa26904a6"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb87745b2e6dc56361bfde481d5a378dc314b252a98d7dd19a651a3fa58f24a9"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58455b79ec2661c3600e65c0a716955adc2410f7383755d537584b0de41b1d8a"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4e42a40a5e164cbfdb7b386c966a588b1047558a990981ace551ed7e12ca9c2"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:91bd7d1773e64019f9288b7a5101f3ae50d3d8e6b1de7edee9c2ccc1d32f0c0a"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bb90fb8bda722a1b9d48ac1e6c38f923ea757b3baf8ebd0c82e09c5c1a0e7a04"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:08e7ce672e35efa54c5024936e559469436f8b8096253404faeb54d2a878416f"}, + {file = "wrapt-1.17.2-cp38-cp38-win32.whl", hash = "sha256:410a92fefd2e0e10d26210e1dfb4a876ddaf8439ef60d6434f21ef8d87efc5b7"}, + {file = "wrapt-1.17.2-cp38-cp38-win_amd64.whl", hash = "sha256:95c658736ec15602da0ed73f312d410117723914a5c91a14ee4cdd72f1d790b3"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9"}, + {file = "wrapt-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb"}, + {file = "wrapt-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb"}, + {file = "wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8"}, + {file = "wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3"}, +] + +[[package]] +name = "xrpl-py" +version = "4.1.0" +description = "A complete Python library for interacting with the XRP ledger" +optional = false +python-versions = "<4.0,>=3.8.1" +groups = ["main"] +files = [ + {file = "xrpl_py-4.1.0-py3-none-any.whl", hash = "sha256:4cbe6eef92b78813713d5029518b16ee8e826389056a2e6a8b3866cb5c332fa4"}, + {file = "xrpl_py-4.1.0.tar.gz", hash = "sha256:be07189dfaaf0472a9d12919a5985329b588eb33f4c9ec099d7c3e470e735d1c"}, +] + +[package.dependencies] +base58 = ">=2.1.0,<3.0.0" +Deprecated = ">=1.2.13,<2.0.0" +ECPy = ">=1.2.5,<2.0.0" +httpx = ">=0.18.1,<0.29.0" +pycryptodome = ">=3.16.0,<4.0.0" +types-Deprecated = ">=1.2.9,<2.0.0" +typing-extensions = ">=4.2.0,<5.0.0" +websockets = ">=11" [metadata] lock-version = "2.1" python-versions = "^3.12" -content-hash = "d74e83d5f195da6bfc3f4673248a32382a4db74d881df9c782e0d9f0d7481f09" +content-hash = "42f1495d3adb6962dbed76ff77e42925b51e93ae08c7a343e88de964e622bd6e" diff --git a/pyproject.toml b/pyproject.toml index 16507bf..dd98bb9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,8 @@ botocore = "^1.37.16" dotenv = "^0.9.9" httpx = "^0.28.1" pymongo = "^4.11.3" +sqlalchemy = "^2.0.39" +xrpl-py = "^4.1.0" [build-system] requires = ["poetry-core"] diff --git a/src/main/nft/nft_Info_dto.py b/src/main/nft/nft_Info_dto.py index f7e8aa3..688cb12 100644 --- a/src/main/nft/nft_Info_dto.py +++ b/src/main/nft/nft_Info_dto.py @@ -1,5 +1,6 @@ from pydantic import BaseModel from typing import Dict +from datetime import datetime # 필요한 필드들(Response) # 1. user-wallet id @@ -15,10 +16,10 @@ class NftResponseDTO(BaseModel): nft_id: str nft_grade: str transaction_hash: str - nft_metadata: Dict[str, str] + nft_metadata_uri: str issued_at: datetime expired_at: datetime - + # db 저자에 필요한 필드 # 1. nft-id @@ -27,4 +28,13 @@ class NftResponseDTO(BaseModel): # 4. meta_uri # nft의 상세 정보(이미지, 설명, 속성 등) 외부 저장소(s3)에 보관 # 5. 발급, 만료일 +class NftSaveDTO(BaseModel): + id: str + user_wallet: str + nft_id: str + nft_grade: str + transaction_hash: str + nft_metadata_uri: str + issued_at: datetime + expired_at: datetime diff --git a/src/main/nft/nft_controller.py b/src/main/nft/nft_controller.py new file mode 100644 index 0000000..c67b352 --- /dev/null +++ b/src/main/nft/nft_controller.py @@ -0,0 +1,25 @@ +# controller/nft_controller.py + +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session +from typing import List + +#from database import get_db +#from nft_issue_service import process_nft_issuance_with_response +#from nft_ +from src.main.nft.nft_issue_service import test_process_nft_issuance_without_db +from src.main.nft.nft_Info_dto import NftResponseDTO + +router = APIRouter( + prefix="/nft", + tags=["NFT 발급"] +) + +@router.post("/issue", response_model=List[NftResponseDTO]) +async def issue_nfts(): + try: + #results = await process_nft_issuance_with_response() + results = await test_process_nft_issuance_without_db() + return results + except Exception as e: + raise HTTPException(status_code=500, detail=f"NFT 발급 중 오류 발생: {str(e)}") diff --git a/src/main/nft/nft_issue_service.py b/src/main/nft/nft_issue_service.py index eec16bd..aed4445 100644 --- a/src/main/nft/nft_issue_service.py +++ b/src/main/nft/nft_issue_service.py @@ -11,3 +11,259 @@ #4. nft 발급 구현(병렬 처리) #5. db에 저장 #6. return 값은 void로 하는데 checking 용으로 해보기 +# service/nft_service.py + +from src.main.nft.nft_repository import get_users_with_point_over +#from repository.nft_repository import save_nfts_bulk +from src.main.nft.nft_model import NFTRecord +from src.main.nft.nft_Info_dto import NftResponseDTO +from datetime import datetime, timezone, timedelta +import asyncio + +# XRPL 관련 모듈 +#pip install xrpl-py + python -m poetry add xrpl.py +from xrpl.asyncio.transaction import submit_and_wait +from xrpl.models.transactions.nftoken_mint import NFTokenMint, NFTokenMintFlag +from xrpl.asyncio.wallet import generate_faucet_wallet +from xrpl.wallet import Wallet +from xrpl.clients import JsonRpcClient +import json + +# service 내부에서 임시 유저 생성 +# 예시 User 클래스 (있다고 가정) +class User: + def __init__(self, wallet, point): + self.wallet = wallet + self.point = point + +# 개별 사용자 생성 +user1 = User(wallet="rWallet1", point=1800) +user2 = User(wallet="rWallet2", point=1300) +user3 = User(wallet="rWallet3", point=800) + +# 리스트로 묶어서 사용 +users = [user1, user2, user3] + + +# XRPL 설정 +print("Connecting to Testnet...") +JSON_RPC_URL = "https://s.devnet.rippletest.net:51234/" +client = JsonRpcClient(JSON_RPC_URL) + + +#등급 조건건 +GRADE_RULES = [ + {"grade": "platinum", "percent": 0.03, "min_point": 1500}, + {"grade": "gold", "percent": 0.10, "min_point": 1000}, + {"grade": "silver", "percent": 0.40, "min_point": 500}, + {"grade": "bronze", "percent": 1, "min_point": 0} +] + +# 등급별 Taxon 값 설정 (NFT 분류 번호) +NFT_GRADE_TAXON = { + "platinum": 4, + "gold": 3, + "silver": 2, + "bronze": 1 # 기본값 +} + +#metadataUri +NFT_METADATA_URI = { + "platinum" : "일단 플래티넘 주소", + "gold": "일단 골드 주소", + "silver" : "일단 실버 주소", + "bronze": "일단 브론즈즈 주소" +} + +# 테스트 지갑 생성 (테스트넷용) +# generate~ 함수는 내부적으로 비동기 함수임(asyncio.run()) -> ㅇ미 비동기에서 비동기로 겹침침 +async def generate_wallet (): + wallet = await generate_faucet_wallet(client=client) + return wallet, wallet.address + +#포인트별로 필터링 함수 +def filter_users_by_rank_and_point(users, start_idx, end_idx, min_point): + return [ + #전체 사용자 중 + # 특정 범위의 사용자 중 최소 포인트 이상인 사람만 필터링해서 반환환 + user + for i, user in enumerate(users[start_idx:end_idx]) + if user.point >= min_point + ] + +# XRPL 기반 NFT 민팅(XRPL 에서 NFT를 실제로 발급(MINt) 하는 핵심 함수) +# 개별 사용자 1명에게 NFT를 발급하는 함수 +async def mint_nft_on_xrpl(user, grade, issuser_wallet, issuserAddr): + # 발급일 = 현재 날짜 + 자정 + issued_at = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) + # 만료일 = 6개월 자정 + expired_at = issued_at + timedelta(days=180) + + # now = datetime.utcnow() + # NFT 발급을 위한 트랜잭션 객체 생성 + # 트랜잭션 : 블록체인에서 일어나는 모든 행동을 기록한 데이터 조각 + # XRPL노드로 전송되면 블록체인에 기록 되고 실제 NFT가 생성됨됨 + # ex) A가 B에게 10코인을 보냄 등.,, + mint_tx = NFTokenMint( + account=issuserAddr, # 발급자(대표자) + nftoken_taxon=NFT_GRADE_TAXON[grade], # NFT 분류 Id (의미를 부여) grade에 따라 자동 적용용 + flags=NFTokenMintFlag.TF_TRANSFERABLE,# NFT 가 전송 가능한 것인지 여부 설정정 + uri=NFT_METADATA_URI[grade].encode("utf-8").hex() # NFT의 이미지, 무슨 등급, 이름/설명/속성 등을 설명해주는 정보 묶음 + ) + + try: + # xrpl에서 해당 정보를 트랜잭션에 넘기기기 + # submit_and_wait(transaction, client(노드 클라이언트 자체), wallet(서명에 사용될 지갑 객체체)) + response = await submit_and_wait(transaction=mint_tx, client=client, wallet=issuser_wallet) + # transaction 처리 결과 + result = response.result + + #print("result" + result) + + # 트랜잭션 해시 추출(트랜잭션 고유 ID) -> 블록 탐색기에서 이 해시로 NFT 상태 조회 가능 + tx_hash = result['hash'] + if not tx_hash or not isinstance(tx_hash, str): + raise Exception("트랜잭션 해시를 정상적으로 받지 못했습니다.") + + # NFT ID 파싱을 위함 + nft_id = "" + + + #print("트랜잭션 결과:\n", json.dumps(result, indent=2)) + + + # XRPL 은 발급된 NFT의 ID를 직접 반환 x + # -> AffectedNodes에서 새로 생성된 노드를 찾아서 안의 ID를 추출해야함함 + #for node in result['meta']['AffectedNodes']: + # if "CreatedNode" in list(node.keys())[0]: + # created = node['CreatedNode']['NewFields'] + # if "NFToken" in created.get("NFTokens", [{}])[0]: + # nft_id = created["NFTokens"][0]["NFToken"]["NFTokenID"] + # break + + for node in result['meta']['AffectedNodes']: + node_data = node.get("CreatedNode") or node.get("ModifiedNode") + if node_data and node_data["LedgerEntryType"] == "NFTokenPage": + tokens = node_data.get("NewFields", {}).get("NFTokens") or node_data.get("FinalFields", {}).get("NFTokens") + if tokens: + for token in tokens: + nft = token.get("NFToken") + if nft and "NFTokenID" in nft: + nft_id = nft["NFTokenID"] + break + if nft_id: + break + + + + if not nft_id: + raise Exception("NFT ID 추출 실패") + + return { + "user_wallet": user.wallet, + "point": user.point, + "nft_id": nft_id, + "nft_grade": grade, + "transaction_hash": tx_hash, + "nft_metadata_uri": NFT_METADATA_URI[grade].encode("utf-8").hex(), + "issued_at": issued_at, + "expired_at": expired_at + } + + except Exception as e: + print(f"NFT 민팅 실패: {e}") + return None + +# 등급별 민팅(여러 사용자에게 등급별로 NFT를 발급해주는 함수) +async def mint_all_nfts(users, issuser_wallet, issuserAddr): + total = len(users) # user가 몇명인지지 + used_indices = 0 # 등급별로 인덱스 나누기 위한 변수 + results = [] # 민팅 결과 담는 곳곳 + + # 등급 조건 별로 for 문 돌리기기 + for rule in GRADE_RULES: + # 퍼센트가 몇 등인지를 계산 + size = max(1, int(total * rule["percent"])) + # 후보자 필터링(상위 인덱스 구간 내에서 최소 포인트 이상인 유저만) + # fitler_user_by_rank_and_point(user, start_idx, end_idx, point) + candidates = filter_users_by_rank_and_point( + users, used_indices, min(used_indices + size, total), rule["min_point"] + ) + # 다음 등급의 start_idx 설정정 + used_indices += size + + # 후보자 리스트에서 nft 발급 준비 + # mint_nft_on_xrpl(user, grade) + # tasks = [] + # for user in candidates : + # task = mint_nft_on_xrpl(user, rule["grade"]) + # tasks.append(task) + tasks = [mint_nft_on_xrpl(user, rule["grade"], issuser_wallet, issuserAddr) for user in candidates] + # nft 발급 결과를 비동기식으로 minted에 저장(minted) + minted = await asyncio.gather(*tasks) # 위 모든 동작을 동시에 실행행 + + # 발급 실패한 사람 제외 results 에 저장해두기 + # extend([저장된 발급 결과에 None 이라 저장되어있는 것]) + results.extend([r for r in minted if r is not None]) + + return results + +# 전체 로직 +''' +async def process_nft_issuance_with_response(session) -> list[NftResponseDTO]: + users = get_users_with_point_over(session, 500) + users.sort(key=lambda u: u.point, reverse=True) + + mint_results = await mint_all_nfts(users) + + # DB 저장용 객체 변환 + nft_records = [ + NFTRecord( + nft_id=result["nft_id"], + user_wallet=result["user_wallet"], + nft_grade=result["nft_grade"], + transaction_hash=result["transaction_hash"], + metadata_uri=result["nft_metadata_uri"], + issued_at=result["issued_at"], + expires_at=result["expired_at"] + ) for result in mint_results + ] + save_nfts_bulk(session, nft_records) + + # DTO 변환 + return [ + NftResponseDTO( + user_wallet_id=r["user_wallet"], + point=r["point"], + nft_id=r["nft_id"], + nft_grade=r["nft_grade"], + transaction_hash=r["transaction_hash"], + nft_metadata_uri=r["nft_metadata_uri"], + issued_at=r["issued_at"], + expired_at=r["expired_at"] + ) for r in mint_results + ] +''' + +async def test_process_nft_issuance_without_db() -> list[NftResponseDTO]: + issuser_wallet, issuserAddr = await generate_wallet() + + # 포인트 별로 내림차순순 + users.sort(key=lambda u: u.point, reverse=True) + # 모든 사용자 nft 발급 요청하기 + mint_results = await mint_all_nfts(users, issuser_wallet, issuserAddr) + + return [ + NftResponseDTO( + user_wallet_id=r["user_wallet"], + point=r["point"], + nft_id=r["nft_id"], + nft_grade=r["nft_grade"], + transaction_hash=r["transaction_hash"], + nft_metadata_uri=r["nft_metadata_uri"], + issued_at=r["issued_at"], + expired_at=r["expired_at"] + ) + for r in mint_results + ] + diff --git a/src/main/nft/nft_model.py b/src/main/nft/nft_model.py new file mode 100644 index 0000000..bd27ee6 --- /dev/null +++ b/src/main/nft/nft_model.py @@ -0,0 +1,18 @@ +##entity 같은 역할입니다. +from sqlalchemy import Column, String, Integer, DateTime +from sqlalchemy.ext.declarative import declarative_base +import datetime + +Base = declarative_base() + +class NFTRecord(Base): + __tablename__ = "nft_records" + + id = Column(Integer, primary_key=True, autoincrement=True) + user_wallet = Column(String, index=True, nullable=False) + nft_id = Column(String, unique=True, nullable=False) + nft_grade = Column(String, nullable=False) + transaction_hash = Column(String, unique=True, nullable=False) + nft_metadata_uri = Column(String, nullable=True) + issued_at = Column(DateTime, default=datetime.datetime.utcnow) + expires_at = Column(DateTime, nullable=True) diff --git a/src/main/nft/nft_repository.py b/src/main/nft/nft_repository.py new file mode 100644 index 0000000..fa01366 --- /dev/null +++ b/src/main/nft/nft_repository.py @@ -0,0 +1,12 @@ +from sqlalchemy.orm import Session +from src.main.nft.nft_model import NFTRecord +from datetime import datetime + +# repository/user_repository.py +def get_users_with_point_over(session, min_point: int): + return session.query(User).filter(User.point >= min_point).all() + +# repository/nft_repository.py +def save_nfts_bulk(session, nft_records: list): + session.bulk_save_objects(nft_records) + session.commit() diff --git a/src/router.py b/src/router.py index 9172beb..dee1a5b 100644 --- a/src/router.py +++ b/src/router.py @@ -1,10 +1,11 @@ from fastapi import APIRouter from src.main.health.router import HealthAPIRouter - +from src.main.nft import nft_controller as NftAPIRouter router = APIRouter( prefix="", ) -router.include_router(HealthAPIRouter.router) \ No newline at end of file +router.include_router(HealthAPIRouter.router) +router.include_router(NftAPIRouter.router) \ No newline at end of file From e21720898e431d65e052e51b962bae1ab6e047f0 Mon Sep 17 00:00:00 2001 From: seoyeon0103 Date: Sat, 22 Mar 2025 18:18:08 +0900 Subject: [PATCH 04/10] =?UTF-8?q?Refactor:=20userRepostiory=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/users/repository/UserRepository.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/users/repository/UserRepository.py b/src/main/users/repository/UserRepository.py index 70547a8..4244927 100644 --- a/src/main/users/repository/UserRepository.py +++ b/src/main/users/repository/UserRepository.py @@ -17,8 +17,8 @@ def create_or_update_wallet(self, user_id: str, wallet_address: str) -> dict: if existing_wallet: self.wallets_collection.update_one({"user_id": user_id}, {"$set": {"address": wallet_address}}) - return {"message": "Wallet updated", "user_id": user_id, "wallet_address": wallet_address} + return {"message": "Wallet updated", "user_id": user_id, "wallet_address": wallet_address, "point": existing_wallet.get("point", 0)} else: - self.wallets_collection.insert_one({"user_id": user_id, "address": wallet_address}) - return {"message": "Wallet created", "user_id": user_id, "wallet_address": wallet_address} \ No newline at end of file + self.wallets_collection.insert_one({"user_id": user_id, "address": wallet_address, "point": 0}) + return {"message": "Wallet created", "user_id": user_id, "wallet_address": wallet_address, "point": 0} \ No newline at end of file From 1434810227a6ab9915b0265c936ff31f91a5f99f Mon Sep 17 00:00:00 2001 From: seoyeon0103 Date: Sat, 22 Mar 2025 18:32:52 +0900 Subject: [PATCH 05/10] =?UTF-8?q?Feat:=20user=5Finfo=5Fdto=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/nft/nft_user_info.py | 11 +++++++++++ src/main/users/service/UserService.py | 2 +- src/router.py | 6 ------ 3 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 src/main/nft/nft_user_info.py diff --git a/src/main/nft/nft_user_info.py b/src/main/nft/nft_user_info.py new file mode 100644 index 0000000..cc8d9e6 --- /dev/null +++ b/src/main/nft/nft_user_info.py @@ -0,0 +1,11 @@ +from pydantic import BaseModel +from typing import Dict +from datetime import datetime + +class User: + def __init__(self, wallet: str, point: int): + self.wallet = wallet + self.point = point + + def __repr__(self): + return f"User(wallet={self.wallet}, point={self.point})" \ No newline at end of file diff --git a/src/main/users/service/UserService.py b/src/main/users/service/UserService.py index e0bd077..f004c82 100644 --- a/src/main/users/service/UserService.py +++ b/src/main/users/service/UserService.py @@ -13,4 +13,4 @@ def get_wallets(self, user_id: str): return wallets def add_wallet(self, user_id: str, wallet_address: str): - return self.user_repository.create_or_update_wallet(user_id, wallet_address, point = 0) \ No newline at end of file + return self.user_repository.create_or_update_wallet(user_id, wallet_address, point=0) \ No newline at end of file diff --git a/src/router.py b/src/router.py index 988f36c..bbe6f65 100644 --- a/src/router.py +++ b/src/router.py @@ -1,20 +1,14 @@ from fastapi import APIRouter from src.main.health.router import HealthAPIRouter -<<<<<<< HEAD from src.main.nft import nft_controller as NftAPIRouter -======= from src.main.users.router import UserAPIRouter ->>>>>>> a0655f32633b81938852724a40fe98e4df2ebfd7 router = APIRouter( prefix="", ) router.include_router(HealthAPIRouter.router) -<<<<<<< HEAD router.include_router(NftAPIRouter.router) -======= router.include_router(UserAPIRouter.router) ->>>>>>> a0655f32633b81938852724a40fe98e4df2ebfd7 From 91602a4f3a6228b946b35ce83e8052abee514659 Mon Sep 17 00:00:00 2001 From: seoyeon0103 Date: Sat, 22 Mar 2025 18:39:15 +0900 Subject: [PATCH 06/10] =?UTF-8?q?Feat:=20get=5Fall=5Fuser=20function=20?= =?UTF-8?q?=EC=B6=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/users/repository/UserRepository.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/users/repository/UserRepository.py b/src/main/users/repository/UserRepository.py index 4244927..d2d1643 100644 --- a/src/main/users/repository/UserRepository.py +++ b/src/main/users/repository/UserRepository.py @@ -1,5 +1,6 @@ from typing import Optional from src.main.config.mongodb import get_mongo_client +from src.main.nft.nft_Info_dto import User class UserRepository: def __init__(self): @@ -21,4 +22,10 @@ def create_or_update_wallet(self, user_id: str, wallet_address: str) -> dict: else: self.wallets_collection.insert_one({"user_id": user_id, "address": wallet_address, "point": 0}) - return {"message": "Wallet created", "user_id": user_id, "wallet_address": wallet_address, "point": 0} \ No newline at end of file + return {"message": "Wallet created", "user_id": user_id, "wallet_address": wallet_address, "point": 0} + + + def get_all_user(self) -> list[User]: + docs = self.wallets_collection.find({}, {"id":0, "address":1, "point": 1}) + users = [User(wallect=doc["address"], point=doc.get("point", 0)) for doc in docs] + return users From aee1a7d50bc7097fdca54aaa0d1f3944b1fd288c Mon Sep 17 00:00:00 2001 From: seoyeon0103 Date: Sat, 22 Mar 2025 19:04:49 +0900 Subject: [PATCH 07/10] =?UTF-8?q?Refactor:=20db=20=EC=97=B0=EA=B2=B0=20->?= =?UTF-8?q?=20db=20=EC=A0=80=EC=9E=A5=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EA=B5=AC=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/nft/nft_issue_service.py | 81 +++++++++++++++++-------------- src/main/nft/nft_model.py | 4 +- src/main/nft/nft_repository.py | 29 ++++++++--- 3 files changed, 67 insertions(+), 47 deletions(-) diff --git a/src/main/nft/nft_issue_service.py b/src/main/nft/nft_issue_service.py index aed4445..0b92e0e 100644 --- a/src/main/nft/nft_issue_service.py +++ b/src/main/nft/nft_issue_service.py @@ -17,6 +17,8 @@ #from repository.nft_repository import save_nfts_bulk from src.main.nft.nft_model import NFTRecord from src.main.nft.nft_Info_dto import NftResponseDTO +from src.main.users.repository.UserRepository import get_all_user +from src.main.nft.nft_repository import save_nfts_bulk from datetime import datetime, timezone, timedelta import asyncio @@ -31,18 +33,20 @@ # service 내부에서 임시 유저 생성 # 예시 User 클래스 (있다고 가정) -class User: - def __init__(self, wallet, point): - self.wallet = wallet - self.point = point +# class User: +# def __init__(self, wallet, point): +# self.wallet = wallet +# self.point = point -# 개별 사용자 생성 -user1 = User(wallet="rWallet1", point=1800) -user2 = User(wallet="rWallet2", point=1300) -user3 = User(wallet="rWallet3", point=800) +# # 개별 사용자 생성 +# user1 = User(wallet="rWallet1", point=1800) +# user2 = User(wallet="rWallet2", point=1300) +# user3 = User(wallet="rWallet3", point=800) -# 리스트로 묶어서 사용 -users = [user1, user2, user3] +# # 리스트로 묶어서 사용 +# users = [user1, user2, user3] + +users = get_all_user() # XRPL 설정 @@ -209,12 +213,15 @@ async def mint_all_nfts(users, issuser_wallet, issuserAddr): return results # 전체 로직 -''' -async def process_nft_issuance_with_response(session) -> list[NftResponseDTO]: - users = get_users_with_point_over(session, 500) - users.sort(key=lambda u: u.point, reverse=True) - mint_results = await mint_all_nfts(users) +async def process_nft_issuance_with_response() -> list[NftResponseDTO]: + issuser_wallet, issuserAddr = await generate_wallet() + + # 포인트 별로 내림차순순 + users.sort(key=lambda u: u.point, reverse=True) + + # 모든 사용자 nft 발급 요청하기 + mint_results = await mint_all_nfts(users, issuser_wallet, issuserAddr) # DB 저장용 객체 변환 nft_records = [ @@ -228,7 +235,7 @@ async def process_nft_issuance_with_response(session) -> list[NftResponseDTO]: expires_at=result["expired_at"] ) for result in mint_results ] - save_nfts_bulk(session, nft_records) + save_nfts_bulk(nft_records) # DTO 변환 return [ @@ -243,27 +250,27 @@ async def process_nft_issuance_with_response(session) -> list[NftResponseDTO]: expired_at=r["expired_at"] ) for r in mint_results ] -''' -async def test_process_nft_issuance_without_db() -> list[NftResponseDTO]: - issuser_wallet, issuserAddr = await generate_wallet() - # 포인트 별로 내림차순순 - users.sort(key=lambda u: u.point, reverse=True) - # 모든 사용자 nft 발급 요청하기 - mint_results = await mint_all_nfts(users, issuser_wallet, issuserAddr) - - return [ - NftResponseDTO( - user_wallet_id=r["user_wallet"], - point=r["point"], - nft_id=r["nft_id"], - nft_grade=r["nft_grade"], - transaction_hash=r["transaction_hash"], - nft_metadata_uri=r["nft_metadata_uri"], - issued_at=r["issued_at"], - expired_at=r["expired_at"] - ) - for r in mint_results - ] +# async def test_process_nft_issuance_without_db() -> list[NftResponseDTO]: +# issuser_wallet, issuserAddr = await generate_wallet() + +# # 포인트 별로 내림차순순 +# users.sort(key=lambda u: u.point, reverse=True) +# # 모든 사용자 nft 발급 요청하기 +# mint_results = await mint_all_nfts(users, issuser_wallet, issuserAddr) + +# return [ +# NftResponseDTO( +# user_wallet_id=r["user_wallet"], +# point=r["point"], +# nft_id=r["nft_id"], +# nft_grade=r["nft_grade"], +# transaction_hash=r["transaction_hash"], +# nft_metadata_uri=r["nft_metadata_uri"], +# issued_at=r["issued_at"], +# expired_at=r["expired_at"] +# ) +# for r in mint_results +# ] diff --git a/src/main/nft/nft_model.py b/src/main/nft/nft_model.py index bd27ee6..877e4cf 100644 --- a/src/main/nft/nft_model.py +++ b/src/main/nft/nft_model.py @@ -14,5 +14,5 @@ class NFTRecord(Base): nft_grade = Column(String, nullable=False) transaction_hash = Column(String, unique=True, nullable=False) nft_metadata_uri = Column(String, nullable=True) - issued_at = Column(DateTime, default=datetime.datetime.utcnow) - expires_at = Column(DateTime, nullable=True) + issued_at = Column(DateTime, nullable=False) + expires_at = Column(DateTime, nullable=False) diff --git a/src/main/nft/nft_repository.py b/src/main/nft/nft_repository.py index fa01366..8d5a959 100644 --- a/src/main/nft/nft_repository.py +++ b/src/main/nft/nft_repository.py @@ -1,12 +1,25 @@ -from sqlalchemy.orm import Session +from src.main.config.mongodb import get_mongo_client from src.main.nft.nft_model import NFTRecord from datetime import datetime -# repository/user_repository.py -def get_users_with_point_over(session, min_point: int): - return session.query(User).filter(User.point >= min_point).all() - # repository/nft_repository.py -def save_nfts_bulk(session, nft_records: list): - session.bulk_save_objects(nft_records) - session.commit() +def save_nfts_bulk(nft_records: list): + client = get_mongo_client() + db = client['xrpedia-data'] + nft_collection = db['nft_records'] + + docs = [ + { + "nft_id": r.nft_id, + "user_wallet": r.user_wallet, + "nft_grade": r.nft_grade, + "transaction_hash": r.transaction_hash, + "metadata_uri": r.metadata_uri, + "issued_at": r.issued_at, + "expires_at": r.expires_at, + } + for r in nft_records + ] + + if docs: + nft_collection.insert_many(docs) \ No newline at end of file From d21f16a1d7b9efbb8b8739c34281f0bc942047fb Mon Sep 17 00:00:00 2001 From: seoyeon0103 Date: Sun, 23 Mar 2025 02:02:58 +0900 Subject: [PATCH 08/10] =?UTF-8?q?Feat:=20merge=20=EC=A0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 5 --- src/main/nft/nft_controller.py | 7 ++-- src/main/nft/nft_issue_service.py | 36 ++++++++++----------- src/main/users/repository/UserRepository.py | 10 +++--- 4 files changed, 26 insertions(+), 32 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b54caa2..1159da7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,15 +16,10 @@ botocore = "^1.37.16" dotenv = "^0.9.9" httpx = "^0.28.1" pymongo = "^4.11.3" -<<<<<<< HEAD -sqlalchemy = "^2.0.39" -xrpl-py = "^4.1.0" -======= requests = "^2.32.3" [tool.poetry.group.dev.dependencies] pytest = "^8.3.5" ->>>>>>> a0655f32633b81938852724a40fe98e4df2ebfd7 [build-system] requires = ["poetry-core"] diff --git a/src/main/nft/nft_controller.py b/src/main/nft/nft_controller.py index c67b352..37a8597 100644 --- a/src/main/nft/nft_controller.py +++ b/src/main/nft/nft_controller.py @@ -5,9 +5,8 @@ from typing import List #from database import get_db -#from nft_issue_service import process_nft_issuance_with_response #from nft_ -from src.main.nft.nft_issue_service import test_process_nft_issuance_without_db +from src.main.nft.nft_issue_service import process_nft_issuance_with_response from src.main.nft.nft_Info_dto import NftResponseDTO router = APIRouter( @@ -18,8 +17,8 @@ @router.post("/issue", response_model=List[NftResponseDTO]) async def issue_nfts(): try: - #results = await process_nft_issuance_with_response() - results = await test_process_nft_issuance_without_db() + results = await process_nft_issuance_with_response() + #results = await test_process_nft_issuance_without_db() return results except Exception as e: raise HTTPException(status_code=500, detail=f"NFT 발급 중 오류 발생: {str(e)}") diff --git a/src/main/nft/nft_issue_service.py b/src/main/nft/nft_issue_service.py index 0b92e0e..5c5abda 100644 --- a/src/main/nft/nft_issue_service.py +++ b/src/main/nft/nft_issue_service.py @@ -13,11 +13,9 @@ #6. return 값은 void로 하는데 checking 용으로 해보기 # service/nft_service.py -from src.main.nft.nft_repository import get_users_with_point_over -#from repository.nft_repository import save_nfts_bulk from src.main.nft.nft_model import NFTRecord from src.main.nft.nft_Info_dto import NftResponseDTO -from src.main.users.repository.UserRepository import get_all_user +from src.main.users.repository.UserRepository import UserRepository from src.main.nft.nft_repository import save_nfts_bulk from datetime import datetime, timezone, timedelta import asyncio @@ -46,7 +44,8 @@ # # 리스트로 묶어서 사용 # users = [user1, user2, user3] -users = get_all_user() +repo = UserRepository() +users = repo.get_all_user() # XRPL 설정 @@ -85,15 +84,15 @@ async def generate_wallet (): wallet = await generate_faucet_wallet(client=client) return wallet, wallet.address -#포인트별로 필터링 함수 -def filter_users_by_rank_and_point(users, start_idx, end_idx, min_point): - return [ - #전체 사용자 중 - # 특정 범위의 사용자 중 최소 포인트 이상인 사람만 필터링해서 반환환 - user - for i, user in enumerate(users[start_idx:end_idx]) - if user.point >= min_point - ] +# #포인트별로 필터링 함수 +# def filter_users_by_rank_and_point(users, start_idx, end_idx, min_point): +# return [ +# #전체 사용자 중 +# # 특정 범위의 사용자 중 최소 포인트 이상인 사람만 필터링해서 반환환 +# user +# for i, user in enumerate(users[start_idx:end_idx]) +# if user.point >= min_point +# ] # XRPL 기반 NFT 민팅(XRPL 에서 NFT를 실제로 발급(MINt) 하는 핵심 함수) # 개별 사용자 1명에게 NFT를 발급하는 함수 @@ -190,11 +189,11 @@ async def mint_all_nfts(users, issuser_wallet, issuserAddr): size = max(1, int(total * rule["percent"])) # 후보자 필터링(상위 인덱스 구간 내에서 최소 포인트 이상인 유저만) # fitler_user_by_rank_and_point(user, start_idx, end_idx, point) - candidates = filter_users_by_rank_and_point( - users, used_indices, min(used_indices + size, total), rule["min_point"] - ) + # candidates = filter_users_by_rank_and_point( + # users, used_indices, min(used_indices + size, total), rule["min_point"] + # ) # 다음 등급의 start_idx 설정정 - used_indices += size + # used_indices += size # 후보자 리스트에서 nft 발급 준비 # mint_nft_on_xrpl(user, grade) @@ -202,7 +201,8 @@ async def mint_all_nfts(users, issuser_wallet, issuserAddr): # for user in candidates : # task = mint_nft_on_xrpl(user, rule["grade"]) # tasks.append(task) - tasks = [mint_nft_on_xrpl(user, rule["grade"], issuser_wallet, issuserAddr) for user in candidates] + # tasks = [mint_nft_on_xrpl(user, rule["grade"], issuser_wallet, issuserAddr) for user in candidates] + tasks = [mint_nft_on_xrpl(user, rule["grade"], issuser_wallet, issuserAddr) for user in users] # nft 발급 결과를 비동기식으로 minted에 저장(minted) minted = await asyncio.gather(*tasks) # 위 모든 동작을 동시에 실행행 diff --git a/src/main/users/repository/UserRepository.py b/src/main/users/repository/UserRepository.py index d2d1643..2002381 100644 --- a/src/main/users/repository/UserRepository.py +++ b/src/main/users/repository/UserRepository.py @@ -1,6 +1,6 @@ from typing import Optional from src.main.config.mongodb import get_mongo_client -from src.main.nft.nft_Info_dto import User +from src.main.nft.nft_user_info import User class UserRepository: def __init__(self): @@ -12,7 +12,7 @@ def find_wallets_by_user_id(self, user_id: str) -> list: wallets = list(self.wallets_collection.find({"user_id": user_id}, {"_id": 0})) return wallets if wallets else [] - def create_or_update_wallet(self, user_id: str, wallet_address: str) -> dict: + def create_or_update_wallet(self, user_id: str, wallet_address: str, point:int = 0) -> dict: existing_wallet = self.wallets_collection.find_one({"user_id": user_id}) if existing_wallet: @@ -22,10 +22,10 @@ def create_or_update_wallet(self, user_id: str, wallet_address: str) -> dict: else: self.wallets_collection.insert_one({"user_id": user_id, "address": wallet_address, "point": 0}) - return {"message": "Wallet created", "user_id": user_id, "wallet_address": wallet_address, "point": 0} + return {"message": "Wallet created", "user_id": user_id, "wallet_address": wallet_address, "point": point} def get_all_user(self) -> list[User]: - docs = self.wallets_collection.find({}, {"id":0, "address":1, "point": 1}) - users = [User(wallect=doc["address"], point=doc.get("point", 0)) for doc in docs] + docs = self.wallets_collection.find({}, {"_id": 0, "address":1, "point": 1}) + users = [User(wallet=doc["address"], point=doc.get("point", 0)) for doc in docs] return users From 2d6e7a3788bc8fb353fc2ac745df8736c4022a28 Mon Sep 17 00:00:00 2001 From: seoyeon0103 Date: Sun, 23 Mar 2025 02:46:58 +0900 Subject: [PATCH 09/10] =?UTF-8?q?Refactor:=20UserRepoistory=20=EC=97=90=20?= =?UTF-8?q?grade=20=EB=93=B1=EC=9D=84=20db=EC=97=90=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/nft/nft_controller.py | 40 +- src/main/nft/nft_issue_service.py | 500 ++++++++++---------- src/main/users/repository/UserRepository.py | 4 +- src/main/users/service/UserService.py | 5 - src/router.py | 4 +- 5 files changed, 275 insertions(+), 278 deletions(-) diff --git a/src/main/nft/nft_controller.py b/src/main/nft/nft_controller.py index 37a8597..ada644c 100644 --- a/src/main/nft/nft_controller.py +++ b/src/main/nft/nft_controller.py @@ -1,24 +1,24 @@ -# controller/nft_controller.py +# # controller/nft_controller.py -from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy.orm import Session -from typing import List +# from fastapi import APIRouter, Depends, HTTPException +# from sqlalchemy.orm import Session +# from typing import List -#from database import get_db -#from nft_ -from src.main.nft.nft_issue_service import process_nft_issuance_with_response -from src.main.nft.nft_Info_dto import NftResponseDTO +# #from database import get_db +# #from nft_ +# from src.main.nft.nft_issue_service import process_nft_issuance_with_response +# from src.main.nft.nft_Info_dto import NftResponseDTO -router = APIRouter( - prefix="/nft", - tags=["NFT 발급"] -) +# router = APIRouter( +# prefix="/nft", +# tags=["NFT 발급"] +# ) -@router.post("/issue", response_model=List[NftResponseDTO]) -async def issue_nfts(): - try: - results = await process_nft_issuance_with_response() - #results = await test_process_nft_issuance_without_db() - return results - except Exception as e: - raise HTTPException(status_code=500, detail=f"NFT 발급 중 오류 발생: {str(e)}") +# @router.post("/issue", response_model=List[NftResponseDTO]) +# async def issue_nfts(): +# try: +# results = await process_nft_issuance_with_response() +# #results = await test_process_nft_issuance_without_db() +# return results +# except Exception as e: +# raise HTTPException(status_code=500, detail=f"NFT 발급 중 오류 발생: {str(e)}") diff --git a/src/main/nft/nft_issue_service.py b/src/main/nft/nft_issue_service.py index 5c5abda..dd40abc 100644 --- a/src/main/nft/nft_issue_service.py +++ b/src/main/nft/nft_issue_service.py @@ -1,265 +1,243 @@ -#1. 포인트를 기준으로 사용자 나열하기 -# 조건) 500포인트 이상인 사람만 걸러서 나열할 것 - -#2. 포인트에 따른 nft 등급 매핑 - -#3.nft 발급 대상자 선정 -# 1) 전체 상위 3퍼센트 + 1500포가 넘는지 checking(저장) -> nft 발급 -# 2) 전체 상위 10퍼 + 상위 3퍼센트 제외 + 1000포가 넘는지 checking(저장) -> nft 발급급 -# 3) 전체 상위 40퍼 + 상위 3 + 상위 10 제외 + 500포 이상 chekcing(저장) -> ntt 발급 - -#4. nft 발급 구현(병렬 처리) -#5. db에 저장 -#6. return 값은 void로 하는데 checking 용으로 해보기 -# service/nft_service.py - -from src.main.nft.nft_model import NFTRecord -from src.main.nft.nft_Info_dto import NftResponseDTO -from src.main.users.repository.UserRepository import UserRepository -from src.main.nft.nft_repository import save_nfts_bulk -from datetime import datetime, timezone, timedelta -import asyncio - -# XRPL 관련 모듈 -#pip install xrpl-py + python -m poetry add xrpl.py -from xrpl.asyncio.transaction import submit_and_wait -from xrpl.models.transactions.nftoken_mint import NFTokenMint, NFTokenMintFlag -from xrpl.asyncio.wallet import generate_faucet_wallet -from xrpl.wallet import Wallet -from xrpl.clients import JsonRpcClient -import json - -# service 내부에서 임시 유저 생성 -# 예시 User 클래스 (있다고 가정) -# class User: -# def __init__(self, wallet, point): -# self.wallet = wallet -# self.point = point - -# # 개별 사용자 생성 -# user1 = User(wallet="rWallet1", point=1800) -# user2 = User(wallet="rWallet2", point=1300) -# user3 = User(wallet="rWallet3", point=800) - -# # 리스트로 묶어서 사용 -# users = [user1, user2, user3] - -repo = UserRepository() -users = repo.get_all_user() - - -# XRPL 설정 -print("Connecting to Testnet...") -JSON_RPC_URL = "https://s.devnet.rippletest.net:51234/" -client = JsonRpcClient(JSON_RPC_URL) - - -#등급 조건건 -GRADE_RULES = [ - {"grade": "platinum", "percent": 0.03, "min_point": 1500}, - {"grade": "gold", "percent": 0.10, "min_point": 1000}, - {"grade": "silver", "percent": 0.40, "min_point": 500}, - {"grade": "bronze", "percent": 1, "min_point": 0} -] - -# 등급별 Taxon 값 설정 (NFT 분류 번호) -NFT_GRADE_TAXON = { - "platinum": 4, - "gold": 3, - "silver": 2, - "bronze": 1 # 기본값 -} - -#metadataUri -NFT_METADATA_URI = { - "platinum" : "일단 플래티넘 주소", - "gold": "일단 골드 주소", - "silver" : "일단 실버 주소", - "bronze": "일단 브론즈즈 주소" -} - -# 테스트 지갑 생성 (테스트넷용) -# generate~ 함수는 내부적으로 비동기 함수임(asyncio.run()) -> ㅇ미 비동기에서 비동기로 겹침침 -async def generate_wallet (): - wallet = await generate_faucet_wallet(client=client) - return wallet, wallet.address - -# #포인트별로 필터링 함수 -# def filter_users_by_rank_and_point(users, start_idx, end_idx, min_point): -# return [ -# #전체 사용자 중 -# # 특정 범위의 사용자 중 최소 포인트 이상인 사람만 필터링해서 반환환 -# user -# for i, user in enumerate(users[start_idx:end_idx]) -# if user.point >= min_point -# ] - -# XRPL 기반 NFT 민팅(XRPL 에서 NFT를 실제로 발급(MINt) 하는 핵심 함수) -# 개별 사용자 1명에게 NFT를 발급하는 함수 -async def mint_nft_on_xrpl(user, grade, issuser_wallet, issuserAddr): - # 발급일 = 현재 날짜 + 자정 - issued_at = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) - # 만료일 = 6개월 자정 - expired_at = issued_at + timedelta(days=180) - - # now = datetime.utcnow() - # NFT 발급을 위한 트랜잭션 객체 생성 - # 트랜잭션 : 블록체인에서 일어나는 모든 행동을 기록한 데이터 조각 - # XRPL노드로 전송되면 블록체인에 기록 되고 실제 NFT가 생성됨됨 - # ex) A가 B에게 10코인을 보냄 등.,, - mint_tx = NFTokenMint( - account=issuserAddr, # 발급자(대표자) - nftoken_taxon=NFT_GRADE_TAXON[grade], # NFT 분류 Id (의미를 부여) grade에 따라 자동 적용용 - flags=NFTokenMintFlag.TF_TRANSFERABLE,# NFT 가 전송 가능한 것인지 여부 설정정 - uri=NFT_METADATA_URI[grade].encode("utf-8").hex() # NFT의 이미지, 무슨 등급, 이름/설명/속성 등을 설명해주는 정보 묶음 - ) - - try: - # xrpl에서 해당 정보를 트랜잭션에 넘기기기 - # submit_and_wait(transaction, client(노드 클라이언트 자체), wallet(서명에 사용될 지갑 객체체)) - response = await submit_and_wait(transaction=mint_tx, client=client, wallet=issuser_wallet) - # transaction 처리 결과 - result = response.result - - #print("result" + result) - - # 트랜잭션 해시 추출(트랜잭션 고유 ID) -> 블록 탐색기에서 이 해시로 NFT 상태 조회 가능 - tx_hash = result['hash'] - if not tx_hash or not isinstance(tx_hash, str): - raise Exception("트랜잭션 해시를 정상적으로 받지 못했습니다.") +# #1. 포인트를 기준으로 사용자 나열하기 +# # 조건) 500포인트 이상인 사람만 걸러서 나열할 것 + +# #2. 포인트에 따른 nft 등급 매핑 + +# #3.nft 발급 대상자 선정 +# # 1) 전체 상위 3퍼센트 + 1500포가 넘는지 checking(저장) -> nft 발급 +# # 2) 전체 상위 10퍼 + 상위 3퍼센트 제외 + 1000포가 넘는지 checking(저장) -> nft 발급급 +# # 3) 전체 상위 40퍼 + 상위 3 + 상위 10 제외 + 500포 이상 chekcing(저장) -> ntt 발급 + +# #4. nft 발급 구현(병렬 처리) +# #5. db에 저장 +# #6. return 값은 void로 하는데 checking 용으로 해보기 +# # service/nft_service.py + +# from src.main.nft.nft_model import NFTRecord +# from src.main.nft.nft_Info_dto import NftResponseDTO +# from src.main.users.repository.UserRepository import UserRepository +# from src.main.nft.nft_repository import save_nfts_bulk +# from datetime import datetime, timezone, timedelta +# import asyncio + +# # XRPL 관련 모듈 +# #pip install xrpl-py + python -m poetry add xrpl.py +# from xrpl.asyncio.transaction import submit_and_wait +# from xrpl.models.transactions.nftoken_mint import NFTokenMint, NFTokenMintFlag +# from xrpl.asyncio.wallet import generate_faucet_wallet +# from xrpl.wallet import Wallet +# from xrpl.clients import JsonRpcClient +# import json + +# # service 내부에서 임시 유저 생성 +# # 예시 User 클래스 (있다고 가정) +# # class User: +# # def __init__(self, wallet, point): +# # self.wallet = wallet +# # self.point = point + +# # # 개별 사용자 생성 +# # user1 = User(wallet="rWallet1", point=1800) +# # user2 = User(wallet="rWallet2", point=1300) +# # user3 = User(wallet="rWallet3", point=800) + +# # # 리스트로 묶어서 사용 +# # users = [user1, user2, user3] + +# repo = UserRepository() +# users = repo.get_all_user() + + +# # XRPL 설정 +# print("Connecting to Testnet...") +# JSON_RPC_URL = "https://s.devnet.rippletest.net:51234/" +# client = JsonRpcClient(JSON_RPC_URL) + + +# #등급 조건건 +# GRADE_RULES = [ +# {"grade": "platinum", "percent": 0.03, "min_point": 1500}, +# {"grade": "gold", "percent": 0.10, "min_point": 1000}, +# {"grade": "silver", "percent": 0.40, "min_point": 500}, +# {"grade": "bronze", "percent": 1, "min_point": 0} +# ] + +# # 등급별 Taxon 값 설정 (NFT 분류 번호) +# NFT_GRADE_TAXON = { +# "platinum": 4, +# "gold": 3, +# "silver": 2, +# "bronze": 1 # 기본값 +# } + +# #metadataUri +# NFT_METADATA_URI = { +# "platinum" : "일단 플래티넘 주소", +# "gold": "일단 골드 주소", +# "silver" : "일단 실버 주소", +# "bronze": "일단 브론즈즈 주소" +# } + +# # 테스트 지갑 생성 (테스트넷용) +# # generate~ 함수는 내부적으로 비동기 함수임(asyncio.run()) -> ㅇ미 비동기에서 비동기로 겹침침 +# async def generate_wallet (): +# wallet = await generate_faucet_wallet(client=client) +# return wallet, wallet.address + +# # #포인트별로 필터링 함수 +# # def filter_users_by_rank_and_point(users, start_idx, end_idx, min_point): +# # return [ +# # #전체 사용자 중 +# # # 특정 범위의 사용자 중 최소 포인트 이상인 사람만 필터링해서 반환환 +# # user +# # for i, user in enumerate(users[start_idx:end_idx]) +# # if user.point >= min_point +# # ] + +# # XRPL 기반 NFT 민팅(XRPL 에서 NFT를 실제로 발급(MINt) 하는 핵심 함수) +# # 개별 사용자 1명에게 NFT를 발급하는 함수 +# async def mint_nft_on_xrpl(user, grade, issuser_wallet, issuserAddr): +# # 발급일 = 현재 날짜 + 자정 +# issued_at = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) +# # 만료일 = 6개월 자정 +# expired_at = issued_at + timedelta(days=180) + +# # now = datetime.utcnow() +# # NFT 발급을 위한 트랜잭션 객체 생성 +# # 트랜잭션 : 블록체인에서 일어나는 모든 행동을 기록한 데이터 조각 +# # XRPL노드로 전송되면 블록체인에 기록 되고 실제 NFT가 생성됨됨 +# # ex) A가 B에게 10코인을 보냄 등.,, +# mint_tx = NFTokenMint( +# account=issuserAddr, # 발급자(대표자) +# nftoken_taxon=NFT_GRADE_TAXON[grade], # NFT 분류 Id (의미를 부여) grade에 따라 자동 적용용 +# flags=NFTokenMintFlag.TF_TRANSFERABLE,# NFT 가 전송 가능한 것인지 여부 설정정 +# uri=NFT_METADATA_URI[grade].encode("utf-8").hex() # NFT의 이미지, 무슨 등급, 이름/설명/속성 등을 설명해주는 정보 묶음 +# ) + +# try: +# # xrpl에서 해당 정보를 트랜잭션에 넘기기기 +# # submit_and_wait(transaction, client(노드 클라이언트 자체), wallet(서명에 사용될 지갑 객체체)) +# response = await submit_and_wait(transaction=mint_tx, client=client, wallet=issuser_wallet) +# # transaction 처리 결과 +# result = response.result + +# #print("result" + result) + +# # 트랜잭션 해시 추출(트랜잭션 고유 ID) -> 블록 탐색기에서 이 해시로 NFT 상태 조회 가능 +# tx_hash = result['hash'] +# if not tx_hash or not isinstance(tx_hash, str): +# raise Exception("트랜잭션 해시를 정상적으로 받지 못했습니다.") - # NFT ID 파싱을 위함 - nft_id = "" +# # NFT ID 파싱을 위함 +# nft_id = "" - #print("트랜잭션 결과:\n", json.dumps(result, indent=2)) +# #print("트랜잭션 결과:\n", json.dumps(result, indent=2)) - # XRPL 은 발급된 NFT의 ID를 직접 반환 x - # -> AffectedNodes에서 새로 생성된 노드를 찾아서 안의 ID를 추출해야함함 - #for node in result['meta']['AffectedNodes']: - # if "CreatedNode" in list(node.keys())[0]: - # created = node['CreatedNode']['NewFields'] - # if "NFToken" in created.get("NFTokens", [{}])[0]: - # nft_id = created["NFTokens"][0]["NFToken"]["NFTokenID"] - # break +# # XRPL 은 발급된 NFT의 ID를 직접 반환 x +# # -> AffectedNodes에서 새로 생성된 노드를 찾아서 안의 ID를 추출해야함함 +# #for node in result['meta']['AffectedNodes']: +# # if "CreatedNode" in list(node.keys())[0]: +# # created = node['CreatedNode']['NewFields'] +# # if "NFToken" in created.get("NFTokens", [{}])[0]: +# # nft_id = created["NFTokens"][0]["NFToken"]["NFTokenID"] +# # break - for node in result['meta']['AffectedNodes']: - node_data = node.get("CreatedNode") or node.get("ModifiedNode") - if node_data and node_data["LedgerEntryType"] == "NFTokenPage": - tokens = node_data.get("NewFields", {}).get("NFTokens") or node_data.get("FinalFields", {}).get("NFTokens") - if tokens: - for token in tokens: - nft = token.get("NFToken") - if nft and "NFTokenID" in nft: - nft_id = nft["NFTokenID"] - break - if nft_id: - break - - - - if not nft_id: - raise Exception("NFT ID 추출 실패") - - return { - "user_wallet": user.wallet, - "point": user.point, - "nft_id": nft_id, - "nft_grade": grade, - "transaction_hash": tx_hash, - "nft_metadata_uri": NFT_METADATA_URI[grade].encode("utf-8").hex(), - "issued_at": issued_at, - "expired_at": expired_at - } - - except Exception as e: - print(f"NFT 민팅 실패: {e}") - return None - -# 등급별 민팅(여러 사용자에게 등급별로 NFT를 발급해주는 함수) -async def mint_all_nfts(users, issuser_wallet, issuserAddr): - total = len(users) # user가 몇명인지지 - used_indices = 0 # 등급별로 인덱스 나누기 위한 변수 - results = [] # 민팅 결과 담는 곳곳 - - # 등급 조건 별로 for 문 돌리기기 - for rule in GRADE_RULES: - # 퍼센트가 몇 등인지를 계산 - size = max(1, int(total * rule["percent"])) - # 후보자 필터링(상위 인덱스 구간 내에서 최소 포인트 이상인 유저만) - # fitler_user_by_rank_and_point(user, start_idx, end_idx, point) - # candidates = filter_users_by_rank_and_point( - # users, used_indices, min(used_indices + size, total), rule["min_point"] - # ) - # 다음 등급의 start_idx 설정정 - # used_indices += size - - # 후보자 리스트에서 nft 발급 준비 - # mint_nft_on_xrpl(user, grade) - # tasks = [] - # for user in candidates : - # task = mint_nft_on_xrpl(user, rule["grade"]) - # tasks.append(task) - # tasks = [mint_nft_on_xrpl(user, rule["grade"], issuser_wallet, issuserAddr) for user in candidates] - tasks = [mint_nft_on_xrpl(user, rule["grade"], issuser_wallet, issuserAddr) for user in users] - # nft 발급 결과를 비동기식으로 minted에 저장(minted) - minted = await asyncio.gather(*tasks) # 위 모든 동작을 동시에 실행행 - - # 발급 실패한 사람 제외 results 에 저장해두기 - # extend([저장된 발급 결과에 None 이라 저장되어있는 것]) - results.extend([r for r in minted if r is not None]) - - return results - -# 전체 로직 - -async def process_nft_issuance_with_response() -> list[NftResponseDTO]: - issuser_wallet, issuserAddr = await generate_wallet() - - # 포인트 별로 내림차순순 - users.sort(key=lambda u: u.point, reverse=True) +# for node in result['meta']['AffectedNodes']: +# node_data = node.get("CreatedNode") or node.get("ModifiedNode") +# if node_data and node_data["LedgerEntryType"] == "NFTokenPage": +# tokens = node_data.get("NewFields", {}).get("NFTokens") or node_data.get("FinalFields", {}).get("NFTokens") +# if tokens: +# for token in tokens: +# nft = token.get("NFToken") +# if nft and "NFTokenID" in nft: +# nft_id = nft["NFTokenID"] +# break +# if nft_id: +# break + + + +# if not nft_id: +# raise Exception("NFT ID 추출 실패") + +# return { +# "user_wallet": user.wallet, +# "point": user.point, +# "nft_id": nft_id, +# "nft_grade": grade, +# "transaction_hash": tx_hash, +# "nft_metadata_uri": NFT_METADATA_URI[grade].encode("utf-8").hex(), +# "issued_at": issued_at, +# "expired_at": expired_at +# } - # 모든 사용자 nft 발급 요청하기 - mint_results = await mint_all_nfts(users, issuser_wallet, issuserAddr) - - # DB 저장용 객체 변환 - nft_records = [ - NFTRecord( - nft_id=result["nft_id"], - user_wallet=result["user_wallet"], - nft_grade=result["nft_grade"], - transaction_hash=result["transaction_hash"], - metadata_uri=result["nft_metadata_uri"], - issued_at=result["issued_at"], - expires_at=result["expired_at"] - ) for result in mint_results - ] - save_nfts_bulk(nft_records) - - # DTO 변환 - return [ - NftResponseDTO( - user_wallet_id=r["user_wallet"], - point=r["point"], - nft_id=r["nft_id"], - nft_grade=r["nft_grade"], - transaction_hash=r["transaction_hash"], - nft_metadata_uri=r["nft_metadata_uri"], - issued_at=r["issued_at"], - expired_at=r["expired_at"] - ) for r in mint_results - ] - - -# async def test_process_nft_issuance_without_db() -> list[NftResponseDTO]: +# except Exception as e: +# print(f"NFT 민팅 실패: {e}") +# return None + +# # 등급별 민팅(여러 사용자에게 등급별로 NFT를 발급해주는 함수) +# async def mint_all_nfts(users, issuser_wallet, issuserAddr): +# total = len(users) # user가 몇명인지지 +# used_indices = 0 # 등급별로 인덱스 나누기 위한 변수 +# results = [] # 민팅 결과 담는 곳곳 + +# # 등급 조건 별로 for 문 돌리기기 +# for rule in GRADE_RULES: +# # 퍼센트가 몇 등인지를 계산 +# size = max(1, int(total * rule["percent"])) +# # 후보자 필터링(상위 인덱스 구간 내에서 최소 포인트 이상인 유저만) +# # fitler_user_by_rank_and_point(user, start_idx, end_idx, point) +# # candidates = filter_users_by_rank_and_point( +# # users, used_indices, min(used_indices + size, total), rule["min_point"] +# # ) +# # 다음 등급의 start_idx 설정정 +# # used_indices += size + +# # 후보자 리스트에서 nft 발급 준비 +# # mint_nft_on_xrpl(user, grade) +# # tasks = [] +# # for user in candidates : +# # task = mint_nft_on_xrpl(user, rule["grade"]) +# # tasks.append(task) +# # tasks = [mint_nft_on_xrpl(user, rule["grade"], issuser_wallet, issuserAddr) for user in candidates] +# tasks = [mint_nft_on_xrpl(user, rule["grade"], issuser_wallet, issuserAddr) for user in users] +# # nft 발급 결과를 비동기식으로 minted에 저장(minted) +# minted = await asyncio.gather(*tasks) # 위 모든 동작을 동시에 실행행 + +# # 발급 실패한 사람 제외 results 에 저장해두기 +# # extend([저장된 발급 결과에 None 이라 저장되어있는 것]) +# results.extend([r for r in minted if r is not None]) + +# return results + +# # 전체 로직 + +# async def process_nft_issuance_with_response() -> list[NftResponseDTO]: # issuser_wallet, issuserAddr = await generate_wallet() # # 포인트 별로 내림차순순 # users.sort(key=lambda u: u.point, reverse=True) + # # 모든 사용자 nft 발급 요청하기 # mint_results = await mint_all_nfts(users, issuser_wallet, issuserAddr) +# # DB 저장용 객체 변환 +# nft_records = [ +# NFTRecord( +# nft_id=result["nft_id"], +# user_wallet=result["user_wallet"], +# nft_grade=result["nft_grade"], +# transaction_hash=result["transaction_hash"], +# metadata_uri=result["nft_metadata_uri"], +# issued_at=result["issued_at"], +# expires_at=result["expired_at"] +# ) for result in mint_results +# ] +# save_nfts_bulk(nft_records) + +# # DTO 변환 # return [ # NftResponseDTO( # user_wallet_id=r["user_wallet"], @@ -270,7 +248,29 @@ async def process_nft_issuance_with_response() -> list[NftResponseDTO]: # nft_metadata_uri=r["nft_metadata_uri"], # issued_at=r["issued_at"], # expired_at=r["expired_at"] -# ) -# for r in mint_results +# ) for r in mint_results # ] + +# # async def test_process_nft_issuance_without_db() -> list[NftResponseDTO]: +# # issuser_wallet, issuserAddr = await generate_wallet() + +# # # 포인트 별로 내림차순순 +# # users.sort(key=lambda u: u.point, reverse=True) +# # # 모든 사용자 nft 발급 요청하기 +# # mint_results = await mint_all_nfts(users, issuser_wallet, issuserAddr) + +# # return [ +# # NftResponseDTO( +# # user_wallet_id=r["user_wallet"], +# # point=r["point"], +# # nft_id=r["nft_id"], +# # nft_grade=r["nft_grade"], +# # transaction_hash=r["transaction_hash"], +# # nft_metadata_uri=r["nft_metadata_uri"], +# # issued_at=r["issued_at"], +# # expired_at=r["expired_at"] +# # ) +# # for r in mint_results +# # ] + diff --git a/src/main/users/repository/UserRepository.py b/src/main/users/repository/UserRepository.py index 742ed67..ccaf1e9 100644 --- a/src/main/users/repository/UserRepository.py +++ b/src/main/users/repository/UserRepository.py @@ -17,7 +17,9 @@ def save_wallet(self, user_id: str, wallet_address: str, point: int = 0, nft_gra if existing: self.wallets_collection.update_one( {"user_id": user_id}, - {"$set": {"address": wallet_address}} + {"$set": {"address": wallet_address, + "point": point, + "nft_grade": nft_grade}} ) return {"message": "Wallet updated", "user_id": user_id, diff --git a/src/main/users/service/UserService.py b/src/main/users/service/UserService.py index c56241c..bf44e0b 100644 --- a/src/main/users/service/UserService.py +++ b/src/main/users/service/UserService.py @@ -36,10 +36,6 @@ def get_wallets(self, user_id: str): return {"message": "No wallets found for this user"} return wallets -<<<<<<< HEAD - def add_wallet(self, user_id: str, wallet_address: str): - return self.user_repository.create_or_update_wallet(user_id, wallet_address, point=0) -======= async def generate_wallet(self, user_id: str): wallet = await generate_faucet_wallet(client=client, debug=True) wallet_address = wallet.classic_address @@ -159,4 +155,3 @@ def get_user_info(self, user_id: str) -> UserInfoResponse: ) return user_info ->>>>>>> 19032c68133e3e96007ea5d383a7774dbbd2005e diff --git a/src/router.py b/src/router.py index bbe6f65..c801c0b 100644 --- a/src/router.py +++ b/src/router.py @@ -1,7 +1,7 @@ from fastapi import APIRouter from src.main.health.router import HealthAPIRouter -from src.main.nft import nft_controller as NftAPIRouter +# from src.main.nft import nft_controller as NftAPIRouter from src.main.users.router import UserAPIRouter @@ -10,5 +10,5 @@ ) router.include_router(HealthAPIRouter.router) -router.include_router(NftAPIRouter.router) +# router.include_router(NftAPIRouter.router) router.include_router(UserAPIRouter.router) From 4fca860190fdc85daf19c18050e009da38fd6660 Mon Sep 17 00:00:00 2001 From: seoyeon0103 Date: Sun, 23 Mar 2025 02:48:33 +0900 Subject: [PATCH 10/10] =?UTF-8?q?Refacor:=20nft=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=EC=B2=98=EB=A6=AC=ED=95=98=EA=B8=B0(git?= =?UTF-8?q?=EC=97=90=20=EC=98=AC=EB=A6=AC=EA=B8=B0=20=E3=85=9F=ED=95=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/nft/nft_Info_dto.py | 72 +++++++++++++++++----------------- src/main/nft/nft_model.py | 30 +++++++------- src/main/nft/nft_repository.py | 44 ++++++++++----------- src/main/nft/nft_user_info.py | 18 ++++----- 4 files changed, 82 insertions(+), 82 deletions(-) diff --git a/src/main/nft/nft_Info_dto.py b/src/main/nft/nft_Info_dto.py index 688cb12..695dab8 100644 --- a/src/main/nft/nft_Info_dto.py +++ b/src/main/nft/nft_Info_dto.py @@ -1,40 +1,40 @@ -from pydantic import BaseModel -from typing import Dict -from datetime import datetime +# from pydantic import BaseModel +# from typing import Dict +# from datetime import datetime -# 필요한 필드들(Response) -# 1. user-wallet id -# 2. nft-id -# 3. nft-grade -# 4. transaction_hash #nft 발급 트ㅐㄴ잭션이 성공적으로 기록 되어있는지 확인 가능한 정보 -#(블록체인 익스플로어 에서 트ㅐㄴ잭션을 조회 가능함) -# 5. nft 메타 데이터 -# 6. 발급일, 만료일 -class NftResponseDTO(BaseModel): - user_wallet_id: str - point: int - nft_id: str - nft_grade: str - transaction_hash: str - nft_metadata_uri: str - issued_at: datetime - expired_at: datetime +# # 필요한 필드들(Response) +# # 1. user-wallet id +# # 2. nft-id +# # 3. nft-grade +# # 4. transaction_hash #nft 발급 트ㅐㄴ잭션이 성공적으로 기록 되어있는지 확인 가능한 정보 +# #(블록체인 익스플로어 에서 트ㅐㄴ잭션을 조회 가능함) +# # 5. nft 메타 데이터 +# # 6. 발급일, 만료일 +# class NftResponseDTO(BaseModel): +# user_wallet_id: str +# point: int +# nft_id: str +# nft_grade: str +# transaction_hash: str +# nft_metadata_uri: str +# issued_at: datetime +# expired_at: datetime -# db 저자에 필요한 필드 -# 1. nft-id -# 2. nft-grade -# 3. transacation_hash -# 4. meta_uri -# nft의 상세 정보(이미지, 설명, 속성 등) 외부 저장소(s3)에 보관 -# 5. 발급, 만료일 -class NftSaveDTO(BaseModel): - id: str - user_wallet: str - nft_id: str - nft_grade: str - transaction_hash: str - nft_metadata_uri: str - issued_at: datetime - expired_at: datetime +# # db 저자에 필요한 필드 +# # 1. nft-id +# # 2. nft-grade +# # 3. transacation_hash +# # 4. meta_uri +# # nft의 상세 정보(이미지, 설명, 속성 등) 외부 저장소(s3)에 보관 +# # 5. 발급, 만료일 +# class NftSaveDTO(BaseModel): +# id: str +# user_wallet: str +# nft_id: str +# nft_grade: str +# transaction_hash: str +# nft_metadata_uri: str +# issued_at: datetime +# expired_at: datetime diff --git a/src/main/nft/nft_model.py b/src/main/nft/nft_model.py index 877e4cf..86e59f4 100644 --- a/src/main/nft/nft_model.py +++ b/src/main/nft/nft_model.py @@ -1,18 +1,18 @@ -##entity 같은 역할입니다. -from sqlalchemy import Column, String, Integer, DateTime -from sqlalchemy.ext.declarative import declarative_base -import datetime +# ##entity 같은 역할입니다. +# from sqlalchemy import Column, String, Integer, DateTime +# from sqlalchemy.ext.declarative import declarative_base +# import datetime -Base = declarative_base() +# Base = declarative_base() -class NFTRecord(Base): - __tablename__ = "nft_records" +# class NFTRecord(Base): +# __tablename__ = "nft_records" - id = Column(Integer, primary_key=True, autoincrement=True) - user_wallet = Column(String, index=True, nullable=False) - nft_id = Column(String, unique=True, nullable=False) - nft_grade = Column(String, nullable=False) - transaction_hash = Column(String, unique=True, nullable=False) - nft_metadata_uri = Column(String, nullable=True) - issued_at = Column(DateTime, nullable=False) - expires_at = Column(DateTime, nullable=False) +# id = Column(Integer, primary_key=True, autoincrement=True) +# user_wallet = Column(String, index=True, nullable=False) +# nft_id = Column(String, unique=True, nullable=False) +# nft_grade = Column(String, nullable=False) +# transaction_hash = Column(String, unique=True, nullable=False) +# nft_metadata_uri = Column(String, nullable=True) +# issued_at = Column(DateTime, nullable=False) +# expires_at = Column(DateTime, nullable=False) diff --git a/src/main/nft/nft_repository.py b/src/main/nft/nft_repository.py index 8d5a959..6af9afa 100644 --- a/src/main/nft/nft_repository.py +++ b/src/main/nft/nft_repository.py @@ -1,25 +1,25 @@ -from src.main.config.mongodb import get_mongo_client -from src.main.nft.nft_model import NFTRecord -from datetime import datetime +# from src.main.config.mongodb import get_mongo_client +# from src.main.nft.nft_model import NFTRecord +# from datetime import datetime -# repository/nft_repository.py -def save_nfts_bulk(nft_records: list): - client = get_mongo_client() - db = client['xrpedia-data'] - nft_collection = db['nft_records'] +# # repository/nft_repository.py +# def save_nfts_bulk(nft_records: list): +# client = get_mongo_client() +# db = client['xrpedia-data'] +# nft_collection = db['nft_records'] - docs = [ - { - "nft_id": r.nft_id, - "user_wallet": r.user_wallet, - "nft_grade": r.nft_grade, - "transaction_hash": r.transaction_hash, - "metadata_uri": r.metadata_uri, - "issued_at": r.issued_at, - "expires_at": r.expires_at, - } - for r in nft_records - ] +# docs = [ +# { +# "nft_id": r.nft_id, +# "user_wallet": r.user_wallet, +# "nft_grade": r.nft_grade, +# "transaction_hash": r.transaction_hash, +# "metadata_uri": r.metadata_uri, +# "issued_at": r.issued_at, +# "expires_at": r.expires_at, +# } +# for r in nft_records +# ] - if docs: - nft_collection.insert_many(docs) \ No newline at end of file +# if docs: +# nft_collection.insert_many(docs) \ No newline at end of file diff --git a/src/main/nft/nft_user_info.py b/src/main/nft/nft_user_info.py index cc8d9e6..17ba849 100644 --- a/src/main/nft/nft_user_info.py +++ b/src/main/nft/nft_user_info.py @@ -1,11 +1,11 @@ -from pydantic import BaseModel -from typing import Dict -from datetime import datetime +# from pydantic import BaseModel +# from typing import Dict +# from datetime import datetime -class User: - def __init__(self, wallet: str, point: int): - self.wallet = wallet - self.point = point +# class User: +# def __init__(self, wallet: str, point: int): +# self.wallet = wallet +# self.point = point - def __repr__(self): - return f"User(wallet={self.wallet}, point={self.point})" \ No newline at end of file +# def __repr__(self): +# return f"User(wallet={self.wallet}, point={self.point})" \ No newline at end of file