This commit is contained in:
Piotr Domański 2026-04-07 17:12:27 +02:00
parent e34208a91b
commit e7160c922b
10 changed files with 46 additions and 18 deletions

View file

@ -47,6 +47,9 @@ class ContextDependency:
return Context(repo=Repository(session))
_oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
class AuthContextDependency:
"""
Context dependency for authorized endpoints
@ -54,7 +57,7 @@ class AuthContextDependency:
async def __call__(
self,
token: str = Depends(OAuth2PasswordBearer(tokenUrl="/token")),
token: str = Depends(_oauth2_scheme),
session: AsyncSession = Depends(get_db_session),
) -> Context:
ctx = Context(repo=Repository(session))

View file

@ -1,2 +1,4 @@
from .user import UserController
from .product import ProductController
from fooder.controller.user import UserController
from fooder.controller.product import ProductController
__all__ = ["UserController", "ProductController"]

View file

@ -23,11 +23,13 @@ class DatabaseSessionManager:
else {}
),
)
self._sessionmaker: async_sessionmaker[AsyncSession] | None = async_sessionmaker(
autocommit=False,
autoflush=False,
bind=self._engine,
expire_on_commit=False,
self._sessionmaker: async_sessionmaker[AsyncSession] | None = (
async_sessionmaker(
autocommit=False,
autoflush=False,
bind=self._engine,
expire_on_commit=False,
)
)
async def close(self) -> None:

View file

@ -7,6 +7,7 @@ class ApiException(Exception):
def __init__(self, message: str | None = None) -> None:
self.message = message or self.MESSAGE
super().__init__(self.message)
class NotFound(ApiException):

View file

@ -1,4 +1,10 @@
from .base import ObjModelMixin, Macronutrient, OptionalMacronutrient, Calories, OptionalCalories
from .base import (
ObjModelMixin,
Macronutrient,
OptionalMacronutrient,
Calories,
OptionalCalories,
)
from pydantic import BaseModel
from fooder.utils.calories import calculate_calories

View file

@ -1 +1,3 @@
from .repository import Repository
from fooder.repository.repository import Repository
__all__ = ["Repository"]

View file

@ -1,6 +1,13 @@
from typing import TypeVar, Generic, Type, Sequence
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import Delete, Update, select, delete as sa_delete, update as sa_update, ColumnElement
from sqlalchemy import (
Delete,
Update,
ColumnElement,
select,
delete as sa_delete,
update as sa_update,
)
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm.exc import StaleDataError
from sqlalchemy.sql import Select

View file

@ -7,10 +7,12 @@ from fooder.exc import NotFound
router = APIRouter(tags=["product"])
_auth_ctx = AuthContextDependency()
@router.get("", response_model=list[ProductModel])
async def list_products(
ctx: Context = Depends(AuthContextDependency()),
ctx: Context = Depends(_auth_ctx),
limit: int = 10,
offset: int = 0,
q: str | None = None,
@ -22,7 +24,7 @@ async def list_products(
async def update_product(
product_id: int,
data: ProductUpdateModel,
ctx: Context = Depends(AuthContextDependency()),
ctx: Context = Depends(_auth_ctx),
):
async with ctx.repo.transaction():
obj = await ctx.repo.product.get_by_id(product_id)
@ -33,7 +35,7 @@ async def update_product(
@router.get("/barcode/{barcode}", response_model=ProductModel)
async def get_by_barcode(
barcode: str,
ctx: Context = Depends(AuthContextDependency()),
ctx: Context = Depends(_auth_ctx),
):
try:
return await ctx.repo.product.get_by_barcode(barcode)
@ -46,7 +48,7 @@ async def get_by_barcode(
@router.post("", response_model=ProductModel, status_code=201)
async def create_product(
data: ProductCreateModel,
ctx: Context = Depends(AuthContextDependency()),
ctx: Context = Depends(_auth_ctx),
):
async with ctx.repo.transaction():
ctrl = await ProductController.create(ctx, data)

View file

@ -11,6 +11,8 @@ from fooder.utils.jwt import RefreshToken, generate_token_pair
router = APIRouter(tags=["token"])
_ctx = ContextDependency()
def gen_token_response(entity_id: int, now: datetime) -> TokenResponse:
access_token, refresh_token = generate_token_pair(entity_id, now)
@ -23,7 +25,7 @@ def gen_token_response(entity_id: int, now: datetime) -> TokenResponse:
@router.post("", response_model=TokenResponse)
async def token_create(
data: Annotated[OAuth2PasswordRequestForm, Depends()],
ctx: Context = Depends(ContextDependency()),
ctx: Context = Depends(_ctx),
) -> TokenResponse:
now = ctx.clock()
user_ctrl = await UserController.session_start(ctx, data.username, data.password)
@ -33,7 +35,7 @@ async def token_create(
@router.post("/refresh", response_model=TokenResponse)
async def token_refresh(
refresh_token: str,
ctx: Context = Depends(ContextDependency()),
ctx: Context = Depends(_ctx),
) -> TokenResponse:
now = ctx.clock()
token = RefreshToken.decode(refresh_token)

View file

@ -1,6 +1,7 @@
[flake8]
max-line-length = 80
max-line-length = 89
extend-select = B950
extend-ignore = E203,E501,E701,E712
exclude = fooder/alembic,fooder/test
extend-immutable-calls =
Depends