mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2026-02-06 10:46:46 +00:00
feat: batch room meeting status queries into single bulk endpoint
Reduces rooms list page from 2N+2 HTTP requests to 1 POST request. Backend: POST /v1/rooms/meetings/bulk-status with 3 DB queries total. Frontend: @yornaath/batshit DataLoader-style batcher with 10ms window.
This commit is contained in:
@@ -104,6 +104,26 @@ class CalendarEventController:
|
||||
results = await get_database().fetch_all(query)
|
||||
return [CalendarEvent(**result) for result in results]
|
||||
|
||||
async def get_upcoming_for_rooms(
|
||||
self, room_ids: list[str], minutes_ahead: int = 120
|
||||
) -> list[CalendarEvent]:
|
||||
now = datetime.now(timezone.utc)
|
||||
future_time = now + timedelta(minutes=minutes_ahead)
|
||||
query = (
|
||||
calendar_events.select()
|
||||
.where(
|
||||
sa.and_(
|
||||
calendar_events.c.room_id.in_(room_ids),
|
||||
calendar_events.c.is_deleted == False,
|
||||
calendar_events.c.start_time <= future_time,
|
||||
calendar_events.c.end_time >= now,
|
||||
)
|
||||
)
|
||||
.order_by(calendar_events.c.start_time.asc())
|
||||
)
|
||||
results = await get_database().fetch_all(query)
|
||||
return [CalendarEvent(**result) for result in results]
|
||||
|
||||
async def get_by_id(self, event_id: str) -> CalendarEvent | None:
|
||||
query = calendar_events.select().where(calendar_events.c.id == event_id)
|
||||
result = await get_database().fetch_one(query)
|
||||
|
||||
@@ -301,6 +301,23 @@ class MeetingController:
|
||||
results = await get_database().fetch_all(query)
|
||||
return [Meeting(**result) for result in results]
|
||||
|
||||
async def get_all_active_for_rooms(
|
||||
self, room_ids: list[str], current_time: datetime
|
||||
) -> list[Meeting]:
|
||||
query = (
|
||||
meetings.select()
|
||||
.where(
|
||||
sa.and_(
|
||||
meetings.c.room_id.in_(room_ids),
|
||||
meetings.c.end_date > current_time,
|
||||
meetings.c.is_active,
|
||||
)
|
||||
)
|
||||
.order_by(meetings.c.end_date.desc())
|
||||
)
|
||||
results = await get_database().fetch_all(query)
|
||||
return [Meeting(**result) for result in results]
|
||||
|
||||
async def get_active_by_calendar_event(
|
||||
self, room: Room, calendar_event_id: str, current_time: datetime
|
||||
) -> Meeting | None:
|
||||
|
||||
@@ -245,6 +245,11 @@ class RoomController:
|
||||
|
||||
return room
|
||||
|
||||
async def get_by_names(self, names: list[str]) -> list[Room]:
|
||||
query = rooms.select().where(rooms.c.name.in_(names))
|
||||
results = await get_database().fetch_all(query)
|
||||
return [Room(**r) for r in results]
|
||||
|
||||
async def get_ics_enabled(self) -> list[Room]:
|
||||
query = rooms.select().where(
|
||||
rooms.c.ics_enabled == True, rooms.c.ics_url != None
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from enum import Enum
|
||||
from typing import Annotated, Any, Literal, Optional
|
||||
@@ -195,6 +197,63 @@ async def rooms_list(
|
||||
return paginated
|
||||
|
||||
|
||||
class BulkStatusRequest(BaseModel):
|
||||
room_names: list[str]
|
||||
|
||||
|
||||
class RoomMeetingStatus(BaseModel):
|
||||
active_meetings: list[Meeting]
|
||||
upcoming_events: list[CalendarEventResponse]
|
||||
|
||||
|
||||
@router.post("/rooms/meetings/bulk-status", response_model=dict[str, RoomMeetingStatus])
|
||||
async def rooms_bulk_meeting_status(
|
||||
request: BulkStatusRequest,
|
||||
user: Annotated[Optional[auth.UserInfo], Depends(auth.current_user_optional)],
|
||||
):
|
||||
user_id = user["sub"] if user else None
|
||||
|
||||
rooms = await rooms_controller.get_by_names(request.room_names)
|
||||
room_by_id: dict[str, Any] = {r.id: r for r in rooms}
|
||||
room_ids = list(room_by_id.keys())
|
||||
|
||||
current_time = datetime.now(timezone.utc)
|
||||
active_meetings, upcoming_events = await asyncio.gather(
|
||||
meetings_controller.get_all_active_for_rooms(room_ids, current_time),
|
||||
calendar_events_controller.get_upcoming_for_rooms(room_ids),
|
||||
)
|
||||
|
||||
# Group by room name
|
||||
active_by_room: dict[str, list[Meeting]] = defaultdict(list)
|
||||
for m in active_meetings:
|
||||
room = room_by_id.get(m.room_id)
|
||||
if not room:
|
||||
continue
|
||||
m.platform = room.platform
|
||||
if user_id != room.user_id and m.platform == "whereby":
|
||||
m.host_room_url = ""
|
||||
active_by_room[room.name].append(m)
|
||||
|
||||
upcoming_by_room: dict[str, list[CalendarEventResponse]] = defaultdict(list)
|
||||
for e in upcoming_events:
|
||||
room = room_by_id.get(e.room_id)
|
||||
if not room:
|
||||
continue
|
||||
if user_id != room.user_id:
|
||||
e.description = None
|
||||
e.attendees = None
|
||||
upcoming_by_room[room.name].append(e)
|
||||
|
||||
result: dict[str, RoomMeetingStatus] = {}
|
||||
for name in request.room_names:
|
||||
result[name] = RoomMeetingStatus(
|
||||
active_meetings=active_by_room.get(name, []),
|
||||
upcoming_events=upcoming_by_room.get(name, []),
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/rooms/{room_id}", response_model=RoomDetails)
|
||||
async def rooms_get(
|
||||
room_id: str,
|
||||
|
||||
Reference in New Issue
Block a user