# app/api/v1/endpoints/lists.py import logging from typing import List as PyList # Alias for Python List type hint 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 from app.models import User as UserModel from app.schemas.list import ListCreate, ListUpdate, ListPublic, ListDetail from app.schemas.message import Message # For simple responses from app.crud import list as crud_list from app.crud import group as crud_group # Need for group membership check from app.schemas.list import ListStatus logger = logging.getLogger(__name__) router = APIRouter() @router.post( "", # Route relative to prefix "/lists" response_model=ListPublic, # Return basic list info on creation status_code=status.HTTP_201_CREATED, summary="Create New List", tags=["Lists"] ) async def create_list( list_in: ListCreate, db: AsyncSession = Depends(get_db), current_user: UserModel = Depends(get_current_user), ): """ Creates a new shopping list. - If `group_id` is provided, the user must be a member of that group. - If `group_id` is null, it's a personal list. """ logger.info(f"User {current_user.email} creating list: {list_in.name}") group_id = list_in.group_id # Permission Check: If sharing with a group, verify membership if group_id: is_member = await crud_group.is_user_member(db, group_id=group_id, user_id=current_user.id) if not is_member: logger.warning(f"User {current_user.email} attempted to create list in group {group_id} but is not a member.") raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You are not a member of the specified group", ) created_list = await crud_list.create_list(db=db, list_in=list_in, creator_id=current_user.id) logger.info(f"List '{created_list.name}' (ID: {created_list.id}) created successfully for user {current_user.email}.") return created_list @router.get( "", # Route relative to prefix "/lists" response_model=PyList[ListPublic], # Return a list of basic list info summary="List Accessible Lists", tags=["Lists"] ) async def read_lists( db: AsyncSession = Depends(get_db), current_user: UserModel = Depends(get_current_user), # Add pagination parameters later if needed: skip: int = 0, limit: int = 100 ): """ Retrieves lists accessible to the current user: - Personal lists created by the user. - Lists belonging to groups the user is a member of. """ logger.info(f"Fetching lists accessible to user: {current_user.email}") lists = await crud_list.get_lists_for_user(db=db, user_id=current_user.id) return lists @router.get( "/{list_id}", response_model=ListDetail, # Return detailed list info including items summary="Get List Details", tags=["Lists"] ) async def read_list( list_id: int, db: AsyncSession = Depends(get_db), current_user: UserModel = Depends(get_current_user), ): """ Retrieves details for a specific list, including its items, if the user has permission (creator or group member). """ logger.info(f"User {current_user.email} requesting details for list ID: {list_id}") # Use the helper to fetch and check permission simultaneously list_db = await crud_list.check_list_permission(db=db, list_id=list_id, user_id=current_user.id) if not list_db: # check_list_permission returns None if list not found OR permission denied # We need to check if the list exists at all to return 404 vs 403 exists = await crud_list.get_list_by_id(db, list_id) if not exists: logger.warning(f"List ID {list_id} not found for request by user {current_user.email}.") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="List not found") else: logger.warning(f"Access denied: User {current_user.email} cannot access list {list_id}.") raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="You do not have permission to access this list") # list_db already has items loaded due to check_list_permission return list_db @router.put( "/{list_id}", response_model=ListPublic, # Return updated basic info summary="Update List", tags=["Lists"] ) async def update_list( list_id: int, list_in: ListUpdate, db: AsyncSession = Depends(get_db), current_user: UserModel = Depends(get_current_user), ): """ Updates a list's details (name, description, is_complete). Requires user to be the creator or a member of the list's group. (MVP: Allows any member to update these fields). """ logger.info(f"User {current_user.email} attempting to update list ID: {list_id}") 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 update this list" logger.warning(f"Update failed for list {list_id} by user {current_user.email}: {detail}") raise HTTPException(status_code=status_code, detail=detail) # Prevent changing group_id or creator via this endpoint for simplicity # if list_in.group_id is not None or list_in.created_by_id is not None: # raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot change group or creator via this endpoint") updated_list = await crud_list.update_list(db=db, list_db=list_db, list_in=list_in) logger.info(f"List {list_id} updated successfully by user {current_user.email}.") return updated_list @router.delete( "/{list_id}", status_code=status.HTTP_204_NO_CONTENT, # Standard for successful DELETE with no body summary="Delete List", tags=["Lists"] ) async def delete_list( list_id: int, db: AsyncSession = Depends(get_db), current_user: UserModel = Depends(get_current_user), ): """ Deletes a list. Requires user to be the creator of the list. (Alternatively, could allow group owner). """ logger.info(f"User {current_user.email} attempting to delete list ID: {list_id}") # Use the helper, requiring creator permission list_db = await crud_list.check_list_permission(db=db, list_id=list_id, user_id=current_user.id, require_creator=True) 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 "Only the list creator can delete this list" logger.warning(f"Delete failed for list {list_id} by user {current_user.email}: {detail}") raise HTTPException(status_code=status_code, detail=detail) await crud_list.delete_list(db=db, list_db=list_db) logger.info(f"List {list_id} deleted successfully by user {current_user.email}.") # Return Response with 204 status explicitly if needed, otherwise FastAPI handles it return Response(status_code=status.HTTP_204_NO_CONTENT) @router.get( "/{list_id}/status", response_model=ListStatus, summary="Get List Status (for polling)", tags=["Lists"] ) async def read_list_status( list_id: int, db: AsyncSession = Depends(get_db), current_user: UserModel = Depends(get_current_user), ): """ Retrieves the last update time for the list and its items, plus item count. Used for polling to check if a full refresh is needed. Requires user to have permission to view the list. """ # Verify user has access to the list first 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 access this list's status" logger.warning(f"Status check failed for list {list_id} by user {current_user.email}: {detail}") raise HTTPException(status_code=status_code, detail=detail) # Fetch the status details list_status = await crud_list.get_list_status(db=db, list_id=list_id) if not list_status: # Should not happen if check_list_permission passed, but handle defensively logger.error(f"Could not retrieve status for list {list_id} even though permission check passed.") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="List status not found") return list_status