Public Access
mirror of
https://github.com/xlorepdarkhelm/numinar-coding-project.git
synced 2026-09-10 18:22:42 -04:00
Doing cleanup of backend.
Signed-off-by: Cliff Hill <xlorep@darkhelm.org>
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
[project]
|
||||
id = numinar-coding-project-backend
|
||||
url = /codebases/numinar-coding-project-backend/findings
|
||||
name = numinar-coding-project-backend
|
||||
@@ -118,7 +118,7 @@ class Booking(Base):
|
||||
return self._invitee_emails
|
||||
|
||||
@invitees.setter
|
||||
def invitees(self, emails: list[str]):
|
||||
def invitees(self, emails: list[str]) -> None:
|
||||
"""Set the list of invitee email addresses for this booking.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Routes for booking-related operations in the backend."""
|
||||
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from fastapi import APIRouter
|
||||
from fastapi import HTTPException
|
||||
@@ -123,7 +122,7 @@ async def create_booking(
|
||||
booking_response = await new_booking_func(
|
||||
session, booking, publish_room_availability_event
|
||||
)
|
||||
return booking_response
|
||||
return BookingResponse.model_validate(booking_response)
|
||||
except ValueError as err:
|
||||
logger.warning(f"Validation error while creating booking: {str(err)}")
|
||||
# Detect overlap/conflict error
|
||||
@@ -286,7 +285,7 @@ async def read_bookings_for_month(
|
||||
session: DBSession,
|
||||
get_bookings_for_month_func: GetBookingsForMonthService,
|
||||
get_room_func: GetRoomService,
|
||||
) -> List[BookingResponse]:
|
||||
) -> list[BookingResponse]:
|
||||
"""Retrieve all bookings for all rooms for a given month.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -182,7 +182,7 @@ async def read_rooms(
|
||||
try:
|
||||
rooms = await get_rooms_func(session)
|
||||
logger.info(f"Successfully retrieved {len(rooms)} rooms")
|
||||
return rooms
|
||||
return list(rooms)
|
||||
except NoResultFound as err:
|
||||
logger.warning(f"Room not found: {str(err)}")
|
||||
raise HTTPException(status_code=404, detail="Room not found") from err
|
||||
|
||||
@@ -45,7 +45,7 @@ async def read_users(
|
||||
try:
|
||||
users = await get_users_func(session)
|
||||
logger.info(f"Successfully retrieved {len(users)} users")
|
||||
return users
|
||||
return list(users)
|
||||
except NoResultFound as err:
|
||||
logger.warning(f"User not found: {str(err)}")
|
||||
raise HTTPException(status_code=404, detail="User not found") from err
|
||||
|
||||
@@ -9,7 +9,6 @@ from typing import Awaitable
|
||||
from typing import Callable
|
||||
from typing import TypedDict
|
||||
from typing import Unpack
|
||||
from typing import cast
|
||||
|
||||
import asyncpg
|
||||
from sqlalchemy import and_
|
||||
@@ -59,7 +58,9 @@ async def get_booking(session: AsyncSession, booking_id: int) -> Booking:
|
||||
stmt = (
|
||||
select(Booking)
|
||||
.where(Booking.id == booking_id)
|
||||
.options(selectinload(Booking._invitees)) # type: ignore
|
||||
.options(
|
||||
selectinload(Booking._invitees) # pyright: ignore[reportPrivateUsage]
|
||||
)
|
||||
)
|
||||
booking = await session.scalar(stmt)
|
||||
if booking is None:
|
||||
@@ -281,7 +282,7 @@ async def get_bookings_for_month(session: AsyncSession, month: str) -> list[Book
|
||||
result = await session.execute(
|
||||
select(Booking)
|
||||
.where(and_(Booking.start_time >= start_date, Booking.start_time < end_date))
|
||||
.options(selectinload(Booking._invitees)) # type: ignore
|
||||
.options(selectinload(Booking._invitees)) # pyright: ignore[reportPrivateUsage]
|
||||
)
|
||||
bookings = list(result.scalars().all())
|
||||
booking_ids = [b.id for b in bookings]
|
||||
@@ -396,6 +397,40 @@ async def validate_no_overlap(
|
||||
)
|
||||
|
||||
|
||||
def _normalize_invitees(invitees_raw: object) -> list[str]:
|
||||
if isinstance(invitees_raw, list):
|
||||
return [str(e) for e in invitees_raw] # pyright: ignore
|
||||
elif hasattr(invitees_raw, "__iter__") and not isinstance(invitees_raw, str):
|
||||
return [str(e) for e in invitees_raw] # pyright: ignore
|
||||
return []
|
||||
|
||||
|
||||
async def _remove_old_invitees(
|
||||
session: AsyncSession, current: Booking, new_invitees: list[str]
|
||||
) -> None:
|
||||
current_invitees = {
|
||||
invitee.user_email: invitee for invitee in current.get_invitee_objects()
|
||||
}
|
||||
new_invitees_set = set(new_invitees)
|
||||
for email in list(current_invitees.keys()):
|
||||
if email not in new_invitees_set:
|
||||
invitee_obj = current_invitees[email]
|
||||
await session.delete(invitee_obj)
|
||||
await session.flush()
|
||||
|
||||
|
||||
async def _update_booking_record(
|
||||
session: AsyncSession, booking_id: int, kwargs: dict[str, object]
|
||||
) -> None:
|
||||
stmt = (
|
||||
update(Booking)
|
||||
.where(Booking.id == booking_id)
|
||||
.values(**{k: v for k, v in kwargs.items() if k != "invitees"})
|
||||
)
|
||||
await session.execute(stmt)
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def update_booking(
|
||||
session: AsyncSession,
|
||||
booking_id: int,
|
||||
@@ -414,49 +449,43 @@ async def update_booking(
|
||||
The updated booking object.
|
||||
|
||||
Raises:
|
||||
NoResultFound: If no booking with the specified ID is found.
|
||||
asyncpg.InterfaceError: If a database connection error occurs.
|
||||
TypeError: If room_id is not int or str.
|
||||
"""
|
||||
logger.debug(
|
||||
f"Entering update_booking with booking_id: {booking_id}, params: {kwargs}"
|
||||
)
|
||||
try:
|
||||
current = await get_booking(session, booking_id)
|
||||
new_room_id = cast(int, kwargs.get("room_id", current.room_id))
|
||||
new_start = cast(datetime, kwargs.get("start_time", current.start_time))
|
||||
new_end = cast(datetime, kwargs.get("end_time", current.end_time))
|
||||
new_invitees = kwargs.get("invitees", current.invitees) or []
|
||||
# Use model property for desired state
|
||||
room_id_val = kwargs.get("room_id", current.room_id)
|
||||
if isinstance(room_id_val, int):
|
||||
new_room_id = room_id_val
|
||||
elif isinstance(room_id_val, str):
|
||||
new_room_id = int(room_id_val)
|
||||
else:
|
||||
raise TypeError(f"room_id must be int or str, got {type(room_id_val)}")
|
||||
new_start = kwargs.get("start_time", current.start_time)
|
||||
if not isinstance(new_start, datetime):
|
||||
new_start = datetime.fromisoformat(str(new_start))
|
||||
new_end = kwargs.get("end_time", current.end_time)
|
||||
if not isinstance(new_end, datetime):
|
||||
new_end = datetime.fromisoformat(str(new_end))
|
||||
new_invitees_raw = kwargs.get("invitees", current.invitees) or []
|
||||
new_invitees = _normalize_invitees(new_invitees_raw)
|
||||
current.invitees = new_invitees
|
||||
# Remove invitees not in new list
|
||||
current_invitees = {
|
||||
invitee.user_email: invitee for invitee in current.get_invitee_objects()
|
||||
}
|
||||
new_invitees_set = set(new_invitees)
|
||||
for email in list(current_invitees.keys()):
|
||||
if email not in new_invitees_set:
|
||||
invitee_obj = current_invitees[email]
|
||||
await session.delete(invitee_obj)
|
||||
await session.flush()
|
||||
# Update booking itself
|
||||
await _remove_old_invitees(session, current, new_invitees)
|
||||
await validate_room_exists(session, new_room_id, get_room)
|
||||
now = datetime.now(timezone.utc)
|
||||
max_months = config("BOOKING_MAX_MONTHS", default=12, cast=int)
|
||||
validate_time_constraints(new_start, new_end, now, max_months)
|
||||
await validate_no_overlap(session, booking_id, new_room_id, new_start, new_end)
|
||||
stmt = (
|
||||
update(Booking)
|
||||
.where(Booking.id == booking_id)
|
||||
.values(**{k: v for k, v in kwargs.items() if k != "invitees"})
|
||||
)
|
||||
await session.execute(stmt)
|
||||
await session.commit()
|
||||
await _update_booking_record(session, booking_id, dict(kwargs))
|
||||
# Add invitees new to list
|
||||
updated = await get_booking(session, booking_id)
|
||||
updated_emails = {
|
||||
invitee.user_email for invitee in updated.get_invitee_objects()
|
||||
}
|
||||
for email in new_invitees_set - updated_emails:
|
||||
for email in set(new_invitees) - updated_emails:
|
||||
new_invitee = Invitee(booking_id=booking_id, user_email=email)
|
||||
session.add(new_invitee)
|
||||
await session.commit()
|
||||
|
||||
@@ -405,12 +405,12 @@ def updated_booking(
|
||||
"""
|
||||
updated = Booking(
|
||||
room_id=2, # Match the update data in the test
|
||||
title=booking_update_data["title"], # type: ignore
|
||||
title=booking_update_data.get("title", sample_booking.title),
|
||||
start_time=datetime.fromisoformat(
|
||||
booking_update_data["start_time"] # type: ignore
|
||||
booking_update_data.get("start_time", sample_booking.start_time.isoformat())
|
||||
),
|
||||
end_time=datetime.fromisoformat(
|
||||
booking_update_data["end_time"] # type: ignore
|
||||
booking_update_data.get("end_time", sample_booking.end_time.isoformat())
|
||||
),
|
||||
invitees=sample_booking.invitees,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user