diff --git a/backend/src/backend/db.py b/backend/src/backend/db.py index 8639823c..bab15385 100644 --- a/backend/src/backend/db.py +++ b/backend/src/backend/db.py @@ -1,10 +1,5 @@ -"""DB connection and session management for SQLModel.""" +"""Database configuration and engine setup.""" -from collections.abc import AsyncGenerator -from typing import TypeVar - -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.ext.asyncio import async_sessionmaker from sqlalchemy.ext.asyncio import create_async_engine from backend import config @@ -23,15 +18,3 @@ echo = config("ENVIRONMENT", default="development") in {"development"} # Create the async engine engine = create_async_engine(db_url, echo=echo) - -# Used in the sessionize decorator -T = TypeVar("T") - -# Create an asynchronous session maker for SQLModel, used in the sessionize decorator -async_maker = async_sessionmaker(engine) - - -async def get_session() -> AsyncGenerator[AsyncSession, None]: # pragma: no cover - """Inject a new DB session.""" - async with async_maker() as session: - yield session diff --git a/backend/src/backend/dependencies.py b/backend/src/backend/dependencies.py deleted file mode 100644 index 10e3ec77..00000000 --- a/backend/src/backend/dependencies.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Dependency definitions for FastAPI routes.""" - -from typing import Annotated - -from fastapi import Depends -from sqlalchemy.ext.asyncio import AsyncSession - -from backend.db import get_session - - -type SessionDep = Annotated[AsyncSession, Depends(get_session)] diff --git a/backend/src/backend/dependencies/db.py b/backend/src/backend/dependencies/db.py new file mode 100644 index 00000000..023a525d --- /dev/null +++ b/backend/src/backend/dependencies/db.py @@ -0,0 +1,23 @@ +"""Dependency definitions for FastAPI routes.""" + +from typing import Annotated +from typing import AsyncGenerator + +from fastapi import Depends +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.ext.asyncio import async_sessionmaker + +from backend import db + + +# Create an asynchronous session maker for SQLModel, used in the sessionize decorator +async_maker = async_sessionmaker(db.engine) + + +async def get_session() -> AsyncGenerator[AsyncSession, None]: # pragma: no cover + """Inject a new DB session.""" + async with async_maker() as session: + yield session + + +type SessionDep = Annotated[AsyncSession, Depends(get_session)] diff --git a/backend/src/backend/services/__init__.py b/backend/src/backend/services/__init__.py new file mode 100644 index 00000000..b20290b2 --- /dev/null +++ b/backend/src/backend/services/__init__.py @@ -0,0 +1 @@ +"""Services package for the Numinar coding project backend.""" diff --git a/backend/src/backend/services/bookings.py b/backend/src/backend/services/bookings.py new file mode 100644 index 00000000..9902b1ab --- /dev/null +++ b/backend/src/backend/services/bookings.py @@ -0,0 +1,130 @@ +"""SQL Database services for managing bookings.""" + +import logging +from datetime import datetime +from typing import TypedDict +from typing import Unpack +from typing import cast + +from sqlalchemy import delete +from sqlalchemy import select +from sqlalchemy import update +from sqlalchemy.exc import NoResultFound +from sqlalchemy.ext.asyncio import AsyncSession + +from backend.models import Booking +from backend.models import Room +from backend.models import User + + +logger = logging.getLogger(__name__) + +type UserList = list[User] +type RoomList = list[Room] +type BookingList = list[Booking] + + +async def get_bookings_for_room(session: AsyncSession, room_id: int) -> BookingList: + """Retrieve all bookings for a specific room.""" + logger.debug(f"Entering get_bookings_for_room with room_id: {room_id}") + try: + stmt = select(Booking).where(Booking.room_id == room_id) + result = await session.scalars(stmt) + bookings = cast(BookingList, result.all()) + logger.info( + f"Successfully retrieved {len(bookings)} bookings for room_id: {room_id}" + ) + return bookings + except Exception as e: + logger.error(f"Failed to retrieve bookings for room_id {room_id}: {str(e)}") + raise + finally: + logger.debug("Exiting get_bookings_for_room") + + +async def get_booking(session: AsyncSession, booking_id: int) -> Booking: + """Retrieve a booking by its ID.""" + logger.debug(f"Entering get_booking with booking_id: {booking_id}") + try: + stmt = select(Booking).where(Booking.id == booking_id) + booking = await session.scalar(stmt) + logger.info(f"Successfully retrieved booking with id: {booking_id}") + return cast(Booking, booking) + except NoResultFound: + logger.error(f"Booking with id {booking_id} not found") + raise + except Exception as e: + logger.error(f"Failed to retrieve booking with id {booking_id}: {str(e)}") + raise + finally: + logger.debug("Exiting get_booking") + + +async def new_booking(session: AsyncSession, booking: Booking) -> Booking: + """Create a new booking in the database.""" + logger.debug("Entering new_booking") + try: + session.add(booking) + await session.commit() + logger.info(f"Successfully created new booking with id: {booking.id}") + return booking + except Exception as e: + logger.error(f"Failed to create new booking: {str(e)}") + await session.rollback() + raise + finally: + logger.debug("Exiting new_booking") + + +class BookingParams(TypedDict): + """Parameters for updating a booking.""" + + room_id: int + start_time: datetime + end_time: datetime + + +async def update_booking( + session: AsyncSession, + booking_id: int, + **kwargs: Unpack[BookingParams], +) -> Booking: + """Update an existing booking.""" + logger.debug( + f"Entering update_booking with booking_id: {booking_id}, params: {kwargs}" + ) + try: + stmt = update(Booking).where(Booking.id == booking_id).values(**kwargs) + result = await session.execute(stmt) + if result.rowcount == 0: + logger.error(f"No booking found with id {booking_id} for update") + raise ValueError(f"No booking found with id {booking_id}") + await session.commit() + booking = await get_booking(session, booking_id) + logger.info(f"Successfully updated booking with id: {booking_id}") + return booking + except Exception as e: + logger.error(f"Failed to update booking with id {booking_id}: {str(e)}") + await session.rollback() + raise + finally: + logger.debug("Exiting update_booking") + + +async def delete_booking(session: AsyncSession, booking_id: int) -> None: + """Delete a booking from the database.""" + logger.debug(f"Entering delete_booking with booking_id: {booking_id}") + try: + stmt = delete(Booking).where(Booking.id == booking_id) + result = await session.execute(stmt) + if result.rowcount == 0: + logger.error(f"No booking found with id {booking_id} for deletion") + raise ValueError(f"No booking found with id {booking_id}") + await session.commit() + logger.info(f"Successfully deleted booking with id: {booking_id}") + except Exception as e: + logger.error(f"Failed to delete booking with id {booking_id}: {str(e)}") + await session.rollback() + raise + finally: + logger.debug("Exiting delete_booking") diff --git a/backend/src/backend/services/invitees.py b/backend/src/backend/services/invitees.py new file mode 100644 index 00000000..be1a5b66 --- /dev/null +++ b/backend/src/backend/services/invitees.py @@ -0,0 +1,113 @@ +"""SQL Database services for managing bookings.""" + +import logging +from typing import cast + +from sqlalchemy import delete +from sqlalchemy import select +from sqlalchemy.exc import NoResultFound +from sqlalchemy.ext.asyncio import AsyncSession + +from backend.models import Booking +from backend.models import Invitee +from backend.models import Room +from backend.models import User +from backend.services.users import get_user_by_email + + +logger = logging.getLogger(__name__) + +type UserList = list[User] +type RoomList = list[Room] +type BookingList = list[Booking] + + +async def get_invitees_for_booking(session: AsyncSession, booking_id: int) -> UserList: + """Retrieve all invitees for a specific booking.""" + logger.debug(f"Entering get_invitees_for_booking with booking_id: {booking_id}") + try: + stmt = select(Invitee.user).where(Invitee.booking_id == booking_id) + result = await session.scalars(stmt) + invitees = cast(UserList, result.all()) + logger.info( + f"Successfully retrieved {len(invitees)} invitees for booking_id: {booking_id}" + ) + return invitees + except Exception as e: + logger.error( + f"Failed to retrieve invitees for booking_id {booking_id}: {str(e)}" + ) + raise + finally: + logger.debug("Exiting get_invitees_for_booking") + + +async def add_invitee_to_booking( + session: AsyncSession, booking_id: int, email: str +) -> Invitee: + """Add an invitee to a booking.""" + logger.debug( + f"Entering add_invitee_to_booking with booking_id: {booking_id}, email: {email}" + ) + try: + user = await get_user_by_email(session, email) + invitee = Invitee(booking_id=booking_id, user_id=user.id) + session.add(invitee) + await session.commit() + logger.info( + f"Successfully added invitee with email {email} to booking_id: {booking_id}" + ) + return invitee + except NoResultFound as e: + logger.error( + f"User with email {email} does not exist for booking_id {booking_id}" + ) + raise ValueError(f"User with email {email} does not exist.") from e + except Exception as e: + logger.error( + f"Failed to add invitee with email {email} to booking_id {booking_id}: {str(e)}" + ) + await session.rollback() + raise + finally: + logger.debug("Exiting add_invitee_to_booking") + + +async def remove_invitee_from_booking( + session: AsyncSession, booking_id: int, email: str +) -> None: + """Remove an invitee from a booking.""" + logger.debug( + f"Entering remove_invitee_from_booking with booking_id: {booking_id}, email: {email}" + ) + try: + user = await get_user_by_email(session, email) + stmt = delete(Invitee).where( + Invitee.booking_id == booking_id, Invitee.user_id == user.id + ) + result = await session.execute(stmt) + if result.rowcount == 0: + logger.error( + f"No invitee with email {email} found for booking_id {booking_id}" + ) + raise ValueError( + f"No invitee with email {email} found for booking_id {booking_id}" + ) + await session.commit() + logger.info( + f"Successfully removed invitee with email {email} from booking_id: {booking_id}" + ) + except NoResultFound as e: + logger.error( + f"User with email {email} does not exist for booking_id {booking_id}" + ) + raise ValueError(f"User with email {email} does not exist.") from e + except Exception as e: + logger.error( + f"Failed to remove invitee with email {email}" + f" from booking_id {booking_id}: {str(e)}" + ) + await session.rollback() + raise + finally: + logger.debug("Exiting remove_invitee_from_booking") diff --git a/backend/src/backend/services/rooms.py b/backend/src/backend/services/rooms.py new file mode 100644 index 00000000..09c94bc2 --- /dev/null +++ b/backend/src/backend/services/rooms.py @@ -0,0 +1,124 @@ +"""SQL Database services for managing rooms.""" + +import logging +from typing import TypedDict +from typing import Unpack +from typing import cast + +from sqlalchemy import delete +from sqlalchemy import select +from sqlalchemy import update +from sqlalchemy.exc import NoResultFound +from sqlalchemy.ext.asyncio import AsyncSession + +from backend.models import Booking +from backend.models import Room +from backend.models import User + + +logger = logging.getLogger(__name__) + +type UserList = list[User] +type RoomList = list[Room] +type BookingList = list[Booking] + + +async def get_rooms(session: AsyncSession) -> RoomList: + """Retrieve all rooms from the database.""" + logger.debug("Entering get_rooms") + try: + stmt = select(Room) + result = await session.scalars(stmt) + rooms = cast(RoomList, result.all()) + logger.info(f"Successfully retrieved {len(rooms)} rooms") + return rooms + except Exception as e: + logger.error(f"Failed to retrieve rooms: {str(e)}") + raise + finally: + logger.debug("Exiting get_rooms") + + +async def get_room(session: AsyncSession, room_id: int) -> Room: + """Retrieve a room by its ID.""" + logger.debug(f"Entering get_room with room_id: {room_id}") + try: + stmt = select(Room).where(Room.id == room_id) + room = await session.scalar(stmt) + logger.info(f"Successfully retrieved room with id: {room_id}") + return cast(Room, room) + except NoResultFound: + logger.error(f"Room with id {room_id} not found") + raise + except Exception as e: + logger.error(f"Failed to retrieve room with id {room_id}: {str(e)}") + raise + finally: + logger.debug("Exiting get_room") + + +async def new_room(session: AsyncSession, room: Room) -> Room: + """Create a new room in the database.""" + logger.debug("Entering new_room") + try: + session.add(room) + await session.commit() + logger.info(f"Successfully created new room with id: {room.id}") + return room + except Exception as e: + logger.error(f"Failed to create new room: {str(e)}") + await session.rollback() + raise + finally: + logger.debug("Exiting new_room") + + +class RoomParams(TypedDict): + """Parameters for updating a room.""" + + name: str + location: str + equipment: str + capacity: int + + +async def update_room( + session: AsyncSession, room_id: int, **kwargs: Unpack[RoomParams] +) -> Room: + """Update an existing room.""" + logger.debug(f"Entering update_room with room_id: {room_id}, params: {kwargs}") + try: + stmt = update(Room).where(Room.id == room_id).values(**kwargs) + result = await session.execute(stmt) + if result.rowcount == 0: + logger.error(f"No room found with id {room_id} for update") + raise ValueError(f"No room found with id {room_id}") + await session.commit() + room = await get_room(session, room_id) + logger.info(f"Successfully updated room with id: {room_id}") + return room + except Exception as e: + logger.error(f"Failed to update room with id {room_id}: {str(e)}") + await session.rollback() + raise + finally: + logger.debug("Exiting update_room") + + +async def delete_room(session: AsyncSession, room_id: int) -> None: + """Delete a room from the database.""" + logger.debug(f"Entering delete_room with room_id: {room_id}") + try: + stmt = delete(Room).where(Room.id == room_id) + result = await session.execute(stmt) + if result.rowcount == 0: + logger.error(f"No room found with id {room_id} for deletion") + raise ValueError(f"No room found with id {room_id}") + await session.commit() + logger.info(f"Successfully deleted room with id: {room_id}") + except Exception as e: + logger.error(f"Failed to delete room with id {room_id}: {str(e)}") + await session.rollback() + raise + finally: + logger.debug("Exiting delete_room") diff --git a/backend/src/backend/services/users.py b/backend/src/backend/services/users.py new file mode 100644 index 00000000..68174005 --- /dev/null +++ b/backend/src/backend/services/users.py @@ -0,0 +1,53 @@ +"""SQL Database for managing users.""" + +import logging +from typing import cast + +from sqlalchemy import select +from sqlalchemy.exc import NoResultFound +from sqlalchemy.ext.asyncio import AsyncSession + +from backend.models import Booking +from backend.models import Room +from backend.models import User + + +logger = logging.getLogger(__name__) + +type UserList = list[User] +type RoomList = list[Room] +type BookingList = list[Booking] + + +async def get_users(session: AsyncSession) -> UserList: + """Retrieve all users from the database.""" + logger.debug("Entering get_users") + try: + stmt = select(User) + result = await session.scalars(stmt) + users = cast(UserList, result.all()) + logger.info(f"Successfully retrieved {len(users)} users") + return users + except Exception as e: + logger.error(f"Failed to retrieve users: {str(e)}") + raise + finally: + logger.debug("Exiting get_users") + + +async def get_user_by_email(session: AsyncSession, email: str) -> User: + """Retrieve a user by their email address.""" + logger.debug(f"Entering get_user_by_email with email: {email}") + try: + stmt = select(User).where(User.email == email) + user = await session.scalar(stmt) + logger.info(f"Successfully retrieved user with email: {email}") + return cast(User, user) + except NoResultFound: + logger.error(f"User with email {email} not found") + raise + except Exception as e: + logger.error(f"Failed to retrieve user with email {email}: {str(e)}") + raise + finally: + logger.debug("Exiting get_user_by_email") diff --git a/backend/src/backend/sql.py b/backend/src/backend/sql.py deleted file mode 100644 index 6e3f2e7a..00000000 --- a/backend/src/backend/sql.py +++ /dev/null @@ -1,357 +0,0 @@ -"""SQL Database access functions for the Numinar coding project backend.""" - -import logging -from datetime import datetime -from typing import TypedDict -from typing import Unpack -from typing import cast - -from sqlalchemy import delete -from sqlalchemy import select -from sqlalchemy import update -from sqlalchemy.exc import NoResultFound -from sqlalchemy.ext.asyncio import AsyncSession - -from backend.models import Booking -from backend.models import Invitee -from backend.models import Room -from backend.models import User - - -logger = logging.getLogger(__name__) - -type UserList = list[User] -type RoomList = list[Room] -type BookingList = list[Booking] - - -async def get_users(session: AsyncSession) -> UserList: - """Retrieve all users from the database.""" - logger.debug("Entering get_users") - try: - stmt = select(User) - result = await session.scalars(stmt) - users = cast(UserList, result.all()) - logger.info(f"Successfully retrieved {len(users)} users") - return users - except Exception as e: - logger.error(f"Failed to retrieve users: {str(e)}") - raise - finally: - logger.debug("Exiting get_users") - - -async def get_user_by_email(session: AsyncSession, email: str) -> User: - """Retrieve a user by their email address.""" - logger.debug(f"Entering get_user_by_email with email: {email}") - try: - stmt = select(User).where(User.email == email) - user = await session.scalar(stmt) - logger.info(f"Successfully retrieved user with email: {email}") - return cast(User, user) - except NoResultFound: - logger.error(f"User with email {email} not found") - raise - except Exception as e: - logger.error(f"Failed to retrieve user with email {email}: {str(e)}") - raise - finally: - logger.debug("Exiting get_user_by_email") - - -async def get_rooms(session: AsyncSession) -> RoomList: - """Retrieve all rooms from the database.""" - logger.debug("Entering get_rooms") - try: - stmt = select(Room) - result = await session.scalars(stmt) - rooms = cast(RoomList, result.all()) - logger.info(f"Successfully retrieved {len(rooms)} rooms") - return rooms - except Exception as e: - logger.error(f"Failed to retrieve rooms: {str(e)}") - raise - finally: - logger.debug("Exiting get_rooms") - - -async def get_room(session: AsyncSession, room_id: int) -> Room: - """Retrieve a room by its ID.""" - logger.debug(f"Entering get_room with room_id: {room_id}") - try: - stmt = select(Room).where(Room.id == room_id) - room = await session.scalar(stmt) - logger.info(f"Successfully retrieved room with id: {room_id}") - return cast(Room, room) - except NoResultFound: - logger.error(f"Room with id {room_id} not found") - raise - except Exception as e: - logger.error(f"Failed to retrieve room with id {room_id}: {str(e)}") - raise - finally: - logger.debug("Exiting get_room") - - -async def new_room(session: AsyncSession, room: Room) -> Room: - """Create a new room in the database.""" - logger.debug("Entering new_room") - try: - session.add(room) - await session.commit() - logger.info(f"Successfully created new room with id: {room.id}") - return room - except Exception as e: - logger.error(f"Failed to create new room: {str(e)}") - await session.rollback() - raise - finally: - logger.debug("Exiting new_room") - - -class RoomParams(TypedDict): - """Parameters for updating a room.""" - - name: str - location: str - equipment: str - capacity: int - - -async def update_room( - session: AsyncSession, room_id: int, **kwargs: Unpack[RoomParams] -) -> Room: - """Update an existing room.""" - logger.debug(f"Entering update_room with room_id: {room_id}, params: {kwargs}") - try: - stmt = update(Room).where(Room.id == room_id).values(**kwargs) - result = await session.execute(stmt) - if result.rowcount == 0: - logger.error(f"No room found with id {room_id} for update") - raise ValueError(f"No room found with id {room_id}") - await session.commit() - room = await get_room(session, room_id) - logger.info(f"Successfully updated room with id: {room_id}") - return room - except Exception as e: - logger.error(f"Failed to update room with id {room_id}: {str(e)}") - await session.rollback() - raise - finally: - logger.debug("Exiting update_room") - - -async def delete_room(session: AsyncSession, room_id: int) -> None: - """Delete a room from the database.""" - logger.debug(f"Entering delete_room with room_id: {room_id}") - try: - stmt = delete(Room).where(Room.id == room_id) - result = await session.execute(stmt) - if result.rowcount == 0: - logger.error(f"No room found with id {room_id} for deletion") - raise ValueError(f"No room found with id {room_id}") - await session.commit() - logger.info(f"Successfully deleted room with id: {room_id}") - except Exception as e: - logger.error(f"Failed to delete room with id {room_id}: {str(e)}") - await session.rollback() - raise - finally: - logger.debug("Exiting delete_room") - - -async def get_bookings_for_room(session: AsyncSession, room_id: int) -> BookingList: - """Retrieve all bookings for a specific room.""" - logger.debug(f"Entering get_bookings_for_room with room_id: {room_id}") - try: - stmt = select(Booking).where(Booking.room_id == room_id) - result = await session.scalars(stmt) - bookings = cast(BookingList, result.all()) - logger.info( - f"Successfully retrieved {len(bookings)} bookings for room_id: {room_id}" - ) - return bookings - except Exception as e: - logger.error(f"Failed to retrieve bookings for room_id {room_id}: {str(e)}") - raise - finally: - logger.debug("Exiting get_bookings_for_room") - - -async def get_booking(session: AsyncSession, booking_id: int) -> Booking: - """Retrieve a booking by its ID.""" - logger.debug(f"Entering get_booking with booking_id: {booking_id}") - try: - stmt = select(Booking).where(Booking.id == booking_id) - booking = await session.scalar(stmt) - logger.info(f"Successfully retrieved booking with id: {booking_id}") - return cast(Booking, booking) - except NoResultFound: - logger.error(f"Booking with id {booking_id} not found") - raise - except Exception as e: - logger.error(f"Failed to retrieve booking with id {booking_id}: {str(e)}") - raise - finally: - logger.debug("Exiting get_booking") - - -async def new_booking(session: AsyncSession, booking: Booking) -> Booking: - """Create a new booking in the database.""" - logger.debug("Entering new_booking") - try: - session.add(booking) - await session.commit() - logger.info(f"Successfully created new booking with id: {booking.id}") - return booking - except Exception as e: - logger.error(f"Failed to create new booking: {str(e)}") - await session.rollback() - raise - finally: - logger.debug("Exiting new_booking") - - -class BookingParams(TypedDict): - """Parameters for updating a booking.""" - - room_id: int - start_time: datetime - end_time: datetime - - -async def update_booking( - session: AsyncSession, - booking_id: int, - **kwargs: Unpack[BookingParams], -) -> Booking: - """Update an existing booking.""" - logger.debug( - f"Entering update_booking with booking_id: {booking_id}, params: {kwargs}" - ) - try: - stmt = update(Booking).where(Booking.id == booking_id).values(**kwargs) - result = await session.execute(stmt) - if result.rowcount == 0: - logger.error(f"No booking found with id {booking_id} for update") - raise ValueError(f"No booking found with id {booking_id}") - await session.commit() - booking = await get_booking(session, booking_id) - logger.info(f"Successfully updated booking with id: {booking_id}") - return booking - except Exception as e: - logger.error(f"Failed to update booking with id {booking_id}: {str(e)}") - await session.rollback() - raise - finally: - logger.debug("Exiting update_booking") - - -async def delete_booking(session: AsyncSession, booking_id: int) -> None: - """Delete a booking from the database.""" - logger.debug(f"Entering delete_booking with booking_id: {booking_id}") - try: - stmt = delete(Booking).where(Booking.id == booking_id) - result = await session.execute(stmt) - if result.rowcount == 0: - logger.error(f"No booking found with id {booking_id} for deletion") - raise ValueError(f"No booking found with id {booking_id}") - await session.commit() - logger.info(f"Successfully deleted booking with id: {booking_id}") - except Exception as e: - logger.error(f"Failed to delete booking with id {booking_id}: {str(e)}") - await session.rollback() - raise - finally: - logger.debug("Exiting delete_booking") - - -async def get_invitees_for_booking(session: AsyncSession, booking_id: int) -> UserList: - """Retrieve all invitees for a specific booking.""" - logger.debug(f"Entering get_invitees_for_booking with booking_id: {booking_id}") - try: - stmt = select(Invitee.user).where(Invitee.booking_id == booking_id) - result = await session.scalars(stmt) - invitees = cast(UserList, result.all()) - logger.info( - f"Successfully retrieved {len(invitees)} invitees for booking_id: {booking_id}" - ) - return invitees - except Exception as e: - logger.error( - f"Failed to retrieve invitees for booking_id {booking_id}: {str(e)}" - ) - raise - finally: - logger.debug("Exiting get_invitees_for_booking") - - -async def add_invitee_to_booking( - session: AsyncSession, booking_id: int, email: str -) -> Invitee: - """Add an invitee to a booking.""" - logger.debug( - f"Entering add_invitee_to_booking with booking_id: {booking_id}, email: {email}" - ) - try: - user = await get_user_by_email(session, email) - invitee = Invitee(booking_id=booking_id, user_id=user.id) - session.add(invitee) - await session.commit() - logger.info( - f"Successfully added invitee with email {email} to booking_id: {booking_id}" - ) - return invitee - except NoResultFound as e: - logger.error( - f"User with email {email} does not exist for booking_id {booking_id}" - ) - raise ValueError(f"User with email {email} does not exist.") from e - except Exception as e: - logger.error( - f"Failed to add invitee with email {email} to booking_id {booking_id}: {str(e)}" - ) - await session.rollback() - raise - finally: - logger.debug("Exiting add_invitee_to_booking") - - -async def remove_invitee_from_booking( - session: AsyncSession, booking_id: int, email: str -) -> None: - """Remove an invitee from a booking.""" - logger.debug( - f"Entering remove_invitee_from_booking with booking_id: {booking_id}, email: {email}" - ) - try: - user = await get_user_by_email(session, email) - stmt = delete(Invitee).where( - Invitee.booking_id == booking_id, Invitee.user_id == user.id - ) - result = await session.execute(stmt) - if result.rowcount == 0: - logger.error( - f"No invitee with email {email} found for booking_id {booking_id}" - ) - raise ValueError( - f"No invitee with email {email} found for booking_id {booking_id}" - ) - await session.commit() - logger.info( - f"Successfully removed invitee with email {email} from booking_id: {booking_id}" - ) - except NoResultFound as e: - logger.error( - f"User with email {email} does not exist for booking_id {booking_id}" - ) - raise ValueError(f"User with email {email} does not exist.") from e - except Exception as e: - logger.error( - f"Failed to remove invitee with email {email}" - f" from booking_id {booking_id}: {str(e)}" - ) - await session.rollback() - raise - finally: - logger.debug("Exiting remove_invitee_from_booking")