Add search functionality to API
This commit is contained in:
parent
244afc42b4
commit
6435ac6d82
36
README.md
36
README.md
@ -8,6 +8,7 @@ A simple REST API built with FastAPI and SQLite.
|
|||||||
- SQLite database with SQLAlchemy ORM
|
- SQLite database with SQLAlchemy ORM
|
||||||
- Alembic migrations
|
- Alembic migrations
|
||||||
- CRUD operations for items
|
- CRUD operations for items
|
||||||
|
- Search functionality with multiple filters
|
||||||
- Health check endpoint
|
- Health check endpoint
|
||||||
- Input validation and error handling
|
- Input validation and error handling
|
||||||
- Pagination and filtering
|
- Pagination and filtering
|
||||||
@ -98,12 +99,45 @@ The API will be available at http://localhost:8000.
|
|||||||
|
|
||||||
### Items
|
### 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
|
- `GET /api/v1/items/{item_id}` - Get a specific item
|
||||||
- `POST /api/v1/items` - Create a new item
|
- `POST /api/v1/items` - Create a new item
|
||||||
- `PUT /api/v1/items/{item_id}` - Update an item
|
- `PUT /api/v1/items/{item_id}` - Update an item
|
||||||
- `DELETE /api/v1/items/{item_id}` - Delete 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
|
## Environment Variables
|
||||||
|
|
||||||
You can configure the application using environment variables:
|
You can configure the application using environment variables:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from typing import Any, List, Optional
|
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 sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app import crud, schemas
|
from app import crud, schemas
|
||||||
@ -14,21 +14,76 @@ router = APIRouter()
|
|||||||
response_model=List[schemas.Item],
|
response_model=List[schemas.Item],
|
||||||
status_code=status.HTTP_200_OK,
|
status_code=status.HTTP_200_OK,
|
||||||
summary="Get all items",
|
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(
|
def read_items(
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
skip: int = 0,
|
query: Optional[str] = Query(None, description="Search text in name and description"),
|
||||||
limit: int = 100,
|
active: Optional[bool] = Query(None, description="Filter by active status"),
|
||||||
active: Optional[bool] = None,
|
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:
|
) -> 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)
|
items = crud.item.get_multi_by_active(db, active=active, skip=skip, limit=limit)
|
||||||
else:
|
else:
|
||||||
items = crud.item.get_multi(db, skip=skip, limit=limit)
|
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
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
@ -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 sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.database.crud_base import CRUDBase
|
from app.database.crud_base import CRUDBase
|
||||||
@ -21,6 +22,46 @@ class CRUDItem(CRUDBase[Item, ItemCreate, ItemUpdate]):
|
|||||||
.limit(limit)
|
.limit(limit)
|
||||||
.all()
|
.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)
|
item = CRUDItem(Item)
|
Loading…
x
Reference in New Issue
Block a user