From 7e98c2eea7435c33016b52e5dbc6d85245897a09 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Fri, 12 Sep 2025 16:40:24 -0600 Subject: [PATCH] fix: creation of meeting --- server/reflector/views/rooms.py | 12 +- server/reflector/worker/process.py | 1 + www/app/[roomName]/MeetingSelection.tsx | 177 ++++++++++++++++++-- www/app/[roomName]/useRoomMeeting.tsx | 3 + www/app/components/MeetingMinimalHeader.tsx | 11 +- www/app/reflector-api.d.ts | 29 ++-- 6 files changed, 202 insertions(+), 31 deletions(-) diff --git a/server/reflector/views/rooms.py b/server/reflector/views/rooms.py index fe285159..d3de0185 100644 --- a/server/reflector/views/rooms.py +++ b/server/reflector/views/rooms.py @@ -113,6 +113,10 @@ class UpdateRoom(BaseModel): ics_enabled: Optional[bool] = None +class CreateRoomMeeting(BaseModel): + allow_duplicated: Optional[bool] = False + + class DeletionStatus(BaseModel): status: str @@ -235,6 +239,7 @@ async def rooms_delete( @router.post("/rooms/{room_name}/meeting", response_model=Meeting) async def rooms_create_meeting( room_name: str, + info: CreateRoomMeeting, user: Annotated[Optional[auth.UserInfo], Depends(auth.current_user_optional)], ): user_id = user["sub"] if user else None @@ -243,7 +248,12 @@ async def rooms_create_meeting( raise HTTPException(status_code=404, detail="Room not found") current_time = datetime.now(timezone.utc) - meeting = await meetings_controller.get_active(room=room, current_time=current_time) + + meeting = None + if not info.allow_duplicated: + meeting = await meetings_controller.get_active( + room=room, current_time=current_time + ) if meeting is None: end_date = current_time + timedelta(hours=8) diff --git a/server/reflector/worker/process.py b/server/reflector/worker/process.py index 9829742a..5641bb46 100644 --- a/server/reflector/worker/process.py +++ b/server/reflector/worker/process.py @@ -191,6 +191,7 @@ async def process_meetings(): # This API call could be slow, extend lock if needed response = await get_room_sessions(meeting.room_name) + print(response) try: # Extend lock after slow operation to ensure we still hold it diff --git a/www/app/[roomName]/MeetingSelection.tsx b/www/app/[roomName]/MeetingSelection.tsx index 2302a21c..13bdceb4 100644 --- a/www/app/[roomName]/MeetingSelection.tsx +++ b/www/app/[roomName]/MeetingSelection.tsx @@ -25,7 +25,6 @@ import { import { useRouter } from "next/navigation"; import { formatDateTime, formatStartedAgo } from "../lib/timeUtils"; import MeetingMinimalHeader from "../components/MeetingMinimalHeader"; -import { MEETING_DEFAULT_TIME_MINUTES } from "./[meetingId]/constants"; type Meeting = components["schemas"]["Meeting"]; @@ -36,6 +35,7 @@ interface MeetingSelectionProps { authLoading: boolean; onMeetingSelect: (meeting: Meeting) => void; onCreateUnscheduled: () => void; + isCreatingMeeting?: boolean; } export default function MeetingSelection({ @@ -44,6 +44,7 @@ export default function MeetingSelection({ isSharedRoom, onMeetingSelect, onCreateUnscheduled, + isCreatingMeeting = false, }: MeetingSelectionProps) { const router = useRouter(); @@ -56,19 +57,22 @@ export default function MeetingSelection({ const allMeetings = activeMeetingsQuery.data || []; const now = new Date(); - const [currentMeetings, upcomingMeetings] = partition( + const [currentMeetings, nonCurrentMeetings] = partition( allMeetings, (meeting) => { const startTime = new Date(meeting.start_date); - // Meeting is ongoing if it started and participants have joined or it's been running for a while - return ( - meeting.num_clients > 0 || - now.getTime() - startTime.getTime() > - MEETING_DEFAULT_TIME_MINUTES * 1000 - ); + const endTime = new Date(meeting.end_date); + // Meeting is ongoing if current time is between start and end + return now >= startTime && now <= endTime; }, ); + const upcomingMeetings = nonCurrentMeetings.filter((meeting) => { + const startTime = new Date(meeting.start_date); + // Meeting is upcoming if it hasn't started yet + return now < startTime; + }); + const loading = roomQuery.isLoading || activeMeetingsQuery.isLoading; const error = roomQuery.error || activeMeetingsQuery.error; @@ -139,7 +143,30 @@ export default function MeetingSelection({ }; return ( - + + {/* Loading overlay */} + {isCreatingMeeting && ( + + + + + Creating meeting... + + + + )} + {/* Current Ongoing Meetings - BIG DISPLAY */} - {currentMeetings.length > 0 && ( + {currentMeetings.length > 0 ? ( - - Live Meeting{currentMeetings.length > 1 ? "s" : ""} - {currentMeetings.map((meeting) => ( ))} - )} + ) : upcomingMeetings.length > 0 ? ( + /* Upcoming Meetings - BIG DISPLAY when no ongoing meetings */ + + + Upcoming Meeting{upcomingMeetings.length > 1 ? "s" : ""} + + {upcomingMeetings.map((meeting) => { + const now = new Date(); + const startTime = new Date(meeting.start_date); + const minutesUntilStart = Math.floor( + (startTime.getTime() - now.getTime()) / (1000 * 60), + ); - {/* Upcoming Meetings - SMALLER ASIDE DISPLAY */} - {upcomingMeetings.length > 0 && ( + return ( + + + + + + + {(meeting.calendar_metadata as any)?.title || + "Upcoming Meeting"} + + + + {isOwner && + (meeting.calendar_metadata as any)?.description && ( + + {(meeting.calendar_metadata as any).description} + + )} + + + + Starts in {minutesUntilStart} minute + {minutesUntilStart !== 1 ? "s" : ""} + + {formatDateTime(meeting.start_date)} + + + {isOwner && + (meeting.calendar_metadata as any)?.attendees && ( + + {(meeting.calendar_metadata as any).attendees + .slice(0, 4) + .map((attendee: any, idx: number) => ( + + {attendee.name || attendee.email} + + ))} + {(meeting.calendar_metadata as any).attendees + .length > 4 && ( + + + + {(meeting.calendar_metadata as any).attendees + .length - 4}{" "} + more + + )} + + )} + + + + + {isOwner && ( + + )} + + + + ); + })} + + ) : null} + + {/* Upcoming Meetings - SMALLER ASIDE DISPLAY when there are ongoing meetings */} + {currentMeetings.length > 0 && upcomingMeetings.length > 0 && ( Starting Soon diff --git a/www/app/[roomName]/useRoomMeeting.tsx b/www/app/[roomName]/useRoomMeeting.tsx index 331f0b1a..8b81277b 100644 --- a/www/app/[roomName]/useRoomMeeting.tsx +++ b/www/app/[roomName]/useRoomMeeting.tsx @@ -49,6 +49,9 @@ const useRoomMeeting = ( room_name: roomName, }, }, + body: { + allow_duplicated: false, + }, }); setResponse(result); } catch (error: any) { diff --git a/www/app/components/MeetingMinimalHeader.tsx b/www/app/components/MeetingMinimalHeader.tsx index b435407f..d66753e3 100644 --- a/www/app/components/MeetingMinimalHeader.tsx +++ b/www/app/components/MeetingMinimalHeader.tsx @@ -12,6 +12,7 @@ interface MeetingMinimalHeaderProps { onLeave?: () => void; showCreateButton?: boolean; onCreateMeeting?: () => void; + isCreatingMeeting?: boolean; } export default function MeetingMinimalHeader({ @@ -21,6 +22,7 @@ export default function MeetingMinimalHeader({ onLeave, showCreateButton = false, onCreateMeeting, + isCreatingMeeting = false, }: MeetingMinimalHeaderProps) { const router = useRouter(); @@ -70,7 +72,13 @@ export default function MeetingMinimalHeader({ {/* Action Buttons */} {showCreateButton && onCreateMeeting && ( - )} @@ -80,6 +88,7 @@ export default function MeetingMinimalHeader({ colorScheme="gray" size="sm" onClick={handleLeaveMeeting} + disabled={isCreatingMeeting} > Leave Room diff --git a/www/app/reflector-api.d.ts b/www/app/reflector-api.d.ts index e898a75e..106efae1 100644 --- a/www/app/reflector-api.d.ts +++ b/www/app/reflector-api.d.ts @@ -54,10 +54,7 @@ export interface paths { delete?: never; options?: never; head?: never; - /** - * Meeting Deactivate - * @description Deactivate a meeting (owner only) - */ + /** Meeting Deactivate */ patch: operations["v1_meeting_deactivate"]; trace?: never; }; @@ -227,10 +224,7 @@ export interface paths { path?: never; cookie?: never; }; - /** - * Rooms List Active Meetings - * @description List all active meetings for a room (supports multiple active meetings) - */ + /** Rooms List Active Meetings */ get: operations["v1_rooms_list_active_meetings"]; put?: never; post?: never; @@ -249,10 +243,7 @@ export interface paths { }; get?: never; put?: never; - /** - * Rooms Join Meeting - * @description Join a specific meeting by ID - */ + /** Rooms Join Meeting */ post: operations["v1_rooms_join_meeting"]; delete?: never; options?: never; @@ -740,6 +731,14 @@ export interface components { */ ics_enabled: boolean; }; + /** CreateRoomMeeting */ + CreateRoomMeeting: { + /** + * Allow Duplicated + * @default false + */ + allow_duplicated: boolean | null; + }; /** CreateTranscript */ CreateTranscript: { /** Name */ @@ -1780,7 +1779,11 @@ export interface operations { }; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["CreateRoomMeeting"]; + }; + }; responses: { /** @description Successful Response */ 200: {