Skip to content

ultra super mega refactoring #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
│   ├── deps.py
│   └── v1
│   ├── __init__.py
│   ├── auth
│   │   ├── __init__.py
│   │   └── token.py
│   └── users
│   ├── __init__.py
│   ├── auth
│   │   ├── __init__.py
│   │   └── token.py
│   ├── create.py
│   └── retrieve.py
├── core
Expand All @@ -23,27 +23,36 @@
│   └── settings.py
├── logic
│   ├── __init__.py
│   ├── auth
│   │   ├── __init__.py
│   │   └── auth.py
│   ├── logic.py
│   ├── security
│   │   ├── __init__.py
│   │   ├── jwt.py
│   │   └── pwd.py
│   │   ├── pwd.py
│   │   └── security.py
│   └── users
│   ├── __init__.py
│   ├── auth
│   │   ├── __init__.py
│   │   └── auth.py
│   └── users.py
├── models
│   ├── __init__.py
│   ├── auth
│   │   ├── __init__.py
│   │   └── token.py
│   ├── base.py
│   ├── token.py
│   └── user.py
│   ├── types
│   │   ├── __init__.py
│   │   └── unix.py
│   └── users
│   ├── __init__.py
│   └── user.py
└── repositories
├── __init__.py
├── abstract.py
├── base.py
└── user.py

