feat: deals filters

This commit is contained in:
2025-09-01 17:54:45 +04:00
parent 93141da22c
commit 57c3ada2fa
8 changed files with 90 additions and 14 deletions

View File

@ -4,8 +4,10 @@ from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from backend.session import get_session from backend.session import get_session
from schemas.base import PaginationSchema from schemas.base import PaginationSchema, SortingSchema
from utils.pagination import pagination_parameters from utils.pagination import pagination_parameters
from utils.sorting import sorting_parameters
SessionDependency = Annotated[AsyncSession, Depends(get_session)] SessionDependency = Annotated[AsyncSession, Depends(get_session)]
PaginationDependency = Annotated[PaginationSchema, Depends(pagination_parameters)] PaginationDependency = Annotated[PaginationSchema, Depends(pagination_parameters)]
SortingDependency = Annotated[SortingSchema, Depends(sorting_parameters)]

View File

@ -4,26 +4,42 @@ from sqlalchemy import select
from models import Deal, CardStatusHistory, Board from models import Deal, CardStatusHistory, Board
from repositories.base import BaseRepository from repositories.base import BaseRepository
from schemas.base import SortDir
from schemas.deal import UpdateDealSchema, CreateDealSchema from schemas.deal import UpdateDealSchema, CreateDealSchema
from utils.sorting import apply_sorting
class DealRepository(BaseRepository): class DealRepository(BaseRepository):
async def get_all( async def get_all(
self, self,
board_id: Optional[int],
project_id: Optional[int],
page: Optional[int], page: Optional[int],
items_per_page: Optional[int], items_per_page: Optional[int],
field: Optional[str],
direction: Optional[SortDir],
project_id: Optional[int],
board_id: Optional[int],
status_id: Optional[int],
id: Optional[int],
name: Optional[str],
) -> tuple[list[Deal], int]: ) -> tuple[list[Deal], int]:
stmt = select(Deal).where(Deal.is_deleted.is_(False)) stmt = select(Deal).where(Deal.is_deleted.is_(False))
if board_id: if id:
stmt = stmt.where(Deal.board_id == board_id) stmt = stmt.where(Deal.id == id)
if project_id: if project_id:
stmt = stmt.join(Board).where(Board.project_id == project_id) stmt = stmt.join(Board).where(Board.project_id == project_id)
if board_id:
stmt = stmt.where(Deal.board_id == board_id)
if status_id:
stmt = stmt.where(Deal.status_id == status_id)
if name:
stmt = stmt.where(Deal.name.ilike(f"%{name}%"))
total_items = len((await self.session.execute(stmt)).all()) total_items = len((await self.session.execute(stmt)).all())
if field and direction is not None:
stmt = apply_sorting(stmt, Deal, field, direction)
else:
stmt = stmt.order_by(Deal.lexorank) stmt = stmt.order_by(Deal.lexorank)
if page and items_per_page: if page and items_per_page:

View File

@ -1,6 +1,10 @@
from fastapi import APIRouter, Path, Query from fastapi import APIRouter, Path, Query
from backend.dependecies import SessionDependency, PaginationDependency from backend.dependecies import (
SessionDependency,
PaginationDependency,
SortingDependency,
)
from schemas.deal import * from schemas.deal import *
from services import DealService from services import DealService
@ -17,10 +21,22 @@ deal_router = APIRouter(
async def get_deals( async def get_deals(
session: SessionDependency, session: SessionDependency,
pagination: PaginationDependency, pagination: PaginationDependency,
board_id: Optional[int] = Query(alias="boardId", default=None), sorting: SortingDependency,
project_id: Optional[int] = Query(alias="projectId", default=None), project_id: Optional[int] = Query(alias="projectId", default=None),
board_id: Optional[int] = Query(alias="boardId", default=None),
status_id: Optional[int] = Query(alias="statusId", default=None),
id: Optional[int] = Query(default=None),
name: Optional[str] = Query(default=None),
): ):
return await DealService(session).get_deals(pagination, board_id, project_id) return await DealService(session).get_deals(
pagination,
sorting,
project_id,
board_id,
status_id,
id,
name,
)
@deal_router.post( @deal_router.post(

View File

@ -1,4 +1,4 @@
from typing import Self, Optional from typing import Self, Optional, Literal
from pydantic import BaseModel from pydantic import BaseModel
from pydantic.alias_generators import to_camel from pydantic.alias_generators import to_camel
@ -45,6 +45,14 @@ class PaginationInfoSchema(BaseSchema):
total_items: int total_items: int
type SortDir = Literal["asc", "desc"]
class SortingSchema(BaseSchema):
field: Optional[str] = None
direction: Optional[SortDir] = None
class BaseEnumSchema(BaseSchema): class BaseEnumSchema(BaseSchema):
id: int id: int
name: str name: str

View File

@ -12,6 +12,7 @@ class DealSchema(BaseSchema):
name: str name: str
lexorank: str lexorank: str
status_id: int status_id: int
board_id: int
created_at: datetime created_at: datetime

View File

@ -4,7 +4,7 @@ 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.base import PaginationSchema, SortingSchema
from schemas.deal import * from schemas.deal import *
@ -15,11 +15,15 @@ class DealService:
async def get_deals( async def get_deals(
self, self,
pagination: PaginationSchema, pagination: PaginationSchema,
board_id: Optional[int], sorting: SortingSchema,
project_id: Optional[int], *filters,
) -> GetDealsResponse: ) -> GetDealsResponse:
deals, total_items = await self.repository.get_all( deals, total_items = await self.repository.get_all(
board_id, project_id, pagination.page, pagination.items_per_page pagination.page,
pagination.items_per_page,
sorting.field,
sorting.direction,
*filters,
) )
total_pages = 1 total_pages = 1

21
utils/sorting.py Normal file
View File

@ -0,0 +1,21 @@
from typing import Optional
from fastapi import Query
from sqlalchemy import Select
from schemas.base import SortingSchema, SortDir
from utils.strings import camel_to_snake
async def sorting_parameters(
field: Optional[str] = Query(default=None, alias="sortingField"),
direction: Optional[SortDir] = Query(default=None, alias="sortingDirection"),
) -> SortingSchema:
return SortingSchema(field=camel_to_snake(field), direction=direction)
def apply_sorting(stmt: Select, cls: type, field: str, direction: SortDir) -> Select:
attr = getattr(cls, field)
if direction == "asc":
return stmt.order_by(attr.asc())
return stmt.order_by(attr.desc())

8
utils/strings.py Normal file
View File

@ -0,0 +1,8 @@
import re
from typing import Optional
def camel_to_snake(string: Optional[str]) -> str:
if not string:
return string
return re.sub(r"(?<!^)(?=[A-Z])", "_", string).lower()