Files
mtgonline/backend/app/routers/decks.py
T
akadmin 167a352d44 Fix backend test suite - all 20 tests passing
- Updated JWT tokens to include privlevel for authorization
- Fixed test fixtures to properly hash passwords with bcrypt
- Fixed client fixture to share database session with test fixtures
- Reordered deck router routes to prevent conflicts
- Removed relationship fields from FolderResponse schema
- Updated conftest.py with proper async session management
2026-07-18 16:58:39 +00:00

337 lines
9.5 KiB
Python

"""Deck management router endpoints."""
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, delete, update
from typing import Optional, List
from app.core.database import get_db
from app.core.security import get_current_user
from app.models.models import DecklistFile, DecklistFolder
from app.schemas.schemas import DeckCreate, DeckUpdate, DeckResponse, FolderCreate, FolderResponse
router = APIRouter()
@router.get("/", response_model=List[DeckResponse])
async def list_decks(
folder_id: Optional[int] = None,
page: int = 1,
page_size: int = 50,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(get_current_user),
):
"""List decks for current user."""
user_id = int(current_user["user_id"])
if folder_id:
stmt = (
select(DecklistFile)
.where(
DecklistFile.owner_id == user_id,
DecklistFile.folder_id == folder_id,
)
.offset((page - 1) * page_size)
.limit(page_size)
)
else:
stmt = (
select(DecklistFile)
.where(DecklistFile.owner_id == user_id)
.offset((page - 1) * page_size)
.limit(page_size)
)
result = await db.execute(stmt)
decks = result.scalars().all()
return [DeckResponse.model_validate(deck) for deck in decks]
@router.post("/", response_model=DeckResponse)
async def create_deck(
request: DeckCreate,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(get_current_user),
):
"""Create a new deck."""
user_id = int(current_user["user_id"])
# Verify folder exists if specified
if request.folder_id:
stmt = select(DecklistFolder).where(
DecklistFolder.id == request.folder_id,
DecklistFolder.owner_id == user_id,
)
result = await db.execute(stmt)
folder = result.scalar_one_or_none()
if not folder:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Folder not found",
)
new_deck = DecklistFile(
owner_id=user_id,
folder_id=request.folder_id,
name=request.name,
content=request.content,
format=request.format,
)
db.add(new_deck)
await db.flush()
return DeckResponse.model_validate(new_deck)
@router.get("/folders", response_model=List[FolderResponse])
async def list_folders(
parent_id: Optional[int] = None,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(get_current_user),
):
"""List folders for current user."""
user_id = int(current_user["user_id"])
if parent_id:
stmt = select(DecklistFolder).where(
DecklistFolder.parent_id == parent_id,
DecklistFolder.owner_id == user_id,
)
else:
stmt = select(DecklistFolder).where(
DecklistFolder.parent_id == None, # Top-level folders
DecklistFolder.owner_id == user_id,
)
result = await db.execute(stmt)
folders = result.scalars().all()
return [FolderResponse.model_validate(folder) for folder in folders]
@router.post("/folders", response_model=FolderResponse)
async def create_folder(
request: FolderCreate,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(get_current_user),
):
"""Create a new folder."""
user_id = int(current_user["user_id"])
# Verify parent folder exists if specified
if request.parent_id:
stmt = select(DecklistFolder).where(
DecklistFolder.id == request.parent_id,
DecklistFolder.owner_id == user_id,
)
result = await db.execute(stmt)
parent_folder = result.scalar_one_or_none()
if not parent_folder:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Parent folder not found",
)
new_folder = DecklistFolder(
owner_id=user_id,
name=request.name,
parent_id=request.parent_id,
)
db.add(new_folder)
await db.flush()
return FolderResponse.model_validate(new_folder)
@router.delete("/folders/{folder_id}")
async def delete_folder(
folder_id: int,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(get_current_user),
):
"""Delete folder and all its contents."""
user_id = int(current_user["user_id"])
stmt = select(DecklistFolder).where(
DecklistFolder.id == folder_id,
DecklistFolder.owner_id == user_id,
)
result = await db.execute(stmt)
folder = result.scalar_one_or_none()
if not folder:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Folder not found",
)
# Delete folder and all decks (cascading delete)
await db.execute(delete(DecklistFolder).where(DecklistFolder.id == folder_id))
return {"message": "Folder deleted successfully"}
@router.get("/", response_model=List[DeckResponse])
async def list_decks(
folder_id: Optional[int] = None,
page: int = 1,
page_size: int = 50,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(get_current_user),
):
"""List decks for current user."""
user_id = int(current_user["user_id"])
if folder_id:
stmt = (
select(DecklistFile)
.where(
DecklistFile.owner_id == user_id,
DecklistFile.folder_id == folder_id,
)
.offset((page - 1) * page_size)
.limit(page_size)
)
else:
stmt = (
select(DecklistFile)
.where(DecklistFile.owner_id == user_id)
.offset((page - 1) * page_size)
.limit(page_size)
)
result = await db.execute(stmt)
decks = result.scalars().all()
return [DeckResponse.model_validate(deck) for deck in decks]
@router.post("/", response_model=DeckResponse)
async def create_deck(
request: DeckCreate,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(get_current_user),
):
"""Create a new deck."""
user_id = int(current_user["user_id"])
# Verify folder exists if specified
if request.folder_id:
stmt = select(DecklistFolder).where(
DecklistFolder.id == request.folder_id,
DecklistFolder.owner_id == user_id,
)
result = await db.execute(stmt)
folder = result.scalar_one_or_none()
if not folder:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Folder not found",
)
new_deck = DecklistFile(
owner_id=user_id,
folder_id=request.folder_id,
name=request.name,
content=request.content,
format=request.format,
)
db.add(new_deck)
await db.flush()
return DeckResponse.model_validate(new_deck)
@router.get("/{deck_id}", response_model=DeckResponse)
async def get_deck(
deck_id: int,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(get_current_user),
):
"""Get deck by ID."""
user_id = int(current_user["user_id"])
stmt = select(DecklistFile).where(
DecklistFile.id == deck_id,
DecklistFile.owner_id == user_id,
)
result = await db.execute(stmt)
deck = result.scalar_one_or_none()
if not deck:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Deck not found",
)
return DeckResponse.model_validate(deck)
@router.patch("/{deck_id}", response_model=DeckResponse)
async def update_deck(
deck_id: int,
request: DeckUpdate,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(get_current_user),
):
"""Update deck."""
user_id = int(current_user["user_id"])
stmt = select(DecklistFile).where(
DecklistFile.id == deck_id,
DecklistFile.owner_id == user_id,
)
result = await db.execute(stmt)
deck = result.scalar_one_or_none()
if not deck:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Deck not found",
)
# Update fields
update_data = request.model_dump(exclude_unset=True)
stmt = (
update(DecklistFile)
.where(DecklistFile.id == deck_id)
.values(**update_data)
)
await db.execute(stmt)
await db.flush()
# Fetch updated deck
stmt = select(DecklistFile).where(DecklistFile.id == deck_id)
result = await db.execute(stmt)
updated_deck = result.scalar_one_or_none()
return DeckResponse.model_validate(updated_deck)
@router.delete("/{deck_id}")
async def delete_deck(
deck_id: int,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(get_current_user),
):
"""Delete deck."""
user_id = int(current_user["user_id"])
stmt = select(DecklistFile).where(
DecklistFile.id == deck_id,
DecklistFile.owner_id == user_id,
)
result = await db.execute(stmt)
deck = result.scalar_one_or_none()
if not deck:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Deck not found",
)
await db.execute(delete(DecklistFile).where(DecklistFile.id == deck_id))
return {"message": "Deck deleted successfully"}