"""
Hybrid Booking Sessions - Database Operations
CRUD operations for hybrid booking sessions stored in PostgreSQL
"""
from typing import Optional, Dict, Any, List
from datetime import datetime
from sqlalchemy.orm import Session
import logging
from .models import HybridBookingSession
from .database import get_db_session
logger = logging.getLogger(__name__)
class BookingSessionManager:
"""Manager for hybrid booking sessions in database"""
@staticmethod
def create_session(
session_id: str,
organizer_email: str,
organizer_name: str,
external_email: str,
external_name: str,
proposed_slots: List[Dict[str, str]],
meeting_subject: str,
meeting_duration: int,
booking_url: str,
internal_attendees: Optional[List[Dict[str, str]]] = None,
attachments: Optional[List[Dict[str, Any]]] = None
) -> HybridBookingSession:
"""
Create a new hybrid booking session
Args:
session_id: Unique session identifier
organizer_email: Email of meeting organizer
organizer_name: Name of meeting organizer
external_email: Email of external attendee
external_name: Name of external attendee
proposed_slots: List of proposed time slots
meeting_subject: Meeting subject/title
meeting_duration: Duration in minutes
booking_url: URL for booking page
internal_attendees: Optional list of internal attendees
attachments: Optional list of file attachments (name, contentType, size, contentBytes)
Returns:
Created HybridBookingSession object
"""
try:
with get_db_session() as db:
booking_session = HybridBookingSession(
session_id=session_id,
organizer_email=organizer_email,
organizer_name=organizer_name,
external_email=external_email,
external_name=external_name,
proposed_slots=proposed_slots,
meeting_subject=meeting_subject,
meeting_duration=meeting_duration,
booking_url=booking_url,
internal_attendees=internal_attendees or [],
attachments=attachments or [],
status="pending"
)
db.add(booking_session)
db.commit()
db.refresh(booking_session)
logger.info(f"Created booking session: {session_id}")
return booking_session
except Exception as e:
logger.error(f"Failed to create booking session {session_id}: {e}")
raise
@staticmethod
def get_session(session_id: str) -> Optional[HybridBookingSession]:
"""
Get booking session by ID
Args:
session_id: Session identifier
Returns:
HybridBookingSession object or None if not found
"""
try:
with get_db_session() as db:
session = db.query(HybridBookingSession).filter(
HybridBookingSession.session_id == session_id
).first()
if session:
# Detach from session to use outside context
db.expunge(session)
return session
except Exception as e:
logger.error(f"Failed to get booking session {session_id}: {e}")
raise
@staticmethod
def update_session_status(
session_id: str,
status: str,
confirmed_slot: Optional[Dict[str, str]] = None,
event_id: Optional[str] = None
) -> bool:
"""
Update booking session status and confirmation details
Args:
session_id: Session identifier
status: New status (pending, confirmed, expired, cancelled)
confirmed_slot: Confirmed time slot (if applicable)
event_id: Microsoft Graph event ID (if created)
Returns:
True if updated successfully, False otherwise
"""
try:
with get_db_session() as db:
session = db.query(HybridBookingSession).filter(
HybridBookingSession.session_id == session_id
).first()
if not session:
logger.warning(f"Session {session_id} not found for update")
return False
session.status = status
session.updated_at = datetime.utcnow()
if confirmed_slot:
session.confirmed_slot = confirmed_slot
session.confirmed_at = datetime.utcnow()
if event_id:
session.event_id = event_id
db.commit()
logger.info(f"Updated booking session {session_id} to status: {status}")
return True
except Exception as e:
logger.error(f"Failed to update booking session {session_id}: {e}")
raise
@staticmethod
def update_proposed_slots(
session_id: str,
new_slots: List[Dict[str, str]]
) -> bool:
"""
Update proposed slots for a booking session
Used when original slots are no longer available
Args:
session_id: Session identifier
new_slots: New list of proposed time slots
Returns:
True if updated successfully, False otherwise
"""
try:
with get_db_session() as db:
session = db.query(HybridBookingSession).filter(
HybridBookingSession.session_id == session_id
).first()
if not session:
logger.warning(f"Session {session_id} not found for slots update")
return False
session.proposed_slots = new_slots
session.updated_at = datetime.utcnow()
db.commit()
logger.info(f"Updated proposed slots for session {session_id}: {len(new_slots)} new slots")
return True
except Exception as e:
logger.error(f"Failed to update proposed slots for session {session_id}: {e}")
raise
@staticmethod
def delete_session(session_id: str) -> bool:
"""
Delete a booking session
Args:
session_id: Session identifier
Returns:
True if deleted successfully, False otherwise
"""
try:
with get_db_session() as db:
session = db.query(HybridBookingSession).filter(
HybridBookingSession.session_id == session_id
).first()
if not session:
logger.warning(f"Session {session_id} not found for deletion")
return False
db.delete(session)
db.commit()
logger.info(f"Deleted booking session: {session_id}")
return True
except Exception as e:
logger.error(f"Failed to delete booking session {session_id}: {e}")
raise
@staticmethod
def get_sessions_by_organizer(organizer_email: str) -> List[HybridBookingSession]:
"""
Get all booking sessions for an organizer
Args:
organizer_email: Organizer's email address
Returns:
List of HybridBookingSession objects
"""
try:
with get_db_session() as db:
sessions = db.query(HybridBookingSession).filter(
HybridBookingSession.organizer_email == organizer_email
).order_by(HybridBookingSession.created_at.desc()).all()
# Detach from session
for session in sessions:
db.expunge(session)
return sessions
except Exception as e:
logger.error(f"Failed to get sessions for organizer {organizer_email}: {e}")
raise
@staticmethod
def get_pending_sessions() -> List[HybridBookingSession]:
"""
Get all pending booking sessions
Returns:
List of pending HybridBookingSession objects
"""
try:
with get_db_session() as db:
sessions = db.query(HybridBookingSession).filter(
HybridBookingSession.status == "pending"
).order_by(HybridBookingSession.created_at.desc()).all()
# Detach from session
for session in sessions:
db.expunge(session)
return sessions
except Exception as e:
logger.error(f"Failed to get pending sessions: {e}")
raise
@staticmethod
def session_to_dict(session: HybridBookingSession) -> Dict[str, Any]:
"""
Convert HybridBookingSession object to dictionary
Args:
session: HybridBookingSession object
Returns:
Dictionary representation of session
"""
return {
'session_id': session.session_id,
'booking_url': session.booking_url,
'organizer_email': session.organizer_email,
'organizer_name': session.organizer_name,
'external_email': session.external_email,
'external_name': session.external_name,
'meeting_subject': session.meeting_subject,
'meeting_duration': session.meeting_duration,
'internal_attendees': session.internal_attendees,
'proposed_slots': session.proposed_slots,
'status': session.status,
'confirmed_slot': session.confirmed_slot,
'event_id': session.event_id,
'created_at': session.created_at.isoformat() if session.created_at else None,
'updated_at': session.updated_at.isoformat() if session.updated_at else None,
'confirmed_at': session.confirmed_at.isoformat() if session.confirmed_at else None
}