Big refactor of gift categories model
All checks were successful
Build and Push Docker Images / changes (push) Successful in 5s
Build and Push Docker Images / build-backend (push) Successful in 52s
Build and Push Docker Images / build-frontend (push) Has been skipped

This commit is contained in:
2025-03-16 14:51:04 +01:00
parent ed017a42ed
commit 4ef202cc5a
7 changed files with 643 additions and 97 deletions

View File

@@ -1,19 +1,20 @@
from typing import List, Optional, Dict, Any
from typing import List, Optional, Dict, Any, Union
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Query, Path
from sqlalchemy.orm import Session
from app.api.dependencies.auth import get_current_active_user, get_current_user
from app.crud.gift import gift_item_crud, gift_category_crud, gift_purchase_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.crud.event import event_crud
from app.models.gift import GiftStatus, GiftPriority
from app.models.gift import GiftStatus, GiftPriority, EventGiftCategory, GiftItem
from app.models.user import User
from app.schemas.gifts import (
GiftItem, GiftItemCreate, GiftItemUpdate,
GiftCategory, GiftCategoryCreate, GiftCategoryUpdate,
GiftPurchase, GiftPurchaseCreate, GiftPurchaseUpdate
GiftPurchase, GiftPurchaseCreate, GiftPurchaseUpdate,
EventGiftCategoryCreate, EventGiftCategoryUpdate, EventGiftCategoryInDB
)
from app.core.database import get_db
@@ -26,13 +27,14 @@ 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.
Create new gift category and associate it with an event.
"""
# Check if user has permission to manage this event
event = event_crud.get(db, category_in.event_id)
event = event_crud.get(db, event_id)
if not event:
raise HTTPException(status_code=404, detail="Event not found")
@@ -41,7 +43,23 @@ def create_gift_category(
if event.created_by != current_user.id:
raise HTTPException(status_code=403, detail="Not enough permissions")
return gift_category_crud.create(db, obj_in=category_in)
# 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])
@@ -67,11 +85,34 @@ def read_gift_categories(
if not current_user and not event.is_public:
raise HTTPException(status_code=403, detail="Not enough permissions")
# Get categories
categories = gift_category_crud.get_multi_by_event(
# 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
)
# Enhance categories with display information from the association
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
)
if association:
category.display_order = association.display_order
category.is_visible = association.is_visible
# Calculate statistics for this event
total_gifts = 0
available_gifts = 0
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
category.total_gifts = total_gifts
category.available_gifts = available_gifts
# If include_gifts is true, fetch gift items for each category
if include_gifts:
for category in categories:
@@ -89,31 +130,79 @@ 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_current_user)
) -> Any:
"""
Get gift category by ID.
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")
# Check if event is public or user is authorized
event = event_crud.get(db, category.event_id)
if not event:
raise HTTPException(status_code=404, detail="Event not found")
# Initialize event-specific properties
category.display_order = None
category.is_visible = None
category.total_gifts = None
category.available_gifts = None
if not event.is_public and not current_user:
raise HTTPException(status_code=403, detail="Not enough permissions")
# 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")
# 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=category.event_id, category_id=category.id,
include_hidden=current_user is not None # Only include hidden for logged-in users
)
# Set the gifts attribute which is initially None in the model
# 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")
# Set event-specific display properties
category.display_order = association.display_order
category.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
)
# Set the gifts attribute
setattr(category, "gifts", 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(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()
category.total_gifts = total_gifts
category.available_gifts = available_gifts
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(GiftItem).filter(GiftItem.category_id == category_id).all()
setattr(category, "gifts", gifts)
return category
@@ -125,25 +214,76 @@ 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.
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")
# 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")
# Update the category itself
updated_category = gift_category_crud.update(db, db_obj=category, obj_in=category_in)
# Check permissions (basic implementation)
if event.created_by != current_user.id:
raise HTTPException(status_code=403, detail="Not enough permissions")
# Initialize event-specific properties for the response
updated_category.display_order = None
updated_category.is_visible = None
updated_category.total_gifts = None
updated_category.available_gifts = None
return gift_category_crud.update(db, db_obj=category, obj_in=category_in)
# 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
)
# Set event-specific properties for the response
updated_category.display_order = association.display_order
updated_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
)
updated_category.total_gifts = gifts_query.count()
updated_category.available_gifts = gifts_query.filter(
GiftItem.status == GiftStatus.AVAILABLE,
GiftItem.is_visible == True
).count()
return updated_category
@router.delete("/categories/{category_id}", response_model=GiftCategory)
@@ -151,25 +291,242 @@ 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.
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")
# Check if user has permission to manage this event
event = event_crud.get(db, category.event_id)
# Make a copy of the category for the response
category_copy = GiftCategory.model_validate(category)
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)
# Return the category with event-specific properties set to None
category_copy.display_order = None
category_copy.is_visible = None
category_copy.total_gifts = None
category_copy.available_gifts = None
return category_copy
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
return gift_category_crud.remove(db, id=category_id)
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)
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 (basic implementation)
# Check permissions
if event.created_by != current_user.id:
raise HTTPException(status_code=403, detail="Not enough permissions")
return gift_category_crud.remove(db, id=category_id)
# 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)
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]])
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)
@@ -404,7 +761,6 @@ def reserve_gift_item(
Reserve a gift item for a guest.
"""
gift = gift_item_crud.get(db, id=item_id)
print(f"Gift {gift}")
if not gift:
raise HTTPException(status_code=404, detail="Gift item not found")
@@ -581,12 +937,3 @@ def read_gift_purchases_by_guest(
raise HTTPException(status_code=403, detail="Not enough permissions")
return gift_purchase_crud.get_by_guest(db, guest_id=guest_id)
# 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.cancel_reservation(db, gift_id=item_id, guest_id=guest_id)

View File

@@ -23,11 +23,13 @@ from app.schemas.events import (
from app.api.routes.events import guests
from app.api.routes.events import rsvps
from app.api.routes.events import gifts
logger = logging.getLogger(__name__)
events_router = APIRouter()
events_router.include_router(guests.router, prefix="/guests", tags=["guests"])
events_router.include_router(rsvps.router, prefix="/rsvps", tags=["rsvps"])
events_router.include_router(gifts.router, prefix="/gifts", tags=["gifts"])
def validate_event_access(
*,