# app/api/v1/endpoints/items.py import logging from typing import List as PyList from fastapi import APIRouter, Depends, HTTPException, status, Response from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.api.dependencies import get_current_user # --- Import Models Correctly --- from app.models import User as UserModel from app.models import Item as ItemModel # <-- IMPORT Item and alias it # --- End Import Models --- from app.schemas.item import ItemCreate, ItemUpdate, ItemPublic from app.crud import item as crud_item from app.crud import list as crud_list from app.core.exceptions import ItemNotFoundError, ListPermissionError logger = logging.getLogger(__name__) router = APIRouter() # --- Helper Dependency for Item Permissions --- # Now ItemModel is defined before being used as a type hint async def get_item_and_verify_access( item_id: int, db: AsyncSession = Depends(get_db), current_user: UserModel = Depends(get_current_user) ) -> ItemModel: """Dependency to get an item and verify the user has access to its list.""" item_db = await crud_item.get_item_by_id(db, item_id=item_id) if not item_db: raise ItemNotFoundError(item_id) # Check permission on the parent list try: await crud_list.check_list_permission(db=db, list_id=item_db.list_id, user_id=current_user.id) except ListPermissionError as e: # Re-raise with a more specific message raise ListPermissionError(item_db.list_id, "access this item's list") return item_db # --- Endpoints --- @router.post( "/lists/{list_id}/items", # Nested under lists response_model=ItemPublic, status_code=status.HTTP_201_CREATED, summary="Add Item to List", tags=["Items"] ) async def create_list_item( list_id: int, item_in: ItemCreate, db: AsyncSession = Depends(get_db), current_user: UserModel = Depends(get_current_user), ): """Adds a new item to a specific list. User must have access to the list.""" logger.info(f"User {current_user.email} adding item to list {list_id}: {item_in.name}") # Verify user has access to the target list try: await crud_list.check_list_permission(db=db, list_id=list_id, user_id=current_user.id) except ListPermissionError as e: # Re-raise with a more specific message raise ListPermissionError(list_id, "add items to this list") created_item = await crud_item.create_item( db=db, item_in=item_in, list_id=list_id, user_id=current_user.id ) logger.info(f"Item '{created_item.name}' (ID: {created_item.id}) added to list {list_id} by user {current_user.email}.") return created_item @router.get( "/lists/{list_id}/items", # Nested under lists response_model=PyList[ItemPublic], summary="List Items in List", tags=["Items"] ) async def read_list_items( list_id: int, db: AsyncSession = Depends(get_db), current_user: UserModel = Depends(get_current_user), # Add sorting/filtering params later if needed: sort_by: str = 'created_at', order: str = 'asc' ): """Retrieves all items for a specific list if the user has access.""" logger.info(f"User {current_user.email} listing items for list {list_id}") # Verify user has access to the list try: await crud_list.check_list_permission(db=db, list_id=list_id, user_id=current_user.id) except ListPermissionError as e: # Re-raise with a more specific message raise ListPermissionError(list_id, "view items in this list") items = await crud_item.get_items_by_list_id(db=db, list_id=list_id) return items @router.put( "/items/{item_id}", # Operate directly on item ID response_model=ItemPublic, summary="Update Item", tags=["Items"] ) async def update_item( item_id: int, # Item ID from path item_in: ItemUpdate, item_db: ItemModel = Depends(get_item_and_verify_access), # Use dependency to get item and check list access db: AsyncSession = Depends(get_db), current_user: UserModel = Depends(get_current_user), # Need user ID for completed_by ): """ Updates an item's details (name, quantity, is_complete, price). User must have access to the list the item belongs to. Sets/unsets `completed_by_id` based on `is_complete` flag. """ logger.info(f"User {current_user.email} attempting to update item ID: {item_id}") # Permission check is handled by get_item_and_verify_access dependency updated_item = await crud_item.update_item( db=db, item_db=item_db, item_in=item_in, user_id=current_user.id ) logger.info(f"Item {item_id} updated successfully by user {current_user.email}.") return updated_item @router.delete( "/items/{item_id}", # Operate directly on item ID status_code=status.HTTP_204_NO_CONTENT, summary="Delete Item", tags=["Items"] ) async def delete_item( item_id: int, # Item ID from path item_db: ItemModel = Depends(get_item_and_verify_access), # Use dependency to get item and check list access db: AsyncSession = Depends(get_db), current_user: UserModel = Depends(get_current_user), # Log who deleted it ): """ Deletes an item. User must have access to the list the item belongs to. (MVP: Any member with list access can delete items). """ logger.info(f"User {current_user.email} attempting to delete item ID: {item_id}") # Permission check is handled by get_item_and_verify_access dependency await crud_item.delete_item(db=db, item_db=item_db) logger.info(f"Item {item_id} deleted successfully by user {current_user.email}.") return Response(status_code=status.HTTP_204_NO_CONTENT)