backend: implement room member management with repository and database schema definition
This commit is contained in:
parent
79cef519b8
commit
e72566dd8c
@ -7,6 +7,7 @@ import { GlobalConfigRepository } from '../repositories/global-config.repository
|
||||
import { MigrationRepository } from '../repositories/migration.repository.js';
|
||||
import { RecordingRepository } from '../repositories/recording.repository.js';
|
||||
import { RoomRepository } from '../repositories/room.repository.js';
|
||||
import { RoomMemberRepository } from '../repositories/room-member.repository.js';
|
||||
import { UserRepository } from '../repositories/user.repository.js';
|
||||
|
||||
/*
|
||||
@ -86,6 +87,7 @@ export const registerDependencies = () => {
|
||||
container.bind(MongoDBService).toSelf().inSingletonScope();
|
||||
container.bind(BaseRepository).toSelf().inSingletonScope();
|
||||
container.bind(RoomRepository).toSelf().inSingletonScope();
|
||||
container.bind(RoomMemberRepository).toSelf().inSingletonScope();
|
||||
container.bind(UserRepository).toSelf().inSingletonScope();
|
||||
container.bind(ApiKeyRepository).toSelf().inSingletonScope();
|
||||
container.bind(GlobalConfigRepository).toSelf().inSingletonScope();
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { AuthMode, AuthType, GlobalConfig } from '@openvidu-meet/typings';
|
||||
import { GlobalConfig, OAuthProvider } from '@openvidu-meet/typings';
|
||||
import { Document, model, Schema } from 'mongoose';
|
||||
import { INTERNAL_CONFIG } from '../../config/internal-config.js';
|
||||
import { MeetAppearanceConfigSchema } from './room.schema.js';
|
||||
@ -13,14 +13,25 @@ export interface MeetGlobalConfigDocument extends GlobalConfig, Document {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-schema for authentication method.
|
||||
* Currently only supports single_user type.
|
||||
* Sub-schema for OAuth provider configuration.
|
||||
*/
|
||||
const AuthMethodSchema = new Schema(
|
||||
const OAuthProviderConfigSchema = new Schema(
|
||||
{
|
||||
type: {
|
||||
provider: {
|
||||
type: String,
|
||||
enum: Object.values(OAuthProvider),
|
||||
required: true
|
||||
},
|
||||
clientId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
clientSecret: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
redirectUri: {
|
||||
type: String,
|
||||
enum: Object.values(AuthType),
|
||||
required: true
|
||||
}
|
||||
},
|
||||
@ -32,14 +43,13 @@ const AuthMethodSchema = new Schema(
|
||||
*/
|
||||
const AuthenticationConfigSchema = new Schema(
|
||||
{
|
||||
authMethod: {
|
||||
type: AuthMethodSchema,
|
||||
allowUserCreation: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
authModeToAccessRoom: {
|
||||
type: String,
|
||||
enum: Object.values(AuthMode),
|
||||
required: true
|
||||
oauthProviders: {
|
||||
type: [OAuthProviderConfigSchema],
|
||||
required: false
|
||||
}
|
||||
},
|
||||
{ _id: false }
|
||||
|
||||
@ -0,0 +1,100 @@
|
||||
import { MeetRoomMember, MeetRoomMemberRole } from '@openvidu-meet/typings';
|
||||
import { Document, Schema, model } from 'mongoose';
|
||||
|
||||
/**
|
||||
* Mongoose Document interface for MeetRoomMember.
|
||||
* Extends the MeetRoomMember interface with MongoDB Document functionality.
|
||||
* Note: effectivePermissions is computed, not stored.
|
||||
*/
|
||||
export interface MeetRoomMemberDocument extends Omit<MeetRoomMember, 'effectivePermissions'>, Document {
|
||||
/** Schema version for migration tracking (internal use only) */
|
||||
schemaVersion?: number;
|
||||
}
|
||||
|
||||
const permissionFields = {
|
||||
canRecord: { type: Boolean },
|
||||
canRetrieveRecordings: { type: Boolean },
|
||||
canDeleteRecordings: { type: Boolean },
|
||||
canJoinMeeting: { type: Boolean },
|
||||
canShareAccessLinks: { type: Boolean },
|
||||
canMakeModerator: { type: Boolean },
|
||||
canKickParticipants: { type: Boolean },
|
||||
canEndMeeting: { type: Boolean },
|
||||
canPublishVideo: { type: Boolean },
|
||||
canPublishAudio: { type: Boolean },
|
||||
canShareScreen: { type: Boolean },
|
||||
canReadChat: { type: Boolean },
|
||||
canWriteChat: { type: Boolean },
|
||||
canChangeVirtualBackground: { type: Boolean }
|
||||
};
|
||||
|
||||
function createPermissionsSchema(required: boolean) {
|
||||
const schemaDefinition: Record<string, unknown> = {};
|
||||
|
||||
for (const key of Object.keys(permissionFields)) {
|
||||
schemaDefinition[key] = { ...permissionFields[key as keyof typeof permissionFields], required };
|
||||
}
|
||||
|
||||
return new Schema(schemaDefinition, { _id: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-schema for room member permissions.
|
||||
*/
|
||||
export const MeetRoomMemberPermissionsSchema = createPermissionsSchema(true);
|
||||
|
||||
/**
|
||||
* Sub-schema for partial room member permissions.
|
||||
*/
|
||||
const MeetRoomMemberPartialPermissionsSchema = createPermissionsSchema(false);
|
||||
|
||||
/**
|
||||
* Mongoose schema for MeetRoomMember entity.
|
||||
*/
|
||||
const MeetRoomMemberSchema = new Schema<MeetRoomMemberDocument>(
|
||||
{
|
||||
memberId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
roomId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
baseRole: {
|
||||
type: String,
|
||||
enum: Object.values(MeetRoomMemberRole),
|
||||
required: true
|
||||
},
|
||||
customPermissions: {
|
||||
type: MeetRoomMemberPartialPermissionsSchema,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
{
|
||||
toObject: {
|
||||
versionKey: false,
|
||||
transform: (_doc, ret) => {
|
||||
delete ret._id;
|
||||
delete ret.schemaVersion;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Create indexes for efficient querying
|
||||
MeetRoomMemberSchema.index({ roomId: 1, memberId: 1 }, { unique: true });
|
||||
MeetRoomMemberSchema.index({ roomId: 1, name: 1, _id: 1 });
|
||||
MeetRoomMemberSchema.index({ memberId: 1 });
|
||||
|
||||
export const meetRoomMemberCollectionName = 'MeetRoomMember';
|
||||
|
||||
/**
|
||||
* Mongoose model for MeetRoomMember.
|
||||
*/
|
||||
export const MeetRoomMemberModel = model<MeetRoomMemberDocument>(meetRoomMemberCollectionName, MeetRoomMemberSchema);
|
||||
@ -1,5 +1,4 @@
|
||||
import {
|
||||
MeetRecordingAccess,
|
||||
MeetRoom,
|
||||
MeetRoomDeletionPolicyWithMeeting,
|
||||
MeetRoomDeletionPolicyWithRecordings,
|
||||
@ -9,6 +8,7 @@ import {
|
||||
} from '@openvidu-meet/typings';
|
||||
import { Document, Schema, model } from 'mongoose';
|
||||
import { INTERNAL_CONFIG } from '../../config/internal-config.js';
|
||||
import { MeetRoomMemberPermissionsSchema } from './room-member.schema.js';
|
||||
|
||||
/**
|
||||
* Mongoose Document interface for MeetRoom.
|
||||
@ -48,11 +48,6 @@ const MeetRecordingConfigSchema = new Schema(
|
||||
enabled: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
allowAccessTo: {
|
||||
type: String,
|
||||
enum: Object.values(MeetRecordingAccess),
|
||||
required: false
|
||||
}
|
||||
},
|
||||
{ _id: false }
|
||||
@ -91,8 +86,7 @@ const MeetE2EEConfigSchema = new Schema(
|
||||
{
|
||||
enabled: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false
|
||||
required: true
|
||||
}
|
||||
},
|
||||
{ _id: false }
|
||||
@ -153,6 +147,56 @@ export const MeetAppearanceConfigSchema = new Schema(
|
||||
{ _id: false }
|
||||
);
|
||||
|
||||
/**
|
||||
* Sub-schema for room roles configuration.
|
||||
*/
|
||||
const MeetRoomRolesSchema = new Schema(
|
||||
{
|
||||
moderator: {
|
||||
permissions: {
|
||||
type: MeetRoomMemberPermissionsSchema,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
speaker: {
|
||||
permissions: {
|
||||
type: MeetRoomMemberPermissionsSchema,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
},
|
||||
{ _id: false }
|
||||
);
|
||||
|
||||
/**
|
||||
* Sub-schema for anonymous access configuration.
|
||||
*/
|
||||
const MeetRoomAnonymousSchema = new Schema(
|
||||
{
|
||||
moderator: {
|
||||
enabled: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
accessUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
speaker: {
|
||||
enabled: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
accessUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
},
|
||||
{ _id: false }
|
||||
);
|
||||
|
||||
/**
|
||||
* Mongoose schema for MeetRoom configuration.
|
||||
*/
|
||||
@ -172,8 +216,7 @@ const MeetRoomConfigSchema = new Schema(
|
||||
},
|
||||
e2ee: {
|
||||
type: MeetE2EEConfigSchema,
|
||||
required: true,
|
||||
default: { enabled: false }
|
||||
required: true
|
||||
}
|
||||
},
|
||||
{ _id: false }
|
||||
@ -198,6 +241,10 @@ const MeetRoomSchema = new Schema<MeetRoomDocument>(
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
owner: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
creationDate: {
|
||||
type: Number,
|
||||
required: true
|
||||
@ -214,11 +261,15 @@ const MeetRoomSchema = new Schema<MeetRoomDocument>(
|
||||
type: MeetRoomConfigSchema,
|
||||
required: true
|
||||
},
|
||||
moderatorUrl: {
|
||||
type: String,
|
||||
roles: {
|
||||
type: MeetRoomRolesSchema,
|
||||
required: true
|
||||
},
|
||||
speakerUrl: {
|
||||
anonymous: {
|
||||
type: MeetRoomAnonymousSchema,
|
||||
required: true
|
||||
},
|
||||
accessUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
|
||||
@ -22,19 +22,22 @@ const MeetUserSchema = new Schema<MeetUserDocument>(
|
||||
required: true,
|
||||
default: INTERNAL_CONFIG.USER_SCHEMA_VERSION
|
||||
},
|
||||
username: {
|
||||
userId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
role: {
|
||||
type: String,
|
||||
enum: Object.values(MeetUserRole),
|
||||
required: true
|
||||
},
|
||||
passwordHash: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
roles: {
|
||||
type: [String],
|
||||
enum: Object.values(MeetUserRole),
|
||||
required: true,
|
||||
default: [MeetUserRole.USER]
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -50,7 +53,8 @@ const MeetUserSchema = new Schema<MeetUserDocument>(
|
||||
);
|
||||
|
||||
// Create indexes for efficient querying
|
||||
MeetUserSchema.index({ username: 1 }, { unique: true });
|
||||
MeetUserSchema.index({ userId: 1 }, { unique: true });
|
||||
MeetUserSchema.index({ name: 1, _id: 1 });
|
||||
|
||||
export const meetUserCollectionName = 'MeetUser';
|
||||
|
||||
|
||||
227
meet-ce/backend/src/repositories/room-member.repository.ts
Normal file
227
meet-ce/backend/src/repositories/room-member.repository.ts
Normal file
@ -0,0 +1,227 @@
|
||||
import { MeetRoomMember, MeetRoomMemberPermissions, MeetRoomMemberRole, MeetRoomRoles } from '@openvidu-meet/typings';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { MeetRoomMemberDocument, MeetRoomMemberModel } from '../models/mongoose-schemas/room-member.schema.js';
|
||||
import { LoggerService } from '../services/logger.service.js';
|
||||
import { BaseRepository } from './base.repository.js';
|
||||
import { RoomRepository } from './room.repository.js';
|
||||
import { errorRoomNotFound } from '../models/error.model.js';
|
||||
|
||||
/**
|
||||
* Repository for managing MeetRoomMember entities in MongoDB.
|
||||
* Handles the storage and retrieval of room members.
|
||||
*/
|
||||
@injectable()
|
||||
export class RoomMemberRepository extends BaseRepository<MeetRoomMember, MeetRoomMemberDocument> {
|
||||
private currentRoomRoles: MeetRoomRoles | undefined;
|
||||
|
||||
constructor(
|
||||
@inject(LoggerService) logger: LoggerService,
|
||||
@inject(RoomRepository) private roomRepository: RoomRepository
|
||||
) {
|
||||
super(logger, MeetRoomMemberModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a MongoDB document into a domain room member object.
|
||||
* Computes effective permissions based on base role and custom permissions.
|
||||
*
|
||||
* @param document - The MongoDB document
|
||||
* @returns Room member with computed permissions
|
||||
*/
|
||||
protected toDomain(document: MeetRoomMemberDocument): MeetRoomMember {
|
||||
const doc = document.toObject();
|
||||
const effectivePermissions = this.computeEffectivePermissions(
|
||||
this.currentRoomRoles!,
|
||||
doc.baseRole,
|
||||
doc.customPermissions
|
||||
);
|
||||
|
||||
return {
|
||||
...doc,
|
||||
effectivePermissions
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a member to a room.
|
||||
*
|
||||
* @param member - The room member data to add
|
||||
* @returns The created room member
|
||||
*/
|
||||
async create(member: MeetRoomMember): Promise<MeetRoomMember> {
|
||||
const room = await this.roomRepository.findByRoomId(member.roomId);
|
||||
|
||||
if (!room) {
|
||||
throw errorRoomNotFound(member.roomId);
|
||||
}
|
||||
|
||||
this.currentRoomRoles = room.roles;
|
||||
const document = await this.createDocument(member);
|
||||
const domain = this.toDomain(document);
|
||||
this.currentRoomRoles = undefined;
|
||||
return domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing room member.
|
||||
*
|
||||
* @param member - The complete updated room member data
|
||||
* @returns The updated room member
|
||||
* @throws Error if room member not found
|
||||
*/
|
||||
async update(member: MeetRoomMember): Promise<MeetRoomMember> {
|
||||
const room = await this.roomRepository.findByRoomId(member.roomId);
|
||||
|
||||
if (!room) {
|
||||
throw errorRoomNotFound(member.roomId);
|
||||
}
|
||||
|
||||
this.currentRoomRoles = room.roles;
|
||||
const document = await this.updateOne({ roomId: member.roomId, memberId: member.memberId }, member);
|
||||
const domain = this.toDomain(document);
|
||||
this.currentRoomRoles = undefined;
|
||||
return domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a specific member in a room.
|
||||
*
|
||||
* @param roomId - The ID of the room
|
||||
* @param memberId - The ID of the member
|
||||
* @returns The room member or null if not found
|
||||
*/
|
||||
async findByRoomAndMemberId(roomId: string, memberId: string): Promise<MeetRoomMember | null> {
|
||||
const room = await this.roomRepository.findByRoomId(roomId);
|
||||
|
||||
if (!room) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.currentRoomRoles = room.roles;
|
||||
const document = await this.findOne({ roomId, memberId });
|
||||
const domain = document ? this.toDomain(document) : null;
|
||||
this.currentRoomRoles = undefined;
|
||||
return domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds members of a room with optional filtering, pagination, and sorting.
|
||||
*
|
||||
* @param roomId - The ID of the room
|
||||
* @param options - Query options
|
||||
* @param options.name - Optional member name to filter by (case-insensitive partial match)
|
||||
* @param options.maxItems - Maximum number of results to return (default: 100)
|
||||
* @param options.nextPageToken - Token for pagination
|
||||
* @param options.sortField - Field to sort by (default: 'name')
|
||||
* @param options.sortOrder - Sort order: 'asc' or 'desc' (default: 'asc')
|
||||
* @returns Object containing members array, pagination info, and optional next page token
|
||||
*/
|
||||
async findByRoomId(
|
||||
roomId: string,
|
||||
options: {
|
||||
name?: string;
|
||||
maxItems?: number;
|
||||
nextPageToken?: string;
|
||||
sortField?: string;
|
||||
sortOrder?: 'asc' | 'desc';
|
||||
} = {}
|
||||
): Promise<{
|
||||
members: MeetRoomMember[];
|
||||
isTruncated: boolean;
|
||||
nextPageToken?: string;
|
||||
}> {
|
||||
const room = await this.roomRepository.findByRoomId(roomId);
|
||||
|
||||
if (!room) {
|
||||
throw errorRoomNotFound(roomId);
|
||||
}
|
||||
|
||||
this.currentRoomRoles = room.roles;
|
||||
|
||||
const { name, maxItems = 100, nextPageToken, sortField = 'name', sortOrder = 'asc' } = options;
|
||||
|
||||
// Build base filter
|
||||
const filter: Record<string, unknown> = { roomId };
|
||||
|
||||
if (name) {
|
||||
filter.name = new RegExp(name, 'i');
|
||||
}
|
||||
|
||||
// Use base repository's pagination method
|
||||
const result = await this.findMany(filter, {
|
||||
maxItems,
|
||||
nextPageToken,
|
||||
sortField,
|
||||
sortOrder
|
||||
});
|
||||
|
||||
this.currentRoomRoles = undefined;
|
||||
|
||||
return {
|
||||
members: result.items,
|
||||
isTruncated: result.isTruncated,
|
||||
nextPageToken: result.nextPageToken
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a member from a room.
|
||||
*
|
||||
* @param roomId - The ID of the room
|
||||
* @param memberId - The ID of the member to remove
|
||||
* @throws Error if room member not found or could not be deleted
|
||||
*/
|
||||
async deleteByRoomAndMemberId(roomId: string, memberId: string): Promise<void> {
|
||||
await this.deleteOne({ roomId, memberId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes multiple members from a room.
|
||||
*
|
||||
* @param roomId - The ID of the room
|
||||
* @param memberIds - Array of member IDs to remove
|
||||
* @throws Error if no room members were found or could not be deleted
|
||||
*/
|
||||
async deleteByRoomIdAndMemberIds(roomId: string, memberIds: string[]): Promise<void> {
|
||||
await this.deleteMany({ roomId, memberId: { $in: memberIds } });
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all members from a room.
|
||||
*
|
||||
* @param roomId - The ID of the room
|
||||
* @throws Error if members could not be deleted
|
||||
*/
|
||||
async deleteAllByRoomId(roomId: string): Promise<void> {
|
||||
await this.deleteMany({ roomId });
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// PRIVATE HELPER METHODS
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Computes effective permissions by merging base role permissions with custom permissions.
|
||||
*
|
||||
* @param roomRoles - The room roles configuration
|
||||
* @param baseRole - The base role of the member
|
||||
* @param customPermissions - Optional custom permissions that override the base role
|
||||
* @returns The effective permissions object
|
||||
*/
|
||||
private computeEffectivePermissions(
|
||||
roomRoles: MeetRoomRoles,
|
||||
baseRole: MeetRoomMemberRole,
|
||||
customPermissions?: Partial<MeetRoomMemberPermissions>
|
||||
): MeetRoomMemberPermissions {
|
||||
const basePermissions = roomRoles[baseRole].permissions;
|
||||
|
||||
if (!customPermissions) {
|
||||
return basePermissions;
|
||||
}
|
||||
|
||||
return {
|
||||
...basePermissions,
|
||||
...customPermissions
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -196,7 +196,7 @@ export class RoomRepository<TRoom extends MeetRoom = MeetRoom> extends BaseRepos
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Normalizes room data for storage by removing the base URL from URLs.
|
||||
* Normalizes room data for storage by removing the base URL from access URLs.
|
||||
* This ensures only the path is stored in the database.
|
||||
*
|
||||
* @param room - The room data to normalize
|
||||
@ -205,8 +205,18 @@ export class RoomRepository<TRoom extends MeetRoom = MeetRoom> extends BaseRepos
|
||||
private normalizeRoomForStorage(room: TRoom): TRoom {
|
||||
return {
|
||||
...room,
|
||||
moderatorUrl: this.extractPathFromUrl(room.moderatorUrl),
|
||||
speakerUrl: this.extractPathFromUrl(room.speakerUrl)
|
||||
accessUrl: this.extractPathFromUrl(room.accessUrl),
|
||||
anonymous: {
|
||||
...room.anonymous,
|
||||
moderator: {
|
||||
...room.anonymous.moderator,
|
||||
accessUrl: this.extractPathFromUrl(room.anonymous.moderator.accessUrl)
|
||||
},
|
||||
speaker: {
|
||||
...room.anonymous.speaker,
|
||||
accessUrl: this.extractPathFromUrl(room.anonymous.speaker.accessUrl)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -232,7 +242,7 @@ export class RoomRepository<TRoom extends MeetRoom = MeetRoom> extends BaseRepos
|
||||
}
|
||||
|
||||
/**
|
||||
* Enriches room data by adding the base URL to URLs.
|
||||
* Enriches room data by adding the base URL to access URLs.
|
||||
* Converts MongoDB document to domain object.
|
||||
* Only enriches URLs that are present in the document.
|
||||
*
|
||||
@ -245,8 +255,20 @@ export class RoomRepository<TRoom extends MeetRoom = MeetRoom> extends BaseRepos
|
||||
|
||||
return {
|
||||
...room,
|
||||
...(room.moderatorUrl !== undefined && { moderatorUrl: `${baseUrl}${room.moderatorUrl}` }),
|
||||
...(room.speakerUrl !== undefined && { speakerUrl: `${baseUrl}${room.speakerUrl}` })
|
||||
...(room.accessUrl !== undefined && { accessUrl: `${baseUrl}${room.accessUrl}` }),
|
||||
...(room.anonymous !== undefined && {
|
||||
anonymous: {
|
||||
...room.anonymous,
|
||||
moderator: {
|
||||
...room.anonymous.moderator,
|
||||
accessUrl: `${baseUrl}${room.anonymous.moderator.accessUrl}`
|
||||
},
|
||||
speaker: {
|
||||
...room.anonymous.speaker,
|
||||
accessUrl: `${baseUrl}${room.anonymous.speaker.accessUrl}`
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,23 +40,100 @@ export class UserRepository<TUser extends MeetUser = MeetUser> extends BaseRepos
|
||||
/**
|
||||
* Updates an existing user.
|
||||
*
|
||||
* @param user - The complete updated user data (must include username)
|
||||
* @param user - The complete updated user data
|
||||
* @returns The updated user
|
||||
* @throws Error if user not found
|
||||
*/
|
||||
async update(user: TUser): Promise<TUser> {
|
||||
const document = await this.updateOne({ username: user.username }, user);
|
||||
const document = await this.updateOne({ userId: user.userId }, user);
|
||||
return this.toDomain(document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a user by their username.
|
||||
*
|
||||
* @param username - The unique username identifier
|
||||
* @param userId - The unique user identifier
|
||||
* @returns The user or null if not found
|
||||
*/
|
||||
async findByUsername(username: string): Promise<TUser | null> {
|
||||
const document = await this.findOne({ username });
|
||||
async findByUserId(userId: string): Promise<TUser | null> {
|
||||
const document = await this.findOne({ userId });
|
||||
return document ? this.toDomain(document) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds users with optional filtering, pagination, and sorting.
|
||||
*
|
||||
* @param options - Query options
|
||||
* @param options.userId - Optional user ID to filter by (case-insensitive partial match)
|
||||
* @param options.name - Optional name to filter by (case-insensitive partial match)
|
||||
* @param options.maxItems - Maximum number of results to return (default: 100)
|
||||
* @param options.nextPageToken - Token for pagination
|
||||
* @param options.sortField - Field to sort by (default: 'name')
|
||||
* @param options.sortOrder - Sort order: 'asc' or 'desc' (default: 'asc')
|
||||
* @returns Object containing users array, pagination info, and optional next page token
|
||||
*/
|
||||
async find(
|
||||
options: {
|
||||
userId?: string;
|
||||
name?: string;
|
||||
maxItems?: number;
|
||||
nextPageToken?: string;
|
||||
sortField?: string;
|
||||
sortOrder?: 'asc' | 'desc';
|
||||
} = {}
|
||||
): Promise<{
|
||||
users: TUser[];
|
||||
isTruncated: boolean;
|
||||
nextPageToken?: string;
|
||||
}> {
|
||||
const { userId, name, maxItems = 100, nextPageToken, sortField = 'name', sortOrder = 'asc' } = options;
|
||||
|
||||
// Build base filter
|
||||
const filter: Record<string, unknown> = {};
|
||||
|
||||
if (userId && name) {
|
||||
// Both defined: OR filter with regex userId match and regex name match
|
||||
filter.$or = [{ userId: new RegExp(userId, 'i') }, { name: new RegExp(name, 'i') }];
|
||||
} else if (userId) {
|
||||
// Only userId defined: regex match (case-insensitive)
|
||||
filter.userId = new RegExp(userId, 'i');
|
||||
} else if (name) {
|
||||
// Only name defined: regex match (case-insensitive)
|
||||
filter.name = new RegExp(name, 'i');
|
||||
}
|
||||
|
||||
// Use base repository's pagination method
|
||||
const result = await this.findMany(filter, {
|
||||
maxItems,
|
||||
nextPageToken,
|
||||
sortField,
|
||||
sortOrder
|
||||
});
|
||||
|
||||
return {
|
||||
users: result.items,
|
||||
isTruncated: result.isTruncated,
|
||||
nextPageToken: result.nextPageToken
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a user by their userId.
|
||||
*
|
||||
* @param userId - The unique user identifier
|
||||
* @throws Error if the user was not found or could not be deleted
|
||||
*/
|
||||
async deleteByUserId(userId: string): Promise<void> {
|
||||
await this.deleteOne({ userId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes multiple users by their userIds.
|
||||
*
|
||||
* @param userIds - Array of user identifiers
|
||||
* @throws Error if no users were found or could not be deleted
|
||||
*/
|
||||
async deleteByUserIds(userIds: string[]): Promise<void> {
|
||||
await this.deleteMany({ userId: { $in: userIds } });
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ export interface MeetRoomMemberOptions {
|
||||
*/
|
||||
export interface MeetRoomMember {
|
||||
memberId: string; // Unique identifier for the member (equals userId for internal users, or generated for external users)
|
||||
roomId: string; // ID of the room the member belongs to
|
||||
name: string; // Name of the member (either internal or external user name)
|
||||
baseRole: MeetRoomMemberRole; // The base role of the member in the room
|
||||
customPermissions?: Partial<MeetRoomMemberPermissions>; // Custom permissions for the member (if any)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user