diff --git a/meet-ce/backend/src/models/zod-schemas/recording.schema.ts b/meet-ce/backend/src/models/zod-schemas/recording.schema.ts index f249b019..fb5d30b2 100644 --- a/meet-ce/backend/src/models/zod-schemas/recording.schema.ts +++ b/meet-ce/backend/src/models/zod-schemas/recording.schema.ts @@ -9,9 +9,11 @@ import { import { z } from 'zod'; import { encodingValidator, nonEmptySanitizedRoomId } from './room.schema.js'; -// Shared fields validation schema for Recording entity -// Validates and transforms comma-separated string to typed array -// Only allows fields that exist in MEET_RECORDING_FIELDS +/** + * Shared fields validation schema for Recording entity + * Validates and transforms comma-separated string to typed array + * Only allows fields that exist in MEET_RECORDING_FIELDS + */ const fieldsSchema = z .string() .optional() diff --git a/meet-ce/backend/src/models/zod-schemas/room-member.schema.ts b/meet-ce/backend/src/models/zod-schemas/room-member.schema.ts index c5e6df7d..d0256ad9 100644 --- a/meet-ce/backend/src/models/zod-schemas/room-member.schema.ts +++ b/meet-ce/backend/src/models/zod-schemas/room-member.schema.ts @@ -1,6 +1,7 @@ import { + MEET_ROOM_MEMBER_FIELDS, MEET_ROOM_MEMBER_SORT_FIELDS, - MeetRoomMemberFilters, + MeetRoomMemberField, MeetRoomMemberOptions, MeetRoomMemberPermissions, MeetRoomMemberRole, @@ -10,6 +11,32 @@ import { } from '@openvidu-meet/typings'; import { z } from 'zod'; +/** + * Shared fields validation schema for RoomMember entity + * Validates and transforms comma-separated string to typed array + * Only allows fields that exist in MEET_ROOM_MEMBER_FIELDS + */ +const fieldsSchema = z + .string() + .optional() + .transform((value) => { + if (!value) return undefined; + + const requested = value + .split(',') + .map((field) => field.trim()) + .filter((field) => field !== ''); + + // Filter: only keep valid fields that exist in MeetRoomMember + const validFields = requested.filter((field) => + MEET_ROOM_MEMBER_FIELDS.includes(field as MeetRoomMemberField) + ) as MeetRoomMemberField[]; + + const unique = Array.from(new Set(validFields)); + + return unique.length > 0 ? unique : undefined; + }); + const RoomMemberRoleSchema: z.ZodType = z.nativeEnum(MeetRoomMemberRole); export const MeetPermissionsSchema: z.ZodType = z.object({ @@ -67,9 +94,9 @@ export const RoomMemberOptionsSchema: z.ZodType = z } ); -export const RoomMemberFiltersSchema: z.ZodType = z.object({ +export const RoomMemberFiltersSchema = z.object({ name: z.string().optional(), - fields: z.string().optional(), + fields: fieldsSchema, maxItems: z.coerce .number() .positive('maxItems must be a positive number') diff --git a/meet-ce/typings/src/response/room-member-response.ts b/meet-ce/typings/src/response/room-member-response.ts index eb0395b7..7b04bad9 100644 --- a/meet-ce/typings/src/response/room-member-response.ts +++ b/meet-ce/typings/src/response/room-member-response.ts @@ -2,6 +2,27 @@ import { MeetRoomMemberPermissions } from '../database/room-member-permissions.j import { MeetRoomMember, MeetRoomMemberRole } from '../database/room-member.entity.js'; import { SortAndPagination, SortableFieldKey } from './sort-pagination.js'; +/** + * List of all valid fields that can be selected from a MeetRoomMember. + * This array is the source of truth and TypeScript validates it matches the MeetRoomMember interface. + */ +export const MEET_ROOM_MEMBER_FIELDS = [ + 'memberId', + 'roomId', + 'name', + 'membershipDate', + 'accessUrl', + 'baseRole', + 'customPermissions', + 'effectivePermissions', + 'permissionsUpdatedAt' +] as const satisfies readonly (keyof MeetRoomMember)[]; + +/** + * Properties of a {@link MeetRoomMember} that can be included in the API response when fields filtering is applied. + */ +export type MeetRoomMemberField = (typeof MEET_ROOM_MEMBER_FIELDS)[number]; + /** * Room member fields that are allowed for sorting in room member list queries. */ @@ -21,8 +42,8 @@ export type MeetRoomMemberSortField = (typeof MEET_ROOM_MEMBER_SORT_FIELDS)[numb export interface MeetRoomMemberFilters extends SortAndPagination { /** Filter by member name (partial match) */ name?: string; - /** Comma-separated list of fields to include in the response (e.g., "userId,name,baseRole") */ - fields?: string; + /** Array of fields to include in the response */ + fields?: MeetRoomMemberField[]; } /**