Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
T
task_treker
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Мырзабеков Бекайдар
task_treker
Commits
8707193b
Commit
8707193b
authored
Dec 22, 2023
by
Мырзабеков Бекайдар
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
added authentification by role in payload
parent
5f097349
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
417 additions
and
79 deletions
+417
-79
poetry.lock
poetry.lock
+110
-1
pyproject.toml
pyproject.toml
+3
-0
routes.py
src/app/api/category/routes.py
+12
-6
dependencies.py
src/app/api/dependencies.py
+14
-31
router.py
src/app/api/router.py
+2
-1
routes.py
src/app/api/status/routes.py
+12
-6
routes.py
src/app/api/task/routes.py
+19
-9
routes.py
src/app/api/type/routes.py
+4
-4
const.py
src/app/api/user/const.py
+7
-0
routes.py
src/app/api/user/routes.py
+47
-6
schemas.py
src/app/api/user/schemas.py
+43
-0
services.py
src/app/api/user/services.py
+98
-5
models.py
src/app/db/models.py
+10
-0
repositories.py
src/app/db/repositories.py
+19
-10
6_20231222065854_update.py
src/migrations/models/6_20231222065854_update.py
+17
-0
No files found.
poetry.lock
View file @
8707193b
...
...
@@ -150,6 +150,46 @@ async-timeout = {version = ">=4.0.3", markers = "python_version < \"3.12.0\""}
docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"]
[[package]]
name = "bcrypt"
version = "4.1.2"
description = "Modern password hashing for your software and your servers"
optional = false
python-versions = ">=3.7"
files = [
{file = "bcrypt-4.1.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:ac621c093edb28200728a9cca214d7e838529e557027ef0581685909acd28b5e"},
{file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea505c97a5c465ab8c3ba75c0805a102ce526695cd6818c6de3b1a38f6f60da1"},
{file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57fa9442758da926ed33a91644649d3e340a71e2d0a5a8de064fb621fd5a3326"},
{file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb3bd3321517916696233b5e0c67fd7d6281f0ef48e66812db35fc963a422a1c"},
{file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6cad43d8c63f34b26aef462b6f5e44fdcf9860b723d2453b5d391258c4c8e966"},
{file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:44290ccc827d3a24604f2c8bcd00d0da349e336e6503656cb8192133e27335e2"},
{file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:732b3920a08eacf12f93e6b04ea276c489f1c8fb49344f564cca2adb663b3e4c"},
{file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1c28973decf4e0e69cee78c68e30a523be441972c826703bb93099868a8ff5b5"},
{file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b8df79979c5bae07f1db22dcc49cc5bccf08a0380ca5c6f391cbb5790355c0b0"},
{file = "bcrypt-4.1.2-cp37-abi3-win32.whl", hash = "sha256:fbe188b878313d01b7718390f31528be4010fed1faa798c5a1d0469c9c48c369"},
{file = "bcrypt-4.1.2-cp37-abi3-win_amd64.whl", hash = "sha256:9800ae5bd5077b13725e2e3934aa3c9c37e49d3ea3d06318010aa40f54c63551"},
{file = "bcrypt-4.1.2-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:71b8be82bc46cedd61a9f4ccb6c1a493211d031415a34adde3669ee1b0afbb63"},
{file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e3c6642077b0c8092580c819c1684161262b2e30c4f45deb000c38947bf483"},
{file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:387e7e1af9a4dd636b9505a465032f2f5cb8e61ba1120e79a0e1cd0b512f3dfc"},
{file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f70d9c61f9c4ca7d57f3bfe88a5ccf62546ffbadf3681bb1e268d9d2e41c91a7"},
{file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2a298db2a8ab20056120b45e86c00a0a5eb50ec4075b6142db35f593b97cb3fb"},
{file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ba55e40de38a24e2d78d34c2d36d6e864f93e0d79d0b6ce915e4335aa81d01b1"},
{file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3566a88234e8de2ccae31968127b0ecccbb4cddb629da744165db72b58d88ca4"},
{file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b90e216dc36864ae7132cb151ffe95155a37a14e0de3a8f64b49655dd959ff9c"},
{file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:69057b9fc5093ea1ab00dd24ede891f3e5e65bee040395fb1e66ee196f9c9b4a"},
{file = "bcrypt-4.1.2-cp39-abi3-win32.whl", hash = "sha256:02d9ef8915f72dd6daaef40e0baeef8a017ce624369f09754baf32bb32dba25f"},
{file = "bcrypt-4.1.2-cp39-abi3-win_amd64.whl", hash = "sha256:be3ab1071662f6065899fe08428e45c16aa36e28bc42921c4901a191fda6ee42"},
{file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d75fc8cd0ba23f97bae88a6ec04e9e5351ff3c6ad06f38fe32ba50cbd0d11946"},
{file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:a97e07e83e3262599434816f631cc4c7ca2aa8e9c072c1b1a7fec2ae809a1d2d"},
{file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e51c42750b7585cee7892c2614be0d14107fad9581d1738d954a262556dd1aab"},
{file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba4e4cc26610581a6329b3937e02d319f5ad4b85b074846bf4fef8a8cf51e7bb"},
{file = "bcrypt-4.1.2.tar.gz", hash = "sha256:33313a1200a3ae90b75587ceac502b048b840fc69e7f7a0905b5f87fac7a1258"},
]
[package.extras]
tests = ["pytest (>=3.2.1,!=3.3.0)"]
typecheck = ["mypy"]
[[package]]
name = "certifi"
version = "2023.11.17"
...
...
@@ -312,6 +352,25 @@ docs = ["Sphinx (>=3)", "sphinx-rtd-theme (>=0.2)"]
numpy = ["numpy (>=1.13.0)", "numpy (>=1.15.0)", "numpy (>=1.18.0)", "numpy (>=1.20.0)"]
tests = ["check-manifest (>=0.42)", "mock (>=1.3.0)", "pytest (==5.4.3)", "pytest (>=6)", "pytest-cov (>=2.10.1)", "pytest-isort (>=1.2.0)", "pytest-pycodestyle (>=2)", "pytest-pycodestyle (>=2.2.0)", "pytest-pydocstyle (>=2)", "pytest-pydocstyle (>=2.2.0)", "sphinx (>=3)", "tox (>=3.7.0)"]
[[package]]
name = "dnspython"
version = "2.4.2"
description = "DNS toolkit"
optional = false
python-versions = ">=3.8,<4.0"
files = [
{file = "dnspython-2.4.2-py3-none-any.whl", hash = "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8"},
{file = "dnspython-2.4.2.tar.gz", hash = "sha256:8dcfae8c7460a2f84b4072e26f1c9f4101ca20c071649cb7c34e8b6a93d58984"},
]
[package.extras]
dnssec = ["cryptography (>=2.6,<42.0)"]
doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.24.1)"]
doq = ["aioquic (>=0.9.20)"]
idna = ["idna (>=2.1,<4.0)"]
trio = ["trio (>=0.14,<0.23)"]
wmi = ["wmi (>=1.5.1,<2.0.0)"]
[[package]]
name = "ecdsa"
version = "0.18.0"
...
...
@@ -330,6 +389,21 @@ six = ">=1.9.0"
gmpy = ["gmpy"]
gmpy2 = ["gmpy2"]
[[package]]
name = "email-validator"
version = "2.1.0.post1"
description = "A robust email address syntax and deliverability validation library."
optional = false
python-versions = ">=3.8"
files = [
{file = "email_validator-2.1.0.post1-py3-none-any.whl", hash = "sha256:c973053efbeddfef924dc0bd93f6e77a1ea7ee0fce935aea7103c7a3d6d2d637"},
{file = "email_validator-2.1.0.post1.tar.gz", hash = "sha256:a4b0bd1cf55f073b924258d19321b1f3aa74b4b5a71a42c305575dba920e1a44"},
]
[package.dependencies]
dnspython = ">=2.0.0"
idna = ">=2.0.0"
[[package]]
name = "exceptiongroup"
version = "1.2.0"
...
...
@@ -475,6 +549,26 @@ files = [
{file = "iso8601-1.1.0.tar.gz", hash = "sha256:32811e7b81deee2063ea6d2e94f8819a86d1f3811e49d23623a41fa832bef03f"},
]
[[package]]
name = "passlib"
version = "1.7.4"
description = "comprehensive password hashing framework supporting over 30 schemes"
optional = false
python-versions = "*"
files = [
{file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"},
{file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"},
]
[package.dependencies]
bcrypt = {version = ">=3.1.0", optional = true, markers = "extra == \"bcrypt\""}
[package.extras]
argon2 = ["argon2-cffi (>=18.2.0)"]
bcrypt = ["bcrypt (>=3.1.0)"]
build-docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"]
totp = ["cryptography"]
[[package]]
name = "pyasn1"
version = "0.5.1"
...
...
@@ -510,6 +604,7 @@ files = [
[package.dependencies]
annotated-types = ">=0.4.0"
email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"email\""}
pydantic-core = "2.14.5"
typing-extensions = ">=4.6.1"
...
...
@@ -706,6 +801,20 @@ cryptography = ["cryptography (>=3.4.0)"]
pycrypto = ["pyasn1", "pycrypto (>=2.6.0,<2.7.0)"]
pycryptodome = ["pyasn1", "pycryptodome (>=3.3.1,<4.0.0)"]
[[package]]
name = "python-multipart"
version = "0.0.6"
description = "A streaming multipart parser for Python"
optional = false
python-versions = ">=3.7"
files = [
{file = "python_multipart-0.0.6-py3-none-any.whl", hash = "sha256:ee698bab5ef148b0a760751c261902cd096e57e10558e11aca17646b74ee1c18"},
{file = "python_multipart-0.0.6.tar.gz", hash = "sha256:e9925a80bb668529f1b67c7fdb0a5dacdd7cbfc6fb0bff3ea443fe22bdd62132"},
]
[package.extras]
dev = ["atomicwrites (==1.2.1)", "attrs (==19.2.0)", "coverage (==6.5.0)", "hatch", "invoke (==1.7.3)", "more-itertools (==4.3.0)", "pbr (==4.3.0)", "pluggy (==1.0.0)", "py (==1.11.0)", "pytest (==7.2.0)", "pytest-cov (==4.0.0)", "pytest-timeout (==2.1.0)", "pyyaml (==5.1)"]
[[package]]
name = "pytz"
version = "2023.3.post1"
...
...
@@ -840,4 +949,4 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)",
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "
9191f5d40c10b0c9370146ca214d8127d45f6f21b080662e4c3b275189e6c25b
"
content-hash = "
34fdb29516e9b3de47fa3d5192c00c708a90c528dac4ce25aeaa37383bac1a11
"
pyproject.toml
View file @
8707193b
...
...
@@ -15,6 +15,9 @@ asyncpg = "^0.29.0"
fastapi-pagination
=
"^0.12.12"
httpx
=
"^0.25.2"
python-jose
=
{extras
=
["cryptography"]
,
version
=
"^3.3.0"
}
passlib
=
{extras
=
["bcrypt"]
,
version
=
"^1.7.4"
}
python-multipart
=
"^0.0.6"
pydantic
=
{extras
=
["email"]
,
version
=
"^2.5.2"
}
...
...
src/app/api/category/routes.py
View file @
8707193b
...
...
@@ -4,7 +4,7 @@ import fastapi
from
.schemas
import
CategoryBaseSchema
from
.ctrl
import
CategoryController
from
..dependencies
import
get_
current_user
from
..dependencies
import
get_
user_role
router
=
fastapi
.
APIRouter
(
prefix
=
'/categories'
,
tags
=
[
'Category'
])
...
...
@@ -12,19 +12,25 @@ ctrl = CategoryController()
@
router
.
get
(
''
)
async
def
get_categories
(
query
:
CategoryBaseSchema
=
fastapi
.
Depends
()):
async
def
get_categories
(
query
:
CategoryBaseSchema
=
fastapi
.
Depends
(),
current_user
:
str
=
fastapi
.
Depends
(
get_user_role
)
):
return
await
ctrl
.
get_list
(
**
query
.
model_dump
(
exclude_none
=
True
))
@
router
.
get
(
'/{id}'
)
async
def
get_category
(
id
:
UUID
):
async
def
get_category
(
id
:
UUID
,
current_user
:
str
=
fastapi
.
Depends
(
get_user_role
)
):
return
await
ctrl
.
get
(
id
)
@
router
.
post
(
''
)
async
def
create_category
(
body
:
CategoryBaseSchema
,
current_user
:
str
=
fastapi
.
Depends
(
get_
current_user
)
current_user
:
str
=
fastapi
.
Depends
(
get_
user_role
)
):
return
await
ctrl
.
create
(
**
body
.
model_dump
(
exclude_none
=
True
))
...
...
@@ -32,7 +38,7 @@ async def create_category(
@
router
.
patch
(
'/{id}'
)
async
def
update_category
(
id
:
UUID
,
body
:
CategoryBaseSchema
,
current_user
:
str
=
fastapi
.
Depends
(
get_
current_user
)
current_user
:
str
=
fastapi
.
Depends
(
get_
user_role
)
):
return
await
ctrl
.
update
(
id
,
**
body
.
model_dump
(
exclude_none
=
True
))
...
...
@@ -40,6 +46,6 @@ async def update_category(
@
router
.
delete
(
'/{id}'
)
async
def
delete_category
(
id
:
UUID
,
current_user
:
str
=
fastapi
.
Depends
(
get_
current_user
)
current_user
:
str
=
fastapi
.
Depends
(
get_
user_role
)
):
return
await
ctrl
.
delete
(
id
)
src/app/api/dependencies.py
View file @
8707193b
import
os
import
fastapi
as
fa
from
fastapi
import
Depends
,
HTTPException
,
status
from
jose
import
jwt
,
JWTError
from
fastapi
import
HTTPException
,
status
from
.user.services
import
TokenService
,
oauth2_scheme
from
db.repositories
import
UserRepository
from
db.models
import
UserRoleEnum
SECRET_KEY
=
os
.
getenv
(
'SECRET_KEY'
)
ALGORITHM
=
os
.
getenv
(
'ALGORITHM'
)
token_service
=
TokenService
()
user_repo
=
UserRepository
()
def
get_token
(
token
:
str
=
fa
.
Header
(
...
)):
return
token
async
def
get_user_role
(
token
:
str
=
fa
.
Security
(
oauth2_scheme
)):
token_data
=
token_service
.
decode_token
(
token
)
def
authenticate_user
(
token
:
str
=
Depends
(
get_token
)):
credentials_exception
=
HTTPException
(
if
token_data
.
role
==
UserRoleEnum
.
system_consumer
:
return
await
user_repo
.
get
(
id
=
token_data
.
sub
)
raise
HTTPException
(
status_code
=
status
.
HTTP_401_UNAUTHORIZED
,
detail
=
"
Could not validate credentials
"
,
detail
=
"
Not authenticated
"
,
headers
=
{
"WWW-Authenticate"
:
"Bearer"
},
)
try
:
payload
=
jwt
.
decode
(
token
,
SECRET_KEY
,
algorithms
=
[
ALGORITHM
])
username
:
str
=
payload
.
get
(
"sub"
)
if
username
is
None
:
raise
credentials_exception
return
username
except
JWTError
:
raise
credentials_exception
def
get_current_user
(
username
:
str
=
Depends
(
authenticate_user
)):
if
username
==
"johndoe"
or
username
==
'janedoe'
:
return
username
else
:
raise
HTTPException
(
status_code
=
status
.
HTTP_401_UNAUTHORIZED
,
detail
=
"Not authorized"
,
headers
=
{
"WWW-Authenticate"
:
"Bearer"
},
)
src/app/api/router.py
View file @
8707193b
...
...
@@ -3,7 +3,7 @@ from fastapi import APIRouter
from
.status.routes
import
router
as
status_route
from
.type.routes
import
router
as
type_route
from
.task.routes
import
router
as
task_route
from
.user.routes
import
router
as
user_route
from
.user.routes
import
router
as
user_route
,
auth_router
as
auth_route
from
.category.routes
import
router
as
category_route
...
...
@@ -12,4 +12,5 @@ router.include_router(status_route)
router
.
include_router
(
type_route
)
router
.
include_router
(
task_route
)
router
.
include_router
(
user_route
)
router
.
include_router
(
auth_route
)
router
.
include_router
(
category_route
)
src/app/api/status/routes.py
View file @
8707193b
...
...
@@ -4,7 +4,7 @@ import fastapi
from
.schemas
import
StatusBaseSchema
from
.ctrl
import
StatusController
from
..dependencies
import
get_
current_user
from
..dependencies
import
get_
user_role
router
=
fastapi
.
APIRouter
(
prefix
=
'/statuses'
,
tags
=
[
'Status'
])
...
...
@@ -12,19 +12,25 @@ ctrl = StatusController()
@
router
.
get
(
''
)
async
def
get_statuses
(
query
:
StatusBaseSchema
=
fastapi
.
Depends
()):
async
def
get_statuses
(
query
:
StatusBaseSchema
=
fastapi
.
Depends
(),
current_user
:
str
=
fastapi
.
Depends
(
get_user_role
)
):
return
await
ctrl
.
get_list
(
**
query
.
model_dump
(
exclude_none
=
True
))
@
router
.
get
(
'/{id}'
)
async
def
get_status
(
id
:
UUID
):
async
def
get_status
(
id
:
UUID
,
current_user
:
str
=
fastapi
.
Depends
(
get_user_role
)
):
return
await
ctrl
.
get
(
id
)
@
router
.
post
(
''
)
async
def
create_status
(
body
:
StatusBaseSchema
,
current_user
:
str
=
fastapi
.
Depends
(
get_
current_user
)
current_user
:
str
=
fastapi
.
Depends
(
get_
user_role
)
):
return
await
ctrl
.
create
(
**
body
.
model_dump
(
exclude_none
=
True
))
...
...
@@ -32,7 +38,7 @@ async def create_status(
@
router
.
patch
(
'/{id}'
)
async
def
update_status
(
id
:
UUID
,
body
:
StatusBaseSchema
,
current_user
:
str
=
fastapi
.
Depends
(
get_
current_user
)
current_user
:
str
=
fastapi
.
Depends
(
get_
user_role
)
):
return
await
ctrl
.
update
(
id
,
**
body
.
model_dump
(
exclude_none
=
True
))
...
...
@@ -40,6 +46,6 @@ async def update_status(
@
router
.
delete
(
'/{id}'
)
async
def
delete_status
(
id
:
UUID
,
current_user
:
str
=
fastapi
.
Depends
(
get_
current_user
)
current_user
:
str
=
fastapi
.
Depends
(
get_
user_role
)
):
return
await
ctrl
.
delete
(
id
)
src/app/api/task/routes.py
View file @
8707193b
...
...
@@ -6,7 +6,7 @@ from .schemas import (
TaskPostSchema
,
TaskPatchSchema
,
CommentSchema
)
from
.ctrl
import
TaskController
from
..dependencies
import
get_
current_user
from
..dependencies
import
get_
user_role
router
=
fastapi
.
APIRouter
(
prefix
=
'/tasks'
,
tags
=
[
'Task'
])
...
...
@@ -22,6 +22,7 @@ async def get_tasks(
sort_by
:
str
|
None
=
None
,
page
:
int
=
1
,
size
:
int
=
10
,
current_user
:
str
=
fastapi
.
Depends
(
get_user_role
)
):
return
await
ctrl
.
get_list
(
title
=
title
,
...
...
@@ -34,14 +35,17 @@ async def get_tasks(
)
@
router
.
get
(
'/{id}'
)
async
def
get_task
(
id
:
UUID
):
async
def
get_task
(
id
:
UUID
,
current_user
:
str
=
fastapi
.
Depends
(
get_user_role
)
):
return
await
ctrl
.
get
(
id
)
@
router
.
post
(
''
)
async
def
create_task
(
body
:
TaskPostSchema
,
current_user
:
str
=
fastapi
.
Depends
(
get_
current_user
)
current_user
:
str
=
fastapi
.
Depends
(
get_
user_role
)
):
return
await
ctrl
.
create
(
**
body
.
model_dump
(
exclude_none
=
True
))
...
...
@@ -50,7 +54,7 @@ async def create_task(
async
def
update_task
(
id
:
UUID
,
body
:
TaskPatchSchema
,
current_user
:
str
=
fastapi
.
Depends
(
get_
current_user
)
current_user
:
str
=
fastapi
.
Depends
(
get_
user_role
)
):
return
await
ctrl
.
update
(
id
,
**
body
.
model_dump
(
exclude_none
=
True
))
...
...
@@ -58,13 +62,16 @@ async def update_task(
@
router
.
delete
(
'/{id}'
)
async
def
delete_task
(
id
:
UUID
,
current_user
:
str
=
fastapi
.
Depends
(
get_
current_user
)
current_user
:
str
=
fastapi
.
Depends
(
get_
user_role
)
):
return
await
ctrl
.
delete
(
id
)
@
router
.
get
(
'/{id}/comments'
)
async
def
get_comments
(
id
:
UUID
):
async
def
get_comments
(
id
:
UUID
,
current_user
:
str
=
fastapi
.
Depends
(
get_user_role
)
):
return
await
ctrl
.
get_comments
(
id
)
...
...
@@ -72,7 +79,7 @@ async def get_comments(id: UUID):
async
def
add_comment
(
id
:
UUID
,
body
:
CommentSchema
,
current_user
:
str
=
fastapi
.
Depends
(
get_
current_user
)
current_user
:
str
=
fastapi
.
Depends
(
get_
user_role
)
):
return
await
ctrl
.
add_comment
(
id
,
**
body
.
model_dump
(
exclude_none
=
True
))
...
...
@@ -80,11 +87,14 @@ async def add_comment(
@
router
.
delete
(
'/comments/{id}'
)
async
def
delete_comment
(
id
:
UUID
,
current_user
:
str
=
fastapi
.
Depends
(
get_
current_user
)
current_user
:
str
=
fastapi
.
Depends
(
get_
user_role
)
):
return
await
ctrl
.
delete_comment
(
id
)
@
router
.
get
(
'/tasks/search'
)
async
def
search_tasks
(
keywords
:
str
):
async
def
search_tasks
(
keywords
:
str
,
current_user
:
str
=
fastapi
.
Depends
(
get_user_role
)
):
return
await
ctrl
.
search
(
keywords
)
src/app/api/type/routes.py
View file @
8707193b
...
...
@@ -4,7 +4,7 @@ import fastapi
from
.schemas
import
TypeBaseSchema
from
.ctrl
import
TypeController
from
..dependencies
import
get_
current_user
from
..dependencies
import
get_
user_role
router
=
fastapi
.
APIRouter
(
prefix
=
'/types'
,
tags
=
[
'Type'
])
...
...
@@ -24,7 +24,7 @@ async def get_type(id: UUID):
@
router
.
post
(
''
)
async
def
create_type
(
body
:
TypeBaseSchema
,
current_user
:
str
=
fastapi
.
Depends
(
get_
current_user
)
current_user
:
str
=
fastapi
.
Depends
(
get_
user_role
)
):
return
await
ctrl
.
create
(
**
body
.
model_dump
(
exclude_none
=
True
))
...
...
@@ -33,7 +33,7 @@ async def create_type(
async
def
update_type
(
id
:
UUID
,
body
:
TypeBaseSchema
,
current_user
:
str
=
fastapi
.
Depends
(
get_
current_user
)
current_user
:
str
=
fastapi
.
Depends
(
get_
user_role
)
):
return
await
ctrl
.
update
(
id
,
**
body
.
model_dump
(
exclude_none
=
True
))
...
...
@@ -41,6 +41,6 @@ async def update_type(
@
router
.
delete
(
'/{id}'
)
async
def
delete_type
(
id
:
UUID
,
current_user
:
str
=
fastapi
.
Depends
(
get_
current_user
)
current_user
:
str
=
fastapi
.
Depends
(
get_
user_role
)
):
return
await
ctrl
.
delete
(
id
)
src/app/api/user/const.py
0 → 100644
View file @
8707193b
import
os
SECRET_KEY
=
os
.
getenv
(
'SECRET_KEY'
)
ALGORITHM
=
os
.
getenv
(
'ALGORITHM'
)
ACCESS_TOKEN_EXPIRE_MINUTES
=
int
(
os
.
getenv
(
'ACCESS_TOKEN_EXPIRE_MINUTES'
))
REFRESH_TOKEN_EXPIRE_HOURS
=
int
(
os
.
getenv
(
'REFRESH_TOKEN_EXPIRE_HOURS'
))
src/app/api/user/routes.py
View file @
8707193b
from
uuid
import
UUID
import
fastapi
from
fastapi.security
import
OAuth2PasswordRequestForm
from
.schemas
import
User
Base
Schema
from
.schemas
import
User
CreateSchema
,
UserUpdateSchema
,
UserGet
Schema
from
.ctrl
import
UserController
from
.services
import
auth_service
,
token_service
from
..dependencies
import
get_user_role
router
=
fastapi
.
APIRouter
(
prefix
=
'/users'
,
tags
=
[
'User'
])
auth_router
=
fastapi
.
APIRouter
(
prefix
=
'/auth'
,
tags
=
[
'Auth'
])
ctrl
=
UserController
()
@
router
.
get
(
''
)
async
def
get_users
(
query
:
UserBaseSchema
=
fastapi
.
Depends
()):
async
def
get_users
(
query
:
UserGetSchema
=
fastapi
.
Depends
(),
current_user
:
str
=
fastapi
.
Depends
(
get_user_role
)
):
return
await
ctrl
.
get_list
(
**
query
.
model_dump
(
exclude_none
=
True
))
@
router
.
get
(
'/{id}'
)
async
def
get_user
(
id
:
UUID
):
async
def
get_user
(
id
:
UUID
,
current_user
:
str
=
fastapi
.
Depends
(
get_user_role
)
):
return
await
ctrl
.
get
(
id
)
@
router
.
post
(
''
)
async
def
create_user
(
body
:
UserBaseSchema
):
async
def
create_user
(
body
:
UserCreateSchema
,
current_user
:
str
=
fastapi
.
Depends
(
get_user_role
)
):
return
await
ctrl
.
create
(
**
body
.
model_dump
(
exclude_none
=
True
))
@
router
.
patch
(
'/{id}'
)
async
def
update_user
(
id
:
UUID
,
body
:
UserBaseSchema
):
async
def
update_user
(
id
:
UUID
,
body
:
UserUpdateSchema
,
current_user
:
str
=
fastapi
.
Depends
(
get_user_role
)
):
return
await
ctrl
.
update
(
id
,
**
body
.
model_dump
(
exclude_none
=
True
))
@
router
.
delete
(
'/{id}'
)
async
def
delete_user
(
id
:
UUID
):
async
def
delete_user
(
id
:
UUID
,
current_user
:
str
=
fastapi
.
Depends
(
get_user_role
)
):
return
await
ctrl
.
delete
(
id
)
@
auth_router
.
post
(
'/login'
)
async
def
login
(
form_data
:
OAuth2PasswordRequestForm
=
fastapi
.
Depends
(),
):
return
await
auth_service
.
login
(
form_data
)
@
auth_router
.
get
(
'/users/me'
)
async
def
read_user_me
(
user
:
dict
=
fastapi
.
Depends
(
auth_service
.
get_current_user
)
):
return
UserGetSchema
.
model_validate
(
user
)
@
auth_router
.
post
(
'/refresh'
)
async
def
refresh_token
(
data
:
dict
,
user
:
dict
=
fastapi
.
Depends
(
auth_service
.
get_current_user
)
):
return
token_service
.
refresh_token
(
data
[
'refresh_token'
])
src/app/api/user/schemas.py
View file @
8707193b
from
uuid
import
UUID
from
dataclasses
import
dataclass
from
datetime
import
datetime
from
pydantic
import
BaseModel
,
ConfigDict
from
pydantic
import
EmailStr
from
db.models
import
UserRoleEnum
class
UserBaseSchema
(
BaseModel
):
...
...
@@ -16,3 +21,41 @@ class UserIdSchema(BaseModel):
class
UserSchema
(
UserBaseSchema
,
UserIdSchema
):
pass
class
UserGetSchema
(
BaseModel
):
model_config
=
ConfigDict
(
from_attributes
=
True
,
use_enum_values
=
True
)
id
:
UUID
|
None
=
None
name
:
str
|
None
=
None
surname
:
str
|
None
=
None
email
:
EmailStr
|
None
=
None
role
:
UserRoleEnum
class
UserCreateSchema
(
UserBaseSchema
):
model_config
=
ConfigDict
(
use_enum_values
=
True
)
name
:
str
surname
:
str
email
:
EmailStr
role
:
UserRoleEnum
class
UserUpdateSchema
(
BaseModel
):
model_config
=
ConfigDict
(
use_enum_values
=
True
)
name
:
str
|
None
=
None
surname
:
str
|
None
=
None
email
:
EmailStr
|
None
=
None
role
:
UserRoleEnum
|
None
=
None
@
dataclass
class
TokenData
:
sub
:
int
role
:
str
exp
:
datetime
|
None
=
None
@
dataclass
class
LoginRequestModel
:
username
:
str
password
:
str
src/app/api/user/services.py
View file @
8707193b
from
uuid
import
UUID
from
datetime
import
datetime
,
timedelta
import
fastapi
as
fa
import
fastapi.security
as
fs
from
passlib.context
import
CryptContext
from
jose
import
jwt
,
JWTError
from
db.repositories
import
UserRepository
from
.schemas
import
UserSchema
from
.schemas
import
TokenData
,
LoginRequestModel
,
UserGetSchema
from
.const
import
(
SECRET_KEY
,
ACCESS_TOKEN_EXPIRE_MINUTES
,
REFRESH_TOKEN_EXPIRE_HOURS
,
ALGORITHM
)
pwd_context
=
CryptContext
(
schemes
=
[
'bcrypt'
],
deprecated
=
'auto'
)
oauth2_scheme
=
fs
.
OAuth2PasswordBearer
(
tokenUrl
=
'/api/auth/login'
)
class
TokenService
:
def
create_token
(
self
,
data
:
TokenData
,
expires_delta
:
timedelta
)
->
str
:
payload
=
{
'sub'
:
str
(
data
.
sub
),
'role'
:
data
.
role
,
'exp'
:
datetime
.
utcnow
()
+
expires_delta
,
}
return
jwt
.
encode
(
payload
,
SECRET_KEY
,
algorithm
=
ALGORITHM
)
def
create_tokens
(
self
,
data
:
TokenData
):
access
=
self
.
create_token
(
data
,
expires_delta
=
timedelta
(
minutes
=
ACCESS_TOKEN_EXPIRE_MINUTES
)
)
refresh
=
self
.
create_token
(
data
,
expires_delta
=
timedelta
(
hours
=
REFRESH_TOKEN_EXPIRE_HOURS
)
)
return
{
"access_token"
:
access
,
"refresh_token"
:
refresh
}
def
decode_token
(
self
,
token
:
str
)
->
TokenData
:
credentials_exception
=
fa
.
HTTPException
(
status_code
=
fa
.
status
.
HTTP_401_UNAUTHORIZED
,
detail
=
"Could not validate credentials"
,
headers
=
{
"WWW-Authenticate"
:
"Bearer"
},
)
try
:
decoded_token_payload
=
jwt
.
decode
(
token
,
SECRET_KEY
,
algorithms
=
[
ALGORITHM
])
user_id
=
decoded_token_payload
.
get
(
'sub'
)
if
user_id
is
None
:
raise
credentials_exception
except
JWTError
:
raise
credentials_exception
return
TokenData
(
**
decoded_token_payload
)
def
refresh_token
(
self
,
refresh_token
:
str
):
token_data
=
self
.
decode_token
(
refresh_token
)
access
=
self
.
create_token
(
token_data
,
timedelta
(
minutes
=
ACCESS_TOKEN_EXPIRE_MINUTES
))
return
{
'access_token'
:
access
,
'refresh_token'
:
refresh_token
}
token_service
=
TokenService
()
class
AuthService
:
def
__init__
(
self
)
->
None
:
self
.
user_repo
=
UserRepository
()
async
def
verify_password
(
self
,
plain_password
:
str
,
hashed_password
:
str
)
->
bool
:
return
pwd_context
.
verify
(
plain_password
,
hashed_password
)
async
def
authenticate_user
(
self
,
username
:
str
,
password
:
str
):
users
=
await
self
.
user_repo
.
get_list
(
username
=
username
)
if
users
and
self
.
verify_password
(
password
,
users
[
0
]
.
password
):
return
users
[
0
]
async
def
login
(
self
,
data
:
LoginRequestModel
):
user
=
await
self
.
authenticate_user
(
username
=
data
.
username
,
password
=
data
.
password
)
if
not
user
:
raise
fa
.
HTTPException
(
status_code
=
fa
.
status
.
HTTP_404_NOT_FOUND
,
detail
=
"Incorrect username or password"
,
headers
=
{
"WWW-Authenticate"
:
"Bearer"
},
)
return
token_service
.
create_tokens
(
TokenData
(
sub
=
user
.
id
,
role
=
user
.
role
))
async
def
get_current_user
(
self
,
token
:
str
=
fa
.
Security
(
oauth2_scheme
)):
token_data
=
token_service
.
decode_token
(
token
)
return
await
self
.
user_repo
.
get
(
id
=
token_data
.
sub
)
auth_service
=
AuthService
()
class
UserService
:
...
...
@@ -9,19 +102,19 @@ class UserService:
async
def
get_user_list
(
self
,
**
kwargs
):
users
=
await
self
.
user_repository
.
get_list
(
**
kwargs
)
return
[
UserSchema
.
model_validate
(
user
)
for
user
in
users
]
return
[
User
Get
Schema
.
model_validate
(
user
)
for
user
in
users
]
async
def
get_user
(
self
,
id
:
UUID
):
user
=
await
self
.
user_repository
.
get
(
id
)
return
UserSchema
.
model_validate
(
user
)
return
User
Get
Schema
.
model_validate
(
user
)
async
def
create_user
(
self
,
**
kwargs
):
user
=
await
self
.
user_repository
.
create
(
**
kwargs
)
return
UserSchema
.
model_validate
(
user
)
return
User
Get
Schema
.
model_validate
(
user
)
async
def
update_user
(
self
,
id
:
UUID
,
**
kwargs
):
user
=
await
self
.
user_repository
.
update
(
id
,
**
kwargs
)
return
UserSchema
.
model_validate
(
user
)
return
User
Get
Schema
.
model_validate
(
user
)
async
def
delete_user
(
self
,
id
:
UUID
):
return
await
self
.
user_repository
.
delete
(
id
)
src/app/db/models.py
View file @
8707193b
...
...
@@ -55,8 +55,18 @@ class Task(BaseModel):
table
=
'tasks'
class
UserRoleEnum
(
str
,
Enum
):
admin
=
"admin"
user
=
"user"
system_consumer
=
"system_consumer"
class
User
(
BaseModel
):
username
=
fields
.
CharField
(
max_length
=
100
,
unique
=
True
)
name
=
fields
.
CharField
(
max_length
=
100
)
surname
=
fields
.
CharField
(
max_length
=
100
)
email
=
fields
.
CharField
(
max_length
=
100
)
role
=
fields
.
CharEnumField
(
enum_type
=
UserRoleEnum
,
default
=
UserRoleEnum
.
user
)
password
=
fields
.
CharField
(
max_length
=
255
)
def
__str__
(
self
):
...
...
src/app/db/repositories.py
View file @
8707193b
...
...
@@ -2,6 +2,7 @@ import math
import
tortoise
from
tortoise.expressions
import
Q
from
passlib.context
import
CryptContext
from
.models
import
(
Status
,
Type
,
Task
,
User
,
Comment
,
Category
...
...
@@ -98,26 +99,34 @@ class TaskRepository(BaseRepository):
}
return
result
async
def
search
(
self
,
keywords
:
str
):
query
=
self
.
model
search_terms
=
keywords
.
split
()
async
def
search
(
self
,
keywords
:
str
):
query
=
self
.
model
search_terms
=
keywords
.
split
()
conditions
=
[
Q
(
title__icontains
=
term
)
|
Q
(
description__icontains
=
term
)
for
term
in
search_terms
]
conditions
=
[
Q
(
title__icontains
=
term
)
|
Q
(
description__icontains
=
term
)
for
term
in
search_terms
]
combined_condition
=
conditions
.
pop
()
combined_condition
=
conditions
.
pop
()
for
condition
in
conditions
:
combined_condition
|=
condition
for
condition
in
conditions
:
combined_condition
|=
condition
query
=
query
.
filter
(
combined_condition
)
query
=
query
.
filter
(
combined_condition
)
return
await
query
.
all
()
return
await
query
.
all
()
class
UserRepository
(
BaseRepository
):
model
=
User
@
staticmethod
def
__get_password_hash
(
password
):
pwd_context
=
CryptContext
(
schemes
=
[
"bcrypt"
],
deprecated
=
"auto"
)
return
pwd_context
.
hash
(
password
)
async
def
create
(
self
,
**
kwargs
):
kwargs
[
'password'
]
=
self
.
__get_password_hash
(
kwargs
[
'password'
])
return
await
super
()
.
create
(
**
kwargs
)
class
CommentRepository
(
BaseRepository
):
model
=
Comment
...
...
src/migrations/models/6_20231222065854_update.py
0 → 100644
View file @
8707193b
from
tortoise
import
BaseDBAsyncClient
async
def
upgrade
(
db
:
BaseDBAsyncClient
)
->
str
:
return
"""
ALTER TABLE "users" ADD "role" VARCHAR(15) NOT NULL DEFAULT 'user';
ALTER TABLE "users" ADD "surname" VARCHAR(100) NOT NULL;
ALTER TABLE "users" ADD "name" VARCHAR(100) NOT NULL;
ALTER TABLE "users" ADD "email" VARCHAR(100) NOT NULL;"""
async
def
downgrade
(
db
:
BaseDBAsyncClient
)
->
str
:
return
"""
ALTER TABLE "users" DROP COLUMN "role";
ALTER TABLE "users" DROP COLUMN "surname";
ALTER TABLE "users" DROP COLUMN "name";
ALTER TABLE "users" DROP COLUMN "email";"""
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment