diff --git a/meet-ce/backend/src/models/db-pagination.model.ts b/meet-ce/backend/src/models/database.model.ts similarity index 55% rename from meet-ce/backend/src/models/db-pagination.model.ts rename to meet-ce/backend/src/models/database.model.ts index 19f0299b..fb00a361 100644 --- a/meet-ce/backend/src/models/db-pagination.model.ts +++ b/meet-ce/backend/src/models/database.model.ts @@ -1,3 +1,8 @@ +/** + * Type for fields that exist only in the persistence document and not in the domain model. + */ +export type DocumentOnlyField = Exclude; + /** * Result of a paginated find operation. */ diff --git a/meet-ce/backend/src/models/index.ts b/meet-ce/backend/src/models/index.ts index 35bfaeb1..d106b4ee 100644 --- a/meet-ce/backend/src/models/index.ts +++ b/meet-ce/backend/src/models/index.ts @@ -1,5 +1,5 @@ // Core models -export * from './db-pagination.model.js'; +export * from './database.model.js'; export * from './distributed-event.model.js'; export * from './error.model.js'; export * from './migration.model.js'; diff --git a/meet-ce/backend/src/repositories/base.repository.ts b/meet-ce/backend/src/repositories/base.repository.ts index 091cde2e..dae5a43e 100644 --- a/meet-ce/backend/src/repositories/base.repository.ts +++ b/meet-ce/backend/src/repositories/base.repository.ts @@ -1,7 +1,7 @@ import { SortAndPagination, SortOrder } from '@openvidu-meet/typings'; import { inject, injectable, unmanaged } from 'inversify'; import { Model, QueryFilter, Require_id, UpdateQuery } from 'mongoose'; -import { PaginatedResult, PaginationCursor } from '../models/db-pagination.model.js'; +import { DocumentOnlyField, PaginatedResult, PaginationCursor } from '../models/database.model.js'; import { LoggerService } from '../services/logger.service.js'; /** @@ -27,6 +27,14 @@ export abstract class BaseRepository & { __v: number }): TDomain; + /** + * Returns the list of fields that exist only in the persistence model (TDocument) + * and are not part of the domain contract (TDomain). + */ + protected getDocumentOnlyFields(): readonly DocumentOnlyField[] { + return []; + } + /** * Creates a new document. * @@ -171,7 +179,7 @@ export abstract class BaseRepository, replacement: TDomain): Promise { try { - const existingDocument = (await this.model.findOne(filter).lean().exec()) as - | (Require_id & { __v: number }) - | null; + const documentOnlyFields = this.getDocumentOnlyFields(); + const replacementDocument = { ...replacement } as TDocument; - if (!existingDocument) { - this.logger.error('No document found to replace with filter:', filter); - throw new Error('Document not found for replacement'); + if (documentOnlyFields.length > 0) { + const projection = documentOnlyFields.join(' '); + const existingDocument = (await this.model + .findOne(filter, projection) + .lean() + .exec()) as TDocument | null; + + if (!existingDocument) { + this.logger.error('No document found to replace with filter:', filter); + throw new Error('Document not found for replacement'); + } + + // Copy document-only fields from existing document to replacement document + for (const key of documentOnlyFields) { + replacementDocument[key] = existingDocument[key]; + } } - // Build replacement document by merging existing document's fields that are not in the replacement object - const documentOnlyFields = Object.fromEntries( - Object.entries(existingDocument).filter( - ([key]) => !Object.prototype.hasOwnProperty.call(replacement, key) - ) - ); - const replacementDocument = { - ...documentOnlyFields, - ...replacement - } as TDocument; - const document = (await this.model .findOneAndReplace(filter, replacementDocument, { - new: true, - runValidators: true, - lean: true, - upsert: false + returnDocument: 'after', // Return the document after replacement + runValidators: true, // Validate replacement document against schema + lean: true, // Return plain JavaScript object instead of Mongoose document + upsert: false // Do not create a new document if none matches the filter }) .exec()) as (Require_id & { __v: number }) | null; if (!document) { - this.logger.error('Document disappeared during replacement with filter:', filter); - throw new Error('Document not found during replacement'); + this.logger.error('No document found to replace with filter:', filter); + throw new Error('Document not found for replacement'); } this.logger.debug(`Document with ID '${document._id}' replaced`);