
- Add proper model relationships for better querying - Implement character model and endpoints for managing anime characters - Add advanced filtering options (year, score range, source, studio, etc.) - Add statistics endpoint for analyzing anime collection - Include pagination metadata for easier navigation - Create Alembic migration for the new character model - Update README with new features and documentation
171 lines
4.6 KiB
Python
171 lines
4.6 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) |