backend: simplify repository classes by removing generic types and enhancing type handling

This commit is contained in:
juancarmore 2026-02-22 13:41:40 +01:00
parent 83583259ba
commit 27d4249c57
7 changed files with 119 additions and 179 deletions

View File

@ -1,39 +1,36 @@
import { MeetApiKey } from '@openvidu-meet/typings';
import { inject, injectable } from 'inversify';
import { Require_id } from 'mongoose';
import { MeetApiKeyDocument, MeetApiKeyModel } from '../models/mongoose-schemas/api-key.schema.js';
import { LoggerService } from '../services/logger.service.js';
import { BaseRepository } from './base.repository.js';
/**
* Repository for managing MeetApiKey entities in MongoDB.
*
* @template TApiKey - The domain type extending MeetApiKey (default: MeetApiKey)
*/
@injectable()
export class ApiKeyRepository<TApiKey extends MeetApiKey = MeetApiKey> extends BaseRepository<
TApiKey,
MeetApiKeyDocument
> {
export class ApiKeyRepository extends BaseRepository<MeetApiKey, MeetApiKeyDocument> {
constructor(@inject(LoggerService) logger: LoggerService) {
super(logger, MeetApiKeyModel);
}
protected toDomain(document: MeetApiKeyDocument): TApiKey {
return document.toObject() as TApiKey;
protected toDomain(dbObject: Require_id<MeetApiKeyDocument> & { __v: number }): MeetApiKey {
const { _id, __v, schemaVersion, ...apiKey } = dbObject;
(void _id, __v, schemaVersion);
return apiKey as MeetApiKey;
}
/**
* Creates a new API key.
*/
async create(apiKey: TApiKey): Promise<TApiKey> {
const doc = await this.createDocument(apiKey);
return this.toDomain(doc);
async create(apiKey: MeetApiKey): Promise<MeetApiKey> {
return this.createDocument(apiKey);
}
/**
* Returns all API keys.
*/
async findAll(): Promise<TApiKey[]> {
async findAll(): Promise<MeetApiKey[]> {
return await super.findAll();
}

View File

@ -1,5 +1,6 @@
import { GlobalConfig } from '@openvidu-meet/typings';
import { inject, injectable } from 'inversify';
import { Require_id } from 'mongoose';
import { MeetGlobalConfigDocument, MeetGlobalConfigModel } from '../models/mongoose-schemas/global-config.schema.js';
import { LoggerService } from '../services/logger.service.js';
import { BaseRepository } from './base.repository.js';
@ -9,26 +10,17 @@ import { BaseRepository } from './base.repository.js';
*
* IMPORTANT: This collection should only contain ONE document representing the
* system-wide global configuration. Methods are designed to work with this singleton pattern.
*
* @template TGlobalConfig - The domain type extending GlobalConfig (default: GlobalConfig)
*/
@injectable()
export class GlobalConfigRepository<TGlobalConfig extends GlobalConfig = GlobalConfig> extends BaseRepository<
TGlobalConfig,
MeetGlobalConfigDocument
> {
export class GlobalConfigRepository extends BaseRepository<GlobalConfig, MeetGlobalConfigDocument> {
constructor(@inject(LoggerService) logger: LoggerService) {
super(logger, MeetGlobalConfigModel);
}
/**
* Transforms a MongoDB document into a domain GlobalConfig object.
*
* @param document - The MongoDB document
* @returns GlobalConfig domain object
*/
protected toDomain(document: MeetGlobalConfigDocument): TGlobalConfig {
return document.toObject() as TGlobalConfig;
protected toDomain(dbObject: Require_id<MeetGlobalConfigDocument> & { __v: number }): GlobalConfig {
const { _id, __v, schemaVersion, ...globalConfig } = dbObject;
(void _id, __v, schemaVersion);
return globalConfig as GlobalConfig;
}
/**
@ -40,9 +32,8 @@ export class GlobalConfigRepository<TGlobalConfig extends GlobalConfig = GlobalC
* @param config - The global configuration data to create
* @returns The created global configuration
*/
async create(config: TGlobalConfig): Promise<TGlobalConfig> {
const document = await this.createDocument(config);
return this.toDomain(document);
async create(config: GlobalConfig): Promise<GlobalConfig> {
return this.createDocument(config);
}
/**
@ -54,10 +45,9 @@ export class GlobalConfigRepository<TGlobalConfig extends GlobalConfig = GlobalC
* @returns The updated global configuration
* @throws Error if no config exists
*/
async update(config: TGlobalConfig): Promise<TGlobalConfig> {
async update(config: GlobalConfig): Promise<GlobalConfig> {
// Update the first document in the collection (there should only be one)
const document = await this.updateOne({}, config);
return this.toDomain(document);
return this.updateOne({}, config);
}
/**
@ -65,10 +55,9 @@ export class GlobalConfigRepository<TGlobalConfig extends GlobalConfig = GlobalC
*
* @returns The global configuration or null if not found
*/
async get(): Promise<TGlobalConfig | null> {
async get(): Promise<GlobalConfig | null> {
// Get the first (and only) document from the collection
const document = await this.findOne({});
return document ? this.toDomain(document) : null;
return this.findOne({});
}
/**

View File

@ -1,4 +1,5 @@
import { inject, injectable } from 'inversify';
import { Require_id } from 'mongoose';
import { MeetMigration, MigrationName, MigrationStatus } from '../models/migration.model.js';
import { MeetMigrationDocument, MeetMigrationModel } from '../models/mongoose-schemas/migration.schema.js';
import { LoggerService } from '../services/logger.service.js';
@ -10,14 +11,10 @@ export class MigrationRepository extends BaseRepository<MeetMigration, MeetMigra
super(logger, MeetMigrationModel);
}
/**
* Transforms a MongoDB document into a domain MeetMigration object.
*
* @param document - The MongoDB document
* @returns MeetMigration domain object
*/
protected toDomain(document: MeetMigrationDocument): MeetMigration {
return document.toObject() as MeetMigration;
protected toDomain(dbObject: Require_id<MeetMigrationDocument> & { __v: number }): MeetMigration {
const { _id, __v, ...migration } = dbObject;
(void _id, __v);
return migration as MeetMigration;
}
/**
@ -34,7 +31,7 @@ export class MigrationRepository extends BaseRepository<MeetMigration, MeetMigra
if (existingMigration) {
// Update existing document to RUNNING status
const document = await this.updateOne(
return this.updateOne(
{ name },
{
$set: {
@ -47,16 +44,14 @@ export class MigrationRepository extends BaseRepository<MeetMigration, MeetMigra
}
}
);
return this.toDomain(document);
}
// Create new migration document
const document = await this.createDocument({
return this.createDocument({
name,
status: MigrationStatus.RUNNING,
startedAt: Date.now()
});
return this.toDomain(document);
}
/**
@ -68,7 +63,7 @@ export class MigrationRepository extends BaseRepository<MeetMigration, MeetMigra
* @returns The updated migration document
*/
async markAsCompleted(name: MigrationName, metadata?: Record<string, unknown>): Promise<MeetMigration> {
const document = await this.updateOne(
return this.updateOne(
{ name },
{
$set: {
@ -78,7 +73,6 @@ export class MigrationRepository extends BaseRepository<MeetMigration, MeetMigra
}
}
);
return this.toDomain(document);
}
/**
@ -90,7 +84,7 @@ export class MigrationRepository extends BaseRepository<MeetMigration, MeetMigra
* @returns The updated migration document
*/
async markAsFailed(name: MigrationName, error: string): Promise<MeetMigration> {
const document = await this.updateOne(
return this.updateOne(
{ name },
{
$set: {
@ -100,7 +94,6 @@ export class MigrationRepository extends BaseRepository<MeetMigration, MeetMigra
}
}
);
return this.toDomain(document);
}
/**

View File

@ -6,6 +6,7 @@ import {
SortOrder
} from '@openvidu-meet/typings';
import { inject, injectable } from 'inversify';
import { FilterQuery, Require_id } from 'mongoose';
import { uid as secureUid } from 'uid/secure';
import { MeetRecordingDocument, MeetRecordingModel } from '../models/mongoose-schemas/recording.schema.js';
import { LoggerService } from '../services/logger.service.js';
@ -13,54 +14,36 @@ import { BaseRepository } from './base.repository.js';
/**
* Repository for managing recording entities in MongoDB.
* Handles CRUD operations and query filtering for recordings.
* Provides CRUD operations and specialized queries for recording data.
*/
@injectable()
export class RecordingRepository<TRecording extends MeetRecordingInfo = MeetRecordingInfo> extends BaseRepository<
TRecording,
MeetRecordingDocument
> {
export class RecordingRepository extends BaseRepository<MeetRecordingInfo, MeetRecordingDocument> {
constructor(@inject(LoggerService) logger: LoggerService) {
super(logger, MeetRecordingModel);
}
/**
* Transforms a Mongoose document to a domain object.
* Removes access secrets before returning.
*/
protected toDomain(document: MeetRecordingDocument): TRecording {
return document.toObject() as TRecording;
protected toDomain(dbObject: Require_id<MeetRecordingDocument> & { __v: number }): MeetRecordingInfo {
const { _id, __v, schemaVersion, accessSecrets, ...recording } = dbObject;
(void _id, __v, schemaVersion, accessSecrets);
return recording as MeetRecordingInfo;
}
/**
* Creates a new recording document in the database.
* Automatically generates access secrets if not provided.
* Creates a new recording with generated access secrets.
*
* @param recording - The recording information to create (optionally includes accessSecrets)
* @param recording - The recording information to create (excluding access secrets)
* @returns The created recording (without access secrets)
*/
async create(recording: TRecording): Promise<TRecording> {
// Check if recording already includes accessSecrets
const hasAccessSecrets =
'accessSecrets' in recording &&
recording.accessSecrets &&
typeof recording.accessSecrets === 'object' &&
'public' in recording.accessSecrets &&
'private' in recording.accessSecrets;
// Generate access secrets only if not provided
const recordingDoc = {
async create(recording: MeetRecordingInfo): Promise<MeetRecordingInfo> {
// Generate access secrets
const recordingDoc: Omit<MeetRecordingDocument, 'schemaVersion'> = {
...recording,
accessSecrets: hasAccessSecrets
? recording.accessSecrets
: {
public: secureUid(10),
private: secureUid(10)
}
accessSecrets: {
public: secureUid(10),
private: secureUid(10)
}
};
const result = await this.createDocument(recordingDoc);
return this.toDomain(result);
return this.createDocument(recordingDoc);
}
/**
@ -70,9 +53,8 @@ export class RecordingRepository<TRecording extends MeetRecordingInfo = MeetReco
* @returns The updated recording (without access secrets)
* @throws Error if recording not found
*/
async update(recording: TRecording): Promise<TRecording> {
const document = await this.updateOne({ recordingId: recording.recordingId }, { $set: recording });
return this.toDomain(document);
async update(recording: MeetRecordingInfo): Promise<MeetRecordingInfo> {
return this.updateOne({ recordingId: recording.recordingId }, { $set: recording });
}
/**
@ -82,9 +64,8 @@ export class RecordingRepository<TRecording extends MeetRecordingInfo = MeetReco
* @param fields - Array of field names to include in the result
* @returns The recording (without access secrets), or null if not found
*/
async findByRecordingId(recordingId: string, fields?: MeetRecordingField[]): Promise<TRecording | null> {
const document = await this.findOne({ recordingId }, fields);
return document ? this.toDomain(document) : null;
async findByRecordingId(recordingId: string, fields?: MeetRecordingField[]): Promise<MeetRecordingInfo | null> {
return this.findOne({ recordingId }, fields);
}
/**
@ -107,7 +88,7 @@ export class RecordingRepository<TRecording extends MeetRecordingInfo = MeetReco
* @returns Object containing recordings array, pagination info, and optional next page token
*/
async find(options: MeetRecordingFilters & { roomIds?: string[] } = {}): Promise<{
recordings: TRecording[];
recordings: MeetRecordingInfo[];
isTruncated: boolean;
nextPageToken?: string;
}> {
@ -124,7 +105,7 @@ export class RecordingRepository<TRecording extends MeetRecordingInfo = MeetReco
} = options;
// Build base filter
const filter: Record<string, unknown> = {};
const filter: FilterQuery<MeetRecordingDocument> = {};
if (roomIds) {
// Filter by multiple room IDs
@ -171,7 +152,7 @@ export class RecordingRepository<TRecording extends MeetRecordingInfo = MeetReco
* @param roomId - The ID of the room
* @returns Array of recordings for the specified room
*/
async findAllByRoomId(roomId: string): Promise<TRecording[]> {
async findAllByRoomId(roomId: string): Promise<MeetRecordingInfo[]> {
return await this.findAll({ roomId });
}
@ -203,8 +184,8 @@ export class RecordingRepository<TRecording extends MeetRecordingInfo = MeetReco
*
* @returns Array of active recordings
*/
async findActiveRecordings(): Promise<TRecording[]> {
return await this.findAll({
async findActiveRecordings(): Promise<MeetRecordingInfo[]> {
return this.findAll({
status: { $in: [MeetRecordingStatus.ACTIVE, MeetRecordingStatus.ENDING] }
});
}
@ -216,7 +197,7 @@ export class RecordingRepository<TRecording extends MeetRecordingInfo = MeetReco
* @throws Error if the recording was not found or could not be deleted
*/
async deleteByRecordingId(recordingId: string): Promise<void> {
await this.deleteOne({ recordingId });
this.deleteOne({ recordingId });
}
/**
@ -226,14 +207,14 @@ export class RecordingRepository<TRecording extends MeetRecordingInfo = MeetReco
* @throws Error if any recording was not found or could not be deleted
*/
async deleteByRecordingIds(recordingIds: string[]): Promise<void> {
await this.deleteMany({ recordingId: { $in: recordingIds } });
this.deleteMany({ recordingId: { $in: recordingIds } });
}
/**
* Counts the total number of recordings.
*/
async countTotal(): Promise<number> {
return await this.count();
return this.count();
}
/**

View File

@ -1,30 +1,24 @@
import { MeetRoomMember, MeetRoomMemberFilters, MeetRoomMemberPermissions, SortOrder } from '@openvidu-meet/typings';
import { inject, injectable } from 'inversify';
import { FilterQuery, Require_id } from 'mongoose';
import { MeetRoomMemberDocument, MeetRoomMemberModel } from '../models/mongoose-schemas/room-member.schema.js';
import { LoggerService } from '../services/logger.service.js';
import { BaseRepository } from './base.repository.js';
/**
* Repository for managing MeetRoomMember entities in MongoDB.
* Handles the storage and retrieval of room members.
* Provides CRUD operations and specialized queries for room member data.
*/
@injectable()
export class RoomMemberRepository<TRoomMember extends MeetRoomMember = MeetRoomMember> extends BaseRepository<
TRoomMember,
MeetRoomMemberDocument
> {
export class RoomMemberRepository extends BaseRepository<MeetRoomMember, MeetRoomMemberDocument> {
constructor(@inject(LoggerService) logger: LoggerService) {
super(logger, MeetRoomMemberModel);
}
/**
* Transforms a MongoDB document into a domain room member object.
*
* @param document - The MongoDB document
* @returns Room member with computed permissions
*/
protected toDomain(document: MeetRoomMemberDocument): TRoomMember {
return document.toObject() as TRoomMember;
protected toDomain(dbObject: Require_id<MeetRoomMemberDocument> & { __v: number }): MeetRoomMember {
const { _id, __v, schemaVersion, ...member } = dbObject;
(void _id, __v, schemaVersion);
return member as MeetRoomMember;
}
/**
@ -33,9 +27,8 @@ export class RoomMemberRepository<TRoomMember extends MeetRoomMember = MeetRoomM
* @param member - The room member data to add
* @returns The created room member
*/
async create(member: TRoomMember): Promise<TRoomMember> {
const document = await this.createDocument(member as TRoomMember);
return this.toDomain(document);
async create(member: MeetRoomMember): Promise<MeetRoomMember> {
return this.createDocument(member);
}
/**
@ -45,9 +38,8 @@ export class RoomMemberRepository<TRoomMember extends MeetRoomMember = MeetRoomM
* @returns The updated room member
* @throws Error if room member not found
*/
async update(member: TRoomMember): Promise<TRoomMember> {
const document = await this.updateOne({ roomId: member.roomId, memberId: member.memberId }, member);
return this.toDomain(document);
async update(member: MeetRoomMember): Promise<MeetRoomMember> {
return this.updateOne({ roomId: member.roomId, memberId: member.memberId }, member);
}
/**
@ -57,9 +49,8 @@ export class RoomMemberRepository<TRoomMember extends MeetRoomMember = MeetRoomM
* @param memberId - The ID of the member
* @returns The room member or null if not found
*/
async findByRoomAndMemberId(roomId: string, memberId: string): Promise<TRoomMember | null> {
const document = await this.findOne({ roomId, memberId });
return document ? this.toDomain(document) : null;
async findByRoomAndMemberId(roomId: string, memberId: string): Promise<MeetRoomMember | null> {
return this.findOne({ roomId, memberId });
}
/**
@ -70,8 +61,8 @@ export class RoomMemberRepository<TRoomMember extends MeetRoomMember = MeetRoomM
* @param fields - Array of field names to include in the result
* @returns Array of found room members
*/
async findByRoomAndMemberIds(roomId: string, memberIds: string[], fields?: string[]): Promise<TRoomMember[]> {
return await this.findAll({ roomId, memberId: { $in: memberIds } }, fields);
async findByRoomAndMemberIds(roomId: string, memberIds: string[], fields?: string[]): Promise<MeetRoomMember[]> {
return this.findAll({ roomId, memberId: { $in: memberIds } }, fields);
}
/**
@ -123,7 +114,7 @@ export class RoomMemberRepository<TRoomMember extends MeetRoomMember = MeetRoomM
roomId: string,
options: MeetRoomMemberFilters = {}
): Promise<{
members: TRoomMember[];
members: MeetRoomMember[];
isTruncated: boolean;
nextPageToken?: string;
}> {
@ -137,7 +128,7 @@ export class RoomMemberRepository<TRoomMember extends MeetRoomMember = MeetRoomM
} = options;
// Build base filter
const filter: Record<string, unknown> = { roomId };
const filter: FilterQuery<MeetRoomMemberDocument> = { roomId };
if (name) {
filter.name = new RegExp(name, 'i');

View File

@ -1,5 +1,6 @@
import { MeetRoom, MeetRoomField, MeetRoomFilters, MeetRoomStatus, SortOrder } from '@openvidu-meet/typings';
import { inject, injectable } from 'inversify';
import { FilterQuery, Require_id } from 'mongoose';
import { MeetRoomDocument, MeetRoomModel } from '../models/mongoose-schemas/room.schema.js';
import { LoggerService } from '../services/logger.service.js';
import { getBasePath } from '../utils/html-dynamic-base-path.utils.js';
@ -9,24 +10,24 @@ import { BaseRepository } from './base.repository.js';
/**
* Repository for managing MeetRoom entities in MongoDB.
* Provides CRUD operations and specialized queries for room data.
*
* @template TRoom - The domain type extending MeetRoom (default: MeetRoom)
*/
@injectable()
export class RoomRepository<TRoom extends MeetRoom = MeetRoom> extends BaseRepository<TRoom, MeetRoomDocument> {
export class RoomRepository extends BaseRepository<MeetRoom, MeetRoomDocument> {
constructor(@inject(LoggerService) logger: LoggerService) {
super(logger, MeetRoomModel);
}
/**
* Transforms a MongoDB document into a domain room object.
* Enriches URLs with the base URL.
* Transforms a persisted MeetRoom document into a domain MeetRoom object.
* Enriches access URLs with the base URL.
*
* @param document - The MongoDB document
* @param dbObject - The MongoDB document representing a room
* @returns Room with complete URLs
*/
protected toDomain(document: MeetRoomDocument): TRoom {
return this.enrichRoomWithBaseUrls(document);
protected toDomain(dbObject: Require_id<MeetRoomDocument> & { __v: number }): MeetRoom {
const { _id, __v, schemaVersion, ...room } = dbObject;
(void _id, __v, schemaVersion);
return this.enrichRoomWithBaseUrls(room as MeetRoom);
}
/**
@ -36,10 +37,9 @@ export class RoomRepository<TRoom extends MeetRoom = MeetRoom> extends BaseRepos
* @param room - The room data to create
* @returns The created room with enriched URLs
*/
async create(room: TRoom): Promise<TRoom> {
async create(room: MeetRoom): Promise<MeetRoom> {
const normalizedRoom = this.normalizeRoomForStorage(room);
const document = await this.createDocument(normalizedRoom);
return this.enrichRoomWithBaseUrls(document);
return this.createDocument(normalizedRoom);
}
/**
@ -52,10 +52,9 @@ export class RoomRepository<TRoom extends MeetRoom = MeetRoom> extends BaseRepos
*
* TODO: This method should be renamed to replace or updateFull to better reflect that it replaces the entire document
*/
async update(room: TRoom): Promise<TRoom> {
async update(room: MeetRoom): Promise<MeetRoom> {
const normalizedRoom = this.normalizeRoomForStorage(room);
const document = await this.updateOne({ roomId: room.roomId }, normalizedRoom);
return this.enrichRoomWithBaseUrls(document);
return this.updateOne({ roomId: room.roomId }, normalizedRoom);
}
/**
@ -64,7 +63,7 @@ export class RoomRepository<TRoom extends MeetRoom = MeetRoom> extends BaseRepos
* @param roomId
* @param fieldsToUpdate
*/
async updatePartial(roomId: string, fieldsToUpdate: Partial<TRoom>): Promise<void> {
async updatePartial(roomId: string, fieldsToUpdate: Partial<MeetRoom>): Promise<void> {
await this.updateOne({ roomId }, fieldsToUpdate);
}
@ -76,9 +75,8 @@ export class RoomRepository<TRoom extends MeetRoom = MeetRoom> extends BaseRepos
* @param fields - Array of field names to include in the result
* @returns The room or null if not found
*/
async findByRoomId(roomId: string, fields?: MeetRoomField[]): Promise<TRoom | null> {
const document = await this.findOne({ roomId }, fields);
return document ? this.enrichRoomWithBaseUrls(document) : null;
async findByRoomId(roomId: string, fields?: MeetRoomField[]): Promise<MeetRoom | null> {
return this.findOne({ roomId }, fields);
}
/**
@ -89,8 +87,8 @@ export class RoomRepository<TRoom extends MeetRoom = MeetRoom> extends BaseRepos
* @param fields - Array of field names to include in the result
* @returns Array of rooms owned by the user
*/
async findByOwner(owner: string, fields?: MeetRoomField[]): Promise<TRoom[]> {
return await this.findAll({ owner }, fields);
async findByOwner(owner: string, fields?: MeetRoomField[]): Promise<MeetRoom[]> {
return this.findAll({ owner }, fields);
}
/**
@ -113,7 +111,7 @@ export class RoomRepository<TRoom extends MeetRoom = MeetRoom> extends BaseRepos
* @returns Object containing rooms array, pagination info, and optional next page token
*/
async find(options: MeetRoomFilters & { owner?: string; roomIds?: string[] } = {}): Promise<{
rooms: TRoom[];
rooms: MeetRoom[];
isTruncated: boolean;
nextPageToken?: string;
}> {
@ -130,7 +128,7 @@ export class RoomRepository<TRoom extends MeetRoom = MeetRoom> extends BaseRepos
} = options;
// Build base filter
const filter: Record<string, unknown> = {};
const filter: FilterQuery<MeetRoomDocument> = {};
// Handle owner and roomIds with $or when both are present
if (owner && roomIds) {
@ -174,11 +172,11 @@ export class RoomRepository<TRoom extends MeetRoom = MeetRoom> extends BaseRepos
*
* @returns Array of expired rooms with enriched URLs
*/
async findExpiredRooms(): Promise<TRoom[]> {
async findExpiredRooms(): Promise<MeetRoom[]> {
const now = Date.now();
// Find all rooms where autoDeletionDate exists and is less than now
return await this.findAll({
return this.findAll({
autoDeletionDate: { $exists: true, $lt: now }
});
}
@ -189,8 +187,8 @@ export class RoomRepository<TRoom extends MeetRoom = MeetRoom> extends BaseRepos
*
* @returns Array of active rooms with enriched URLs
*/
async findActiveRooms(): Promise<TRoom[]> {
return await this.findAll({
async findActiveRooms(): Promise<MeetRoom[]> {
return this.findAll({
status: MeetRoomStatus.ACTIVE_MEETING
});
}
@ -219,14 +217,14 @@ export class RoomRepository<TRoom extends MeetRoom = MeetRoom> extends BaseRepos
* Counts the total number of rooms.
*/
async countTotal(): Promise<number> {
return await this.count();
return this.count();
}
/**
* Counts the number of rooms with active meetings.
*/
async countActiveRooms(): Promise<number> {
return await this.count({ status: MeetRoomStatus.ACTIVE_MEETING });
return this.count({ status: MeetRoomStatus.ACTIVE_MEETING });
}
// ==========================================
@ -240,7 +238,7 @@ export class RoomRepository<TRoom extends MeetRoom = MeetRoom> extends BaseRepos
* @param room - The room data to normalize
* @returns Normalized room data
*/
private normalizeRoomForStorage(room: TRoom): TRoom {
private normalizeRoomForStorage(room: MeetRoom): MeetRoom {
return {
...room,
accessUrl: this.extractPathFromUrl(room.accessUrl),
@ -307,9 +305,8 @@ export class RoomRepository<TRoom extends MeetRoom = MeetRoom> extends BaseRepos
* @param document - The MongoDB document
* @returns Room data with complete URLs
*/
private enrichRoomWithBaseUrls(document: MeetRoomDocument): TRoom {
private enrichRoomWithBaseUrls(room: MeetRoom): MeetRoom {
const baseUrl = getBaseUrl();
const room = document.toObject() as TRoom;
return {
...room,

View File

@ -1,5 +1,6 @@
import { MeetUser, MeetUserFilters, SortOrder } from '@openvidu-meet/typings';
import { inject, injectable } from 'inversify';
import { FilterQuery, Require_id } from 'mongoose';
import { MeetUserDocument, MeetUserModel } from '../models/mongoose-schemas/user.schema.js';
import { LoggerService } from '../services/logger.service.js';
import { BaseRepository } from './base.repository.js';
@ -7,23 +8,17 @@ import { BaseRepository } from './base.repository.js';
/**
* Repository for managing MeetUser entities in MongoDB.
* Provides CRUD operations and specialized queries for user data.
*
* @template TUser - The domain type extending MeetUser (default: MeetUser)
*/
@injectable()
export class UserRepository<TUser extends MeetUser = MeetUser> extends BaseRepository<TUser, MeetUserDocument> {
export class UserRepository extends BaseRepository<MeetUser, MeetUserDocument> {
constructor(@inject(LoggerService) logger: LoggerService) {
super(logger, MeetUserModel);
}
/**
* Transforms a MongoDB document into a domain user object.
*
* @param document - The MongoDB document
* @returns User domain object
*/
protected toDomain(document: MeetUserDocument): TUser {
return document.toObject() as TUser;
protected toDomain(dbObject: Require_id<MeetUserDocument> & { __v: number }): MeetUser {
const { _id, __v, schemaVersion, ...user } = dbObject;
(void _id, __v, schemaVersion);
return user as MeetUser;
}
/**
@ -32,9 +27,8 @@ export class UserRepository<TUser extends MeetUser = MeetUser> extends BaseRepos
* @param user - The user data to create
* @returns The created user
*/
async create(user: TUser): Promise<TUser> {
const document = await this.createDocument(user);
return this.toDomain(document);
async create(user: MeetUser): Promise<MeetUser> {
return this.createDocument(user);
}
/**
@ -44,9 +38,8 @@ export class UserRepository<TUser extends MeetUser = MeetUser> extends BaseRepos
* @returns The updated user
* @throws Error if user not found
*/
async update(user: TUser): Promise<TUser> {
const document = await this.updateOne({ userId: user.userId }, user);
return this.toDomain(document);
async update(user: MeetUser): Promise<MeetUser> {
return this.updateOne({ userId: user.userId }, user);
}
/**
@ -55,9 +48,8 @@ export class UserRepository<TUser extends MeetUser = MeetUser> extends BaseRepos
* @param userId - The unique user identifier
* @returns The user or null if not found
*/
async findByUserId(userId: string): Promise<TUser | null> {
const document = await this.findOne({ userId });
return document ? this.toDomain(document) : null;
async findByUserId(userId: string): Promise<MeetUser | null> {
return this.findOne({ userId });
}
/**
@ -66,7 +58,7 @@ export class UserRepository<TUser extends MeetUser = MeetUser> extends BaseRepos
* @param userIds - Array of user identifiers
* @returns Array of found users
*/
async findByUserIds(userIds: string[]): Promise<TUser[]> {
async findByUserIds(userIds: string[]): Promise<MeetUser[]> {
return await this.findAll({ userId: { $in: userIds } });
}
@ -84,7 +76,7 @@ export class UserRepository<TUser extends MeetUser = MeetUser> extends BaseRepos
* @returns Object containing users array, pagination info, and optional next page token
*/
async find(options: MeetUserFilters = {}): Promise<{
users: TUser[];
users: MeetUser[];
isTruncated: boolean;
nextPageToken?: string;
}> {
@ -99,7 +91,7 @@ export class UserRepository<TUser extends MeetUser = MeetUser> extends BaseRepos
} = options;
// Build base filter
const filter: Record<string, unknown> = {};
const filter: FilterQuery<MeetUserDocument> = {};
if (userId && name) {
// Both defined: OR filter with regex userId match and regex name match