11 directories, 28 files
14 directories, 34 files
```

## Create a `.env` file based on `.env.dist` and make all the necessary customizations
Expand Down
9 changes: 5 additions & 4 deletions app/api/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,20 @@
from fastapi.security import APIKeyHeader

from app.logic import Logic as _Logic
from app.models.user import User as _User
from app.models.users.user import User as _User


async def get_logic() -> _Logic:
return await _Logic.create()
async with Logic.create() as logic:
yield logic


Logic = Annotated[_Logic, Depends(get_logic)]


async def get_user(
token: Annotated[str, Depends(APIKeyHeader(name='access-token'))],
logic: Logic,
token: Annotated[str, Depends(APIKeyHeader(name='access-token'))],
logic: Logic,
) -> _User | None:
return await logic.users.retrieve_by_token(token)

Expand Down
3 changes: 2 additions & 1 deletion app/api/v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@

from fastapi import APIRouter

from . import users
from . import auth, users

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

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

__all__ = ['router']
File renamed without changes.
6 changes: 3 additions & 3 deletions app/api/v1/users/auth/token.py → app/api/v1/auth/token.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from fastapi import APIRouter

from app.api import deps
from app.models.token import AccessToken
from app.models.user import UserCreate
from app.models.auth import AccessToken
from app.models.users.user import UserCreate

router = APIRouter(prefix='/token')

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


__all__ = ['router']
3 changes: 1 addition & 2 deletions app/api/v1/users/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@

from fastapi import APIRouter

from . import auth, create, retrieve
from . import create, retrieve

router = APIRouter(prefix='/users', tags=['users'])
router.include_router(auth.router)
router.include_router(create.router)
router.include_router(retrieve.router)

Expand Down
4 changes: 2 additions & 2 deletions app/api/v1/users/create.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from fastapi import APIRouter

from app.api import deps
from app.models.user import UserCreate, UserRead
from app.models.users.user import UserCreate, UserRead

router = APIRouter(prefix='/create')

Expand All @@ -11,7 +11,7 @@ async def create(data: UserCreate, logic: deps.Logic):
"""
Create user
"""
return await logic.users.create(**data.model_dump())
return await logic.users.create(data)


__all__ = ['router']
2 changes: 1 addition & 1 deletion app/api/v1/users/retrieve.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from fastapi import APIRouter

from app.api import deps
from app.models.user import UserRead
from app.models.users.user import UserRead

router = APIRouter()

Expand Down
42 changes: 22 additions & 20 deletions app/core/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Database
"""

from typing import NoReturn, Self
from typing import Self

from sqlalchemy.ext.asyncio import (AsyncEngine, async_sessionmaker,
create_async_engine)
Expand All @@ -15,47 +15,49 @@
class Database:
_instance = None

def __new__(cls, *args, **kwargs) -> Self:
def __new__(cls, *args, **kwargs) -> 'Database':
if cls._instance is None:
cls._instance = super(Database, cls).__new__(cls)
return cls._instance

def __init__(
self,
engine: AsyncEngine | None = None,
session: AsyncSession | None = None,
self,
engine: AsyncEngine | None = None,
session: AsyncSession | None = None,
) -> None:
if not hasattr(self, 'initialized'):
self.engine = engine
self.session = session
self.__engine = engine
self.__session = session
self.initialized = True

async def __set_async_engine(self) -> NoReturn:
if self.engine is None:
self.engine = create_async_engine(
async def __set_async_engine(self) -> None:
if self.__engine is None:
self.__engine = create_async_engine(
settings.pg_dsn.unicode_string(), echo=False, future=True
)

async def __set_async_session(self) -> NoReturn:
if self.session is None:
self.session = async_sessionmaker(
async def __set_async_session(self) -> None:
if self.__session is None:
self.__session = async_sessionmaker(
autocommit=False,
autoflush=False,
bind=self.engine,
bind=self.__engine,
class_=AsyncSession,
expire_on_commit=False,
)()

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

async def __aenter__(self) -> Self:
await self.__set_async_engine()
await self.__set_async_session()
await self.__set_repositories()
return self

async def __aexit__(self, exc_type, exc_value, traceback) -> NoReturn:
if self.session is not None:
await self.session.close()
async def __aexit__(self, exc_type, exc_value, traceback) -> None:
if self.__session is not None:
await self.__session.commit()
await self.__session.close()
self.__session = None
20 changes: 1 addition & 19 deletions app/logic/__init__.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,3 @@
from typing import Self

from app.core.db import Database

from .security import Security
from .users import Users


class Logic:
def __init__(self, db: Database):
self.db = db
self.security = Security()
self.users = Users(self)

@classmethod
async def create(cls) -> Self:
async with Database() as db:
return cls(db)

from .logic import Logic

__all__ = ['Logic']
File renamed without changes.
24 changes: 24 additions & 0 deletions app/logic/auth/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from typing import TYPE_CHECKING

from app.core import exps
from app.models.auth import AccessToken

if TYPE_CHECKING:
from app.logic import Logic


class Auth:
def __init__(self, logic: 'Logic'):
self.logic = logic

async def generate_token(
self, email: str, password: str
) -> AccessToken | None:
if (user := await self.logic.db.user.retrieve_by_email(email)) is None:
raise exps.UserNotFoundException()
if not self.logic.security.pwd.checkpwd(password, user.password):
raise exps.UserIsCorrectException()
access_token = self.logic.security.jwt.encode_token(
{'id': user.id}, 1440
)
return AccessToken(token=access_token)
22 changes: 22 additions & 0 deletions app/logic/logic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from typing import Self, AsyncGenerator
from contextlib import asynccontextmanager

from app.core.db import Database

from .security import Security
from .users import Users
from .auth import Auth


class Logic:
def __init__(self, db: Database):
self.db = db
self.security = Security()
self.users = Users(self)
self.auth = Auth(self)

@classmethod
@asynccontextmanager
async def create(cls) -> AsyncGenerator[Self, None]:
async with Database() as db:
yield cls(db)
12 changes: 1 addition & 11 deletions app/logic/security/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,3 @@
from app.core.settings import settings

from .jwt import JWT
from .pwd import PWD


class Security:
def __init__(self):
self.jwt = JWT(settings.APP_SECRET_KEY)
self.pwd = PWD()

from .security import Security

__all__ = ['Security']
6 changes: 4 additions & 2 deletions app/logic/security/jwt.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class JWT:
def __init__(self, secret_key: str):
self.secret_key: str = secret_key

def decode_token(self, token: str) -> dict | None:
def decode_token(self, token: str) -> dict:
try:
payload = jwt.decode(token, self.secret_key, algorithms=['HS256'])
except Exception:
Expand All @@ -18,7 +18,9 @@ def decode_token(self, token: str) -> dict | None:
exp = payload.get('exp')
if exp and dt.datetime.now(dt.UTC).timestamp() > exp:
raise exps.TokenExpiredException()
return payload.get('payload')
if (payload := payload.get('payload', None)) is None:
raise exps.TokenInvalidException()
return payload

def encode_token(self, payload: dict, minutes: int) -> str:
claims = {
Expand Down
6 changes: 4 additions & 2 deletions app/logic/security/pwd.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@


class PWD:
def hashpwd(self, password: str) -> str:
@staticmethod
def hashpwd(password: str) -> str:
return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()

def checkpwd(self, password: str, hashed_password: str) -> bool:
@staticmethod
def checkpwd(password: str, hashed_password: str) -> bool:
return bcrypt.checkpw(password.encode(), hashed_password.encode())
10 changes: 10 additions & 0 deletions app/logic/security/security.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from app.core.settings import settings

from .jwt import JWT
from .pwd import PWD


class Security:
def __init__(self):
self.jwt = JWT(settings.APP_SECRET_KEY)
self.pwd = PWD()
24 changes: 0 additions & 24 deletions app/logic/users/auth/auth.py

This file was deleted.

Loading