Skip to content

Commit 5da21a9

Browse files
authored
Merge pull request #4 from aSel1x/main
2 parents 4f24433 + 65ca4c4 commit 5da21a9

31 files changed

+202
-163
lines changed

README.md

+20-11
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
│   ├── deps.py
1010
│   └── v1
1111
│   ├── __init__.py
12+
│   ├── auth
13+
│   │   ├── __init__.py
14+
│   │   └── token.py
1215
│   └── users
1316
│   ├── __init__.py
14-
│   ├── auth
15-
│   │   ├── __init__.py
16-
│   │   └── token.py
1717
│   ├── create.py
1818
│   └── retrieve.py
1919
├── core
@@ -23,27 +23,36 @@
2323
│   └── settings.py
2424
├── logic
2525
│   ├── __init__.py
26+
│   ├── auth
27+
│   │   ├── __init__.py
28+
│   │   └── auth.py
29+
│   ├── logic.py
2630
│   ├── security
2731
│   │   ├── __init__.py
2832
│   │   ├── jwt.py
29-
│   │   └── pwd.py
33+
│   │   ├── pwd.py
34+
│   │   └── security.py
3035
│   └── users
3136
│   ├── __init__.py
32-
│   ├── auth
33-
│   │   ├── __init__.py
34-
│   │   └── auth.py
3537
│   └── users.py
3638
├── models
3739
│   ├── __init__.py
40+
│   ├── auth
41+
│   │   ├── __init__.py
42+
│   │   └── token.py
3843
│   ├── base.py
39-
│   ├── token.py
40-
│   └── user.py
44+
│   ├── types
45+
│   │   ├── __init__.py
46+
│   │   └── unix.py
47+
│   └── users
48+
│   ├── __init__.py
49+
│   └── user.py
4150
└── repositories
4251
├── __init__.py
43-
├── abstract.py
52+
├── base.py
4453
└── user.py
4554
46-
11 directories, 28 files
55+
14 directories, 34 files
4756
```
4857

4958
## Create a `.env` file based on `.env.dist` and make all the necessary customizations

app/api/deps.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,20 @@
88
from fastapi.security import APIKeyHeader
99

1010
from app.logic import Logic as _Logic
11-
from app.models.user import User as _User
11+
from app.models.users.user import User as _User
1212

1313

1414
async def get_logic() -> _Logic:
15-
return await _Logic.create()
15+
async with Logic.create() as logic:
16+
yield logic
1617

1718

1819
Logic = Annotated[_Logic, Depends(get_logic)]
1920

2021

2122
async def get_user(
22-
token: Annotated[str, Depends(APIKeyHeader(name='access-token'))],
23-
logic: Logic,
23+
token: Annotated[str, Depends(APIKeyHeader(name='access-token'))],
24+
logic: Logic,
2425
) -> _User | None:
2526
return await logic.users.retrieve_by_token(token)
2627

app/api/v1/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66

77
from fastapi import APIRouter
88

9-
from . import users
9+
from . import auth, users
1010

1111
FOLDER_NAME = f'{Path(__file__).parent.name}'
1212

1313
router = APIRouter(prefix=f'/{FOLDER_NAME}', tags=[FOLDER_NAME])
14+
router.include_router(auth.router)
1415
router.include_router(users.router)
1516

1617
__all__ = ['router']
File renamed without changes.
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from fastapi import APIRouter
22

33
from app.api import deps
4-
from app.models.token import AccessToken
5-
from app.models.user import UserCreate
4+
from app.models.auth import AccessToken
5+
from app.models.users.user import UserCreate
66

77
router = APIRouter(prefix='/token')
88

@@ -12,7 +12,7 @@ async def token(data: UserCreate, logic: deps.Logic):
1212
"""
1313
Retrieve new access token
1414
"""
15-
return await logic.users.auth.generate_token(**data.model_dump())
15+
return await logic.auth.generate_token(**data.model_dump())
1616

1717

1818
__all__ = ['router']

app/api/v1/users/__init__.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44

55
from fastapi import APIRouter
66

7-
from . import auth, create, retrieve
7+
from . import create, retrieve
88

99
router = APIRouter(prefix='/users', tags=['users'])
10-
router.include_router(auth.router)
1110
router.include_router(create.router)
1211
router.include_router(retrieve.router)
1312

app/api/v1/users/create.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from fastapi import APIRouter
22

33
from app.api import deps
4-
from app.models.user import UserCreate, UserRead
4+
from app.models.users.user import UserCreate, UserRead
55

66
router = APIRouter(prefix='/create')
77

@@ -11,7 +11,7 @@ async def create(data: UserCreate, logic: deps.Logic):
1111
"""
1212
Create user
1313
"""
14-
return await logic.users.create(**data.model_dump())
14+
return await logic.users.create(data)
1515

1616

1717
__all__ = ['router']

app/api/v1/users/retrieve.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from fastapi import APIRouter
66

77
from app.api import deps
8-
from app.models.user import UserRead
8+
from app.models.users.user import UserRead
99

1010
router = APIRouter()
1111

app/core/db.py

+22-20
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Database
33
"""
44

5-
from typing import NoReturn, Self
5+
from typing import Self
66

77
from sqlalchemy.ext.asyncio import (AsyncEngine, async_sessionmaker,
88
create_async_engine)
@@ -15,47 +15,49 @@
1515
class Database:
1616
_instance = None
1717

18-
def __new__(cls, *args, **kwargs) -> Self:
18+
def __new__(cls, *args, **kwargs) -> 'Database':
1919
if cls._instance is None:
2020
cls._instance = super(Database, cls).__new__(cls)
2121
return cls._instance
2222

2323
def __init__(
24-
self,
25-
engine: AsyncEngine | None = None,
26-
session: AsyncSession | None = None,
24+
self,
25+
engine: AsyncEngine | None = None,
26+
session: AsyncSession | None = None,
2727
) -> None:
2828
if not hasattr(self, 'initialized'):
29-
self.engine = engine
30-
self.session = session
29+
self.__engine = engine
30+
self.__session = session
3131
self.initialized = True
3232

33-
async def __set_async_engine(self) -> NoReturn:
34-
if self.engine is None:
35-
self.engine = create_async_engine(
33+
async def __set_async_engine(self) -> None:
34+
if self.__engine is None:
35+
self.__engine = create_async_engine(
3636
settings.pg_dsn.unicode_string(), echo=False, future=True
3737
)
3838

39-
async def __set_async_session(self) -> NoReturn:
40-
if self.session is None:
41-
self.session = async_sessionmaker(
39+
async def __set_async_session(self) -> None:
40+
if self.__session is None:
41+
self.__session = async_sessionmaker(
4242
autocommit=False,
4343
autoflush=False,
44-
bind=self.engine,
44+
bind=self.__engine,
4545
class_=AsyncSession,
4646
expire_on_commit=False,
4747
)()
4848

49-
async def __set_repositories(self) -> NoReturn:
50-
if self.session is not None:
51-
self.user = repos.UserRepo(session=self.session)
49+
async def __set_repositories(self) -> None:
50+
if self.__session is not None:
51+
self.user = repos.UserRepo(session=self.__session)
5252

5353
async def __aenter__(self) -> Self:
5454
await self.__set_async_engine()
5555
await self.__set_async_session()
5656
await self.__set_repositories()
5757
return self
5858

59-
async def __aexit__(self, exc_type, exc_value, traceback) -> NoReturn:
60-
if self.session is not None:
61-
await self.session.close()
59+
async def __aexit__(self, exc_type, exc_value, traceback) -> None:
60+
if self.__session is not None:
61+
await self.__session.commit()
62+
await self.__session.close()
63+
self.__session = None

app/logic/__init__.py

+1-19
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,3 @@
1-
from typing import Self
2-
3-
from app.core.db import Database
4-
5-
from .security import Security
6-
from .users import Users
7-
8-
9-
class Logic:
10-
def __init__(self, db: Database):
11-
self.db = db
12-
self.security = Security()
13-
self.users = Users(self)
14-
15-
@classmethod
16-
async def create(cls) -> Self:
17-
async with Database() as db:
18-
return cls(db)
19-
1+
from .logic import Logic
202

213
__all__ = ['Logic']
File renamed without changes.

app/logic/auth/auth.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from typing import TYPE_CHECKING
2+
3+
from app.core import exps
4+
from app.models.auth import AccessToken
5+
6+
if TYPE_CHECKING:
7+
from app.logic import Logic
8+
9+
10+
class Auth:
11+
def __init__(self, logic: 'Logic'):
12+
self.logic = logic
13+
14+
async def generate_token(
15+
self, email: str, password: str
16+
) -> AccessToken | None:
17+
if (user := await self.logic.db.user.retrieve_by_email(email)) is None:
18+
raise exps.UserNotFoundException()
19+
if not self.logic.security.pwd.checkpwd(password, user.password):
20+
raise exps.UserIsCorrectException()
21+
access_token = self.logic.security.jwt.encode_token(
22+
{'id': user.id}, 1440
23+
)
24+
return AccessToken(token=access_token)

app/logic/logic.py

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from typing import Self, AsyncGenerator
2+
from contextlib import asynccontextmanager
3+
4+
from app.core.db import Database
5+
6+
from .security import Security
7+
from .users import Users
8+
from .auth import Auth
9+
10+
11+
class Logic:
12+
def __init__(self, db: Database):
13+
self.db = db
14+
self.security = Security()
15+
self.users = Users(self)
16+
self.auth = Auth(self)
17+
18+
@classmethod
19+
@asynccontextmanager
20+
async def create(cls) -> AsyncGenerator[Self, None]:
21+
async with Database() as db:
22+
yield cls(db)

app/logic/security/__init__.py

+1-11
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,3 @@
1-
from app.core.settings import settings
2-
3-
from .jwt import JWT
4-
from .pwd import PWD
5-
6-
7-
class Security:
8-
def __init__(self):
9-
self.jwt = JWT(settings.APP_SECRET_KEY)
10-
self.pwd = PWD()
11-
1+
from .security import Security
122

133
__all__ = ['Security']

app/logic/security/jwt.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class JWT:
99
def __init__(self, secret_key: str):
1010
self.secret_key: str = secret_key
1111

12-
def decode_token(self, token: str) -> dict | None:
12+
def decode_token(self, token: str) -> dict:
1313
try:
1414
payload = jwt.decode(token, self.secret_key, algorithms=['HS256'])
1515
except Exception:
@@ -18,7 +18,9 @@ def decode_token(self, token: str) -> dict | None:
1818
exp = payload.get('exp')
1919
if exp and dt.datetime.now(dt.UTC).timestamp() > exp:
2020
raise exps.TokenExpiredException()
21-
return payload.get('payload')
21+
if (payload := payload.get('payload', None)) is None:
22+
raise exps.TokenInvalidException()
23+
return payload
2224

2325
def encode_token(self, payload: dict, minutes: int) -> str:
2426
claims = {

app/logic/security/pwd.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33

44
class PWD:
5-
def hashpwd(self, password: str) -> str:
5+
@staticmethod
6+
def hashpwd(password: str) -> str:
67
return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
78

8-
def checkpwd(self, password: str, hashed_password: str) -> bool:
9+
@staticmethod
10+
def checkpwd(password: str, hashed_password: str) -> bool:
911
return bcrypt.checkpw(password.encode(), hashed_password.encode())

app/logic/security/security.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from app.core.settings import settings
2+
3+
from .jwt import JWT
4+
from .pwd import PWD
5+
6+
7+
class Security:
8+
def __init__(self):
9+
self.jwt = JWT(settings.APP_SECRET_KEY)
10+
self.pwd = PWD()

app/logic/users/auth/auth.py

-24
This file was deleted.

0 commit comments

Comments
 (0)