
- Introduced a new `notes.md` file to document critical tasks and progress for stabilizing the core functionality of the MitList application. - Documented the status and findings for key tasks, including backend financial logic fixes, frontend expense split settlement implementation, and core authentication flow reviews. - Outlined remaining work for production deployment, including secret management, CI/CD pipeline setup, and performance optimizations. - Updated the logging configuration to change the log level to WARNING for production readiness. - Enhanced the database connection settings to disable SQL query logging in production. - Added a new endpoint to list all chores for improved user experience and optimized database queries. - Implemented various CRUD operations for chore assignments, including creation, retrieval, updating, and deletion. - Updated frontend components and services to support new chore assignment features and improved error handling. - Enhanced the expense management system with new fields and improved API interactions for better user experience.
453 lines
24 KiB
Python
453 lines
24 KiB
Python
# app/api/v1/endpoints/chores.py
|
|
import logging
|
|
from typing import List as PyList, Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status, Response
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.database import get_transactional_session, get_session
|
|
from app.auth import current_active_user
|
|
from app.models import User as UserModel, Chore as ChoreModel, ChoreTypeEnum
|
|
from app.schemas.chore import ChoreCreate, ChoreUpdate, ChorePublic, ChoreAssignmentCreate, ChoreAssignmentUpdate, ChoreAssignmentPublic
|
|
from app.crud import chore as crud_chore
|
|
from app.core.exceptions import ChoreNotFoundError, PermissionDeniedError, GroupNotFoundError, DatabaseIntegrityError
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter()
|
|
|
|
# Add this new endpoint before the personal chores section
|
|
@router.get(
|
|
"/all",
|
|
response_model=PyList[ChorePublic],
|
|
summary="List All Chores",
|
|
tags=["Chores"]
|
|
)
|
|
async def list_all_chores(
|
|
db: AsyncSession = Depends(get_session), # Use read-only session for GET
|
|
current_user: UserModel = Depends(current_active_user),
|
|
):
|
|
"""Retrieves all chores (personal and group) for the current user in a single optimized request."""
|
|
logger.info(f"User {current_user.email} listing all their chores")
|
|
|
|
# Use the optimized function that reduces database queries
|
|
all_chores = await crud_chore.get_all_user_chores(db=db, user_id=current_user.id)
|
|
|
|
return all_chores
|
|
|
|
# --- Personal Chores Endpoints ---
|
|
|
|
@router.post(
|
|
"/personal",
|
|
response_model=ChorePublic,
|
|
status_code=status.HTTP_201_CREATED,
|
|
summary="Create Personal Chore",
|
|
tags=["Chores", "Personal Chores"]
|
|
)
|
|
async def create_personal_chore(
|
|
chore_in: ChoreCreate,
|
|
db: AsyncSession = Depends(get_transactional_session),
|
|
current_user: UserModel = Depends(current_active_user),
|
|
):
|
|
"""Creates a new personal chore for the current user."""
|
|
logger.info(f"User {current_user.email} creating personal chore: {chore_in.name}")
|
|
if chore_in.type != ChoreTypeEnum.personal:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Chore type must be personal.")
|
|
if chore_in.group_id is not None:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="group_id must be null for personal chores.")
|
|
try:
|
|
return await crud_chore.create_chore(db=db, chore_in=chore_in, user_id=current_user.id)
|
|
except ValueError as e:
|
|
logger.warning(f"ValueError creating personal chore for user {current_user.email}: {str(e)}")
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
except DatabaseIntegrityError as e:
|
|
logger.error(f"DatabaseIntegrityError creating personal chore for {current_user.email}: {e.detail}", exc_info=True)
|
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=e.detail)
|
|
|
|
@router.get(
|
|
"/personal",
|
|
response_model=PyList[ChorePublic],
|
|
summary="List Personal Chores",
|
|
tags=["Chores", "Personal Chores"]
|
|
)
|
|
async def list_personal_chores(
|
|
db: AsyncSession = Depends(get_session),
|
|
current_user: UserModel = Depends(current_active_user),
|
|
):
|
|
"""Retrieves all personal chores for the current user."""
|
|
logger.info(f"User {current_user.email} listing their personal chores")
|
|
return await crud_chore.get_personal_chores(db=db, user_id=current_user.id)
|
|
|
|
@router.put(
|
|
"/personal/{chore_id}",
|
|
response_model=ChorePublic,
|
|
summary="Update Personal Chore",
|
|
tags=["Chores", "Personal Chores"]
|
|
)
|
|
async def update_personal_chore(
|
|
chore_id: int,
|
|
chore_in: ChoreUpdate,
|
|
db: AsyncSession = Depends(get_transactional_session),
|
|
current_user: UserModel = Depends(current_active_user),
|
|
):
|
|
"""Updates a personal chore for the current user."""
|
|
logger.info(f"User {current_user.email} updating personal chore ID: {chore_id}")
|
|
if chore_in.type is not None and chore_in.type != ChoreTypeEnum.personal:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot change chore type to group via this endpoint.")
|
|
if chore_in.group_id is not None:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="group_id must be null for personal chores.")
|
|
try:
|
|
updated_chore = await crud_chore.update_chore(db=db, chore_id=chore_id, chore_in=chore_in, user_id=current_user.id, group_id=None)
|
|
if not updated_chore:
|
|
raise ChoreNotFoundError(chore_id=chore_id)
|
|
if updated_chore.type != ChoreTypeEnum.personal or updated_chore.created_by_id != current_user.id:
|
|
# This should ideally be caught by the CRUD layer permission checks
|
|
raise PermissionDeniedError(detail="Chore is not a personal chore of the current user or does not exist.")
|
|
return updated_chore
|
|
except ChoreNotFoundError as e:
|
|
logger.warning(f"Personal chore {e.chore_id} not found for user {current_user.email} during update.")
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=e.detail)
|
|
except PermissionDeniedError as e:
|
|
logger.warning(f"Permission denied for user {current_user.email} updating personal chore {chore_id}: {e.detail}")
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=e.detail)
|
|
except ValueError as e:
|
|
logger.warning(f"ValueError updating personal chore {chore_id} for user {current_user.email}: {str(e)}")
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
except DatabaseIntegrityError as e:
|
|
logger.error(f"DatabaseIntegrityError updating personal chore {chore_id} for {current_user.email}: {e.detail}", exc_info=True)
|
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=e.detail)
|
|
|
|
@router.delete(
|
|
"/personal/{chore_id}",
|
|
status_code=status.HTTP_204_NO_CONTENT,
|
|
summary="Delete Personal Chore",
|
|
tags=["Chores", "Personal Chores"]
|
|
)
|
|
async def delete_personal_chore(
|
|
chore_id: int,
|
|
db: AsyncSession = Depends(get_transactional_session),
|
|
current_user: UserModel = Depends(current_active_user),
|
|
):
|
|
"""Deletes a personal chore for the current user."""
|
|
logger.info(f"User {current_user.email} deleting personal chore ID: {chore_id}")
|
|
try:
|
|
# First, verify it's a personal chore belonging to the user
|
|
chore_to_delete = await crud_chore.get_chore_by_id(db, chore_id)
|
|
if not chore_to_delete or chore_to_delete.type != ChoreTypeEnum.personal or chore_to_delete.created_by_id != current_user.id:
|
|
raise ChoreNotFoundError(chore_id=chore_id, detail="Personal chore not found or not owned by user.")
|
|
|
|
success = await crud_chore.delete_chore(db=db, chore_id=chore_id, user_id=current_user.id, group_id=None)
|
|
if not success:
|
|
# This case should be rare if the above check passes and DB is consistent
|
|
raise ChoreNotFoundError(chore_id=chore_id)
|
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
except ChoreNotFoundError as e:
|
|
logger.warning(f"Personal chore {e.chore_id} not found for user {current_user.email} during delete.")
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=e.detail)
|
|
except PermissionDeniedError as e: # Should be caught by the check above
|
|
logger.warning(f"Permission denied for user {current_user.email} deleting personal chore {chore_id}: {e.detail}")
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=e.detail)
|
|
except DatabaseIntegrityError as e:
|
|
logger.error(f"DatabaseIntegrityError deleting personal chore {chore_id} for {current_user.email}: {e.detail}", exc_info=True)
|
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=e.detail)
|
|
|
|
# --- Group Chores Endpoints ---
|
|
# (These would be similar to what you might have had before, but now explicitly part of this router)
|
|
|
|
@router.post(
|
|
"/groups/{group_id}/chores",
|
|
response_model=ChorePublic,
|
|
status_code=status.HTTP_201_CREATED,
|
|
summary="Create Group Chore",
|
|
tags=["Chores", "Group Chores"]
|
|
)
|
|
async def create_group_chore(
|
|
group_id: int,
|
|
chore_in: ChoreCreate,
|
|
db: AsyncSession = Depends(get_transactional_session),
|
|
current_user: UserModel = Depends(current_active_user),
|
|
):
|
|
"""Creates a new chore within a specific group."""
|
|
logger.info(f"User {current_user.email} creating chore in group {group_id}: {chore_in.name}")
|
|
if chore_in.type != ChoreTypeEnum.group:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Chore type must be group.")
|
|
if chore_in.group_id != group_id and chore_in.group_id is not None: # Make sure chore_in.group_id matches path if provided
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Chore's group_id ({chore_in.group_id}) must match path group_id ({group_id}) or be omitted.")
|
|
|
|
# Ensure chore_in has the correct group_id and type for the CRUD operation
|
|
chore_payload = chore_in.model_copy(update={"group_id": group_id, "type": ChoreTypeEnum.group})
|
|
|
|
try:
|
|
return await crud_chore.create_chore(db=db, chore_in=chore_payload, user_id=current_user.id, group_id=group_id)
|
|
except GroupNotFoundError as e:
|
|
logger.warning(f"Group {e.group_id} not found for chore creation by user {current_user.email}.")
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=e.detail)
|
|
except PermissionDeniedError as e:
|
|
logger.warning(f"Permission denied for user {current_user.email} in group {group_id} for chore creation: {e.detail}")
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=e.detail)
|
|
except ValueError as e:
|
|
logger.warning(f"ValueError creating group chore for user {current_user.email} in group {group_id}: {str(e)}")
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
except DatabaseIntegrityError as e:
|
|
logger.error(f"DatabaseIntegrityError creating group chore for {current_user.email} in group {group_id}: {e.detail}", exc_info=True)
|
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=e.detail)
|
|
|
|
@router.get(
|
|
"/groups/{group_id}/chores",
|
|
response_model=PyList[ChorePublic],
|
|
summary="List Group Chores",
|
|
tags=["Chores", "Group Chores"]
|
|
)
|
|
async def list_group_chores(
|
|
group_id: int,
|
|
db: AsyncSession = Depends(get_session),
|
|
current_user: UserModel = Depends(current_active_user),
|
|
):
|
|
"""Retrieves all chores for a specific group, if the user is a member."""
|
|
logger.info(f"User {current_user.email} listing chores for group {group_id}")
|
|
try:
|
|
return await crud_chore.get_chores_by_group_id(db=db, group_id=group_id, user_id=current_user.id)
|
|
except PermissionDeniedError as e:
|
|
logger.warning(f"Permission denied for user {current_user.email} accessing chores for group {group_id}: {e.detail}")
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=e.detail)
|
|
|
|
@router.put(
|
|
"/groups/{group_id}/chores/{chore_id}",
|
|
response_model=ChorePublic,
|
|
summary="Update Group Chore",
|
|
tags=["Chores", "Group Chores"]
|
|
)
|
|
async def update_group_chore(
|
|
group_id: int,
|
|
chore_id: int,
|
|
chore_in: ChoreUpdate,
|
|
db: AsyncSession = Depends(get_transactional_session),
|
|
current_user: UserModel = Depends(current_active_user),
|
|
):
|
|
"""Updates a chore's details within a specific group."""
|
|
logger.info(f"User {current_user.email} updating chore ID {chore_id} in group {group_id}")
|
|
if chore_in.type is not None and chore_in.type != ChoreTypeEnum.group:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot change chore type to personal via this endpoint.")
|
|
if chore_in.group_id is not None and chore_in.group_id != group_id:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Chore's group_id if provided must match path group_id ({group_id}).")
|
|
|
|
# Ensure chore_in has the correct type for the CRUD operation
|
|
chore_payload = chore_in.model_copy(update={"type": ChoreTypeEnum.group, "group_id": group_id} if chore_in.type is None else chore_in)
|
|
|
|
try:
|
|
updated_chore = await crud_chore.update_chore(db=db, chore_id=chore_id, chore_in=chore_payload, user_id=current_user.id, group_id=group_id)
|
|
if not updated_chore:
|
|
raise ChoreNotFoundError(chore_id=chore_id, group_id=group_id)
|
|
return updated_chore
|
|
except ChoreNotFoundError as e:
|
|
logger.warning(f"Chore {e.chore_id} in group {e.group_id} not found for user {current_user.email} during update.")
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=e.detail)
|
|
except PermissionDeniedError as e:
|
|
logger.warning(f"Permission denied for user {current_user.email} updating chore {chore_id} in group {group_id}: {e.detail}")
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=e.detail)
|
|
except ValueError as e:
|
|
logger.warning(f"ValueError updating group chore {chore_id} for user {current_user.email} in group {group_id}: {str(e)}")
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
except DatabaseIntegrityError as e:
|
|
logger.error(f"DatabaseIntegrityError updating group chore {chore_id} for {current_user.email}: {e.detail}", exc_info=True)
|
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=e.detail)
|
|
|
|
@router.delete(
|
|
"/groups/{group_id}/chores/{chore_id}",
|
|
status_code=status.HTTP_204_NO_CONTENT,
|
|
summary="Delete Group Chore",
|
|
tags=["Chores", "Group Chores"]
|
|
)
|
|
async def delete_group_chore(
|
|
group_id: int,
|
|
chore_id: int,
|
|
db: AsyncSession = Depends(get_transactional_session),
|
|
current_user: UserModel = Depends(current_active_user),
|
|
):
|
|
"""Deletes a chore from a group, ensuring user has permission."""
|
|
logger.info(f"User {current_user.email} deleting chore ID {chore_id} from group {group_id}")
|
|
try:
|
|
# Verify chore exists and belongs to the group before attempting deletion via CRUD
|
|
# This gives a more precise error if the chore exists but isn't in this group.
|
|
chore_to_delete = await crud_chore.get_chore_by_id_and_group(db, chore_id, group_id, current_user.id) # checks permission too
|
|
if not chore_to_delete : # get_chore_by_id_and_group will raise PermissionDeniedError if user not member
|
|
raise ChoreNotFoundError(chore_id=chore_id, group_id=group_id)
|
|
|
|
success = await crud_chore.delete_chore(db=db, chore_id=chore_id, user_id=current_user.id, group_id=group_id)
|
|
if not success:
|
|
# This case should be rare if the above check passes and DB is consistent
|
|
raise ChoreNotFoundError(chore_id=chore_id, group_id=group_id)
|
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
except ChoreNotFoundError as e:
|
|
logger.warning(f"Chore {e.chore_id} in group {e.group_id} not found for user {current_user.email} during delete.")
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=e.detail)
|
|
except PermissionDeniedError as e:
|
|
logger.warning(f"Permission denied for user {current_user.email} deleting chore {chore_id} in group {group_id}: {e.detail}")
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=e.detail)
|
|
except DatabaseIntegrityError as e:
|
|
logger.error(f"DatabaseIntegrityError deleting group chore {chore_id} for {current_user.email}: {e.detail}", exc_info=True)
|
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=e.detail)
|
|
|
|
# === CHORE ASSIGNMENT ENDPOINTS ===
|
|
|
|
@router.post(
|
|
"/assignments",
|
|
response_model=ChoreAssignmentPublic,
|
|
status_code=status.HTTP_201_CREATED,
|
|
summary="Create Chore Assignment",
|
|
tags=["Chore Assignments"]
|
|
)
|
|
async def create_chore_assignment(
|
|
assignment_in: ChoreAssignmentCreate,
|
|
db: AsyncSession = Depends(get_transactional_session),
|
|
current_user: UserModel = Depends(current_active_user),
|
|
):
|
|
"""Creates a new chore assignment. User must have permission to manage the chore."""
|
|
logger.info(f"User {current_user.email} creating assignment for chore {assignment_in.chore_id}")
|
|
try:
|
|
return await crud_chore.create_chore_assignment(db=db, assignment_in=assignment_in, user_id=current_user.id)
|
|
except ChoreNotFoundError as e:
|
|
logger.warning(f"Chore {e.chore_id} not found for assignment creation by user {current_user.email}.")
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=e.detail)
|
|
except PermissionDeniedError as e:
|
|
logger.warning(f"Permission denied for user {current_user.email} creating assignment: {e.detail}")
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=e.detail)
|
|
except ValueError as e:
|
|
logger.warning(f"ValueError creating assignment for user {current_user.email}: {str(e)}")
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
except DatabaseIntegrityError as e:
|
|
logger.error(f"DatabaseIntegrityError creating assignment for {current_user.email}: {e.detail}", exc_info=True)
|
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=e.detail)
|
|
|
|
@router.get(
|
|
"/assignments/my",
|
|
response_model=PyList[ChoreAssignmentPublic],
|
|
summary="List My Chore Assignments",
|
|
tags=["Chore Assignments"]
|
|
)
|
|
async def list_my_assignments(
|
|
include_completed: bool = False,
|
|
db: AsyncSession = Depends(get_session), # Use read-only session for GET
|
|
current_user: UserModel = Depends(current_active_user),
|
|
):
|
|
"""Retrieves all chore assignments for the current user."""
|
|
logger.info(f"User {current_user.email} listing their assignments (include_completed={include_completed})")
|
|
try:
|
|
return await crud_chore.get_user_assignments(db=db, user_id=current_user.id, include_completed=include_completed)
|
|
except Exception as e:
|
|
logger.error(f"Error listing assignments for user {current_user.email}: {e}", exc_info=True)
|
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve assignments")
|
|
|
|
@router.get(
|
|
"/chores/{chore_id}/assignments",
|
|
response_model=PyList[ChoreAssignmentPublic],
|
|
summary="List Chore Assignments",
|
|
tags=["Chore Assignments"]
|
|
)
|
|
async def list_chore_assignments(
|
|
chore_id: int,
|
|
db: AsyncSession = Depends(get_session), # Use read-only session for GET
|
|
current_user: UserModel = Depends(current_active_user),
|
|
):
|
|
"""Retrieves all assignments for a specific chore."""
|
|
logger.info(f"User {current_user.email} listing assignments for chore {chore_id}")
|
|
try:
|
|
return await crud_chore.get_chore_assignments(db=db, chore_id=chore_id, user_id=current_user.id)
|
|
except ChoreNotFoundError as e:
|
|
logger.warning(f"Chore {e.chore_id} not found for assignment listing by user {current_user.email}.")
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=e.detail)
|
|
except PermissionDeniedError as e:
|
|
logger.warning(f"Permission denied for user {current_user.email} listing assignments for chore {chore_id}: {e.detail}")
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=e.detail)
|
|
|
|
@router.put(
|
|
"/assignments/{assignment_id}",
|
|
response_model=ChoreAssignmentPublic,
|
|
summary="Update Chore Assignment",
|
|
tags=["Chore Assignments"]
|
|
)
|
|
async def update_chore_assignment(
|
|
assignment_id: int,
|
|
assignment_in: ChoreAssignmentUpdate,
|
|
db: AsyncSession = Depends(get_transactional_session),
|
|
current_user: UserModel = Depends(current_active_user),
|
|
):
|
|
"""Updates a chore assignment. Only assignee can mark complete, managers can reschedule."""
|
|
logger.info(f"User {current_user.email} updating assignment {assignment_id}")
|
|
try:
|
|
updated_assignment = await crud_chore.update_chore_assignment(
|
|
db=db, assignment_id=assignment_id, assignment_in=assignment_in, user_id=current_user.id
|
|
)
|
|
if not updated_assignment:
|
|
raise ChoreNotFoundError(assignment_id=assignment_id)
|
|
return updated_assignment
|
|
except ChoreNotFoundError as e:
|
|
logger.warning(f"Assignment {assignment_id} not found for user {current_user.email} during update.")
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=e.detail)
|
|
except PermissionDeniedError as e:
|
|
logger.warning(f"Permission denied for user {current_user.email} updating assignment {assignment_id}: {e.detail}")
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=e.detail)
|
|
except ValueError as e:
|
|
logger.warning(f"ValueError updating assignment {assignment_id} for user {current_user.email}: {str(e)}")
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
except DatabaseIntegrityError as e:
|
|
logger.error(f"DatabaseIntegrityError updating assignment {assignment_id} for {current_user.email}: {e.detail}", exc_info=True)
|
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=e.detail)
|
|
|
|
@router.delete(
|
|
"/assignments/{assignment_id}",
|
|
status_code=status.HTTP_204_NO_CONTENT,
|
|
summary="Delete Chore Assignment",
|
|
tags=["Chore Assignments"]
|
|
)
|
|
async def delete_chore_assignment(
|
|
assignment_id: int,
|
|
db: AsyncSession = Depends(get_transactional_session),
|
|
current_user: UserModel = Depends(current_active_user),
|
|
):
|
|
"""Deletes a chore assignment. User must have permission to manage the chore."""
|
|
logger.info(f"User {current_user.email} deleting assignment {assignment_id}")
|
|
try:
|
|
success = await crud_chore.delete_chore_assignment(db=db, assignment_id=assignment_id, user_id=current_user.id)
|
|
if not success:
|
|
raise ChoreNotFoundError(assignment_id=assignment_id)
|
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
except ChoreNotFoundError as e:
|
|
logger.warning(f"Assignment {assignment_id} not found for user {current_user.email} during delete.")
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=e.detail)
|
|
except PermissionDeniedError as e:
|
|
logger.warning(f"Permission denied for user {current_user.email} deleting assignment {assignment_id}: {e.detail}")
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=e.detail)
|
|
except DatabaseIntegrityError as e:
|
|
logger.error(f"DatabaseIntegrityError deleting assignment {assignment_id} for {current_user.email}: {e.detail}", exc_info=True)
|
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=e.detail)
|
|
|
|
@router.patch(
|
|
"/assignments/{assignment_id}/complete",
|
|
response_model=ChoreAssignmentPublic,
|
|
summary="Mark Assignment Complete",
|
|
tags=["Chore Assignments"]
|
|
)
|
|
async def complete_chore_assignment(
|
|
assignment_id: int,
|
|
db: AsyncSession = Depends(get_transactional_session),
|
|
current_user: UserModel = Depends(current_active_user),
|
|
):
|
|
"""Convenience endpoint to mark an assignment as complete."""
|
|
logger.info(f"User {current_user.email} marking assignment {assignment_id} as complete")
|
|
assignment_update = ChoreAssignmentUpdate(is_complete=True)
|
|
try:
|
|
updated_assignment = await crud_chore.update_chore_assignment(
|
|
db=db, assignment_id=assignment_id, assignment_in=assignment_update, user_id=current_user.id
|
|
)
|
|
if not updated_assignment:
|
|
raise ChoreNotFoundError(assignment_id=assignment_id)
|
|
return updated_assignment
|
|
except ChoreNotFoundError as e:
|
|
logger.warning(f"Assignment {assignment_id} not found for user {current_user.email} during completion.")
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=e.detail)
|
|
except PermissionDeniedError as e:
|
|
logger.warning(f"Permission denied for user {current_user.email} completing assignment {assignment_id}: {e.detail}")
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=e.detail)
|
|
except DatabaseIntegrityError as e:
|
|
logger.error(f"DatabaseIntegrityError completing assignment {assignment_id} for {current_user.email}: {e.detail}", exc_info=True)
|
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=e.detail) |