
- Add advanced search filters (year range, score, studio, source) - Create detailed statistics endpoint for anime collection - Implement enhanced pagination metadata for better navigation - Add sorting options for search results - Enhance anime model with additional fields (season info, ratings, etc.) - Add ability to search anime by character attributes - Create migration for new anime model fields - Update README with detailed documentation of new features
275 lines
8.1 KiB
Python
275 lines
8.1 KiB
Python
from typing import Any, Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app import crud, schemas
|
|
from app.api import deps
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/", response_model=schemas.anime.AnimeSearchResults)
|
|
def search_anime(
|
|
*,
|
|
db: Session = Depends(deps.get_db),
|
|
title: Optional[str] = None,
|
|
genre_id: Optional[int] = None,
|
|
status: Optional[str] = None,
|
|
year_from: Optional[int] = None,
|
|
year_to: Optional[int] = None,
|
|
score_min: Optional[float] = None,
|
|
score_max: Optional[float] = None,
|
|
source: Optional[str] = None,
|
|
studio: Optional[str] = None,
|
|
sort_by: Optional[str] = None,
|
|
sort_order: Optional[str] = "asc",
|
|
skip: int = 0,
|
|
limit: int = 100,
|
|
) -> Any:
|
|
"""
|
|
Search for anime with advanced filters.
|
|
|
|
- **title**: Filter by title (partial match)
|
|
- **genre_id**: Filter by genre ID
|
|
- **status**: Filter by anime status (airing, finished, upcoming)
|
|
- **year_from**: Filter by starting year (inclusive)
|
|
- **year_to**: Filter by ending year (inclusive)
|
|
- **score_min**: Filter by minimum score (inclusive)
|
|
- **score_max**: Filter by maximum score (inclusive)
|
|
- **source**: Filter by source material (manga, light novel, etc.)
|
|
- **studio**: Filter by studio name (partial match)
|
|
- **sort_by**: Sort by field (id, title, score, popularity, etc.)
|
|
- **sort_order**: Sort order (asc, desc)
|
|
- **skip**: Number of items to skip for pagination
|
|
- **limit**: Maximum number of items to return
|
|
"""
|
|
anime = crud.anime.search(
|
|
db,
|
|
title=title,
|
|
genre_id=genre_id,
|
|
status=status,
|
|
year_from=year_from,
|
|
year_to=year_to,
|
|
score_min=score_min,
|
|
score_max=score_max,
|
|
source=source,
|
|
studio=studio,
|
|
sort_by=sort_by,
|
|
sort_order=sort_order,
|
|
skip=skip,
|
|
limit=limit
|
|
)
|
|
|
|
total = crud.anime.search_count(
|
|
db,
|
|
title=title,
|
|
genre_id=genre_id,
|
|
status=status,
|
|
year_from=year_from,
|
|
year_to=year_to,
|
|
score_min=score_min,
|
|
score_max=score_max,
|
|
source=source,
|
|
studio=studio
|
|
)
|
|
|
|
# Calculate pagination metadata
|
|
page = skip // limit + 1 if limit > 0 else 1
|
|
total_pages = (total + limit - 1) // limit if limit > 0 else 1
|
|
has_next = page < total_pages
|
|
has_prev = page > 1
|
|
|
|
return {
|
|
"results": anime,
|
|
"total": total,
|
|
"page": page,
|
|
"size": limit,
|
|
"pages": total_pages,
|
|
"has_next": has_next,
|
|
"has_prev": has_prev,
|
|
"next_page": page + 1 if has_next else None,
|
|
"prev_page": page - 1 if has_prev else None
|
|
}
|
|
|
|
|
|
@router.post("/", response_model=schemas.anime.Anime)
|
|
def create_anime(
|
|
*,
|
|
db: Session = Depends(deps.get_db),
|
|
anime_in: schemas.anime.AnimeCreate,
|
|
) -> Any:
|
|
"""
|
|
Create new anime.
|
|
"""
|
|
return crud.anime.create_with_genres(db=db, obj_in=anime_in)
|
|
|
|
|
|
@router.get("/{id}", response_model=schemas.anime.Anime)
|
|
def read_anime(
|
|
*,
|
|
db: Session = Depends(deps.get_db),
|
|
id: int,
|
|
) -> Any:
|
|
"""
|
|
Get anime by ID.
|
|
"""
|
|
anime = crud.anime.get(db=db, id=id)
|
|
if not anime:
|
|
raise HTTPException(status_code=404, detail="Anime not found")
|
|
return anime
|
|
|
|
|
|
@router.put("/{id}", response_model=schemas.anime.Anime)
|
|
def update_anime(
|
|
*,
|
|
db: Session = Depends(deps.get_db),
|
|
id: int,
|
|
anime_in: schemas.anime.AnimeUpdate,
|
|
) -> Any:
|
|
"""
|
|
Update an anime.
|
|
"""
|
|
anime = crud.anime.get(db=db, id=id)
|
|
if not anime:
|
|
raise HTTPException(status_code=404, detail="Anime not found")
|
|
anime = crud.anime.update_with_genres(db=db, db_obj=anime, obj_in=anime_in)
|
|
return anime
|
|
|
|
|
|
@router.delete("/{id}", response_model=None, status_code=204)
|
|
def delete_anime(
|
|
*,
|
|
db: Session = Depends(deps.get_db),
|
|
id: int,
|
|
) -> Any:
|
|
"""
|
|
Delete an anime.
|
|
"""
|
|
anime = crud.anime.get(db=db, id=id)
|
|
if not anime:
|
|
raise HTTPException(status_code=404, detail="Anime not found")
|
|
crud.anime.remove(db=db, id=id)
|
|
return None
|
|
|
|
|
|
@router.get("/statistics", response_model=schemas.anime.AnimeStatistics)
|
|
def get_anime_statistics(
|
|
db: Session = Depends(deps.get_db),
|
|
) -> Any:
|
|
"""
|
|
Get statistics about the anime collection.
|
|
|
|
Returns various statistical data including:
|
|
- Total anime count
|
|
- Average score and episode count
|
|
- Distribution by status, source, and studio
|
|
- Yearly distribution of releases
|
|
- Score distribution
|
|
- Top genres
|
|
"""
|
|
return crud.anime.get_statistics(db=db)
|
|
|
|
|
|
@router.get("/search-by-character", response_model=schemas.anime.AnimeSearchResults)
|
|
def search_anime_by_character(
|
|
*,
|
|
db: Session = Depends(deps.get_db),
|
|
character_name: Optional[str] = None,
|
|
character_role: Optional[str] = None,
|
|
character_gender: Optional[str] = None,
|
|
include_anime_filters: bool = False,
|
|
title: Optional[str] = None,
|
|
genre_id: Optional[int] = None,
|
|
status: Optional[str] = None,
|
|
year_from: Optional[int] = None,
|
|
year_to: Optional[int] = None,
|
|
score_min: Optional[float] = None,
|
|
score_max: Optional[float] = None,
|
|
source: Optional[str] = None,
|
|
studio: Optional[str] = None,
|
|
sort_by: Optional[str] = None,
|
|
sort_order: Optional[str] = "asc",
|
|
skip: int = 0,
|
|
limit: int = 100,
|
|
) -> Any:
|
|
"""
|
|
Search for anime based on character attributes.
|
|
|
|
- **character_name**: Filter anime by character name (partial match)
|
|
- **character_role**: Filter anime by character role (e.g., "Main", "Supporting")
|
|
- **character_gender**: Filter anime by character gender
|
|
- **include_anime_filters**: Include anime filters below in the search
|
|
|
|
Optional anime filters (if include_anime_filters is True):
|
|
- **title**: Filter by anime title (partial match)
|
|
- **genre_id**: Filter by genre ID
|
|
- **status**: Filter by anime status (airing, finished, upcoming)
|
|
- **year_from**: Filter by starting year (inclusive)
|
|
- **year_to**: Filter by ending year (inclusive)
|
|
- **score_min**: Filter by minimum score (inclusive)
|
|
- **score_max**: Filter by maximum score (inclusive)
|
|
- **source**: Filter by source material (manga, light novel, etc.)
|
|
- **studio**: Filter by studio name (partial match)
|
|
- **sort_by**: Sort by field (id, title, score, popularity, etc.)
|
|
- **sort_order**: Sort order (asc, desc)
|
|
|
|
Pagination parameters:
|
|
- **skip**: Number of items to skip for pagination
|
|
- **limit**: Maximum number of items to return
|
|
"""
|
|
anime = crud.anime.search_by_character(
|
|
db,
|
|
character_name=character_name,
|
|
character_role=character_role,
|
|
character_gender=character_gender,
|
|
include_anime_filters=include_anime_filters,
|
|
title=title,
|
|
genre_id=genre_id,
|
|
status=status,
|
|
year_from=year_from,
|
|
year_to=year_to,
|
|
score_min=score_min,
|
|
score_max=score_max,
|
|
source=source,
|
|
studio=studio,
|
|
sort_by=sort_by,
|
|
sort_order=sort_order,
|
|
skip=skip,
|
|
limit=limit,
|
|
)
|
|
|
|
total = crud.anime.search_by_character_count(
|
|
db,
|
|
character_name=character_name,
|
|
character_role=character_role,
|
|
character_gender=character_gender,
|
|
include_anime_filters=include_anime_filters,
|
|
title=title,
|
|
genre_id=genre_id,
|
|
status=status,
|
|
year_from=year_from,
|
|
year_to=year_to,
|
|
score_min=score_min,
|
|
score_max=score_max,
|
|
source=source,
|
|
studio=studio,
|
|
)
|
|
|
|
# Calculate pagination metadata
|
|
page = skip // limit + 1 if limit > 0 else 1
|
|
total_pages = (total + limit - 1) // limit if limit > 0 else 1
|
|
has_next = page < total_pages
|
|
has_prev = page > 1
|
|
|
|
return {
|
|
"results": anime,
|
|
"total": total,
|
|
"page": page,
|
|
"size": limit,
|
|
"pages": total_pages,
|
|
"has_next": has_next,
|
|
"has_prev": has_prev,
|
|
"next_page": page + 1 if has_next else None,
|
|
"prev_page": page - 1 if has_prev else None
|
|
} |