# 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 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: # Now this type hint is valid item_db = await crud_item.get_item_by_id(db, item_id=item_id) if not item_db: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Item not found") # Check permission on the parent list list_db = await crud_list.check_list_permission(db=db, list_id=item_db.list_id, user_id=current_user.id) if not list_db: # User doesn't have access to the list this item belongs to raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized to access this item's list") return item_db # Return the fetched item if authorized # --- 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 list_db = await crud_list.check_list_permission(db=db, list_id=list_id, user_id=current_user.id) if not list_db: # Check if list exists at all for correct error code exists = await crud_list.get_list_by_id(db, list_id) status_code = status.HTTP_404_NOT_FOUND if not exists else status.HTTP_403_FORBIDDEN detail = "List not found" if not exists else "You do not have permission to add items to this list" logger.warning(f"Add item failed for list {list_id} by user {current_user.email}: {detail}") raise HTTPException(status_code=status_code, detail=detail) 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 list_db = await crud_list.check_list_permission(db=db, list_id=list_id, user_id=current_user.id) if not list_db: exists = await crud_list.get_list_by_id(db, list_id) status_code = status.HTTP_404_NOT_FOUND if not exists else status.HTTP_403_FORBIDDEN detail = "List not found" if not exists else "You do not have permission to view items in this list" logger.warning(f"List items failed for list {list_id} by user {current_user.email}: {detail}") raise HTTPException(status_code=status_code, detail=detail) 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)