using adas_core.Application.Repositories.Interfaces; using adas_core.Domain.Models.AppSettings; using adas_core.Infrastructure.Utils; using Microsoft.Extensions.Options; using MongoDB.Bson; using MongoDB.Driver; using Serilog; namespace adas_core.Infrastructure.Repositories; /// /// Abstract base repository providing common MongoDB persistence operations for a given document type . /// Concrete repositories must implement to identify the target collection, /// and may override , , and /// to customize schema setup, seeding, and insertion behavior. /// /// The domain model type persisted in the MongoDB collection. public abstract class MongoRepository : IMongoRepository { /// /// The MongoDB database instance used by the repository. /// protected readonly IMongoDatabase Db; private IMongoCollection? _collection; // protected MongoRepository(IOptions dbSettings) // { // Db = MongoDbHostBuilderExtension.GetMongoDb(dbSettings); // } /// /// Initializes a new instance of the class using the provided database. /// /// The MongoDB database instance used to access collections. Must not be . protected MongoRepository(IMongoDatabase database) { Db = database; } /// /// When implemented in a derived class, returns the name of the MongoDB collection used to store documents of type . /// /// The MongoDB collection name as a string. public abstract string GetCollectionName(); /// /// Gets the underlying for the repository. /// On first access, ensures the collection exists (creating it if necessary), then triggers asynchronous index creation /// and initial data loading via and . /// /// The MongoDB collection of documents. public IMongoCollection Collection { get { if (_collection == null) { var collectionName = GetCollectionName(); if (!CollectionExists(collectionName)) Db.CreateCollection(collectionName); _collection = Db.GetCollection(collectionName); _ = CreateIndexes(); _ = InsertInitialLoad(); } return _collection; } set => _collection = value; } /// /// Asynchronously inserts a single document into the collection. Errors are logged and swallowed. /// /// The document of type to insert. /// A representing the asynchronous insert operation. public virtual async Task InsertOneAsync(T obj) { try { await Collection.InsertOneAsync(obj); } catch (Exception ex) { Log.Error("An error occurred: {ExMessage}", ex.Message); } } /// /// Asynchronously replaces (or upserts) a single document identified by its _id. /// Uses with set to /// so that the document is created if it does not exist. Errors are logged and swallowed. /// /// The value of the document's _id field. /// The replacement document of type . /// A representing the asynchronous replace/upsert operation. public async Task UpdateOneAsync(ObjectId id, T obj) { try { var filter = Builders.Filter.Eq("_id", id); await Collection.ReplaceOneAsync(filter, obj, new ReplaceOptions { IsUpsert = true }); } catch (Exception ex) { Log.Error("Error updating id: {Id}. Exception:{Ex}, stackTrace: {Trace}", id.ToString(), ex.Message, ex.StackTrace); } } /// /// Asynchronously finds and deletes a single document identified by its _id. /// Returns the deleted document, or the default value of if not found or on error. /// /// The value of the document's _id field. /// /// A representing the asynchronous operation. /// The task result contains the deleted document, or / default if no document matched or an error occurred. /// public async Task DeleteAsync(ObjectId id) { try { var filter = Builders.Filter.Eq("_id", id); return await Collection.FindOneAndDeleteAsync(filter); } catch (Exception ex) { Log.Error("An error occurred: {ExMessage}", ex.Message); return default; } } /// /// Creates the indexes required for the collection. The base implementation is a no-op; /// derived classes should override this method to define their own indexes. /// /// A representing the asynchronous index creation operation. public virtual Task CreateIndexes() { return Task.CompletedTask; } /// /// Performs an initial data load (seeding) for the collection. The base implementation is a no-op; /// derived classes should override this method to provide custom seeding logic. /// /// A representing the asynchronous seeding operation. public virtual Task InsertInitialLoad() { return Task.CompletedTask; } /// /// Asynchronously inserts multiple documents into the collection using unordered semantics /// (a single failed insert does not abort the batch). Errors are logged and swallowed. /// /// The list of documents of type to insert. /// A representing the asynchronous bulk insert operation. public virtual async Task InsertManyAsync(List obj) { try { InsertManyOptions options = new() { IsOrdered = false }; await Collection.InsertManyAsync(obj, options); } catch (Exception ex) { Log.Error("An error occurred: {ExMessage}", ex.Message); } } /// /// Asynchronously updates all documents in the collection where the specified field equals , /// setting that field to the new . Errors are logged and swallowed. /// /// The name of the field to match and update. /// The new value to assign. /// The existing value to replace. /// A representing the asynchronous update operation. protected async Task UpdateManyObjectIdAsync(string nameId, ObjectId id, ObjectId oldId) { try { var update = Builders.Update.Set(nameId, id); var filter = Builders.Filter.Eq(nameId, oldId); await Collection.UpdateManyAsync(filter, update); } catch (Exception ex) { Log.Error("An error occurred: {ExMessage}", ex.Message); } } /// /// Asynchronously finds and deletes a single document identified by a string _id. /// Returns the deleted document, or the default value of if not found or on error. /// /// The string value of the document's _id field. /// /// A representing the asynchronous operation. /// The task result contains the deleted document, or / default if no document matched or an error occurred. /// protected async Task DeleteAsync(string id) { try { var filter = Builders.Filter.Eq("_id", id); return await Collection.FindOneAndDeleteAsync(filter); } catch (Exception ex) { Log.Error("An error occurred: {ExMessage}", ex.Message); return default; } } /// /// Determines whether a collection with the specified name exists in the current database. /// Errors are logged and the method returns in that case. /// /// The name of the collection to check. /// /// if a collection with the given name exists; otherwise, . /// Returns when an exception is thrown while querying the database. /// protected bool CollectionExists(string collectionName) { try { var filter = new BsonDocument("name", collectionName); var options = new ListCollectionNamesOptions { Filter = filter }; return Db.ListCollectionNames(options).Any(); } catch (Exception ex) { Log.Error("An error occurred: {ExMessage}", ex.Message); return false; } } }