From 067a064cf14006a3cb1b1cad982cbd1c1d26938c Mon Sep 17 00:00:00 2001 From: pi3c Date: Fri, 15 Mar 2024 01:49:04 +0300 Subject: [PATCH] user routes --- Dockerfile | 2 +- alembic.ini | 2 +- docker-compose.yml | 2 +- poetry.lock | 83 ++++++++++++++++++- pyproject.toml | 2 + test_api/config.py | 9 +- test_api/migrations/env.py | 4 +- .../migrations/versions/41b339082609_first.py | 38 +++++++++ test_api/models/user_model.py | 18 +--- test_api/repositories/user.py | 20 +++-- test_api/routers/user.py | 47 +++++++++-- test_api/schemas/user_schema.py | 5 +- test_api/services/user.py | 54 +++++++++++- 13 files changed, 242 insertions(+), 44 deletions(-) create mode 100644 test_api/migrations/versions/41b339082609_first.py diff --git a/Dockerfile b/Dockerfile index 067f9df..5a32087 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ COPY ./poetry.lock . COPY ./pyproject.toml . -RUN poetry install --only api --no-root +RUN poetry install --without dev --no-root ENV CONFIG_PATH='/usr/src/test_api/config/api_config.yml' diff --git a/alembic.ini b/alembic.ini index 80d57c6..e5b4167 100644 --- a/alembic.ini +++ b/alembic.ini @@ -60,7 +60,7 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne # are written from script.py.mako # output_encoding = utf-8 -sqlalchemy.url = driver://user:pass@localhost/dbname +sqlalchemy.url = postgresql://demo_user:user_pass@db:5432/serviceman_db [post_write_hooks] diff --git a/docker-compose.yml b/docker-compose.yml index e6c8121..acd764b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,4 +59,4 @@ services: - ./test_api:/usr/src/test_api/test_api - ./alembic.ini:/usr/src/test_api/alembic.ini - command: /bin/bash -c 'cd /usr/src/service_man && alembic upgrade head && poetry run uvicorn api.app:create_app --host 0.0.0.0 --reload --factory' + command: /bin/bash -c 'cd /usr/src/test_api && alembic upgrade head && poetry run uvicorn test_api.app:create_app --host 0.0.0.0 --reload --factory' diff --git a/poetry.lock b/poetry.lock index c4b77ee..4eb17d5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -458,6 +458,87 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" +[[package]] +name = "psycopg2-binary" +version = "2.9.9" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, +] + [[package]] name = "pydantic" version = "2.6.4" @@ -844,4 +925,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "2eb0faf60e5ce3bf4d1db868346f86b86569b3fbc714a5cd1494f4e2d63f3d98" +content-hash = "9b28e04ae14dfa77f629d230f28168647f584a9c28b97367b7c238374e1b1049" diff --git a/pyproject.toml b/pyproject.toml index 1f5f1a0..0109f7b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,8 @@ uvicorn = "^0.28.0" fastapi = "^0.110.0" pydantic-settings = "^2.2.1" alembic = "^1.13.1" +psycopg2-binary = "^2.9.9" +pyyaml = "^6.0.1" [tool.poetry.group.dev.dependencies] diff --git a/test_api/config.py b/test_api/config.py index 2eacce8..4ec9a01 100644 --- a/test_api/config.py +++ b/test_api/config.py @@ -38,14 +38,13 @@ def get_settings(): with open(os.getenv("CONFIG_PATH", "./config/api_config.yml")) as f: config_data: dict = yaml.safe_load(f) - if os.getenv("INDOCKER"): - config_data["db"]["host"] = "db" - config_data["db"]["port"] = 5432 - settings = Settings( db=DBSettings(**config_data["db"]), redis=RedisSettings(**config_data["redis"]), ) - print(settings) + + if os.getenv("INDOCKER"): + settings.db.pg_host = "db" + settings.db.pg_port = 5432 return settings diff --git a/test_api/migrations/env.py b/test_api/migrations/env.py index 9dd6c6c..08e81fa 100644 --- a/test_api/migrations/env.py +++ b/test_api/migrations/env.py @@ -3,6 +3,8 @@ from logging.config import fileConfig from alembic import context from sqlalchemy import engine_from_config, pool +from test_api.models import Base + # this is the Alembic Config object, which provides # access to the values within the .ini file in use. config = context.config @@ -16,7 +18,7 @@ if config.config_file_name is not None: # for 'autogenerate' support # from myapp import mymodel # target_metadata = mymodel.Base.metadata -target_metadata = None +target_metadata = Base.metadata # other values from the config, defined by the needs of env.py, # can be acquired: diff --git a/test_api/migrations/versions/41b339082609_first.py b/test_api/migrations/versions/41b339082609_first.py new file mode 100644 index 0000000..8831b07 --- /dev/null +++ b/test_api/migrations/versions/41b339082609_first.py @@ -0,0 +1,38 @@ +"""First + +Revision ID: 41b339082609 +Revises: 9f9379f12743 +Create Date: 2024-03-15 00:05:37.541998 + +""" + +from collections.abc import Sequence + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "41b339082609" +down_revision: str | None = "9f9379f12743" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "user", + sa.Column("name", sa.String(), nullable=False), + sa.Column("email", sa.String(), nullable=False), + sa.Column("hashed_password", sa.String(), nullable=False), + sa.Column("id", sa.UUID(), nullable=False), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("email"), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("user") + # ### end Alembic commands ### diff --git a/test_api/models/user_model.py b/test_api/models/user_model.py index bd900ed..6c0e81d 100644 --- a/test_api/models/user_model.py +++ b/test_api/models/user_model.py @@ -4,22 +4,8 @@ from . import Base class UserModel(Base): - __tablename__ = "users" - - first_name: Mapped[str] - mid_name: Mapped[str] - last_name: Mapped[str] + __tablename__ = "user" + name: Mapped[str] email: Mapped[str] = mapped_column(unique=True) - telegram_id: Mapped[str] = mapped_column(unique=True, nullable=True, default=None) - hashed_password: Mapped[str] - is_active: Mapped[bool] = mapped_column(default=False) - - def __repr__(self): - return ( - f"" - ) diff --git a/test_api/repositories/user.py b/test_api/repositories/user.py index 2b9eec3..31a4dda 100644 --- a/test_api/repositories/user.py +++ b/test_api/repositories/user.py @@ -1,4 +1,4 @@ -from sqlalchemy import insert, select +from sqlalchemy import delete, insert, select, update from sqlalchemy.ext.asyncio.session import AsyncSession from ..models import UserModel @@ -27,12 +27,22 @@ class UserRepository: stmt = select(UserModel).filter_by(**filter) res = await self.session.execute(stmt) res = res.scalar_one_or_none() + print(res) + if res is not None: return UserReadDTO.model_validate(res) return None - async def update_one(self, filter: dict, data: dict): - return + async def patch_one(self, filter: dict, data: UserWriteDTO) -> UserReadDTO | None: + stmt = update(UserModel).where(UserModel.id == filter["id"]).values(**data.model_dump()).returning(UserModel) + res = await self.session.execute(stmt) + res = res.scalar_one_or_none() - async def delete_one(self, filter: dict): - return + if res is not None: + return UserReadDTO.model_validate(res) + return None + + async def delete_one(self, filter: dict) -> None: + stmt = delete(UserModel).filter_by(**filter) + await self.session.execute(stmt) + return None diff --git a/test_api/routers/user.py b/test_api/routers/user.py index 181c6ab..64461b7 100644 --- a/test_api/routers/user.py +++ b/test_api/routers/user.py @@ -1,3 +1,5 @@ +from uuid import UUID + from fastapi import APIRouter, Depends, HTTPException from ..di import get_user_service @@ -5,17 +7,23 @@ from ..schemas import UserReadDTO from ..schemas.user_schema import UserWriteDTO from ..services import UserService -router = APIRouter() +router = APIRouter( + prefix="/api/v1", +) -@router.get("/users", response_model=list[UserReadDTO]) +@router.get( + "/users", + response_model=list[UserReadDTO], + tags=["users"], +) async def get_user_list( user_service: UserService = Depends(get_user_service), ) -> list[UserReadDTO]: return await user_service.get_all_users() -@router.post("/users", response_model=UserReadDTO) +@router.post("/users", response_model=UserReadDTO, tags=["user"], status_code=201) async def add_user( data: UserWriteDTO, user_service: UserService = Depends(get_user_service), @@ -26,6 +34,33 @@ async def add_user( return res -@router.get("/status") -def get_status(): - return {"status": "OK"} +@router.get("/user/{user_uuid}", response_model=UserReadDTO, tags=["user"]) +async def get_user( + user_uuid: UUID, + user_service: UserService = Depends(get_user_service), +) -> UserReadDTO | dict: + res = await user_service.get_user(id=user_uuid) + if not isinstance(res, UserReadDTO): + raise HTTPException(status_code=400, detail=res) + return res + + +@router.patch("/user/{user_uuid}", response_model=UserReadDTO, tags=["user"]) +async def patch_user( + user_uuid: UUID, + data: UserWriteDTO, + user_service: UserService = Depends(get_user_service), +) -> UserReadDTO | dict: + res = await user_service.patch_one(id=user_uuid, data=data) + if not isinstance(res, UserReadDTO): + raise HTTPException(status_code=400, detail=res) + return res + + +@router.delete("/user/{user_uuid}", status_code=200, tags=["user"]) +async def delete_user( + user_uuid: UUID, + user_service: UserService = Depends(get_user_service), +) -> None: + await user_service.delete_user(id=user_uuid) + return None diff --git a/test_api/schemas/user_schema.py b/test_api/schemas/user_schema.py index 5bff000..9b8e695 100644 --- a/test_api/schemas/user_schema.py +++ b/test_api/schemas/user_schema.py @@ -2,10 +2,7 @@ from . import ReadDTO, WriteDTO class UserWriteDTO(WriteDTO): - first_name: str - mid_name: str - last_name: str - + name: str email: str hashed_password: str diff --git a/test_api/services/user.py b/test_api/services/user.py index 86a42f5..21bd4e5 100644 --- a/test_api/services/user.py +++ b/test_api/services/user.py @@ -1,3 +1,5 @@ +from uuid import UUID + from sqlalchemy.exc import IntegrityError from ..schemas.user_schema import UserWriteDTO @@ -10,9 +12,15 @@ class UserService: async def get_all_users(self): async with self.uow: - res = await self.uow.users.find_all() - - return res + try: + res = await self.uow.users.find_all() + except IntegrityError as e: + await self.uow.rollback() + res = e._message() + else: + await self.uow.commit() + finally: + return res async def add_one(self, data: UserWriteDTO): res = None @@ -26,3 +34,43 @@ class UserService: await self.uow.commit() finally: return res + + async def get_user(self, id: UUID): + async with self.uow: + try: + res = await self.uow.users.find_one(filter={"id": id}) + except IntegrityError as e: + await self.uow.rollback() + res = e._message() + else: + await self.uow.commit() + finally: + return res + + async def patch_one(self, id: UUID, data: UserWriteDTO): + res = None + async with self.uow: + try: + res = await self.uow.users.patch_one(filter={"id": id}, data=data) + except IntegrityError as e: + await self.uow.rollback() + res = e._message() + else: + await self.uow.commit() + finally: + return res + + async def delete_user(self, id: UUID) -> None | str: + async with self.uow: + try: + await self.uow.users.delete_one(filter={"id": id}) + except IntegrityError as e: + await self.uow.rollback() + res = e._message() + else: + res = None + await self.uow.commit() + finally: + return res + + return None