Add guests API with CRUD operations and tests
Introduce a new API for managing event guests, including endpoints for creating, reading, updating, deleting, and changing guest status. Added corresponding Pydantic schemas, database CRUD logic, and extensive test coverage to validate functionality. Integrated the guests API under the events router.
This commit is contained in:
57
backend/app/api/routes/events/guests.py
Normal file
57
backend/app/api/routes/events/guests.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from app.schemas.guests import GuestCreate, GuestUpdate, GuestRead
|
||||
from app.crud.guest import guest_crud
|
||||
from typing import List
|
||||
import uuid
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.models import GuestStatus
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/", response_model=GuestRead, operation_id="create_guest")
|
||||
def create_guest(guest_in: GuestCreate, db: Session = Depends(get_db)):
|
||||
guest = guest_crud.create(db, obj_in=guest_in)
|
||||
return guest
|
||||
|
||||
|
||||
@router.get("/{guest_id}", response_model=GuestRead, operation_id="get_guest")
|
||||
def read_guest(guest_id: uuid.UUID, db: Session = Depends(get_db)):
|
||||
guest = guest_crud.get(db, guest_id)
|
||||
if not guest:
|
||||
raise HTTPException(status_code=404, detail="Guest not found")
|
||||
return guest
|
||||
|
||||
|
||||
@router.get("/", response_model=List[GuestRead], operation_id="get_guests")
|
||||
def read_guests(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
|
||||
guests = guest_crud.get_multi(db, skip=skip, limit=limit)
|
||||
return guests
|
||||
|
||||
|
||||
@router.put("/{guest_id}", response_model=GuestRead, operation_id="update_guest")
|
||||
def update_guest(guest_id: uuid.UUID, guest_in: GuestUpdate, db: Session = Depends(get_db)):
|
||||
guest = guest_crud.get(db, guest_id)
|
||||
if not guest:
|
||||
raise HTTPException(status_code=404, detail="Guest not found")
|
||||
guest = guest_crud.update(db, db_obj=guest, obj_in=guest_in)
|
||||
return guest
|
||||
|
||||
|
||||
@router.delete("/{guest_id}", response_model=GuestRead, operation_id="delete_guest")
|
||||
def delete_guest(guest_id: uuid.UUID, db: Session = Depends(get_db)):
|
||||
guest = guest_crud.get(db, guest_id)
|
||||
if not guest:
|
||||
raise HTTPException(status_code=404, detail="Guest not found")
|
||||
guest = guest_crud.remove(db, guest_id=str(guest_id))
|
||||
return guest
|
||||
|
||||
|
||||
@router.patch("/{guest_id}/status", response_model=GuestRead)
|
||||
def set_guest_status(guest_id: uuid.UUID, status: GuestStatus, db: Session = Depends(get_db)):
|
||||
guest = guest_crud.update_status(db, guest_id=guest_id, status=status)
|
||||
if not guest:
|
||||
raise HTTPException(status_code=404, detail="Guest not found")
|
||||
return guest
|
||||
@@ -21,9 +21,12 @@ from app.schemas.events import (
|
||||
EventResponse, Event,
|
||||
)
|
||||
|
||||
from app.api.routes.events import guests
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
events_router = APIRouter()
|
||||
|
||||
events_router.include_router(guests.router, prefix="/guests", tags=["guests"])
|
||||
|
||||
def validate_event_access(
|
||||
*,
|
||||
|
||||
46
backend/app/crud/guest.py
Normal file
46
backend/app/crud/guest.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from app.crud.base import CRUDBase
|
||||
from app.models.guest import Guest
|
||||
from app.schemas.guests import GuestCreate, GuestUpdate
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Optional
|
||||
from app.models.guest import GuestStatus
|
||||
from datetime import datetime, timezone
|
||||
import uuid
|
||||
|
||||
|
||||
class CRUDGuest(CRUDBase[Guest, GuestCreate, GuestUpdate]):
|
||||
|
||||
def create(self, db, obj_in: GuestCreate):
|
||||
db_guest = Guest(
|
||||
event_id=uuid.UUID(obj_in.event_id) if isinstance(obj_in.event_id, str) else obj_in.event_id, # explicit casting
|
||||
invited_by=uuid.UUID(obj_in.invited_by) if isinstance(obj_in.invited_by, str) else obj_in.invited_by,
|
||||
full_name=obj_in.full_name,
|
||||
email=obj_in.email,
|
||||
invitation_code=obj_in.invitation_code
|
||||
)
|
||||
db.add(db_guest)
|
||||
db.commit()
|
||||
db.refresh(db_guest)
|
||||
return db_guest
|
||||
|
||||
|
||||
def get_by_invitation_code(self, db: Session, invitation_code: str) -> Optional[Guest]:
|
||||
return db.query(Guest).filter(Guest.invitation_code == invitation_code).first()
|
||||
|
||||
def update_status(self, db: Session, guest_id: uuid.UUID, status: GuestStatus):
|
||||
guest = self.get(db, guest_id)
|
||||
if guest:
|
||||
guest.status = status
|
||||
guest.response_date = datetime.now(timezone.utc)
|
||||
db.commit()
|
||||
db.refresh(guest)
|
||||
return guest
|
||||
|
||||
def remove(self, db: Session, *, guest_id: str) -> Guest:
|
||||
guest_id = uuid.UUID(guest_id) if isinstance(guest_id, str) else guest_id
|
||||
obj = db.query(self.model).get(guest_id)
|
||||
db.delete(obj)
|
||||
db.commit()
|
||||
return obj
|
||||
|
||||
guest_crud = CRUDGuest(Guest)
|
||||
48
backend/app/schemas/guests.py
Normal file
48
backend/app/schemas/guests.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import uuid
|
||||
|
||||
from pydantic import BaseModel, EmailStr, ConfigDict
|
||||
from datetime import datetime
|
||||
from typing import Optional, Any, Dict
|
||||
from app.models.guest import GuestStatus
|
||||
from uuid import UUID
|
||||
|
||||
class GuestBase(BaseModel):
|
||||
event_id: UUID
|
||||
invited_by: UUID
|
||||
user_id: Optional[UUID] = None
|
||||
full_name: str
|
||||
email: Optional[EmailStr] = None
|
||||
phone: Optional[str] = None
|
||||
max_additional_guests: Optional[int] = 0
|
||||
dietary_restrictions: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
custom_fields: Optional[Dict[str, Any]] = None
|
||||
can_bring_guests: Optional[bool] = False
|
||||
|
||||
|
||||
class GuestCreate(GuestBase):
|
||||
invitation_code: str
|
||||
|
||||
|
||||
class GuestUpdate(BaseModel):
|
||||
full_name: Optional[str] = None
|
||||
email: Optional[EmailStr] = None
|
||||
phone: Optional[str] = None
|
||||
status: Optional[GuestStatus] = None
|
||||
max_additional_guests: Optional[int] = None
|
||||
actual_additional_guests: Optional[int] = None
|
||||
dietary_restrictions: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
custom_fields: Optional[Dict[str, Any]] = None
|
||||
is_blocked: Optional[bool] = None
|
||||
can_bring_guests: Optional[bool] = None
|
||||
|
||||
|
||||
class GuestRead(GuestBase):
|
||||
id: UUID
|
||||
status: GuestStatus
|
||||
invitation_sent_at: Optional[datetime] = None
|
||||
response_date: Optional[datetime] = None
|
||||
actual_additional_guests: int
|
||||
is_blocked: bool
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
Reference in New Issue
Block a user