141 lines
6.0 KiB
Python
141 lines
6.0 KiB
Python
# 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}", response_model=ItemPublic, summary="Update Item", tags=["Items"])
|
|
async def update_item(
|
|
item_id: int,
|
|
item_in: ItemUpdate,
|
|
item_db: ItemModel = Depends(get_item_and_verify_access),
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: UserModel = Depends(get_current_user),
|
|
):
|
|
"""
|
|
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}")
|
|
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) |