MukeshKapoor25's picture
feat(project): remove heavy nested collections from project customer response for performance improvement
4f4cfa4
from fastapi import APIRouter, Depends, status, Query
from sqlalchemy.orm import Session
from app.db.session import get_db
from app.services.project_service import ProjectService
from app.schemas.project import ProjectCreate, ProjectOut
from app.schemas.project_detail import ProjectDetailOut, ProjectCustomerOut, ProjectDetailNoCustomersOut
from app.schemas.paginated_response import PaginatedResponse
from typing import List, Optional
router = APIRouter(prefix="/api/v1/projects", tags=["projects"])
@router.get("/", response_model=PaginatedResponse[ProjectOut])
def list_projects(
customer_type: Optional[int] = Query(0, description="Customer type filter (0 for all types)", ge=0),
status: Optional[int] = Query(None, description="Status filter (None excludes status=3, pass specific value to include all)"),
order_by: Optional[str] = Query("project_no", description="Field to order by"),
order_direction: Optional[str] = Query("asc", description="Order direction: asc or desc", regex="^(asc|desc)$"),
page: Optional[int] = Query(1, description="Page number (1-indexed)", ge=1),
page_size: Optional[int] = Query(10, description="Number of records per page", ge=1, le=100),
db: Session = Depends(get_db)
):
"""
Get paginated list of projects with filtering and sorting.
- **customer_type**: Filter by customer type (0 = all types)
- **status**: Filter by status (None = exclude status 3, pass specific value to include all)
- **order_by**: Field name to sort by (project_no, project_name, etc.)
- **order_direction**: Sort direction (asc or desc)
- **page**: Page number starting from 1
- **page_size**: Number of records per page (max 100)
"""
service = ProjectService(db)
return service.list_projects(
customer_type=customer_type,
status=status,
order_by=order_by,
order_direction=order_direction,
page=page,
page_size=page_size
)
@router.get("/{project_no}", response_model=ProjectDetailNoCustomersOut)
def get_project(project_no: int, db: Session = Depends(get_db)):
"""Get a specific project by ProjectNo without customers collection
For customer data, use `/api/v1/projects/{project_no}/customers` instead.
"""
service = ProjectService(db)
# Use lean method that does not fetch customers or notes for performance
return service.get_detail_no_customers(project_no)
@router.get(
"/{project_no}/customers",
response_model=List[ProjectCustomerOut],
response_model_exclude={"barrier_sizes", "contacts", "bidder_notes"}
)
def get_project_customers(
project_no: int,
page: Optional[int] = Query(1, description="Page number (1-indexed)", ge=1),
page_size: Optional[int] = Query(25, description="Number of records per page", ge=1, le=1000),
last_id: Optional[int] = Query(None, description="Keyset pagination anchor: last seen bidder Id"),
db: Session = Depends(get_db)
):
"""Get customers associated with a specific project (by ProjectNo)
Returns a paginated list of customers for the project. Pagination defaults
to page=1 and page_size=100 to avoid very large responses.
"""
service = ProjectService(db)
# Use the dedicated service method to fetch only customers for the project
return service.get_customers(project_no, page=page, page_size=page_size, last_id=last_id)
@router.get(
"/{project_no}/customers/{customer_id}",
response_model=ProjectCustomerOut,
response_model_exclude={"barrier_sizes", "contacts", "bidder_notes"}
)
def get_project_customer_detail(
project_no: int,
customer_id: str,
db: Session = Depends(get_db)
):
"""Get detailed bidder information for a specific customer on a project
Returns the complete bidder details including barrier sizes, contacts, and notes
for the specified project number and customer ID combination.
- **project_no**: The project number
- **customer_id**: The customer ID (CustId from Bidders table)
"""
service = ProjectService(db)
return service.get_project_customer_detail(project_no, customer_id)
@router.post("/", response_model=ProjectOut, status_code=status.HTTP_201_CREATED)
def create_project(project_in: ProjectCreate, db: Session = Depends(get_db)):
"""
Create a new project using stored procedure
Creates a new project record using the spProjectsInsert stored procedure.
All project fields including billing, shipping, payment, and project details
can be provided in the request body.
Returns the created project with the generated ProjectNo.
"""
service = ProjectService(db)
return service.create(project_in.model_dump())
@router.put("/{project_no}", response_model=ProjectOut)
def update_project(project_no: int, project_in: ProjectCreate, db: Session = Depends(get_db)):
"""
Update an existing project using stored procedure
Updates an existing project record using the spProjectsUpdate stored procedure.
All project fields including billing, shipping, payment, and project details
can be updated in the request body.
Returns the updated project data.
"""
service = ProjectService(db)
return service.update(project_no, project_in.model_dump())
@router.delete("/{project_no}", status_code=status.HTTP_204_NO_CONTENT)
def delete_project(project_no: int, db: Session = Depends(get_db)):
"""Delete a project"""
service = ProjectService(db)
service.delete(project_no)
return None