Add for gifts: schema, crud, route, tests
This commit is contained in:
592
backend/app/api/routes/events/gifts.py
Normal file
592
backend/app/api/routes/events/gifts.py
Normal file
@@ -0,0 +1,592 @@
|
||||
from typing import List, Optional, Dict, Any
|
||||
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.guest import guest_crud
|
||||
from app.crud.event import event_crud
|
||||
from app.models.gift import GiftStatus, GiftPriority
|
||||
from app.models.user import User
|
||||
from app.schemas.gifts import (
|
||||
GiftItem, GiftItemCreate, GiftItemUpdate,
|
||||
GiftCategory, GiftCategoryCreate, GiftCategoryUpdate,
|
||||
GiftPurchase, GiftPurchaseCreate, GiftPurchaseUpdate
|
||||
)
|
||||
from app.core.database import get_db
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# ===== GIFT CATEGORIES ===== #
|
||||
|
||||
@router.post("/categories/", response_model=GiftCategory)
|
||||
def create_gift_category(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
category_in: GiftCategoryCreate,
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Create new gift category.
|
||||
"""
|
||||
# Check if user has permission to manage this event
|
||||
event = event_crud.get(db, category_in.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")
|
||||
|
||||
return gift_category_crud.create(db, obj_in=category_in)
|
||||
|
||||
|
||||
@router.get("/categories/event/{event_id}", response_model=List[GiftCategory])
|
||||
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_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
|
||||
categories = gift_category_crud.get_multi_by_event(
|
||||
db, event_id=event_id, skip=skip, limit=limit, include_hidden=include_hidden
|
||||
)
|
||||
|
||||
# If include_gifts is true, fetch gift items for each category
|
||||
if include_gifts:
|
||||
for category in categories:
|
||||
gifts = gift_item_crud.get_multi_by_event(
|
||||
db, event_id=event_id, category_id=category.id, include_hidden=include_hidden
|
||||
)
|
||||
# Set the gifts attribute which is initially None in the model
|
||||
setattr(category, "gifts", gifts)
|
||||
|
||||
return categories
|
||||
|
||||
|
||||
@router.get("/categories/{category_id}", response_model=GiftCategory)
|
||||
def read_gift_category(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
category_id: UUID = Path(...),
|
||||
include_gifts: bool = False,
|
||||
current_user: Optional[User] = Depends(get_current_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Get gift category by ID.
|
||||
"""
|
||||
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")
|
||||
|
||||
if not event.is_public and not current_user:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
# 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
|
||||
setattr(category, "gifts", gifts)
|
||||
|
||||
return category
|
||||
|
||||
|
||||
@router.put("/categories/{category_id}", response_model=GiftCategory)
|
||||
def update_gift_category(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
category_id: UUID = Path(...),
|
||||
category_in: GiftCategoryUpdate,
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Update a gift 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.update(db, db_obj=category, obj_in=category_in)
|
||||
|
||||
|
||||
@router.delete("/categories/{category_id}", response_model=GiftCategory)
|
||||
def delete_gift_category(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
category_id: UUID = Path(...),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
) -> Any:
|
||||
"""
|
||||
Delete a gift 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.remove(db, id=category_id)
|
||||
|
||||
|
||||
@router.put("/categories/{category_id}/reorder", response_model=GiftCategory)
|
||||
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)
|
||||
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
|
||||
if category.event_id != item_in.event_id:
|
||||
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])
|
||||
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_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
|
||||
if category.event_id != event_id:
|
||||
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)
|
||||
def read_gift_item(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
item_id: UUID = Path(...),
|
||||
current_user: Optional[User] = Depends(get_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)
|
||||
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
|
||||
if category.event_id != gift.event_id:
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
def reserve_gift_item(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
item_id: UUID = Path(...),
|
||||
guest_id: UUID,
|
||||
notes: Optional[str] = None,
|
||||
current_user: Optional[User] = Depends(get_current_user)
|
||||
) -> Any:
|
||||
"""
|
||||
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")
|
||||
|
||||
# Check if gift is available
|
||||
if gift.status != GiftStatus.AVAILABLE:
|
||||
raise HTTPException(status_code=400, detail="Gift is not available for reservation")
|
||||
|
||||
# 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, 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, notes=notes)
|
||||
|
||||
|
||||
@router.post("/items/{item_id}/cancel-reservation", response_model=GiftItem)
|
||||
def cancel_gift_reservation(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
item_id: UUID = Path(...),
|
||||
guest_id: UUID,
|
||||
current_user: Optional[User] = Depends(get_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 != GiftStatus.RESERVED:
|
||||
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:
|
||||
return gift_item_crud.cancel_reservation(db, gift_id=item_id, guest_id=guest_id)
|
||||
|
||||
|
||||
# ===== GIFT PURCHASES ===== #
|
||||
|
||||
@router.post("/purchases/", response_model=GiftPurchase)
|
||||
def create_gift_purchase(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
purchase_in: GiftPurchaseCreate,
|
||||
current_user: Optional[User] = Depends(get_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)
|
||||
def read_gift_purchase(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
purchase_id: UUID = Path(...),
|
||||
current_user: Optional[User] = Depends(get_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])
|
||||
def read_gift_purchases_by_gift(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
gift_id: UUID = Path(...),
|
||||
current_user: Optional[User] = Depends(get_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])
|
||||
def read_gift_purchases_by_guest(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
guest_id: UUID = Path(...),
|
||||
current_user: Optional[User] = Depends(get_current_user)
|
||||
) -> 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")
|
||||
|
||||
# If user is authenticated, allow access
|
||||
if current_user:
|
||||
return gift_purchase_crud.get_by_guest(db, guest_id=guest_id)
|
||||
|
||||
# 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_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)
|
||||
Reference in New Issue
Block a user