mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2026-02-06 18:56:48 +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:
@@ -2,9 +2,10 @@
|
||||
|
||||
import { $api } from "./apiClient";
|
||||
import { useError } from "../(errors)/errorContext";
|
||||
import { QueryClient, useQueryClient } from "@tanstack/react-query";
|
||||
import { QueryClient, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import type { components } from "../reflector-api";
|
||||
import { useAuth } from "./AuthProvider";
|
||||
import { meetingStatusBatcher } from "./meetingStatusBatcher";
|
||||
import { MeetingId } from "./types";
|
||||
import { NonEmptyString } from "./utils";
|
||||
|
||||
@@ -697,15 +698,7 @@ export function useRoomsCreateMeeting() {
|
||||
queryKey: $api.queryOptions("get", "/v1/rooms").queryKey,
|
||||
}),
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: $api.queryOptions(
|
||||
"get",
|
||||
"/v1/rooms/{room_name}/meetings/active" satisfies `/v1/rooms/{room_name}/${typeof MEETINGS_ACTIVE_PATH_PARTIAL}`,
|
||||
{
|
||||
params: {
|
||||
path: { room_name: roomName },
|
||||
},
|
||||
},
|
||||
).queryKey,
|
||||
queryKey: meetingStatusKeys.active(roomName),
|
||||
}),
|
||||
]);
|
||||
},
|
||||
@@ -734,18 +727,14 @@ export function useRoomGetByName(roomName: string | null) {
|
||||
export function useRoomUpcomingMeetings(roomName: string | null) {
|
||||
const { isAuthenticated } = useAuthReady();
|
||||
|
||||
return $api.useQuery(
|
||||
"get",
|
||||
"/v1/rooms/{room_name}/meetings/upcoming" satisfies `/v1/rooms/{room_name}/${typeof MEETINGS_UPCOMING_PATH_PARTIAL}`,
|
||||
{
|
||||
params: {
|
||||
path: { room_name: roomName! },
|
||||
},
|
||||
return useQuery({
|
||||
queryKey: meetingStatusKeys.upcoming(roomName!),
|
||||
queryFn: async () => {
|
||||
const result = await meetingStatusBatcher.fetch(roomName!);
|
||||
return result.upcoming_events;
|
||||
},
|
||||
{
|
||||
enabled: !!roomName && isAuthenticated,
|
||||
},
|
||||
);
|
||||
enabled: !!roomName && isAuthenticated,
|
||||
});
|
||||
}
|
||||
|
||||
const MEETINGS_PATH_PARTIAL = "meetings" as const;
|
||||
@@ -757,19 +746,22 @@ const MEETING_LIST_PATH_PARTIALS = [
|
||||
MEETINGS_UPCOMING_PATH_PARTIAL,
|
||||
];
|
||||
|
||||
export const meetingStatusKeys = {
|
||||
active: (roomName: string) =>
|
||||
["rooms", roomName, MEETINGS_ACTIVE_PATH_PARTIAL] as const,
|
||||
upcoming: (roomName: string) =>
|
||||
["rooms", roomName, MEETINGS_UPCOMING_PATH_PARTIAL] as const,
|
||||
};
|
||||
|
||||
export function useRoomActiveMeetings(roomName: string | null) {
|
||||
return $api.useQuery(
|
||||
"get",
|
||||
"/v1/rooms/{room_name}/meetings/active" satisfies `/v1/rooms/{room_name}/${typeof MEETINGS_ACTIVE_PATH_PARTIAL}`,
|
||||
{
|
||||
params: {
|
||||
path: { room_name: roomName! },
|
||||
},
|
||||
return useQuery({
|
||||
queryKey: meetingStatusKeys.active(roomName!),
|
||||
queryFn: async () => {
|
||||
const result = await meetingStatusBatcher.fetch(roomName!);
|
||||
return result.active_meetings;
|
||||
},
|
||||
{
|
||||
enabled: !!roomName,
|
||||
},
|
||||
);
|
||||
enabled: !!roomName,
|
||||
});
|
||||
}
|
||||
|
||||
export function useRoomGetMeeting(
|
||||
|
||||
25
www/app/lib/meetingStatusBatcher.ts
Normal file
25
www/app/lib/meetingStatusBatcher.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { create, keyResolver, windowScheduler } from "@yornaath/batshit";
|
||||
import { client } from "./apiClient";
|
||||
import type { components } from "../reflector-api";
|
||||
|
||||
type MeetingStatusResult = {
|
||||
roomName: string;
|
||||
active_meetings: components["schemas"]["Meeting"][];
|
||||
upcoming_events: components["schemas"]["CalendarEventResponse"][];
|
||||
};
|
||||
|
||||
export const meetingStatusBatcher = create({
|
||||
fetcher: async (roomNames: string[]): Promise<MeetingStatusResult[]> => {
|
||||
const unique = [...new Set(roomNames)];
|
||||
const { data } = await client.POST("/v1/rooms/meetings/bulk-status", {
|
||||
body: { room_names: unique },
|
||||
});
|
||||
return roomNames.map((name) => ({
|
||||
roomName: name,
|
||||
active_meetings: data?.[name]?.active_meetings ?? [],
|
||||
upcoming_events: data?.[name]?.upcoming_events ?? [],
|
||||
}));
|
||||
},
|
||||
resolver: keyResolver("roomName"),
|
||||
scheduler: windowScheduler(10),
|
||||
});
|
||||
64
www/app/reflector-api.d.ts
vendored
64
www/app/reflector-api.d.ts
vendored
@@ -118,6 +118,23 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/v1/rooms/meetings/bulk-status": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Rooms Bulk Meeting Status */
|
||||
post: operations["v1_rooms_bulk_meeting_status"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/v1/rooms/{room_id}": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -799,6 +816,11 @@ export interface components {
|
||||
*/
|
||||
chunk: string;
|
||||
};
|
||||
/** BulkStatusRequest */
|
||||
BulkStatusRequest: {
|
||||
/** Room Names */
|
||||
room_names: string[];
|
||||
};
|
||||
/** CalendarEventResponse */
|
||||
CalendarEventResponse: {
|
||||
/** Id */
|
||||
@@ -1735,6 +1757,13 @@ export interface components {
|
||||
/** Webhook Secret */
|
||||
webhook_secret: string | null;
|
||||
};
|
||||
/** RoomMeetingStatus */
|
||||
RoomMeetingStatus: {
|
||||
/** Active Meetings */
|
||||
active_meetings: components["schemas"]["Meeting"][];
|
||||
/** Upcoming Events */
|
||||
upcoming_events: components["schemas"]["CalendarEventResponse"][];
|
||||
};
|
||||
/** RtcOffer */
|
||||
RtcOffer: {
|
||||
/** Sdp */
|
||||
@@ -2272,6 +2301,41 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
v1_rooms_bulk_meeting_status: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["BulkStatusRequest"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": {
|
||||
[key: string]: components["schemas"]["RoomMeetingStatus"];
|
||||
};
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
v1_rooms_get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
||||
Reference in New Issue
Block a user