Files
mtgonline/backend/app/routers/decks.py
T
akadmin 2c74a107bc Phase 2.1: Card mirror system for deckbuilding features
- Created MtgCardMirror and DeckCardLink models in mirror_models.py
- Created card_mirror_service.py with sync_mirrors functionality
- Added sync_mirrors() method to mtgjson_manager.py
- Added mirror_get_db() dependency to database.py
- Added DecklistFile.status column (DRAUGHT/FINAL)
- Updated DeckCreate schema with status field
- Added DeckWithCardsResponse schema with card_count
- Updated deck router to query card mirrors and return card counts
- Added plain text deck content support
2026-07-22 03:35:57 +00:00

303 lines
8.8 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, func
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.models.mirror_models import DeckCardLink
from app.schemas.schemas import DeckCreate, DeckUpdate, DeckResponse, DeckWithCardsResponse, FolderCreate, FolderResponse
router = APIRouter()
@router.get("/", response_model=List[DeckWithCardsResponse])
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()
# Include card counts for each deck
deck_responses = []
for deck in decks:
# Get card count
count_stmt = select(func.count()).select_from(DeckCardLink).where(
DeckCardLink.deck_id == deck.id
)
count_result = await db.execute(count_stmt)
card_count = count_result.scalar() or 0
deck_data = DeckWithCardsResponse.model_validate(deck)
deck_data.card_count = card_count
deck_responses.append(deck_data)
return deck_responses
@router.post("/", response_model=DeckWithCardsResponse)
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,
status=request.status,
)
db.add(new_deck)
await db.flush()
# Return with card count (0 for new deck)
result = DeckWithCardsResponse.model_validate(new_deck)
result.card_count = 0
return result
@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("/{deck_id}", response_model=DeckWithCardsResponse)
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",
)
# Get card count
count_stmt = select(func.count()).select_from(DeckCardLink).where(
DeckCardLink.deck_id == deck_id
)
count_result = await db.execute(count_stmt)
card_count = count_result.scalar() or 0
result = DeckWithCardsResponse.model_validate(deck)
result.card_count = card_count
return result
@router.patch("/{deck_id}", response_model=DeckWithCardsResponse)
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()
# Get card count
count_stmt = select(func.count()).select_from(DeckCardLink).where(
DeckCardLink.deck_id == deck_id
)
count_result = await db.execute(count_stmt)
card_count = count_result.scalar() or 0
result = DeckWithCardsResponse.model_validate(updated_deck)
result.card_count = card_count
return result
@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"}