Files
eventspace/backend/app/api/routes/events/gifts.py
Felipe Cardoso c81e27c602 Add event-specific guest gift reservations endpoint
Introduced a function `get_event_guest_gift_reservations` in the CRUD layer to fetch gift reservations filtered by event ID. Updated the API endpoint to optionally accept an `event_id` query parameter for retrieving reservations specific to an event.
2025-03-19 19:56:10 +01:00

1075 lines
39 KiB
Python

from typing import List, Optional, Dict, Any
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Path, Query
from sqlalchemy.orm import Session
from app.api.dependencies.auth import get_current_active_user, get_current_user, get_optional_current_user
from app.core.database import get_db
from app.crud.event import event_crud
from app.crud.gift import gift_item_crud, gift_category_crud, gift_purchase_crud, event_gift_category_crud
from app.crud.guest import guest_crud
from app.models.gift import GiftStatus, EventGiftCategory, GiftItem as GiftItemModel
from app.models.user import User
from app.schemas.gifts import (
GiftItem, GiftItemCreate, GiftItemUpdate,
GiftCategory, GiftCategoryCreate, GiftCategoryUpdate,
GiftPurchase, GiftPurchaseCreate, EventGiftCategoryCreate
)
router = APIRouter()
# ===== GIFT CATEGORIES ===== #
@router.post("/categories/", response_model=GiftCategory, operation_id="create_gift_category")
def create_gift_category(
*,
db: Session = Depends(get_db),
category_in: GiftCategoryCreate,
event_id: UUID,
current_user: User = Depends(get_current_active_user)
) -> Any:
"""
Create new gift category and associate it with an event.
"""
# Check if user has permission to manage this event
event = event_crud.get(db, event_id)
if not event:
raise HTTPException(status_code=404, detail="Event not found")
# Check permissions (basic implementation)
# In a complete system, use the EventManager permissions
if event.created_by != current_user.id:
raise HTTPException(status_code=403, detail="Not enough permissions")
# Create the category
category = gift_category_crud.create(db, obj_in=category_in)
# Create the association between the category and the event
association_data = EventGiftCategoryCreate(
event_id=event_id,
category_id=category.id,
display_order=0, # Default display order
is_visible=True # Default visibility
)
event_gift_category_crud.create(db, obj_in=association_data)
# Set display properties for the response
category.display_order = 0
category.is_visible = True
return category
@router.get("/categories/event/{event_id}", response_model=List[GiftCategory], operation_id="read_gift_categories")
def read_gift_categories(
*,
db: Session = Depends(get_db),
event_id: UUID = Path(...),
skip: int = 0,
limit: int = 100,
include_hidden: bool = False,
include_gifts: bool = False,
current_user: Optional[User] = Depends(get_optional_current_user)
) -> Any:
"""
Retrieve gift categories for an event.
"""
# Check if event exists and is accessible
event = event_crud.get(db, event_id)
if not event:
raise HTTPException(status_code=404, detail="Event not found")
# For public access, ensure event is public
if not current_user and not event.is_public:
raise HTTPException(status_code=403, detail="Not enough permissions")
# Get categories for this event using the association table
categories = event_gift_category_crud.get_categories_by_event(
db, event_id=event_id, skip=skip, limit=limit, include_hidden=include_hidden
)
# Create a list to hold the enhanced category responses
enhanced_categories = []
for category in categories:
# Get the association to access display_order and is_visible
association = event_gift_category_crud.get(
db, event_id=event_id, category_id=category.id
)
# Default values
display_order = None
is_visible = None
if association:
display_order = association.display_order
is_visible = association.is_visible
# Calculate statistics for this event
total_gifts = 0
available_gifts = 0
gifts_list = None
# If include_gifts is true, fetch gift items for the category
if include_gifts:
gifts = gift_item_crud.get_multi_by_event(
db, event_id=event_id, category_id=category.id, include_hidden=include_hidden
)
gifts_list = gifts
# Calculate statistics
for gift in gifts:
if gift.event_id == event_id:
total_gifts += 1
if gift.status == GiftStatus.AVAILABLE and gift.is_visible:
available_gifts += 1
else:
# Calculate statistics without fetching all gifts
if category.gifts:
for gift in category.gifts:
if gift.event_id == event_id:
total_gifts += 1
if gift.status == GiftStatus.AVAILABLE and gift.is_visible:
available_gifts += 1
# Create a new category response with the calculated values
category_data = {
**category.__dict__,
"display_order": display_order,
"is_visible": is_visible,
"total_gifts": total_gifts,
"available_gifts": available_gifts
}
if gifts_list is not None:
category_data["gifts"] = gifts_list
# Remove SQLAlchemy state attributes
if "_sa_instance_state" in category_data:
del category_data["_sa_instance_state"]
enhanced_category = GiftCategory(**category_data)
enhanced_categories.append(enhanced_category)
# Replace the original categories list with the enhanced one
categories = enhanced_categories
return categories
@router.get("/categories/{category_id}", response_model=GiftCategory, operation_id="read_gift_category")
def read_gift_category(
*,
db: Session = Depends(get_db),
category_id: UUID = Path(...),
event_id: Optional[UUID] = None,
include_gifts: bool = False,
current_user: Optional[User] = Depends(get_optional_current_user)
) -> Any:
"""
Get gift category by ID. If event_id is provided, includes event-specific display settings.
"""
category = gift_category_crud.get(db, id=category_id)
if not category:
raise HTTPException(status_code=404, detail="Gift category not found")
# Default values for event-specific properties
display_order = None
is_visible = None
total_gifts = None
available_gifts = None
gifts_list = None
# If event_id is provided, get event-specific information
if event_id:
# Check if event exists and is accessible
event = event_crud.get(db, event_id)
if not event:
raise HTTPException(status_code=404, detail="Event not found")
# For public access, ensure event is public
if not event.is_public and not current_user:
raise HTTPException(status_code=403, detail="Not enough permissions")
# Check if this category is associated with the event
association = event_gift_category_crud.get(db, event_id=event_id, category_id=category_id)
if not association:
raise HTTPException(status_code=404, detail="Category not associated with this event")
# Get event-specific display properties
display_order = association.display_order
is_visible = association.is_visible
# Calculate statistics for this event
total_gifts = 0
available_gifts = 0
# If include_gifts is true, fetch gift items for the category in this event
if include_gifts:
gifts = gift_item_crud.get_multi_by_event(
db, event_id=event_id, category_id=category.id,
include_hidden=current_user is not None # Only include hidden for logged-in users
)
gifts_list = gifts
# Calculate statistics
for gift in gifts:
total_gifts += 1
if gift.status == GiftStatus.AVAILABLE and gift.is_visible:
available_gifts += 1
else:
# Calculate statistics without fetching all gifts
gifts_query = db.query(GiftItemModel).filter(
GiftItemModel.event_id == event_id,
GiftItemModel.category_id == category_id
)
total_gifts = gifts_query.count()
available_gifts = gifts_query.filter(
GiftItemModel.status == GiftStatus.AVAILABLE,
GiftItemModel.is_visible == True
).count()
elif include_gifts:
# If no event_id but include_gifts is true, just get all gifts for this category
# This is less useful without event context but included for completeness
gifts = db.query(GiftItemModel).filter(GiftItemModel.category_id == category_id).all()
gifts_list = gifts
# Create a new category response with the calculated values
category_data = {
**category.__dict__,
"display_order": display_order,
"is_visible": is_visible,
"total_gifts": total_gifts,
"available_gifts": available_gifts
}
if gifts_list is not None:
category_data["gifts"] = gifts_list
# Remove SQLAlchemy state attributes
if "_sa_instance_state" in category_data:
del category_data["_sa_instance_state"]
# Create a new category instance with the enhanced data
category = GiftCategory(**category_data)
return category
@router.put("/categories/{category_id}", response_model=GiftCategory, operation_id="update_gift_category")
def update_gift_category(
*,
db: Session = Depends(get_db),
category_id: UUID = Path(...),
category_in: GiftCategoryUpdate,
event_id: Optional[UUID] = None,
current_user: User = Depends(get_current_active_user)
) -> Any:
"""
Update a gift category. If event_id is provided, also updates the event-specific settings.
"""
category = gift_category_crud.get(db, id=category_id)
if not category:
raise HTTPException(status_code=404, detail="Gift category not found")
# Update the category itself
updated_category = gift_category_crud.update(db, db_obj=category, obj_in=category_in)
# Default values for event-specific properties
display_order = None
is_visible = None
total_gifts = None
available_gifts = None
# If event_id is provided, update the event-specific settings
if event_id:
# Check if event exists
event = event_crud.get(db, event_id)
if not event:
raise HTTPException(status_code=404, detail="Event not found")
# Check permissions (basic implementation)
if event.created_by != current_user.id:
raise HTTPException(status_code=403, detail="Not enough permissions")
# Check if this category is associated with the event
association = event_gift_category_crud.get(db, event_id=event_id, category_id=category_id)
if not association:
# If not associated, create the association
association_data = EventGiftCategoryCreate(
event_id=event_id,
category_id=category_id,
display_order=0, # Default display order
is_visible=True # Default visibility
)
association = event_gift_category_crud.create(db, obj_in=association_data)
else:
# If display_order or is_visible are in the update data, update the association
association_update = {}
if hasattr(category_in, 'display_order') and category_in.display_order is not None:
association_update['display_order'] = category_in.display_order
if hasattr(category_in, 'is_visible') and category_in.is_visible is not None:
association_update['is_visible'] = category_in.is_visible
if association_update:
association = event_gift_category_crud.update(
db, db_obj=association, obj_in=association_update
)
# Get event-specific properties for the response
display_order = association.display_order
is_visible = association.is_visible
# Calculate statistics for this event
gifts_query = db.query(GiftItem).filter(
GiftItem.event_id == event_id,
GiftItem.category_id == category_id
)
total_gifts = gifts_query.count()
available_gifts = gifts_query.filter(
GiftItem.status == GiftStatus.AVAILABLE,
GiftItem.is_visible == True
).count()
# Create a new category response with the calculated values
category_data = {
**updated_category.__dict__,
"display_order": display_order,
"is_visible": is_visible,
"total_gifts": total_gifts,
"available_gifts": available_gifts
}
# Remove SQLAlchemy state attributes
if "_sa_instance_state" in category_data:
del category_data["_sa_instance_state"]
# Create a new category instance with the enhanced data
updated_category = GiftCategory(**category_data)
return updated_category
@router.delete("/categories/{category_id}", response_model=GiftCategory, operation_id="delete_gift_category")
def delete_gift_category(
*,
db: Session = Depends(get_db),
category_id: UUID = Path(...),
event_id: Optional[UUID] = None,
force: bool = False,
current_user: User = Depends(get_current_active_user)
) -> Any:
"""
Delete a gift category. If event_id is provided, only removes the association with that event.
If force=True and no event_id is provided, deletes the category completely.
"""
category = gift_category_crud.get(db, id=category_id)
if not category:
raise HTTPException(status_code=404, detail="Gift category not found")
# Default values for event-specific properties
display_order = None
is_visible = None
total_gifts = None
available_gifts = None
gifts_list = None
if event_id:
# Check if event exists
event = event_crud.get(db, event_id)
if not event:
raise HTTPException(status_code=404, detail="Event not found")
# Check permissions (basic implementation)
if event.created_by != current_user.id:
raise HTTPException(status_code=403, detail="Not enough permissions")
# Check if this category is associated with the event
association = event_gift_category_crud.get(db, event_id=event_id, category_id=category_id)
if not association:
raise HTTPException(status_code=404, detail="Category not associated with this event")
# Remove the association
event_gift_category_crud.remove(db, event_id=event_id, category_id=category_id)
# Create a new category response with the calculated values
category_data = {
**category.__dict__,
"display_order": display_order,
"is_visible": is_visible,
"total_gifts": total_gifts,
"available_gifts": available_gifts
}
# Remove SQLAlchemy state attributes
if "_sa_instance_state" in category_data:
del category_data["_sa_instance_state"]
# Create a new category instance with the enhanced data
category_response = GiftCategory(**category_data)
return category_response
elif force:
# Check if the user has permission to delete the category
# This is a more restrictive operation, so we might want to add additional checks
# Check if the category is used by any events
# Get all associations for this category
associations = db.query(EventGiftCategory).filter(
EventGiftCategory.category_id == category_id
).all()
if associations and len(associations) > 0:
# If there are associations, we need to check if the user has permission to manage all events
for assoc in associations:
event = event_crud.get(db, assoc.event_id)
if not event:
continue
# If the user doesn't have permission for any of the events, deny the operation
if event.created_by != current_user.id:
raise HTTPException(
status_code=403,
detail="Not enough permissions. Category is used by events you don't manage."
)
# Remove all associations
for assoc in associations:
event_gift_category_crud.remove(db, event_id=assoc.event_id, category_id=category_id)
# Now delete the category itself
deleted_category = gift_category_crud.remove(db, id=category_id)
# Create a new category response with the calculated values
category_data = {
**deleted_category.__dict__,
"display_order": display_order,
"is_visible": is_visible,
"total_gifts": total_gifts,
"available_gifts": available_gifts
}
# Remove SQLAlchemy state attributes
if "_sa_instance_state" in category_data:
del category_data["_sa_instance_state"]
# Create a new category instance with the enhanced data
category_response = GiftCategory(**category_data)
return category_response
else:
# If no event_id and not force, raise an error
raise HTTPException(
status_code=400,
detail="Must provide event_id to remove association or set force=True to delete category completely"
)
# ===== EVENT-CATEGORY ASSOCIATIONS ===== #
@router.post("/events/{event_id}/categories/{category_id}", response_model=GiftCategory, operation_id="associate_category_with_event")
def associate_category_with_event(
*,
db: Session = Depends(get_db),
event_id: UUID = Path(...),
category_id: UUID = Path(...),
display_order: int = 0,
is_visible: bool = True,
current_user: User = Depends(get_current_active_user)
) -> Any:
"""
Associate an existing category with an event.
"""
# Check if event exists
event = event_crud.get(db, event_id)
if not event:
raise HTTPException(status_code=404, detail="Event not found")
# Check permissions
if event.created_by != current_user.id:
raise HTTPException(status_code=403, detail="Not enough permissions")
# Check if category exists
category = gift_category_crud.get(db, id=category_id)
if not category:
raise HTTPException(status_code=404, detail="Gift category not found")
# Check if association already exists
existing_association = event_gift_category_crud.get(db, event_id=event_id, category_id=category_id)
if existing_association:
raise HTTPException(status_code=400, detail="Category already associated with this event")
# Create the association
association_data = EventGiftCategoryCreate(
event_id=event_id,
category_id=category_id,
display_order=display_order,
is_visible=is_visible
)
event_gift_category_crud.create(db, obj_in=association_data)
# Set display properties for the response
category.display_order = display_order
category.is_visible = is_visible
# Calculate statistics for this event
gifts_query = db.query(GiftItem).filter(
GiftItem.event_id == event_id,
GiftItem.category_id == category_id
)
category.total_gifts = gifts_query.count()
category.available_gifts = gifts_query.filter(
GiftItem.status == GiftStatus.AVAILABLE,
GiftItem.is_visible == True
).count()
return category
@router.put("/events/{event_id}/categories/{category_id}", response_model=GiftCategory, operation_id="update_category_event_settings")
def update_category_event_settings(
*,
db: Session = Depends(get_db),
event_id: UUID = Path(...),
category_id: UUID = Path(...),
display_order: Optional[int] = None,
is_visible: Optional[bool] = None,
current_user: User = Depends(get_current_active_user)
) -> Any:
"""
Update the display settings for a category in an event.
"""
# Check if event exists
event = event_crud.get(db, event_id)
if not event:
raise HTTPException(status_code=404, detail="Event not found")
# Check permissions
if event.created_by != current_user.id:
raise HTTPException(status_code=403, detail="Not enough permissions")
# Check if category exists
category = gift_category_crud.get(db, id=category_id)
if not category:
raise HTTPException(status_code=404, detail="Gift category not found")
# Check if association exists
association = event_gift_category_crud.get(db, event_id=event_id, category_id=category_id)
if not association:
raise HTTPException(status_code=404, detail="Category not associated with this event")
# Update the association
update_data = {}
if display_order is not None:
update_data['display_order'] = display_order
if is_visible is not None:
update_data['is_visible'] = is_visible
if update_data:
association = event_gift_category_crud.update(db, db_obj=association, obj_in=update_data)
# Set display properties for the response
category.display_order = association.display_order
category.is_visible = association.is_visible
# Calculate statistics for this event
gifts_query = db.query(GiftItem).filter(
GiftItem.event_id == event_id,
GiftItem.category_id == category_id
)
category.total_gifts = gifts_query.count()
category.available_gifts = gifts_query.filter(
GiftItem.status == GiftStatus.AVAILABLE,
GiftItem.is_visible == True
).count()
return category
@router.get("/categories/{category_id}/events", response_model=List[Dict[str, Any]], operation_id="get_events_for_category")
def get_events_for_category(
*,
db: Session = Depends(get_db),
category_id: UUID = Path(...),
current_user: User = Depends(get_current_active_user)
) -> Any:
"""
Get all events that use a specific category.
"""
# Check if category exists
category = gift_category_crud.get(db, id=category_id)
if not category:
raise HTTPException(status_code=404, detail="Gift category not found")
# Get all events for this category
events = event_gift_category_crud.get_events_by_category(db, category_id=category_id)
# Filter events that the user has permission to see
result = []
for event in events:
# Check if user has permission to see this event
if event.created_by == current_user.id or event.is_public:
# Get the association to access display_order and is_visible
association = event_gift_category_crud.get(db, event_id=event.id, category_id=category_id)
if association:
# Create a response with event details and association settings
event_data = {
"event_id": event.id,
"event_title": event.title,
"event_date": event.event_date,
"display_order": association.display_order,
"is_visible": association.is_visible
}
result.append(event_data)
return result
@router.put("/categories/{category_id}/reorder", response_model=GiftCategory, operation_id="reorder_gifts_in_category")
def reorder_gifts_in_category(
*,
db: Session = Depends(get_db),
category_id: UUID = Path(...),
gift_orders: Dict[UUID, int],
current_user: User = Depends(get_current_active_user)
) -> Any:
"""
Reorder gifts within a category.
"""
category = gift_category_crud.get(db, id=category_id)
if not category:
raise HTTPException(status_code=404, detail="Gift category not found")
# Check if user has permission to manage this event
event = event_crud.get(db, category.event_id)
if not event:
raise HTTPException(status_code=404, detail="Event not found")
# Check permissions (basic implementation)
if event.created_by != current_user.id:
raise HTTPException(status_code=403, detail="Not enough permissions")
return gift_category_crud.reorder_gifts(db, category_id=category_id, gift_orders=gift_orders)
# ===== GIFT ITEMS ===== #
@router.post("/items/", response_model=GiftItem, operation_id="create_gift_item")
def create_gift_item(
*,
db: Session = Depends(get_db),
item_in: GiftItemCreate,
current_user: User = Depends(get_current_active_user)
) -> Any:
"""
Create new gift item.
"""
# Check if user has permission to manage this event
event = event_crud.get(db, item_in.event_id)
if not event:
raise HTTPException(status_code=404, detail="Event not found")
# Check permissions (basic implementation)
if event.created_by != current_user.id:
raise HTTPException(status_code=403, detail="Not enough permissions")
# If category is specified, check if it exists
if item_in.category_id:
category = gift_category_crud.get(db, id=item_in.category_id)
if not category:
raise HTTPException(status_code=404, detail="Gift category not found")
# Check category belongs to the same event by checking the association
association = event_gift_category_crud.get(db, event_id=item_in.event_id, category_id=item_in.category_id)
if not association:
raise HTTPException(status_code=400, detail="Category does not belong to this event")
return gift_item_crud.create(db, obj_in=item_in)
@router.get("/items/event/{event_id}", response_model=List[GiftItem], operation_id="read_gift_items")
def read_gift_items(
*,
db: Session = Depends(get_db),
event_id: UUID = Path(...),
skip: int = 0,
limit: int = 100,
include_hidden: bool = False,
category_id: Optional[UUID] = None,
current_user: Optional[User] = Depends(get_optional_current_user)
) -> Any:
"""
Retrieve gift items for an event.
"""
# Check if event exists and is accessible
event = event_crud.get(db, event_id)
if not event:
raise HTTPException(status_code=404, detail="Event not found")
# For public access, ensure event is public
if not current_user and not event.is_public:
raise HTTPException(status_code=403, detail="Not enough permissions")
# If category is specified, check if it exists
if category_id:
category = gift_category_crud.get(db, id=category_id)
if not category:
raise HTTPException(status_code=404, detail="Gift category not found")
# Check category belongs to the requested event by checking the association
association = event_gift_category_crud.get(db, event_id=event_id, category_id=category_id)
if not association:
raise HTTPException(status_code=400, detail="Category does not belong to this event")
return gift_item_crud.get_multi_by_event(
db, event_id=event_id, skip=skip, limit=limit,
include_hidden=include_hidden, category_id=category_id
)
@router.get("/items/{item_id}", response_model=GiftItem, operation_id="read_gift_item")
def read_gift_item(
*,
db: Session = Depends(get_db),
item_id: UUID = Path(...),
current_user: Optional[User] = Depends(get_optional_current_user)
) -> Any:
"""
Get gift item by ID.
"""
gift = gift_item_crud.get(db, id=item_id)
if not gift:
raise HTTPException(status_code=404, detail="Gift item not found")
# Check if event is public or user is authorized
event = event_crud.get(db, gift.event_id)
if not event:
raise HTTPException(status_code=404, detail="Event not found")
if not event.is_public and not current_user:
raise HTTPException(status_code=403, detail="Not enough permissions")
# Check if gift is visible for public users
if not current_user and not gift.is_visible:
raise HTTPException(status_code=404, detail="Gift item not found")
return gift
@router.put("/items/{item_id}", response_model=GiftItem, operation_id="update_gift_item")
def update_gift_item(
*,
db: Session = Depends(get_db),
item_id: UUID = Path(...),
item_in: GiftItemUpdate,
current_user: User = Depends(get_current_active_user)
) -> Any:
"""
Update a gift item.
"""
gift = gift_item_crud.get(db, id=item_id)
if not gift:
raise HTTPException(status_code=404, detail="Gift item not found")
# Check if user has permission to manage this event
event = event_crud.get(db, gift.event_id)
if not event:
raise HTTPException(status_code=404, detail="Event not found")
# Check permissions (basic implementation)
if event.created_by != current_user.id:
raise HTTPException(status_code=403, detail="Not enough permissions")
# If changing category, check if new category exists and belongs to same event
if item_in.category_id and item_in.category_id != gift.category_id:
category = gift_category_crud.get(db, id=item_in.category_id)
if not category:
raise HTTPException(status_code=404, detail="Gift category not found")
# Check category belongs to the same event by checking the association
association = event_gift_category_crud.get(db, event_id=gift.event_id, category_id=item_in.category_id)
if not association:
raise HTTPException(status_code=400, detail="Category does not belong to this event")
return gift_item_crud.update(db, db_obj=gift, obj_in=item_in)
@router.delete("/items/{item_id}", response_model=GiftItem, operation_id="delete_gift_item")
def delete_gift_item(
*,
db: Session = Depends(get_db),
item_id: UUID = Path(...),
current_user: User = Depends(get_current_active_user)
) -> Any:
"""
Delete a gift item.
"""
gift = gift_item_crud.get(db, id=item_id)
if not gift:
raise HTTPException(status_code=404, detail="Gift item not found")
# Check if user has permission to manage this event
event = event_crud.get(db, gift.event_id)
if not event:
raise HTTPException(status_code=404, detail="Event not found")
# Check permissions (basic implementation)
if event.created_by != current_user.id:
raise HTTPException(status_code=403, detail="Not enough permissions")
return gift_item_crud.remove(db, id=item_id)
@router.put("/items/{item_id}/status", response_model=GiftItem, operation_id="update_gift_item_status")
def update_gift_item_status(
*,
db: Session = Depends(get_db),
item_id: UUID = Path(...),
status: GiftStatus,
current_user: User = Depends(get_current_active_user)
) -> Any:
"""
Update a gift item's status.
"""
gift = gift_item_crud.get(db, id=item_id)
if not gift:
raise HTTPException(status_code=404, detail="Gift item not found")
# Check if user has permission to manage this event
event = event_crud.get(db, gift.event_id)
if not event:
raise HTTPException(status_code=404, detail="Event not found")
# Check permissions (basic implementation)
if event.created_by != current_user.id:
raise HTTPException(status_code=403, detail="Not enough permissions")
return gift_item_crud.update_status(db, gift_id=item_id, new_status=status)
@router.post("/items/{item_id}/reserve", response_model=GiftItem, operation_id="reserve_gift_item")
def reserve_gift_item(
*,
db: Session = Depends(get_db),
item_id: UUID = Path(...),
guest_id: UUID,
quantity: Optional[int] = 1,
notes: Optional[str] = None,
current_user: Optional[User] = Depends(get_optional_current_user)
) -> Any:
"""
Reserve a gift item for a guest with specified quantity.
Default quantity is 1 if not provided.
"""
gift = gift_item_crud.get(db, id=item_id)
if not gift:
raise HTTPException(status_code=404, detail="Gift item not found")
# Check if gift is available
if gift.status == GiftStatus.RECEIVED or gift.status == GiftStatus.REMOVED:
raise HTTPException(status_code=400, detail="Gift is not available for reservation")
# Validate quantity
if quantity <= 0:
raise HTTPException(status_code=400, detail="Quantity must be a positive number")
# Check if requested quantity is valid
if quantity > gift.remaining_quantity:
raise HTTPException(status_code=400,
detail=f"Requested quantity ({quantity}) exceeds available quantity ({gift.remaining_quantity})")
# Check if guest exists
guest = guest_crud.get(db, id=guest_id)
if not guest:
raise HTTPException(status_code=404, detail="Guest not found")
# Check if guest belongs to the same event
if guest.event_id != gift.event_id:
raise HTTPException(status_code=400, detail="Guest does not belong to this event")
# For admin users, allow direct reservation
if current_user:
return gift_item_crud.reserve_gift(db, gift_id=item_id, guest_id=guest_id, quantity=quantity, notes=notes)
# For public users, additional validation
# Check if event is public
event = event_crud.get(db, gift.event_id)
if not event or not event.is_public:
raise HTTPException(status_code=403, detail="Not enough permissions")
return gift_item_crud.reserve_gift(db, gift_id=item_id, guest_id=guest_id, quantity=quantity, notes=notes)
@router.post("/items/{item_id}/cancel-reservation", response_model=GiftItem, operation_id="cancel_gift_reservation")
def cancel_gift_reservation(
*,
db: Session = Depends(get_db),
item_id: UUID = Path(...),
guest_id: UUID,
current_user: Optional[User] = Depends(get_optional_current_user)
) -> Any:
"""
Cancel a gift reservation.
"""
gift = gift_item_crud.get(db, id=item_id)
if not gift:
raise HTTPException(status_code=404, detail="Gift item not found")
# Check if gift is reserved
if gift.status not in [GiftStatus.RESERVED, GiftStatus.RECEIVED]:
raise HTTPException(status_code=400, detail="Gift is not currently reserved")
# Check if guest exists
guest = guest_crud.get(db, id=guest_id)
if not guest:
raise HTTPException(status_code=404, detail="Guest not found")
# For admin users, allow direct cancellation
if current_user:
try:
return gift_item_crud.cancel_reservation(db, gift_id=item_id, guest_id=guest_id)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
return gift_item_crud.cancel_reservation(db, gift_id=item_id, guest_id=guest_id)
# ===== GIFT PURCHASES ===== #
@router.post("/purchases/", response_model=GiftPurchase, operation_id="create_gift_purchase")
def create_gift_purchase(
*,
db: Session = Depends(get_db),
purchase_in: GiftPurchaseCreate,
current_user: Optional[User] = Depends(get_optional_current_user)
) -> Any:
"""
Create a gift purchase record.
"""
# Check if gift exists
gift = gift_item_crud.get(db, id=purchase_in.gift_id)
if not gift:
raise HTTPException(status_code=404, detail="Gift item not found")
# Check if guest exists
guest = guest_crud.get(db, id=purchase_in.guest_id)
if not guest:
raise HTTPException(status_code=404, detail="Guest not found")
# Check if guest belongs to the same event as the gift
if guest.event_id != gift.event_id:
raise HTTPException(status_code=400, detail="Guest does not belong to this event")
# For public users, additional validation
if not current_user:
# Check if event is public
event = event_crud.get(db, gift.event_id)
if not event or not event.is_public:
raise HTTPException(status_code=403, detail="Not enough permissions")
return gift_purchase_crud.create(db, obj_in=purchase_in)
@router.get("/purchases/{purchase_id}", response_model=GiftPurchase, operation_id="read_gift_purchase")
def read_gift_purchase(
*,
db: Session = Depends(get_db),
purchase_id: UUID = Path(...),
current_user: Optional[User] = Depends(get_optional_current_user)
) -> Any:
"""
Get a gift purchase by ID.
"""
purchase = gift_purchase_crud.get(db, id=purchase_id)
if not purchase:
raise HTTPException(status_code=404, detail="Gift purchase not found")
# If user is authenticated, allow access
if current_user:
return purchase
# For public users, check if the event is public
gift = gift_item_crud.get(db, id=purchase.gift_id)
if not gift:
raise HTTPException(status_code=404, detail="Gift item not found")
event = event_crud.get(db, gift.event_id)
if not event or not event.is_public:
raise HTTPException(status_code=403, detail="Not enough permissions")
return purchase
@router.get("/purchases/gift/{gift_id}", response_model=List[GiftPurchase], operation_id="read_gift_purchases_by_gift")
def read_gift_purchases_by_gift(
*,
db: Session = Depends(get_db),
gift_id: UUID = Path(...),
current_user: Optional[User] = Depends(get_optional_current_user)
) -> Any:
"""
Get all purchases for a specific gift.
"""
# Check if gift exists
gift = gift_item_crud.get(db, id=gift_id)
if not gift:
raise HTTPException(status_code=404, detail="Gift item not found")
# If user is authenticated, allow access
if current_user:
return gift_purchase_crud.get_by_gift(db, gift_id=gift_id)
# For public users, check if the event is public
event = event_crud.get(db, gift.event_id)
if not event or not event.is_public:
raise HTTPException(status_code=403, detail="Not enough permissions")
return gift_purchase_crud.get_by_gift(db, gift_id=gift_id)
@router.get("/purchases/guest/{guest_id}", response_model=List[GiftPurchase], operation_id="read_gift_purchases_by_guest")
def read_gift_purchases_by_guest(
*,
db: Session = Depends(get_db),
guest_id: UUID = Path(...),
) -> Any:
"""
Get all purchases made by a specific guest.
"""
# Check if guest exists
guest = guest_crud.get(db, id=guest_id)
if not guest:
raise HTTPException(status_code=404, detail="Guest not found")
# For public users, check if the event is public
event = event_crud.get(db, guest.event_id)
if not event or not event.is_public:
raise HTTPException(status_code=403, detail="Not enough permissions")
return gift_purchase_crud.get_gift_reservations_by_guest(db, guest_id=guest_id)
@router.get(
"/reservations/guests",
response_model=List[GiftPurchase],
operation_id="read_guests_gift_reservations",
)
def read_all_guest_gift_reservations(
*,
db: Session = Depends(get_db),
event_id: Optional[str] = Query(None, description="Optional event ID to filter reservations by event"),
) -> Any:
"""
Retrieve all guest gift reservations, optionally filtered by event ID.
"""
if event_id:
event_id = UUID(event_id)
reservations = gift_purchase_crud.get_event_guest_gift_reservations(db=db, event_id=event_id)
else:
reservations = gift_purchase_crud.get_all_guest_gift_reservations(db=db)
if not reservations:
reservations = []
return reservations