app v1
This commit is contained in:
0
backend/app/routers/__init__.py
Normal file
0
backend/app/routers/__init__.py
Normal file
134
backend/app/routers/backgrounds.py
Normal file
134
backend/app/routers/backgrounds.py
Normal file
@@ -0,0 +1,134 @@
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from ..database import get_db
|
||||
from ..db_models import Background, Difficulty
|
||||
from ..schemas import (
|
||||
BackgroundCreate,
|
||||
BackgroundUpdate,
|
||||
BackgroundResponse,
|
||||
BackgroundListResponse,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/backgrounds", tags=["backgrounds"])
|
||||
|
||||
|
||||
@router.get("", response_model=BackgroundListResponse)
|
||||
async def list_backgrounds(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=500),
|
||||
difficulty: Optional[Difficulty] = None,
|
||||
search: Optional[str] = None,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""List all backgrounds with pagination and filtering."""
|
||||
query = select(Background)
|
||||
|
||||
if difficulty:
|
||||
query = query.where(Background.difficulty == difficulty)
|
||||
if search:
|
||||
query = query.where(Background.name.ilike(f"%{search}%"))
|
||||
|
||||
# Count total
|
||||
count_query = select(func.count(Background.id))
|
||||
if difficulty:
|
||||
count_query = count_query.where(Background.difficulty == difficulty)
|
||||
if search:
|
||||
count_query = count_query.where(Background.name.ilike(f"%{search}%"))
|
||||
total_result = await db.execute(count_query)
|
||||
total = total_result.scalar()
|
||||
|
||||
# Get items
|
||||
query = query.order_by(Background.difficulty, Background.name)
|
||||
query = query.offset(skip).limit(limit)
|
||||
result = await db.execute(query)
|
||||
backgrounds = result.scalars().all()
|
||||
|
||||
return BackgroundListResponse(backgrounds=backgrounds, total=total)
|
||||
|
||||
|
||||
@router.get("/{background_id}", response_model=BackgroundResponse)
|
||||
async def get_background(background_id: int, db: AsyncSession = Depends(get_db)):
|
||||
"""Get a single background by ID."""
|
||||
query = select(Background).where(Background.id == background_id)
|
||||
result = await db.execute(query)
|
||||
background = result.scalar_one_or_none()
|
||||
|
||||
if not background:
|
||||
raise HTTPException(status_code=404, detail="Background not found")
|
||||
|
||||
return background
|
||||
|
||||
|
||||
@router.post("", response_model=BackgroundResponse, status_code=201)
|
||||
async def create_background(data: BackgroundCreate, db: AsyncSession = Depends(get_db)):
|
||||
"""Create a new background."""
|
||||
background = Background(
|
||||
name=data.name,
|
||||
video_file=data.video_file,
|
||||
difficulty=data.difficulty,
|
||||
)
|
||||
|
||||
db.add(background)
|
||||
await db.commit()
|
||||
await db.refresh(background)
|
||||
|
||||
return background
|
||||
|
||||
|
||||
@router.put("/{background_id}", response_model=BackgroundResponse)
|
||||
async def update_background(
|
||||
background_id: int,
|
||||
data: BackgroundUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Update a background."""
|
||||
query = select(Background).where(Background.id == background_id)
|
||||
result = await db.execute(query)
|
||||
background = result.scalar_one_or_none()
|
||||
|
||||
if not background:
|
||||
raise HTTPException(status_code=404, detail="Background not found")
|
||||
|
||||
# Update fields
|
||||
if data.name is not None:
|
||||
background.name = data.name
|
||||
if data.video_file is not None:
|
||||
background.video_file = data.video_file
|
||||
if data.difficulty is not None:
|
||||
background.difficulty = data.difficulty
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(background)
|
||||
|
||||
return background
|
||||
|
||||
|
||||
@router.delete("/{background_id}", status_code=204)
|
||||
async def delete_background(background_id: int, db: AsyncSession = Depends(get_db)):
|
||||
"""Delete a background."""
|
||||
query = select(Background).where(Background.id == background_id)
|
||||
result = await db.execute(query)
|
||||
background = result.scalar_one_or_none()
|
||||
|
||||
if not background:
|
||||
raise HTTPException(status_code=404, detail="Background not found")
|
||||
|
||||
await db.delete(background)
|
||||
await db.commit()
|
||||
|
||||
|
||||
@router.get("/by-difficulty/{difficulty}", response_model=BackgroundListResponse)
|
||||
async def get_backgrounds_by_difficulty(
|
||||
difficulty: Difficulty,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Get all backgrounds for a specific difficulty."""
|
||||
query = select(Background).where(Background.difficulty == difficulty)
|
||||
query = query.order_by(Background.name)
|
||||
result = await db.execute(query)
|
||||
backgrounds = result.scalars().all()
|
||||
|
||||
return BackgroundListResponse(backgrounds=backgrounds, total=len(backgrounds))
|
||||
223
backend/app/routers/openings.py
Normal file
223
backend/app/routers/openings.py
Normal file
@@ -0,0 +1,223 @@
|
||||
from typing import List, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from ..database import get_db
|
||||
from ..db_models import Opening, OpeningPoster
|
||||
from ..schemas import (
|
||||
OpeningCreate,
|
||||
OpeningUpdate,
|
||||
OpeningResponse,
|
||||
OpeningListResponse,
|
||||
OpeningPosterResponse,
|
||||
AddPosterRequest,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/openings", tags=["openings"])
|
||||
|
||||
|
||||
@router.get("", response_model=OpeningListResponse)
|
||||
async def list_openings(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=500),
|
||||
search: Optional[str] = None,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""List all openings with pagination and search."""
|
||||
query = select(Opening).options(selectinload(Opening.posters))
|
||||
|
||||
if search:
|
||||
query = query.where(Opening.anime_name.ilike(f"%{search}%"))
|
||||
|
||||
# Count total
|
||||
count_query = select(func.count(Opening.id))
|
||||
if search:
|
||||
count_query = count_query.where(Opening.anime_name.ilike(f"%{search}%"))
|
||||
total_result = await db.execute(count_query)
|
||||
total = total_result.scalar()
|
||||
|
||||
# Get items
|
||||
query = query.order_by(Opening.anime_name, Opening.op_number)
|
||||
query = query.offset(skip).limit(limit)
|
||||
result = await db.execute(query)
|
||||
openings = result.scalars().all()
|
||||
|
||||
return OpeningListResponse(openings=openings, total=total)
|
||||
|
||||
|
||||
@router.get("/{opening_id}", response_model=OpeningResponse)
|
||||
async def get_opening(opening_id: int, db: AsyncSession = Depends(get_db)):
|
||||
"""Get a single opening by ID."""
|
||||
query = select(Opening).options(selectinload(Opening.posters)).where(Opening.id == opening_id)
|
||||
result = await db.execute(query)
|
||||
opening = result.scalar_one_or_none()
|
||||
|
||||
if not opening:
|
||||
raise HTTPException(status_code=404, detail="Opening not found")
|
||||
|
||||
return opening
|
||||
|
||||
|
||||
@router.post("", response_model=OpeningResponse, status_code=201)
|
||||
async def create_opening(data: OpeningCreate, db: AsyncSession = Depends(get_db)):
|
||||
"""Create a new opening."""
|
||||
opening = Opening(
|
||||
anime_name=data.anime_name,
|
||||
op_number=data.op_number,
|
||||
song_name=data.song_name,
|
||||
audio_file=data.audio_file,
|
||||
)
|
||||
|
||||
# Add posters
|
||||
for i, poster_file in enumerate(data.poster_files):
|
||||
poster = OpeningPoster(
|
||||
poster_file=poster_file,
|
||||
is_default=(i == 0) # First poster is default
|
||||
)
|
||||
opening.posters.append(poster)
|
||||
|
||||
db.add(opening)
|
||||
await db.commit()
|
||||
await db.refresh(opening)
|
||||
|
||||
# Reload with posters
|
||||
query = select(Opening).options(selectinload(Opening.posters)).where(Opening.id == opening.id)
|
||||
result = await db.execute(query)
|
||||
return result.scalar_one()
|
||||
|
||||
|
||||
@router.put("/{opening_id}", response_model=OpeningResponse)
|
||||
async def update_opening(
|
||||
opening_id: int,
|
||||
data: OpeningUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Update an opening."""
|
||||
query = select(Opening).options(selectinload(Opening.posters)).where(Opening.id == opening_id)
|
||||
result = await db.execute(query)
|
||||
opening = result.scalar_one_or_none()
|
||||
|
||||
if not opening:
|
||||
raise HTTPException(status_code=404, detail="Opening not found")
|
||||
|
||||
# Update fields
|
||||
if data.anime_name is not None:
|
||||
opening.anime_name = data.anime_name
|
||||
if data.op_number is not None:
|
||||
opening.op_number = data.op_number
|
||||
if data.song_name is not None:
|
||||
opening.song_name = data.song_name
|
||||
if data.audio_file is not None:
|
||||
opening.audio_file = data.audio_file
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(opening)
|
||||
|
||||
return opening
|
||||
|
||||
|
||||
@router.delete("/{opening_id}", status_code=204)
|
||||
async def delete_opening(opening_id: int, db: AsyncSession = Depends(get_db)):
|
||||
"""Delete an opening."""
|
||||
query = select(Opening).where(Opening.id == opening_id)
|
||||
result = await db.execute(query)
|
||||
opening = result.scalar_one_or_none()
|
||||
|
||||
if not opening:
|
||||
raise HTTPException(status_code=404, detail="Opening not found")
|
||||
|
||||
await db.delete(opening)
|
||||
await db.commit()
|
||||
|
||||
|
||||
# ============== Poster Management ==============
|
||||
|
||||
@router.post("/{opening_id}/posters", response_model=OpeningPosterResponse, status_code=201)
|
||||
async def add_poster(
|
||||
opening_id: int,
|
||||
data: AddPosterRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Add a poster to an opening."""
|
||||
query = select(Opening).where(Opening.id == opening_id)
|
||||
result = await db.execute(query)
|
||||
opening = result.scalar_one_or_none()
|
||||
|
||||
if not opening:
|
||||
raise HTTPException(status_code=404, detail="Opening not found")
|
||||
|
||||
poster = OpeningPoster(
|
||||
opening_id=opening_id,
|
||||
poster_file=data.poster_file,
|
||||
is_default=data.is_default,
|
||||
)
|
||||
|
||||
# If this is set as default, unset others
|
||||
if data.is_default:
|
||||
await db.execute(
|
||||
select(OpeningPoster)
|
||||
.where(OpeningPoster.opening_id == opening_id)
|
||||
)
|
||||
posters_result = await db.execute(
|
||||
select(OpeningPoster).where(OpeningPoster.opening_id == opening_id)
|
||||
)
|
||||
for p in posters_result.scalars():
|
||||
p.is_default = False
|
||||
|
||||
db.add(poster)
|
||||
await db.commit()
|
||||
await db.refresh(poster)
|
||||
|
||||
return poster
|
||||
|
||||
|
||||
@router.delete("/{opening_id}/posters/{poster_id}", status_code=204)
|
||||
async def remove_poster(
|
||||
opening_id: int,
|
||||
poster_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Remove a poster from an opening."""
|
||||
query = select(OpeningPoster).where(
|
||||
OpeningPoster.id == poster_id,
|
||||
OpeningPoster.opening_id == opening_id,
|
||||
)
|
||||
result = await db.execute(query)
|
||||
poster = result.scalar_one_or_none()
|
||||
|
||||
if not poster:
|
||||
raise HTTPException(status_code=404, detail="Poster not found")
|
||||
|
||||
await db.delete(poster)
|
||||
await db.commit()
|
||||
|
||||
|
||||
@router.post("/{opening_id}/posters/{poster_id}/set-default", response_model=OpeningPosterResponse)
|
||||
async def set_default_poster(
|
||||
opening_id: int,
|
||||
poster_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Set a poster as the default for an opening."""
|
||||
# Get all posters for this opening
|
||||
query = select(OpeningPoster).where(OpeningPoster.opening_id == opening_id)
|
||||
result = await db.execute(query)
|
||||
posters = result.scalars().all()
|
||||
|
||||
target_poster = None
|
||||
for poster in posters:
|
||||
if poster.id == poster_id:
|
||||
poster.is_default = True
|
||||
target_poster = poster
|
||||
else:
|
||||
poster.is_default = False
|
||||
|
||||
if not target_poster:
|
||||
raise HTTPException(status_code=404, detail="Poster not found")
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(target_poster)
|
||||
|
||||
return target_poster
|
||||
Reference in New Issue
Block a user