feat: pagination and query params for a deal end-point

This commit is contained in:
2025-08-28 20:24:24 +04:00
parent 4c7a997be6
commit 5fbd6d6185
6 changed files with 66 additions and 20 deletions

View File

@ -1,6 +1,13 @@
from sqlalchemy import Select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
class BaseRepository: class BaseRepository:
def __init__(self, session: AsyncSession): def __init__(self, session: AsyncSession):
self.session = session self.session = session
@staticmethod
def _apply_pagination(query: Select, page: int, items_per_page: int) -> Select:
offset = (page - 1) * items_per_page
query = query.offset(offset).limit(items_per_page)
return query

View File

@ -2,20 +2,35 @@ from typing import Optional
from sqlalchemy import select from sqlalchemy import select
from models import Deal, CardStatusHistory from models import Deal, CardStatusHistory, Board
from repositories.base import BaseRepository from repositories.base import BaseRepository
from schemas.deal import UpdateDealSchema, CreateDealSchema from schemas.deal import UpdateDealSchema, CreateDealSchema
class DealRepository(BaseRepository): class DealRepository(BaseRepository):
async def get_all(self, board_id: int) -> list[Deal]: async def get_all(
stmt = ( self,
select(Deal) board_id: Optional[int],
.where(Deal.is_deleted.is_(False), Deal.board_id == board_id) project_id: Optional[int],
.order_by(Deal.lexorank) page: Optional[int],
) items_per_page: Optional[int],
) -> tuple[list[Deal], int]:
stmt = select(Deal).where(Deal.is_deleted.is_(False))
if board_id:
stmt = stmt.where(Deal.board_id == board_id)
if project_id:
stmt = stmt.join(Board).where(Board.project_id == project_id)
total_items = len((await self.session.execute(stmt)).all())
stmt = stmt.order_by(Deal.lexorank)
if page and items_per_page:
stmt = self._apply_pagination(stmt, page, items_per_page)
result = await self.session.execute(stmt) result = await self.session.execute(stmt)
return list(result.scalars().all()) return list(result.scalars().all()), total_items
async def get_by_id(self, deal_id: int) -> Optional[Deal]: async def get_by_id(self, deal_id: int) -> Optional[Deal]:
stmt = select(Deal).where(Deal.id == deal_id, Deal.is_deleted.is_(False)) stmt = select(Deal).where(Deal.id == deal_id, Deal.is_deleted.is_(False))

View File

@ -1,6 +1,6 @@
from fastapi import APIRouter, Path from fastapi import APIRouter, Path, Query
from backend.dependecies import SessionDependency from backend.dependecies import SessionDependency, PaginationDependency
from schemas.deal import * from schemas.deal import *
from services import DealService from services import DealService
@ -10,15 +10,17 @@ deal_router = APIRouter(
@deal_router.get( @deal_router.get(
"/{boardId}", "/",
response_model=GetDealsResponse, response_model=GetDealsResponse,
operation_id="get_deals", operation_id="get_deals",
) )
async def get_deals( async def get_deals(
session: SessionDependency, session: SessionDependency,
board_id: int = Path(alias="boardId"), pagination: PaginationDependency,
board_id: Optional[int] = Query(alias="boardId", default=None),
project_id: Optional[int] = Query(alias="projectId", default=None),
): ):
return await DealService(session).get_deals(board_id) return await DealService(session).get_deals(pagination, board_id, project_id)
@deal_router.post( @deal_router.post(

View File

@ -1,7 +1,7 @@
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
from schemas.base import BaseSchema, BaseResponse from schemas.base import BaseSchema, BaseResponse, PaginationInfoSchema
# region Entities # region Entities
@ -48,6 +48,7 @@ class UpdateDealRequest(BaseSchema):
class GetDealsResponse(BaseSchema): class GetDealsResponse(BaseSchema):
deals: list[DealSchema] deals: list[DealSchema]
pagination_info: PaginationInfoSchema
class CreateDealResponse(BaseResponse): class CreateDealResponse(BaseResponse):

View File

@ -1,7 +1,10 @@
import math
from fastapi import HTTPException from fastapi import HTTPException
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from repositories import DealRepository from repositories import DealRepository
from schemas.base import PaginationSchema
from schemas.deal import * from schemas.deal import *
@ -9,10 +12,25 @@ class DealService:
def __init__(self, session: AsyncSession): def __init__(self, session: AsyncSession):
self.repository = DealRepository(session) self.repository = DealRepository(session)
async def get_deals(self, board_id: int) -> GetDealsResponse: async def get_deals(
deals = await self.repository.get_all(board_id) self,
pagination: PaginationSchema,
board_id: Optional[int],
project_id: Optional[int],
) -> GetDealsResponse:
deals, total_items = await self.repository.get_all(
board_id, project_id, pagination.page, pagination.items_per_page
)
total_pages = 1
if pagination.items_per_page:
total_pages = math.ceil(total_items / pagination.items_per_page)
return GetDealsResponse( return GetDealsResponse(
deals=[DealSchema.model_validate(deal) for deal in deals] deals=[DealSchema.model_validate(deal) for deal in deals],
pagination_info=PaginationInfoSchema(
total_pages=total_pages, total_items=total_items
),
) )
async def create_deal(self, request: CreateDealRequest) -> CreateDealResponse: async def create_deal(self, request: CreateDealRequest) -> CreateDealResponse:

View File

@ -1,10 +1,13 @@
from typing import Optional from typing import Optional
from fastapi import Query
from schemas.base import PaginationSchema from schemas.base import PaginationSchema
async def pagination_parameters( async def pagination_parameters(
page: Optional[int] = None, items_per_page: Optional[int] = None page: Optional[int] = Query(default=None),
items_per_page: Optional[int] = Query(default=None, alias="itemsPerPage"),
) -> PaginationSchema: ) -> PaginationSchema:
return PaginationSchema(page=page, items_per_page=items_per_page) return PaginationSchema(page=page, items_per_page=items_per_page)
@ -12,6 +15,6 @@ async def pagination_parameters(
def is_valid_pagination(pagination: Optional[PaginationSchema]) -> bool: def is_valid_pagination(pagination: Optional[PaginationSchema]) -> bool:
if not pagination: if not pagination:
return False return False
return all( return isinstance(pagination.items_per_page, int) and isinstance(
[isinstance(pagination.items_per_page, int), isinstance(pagination.page, int)] pagination.page, int
) )