From 6435ac6d82867b6779f57c70a52ea448570c148a Mon Sep 17 00:00:00 2001 From: Automated Action Date: Sat, 17 May 2025 16:27:27 +0000 Subject: [PATCH] Add search functionality to API --- README.md | 36 +++++++++++++++++++- app/api/endpoints/items.py | 69 ++++++++++++++++++++++++++++++++++---- app/crud/crud_item.py | 43 +++++++++++++++++++++++- 3 files changed, 139 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a3806db..835142d 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ A simple REST API built with FastAPI and SQLite. - SQLite database with SQLAlchemy ORM - Alembic migrations - CRUD operations for items +- Search functionality with multiple filters - Health check endpoint - Input validation and error handling - Pagination and filtering @@ -98,12 +99,45 @@ The API will be available at http://localhost:8000. ### Items -- `GET /api/v1/items` - List all items (with pagination and filtering) +- `GET /api/v1/items` - List all items (with pagination, filtering, and search) + - Query Parameters: + - `query` - Search text in name and description + - `active` - Filter by active status (true/false) + - `price_min` - Filter by minimum price (in cents) + - `price_max` - Filter by maximum price (in cents) + - `skip` - Number of records to skip (for pagination) + - `limit` - Maximum number of records to return (for pagination) + +- `GET /api/v1/items/search` - Search for items by name or description + - Query Parameters: + - `query` - Search text in name and description (required) + - `active` - Filter by active status (true/false) + - `price_min` - Filter by minimum price (in cents) + - `price_max` - Filter by maximum price (in cents) + - `skip` - Number of records to skip (for pagination) + - `limit` - Maximum number of records to return (for pagination) + - `GET /api/v1/items/{item_id}` - Get a specific item - `POST /api/v1/items` - Create a new item - `PUT /api/v1/items/{item_id}` - Update an item - `DELETE /api/v1/items/{item_id}` - Delete an item +## Search Functionality + +The API supports searching items by: + +1. Text matching in name and description fields (case-insensitive) +2. Filtering by active status +3. Price range filtering (minimum and maximum price) +4. Combination of all the above filters + +Example search request: +``` +GET /api/v1/items/search?query=phone&active=true&price_min=10000&price_max=50000 +``` + +This will find all active items that contain "phone" in their name or description, with a price between 100.00 and 500.00 (prices are stored in cents). + ## Environment Variables You can configure the application using environment variables: diff --git a/app/api/endpoints/items.py b/app/api/endpoints/items.py index 63b9fd0..053d230 100644 --- a/app/api/endpoints/items.py +++ b/app/api/endpoints/items.py @@ -1,6 +1,6 @@ from typing import Any, List, Optional -from fastapi import APIRouter, Depends, HTTPException, status +from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy.orm import Session from app import crud, schemas @@ -14,21 +14,76 @@ router = APIRouter() response_model=List[schemas.Item], status_code=status.HTTP_200_OK, summary="Get all items", - description="Retrieve all items with pagination and filtering options", + description="Retrieve all items with pagination, filtering, and search options", ) def read_items( db: Session = Depends(get_db), - skip: int = 0, - limit: int = 100, - active: Optional[bool] = None, + query: Optional[str] = Query(None, description="Search text in name and description"), + active: Optional[bool] = Query(None, description="Filter by active status"), + price_min: Optional[int] = Query(None, description="Minimum price in cents"), + price_max: Optional[int] = Query(None, description="Maximum price in cents"), + skip: int = Query(0, description="Number of records to skip"), + limit: int = Query(100, description="Maximum number of records to return"), ) -> Any: """ - Retrieve all items with pagination and optional active status filtering. + Retrieve items with support for: + - Text search in name and description + - Filtering by active status + - Price range filtering + - Pagination with skip and limit parameters """ - if active is not None: + # If search query or price filters are provided, use search method + if query is not None or price_min is not None or price_max is not None: + items = crud.item.search( + db, + query=query or "", + skip=skip, + limit=limit, + active=active, + price_min=price_min, + price_max=price_max + ) + # Otherwise use existing methods + elif active is not None: items = crud.item.get_multi_by_active(db, active=active, skip=skip, limit=limit) else: items = crud.item.get_multi(db, skip=skip, limit=limit) + + return items + + +@router.get( + "/search", + response_model=List[schemas.Item], + status_code=status.HTTP_200_OK, + summary="Search items", + description="Search for items by query text in name and description with additional filters", +) +def search_items( + query: str = Query(..., description="Search text in name and description"), + db: Session = Depends(get_db), + active: Optional[bool] = Query(None, description="Filter by active status"), + price_min: Optional[int] = Query(None, description="Minimum price in cents"), + price_max: Optional[int] = Query(None, description="Maximum price in cents"), + skip: int = Query(0, description="Number of records to skip"), + limit: int = Query(100, description="Maximum number of records to return"), +) -> Any: + """ + Search for items with support for: + - Text search in name and description (required) + - Filtering by active status + - Price range filtering + - Pagination with skip and limit parameters + """ + items = crud.item.search( + db, + query=query, + skip=skip, + limit=limit, + active=active, + price_min=price_min, + price_max=price_max + ) return items diff --git a/app/crud/crud_item.py b/app/crud/crud_item.py index efdf100..5d8de07 100644 --- a/app/crud/crud_item.py +++ b/app/crud/crud_item.py @@ -1,5 +1,6 @@ -from typing import List, Optional +from typing import List, Optional, Any +from sqlalchemy import or_ from sqlalchemy.orm import Session from app.database.crud_base import CRUDBase @@ -21,6 +22,46 @@ class CRUDItem(CRUDBase[Item, ItemCreate, ItemUpdate]): .limit(limit) .all() ) + + def search( + self, db: Session, *, query: str, skip: int = 0, limit: int = 100, **filters: Any + ) -> List[Item]: + """ + Search for items based on the provided query string and additional filters. + + Args: + db: Database session + query: Search query to match against name and description + skip: Number of records to skip + limit: Maximum number of records to return + **filters: Additional filters (e.g., active=True) + + Returns: + List of items matching the search criteria + """ + db_query = db.query(Item) + + # Apply search query to name and description + if query: + search_filter = or_( + Item.name.ilike(f"%{query}%"), + Item.description.ilike(f"%{query}%"), + ) + db_query = db_query.filter(search_filter) + + # Apply additional filters + if "active" in filters and filters["active"] is not None: + db_query = db_query.filter(Item.is_active == filters["active"]) + + # Apply price range filter if provided + if "price_min" in filters and filters["price_min"] is not None: + db_query = db_query.filter(Item.price >= filters["price_min"]) + + if "price_max" in filters and filters["price_max"] is not None: + db_query = db_query.filter(Item.price <= filters["price_max"]) + + # Return paginated results + return db_query.offset(skip).limit(limit).all() item = CRUDItem(Item) \ No newline at end of file