- 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
303 lines
8.8 KiB
Python
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"}
|