Conflicto de fusión en adas-core.LdapLogin/LdapLoginService.cs

This commit is contained in:
jrojas
2026-07-06 14:30:23 +02:00
2810 changed files with 1927407 additions and 25397 deletions
@@ -5,6 +5,12 @@ using MongoMigrations.Core;
namespace adas_core.Infrastructure.Migrations.MongoMigrations;
// ReSharper disable once InconsistentNaming
/// <summary>
/// Represents a database migration (version 0.1.0) responsible for updating the patient data schema.
/// </summary>
/// <remarks>
/// Inherits from the <see cref="Migration"/> base class, indicating it is intended to be applied as part of an Entity Framework (or similar) migration pipeline to evolve the underlying data store.
/// </remarks>
public class U_0_1_0_UpdateDataPatien : Migration
{
public U_0_1_0_UpdateDataPatien() : base(10)
@@ -4,6 +4,12 @@ using MongoMigrations.Core;
namespace adas_core.Infrastructure.Migrations.MongoMigrations;
/// <summary>
/// Represents a database migration responsible for updating the display configuration related to a driver in version 0.1.1.
/// </summary>
/// <remarks>
/// Inherits from <see cref="Migration"/> to support schema or data changes associated with the 0.1.1 release.
/// </remarks>
public class U_0_1_1_UpdateDisplayConfigDriver : Migration
{
public U_0_1_1_UpdateDisplayConfigDriver() : base(11)
@@ -12,55 +18,61 @@ public class U_0_1_1_UpdateDisplayConfigDriver : Migration
"Genera el campo Type en todos los documentos de la coleccion DisplayConfig por que el nuevo driver no expone _t para polimorfia";
}
/// <summary>
/// Updates documents in the <c>config_displays</c> collection by mapping the <c>_t</c> discriminator field
/// to a new <c>type</c> field, normalizing values to <c>DisplayNurse</c>, <c>StandarDisplay</c>, or <c>SmartDisplay</c>
/// (defaulting to <c>Unknown</c> for any other value), removing the original <c>_t</c> field, and merging the
/// results back into the same collection while discarding documents that do not already have a match.
/// </summary>
public override void Update()
{
var collection = Database.GetCollection<BsonDocument>("config_displays");
var pipeline = new[]
{
new BsonDocument("$set", new BsonDocument
var collection = Database.GetCollection<BsonDocument>("config_displays");
var pipeline = new[]
{
new BsonDocument("$set", new BsonDocument
{
"type", new BsonDocument("$switch", new BsonDocument
{
"type", new BsonDocument("$switch", new BsonDocument
{
"branches", new BsonArray
{
new BsonDocument
"branches", new BsonArray
{
{ "case", new BsonDocument("$eq", new BsonArray { "$_t", "DisplayNurse" }) },
{ "then", "DisplayNurse" }
},
new BsonDocument
{
{ "case", new BsonDocument("$eq", new BsonArray { "$_t", "StandarDisplay" }) },
{ "then", "StandarDisplay" }
},
new BsonDocument
{
{ "case", new BsonDocument("$eq", new BsonArray { "$_t", "SmartDisplay" }) },
{ "then", "SmartDisplay" }
new BsonDocument
{
{ "case", new BsonDocument("$eq", new BsonArray { "$_t", "DisplayNurse" }) },
{ "then", "DisplayNurse" }
},
new BsonDocument
{
{ "case", new BsonDocument("$eq", new BsonArray { "$_t", "StandarDisplay" }) },
{ "then", "StandarDisplay" }
},
new BsonDocument
{
{ "case", new BsonDocument("$eq", new BsonArray { "$_t", "SmartDisplay" }) },
{ "then", "SmartDisplay" }
}
}
}
},
{ "default", "Unknown" }
})
}
}),
new BsonDocument("$unset", "_t"),
new BsonDocument("$merge", new BsonDocument
{
{ "into", "config_displays" },
{ "whenMatched", "merge" },
{ "whenNotMatched", "discard" }
})
};
collection.AggregateToCollection<BsonDocument>(pipeline);
// collection.AggregateToCollection<BsonDocument>(pipeline, "config_displays");
}
},
{ "default", "Unknown" }
})
}
}),
new BsonDocument("$unset", "_t"),
new BsonDocument("$merge", new BsonDocument
{
{ "into", "config_displays" },
{ "whenMatched", "merge" },
{ "whenNotMatched", "discard" }
})
};
collection.AggregateToCollection<BsonDocument>(pipeline);
// collection.AggregateToCollection<BsonDocument>(pipeline, "config_displays");
}
}
@@ -4,6 +4,12 @@ using MongoMigrations.Core;
namespace adas_core.Infrastructure.Migrations.MongoMigrations;
/// <summary>
/// Represents a database migration responsible for updating the Point of Care configuration schema.
/// </summary>
/// <remarks>
/// The migration identifier prefix (U_0_1_2) indicates a specific ordering or version step within the migration sequence.
/// </remarks>
public class U_0_1_2_UpdatePointOfCareConfig : Migration
{
public U_0_1_2_UpdatePointOfCareConfig() : base(12)
@@ -12,48 +18,51 @@ public class U_0_1_2_UpdatePointOfCareConfig : Migration
"Borra los elementos de configuracion de poc, relay, camera y beacon y deja el place holder del nuevo modelo";
}
/// <summary>
/// Updates all point-of-care documents in the database by migrating their embedded configuration arrays (beacons, cameras, and relayList) to empty identifier list structures (beaconIdList, cameraIdList, and relayIdList), preserving the existing arrays' data not retained in this operation.
/// </summary>
public override void Update()
{
var collection = Database.GetCollection<BsonDocument>("pointOfCares");
var filter = Builders<BsonDocument>.Filter.Exists("configuration");
var documents = collection.Find(filter).ToList();
foreach (var doc in documents)
{
var configuration = doc["configuration"].AsBsonDocument;
// Listas nuevas de IDs
var beaconIdList = new BsonArray();
var cameraIdList = new BsonArray();
var relayIdList = new BsonArray();
// 1. Procesar Beacons (si existen)
if (configuration.Contains("beacons") && configuration["beacons"].IsBsonArray)
var collection = Database.GetCollection<BsonDocument>("pointOfCares");
var filter = Builders<BsonDocument>.Filter.Exists("configuration");
var documents = collection.Find(filter).ToList();
foreach (var doc in documents)
{
configuration.Remove("beacons");
var configuration = doc["configuration"].AsBsonDocument;
// Listas nuevas de IDs
var beaconIdList = new BsonArray();
var cameraIdList = new BsonArray();
var relayIdList = new BsonArray();
// 1. Procesar Beacons (si existen)
if (configuration.Contains("beacons") && configuration["beacons"].IsBsonArray)
{
configuration.Remove("beacons");
}
configuration["beaconIdList"] = beaconIdList;
// 2. Procesar Cameras (si existen)
if (configuration.Contains("cameras") && configuration["cameras"].IsBsonArray)
{
configuration.Remove("cameras");
}
configuration["cameraIdList"] = cameraIdList;
// 3. Procesar RelayList (si existen)
if (configuration.Contains("relayList") && configuration["relayList"].IsBsonArray)
{
configuration.Remove("relayList");
}
configuration["relayIdList"] = relayIdList;
// Actualizar el documento en la base de datos
collection.ReplaceOne(Builders<BsonDocument>.Filter.Eq("_id", doc["_id"]), doc);
}
configuration["beaconIdList"] = beaconIdList;
// 2. Procesar Cameras (si existen)
if (configuration.Contains("cameras") && configuration["cameras"].IsBsonArray)
{
configuration.Remove("cameras");
}
configuration["cameraIdList"] = cameraIdList;
// 3. Procesar RelayList (si existen)
if (configuration.Contains("relayList") && configuration["relayList"].IsBsonArray)
{
configuration.Remove("relayList");
}
configuration["relayIdList"] = relayIdList;
// Actualizar el documento en la base de datos
collection.ReplaceOne(Builders<BsonDocument>.Filter.Eq("_id", doc["_id"]), doc);
}
}
}
@@ -4,6 +4,9 @@ using MongoMigrations.Core;
namespace adas_core.Infrastructure.Migrations.MongoMigrations;
/// <summary>
/// Represents a database migration that updates the LanguageBarrier component to version 0.1.3.
/// </summary>
public class U_0_1_3_UpdateLanguageBarrier : Migration
{
public U_0_1_3_UpdateLanguageBarrier() : base(13)
+281
View File
@@ -0,0 +1,281 @@
# adas-core.Infrastructure — Persistence & Communication Layer
> The **Infrastructure Layer** of the ADAS Core platform.
> Contains concrete implementations of repository contracts, MongoDB mapping configurations, message-bus integrations (RabbitMQ via EasyNetQ), Redis connectivity, and migration tooling. This layer translates between the **Domain** layer and external resources — databases, caches, and messaging brokers.
---
## Table of Contents
1. [Overview](#overview)
2. [Responsibilities](#responsibilities)
3. [Project Structure](#project-structure)
4. [Dependencies](#dependencies)
5. [MongoDB Repositories](#mongodb-repositories)
6. [MongoDB Class Maps](#mongodb-class-maps)
7. [MongoDB Migrations](#mongodb-migrations)
8. [Messaging (RabbitMQ / EasyNetQ)](#messaging-rabbitmq--easynetq)
9. [Redis Integration](#redis-integration)
10. [Infrastructure Utilities](#infrastructure-utilities)
11. [Design Rules](#design-rules)
---
## Overview
`adas-core.Infrastructure` implements the technical capabilities required by the Application and Domain layers. It is the only layer that knows about MongoDB, Redis, and RabbitMQ. Every interaction with external systems flows through this project, isolating the rest of the codebase from vendor-specific APIs.
Key characteristics:
- **Implements Contracts** — Satisfies every interface defined in `adas-core.Application.Repositories/Interfaces`.
- **Persistence Ignorance for Domain** — Domain entities remain POCOs; class-map contributors in `Utils/MongoMaps/` configure BSON serialization externally.
- **Message-Bus Abstraction** — RabbitMQ producer/consumer logic is abstracted behind Application-layer contracts while the concrete wiring lives here.
- **Migration Tooling** — MongoDB schema evolution scripts are versioned and executed by `MongoMigrations.Core`.
- **Host Builder Extensions** — Provides `MongoDbHostBuilderExtension` and `CacheHostBuilderExtension` for streamlined Host startup registration.
---
## Responsibilities
| Concern | What this project does |
|---------|----------------------|
| **Repository Implementation** | Concrete classes for ~40 MongoDB-backed repositories (CRUD, aggregate queries, archive operations). |
| **MongoDB Class Mapping** | `MongoMaps` contributors register BSON discriminators, conventions, and property mappings. |
| **Database Migrations** | Versioned migration scripts that evolve the MongoDB schema without downtime. |
| **Message Publishing** | `PublisherService` dispatches domain events through RabbitMQ via EasyNetQ. |
| **Message Receiving** | `ReceiverService` subscribes to inbound queues and routes messages to Application-layer handlers. |
| **Alert Dispatch** | `SendAlertService` delivers system alerts to external notification channels. |
| **Relay Integration** | `RelayService` communicates with physical relay hardware. |
| **Host Builder Extensions** | Convenience helpers for wiring MongoDB and Redis into the ASP.NET Core host. |
| **Custom BSON Converters** | Entity-specific serialization logic registered with the MongoDB driver. |
---
## Project Structure
```
adas-core.Infrastructure/
├── Repositories/ # ~40 concrete MongoDB repository implementations
│ ├── MongoRepository.cs # Generic base repository
│ ├── UserRepository.cs / PatientRepository.cs
│ ├── AdmissionRepository.cs / DischargeRepository.cs
│ ├── ObservationRepository.cs / ObservationArchiveRepository.cs
│ ├── TreatmentRepository.cs / TreatmentArchiveRepository.cs
│ ├── DiagnosisRepository.cs / DiagnosisArchiveRepository.cs
│ ├── AlarmRepository.cs / DeviceRepository.cs / CameraRepository.cs
│ ├── LightBeaconRepository.cs / RelayRepository.cs
│ ├── PumpStateRepository.cs / PumpObservationRepository.cs
│ ├── PumpArchiveRepository.cs / PumpAlarmEventRepository.cs
│ ├── PumpAlarmStateRepository.cs
│ ├── RecordingAlertRepository.cs / RecordingAlertArchiveRepository.cs
│ ├── DisplayRepository.cs / DisplayConfigRepository.cs
│ ├── DisplayCardConfigRepository.cs / DisplayChartConfigRepository.cs
│ ├── DisplayDetailConfigRepository.cs
│ ├── ConfigObservationRepository.cs
│ ├── ConfigPumpsRepository.cs / ConfigUnitsRepository.cs
│ ├── PointOfCareRepository.cs / PoCMappingRepository.cs
│ ├── PoCSettingsRepository.cs / UnitRepository.cs / SectionRepository.cs
│ ├── NoticeRepository.cs / ServiceConfigRepository.cs
│ ├── MasterListRepository.cs / MedicineRepository.cs
│ ├── PatientCarePlanRepository.cs / ArchivePatientCarePlanRepository.cs
│ ├── AppointmentRepository.cs / AppointmentArchiveRepository.cs
│ ├── PatientArchiveRepository.cs / AuthorityRepository.cs
│ └── HistoricalConfigChangesRepository.cs
├── Services/ # Infrastructure-level services
│ ├── PublisherService.cs # RabbitMQ outbound publishing via EasyNetQ
│ ├── ReceiverService.cs # RabbitMQ inbound subscription via EasyNetQ
│ ├── SendAlertService.cs # Alert delivery to external channels
│ └── RelayService.cs # Physical relay communication
├── Utils/ # Infrastructure helpers and extensions
│ ├── MongoDbHostBuilderExtension.cs
│ ├── CacheHostBuilderExtension.cs
│ ├── MongoUtils.cs
│ ├── TypesUtils.cs
│ ├── RabbitConsumerErrorStrategy.cs
│ ├── RabbitIErrorMessageSerializer.cs
│ ├── PatientObservationConverter.cs
│ ├── PatientObservationAlarmConverter.cs
│ ├── PatientPumpObservationConverter.cs
│ ├── PersonConverter.cs
│ ├── CustomPointOfCareConverter.cs
│ └── MongoMaps/ # ~30 BSON class-map contributors
│ ├── IEntityMapContributor.cs
│ ├── PatientMapContributor.cs / UserMapContributor.cs
│ ├── ObservationMapContributor.cs / AlarmMapContributor.cs
│ ├── DeviceMapContributor.cs
│ ├── DisplayMapContributor.cs / DisplayHomeConfigContributor.cs
│ ├── CardMapContributor.cs / CellMapContributor.cs / RowMapContributor.cs
│ ├── ColorConfigMapContributor.cs / HeaderMapContributor.cs
│ ├── GraphMapContributor.cs / FormMapContributor.cs
│ ├── BannerConfigMapContributor.cs / BoxConfigMapContributor.cs
│ ├── ComunicationFlowMapContributor.cs / CameraContributor.cs
│ ├── BeaconMapContributor.cs / PumpMapContributor.cs
│ ├── RelayContributor.cs / UnitMapContributor.cs / SectionMapContributor.cs
│ ├── PoCMapContributor.cs / ObsUnitMapContributor.cs
│ ├── MasterListMapContributor.cs / NoticeControlMapContributor.cs
│ ├── ServiceConfigMapContributor.cs / StandardMapContributor.cs
│ └── TreatmentMapContributor.cs / AppointmentMapContributor.cs / AuthMapContributor.cs
└── Migrations/
└── MongoMigrations/ # Schema evolution scripts
├── U_0_1_0_UpdateDataPatien.cs
├── U_0_1_1_UpdateDisplayConfigDriver.cs
├── U_0_1_2_UpdatePointOfCareConfig.cs
└── U_0_1_3_UpdateLanguageBarrier.cs
```
---
## Dependencies
### Downstream References
| Project | Role |
|---------|------|
| `adas-core.Domain` | Entities, value objects, enums, DTOs, settings models, and `IDefaultRepository` that this layer persists. |
| `adas-core.Application` | All repository interfaces (`I*Repository`) and service contracts (`IPublisherService`, `ISendAlertService`) implemented in this project. |
| `adas-core.module.LightBeacons` | Module contracts for beacon-specific persistence. |
| `adas-core.module.Relays` | Module contracts for relay-specific persistence. |
### Upstream References (projects that depend on this)
| Project | Reason |
|---------|--------|
| `adas-core` (Host) | Registers all infrastructure services and repositories in the DI container during startup. |
| `adas-core.Test` | Integration tests swap real repositories with in-memory substitutes or exercise the actual MongoDB-backed implementations. |
### NuGet Packages
| Package | Version | Purpose |
|---------|---------|---------|
| `MongoDB.Driver` | latest | MongoDB client for document persistence. |
| `EasyNetQ` | 8.1.4 | Higher-level RabbitMQ abstractions (publisher/consumer services). |
| `StackExchange.Redis` | 2.13.17 | Redis client for caching and distributed locking. |
| `MongoMigrations.Core` | 4.0.15 | Document schema migration framework. |
| `Microsoft.Extensions.Hosting.Abstractions` | 10.0.8 | `IHostedService` and background service registration. |
| `Microsoft.Extensions.Options` | 10.0.8 | Options-pattern configuration binding. |
| `AuditLogs` | 1.0.59 | Audit trail emission from repository write operations. |
## MongoDB Repositories
Every repository in this project implements a corresponding interface from `adas-core.Application.Repositories.Interfaces`. The generic base `MongoRepository<T>` provides CRUD, pagination, and queryable access using the MongoDB C# Driver.
### Base Repository
`MongoRepository<T>` implements `IMongoRepository<T>` and offers:
- `GetByIdAsync(ObjectId)` — single-document lookup by `_id`.
- `GetAllAsync()` — unfiltered collection enumeration.
- `InsertAsync(T)` — atomic insert.
- `UpdateAsync(ObjectId, T)` — full-document replacement.
- `DeleteAsync(ObjectId)` — removal by `_id`.
Specialized repositories override or extend these behaviors for aggregate-specific queries (e.g., `PatientRepository.GetActiveByUnitAsync`, `ObservationRepository.GetRangeAsync`).
### Repository Lifetime
Most repositories are registered as **Singletons** because they are stateless wrappers around a shared `IMongoDatabase`. The exception is `PumpStateRepository`, which may be **Scoped** when per-request pump-session tracking is required.
---
## MongoDB Class Maps
The `Utils/MongoMaps/` folder contains ~30 `IEntityMapContributor` implementations that register BSON mappings at application startup. This keeps the Domain layer free of MongoDB-specific attributes.
```csharp
public interface IEntityMapContributor
{
void Configure();
}
```
| Contributor | Maps |
|-------------|------|
| `PatientMapContributor` | `Patient` entity — location history embedded array, `ObjectId` references to `Person` and `PointOfCare`. |
| `ObservationMapContributor` | Polymorphic observation hierarchy using BSON discriminators. |
| `DisplayMapContributor` | `Display` + `DisplayConfig` nested tree serialization. |
| `PumpMapContributor` | `PumpState`, `PumpAlarmEvent`, `PumpObservation` telemetry documents. |
| `DeviceMapContributor` | Discriminator-based polymorphic `Device` storage. |
| `AuthMapContributor` | `User` + `Authorization` embedded arrays. |
These contributors are invoked during Host startup via `MongoDbHostBuilderExtension`, ensuring all mappings are registered before any repository is accessed.
---
## MongoDB Migrations
Schema evolution is handled by `MongoMigrations.Core`. Each migration is a C# class named with a version prefix (`U_M_m_p_Description`) that executes idempotent update scripts.
| Migration | Purpose |
|-----------|---------|
| `U_0_1_0_UpdateDataPatien` | Corrects patient data inconsistencies post-deployment. |
| `U_0_1_1_UpdateDisplayConfigDriver` | Adds driver metadata to display configuration documents. |
| `U_0_1_2_UpdatePointOfCareConfig` | Introduces PoC-specific configuration fields. |
| `U_0_1_3_UpdateLanguageBarrier` | Adds language-barrier flag to patient records. |
Migrations are executed automatically at Host startup before controllers become available.
---
## Messaging (RabbitMQ / EasyNetQ)
The project integrates RabbitMQ through `EasyNetQ`, which simplifies AMQP operations to a pub/sub model.
| Service | Direction | Purpose |
|---------|-----------|---------|
| `PublisherService` | Outbound | Dispatches domain events (alarm triggered, patient admitted, observation recorded) to configured exchanges. |
| `ReceiverService` | Inbound | Subscribes to topic queues and forwards messages to Application-layer handlers. |
| `RabbitConsumerErrorStrategy` | Error handling | Implements dead-letter queue behavior and exponential backoff for failed deliveries. |
| `RabbitIErrorMessageSerializer` | Serialization | Custom error-body serializer for failed-message auditing. |
Connection strings and exchange topology are configured through `appsettings.json` (`RabbitMQSettings` defined in Domain).
---
## Redis Integration
Redis support is provided via `StackExchange.Redis` and registered through `CacheHostBuilderExtension`. The Infrastructure layer:
- Opens multiplexed connections to the Redis cluster.
- Provides connection management for the `RedisService` and `RedisLockProvider` defined in `adas-core.Application`.
- Registers `IDistributedCache` when available.
Redis is used primarily for:
- Distributed caching of frequently-read reference data (master lists, service configs).
- Distributed locking via `RedisLockProvider` to prevent race conditions on critical operations.
- Session token storage for refresh-token rotation.
---
## Infrastructure Utilities
| Utility | Purpose |
|---------|---------|
| `MongoUtils` | Connection-string building, database factory, and collection accessor helpers. |
| `TypesUtils` | Reflection-based type resolution for generic repository instantiation and converter discovery. |
| `PatientObservationConverter` | Custom BSON serialization for `PatientObservation` polymorphic values. |
| `PersonConverter` | BSON mapping for the `Person` embedded document. |
| `CustomPointOfCareConverter` | Specialized BSON logic for `PointOfCare` nested structures. |
---
## Design Rules
1. **Contracts Only from Upstream** — Infrastructure never defines its own interfaces for cross-cutting concerns. It implements contracts from `Application` or `Domain`.
2. **No Business Logic** — Repositories execute queries and return entities. No validation, no workflow, no decision-making. That belongs in Application.
3. **Technology Concentration** — All MongoDB, RabbitMQ, Redis, SMTP, and HTTP-specific code lives here. Inner layers remain technology-agnostic.
4. **Mapping Isolation** — BSON class maps, EF configurations, and serialization rules are centralized in `MongoMaps/`. No mapping attributes pollute Domain entities.
5. **Connection String Injection** — All external resource connection strings and API keys are injected via `IOptions<T>`; never hardcoded.
6. **Singleton by Default** — Stateless repositories and services are singletons. Scoped lifetimes are reserved only for per-request state.
7. **Migrations Are Idempotent** — Each migration script must be safe to run multiple times. Document versioning guards prevent double application.
8. **Error Translation** — Infrastructure exceptions (Mongo socket errors, RabbitMQ disconnects, SMTP failures) are caught and re-thrown as domain exceptions or logged, never leaked raw to controllers.
9. **No Direct Host Coupling** — Infrastructure is registered by the Host via extension methods (`AddMongoDb`, `AddCache`), but the project does not reference the Host assembly.
10. **Audit Trail Emission** — Write operations that mutate patient data, configurations, or alarms must emit an audit event via the `AuditLogs` package before returning.
---
<p align="center">
Back to <a href="../README.md">adas-core Root README</a>
</p>
@@ -13,23 +13,37 @@ using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repositorio para gestionar las operaciones de la entidad Admission en MongoDB. Proporciona métodos para insertar, actualizar, eliminar y buscar admisiones, así como para manejar opciones de listas maestras relacionadas con las admisiones.
/// Utiliza Serilog para el registro de errores y eventos importantes durante las operaciones de la base de datos.
/// </summary>
public class AdmissionRepository : MongoRepository<Admission>, IAdmissionRepository
{
private readonly ApiSettings _apiSettings;
public AdmissionRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
_apiSettings = apiSettings.Value;
}
/// <summary>
/// Sobrescribe el método GetCollectionName para devolver el nombre de la colección de admisiones en MongoDB, que se obtiene de la configuración de la aplicación a través de ApiSettings.
/// Esto permite que el repositorio se conecte a la colección correcta para realizar las operaciones de base de datos relacionadas con las admisiones.
/// </summary>
/// <returns>El nombre de la colección de admisiones en MongoDB.</returns>
public override string GetCollectionName()
{
return _apiSettings.Admissions;
}
/// <summary>
/// Sobrescribe el método InsertOneAsync para insertar una nueva admisión en la base de datos. Antes de insertar, establece la fecha de admisión (AdmissionDate) a la fecha y hora actual en formato UTC.
/// </summary>
/// <param name="admission">La admisión a insertar en la base de datos.</param>
/// <returns>Una tarea que representa la operación asincrónica.</returns>
public override async Task InsertOneAsync(Admission admission)
{
try
@@ -43,6 +57,12 @@ public class AdmissionRepository : MongoRepository<Admission>, IAdmissionReposit
}
}
/// <summary>
/// Elimina una admisión de la base de datos utilizando su identificador único (ObjectId). El método construye un filtro para encontrar la admisión por su Id y luego utiliza el método DeleteOneAsync para eliminarla.
/// Si ocurre una excepción durante el proceso, se registra un error con Serilog y se vuelve a lanzar la excepción para que pueda ser manejada por el llamador.
/// </summary>
/// <param name="id">El identificador único (ObjectId) de la admisión a eliminar.</param>
/// <returns>Una tarea que representa la operación asincrónica.</returns>
public async Task Delete(ObjectId id)
{
try
@@ -57,6 +77,11 @@ public class AdmissionRepository : MongoRepository<Admission>, IAdmissionReposit
}
}
/// <summary>
/// Actualiza una admisión existente en la base de datos. El método recibe una instancia de Admission con los datos actualizados, construye un filtro para encontrar la admisión por su Id y luego utiliza el método UpdateOneAsync para aplicar los cambios.
/// </summary>
/// <param name="admission">La admisión con los datos actualizados.</param>
/// <returns>Una tarea que representa la operación asincrónica.</returns>
public async Task Update(Admission admission)
{
try
@@ -70,6 +95,13 @@ public class AdmissionRepository : MongoRepository<Admission>, IAdmissionReposit
}
}
/// <summary>
/// Actualiza la ubicación de una admisión existente en la base de datos. El método recibe el identificador único (ObjectId) de la admisión a actualizar y el nuevo identificador de la ubicación (PointOfCareId).
/// Construye un filtro para encontrar la admisión por su Id y luego utiliza el método UpdateOneAsync para establecer el nuevo PointOfCareId.
/// </summary>
/// <param name="id">El identificador único (ObjectId) de la admisión a actualizar.</param>
/// <param name="newLocation">El nuevo identificador de la ubicación (PointOfCareId) de la admisión.</param>
/// <returns>Una tarea que representa la operación asincrónica.</returns>
public async Task UpdateLocation(ObjectId id, ObjectId newLocation)
{
var filterBuilder = Builders<Admission>.Filter;
@@ -81,7 +113,12 @@ public class AdmissionRepository : MongoRepository<Admission>, IAdmissionReposit
await Collection.UpdateOneAsync(filter, update);
}
/// <summary>
/// Actualiza los datos personales de una admisión existente en la base de datos. El método recibe el identificador único (ObjectId) de la admisión a actualizar y una instancia de Person con los nuevos datos personales.
/// </summary>
/// <param name="id">El identificador único (ObjectId) de la admisión a actualizar.</param>
/// <param name="patient">La instancia de Person con los nuevos datos personales.</param>
/// <returns>Una tarea que representa la operación asincrónica.</returns>
public async Task UpdatePatient(ObjectId id, Person patient)
{
var filterBuilder = Builders<Admission>.Filter;
@@ -93,6 +130,11 @@ public class AdmissionRepository : MongoRepository<Admission>, IAdmissionReposit
await Collection.UpdateOneAsync(filter, update);
}
/// <summary>
/// Recupera todas las admisiones almacenadas en la base de datos. El método utiliza el método FindAsync con un filtro vacío para obtener todas las admisiones y luego convierte el resultado a una lista.
/// Si ocurre una excepción durante el proceso, se registra un error con Serilog y se devuelve una lista vacía.
/// </summary>
/// <returns>Una lista de todas las admisiones almacenadas en la base de datos.</returns>
public async Task<IEnumerable<Admission>> FindAll()
{
try
@@ -107,6 +149,13 @@ public class AdmissionRepository : MongoRepository<Admission>, IAdmissionReposit
}
}
/// <summary>
/// Busca una admisión en la base de datos utilizando el número de paciente (patientNumber) y un identificador de unidad (unitId) distinto.
/// El método construye un filtro para encontrar una admisión que coincida con el número de paciente pero que tenga un unitId diferente al proporcionado.
/// </summary>
/// <param name="patientNumber">El número de paciente (NHC) a buscar.</param>
/// <param name="unitId">El identificador de unidad (ObjectId) que debe ser distinto al de la admisión encontrada.</param>
/// <returns>Una tarea que representa la operación asincrónica y devuelve la admisión encontrada o null si no se encuentra ninguna.</returns>
public async Task<Admission?> SearchByPatientNumberAndDistinctUnit(string patientNumber, ObjectId unitId)
{
if (string.IsNullOrWhiteSpace(patientNumber)) return null;
@@ -121,6 +170,11 @@ public class AdmissionRepository : MongoRepository<Admission>, IAdmissionReposit
return patient.FirstOrDefault();
}
/// <summary>
/// Busca una admisión en la base de datos utilizando su identificador único (ObjectId). El método construye un filtro para encontrar la admisión por su Id y luego utiliza el método FindAsync para obtenerla.
/// </summary>
/// <param name="id">El identificador único (ObjectId) de la admisión a buscar.</param>
/// <returns>Una tarea que representa la operación asincrónica y devuelve la admisión encontrada o null si no se encuentra ninguna.</returns>
public async Task<Admission?> FindById(ObjectId id)
{
try
@@ -137,6 +191,11 @@ public class AdmissionRepository : MongoRepository<Admission>, IAdmissionReposit
}
}
/// <summary>
/// Busca una admisión en la base de datos utilizando el número de paciente (NHC). El método construye un filtro para encontrar la admisión por su NHC y luego utiliza el método FindAsync para obtenerla.
/// </summary>
/// <param name="nhc">El número de paciente (NHC) a buscar.</param>
/// <returns>Una tarea que representa la operación asincrónica y devuelve la admisión encontrada o null si no se encuentra ninguna.</returns>
public async Task<Admission?> FindByNhc(string nhc)
{
try
@@ -153,6 +212,12 @@ public class AdmissionRepository : MongoRepository<Admission>, IAdmissionReposit
}
}
/// <summary>
/// Busca admisiones en la base de datos utilizando la ubicación del paciente (PatientLocation).
/// El método construye un filtro para encontrar admisiones que coincidan con la unidad, cama y habitación especificadas en la ubicación del paciente.
/// </summary>
/// <param name="location">La ubicación del paciente (PatientLocation) a buscar.</param>
/// <returns>Una tarea que representa la operación asincrónica y devuelve una lista de admisiones que coinciden con la ubicación especificada.</returns>
public async Task<List<Admission>> FindByLocation(PatientLocation location)
{
try
@@ -172,6 +237,11 @@ public class AdmissionRepository : MongoRepository<Admission>, IAdmissionReposit
}
}
/// <summary>
/// Busca admisiones en la base de datos utilizando el origen de la admisión (Origin). El método construye un filtro para encontrar admisiones que tengan un origen que coincida con el nombre del origen especificado.
/// </summary>
/// <param name="origin">El nombre del origen de la admisión a buscar.</param>
/// <returns>Una tarea que representa la operación asincrónica y devuelve una lista de admisiones que coinciden con el origen especificado.</returns>
public async Task<IEnumerable<Admission>?> FindByOrigin(string origin)
{
try
@@ -191,6 +261,11 @@ public class AdmissionRepository : MongoRepository<Admission>, IAdmissionReposit
}
}
/// <summary>
/// Inserta una nueva admisión en la base de datos y devuelve la admisión insertada. El método establece la fecha de admisión (AdmissionDate) a la fecha y hora actual en formato UTC antes de insertar la admisión.
/// </summary>
/// <param name="origin">La admisión a insertar.</param>
/// <returns>Una tarea que representa la operación asincrónica y devuelve la admisión insertada o null si ocurre un error.</returns>
public async Task<Admission?> InsertOneAsyncAndReturn(Admission origin)
{
try
@@ -205,6 +280,12 @@ public class AdmissionRepository : MongoRepository<Admission>, IAdmissionReposit
}
}
/// <summary>
/// Busca admisiones en la base de datos utilizando el identificador de unidad (unitId) y que no tengan un PointOfCareId asignado.
/// El método construye un filtro para encontrar admisiones que coincidan con el unitId especificado y que tengan un PointOfCareId nulo.
/// </summary>
/// <param name="unitId">El identificador de la unidad a buscar.</param>
/// <returns>Una tarea que representa la operación asincrónica y devuelve una lista de admisiones que coinciden con los criterios especificados.</returns>
public async Task<List<Admission>> GetAdmissionByUnitIdWithOutPoC(ObjectId unitId)
{
try
@@ -221,6 +302,11 @@ public class AdmissionRepository : MongoRepository<Admission>, IAdmissionReposit
}
}
/// <summary>
/// Cuenta el número de admisiones en la base de datos que coinciden con un identificador de unidad (unitId) específico. El método construye un filtro para encontrar admisiones que coincidan con el unitId especificado y luego utiliza el método CountDocumentsAsync para obtener el conteo.
/// </summary>
/// <param name="unitId">El identificador de la unidad a buscar.</param>
/// <returns>Una tarea que representa la operación asincrónica y devuelve el número de admisiones que coinciden con el unitId especificado.</returns>
public async Task<long> CountByUnitId(ObjectId unitId)
{
try
@@ -236,6 +322,12 @@ public class AdmissionRepository : MongoRepository<Admission>, IAdmissionReposit
}
}
/// <summary>
/// Busca admisiones en la base de datos utilizando el identificador de punto de atención (PointOfCareId).
/// El método construye un filtro para encontrar admisiones que coincidan con el PointOfCareId especificado y luego utiliza el método FindAsync para obtenerlas.
/// </summary>
/// <param name="pocId">El identificador del punto de atención a buscar.</param>
/// <returns>Una tarea que representa la operación asincrónica y devuelve una lista de admisiones que coinciden con el PointOfCareId especificado.</returns>
public async Task<List<Admission>> FindByPointOfCareId(ObjectId pocId)
{
try
@@ -252,12 +344,24 @@ public class AdmissionRepository : MongoRepository<Admission>, IAdmissionReposit
}
}
/// <summary>
/// Busca admisiones en la base de datos utilizando una lista de identificadores de unidad (unitIds). El método construye un filtro para encontrar admisiones que tengan un unitId que coincida con cualquiera de los unitIds especificados y luego utiliza el método FindAsync para obtenerlas.
/// </summary>
/// <param name="unitIds">La lista de identificadores de unidad a buscar.</param>
/// <returns>Una tarea que representa la operación asincrónica y devuelve una lista de admisiones que coinciden con los unitIds especificados.</returns>
public async Task<List<Admission>> FindByUnitIds(List<ObjectId> unitIds)
{
var filterUnit = Builders<Admission>.Filter.In("unitId", unitIds);
return await Collection.Find(filterUnit).ToListAsync();
}
/// <summary>
/// Actualiza las opciones de la lista maestra en las admisiones que coinciden con los identificadores de unidad especificados.
/// </summary>
/// <param name="unitIds">La lista de identificadores de unidad a buscar.</param>
/// <param name="opt">La opción de actualización de la lista maestra.</param>
/// <param name="typeName">El nombre del tipo de lista maestra.</param>
/// <returns>Una tarea que representa la operación asincrónica y devuelve una lista de admisiones actualizadas.</returns>
public async Task<IEnumerable<Admission>> UpdateMasterListOption(List<ObjectId> unitIds,
UpdateOptionMasterListDto opt, string typeName)
{
@@ -340,6 +444,14 @@ public class AdmissionRepository : MongoRepository<Admission>, IAdmissionReposit
return new List<Admission>();
}
/// <summary>
/// Elimina las opciones de la lista maestra en las admisiones que coinciden con los identificadores de unidad especificados.
/// El método recibe una lista de identificadores de unidad, una opción de actualización de la lista maestra y el nombre del tipo de lista maestra.
/// </summary>
/// <param name="unitIds">La lista de identificadores de unidad a buscar.</param>
/// <param name="opt">La opción de actualización de la lista maestra.</param>
/// <param name="typeName">El nombre del tipo de lista maestra.</param>
/// <returns>Una tarea que representa la operación asincrónica y devuelve una lista de admisiones actualizadas.</returns>
public async Task<IEnumerable<Admission>> DeleteMasterListOption(List<ObjectId> unitIds, OptionList opt,
string typeName)
{
@@ -427,6 +539,12 @@ public class AdmissionRepository : MongoRepository<Admission>, IAdmissionReposit
return new List<Admission>();
}
/// <summary>
/// Elimina todas las admisiones de la base de datos que coinciden con un identificador de unidad (unitId) específico.
/// El método construye un filtro para encontrar admisiones que coincidan con el unitId especificado y luego utiliza el método DeleteManyAsync para eliminarlas.
/// </summary>
/// <param name="unitId">El identificador de la unidad cuyas admisiones se eliminarán.</param>
/// <returns>Una tarea que representa la operación asincrónica y devuelve un valor booleano que indica si la eliminación fue exitosa.</returns>
public async Task<bool> DeleteAdmissionsByUnitId(ObjectId unitId)
{
try
@@ -442,6 +560,10 @@ public class AdmissionRepository : MongoRepository<Admission>, IAdmissionReposit
}
}
/// <summary>
/// Crea índices en la colección de admisiones para mejorar el rendimiento de las consultas. En este caso, se crea un índice único en el campo "nhc" (número de paciente) que solo se aplica a los documentos que tienen un valor para "nhc".
/// </summary>
/// <returns>Una tarea que representa la operación asincrónica.</returns>
public override async Task CreateIndexes()
{
var optionsUq = new CreateIndexOptions<Admission>
@@ -459,6 +581,11 @@ public class AdmissionRepository : MongoRepository<Admission>, IAdmissionReposit
await MongoUtils.EnsureIndexes(Collection, indexes);
}
/// <summary>
/// Busca admisiones en la base de datos utilizando el diagnóstico de la admisión (Diagnosis). El método construye un filtro para encontrar admisiones que tengan un diagnóstico que coincida con el nombre del diagnóstico especificado y luego utiliza el método FindAsync para obtenerlas.
/// </summary>
/// <param name="diagnosis">El nombre del diagnóstico que se utilizará para buscar admisiones.</param>
/// <returns>Una tarea que representa la operación asincrónica y devuelve una colección de admisiones que coinciden con el diagnóstico especificado.</returns>
public async Task<IEnumerable<Admission>?> FindByDiagnosis(string diagnosis)
{
try
@@ -11,11 +11,30 @@ using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository for managing patient observation alarms in MongoDB. Provides methods to retrieve aggregated patient observations based on specified fields and expiration status.
/// Implements the IAlarmRepository interface and extends the MongoRepository base class for common MongoDB operations.
/// </summary>
public class AlarmRepository : MongoRepository<PatientObservationAlarm>, IAlarmRepository
{
/// <summary>
/// API settings containing configuration for the MongoDB collection name and other relevant settings. Injected via constructor and used to determine the collection name for patient observation alarms.
/// </summary>
private readonly ApiSettings _apiSettings;
/// <summary>
/// Logger instance for logging errors and information related to the AlarmRepository operations. Injected via constructor and used throughout the repository methods to log exceptions and important events.
/// </summary>
private readonly ILogger<AlarmRepository> _logger;
/// <summary>
/// Constructor for the AlarmRepository class. Initializes the repository with the provided API settings, MongoDB database instance, and logger.
/// Validates the input parameters and sets up the necessary configurations for accessing the patient observation alarms collection in MongoDB.
/// </summary>
/// <param name="apiSettings">The API settings containing configuration for the MongoDB collection name and other relevant settings.</param>
/// <param name="database">The MongoDB database instance.</param>
/// <param name="logger">The logger instance for logging errors and information.</param>
/// <exception cref="ArgumentNullException">Thrown when any of the input parameters are null.</exception>
public AlarmRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database, ILogger<AlarmRepository> logger)
: base(database)
{
@@ -24,8 +43,14 @@ public class AlarmRepository : MongoRepository<PatientObservationAlarm>, IAlarmR
_apiSettings = apiSettings.Value;
} //For testing
/// <summary>
/// Retrieves a list of patient observation alarms for a specific patient based on the provided filter criteria.
/// The method allows filtering observations by name and expiration status, and returns the most recent observations for each specified field. If no filter is provided, it retrieves all observations for the patient sorted by time in descending order.
/// </summary>
/// <param name="patientId">The unique identifier of the patient.</param>
/// <param name="filterObservations">A list of fields to filter the observations. If null, all observations for the patient are retrieved.</param>
/// <returns>A list of patient observation alarms matching the filter criteria.</returns>
public async Task<List<PatientObservationAlarm>> AggregatedPatientLastObservationsByField(ObjectId patientId,
List<Field>? filterObservations = null)
{
@@ -86,6 +111,13 @@ public class AlarmRepository : MongoRepository<PatientObservationAlarm>, IAlarmR
}
}
/// <summary>
/// Retrieves a list of patient observation alarms for a specific patient based on the provided filter criteria, including expiration status.
/// </summary>
/// <param name="patientId">The unique identifier of the patient.</param>
/// <param name="filterObservations">A list of fields to filter the observations. If null, all observations for the patient are retrieved.</param>
/// <param name="configAlarm">A list of configuration settings for the observations, including expiration times.</param>
/// <returns>A list of patient observation alarms matching the filter criteria and expiration settings.</returns>
public async Task<List<PatientObservationAlarm>> AggregatedPatientNotExpiredObservationsByField(
ObjectId patientId,
List<Field>? filterObservations,
@@ -155,11 +187,21 @@ public class AlarmRepository : MongoRepository<PatientObservationAlarm>, IAlarmR
}
}
/// <summary>
/// Gets the name of the MongoDB collection for patient observation alarms.
/// The collection name is determined based on the API settings provided during the repository initialization.
/// If the collection name is not specified in the API settings, it defaults to "patients_alarms".
/// </summary>
/// <returns>The name of the MongoDB collection for patient observation alarms.</returns>
public override string GetCollectionName()
{
return _apiSettings.PatientsAlarms ?? "patients_alarms";
}
/// <summary>
/// Creates indexes for the patient observation alarms collection in MongoDB to optimize query performance.
/// </summary>
/// <returns>A task that represents the asynchronous operation of creating indexes.</returns>
public override async Task CreateIndexes()
{
try
@@ -7,31 +7,58 @@ using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository for managing archived patient appointments in MongoDB. This repository provides methods to insert, delete, and manage archived appointments, ensuring efficient storage and retrieval of historical appointment data.
/// </summary>
public class AppointmentArchiveRepository : MongoRepository<PatientAppointment>, IAppointmentArchiveRepository
{
/// <summary>
/// API settings containing configuration for the archive collection, such as the collection name. This allows for flexible configuration and easy adjustments without changing the code.
/// </summary>
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the <see cref="AppointmentArchiveRepository"/> class with the specified API settings and MongoDB database.
/// The constructor ensures that the API settings are provided and initializes the base repository with the given database connection.
/// </summary>
/// <param name="apiSettings">The API settings containing configuration for the archive collection.</param>
/// <param name="database">The MongoDB database instance.</param>
/// <exception cref="ArgumentNullException">Thrown when any of the input parameters are null.</exception>
public AppointmentArchiveRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
_apiSettings = apiSettings.Value;
} //For testing
/// <summary>
/// Inserts a single patient appointment into the archive collection. This method is asynchronous and ensures that the appointment is stored in the MongoDB collection designated for archived appointments.
/// It uses the MongoDB driver to perform the insertion operation efficiently.
/// </summary>
/// <param name="appointment">The patient appointment to be inserted into the archive collection.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public override async Task InsertOneAsync(PatientAppointment appointment)
{
await Collection.InsertOneAsync(appointment);
}
/// <summary>
/// Deletes all patient appointments from the archive collection that were created before the specified date. This method is asynchronous and uses a filter to identify and remove the relevant documents from the MongoDB collection.
/// </summary>
/// <param name="date">The date before which all patient appointments should be deleted.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task DeleteBeforeDate(DateTime date)
{
var filter = Builders<PatientAppointment>.Filter.Lt(pa => pa.CreateTime, date);
await Collection.DeleteManyAsync(filter);
}
/// <summary>
/// Inserts a batch of patient appointments into the archive collection. This method is asynchronous and uses the MongoDB driver's bulk write capabilities to efficiently insert multiple documents in a single operation.
/// It returns the count of inserted documents, allowing for verification of the operation's success.
/// </summary>
/// <param name="appointment">The collection of patient appointments to be inserted into the archive.</param>
/// <returns>A task representing the asynchronous operation, with the result being the count of inserted documents.</returns>
public async Task<long> InsertBatch(IEnumerable<PatientAppointment> appointment)
{
var writes = new List<WriteModel<PatientAppointment>>();
@@ -42,11 +69,20 @@ public class AppointmentArchiveRepository : MongoRepository<PatientAppointment>,
return bulkInsert.InsertedCount;
}
/// <summary>
/// Gets the name of the MongoDB collection used for storing archived patient appointments. This method retrieves the collection name from the API settings, allowing for flexible configuration.
/// If the collection name is not specified in the settings, it defaults to "archive_patients_appointments".
/// </summary>
/// <returns>The name of the MongoDB collection for archived patient appointments.</returns>
public override string GetCollectionName()
{
return _apiSettings.ArchivePatientsAppointments ?? "archive_patients_appointments";
}
/// <summary>
/// Creates indexes on the MongoDB collection for archived patient appointments to optimize query performance. This method defines the necessary indexes, such as an index on the "patientid" field, and ensures that they are created in the background without blocking other operations.
/// </summary>
/// <returns>A task representing the asynchronous operation.</returns>
public override async Task CreateIndexes()
{
var options = new CreateIndexOptions<PatientAppointment> { Background = true, Unique = false };
@@ -9,10 +9,24 @@ using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository for managing patient appointments in a MongoDB database.
/// This class provides methods to perform CRUD operations and queries related to patient appointments, such as retrieving appointments by patient ID, finding appointments by point of care, and updating or deleting appointments.
/// It utilizes the MongoDB driver for database interactions and is configured using application settings for collection names and other parameters.
/// </summary>
public class AppointmentRepository : MongoRepository<PatientAppointment>, IAppointmentRepository
{
/// <summary>
/// Holds the API settings for the repository, including collection names and other configuration parameters.
/// </summary>
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the <see cref="AppointmentRepository"/> class with the specified API settings and MongoDB database.
/// </summary>
/// <param name="apiSettings">The API settings containing configuration for the repository.</param>
/// <param name="database">The MongoDB database instance.</param>
/// <exception cref="ArgumentNullException">Thrown when any of the input parameters are null.</exception>
public AppointmentRepository(
IOptions<ApiSettings> apiSettings,
IMongoDatabase database) : base(database)
@@ -21,13 +35,22 @@ public class AppointmentRepository : MongoRepository<PatientAppointment>, IAppoi
_apiSettings = apiSettings.Value;
} //For testing
/// <summary>
/// Gets the name of the MongoDB collection for patient appointments. This method retrieves the collection name from the API settings, allowing for flexible configuration.
/// If the collection name is not specified in the settings, it defaults to "patients_appointments".
/// </summary>
/// <returns>The name of the MongoDB collection for patient appointments.</returns>
public override string GetCollectionName()
{
return _apiSettings.PatientsAppointments ?? "patients_appointments";
}
/// <summary>
/// Retrieves a list of patient appointments for a given patient ID. This method constructs a filter to query the MongoDB collection based on the patient ID and sorts the results by creation time in descending order.
/// </summary>
/// <param name="patientId">The ID of the patient whose appointments are to be retrieved.</param>
/// <returns>A list of patient appointments for the specified patient ID.</returns>
public async Task<List<PatientAppointment>> GetByPatient(ObjectId patientId)
{
var filter = Builders<PatientAppointment>.Filter.Eq(ob => ob.PatientId, patientId);
@@ -40,6 +63,12 @@ public class AppointmentRepository : MongoRepository<PatientAppointment>, IAppoi
return result.ToList();
}
/// <summary>
/// Finds patient appointments based on a given point of care (PoC). This method constructs a filter to query the MongoDB collection for appointments that match the specified point of care, which includes details such as bed, room, and unit name. It utilizes the FindByLocation method to perform the actual query based on the constructed patient location.
/// </summary>
/// <param name="poc">The point of care details to filter appointments.</param>
/// <returns>A list of patient appointments that match the specified point of care.</returns>
public async Task<List<PatientAppointment>> FindByPoC(PointOfCare poc)
{
var location = new PatientLocation()
@@ -51,39 +80,67 @@ public class AppointmentRepository : MongoRepository<PatientAppointment>, IAppoi
return await FindByLocation(location);
}
/// <summary>
/// Inserts a new patient appointment into the MongoDB collection. This method ensures that the creation time of the appointment is set to the current UTC time if it is not already specified before inserting the appointment into the database using the base class's InsertOneAsync method.
/// </summary>
/// <param name="appointment">The patient appointment to be inserted.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public override async Task InsertOneAsync(PatientAppointment appointment)
{
appointment.CreateTime ??= DateTime.UtcNow;
await base.InsertOneAsync(appointment);
}
/// <summary>
/// Updates an existing patient appointment in the MongoDB collection. This method takes a patient appointment object as input and updates the corresponding document in the database based on the appointment's ID.
/// It uses the UpdateOneAsync method from the base class to perform the update operation.
/// </summary>
/// <param name="appointment">The patient appointment to be updated.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task Update(PatientAppointment appointment)
{
await UpdateOneAsync(appointment.Id, appointment);
}
/// <summary>
/// Deletes a patient appointment from the MongoDB collection based on the appointment's ID. This method constructs a filter to identify the document to be deleted using the appointment's ID and then calls the DeleteOneAsync method to remove the document from the database.
/// </summary>
/// <param name="id">The ID of the patient appointment to be deleted.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public new async Task DeleteAsync(ObjectId id)
{
var filter = Builders<PatientAppointment>.Filter.Eq(t => t.Id, id); // Replace 'T' with your actual class name.
await Collection.DeleteOneAsync(filter);
}
/// <summary>
/// Finds patient appointments based on the patient ID. This method constructs a filter to query the MongoDB collection for appointments that match the specified patient ID and returns an asynchronous cursor to iterate through the results.
/// </summary>
/// <param name="patientId">The ID of the patient whose appointments are to be retrieved.</param>
/// <returns>An asynchronous cursor to iterate through the patient appointments.</returns>
public Task<IAsyncCursor<PatientAppointment>> FindByPatientIdAsync(ObjectId patientId)
{
var filter = Builders<PatientAppointment>.Filter.Eq(ob => ob.PatientId, patientId);
return Collection.FindAsync(filter);
}
/// <summary>
/// Deletes all patient appointments associated with a specific patient ID. This method constructs a filter to identify all documents in the MongoDB collection that match the specified patient ID and then calls the DeleteManyAsync method to remove all matching documents from the database.
/// </summary>
/// <param name="patientId">The ID of the patient whose appointments are to be deleted.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task DeleteByPatientId(ObjectId patientId)
{
var filter = Builders<PatientAppointment>.Filter.Eq(po => po.PatientId, patientId);
await Collection.DeleteManyAsync(filter);
}
/// <summary>
/// Finds a patient appointment based on the patient ID and visit number. This method constructs a filter to query the MongoDB collection for an appointment that matches both the specified patient ID and visit number, and returns the first matching appointment if found, or null if no match is found.
/// </summary>
/// <param name="patientId">The ID of the patient whose appointment is to be retrieved.</param>
/// <param name="visitNumber">The visit number of the appointment to be retrieved.</param>
/// <returns>The first matching patient appointment if found, or null if no match is found.</returns>
public async Task<PatientAppointment?> FindByPatientAndVisitNumber(ObjectId patientId, string visitNumber)
{
var builder = Builders<PatientAppointment>.Filter;
@@ -97,7 +154,12 @@ public class AppointmentRepository : MongoRepository<PatientAppointment>, IAppoi
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Finds a patient appointment based on the patient ID and appointment reason. This method constructs a filter to query the MongoDB collection for an appointment that matches both the specified patient ID and appointment reason, and returns the first matching appointment if found, or null if no match is found.
/// </summary>
/// <param name="patientId">The ID of the patient whose appointment is to be retrieved.</param>
/// <param name="appointmentReason">The reason for the appointment to be retrieved.</param>
/// <returns>The first matching patient appointment if found, or null if no match is found.</returns>
public async Task<PatientAppointment?> FindByPatientAndReason(ObjectId patientId, string? appointmentReason)
{
var builder = Builders<PatientAppointment>.Filter;
@@ -111,6 +173,11 @@ public class AppointmentRepository : MongoRepository<PatientAppointment>, IAppoi
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Finds patient appointments based on a given patient location. This method constructs a filter to query the MongoDB collection for appointments that match the specified patient location, which includes details such as bed, room, and unit name. It uses the Builders class to create a filter that checks for the existence of the Locations field and matches the specified location details within the ResourceGroups of the appointments.
/// </summary>
/// <param name="location">The location details to filter patient appointments by.</param>
/// <returns>A list of patient appointments that match the specified location.</returns>
public async Task<List<PatientAppointment>> FindByLocation(PatientLocation location)
{
var builder = Builders<PatientAppointmentResourceGroup>.Filter;
@@ -126,11 +193,23 @@ public class AppointmentRepository : MongoRepository<PatientAppointment>, IAppoi
return result?.ToList()??[];
}
/// <summary>
/// Updates the ObjectId references in patient appointments when a patient's ID changes. This method constructs a filter to identify all documents in the MongoDB collection that match the specified old patient ID and then updates those documents to reference the new patient ID using the UpdateManyAsync method.
/// </summary>
/// <param name="nameId">The name ID associated with the patient appointments to be updated.</param>
/// <param name="id">The new ObjectId to replace the old patient ID.</param>
/// <param name="oldId">The old ObjectId of the patient whose appointments are to be updated.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId)
{
await UpdateManyObjectIdAsync(nameId, id, oldId);
}
/// <summary>
/// Creates indexes for the patient appointments collection in MongoDB. This method defines a list of indexes to be created, including an index on the patient ID field, and then calls the EnsureIndexes method from the MongoUtils class to ensure that the specified indexes are created in the database.
/// The indexes are created in the background and are not unique.
/// </summary>
/// <returns>A task representing the asynchronous operation.</returns>
public override async Task CreateIndexes()
{
var options = new CreateIndexOptions { Background = true, Unique = false };
@@ -9,18 +9,34 @@ using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository for managing archived patient care plans in a MongoDB database.
/// This class provides methods to perform CRUD operations and queries related to archived patient care plans, such as retrieving care plans by patient ID, inserting new care plans, and creating indexes.
/// It utilizes the MongoDB driver for database interactions and is configured using application settings for collection names and other parameters.
/// </summary>
public class ArchivePatientCarePlanRepository : MongoRepository<PatientCarePlan>, IArchivePatientCarePlanRepository
{
#region Properties
/// <summary>
/// API settings containing configuration for the repository, such as collection names and other relevant parameters.
/// </summary>
private readonly ApiSettings _apiSettings;
/// <summary>
/// Logger for logging information, warnings, and errors related to the operations performed by this repository.
/// </summary>
private readonly ILogger<ArchivePatientCarePlanRepository> _logger;
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="ArchivePatientCarePlanRepository"/> class with the specified API settings, MongoDB database, and logger.
/// </summary>
/// <param name="apiSettings">The API settings containing configuration for the repository.</param>
/// <param name="database">The MongoDB database instance.</param>
/// <param name="logger">The logger for logging information, warnings, and errors.</param>
public ArchivePatientCarePlanRepository(
IOptions<ApiSettings> apiSettings,
IMongoDatabase database,
@@ -30,6 +46,11 @@ public class ArchivePatientCarePlanRepository : MongoRepository<PatientCarePlan>
_apiSettings = apiSettings.Value;
}
/// <summary>
/// Creates indexes for the MongoDB collection associated with this repository.
/// This method ensures that the necessary indexes are created to optimize query performance, particularly for queries based on patient ID. The indexes are created in the background to avoid blocking operations on the database.
/// </summary>
/// <returns></returns>
public override async Task CreateIndexes()
{
var options = new CreateIndexOptions<PatientCarePlan> { Background = true, Unique = false };
@@ -46,6 +67,12 @@ public class ArchivePatientCarePlanRepository : MongoRepository<PatientCarePlan>
#region Create
/// <summary>
/// Inserts a single patient care plan into the MongoDB collection. This method takes a <see cref="PatientCarePlan"/> object as input and attempts to insert it into the database.
/// If an exception occurs during the insertion process, it logs the error with details about the patient and the exception, and then rethrows the exception to be handled by the calling code.
/// </summary>
/// <param name="patient">The patient care plan to be inserted.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public override async Task InsertOneAsync(PatientCarePlan patient)
{
try
@@ -61,6 +88,11 @@ public class ArchivePatientCarePlanRepository : MongoRepository<PatientCarePlan>
}
}
/// <summary>
/// Inserts multiple patient care plans into the MongoDB collection. This method takes a list of <see cref="PatientCarePlan"/> objects as input and attempts to insert them into the database in a single operation.
/// </summary>
/// <param name="patient">The list of patient care plans to be inserted.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public override async Task InsertManyAsync(List<PatientCarePlan> patient)
{
try
@@ -79,17 +111,33 @@ public class ArchivePatientCarePlanRepository : MongoRepository<PatientCarePlan>
#region Read
/// <summary>
/// Gets the name of the MongoDB collection associated with this repository. The collection name is determined based on the API settings provided during the initialization of the repository. If the collection name is not specified in the settings, it defaults to "archive_patients_care_plan".
/// This method is used by the base repository class to determine which collection to interact with for CRUD operations.
/// </summary>
/// <returns></returns>
public override string GetCollectionName()
{
return _apiSettings.ArchivePatientProcedure ?? "archive_patients_care_plan";
}
/// <summary>
/// Finds patient care plans by the specified patient ID. This method takes a patient ID as input and queries the MongoDB collection for care plans associated with that patient ID.
/// It returns a list of <see cref="PatientCarePlan"/> objects that match the query. If no care plans are found, it returns an empty list.
/// </summary>
/// <param name="patientId">The ID of the patient whose care plans are to be retrieved.</param>
/// <returns>A task representing the asynchronous operation, containing a list of <see cref="PatientCarePlan"/> objects.</returns>
public async Task<List<PatientCarePlan>?> FindByPatientId(ObjectId patientId)
{
var result = await Collection.FindAsync(Builders<PatientCarePlan>.Filter.Eq(p => p.PatientId, patientId));
return result.ToList();
}
/// <summary>
/// Finds patient care plans by the specified patient ID, where the patient ID is provided as a string. This method attempts to parse the string into an ObjectId and then queries the MongoDB collection for care plans associated with that patient ID.
/// </summary>
/// <param name="patientId">The ID of the patient whose care plans are to be retrieved, provided as a string.</param>
/// <returns>A task representing the asynchronous operation, containing a list of <see cref="PatientCarePlan"/> objects.</returns>
public async Task<List<PatientCarePlan>?> FindByPatientId(string patientId)
{
var isParsed = ObjectId.TryParse(patientId, out var patientIdParsed);
@@ -98,18 +146,36 @@ public class ArchivePatientCarePlanRepository : MongoRepository<PatientCarePlan>
return result.ToList();
}
public async Task<List<PatientCarePlan>?> FindByPatientNumber(string patientId)
/// <summary>
/// Finds patient care plans by the specified patient number. This method takes a patient number as input and queries the MongoDB collection for care plans associated with that patient number.
/// </summary>
/// <param name="patientNumber">The number of the patient whose care plans are to be retrieved.</param>
/// <returns>A task representing the asynchronous operation, containing a list of <see cref="PatientCarePlan"/> objects.</returns>
public async Task<List<PatientCarePlan>?> FindByPatientNumber(string patientNumber)
{
var result = await Collection.FindAsync(Builders<PatientCarePlan>.Filter.Eq(p => p.PatientNumber, patientId));
var result = await Collection.FindAsync(Builders<PatientCarePlan>.Filter.Eq(p => p.PatientNumber, patientNumber));
return result.ToList();
}
/// <summary>
/// Finds all patient care plans in the MongoDB collection. This method retrieves all documents from the collection and returns them as a list of <see cref="PatientCarePlan"/> objects.
/// </summary>
/// <returns>A task representing the asynchronous operation, containing a list of <see cref="PatientCarePlan"/> objects.</returns>
public async Task<List<PatientCarePlan>> FindAll()
{
var result = await Collection.Find(Builders<PatientCarePlan>.Filter.Empty).ToListAsync();
return result;
}
/// <summary>
/// Finds patient care plans by multiple identifiers, including patient ID, patient number, and patient ID as a string.
/// This method attempts to find care plans using the provided identifiers in a specific order: it first tries to find care plans by the patient ID (as an ObjectId), then by the patient ID (as a string), and finally by the patient number.
/// If any of the queries return results, it returns those results immediately. If no care plans are found using any of the identifiers, it returns an empty list.
/// </summary>
/// <param name="oldPatientId">The ID of the patient whose care plans are to be retrieved, provided as an ObjectId.</param>
/// <param name="oldPatientPatientId">The ID of the patient whose care plans are to be retrieved, provided as a string.</param>
/// <param name="oldPatientPatientNumber">The number of the patient whose care plans are to be retrieved.</param>
/// <returns>A task representing the asynchronous operation, containing a list of <see cref="PatientCarePlan"/> objects.</returns>
public async Task<List<PatientCarePlan>?> FindByIds(ObjectId oldPatientId, string? oldPatientPatientId,
string? oldPatientPatientNumber)
{
@@ -8,6 +8,13 @@ using MongoDB.Driver;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository for managing user authorities and permissions in the system. This repository provides methods to create, retrieve, and delete authorities based on user and unit identifiers.
/// It also includes logging for debugging purposes and ensures that initial data is loaded when necessary.
/// </summary>
/// <param name="apiSettings">The API settings containing configuration for the repository.</param>
/// <param name="database">The MongoDB database instance.</param>
/// <param name="logger">The logger for logging information, warnings, and errors.</param>
public class AuthorityRepository(
IOptions<ApiSettings> apiSettings,
IMongoDatabase database,
@@ -17,6 +24,11 @@ public class AuthorityRepository(
private readonly ApiSettings _apiSettings = apiSettings.Value;
private readonly ILogger _logger = logger;
/// <summary>
/// Creates a new authority for a user with the specified role. This method generates a new display ID for the authority and inserts it into the database. It also logs the creation of the new authority for debugging purposes.
/// </summary>
/// <param name="roleName">The name of the role to be assigned to the new authority.</param>
/// <param name="userId">The ID of the user for whom the new authority is being created.</param>
public void CreateNewAuthority(string roleName, ObjectId userId)
{
var newAuthorization = new Authorization
@@ -30,6 +42,12 @@ public class AuthorityRepository(
_logger.LogDebug("New authority created for user {UserId} with role {RoleName}", userId, roleName);
}
/// <summary>
/// Retrieves a list of authorities associated with a specific user ID. This method queries the database for authorities that match the provided user ID and returns them as a list.
/// It also logs the retrieval of authorities for debugging purposes.
/// </summary>
/// <param name="userId">The ID of the user whose authorities are being retrieved.</param>
/// <returns>A list of authorities associated with the specified user ID.</returns>
public async Task<List<Authorization>> GetUserAuthorities(ObjectId userId)
{
var filter = Builders<Authorization>.Filter.Eq(p => p.UserId, userId);
@@ -38,6 +56,10 @@ public class AuthorityRepository(
return result.ToList();
}
/// <summary>
/// Retrieves a list of all authorities in the system. This method queries the database for all authority records and returns them as a list. It also logs the retrieval of all authorities for debugging purposes.
/// </summary>
/// <returns>A list of all authorities in the system.</returns>
public async Task<List<Authorization>> GetAllAuthorities()
{
var result = await Collection.FindAsync(Builders<Authorization>.Filter.Empty);
@@ -45,6 +67,12 @@ public class AuthorityRepository(
return result.ToList();
}
/// <summary>
/// Retrieves a list of authorities associated with a specific unit ID. This method queries the database for authorities that match the provided unit ID and returns them as a list.
/// It also logs the retrieval of authorities for debugging purposes.
/// </summary>
/// <param name="unitId">The ID of the unit whose authorities are being retrieved.</param>
/// <returns>A list of authorities associated with the specified unit ID.</returns>
public async Task<List<Authorization>> GetByUnitId(ObjectId unitId)
{
var filter = Builders<Authorization>.Filter.Eq(a => a.UnitId, unitId.ToString());
@@ -56,6 +84,12 @@ public class AuthorityRepository(
return result.ToList();
}
/// <summary>
/// Deletes all authorities associated with a specific user ID. This method constructs a filter to identify authorities that match the provided user ID and deletes them from the database.
/// It also logs the deletion of authorities for debugging purposes.
/// </summary>
/// <param name="userId">The ID of the user whose authorities are being deleted.</param>
/// <returns>A boolean value indicating whether the deletion was successful.</returns>
public async Task<bool> DeleteAllAuthoritiesByUser(ObjectId userId)
{
try
@@ -73,7 +107,12 @@ public class AuthorityRepository(
}
}
/// <summary>
/// Deletes all authorities associated with a specific unit ID. This method constructs a filter to identify authorities that match the provided unit ID and deletes them from the database.
/// It also logs the deletion of authorities for debugging purposes.
/// </summary>
/// <param name="unitId">The ID of the unit whose authorities are being deleted.</param>
/// <returns>A boolean value indicating whether the deletion was successful.</returns>
public async Task<bool> DeleteAllAuthoritiesByUnit(ObjectId unitId)
{
try
@@ -89,6 +128,12 @@ public class AuthorityRepository(
}
}
/// <summary>
/// Deletes all authorities associated with a specific display ID. This method constructs a filter to identify authorities that match the provided display ID and deletes them from the database.
/// It also logs the deletion of authorities for debugging purposes.
/// </summary>
/// <param name="displayId">The ID of the display whose authorities are being deleted.</param>
/// <returns>A boolean value indicating whether the deletion was successful.</returns>
public async Task<bool> DeleteAllAuthoritiesByDisplay(ObjectId displayId)
{
try
@@ -104,17 +149,33 @@ public class AuthorityRepository(
}
}
/// <summary>
/// Retrieves the name of the collection used for storing authorities in the database. This method returns the collection name as specified in the API settings configuration.
/// </summary>
/// <returns></returns>
public override string GetCollectionName()
{
return _apiSettings.Authorizations;
}
/// <summary>
/// Retrieves an authority by its unique identifier. This method constructs a filter to identify the authority that matches the provided ID and retrieves it from the database.
/// It also logs the retrieval of the authority for debugging purposes.
/// </summary>
/// <param name="authId">The unique identifier of the authority to be retrieved.</param>
/// <returns>The authority that matches the provided ID, or null if no matching authority is found.</returns>
public async Task<Authorization> GetById(ObjectId authId)
{
var filter = Builders<Authorization>.Filter.Eq(p => p.Id, authId);
return await Collection.Find(filter).FirstOrDefaultAsync();
}
/// <summary>
/// Inserts initial data into the database for authorities. This method checks if a system user exists and if they do, it ensures that they have the necessary panel authorization.
/// If the system user does not have panel authorization, it creates a new authority for them with the appropriate permissions.
/// This method is typically called during the initial setup of the application to ensure that essential data is present in the database.
/// </summary>
/// <returns>A task representing the asynchronous operation.</returns>
public sealed override async Task InsertInitialLoad()
{
var user = Db.GetCollection<User>(apiSettings.Value.Users);
@@ -12,21 +12,40 @@ using System.Text.RegularExpressions;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository for managing camera entities in the MongoDB database. This repository provides methods to create, retrieve, update, and search for cameras based on various criteria.
/// It also includes error handling and logging for debugging purposes.
/// </summary>
public class CameraRepository : MongoRepository<Camera>, ICameraRepository
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the CameraRepository class with the specified MongoDB database and API settings.
/// The constructor sets up the repository to interact with the "Cameras" collection in the database and allows for configuration through the provided API settings.
/// </summary>
/// <param name="database">The MongoDB database instance.</param>
/// <param name="apiSettings">The API settings containing configuration for the repository.</param>
public CameraRepository(IMongoDatabase database, IOptions<ApiSettings> apiSettings) : base(database)
{
_apiSettings = apiSettings.Value;
}
/// <summary>
/// Gets the name of the MongoDB collection that this repository interacts with. In this case, it returns the collection name for cameras as specified in the API settings.
/// </summary>
/// <returns>The name of the MongoDB collection for cameras.</returns>
public override string GetCollectionName()
{
return _apiSettings.Cameras;
}
/// <summary>
/// Retrieves a camera entity from the MongoDB database based on its unique identifier. The method takes an ObjectId as a parameter and returns the corresponding Camera object if found, or null if no matching camera is found.
/// It also includes error handling to log any exceptions that occur during the retrieval process.
/// </summary>
/// <param name="cameraId">The unique identifier of the camera to retrieve.</param>
/// <returns>The Camera object if found; otherwise, null.</returns>
public async Task<Camera?> GetById(ObjectId cameraId)
{
try
@@ -42,6 +61,11 @@ public class CameraRepository : MongoRepository<Camera>, ICameraRepository
}
}
/// <summary>
/// Retrieves a camera entity from the MongoDB database based on its name. The method takes a string parameter representing the name of the camera and returns the corresponding Camera object if found, or null if no matching camera is found.
/// </summary>
/// <param name="name">The name of the camera to retrieve.</param>
/// <returns>The Camera object if found; otherwise, null.</returns>
public async Task<Camera?> GetByName(string name)
{
var filterBuilder = Builders<Camera>.Filter;
@@ -51,6 +75,12 @@ public class CameraRepository : MongoRepository<Camera>, ICameraRepository
return await Collection.Find(filter).FirstOrDefaultAsync();
}
/// <summary>
/// Retrieves a list of camera entities from the MongoDB database based on a list of unique identifiers.
/// The method takes a list of ObjectId values representing the camera IDs and returns a list of Camera objects that match any of the provided IDs.
/// </summary>
/// <param name="configurationRelayList">The list of camera IDs to retrieve.</param>
/// <returns>A list of Camera objects that match the provided IDs.</returns>
public List<Camera> GetCameraInList(List<ObjectId> configurationRelayList)
{
var filterBuilder = Builders<Camera>.Filter;
@@ -61,6 +91,13 @@ public class CameraRepository : MongoRepository<Camera>, ICameraRepository
return Collection.Find(filter).ToList();
}
/// <summary>
/// Retrieves a paginated list of camera entities from the MongoDB database based on the provided pagination filter.
/// The method takes a PaginationFilter object as a parameter, which contains information about the page number, page size, and any additional filtering criteria.
/// </summary>
/// <param name="filter">The pagination filter containing page number, page size, and any additional filtering criteria.</param>
/// <returns>A paginated list of Camera objects.</returns>
/// <exception cref="BadRequestException">Thrown when the provided filter is invalid or contains invalid data.</exception>
public IFindFluent<Camera, Camera> GetPaginatedCameras(PaginationFilter filter)
{
var filterBuilder = Builders<Camera>.Filter;
@@ -90,6 +127,12 @@ public class CameraRepository : MongoRepository<Camera>, ICameraRepository
return CreateFindFluent(filters, sort);
}
/// <summary>
/// Inserts a new camera entity into the MongoDB database. The method takes a Camera object as a parameter and attempts to insert it into the collection.
/// </summary>
/// <param name="camera">The Camera object to insert into the database.</param>
/// <returns>The inserted Camera object if successful; otherwise, null.</returns>
public async Task<Camera?> InsertOneCamera(Camera camera)
{
try
@@ -106,6 +149,12 @@ public class CameraRepository : MongoRepository<Camera>, ICameraRepository
}
}
/// <summary>
/// Updates an existing camera entity in the MongoDB database based on its unique identifier. The method takes an ObjectId representing the camera ID and a Camera object containing the updated information.
/// </summary>
/// <param name="objectId">The unique identifier of the camera to update.</param>
/// <param name="camera">The Camera object containing the updated information.</param>
/// <returns>The updated Camera object if successful; otherwise, null.</returns>
public async Task<Camera?> UpdateCameraAsync(ObjectId objectId, Camera camera)
{
var filter = Builders<Camera>.Filter.Eq("_id", objectId);
@@ -122,6 +171,13 @@ public class CameraRepository : MongoRepository<Camera>, ICameraRepository
new FindOneAndUpdateOptions<Camera, Camera> { ReturnDocument = ReturnDocument.After });
}
/// <summary>
/// Searches for camera entities in the MongoDB database based on a text string that matches the camera's name.
/// The method takes a string parameter representing the text to search for and returns a list of Camera objects whose names match the search criteria.
/// </summary>
/// <param name="textToSearch">The text string to search for in the camera names.</param>
/// <returns>A list of Camera objects whose names match the search criteria.</returns>
/// <exception cref="BadRequestException">Thrown when the search text is too long.</exception>
public async Task<List<Camera>> GetSearchByNameCameras(string textToSearch)
{
if (string.IsNullOrWhiteSpace(textToSearch))
@@ -140,6 +196,12 @@ public class CameraRepository : MongoRepository<Camera>, ICameraRepository
return await Collection.Find(filter).ToListAsync();
}
/// <summary>
/// Creates an IFindFluent object for querying the MongoDB collection based on a list of filter definitions and a sort definition.
/// </summary>
/// <param name="filters">A list of filter definitions to apply to the query.</param>
/// <param name="sort">A sort definition to apply to the query results.</param>
/// <returns>An IFindFluent object for further query customization or execution.</returns>
private IFindFluent<Camera, Camera> CreateFindFluent(List<FilterDefinition<Camera>> filters, SortDefinition<Camera> sort)
{
var combinedFilter = filters.Any()
@@ -12,41 +12,72 @@ using System.Text.RegularExpressions;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repositorio para manejar las operaciones CRUD de ConfigObservation en MongoDB.
/// </summary>
public class ConfigObservationRepository : MongoRepository<ConfigObservation>, IConfigObservationRepository
{
private readonly ApiSettings _apiSettings;
private readonly IMasterListServiceFactory _masterListServiceFactory;
/// <summary>
/// Constructor para ConfigObservationRepository, inyecta las dependencias necesarias.
/// </summary>
/// <param name="apiSettings">Configuración de la API.</param>
/// <param name="database">Instancia de la base de datos MongoDB.</param>
/// <param name="masterListServiceFactory">Fábrica de servicios de lista maestra.</param>
public ConfigObservationRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database,
IMasterListServiceFactory masterListServiceFactory) : base(database)
{
_masterListServiceFactory = masterListServiceFactory;
_apiSettings = apiSettings.Value;
}
/// <summary>
/// Obtiene el nombre de la colección de MongoDB para ConfigObservation, utilizando la configuración proporcionada o un valor predeterminado.
/// </summary>
/// <returns>El nombre de la colección de MongoDB para ConfigObservation.</returns>
public override string GetCollectionName()
{
return _apiSettings.ConfigObservations ?? "config_observations";
}
/// <summary>
/// Busca un ConfigObservation por su ID en la base de datos MongoDB.
/// </summary>
/// <param name="id">El ID del ConfigObservation a buscar.</param>
/// <returns>El ConfigObservation encontrado, o null si no se encuentra.</returns>
public async Task<ConfigObservation?> FindById(ObjectId id)
{
var result = await Collection.FindAsync(Builders<ConfigObservation>.Filter.Eq(x => x.Id, id));
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Actualiza un ConfigObservation existente en la base de datos MongoDB.
/// </summary>
/// <param name="configObservation">El ConfigObservation con los datos actualizados.</param>
/// <returns>El ConfigObservation actualizado, o null si no se encuentra.</returns>
public async Task<ConfigObservation?> Update(ConfigObservation configObservation)
{
await UpdateOneAsync(configObservation.Id, configObservation);
return configObservation;
}
/// <summary>
/// Elimina un ConfigObservation por su ID en la base de datos MongoDB.
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<ConfigObservation?> Delete(ObjectId id)
{
return await DeleteAsync(id);
}
/// <summary>
/// Busca todos los IDs de ConfigObservation en la base de datos MongoDB.
/// </summary>
/// <returns>Una lista de todos los IDs de ConfigObservation.</returns>
public async Task<List<ObjectId>> FindAllIds()
{
var allCollection = await Collection.FindAsync(_ => true);
@@ -54,12 +85,21 @@ public class ConfigObservationRepository : MongoRepository<ConfigObservation>, I
return allCollection.ToList().Select(item => item.Id).ToList();
}
/// <summary>
/// Busca todos los ConfigObservation en la base de datos MongoDB.
/// </summary>
/// <returns>Una colección de todos los ConfigObservation.</returns>
public async Task<ICollection<ConfigObservation>> FindAll()
{
var result = await Collection.FindAsync(_ => true);
return await result.ToListAsync();
}
/// <summary>
/// Busca todos los nombres de ConfigObservation en la base de datos MongoDB, eliminando duplicados y espacios en blanco.
/// </summary>
/// <param name="id">El ID del ConfigObservation para filtrar los nombres (opcional).</param>
/// <returns>Una lista de nombres de ConfigObservation.</returns>
public async Task<List<string>> GetConfigNames(string id)
{
var allConfigs = await Collection.Find(_ => true).ToListAsync();
@@ -73,6 +113,10 @@ public class ConfigObservationRepository : MongoRepository<ConfigObservation>, I
return distinctNames;
}
/// <summary>
/// Busca todos los nombres de ConfigObservation en la base de datos MongoDB, eliminando duplicados y espacios en blanco.
/// </summary>
/// <returns></returns>
public async Task<List<string>> GetConfigNames()
{
var allConfigs = await Collection.Find(_ => true).ToListAsync();
@@ -86,6 +130,10 @@ public class ConfigObservationRepository : MongoRepository<ConfigObservation>, I
return distinctNames;
}
/// <summary>
/// Cuenta el número total de ConfigObservation en la base de datos MongoDB.
/// </summary>
/// <returns>El número total de ConfigObservation.</returns>
public async Task<long> Count()
{
var filter = Builders<ConfigObservation>.Filter.Empty;
@@ -93,6 +141,12 @@ public class ConfigObservationRepository : MongoRepository<ConfigObservation>, I
return result;
}
/// <summary>
/// Busca ConfigObservation en la base de datos MongoDB utilizando paginación y un filtro de texto opcional que busca en varios campos.
/// </summary>
/// <param name="filter">El filtro de paginación y búsqueda.</param>
/// <returns>Una colección de ConfigObservation que cumple con los criterios de búsqueda y paginación.</returns>
/// <exception cref="BadRequestException">Se lanza cuando el texto de búsqueda es demasiado largo.</exception>
public async Task<ICollection<ConfigObservation>> GetPaginatedItems(PaginationFilter filter)
{
var builder = Builders<ConfigObservation>.Filter;
@@ -135,6 +189,13 @@ public class ConfigObservationRepository : MongoRepository<ConfigObservation>, I
return items;
}
/// <summary>
/// Busca un ConfigObservation por su nombre en la base de datos MongoDB, utilizando una búsqueda insensible a mayúsculas y minúsculas.
/// </summary>
/// <param name="name">El nombre del ConfigObservation a buscar.</param>
/// <returns>El ConfigObservation que coincide con el nombre proporcionado, o null si no se encuentra.</returns>
/// <exception cref="BadRequestException">Se lanza cuando el nombre es nulo, vacío o demasiado largo.</exception>
public async Task<ConfigObservation?> FindByName(string name)
{
if (string.IsNullOrWhiteSpace(name))
@@ -155,7 +216,12 @@ public class ConfigObservationRepository : MongoRepository<ConfigObservation>, I
.FirstOrDefaultAsync();
}
/// <summary>
/// Busca un ConfigObservation por su sistema de codificación y código en la base de datos MongoDB, utilizando una búsqueda insensible a mayúsculas y minúsculas.
/// </summary>
/// <param name="codingSystem">El sistema de codificación del ConfigObservation a buscar.</param>
/// <param name="code">El código del ConfigObservation a buscar.</param>
/// <returns>El ConfigObservation que coincide con el sistema de codificación y código proporcionados, o null si no se encuentra.</returns>
public async Task<ConfigObservation?> GetByCodeSysAndCode(string? codingSystem, string? code)
{
var builder = Builders<ConfigObservation>.Filter;
@@ -174,12 +240,23 @@ public class ConfigObservationRepository : MongoRepository<ConfigObservation>, I
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Inserta un nuevo ConfigObservation en la base de datos MongoDB y devuelve el objeto insertado, incluyendo su ID generado.
/// </summary>
/// <param name="configObservationItem">El ConfigObservation a insertar en la base de datos.</param>
/// <returns>El ConfigObservation insertado, incluyendo su ID generado.</returns>
public async Task<ConfigObservation> InsertOneAsyncAndReturn(ConfigObservation configObservationItem)
{
await Collection.InsertOneAsync(configObservationItem);
return configObservationItem;
}
/// <summary>
/// Busca todos los ConfigObservation que coinciden con un nombre específico en la base de datos MongoDB, utilizando una búsqueda insensible a mayúsculas y minúsculas.
/// </summary>
/// <param name="name">El nombre del ConfigObservation a buscar.</param>
/// <returns>Una lista de ConfigObservation que coinciden con el nombre proporcionado.</returns>
public async Task<List<ConfigObservation>> FindAllByName(string name)
{
var builder = Builders<ConfigObservation>.Filter;
@@ -194,6 +271,14 @@ public class ConfigObservationRepository : MongoRepository<ConfigObservation>, I
return await result.ToListAsync();
}
/// <summary>
/// Busca un ConfigObservation que coincida con los parámetros proporcionados (código, sistema de codificación, nombre u nombre original) en la base de datos MongoDB, utilizando una búsqueda insensible a mayúsculas y minúsculas.
/// </summary>
/// <param name="code">El código del ConfigObservation a buscar.</param>
/// <param name="codingSystem">El sistema de codificación del ConfigObservation a buscar.</param>
/// <param name="name">El nombre del ConfigObservation a buscar.</param>
/// <param name="originalName">El nombre original del ConfigObservation a buscar.</param>
/// <returns>El ConfigObservation que coincide con los parámetros proporcionados, o null si no se encuentra.</returns>
public async Task<ConfigObservation?> GetSingleConfigObservationItem(string? code, string? codingSystem,
string? name, string? originalName)
{
@@ -211,6 +296,11 @@ public class ConfigObservationRepository : MongoRepository<ConfigObservation>, I
.FirstOrDefaultAsync();
}
/// <summary>
/// Inserta una carga inicial de ConfigObservation en la base de datos MongoDB utilizando una lista maestra de observaciones de enfermería.
/// Si ya existen ConfigObservation en la base de datos, solo se insertan aquellos que faltan en comparación con la lista maestra.
/// </summary>
/// <returns></returns>
public sealed override async Task InsertInitialLoad()
{
var stringNurseObs = _masterListServiceFactory.StringNurseObs();
@@ -6,23 +6,39 @@ using MongoDB.Driver;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository for managing ConfigPumps in MongoDB. Provides methods to retrieve, update, and delete pump configurations.
/// </summary>
public class ConfigPumpsRepository : MongoRepository<ConfigPumps>, IConfigPumpsRepository
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the ConfigPumpsRepository class with the specified API settings and MongoDB database.
/// </summary>
/// <param name="apiSettings">The API settings.</param>
/// <param name="database">The MongoDB database instance.</param>
/// <exception cref="ArgumentNullException">Thrown when apiSettings is null.</exception>
public ConfigPumpsRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
_apiSettings = apiSettings.Value;
} //For testing
/// <summary>
/// Gets the name of the MongoDB collection for ConfigPumps. Uses the value from API settings or defaults to "config_pumps".
/// </summary>
/// <returns>The name of the MongoDB collection for ConfigPumps.</returns>
public override string GetCollectionName()
{
return _apiSettings.ConfigPumps ?? "config_pumps";
}
/// <summary>
/// Retrieves all ConfigPumps documents from the MongoDB collection. Returns a list of ConfigPumps or null if no documents are found.
/// </summary>
/// <returns>A list of ConfigPumps or null if no documents are found.</returns>
public async Task<List<ConfigPumps>?> GetAllConfigs()
{
var result = await Collection.FindAsync(Builders<ConfigPumps>.Filter.Empty);
@@ -30,6 +46,11 @@ public class ConfigPumpsRepository : MongoRepository<ConfigPumps>, IConfigPumpsR
return result.ToList();
}
/// <summary>
/// Finds a ConfigPumps document by its unique identifier. Returns the ConfigPumps if found, or null if not found.
/// </summary>
/// <param name="id">The unique identifier of the ConfigPumps document.</param>
/// <returns>The ConfigPumps document if found, or null if not found.</returns>
public async Task<ConfigPumps?> FindById(string id)
{
var result = await Collection.FindAsync(Builders<ConfigPumps>.Filter.Eq(x => x.Id, id));
@@ -37,6 +58,13 @@ public class ConfigPumpsRepository : MongoRepository<ConfigPumps>, IConfigPumpsR
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Updates an existing ConfigPumps document in the MongoDB collection.
/// The method takes a ConfigPumps object, updates the corresponding document based on its Id, and returns the updated ConfigPumps.
/// If the document is not found, it returns null.
/// </summary>
/// <param name="config">The ConfigPumps object containing the updated data.</param>
/// <returns>The updated ConfigPumps document if found, or null if not found.</returns>
public async Task<ConfigPumps?> UpdateConfig(ConfigPumps config)
{
var filter = Builders<ConfigPumps>.Filter.Eq("_id", config.Id);
@@ -46,6 +74,12 @@ public class ConfigPumpsRepository : MongoRepository<ConfigPumps>, IConfigPumpsR
new FindOneAndUpdateOptions<ConfigPumps, ConfigPumps> { ReturnDocument = ReturnDocument.After });
}
/// <summary>
/// Deletes a ConfigPumps document from the MongoDB collection based on its Id.
/// The method takes a ConfigPumps object, deletes the corresponding document, and returns true if the deletion was successful (i.e., the document no longer exists), or false if the document still exists after the deletion attempt.
/// </summary>
/// <param name="config">The ConfigPumps object to be deleted.</param>
/// <returns>True if the deletion was successful, false otherwise.</returns>
public async Task<bool> DeleteConfig(ConfigPumps config)
{
var filter = Builders<ConfigPumps>.Filter.Eq("_id", config.Id);
@@ -6,24 +6,40 @@ using MongoDB.Driver;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository for managing ConfigUnits in MongoDB. Provides methods to retrieve and manipulate ConfigUnits data.
/// </summary>
public class ConfigUnitsRepository : MongoRepository<ConfigUnits>, IConfigUnitsRepository
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the ConfigUnitsRepository class with the specified API settings and MongoDB database.
/// </summary>
/// <param name="apiSettings">The API settings.</param>
/// <param name="database">The MongoDB database.</param>
/// <exception cref="ArgumentNullException">Thrown when apiSettings is null.</exception>
public ConfigUnitsRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
_apiSettings = apiSettings.Value;
} //For testing
/// <summary>
/// Gets the name of the MongoDB collection for ConfigUnits. This method retrieves the collection name from the API settings, or defaults to "config_units" if not specified.
/// </summary>
/// <returns>The name of the MongoDB collection for ConfigUnits.</returns>
public override string GetCollectionName()
{
return _apiSettings.ConfigUnits ?? "config_units";
}
/// <summary>
/// Finds a ConfigUnits document by its unique identifier. This method queries the MongoDB collection for a document with the specified ID and returns it if found, or null if not found.
/// </summary>
/// <param name="id">The unique identifier of the ConfigUnits document.</param>
/// <returns>The ConfigUnits document if found; otherwise, null.</returns>
public async Task<ConfigUnits?> FindById(string id)
{
var resutl = await Collection.FindAsync(Builders<ConfigUnits>.Filter.Eq(x => x.Id, id));
@@ -8,20 +8,38 @@ using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository for managing Device entities in MongoDB. Provides methods for finding devices by various attributes and updating device statistics.
/// </summary>
public class DeviceRepository : MongoRepository<Device>, IDeviceRepository
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the DeviceRepository class with the specified API settings and MongoDB database.
/// </summary>
/// <param name="apiSettings">The API settings.</param>
/// <param name="database">The MongoDB database.</param>
public DeviceRepository(ApiSettings apiSettings, IMongoDatabase database) : base(database)
{
_apiSettings = apiSettings;
}
/// <summary>
/// Gets the name of the MongoDB collection for devices. The collection name is determined by the API settings, with a default value of "devices" if not specified.
/// </summary>
/// <returns>The name of the MongoDB collection for devices.</returns>
public override string GetCollectionName()
{
return _apiSettings.Devices ?? "devices";
}
/// <summary>
/// Creates indexes for the Device collection in MongoDB.
/// This method ensures that indexes are created for the MacAddr, SerialNumber, Uuid, and Key fields to optimize query performance.
/// The MacAddr index is unique, while the others are non-unique and created in the background.
/// </summary>
/// <returns>A task representing the asynchronous operation.</returns>
public override async Task CreateIndexes()
{
var options = new CreateIndexOptions { Background = true, Unique = true };
@@ -36,6 +54,11 @@ public class DeviceRepository : MongoRepository<Device>, IDeviceRepository
await MongoUtils.EnsureIndexes(Collection, indexes);
}
/// <summary>
/// Finds a device by its MAC address. This method queries the MongoDB collection for a device with the specified MAC address and returns the first matching device, or null if no match is found.
/// </summary>
/// <param name="deviceDtoMacAddr">The MAC address of the device to find.</param>
/// <returns>The device with the specified MAC address, or null if not found.</returns>
public async Task<Device?> FindByMacAddr(string deviceDtoMacAddr)
{
return await Collection
@@ -43,6 +66,11 @@ public class DeviceRepository : MongoRepository<Device>, IDeviceRepository
.FirstOrDefaultAsync();
}
/// <summary>
/// Finds a device by its serial number. This method queries the MongoDB collection for a device with the specified serial number and returns the first matching device, or null if no match is found.
/// </summary>
/// <param name="deviceDtoSerialNumber">The serial number of the device to find.</param>
/// <returns>The device with the specified serial number, or null if not found.</returns>
public async Task<Device?> FindBySerialNumber(string deviceDtoSerialNumber)
{
return await Collection
@@ -50,6 +78,11 @@ public class DeviceRepository : MongoRepository<Device>, IDeviceRepository
.FirstOrDefaultAsync();
}
/// <summary>
/// Finds a device by its UUID. This method queries the MongoDB collection for a device with the specified UUID and returns the first matching device, or null if no match is found.
/// </summary>
/// <param name="deviceDtoUuid">The UUID of the device to find.</param>
/// <returns>The device with the specified UUID, or null if not found.</returns>
public async Task<Device?> FindByUuid(string deviceDtoUuid)
{
return await Collection
@@ -57,6 +90,11 @@ public class DeviceRepository : MongoRepository<Device>, IDeviceRepository
.FirstOrDefaultAsync();
}
/// <summary>
/// Finds a device by its key. This method queries the MongoDB collection for a device with the specified key and returns the first matching device, or null if no match is found.
/// </summary>
/// <param name="deviceDtoKey">The key of the device to find.</param>
/// <returns>The device with the specified key, or null if not found.</returns>
public async Task<Device?> FindByKey(string deviceDtoKey)
{
return await Collection
@@ -64,6 +102,13 @@ public class DeviceRepository : MongoRepository<Device>, IDeviceRepository
.FirstOrDefaultAsync();
}
/// <summary>
/// Updates the statistics of a device. This method takes the device ID and an existing DeviceDto object, and updates the corresponding fields (Battery, Connected, Ready, Name) in the MongoDB collection for the device with the specified ID.
/// The UpdatedAt field is also set to the current UTC time.
/// </summary>
/// <param name="id">The ID of the device to update.</param>
/// <param name="deviceExist">The existing DeviceDto object containing the updated statistics.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task UpdateDeviceStats(ObjectId id, DeviceDto deviceExist)
{
var update = Builders<Device>.Update
@@ -7,28 +7,54 @@ using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository for managing archived patient diagnoses in MongoDB. This repository provides methods for inserting, deleting, and indexing patient diagnosis records in the archive collection.
/// </summary>
public class DiagnosisArchiveRepository : MongoRepository<PatientDiagnosis>, IDiagnosisArchiveRepository
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the <see cref="DiagnosisArchiveRepository"/> class with the specified API settings and MongoDB database.
/// The API settings are used to determine the collection name for storing archived patient diagnoses.
/// </summary>
/// <param name="apiSettings"></param>
/// <param name="database"></param>
/// <exception cref="ArgumentNullException"></exception>
public DiagnosisArchiveRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
_apiSettings = apiSettings.Value;
} //For testing
/// <summary>
/// Inserts a single patient diagnosis record into the archive collection asynchronously.
/// This method uses the MongoDB driver to perform the insertion operation and ensures that the record is added to the correct collection as defined in the API settings.
/// </summary>
/// <param name="patientDiagnosis">The patient diagnosis record to insert.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public override async Task InsertOneAsync(PatientDiagnosis patientDiagnosis)
{
await Collection.InsertOneAsync(patientDiagnosis);
}
/// <summary>
/// Deletes all patient diagnosis records from the archive collection that have a timestamp earlier than the specified date.
/// </summary>
/// <param name="date">The date before which patient diagnosis records should be deleted.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task DeleteBeforeDate(DateTime date)
{
var filter = Builders<PatientDiagnosis>.Filter.Lt(po => po.Time, date);
await Collection.DeleteManyAsync(filter);
}
/// <summary>
/// Inserts a batch of patient diagnosis records into the archive collection asynchronously.
/// This method uses the MongoDB driver's bulk write functionality to efficiently insert multiple records in a single operation, improving performance when dealing with large datasets.
/// </summary>
/// <param name="observations">The collection of patient diagnosis records to insert.</param>
/// <returns>The number of records successfully inserted.</returns>
public async Task<long> InsertBatch(IEnumerable<PatientDiagnosis> observations)
{
var writes = new List<WriteModel<PatientDiagnosis>>();
@@ -39,11 +65,21 @@ public class DiagnosisArchiveRepository : MongoRepository<PatientDiagnosis>, IDi
return bulkInsert.InsertedCount;
}
/// <summary>
/// Gets the name of the MongoDB collection used for storing archived patient diagnoses.
/// The collection name is determined based on the API settings, allowing for flexibility in configuration.
/// If the API settings do not specify a collection name, a default name of "archive_patients_diagnosis" is used.
/// </summary>
/// <returns>The name of the MongoDB collection for archived patient diagnoses.</returns>
public override string GetCollectionName()
{
return _apiSettings.ArchivePatientsDiagnosis ?? "archive_patients_diagnosis";
}
/// <summary>
/// Creates indexes on the MongoDB collection for archived patient diagnoses to optimize query performance.
/// </summary>
/// <returns>A task representing the asynchronous operation.</returns>
public override async Task CreateIndexes()
{
var options = new CreateIndexOptions<PatientDiagnosis> { Background = true, Unique = false };
@@ -8,23 +8,39 @@ using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository for managing patient diagnoses in a MongoDB collection. Provides methods for CRUD operations and querying diagnoses by patient ID and code.
/// </summary>
public class DiagnosisRepository : MongoRepository<PatientDiagnosis>, IDiagnosisRepository
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the <see cref="DiagnosisRepository"/> class with the specified API settings and MongoDB database. The API settings are used to determine the collection name for storing patient diagnoses.
/// </summary>
/// <param name="apiSettings">The API settings containing configuration for the repository.</param>
/// <param name="database">The MongoDB database instance.</param>
/// <exception cref="ArgumentNullException">Thrown when the API settings are null.</exception>
public DiagnosisRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
_apiSettings = apiSettings.Value;
} //For testing
/// <summary>
/// Gets the name of the MongoDB collection for storing patient diagnoses. The collection name is determined by the API settings, and defaults to "patients_diagnosis" if not specified.
/// </summary>
/// <returns>The name of the MongoDB collection for patient diagnoses.</returns>
public override string GetCollectionName()
{
return _apiSettings.PatientsDiagnosis ?? "patients_diagnosis";
}
/// <summary>
/// Retrieves a list of patient diagnoses for a given patient ID. The diagnoses are sorted in descending order by time, with the most recent diagnoses appearing first.
/// </summary>
/// <param name="patientId">The ID of the patient whose diagnoses are to be retrieved.</param>
/// <returns>A list of patient diagnoses for the specified patient ID.</returns>
public async Task<List<PatientDiagnosis>> GetByPatient(ObjectId patientId)
{
var filter = Builders<PatientDiagnosis>.Filter.Eq(ob => ob.PatientId, patientId);
@@ -34,24 +50,47 @@ public class DiagnosisRepository : MongoRepository<PatientDiagnosis>, IDiagnosis
return result.ToList();
}
/// <summary>
/// Deletes a patient diagnosis by its ID. This method removes the diagnosis document from the MongoDB collection based on the provided ID.
/// </summary>
/// <param name="id">The ID of the patient diagnosis to delete.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public new async Task DeleteAsync(ObjectId id)
{
var filter = Builders<PatientDiagnosis>.Filter.Eq(t => t.Id, id);
await Collection.DeleteOneAsync(filter);
}
/// <summary>
/// Inserts a new patient diagnosis into the MongoDB collection. This method adds a new diagnosis document to the collection based on the provided <see cref="PatientDiagnosis"/> object.
/// </summary>
/// <param name="diagnosis">The patient diagnosis to insert.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public override async Task InsertOneAsync(PatientDiagnosis diagnosis)
{
await Collection.InsertOneAsync(diagnosis);
}
/// <summary>
/// Deletes all patient diagnoses associated with a specific patient ID. This method removes all diagnosis documents from the MongoDB collection that match the provided patient ID.
/// </summary>
/// <param name="patientId">The ID of the patient whose diagnoses are to be deleted.</param>
/// <returns></returns>
public async Task DeleteByPatientId(ObjectId patientId)
{
var filter = Builders<PatientDiagnosis>.Filter.Eq(po => po.PatientId, patientId);
await Collection.DeleteManyAsync(filter);
}
/// <summary>
/// Finds a patient diagnosis by patient ID, code, and coding system.
/// This method retrieves a single diagnosis document from the MongoDB collection that matches the provided patient ID, code, and coding system.
/// If no matching diagnosis is found, it returns null.
/// </summary>
/// <param name="patientId">The ID of the patient.</param>
/// <param name="code">The code of the diagnosis.</param>
/// <param name="codingSystem">The coding system of the diagnosis.</param>
/// <returns>The matching patient diagnosis, or null if not found.</returns>
public async Task<PatientDiagnosis?> FindByPatientIdAndCode(ObjectId patientId, string? code, string? codingSystem)
{
var builder = Builders<PatientDiagnosis>.Filter;
@@ -66,7 +105,12 @@ public class DiagnosisRepository : MongoRepository<PatientDiagnosis>, IDiagnosis
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Finds patient diagnoses by patient ID. This method retrieves all diagnosis documents from the MongoDB collection that match the provided patient ID.
/// The results are returned as an asynchronous cursor, allowing for efficient retrieval of large datasets.
/// </summary>
/// <param name="patientId">The ID of the patient whose diagnoses are to be retrieved.</param>
/// <returns>An asynchronous cursor of patient diagnoses.</returns>
public async Task<IAsyncCursor<PatientDiagnosis>> FindByPatientIdAsync(ObjectId patientId)
{
var filter = Builders<PatientDiagnosis>.Filter.Eq(ob => ob.PatientId, patientId);
@@ -74,11 +118,23 @@ public class DiagnosisRepository : MongoRepository<PatientDiagnosis>, IDiagnosis
return await Collection.FindAsync(filter);
}
/// <summary>
/// Updates the patient ID for all diagnoses that match the old patient ID.
/// This method performs a bulk update operation on the MongoDB collection, changing the patient ID from the old value to the new value for all matching diagnosis documents.
/// </summary>
/// <param name="nameId">The name ID associated with the patient.</param>
/// <param name="id">The new patient ID to be set.</param>
/// <param name="oldId">The old patient ID to be replaced.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId)
{
await UpdateManyObjectIdAsync(nameId, id, oldId);
}
/// <summary>
/// Creates indexes for the patient diagnosis collection. This method ensures that the necessary indexes are created on the MongoDB collection to optimize query performance.
/// </summary>
/// <returns>A task representing the asynchronous operation.</returns>
public override async Task CreateIndexes()
{
var options = new CreateIndexOptions { Background = true, Unique = false };
@@ -14,20 +14,37 @@ using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository for managing Discharge entities in MongoDB. Provides methods for CRUD operations and specific queries related to discharges.
/// </summary>
public class DischargeRepository : MongoRepository<Discharge>, IDischargeRepository
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the DischargeRepository class with the specified API settings and MongoDB database.
/// </summary>
/// <param name="apiSettings">The API settings containing configuration for the repository.</param>
/// <param name="database">The MongoDB database instance.</param>
public DischargeRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
_apiSettings = apiSettings.Value;
}
/// <summary>
/// Gets the name of the MongoDB collection for discharges from the API settings.
/// </summary>
/// <returns>The name of the MongoDB collection for discharges.</returns>
public override string GetCollectionName()
{
return _apiSettings.Discharges;
}
/// <summary>
/// Inserts a new discharge record into the MongoDB collection. Sets the discharge date to the current UTC time before insertion.
/// </summary>
/// <param name="discharge">The discharge record to insert.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public override async Task InsertOneAsync(Discharge discharge)
{
try
@@ -41,6 +58,11 @@ public class DischargeRepository : MongoRepository<Discharge>, IDischargeReposit
}
}
/// <summary>
/// Deletes a discharge record from the MongoDB collection based on the specified ID.
/// </summary>
/// <param name="id">The ID of the discharge record to delete.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task Delete(ObjectId id)
{
try
@@ -55,6 +77,12 @@ public class DischargeRepository : MongoRepository<Discharge>, IDischargeReposit
}
}
/// <summary>
/// Updates an existing discharge record in the MongoDB collection. If the update operation fails, a ConflictException is thrown.
/// </summary>
/// <param name="discharge">The discharge record to update.</param>
/// <returns>A task representing the asynchronous operation.</returns>
/// <exception cref="ConflictException"></exception>
public async Task Update(Discharge discharge)
{
try
@@ -68,7 +96,12 @@ public class DischargeRepository : MongoRepository<Discharge>, IDischargeReposit
}
}
/// <summary>
/// Updates the unit name of a discharge record in the MongoDB collection based on the specified ID. If the update operation fails, an error is logged.
/// </summary>
/// <param name="id">The ID of the discharge record to update.</param>
/// <param name="unit">The new unit name to set.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task UpdateUnit(ObjectId id, string unit)
{
var filterBuilder = Builders<Discharge>.Filter;
@@ -80,6 +113,12 @@ public class DischargeRepository : MongoRepository<Discharge>, IDischargeReposit
await Collection.UpdateOneAsync(filter, update);
}
/// <summary>
/// Updates the patient information of a discharge record in the MongoDB collection based on the specified ID. If the update operation fails, an error is logged.
/// </summary>
/// <param name="id">The ID of the discharge record to update.</param>
/// <param name="patient">The new patient information to set.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task UpdatePatient(ObjectId id, Patient patient)
{
var filterBuilder = Builders<Discharge>.Filter;
@@ -91,7 +130,10 @@ public class DischargeRepository : MongoRepository<Discharge>, IDischargeReposit
await Collection.UpdateOneAsync(filter, update);
}
/// <summary>
/// Finds and retrieves all discharge records from the MongoDB collection. If an error occurs during retrieval, an error is logged and an empty list is returned.
/// </summary>
/// <returns>A task representing the asynchronous operation, containing a list of all discharge records.</returns>
public async Task<IEnumerable<Discharge>> FindAll()
{
try
@@ -106,6 +148,11 @@ public class DischargeRepository : MongoRepository<Discharge>, IDischargeReposit
}
}
/// <summary>
/// Finds and retrieves a discharge record from the MongoDB collection based on the specified ID. If an error occurs during retrieval, an error is logged and null is returned.
/// </summary>
/// <param name="id">The ID of the discharge record to retrieve.</param>
/// <returns>A task representing the asynchronous operation, containing the discharge record if found, or null if not found or an error occurs.</returns>
public async Task<Discharge?> FindById(ObjectId id)
{
try
@@ -122,7 +169,11 @@ public class DischargeRepository : MongoRepository<Discharge>, IDischargeReposit
}
}
/// <summary>
/// Finds and retrieves discharge records from the MongoDB collection based on the specified unit name. If an error occurs during retrieval, an error is logged and null is returned.
/// </summary>
/// <param name="unit">The unit name to search for.</param>
/// <returns>A task representing the asynchronous operation, containing a list of discharge records matching the specified unit name, or null if an error occurs.</returns>
public async Task<IEnumerable<Discharge>?> FindByUnit(string unit)
{
try
@@ -139,6 +190,11 @@ public class DischargeRepository : MongoRepository<Discharge>, IDischargeReposit
}
}
/// <summary>
/// Counts the number of discharge records in the MongoDB collection that match the specified unit ID. If an error occurs during counting, an error is logged and 0 is returned.
/// </summary>
/// <param name="unitId">The ID of the unit to count discharge records for.</param>
/// <returns>A task representing the asynchronous operation, containing the count of discharge records matching the specified unit ID, or 0 if an error occurs.</returns>
public async Task<long> CountByUnitId(ObjectId unitId)
{
try
@@ -153,6 +209,11 @@ public class DischargeRepository : MongoRepository<Discharge>, IDischargeReposit
}
}
/// <summary>
/// Finds and retrieves discharge records from the MongoDB collection based on the specified destination. If an error occurs during retrieval, an error is logged and null is returned.
/// </summary>
/// <param name="destination">The destination to search for.</param>
/// <returns>A task representing the asynchronous operation, containing a list of discharge records matching the specified destination, or null if an error occurs.</returns>
public async Task<IEnumerable<Discharge>?> FindByDestination(string destination)
{
try
@@ -172,13 +233,22 @@ public class DischargeRepository : MongoRepository<Discharge>, IDischargeReposit
}
}
/// <summary>
/// Finds and retrieves discharge records from the MongoDB collection based on the specified unit IDs. If an error occurs during retrieval, an error is logged and null is returned.
/// </summary>
/// <param name="unitIds">The IDs of the units to search for.</param>
/// <returns>A task representing the asynchronous operation, containing a list of discharge records matching the specified unit IDs, or null if an error occurs.</returns>
public async Task<IEnumerable<Discharge>?> GetDischargesByUnitIds(IEnumerable<ObjectId>? unitIds)
{
var filterUnit = Builders<Discharge>.Filter.In("unitId", unitIds);
return await Collection.Find(filterUnit).ToListAsync();
}
/// <summary>
/// Finds and retrieves discharge records from the MongoDB collection based on the specified Point of Care ID. If an error occurs during retrieval, an error is logged and null is returned.
/// </summary>
/// <param name="pocId">The ID of the Point of Care to search for.</param>
/// <returns>A task representing the asynchronous operation, containing a list of discharge records matching the specified Point of Care ID, or null if an error occurs.</returns>
public async Task<IEnumerable<Discharge>?> FindByPoCId(ObjectId pocId)
{
try
@@ -195,7 +265,12 @@ public class DischargeRepository : MongoRepository<Discharge>, IDischargeReposit
return null;
}
}
/// <summary>
/// Finds and retrieves discharge records from the MongoDB collection based on the specified service. If an error occurs during retrieval, an error is logged and null is returned.
/// </summary>
/// <param name="service">The service to search for.</param>
/// <returns>A task representing the asynchronous operation, containing a list of discharge records matching the specified service, or null if an error occurs.</returns>
public async Task<IEnumerable<Discharge>?> FindByService(string service)
{
try
@@ -212,6 +287,11 @@ public class DischargeRepository : MongoRepository<Discharge>, IDischargeReposit
}
}
/// <summary>
/// Finds and retrieves a discharge record from the MongoDB collection based on the specified patient location. If an error occurs during retrieval, an error is logged and null is returned.
/// </summary>
/// <param name="location">The patient location to search for.</param>
/// <returns>A task representing the asynchronous operation, containing the discharge record matching the specified patient location, or null if an error occurs.</returns>
public async Task<Discharge?> GetDischargeByLocation(PatientLocation location)
{
try
@@ -228,6 +308,11 @@ public class DischargeRepository : MongoRepository<Discharge>, IDischargeReposit
}
}
/// <summary>
/// Finds and retrieves a discharge record from the MongoDB collection based on the specified Point of Care ID. If an error occurs during retrieval, an error is logged and null is returned.
/// </summary>
/// <param name="id">The ID of the Point of Care to search for.</param>
/// <returns>A task representing the asynchronous operation, containing the discharge record matching the specified Point of Care ID, or null if an error occurs.</returns>
public async Task<Discharge?> GetDischargeByPointOfCareId(ObjectId id)
{
try
@@ -244,6 +329,14 @@ public class DischargeRepository : MongoRepository<Discharge>, IDischargeReposit
}
}
/// <summary>
/// Updates the master list options for discharge records in the MongoDB collection based on the specified unit IDs, update option, and master list type.
/// The method parses the master list type and performs updates accordingly. If an error occurs during the update process, an error is logged and an empty list is returned.
/// </summary>
/// <param name="unitIds">The list of unit IDs for which to update the master list options.</param>
/// <param name="opt">The update option specifying the changes to be applied to the master list.</param>
/// <param name="typeName">The name of the master list type to be updated.</param>
/// <returns>A task representing the asynchronous operation, containing the updated discharge records, or an empty list if an error occurs.</returns>
public Task<IEnumerable<Discharge>> UpdateMasterListOption(List<ObjectId> unitIds, UpdateOptionMasterListDto opt,
string typeName)
{
@@ -263,7 +356,11 @@ public class DischargeRepository : MongoRepository<Discharge>, IDischargeReposit
return Task.FromResult<IEnumerable<Discharge>>(new List<Discharge>());
}
/// <summary>
/// Deletes discharge records from the MongoDB collection based on the specified unit ID. If an error occurs during deletion, an error is logged and false is returned; otherwise, true is returned upon successful deletion.
/// </summary>
/// <param name="unitId">The ID of the unit for which to delete discharge records.</param>
/// <returns>A task representing the asynchronous operation, containing true if the deletion was successful, or false if an error occurred.</returns>
public async Task<bool> DeleteByUnitId(ObjectId unitId)
{
try
@@ -279,6 +376,13 @@ public class DischargeRepository : MongoRepository<Discharge>, IDischargeReposit
}
}
/// <summary>
/// Deletes master list options for discharge records in the MongoDB collection based on the specified unit IDs, update option, and master list type.
/// </summary>
/// <param name="unitIds">The list of unit IDs for which to delete master list options.</param>
/// <param name="opt">The update option specifying the changes to be applied to the master list.</param>
/// <param name="typeName">The name of the master list type to be updated.</param>
/// <returns>A task representing the asynchronous operation, containing the updated discharge records, or an empty list if an error occurs.</returns>
public Task<IEnumerable<Discharge>> DeleteMasterListOption(List<ObjectId> unitIds, OptionList opt, string typeName)
{
var isParsed = Enum.TryParse<MasterListType>(typeName, out var parsedTypeName);
@@ -297,6 +401,11 @@ public class DischargeRepository : MongoRepository<Discharge>, IDischargeReposit
return Task.FromResult<IEnumerable<Discharge>>(new List<Discharge>());
}
/// <summary>
/// Finds and retrieves a discharge record from the MongoDB collection based on the specified patient ID. If an error occurs during retrieval, an error is logged and null is returned.
/// </summary>
/// <param name="patientId">The ID of the patient for which to retrieve the discharge record.</param>
/// <returns>A task representing the asynchronous operation, containing the discharge record if found, or null if an error occurs or the record is not found.</returns>
public async Task<Discharge?> GetByPatientId(ObjectId patientId)
{
try
@@ -313,6 +422,12 @@ public class DischargeRepository : MongoRepository<Discharge>, IDischargeReposit
}
}
/// <summary>
/// Creates indexes for the MongoDB collection based on the specified fields and options.
/// The method ensures that unique indexes are created for the medical discharge and admin discharge fields, with a partial filter expression to only include documents where both fields exist.
/// If an error occurs during index creation, an error is logged.
/// </summary>
/// <returns></returns>
public override async Task CreateIndexes()
{
var optionsUq = new CreateIndexOptions<Discharge>
@@ -9,12 +9,20 @@ using MongoDB.Driver;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository for managing display card configurations in MongoDB. Provides methods to perform CRUD operations on CardConfig documents.
/// </summary>
public class DisplayCardConfigRepository : MongoRepository<CardConfig>, IDisplayCardConfigRepository
{
private readonly ApiSettings _apiSettings;
private readonly ILogger<DisplayCardConfigRepository> _logger;
/// <summary>
/// Initializes a new instance of the DisplayCardConfigRepository class with the specified MongoDB database, API settings, and logger.
/// </summary>
/// <param name="database">The MongoDB database instance.</param>
/// <param name="apiSettings">The API settings containing configuration for the repository.</param>
/// <param name="logger">The logger instance for logging repository operations.</param>
public DisplayCardConfigRepository(
IMongoDatabase database,
ApiSettings apiSettings,
@@ -25,17 +33,30 @@ public class DisplayCardConfigRepository : MongoRepository<CardConfig>, IDisplay
_logger = logger;
}
/// <summary>
/// Gets the name of the MongoDB collection for CardConfig documents, as specified in the API settings.
/// </summary>
/// <returns>The name of the MongoDB collection for CardConfig documents.</returns>
public override string GetCollectionName()
{
return _apiSettings.DisplayCardConfig;
}
/// <summary>
/// Retrieves all CardConfig documents from the MongoDB collection and returns them as a list. Logs any exceptions that occur during the retrieval process.
/// </summary>
/// <returns>A task representing the asynchronous operation, containing a list of all CardConfig documents.</returns>
public async Task<List<CardConfig>> GetAll()
{
var result = await Collection.Find(Builders<CardConfig>.Filter.Empty).ToListAsync();
return result;
}
/// <summary>
/// Retrieves a CardConfig document by its unique identifier from the MongoDB collection. Logs any exceptions that occur during the retrieval process and returns null if an error occurs or if the document is not found.
/// </summary>
/// <param name="configId">The unique identifier of the CardConfig document.</param>
/// <returns>A task representing the asynchronous operation, containing the CardConfig document if found, or null if not found or an error occurs.</returns>
public async Task<CardConfig?> GetById(ObjectId configId)
{
try
@@ -50,6 +71,11 @@ public class DisplayCardConfigRepository : MongoRepository<CardConfig>, IDisplay
}
}
/// <summary>
/// Inserts a new CardConfig document into the MongoDB collection and returns the inserted document. Logs any exceptions that occur during the insertion process and returns null if an error occurs.
/// </summary>
/// <param name="config">The CardConfig document to insert.</param>
/// <returns>A task representing the asynchronous operation, containing the inserted CardConfig document if successful, or null if an error occurs.</returns>
public async Task<CardConfig?> InsertOneAsyncAndReturn(CardConfig config)
{
try
@@ -64,6 +90,14 @@ public class DisplayCardConfigRepository : MongoRepository<CardConfig>, IDisplay
}
}
/// <summary>
/// Updates an existing CardConfig document in the MongoDB collection based on the provided CardConfig object.
/// The method updates the Rows field of the document with the matching Id.
/// It returns an UpdateResponse containing the number of modified documents and the updated document itself.
/// Logs any exceptions that occur during the update process and returns an UpdateResponse with zero changes and null data if an error occurs or if the input config is null.
/// </summary>
/// <param name="config">The CardConfig document to update.</param>
/// <returns>A task representing the asynchronous operation, containing an UpdateResponse with the number of modified documents and the updated document.</returns>
public async Task<UpdateResponse<CardConfig?>> UpdateOne(CardConfig? config)
{
try
@@ -85,11 +119,23 @@ public class DisplayCardConfigRepository : MongoRepository<CardConfig>, IDisplay
}
}
/// <summary>
/// Deletes a CardConfig document from the MongoDB collection based on the provided unique identifier.
/// </summary>
/// <param name="configId">The unique identifier of the CardConfig document to delete.</param>
/// <returns>A task representing the asynchronous operation, containing the deleted CardConfig document if successful, or null if not found or an error occurs.</returns>
public async Task<CardConfig?> DeleteOne(ObjectId configId)
{
return await DeleteAsync(configId);
}
/// <summary>
/// Updates the CardConfig document associated with the specified display configuration ID and result ID.
/// </summary>
/// <param name="displayConfigId">The unique identifier of the display configuration.</param>
/// <param name="resultId">The unique identifier of the result.</param>
/// <returns>A task representing the asynchronous operation, containing the updated CardConfig document if successful, or null if not found or an error occurs. </returns>
/// <exception cref="NotImplementedException"></exception>
public Task<object> UpdateCardConfigId(ObjectId? displayConfigId, ObjectId? resultId)
{
throw new NotImplementedException();
@@ -9,31 +9,52 @@ using MongoDB.Driver;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository for managing ChartConfig in MongoDB. Provides methods to retrieve and manipulate ChartConfig data.
/// </summary>
public class DisplayChartConfigRepository : MongoRepository<ChartConfig>, IDisplayChartConfigRepository
{
private readonly ApiSettings _apiSettings;
private readonly ILogger<DisplayDetailConfigRepository> _logger;
private readonly ILogger<DisplayChartConfigRepository> _logger;
/// <summary>
/// Initializes a new instance of the DisplayChartConfigRepository class with the specified API settings, MongoDB database, and logger.
/// </summary>
/// <param name="database">The MongoDB database instance.</param>
/// <param name="apiSettings">The API settings instance.</param>
/// <param name="logger">The logger instance.</param>
public DisplayChartConfigRepository(IMongoDatabase database, ApiSettings apiSettings,
ILogger<DisplayDetailConfigRepository> logger) : base(database)
ILogger<DisplayChartConfigRepository> logger) : base(database)
{
_apiSettings = apiSettings;
_logger = logger;
}
/// <summary>
/// Gets the name of the MongoDB collection for ChartConfig. This method retrieves the collection name from the API settings.
/// </summary>
/// <returns>The name of the MongoDB collection for ChartConfig.</returns>
public override string GetCollectionName()
{
return _apiSettings.DisplayChartConfig;
}
/// <summary>
/// Retrieves all ChartConfig documents from the MongoDB collection. This method returns a list of ChartConfig objects representing all the configurations stored in the database.
/// </summary>
/// <returns>A list of ChartConfig objects representing all the configurations stored in the database.</returns>
public async Task<List<ChartConfig>> GetAll()
{
var result = await Collection.Find(Builders<ChartConfig>.Filter.Empty).ToListAsync();
return result;
}
/// <summary>
/// Retrieves a ChartConfig document from the MongoDB collection by its unique identifier. This method takes an ObjectId as a parameter and returns the corresponding ChartConfig object if found, or null if no matching document is found.
/// </summary>
/// <param name="configId">The unique identifier of the ChartConfig document.</param>
/// <returns>The ChartConfig object if found, or null if no matching document is found.</returns>
public async Task<ChartConfig?> GetById(ObjectId configId)
{
try
@@ -48,6 +69,12 @@ public class DisplayChartConfigRepository : MongoRepository<ChartConfig>, IDispl
}
}
/// <summary>
/// Inserts a new ChartConfig document into the MongoDB collection and returns the inserted document.
/// This method takes a ChartConfig object as a parameter, inserts it into the database, and returns the same object if the insertion is successful. If an error occurs during the insertion process, it logs the error and returns null.
/// </summary>
/// <param name="config">The ChartConfig object to be inserted into the MongoDB collection.</param>
/// <returns>The inserted ChartConfig object if successful, or null if an error occurs.</returns>
public async Task<ChartConfig?> InsertOneAsyncAndReturn(ChartConfig config)
{
try
@@ -62,6 +89,14 @@ public class DisplayChartConfigRepository : MongoRepository<ChartConfig>, IDispl
}
}
/// <summary>
/// Updates an existing ChartConfig document in the MongoDB collection based on the provided ChartConfig object.
/// This method takes a ChartConfig object as a parameter, identifies the document to be updated using the Id property, and updates the BaseConfig, AxesConfig, and SeriesConfig fields of the matching document.
/// It returns an UpdateResponse object containing the number of changes made and the updated ChartConfig document.
/// If an error occurs during the update process, it logs the error and returns an UpdateResponse with zero changes and null data.
/// </summary>
/// <param name="config">The ChartConfig object containing the updated data.</param>
/// <returns>An UpdateResponse object containing the number of changes made and the updated ChartConfig document.</returns>
public async Task<UpdateResponse<ChartConfig?>> UpdateOne(ChartConfig? config)
{
try
@@ -89,6 +124,11 @@ public class DisplayChartConfigRepository : MongoRepository<ChartConfig>, IDispl
}
}
/// <summary>
/// Deletes a ChartConfig document from the MongoDB collection based on the provided unique identifier.
/// </summary>
/// <param name="configId">ChartConfig Id to be deleted </param>
/// <returns></returns>
public async Task<ChartConfig?> DeleteOne(ObjectId configId)
{
return await DeleteAsync(configId);
@@ -18,16 +18,23 @@ using System.Text.RegularExpressions;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing DisplayConfig entities in MongoDB.
/// Provides CRUD operations and specialized queries for display configurations.
/// </summary>
public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayConfigRepository
{
private readonly ApiSettings _apiSettings;
private readonly ILogger<DisplayConfigRepository> _logger;
private readonly IUnitRepository _unitRepository;
/// <summary>
/// Initializes a new instance of the DisplayConfigRepository.
/// </summary>
/// <param name="database">The MongoDB database instance.</param>
/// <param name="apiSettings">API settings containing collection names configuration.</param>
/// <param name="logger">Logger for repository operations.</param>
/// <param name="unitRepository">Repository for unit-related operations.</param>
public DisplayConfigRepository(
IMongoDatabase database,
ApiSettings apiSettings,
@@ -39,17 +46,31 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
_unitRepository = unitRepository;
}
/// <summary>
/// Gets the name of the collection for display configurations.
/// </summary>
/// <returns>The collection name from API settings.</returns>
public override string GetCollectionName()
{
return _apiSettings.DisplaysConfig;
}
/// <summary>
/// Retrieves all display configurations from the database.
/// </summary>
/// <returns>A list of all DisplayConfig entities.</returns>
public async Task<List<DisplayConfig>> GetAll()
{
var result = await Collection.Find(Builders<DisplayConfig>.Filter.Empty).ToListAsync();
return result;
}
/// <summary>
/// Retrieves paginated display configurations with optional filtering.
/// </summary>
/// <param name="filter">The pagination and filtering parameters.</param>
/// <returns>A fluent queryable for DisplayConfigSummary results.</returns>
/// <exception cref="BadRequestException">Thrown when the text filter exceeds 100 characters.</exception>
public IFindFluent<DisplayConfig, DisplayConfigSummary> GetAllPaginated(PaginationFilter filter)
{
var filterBuilder = Builders<DisplayConfig>.Filter;
@@ -90,6 +111,11 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
return CreateFindFluentMinimal(filters, sort);
}
/// <summary>
/// Retrieves all display configurations of a specific type.
/// </summary>
/// <param name="type">The display type to filter by.</param>
/// <returns>A list of DisplayConfig entities matching the specified type.</returns>
public async Task<List<DisplayConfig>> GetByType(DisplayConfigEnums.DisplayType type)
{
var filter = Builders<DisplayConfig>.Filter.Eq(p => p.Type, type);
@@ -97,6 +123,12 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
return result;
}
/// <summary>
/// Retrieves a display configuration by its ID with related configurations populated.
/// Performs aggregation to include CardConfig, DetailConfig, ChartConfig, and rotating layout data.
/// </summary>
/// <param name="id">The ObjectId of the display configuration to retrieve.</param>
/// <returns>The DisplayConfig with related data, or null if not found.</returns>
public async Task<DisplayConfig?> GetById(ObjectId id)
{
try
@@ -193,6 +225,12 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
}
}
/// <summary>
/// Retrieves the default display configuration for a given type.
/// Default configurations are identified by having "Default" as the hospital name.
/// </summary>
/// <param name="type">The display type to search for.</param>
/// <returns>The default DisplayConfig for the specified type, or null if not found.</returns>
public async Task<DisplayConfig?> GetDefault(DisplayConfigEnums.DisplayType type)
{
var filter = Builders<DisplayConfig>.Filter.And(
@@ -204,6 +242,12 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
return result.FirstOrDefault();
}
/// <summary>
/// Inserts a new display configuration and returns the inserted document.
/// </summary>
/// <param name="config">The DisplayConfig to insert.</param>
/// The inserted DisplayConfig, or null if insertion fails.
/// <returns></returns>
public async Task<DisplayConfig?> InsertOneAsyncAndReturn(DisplayConfig config)
{
try
@@ -218,6 +262,12 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
}
}
/// <summary>
/// Updates the smart display configuration for a specific display.
/// </summary>
/// <param name="displayConfigId">The ObjectId of the display configuration to update.</param>
/// <param name="newDisplayConfig">The new SmartDisplay configuration to apply.</param>
/// <returns>The updated SmartDisplay configuration, or null if update fails.</returns>
public async Task<SmartDisplay?> UpdateSmartDisplay(ObjectId displayConfigId, SmartDisplay? newDisplayConfig)
{
try
@@ -258,6 +308,13 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
}
}
/// <summary>
/// Updates the color configuration for a specific display.
/// </summary>
/// <param name="objectIdConfigDisplay">The ObjectId of the display configuration to update.</param>
/// <param name="colorConfig">The new ColorConfig to apply.</param>
/// <returns>True if the update was successful; otherwise, false.</returns>
/// <exception cref="Exception">Throws an exception if MongoDB update fails.</exception>
public async Task<bool> UpdateConfigColor(ObjectId objectIdConfigDisplay, ColorConfig colorConfig)
{
var filter = Builders<DisplayConfig>.Filter.Eq("Id", objectIdConfigDisplay);
@@ -298,6 +355,13 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
}
}
/// <summary>
/// Updates the header configuration for a specific display.
/// </summary>
/// <param name="objectIdConfigDisplay">The ObjectId of the display configuration to update.</param>
/// <param name="headerConfig">The new HeaderConfig to apply.</param>
/// <returns>True if the update was successful; otherwise, false.</returns>
/// <exception cref="Exception">Throws an exception if MongoDB update fails.</exception>
public async Task<bool> UpdateHeaderConfig(ObjectId objectIdConfigDisplay, HeaderConfig headerConfig)
{
var filter = Builders<DisplayConfig>.Filter.Eq("Id", objectIdConfigDisplay);
@@ -348,6 +412,13 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
}
}
/// <summary>
/// Updates the home banner configuration for a specific display.
/// </summary>
/// <param name="objectIdConfigDisplay">The ObjectId of the display configuration to update.</param>
/// <param name="bannerItems">The list of banner items to set.</param>
/// <returns>True if the update was successful; otherwise, false.</returns>
/// <exception cref="Exception">Throws an exception if MongoDB update fails.</exception>
public async Task<bool> UpdateSetHomeBanner(ObjectId objectIdConfigDisplay, List<BannerItem> bannerItems)
{
var filter = Builders<DisplayConfig>.Filter.Eq("Id", objectIdConfigDisplay);
@@ -367,6 +438,13 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
}
}
/// <summary>
/// Updates the base configuration for a specific display.
/// </summary>
/// <param name="objectIdConfigDisplay">The ObjectId of the display configuration to update.</param>
/// <param name="baseConfig">The base DisplayConfig with updated values.</param>
/// <returns>True if the update was successful; otherwise, false.</returns>
/// <exception cref="Exception">Throws an exception if MongoDB update fails.</exception>
public async Task<bool> UpdateBaseConfig(ObjectId objectIdConfigDisplay, DisplayConfig baseConfig)
{
var filter = Builders<DisplayConfig>.Filter.Eq("Id", objectIdConfigDisplay);
@@ -390,6 +468,13 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
}
}
/// <summary>
/// Updates the nurse display configuration with additional observation fields.
/// </summary>
/// <param name="displayConfigId">The ObjectId of the display configuration to update.</param>
/// <param name="newDisplayConfig">The new DisplayNurseDto configuration to apply.</param>
/// <param name="nurseObs">List of observation names to mark as "last".</param>
/// <returns>The updated DisplayNurse configuration, or null if update fails.</returns>
public async Task<DisplayNurse?> UpdateDisplayNurse(ObjectId displayConfigId, DisplayNurseDto? newDisplayConfig,
List<string> nurseObs)
{
@@ -446,6 +531,12 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
}
}
/// <summary>
/// Updates the hospital name for a specific display configuration.
/// </summary>
/// <param name="objectIdConfigDisplay">The ObjectId of the display configuration to update.</param>
/// <param name="name">The new hospital name.</param>
/// <returns>True if the update was successful; otherwise, false.</returns>
public async Task<bool> UpdateDisplayConfigHospitalName(ObjectId objectIdConfigDisplay, string name)
{
var filter = Builders<DisplayConfig>.Filter.Eq(c => c.Id, objectIdConfigDisplay);
@@ -466,6 +557,12 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
return true;
}
/// <summary>
/// Updates the field list for a specific display configuration.
/// </summary>
/// <param name="objectIdConfigDisplay">The ObjectId of the display configuration to update.</param>
/// <param name="fields">The new list of fields to set.</param>
/// <returns>True if the update was successful; otherwise, false.</returns>
public async Task<bool> UpdateFieldList(ObjectId objectIdConfigDisplay, List<Field> fields)
{
var filter = Builders<DisplayConfig>.Filter.Eq(x => x.Id, objectIdConfigDisplay);
@@ -475,6 +572,12 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
return result.ModifiedCount > 0;
}
/// <summary>
/// Gets the default display configuration for a unit based on its ID and display type.
/// </summary>
/// <param name="unitId">The ObjectId of the unit.</param>
/// <param name="displayType">The type of display configuration to retrieve.</param>
/// <returns>The default DisplayConfig for the unit, or null if not found.</returns>
public async Task<DisplayConfig?> GetDefaultByUnitIdAndType(ObjectId unitId,
DisplayConfigEnums.DisplayType displayType)
{
@@ -494,11 +597,20 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
}
}
/// <summary>
/// Deletes a display configuration by its ID.
/// </summary>
/// <param name="objectIdConfigDisplay">The ObjectId of the display configuration to delete.</param>
/// <returns>The deleted DisplayConfig, or null if not found.</returns>
public async Task<DisplayConfig?> DeleteDisplayConfig(ObjectId objectIdConfigDisplay)
{
return await DeleteAsync(objectIdConfigDisplay);
}
/// <summary>
/// Retrieves all display configurations in compact format (minimal response).
/// </summary>
/// <returns>A list of DisplayConfigMinimalResponse containing Id, Hospital, and Type.</returns>
public Task<List<DisplayConfigMinimalResponse>> GetAllCompact()
{
var filter = Builders<DisplayConfig>.Filter.Empty;
@@ -512,6 +624,11 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
}).ToListAsync();
}
/// <summary>
/// Retrieves all display configuration IDs associated with a specific card configuration.
/// </summary>
/// <param name="cardConfigId">The ObjectId of the card configuration.</param>
/// <returns>A list of ObjectIds for display configurations using the specified card config.</returns>
public async Task<List<ObjectId>> GetAllByCardConfigId(ObjectId cardConfigId)
{
var filter = Builders<DisplayConfig>.Filter.Eq(d => d.CardConfigId, cardConfigId);
@@ -522,12 +639,18 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
return result;
}
/// <summary>
/// Retrieves all display configuration IDs associated with a card config, including rotating layouts.
/// </summary>
/// <param name="cardConfigId">The ObjectId of the card configuration.</param>
/// <returns>A list of ObjectIds for display configurations using the specified card config in main or rotating layout.</returns>
public async Task<List<ObjectId>> GetAllByCardConfigIdAndRotating(ObjectId cardConfigId)
{
var mainFilter = Builders<DisplayConfig>.Filter.Eq(d => d.CardConfigId, cardConfigId);
var rotatingFilter = Builders<DisplayConfig>.Filter.ElemMatch(
"cardRotatingLayout",
"cardRotatingLayout",
Builders<BsonDocument>.Filter.Eq("dataId", cardConfigId)
);
@@ -540,6 +663,12 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
return result;
}
/// <summary>
/// Updates the card configuration ID for a specific display.
/// </summary>
/// <param name="displayConfigId">The ObjectId of the display configuration to update.</param>
/// <param name="resultId">The new card configuration ID to set.</param>
/// <returns>True if the update was successful; otherwise, false.</returns>
public async Task<bool> UpdateCardConfigId(ObjectId? displayConfigId, ObjectId? resultId)
{
var filter = Builders<DisplayConfig>.Filter.Eq(x => x.Id, displayConfigId);
@@ -549,6 +678,12 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
return result.ModifiedCount > 0;
}
/// <summary>
/// Adds a new chart configuration ID to a smart display's chart config list.
/// </summary>
/// <param name="displayConfigId">The ObjectId of the display configuration to update.</param>
/// <param name="newChartIdToAdd">The new chart configuration ID to add.</param>
/// <returns>True if the update was successful; otherwise, false.</returns>
public async Task<bool> AddChartId(ObjectId? displayConfigId, ObjectId newChartIdToAdd)
{
var filter = Builders<DisplayConfig>.Filter.Eq(x => x.Id, displayConfigId);
@@ -559,6 +694,12 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
return result.ModifiedCount > 0;
}
/// <summary>
/// Removes a deleted chart configuration ID from all display configurations.
/// </summary>
/// <param name="deletedId">The ObjectId of the chart configuration that was deleted.</param>
/// <returns>The MongoDB UpdateResult indicating the number of modified documents.</returns>
/// <exception cref="Exception">Throws an exception if MongoDB operation fails.</exception>
public async Task<UpdateResult> UpdateDeletedChartConfig(ObjectId deletedId)
{
try
@@ -576,11 +717,23 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
}
}
/// <summary>
/// Gets a chart configuration by ID. This method is not implemented.
/// </summary>
/// <param name="objectIdConfigChart">The ObjectId of the chart configuration.</param>
/// <returns>Always throws NotImplementedException.</returns>
/// <exception cref="NotImplementedException">This method is not implemented.</exception>
public Task<ChartConfig> GetChartConfig(ObjectId objectIdConfigChart)
{
throw new NotImplementedException();
}
/// <summary>
/// Updates the detail configuration ID for a specific display.
/// </summary>
/// <param name="displayConfigId">The ObjectId of the display configuration to update.</param>
/// <param name="resultId">The new detail configuration ID to set.</param>
/// <returns>True if the update was successful; otherwise, false.</returns>
public async Task<bool> UpdateDetailConfigId(ObjectId? displayConfigId, ObjectId? resultId)
{
var filter = Builders<DisplayConfig>.Filter.Eq(x => x.Id, displayConfigId);
@@ -590,6 +743,11 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
return result.ModifiedCount > 0;
}
/// <summary>
/// Retrieves all display configuration IDs associated with a specific detail configuration.
/// </summary>
/// <param name="baseConfigId">The ObjectId of the detail configuration.</param>
/// <returns>A list of ObjectIds for display configurations using the specified detail config.</returns>
public async Task<List<ObjectId>> GetAllByCardDetailConfigId(ObjectId baseConfigId)
{
var filter = Builders<DisplayConfig>.Filter.Eq(d => d.DetailConfigId, baseConfigId);
@@ -601,6 +759,15 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
return result;
}
/// <summary>
/// Performs initial data load by creating default configurations for each display type if they don't exist.
/// </summary>
/// <remarks>
/// This method runs on application startup to ensure default configurations exist for:
/// - DisplayNurse
/// - SmartDisplay
/// - StandarDisplay
/// </remarks>
public sealed override async Task InsertInitialLoad()
{
// 1. Obtener todos los valores y castear al tipo IEnumerable<DisplayType>
@@ -655,6 +822,12 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
// Si alguno no existe crearlos
}
/// <summary>
/// Extracts observation field names from a DisplayNurseDto configuration.
/// </summary>
/// <param name="newDisplayConfig">The DisplayNurseDto configuration to extract fields from.</param>
/// <param name="nurseObs">List of observation names to mark as "last" priority.</param>
/// <returns>A list of Field objects with extracted observation names.</returns>
private List<Field> ExtractObservationFields(DisplayNurseDto newDisplayConfig, List<string> nurseObs)
{
var fieldSet = new HashSet<string>();
@@ -680,6 +853,12 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
.ToList();
}
/// <summary>
/// Fills a hash set with observation names extracted from JSON using regex.
/// </summary>
/// <param name="newDisplayConfig">The JSON string to search for observation names.</param>
/// <param name="regex">The regex pattern to match observation name arrays.</param>
/// <param name="fieldSet">The hash set to populate with field names.</param>
private void FillHashSet(string newDisplayConfig, Regex regex, HashSet<string> fieldSet)
{
var matches = regex.Matches(newDisplayConfig);
@@ -693,6 +872,11 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
}
}
/// <summary>
/// Recursively extracts observation names from cell configurations.
/// </summary>
/// <param name="cells">The list of cells to extract from.</param>
/// <param name="fieldSet">The hash set to populate with field names.</param>
private void ExtractFromCells(List<Cell>? cells, HashSet<string> fieldSet)
{
if (cells is null)
@@ -711,6 +895,11 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
}
}
/// <summary>
/// Recursively extracts observation names from detail cell configurations.
/// </summary>
/// <param name="cells">The list of detail cells to extract from.</param>
/// <param name="fieldSet">The hash set to populate with field names.</param>
private void ExtractFromDetailsCells(List<CellDetails>? cells, HashSet<string> fieldSet)
{
if (cells is null)
@@ -729,6 +918,12 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
}
}
/// <summary>
/// Creates a minimal fluent query for paginated display configs with optional filters.
/// </summary>
/// <param name="filters">List of filter definitions to apply.</param>
/// <param name="sort">Sort definition for the query results.</param>
/// <returns>A fluent queryable for DisplayConfigSummary.</returns>
private IFindFluent<DisplayConfig, DisplayConfigSummary> CreateFindFluentMinimal(
List<FilterDefinition<DisplayConfig>> filters,
SortDefinition<DisplayConfig> sort)
@@ -748,6 +943,11 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
});
}
/// <summary>
/// Generates update definitions from a base DisplayConfig.
/// </summary>
/// <param name="baseConfig">The base DisplayConfig with values to update.</param>
/// <returns>A tuple containing list of update definitions and list of field names.</returns>
private (List<UpdateDefinition<DisplayConfig>> Updates, List<string> Fields) GetBaseUpdateDefinition(
DisplayConfig baseConfig)
{
@@ -772,4 +972,6 @@ public class DisplayConfigRepository : MongoRepository<DisplayConfig>, IDisplayC
updateDefinition.Add(Builders<DisplayConfig>.Update.Set(c => c.HeaderConfig, baseConfig.HeaderConfig));
return (updateDefinition, fieldList);
}
}
}
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
@@ -9,13 +9,21 @@ using MongoDB.Driver;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing CardDetailsConfig entities in MongoDB.
/// Provides CRUD operations for display detail configurations used in nurse and smart displays.
/// </summary>
public class DisplayDetailConfigRepository : MongoRepository<CardDetailsConfig>, IDisplayDetailConfigRepository
{
private readonly ApiSettings _apiSettings;
private readonly ILogger<DisplayDetailConfigRepository> _logger;
/// <summary>
/// Initializes a new instance of the DisplayDetailConfigRepository.
/// </summary>
/// <param name="database">The MongoDB database instance.</param>
/// <param name="apiSettings">API settings containing collection names configuration.</param>
/// <param name="logger">Logger for repository operations.</param>
public DisplayDetailConfigRepository(
IMongoDatabase database,
ApiSettings apiSettings,
@@ -26,17 +34,31 @@ public class DisplayDetailConfigRepository : MongoRepository<CardDetailsConfig>,
_logger = logger;
}
/// <summary>
/// Gets the name of the collection for display detail configurations.
/// </summary>
/// <returns>The collection name from API settings.</returns>
public override string GetCollectionName()
{
return _apiSettings.DisplayDetailConfig;
}
/// <summary>
/// Retrieves all display detail configurations from the database.
/// </summary>
/// <returns>A list of all CardDetailsConfig entities.</returns>
public async Task<List<CardDetailsConfig>> GetAll()
{
var result = await Collection.Find(Builders<CardDetailsConfig>.Filter.Empty).ToListAsync();
return result;
}
/// <summary>
/// Retrieves a display detail configuration by its ID.
/// </summary>
/// <param name="configId">The ObjectId of the detail configuration to retrieve.</param>
/// <returns>The CardDetailsConfig if found; otherwise, null.</returns>
/// <exception cref="Exception">Throws an exception if MongoDB query fails; returns null instead.</exception>
public async Task<CardDetailsConfig?> GetById(ObjectId configId)
{
try
@@ -51,6 +73,11 @@ public class DisplayDetailConfigRepository : MongoRepository<CardDetailsConfig>,
}
}
/// <summary>
/// Inserts a new display detail configuration and returns the inserted document.
/// </summary>
/// <param name="config">The CardDetailsConfig to insert.</param>
/// <returns>The inserted CardDetailsConfig, or null if insertion fails.</returns>
public async Task<CardDetailsConfig?> InsertOneAsyncAndReturn(CardDetailsConfig config)
{
try
@@ -65,6 +92,12 @@ public class DisplayDetailConfigRepository : MongoRepository<CardDetailsConfig>,
}
}
/// <summary>
/// Updates an existing display detail configuration with new values.
/// </summary>
/// <param name="config">The CardDetailsConfig with updated values.</param>
/// <returns>An UpdateResponse containing the modified count and the updated document.</returns>
/// <exception cref="Exception">Throws an exception if MongoDB update fails; returns UpdateResponse with null document.</exception>
public async Task<UpdateResponse<CardDetailsConfig?>> UpdateOne(CardDetailsConfig? config)
{
try
@@ -92,11 +125,23 @@ public class DisplayDetailConfigRepository : MongoRepository<CardDetailsConfig>,
}
}
/// <summary>
/// Deletes a display detail configuration by its ID.
/// </summary>
/// <param name="configId">The ObjectId of the configuration to delete.</param>
/// <returns>The deleted CardDetailsConfig if found; otherwise, null.</returns>
public async Task<CardDetailsConfig?> DeleteOne(ObjectId configId)
{
return await DeleteAsync(configId);
}
/// <summary>
/// Updates the display configuration ID reference. This method is not implemented.
/// </summary>
/// <param name="displayConfigId">The ObjectId of the display configuration.</param>
/// <param name="resultId">The new reference ID to set.</param>
/// <returns>Always throws NotImplementedException.</returns>
/// <exception cref="NotImplementedException">This method is not implemented.</exception>
public Task<object> UpdateCardConfigId(ObjectId? displayConfigId, ObjectId? resultId)
{
throw new NotImplementedException();
@@ -13,19 +13,27 @@ using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing Display entities in MongoDB.
/// Provides CRUD operations and specialized queries for display devices.
/// </summary>
public class DisplayRepository : MongoRepository<Display>, IDisplayRepository
{
#region Properties
private readonly ApiSettings _apiSettings;
private readonly ILogger<DisplayRepository> _logger;
// private readonly Idisplay<DisplayRepository> _logger;
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the DisplayRepository.
/// </summary>
/// <param name="apiSettings">API settings containing collection names configuration.</param>
/// <param name="database">The MongoDB database instance.</param>
/// <param name="logger">Logger for repository operations.</param>
public DisplayRepository(
IOptions<ApiSettings> apiSettings,
IMongoDatabase database,
@@ -35,14 +43,16 @@ public class DisplayRepository : MongoRepository<Display>, IDisplayRepository
_apiSettings = apiSettings.Value;
}
#endregion
#region Methods
#region Create
/// <summary>
/// Creates the necessary indexes for the Display collection.
/// Creates indexes on displayConfigId and unitId fields for improved query performance.
/// </summary>
public override async Task CreateIndexes()
{
var options = new CreateIndexOptions<Display> { Background = true, Unique = false };
@@ -60,17 +70,32 @@ public class DisplayRepository : MongoRepository<Display>, IDisplayRepository
#region Read
/// <summary>
/// Gets the name of the collection for displays.
/// </summary>
/// <returns>The collection name from API settings.</returns>
public override string GetCollectionName()
{
return _apiSettings.Displays;
}
/// <summary>
/// Retrieves all displays from the database.
/// </summary>
/// <returns>A list of all Display entities.</returns>
public async Task<List<Display>> GetAll()
{
var result = await Collection.Find(Builders<Display>.Filter.Empty).ToListAsync();
return result;
}
/// <summary>
/// Retrieves paginated displays with optional filtering.
/// Supports filtering by text, unit ID, unit name, and display type.
/// </summary>
/// <param name="filter">The pagination and filtering parameters.</param>
/// <returns>A fluent queryable for Display results.</returns>
/// <exception cref="BadRequestException">Thrown when the text filter exceeds 100 characters.</exception>
public IFindFluent<Display, Display> GetPaginatedDisplays(PaginationFilter filter)
{
var filterBuilder = Builders<Display>.Filter;
@@ -117,6 +142,12 @@ public class DisplayRepository : MongoRepository<Display>, IDisplayRepository
return CreateFindFluent(filters, sort);
}
/// <summary>
/// Creates a fluent query for paginated displays with combined filters.
/// </summary>
/// <param name="filters">List of filter definitions to apply.</param>
/// <param name="sort">Sort definition for the query results.</param>
/// <returns>A fluent queryable for Display results.</returns>
private IFindFluent<Display, Display> CreateFindFluent(List<FilterDefinition<Display>> filters,
SortDefinition<Display> sort)
{
@@ -126,6 +157,11 @@ public class DisplayRepository : MongoRepository<Display>, IDisplayRepository
return Collection.Find(combinedFilter).Sort(sort);
}
/// <summary>
/// Retrieves all displays associated with a specific point of care.
/// </summary>
/// <param name="pointOfCare">The PointOfCare to filter by.</param>
/// <returns>A list of Display entities associated with the point of care.</returns>
public async Task<List<Display>> GetByPointOfCare(PointOfCare pointOfCare)
{
var filter = Builders<Display>.Filter.AnyEq(x => x.PointOfCareIdList, pointOfCare.Id);
@@ -133,19 +169,34 @@ public class DisplayRepository : MongoRepository<Display>, IDisplayRepository
return result;
}
/// <summary>
/// Retrieves a display by its name.
/// </summary>
/// <param name="name">The name of the display to retrieve.</param>
/// <returns>The Display if found; otherwise, null.</returns>
public async Task<Display?> GetByName(string name)
{
var result = await Collection.FindAsync(Builders<Display>.Filter.Eq(p => p.Name, name));
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Retrieves a display by its ID.
/// </summary>
/// <param name="id">The ObjectId of the display to retrieve.</param>
/// <returns>The Display if found; otherwise, null.</returns>
public async Task<Display?> GetById(ObjectId id)
{
var result = await Collection.FindAsync(Builders<Display>.Filter.Eq(p => p.Id, id));
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Retrieves a display by its ID with the associated display configuration.
/// Performs a MongoDB lookup to join the display with its configuration.
/// </summary>
/// <param name="id">The ObjectId of the display to retrieve.</param>
/// <returns>The Display with DisplayConfig populated if found; otherwise, null.</returns>
public async Task<Display?> GetByIdWithConfigDisplay(ObjectId id)
{
var pipeline = new BsonDocument[]
@@ -169,6 +220,11 @@ public class DisplayRepository : MongoRepository<Display>, IDisplayRepository
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Retrieves all displays associated with a specific unit.
/// </summary>
/// <param name="id">The ObjectId of the unit.</param>
/// <returns>A list of Display entities associated with the unit.</returns>
public async Task<List<Display>> GetByUnitId(ObjectId id)
{
var filter = Builders<Display>.Filter.Eq(p => p.UnitId, id);
@@ -176,6 +232,12 @@ public class DisplayRepository : MongoRepository<Display>, IDisplayRepository
return result;
}
/// <summary>
/// Counts the number of displays associated with a specific unit.
/// </summary>
/// <param name="unitId">The ObjectId of the unit.</param>
/// <returns>The count of displays for the unit, or 0 if an error occurs.</returns>
/// <exception cref="Exception">Logs errors and returns 0 on failure.</exception>
public async Task<long> CountByUnitId(ObjectId unitId)
{
try
@@ -189,12 +251,24 @@ public class DisplayRepository : MongoRepository<Display>, IDisplayRepository
}
}
/// <summary>
/// Retrieves all displays associated with a specific display configuration.
/// </summary>
/// <param name="id">The ObjectId of the display configuration.</param>
/// <returns>A list of Display entities using the specified configuration.</returns>
public async Task<List<Display>> GetByConfigId(ObjectId id)
{
var result = await Collection.FindAsync(Builders<Display>.Filter.Eq(p => p.DisplayConfigId, id));
return result.ToList();
}
/// <summary>
/// Retrieves all displays that use a specific card configuration through their display configuration.
/// Performs an aggregation to join Display with DisplayConfig and filter by cardConfigId.
/// </summary>
/// <param name="configId">The ObjectId of the card configuration.</param>
/// <returns>A list of Display entities using the specified card config, or empty list on error.</returns>
/// <exception cref="Exception">Logs errors and returns empty list on failure.</exception>
public async Task<List<Display>> GetByCardConfigId(ObjectId configId)
{
try
@@ -228,6 +302,11 @@ public class DisplayRepository : MongoRepository<Display>, IDisplayRepository
}
}
/// <summary>
/// Checks if a display configuration is currently in use by any displays.
/// </summary>
/// <param name="displayConfigId">The ObjectId of the display configuration to check.</param>
/// <returns>The count of displays using the configuration.</returns>
public async Task<long> IsDisplayConfigInUse(ObjectId displayConfigId)
{
return await Collection.CountDocumentsAsync(
@@ -238,6 +317,13 @@ public class DisplayRepository : MongoRepository<Display>, IDisplayRepository
#region Update
/// <summary>
/// Updates the point of care list for a specific display.
/// </summary>
/// <param name="objectId">The ObjectId of the display to update.</param>
/// <param name="listPocObId">The new list of point of care ObjectIds.</param>
/// <returns>The updated Display if successful; otherwise, null.</returns>
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
public async Task<Display?> UpdatePointOfCareList(ObjectId objectId, List<ObjectId> listPocObId)
{
try
@@ -256,6 +342,13 @@ public class DisplayRepository : MongoRepository<Display>, IDisplayRepository
}
}
/// <summary>
/// Updates the display configuration for a specific display.
/// </summary>
/// <param name="objectId">The ObjectId of the display to update.</param>
/// <param name="config">The new DisplayConfig to set.</param>
/// <returns>The updated Display if successful; otherwise, null.</returns>
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
public async Task<Display?> UpdateConfig(ObjectId objectId, DisplayConfig config)
{
try
@@ -274,6 +367,13 @@ public class DisplayRepository : MongoRepository<Display>, IDisplayRepository
}
}
/// <summary>
/// Updates the display configuration ID reference for a specific display.
/// </summary>
/// <param name="objectId">The ObjectId of the display to update.</param>
/// <param name="displayConfigId">The new display configuration ObjectId to set.</param>
/// <returns>The updated Display if successful; otherwise, null.</returns>
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
public async Task<Display?> UpdateConfigId(ObjectId objectId, ObjectId displayConfigId)
{
try
@@ -292,6 +392,13 @@ public class DisplayRepository : MongoRepository<Display>, IDisplayRepository
}
}
/// <summary>
/// Updates the display configuration preset for a specific display.
/// </summary>
/// <param name="objectIdDisplay">The ObjectId of the display to update.</param>
/// <param name="objectIdConfigDisplay">The ObjectId of the display configuration preset.</param>
/// <returns>The updated Display if successful; otherwise, null.</returns>
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
public async Task<Display?> UpdateConfigPreset(ObjectId objectIdDisplay, ObjectId objectIdConfigDisplay)
{
try
@@ -310,6 +417,13 @@ public class DisplayRepository : MongoRepository<Display>, IDisplayRepository
}
}
/// <summary>
/// Updates the name of a specific display.
/// </summary>
/// <param name="display">The Display entity to update.</param>
/// <param name="name">The new name for the display.</param>
/// <returns>The updated Display if successful.</returns>
/// <exception cref="Exception">Throws an exception if MongoDB update fails.</exception>
public async Task<Display> UpdateName(Display display, string name)
{
try
@@ -333,6 +447,12 @@ public class DisplayRepository : MongoRepository<Display>, IDisplayRepository
#region Delete
/// <summary>
/// Deletes all displays associated with a specific unit.
/// </summary>
/// <param name="unitId">The ObjectId of the unit whose displays should be deleted.</param>
/// <returns>True if deletion was successful; otherwise, false.</returns>
/// <exception cref="Exception">Logs errors and returns false on failure.</exception>
public async Task<bool> DeleteManyByUnitId(ObjectId unitId)
{
try
@@ -10,12 +10,23 @@ using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing HistoricalConfigChanges entities in MongoDB.
/// Provides CRUD operations and specialized queries for tracking configuration changes over time.
/// </summary>
public class HistoricalConfigChangesRepository : MongoRepository<HistoricalConfigChanges>,
IHistoricalConfigChangesRepository
{
private readonly ApiSettings _apiSettings;
private readonly ILogger<HistoricalConfigChangesRepository> _logger;
/// <summary>
/// Initializes a new instance of the HistoricalConfigChangesRepository.
/// </summary>
/// <param name="apiSettings">API settings containing collection names configuration.</param>
/// <param name="database">The MongoDB database instance.</param>
/// <param name="logger">Logger for repository operations.</param>
/// <exception cref="ArgumentNullException">Thrown when apiSettings is null.</exception>
public HistoricalConfigChangesRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database,
ILogger<HistoricalConfigChangesRepository> logger) : base(database)
{
@@ -24,12 +35,21 @@ public class HistoricalConfigChangesRepository : MongoRepository<HistoricalConfi
_logger = logger;
}
/// <summary>
/// Gets the name of the collection for historical configuration changes.
/// </summary>
/// <returns>The collection name from API settings, or default "historicalConfigChanges".</returns>
public override string GetCollectionName()
{
return _apiSettings.HistoricalConfigChanges ?? "historicalConfigChanges";
}
/// <summary>
/// Inserts a new historical configuration change record and returns the inserted document.
/// </summary>
/// <param name="historicalConfigChanges">The HistoricalConfigChanges entity to insert.</param>
/// <returns>The inserted HistoricalConfigChanges with generated ID, or null if insertion fails.</returns>
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
public override async Task<HistoricalConfigChanges?> InsertOneAsync(HistoricalConfigChanges historicalConfigChanges)
{
try
@@ -44,18 +64,30 @@ public class HistoricalConfigChangesRepository : MongoRepository<HistoricalConfi
}
}
/// <summary>
/// Deletes a historical configuration change record by its ID.
/// </summary>
/// <param name="id">The ObjectId of the record to delete.</param>
/// <returns>The deleted HistoricalConfigChanges if found; otherwise, null.</returns>
public async Task<HistoricalConfigChanges?> Delete(ObjectId id)
{
return await DeleteAsync(id);
}
/// <summary>
/// Retrieves all historical configuration change records.
/// </summary>
/// <returns>A collection of all HistoricalConfigChanges entities.</returns>
public async Task<ICollection<HistoricalConfigChanges>> FindAll()
{
var result = await Collection.FindAsync(_ => true);
return await result.ToListAsync();
}
/// <summary>
/// Retrieves all IDs of historical configuration change records.
/// </summary>
/// <returns>A list of all ObjectIds in the collection.</returns>
public async Task<List<ObjectId>> FindAllIds()
{
List<ObjectId> listCollection = [];
@@ -66,13 +98,22 @@ public class HistoricalConfigChangesRepository : MongoRepository<HistoricalConfi
return listCollection;
}
/// <summary>
/// Retrieves a historical configuration change record by its ID.
/// </summary>
/// <param name="id">The ObjectId of the record to retrieve.</param>
/// <returns>The HistoricalConfigChanges if found; otherwise, null.</returns>
public async Task<HistoricalConfigChanges?> FindById(ObjectId id)
{
var result = await Collection.FindAsync(Builders<HistoricalConfigChanges>.Filter.Eq(x => x.Id, id));
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Updates an existing historical configuration change record.
/// </summary>
/// <param name="historicalConfigChanges">The HistoricalConfigChanges with updated values.</param>
/// <returns>The updated HistoricalConfigChanges if successful; otherwise, null.</returns>
public async Task<HistoricalConfigChanges?> Update(HistoricalConfigChanges historicalConfigChanges)
{
var filter = Builders<HistoricalConfigChanges>.Filter.Eq("_id", historicalConfigChanges.Id);
@@ -88,6 +129,13 @@ public class HistoricalConfigChangesRepository : MongoRepository<HistoricalConfi
});
}
/// <summary>
/// Retrieves the most recent historical configuration changes for a specific configuration type.
/// Results are sorted by time in descending order.
/// </summary>
/// <param name="cfgType">The type of configuration to filter by.</param>
/// <param name="num">The maximum number of records to retrieve. Default is 10.</param>
/// <returns>A collection of the most recent HistoricalConfigChanges for the specified type.</returns>
public async Task<ICollection<HistoricalConfigChanges>> FindLastHistoricalConfigChangesByType(
DisplayConfigEnums.ConfigTypes cfgType, int num = 10)
{
@@ -97,12 +145,20 @@ public class HistoricalConfigChangesRepository : MongoRepository<HistoricalConfi
var result = await Collection.FindAsync(
filter,
new FindOptions<HistoricalConfigChanges>
{ Sort = Builders<HistoricalConfigChanges>.Sort.Descending("time"), Limit = num }
{ Sort = Builders<HistoricalConfigChanges>.Sort.Descending("time"), Limit = num }
);
return await result.ToListAsync();
}
/// <summary>
/// Retrieves the most recent historical configuration changes for a specific user.
/// Optionally filters by configuration type. Results are sorted by time in descending order.
/// </summary>
/// <param name="user">The username to filter by.</param>
/// <param name="cfgType">Optional configuration type to filter by. If null, all types are included.</param>
/// <param name="num">The maximum number of records to retrieve. Default is 10.</param>
/// <returns>A collection of the most recent HistoricalConfigChanges for the specified user.</returns>
public async Task<ICollection<HistoricalConfigChanges>> FindLastHistoricalConfigChangesByUser(string user,
DisplayConfigEnums.ConfigTypes? cfgType = null, int num = 10)
{
@@ -114,12 +170,17 @@ public class HistoricalConfigChangesRepository : MongoRepository<HistoricalConfi
var result = await Collection.FindAsync(
filter,
new FindOptions<HistoricalConfigChanges>
{ Sort = Builders<HistoricalConfigChanges>.Sort.Descending("time"), Limit = num }
{ Sort = Builders<HistoricalConfigChanges>.Sort.Descending("time"), Limit = num }
);
return await result.ToListAsync();
}
/// <summary>
/// Creates the necessary indexes for the HistoricalConfigChanges collection.
/// Creates compound indexes on (configType, time) and (username, time) for improved query performance.
/// </summary>
/// <exception cref="Exception">Throws an exception if index creation fails; logs error details before throwing.</exception>
public override async Task CreateIndexes()
{
try
@@ -11,21 +11,39 @@ using System.Text.RegularExpressions;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing <see cref="LightBeacon"/> entities in MongoDB.
/// Provides CRUD operations and query capabilities specific to light beacons.
/// </summary>
public class LightBeaconRepository : MongoRepository<LightBeacon>, ILightBeaconRepository
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the <see cref="LightBeaconRepository"/> class.
/// </summary>
/// <param name="database">The MongoDB database instance used to access the collection.</param>
/// <param name="apiSettings">The application API settings containing configuration values, including the collection name.</param>
public LightBeaconRepository(IMongoDatabase database, IOptions<ApiSettings> apiSettings) : base(database)
{
_apiSettings = apiSettings.Value;
}
/// <summary>
/// Gets the name of the MongoDB collection used to store light beacons.
/// </summary>
/// <returns>The collection name retrieved from the API settings.</returns>
public override string GetCollectionName()
{
return _apiSettings.LightBeacons;
}
/// <summary>
/// Retrieves a list of light beacons whose identifiers are contained in the provided list of configuration relay identifiers.
/// </summary>
/// <param name="configurationRelayList">A list of <see cref="ObjectId"/> values representing the relay identifiers to filter by.</param>
/// <returns>A <see cref="List{LightBeacon}"/> containing the matching light beacons. Returns an empty list if no matches are found.</returns>
public List<LightBeacon> GetLightBeaconInList(List<ObjectId> configurationRelayList)
{
var filterBuilder = Builders<LightBeacon>.Filter;
@@ -36,6 +54,14 @@ public class LightBeaconRepository : MongoRepository<LightBeacon>, ILightBeaconR
return Collection.Find(filter).ToList();
}
/// <summary>
/// Asynchronously retrieves a light beacon by its unique identifier.
/// </summary>
/// <param name="relayId">The <see cref="ObjectId"/> of the relay (light beacon) to retrieve.</param>
/// <returns>
/// A <see cref="Task{LightBeacon}"/> representing the asynchronous operation.
/// The task result contains the <see cref="LightBeacon"/> if found; otherwise, <see langword="null"/>.
/// </returns>
public async Task<LightBeacon?> GetById(ObjectId relayId)
{
try
@@ -51,6 +77,14 @@ public class LightBeaconRepository : MongoRepository<LightBeacon>, ILightBeaconR
}
}
/// <summary>
/// Asynchronously retrieves a light beacon by its name.
/// </summary>
/// <param name="requestRelayName">The name of the relay (light beacon) to search for. Can be <see langword="null"/>.</param>
/// <returns>
/// A <see cref="Task{LightBeacon}"/> representing the asynchronous operation.
/// The task result contains the <see cref="LightBeacon"/> if found; otherwise, <see langword="null"/>.
/// </returns>
public async Task<LightBeacon?> GetByName(string? requestRelayName)
{
var filterBuilder = Builders<LightBeacon>.Filter;
@@ -60,6 +94,14 @@ public class LightBeaconRepository : MongoRepository<LightBeacon>, ILightBeaconR
return await Collection.Find(filter).FirstOrDefaultAsync();
}
/// <summary>
/// Asynchronously inserts a new light beacon into the database and returns the inserted entity.
/// </summary>
/// <param name="beacon">The <see cref="LightBeacon"/> instance to insert.</param>
/// <returns>
/// A <see cref="Task{LightBeacon}"/> representing the asynchronous operation.
/// The task result contains the inserted <see cref="LightBeacon"/> if successful; otherwise, <see langword="null"/> if the operation fails.
/// </returns>
public async Task<LightBeacon?> InsertOneAsyncAndReturn(LightBeacon beacon)
{
try
@@ -74,6 +116,15 @@ public class LightBeaconRepository : MongoRepository<LightBeacon>, ILightBeaconR
}
}
/// <summary>
/// Retrieves a paginated, sorted, and filtered set of light beacons based on the provided pagination filter.
/// Results are sorted ascending by name. When a text filter is provided, a case-insensitive regex match is performed on the name field.
/// </summary>
/// <param name="filter">The <see cref="PaginationFilter"/> containing pagination and filtering criteria.</param>
/// <returns>
/// An <see cref="IFindFluent{LightBeacon, LightBeacon}"/> instance that can be used to further refine and execute the query.
/// </returns>
/// <exception cref="BadRequestException">Thrown when the text filter exceeds 100 characters in length.</exception>
public IFindFluent<LightBeacon, LightBeacon> GetPaginatedRelays(PaginationFilter filter)
{
var filterBuilder = Builders<LightBeacon>.Filter;
@@ -103,16 +154,32 @@ public class LightBeaconRepository : MongoRepository<LightBeacon>, ILightBeaconR
return CreateFindFluent(filters, sort);
}
/// <summary>
/// Searches for light beacons whose name matches the specified text.
/// </summary>
/// <param name="textToSearch">The text to search for within light beacon names.</param>
/// <returns>
/// A <see cref="Task{List{LightBeacon}}"/> representing the asynchronous operation,
/// containing a list of matching <see cref="LightBeacon"/> objects.
/// </returns>
/// <exception cref="NotImplementedException">This method is not yet implemented.</exception>
public Task<List<LightBeacon>> GetSearchByName(string textToSearch)
{
throw new NotImplementedException();
}
/// <summary>
/// Creates an <see cref="IFindFluent{LightBeacon, LightBeacon}"/> instance by combining the provided filter definitions
/// and applying the specified sort order. If no filters are provided, an empty filter is used.
/// </summary>
/// <param name="filters">A list of <see cref="FilterDefinition{LightBeacon}"/> to be combined into the query.</param>
/// <param name="sort">The <see cref="SortDefinition{LightBeacon}"/> defining the sort order of the results.</param>
/// <returns>An <see cref="IFindFluent{LightBeacon, LightBeacon}"/> instance representing the constructed query.</returns>
private IFindFluent<LightBeacon, LightBeacon> CreateFindFluent(List<FilterDefinition<LightBeacon>> filters, SortDefinition<LightBeacon> sort)
{
var combinedFilter = filters.Any()
? Builders<LightBeacon>.Filter.And(filters)
: Builders<LightBeacon>.Filter.Empty;
: Builders<LightBeacon>.Filter.Empty;
return Collection.Find(combinedFilter).Sort(sort);
}
}
@@ -14,17 +14,33 @@ using Serilog;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Generic repository implementation for managing MasterList entities in MongoDB.
/// Provides CRUD operations and specialized queries for various master list types including
/// options lists, diagnoses, allergies, procedures, treatments, and other reference data.
/// </summary>
/// <typeparam name="T">The type of MasterList entity to manage.</typeparam>
public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository<T> where T : MasterList
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the MasterListRepository.
/// </summary>
/// <param name="apiSettings">API settings containing collection names configuration.</param>
/// <param name="database">The MongoDB database instance.</param>
/// <exception cref="ArgumentNullException">Thrown when apiSettings is null.</exception>
public MasterListRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
_apiSettings = apiSettings.Value;
}
/// <summary>
/// Gets the name of the collection based on the entity type T.
/// Maps different MasterList subtypes to their corresponding MongoDB collection names.
/// </summary>
/// <returns>The collection name for the current MasterList type.</returns>
public override string GetCollectionName()
{
return typeof(T) switch
@@ -55,6 +71,11 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
};
}
/// <summary>
/// Inserts a new master list entity into the database.
/// </summary>
/// <param name="entity">The entity to insert.</param>
/// <exception cref="Exception">Throws and re-throws exceptions after logging.</exception>
public override async Task InsertOneAsync(T entity)
{
try
@@ -68,6 +89,11 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}
/// <summary>
/// Deletes a master list entity by its ID.
/// </summary>
/// <param name="id">The ObjectId of the entity to delete.</param>
/// <exception cref="Exception">Throws and re-throws exceptions after logging.</exception>
public async Task Delete(ObjectId id)
{
try
@@ -82,6 +108,11 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}
/// <summary>
/// Updates an existing master list entity with full replacement.
/// </summary>
/// <param name="entity">The entity with updated values.</param>
/// <exception cref="Exception">Throws and re-throws exceptions after logging.</exception>
public async Task Update(T entity)
{
try
@@ -96,6 +127,12 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}
/// <summary>
/// Updates a specific option within a master list with full property replacement.
/// </summary>
/// <param name="id">The ObjectId of the master list.</param>
/// <param name="newOpt">The OptionList with updated values.</param>
/// <returns>The updated OptionList if found; otherwise, null.</returns>
public async Task<OptionList?> UpdateFullMasterListOption(ObjectId id, OptionList newOpt)
{
var filter = Builders<T>.Filter.Where(o => o.Id == id && o.Options.Any(opt => opt.Id == newOpt.Id)
@@ -123,6 +160,12 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
return updatedList;
}
/// <summary>
/// Finds a master list entity by its ID with options projection limited to 100 items.
/// </summary>
/// <param name="id">The ObjectId of the entity to retrieve.</param>
/// <returns>The MasterList entity if found; otherwise, null.</returns>
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
public async Task<T?> FindById(ObjectId id)
{
try
@@ -145,6 +188,15 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}
/// <summary>
/// Finds a specific option within a master list by master and option IDs with locale translation.
/// Uses MongoDB aggregation to apply translations and return the translated option.
/// </summary>
/// <param name="masterId">The ObjectId of the master list.</param>
/// <param name="optionId">The ObjectId of the option to retrieve.</param>
/// <param name="locale">The locale for translation.</param>
/// <returns>The OptionList with translated fields if found; otherwise, null.</returns>
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
public async Task<OptionList?> FindOptionItemById(ObjectId masterId, ObjectId optionId, LocaleEnum locale)
{
try
@@ -283,6 +335,13 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}
/// <summary>
/// Finds a specific option within a master list by master and option IDs without locale translation.
/// </summary>
/// <param name="masterId">The ObjectId of the master list.</param>
/// <param name="optionId">The ObjectId of the option to retrieve.</param>
/// <returns>The OptionList if found; otherwise, null.</returns>
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
public async Task<OptionList?> FindOptionItemById(ObjectId masterId, ObjectId optionId)
{
try
@@ -307,7 +366,14 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}
/// <summary>
/// Finds a master list entity by its ID with optional locale translation for options.
/// Uses MongoDB aggregation to unwind options and apply translations.
/// </summary>
/// <param name="id">The ObjectId of the entity to retrieve.</param>
/// <param name="locale">Optional locale for translated option names.</param>
/// <returns>The MasterList entity with translated options if found; otherwise, null.</returns>
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
public async Task<T?> FindById(ObjectId id, LocaleEnum? locale)
{
try
@@ -442,7 +508,6 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
})
}
}),
"$options.name"
})
})
@@ -460,7 +525,6 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}),
// 4. Agrupar de vuelta
new BsonDocument("$group", new BsonDocument
{
@@ -486,6 +550,12 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}
/// <summary>
/// Finds a master list entity by its name.
/// </summary>
/// <param name="name">The name of the master list to retrieve.</param>
/// <returns>The MasterList entity if found; otherwise, null.</returns>
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
public async Task<T?> FindByName(string name)
{
try
@@ -508,11 +578,22 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}
/// <summary>
/// Gets options from a master list filtered by text search.
/// </summary>
/// <param name="id">The ObjectId of the master list.</param>
/// <param name="textSearch">Optional text to search within options.</param>
/// <returns>A list of matching OptionList items.</returns>
public async Task<List<OptionList>> GetMasterListByIdAndTextSearchContaining(ObjectId id, string? textSearch)
{
return await GetOptionsByTextSearch(textSearch, id);
}
/// <summary>
/// Retrieves paginated master lists with optional text filtering.
/// </summary>
/// <param name="filter">The pagination and filtering parameters.</param>
/// <returns>A fluent queryable for MasterList results.</returns>
public IFindFluent<T, T> GetPaginatedMasterList(PaginationFilter filter)
{
var filterBuilder = Builders<T>.Filter;
@@ -534,6 +615,12 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
return CreateFindFluent(filters, sort);
}
/// <summary>
/// Retrieves paginated options within a master list with optional text filtering.
/// </summary>
/// <param name="filter">The pagination and filtering parameters.</param>
/// <param name="listId">The ObjectId of the master list.</param>
/// <returns>A list of filtered OptionList items.</returns>
public async Task<List<OptionList>> GetPaginatedOptions(PaginationFilter filter, ObjectId listId)
{
//TODO: LOCALE
@@ -552,6 +639,13 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
return options.ToList();
}
/// <summary>
/// Adds a new option to a master list.
/// </summary>
/// <param name="id">The ObjectId of the master list.</param>
/// <param name="opt">The option element to add.</param>
/// <returns>The newly created OptionList if successful; otherwise, null if duplicate exists.</returns>
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
public async Task<OptionList?> AddOptionToMasterList(ObjectId id, FilterOptionListElement opt)
{
var exist = await GetMasterListByIdAndSearchOptions(id, opt);
@@ -590,6 +684,11 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}
/// <summary>
/// Retrieves all master list entities.
/// </summary>
/// <returns>An enumerable of all MasterList entities.</returns>
/// <exception cref="Exception">Logs errors and returns empty list on failure.</exception>
public async Task<IEnumerable<T>> GetAll()
{
try
@@ -604,6 +703,11 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}
/// <summary>
/// Retrieves all master list entities without options, returning only metadata.
/// </summary>
/// <returns>An enumerable of MasterListDto containing id, name, description, listType, and options count.</returns>
/// <exception cref="Exception">Logs errors and returns empty list on failure.</exception>
public async Task<IEnumerable<MasterListDto>> GetAllWithoutOptions()
{
try
@@ -627,6 +731,11 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}
/// <summary>
/// Counts the total number of master list entities in the collection.
/// </summary>
/// <returns>The total count of entities.</returns>
/// <exception cref="Exception">Logs errors and returns 0 on failure.</exception>
public async Task<int> Count()
{
try
@@ -641,6 +750,14 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}
/// <summary>
/// Searches for options within a master list using multiple filter criteria.
/// Uses MongoDB aggregation pipeline to apply filters and locale translations.
/// </summary>
/// <param name="id">The ObjectId of the master list.</param>
/// <param name="filters">The filter criteria including text, name, description, and optionType.</param>
/// <returns>A list of matching OptionList items ordered by name.</returns>
/// <exception cref="Exception">Logs errors and returns empty list on failure.</exception>
public async Task<List<OptionList>> GetMasterListByIdAndSearchOptions(ObjectId id, FilterOptionListElement? filters)
{
try
@@ -713,10 +830,8 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
initDate: '$$o.initDate',
endDate: '$$o.endDate',
isDefault: '$$o.isDefault',
originalName: '$$o.name',
originalDescription: '$$o.description',
translatedName: {{
$let: {{
vars: {{
@@ -745,7 +860,6 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
in: {{ $ifNull: ['$$li.name', null] }}
}}
}},
translatedDescription: {{
$let: {{
vars: {{
@@ -851,6 +965,14 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}
/// <summary>
/// Updates a specific option within a master list with locale-aware field updates.
/// </summary>
/// <param name="id">The ObjectId of the master list.</param>
/// <param name="newOpt">The OptionList with updated values.</param>
/// <param name="locale">The locale for translation updates.</param>
/// <returns>The updated OptionList if successful; otherwise, null.</returns>
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
public async Task<OptionList?> UpdateMasterListOption(ObjectId id, OptionList newOpt, LocaleEnum locale)
{
// 1. Evitar duplicados
@@ -944,6 +1066,13 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}
/// <summary>
/// Updates a specific option within a master list with full replacement.
/// </summary>
/// <param name="id">The ObjectId of the master list.</param>
/// <param name="newOpt">The OptionList with updated values.</param>
/// <returns>The updated OptionList if successful; otherwise, null.</returns>
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
public async Task<OptionList?> UpdateMasterListOption(ObjectId id, OptionList newOpt)
{
var master = await FindById(id);
@@ -973,6 +1102,12 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}
/// <summary>
/// Deletes a specific option from a master list.
/// </summary>
/// <param name="id">The ObjectId of the master list.</param>
/// <param name="deleteOptId">The ObjectId of the option to delete.</param>
/// <returns>True if the option was deleted; otherwise, false.</returns>
public async Task<bool> DeleteMasterListOption(ObjectId id, ObjectId deleteOptId)
{
// Define el filtro para encontrar el documento por su _id
@@ -990,6 +1125,13 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
return false;
}
/// <summary>
/// Updates the metadata details for a master list.
/// </summary>
/// <param name="id">The ObjectId of the master list.</param>
/// <param name="opt">The UpdateMasterListDetailsDto with updated values.</param>
/// <returns>The updated UpdateMasterListDetailsDto if successful; otherwise, null.</returns>
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
public async Task<UpdateMasterListDetailsDto?> UpdateOptionDetailsToMasterList(ObjectId id,
UpdateMasterListDetailsDto opt)
{
@@ -1018,6 +1160,13 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}
/// <summary>
/// Updates the name of a master list.
/// </summary>
/// <param name="id">The ObjectId of the master list.</param>
/// <param name="name">The new name.</param>
/// <returns>True if the update was successful; otherwise, false.</returns>
/// <exception cref="Exception">Logs errors and returns false on failure.</exception>
public async Task<bool> UpdateMasterListName(ObjectId id, string name)
{
var filter = Builders<T>.Filter.And(
@@ -1037,6 +1186,13 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}
/// <summary>
/// Updates the description of a master list.
/// </summary>
/// <param name="id">The ObjectId of the master list.</param>
/// <param name="description">The new description.</param>
/// <returns>True if the update was successful; otherwise, false.</returns>
/// <exception cref="Exception">Logs errors and returns false on failure.</exception>
public async Task<bool> UpdateMasterListDescription(ObjectId id, string description)
{
var filter = Builders<T>.Filter.And(
@@ -1056,6 +1212,13 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}
/// <summary>
/// Removes an option from a master list by matching all its properties.
/// </summary>
/// <param name="id">The ObjectId of the master list.</param>
/// <param name="oldOpt">The OptionList to remove.</param>
/// <returns>True if the option was removed; otherwise, false.</returns>
/// <exception cref="Exception">Logs errors and returns false on failure.</exception>
public async Task<bool> RemoveMasterListOption(ObjectId id, OptionList oldOpt)
{
var filter = Builders<T>.Filter.Eq("_id", id);
@@ -1083,12 +1246,16 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}
/// <summary>
/// Creates necessary indexes for the MasterList collection.
/// Currently creates text indexes for DiagnosisList on options.name, options.description, and options._id.
/// </summary>
public override async Task CreateIndexes()
{
if (typeof(T) == typeof(DiagnosisList))
{
var options = new CreateIndexOptions
{ Background = true, Unique = false, LanguageOverride = "spanish", DefaultLanguage = "spanish" };
{ Background = true, Unique = false, LanguageOverride = "spanish", DefaultLanguage = "spanish" };
var optionsIndex = new CreateIndexModel<T>(
Builders<T>.IndexKeys.Ascending("options.name"), options);
@@ -1103,11 +1270,23 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}
/// <summary>
/// Searches for options across all master lists using text search.
/// </summary>
/// <param name="textSearch">Optional text to search within options.</param>
/// <returns>A list of matching OptionList items.</returns>
public async Task<List<OptionList>> GetMasterListByTextSearch(string? textSearch)
{
return await GetOptionsByTextSearch(textSearch);
}
/// <summary>
/// Searches for options within a master list using text search with accent-aware regex.
/// </summary>
/// <param name="textSearch">Optional text to search within options.</param>
/// <param name="id">Optional master list ObjectId to filter results.</param>
/// <returns>A list of matching OptionList items ordered by name.</returns>
/// <exception cref="Exception">Logs errors and returns empty list on failure.</exception>
private async Task<List<OptionList>> GetOptionsByTextSearch(string? textSearch, ObjectId? id = null)
{
try
@@ -1179,6 +1358,12 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
}
/// <summary>
/// Creates a fluent query for paginated results with combined filters.
/// </summary>
/// <param name="filters">List of filter definitions to apply.</param>
/// <param name="sort">Sort definition for the query results.</param>
/// <returns>A fluent queryable for T results.</returns>
private IFindFluent<T, T> CreateFindFluent(List<FilterDefinition<T>> filters, SortDefinition<T> sort)
{
var combinedFilter = filters.Any()
@@ -1187,6 +1372,13 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
return Collection.Find(combinedFilter).Sort(sort);
}
/// <summary>
/// Generates new locale items for a master list option based on a default locale.
/// Creates LocaleItem entries for all locales except the specified default.
/// </summary>
/// <param name="localeList">The default locale to exclude from translations.</param>
/// <param name="opt">The option name to use as default translation.</param>
/// <returns>A Locale object with translations for all other locales.</returns>
private Locale GetNewItemLocale(LocaleEnum localeList, string opt)
{
var newLocale = new Locale();
@@ -1220,6 +1412,12 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
}
// Método auxiliar para construir patrones regex con soporte de acentos
/// Builds a regex pattern that matches accented and non-accented versions of vowels.
/// Supports Spanish accent handling (á, é, í, ó, ú) and digits.
/// </summary>
/// <param name="input">The input string to build the pattern from.</param>
/// <returns>A regex-compatible pattern string.</returns>
private static string BuildRegexPattern(string input)
{
var regexPattern = new StringBuilder();
@@ -1252,6 +1450,15 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
return regexPattern.ToString();
}
/// <summary>
/// Searches for options within a master list by name with locale translation.
/// Uses MongoDB aggregation pipeline to apply locale-aware filtering.
/// </summary>
/// <param name="id">The ObjectId of the master list.</param>
/// <param name="newOptName">The option name to search for.</param>
/// <param name="locale">The locale for translation.</param>
/// <returns>A list of matching OptionList items ordered by name.</returns>
/// <exception cref="Exception">Logs errors and returns empty list on failure.</exception>
private async Task<List<OptionList>> GetMasterListByIdAndTextSearch(
ObjectId id, string newOptName, LocaleEnum locale)
{
@@ -1289,10 +1496,8 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
initDate: '$$o.initDate',
endDate: '$$o.endDate',
isDefault: '$$o.isDefault',
originalName: '$$o.name',
originalDescription: '$$o.description',
translatedName: {{
$let: {{
vars: {{
@@ -1348,7 +1553,6 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
initDate: '$$o.initDate',
endDate: '$$o.endDate',
isDefault: '$$o.isDefault',
name: {{
$cond: [
{{ $eq: ['{localeCode}', '$defaultLocale'] }},
@@ -9,23 +9,46 @@ using System.Text.RegularExpressions;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing <see cref="Medicine"/> entities in MongoDB.
/// Provides CRUD operations, search, pagination, and aggregation capabilities specific to medicines.
/// </summary>
public class MedicineRepository : MongoRepository<Medicine>, IMedicineRepository
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the <see cref="MedicineRepository"/> class.
/// </summary>
/// <param name="apiSettings">The application API settings containing configuration values, including the collection name. Cannot be <see langword="null"/>.</param>
/// <param name="database">The MongoDB database instance used to access the collection.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="apiSettings"/> is <see langword="null"/>.</exception>
public MedicineRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
_apiSettings = apiSettings.Value;
} //For testing
/// <summary>
/// Gets the name of the MongoDB collection used to store medicines.
/// Falls back to the default "medicines" collection name when not configured in the API settings.
/// </summary>
/// <returns>The collection name retrieved from the API settings, or "medicines" if not configured.</returns>
public override string GetCollectionName()
{
return _apiSettings.Medicines ?? "medicines";
}
/// <summary>
/// Asynchronously retrieves a medicine whose <c>Codes</c> or <c>Notes</c> collection contains the specified code.
/// </summary>
/// <param name="code">The code or note text to search for within the medicine's codes or notes.</param>
/// <returns>
/// A <see cref="Task{Medicine}"/> representing the asynchronous operation.
/// The task result contains the first <see cref="Medicine"/> matching the criteria, or <see langword="null"/> if no match is found.
/// </returns>
public async Task<Medicine?> GetMedicine(string code)
{
var result = await Collection.FindAsync(x => x.Codes.Contains(code) || x.Notes.Contains(code));
@@ -34,6 +57,14 @@ public class MedicineRepository : MongoRepository<Medicine>, IMedicineRepository
}
/// <summary>
/// Asynchronously retrieves all medicines whose <c>Codes</c> array contains any of the specified code values.
/// </summary>
/// <param name="codeNotes">A list of code strings to match against the medicine's codes. At least one code must match.</param>
/// <returns>
/// A <see cref="Task{List{Medicine}}"/> representing the asynchronous operation.
/// The task result contains a list of matching <see cref="Medicine"/> objects. Returns an empty list if no matches are found.
/// </returns>
public async Task<List<Medicine>> GetMedicineByCodeOrNote(List<string> codeNotes)
{
var filter = Builders<Medicine>.Filter.AnyIn("Codes", codeNotes.ToArray());
@@ -42,6 +73,14 @@ public class MedicineRepository : MongoRepository<Medicine>, IMedicineRepository
return result.ToList();
}
/// <summary>
/// Asynchronously retrieves a medicine by its exact name.
/// </summary>
/// <param name="name">The exact name of the medicine to search for.</param>
/// <returns>
/// A <see cref="Task{Medicine}"/> representing the asynchronous operation.
/// The task result contains the <see cref="Medicine"/> if found; otherwise, <see langword="null"/>.
/// </returns>
public async Task<Medicine?> GetMedicineByName(string name)
{
var filter = Builders<Medicine>.Filter.Eq(p => p.Name, name);
@@ -50,6 +89,13 @@ public class MedicineRepository : MongoRepository<Medicine>, IMedicineRepository
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Asynchronously retrieves all medicines stored in the collection.
/// </summary>
/// <returns>
/// A <see cref="Task{List{Medicine}}"/> representing the asynchronous operation.
/// The task result contains a list of all <see cref="Medicine"/> objects. Returns an empty list if the collection is empty.
/// </returns>
public async Task<List<Medicine>> GetAll()
{
var result = await Collection.FindAsync(_ => true);
@@ -57,6 +103,14 @@ public class MedicineRepository : MongoRepository<Medicine>, IMedicineRepository
return result.ToList();
}
/// <summary>
/// Asynchronously retrieves a medicine by its unique identifier.
/// </summary>
/// <param name="medicineId">The <see cref="ObjectId"/> of the medicine to retrieve.</param>
/// <returns>
/// A <see cref="Task{Medicine}"/> representing the asynchronous operation.
/// The task result contains the <see cref="Medicine"/> if found; otherwise, <see langword="null"/>.
/// </returns>
public async Task<Medicine?> GetMedicineById(ObjectId medicineId)
{
var filter = Builders<Medicine>.Filter.Eq(p => p.Id, medicineId);
@@ -65,6 +119,14 @@ public class MedicineRepository : MongoRepository<Medicine>, IMedicineRepository
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Asynchronously inserts a new medicine into the database and returns the inserted entity (looked up by name).
/// </summary>
/// <param name="medicine">The <see cref="Medicine"/> instance to insert.</param>
/// <returns>
/// A <see cref="Task{Medicine}"/> representing the asynchronous operation.
/// The task result contains the newly inserted <see cref="Medicine"/> retrieved by its name, or <see langword="null"/> if the lookup fails.
/// </returns>
public async Task<Medicine?> PostMedicine(Medicine medicine)
{
await Collection.InsertOneAsync(medicine);
@@ -73,6 +135,14 @@ public class MedicineRepository : MongoRepository<Medicine>, IMedicineRepository
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Asynchronously updates an existing medicine in the database and returns the updated entity.
/// </summary>
/// <param name="medicine">The <see cref="Medicine"/> instance containing the updated values. The <see cref="ObjectId"/> is used to identify the document.</param>
/// <returns>
/// A <see cref="Task{Medicine}"/> representing the asynchronous operation.
/// The task result contains the same <see cref="Medicine"/> instance that was passed in, after the update operation has been issued.
/// </returns>
public async Task<Medicine?> UpdateMedicine(Medicine medicine)
{
await UpdateOneAsync(medicine.Id, medicine);
@@ -80,6 +150,11 @@ public class MedicineRepository : MongoRepository<Medicine>, IMedicineRepository
return medicine;
}
/// <summary>
/// Asynchronously deletes a medicine from the database by its unique identifier.
/// </summary>
/// <param name="medicineId">The <see cref="ObjectId"/> of the medicine to delete.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous delete operation.</returns>
public async Task DeleteMedicineById(ObjectId medicineId)
{
var filter = Builders<Medicine>.Filter.Eq(po => po.Id, medicineId);
@@ -87,6 +162,16 @@ public class MedicineRepository : MongoRepository<Medicine>, IMedicineRepository
await Collection.DeleteOneAsync(filter);
}
/// <summary>
/// Retrieves a paginated, sorted, and filtered set of medicines based on the provided pagination filter.
/// Results are sorted ascending by name. Supports optional case-insensitive regex match on the medicine name,
/// and exact matches on code, type, and group.
/// </summary>
/// <param name="filter">The <see cref="PaginationFilter"/> containing pagination and filtering criteria.</param>
/// <returns>
/// An <see cref="IFindFluent{Medicine, Medicine}"/> instance that can be used to further refine and execute the query.
/// When no filters are provided, all medicines are returned sorted by name.
/// </returns>
public IFindFluent<Medicine, Medicine> GetPaginatedMedicines(PaginationFilter filter)
{
// Crear variable con la clase que construye los filtros que necesitamos
@@ -125,6 +210,16 @@ public class MedicineRepository : MongoRepository<Medicine>, IMedicineRepository
.Sort(sort);
}
/// <summary>
/// Builds an aggregation pipeline that retrieves the distinct values of a specified array field
/// from all medicines. The pipeline unwinds the field, groups by its value, sorts alphabetically,
/// and projects the result with the original field name.
/// </summary>
/// <param name="field">The name of the array field on the <see cref="Medicine"/> document to retrieve distinct values for (for example, "Codes", "Type", or "Group").</param>
/// <returns>
/// An <see cref="IAggregateFluent{BsonDocument}"/> representing the aggregation pipeline that, when executed,
/// yields documents containing the distinct values of the specified field.
/// </returns>
public IAggregateFluent<BsonDocument> GetDistinctFieldDataQuery(string field)
{
return Collection.Aggregate()
@@ -135,6 +230,14 @@ public class MedicineRepository : MongoRepository<Medicine>, IMedicineRepository
}
/// <summary>
/// Retrieves all distinct medicine groups available in the collection.
/// </summary>
/// <returns>
/// A <see cref="Task{List{String}}"/> representing the asynchronous operation,
/// containing a list of distinct group names as strings.
/// </returns>
/// <exception cref="NotImplementedException">This method is not yet implemented.</exception>
public Task<List<string>> GetAllGroups()
{
throw new NotImplementedException();
@@ -8,8 +8,18 @@ using Serilog;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Abstract base repository providing common MongoDB persistence operations for a given document type <typeparamref name="T"/>.
/// Concrete repositories must implement <see cref="GetCollectionName"/> to identify the target collection,
/// and may override <see cref="CreateIndexes"/>, <see cref="InsertInitialLoad"/>, and <see cref="InsertOneAsync(T)"/>
/// to customize schema setup, seeding, and insertion behavior.
/// </summary>
/// <typeparam name="T">The domain model type persisted in the MongoDB collection.</typeparam>
public abstract class MongoRepository<T> : IMongoRepository<T>
{
/// <summary>
/// The MongoDB database instance used by the repository.
/// </summary>
protected readonly IMongoDatabase Db;
private IMongoCollection<T>? _collection;
@@ -19,13 +29,27 @@ public abstract class MongoRepository<T> : IMongoRepository<T>
// Db = MongoDbHostBuilderExtension.GetMongoDb(dbSettings);
// }
/// <summary>
/// Initializes a new instance of the <see cref="MongoRepository{T}"/> class using the provided database.
/// </summary>
/// <param name="database">The MongoDB database instance used to access collections. Must not be <see langword="null"/>.</param>
protected MongoRepository(IMongoDatabase database)
{
Db = database;
}
/// <summary>
/// When implemented in a derived class, returns the name of the MongoDB collection used to store documents of type <typeparamref name="T"/>.
/// </summary>
/// <returns>The MongoDB collection name as a string.</returns>
public abstract string GetCollectionName();
/// <summary>
/// Gets the underlying <see cref="IMongoCollection{TDocument}"/> for the repository.
/// On first access, ensures the collection exists (creating it if necessary), then triggers asynchronous index creation
/// and initial data loading via <see cref="CreateIndexes"/> and <see cref="InsertInitialLoad"/>.
/// </summary>
/// <returns>The MongoDB collection of <typeparamref name="T"/> documents.</returns>
public IMongoCollection<T> Collection
{
get
@@ -44,6 +68,11 @@ public abstract class MongoRepository<T> : IMongoRepository<T>
set => _collection = value;
}
/// <summary>
/// Asynchronously inserts a single document into the collection. Errors are logged and swallowed.
/// </summary>
/// <param name="obj">The document of type <typeparamref name="T"/> to insert.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous insert operation.</returns>
public virtual async Task InsertOneAsync(T obj)
{
try
@@ -56,6 +85,14 @@ public abstract class MongoRepository<T> : IMongoRepository<T>
}
}
/// <summary>
/// Asynchronously replaces (or upserts) a single document identified by its <c>_id</c>.
/// Uses <see cref="ReplaceOptions"/> with <see cref="ReplaceOptions.IsUpsert"/> set to <see langword="true"/>
/// so that the document is created if it does not exist. Errors are logged and swallowed.
/// </summary>
/// <param name="id">The <see cref="ObjectId"/> value of the document's <c>_id</c> field.</param>
/// <param name="obj">The replacement document of type <typeparamref name="T"/>.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous replace/upsert operation.</returns>
public async Task UpdateOneAsync(ObjectId id, T obj)
{
try
@@ -71,6 +108,15 @@ public abstract class MongoRepository<T> : IMongoRepository<T>
}
}
/// <summary>
/// Asynchronously finds and deletes a single document identified by its <c>_id</c>.
/// Returns the deleted document, or the default value of <typeparamref name="T"/> if not found or on error.
/// </summary>
/// <param name="id">The <see cref="ObjectId"/> value of the document's <c>_id</c> field.</param>
/// <returns>
/// A <see cref="Task{T}"/> representing the asynchronous operation.
/// The task result contains the deleted document, or <see langword="null"/> / default if no document matched or an error occurred.
/// </returns>
public async Task<T?> DeleteAsync(ObjectId id)
{
try
@@ -85,16 +131,32 @@ public abstract class MongoRepository<T> : IMongoRepository<T>
}
}
/// <summary>
/// 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.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous index creation operation.</returns>
public virtual Task CreateIndexes()
{
return Task.CompletedTask;
}
/// <summary>
/// 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.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous seeding operation.</returns>
public virtual Task InsertInitialLoad()
{
return Task.CompletedTask;
}
/// <summary>
/// Asynchronously inserts multiple documents into the collection using unordered semantics
/// (a single failed insert does not abort the batch). Errors are logged and swallowed.
/// </summary>
/// <param name="obj">The list of documents of type <typeparamref name="T"/> to insert.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous bulk insert operation.</returns>
public virtual async Task InsertManyAsync(List<T> obj)
{
try
@@ -108,6 +170,14 @@ public abstract class MongoRepository<T> : IMongoRepository<T>
}
}
/// <summary>
/// Asynchronously updates all documents in the collection where the specified field equals <paramref name="oldId"/>,
/// setting that field to the new <paramref name="id"/>. Errors are logged and swallowed.
/// </summary>
/// <param name="nameId">The name of the field to match and update.</param>
/// <param name="id">The new <see cref="ObjectId"/> value to assign.</param>
/// <param name="oldId">The existing <see cref="ObjectId"/> value to replace.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous update operation.</returns>
protected async Task UpdateManyObjectIdAsync(string nameId, ObjectId id, ObjectId oldId)
{
try
@@ -123,6 +193,15 @@ public abstract class MongoRepository<T> : IMongoRepository<T>
}
}
/// <summary>
/// Asynchronously finds and deletes a single document identified by a string <c>_id</c>.
/// Returns the deleted document, or the default value of <typeparamref name="T"/> if not found or on error.
/// </summary>
/// <param name="id">The string value of the document's <c>_id</c> field.</param>
/// <returns>
/// A <see cref="Task{T}"/> representing the asynchronous operation.
/// The task result contains the deleted document, or <see langword="null"/> / default if no document matched or an error occurred.
/// </returns>
protected async Task<T?> DeleteAsync(string id)
{
try
@@ -137,6 +216,15 @@ public abstract class MongoRepository<T> : IMongoRepository<T>
}
}
/// <summary>
/// Determines whether a collection with the specified name exists in the current database.
/// Errors are logged and the method returns <see langword="false"/> in that case.
/// </summary>
/// <param name="collectionName">The name of the collection to check.</param>
/// <returns>
/// <see langword="true"/> if a collection with the given name exists; otherwise, <see langword="false"/>.
/// Returns <see langword="false"/> when an exception is thrown while querying the database.
/// </returns>
protected bool CollectionExists(string collectionName)
{
try
@@ -152,4 +240,4 @@ public abstract class MongoRepository<T> : IMongoRepository<T>
return false;
}
}
}
}
@@ -9,10 +9,20 @@ using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing Notice entities in MongoDB.
/// Provides CRUD operations for notices/notifications that can be displayed on screens.
/// </summary>
public class NoticeRepository : MongoRepository<Notice>, INoticeRepository
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the NoticeRepository.
/// </summary>
/// <param name="apiSettings">API settings containing collection names configuration.</param>
/// <param name="database">The MongoDB database instance.</param>
/// <exception cref="ArgumentNullException">Thrown when apiSettings is null.</exception>
public NoticeRepository(IOptions<ApiSettings>? apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings != null)
@@ -21,13 +31,21 @@ public class NoticeRepository : MongoRepository<Notice>, INoticeRepository
throw new ArgumentNullException(nameof(apiSettings));
}
/// <summary>
/// Gets the name of the collection for notices.
/// </summary>
/// <returns>The collection name from API settings.</returns>
public override string GetCollectionName()
{
return _apiSettings.Notices;
}
/// <summary>
/// Inserts a new notice into the database.
/// Automatically sets the NoticeDate to UTC current date and time before inserting.
/// </summary>
/// <param name="notice">The Notice entity to insert.</param>
/// <exception cref="Exception">Logs warning and silently fails on insertion error.</exception>
public override async Task InsertOneAsync(Notice notice)
{
try
@@ -41,6 +59,11 @@ public class NoticeRepository : MongoRepository<Notice>, INoticeRepository
}
}
/// <summary>
/// Deletes a notice by its ID.
/// </summary>
/// <param name="id">The ObjectId of the notice to delete.</param>
/// <exception cref="Exception">Throws and re-throws exceptions after logging.</exception>
public async Task Delete(ObjectId id)
{
try
@@ -55,6 +78,11 @@ public class NoticeRepository : MongoRepository<Notice>, INoticeRepository
}
}
/// <summary>
/// Updates an existing notice with new values.
/// </summary>
/// <param name="notice">The Notice entity with updated values.</param>
/// <exception cref="Exception">Throws and re-throws exceptions after logging.</exception>
public async Task Update(Notice notice)
{
try
@@ -68,6 +96,11 @@ public class NoticeRepository : MongoRepository<Notice>, INoticeRepository
}
}
/// <summary>
/// Retrieves all notices from the database.
/// </summary>
/// <returns>An enumerable of all Notice entities.</returns>
/// <exception cref="Exception">Logs errors and returns empty list on failure.</exception>
public async Task<IEnumerable<Notice>> FindAll()
{
try
@@ -82,6 +115,12 @@ public class NoticeRepository : MongoRepository<Notice>, INoticeRepository
}
}
/// <summary>
/// Retrieves a notice by its ID.
/// </summary>
/// <param name="id">The ObjectId of the notice to retrieve.</param>
/// <returns>The Notice if found; otherwise, null.</returns>
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
public async Task<Notice?> FindById(ObjectId id)
{
try
@@ -98,6 +137,12 @@ public class NoticeRepository : MongoRepository<Notice>, INoticeRepository
}
}
/// <summary>
/// Retrieves all notices for a specific date.
/// </summary>
/// <param name="date">The date to filter notices by.</param>
/// <returns>An enumerable of Notice entities matching the date, or null on error.</returns>
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
public async Task<IEnumerable<Notice>?> FindByDate(DateTime date)
{
try
@@ -114,6 +159,12 @@ public class NoticeRepository : MongoRepository<Notice>, INoticeRepository
}
}
/// <summary>
/// Retrieves all notices of a specific type.
/// </summary>
/// <param name="type">The notice type to filter by.</param>
/// <returns>An enumerable of Notice entities matching the type, or null on error.</returns>
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
public async Task<IEnumerable<Notice>?> FindByType(string type)
{
try
@@ -130,6 +181,12 @@ public class NoticeRepository : MongoRepository<Notice>, INoticeRepository
}
}
/// <summary>
/// Retrieves all notices associated with a specific display.
/// </summary>
/// <param name="displayId">The ObjectId of the display to filter by.</param>
/// <returns>An enumerable of Notice entities for the display, or null on error.</returns>
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
public async Task<IEnumerable<Notice>?> FindByDisplayId(ObjectId displayId)
{
try
@@ -146,6 +203,10 @@ public class NoticeRepository : MongoRepository<Notice>, INoticeRepository
}
}
/// <summary>
/// Creates the necessary indexes for the Notice collection.
/// Creates compound indexes on (noticeType, noticeDate) and (noticeDate) for improved query performance.
/// </summary>
public override async Task CreateIndexes()
{
var options = new CreateIndexOptions { Background = true, Unique = false };
@@ -9,19 +9,35 @@ using Serilog;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing PatientObservation archive entities in MongoDB.
/// Provides operations for storing and retrieving historical patient observations.
/// </summary>
public class ObservationArchiveRepository : MongoRepository<PatientObservation>, IObservationArchiveRepository
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the ObservationArchiveRepository.
/// </summary>
/// <param name="apiSettings">API settings containing collection names configuration.</param>
/// <param name="database">The MongoDB database instance.</param>
/// <exception cref="ArgumentNullException">Thrown when apiSettings is null.</exception>
public ObservationArchiveRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
_apiSettings = apiSettings.Value;
} //For testing
/// <summary>
/// Retrieves the aggregated last observations for a specific patient.
/// Filters observations by name and date, returning the most recent ones.
/// </summary>
/// <param name="patientId">The ObjectId of the patient.</param>
/// <param name="num">The maximum number of observations to return per observation type.</param>
/// <param name="lastDate">The cutoff date to filter observations (inclusive).</param>
/// <param name="filterObservations">Optional list of observation names to filter by. If null, retrieves all distinct observations.</param>
/// <returns>A list of PatientObservation entities sorted by time descending.</returns>
public async Task<List<PatientObservation>> AggregatedPatientLastObservations(ObjectId patientId, int num,
DateTime lastDate, List<string>? filterObservations = null)
{
@@ -44,11 +60,22 @@ public class ObservationArchiveRepository : MongoRepository<PatientObservation>,
return results;
}
/// <summary>
/// Gets the name of the collection for archived patient observations.
/// </summary>
/// <returns>The collection name from API settings, or default "archive_patients_observations".</returns>
public override string GetCollectionName()
{
return _apiSettings.ArchivePatientsObservations ?? "archive_patients_observations";
}
/// <summary>
/// Inserts a new patient observation into the archive with retry logic for duplicate key errors.
/// If a duplicate key error occurs, generates a new ObjectId and retries up to maxRetries times.
/// </summary>
/// <param name="patientObservation">The PatientObservation entity to insert.</param>
/// <exception cref="MongoWriteException">Throws when duplicate key error persists after max retries.</exception>
/// <exception cref="Exception">Throws when insertion fails for reasons other than duplicate key.</exception>
public new async Task InsertOneAsync(PatientObservation patientObservation)
{
const int maxRetries = 2; // Número máximo de reintentos
@@ -84,12 +111,23 @@ public class ObservationArchiveRepository : MongoRepository<PatientObservation>,
}
}
/// <summary>
/// Deletes all patient observations before a specified date.
/// Useful for archival cleanup operations.
/// </summary>
/// <param name="date">The cutoff date. Observations older than this date will be deleted.</param>
/// <returns>The number of deleted documents.</returns>
public async Task DeleteBeforeDate(DateTime date)
{
var filter = Builders<PatientObservation>.Filter.Lt(po => po.Time, date);
await Collection.DeleteManyAsync(filter);
}
/// <summary>
/// Inserts a batch of patient observations using bulk write operation.
/// </summary>
/// <param name="observations">An enumerable of PatientObservation entities to insert.</param>
/// <returns>The count of successfully inserted documents.</returns>
public async Task<long> InsertBatch(IEnumerable<PatientObservation> observations)
{
var writes = new List<WriteModel<PatientObservation>>();
@@ -100,6 +138,11 @@ public class ObservationArchiveRepository : MongoRepository<PatientObservation>,
return bulkInsert.InsertedCount;
}
/// <summary>
/// Retrieves all archived observations for a specific patient.
/// </summary>
/// <param name="patientId">The ObjectId of the patient.</param>
/// <returns>A list of all PatientObservation entities for the patient.</returns>
public async Task<List<PatientObservation>> FindAllFromPatient(ObjectId patientId)
{
var filter = Builders<PatientObservation>.Filter.Eq(p => p.PatientId, patientId);
@@ -108,6 +151,13 @@ public class ObservationArchiveRepository : MongoRepository<PatientObservation>,
return await result.ToListAsync();
}
/// <summary>
/// Aggregates distinct observation names for a patient using MongoDB aggregation pipeline.
/// If filterObservations is provided, returns that list; otherwise, computes distinct observations.
/// </summary>
/// <param name="patientId">The ObjectId of the patient.</param>
/// <param name="filterObservations">Optional pre-filtered list of observation names. If null or empty, computes distinct values.</param>
/// <returns>A list of distinct observation name strings.</returns>
private async Task<List<string>> AggregatePatientObservations(ObjectId patientId,
List<string>? filterObservations = null)
{
@@ -17,12 +17,23 @@ using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing <see cref="PatientObservation"/> entities in MongoDB.
/// Provides specialized query, aggregation, retention, and expiration operations for patient clinical observations.
/// </summary>
public class ObservationRepository : MongoRepository<PatientObservation>, IObservationRepository
{
private readonly ApiSettings _apiSettings;
private readonly ILogger<ObservationRepository> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="ObservationRepository"/> class.
/// </summary>
/// <param name="apiSettings">The application API settings containing configuration values, including the collection name. Must not be <see langword="null"/>.</param>
/// <param name="database">The MongoDB database instance used to access the collection.</param>
/// <param name="logger">The logger used to record diagnostic and error information.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="apiSettings"/> is <see langword="null"/>.</exception>
public ObservationRepository(
IOptions<ApiSettings>? apiSettings,
IMongoDatabase database,
@@ -38,13 +49,27 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
throw new ArgumentNullException(nameof(apiSettings));
}
} //For testing
/// <summary>
/// Gets the name of the MongoDB collection used to store patient observations.
/// Falls back to the default "patients_observations" collection name when not configured in the API settings.
/// </summary>
/// <returns>The collection name retrieved from the API settings, or "patients_observations" if not configured.</returns>
public override string GetCollectionName()
{
return _apiSettings.PatientsObservations ?? "patients_observations";
}
/// <summary>
/// Asynchronously finds the most recent observations of a specific type (by coding system and code) for a patient.
/// Results are sorted by time in descending order and limited to <paramref name="num"/> entries.
/// </summary>
/// <param name="patientId">The unique identifier of the patient.</param>
/// <param name="codingSystem">The coding system used (for example, LOINC, SNOMED).</param>
/// <param name="code">The code identifying the observation type within the coding system.</param>
/// <param name="num">The maximum number of recent observations to return. Defaults to 2.</param>
/// <returns>An <see cref="IEnumerable{PatientObservation}"/> containing the matching observations ordered from newest to oldest.</returns>
public async Task<IEnumerable<PatientObservation>> FindLastObservations(ObjectId patientId, string codingSystem,
string code, int num = 2)
{
@@ -57,12 +82,20 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
var result = await Collection.FindAsync(
filter,
new FindOptions<PatientObservation>
{ Sort = Builders<PatientObservation>.Sort.Descending("time"), Limit = num }
{ Sort = Builders<PatientObservation>.Sort.Descending("time"), Limit = num }
);
return result.ToEnumerable();
}
/// <summary>
/// Asynchronously finds the most recent observations of a specific coding system for a patient.
/// Results are sorted by time in descending order and limited to <paramref name="num"/> entries.
/// </summary>
/// <param name="patientId">The unique identifier of the patient.</param>
/// <param name="codingSystem">The coding system to filter observations by.</param>
/// <param name="num">The maximum number of recent observations to return. Defaults to 10.</param>
/// <returns>A <see cref="List{PatientObservation}"/> containing the matching observations ordered from newest to oldest.</returns>
public async Task<List<PatientObservation>> FindLastObservationsByCodingSystem(ObjectId patientId,
string codingSystem, int num = 10)
{
@@ -74,12 +107,20 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
var result = await Collection.FindAsync(
filter,
new FindOptions<PatientObservation>
{ Sort = Builders<PatientObservation>.Sort.Descending("time"), Limit = num }
{ Sort = Builders<PatientObservation>.Sort.Descending("time"), Limit = num }
);
return await result.ToListAsync();
}
/// <summary>
/// Asynchronously retrieves the most recent observations for a patient, optionally restricted to a list of observation names.
/// If <paramref name="filterObservations"/> is <see langword="null"/>, all distinct observation names for the patient are used.
/// </summary>
/// <param name="patientId">The unique identifier of the patient.</param>
/// <param name="num">The maximum number of observations to return per observation name.</param>
/// <param name="filterObservations">Optional list of observation names to restrict the query to. When <see langword="null"/>, all distinct names are discovered.</param>
/// <returns>A <see cref="List{PatientObservation}"/> containing the aggregated latest observations across all matching names.</returns>
public async Task<List<PatientObservation>> AggregatedPatientLastObservations(ObjectId patientId, int num,
List<string>? filterObservations = null)
{
@@ -107,6 +148,15 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
}
/// <summary>
/// Asynchronously retrieves the most recent observations for a patient that occurred on or before a given date,
/// optionally restricted to a list of observation names.
/// </summary>
/// <param name="patientId">The unique identifier of the patient.</param>
/// <param name="num">The maximum number of observations to return per observation name.</param>
/// <param name="lastDate">The inclusive upper bound (UTC) for the observation <c>time</c> field.</param>
/// <param name="filterObservations">Optional list of observation names to restrict the query to. When <see langword="null"/>, all distinct names are discovered.</param>
/// <returns>A <see cref="List{PatientObservation}"/> containing the matching observations.</returns>
public async Task<List<PatientObservation>> AggregatedPatientLastObservationsByLastDate(ObjectId patientId, int num,
DateTime lastDate, List<string>? filterObservations = null)
{
@@ -124,7 +174,7 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
var cursor = await Collection.FindAsync(
filter,
new FindOptions<PatientObservation>
{ Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("_id"), Limit = num }
{ Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("_id"), Limit = num }
);
results.AddRange(await cursor.ToListAsync());
@@ -134,6 +184,17 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
}
/// <summary>
/// Asynchronously retrieves the most recent observations for a patient, with per-field limits and an optional "expired" flag filter.
/// When a <see cref="Field"/> specifies <see cref="Field.OnlyExpired"/> as <see langword="true"/>, only expired observations are returned.
/// When <paramref name="filterObservations"/> is <see langword="null"/>, all observations for the patient are returned.
/// </summary>
/// <param name="patientId">The unique identifier of the patient.</param>
/// <param name="filterObservations">Optional list of <see cref="Field"/> descriptors defining the names, limits, and expiration filter. When <see langword="null"/>, all observations for the patient are returned.</param>
/// <returns>
/// A <see cref="List{PatientObservation}"/> containing the matching observations.
/// Returns an empty list when an exception occurs while querying the database.
/// </returns>
public async Task<List<PatientObservation>> AggregatedPatientLastObservationsByField(ObjectId patientId,
List<Field>? filterObservations)
{
@@ -179,7 +240,7 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
cursor = await Collection.FindAsync(
filter,
new FindOptions<PatientObservation>
{ Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("id") });
{ Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("id") });
results.AddRange(cursor.ToEnumerable());
}
@@ -194,6 +255,17 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
}
/// <summary>
/// Asynchronously executes an aggregation pipeline that groups a patient's observations into time buckets
/// (second / minute / hour / day / shift / times) and computes per-bucket results such as first, last, min, max, sum, average, or count.
/// Supports a "since last observation" mode and a configurable look-back window based on the regularity.
/// </summary>
/// <param name="patientId">The unique identifier of the patient.</param>
/// <param name="groupedField">A <see cref="GroupedField"/> descriptor containing the observation name(s), regularity, look-back window, and the result computations to perform.</param>
/// <returns>
/// A <see cref="List{BsonDocument}"/> with one document per time bucket.
/// Returns an empty list when an exception occurs while executing the pipeline.
/// </returns>
public async Task<List<BsonDocument>> AggregatedPatientGroupedObservations(ObjectId patientId,
GroupedField groupedField)
{
@@ -267,9 +339,9 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
if (groupedField.Regularity == GroupedObservationEnum.Regularity.Minute ||
groupedField.Regularity == GroupedObservationEnum.Regularity.Second ||
groupedField.Regularity == GroupedObservationEnum.Regularity.Times
//No queremos los minutos cuando pedimos por turno, en principio. Revisar si en algún caso necesitamos los minutos, quitado de momento por problemas
// a la hora de devolver el last.
//|| groupedField.regularity == Regularity.Shift
//No queremos los minutos cuando pedimos por turno, en principio. Revisar si en algún caso necesitamos los minutos, quitado de momento por problemas
// a la hora de devolver el last.
//|| groupedField.regularity == Regularity.Shift
)
{
projectDate.Add("m", new BsonDocument { { "$minute", "$time" } });
@@ -470,6 +542,14 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
}
/// <summary>
/// Asynchronously inserts a single patient observation, automatically retrying with a new <see cref="ObjectId"/>
/// when a MongoDB duplicate-key error is encountered. The retry strategy is bounded by an internal maximum.
/// </summary>
/// <param name="patientObservation">The <see cref="PatientObservation"/> to insert. If a duplicate-key error occurs, a new identifier is generated and the insert is retried.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous insert operation.</returns>
/// <exception cref="MongoWriteException">Rethrown after the maximum number of retries has been reached when a duplicate-key error keeps occurring.</exception>
/// <exception cref="Exception">Rethrown when an unexpected error occurs during the insert operation.</exception>
public new async Task InsertOneAsync(PatientObservation patientObservation)
{
const int maxRetries = 2; // Número máximo de reintentos
@@ -489,7 +569,7 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
retryCount, maxRetries);
if (retryCount < maxRetries) continue;
_logger.LogError(
"Maximum retry attempts reached. Could not insert document due to duplicate key error.");
throw; // Relanzar la excepción después de alcanzar el número máximo de reintentos
@@ -501,18 +581,40 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
}
}
/// <summary>
/// Asynchronously deletes all observations associated with the specified patient.
/// </summary>
/// <param name="id">The <see cref="ObjectId"/> of the patient whose observations should be removed.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous delete operation.</returns>
public async Task DeleteByPatientId(ObjectId id)
{
var filter = Builders<PatientObservation>.Filter.Eq(po => po.PatientId, id);
await Collection.DeleteManyAsync(filter);
}
/// <summary>
/// Asynchronously returns a cursor over all observations for a given patient, using a server-side batch size of 100.
/// </summary>
/// <param name="patientId">The unique identifier of the patient.</param>
/// <returns>
/// An <see cref="IAsyncCursor{PatientObservation}"/> that can be enumerated to retrieve the patient's observations.
/// </returns>
public async Task<IAsyncCursor<PatientObservation>> FindByPatientIdAsync(ObjectId patientId)
{
var filter = Builders<PatientObservation>.Filter.Eq(ob => ob.PatientId, patientId);
return await Collection.FindAsync(filter, new FindOptions<PatientObservation> { BatchSize = 100 });
}
/// <summary>
/// Asynchronously returns a cursor over all observations for a patient that match a given coding system and observation name.
/// </summary>
/// <param name="patientId">The unique identifier of the patient.</param>
/// <param name="codingSystem">The coding system to filter by.</param>
/// <param name="name">The observation name to filter by.</param>
/// <returns>
/// An <see cref="IAsyncCursor{PatientObservation}"/> containing the matching observations,
/// retrieved with a server-side batch size of 100.
/// </returns>
public async Task<IAsyncCursor<PatientObservation>> FindByPatientIdAndCodingSystemAsync(ObjectId patientId,
string codingSystem, string name)
{
@@ -527,6 +629,13 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
}
/// <summary>
/// Asynchronously deletes a single observation by its identifier. This method hides the base
/// <c>DeleteAsync(ObjectId)</c> defined on <see cref="MongoRepository{T}"/> because the base returns the deleted document,
/// while this implementation is fire-and-forget.
/// </summary>
/// <param name="id">The <see cref="ObjectId"/> of the observation to delete.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous delete operation.</returns>
public new async Task DeleteAsync(ObjectId id)
{
var filter = Builders<PatientObservation>.Filter.Eq(obs => obs.Id, id);
@@ -534,6 +643,13 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
}
/// <summary>
/// Deletes all observations with the specified name that are older than the configured number of days.
/// Returns the documents that were deleted for downstream processing.
/// </summary>
/// <param name="name">The observation name to filter by.</param>
/// <param name="retentionPolicyValue">The retention window expressed in days. Observations older than <c>UtcNow - retentionPolicyValue</c> days are removed.</param>
/// <returns>A <see cref="List{PatientObservation}"/> containing the documents that were deleted.</returns>
public async Task<List<PatientObservation>> DeleteOlderDaysAsync(string name, int retentionPolicyValue)
{
var dateLimit = DateTime.UtcNow.AddDays(-1 * retentionPolicyValue);
@@ -549,6 +665,13 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
}
/// <summary>
/// Deletes all observations with the specified name that are older than the configured number of seconds.
/// Returns the documents that were deleted for downstream processing.
/// </summary>
/// <param name="name">The observation name to filter by.</param>
/// <param name="retentionPolicyValue">The retention window expressed in seconds. Observations older than <c>UtcNow - retentionPolicyValue</c> seconds are removed.</param>
/// <returns>A <see cref="List{PatientObservation}"/> containing the documents that were deleted.</returns>
public async Task<List<PatientObservation>> DeleteOlderSecondsAsync(string name, int retentionPolicyValue)
{
var dateLimit = DateTime.UtcNow.AddSeconds(-1 * retentionPolicyValue);
@@ -564,6 +687,13 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
}
/// <summary>
/// Keeps only the <paramref name="retentionPolicyValue"/> most recent observations for the specified name,
/// deleting all older ones. Returns the documents that were deleted for downstream processing.
/// </summary>
/// <param name="name">The observation name to apply the retention policy to.</param>
/// <param name="retentionPolicyValue">The maximum number of observations to retain. The remainder are deleted.</param>
/// <returns>A <see cref="List{PatientObservation}"/> containing the documents that were deleted. Returns an empty list when nothing had to be deleted.</returns>
public async Task<List<PatientObservation>> DeleteOlderNumberAsync(string name, int retentionPolicyValue)
{
var builder = Builders<PatientObservation>.Filter;
@@ -587,6 +717,13 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
}
/// <summary>
/// Asynchronously checks whether an observation exists for a given patient with the specified <c>SystemId</c>.
/// Only the document identifier is projected, making the query lightweight.
/// </summary>
/// <param name="patientid">The unique identifier of the patient.</param>
/// <param name="systemId">The external system identifier to look up.</param>
/// <returns><see langword="true"/> if a matching observation exists; otherwise, <see langword="false"/>.</returns>
public async Task<bool> ExistBySystemId(ObjectId patientid, string systemId)
{
return await Collection.Find(Builders<PatientObservation>.Filter.And(
@@ -598,6 +735,16 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
}
/// <summary>
/// Asynchronously finds the most recent observation for a patient with the given name that occurred strictly before the specified date.
/// </summary>
/// <param name="patientId">The unique identifier of the patient.</param>
/// <param name="name">The observation name to search for. Can be <see langword="null"/>.</param>
/// <param name="date">The upper-bound (exclusive) observation <c>time</c>.</param>
/// <returns>
/// A <see cref="Task{PatientObservation}"/> representing the asynchronous operation.
/// The task result contains the most recent matching observation, or <see langword="null"/> if no observation matches.
/// </returns>
public async Task<PatientObservation?> FindLastObservationBeforeDate(ObjectId patientId, string? name,
DateTime date)
{
@@ -610,11 +757,21 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
var result = await Collection.FindAsync(filter,
new FindOptions<PatientObservation>
{ Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("_id") });
{ Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("_id") });
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Asynchronously retrieves all observations for a patient with a given name whose <c>time</c> exactly matches the provided value.
/// </summary>
/// <param name="patientId">The unique identifier of the patient.</param>
/// <param name="name">The observation name to match. Can be <see langword="null"/>.</param>
/// <param name="date">The exact <c>time</c> value to match.</param>
/// <returns>
/// A <see cref="Task{List{PatientObservation}}"/> representing the asynchronous operation.
/// The task result contains a list of matching observations, which may be empty.
/// </returns>
public async Task<List<PatientObservation>?> FindAnyWithSameDate(ObjectId patientId, string? name, DateTime date)
{
var builder = Builders<PatientObservation>.Filter;
@@ -627,6 +784,14 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
}
/// <summary>
/// Asynchronously retrieves all observations for a patient whose <c>time</c> is strictly before the specified date.
/// </summary>
/// <param name="patientId">The unique identifier of the patient.</param>
/// <param name="date">The upper-bound (exclusive) observation <c>time</c>.</param>
/// <returns>
/// A <see cref="List{PatientObservation}"/> containing the matching observations, which may be empty.
/// </returns>
public async Task<List<PatientObservation>> FindAnyBeforeDate(ObjectId patientId, DateTime date)
{
var filterBuilder = Builders<PatientObservation>.Filter;
@@ -639,6 +804,19 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
return result.ToList();
}
/// <summary>
/// Asynchronously retrieves, for a given patient and observation name, the latest occurrence of each distinct value.
/// Optionally restricts the result to observations not older than <paramref name="expires"/> seconds.
/// </summary>
/// <param name="patientId">The unique identifier of the patient.</param>
/// <param name="name">The observation name to search for.</param>
/// <param name="expires">
/// Optional expiration window expressed in seconds. When provided, only observations newer than
/// <c>UtcNow - expires</c> seconds are considered.
/// </param>
/// <returns>
/// A <see cref="List{PatientObservation}"/> containing the most recent observation for each distinct value.
/// </returns>
public async Task<List<PatientObservation>> FindLatestUniqueValuesByName(ObjectId patientId, string name,
int? expires)
{
@@ -680,6 +858,15 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
}
/// <summary>
/// Asynchronously retrieves the currently active intravenous-line observations for a patient,
/// grouping by the <c>Location</c> and <c>Type</c> of the <see cref="PatientIntravenousLinesValue"/>.
/// Within each group, only the most recent observation (ordered by <c>time</c> and <c>id</c>) is returned.
/// </summary>
/// <param name="patientId">The unique identifier of the patient.</param>
/// <returns>
/// A <see cref="List{PatientObservation}"/> containing one observation per unique (Location, Type) combination.
/// </returns>
public async Task<List<PatientObservation?>> AggregatedPatientActiveIntravenousLinesObservations(ObjectId patientId)
{
var results = new List<PatientObservation>();
@@ -687,7 +874,7 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
var cursor = await Collection.FindAsync(
o => o.PatientId == patientId && o.Name == "IntravenousLinesObs",
new FindOptions<PatientObservation>
{ Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("id") });
{ Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("id") });
results.AddRange(cursor.ToEnumerable());
@@ -705,6 +892,13 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
}
/// <summary>
/// Asynchronously retrieves, for every patient with observations, the timestamp of the most recent observation.
/// Implemented as a server-side aggregation that groups by <c>patientid</c> and selects the latest <c>time</c> value.
/// </summary>
/// <returns>
/// A <see cref="Dictionary{ObjectId, DateTime}"/> mapping each patient's <see cref="ObjectId"/> to the UTC timestamp of their latest observation.
/// </returns>
public async Task<Dictionary<ObjectId, DateTime>> FindAllLastPatientObservationTime()
{
var group = new BsonDocument
@@ -739,15 +933,23 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
.Where(obs => obs.GetValue("_id").BsonType != BsonType.Null)
.ToList()
.ForEach(obs =>
{
if (obs.Get("_id") != null)
result.Add(obs.Get("_id")?.AsObjectId ?? new ObjectId(),
obs.Get("time")?.ToUniversalTime() ?? DateTime.MinValue);
}
{
if (obs.Get("_id") != null)
result.Add(obs.Get("_id")?.AsObjectId ?? new ObjectId(),
obs.Get("time")?.ToUniversalTime() ?? DateTime.MinValue);
}
);
return result;
}
/// <summary>
/// Asynchronously finds an observation by its unique identifier.
/// </summary>
/// <param name="id">The <see cref="ObjectId"/> of the observation to retrieve.</param>
/// <returns>
/// A <see cref="Task{PatientObservation}"/> representing the asynchronous operation.
/// The task result contains the <see cref="PatientObservation"/> if found; otherwise, <see langword="null"/>.
/// </returns>
public async Task<PatientObservation?> FindById(ObjectId id)
{
var filter = Builders<PatientObservation>.Filter.Eq(o => o.Id, id);
@@ -757,6 +959,14 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Asynchronously retrieves all observations for a patient by the patient's identifier.
/// </summary>
/// <param name="id">The <see cref="ObjectId"/> of the patient whose observations should be retrieved.</param>
/// <returns>
/// A <see cref="Task{List{PatientObservation}}"/> representing the asynchronous operation.
/// The task result contains a list of observations, which may be empty.
/// </returns>
public async Task<List<PatientObservation>?> FindByPatientId(ObjectId id)
{
var filter = Builders<PatientObservation>.Filter.Eq(o => o.PatientId, id);
@@ -767,16 +977,34 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
}
/// <summary>
/// Asynchronously updates a single observation by replacing its document with the provided instance.
/// </summary>
/// <param name="observation">The <see cref="PatientObservation"/> whose <see cref="ObjectId"/> identifies the document to update.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous update operation.</returns>
public async Task Update(PatientObservation observation)
{
await UpdateOneAsync(observation.Id, observation);
}
/// <summary>
/// Asynchronously updates all documents in the collection where the specified field equals <paramref name="oldId"/>,
/// setting that field to the new <paramref name="id"/>. Thin wrapper around the protected helper on the base repository.
/// </summary>
/// <param name="nameId">The name of the field to match and update.</param>
/// <param name="id">The new <see cref="ObjectId"/> value to assign.</param>
/// <param name="oldId">The existing <see cref="ObjectId"/> value to replace.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous update operation.</returns>
public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId)
{
await UpdateManyObjectIdAsync(nameId, id, oldId);
}
/// <summary>
/// Asynchronously marks the supplied list of observations as expired by setting their <c>Expired</c> flag to <see langword="true"/>.
/// </summary>
/// <param name="expiredObservations">The list of <see cref="PatientObservation"/> instances to mark as expired. The set of identifiers is used to build the update filter.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous update operation.</returns>
public async Task UpdateExpiredObservations(List<PatientObservation> expiredObservations)
{
var filter = Builders<PatientObservation>.Filter
@@ -786,6 +1014,12 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
_ = await Collection.UpdateManyAsync(filter, update);
}
/// <summary>
/// Asynchronously applies the given update definition to a set of observations identified by their identifiers.
/// </summary>
/// <param name="patientObservations">The observations whose identifiers form the target set of the update.</param>
/// <param name="update">The <see cref="UpdateDefinition{PatientObservation}"/> describing the changes to apply to each matching document.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous update operation.</returns>
public async Task UpdateMany(IEnumerable<PatientObservation> patientObservations,
UpdateDefinition<PatientObservation> update)
{
@@ -794,6 +1028,12 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
await Collection.UpdateManyAsync(filter, update);
}
/// <summary>
/// Asynchronously returns every observation stored in the collection.
/// </summary>
/// <returns>
/// An <see cref="IEnumerable{PatientObservation}"/> containing all observations.
/// </returns>
public async Task<IEnumerable<PatientObservation>> FindAll()
{
var result = await Collection.FindAsync(_ => true);
@@ -801,6 +1041,16 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
return result.ToEnumerable();
}
/// <summary>
/// Asynchronously marks observations as expired when their <c>time</c> is older than the configured expiration
/// window defined by the supplied <see cref="ConfigObservation"/> entries. Only observations that are not
/// already marked as expired are updated. Errors are logged and swallowed.
/// </summary>
/// <param name="configObservationsToExpire">
/// A list of <see cref="ConfigObservation"/> entries describing the observation names and their expiration windows
/// (in minutes). Observations whose <c>time</c> is older than <c>DateTime.Now - expires</c> minutes are marked as expired.
/// </param>
/// <returns>A <see cref="Task"/> representing the asynchronous update operation.</returns>
public async Task ExpireExpiredObservations(
List<ConfigObservation> configObservationsToExpire)
{
@@ -834,6 +1084,17 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
}
/// <summary>
/// Asynchronously retrieves observations that are not marked as expired, optionally restricted to a list of observation names.
/// Observations with a <see langword="null"/> name are excluded from the result.
/// </summary>
/// <param name="filterObservations">
/// Optional list of observation names to match. When <see langword="null"/>, all non-null named observations are considered
/// (still subject to the not-expired filter).
/// </param>
/// <returns>
/// An <see cref="IEnumerable{PatientObservation}"/> containing the matching non-expired observations.
/// </returns>
public async Task<IEnumerable<PatientObservation>> FindNotExpired(List<string?>? filterObservations)
{
var filterBuilder = Builders<PatientObservation>.Filter;
@@ -855,6 +1116,19 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
}
/// <summary>
/// Asynchronously finds observations whose name matches the given pattern (case-insensitive substring/regex),
/// optionally restricted to those with a <c>time</c> greater than <paramref name="fromDate"/>.
/// </summary>
/// <param name="name">The regex pattern to match against the observation name. Cannot be null, empty, whitespace, or longer than 100 characters.</param>
/// <param name="fromDate">Optional inclusive lower bound on the observation <c>time</c>.</param>
/// <returns>
/// An <see cref="IEnumerable{PatientObservation}"/> containing the matching observations.
/// </returns>
/// <exception cref="BadRequestException">
/// Thrown when <paramref name="name"/> is null, empty, or whitespace,
/// or when its length exceeds 100 characters.
/// </exception>
public async Task<IEnumerable<PatientObservation>> FindByName(string name, DateTime? fromDate = null)
{
if (string.IsNullOrWhiteSpace(name))
@@ -883,6 +1157,16 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
return await result.ToListAsync();
}
/// <summary>
/// Builds a paginated, sorted, and filtered query over observations.
/// Results are sorted by <c>time</c> in descending order. When a <see cref="PaginationFilter.FilteredRequest"/>
/// is provided, observations can be filtered by name list and patient identifier. A mandatory time window
/// (defaulting to <c>DateTime.MinValue</c> / <c>DateTime.MaxValue</c>) is always applied.
/// </summary>
/// <param name="filter">The <see cref="PaginationFilter"/> containing pagination and filter criteria.</param>
/// <returns>
/// An <see cref="IFindFluent{PatientObservation, PatientObservation}"/> instance that can be used to further refine and execute the query.
/// </returns>
public IFindFluent<PatientObservation, PatientObservation> GetPaginatedObservations(PaginationFilter filter)
{
// Crear variable con la clase que construye los filtros
@@ -916,6 +1200,20 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
.Sort(sort);
}
/// <summary>
/// Asynchronously retrieves the most recent observations for a patient with a given name,
/// optionally bounded to a recent time window and an explicit maximum count.
/// </summary>
/// <param name="patientId">The unique identifier of the patient.</param>
/// <param name="name">The observation name to filter by.</param>
/// <param name="endAfter">
/// Optional look-back window in seconds. When provided, only observations whose <c>time</c> is greater than or equal to
/// <c>UtcNow - endAfter</c> are returned.
/// </param>
/// <param name="num">Optional maximum number of observations to return. <see langword="null"/> means no limit.</param>
/// <returns>
/// An <see cref="IEnumerable{PatientObservation}"/> containing the matching observations ordered from newest to oldest.
/// </returns>
public async Task<IEnumerable<PatientObservation>> FindLastNotExpiredObservatonsByPatient(ObjectId patientId,
string name, int? endAfter = null, int? num = null)
{
@@ -950,6 +1248,12 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
return result.ToEnumerable();
}
/// <summary>
/// Creates the indexes required by the observations collection to support the repository's query patterns.
/// Indexes are created in the background and are non-unique. If an error occurs, it is logged and rethrown.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous index creation operation.</returns>
/// <exception cref="Exception">Rethrown when an error occurs while creating the indexes.</exception>
public override async Task CreateIndexes()
{
try
@@ -978,6 +1282,20 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
}
/// <summary>
/// Determines the list of observation names to use when running a per-name aggregation for a patient.
/// If <paramref name="filterObservations"/> is null or empty, the distinct observation names currently
/// stored for the patient are discovered via a server-side <c>$group</c> aggregation.
/// </summary>
/// <param name="patientId">The unique identifier of the patient.</param>
/// <param name="filterObservations">
/// Optional list of observation names to use. When null or empty, the method discovers the patient's
/// distinct observation names automatically.
/// </param>
/// <returns>
/// A <see cref="List{String}"/> containing the observation names to aggregate over.
/// Returns an empty list if no observations exist for the patient.
/// </returns>
private async Task<List<string>> AggregatePatientObservations(ObjectId patientId,
List<string>? filterObservations = null)
{
@@ -6,6 +6,9 @@ using MongoDB.Driver;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Provides a MongoDB-backed repository implementation for PoCMapping entities.
/// </summary>
public class PoCMappingRepository : MongoRepository<PoCMapping>, IPoCMappingRepository
{
private readonly ApiSettings _apiSettings;
@@ -15,13 +18,22 @@ public class PoCMappingRepository : MongoRepository<PoCMapping>, IPoCMappingRepo
_apiSettings = apiSettings.Value;
} //For testing
/// <summary>
/// Retrieves the name of the mappings collection from the API settings configuration.
/// </summary>
/// <returns>The mappings collection name as configured in <c>_apiSettings</c>.</returns>
public override string GetCollectionName()
{
return _apiSettings.Mappings;
}
/// <summary>
/// Asynchronously retrieves a <see cref="PoCMapping"/> from the underlying MongoDB collection by matching its <see cref="PoCMapping.Id"/> against the supplied key, returning <c>null</c> when no matching document is found.
/// </summary>
/// <param name="key">The unique identifier (Id) of the <see cref="PoCMapping"/> to look up.</param>
/// <returns>A Task TResult containing the matching PoCMapping, or <c>null</c> if no document with the specified key exists in the collection.</returns>
public async Task<PoCMapping?> FindByKey(string key)
{
var filterBuilder = Builders<PoCMapping>.Filter;
@@ -10,25 +10,40 @@ using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing Patient archive entities in MongoDB.
/// Provides CRUD operations for historical/archived patient data.
/// </summary>
public class PatientArchiveRepository : MongoRepository<Patient>, IPatientArchiveRepository
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the PatientArchiveRepository.
/// </summary>
/// <param name="apiSettings">API settings containing collection names configuration.</param>
/// <param name="database">The MongoDB database instance.</param>
/// <exception cref="ArgumentNullException">Thrown when apiSettings is null.</exception>
public PatientArchiveRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
_apiSettings = apiSettings.Value;
} //For testing
/// <summary>
/// Gets the name of the collection for archived patients.
/// </summary>
/// <returns>The collection name from API settings, or default "archive_patient".</returns>
public override string GetCollectionName()
{
return _apiSettings.ArchivePatient ?? "archive_patient";
}
/// <summary>
/// Finds an archived patient by their patient number.
/// </summary>
/// <param name="patientNumber">The patient number to search for.</param>
/// <returns>The Patient if found; otherwise, null.</returns>
public async Task<Patient?> FindByPatientNumber(string patientNumber)
{
if (string.IsNullOrWhiteSpace(patientNumber)) return null;
@@ -41,7 +56,10 @@ public class PatientArchiveRepository : MongoRepository<Patient>, IPatientArchiv
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Retrieves all archived patients from the collection.
/// </summary>
/// <returns>A list of all Patient entities in the archive.</returns>
public async Task<List<Patient>> FindAll()
{
var filterBuilder = Builders<Patient>.Filter;
@@ -52,6 +70,13 @@ public class PatientArchiveRepository : MongoRepository<Patient>, IPatientArchiv
return await result.ToListAsync();
}
/// <summary>
/// Searches for an archived patient by patient number, returning null if multiple matches exist.
/// This is useful when patientNumber may be incomplete and could match multiple patients.
/// </summary>
/// <param name="patientNumber">The patient number to search for.</param>
/// <param name="unitId">The unit ID (currently not used in query, kept for interface compatibility).</param>
/// <returns>The unique Patient if exactly one match is found; otherwise, null if multiple or none.</returns>
public async Task<Patient?> SearchByPatientNumberAndDistinctUnit(string patientNumber, ObjectId unitId)
{
if (string.IsNullOrWhiteSpace(patientNumber)) return null;
@@ -64,6 +89,13 @@ public class PatientArchiveRepository : MongoRepository<Patient>, IPatientArchiv
return patient.Count > 1 ? null : patient.FirstOrDefault();
}
/// <summary>
/// Inserts or updates an archived patient.
/// If a patient with the same PatientNumber already exists, merges the data and updates the record.
/// If no match exists, performs a regular insert.
/// </summary>
/// <param name="obj">The Patient entity to insert or merge.</param>
/// <exception cref="Exception">Logs warning and silently fails on error.</exception>
public override async Task InsertOneAsync(Patient obj)
{
try
@@ -118,7 +150,10 @@ public class PatientArchiveRepository : MongoRepository<Patient>, IPatientArchiv
}
}
/// <summary>
/// Creates the necessary indexes for the PatientArchive collection.
/// Creates an index on patientNumber for improved query performance.
/// </summary>
public override async Task CreateIndexes()
{
var options = new CreateIndexOptions { Background = true, Unique = false };
@@ -7,6 +7,11 @@ using MongoDB.Driver;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Represents a MongoDB-backed repository for managing <see cref="PatientCarePlan"/> entities,
/// providing concrete persistence operations defined by the <see cref="IPatientCarePlanRepository"/> contract.
/// </summary>
/// <typeparam name="PatientCarePlan">The type of the patient care plan entity managed by this repository.</typeparam>
public class PatientCarePlanRepository : MongoRepository<PatientCarePlan>, IPatientCarePlanRepository
{
#region Properties
@@ -17,10 +22,16 @@ public class PatientCarePlanRepository : MongoRepository<PatientCarePlan>, IPati
#region Update
/// <summary>
/// Asynchronously updates multiple object identifiers for a patient by delegating to the underlying asynchronous operation.
/// </summary>
/// <param name="patientid">The string identifier of the patient whose object identifiers will be updated.</param>
/// <param name="patientId">The new <see cref="ObjectId"/> to assign to the patient's records.</param>
/// <param name="oldId">The existing <see cref="ObjectId"/> to be replaced.</param>
public async Task UpdateManyObjectId(string patientid, ObjectId patientId, ObjectId oldId)
{
await UpdateManyObjectIdAsync(patientid, patientId, oldId);
}
{
await UpdateManyObjectIdAsync(patientid, patientId, oldId);
}
#endregion
@@ -40,28 +51,47 @@ public class PatientCarePlanRepository : MongoRepository<PatientCarePlan>, IPati
#region Read
/// <summary>
/// Returns the collection name for patient care plan data, using the configured setting if available or a default fallback otherwise.
/// </summary>
/// <returns>The patient care plan collection name from <c>_apiSettings.PatientCarePlan</c>, or <c>"patients_care_plan"</c> when the setting is null.</returns>
public override string GetCollectionName()
{
return _apiSettings.PatientCarePlan ?? "patients_care_plan";
}
{
return _apiSettings.PatientCarePlan ?? "patients_care_plan";
}
/// <summary>
/// Retrieves all care plans associated with the specified patient identifier from the data store.
/// Returns an empty list when no matching care plans exist for the patient.
/// </summary>
/// <param name="patientId">The unique identifier of the patient whose care plans should be retrieved.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains a list of <see cref="PatientCarePlan"/> objects associated with the specified patient, or an empty list if none are found.</returns>
public async Task<List<PatientCarePlan>> FindByPatientId(ObjectId patientId)
{
var result = await Collection.FindAsync(Builders<PatientCarePlan>.Filter.Eq(p => p.PatientId, patientId));
return result.ToList();
}
{
var result = await Collection.FindAsync(Builders<PatientCarePlan>.Filter.Eq(p => p.PatientId, patientId));
return result.ToList();
}
/// <summary>
/// Retrieves all patient care plans associated with the specified user identifier from the collection.
/// </summary>
/// <param name="userId">The unique identifier of the user whose patient care plans are being queried.</param>
/// <returns>A list of <see cref="PatientCarePlan"/> instances matching the specified user identifier; an empty list is returned if no plans are found.</returns>
public async Task<List<PatientCarePlan>> FindByUserId(ObjectId userId)
{
var result = await Collection.FindAsync(Builders<PatientCarePlan>.Filter.Eq(p => p.UserId, userId));
return result.ToList();
}
{
var result = await Collection.FindAsync(Builders<PatientCarePlan>.Filter.Eq(p => p.UserId, userId));
return result.ToList();
}
/// <summary>
/// Retrieves all patient care plans from the data store without applying any filter.
/// </summary>
/// <returns>A task that represents the asynchronous operation, containing a list of all <see cref="PatientCarePlan"/> records.</returns>
public async Task<List<PatientCarePlan>> FindAll()
{
var result = await Collection.Find(Builders<PatientCarePlan>.Filter.Empty).ToListAsync();
return result;
}
{
var result = await Collection.Find(Builders<PatientCarePlan>.Filter.Empty).ToListAsync();
return result;
}
#endregion
}
File diff suppressed because it is too large Load Diff
@@ -10,6 +10,10 @@ using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Represents a MongoDB-backed repository for <see cref="PoCSettings"/> entities, implementing the <see cref="IPoCSettingsRepository"/> contract to provide data access operations.
/// </summary>
/// <typeparam name="PoCSettings">The type of the settings entity managed by the repository.</typeparam>
public class PoCSettingsRepository : MongoRepository<PoCSettings>, IPoCSettingsRepository
{
private readonly ApiSettings _apiSettings;
@@ -21,112 +25,143 @@ public class PoCSettingsRepository : MongoRepository<PoCSettings>, IPoCSettingsR
}
/// <summary>
/// Retrieves the collection name used for Proof of Concept (PoC) settings, returning the value from the API settings or the default "poc_settings" when no value is configured.
/// </summary>
/// <returns>The configured PoC settings collection name, or "poc_settings" as a fallback when <c>_apiSettings.PoCSettings</c> is null.</returns>
public override string GetCollectionName()
{
return _apiSettings.PoCSettings ?? "poc_settings";
}
{
return _apiSettings.PoCSettings ?? "poc_settings";
}
/// <summary>
/// Deletes the PoCSettings document matching the specified identifier from the collection.
/// </summary>
/// <param name="id">The unique identifier of the PoCSettings document to delete.</param>
public async Task Delete(ObjectId id)
{
try
{
var filter = Builders<PoCSettings>.Filter.Eq(x => x.Id, id);
await Collection.DeleteOneAsync(filter, null);
try
{
var filter = Builders<PoCSettings>.Filter.Eq(x => x.Id, id);
await Collection.DeleteOneAsync(filter, null);
}
catch (Exception ex)
{
Log.Error("Error deleting PoCSettings by id: {id}. Exception: {ex}", id, ex);
throw;
}
}
catch (Exception ex)
{
Log.Error("Error deleting PoCSettings by id: {id}. Exception: {ex}", id, ex);
throw;
}
}
/// <summary>
/// Retrieves all PoCSettings records from the collection.
/// If an error occurs during the retrieval, the exception is logged and an empty list is returned as a fallback.
/// </summary>
/// <returns>A task that represents the asynchronous operation. The task result contains a list of all PoCSettings records, or an empty list if an error occurs.</returns>
public async Task<List<PoCSettings>> FindAll()
{
try
{
return (await Collection.FindAsync(Builders<PoCSettings>.Filter.Empty)).ToList();
try
{
return (await Collection.FindAsync(Builders<PoCSettings>.Filter.Empty)).ToList();
}
catch (Exception ex)
{
Log.Error("Error searching PoCSettings. Exception: {ex}", ex);
return [];
}
}
catch (Exception ex)
{
Log.Error("Error searching PoCSettings. Exception: {ex}", ex);
return [];
}
}
/// <summary>
/// Retrieves a <see cref="PoCSettings"/> document from the collection by its unique identifier.
/// Returns <c>null</c> when no matching document is found or when an error occurs while querying the database.
/// </summary>
/// <param name="id">The <see cref="ObjectId"/> used to locate the PoCSettings document.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the matching <see cref="PoCSettings"/> or <c>null</c> if not found.</returns>
public async Task<PoCSettings?> FindById(ObjectId id)
{
try
{
var filter = Builders<PoCSettings>.Filter.Eq(p => p.Id, id);
var result = await Collection.FindAsync(filter);
return await result.FirstOrDefaultAsync();
try
{
var filter = Builders<PoCSettings>.Filter.Eq(p => p.Id, id);
var result = await Collection.FindAsync(filter);
return await result.FirstOrDefaultAsync();
}
catch (Exception ex)
{
Log.Error("Error searching PoCSettings by id: {id}. Exception: {ex}", id, ex);
return null;
}
}
catch (Exception ex)
{
Log.Error("Error searching PoCSettings by id: {id}. Exception: {ex}", id, ex);
return null;
}
}
/// <summary>
/// Retrieves the first PoCSettings record from the collection where the PatientLocation property is not null.
/// </summary>
/// <param name="location">The patient location provided as search criteria. Detailed attribute-based filtering by location fields is currently commented out.</param>
/// <returns>The first matching PoCSettings record, or null if no record is found.</returns>
public async Task<PoCSettings?> FindByLocation(PatientLocation location)
{
try
{
var filterBuilder = Builders<PoCSettings>.Filter;
var filter = filterBuilder.Ne(p => p.PatientLocation, null);
// TODO
// if (!string.IsNullOrEmpty(location.PointOfCare) && !string.IsNullOrEmpty(location.Bed))
// {
// filter = filterBuilder.And(
// filter,
// filterBuilder.Eq(p => p.PatientLocation!.PointOfCare, location.PointOfCare),
// filterBuilder.Eq(p => p.PatientLocation!.Bed, location.Bed)
// );
// }
var result = await Collection.Find(filter).Limit(1).FirstOrDefaultAsync();
return result;
try
{
var filterBuilder = Builders<PoCSettings>.Filter;
var filter = filterBuilder.Ne(p => p.PatientLocation, null);
// TODO
// if (!string.IsNullOrEmpty(location.PointOfCare) && !string.IsNullOrEmpty(location.Bed))
// {
// filter = filterBuilder.And(
// filter,
// filterBuilder.Eq(p => p.PatientLocation!.PointOfCare, location.PointOfCare),
// filterBuilder.Eq(p => p.PatientLocation!.Bed, location.Bed)
// );
// }
var result = await Collection.Find(filter).Limit(1).FirstOrDefaultAsync();
return result;
}
catch (Exception ex)
{
Log.Debug("Error searching by location. Exception: {ex}", ex);
throw;
}
}
catch (Exception ex)
{
Log.Debug("Error searching by location. Exception: {ex}", ex);
throw;
}
}
/// <summary>
/// Updates the existing PoC (Proof of Concept) settings asynchronously. If the update fails, the exception is logged and rethrown to the caller.
/// </summary>
/// <param name="pocSettings">The PoC settings entity to be updated, identified by its <c>Id</c>.</param>
public async Task Update(PoCSettings pocSettings)
{
try
{
await UpdateOneAsync(pocSettings.Id, pocSettings);
try
{
await UpdateOneAsync(pocSettings.Id, pocSettings);
}
catch (Exception ex)
{
Log.Debug("Error updating PoC Settings: {pocS}. Exception: {ex}", pocSettings.ToString(), ex);
throw;
}
}
catch (Exception ex)
{
Log.Debug("Error updating PoC Settings: {pocS}. Exception: {ex}", pocSettings.ToString(), ex);
throw;
}
}
/// <summary>
/// Creates the MongoDB indexes required for the <see cref="PoCSettings"/> collection, applying non-unique background indexing on the <c>patientLocation</c> field.
/// </summary>
public override async Task CreateIndexes()
{
var options = new CreateIndexOptions { Background = true, Unique = false };
//var optionsUq = new CreateIndexOptions<PoCSettings>()
//{
// Background = true,
// Unique = true,
// PartialFilterExpression = Builders<PoCSettings>.Filter.Exists(p => p.PatientLocation) &
// Builders<PoCSettings>.Filter.Exists(p => p.ManualRelayStatus)
//};
var indexes = new List<CreateIndexModel<PoCSettings>>
{
new("{ patientLocation: 1 }", options)
//new("{ relayStatus: 1, bed: 1 }", optionsUq)
};
await MongoUtils.EnsureIndexes(Collection, indexes);
}
var options = new CreateIndexOptions { Background = true, Unique = false };
//var optionsUq = new CreateIndexOptions<PoCSettings>()
//{
// Background = true,
// Unique = true,
// PartialFilterExpression = Builders<PoCSettings>.Filter.Exists(p => p.PatientLocation) &
// Builders<PoCSettings>.Filter.Exists(p => p.ManualRelayStatus)
//};
var indexes = new List<CreateIndexModel<PoCSettings>>
{
new("{ patientLocation: 1 }", options)
//new("{ relayStatus: 1, bed: 1 }", optionsUq)
};
await MongoUtils.EnsureIndexes(Collection, indexes);
}
}
File diff suppressed because it is too large Load Diff
@@ -7,6 +7,12 @@ using MongoDB.Driver;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Represents a MongoDB-backed repository for <see cref="PumpAlarmEvent"/> documents, exposing pump alarm event-specific data access through the <see cref="IPumpAlarmEventRepository"/> contract.
/// </summary>
/// <remarks>
/// Inherits the generic MongoDB persistence capabilities of <see cref="MongoRepository{TDocument}"/>, specializing them for the <see cref="PumpAlarmEvent"/> entity type.
/// </remarks>
public class PumpAlarmEventRepository : MongoRepository<PumpAlarmEvent>, IPumpAlarmEventRepository
{
private readonly ApiSettings _apiSettings;
@@ -8,6 +8,9 @@ using MongoDB.Driver;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Represents a MongoDB-based repository for <see cref="PumpAlarmState"/> entities, providing concrete data access functionality defined by the <see cref="IPumpAlarmStateRepository"/> contract.
/// </summary>
public class PumpAlarmStateRepository : MongoRepository<PumpAlarmState>, IPumpAlarmStateRepository
{
private readonly ApiSettings _apiSettings;
@@ -20,10 +23,14 @@ public class PumpAlarmStateRepository : MongoRepository<PumpAlarmState>, IPumpAl
/// <summary>
/// Gets the collection name for the pump alarm state, returning the value configured in the API settings or the default "pump_alarm_state" when the configuration is not set.
/// </summary>
/// <returns>The configured collection name, or the default "pump_alarm_state" if the API setting is null.</returns>
public override string GetCollectionName()
{
return _apiSettings.PumpAlarmState ?? "pump_alarm_state";
}
{
return _apiSettings.PumpAlarmState ?? "pump_alarm_state";
}
public override async Task CreateIndexes()
{
@@ -51,18 +58,27 @@ public class PumpAlarmStateRepository : MongoRepository<PumpAlarmState>, IPumpAl
await Collection.Indexes.CreateManyAsync(indexModels);
}
/// <summary>
/// Retrieves the first active <see cref="PumpAlarmState"/> matching the specified device and optional alarm criteria.
/// The filter is always constrained by <paramref name="deviceId"/>, and additionally by <paramref name="alarmType"/> and <paramref name="alarmCodeMdc"/> when those values are provided.
/// Returns <c>null</c> when no matching alarm state is found.
/// </summary>
/// <param name="deviceId">The identifier of the device whose alarm state should be retrieved. Always applied to the query filter.</param>
/// <param name="alarmType">The optional alarm type used to further narrow the filter. When <c>null</c>, the alarm type is not applied.</param>
/// <param name="alarmCodeMdc">The optional alarm code (MDC) used to further narrow the filter. Ignored when <c>null</c>, empty, or whitespace.</param>
/// <returns>A <see cref="PumpAlarmState"/> instance if a matching record is found; otherwise, <c>null</c>.</returns>
public async Task<PumpAlarmState?> FindActiveAsync(string deviceId, PumpEnum.AlarmType? alarmType, string? alarmCodeMdc = null)
{
var filter = Builders<PumpAlarmState>.Filter.Eq(x => x.DeviceId, deviceId);
if (alarmType.HasValue)
filter &= Builders<PumpAlarmState>.Filter.Eq(x => x.AlarmType, alarmType);
if (!string.IsNullOrWhiteSpace(alarmCodeMdc))
filter &= Builders<PumpAlarmState>.Filter.Eq(x => x.AlarmCodeMdc, alarmCodeMdc);
return await Collection.Find(filter).FirstOrDefaultAsync();
}
{
var filter = Builders<PumpAlarmState>.Filter.Eq(x => x.DeviceId, deviceId);
if (alarmType.HasValue)
filter &= Builders<PumpAlarmState>.Filter.Eq(x => x.AlarmType, alarmType);
if (!string.IsNullOrWhiteSpace(alarmCodeMdc))
filter &= Builders<PumpAlarmState>.Filter.Eq(x => x.AlarmCodeMdc, alarmCodeMdc);
return await Collection.Find(filter).FirstOrDefaultAsync();
}
public async Task UpsertActiveAsync(PumpAlarmState state)
{
@@ -87,34 +103,56 @@ public class PumpAlarmStateRepository : MongoRepository<PumpAlarmState>, IPumpAl
}
/// <summary>
/// Removes pump alarm state records from the collection that match the specified device identifier, optionally narrowed by alarm type and/or alarm code MDC.
/// </summary>
/// <param name="deviceId">The identifier of the device whose alarm state records should be removed. Used as a mandatory filter criterion.</param>
/// <param name="alarmType">The optional alarm type to further restrict which records are deleted. When <see langword="null"/>, records of any alarm type for the device are removed.</param>
/// <param name="alarmCodeMdc">The optional alarm code MDC used to further narrow the deletion. Blank or whitespace values are ignored.</param>
public async Task RemoveAsync(string? deviceId, PumpEnum.AlarmType? alarmType, string? alarmCodeMdc = null)
{
var filter = Builders<PumpAlarmState>.Filter.Eq(x => x.DeviceId, deviceId);
if (alarmType.HasValue)
filter &= Builders<PumpAlarmState>.Filter.Eq(x => x.AlarmType, alarmType);
if (!string.IsNullOrWhiteSpace(alarmCodeMdc))
filter &= Builders<PumpAlarmState>.Filter.Eq(x => x.AlarmCodeMdc, alarmCodeMdc);
await Collection.DeleteManyAsync(filter);
}
{
var filter = Builders<PumpAlarmState>.Filter.Eq(x => x.DeviceId, deviceId);
if (alarmType.HasValue)
filter &= Builders<PumpAlarmState>.Filter.Eq(x => x.AlarmType, alarmType);
if (!string.IsNullOrWhiteSpace(alarmCodeMdc))
filter &= Builders<PumpAlarmState>.Filter.Eq(x => x.AlarmCodeMdc, alarmCodeMdc);
await Collection.DeleteManyAsync(filter);
}
/// <summary>
/// Asynchronously deletes all records associated with the specified patient identifier by removing every document whose <c>PatientId</c> matches the supplied value.
/// </summary>
/// <param name="patientId">The unique identifier of the patient whose associated records should be removed.</param>
public async Task DeleteByPatientId(ObjectId patientId)
{
await Collection.DeleteManyAsync(p => p.PatientId == patientId);
}
{
await Collection.DeleteManyAsync(p => p.PatientId == patientId);
}
/// <summary>
/// Asynchronously retrieves all pump alarm states associated with the specified device identifier.
/// </summary>
/// <param name="deviceId">The unique identifier of the device whose pump alarm states are being queried.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains an <see cref="IEnumerable{PumpAlarmState}"/> with the pump alarm states matching the provided device identifier.</returns>
public async Task<IEnumerable<PumpAlarmState>> FindAllActiveByDeviceAsync(string deviceId)
{
return await Collection.Find(x => x.DeviceId == deviceId).ToListAsync();
}
{
return await Collection.Find(x => x.DeviceId == deviceId).ToListAsync();
}
/// <summary>
/// Asynchronously updates all <see cref="PumpAlarmState"/> documents that match the specified <paramref name="oldId"/> on the given <paramref name="fieldName"/>, replacing the value with <paramref name="newId"/>. Used to bulk rewire <see cref="MongoDB.Bson.ObjectId"/> references stored on a dynamic field.
/// </summary>
/// <param name="fieldName">Name of the document field to filter and update.</param>
/// <param name="newId">The new <see cref="MongoDB.Bson.ObjectId"/> value to assign to the field.</param>
/// <param name="oldId">The existing <see cref="MongoDB.Bson.ObjectId"/> value used to locate matching documents.</param>
/// <returns>The number of documents that were modified by the update operation.</returns>
public async Task<long> UpdateManyObjectIdByFieldNameAsync(string fieldName, ObjectId newId, ObjectId oldId)
{
var filter = Builders<PumpAlarmState>.Filter.Eq(fieldName, oldId);
var update = Builders<PumpAlarmState>.Update.Set(fieldName, newId);
var result = await Collection.UpdateManyAsync(filter, update);
return result.ModifiedCount;
}
{
var filter = Builders<PumpAlarmState>.Filter.Eq(fieldName, oldId);
var update = Builders<PumpAlarmState>.Update.Set(fieldName, newId);
var result = await Collection.UpdateManyAsync(filter, update);
return result.ModifiedCount;
}
}
@@ -57,42 +57,62 @@ namespace adas_core.Infrastructure.Repositories
await Collection.Indexes.CreateManyAsync(indexModels);
}
/// <summary>
/// Asynchronously inserts a <see cref="PumpObservation"/> into the underlying MongoDB collection.
/// </summary>
/// <param name="obs">The pump observation to persist.</param>
public async Task InsertAsync(PumpObservation obs)
{
await Collection.InsertOneAsync(obs);
}
{
await Collection.InsertOneAsync(obs);
}
/// <summary>
/// Asynchronously inserts a batch of pump observations into the underlying data store. If the collection is empty, the method completes without performing any insertion.
/// </summary>
/// <param name="observations">The pump observations to insert into the collection.</param>
public async Task InsertManyAsync(IEnumerable<PumpObservation> observations)
{
var list = observations as IList<PumpObservation> ?? observations.ToList();
if (list.Count == 0) return;
await Collection.InsertManyAsync(list);
}
{
var list = observations as IList<PumpObservation> ?? observations.ToList();
if (list.Count == 0) return;
await Collection.InsertManyAsync(list);
}
/// <summary>
/// Retrieves a collection of <see cref="PumpObservation"/> records for a specific patient, optionally filtered by a time range and limited in count, sorted by time in descending order.
/// </summary>
/// <param name="patientId">The identifier of the patient whose pump observations are being queried.</param>
/// <param name="from">Optional inclusive lower bound for the observation time. When provided, only observations on or after this time are returned.</param>
/// <param name="to">Optional inclusive upper bound for the observation time. When provided, only observations on or before this time are returned.</param>
/// <param name="limit">Optional maximum number of observations to return. When null, all matching observations are returned.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains an <see cref="IEnumerable{PumpObservation}"/> of matching observations ordered from newest to oldest.</returns>
public async Task<IEnumerable<PumpObservation>> FindByPatientIdAsync(
ObjectId patientId, DateTime? from = null, DateTime? to = null, int? limit = null)
{
var filter = Builders<PumpObservation>.Filter.Eq(x => x.PatientId, patientId);
if (from.HasValue)
filter &= Builders<PumpObservation>.Filter.Gte(x => x.Time, from.Value);
if (to.HasValue)
filter &= Builders<PumpObservation>.Filter.Lte(x => x.Time, to.Value);
var query = Collection.Find(filter).SortByDescending(x => x.Time);
if (limit.HasValue)
query = query.Limit(limit.Value) as IOrderedFindFluent<PumpObservation, PumpObservation>;
return await query.ToListAsync();
}
ObjectId patientId, DateTime? from = null, DateTime? to = null, int? limit = null)
{
var filter = Builders<PumpObservation>.Filter.Eq(x => x.PatientId, patientId);
if (from.HasValue)
filter &= Builders<PumpObservation>.Filter.Gte(x => x.Time, from.Value);
if (to.HasValue)
filter &= Builders<PumpObservation>.Filter.Lte(x => x.Time, to.Value);
var query = Collection.Find(filter).SortByDescending(x => x.Time);
if (limit.HasValue)
query = query.Limit(limit.Value) as IOrderedFindFluent<PumpObservation, PumpObservation>;
return await query.ToListAsync();
}
/// <summary>
/// Deletes all pump observations whose recording time is earlier than the specified cutoff date.
/// </summary>
/// <param name="addDays">The cutoff date; observations with a timestamp before this value are removed.</param>
public async Task DeleteBeforeDate(DateTime addDays)
{
var filter = Builders<PumpObservation>.Filter.Lt(x => x.Time, addDays);
await Collection.DeleteManyAsync(filter);
}
{
var filter = Builders<PumpObservation>.Filter.Lt(x => x.Time, addDays);
await Collection.DeleteManyAsync(filter);
}
}
}
@@ -7,6 +7,16 @@ using MongoDB.Driver;
namespace adas_core.Infrastructure.Repositories
{
/// <summary>
/// A repository for managing <see cref="PumpObservation"/> entities in a MongoDB data store.
/// This class extends the generic <see cref="MongoRepository{T}"/> base class and implements
/// the <see cref="IPumpObservationRepository"/> contract to provide persistence operations for pump observation data.
/// </summary>
/// <typeparam name="PumpObservation">The type of the entity managed by this repository.</typeparam>
/// <remarks>
/// As a specialized repository inheriting from <see cref="MongoRepository{PumpObservation}"/>, this class
/// reuses the base MongoDB storage capabilities while exposing the pump observation-specific repository contract.
/// </remarks>
public class PumpObservationRepository : MongoRepository<PumpObservation>, IPumpObservationRepository
{
private readonly ApiSettings _apiSettings;
@@ -18,10 +28,14 @@ namespace adas_core.Infrastructure.Repositories
_apiSettings = apiSettings.Value;
}
/// <summary>
/// Retrieves the collection name used for pump observations, returning the configured value from API settings if available, or falling back to the default "pump_observations" name when no custom configuration is provided.
/// </summary>
/// <returns>The configured pump observations collection name from API settings, or the default "pump_observations" string if the setting is null.</returns>
public override string GetCollectionName()
{
return _apiSettings.PumpObservations ?? "pump_observations";
}
{
return _apiSettings.PumpObservations ?? "pump_observations";
}
public override async Task CreateIndexes()
{
@@ -49,67 +63,91 @@ namespace adas_core.Infrastructure.Repositories
await Collection.Indexes.CreateManyAsync(indexModels);
}
/// <summary>
/// Asynchronously inserts a pump observation into the underlying collection.
/// </summary>
/// <param name="obs">The pump observation document to be persisted.</param>
public async Task InsertAsync(PumpObservation obs)
{
await Collection.InsertOneAsync(obs);
}
{
await Collection.InsertOneAsync(obs);
}
/// <summary>
/// Inserts a batch of pump observations into the underlying collection in a single operation. Returns immediately when the input is null or contains no elements, performing no insertion in those cases.
/// </summary>
/// <param name="observations">The pump observations to insert. A null or empty collection results in a no-op.</param>
public async Task InsertManyAsync(IEnumerable<PumpObservation>? observations)
{
if (observations == null) return;
var list = observations as IList<PumpObservation> ?? observations.ToList();
if (list.Count == 0) return;
await Collection.InsertManyAsync(list);
}
{
if (observations == null) return;
var list = observations as IList<PumpObservation> ?? observations.ToList();
if (list.Count == 0) return;
await Collection.InsertManyAsync(list);
}
/// <summary>
/// Retrieves pump observations for a specific device, optionally filtered by a time range and limited to a maximum number of results, sorted by time in descending order.
/// </summary>
/// <param name="deviceId">The identifier of the device whose observations should be retrieved.</param>
/// <param name="from">Optional start timestamp; when provided, only observations with a time greater than or equal to this value are returned.</param>
/// <param name="to">Optional end timestamp; when provided, only observations with a time less than or equal to this value are returned.</param>
/// <param name="limit">Optional maximum number of observations to return; when not provided, all matching observations are returned.</param>
/// <returns>A task that represents the asynchronous operation, containing the collection of matching <see cref="PumpObservation"/> records.</returns>
public async Task<IEnumerable<PumpObservation>> FindByDeviceIdAsync(
string deviceId,
DateTime? from = null,
DateTime? to = null,
int? limit = null)
{
var filter = Builders<PumpObservation>.Filter.Eq(x => x.DeviceId, deviceId);
if (from.HasValue)
filter &= Builders<PumpObservation>.Filter.Gte(x => x.Time, from.Value);
if (to.HasValue)
filter &= Builders<PumpObservation>.Filter.Lte(x => x.Time, to.Value);
var query = Collection.Find(filter)
.SortByDescending(x => x.Time);
if (limit.HasValue)
query = query.Limit(limit.Value) as IOrderedFindFluent<PumpObservation, PumpObservation>;
return await query.ToListAsync();
}
string deviceId,
DateTime? from = null,
DateTime? to = null,
int? limit = null)
{
var filter = Builders<PumpObservation>.Filter.Eq(x => x.DeviceId, deviceId);
if (from.HasValue)
filter &= Builders<PumpObservation>.Filter.Gte(x => x.Time, from.Value);
if (to.HasValue)
filter &= Builders<PumpObservation>.Filter.Lte(x => x.Time, to.Value);
var query = Collection.Find(filter)
.SortByDescending(x => x.Time);
if (limit.HasValue)
query = query.Limit(limit.Value) as IOrderedFindFluent<PumpObservation, PumpObservation>;
return await query.ToListAsync();
}
// histórico por paciente
/// <summary>
/// Retrieves pump observations for a specific patient, optionally filtered by a time range and optionally capped to a maximum number of results. Results are ordered from most recent to oldest by observation time.
/// </summary>
/// <param name="patientId">The identifier of the patient whose pump observations should be retrieved.</param>
/// <param name="from">Optional inclusive lower bound for the observation time. When null, no lower time bound is applied.</param>
/// <param name="to">Optional inclusive upper bound for the observation time. When null, no upper time bound is applied.</param>
/// <param name="limit">Optional maximum number of observations to return. When null, all matching observations are returned.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the matching pump observations sorted by time in descending order.</returns>
public async Task<IEnumerable<PumpObservation>> FindByPatientAsync(
ObjectId patientId,
DateTime? from = null,
DateTime? to = null,
int? limit = null)
{
var filter = Builders<PumpObservation>.Filter.Eq(x => x.PatientId, patientId);
if (from.HasValue)
filter &= Builders<PumpObservation>.Filter.Gte(x => x.Time, from.Value);
if (to.HasValue)
filter &= Builders<PumpObservation>.Filter.Lte(x => x.Time, to.Value);
var query = Collection.Find(filter)
.SortByDescending(x => x.Time);
if (limit.HasValue)
query = query.Limit(limit.Value) as IOrderedFindFluent<PumpObservation, PumpObservation>;
return await query.ToListAsync();
}
ObjectId patientId,
DateTime? from = null,
DateTime? to = null,
int? limit = null)
{
var filter = Builders<PumpObservation>.Filter.Eq(x => x.PatientId, patientId);
if (from.HasValue)
filter &= Builders<PumpObservation>.Filter.Gte(x => x.Time, from.Value);
if (to.HasValue)
filter &= Builders<PumpObservation>.Filter.Lte(x => x.Time, to.Value);
var query = Collection.Find(filter)
.SortByDescending(x => x.Time);
if (limit.HasValue)
query = query.Limit(limit.Value) as IOrderedFindFluent<PumpObservation, PumpObservation>;
return await query.ToListAsync();
}
public async Task<Dictionary<ObjectId, DateTime>> FindAllLastPatientObservationTimeAsync()
{
@@ -146,99 +184,141 @@ namespace adas_core.Infrastructure.Repositories
return result;
}
/// <summary>
/// Retrieves the most recent pump observation associated with the specified device identifier by querying the collection, filtering by device, and returning the observation with the latest timestamp. Returns <c>null</c> when no matching observation exists for the device.
/// </summary>
/// <param name="deviceId">The unique identifier of the device whose latest pump observation should be retrieved.</param>
/// <returns>A task that resolves to the most recent <see cref="PumpObservation"/> for the device, or <c>null</c> if no observation is found.</returns>
public async Task<PumpObservation?> FindLastByDeviceIdAsync(string deviceId)
{
return await Collection
.Find(x => x.DeviceId == deviceId)
.SortByDescending(x => x.Time)
.FirstOrDefaultAsync();
}
{
return await Collection
.Find(x => x.DeviceId == deviceId)
.SortByDescending(x => x.Time)
.FirstOrDefaultAsync();
}
/// <summary>
/// Retrieves all pump observations associated with the specified patient identifier.
/// </summary>
/// <param name="patientId">The optional patient identifier used to filter the pump observations.</param>
/// <returns>A task that represents the asynchronous operation, containing a collection of pump observations matching the given patient identifier.</returns>
public async Task<IEnumerable<PumpObservation>> FindByPatientId(ObjectId? patientId)
{
return await Collection.Find(x => x.PatientId == patientId).ToListAsync();
}
{
return await Collection.Find(x => x.PatientId == patientId).ToListAsync();
}
/// <summary>
/// Retrieves the most recent pump observations for a specified patient, deduplicated by code and name, and sorted by time in descending order.
/// </summary>
/// <param name="patientId">The identifier of the patient whose observations are being retrieved.</param>
/// <param name="num">The maximum number of observations to consider before deduplication. Defaults to 100.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains a list of distinct <see cref="PumpObservation"/> entries for the patient, ordered from most recent to oldest.</returns>
public async Task<List<PumpObservation>> AggregatedPatientLastObservations(ObjectId patientId, int num = 100)
{
var filterBuilder = Builders<PumpObservation>.Filter;
var sortBuilder = Builders<PumpObservation>.Sort;
var filter = filterBuilder.Eq(o => o.PatientId, patientId);
var sort = sortBuilder.Descending("time");
var options = new FindOptions<PumpObservation> { Sort = sort, Limit = num };
var result = await Collection.FindAsync(filter, options);
var observations = await result.ToListAsync();
var distinctObservations = observations.DistinctBy(m => new { m.Code, m.Name }).ToList();
var sortedObservations = distinctObservations.OrderByDescending(x => x.Time).ToList();
return sortedObservations;
}
{
var filterBuilder = Builders<PumpObservation>.Filter;
var sortBuilder = Builders<PumpObservation>.Sort;
var filter = filterBuilder.Eq(o => o.PatientId, patientId);
var sort = sortBuilder.Descending("time");
var options = new FindOptions<PumpObservation> { Sort = sort, Limit = num };
var result = await Collection.FindAsync(filter, options);
var observations = await result.ToListAsync();
var distinctObservations = observations.DistinctBy(m => new { m.Code, m.Name }).ToList();
var sortedObservations = distinctObservations.OrderByDescending(x => x.Time).ToList();
return sortedObservations;
}
/// <summary>
/// Deletes all records whose <c>PatientId</c> matches the specified patient identifier.
/// </summary>
/// <param name="patientId">The patient identifier whose associated records should be removed; may be <c>null</c>.</param>
public async Task DeleteByPatientId(ObjectId? patientId)
{
await Collection.DeleteManyAsync(x => x.PatientId == patientId);
}
{
await Collection.DeleteManyAsync(x => x.PatientId == patientId);
}
/// <summary>
/// Deletes <see cref="PumpObservation"/> documents whose timestamp is older than the specified number of days, optionally filtered by name.
/// When <paramref name="name"/> is provided, only observations matching that name are removed; otherwise, all observations older than the cutoff are deleted.
/// The cutoff date is computed using UTC time.
/// </summary>
/// <param name="days">The age threshold in days. Observations with a <c>Time</c> older than <c>DateTime.UtcNow - days</c> are eligible for deletion.</param>
/// <param name="name">Optional name used to further restrict the deletion to observations with a matching <c>Name</c> value. If null or empty, the name filter is not applied.</param>
/// <returns>The number of <see cref="PumpObservation"/> documents that were deleted.</returns>
public async Task<long> DeleteOlderThanDaysAsync(int days, string? name = null)
{
var limitDate = DateTime.UtcNow.AddDays(-days);
var filter = Builders<PumpObservation>.Filter.Lt(x => x.Time, limitDate);
if(!string.IsNullOrEmpty(name))
filter &= Builders<PumpObservation>.Filter.Eq(x=> x.Name, name);
return (await Collection.DeleteManyAsync(filter)).DeletedCount;
}
public async Task<long> DeleteKeepLastNAsync(int maxCount)
{
// Para cada DeviceId:
var deviceIds = await Collection
.Distinct<string>("DeviceId", FilterDefinition<PumpObservation>.Empty)
.ToListAsync();
long totalDeleted = 0;
foreach (var filter in deviceIds.Select(deviceId =>
Builders<PumpObservation>.Filter.Eq(x => x.DeviceId, deviceId)
))
{
var all = await Collection.Find(filter)
.SortByDescending(x => x.Time)
.ToListAsync();
if (all.Count <= maxCount)
continue;
var toDelete = all.Skip(maxCount).Select(x => x.Id).ToList();
var deleteFilter = Builders<PumpObservation>.Filter.In(x => x.Id, toDelete);
var result = await Collection.DeleteManyAsync(deleteFilter);
totalDeleted += result.DeletedCount;
}
return totalDeleted;
}
{
var limitDate = DateTime.UtcNow.AddDays(-days);
var filter = Builders<PumpObservation>.Filter.Lt(x => x.Time, limitDate);
if(!string.IsNullOrEmpty(name))
filter &= Builders<PumpObservation>.Filter.Eq(x=> x.Name, name);
return (await Collection.DeleteManyAsync(filter)).DeletedCount;
}
/// <summary>
/// Deletes older pump observations while retaining only the most recent <paramref name="maxCount"/> records for each device.
/// Devices whose observation count is less than or equal to the threshold are left untouched.
/// </summary>
/// <param name="maxCount">The maximum number of most recent records to keep per device.</param>
/// <returns>The total number of observations deleted across all devices.</returns>
public async Task<long> DeleteKeepLastNAsync(int maxCount)
{
// Para cada DeviceId:
var deviceIds = await Collection
.Distinct<string>("DeviceId", FilterDefinition<PumpObservation>.Empty)
.ToListAsync();
long totalDeleted = 0;
foreach (var filter in deviceIds.Select(deviceId =>
Builders<PumpObservation>.Filter.Eq(x => x.DeviceId, deviceId)
))
{
var all = await Collection.Find(filter)
.SortByDescending(x => x.Time)
.ToListAsync();
if (all.Count <= maxCount)
continue;
var toDelete = all.Skip(maxCount).Select(x => x.Id).ToList();
var deleteFilter = Builders<PumpObservation>.Filter.In(x => x.Id, toDelete);
var result = await Collection.DeleteManyAsync(deleteFilter);
totalDeleted += result.DeletedCount;
}
return totalDeleted;
}
/// <summary>
/// Updates the specified field in multiple <see cref="PumpObservation"/> documents, setting it to a new <see cref="ObjectId"/> where it currently matches the optional old <see cref="ObjectId"/>.
/// </summary>
/// <param name="fieldName">The name of the field to update. Must not be null or empty.</param>
/// <param name="newId">The new <see cref="ObjectId"/> value to assign to the field.</param>
/// <param name="oldId">The current <see cref="ObjectId"/> value used to match documents; if <c>null</c>, the filter matches documents where the field is null.</param>
/// <returns>The number of documents that were modified by the update operation.</returns>
/// <exception cref="ArgumentException">Thrown when <paramref name="fieldName"/> is null, empty, or whitespace.</exception>
public async Task<long> UpdateManyObjectIdByFieldAsync(string fieldName, ObjectId newId, ObjectId? oldId)
{
if (string.IsNullOrWhiteSpace(fieldName))
throw new ArgumentException("fieldName can't be null or empty.", nameof(fieldName));
// Normalize casing (Mongo is case-sensitive)
// if (fieldName.Equals("patientid", StringComparison.OrdinalIgnoreCase))
// fieldName = nameof(PumpObservation.PatientId);
var filter = Builders<PumpObservation>.Filter.Eq(fieldName, oldId);
var update = Builders<PumpObservation>.Update.Set(fieldName, newId);
var result = await Collection.UpdateManyAsync(filter, update);
return result.ModifiedCount;
}
{
if (string.IsNullOrWhiteSpace(fieldName))
throw new ArgumentException("fieldName can't be null or empty.", nameof(fieldName));
// Normalize casing (Mongo is case-sensitive)
// if (fieldName.Equals("patientid", StringComparison.OrdinalIgnoreCase))
// fieldName = nameof(PumpObservation.PatientId);
var filter = Builders<PumpObservation>.Filter.Eq(fieldName, oldId);
var update = Builders<PumpObservation>.Update.Set(fieldName, newId);
var result = await Collection.UpdateManyAsync(filter, update);
return result.ModifiedCount;
}
public async Task<long> DeleteOlderNumberAsync(string name, int maxCount)
@@ -276,22 +356,28 @@ namespace adas_core.Infrastructure.Repositories
}
/// <summary>
/// Retrieves up to the two most recent pump observations for the specified patient and observation name, ordered by time descending. Returns an empty list if the name is null, empty, or whitespace.
/// </summary>
/// <param name="patientId">The unique identifier of the patient whose observations are being queried.</param>
/// <param name="name">The name of the pump observation to filter by. If null, empty, or whitespace, an empty list is returned.</param>
/// <returns>A task representing the asynchronous operation, containing a list of the matching pump observations (at most two) sorted from newest to oldest.</returns>
public async Task<List<PumpObservation>> FindLastObservations(ObjectId patientId, string name)
{
if (string.IsNullOrWhiteSpace(name))
return [];
var filter = Builders<PumpObservation>.Filter.And(
Builders<PumpObservation>.Filter.Eq(x => x.PatientId, patientId),
Builders<PumpObservation>.Filter.Eq(x => x.Name, name)
);
return await Collection
.Find(filter)
.SortByDescending(x => x.Time)
.Limit(2)
.ToListAsync();
}
{
if (string.IsNullOrWhiteSpace(name))
return [];
var filter = Builders<PumpObservation>.Filter.And(
Builders<PumpObservation>.Filter.Eq(x => x.PatientId, patientId),
Builders<PumpObservation>.Filter.Eq(x => x.Name, name)
);
return await Collection
.Find(filter)
.SortByDescending(x => x.Time)
.Limit(2)
.ToListAsync();
}
}
}
@@ -7,6 +7,10 @@ using MongoDB.Driver;
namespace adas_core.Infrastructure.Repositories
{
/// <summary>
/// Provides a repository implementation for <see cref="PumpState"/> entities backed by a MongoDB data store.
/// </summary>
/// <remarks>Inherits base functionality from <see cref="MongoRepository{T}"/> and implements the <see cref="IPumpStateRepository"/> contract.</remarks>
public class PumpStateRepository : MongoRepository<PumpState>, IPumpStateRepository
{
private readonly ApiSettings _apiSettings;
@@ -18,10 +22,14 @@ namespace adas_core.Infrastructure.Repositories
}
/// <summary>
/// Retrieves the collection name for pump states, returning the configured value from API settings or the default name "pump_states" when the setting is not provided.
/// </summary>
/// <returns>The configured pump states collection name, or the default value "pump_states" if the setting is null.</returns>
public override string GetCollectionName()
{
return _apiSettings.PumpStates ?? "pump_states";
}
{
return _apiSettings.PumpStates ?? "pump_states";
}
public override async Task CreateIndexes()
{
@@ -48,32 +56,45 @@ namespace adas_core.Infrastructure.Repositories
await Collection.Indexes.CreateManyAsync(indexModels);
}
/// <summary>
/// Retrieves the pump state associated with the specified device identifier, returning null when no matching record exists.
/// </summary>
/// <param name="deviceId">The unique identifier of the device whose pump state should be looked up.</param>
/// <returns>A <see cref="PumpState"/> instance if a matching record is found; otherwise, null.</returns>
public async Task<PumpState?> FindByDeviceIdAsync(string deviceId)
{
return await Collection.Find(x => x.DeviceId == deviceId).FirstOrDefaultAsync();
}
{
return await Collection.Find(x => x.DeviceId == deviceId).FirstOrDefaultAsync();
}
/// <summary>
/// Inserts the specified <see cref="PumpState"/> or updates the existing one identified by its <c>DeviceId</c>. If a matching record is found, its identifier is reused; otherwise a new identifier is generated when the provided one is empty.
/// </summary>
/// <param name="state">The pump state to persist. Its <c>Id</c> is preserved or assigned based on whether a record with the same <c>DeviceId</c> already exists.</param>
public async Task UpsertAsync(PumpState state)
{
var existing = await Collection
.Find(x => x.DeviceId == state.DeviceId)
.FirstOrDefaultAsync();
if (existing != null)
state.Id = existing.Id;
else
if (state.Id == ObjectId.Empty)
state.Id = ObjectId.GenerateNewId();
await Collection.ReplaceOneAsync(
x => x.DeviceId == state.DeviceId,
state,
new ReplaceOptions { IsUpsert = true });
}
{
var existing = await Collection
.Find(x => x.DeviceId == state.DeviceId)
.FirstOrDefaultAsync();
if (existing != null)
state.Id = existing.Id;
else
if (state.Id == ObjectId.Empty)
state.Id = ObjectId.GenerateNewId();
await Collection.ReplaceOneAsync(
x => x.DeviceId == state.DeviceId,
state,
new ReplaceOptions { IsUpsert = true });
}
/// <summary>
/// Asynchronously retrieves all <see cref="PumpState"/> records from the data store.
/// </summary>
/// <returns>A task that represents the asynchronous operation, containing an <see cref="IEnumerable{T}"/> of all <see cref="PumpState"/> records; an empty collection is returned if no records exist.</returns>
public async Task<IEnumerable<PumpState>> GetAllAsync()
{
return await Collection.Find(Builders<PumpState>.Filter.Empty).ToListAsync();
}
{
return await Collection.Find(Builders<PumpState>.Filter.Empty).ToListAsync();
}
}
}
@@ -6,6 +6,10 @@ using MongoDB.Driver;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Represents a MongoDB repository responsible for managing archived patient recording alerts, exposing archive-specific data access operations through the recording alert archive repository contract.
/// </summary>
/// <typeparam name="PatientRecordingAlert">The type of the patient recording alert entity persisted in the archive.</typeparam>
public class RecordingAlertArchiveRepository : MongoRepository<PatientRecordingAlert>, IRecordingAlertArchiveRepository
{
private readonly ApiSettings _apiSettings;
@@ -19,29 +23,46 @@ public class RecordingAlertArchiveRepository : MongoRepository<PatientRecordingA
/// <summary>
/// Gets the collection name for archive patients recording alerts, returning the configured value from API settings or a default name when no setting is provided.
/// </summary>
/// <returns>The configured collection name from <c>_apiSettings.ArchivePatientsRecordingalerts</c>, or the default "archive_patients_recordingalerts" if the setting is null.</returns>
public override string GetCollectionName()
{
return _apiSettings.ArchivePatientsRecordingalerts ?? "archive_patients_recordingalerts";
}
{
return _apiSettings.ArchivePatientsRecordingalerts ?? "archive_patients_recordingalerts";
}
/// <summary>
/// Asynchronously inserts a new <see cref="PatientRecordingAlert"/> into the underlying collection.
/// </summary>
/// <param name="patientRecordingAlert">The patient recording alert document to persist.</param>
public override async Task InsertOneAsync(PatientRecordingAlert patientRecordingAlert)
{
await Collection.InsertOneAsync(patientRecordingAlert);
}
{
await Collection.InsertOneAsync(patientRecordingAlert);
}
/// <summary>
/// Deletes all <see cref="PatientRecordingAlert"/> records whose <c>Time</c> is earlier than the specified cutoff date.
/// </summary>
/// <param name="date">The cutoff date; records with a time strictly before this value will be removed.</param>
public async Task DeleteBeforeDate(DateTime date)
{
var filter = Builders<PatientRecordingAlert>.Filter.Lt(po => po.Time, date);
await Collection.DeleteManyAsync(filter);
}
{
var filter = Builders<PatientRecordingAlert>.Filter.Lt(po => po.Time, date);
await Collection.DeleteManyAsync(filter);
}
/// <summary>
/// Bulk inserts a collection of patient recording alerts using a single batch operation and returns the number of documents successfully inserted.
/// </summary>
/// <param name="patientRecordingAlerts">The patient recording alerts to insert into the collection.</param>
/// <returns>The number of patient recording alerts that were inserted.</returns>
public async Task<long> InsertBatch(IEnumerable<PatientRecordingAlert> patientRecordingAlerts)
{
var writes = new List<WriteModel<PatientRecordingAlert>>();
writes.AddRange(patientRecordingAlerts.Select(d => new InsertOneModel<PatientRecordingAlert>(d)));
var bulkInsert = await Collection.BulkWriteAsync(writes);
return bulkInsert.InsertedCount;
}
{
var writes = new List<WriteModel<PatientRecordingAlert>>();
writes.AddRange(patientRecordingAlerts.Select(d => new InsertOneModel<PatientRecordingAlert>(d)));
var bulkInsert = await Collection.BulkWriteAsync(writes);
return bulkInsert.InsertedCount;
}
}
@@ -10,6 +10,10 @@ using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Provides a MongoDB-backed repository implementation for <see cref="PatientRecordingAlert"/> entities, exposing data access operations defined by the <see cref="IRecordingAlertRepository"/> contract.
/// </summary>
/// <typeparam name="PatientRecordingAlert">The type of recording alert entity managed by the repository.</typeparam>
public class RecordingAlertRepository : MongoRepository<PatientRecordingAlert>, IRecordingAlertRepository
{
private readonly ApiSettings _apiSettings;
@@ -22,190 +26,245 @@ public class RecordingAlertRepository : MongoRepository<PatientRecordingAlert>,
/// <summary>
/// Retrieves the collection name for patients recording alerts, returning the configured API setting value or a default fallback when the setting is not available.
/// </summary>
/// <returns>The collection name from <c>_apiSettings.PatientsRecordingAlerts</c>, or the default value "patients_recordingalerts" if the setting is null.</returns>
public override string GetCollectionName()
{
return _apiSettings.PatientsRecordingAlerts ?? "patients_recordingalerts";
}
{
return _apiSettings.PatientsRecordingAlerts ?? "patients_recordingalerts";
}
/// <summary>
/// Retrieves the most recent observations for a specified patient from MongoDB using an aggregation pipeline,
/// grouped by observation name, returning up to the specified number of records per group.
/// </summary>
/// <param name="patientId">The unique identifier of the patient whose observations are being retrieved.</param>
/// <param name="num">The maximum number of recent observations to return per grouped observation name.</param>
/// <returns>A task containing a list of <see cref="PatientRecordingAlert"/> objects representing the aggregated last observations for the patient.</returns>
public async Task<List<PatientRecordingAlert>> AggregatedPatientLastObservations(ObjectId patientId, int num)
{
var match = new BsonDocument
{
{ "patientid", patientId }
};
var pipeline = new BsonDocument[]
{
new()
var match = new BsonDocument
{
{
"$match", match
}
},
new()
{ "patientid", patientId }
};
var pipeline = new BsonDocument[]
{
new()
{
"$sort", new BsonDocument
{
{ "codingSystem", 1 },
{ "code", 1 },
{ "time", -1 }
"$match", match
}
}
},
new()
{
},
new()
{
"$group", new BsonDocument
{
"$sort", new BsonDocument
{
{ "codingSystem", 1 },
{ "code", 1 },
{ "time", -1 }
}
}
},
new()
{
{
"$group", new BsonDocument
{
"_id", new BsonDocument
{
// { "codingSystem", "$codingSystem" } ,
// { "code", "$code" } ,
{ "name", "$name" }
"_id", new BsonDocument
{
// { "codingSystem", "$codingSystem" } ,
// { "code", "$code" } ,
{ "name", "$name" }
}
},
{
"results", new BsonDocument
{
{ "$push", "$$ROOT" }
}
}
},
}
}
},
new()
{
{
"$project", new BsonDocument
{
"results", new BsonDocument
{
{ "$push", "$$ROOT" }
"results", new BsonDocument
{
{ "$slice", new BsonArray { "$results", num } }
}
}
}
}
}
},
new()
};
Debug.WriteLine(pipeline.ToJson());
var result =
await Collection.AggregateAsync<BsonDocument>(pipeline, new AggregateOptions { AllowDiskUse = true });
var obs = new List<PatientRecordingAlert>();
result.ToList().ForEach(it =>
{
foreach (var obit in it.GetValue("results").AsBsonArray)
{
"$project", new BsonDocument
{
{
"results", new BsonDocument
{
{ "$slice", new BsonArray { "$results", num } }
}
}
}
var obsit = obit.AsBsonDocument;
var pobs = BsonSerializer.Deserialize<PatientRecordingAlert>(obsit);
pobs.PatientId = patientId;
obs.Add(pobs);
}
}
};
Debug.WriteLine(pipeline.ToJson());
var result =
await Collection.AggregateAsync<BsonDocument>(pipeline, new AggregateOptions { AllowDiskUse = true });
var obs = new List<PatientRecordingAlert>();
result.ToList().ForEach(it =>
{
foreach (var obit in it.GetValue("results").AsBsonArray)
{
var obsit = obit.AsBsonDocument;
var pobs = BsonSerializer.Deserialize<PatientRecordingAlert>(obsit);
pobs.PatientId = patientId;
obs.Add(pobs);
}
});
return obs;
}
});
return obs;
}
/// <summary>
/// Deletes a patient recording alert identified by the specified identifier from the underlying collection.
/// </summary>
/// <param name="id">The unique identifier of the patient recording alert to delete.</param>
public new async Task DeleteAsync(ObjectId id)
{
var filter = Builders<PatientRecordingAlert>.Filter.Eq(obs => obs.Id, id);
await Collection.DeleteOneAsync(filter);
}
{
var filter = Builders<PatientRecordingAlert>.Filter.Eq(obs => obs.Id, id);
await Collection.DeleteOneAsync(filter);
}
/// <summary>
/// Inserts a new patient recording alert into the underlying collection asynchronously.
/// </summary>
/// <param name="patientRecordingAlert">The patient recording alert document to be inserted.</param>
public override async Task InsertOneAsync(PatientRecordingAlert patientRecordingAlert)
{
await Collection.InsertOneAsync(patientRecordingAlert);
}
{
await Collection.InsertOneAsync(patientRecordingAlert);
}
/// <summary>
/// Deletes a single PatientRecordingAlert document whose Name matches the provided value and whose Time is older than the retention period defined by <paramref name="retentionPolicyValue"/> days from now.
/// </summary>
/// <param name="name">The name of the patient recording alert used to match the document for deletion.</param>
/// <param name="retentionPolicyValue">The retention period in days; the cutoff time is calculated as UTC now minus this number of days.</param>
public async Task DeleteOlderDaysAsync(string name, int retentionPolicyValue)
{
var filterBuilder = Builders<PatientRecordingAlert>.Filter;
var filter = filterBuilder.And(
filterBuilder.Eq(obs => obs.Name, name),
filterBuilder.Lt(obs => obs.Time, DateTime.UtcNow.AddDays(-1 * retentionPolicyValue))
);
await Collection.DeleteOneAsync(filter);
}
{
var filterBuilder = Builders<PatientRecordingAlert>.Filter;
var filter = filterBuilder.And(
filterBuilder.Eq(obs => obs.Name, name),
filterBuilder.Lt(obs => obs.Time, DateTime.UtcNow.AddDays(-1 * retentionPolicyValue))
);
await Collection.DeleteOneAsync(filter);
}
/// <summary>
/// Deletes all patient recording alerts associated with the specified patient identifier.
/// </summary>
/// <param name="patientId">The unique identifier of the patient whose recording alerts will be removed.</param>
public async Task DeleteByPatientId(ObjectId patientId)
{
var filter = Builders<PatientRecordingAlert>.Filter.Eq(po => po.PatientId, patientId);
await Collection.DeleteManyAsync(filter);
}
{
var filter = Builders<PatientRecordingAlert>.Filter.Eq(po => po.PatientId, patientId);
await Collection.DeleteManyAsync(filter);
}
/// <summary>
/// Deletes <see cref="PatientRecordingAlert"/> records that exceed the retention policy, keeping only the most recent records for the specified name. The method filters alerts by name, sorts them by time in descending order, skips the most recent records up to the retention threshold, and removes the remaining older entries.
/// </summary>
/// <param name="name">The name used to filter the alerts subject to the retention policy.</param>
/// <param name="retentionPolicyValue">The number of most recent records to retain; any additional older records will be deleted.</param>
public async Task DeleteOlderNumberAsync(string name, int retentionPolicyValue)
{
var filterBuilder = Builders<PatientRecordingAlert>.Filter;
var sortBuilder = Builders<PatientRecordingAlert>.Sort;
var filter = filterBuilder.Eq(obs => obs.Name, name);
var projection = Builders<PatientRecordingAlert>.Projection.Include(obs => obs.Id).Include(obs => obs.Time);
var sort = sortBuilder.Descending("time");
var options = new FindOptions<PatientRecordingAlert>
{
Projection = projection,
Sort = sort,
Skip = retentionPolicyValue
};
var result = await Collection.FindAsync(filter, options);
await result.ForEachAsync(async obs =>
{
var idFilter = filterBuilder.Eq(ob => ob.Id, obs.Id);
await Collection.DeleteOneAsync(idFilter);
});
}
var filterBuilder = Builders<PatientRecordingAlert>.Filter;
var sortBuilder = Builders<PatientRecordingAlert>.Sort;
var filter = filterBuilder.Eq(obs => obs.Name, name);
var projection = Builders<PatientRecordingAlert>.Projection.Include(obs => obs.Id).Include(obs => obs.Time);
var sort = sortBuilder.Descending("time");
var options = new FindOptions<PatientRecordingAlert>
{
Projection = projection,
Sort = sort,
Skip = retentionPolicyValue
};
var result = await Collection.FindAsync(filter, options);
await result.ForEachAsync(async obs =>
{
var idFilter = filterBuilder.Eq(ob => ob.Id, obs.Id);
await Collection.DeleteOneAsync(idFilter);
});
}
/// <summary>
/// Retrieves all patient recording alerts associated with the specified patient identifier.
/// </summary>
/// <param name="patientId">The unique identifier of the patient whose recording alerts should be retrieved.</param>
/// <returns>A task representing the asynchronous operation, containing a cursor over the matching patient recording alerts.</returns>
public async Task<IAsyncCursor<PatientRecordingAlert>> FindByPatientIdAsync(ObjectId patientId)
{
var filter = Builders<PatientRecordingAlert>.Filter.Eq(ob => ob.PatientId, patientId);
return await Collection.FindAsync(filter);
}
{
var filter = Builders<PatientRecordingAlert>.Filter.Eq(ob => ob.PatientId, patientId);
return await Collection.FindAsync(filter);
}
/// <summary>
/// Retrieves the most recent observations for a specified patient, filtered by observation name and ordered by time in descending order.
/// </summary>
/// <param name="patientId">The unique identifier of the patient whose observations are being queried.</param>
/// <param name="name">The name of the observation used to filter the results.</param>
/// <param name="num">The maximum number of observations to return. Defaults to 2.</param>
/// <returns>A list of <see cref="PatientRecordingAlert"/> entries containing the latest matching observations for the patient.</returns>
public async Task<List<PatientRecordingAlert>> FindLastObservations(ObjectId patientId, string name, int num = 2)
{
var filterBuilder = Builders<PatientRecordingAlert>.Filter;
var sortBuilder = Builders<PatientRecordingAlert>.Sort;
var filter = filterBuilder.And(
filterBuilder.Eq(ob => ob.PatientId, patientId),
filterBuilder.Eq(ob => ob.Name, name)
);
var sort = sortBuilder.Descending("time");
var options = new FindOptions<PatientRecordingAlert>
{
Sort = sort,
Limit = num
};
var result = await Collection.FindAsync(filter, options);
return await result.ToListAsync();
}
var filterBuilder = Builders<PatientRecordingAlert>.Filter;
var sortBuilder = Builders<PatientRecordingAlert>.Sort;
var filter = filterBuilder.And(
filterBuilder.Eq(ob => ob.PatientId, patientId),
filterBuilder.Eq(ob => ob.Name, name)
);
var sort = sortBuilder.Descending("time");
var options = new FindOptions<PatientRecordingAlert>
{
Sort = sort,
Limit = num
};
var result = await Collection.FindAsync(filter, options);
return await result.ToListAsync();
}
/// <summary>
/// Updates many records by replacing the old <see cref="ObjectId"/> with the new one, identified by the specified <paramref name="nameId"/>.
/// Delegates the operation to the asynchronous <c>UpdateManyObjectIdAsync</c> implementation.
/// </summary>
/// <param name="nameId">The identifier of the field or collection used to target the records to update.</param>
/// <param name="id">The new <see cref="ObjectId"/> value to assign to the matched records.</param>
/// <param name="oldId">The existing <see cref="ObjectId"/> value to be replaced.</param>
public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId)
{
await UpdateManyObjectIdAsync(nameId, id, oldId);
}
public override async Task CreateIndexes()
{
var options = new CreateIndexOptions { Background = true, Unique = false };
var indexes = new List<CreateIndexModel<PatientRecordingAlert>>
{
new("{ patientid: 1 }", options)
};
await UpdateManyObjectIdAsync(nameId, id, oldId);
}
await MongoUtils.EnsureIndexes(Collection, indexes);
}
/// <summary>
/// Creates MongoDB indexes for the PatientRecordingAlert collection, including a non-unique background index on the patientid field to optimize queries by patient.
/// </summary>
public override async Task CreateIndexes()
{
var options = new CreateIndexOptions { Background = true, Unique = false };
var indexes = new List<CreateIndexModel<PatientRecordingAlert>>
{
new("{ patientid: 1 }", options)
};
await MongoUtils.EnsureIndexes(Collection, indexes);
}
}
@@ -12,6 +12,11 @@ using System.Text.RegularExpressions;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Represents a repository for managing <see cref="Relay"/> entities, providing MongoDB-backed data access through the <see cref="MongoRepository{Relay}"/> base class and exposing the contract defined by <see cref="IRelayRepository"/>.
/// </summary>
/// <typeparam name="Relay">The type of the entity managed by the repository.</typeparam>
/// <remarks>This class combines a concrete MongoDB repository implementation with a domain-specific interface, enabling standardized persistence operations for relay entities.</remarks>
public class RelayRepository : MongoRepository<Relay>, IRelayRepository
{
private readonly ApiSettings _apiSettings;
@@ -23,118 +28,168 @@ public class RelayRepository : MongoRepository<Relay>, IRelayRepository
_apiSettings = apiSettings.Value;
}
/// <summary>
/// Retrieves the collection name for relays from the API settings configuration.
/// </summary>
/// <returns>The configured relays collection name as specified in the API settings.</returns>
public override string GetCollectionName()
{
return _apiSettings.Relays;
}
{
return _apiSettings.Relays;
}
/// <summary>
/// Retrieves a <see cref="Relay"/> from the collection by its identifier, returning the first match or <c>null</c> when no document is found or an error occurs while querying.
/// </summary>
/// <param name="relayId">The unique identifier of the relay to look up.</param>
/// <returns>The matching <see cref="Relay"/>, or <c>null</c> if no document matches the identifier or the query fails.</returns>
public async Task<Relay?> GetById(ObjectId relayId)
{
try
{
var filter = Builders<Relay>.Filter.Eq(x => x.Id, relayId);
var result = await Collection.FindAsync(filter, null);
return result.FirstOrDefault();
try
{
var filter = Builders<Relay>.Filter.Eq(x => x.Id, relayId);
var result = await Collection.FindAsync(filter, null);
return result.FirstOrDefault();
}
catch (Exception e)
{
Log.Error("Exception trying to get relay by id: {id}. Exception {e}", relayId, e);
return null;
}
}
catch (Exception e)
{
Log.Error("Exception trying to get relay by id: {id}. Exception {e}", relayId, e);
return null;
}
}
/// <summary>
/// Retrieves a list of relays from the collection whose identifiers are contained in the provided configuration list and whose type matches the specified value.
/// </summary>
/// <param name="configurationRelayList">The collection of relay identifiers used to filter relays by their Id field.</param>
/// <param name="type">The relay type used as an equality filter on the Type field.</param>
/// <returns>A list of <see cref="Relay"/> instances matching both the identifier and type filters; an empty list is returned when no relays match.</returns>
public List<Relay> GetRelayByTypeInList(List<ObjectId> configurationRelayList, RelayEnum.Type type)
{
var filterBuilder = Builders<Relay>.Filter;
var filter = filterBuilder.And(
filterBuilder.In(r => r.Id, configurationRelayList),
filterBuilder.Eq(r => r.Type, type)
);
return Collection.Find(filter).ToList();
}
public List<Relay> GetRelayInList(List<ObjectId> configurationRelayList)
{
var filterBuilder = Builders<Relay>.Filter;
var filter = filterBuilder.And(
filterBuilder.In(r => r.Id, configurationRelayList));
return Collection.Find(filter).ToList();
}
public IFindFluent<Relay, Relay> GetPaginatedRelays(PaginationFilter filter)
{
var filterBuilder = Builders<Relay>.Filter;
var sort = Builders<Relay>.Sort.Ascending("relayName");
var filters = new List<FilterDefinition<Relay>>();
if (filter.FilteredRequest == null) return CreateFindFluent(filters, sort);
if (!string.IsNullOrEmpty(filter.FilteredRequest?.Text))
{
var textFilter = filter.FilteredRequest.Text;
var textFilterEscaped = Regex.Escape(textFilter);
filters.Add(
filterBuilder.Or(
filterBuilder.Regex(p => p.RelayName,
new BsonRegularExpression(textFilterEscaped, "i"))
)
var filterBuilder = Builders<Relay>.Filter;
var filter = filterBuilder.And(
filterBuilder.In(r => r.Id, configurationRelayList),
filterBuilder.Eq(r => r.Type, type)
);
return Collection.Find(filter).ToList();
}
return CreateFindFluent(filters, sort);
}
/// <summary>
/// Retrieves the <see cref="Relay"/> entries whose identifiers are contained in the supplied list, returning all matches as a list.
/// </summary>
/// <param name="configurationRelayList">The collection of <see cref="ObjectId"/> values used to match relays by their <c>Id</c> field.</param>
/// <returns>A <see cref="List{Relay}"/> containing the relays whose identifiers are found in <paramref name="configurationRelayList"/>; an empty list is returned when no matching relays exist.</returns>
public List<Relay> GetRelayInList(List<ObjectId> configurationRelayList)
{
var filterBuilder = Builders<Relay>.Filter;
var filter = filterBuilder.And(
filterBuilder.In(r => r.Id, configurationRelayList));
return Collection.Find(filter).ToList();
}
/// <summary>
/// Retrieves a paginated, sortable set of relays, optionally filtered by a case-insensitive text match on the relay name.
/// When the filter's text is null or empty, no text-based criteria are applied and the result is returned with the default ascending sort by relay name.
/// </summary>
/// <param name="filter">The pagination criteria containing the optional text filter applied against the relay name.</param>
/// <returns>A fluent find query for <see cref="Relay"/> entities, ordered by relay name in ascending order, ready for further pagination.</returns>
public IFindFluent<Relay, Relay> GetPaginatedRelays(PaginationFilter filter)
{
var filterBuilder = Builders<Relay>.Filter;
var sort = Builders<Relay>.Sort.Ascending("relayName");
var filters = new List<FilterDefinition<Relay>>();
if (filter.FilteredRequest == null) return CreateFindFluent(filters, sort);
if (!string.IsNullOrEmpty(filter.FilteredRequest?.Text))
{
var textFilter = filter.FilteredRequest.Text;
var textFilterEscaped = Regex.Escape(textFilter);
filters.Add(
filterBuilder.Or(
filterBuilder.Regex(p => p.RelayName,
new BsonRegularExpression(textFilterEscaped, "i"))
)
);
}
return CreateFindFluent(filters, sort);
}
/// <summary>
/// Asynchronously inserts a new relay into the collection and returns the persisted entity fetched by its identifier.
/// On failure, the exception is logged and the method returns <c>null</c> instead of propagating the error.
/// </summary>
/// <param name="request">The relay entity to insert into the collection.</param>
/// <returns>The inserted relay retrieved by its identifier, or <c>null</c> if the insertion fails.</returns>
public async Task<Relay?> InsertOneRelayAsync(Relay request)
{
try
{
await Collection.InsertOneAsync(request);
return await GetById(request.Id);
try
{
await Collection.InsertOneAsync(request);
return await GetById(request.Id);
}
catch (Exception ex)
{
Log.Error("Error inserting relay: {relay}. Exception: {ex}",
JsonConvert.SerializeObject(request, Formatting.Indented), ex);
return null;
}
}
catch (Exception ex)
{
Log.Error("Error inserting relay: {relay}. Exception: {ex}",
JsonConvert.SerializeObject(request, Formatting.Indented), ex);
return null;
}
}
/// <summary>
/// Updates an existing relay document identified by the specified object identifier with the provided relay data and returns the updated entity.
/// </summary>
/// <param name="objectId">The unique identifier of the relay document to update in the collection.</param>
/// <param name="relay">The relay instance containing the new values to apply to the existing document.</param>
/// <returns>A task that represents the asynchronous operation, containing the updated <see cref="Relay"/> after the update, or <c>null</c> if no matching document was found.</returns>
public async Task<Relay?> UpdateRelayAsync(ObjectId objectId, Relay relay)
{
var filter = Builders<Relay>.Filter.Eq("_id", objectId);
var update = Builders<Relay>.Update
.Set(c => c.Mode, relay.Mode)
.Set(c => c.RelayNumber, relay.RelayNumber)
.Set(c => c.Username, relay.Username)
.Set(c => c.Password, relay.Password)
.Set(c => c.Driver, relay.Driver)
.Set(c => c.Ip, relay.Ip)
.Set(c => c.Port, relay.Port)
.Set(c => c.RelayName, relay.RelayName);
return await Collection.FindOneAndUpdateAsync(filter, update,
new FindOneAndUpdateOptions<Relay, Relay> { ReturnDocument = ReturnDocument.After });
}
{
var filter = Builders<Relay>.Filter.Eq("_id", objectId);
var update = Builders<Relay>.Update
.Set(c => c.Mode, relay.Mode)
.Set(c => c.RelayNumber, relay.RelayNumber)
.Set(c => c.Username, relay.Username)
.Set(c => c.Password, relay.Password)
.Set(c => c.Driver, relay.Driver)
.Set(c => c.Ip, relay.Ip)
.Set(c => c.Port, relay.Port)
.Set(c => c.RelayName, relay.RelayName);
return await Collection.FindOneAndUpdateAsync(filter, update,
new FindOneAndUpdateOptions<Relay, Relay> { ReturnDocument = ReturnDocument.After });
}
/// <summary>
/// Retrieves a <see cref="Relay"/> from the collection whose <c>RelayName</c> matches the specified name.
/// Returns <c>null</c> when no relay with the given name exists in the collection.
/// </summary>
/// <param name="requestRelayName">The name of the relay to look up. May be <c>null</c>, in which case the query matches relays with a null name.</param>
/// <returns>A <see cref="Task{TResult}"/> containing the matching <see cref="Relay"/>, or <c>null</c> if no relay with the specified name is found.</returns>
public async Task<Relay?> GetByName(string? requestRelayName)
{
var filterBuilder = Builders<Relay>.Filter;
var filter = filterBuilder.Eq(r => r.RelayName, requestRelayName);
return await Collection.Find(filter).FirstOrDefaultAsync();
}
{
var filterBuilder = Builders<Relay>.Filter;
var filter = filterBuilder.Eq(r => r.RelayName, requestRelayName);
return await Collection.Find(filter).FirstOrDefaultAsync();
}
/// <summary>
/// Creates a fluent find query for the Relay collection by combining the provided filters with a logical AND, or applying an empty filter when no filters are supplied, and then applying the given sort definition.
/// </summary>
/// <param name="filters">The list of filter definitions to combine; when empty, an empty filter is used to match all documents.</param>
/// <param name="sort">The sort definition applied to the query results.</param>
/// <returns>An <see cref="IFindFluent{TDocument, TProjection}"/> representing the filtered and sorted Relay query.</returns>
private IFindFluent<Relay, Relay> CreateFindFluent(List<FilterDefinition<Relay>> filters, SortDefinition<Relay> sort)
{
var combinedFilter = filters.Any()
? Builders<Relay>.Filter.And(filters)
: Builders<Relay>.Filter.Empty;
return Collection.Find(combinedFilter).Sort(sort);
}
{
var combinedFilter = filters.Any()
? Builders<Relay>.Filter.And(filters)
: Builders<Relay>.Filter.Empty;
return Collection.Find(combinedFilter).Sort(sort);
}
}
@@ -9,6 +9,11 @@ using Serilog;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Represents a MongoDB repository for <typeparamref name="Section"/> entities,
/// implementing the contract defined by <see cref="ISectionRepository"/>.
/// </summary>
/// <typeparam name="Section">The type of the section entity managed by this repository.</typeparam>
public class SectionRepository : MongoRepository<Section>, ISectionRepository
{
private readonly ApiSettings _apiSettings;
@@ -22,86 +27,134 @@ public class SectionRepository : MongoRepository<Section>, ISectionRepository
/// <summary>
/// Gets the configuration sections collection name from the API settings, falling back to a default value when not configured.
/// </summary>
/// <returns>The configured collection name, or "config_sections" if <c>_apiSettings.ConfigSections</c> is null.</returns>
public override string GetCollectionName()
{
return _apiSettings.ConfigSections ?? "config_sections";
}
{
return _apiSettings.ConfigSections ?? "config_sections";
}
/// <summary>
/// Retrieves all <see cref="Section"/> documents from the underlying collection by querying with an empty filter.
/// </summary>
/// <returns>A task that represents the asynchronous operation, containing a list of all <see cref="Section"/> documents found in the collection.</returns>
public async Task<List<Section>> GetAll()
{
var result = await Collection.FindAsync(Builders<Section>.Filter.Empty);
return result.ToList();
}
{
var result = await Collection.FindAsync(Builders<Section>.Filter.Empty);
return result.ToList();
}
/// <summary>
/// Finds and returns the first <see cref="Section"/> whose <c>SectionTitle</c> matches the specified section value.
/// </summary>
/// <param name="section">The section title used to look up the matching <see cref="Section"/>.</param>
/// <returns>The first matching <see cref="Section"/>, or <c>null</c> if no section with the given title is found.</returns>
public async Task<Section?> FindBySection(string section)
{
var result = await Collection.FindAsync(Builders<Section>.Filter.Eq(x => x.SectionTitle, section));
return await result.FirstOrDefaultAsync();
}
{
var result = await Collection.FindAsync(Builders<Section>.Filter.Eq(x => x.SectionTitle, section));
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Asynchronously retrieves the first <see cref="Section"/> whose <c>PointOfCare</c> matches the specified value.
/// </summary>
/// <param name="pointOfCare">The point of care identifier used to filter the sections.</param>
/// <returns>The first matching <see cref="Section"/>, or <c>null</c> if no section is found.</returns>
public async Task<Section?> FindByPointOfCare(string pointOfCare)
{
var result = await Collection.FindAsync(Builders<Section>.Filter.Eq(x => x.PointOfCare, pointOfCare));
return await result.FirstOrDefaultAsync();
}
{
var result = await Collection.FindAsync(Builders<Section>.Filter.Eq(x => x.PointOfCare, pointOfCare));
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Finds a <see cref="Section"/> by its unique identifier in the underlying collection.
/// Returns <c>null</c> when no matching section exists.
/// </summary>
/// <param name="id">The unique identifier of the section to retrieve.</param>
/// <returns>A task containing the matching <see cref="Section"/> if found; otherwise, <c>null</c>.</returns>
public async Task<Section?> FindById(string id)
{
var result = await Collection.FindAsync(Builders<Section>.Filter.Eq(x => x.Id, id));
return await result.FirstOrDefaultAsync();
}
{
var result = await Collection.FindAsync(Builders<Section>.Filter.Eq(x => x.Id, id));
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Asynchronously retrieves a <see cref="Section"/> by its identifier from the collection, returning <c>null</c> when no matching document is found.
/// </summary>
/// <param name="id">The identifier of the section to locate.</param>
/// <returns>A <see cref="Section"/> instance if a document with the given identifier exists; otherwise, <c>null</c>.</returns>
public async Task<Section?> FindById(object id)
{
var result = await Collection.FindAsync(Builders<Section>.Filter.Eq(x => x._id, id));
return await result.FirstOrDefaultAsync();
}
{
var result = await Collection.FindAsync(Builders<Section>.Filter.Eq(x => x._id, id));
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Retrieves the active sections associated with the specified patient location by matching the unit name and bed against the section hierarchy.
/// Filtering is performed in memory after retrieving all sections because the underlying query does not support direct filtering on the collection's point of care field.
/// A section is considered matching when its point of care equals the unit name and contains a box with a null point of care for the requested bed, or when it contains a box whose point of care equals the unit name for the requested bed.
/// </summary>
/// <param name="location">The patient location containing the unit name and bed used to locate the corresponding sections.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains a list of sections matching the provided patient location; an empty list is returned when no matching sections are found.</returns>
public async Task<List<Section>> FindByLocation(PatientLocation location)
{
//can not filter to Collection the where condition, it throws System.InvalidOperationException: '{}.pointOfCare is not supported.'
var sections = await GetAll();
return sections.Where(section =>
(section.PointOfCare == location.UnitName && section.Items.Any(item =>
item.Boxes.Any(box => box.PointOfCare == null && box.Bed == location.Bed && box.IsActive))) ||
section.Items.Any(item =>
item.Boxes.Any(box =>
box.PointOfCare == location.UnitName && box.Bed == location.Bed && box.IsActive))).ToList();
}
{
//can not filter to Collection the where condition, it throws System.InvalidOperationException: '{}.pointOfCare is not supported.'
var sections = await GetAll();
return sections.Where(section =>
(section.PointOfCare == location.UnitName && section.Items.Any(item =>
item.Boxes.Any(box => box.PointOfCare == null && box.Bed == location.Bed && box.IsActive))) ||
section.Items.Any(item =>
item.Boxes.Any(box =>
box.PointOfCare == location.UnitName && box.Bed == location.Bed && box.IsActive))).ToList();
}
/// <summary>
/// Asynchronously inserts a single <see cref="Section"/> into the collection and returns the persisted entity retrieved by its identifier.
/// If the insertion fails, the error is logged and the method returns <c>null</c> instead of propagating the exception.
/// </summary>
/// <param name="section">The section to insert into the collection.</param>
/// <returns>The inserted <see cref="Section"/> on success, or <c>null</c> if the operation fails.</returns>
public async Task<Section?> InsertOneSection(Section section)
{
try
{
await Collection.InsertOneAsync(section);
return await FindById(section.Id);
try
{
await Collection.InsertOneAsync(section);
return await FindById(section.Id);
}
catch (Exception ex)
{
Log.Error("Error inserting section: {section}. Exception: {ex}",
JsonConvert.SerializeObject(section, Formatting.Indented), ex);
return null;
}
}
catch (Exception ex)
{
Log.Error("Error inserting section: {section}. Exception: {ex}",
JsonConvert.SerializeObject(section, Formatting.Indented), ex);
return null;
}
}
/// <summary>
/// Updates an existing <see cref="Section"/> in the data store and returns the updated document.
/// If no section with the specified identifier is found, the method returns <see langword="null"/>.
/// </summary>
/// <param name="section">The section containing the identifier of the record to update and the new field values to persist.</param>
/// <returns>The updated <see cref="Section"/> as it appears after the update, or <see langword="null"/> if no matching record was found.</returns>
public async Task<Section?> UpdateSection(Section section)
{
var filter = Builders<Section>.Filter.Eq("Id", section.Id);
var update = Builders<Section>.Update
.Set(c => c.Id, section.Id)
.Set(c => c.PointOfCare, section.PointOfCare)
.Set(c => c.SectionTitle, section.SectionTitle)
.Set(c => c.Configuration, section.Configuration)
.Set(c => c.Items, section.Items);
return await Collection.FindOneAndUpdateAsync(filter, update,
new FindOneAndUpdateOptions<Section, Section> { ReturnDocument = ReturnDocument.After });
}
{
var filter = Builders<Section>.Filter.Eq("Id", section.Id);
var update = Builders<Section>.Update
.Set(c => c.Id, section.Id)
.Set(c => c.PointOfCare, section.PointOfCare)
.Set(c => c.SectionTitle, section.SectionTitle)
.Set(c => c.Configuration, section.Configuration)
.Set(c => c.Items, section.Items);
return await Collection.FindOneAndUpdateAsync(filter, update,
new FindOneAndUpdateOptions<Section, Section> { ReturnDocument = ReturnDocument.After });
}
}
@@ -7,6 +7,9 @@ using MongoDB.Driver;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Represents a MongoDB repository for <see cref="ServiceConfig"/> entities, implementing the <see cref="IServiceConfigRepository"/> contract to provide data access operations.
/// </summary>
public class ServiceConfigRepository : MongoRepository<ServiceConfig>, IServiceConfigRepository
{
private readonly ApiSettings _apiSettings;
@@ -19,22 +22,37 @@ public class ServiceConfigRepository : MongoRepository<ServiceConfig>, IServiceC
/// <summary>
/// Retrieves the service configuration collection name from API settings, falling back to the default "service_config" when no value is configured.
/// </summary>
/// <returns>The configured service collection name, or the default "service_config" when the API setting is null.</returns>
public override string GetCollectionName()
{
return _apiSettings.ServiceConfig ?? "service_config";
}
{
return _apiSettings.ServiceConfig ?? "service_config";
}
/// <summary>
/// Finds a <see cref="ServiceConfig"/> document by matching its string identifier (<c>StrId</c>) with the provided value.
/// Returns <c>null</c> when no matching configuration exists in the collection.
/// </summary>
/// <param name="id">The string identifier used to look up the service configuration.</param>
/// <returns>A task that represents the asynchronous operation, containing the matching <see cref="ServiceConfig"/> or <c>null</c> if not found.</returns>
public async Task<ServiceConfig?> FindById(string id)
{
var result = await Collection.FindAsync(Builders<ServiceConfig>.Filter.Eq(x => x.StrId, id));
return await result.FirstOrDefaultAsync();
}
{
var result = await Collection.FindAsync(Builders<ServiceConfig>.Filter.Eq(x => x.StrId, id));
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Asynchronously retrieves a <see cref="ServiceConfig"/> from the collection that matches the specified identifier, returning <see langword="null"/> when no matching document exists.
/// </summary>
/// <param name="oid">The <see cref="ObjectId"/> of the <see cref="ServiceConfig"/> to locate.</param>
/// <returns>A <see cref="ServiceConfig"/> instance if a document with the given identifier is found; otherwise, <see langword="null"/>.</returns>
public async Task<ServiceConfig?> FindById(ObjectId oid)
{
var result = await Collection.FindAsync(Builders<ServiceConfig>.Filter.Eq(x => x.Id, oid));
return await result.FirstOrDefaultAsync();
}
{
var result = await Collection.FindAsync(Builders<ServiceConfig>.Filter.Eq(x => x.Id, oid));
return await result.FirstOrDefaultAsync();
}
}
@@ -7,6 +7,10 @@ using MongoDB.Driver;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Provides a MongoDB-backed repository for persisting and retrieving archived patient treatment records.
/// Inherits from <see cref="MongoRepository{PatientTreatment}"/> and implements the <see cref="ITreatmentArchiveRepository"/> contract.
/// </summary>
public class TreatmentArchiveRepository : MongoRepository<PatientTreatment>, ITreatmentArchiveRepository
{
private readonly ApiSettings _apiSettings;
@@ -19,37 +23,59 @@ public class TreatmentArchiveRepository : MongoRepository<PatientTreatment>, ITr
/// <summary>
/// Asynchronously inserts a single <see cref="PatientTreatment"/> document into the collection.
/// </summary>
/// <param name="patientTreatment">The patient treatment entity to persist.</param>
public override async Task InsertOneAsync(PatientTreatment patientTreatment)
{
await Collection.InsertOneAsync(patientTreatment);
}
{
await Collection.InsertOneAsync(patientTreatment);
}
/// <summary>
/// Deletes all patient treatments whose order time is before the specified date.
/// </summary>
/// <param name="date">The cutoff date; treatments with an order time earlier than this are removed.</param>
public async Task DeleteBeforeDate(DateTime date)
{
var filter = Builders<PatientTreatment>.Filter.Lt(po => po.OrderTime, date);
await Collection.DeleteManyAsync(filter);
}
{
var filter = Builders<PatientTreatment>.Filter.Lt(po => po.OrderTime, date);
await Collection.DeleteManyAsync(filter);
}
/// <summary>
/// Inserts a batch of <see cref="PatientTreatment"/> records into the underlying collection using a bulk write operation and returns the number of documents that were successfully inserted.
/// </summary>
/// <param name="treatments">The collection of patient treatment records to insert into the database.</param>
/// <returns>The total count of patient treatment records inserted by the bulk write operation.</returns>
public async Task<long> InsertBatch(IEnumerable<PatientTreatment> treatments)
{
var writes = new List<WriteModel<PatientTreatment>>();
writes.AddRange(treatments.Select(d => new InsertOneModel<PatientTreatment>(d)));
var bulkInsert = await Collection.BulkWriteAsync(writes);
return bulkInsert.InsertedCount;
}
{
var writes = new List<WriteModel<PatientTreatment>>();
writes.AddRange(treatments.Select(d => new InsertOneModel<PatientTreatment>(d)));
var bulkInsert = await Collection.BulkWriteAsync(writes);
return bulkInsert.InsertedCount;
}
/// <summary>
/// Retrieves all patient treatment records associated with the specified patient identifier from the collection.
/// </summary>
/// <param name="patientId">The unique identifier of the patient whose treatment records are to be retrieved.</param>
/// <returns>A task that represents the asynchronous operation, containing a list of <see cref="PatientTreatment"/> records matching the specified patient.</returns>
public async Task<List<PatientTreatment>> FindAllFromPatient(ObjectId patientId)
{
var filter = Builders<PatientTreatment>.Filter.Eq(t => t.PatientId, patientId);
var result = await Collection.FindAsync(filter);
return await result.ToListAsync();
}
{
var filter = Builders<PatientTreatment>.Filter.Eq(t => t.PatientId, patientId);
var result = await Collection.FindAsync(filter);
return await result.ToListAsync();
}
/// <summary>
/// Retrieves the collection name for archive patients treatments, falling back to the default "archive_patients_treatments" value when the API settings do not specify one.
/// </summary>
/// <returns>The configured collection name from the API settings, or the default "archive_patients_treatments" if the setting is null.</returns>
public override string GetCollectionName()
{
return _apiSettings.ArchivePatientsTreatments ?? "archive_patients_treatments";
}
{
return _apiSettings.ArchivePatientsTreatments ?? "archive_patients_treatments";
}
}
@@ -11,6 +11,9 @@ using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Represents a MongoDB-backed repository for <see cref="PatientTreatment"/> entities, inheriting common data access functionality from <see cref="MongoRepository{T}"/> and implementing the <see cref="ITreatmentRepository"/> contract.
/// </summary>
public class TreatmentRepository : MongoRepository<PatientTreatment>, ITreatmentRepository
{
private readonly ApiSettings _apiSettings;
@@ -23,120 +26,182 @@ public class TreatmentRepository : MongoRepository<PatientTreatment>, ITreatment
} //For testing
/// <summary>
/// Retrieves the collection name for patients treatments, using the value configured in API settings or falling back to the default "patients_treatments" when the setting is not specified.
/// </summary>
/// <returns>The configured patients treatments collection name, or "patients_treatments" if no setting is defined.</returns>
public override string GetCollectionName()
{
return _apiSettings.PatientsTreatments ?? "patients_treatments";
}
{
return _apiSettings.PatientsTreatments ?? "patients_treatments";
}
/// <summary>
/// Retrieves all patient treatment records associated with the specified patient identifier.
/// </summary>
/// <param name="patientId">The unique identifier of the patient whose treatments are being queried.</param>
/// <returns>A collection of <see cref="PatientTreatment"/> records matching the specified patient identifier.</returns>
public async Task<IEnumerable<PatientTreatment>> GetByPatientId(ObjectId patientId)
{
var filter = Builders<PatientTreatment>.Filter.Eq(ob => ob.PatientId, patientId);
var result = await Collection.FindAsync(filter);
return result.ToEnumerable();
}
{
var filter = Builders<PatientTreatment>.Filter.Eq(ob => ob.PatientId, patientId);
var result = await Collection.FindAsync(filter);
return result.ToEnumerable();
}
/// <summary>
/// Retrieves a patient treatment record from the database that matches the specified identifier.
/// </summary>
/// <param name="id">The unique identifier of the patient treatment to locate.</param>
/// <returns>An asynchronous cursor containing the patient treatment matching the provided identifier.</returns>
public async Task<IAsyncCursor<PatientTreatment>> GetById(ObjectId id)
{
var filter = Builders<PatientTreatment>.Filter.Eq(ob => ob.Id, id);
var result = await Collection.FindAsync(filter);
return result;
}
{
var filter = Builders<PatientTreatment>.Filter.Eq(ob => ob.Id, id);
var result = await Collection.FindAsync(filter);
return result;
}
/// <summary>
/// Inserts a patient treatment record, defaulting the order time to the current UTC time when it is not already set.
/// </summary>
/// <param name="treatment">The patient treatment to insert.</param>
public override async Task InsertOneAsync(PatientTreatment treatment)
{
treatment.OrderTime ??= DateTime.UtcNow;
await base.InsertOneAsync(treatment);
}
{
treatment.OrderTime ??= DateTime.UtcNow;
await base.InsertOneAsync(treatment);
}
/// <summary>
/// Deletes a patient treatment record from the database by its unique identifier.
/// </summary>
/// <param name="id">The unique identifier of the patient treatment to remove.</param>
public new async Task DeleteAsync(ObjectId id)
{
var filter = Builders<PatientTreatment>.Filter.Eq(ob => ob.Id, id);
await Collection.DeleteOneAsync(filter);
}
{
var filter = Builders<PatientTreatment>.Filter.Eq(ob => ob.Id, id);
await Collection.DeleteOneAsync(filter);
}
/// <summary>
/// Retrieves all patient treatment records associated with the specified patient identifier from the MongoDB collection.
/// </summary>
/// <param name="patientId">The unique ObjectId of the patient whose treatment records should be returned.</param>
/// <returns>An <see cref="IAsyncCursor{TDocument}"/> of <see cref="PatientTreatment"/> containing the matching treatment records.</returns>
public async Task<IAsyncCursor<PatientTreatment>> FindByPatientIdAsync(ObjectId patientId)
{
var filter = Builders<PatientTreatment>.Filter.Eq(ob => ob.PatientId, patientId);
return await Collection.FindAsync(filter);
}
{
var filter = Builders<PatientTreatment>.Filter.Eq(ob => ob.PatientId, patientId);
return await Collection.FindAsync(filter);
}
/// <summary>
/// Deletes all <see cref="PatientTreatment"/> records associated with the specified patient identifier.
/// Returns <c>true</c> when the delete operation completes, and <c>false</c> if an exception is encountered, in which case the error is logged.
/// </summary>
/// <param name="patientId">The identifier of the patient whose treatment records should be removed.</param>
/// <returns>A task that resolves to <c>true</c> on successful deletion, or <c>false</c> if the operation failed due to an exception.</returns>
public async Task<bool> DeleteByPatientId(ObjectId patientId)
{
try
{
var filter = Builders<PatientTreatment>.Filter.Eq(po => po.PatientId, patientId);
await Collection.DeleteManyAsync(filter);
return true;
try
{
var filter = Builders<PatientTreatment>.Filter.Eq(po => po.PatientId, patientId);
await Collection.DeleteManyAsync(filter);
return true;
}
catch (Exception ex)
{
Log.Error(ex.ToString());
return false;
}
}
catch (Exception ex)
{
Log.Error(ex.ToString());
return false;
}
}
/// <summary>
/// Updates an existing patient treatment record asynchronously, returning a boolean indicating success or failure.
/// If the update operation throws an exception, the error is logged and the method returns false instead of propagating the exception.
/// </summary>
/// <param name="treatment">The patient treatment entity containing the updated information, identified by its <c>Id</c>.</param>
/// <returns><c>true</c> if the update succeeds; otherwise, <c>false</c> if an exception occurs during the operation.</returns>
public async Task<bool> Update(PatientTreatment treatment)
{
try
{
await UpdateOneAsync(treatment.Id, treatment);
return true;
try
{
await UpdateOneAsync(treatment.Id, treatment);
return true;
}
catch (Exception ex)
{
Log.Error(ex.ToString());
return false;
}
}
catch (Exception ex)
{
Log.Error(ex.ToString());
return false;
}
}
/// <summary>
/// Retrieves all bolus treatments associated with the specified patient, filtering for records that have at least one entry in their <c>RequestedGiveCodesStatus</c> array.
/// </summary>
/// <param name="patientId">The unique identifier of the patient whose bolus treatments are being queried.</param>
/// <returns>A task that represents the asynchronous operation, containing a list of <see cref="PatientTreatment"/> documents matching the patient and having a non-empty <c>RequestedGiveCodesStatus</c>.</returns>
public async Task<List<PatientTreatment>> FindBolusTreatments(ObjectId patientId)
{
var builder = Builders<PatientTreatment>.Filter;
var filter = builder.And(
builder.Eq(t => t.PatientId, patientId),
builder.Exists(t => t.RequestedGiveCodesStatus),
builder.SizeGt(t => t.RequestedGiveCodesStatus, 0)
);
var result = await Collection.FindAsync(filter);
return await result.ToListAsync();
}
{
var builder = Builders<PatientTreatment>.Filter;
var filter = builder.And(
builder.Eq(t => t.PatientId, patientId),
builder.Exists(t => t.RequestedGiveCodesStatus),
builder.SizeGt(t => t.RequestedGiveCodesStatus, 0)
);
var result = await Collection.FindAsync(filter);
return await result.ToListAsync();
}
/// <summary>
/// Retrieves the list of patient treatments associated with the specified patient and placer order identifier,
/// limited to treatments whose order control is set to New (Nw) or Xo.
/// </summary>
/// <param name="patientId">The identifier of the patient whose treatments will be searched.</param>
/// <param name="order">The entity identifier of the placer order used to match treatments.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the list of matching <see cref="PatientTreatment"/> entries.</returns>
public async Task<List<PatientTreatment>> GetActiveTreatmentsByPatientIdAndOrder(ObjectId patientId, string order)
{
var builder = Builders<PatientTreatment>.Filter;
var filter = builder.And(
builder.Or(
builder.Eq(t => t.OrderControl, OrderControlType.Nw),
builder.Eq(t => t.OrderControl, OrderControlType.Xo)
),
builder.Eq(t => t.PatientId, patientId),
builder.And(
builder.Ne(t => t.PlacerOrder, null), // Verifica que no sea nulo
builder.Eq(t => t.PlacerOrder!.EntityIdentifier, order)
)
);
var result = await Collection.FindAsync(filter);
return await result.ToListAsync();
}
{
var builder = Builders<PatientTreatment>.Filter;
var filter = builder.And(
builder.Or(
builder.Eq(t => t.OrderControl, OrderControlType.Nw),
builder.Eq(t => t.OrderControl, OrderControlType.Xo)
),
builder.Eq(t => t.PatientId, patientId),
builder.And(
builder.Ne(t => t.PlacerOrder, null), // Verifica que no sea nulo
builder.Eq(t => t.PlacerOrder!.EntityIdentifier, order)
)
);
var result = await Collection.FindAsync(filter);
return await result.ToListAsync();
}
/// <summary>
/// Updates the identifier of a related entity by replacing the old object identifier with the new one across multiple records.
/// </summary>
/// <param name="nameId">The name of the field or relationship whose object identifier should be updated.</param>
/// <param name="id">The new object identifier to replace the old one with.</param>
/// <param name="oldId">The existing object identifier that should be replaced.</param>
public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId)
{
await UpdateManyObjectIdAsync(nameId, id, oldId);
}
{
await UpdateManyObjectIdAsync(nameId, id, oldId);
}
/// <summary>
/// Asynchronously retrieves all patient treatment records associated with the specified patient identifier from the underlying data store.
/// </summary>
/// <param name="patientId">The unique <see cref="ObjectId"/> of the patient whose treatments are being queried.</param>
/// <returns>A task that represents the asynchronous operation, containing an <see cref="IEnumerable{PatientTreatment}"/> with the matching treatment records. Returns an empty sequence if no treatments are found.</returns>
public async Task<IEnumerable<PatientTreatment>> FindByPatientId(ObjectId patientId)
{
var filter = Builders<PatientTreatment>.Filter.Eq(ob => ob.PatientId, patientId);
var result = await Collection.FindAsync(filter);
return result.ToEnumerable();
}
{
var filter = Builders<PatientTreatment>.Filter.Eq(ob => ob.PatientId, patientId);
var result = await Collection.FindAsync(filter);
return result.ToEnumerable();
}
public IFindFluent<PatientTreatment, PatientTreatment> GetPaginatedTreatments(PaginationFilter filter)
{
@@ -171,76 +236,99 @@ public class TreatmentRepository : MongoRepository<PatientTreatment>, ITreatment
return CreateFindFluent(filters, sort);
}
/// <summary>
/// Ensures the required MongoDB indexes exist for the <see cref="PatientTreatment"/> collection, creating a non-unique background index on the <c>patientid</c> field to optimize query performance without blocking other database operations.
/// </summary>
public override async Task CreateIndexes()
{
var options = new CreateIndexOptions { Background = true, Unique = false };
var indexes = new List<CreateIndexModel<PatientTreatment>>
{
new("{ patientid: 1 }", options)
};
await MongoUtils.EnsureIndexes(Collection, indexes);
}
var options = new CreateIndexOptions { Background = true, Unique = false };
var indexes = new List<CreateIndexModel<PatientTreatment>>
{
new("{ patientid: 1 }", options)
};
await MongoUtils.EnsureIndexes(Collection, indexes);
}
/// <summary>
/// Adds filter definitions to identify active patient treatments, including those with a placer order identifier, a valid time range relative to the current time, and an order control type of New or Change Order (excluding Discontinue).
/// </summary>
/// <param name="filters">The collection of filter definitions to which the active treatment criteria will be appended.</param>
/// <param name="filterBuilder">The builder used to construct the individual filter conditions combined for the active treatment logic.</param>
private static void AddActiveTreatmentFilters(List<FilterDefinition<PatientTreatment>> filters,
FilterDefinitionBuilder<PatientTreatment> filterBuilder)
{
var currentTime = DateTime.UtcNow;
filters.Add(
filterBuilder.And(
filterBuilder.Ne(t => t.PlacerOrder, null),
filterBuilder.Ne(t => t.PlacerOrder!.EntityIdentifier, null)
)
);
filters.Add(
filterBuilder.Or(
filterBuilder.Eq(t => t.StartTime, null),
filterBuilder.Lte(t => t.StartTime, currentTime)
)
);
filters.Add(
filterBuilder.Or(
filterBuilder.Eq(t => t.EndTime, null),
filterBuilder.Gte(t => t.EndTime, currentTime)
)
);
filters.Add(filterBuilder.Ne(t => t.OrderControl, OrderControlType.Dc));
filters.Add(
filterBuilder.Or(
filterBuilder.Eq(t => t.OrderControl, OrderControlType.Nw),
filterBuilder.Eq(t => t.OrderControl, OrderControlType.Xo)
)
);
}
FilterDefinitionBuilder<PatientTreatment> filterBuilder)
{
var currentTime = DateTime.UtcNow;
filters.Add(
filterBuilder.And(
filterBuilder.Ne(t => t.PlacerOrder, null),
filterBuilder.Ne(t => t.PlacerOrder!.EntityIdentifier, null)
)
);
filters.Add(
filterBuilder.Or(
filterBuilder.Eq(t => t.StartTime, null),
filterBuilder.Lte(t => t.StartTime, currentTime)
)
);
filters.Add(
filterBuilder.Or(
filterBuilder.Eq(t => t.EndTime, null),
filterBuilder.Gte(t => t.EndTime, currentTime)
)
);
filters.Add(filterBuilder.Ne(t => t.OrderControl, OrderControlType.Dc));
filters.Add(
filterBuilder.Or(
filterBuilder.Eq(t => t.OrderControl, OrderControlType.Nw),
filterBuilder.Eq(t => t.OrderControl, OrderControlType.Xo)
)
);
}
/// <summary>
/// Adds default time-range filter conditions to the specified filters list, including patient treatments that have no start or end time set.
/// Treatments with a null start time are always included, and those with a set start time must be after the configured start date (defaulting to <see cref="DateTime.MinValue"/> if not provided).
/// Treatments with a null end time are always included, and those with a set end time must be before the configured end date (defaulting to <see cref="DateTime.MaxValue"/> if not provided).
/// </summary>
/// <param name="filters">The list of filter definitions to which the start and end time filters will be added.</param>
/// <param name="filter">The pagination filter containing the optional start and end date values from the request.</param>
/// <param name="filterBuilder">The filter definition builder used to construct the MongoDB filter expressions.</param>
private void AddDefaultTimeFilters(List<FilterDefinition<PatientTreatment>> filters, PaginationFilter filter,
FilterDefinitionBuilder<PatientTreatment> filterBuilder)
{
filters.Add(
filterBuilder.Or(
filterBuilder.Eq(t => t.StartTime, null),
filterBuilder.Gt(p => p.StartTime, filter.FilteredRequest?.StartDate ?? DateTime.MinValue)
)
);
filters.Add(
filterBuilder.Or(
filterBuilder.Eq(t => t.EndTime, null),
filterBuilder.Lt(p => p.EndTime, filter.FilteredRequest?.EndDate ?? DateTime.MaxValue)
)
);
}
FilterDefinitionBuilder<PatientTreatment> filterBuilder)
{
filters.Add(
filterBuilder.Or(
filterBuilder.Eq(t => t.StartTime, null),
filterBuilder.Gt(p => p.StartTime, filter.FilteredRequest?.StartDate ?? DateTime.MinValue)
)
);
filters.Add(
filterBuilder.Or(
filterBuilder.Eq(t => t.EndTime, null),
filterBuilder.Lt(p => p.EndTime, filter.FilteredRequest?.EndDate ?? DateTime.MaxValue)
)
);
}
/// <summary>
/// Creates a MongoDB fluent find query for <see cref="PatientTreatment"/> by combining the provided filters with a logical AND
/// and applying the specified sort definition. When no filters are supplied, an empty filter is used to match all documents.
/// </summary>
/// <param name="filters">The list of filter definitions to combine; if empty, an empty filter is used instead.</param>
/// <param name="sort">The sort definition to apply to the query results.</param>
/// <returns>An <see cref="IFindFluent{PatientTreatment, PatientTreatment}"/> representing the configured find query with the combined filter and sort applied.</returns>
private IFindFluent<PatientTreatment, PatientTreatment> CreateFindFluent(
List<FilterDefinition<PatientTreatment>> filters, SortDefinition<PatientTreatment> sort)
{
var combinedFilter = filters.Any()
? Builders<PatientTreatment>.Filter.And(filters)
: Builders<PatientTreatment>.Filter.Empty; // Filtra todo si no hay filtros
return Collection.Find(combinedFilter).Sort(sort);
}
List<FilterDefinition<PatientTreatment>> filters, SortDefinition<PatientTreatment> sort)
{
var combinedFilter = filters.Any()
? Builders<PatientTreatment>.Filter.And(filters)
: Builders<PatientTreatment>.Filter.Empty; // Filtra todo si no hay filtros
return Collection.Find(combinedFilter).Sort(sort);
}
}
@@ -13,6 +13,9 @@ using System.Text.RegularExpressions;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Represents a MongoDB-backed repository for <see cref="Unit"/> entities, inheriting persistence functionality from <see cref="MongoRepository{Unit}"/> and implementing the <see cref="IUnitRepository"/> contract.
/// </summary>
public class UnitRepository : MongoRepository<Unit>, IUnitRepository
{
#region Properties
@@ -36,30 +39,40 @@ public class UnitRepository : MongoRepository<Unit>, IUnitRepository
#region Create
/// <summary>
/// Inserts a new <see cref="Unit"/> into the underlying collection and returns the persisted instance retrieved by its identifier.
/// If the insertion fails, the error is logged and the method returns <c>null</c> as a fallback.
/// </summary>
/// <param name="unit">The <see cref="Unit"/> to be inserted into the collection.</param>
/// <returns>The inserted <see cref="Unit"/> as returned by the lookup by identifier, or <c>null</c> if an exception occurred during insertion.</returns>
public async Task<Unit?> InsertOneUnit(Unit unit)
{
try
{
await Collection.InsertOneAsync(unit);
return await FindById(unit.Id);
try
{
await Collection.InsertOneAsync(unit);
return await FindById(unit.Id);
}
catch (Exception ex)
{
Log.Error("Error inserting Unit: {unit}. Exception: {ex}",
JsonConvert.SerializeObject(unit, Formatting.Indented), ex);
return null;
}
}
catch (Exception ex)
{
Log.Error("Error inserting Unit: {unit}. Exception: {ex}",
JsonConvert.SerializeObject(unit, Formatting.Indented), ex);
return null;
}
}
#endregion
#region Read
/// <summary>
/// Gets the collection name for units, returning the configured value from API settings or falling back to the default "units" when not specified.
/// </summary>
/// <returns>The collection name for units, or "units" if no custom value is configured in the API settings.</returns>
public override string GetCollectionName()
{
return _apiSettings.Units ?? "units";
}
{
return _apiSettings.Units ?? "units";
}
// public async Task<Unit?> FindByLocation(PatientLocation location)
// {
@@ -69,16 +82,28 @@ public class UnitRepository : MongoRepository<Unit>, IUnitRepository
// return result;
// }
/// <summary>
/// Asynchronously finds a <see cref="Unit"/> by its identifier in the underlying collection.
/// Returns <see langword="null"/> when no matching document is found.
/// </summary>
/// <param name="id">The identifier of the <see cref="Unit"/> to locate.</param>
/// <returns>A <see cref="Unit"/> instance if a match is found; otherwise, <see langword="null"/>.</returns>
public async Task<Unit?> FindById(object id)
{
var result = await Collection.FindAsync(Builders<Unit>.Filter.Eq(x => x.Id, id));
return await result.FirstOrDefaultAsync();
}
{
var result = await Collection.FindAsync(Builders<Unit>.Filter.Eq(x => x.Id, id));
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Asynchronously retrieves a <see cref="Unit"/> by its unique identifier, returning <c>null</c> when no matching unit is found.
/// </summary>
/// <param name="id">The unique identifier of the unit to look up.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the matching <see cref="Unit"/>, or <c>null</c> if no unit is found with the specified identifier.</returns>
/// <exception cref="NotImplementedException">The method is not yet implemented.</exception>
public Task<Unit?> FindById(string id)
{
throw new NotImplementedException();
}
{
throw new NotImplementedException();
}
public async Task<IEnumerable<Unit>> FindByMasterListId(ObjectId id, MasterListType masterListType)
{
@@ -101,65 +126,82 @@ public class UnitRepository : MongoRepository<Unit>, IUnitRepository
}
}
/// <summary>
/// Retrieves all <see cref="Unit"/> documents that reference the specified master list identifier in any of their associated list properties (e.g., doctor, allergy, destination, diagnosis, procedure, test, service, treatment, language barrier, and similar lists). Uses an OR-based filter to match the identifier against every list id field, returning matching units; if an error occurs during the query, an empty collection is returned after logging the exception.
/// </summary>
/// <param name="id">The <see cref="ObjectId"/> of the master list to search for across the unit's list reference fields.</param>
/// <returns>A task that yields an <see cref="IEnumerable{Unit}"/> containing the matching units, or an empty list if no matches are found or an error occurs.</returns>
public async Task<IEnumerable<Unit>> FindByMasterListId(ObjectId id)
{
try
{
var filterBuilder = Builders<Unit>.Filter;
var filters = new List<FilterDefinition<Unit>>
try
{
filterBuilder.Or(
filterBuilder.Eq(p => p.DoctorListId, id),
filterBuilder.Eq(p => p.AllergyListId, id),
filterBuilder.Eq(p => p.DestinationListId, id),
filterBuilder.Eq(p => p.DiagnosisListId, id),
filterBuilder.Eq(p => p.InsulationListId, id),
filterBuilder.Eq(p => p.OriginListId, id),
filterBuilder.Eq(p => p.ProcedureListId, id),
filterBuilder.Eq(p => p.TestListId, id),
filterBuilder.Eq(p => p.ServiceListId, id),
filterBuilder.Eq(p => p.TreatmentListId, id),
filterBuilder.Eq(p => p.LanguageBarrierListId, id),
filterBuilder.Eq(p => p.AltableOptionListId, id),
filterBuilder.Eq(p => p.DischargeStatusListId, id),
filterBuilder.Eq(p => p.DoctorTypeListId, id),
filterBuilder.Eq(p => p.InternalDestinationListId, id),
filterBuilder.Eq(p => p.PassiveSittingListId, id),
filterBuilder.Eq(p => p.GenericListId, id),
filterBuilder.Eq(p => p.VisitOptionListId, id),
filterBuilder.Eq(p => p.AccessControlListId, id),
filterBuilder.Eq(p => p.TherapeuticCeilingListId, id),
filterBuilder.Eq(p => p.MobilityOptionListId, id)
)
};
var result = await Collection.Find(Builders<Unit>.Filter.And(filters)).ToListAsync();
var filterBuilder = Builders<Unit>.Filter;
var filters = new List<FilterDefinition<Unit>>
{
filterBuilder.Or(
filterBuilder.Eq(p => p.DoctorListId, id),
filterBuilder.Eq(p => p.AllergyListId, id),
filterBuilder.Eq(p => p.DestinationListId, id),
filterBuilder.Eq(p => p.DiagnosisListId, id),
filterBuilder.Eq(p => p.InsulationListId, id),
filterBuilder.Eq(p => p.OriginListId, id),
filterBuilder.Eq(p => p.ProcedureListId, id),
filterBuilder.Eq(p => p.TestListId, id),
filterBuilder.Eq(p => p.ServiceListId, id),
filterBuilder.Eq(p => p.TreatmentListId, id),
filterBuilder.Eq(p => p.LanguageBarrierListId, id),
filterBuilder.Eq(p => p.AltableOptionListId, id),
filterBuilder.Eq(p => p.DischargeStatusListId, id),
filterBuilder.Eq(p => p.DoctorTypeListId, id),
filterBuilder.Eq(p => p.InternalDestinationListId, id),
filterBuilder.Eq(p => p.PassiveSittingListId, id),
filterBuilder.Eq(p => p.GenericListId, id),
filterBuilder.Eq(p => p.VisitOptionListId, id),
filterBuilder.Eq(p => p.AccessControlListId, id),
filterBuilder.Eq(p => p.TherapeuticCeilingListId, id),
filterBuilder.Eq(p => p.MobilityOptionListId, id)
)
};
var result = await Collection.Find(Builders<Unit>.Filter.And(filters)).ToListAsync();
return result;
}
catch (Exception ex)
{
Log.Error("Error searching unit by masterlist Id {id}. Exception: {ex}", id.ToString(), ex);
return new List<Unit>();
}
}
/// <summary>
/// Asynchronously counts the number of <see cref="Unit"/> documents associated with the specified master list.
/// The filter property is dynamically constructed based on the <paramref name="masterListType"/> (e.g., "{Type}Id"), so the matching field depends on the provided master list type.
/// </summary>
/// <param name="id">The <see cref="ObjectId"/> of the master list used to match units.</param>
/// <param name="masterListType">The type of master list, which determines the property name used in the filter.</param>
/// <returns>The total number of <see cref="Unit"/> documents that match the filter.</returns>
public async Task<long> CountUnitsByMasterListId(ObjectId id, MasterListType masterListType)
{
var propertyName = $"{masterListType}Id";
var filter = Builders<Unit>.Filter.Eq(propertyName, id);
var result = await Collection.CountDocumentsAsync(filter);
return result;
}
catch (Exception ex)
{
Log.Error("Error searching unit by masterlist Id {id}. Exception: {ex}", id.ToString(), ex);
return new List<Unit>();
}
}
public async Task<long> CountUnitsByMasterListId(ObjectId id, MasterListType masterListType)
{
var propertyName = $"{masterListType}Id";
var filter = Builders<Unit>.Filter.Eq(propertyName, id);
var result = await Collection.CountDocumentsAsync(filter);
return result;
}
/// <summary>
/// Asynchronously finds and returns a <see cref="Unit"/> matching the specified name from the collection, or <c>null</c> if no matching document is found.
/// </summary>
/// <param name="unitName">The name of the unit to look up using an exact equality match.</param>
/// <returns>A <see cref="Task{Unit?}"/> containing the matching <see cref="Unit"/> if found; otherwise, <c>null</c>.</returns>
public async Task<Unit?> FindByName(string unitName)
{
var result = await Collection.FindAsync(Builders<Unit>.Filter.Eq(x => x.Name, unitName));
return await result.FirstOrDefaultAsync();
}
{
var result = await Collection.FindAsync(Builders<Unit>.Filter.Eq(x => x.Name, unitName));
return await result.FirstOrDefaultAsync();
}
// public async Task<List<Unit>> FindByPointOfCare(PointOfCare pointOfCare)
// {
@@ -182,219 +224,268 @@ public class UnitRepository : MongoRepository<Unit>, IUnitRepository
//
// return result;
// }
/// <summary>
/// Asynchronously retrieves all <see cref="Unit"/> records from the underlying collection.
/// </summary>
/// <returns>A task that represents the asynchronous operation, containing a list of all <see cref="Unit"/> documents found in the collection.</returns>
public async Task<List<Unit>> GetAll()
{
var result = await Collection.FindAsync(Builders<Unit>.Filter.Empty);
return result.ToList();
}
public IFindFluent<Unit, Unit> GetPaginatedUnits(PaginationFilter filter)
{
var filterBuilder = Builders<Unit>.Filter;
var sort = Builders<Unit>.Sort.Ascending("title");
var filters = new List<FilterDefinition<Unit>>();
if (filter.FilteredRequest == null) return CreateFindFluent(filters, sort);
if (!string.IsNullOrEmpty(filter.FilteredRequest?.Text))
{
var textFilter = filter.FilteredRequest.Text;
var textFilterEscaped = Regex.Escape(textFilter);
filters.Add(
filterBuilder.Or(
filterBuilder.Regex(p => p.Name,
new BsonRegularExpression(textFilterEscaped, "i")), // Case-insensitive regex match for name
filterBuilder.Regex(p => p.Title,
new BsonRegularExpression(textFilterEscaped, "i")) // Case-insensitive regex match for title
)
);
var result = await Collection.FindAsync(Builders<Unit>.Filter.Empty);
return result.ToList();
}
return CreateFindFluent(filters, sort);
}
/// <summary>
/// Retrieves a paginated, sortable query of <see cref="Unit"/> documents, optionally filtered by a case-insensitive text search applied to the Name and Title fields.
/// </summary>
/// <param name="filter">The pagination filter containing the optional text search criteria used to narrow the result set.</param>
/// <returns>An <see cref="IFindFluent{Unit, Unit}"/> representing the sorted and filtered query of units; if no filter request is supplied, an unfiltered query sorted by title is returned.</returns>
public IFindFluent<Unit, Unit> GetPaginatedUnits(PaginationFilter filter)
{
var filterBuilder = Builders<Unit>.Filter;
var sort = Builders<Unit>.Sort.Ascending("title");
var filters = new List<FilterDefinition<Unit>>();
if (filter.FilteredRequest == null) return CreateFindFluent(filters, sort);
if (!string.IsNullOrEmpty(filter.FilteredRequest?.Text))
{
var textFilter = filter.FilteredRequest.Text;
var textFilterEscaped = Regex.Escape(textFilter);
filters.Add(
filterBuilder.Or(
filterBuilder.Regex(p => p.Name,
new BsonRegularExpression(textFilterEscaped, "i")), // Case-insensitive regex match for name
filterBuilder.Regex(p => p.Title,
new BsonRegularExpression(textFilterEscaped, "i")) // Case-insensitive regex match for title
)
);
}
return CreateFindFluent(filters, sort);
}
/// <summary>
/// Creates a fluent find query for the <see cref="Unit"/> collection, applying the supplied filters and sort definition. When the filter list is empty, an empty filter is used so that all documents are matched.
/// </summary>
/// <param name="filters">The list of filter definitions to combine with a logical AND; if empty, no filtering is applied.</param>
/// <param name="sort">The sort definition to apply to the query results.</param>
/// <returns>A fluent find interface for <see cref="Unit"/> that can be further chained to project, limit, or execute the query.</returns>
private IFindFluent<Unit, Unit> CreateFindFluent(List<FilterDefinition<Unit>> filters, SortDefinition<Unit> sort)
{
var combinedFilter = filters.Any()
? Builders<Unit>.Filter.And(filters)
: Builders<Unit>.Filter.Empty;
return Collection.Find(combinedFilter).Sort(sort);
}
{
var combinedFilter = filters.Any()
? Builders<Unit>.Filter.And(filters)
: Builders<Unit>.Filter.Empty;
return Collection.Find(combinedFilter).Sort(sort);
}
#endregion
#region Update
/// <summary>
/// Updates an existing unit document in the collection, returning the updated document.
/// Uses a filter on the unit's identifier and sets the updatable fields; returns null when no matching document is found.
/// </summary>
/// <param name="unit">The unit containing the identifier and the new field values to be persisted.</param>
/// <returns>The updated unit after the operation, or null if no document matched the filter.</returns>
public async Task<Unit?> UpdateUnit(Unit unit)
{
var filter = Builders<Unit>.Filter.Eq("_id", unit.Id);
var update = Builders<Unit>.Update
//.Set(c => c.Id, unit.Id)
.Set(c => c.Title, unit.Title)
.Set(c => c.Name, unit.Name)
.Set(c => c.Configuration, unit.Configuration)
.Set(c => c.AllergyListId, unit.AllergyListId)
.Set(c => c.DestinationListId, unit.DestinationListId)
.Set(c => c.InternalDestinationListId, unit.InternalDestinationListId)
.Set(c => c.DiagnosisListId, unit.DiagnosisListId)
.Set(c => c.DoctorListId, unit.DoctorListId)
.Set(c => c.DoctorTypeListId, unit.DoctorTypeListId)
.Set(c => c.InsulationListId, unit.InsulationListId)
.Set(c => c.MobilityOptionListId, unit.MobilityOptionListId)
.Set(c => c.OriginListId, unit.OriginListId)
.Set(c => c.PatientStatusListId, unit.PatientStatusListId)
.Set(c => c.ProcedureListId, unit.ProcedureListId)
.Set(c => c.TestListId, unit.TestListId)
.Set(c => c.ServiceListId, unit.ServiceListId)
.Set(c => c.TherapeuticCeilingListId, unit.TherapeuticCeilingListId)
.Set(c => c.TreatmentListId, unit.TreatmentListId)
.Set(c => c.VisitOptionListId, unit.VisitOptionListId)
.Set(c => c.AccessControlListId, unit.AccessControlListId)
.Set(c => c.DischargeStatusListId, unit.DischargeStatusListId);
return await Collection.FindOneAndUpdateAsync(filter, update,
new FindOneAndUpdateOptions<Unit, Unit> { ReturnDocument = ReturnDocument.After });
}
public async Task<Unit?> UpdateUnitInfo(ObjectId unitId, string name, string title)
{
var filter = Builders<Unit>.Filter.Eq("_id", unitId);
var update = Builders<Unit>.Update
//.Set(c => c.Id, unit.Id)
.Set(c => c.Title, title)
.Set(c => c.Name, name);
return await Collection.FindOneAndUpdateAsync(filter, update,
new FindOneAndUpdateOptions<Unit, Unit> { ReturnDocument = ReturnDocument.After });
}
public async Task<Unit?> UpdateUnitMasterList(UpdateUnitIdListDto updateUnitListDto)
{
var filter = Builders<Unit>.Filter.Eq("_id", updateUnitListDto.UnitId);
var update = Builders<Unit>.Update;
var updates = new List<UpdateDefinition<Unit>>();
foreach (var masterListData in updateUnitListDto.MasterListData)
if (masterListData.MasterListType.HasValue)
switch (masterListData.MasterListType.Value)
{
case MasterListType.AltableOptionList:
updates.Add(update.Set(u => u.AltableOptionListId, masterListData.MasterListId));
break;
case MasterListType.AllergyList:
updates.Add(update.Set(u => u.AllergyListId, masterListData.MasterListId));
break;
case MasterListType.DestinationList:
updates.Add(update.Set(u => u.DestinationListId, masterListData.MasterListId));
break;
case MasterListType.DiagnosisList:
updates.Add(update.Set(u => u.DiagnosisListId, masterListData.MasterListId));
break;
case MasterListType.DischargeStatusList:
updates.Add(update.Set(u => u.DischargeStatusListId, masterListData.MasterListId));
break;
case MasterListType.DoctorList:
updates.Add(update.Set(u => u.DoctorListId, masterListData.MasterListId));
break;
case MasterListType.DoctorTypeList:
updates.Add(update.Set(u => u.DoctorTypeListId, masterListData.MasterListId));
break;
case MasterListType.InternalDestinationList:
updates.Add(update.Set(u => u.InternalDestinationListId, masterListData.MasterListId));
break;
case MasterListType.InsulationList:
updates.Add(update.Set(u => u.InsulationListId, masterListData.MasterListId));
break;
case MasterListType.LanguageBarrierList:
updates.Add(update.Set(u => u.LanguageBarrierListId, masterListData.MasterListId));
break;
case MasterListType.PassiveSittingList:
updates.Add(update.Set(u => u.PassiveSittingListId, masterListData.MasterListId));
break;
case MasterListType.GenericList:
updates.Add(update.Set(u => u.GenericListId, masterListData.MasterListId));
break;
case MasterListType.MobilityOptionList:
updates.Add(update.Set(u => u.MobilityOptionListId, masterListData.MasterListId));
break;
case MasterListType.OriginList:
updates.Add(update.Set(u => u.OriginListId, masterListData.MasterListId));
break;
case MasterListType.PatientStatusList:
updates.Add(update.Set(u => u.PatientStatusListId, masterListData.MasterListId));
break;
case MasterListType.ProcedureList:
updates.Add(update.Set(u => u.ProcedureListId, masterListData.MasterListId));
break;
case MasterListType.TestList:
updates.Add(update.Set(u => u.TestListId, masterListData.MasterListId));
break;
case MasterListType.ServiceList:
updates.Add(update.Set(u => u.ServiceListId, masterListData.MasterListId));
break;
case MasterListType.TherapeuticCeilingList:
updates.Add(update.Set(u => u.TherapeuticCeilingListId, masterListData.MasterListId));
break;
case MasterListType.TreatmentList:
updates.Add(update.Set(u => u.TreatmentListId, masterListData.MasterListId));
break;
case MasterListType.VisitOptionList:
updates.Add(update.Set(u => u.VisitOptionListId, masterListData.MasterListId));
break;
case MasterListType.AccessControlList:
updates.Add(update.Set(u => u.AccessControlListId, masterListData.MasterListId));
break;
}
if (updates.Any())
{
var combinedUpdate = update.Combine(updates);
return await Collection.FindOneAndUpdateAsync(filter, combinedUpdate,
var filter = Builders<Unit>.Filter.Eq("_id", unit.Id);
var update = Builders<Unit>.Update
//.Set(c => c.Id, unit.Id)
.Set(c => c.Title, unit.Title)
.Set(c => c.Name, unit.Name)
.Set(c => c.Configuration, unit.Configuration)
.Set(c => c.AllergyListId, unit.AllergyListId)
.Set(c => c.DestinationListId, unit.DestinationListId)
.Set(c => c.InternalDestinationListId, unit.InternalDestinationListId)
.Set(c => c.DiagnosisListId, unit.DiagnosisListId)
.Set(c => c.DoctorListId, unit.DoctorListId)
.Set(c => c.DoctorTypeListId, unit.DoctorTypeListId)
.Set(c => c.InsulationListId, unit.InsulationListId)
.Set(c => c.MobilityOptionListId, unit.MobilityOptionListId)
.Set(c => c.OriginListId, unit.OriginListId)
.Set(c => c.PatientStatusListId, unit.PatientStatusListId)
.Set(c => c.ProcedureListId, unit.ProcedureListId)
.Set(c => c.TestListId, unit.TestListId)
.Set(c => c.ServiceListId, unit.ServiceListId)
.Set(c => c.TherapeuticCeilingListId, unit.TherapeuticCeilingListId)
.Set(c => c.TreatmentListId, unit.TreatmentListId)
.Set(c => c.VisitOptionListId, unit.VisitOptionListId)
.Set(c => c.AccessControlListId, unit.AccessControlListId)
.Set(c => c.DischargeStatusListId, unit.DischargeStatusListId);
return await Collection.FindOneAndUpdateAsync(filter, update,
new FindOneAndUpdateOptions<Unit, Unit> { ReturnDocument = ReturnDocument.After });
}
return null;
}
/// <summary>
/// Updates the <see cref="Unit.Name"/> and <see cref="Unit.Title"/> fields of the unit identified by <paramref name="unitId"/>.
/// Returns the updated unit document, or <c>null</c> if no unit with the specified id exists in the collection.
/// </summary>
/// <param name="unitId">The unique identifier of the unit to update.</param>
/// <param name="name">The new name to assign to the unit.</param>
/// <param name="title">The new title to assign to the unit.</param>
/// <returns>The updated <see cref="Unit"/> after the modification, or <c>null</c> if no matching unit was found.</returns>
public async Task<Unit?> UpdateUnitInfo(ObjectId unitId, string name, string title)
{
var filter = Builders<Unit>.Filter.Eq("_id", unitId);
var update = Builders<Unit>.Update
//.Set(c => c.Id, unit.Id)
.Set(c => c.Title, title)
.Set(c => c.Name, name);
return await Collection.FindOneAndUpdateAsync(filter, update,
new FindOneAndUpdateOptions<Unit, Unit> { ReturnDocument = ReturnDocument.After });
}
/// <summary>
/// Updates the master list references on a <see cref="Unit"/> document identified by the supplied id,
/// applying the provided <c>MasterListId</c> values to the appropriate fields based on each entry's
/// <see cref="MasterListType"/>. Returns the updated <see cref="Unit"/> when at least one update is applied,
/// or <c>null</c> when no valid master list entries are provided.
/// </summary>
/// <param name="updateUnitListDto">The DTO containing the target <c>UnitId</c> and the collection of master list entries to update.</param>
/// <returns>The updated <see cref="Unit"/> after the modifications, or <c>null</c> if no updates were performed.</returns>
public async Task<Unit?> UpdateUnitMasterList(UpdateUnitIdListDto updateUnitListDto)
{
var filter = Builders<Unit>.Filter.Eq("_id", updateUnitListDto.UnitId);
var update = Builders<Unit>.Update;
var updates = new List<UpdateDefinition<Unit>>();
foreach (var masterListData in updateUnitListDto.MasterListData)
if (masterListData.MasterListType.HasValue)
switch (masterListData.MasterListType.Value)
{
case MasterListType.AltableOptionList:
updates.Add(update.Set(u => u.AltableOptionListId, masterListData.MasterListId));
break;
case MasterListType.AllergyList:
updates.Add(update.Set(u => u.AllergyListId, masterListData.MasterListId));
break;
case MasterListType.DestinationList:
updates.Add(update.Set(u => u.DestinationListId, masterListData.MasterListId));
break;
case MasterListType.DiagnosisList:
updates.Add(update.Set(u => u.DiagnosisListId, masterListData.MasterListId));
break;
case MasterListType.DischargeStatusList:
updates.Add(update.Set(u => u.DischargeStatusListId, masterListData.MasterListId));
break;
case MasterListType.DoctorList:
updates.Add(update.Set(u => u.DoctorListId, masterListData.MasterListId));
break;
case MasterListType.DoctorTypeList:
updates.Add(update.Set(u => u.DoctorTypeListId, masterListData.MasterListId));
break;
case MasterListType.InternalDestinationList:
updates.Add(update.Set(u => u.InternalDestinationListId, masterListData.MasterListId));
break;
case MasterListType.InsulationList:
updates.Add(update.Set(u => u.InsulationListId, masterListData.MasterListId));
break;
case MasterListType.LanguageBarrierList:
updates.Add(update.Set(u => u.LanguageBarrierListId, masterListData.MasterListId));
break;
case MasterListType.PassiveSittingList:
updates.Add(update.Set(u => u.PassiveSittingListId, masterListData.MasterListId));
break;
case MasterListType.GenericList:
updates.Add(update.Set(u => u.GenericListId, masterListData.MasterListId));
break;
case MasterListType.MobilityOptionList:
updates.Add(update.Set(u => u.MobilityOptionListId, masterListData.MasterListId));
break;
case MasterListType.OriginList:
updates.Add(update.Set(u => u.OriginListId, masterListData.MasterListId));
break;
case MasterListType.PatientStatusList:
updates.Add(update.Set(u => u.PatientStatusListId, masterListData.MasterListId));
break;
case MasterListType.ProcedureList:
updates.Add(update.Set(u => u.ProcedureListId, masterListData.MasterListId));
break;
case MasterListType.TestList:
updates.Add(update.Set(u => u.TestListId, masterListData.MasterListId));
break;
case MasterListType.ServiceList:
updates.Add(update.Set(u => u.ServiceListId, masterListData.MasterListId));
break;
case MasterListType.TherapeuticCeilingList:
updates.Add(update.Set(u => u.TherapeuticCeilingListId, masterListData.MasterListId));
break;
case MasterListType.TreatmentList:
updates.Add(update.Set(u => u.TreatmentListId, masterListData.MasterListId));
break;
case MasterListType.VisitOptionList:
updates.Add(update.Set(u => u.VisitOptionListId, masterListData.MasterListId));
break;
case MasterListType.AccessControlList:
updates.Add(update.Set(u => u.AccessControlListId, masterListData.MasterListId));
break;
}
if (updates.Any())
{
var combinedUpdate = update.Combine(updates);
return await Collection.FindOneAndUpdateAsync(filter, combinedUpdate,
new FindOneAndUpdateOptions<Unit, Unit> { ReturnDocument = ReturnDocument.After });
}
return null;
}
/// <summary>
/// Asynchronously updates the configuration of an existing unit identified by its ID.
/// Returns <c>true</c> if the document was modified, or <c>false</c> if the update failed or no document matched.
/// </summary>
/// <param name="unitIdParsed">The <see cref="ObjectId"/> of the unit whose configuration will be updated.</param>
/// <param name="unitConfiguration">The new <see cref="UnitConfiguration"/> to apply to the unit.</param>
/// <returns>A task that resolves to <c>true</c> when the update modified a document; otherwise <c>false</c> (including when the operation throws and the error is logged).</returns>
public async Task<bool> UpdateConfiguration(ObjectId unitIdParsed, UnitConfiguration unitConfiguration)
{
var filter = Builders<Unit>.Filter.Eq("_id", unitIdParsed);
var update = Builders<Unit>.Update
.Set(c => c.Configuration, unitConfiguration);
try
{
var result = await Collection.UpdateOneAsync(filter, update);
return result.ModifiedCount > 0;
var filter = Builders<Unit>.Filter.Eq("_id", unitIdParsed);
var update = Builders<Unit>.Update
.Set(c => c.Configuration, unitConfiguration);
try
{
var result = await Collection.UpdateOneAsync(filter, update);
return result.ModifiedCount > 0;
}
catch (Exception ex)
{
Log.Error("Error UpdateConfiguration from unit: {name}. Exception: {ex}", unitIdParsed, ex);
return false;
}
}
catch (Exception ex)
{
Log.Error("Error UpdateConfiguration from unit: {name}. Exception: {ex}", unitIdParsed, ex);
return false;
}
}
#endregion
#region Delete
/// <summary>
/// Deletes the unit identified by the specified identifier, returning the deleted entity. If the operation fails, the exception is logged and <c>null</c> is returned.
/// </summary>
/// <param name="id">The identifier of the unit to delete.</param>
/// <returns>The deleted <see cref="Unit"/> if found and removed; otherwise, <c>null</c> when an error occurs.</returns>
public new async Task<Unit?> DeleteAsync(ObjectId id)
{
var filter = Builders<Unit>.Filter.Eq(unit => unit.Id, id);
try
{
return await Collection.FindOneAndDeleteAsync(filter);
var filter = Builders<Unit>.Filter.Eq(unit => unit.Id, id);
try
{
return await Collection.FindOneAndDeleteAsync(filter);
}
catch (Exception e)
{
Log.Error(e.Message);
return null;
}
}
catch (Exception e)
{
Log.Error(e.Message);
return null;
}
}
#endregion
@@ -10,6 +10,12 @@ using System.Text.RegularExpressions;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Represents a MongoDB-backed repository for managing <see cref="User"/> entities.
/// </summary>
/// <remarks>
/// Inherits core data access functionality from <see cref="MongoRepository{User}"/> and implements the <see cref="IUserRepository"/> contract.
/// </remarks>
public class UserRepository : MongoRepository<User>, IUserRepository
{
private readonly ApiSettings _apiSettings;
@@ -20,221 +26,291 @@ public class UserRepository : MongoRepository<User>, IUserRepository
_apiSettings = apiSettings.Value;
}
/// <summary>
/// Retrieves the API collection name for users from the configured API settings.
/// </summary>
/// <returns>The configured name of the users collection.</returns>
public override string GetCollectionName()
{
return _apiSettings.Users;
}
{
return _apiSettings.Users;
}
/// <summary>
/// Retrieves a user from the collection whose username and password match the provided credentials.
/// Returns the first matching user, or <c>null</c> when no user with the given username and password combination is found.
/// </summary>
/// <param name="username">The username to look up in the collection.</param>
/// <param name="password">The password that must match the stored value for the user to be returned.</param>
/// <returns>A <see cref="User"/> instance when a matching record is found; otherwise, <c>null</c>.</returns>
public async Task<User?> GetUser(string username, string password)
{
var filter = Builders<User>
.Filter.Eq(p => p.UserName, username) & Builders<User>
.Filter.Eq(p => p.Password, password);
var result = await Collection.FindAsync(filter);
return await result.FirstOrDefaultAsync();
}
{
var filter = Builders<User>
.Filter.Eq(p => p.UserName, username) & Builders<User>
.Filter.Eq(p => p.Password, password);
var result = await Collection.FindAsync(filter);
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Retrieves a user from the collection by their unique identifier, returning <c>null</c> when no matching document is found.
/// </summary>
/// <param name="id">The <see cref="ObjectId"/> of the user to look up.</param>
/// <returns>A <see cref="User"/> instance if a matching document exists; otherwise, <c>null</c>.</returns>
public async Task<User?> GetById(ObjectId id)
{
var filter = Builders<User>
.Filter.Eq(p => p.Id, id);
var result = await Collection.FindAsync(filter);
return await result.FirstOrDefaultAsync();
}
{
var filter = Builders<User>
.Filter.Eq(p => p.Id, id);
var result = await Collection.FindAsync(filter);
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Retrieves a user from the data store by their unique username.
/// Returns <c>null</c> when no user matches the provided username.
/// </summary>
/// <param name="name">The username used to look up the user.</param>
/// <returns>A <see cref="User"/> instance if a match is found; otherwise, <c>null</c>.</returns>
public async Task<User?> GetByUserName(string name)
{
var filter = Builders<User>
.Filter.Eq(p => p.UserName, name);
var result = await Collection.FindAsync(filter);
return await result.FirstOrDefaultAsync();
}
{
var filter = Builders<User>
.Filter.Eq(p => p.UserName, name);
var result = await Collection.FindAsync(filter);
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Retrieves a user by their username along with their associated authorizations, using a MongoDB aggregation pipeline that joins the user with the authorizations collection and resolves the user's role (Admin role is preserved, otherwise mapped to "Some"). Returns <c>null</c> when no user matches the provided username.
/// </summary>
/// <param name="name">The username used to match the user document in the collection.</param>
/// <returns>A <see cref="Task{TResult}"/> that yields the matching <see cref="User"/> with its authorizations and resolved role, or <c>null</c> if no user is found.</returns>
public async Task<User?> GetByUserAndAuthoritesName(string name)
{
var matchStage = new BsonDocument("$match", new BsonDocument("userName", name));
var lookupStage = new BsonDocument("$lookup", new BsonDocument
{
{ "from", "authorizations" },
{ "localField", "_id" },
{ "foreignField", "userId" },
{ "as", "Authorization" }
});
var projectStage = new BsonDocument("$project", new BsonDocument
{
{ "_id", 1 },
{ "userName", 1 },
{ "email", 1 },
{ "name", 1 },
{ "Authorization", "$Authorization" },
var matchStage = new BsonDocument("$match", new BsonDocument("userName", name));
var lookupStage = new BsonDocument("$lookup", new BsonDocument
{
"rol", new BsonDocument("$cond", new BsonArray
{ "from", "authorizations" },
{ "localField", "_id" },
{ "foreignField", "userId" },
{ "as", "Authorization" }
});
var projectStage = new BsonDocument("$project", new BsonDocument
{
{ "_id", 1 },
{ "userName", 1 },
{ "email", 1 },
{ "name", 1 },
{ "Authorization", "$Authorization" },
{
new BsonDocument("$eq", new BsonArray { "$Authorization.rol", "Admin" }),
"$rol",
"Some"
})
}
});
var pipeline = new[]
{
matchStage,
lookupStage,
projectStage
};
var options = new AggregateOptions { AllowDiskUse = true };
var result = await Collection.AggregateAsync<User>(pipeline, options);
var bsonResult = await result.FirstOrDefaultAsync();
return bsonResult;
}
"rol", new BsonDocument("$cond", new BsonArray
{
new BsonDocument("$eq", new BsonArray { "$Authorization.rol", "Admin" }),
"$rol",
"Some"
})
}
});
var pipeline = new[]
{
matchStage,
lookupStage,
projectStage
};
var options = new AggregateOptions { AllowDiskUse = true };
var result = await Collection.AggregateAsync<User>(pipeline, options);
var bsonResult = await result.FirstOrDefaultAsync();
return bsonResult;
}
/// <summary>
/// Retrieves a user from the data store whose name exactly matches the specified value, returning null if no matching user exists.
/// </summary>
/// <param name="name">The name of the user to look up.</param>
/// <returns>A <see cref="User"/> instance if a match is found; otherwise, null.</returns>
public async Task<User?> GetByName(string name)
{
var filter = Builders<User>
.Filter.Eq(p => p.Name, name);
var result = await Collection.FindAsync(filter);
return await result.FirstOrDefaultAsync();
}
{
var filter = Builders<User>
.Filter.Eq(p => p.Name, name);
var result = await Collection.FindAsync(filter);
return await result.FirstOrDefaultAsync();
}
/// <summary>
/// Updates an existing user in the data store with the provided information, optionally including the password.
/// When <paramref name="updatePass"/> is <c>true</c>, the user's password is also persisted; otherwise, it is left unchanged.
/// </summary>
/// <param name="user">The user entity whose fields will be applied to the existing record, identified by <see cref="User.Id"/>.</param>
/// <param name="updatePass">If <c>true</c>, the password field is included in the update; if <c>false</c>, the password is not modified.</param>
/// <returns>The updated <see cref="User"/> retrieved after the update, or <c>null</c> if no matching user was found.</returns>
public async Task<User?> UpdateUser(User user, bool updatePass)
{
var filter = Builders<User>
.Filter.Eq(p => p.Id, user.Id);
var update = Builders<User>.Update
.Set(u => u.UserName, user.UserName)
.Set(u => u.Name, user.Name)
.Set(u => u.Email, user.Email)
.Set(u => u.LockExpirationDate, user.LockExpirationDate)
.Set(u => u.LastLogin, user.LastLogin)
.Set(u => u.IsEnabled, user.IsEnabled);
if (updatePass) update = update.Set(u => u.Password, user.Password);
await Collection.UpdateOneAsync(filter, update);
var result = await Collection.Find(filter).FirstOrDefaultAsync();
return result;
}
{
var filter = Builders<User>
.Filter.Eq(p => p.Id, user.Id);
var update = Builders<User>.Update
.Set(u => u.UserName, user.UserName)
.Set(u => u.Name, user.Name)
.Set(u => u.Email, user.Email)
.Set(u => u.LockExpirationDate, user.LockExpirationDate)
.Set(u => u.LastLogin, user.LastLogin)
.Set(u => u.IsEnabled, user.IsEnabled);
if (updatePass) update = update.Set(u => u.Password, user.Password);
await Collection.UpdateOneAsync(filter, update);
var result = await Collection.Find(filter).FirstOrDefaultAsync();
return result;
}
/// <summary>
/// Retrieves a paginated and filterable queryable collection of users based on the supplied pagination and filter criteria. Applies a case-insensitive text search across the user's Name, Email, and UserName fields when a text filter is provided, and conditionally combines user status and user type filters; when no filter payload is supplied, returns the base sorted find fluent without applying additional filters.
/// </summary>
/// <param name="filteredRequest">The pagination filter containing paging details and the optional filter payload (text, user status, and user type) used to build the MongoDB query.</param>
/// <returns>An <see cref="IFindFluent{TDocument, TProjection}"/> of <see cref="User"/> sorted ascending by user name and shaped by the assembled filter definitions.</returns>
public IFindFluent<User, User> GetPaginatedUsers(PaginationFilter filteredRequest)
{
// Crear variable con la clase que construye los filtros que necesitamos
var filterBuilder = Builders<User>.Filter;
var sort = Builders<User>.Sort.Ascending("userName");
// Crear una lista de filtros que pueden venir de tu servicio
var filters = new List<FilterDefinition<User>>();
if (filteredRequest.FilteredRequest == null) return CreateFindFluent(filters, sort);
var textFilter = filteredRequest.FilteredRequest?.Text;
if (!string.IsNullOrEmpty(textFilter))
{
var textFilterEscaped = Regex.Escape(textFilter);
var orFilters = new List<FilterDefinition<User>>
// Crear variable con la clase que construye los filtros que necesitamos
var filterBuilder = Builders<User>.Filter;
var sort = Builders<User>.Sort.Ascending("userName");
// Crear una lista de filtros que pueden venir de tu servicio
var filters = new List<FilterDefinition<User>>();
if (filteredRequest.FilteredRequest == null) return CreateFindFluent(filters, sort);
var textFilter = filteredRequest.FilteredRequest?.Text;
if (!string.IsNullOrEmpty(textFilter))
{
filterBuilder.Regex(p => p.Name, new BsonRegularExpression(textFilterEscaped, "i")),
filterBuilder.Regex(p => p.Email, new BsonRegularExpression(textFilterEscaped, "i")),
filterBuilder.Regex(p => p.UserName, new BsonRegularExpression(textFilterEscaped, "i"))
};
filters.Add(filterBuilder.Or(orFilters));
var textFilterEscaped = Regex.Escape(textFilter);
var orFilters = new List<FilterDefinition<User>>
{
filterBuilder.Regex(p => p.Name, new BsonRegularExpression(textFilterEscaped, "i")),
filterBuilder.Regex(p => p.Email, new BsonRegularExpression(textFilterEscaped, "i")),
filterBuilder.Regex(p => p.UserName, new BsonRegularExpression(textFilterEscaped, "i"))
};
filters.Add(filterBuilder.Or(orFilters));
}
var userStatus = filteredRequest.FilteredRequest?.UserStatus;
filters.Add(filterBuilder.And(GetUserStatusFilter(userStatus)));
if (Enum.TryParse(filteredRequest.FilteredRequest?.UserType, out UserEnum.Type userType))
{
GetUserTypeFilter(userType);
filters.Add(filterBuilder.And(GetUserTypeFilter(userType)));
}
return CreateFindFluent(filters, sort);
}
var userStatus = filteredRequest.FilteredRequest?.UserStatus;
filters.Add(filterBuilder.And(GetUserStatusFilter(userStatus)));
if (Enum.TryParse(filteredRequest.FilteredRequest?.UserType, out UserEnum.Type userType))
{
GetUserTypeFilter(userType);
filters.Add(filterBuilder.And(GetUserTypeFilter(userType)));
}
return CreateFindFluent(filters, sort);
}
/// <summary>
/// Retrieves the system user identified by the username "System", creating and inserting a new one with default credentials if no existing user is found.
/// </summary>
/// <returns>The existing system user if found; otherwise, the newly created and inserted system user.</returns>
public async Task<User> GetOrCreateSystemUser()
{
var user = await GetByUserName("System");
if (user == null)
{
var userToInsert = new User
var user = await GetByUserName("System");
if (user == null)
{
UserName = "System",
Name = "System",
Password = "$2a$12$crWa3EN1izcZBXNc81RzmOlfaYW2TPr2NdDQEWI7RzLTnA0Dd68WG"
};
await Collection.InsertOneAsync(userToInsert);
return userToInsert;
var userToInsert = new User
{
UserName = "System",
Name = "System",
Password = "$2a$12$crWa3EN1izcZBXNc81RzmOlfaYW2TPr2NdDQEWI7RzLTnA0Dd68WG"
};
await Collection.InsertOneAsync(userToInsert);
return userToInsert;
}
return user;
}
return user;
}
/// <summary>
/// Inserts the initial load of data by ensuring that the system user exists, creating one if necessary.
/// </summary>
public sealed override async Task InsertInitialLoad()
{
await GetOrCreateSystemUser();
}
{
await GetOrCreateSystemUser();
}
/// <summary>
/// Creates a fluent find query for users by combining the provided filters with an AND operation
/// and applying the specified sort definition. When no filters are supplied, an empty filter is used
/// as a fallback, which matches all documents.
/// </summary>
/// <param name="filters">The list of filter definitions to be combined with logical AND.</param>
/// <param name="sort">The sort definition to apply to the query results.</param>
/// <returns>A fluent find query for the <see cref="User"/> collection with the combined filter and sort applied.</returns>
private IFindFluent<User, User> CreateFindFluent(List<FilterDefinition<User>> filters, SortDefinition<User> sort)
{
var combinedFilter = filters.Any()
? Builders<User>.Filter.And(filters)
: Builders<User>.Filter.Empty; // Filtra todo si no hay filtros
return Collection.Find(combinedFilter).Sort(sort);
}
{
var combinedFilter = filters.Any()
? Builders<User>.Filter.And(filters)
: Builders<User>.Filter.Empty; // Filtra todo si no hay filtros
return Collection.Find(combinedFilter).Sort(sort);
}
/// <summary>
/// Builds a list of MongoDB filter definitions for users based on the specified user type.
/// Only Local and Ldap user types produce filters; any other value or null results in an empty filter list.
/// </summary>
/// <param name="type">The user type to filter by. If null or not one of the handled values, no filter is added.</param>
/// <returns>A list of FilterDefinition objects matching the specified user type, or an empty list if no specific type was matched.</returns>
private List<FilterDefinition<User>> GetUserTypeFilter(UserEnum.Type? type)
{
var filters = new List<FilterDefinition<User>>();
var filterBuilder = Builders<User>.Filter;
switch (type)
{
case UserEnum.Type.Local:
filters.Add(filterBuilder.Eq(u => u.Type, UserEnum.Type.Local));
break;
case UserEnum.Type.Ldap:
filters.Add(filterBuilder.Eq(u => u.Type, UserEnum.Type.Ldap));
break;
var filters = new List<FilterDefinition<User>>();
var filterBuilder = Builders<User>.Filter;
switch (type)
{
case UserEnum.Type.Local:
filters.Add(filterBuilder.Eq(u => u.Type, UserEnum.Type.Local));
break;
case UserEnum.Type.Ldap:
filters.Add(filterBuilder.Eq(u => u.Type, UserEnum.Type.Ldap));
break;
}
return filters;
}
return filters;
}
/// <summary>
/// Builds a list of MongoDB filters that match users according to the requested status, handling enabled (including accounts where the enabled flag is missing), enabled and unlocked, enabled and locked, and disabled cases.
/// </summary>
/// <param name="status">The user status used to select which filter combination is applied; a <c>null</c> or unhandled value yields an empty filter list.</param>
/// <returns>A list of <see cref="FilterDefinition{TDocument}"/> filters that should be combined to query <see cref="User"/> documents for the specified status.</returns>
private List<FilterDefinition<User>> GetUserStatusFilter(StatusEnum.User? status)
{
var filters = new List<FilterDefinition<User>>();
var filterBuilder = Builders<User>.Filter;
switch (status)
{
case StatusEnum.User.Enabled:
filters.Add(filterBuilder.Or(
filterBuilder.Eq(u => u.IsEnabled, true),
filterBuilder.Exists(u => u.IsEnabled, false)
));
break;
case StatusEnum.User.EnabledUnlocked:
filters.Add(filterBuilder.Or(
filterBuilder.Eq(u => u.IsEnabled, true),
filterBuilder.Exists(u => u.IsEnabled, false)
));
filters.Add(filterBuilder.Ne(u => u.LockExpirationDate, null));
break;
case StatusEnum.User.EnabledLocked:
filters.Add(filterBuilder.Or(
filterBuilder.Eq(u => u.IsEnabled, true),
filterBuilder.Exists(u => u.IsEnabled, false)
));
filters.Add(filterBuilder.Eq(u => u.LockExpirationDate, null));
break;
case StatusEnum.User.Disabled:
filters.Add(filterBuilder.Eq(u => u.IsEnabled, false));
break;
var filters = new List<FilterDefinition<User>>();
var filterBuilder = Builders<User>.Filter;
switch (status)
{
case StatusEnum.User.Enabled:
filters.Add(filterBuilder.Or(
filterBuilder.Eq(u => u.IsEnabled, true),
filterBuilder.Exists(u => u.IsEnabled, false)
));
break;
case StatusEnum.User.EnabledUnlocked:
filters.Add(filterBuilder.Or(
filterBuilder.Eq(u => u.IsEnabled, true),
filterBuilder.Exists(u => u.IsEnabled, false)
));
filters.Add(filterBuilder.Ne(u => u.LockExpirationDate, null));
break;
case StatusEnum.User.EnabledLocked:
filters.Add(filterBuilder.Or(
filterBuilder.Eq(u => u.IsEnabled, true),
filterBuilder.Exists(u => u.IsEnabled, false)
));
filters.Add(filterBuilder.Eq(u => u.LockExpirationDate, null));
break;
case StatusEnum.User.Disabled:
filters.Add(filterBuilder.Eq(u => u.IsEnabled, false));
break;
}
return filters;
}
return filters;
}
}
@@ -9,6 +9,9 @@ using Newtonsoft.Json;
namespace adas_core.Infrastructure.Services;
/// <summary>
/// Represents a service that implements the <see cref="IPublisherService"/> contract, providing the concrete implementation of the publishing operations defined by the interface.
/// </summary>
public class PublisherService : IPublisherService
{
private readonly ILogger<PublisherService> _logger;
@@ -64,63 +67,81 @@ public class PublisherService : IPublisherService
return Task.FromResult(true);
}
/// <summary>
/// Sends a message to the specified queue using the configured bus. If the bus is not initialized or an error occurs, the operation is logged and <c>false</c> is returned.
/// </summary>
/// <param name="msg">The message payload to send to the queue.</param>
/// <param name="queueName">The name of the target queue to which the message will be sent.</param>
/// <returns><c>true</c> if the message was sent successfully; otherwise, <c>false</c>.</returns>
public async Task<bool> SendMessage(string msg, string queueName)
{
try
{
if (_bus == null)
try
{
_logger.LogError("Bus is null");
if (_bus == null)
{
_logger.LogError("Bus is null");
return false;
}
await CreateQueue(queueName);
_logger.LogDebug("Sending message to {queueName}", queueName);
await _bus.SendReceive.SendAsync(queueName, msg);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error sending message to {queueName}", queueName);
return false;
}
await CreateQueue(queueName);
_logger.LogDebug("Sending message to {queueName}", queueName);
await _bus.SendReceive.SendAsync(queueName, msg);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error sending message to {queueName}", queueName);
return false;
}
}
/// <summary>
/// Serializes the specified object to JSON and asynchronously sends it to the named queue.
/// </summary>
/// <param name="obj">The object to serialize and send to the queue.</param>
/// <param name="queueName">The name of the destination queue.</param>
/// <returns>A task that resolves to <c>true</c> if the message was sent successfully; otherwise, <c>false</c>.</returns>
public async Task<bool> SendMessage(object obj, string queueName)
{
var json = JsonConvert.SerializeObject(obj);
return await SendMessage(json, queueName);
}
public async Task<bool> SendMessageError(object obj, string queueName)
{
try
{
if (_bus == null)
var json = JsonConvert.SerializeObject(obj);
return await SendMessage(json, queueName);
}
/// <summary>
/// Sends an error message to the specified queue. Validates that the bus instance is available and that the supplied object is a <c>Message&lt;Error&gt;</c>; returns <c>false</c> when either validation fails or when the send operation throws an exception.
/// </summary>
/// <param name="obj">The message object expected to be a <c>Message&lt;Error&gt;</c>. If it is not, the method returns <c>false</c> without sending anything.</param>
/// <param name="queueName">The name of the queue to which the error message body will be sent. The queue is created if it does not already exist.</param>
/// <returns>A <see cref="Task{Boolean}"/> that resolves to <c>true</c> when the error message is successfully sent, and <c>false</c> when the bus is null, the object is not a <c>Message&lt;Error&gt;</c>, or an exception is raised while sending.</returns>
public async Task<bool> SendMessageError(object obj, string queueName)
{
try
{
_logger.LogError("Bus is null - cannot send error message");
if (_bus == null)
{
_logger.LogError("Bus is null - cannot send error message");
return false;
}
if (obj is not Message<Error> errorMessage)
return false;
await CreateQueue(queueName);
_logger.LogDebug("Sending error message to {queueName}", queueName);
// enviamos solo el Error (no Message<Error>)
await _bus.SendReceive.SendAsync(queueName, errorMessage.Body);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error sending error message");
return false;
}
if (obj is not Message<Error> errorMessage)
return false;
await CreateQueue(queueName);
_logger.LogDebug("Sending error message to {queueName}", queueName);
// enviamos solo el Error (no Message<Error>)
await _bus.SendReceive.SendAsync(queueName, errorMessage.Body);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error sending error message");
return false;
}
}
}
@@ -10,6 +10,9 @@ using Newtonsoft.Json;
namespace adas_core.Infrastructure.Services;
/// <summary>
/// Provides functionality for receiving and processing incoming data, messages, or requests.
/// </summary>
public class ReceiverService
{
private readonly ILogger<ReceiverService> _logger;
@@ -63,65 +66,78 @@ public class ReceiverService
}
}
/// <summary>
/// Registers all application queues defined in settings, including queues for observations, treatments, patients, pumps, appointments, recordings, recording alerts, and alarm observations, by adding each one through the <c>AddQueue</c> call.
/// </summary>
private void SetQueues()
{
AddQueue(_settings.ObservationsQueue);
AddQueue(_settings.TreatmentsQueue);
AddQueue(_settings.PatientsQueue);
AddQueue(_settings.PumpsQueue);
AddQueue(_settings.AppointmentsQueue);
AddQueue(_settings.RecordingQueue);
AddQueue(_settings.RecordingAlertQueue);
AddQueue(_settings.AlarmObservationQueue);
}
{
AddQueue(_settings.ObservationsQueue);
AddQueue(_settings.TreatmentsQueue);
AddQueue(_settings.PatientsQueue);
AddQueue(_settings.PumpsQueue);
AddQueue(_settings.AppointmentsQueue);
AddQueue(_settings.RecordingQueue);
AddQueue(_settings.RecordingAlertQueue);
AddQueue(_settings.AlarmObservationQueue);
}
/// <summary>
/// Adds a queue to the internal collection only when the supplied value is not null, empty, or whitespace; otherwise the call is ignored.
/// </summary>
/// <param name="queue">The queue identifier to add to the collection. Null, empty, or whitespace values are silently skipped.</param>
private void AddQueue(string? queue)
{
if (!string.IsNullOrWhiteSpace(queue))
_queues.Add(queue!);
}
{
if (!string.IsNullOrWhiteSpace(queue))
_queues.Add(queue!);
}
/// <summary>
/// Attempts to establish a connection to RabbitMQ by disposing any existing bus, configuring EasyNetQ with the configured connection string, and registering consumers for all configured queues. If the connection fails, the failure is logged and a timer is scheduled to retry the connection after a five-second delay, recursively invoking the method when the timer elapses.
/// </summary>
private void TryToConnect()
{
try
{
DisposeBus();
var services = new ServiceCollection();
services.AddEasyNetQ(_settings.ConnectionString);
var provider = services.BuildServiceProvider();
_bus = provider.GetRequiredService<IBus>();
foreach (var queue in _queues)
try
{
RegisterConsumer(queue);
DisposeBus();
var services = new ServiceCollection();
services.AddEasyNetQ(_settings.ConnectionString);
var provider = services.BuildServiceProvider();
_bus = provider.GetRequiredService<IBus>();
foreach (var queue in _queues)
{
RegisterConsumer(queue);
}
_logger.LogInformation("RabbitMQ connected. Registered {Count} queues", _queues.Count);
}
_logger.LogInformation("RabbitMQ connected. Registered {Count} queues", _queues.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "RabbitMQ connection failed. Retrying in 5 seconds...");
var timer = new System.Timers.Timer(5000);
timer.Elapsed += (_, _) =>
catch (Exception ex)
{
timer.Stop();
TryToConnect();
};
timer.Start();
_logger.LogError(ex, "RabbitMQ connection failed. Retrying in 5 seconds...");
var timer = new System.Timers.Timer(5000);
timer.Elapsed += (_, _) =>
{
timer.Stop();
TryToConnect();
};
timer.Start();
}
}
}
/// <summary>
/// Disposes the underlying bus instance if it implements <see cref="IDisposable"/>.
/// </summary>
private void DisposeBus()
{
if (_bus is IDisposable disposable)
{
disposable.Dispose();
if (_bus is IDisposable disposable)
{
disposable.Dispose();
}
}
}
private void RegisterConsumer(string queue)
{
@@ -154,58 +170,67 @@ public class ReceiverService
});
}
/// <summary>
/// Resolves and returns the <see cref="IApiRequestService"/> instance associated with the specified queue name by matching it against the configured queue settings for observations, treatments, patients, pumps, recordings, recording alerts, alarm observations, and appointments.
/// </summary>
/// <param name="queue">The queue name to resolve to its corresponding API request service.</param>
/// <returns>The matching <see cref="IApiRequestService"/> instance if the queue is recognized; otherwise, <c>null</c>.</returns>
private IApiRequestService? ResolveService(string queue)
{
return queue switch
{
var q when q == _settings.ObservationsQueue => _observationService,
var q when q == _settings.TreatmentsQueue => _treatmentService,
var q when q == _settings.PatientsQueue => _patientsService,
var q when q == _settings.PumpsQueue => _pumpService,
var q when q == _settings.RecordingQueue => _recordingService,
var q when q == _settings.RecordingAlertQueue => _recordingAlertService,
var q when q == _settings.AlarmObservationQueue => _alarmService,
var q when q == _settings.AppointmentsQueue => _appointmentService,
_ => null
};
}
public async Task ProcessErrorQueue(string errorQueueName)
{
if (_bus == null)
{
_logger.LogError("Cannot process error queue - bus is null");
return;
return queue switch
{
var q when q == _settings.ObservationsQueue => _observationService,
var q when q == _settings.TreatmentsQueue => _treatmentService,
var q when q == _settings.PatientsQueue => _patientsService,
var q when q == _settings.PumpsQueue => _pumpService,
var q when q == _settings.RecordingQueue => _recordingService,
var q when q == _settings.RecordingAlertQueue => _recordingAlertService,
var q when q == _settings.AlarmObservationQueue => _alarmService,
var q when q == _settings.AppointmentsQueue => _appointmentService,
_ => null
};
}
await _bus.SendReceive.ReceiveAsync<Error>(errorQueueName, async err =>
/// <summary>
/// Asynchronously reprocesses messages from the specified error queue by deriving the target queue name from the routing key (with "Key" removed), resolving the associated service, and resubmitting the message. Operations are skipped when the bus is uninitialized, the derived queue name is empty, or no service is resolved.
/// </summary>
/// <param name="errorQueueName">The name of the error queue from which to reprocess messages.</param>
public async Task ProcessErrorQueue(string errorQueueName)
{
try
if (_bus == null)
{
var queue = err.RoutingKey?.Replace("Key", "");
if (string.IsNullOrEmpty(queue))
return;
var service = ResolveService(queue);
if (service == null)
return;
var message = new Message<string>(
err.Message,
new MessageProperties()
);
await Task.Run(() => TryToParseAndSend(message, service));
_logger.LogError("Cannot process error queue - bus is null");
return;
}
catch (Exception ex)
await _bus.SendReceive.ReceiveAsync<Error>(errorQueueName, async err =>
{
_logger.LogError(ex, "Error reprocessing message from {queue}", errorQueueName);
throw;
}
});
}
try
{
var queue = err.RoutingKey?.Replace("Key", "");
if (string.IsNullOrEmpty(queue))
return;
var service = ResolveService(queue);
if (service == null)
return;
var message = new Message<string>(
err.Message,
new MessageProperties()
);
await Task.Run(() => TryToParseAndSend(message, service));
}
catch (Exception ex)
{
_logger.LogError(ex, "Error reprocessing message from {queue}", errorQueueName);
throw;
}
});
}
/// <summary>
/// Sends EasyNetQ messages to a service implementing IApiRequestService
+346 -258
View File
@@ -20,6 +20,14 @@ using Quartz.Util;
namespace adas_core.Infrastructure.Services;
/// <summary>
/// Provides a concrete implementation of the <see cref="IRelayService"/> contract,
/// delivering relay-related functionality to consumers of the service.
/// </summary>
/// <remarks>
/// This class is the default implementation of <see cref="IRelayService"/>,
/// and can be substituted via dependency injection where the interface is required.
/// </remarks>
public class RelayService : IRelayService
{
private readonly List<RelayDevice> _devices = [];
@@ -50,22 +58,84 @@ public class RelayService : IRelayService
Task.Run(async () => await InitRelayWithStatus());
}
/// <summary>
/// Asynchronously determines the current status of a relay, preferring a cached value when available and falling back to a direct device read or an external endpoint when the device is unreachable.
/// Returns the cached status if caching is enabled, the cached value is present and not <see cref="RelayEnum.Status.NotInitialized"/>; otherwise queries the relay device, or invokes the configured URL endpoint if the device cannot be obtained.
/// On failure, logs the error and resolves the status to <see cref="RelayEnum.Status.Unknown"/>.
/// </summary>
/// <param name="relay">The relay to check, identified by its IP, port, and relay number used both as the cache key and as the target of the status request.</param>
/// <returns>A task that yields the resolved <see cref="RelayEnum.Status"/> of the relay, or <see cref="RelayEnum.Status.Unknown"/> when the status cannot be determined.</returns>
public async Task<RelayEnum.Status> CheckRelayStatus(Relay relay)
{
var cacheKey = Tuple.Create(relay.Ip, relay.Port, relay.RelayNumber);
try
{
var cacheKey = Tuple.Create(relay.Ip, relay.Port, relay.RelayNumber);
try
{
if (_relaySettings.Cache && relay.Cache)
lock (_relayWithStatus)
{
if (_relayWithStatus.TryGetValue(cacheKey, out var status) &&
status != RelayEnum.Status.NotInitialized)
return status;
_logger.LogDebug("Relay status not found in cache: {Dct}",
DictionaryToString(_relayWithStatus));
}
RelayDevice? relayDevice = null;
try
{
relayDevice = GetRelayDevice(relay);
}
catch (Exception e)
{
_logger.LogWarning("Error getting relay device: {EMessage} {EStackTrace}", e.Message, e.StackTrace);
}
if (relayDevice == null && !string.IsNullOrEmpty(_url)) return await GetRelayStatusByOr(relay);
if (relayDevice != null) return relayDevice.GetStatusRelay(relay.RelayNumber);
}
catch (Exception e)
{
_logger.LogError("Error CheckRelayStatus relay {Relay}: {EMessage} {EStackTrace}", relay, e.Message,
e.StackTrace);
lock (_relayWithStatus)
{
_relayWithStatus[cacheKey] = RelayEnum.Status.Unknown;
}
}
return RelayEnum.Status.Unknown;
}
/// <summary>
/// Checks the status of a relay identified by its unique ID. Returns <see cref="RelayEnum.Status.Unknown"/> when the relay cannot be found, otherwise delegates to the relay-based overload to resolve the current status.
/// </summary>
/// <param name="relayId">The unique identifier of the relay whose status should be checked.</param>
/// <returns>The current <see cref="RelayEnum.Status"/> of the relay, or <see cref="RelayEnum.Status.Unknown"/> if no relay with the given ID exists.</returns>
public async Task<RelayEnum.Status> CheckRelayStatus(ObjectId relayId)
{
var relay = await _relayRepository.GetById(relayId);
if(relay == null) return RelayEnum.Status.Unknown;
return await CheckRelayStatus(relay);
}
/// <summary>
/// Asynchronously powers off the specified relay. Uses a cache to avoid redundant operations when the relay is already off, falls back to an HTTP request when no local relay device is available, and updates the cache after a successful power off.
/// </summary>
/// <param name="relay">The relay to power off, including its network address and relay number.</param>
public async Task PowerOff(Relay relay)
{
var cacheKey = Tuple.Create(relay.Ip, relay.Port, relay.RelayNumber);
if (_relaySettings.Cache && relay.Cache)
lock (_relayWithStatus)
{
if (_relayWithStatus.TryGetValue(cacheKey, out var status) &&
status != RelayEnum.Status.NotInitialized)
return status;
_logger.LogDebug("Relay status not found in cache: {Dct}",
DictionaryToString(_relayWithStatus));
if (_relayWithStatus.Any() &&
_relayWithStatus[cacheKey] == RelayEnum.Status.Off)
return;
}
RelayDevice? relayDevice = null;
try
{
@@ -75,156 +145,137 @@ public class RelayService : IRelayService
{
_logger.LogWarning("Error getting relay device: {EMessage} {EStackTrace}", e.Message, e.StackTrace);
}
if (relayDevice == null && !string.IsNullOrEmpty(_url)) return await GetRelayStatusByOr(relay);
if (relayDevice != null) return relayDevice.GetStatusRelay(relay.RelayNumber);
}
catch (Exception e)
{
_logger.LogError("Error CheckRelayStatus relay {Relay}: {EMessage} {EStackTrace}", relay, e.Message,
e.StackTrace);
lock (_relayWithStatus)
if (relayDevice == null && !string.IsNullOrEmpty(_url))
{
_relayWithStatus[cacheKey] = RelayEnum.Status.Unknown;
_builder = new UriBuilder(_url)
{
Path = $"Relay/{relay.RelayNumber}/powerOff"
};
_logger.LogDebug("Send PowerOff relay {Relay}", relay);
await PowerRelay(relay, _builder);
}
}
return RelayEnum.Status.Unknown;
}
public async Task<RelayEnum.Status> CheckRelayStatus(ObjectId relayId)
{
var relay = await _relayRepository.GetById(relayId);
if(relay == null) return RelayEnum.Status.Unknown;
return await CheckRelayStatus(relay);
}
public async Task PowerOff(Relay relay)
{
var cacheKey = Tuple.Create(relay.Ip, relay.Port, relay.RelayNumber);
if (_relaySettings.Cache && relay.Cache)
lock (_relayWithStatus)
{
if (_relayWithStatus.Any() &&
_relayWithStatus[cacheKey] == RelayEnum.Status.Off)
return;
}
RelayDevice? relayDevice = null;
try
{
relayDevice = GetRelayDevice(relay);
}
catch (Exception e)
{
_logger.LogWarning("Error getting relay device: {EMessage} {EStackTrace}", e.Message, e.StackTrace);
}
if (relayDevice == null && !string.IsNullOrEmpty(_url))
{
_builder = new UriBuilder(_url)
{
Path = $"Relay/{relay.RelayNumber}/powerOff"
};
_logger.LogDebug("Send PowerOff relay {Relay}", relay);
await PowerRelay(relay, _builder);
}
relayDevice?.PowerOffRelay(relay.RelayNumber);
if (_relaySettings.Cache && relay.Cache)
lock (_relayWithStatus)
{
_relayWithStatus[cacheKey] = RelayEnum.Status.Off;
_logger.LogDebug("Relay status cached: {Dct}", DictionaryToString(_relayWithStatus));
}
}
public async Task PowerOn(Relay relay)
{
if (string.IsNullOrEmpty(relay.Driver) || string.IsNullOrEmpty(relay.Ip)) return;
var cacheKey = Tuple.Create(relay.Ip, relay.Port, relay.RelayNumber);
if (_relaySettings.Cache && relay.Cache)
relayDevice?.PowerOffRelay(relay.RelayNumber);
if (_relaySettings.Cache && relay.Cache)
lock (_relayWithStatus)
{
if (_relayWithStatus.Any() &&
_relayWithStatus[cacheKey] == RelayEnum.Status.On)
return;
_relayWithStatus[cacheKey] = RelayEnum.Status.Off;
_logger.LogDebug("Relay status cached: {Dct}", DictionaryToString(_relayWithStatus));
}
RelayDevice? relayDevice = null;
try
{
relayDevice = GetRelayDevice(relay);
}
catch (Exception e)
{
_logger.LogWarning("Error getting relay device: {EMessage} {EStackTrace}", e.Message, e.StackTrace);
}
if (relayDevice == null && !string.IsNullOrEmpty(_url))
/// <summary>
/// Powers on the specified relay. If the relay is configured with a URL, sends a remote power-on request; otherwise, drives the relay device directly. Honors a caching layer to avoid redundant power-on commands when the relay is already reported as on.
/// </summary>
/// <param name="relay">The relay to power on, including driver, IP, port, relay number, and cache settings.</param>
public async Task PowerOn(Relay relay)
{
_builder = new UriBuilder(_url)
if (string.IsNullOrEmpty(relay.Driver) || string.IsNullOrEmpty(relay.Ip)) return;
var cacheKey = Tuple.Create(relay.Ip, relay.Port, relay.RelayNumber);
if (_relaySettings.Cache && relay.Cache)
if (_relaySettings.Cache && relay.Cache)
lock (_relayWithStatus)
{
if (_relayWithStatus.Any() &&
_relayWithStatus[cacheKey] == RelayEnum.Status.On)
return;
}
RelayDevice? relayDevice = null;
try
{
Path = $"Relay/{relay.RelayNumber}/powerOn"
};
_logger.LogDebug("Send PowerOn relay {Relay}", relay);
await PowerRelay(relay, _builder);
}
relayDevice?.PowerOnRelay(relay.RelayNumber);
if (_relaySettings.Cache && relay.Cache)
lock (_relayWithStatus)
{
_relayWithStatus[cacheKey] = RelayEnum.Status.On;
_logger.LogDebug("Relay status cached: {Dct}", DictionaryToString(_relayWithStatus));
relayDevice = GetRelayDevice(relay);
}
}
catch (Exception e)
{
_logger.LogWarning("Error getting relay device: {EMessage} {EStackTrace}", e.Message, e.StackTrace);
}
if (relayDevice == null && !string.IsNullOrEmpty(_url))
{
_builder = new UriBuilder(_url)
{
Path = $"Relay/{relay.RelayNumber}/powerOn"
};
_logger.LogDebug("Send PowerOn relay {Relay}", relay);
await PowerRelay(relay, _builder);
}
relayDevice?.PowerOnRelay(relay.RelayNumber);
if (_relaySettings.Cache && relay.Cache)
lock (_relayWithStatus)
{
_relayWithStatus[cacheKey] = RelayEnum.Status.On;
_logger.LogDebug("Relay status cached: {Dct}", DictionaryToString(_relayWithStatus));
}
}
/// <summary>
/// Sets the manual relay status for a point of care device. If the point of care exists and has a configured relay list, the relay matching the specified type is updated with the new status; otherwise, the operation is silently skipped.
/// </summary>
/// <param name="status">The manual relay status to apply.</param>
/// <param name="pocId">The identifier of the point of care device whose relay configuration should be updated.</param>
/// <param name="type">The relay type used to look up the target relay within the point of care's relay configuration.</param>
public async Task SetManualRelay(RelayEnum.Status status, ObjectId pocId, RelayEnum.Type type)
{
try
{
var poc = await _pointOfCareService.FindById(pocId);
if (poc is { Configuration.RelayIdList: not null })
try
{
var relay = _relayRepository.GetRelayByTypeInList(poc.Configuration.RelayIdList, type).FirstOrDefault();
if (relay != null) relay.ManualRelayStatus = status;
await _pointOfCareService.UpdateRelayConfig(poc);
_logger.LogDebug("SetManualRelay: {Status}, pocId: {Location}, bed: {Bed}", status, poc.Id, poc.Bed);
var poc = await _pointOfCareService.FindById(pocId);
if (poc is { Configuration.RelayIdList: not null })
{
var relay = _relayRepository.GetRelayByTypeInList(poc.Configuration.RelayIdList, type).FirstOrDefault();
if (relay != null) relay.ManualRelayStatus = status;
await _pointOfCareService.UpdateRelayConfig(poc);
_logger.LogDebug("SetManualRelay: {Status}, pocId: {Location}, bed: {Bed}", status, poc.Id, poc.Bed);
}
}
catch (Exception ex)
{
_logger.LogError("Error settings manual relay. Exception: {ex}", ex);
throw;
}
}
catch (Exception ex)
{
_logger.LogError("Error settings manual relay. Exception: {ex}", ex);
throw;
}
}
/// <summary>
/// Retrieves a relay entity by its unique identifier from the repository.
/// Returns <c>null</c> when no matching relay is found.
/// </summary>
/// <param name="relay">The unique identifier of the relay to look up.</param>
/// <returns>A task that resolves to the <see cref="Relay"/> if found, or <c>null</c> if no relay matches the supplied identifier.</returns>
public Task<Relay?> GetById(ObjectId relay)
{
return _relayRepository.GetById(relay);
}
{
return _relayRepository.GetById(relay);
}
/// <summary>
/// Retrieves a list of relays matching the specified list of relay identifiers. Returns an empty list when the provided identifier list is null.
/// </summary>
/// <param name="relayList">The list of relay identifiers to look up. Can be null.</param>
/// <returns>A list of <see cref="Relay"/> objects corresponding to the provided identifiers, or an empty list if the input is null.</returns>
public List<Relay> GetRelayInList(List<ObjectId>? relayList)
{
if(relayList == null) return new List<Relay>();
return _relayRepository.GetRelayInList(relayList);
}
{
if(relayList == null) return new List<Relay>();
return _relayRepository.GetRelayInList(relayList);
}
/// <summary>
/// Retrieves the relays of a specified type from the provided list of configuration relay identifiers. Returns an empty list when the input list is <c>null</c>, otherwise delegates the lookup to the relay repository.
/// </summary>
/// <param name="configurationRelayList">The optional list of <see cref="ObjectId"/> values identifying the configuration relays to filter; when <c>null</c>, the method short-circuits and returns an empty list.</param>
/// <param name="type">The relay type used to filter the matching relays within the supplied list.</param>
/// <returns>A <see cref="List{Relay}"/> containing the relays matching the specified <paramref name="type"/>, or an empty list if <paramref name="configurationRelayList"/> is <c>null</c>.</returns>
public List<Relay> GetRelayByTypeInList(List<ObjectId>? configurationRelayList, RelayEnum.Type type)
{
if(configurationRelayList == null) return new List<Relay>();
return _relayRepository.GetRelayByTypeInList(configurationRelayList, type);
}
{
if(configurationRelayList == null) return new List<Relay>();
return _relayRepository.GetRelayByTypeInList(configurationRelayList, type);
}
public async Task<PaginationResponse<Relay>> GetPaginatedRelays(PaginationFilter filter)
{
@@ -266,17 +317,29 @@ public class RelayService : IRelayService
return new PaginationResponse<Relay>(data, filter.PageNumber, filter.PageSize, count);
}
/// <summary>
/// Inserts a new relay into the repository after verifying that no relay with the same name already exists.
/// </summary>
/// <param name="request">The relay entity to insert, whose name is checked for duplicates prior to persistence.</param>
/// <returns>The inserted <see cref="Relay"/> entity returned by the repository, or <c>null</c> if the repository yields no result.</returns>
/// <exception cref="Exception">Thrown when a relay with the same name as <paramref name="request"/> already exists in the repository.</exception>
public async Task<Relay?> InsertRelay(Relay request)
{
var relayExist = await _relayRepository.GetByName(request.RelayName);
if(relayExist != null) throw new Exception($"Relay with name {request.RelayName} already exists");
return await _relayRepository.InsertOneRelayAsync(request);
}
{
var relayExist = await _relayRepository.GetByName(request.RelayName);
if(relayExist != null) throw new Exception($"Relay with name {request.RelayName} already exists");
return await _relayRepository.InsertOneRelayAsync(request);
}
/// <summary>
/// Updates an existing relay identified by its unique ObjectId by delegating to the relay repository.
/// </summary>
/// <param name="objectId">The unique identifier of the relay to update.</param>
/// <param name="relay">The relay object containing the updated information.</param>
/// <returns>A task representing the asynchronous operation, containing the updated <see cref="Relay"/>, or <c>null</c> if no relay with the specified identifier was found.</returns>
public Task<Relay?> UpdateRelayById(ObjectId objectId, Relay relay)
{
return _relayRepository.UpdateRelayAsync(objectId, relay);
}
{
return _relayRepository.UpdateRelayAsync(objectId, relay);
}
public event EventHandler<Tuple<RelayDevice, int>>? RelayStatusChanged;
@@ -325,135 +388,160 @@ public class RelayService : IRelayService
}
}
/// <summary>
/// Retrieves a <see cref="RelayDevice"/> for the given relay, returning a cached instance when available
/// or dynamically creating and registering a new one based on the relay's driver type. Returns <c>null</c>
/// when the relay has no IP address or its port is zero.
/// </summary>
/// <param name="relay">The relay configuration used to locate an existing device or instantiate a new one.</param>
/// <returns>The existing or newly created <see cref="RelayDevice"/>, or <c>null</c> if the relay is missing a valid IP or port.</returns>
/// <exception cref="AdasException">Thrown when no constructor matching <see cref="Relay"/> and <see cref="RelaySettings"/> is found on the resolved driver type.</exception>
/// <exception cref="AdasException">Thrown when the resolved driver type cannot be instantiated into a <see cref="RelayDevice"/>.</exception>
private RelayDevice? GetRelayDevice(Relay relay)
{
if(relay.Ip.IsNullOrWhiteSpace() || relay.Port == 0) return null;
var device = _devices.FirstOrDefault(d => d.Relay.Ip == relay.Ip && d.Relay.Port == relay.Port);
if (device != null) return device;
var type = TypesUtils.GetDriver("Relay", relay.Driver ?? string.Empty);
var constructor = type.GetConstructor([typeof(Relay), typeof(RelaySettings)]);
if (constructor == null) throw new AdasException("Constructor not found for relay device");
var relayDevice = (RelayDevice)constructor.Invoke([relay, _relaySettings]);
if (relayDevice == null) throw new AdasException("Relay device not found");
relayDevice.RelayStatusChanged += (_, outletId) =>
{
var cacheKey = Tuple.Create(relay.Ip, relay.Port, outletId);
var value = relayDevice.GetStatusRelay(outletId);
lock (_relayWithStatus)
if(relay.Ip.IsNullOrWhiteSpace() || relay.Port == 0) return null;
var device = _devices.FirstOrDefault(d => d.Relay.Ip == relay.Ip && d.Relay.Port == relay.Port);
if (device != null) return device;
var type = TypesUtils.GetDriver("Relay", relay.Driver ?? string.Empty);
var constructor = type.GetConstructor([typeof(Relay), typeof(RelaySettings)]);
if (constructor == null) throw new AdasException("Constructor not found for relay device");
var relayDevice = (RelayDevice)constructor.Invoke([relay, _relaySettings]);
if (relayDevice == null) throw new AdasException("Relay device not found");
relayDevice.RelayStatusChanged += (_, outletId) =>
{
_relayWithStatus[cacheKey] = value;
}
_logger.LogDebug("Relay status changed for {Key} with value {Value}", cacheKey, value);
RelayStatusChanged?.Invoke(this, Tuple.Create(relayDevice, outletId));
};
_devices.Add(relayDevice);
return relayDevice;
}
var cacheKey = Tuple.Create(relay.Ip, relay.Port, outletId);
var value = relayDevice.GetStatusRelay(outletId);
lock (_relayWithStatus)
{
_relayWithStatus[cacheKey] = value;
}
_logger.LogDebug("Relay status changed for {Key} with value {Value}", cacheKey, value);
RelayStatusChanged?.Invoke(this, Tuple.Create(relayDevice, outletId));
};
_devices.Add(relayDevice);
return relayDevice;
}
/// <summary>
/// Sends a power relay request by appending relay configuration details (driver, host, port, name, total count, and credentials) as query parameters to the provided URI and issuing an HTTP POST. Logs a debug message when the response status is not OK, and logs and swallows any exception encountered during the request.
/// </summary>
/// <param name="relay">The relay whose driver, IP, port, name, total count, username, and password are included in the request query string.</param>
/// <param name="builder">The URI builder whose query is updated with the relay parameters and whose resulting URI is used as the request target.</param>
private async Task PowerRelay(Relay relay, UriBuilder builder)
{
var query = HttpUtility.ParseQueryString(builder.Query);
query["driver"] = relay.Driver;
query["host"] = relay.Ip;
query["port"] = relay.Port.ToString();
query["relayName"] = relay.RelayName;
query["relays"] = relay.Total.ToString();
query["username"] = relay.Username;
query["password"] = relay.Password;
builder.Query = query.ToString();
HttpRequestMessage request = new()
{
RequestUri = builder.Uri,
Method = HttpMethod.Post
};
try
{
using var client = _httpClientFactory.CreateClient();
var httpResponse = await client.SendAsync(request);
if (!httpResponse.StatusCode.Equals(HttpStatusCode.OK))
_logger.LogDebug("Send PowerRelay relay {Relay}", relay);
var query = HttpUtility.ParseQueryString(builder.Query);
query["driver"] = relay.Driver;
query["host"] = relay.Ip;
query["port"] = relay.Port.ToString();
query["relayName"] = relay.RelayName;
query["relays"] = relay.Total.ToString();
query["username"] = relay.Username;
query["password"] = relay.Password;
builder.Query = query.ToString();
HttpRequestMessage request = new()
{
RequestUri = builder.Uri,
Method = HttpMethod.Post
};
try
{
using var client = _httpClientFactory.CreateClient();
var httpResponse = await client.SendAsync(request);
if (!httpResponse.StatusCode.Equals(HttpStatusCode.OK))
_logger.LogDebug("Send PowerRelay relay {Relay}", relay);
}
catch (Exception e)
{
_logger.LogError("Error PowerRelay relay {Relay}: {EMessage} {EStackTrace}", relay, e.Message,
e.StackTrace);
}
}
catch (Exception e)
{
_logger.LogError("Error PowerRelay relay {Relay}: {EMessage} {EStackTrace}", relay, e.Message,
e.StackTrace);
}
}
/// <summary>
/// Converts a concurrent dictionary of relay statuses into a human-readable string representation, formatting each entry as a line containing the device ID, port, and current status.
/// </summary>
/// <param name="dictionary">The concurrent dictionary containing relay status entries keyed by a tuple of device ID, port, and an additional integer value.</param>
/// <returns>A string containing one formatted line per dictionary entry describing the device ID, port, and status.</returns>
private static string DictionaryToString(ConcurrentDictionary<Tuple<string, int, int>, RelayEnum.Status> dictionary)
{
var builder = new StringBuilder();
foreach (var pair in dictionary)
builder.AppendLine($"Device ID: {pair.Key.Item1}, Port: {pair.Key.Item2}, Status: {pair.Value}");
return builder.ToString();
}
{
var builder = new StringBuilder();
foreach (var pair in dictionary)
builder.AppendLine($"Device ID: {pair.Key.Item1}, Port: {pair.Key.Item2}, Status: {pair.Value}");
return builder.ToString();
}
/// <summary>
/// Retrieves the current status of the specified relay by querying its external service endpoint, and caches the result (success or failure) when caching is enabled for both the application and the relay.
/// Falls back to <see cref="RelayEnum.Status.Unknown"/> when the HTTP response is not successful.
/// </summary>
/// <param name="relay">The relay instance whose status should be queried; its IP, port, relay number, driver, credentials, and mode are used to build the request.</param>
/// <returns>The parsed <see cref="RelayEnum.Status"/> returned by the remote relay service, or <see cref="RelayEnum.Status.Unknown"/> if the request fails.</returns>
private async Task<RelayEnum.Status> GetRelayStatusByOr(Relay relay)
{
var cacheKey = Tuple.Create(relay.Ip, relay.Port, relay.RelayNumber);
_builder = new UriBuilder(_url)
{
Path = $"Relay/{relay.RelayNumber}/status"
};
var query = HttpUtility.ParseQueryString(_builder.Query);
query["driver"] = relay.Driver;
query["host"] = relay.Ip;
query["port"] = relay.Port.ToString();
query["relayName"] = relay.RelayName;
query["relays"] = relay.Total.ToString();
query["username"] = relay.Username;
query["password"] = relay.Password;
query["mode"] = relay.Mode.ToString();
query["refreshTime"] = "0";
_builder.Query = query.ToString();
HttpRequestMessage request = new()
{
RequestUri = _builder.Uri,
Method = HttpMethod.Get
};
using var client = _httpClientFactory.CreateClient();
client.Timeout = new TimeSpan(0, 0, 3);
var httpResponse = await client.SendAsync(request);
if (httpResponse.StatusCode.Equals(HttpStatusCode.OK))
{
var responseContent = httpResponse.Content;
var response = await responseContent.ReadAsStringAsync();
response = response.Replace("\"", "");
var relayStatusParsed = (RelayEnum.Status)Enum.Parse(typeof(RelayEnum.Status), response);
var cacheKey = Tuple.Create(relay.Ip, relay.Port, relay.RelayNumber);
_builder = new UriBuilder(_url)
{
Path = $"Relay/{relay.RelayNumber}/status"
};
var query = HttpUtility.ParseQueryString(_builder.Query);
query["driver"] = relay.Driver;
query["host"] = relay.Ip;
query["port"] = relay.Port.ToString();
query["relayName"] = relay.RelayName;
query["relays"] = relay.Total.ToString();
query["username"] = relay.Username;
query["password"] = relay.Password;
query["mode"] = relay.Mode.ToString();
query["refreshTime"] = "0";
_builder.Query = query.ToString();
HttpRequestMessage request = new()
{
RequestUri = _builder.Uri,
Method = HttpMethod.Get
};
using var client = _httpClientFactory.CreateClient();
client.Timeout = new TimeSpan(0, 0, 3);
var httpResponse = await client.SendAsync(request);
if (httpResponse.StatusCode.Equals(HttpStatusCode.OK))
{
var responseContent = httpResponse.Content;
var response = await responseContent.ReadAsStringAsync();
response = response.Replace("\"", "");
var relayStatusParsed = (RelayEnum.Status)Enum.Parse(typeof(RelayEnum.Status), response);
if (_relaySettings.Cache && relay.Cache)
lock (_relayWithStatus)
{
_relayWithStatus[cacheKey] = relayStatusParsed;
_logger.LogDebug("Relay status cached: {Dct}", DictionaryToString(_relayWithStatus));
}
return relayStatusParsed;
}
if (_relaySettings.Cache && relay.Cache)
lock (_relayWithStatus)
{
_relayWithStatus[cacheKey] = relayStatusParsed;
_relayWithStatus[cacheKey] = RelayEnum.Status.Unknown;
_logger.LogDebug("Relay status cached: {Dct}", DictionaryToString(_relayWithStatus));
}
return relayStatusParsed;
return RelayEnum.Status.Unknown;
}
if (_relaySettings.Cache && relay.Cache)
lock (_relayWithStatus)
{
_relayWithStatus[cacheKey] = RelayEnum.Status.Unknown;
_logger.LogDebug("Relay status cached: {Dct}", DictionaryToString(_relayWithStatus));
}
return RelayEnum.Status.Unknown;
}
}
@@ -34,249 +34,298 @@ public class SendAlertService(
// PlatformID.WinCE
//};
/// <summary>
/// Asynchronously retrieves the list of available queues from the Rabbit messaging system.
/// If the Rabbit connection string is not defined in the web config, an error is logged and an empty list is returned.
/// </summary>
/// <returns>A task that represents the asynchronous operation. The task result contains a list of <see cref="Queue"/> objects, or an empty list if the Rabbit connection string is not configured.</returns>
public async Task<List<Queue>> GetQueues()
{
if (_rabbitConnectionString != null) return await GetQueuesAsync();
logger.LogError("Rabbit connection string not defined in web config");
return [];
}
{
if (_rabbitConnectionString != null) return await GetQueuesAsync();
logger.LogError("Rabbit connection string not defined in web config");
return [];
}
/// <summary>
/// Retrieves performance data from hospitals, including CPU, RAM, and storage consumption metrics.
/// Logs and suppresses any errors encountered while collecting the data, returning the successfully gathered metrics.
/// </summary>
/// <returns>A list of <see cref="Performance"/> entries containing the collected performance metrics; returns an empty list if all collection attempts fail.</returns>
public List<Performance> GetPerformance()
{
logger.LogDebug("Starting check performance data from hospitals");
var list = new List<Performance>();
try
{
list.Add(GetConsumedCpu());
list.Add(GetConsumedRam());
list.AddRange(GetConsumedStorages());
}
catch (Exception e)
{
logger.LogError("Error check error performance data from hospitals: {eMessage} {eStackTrace}",
e.Message, e.StackTrace);
logger.LogDebug("Starting check performance data from hospitals");
var list = new List<Performance>();
try
{
list.Add(GetConsumedCpu());
list.Add(GetConsumedRam());
list.AddRange(GetConsumedStorages());
}
catch (Exception e)
{
logger.LogError("Error check error performance data from hospitals: {eMessage} {eStackTrace}",
e.Message, e.StackTrace);
}
return list;
}
return list;
}
/// <summary>
/// Asynchronously retrieves a list of API clients by delegating to the underlying data retrieval method.
/// </summary>
/// <returns>A task representing the asynchronous operation, containing the list of <see cref="ApiClients"/> instances retrieved.</returns>
public async Task<List<ApiClients>> GetApiClients()
{
return await GetApiClientsAsync();
}
{
return await GetApiClientsAsync();
}
/// <summary>
/// Asynchronously retrieves statistics for the configured RabbitMQ error queues (recording, patients, treatments, observations, and pumps). Only queues with non-empty names are queried, duplicates are ignored, and any exception raised while connecting or fetching stats is logged and results in an empty list being returned.
/// </summary>
/// <returns>A task that represents the asynchronous operation. The task result contains a list of <see cref="Queue"/> objects with the name, message count, and consumer count of each queried queue, or an empty list if an error occurs.</returns>
private async Task<List<Queue>> GetQueuesAsync()
{
logger.LogDebug("Starting check rabbitMQ data from hospitals");
var errorQueues = new List<Queue>();
var errorQueuesNames = new List<string>();
try
{
var services = new ServiceCollection();
services.AddEasyNetQ(_rabbitConnectionString);
var provider = services.BuildServiceProvider();
var bus = provider.GetRequiredService<IBus>();
var advanced = bus.Advanced;
if (!string.IsNullOrEmpty(_errorRecordingQueue))
errorQueuesNames.Add(_errorRecordingQueue);
if (!string.IsNullOrEmpty(_errorPatientsQueue))
errorQueuesNames.Add(_errorPatientsQueue);
if (!string.IsNullOrEmpty(_errorTreatmetsQueue))
errorQueuesNames.Add(_errorTreatmetsQueue);
if (!string.IsNullOrEmpty(_errorObservationsQueue))
errorQueuesNames.Add(_errorObservationsQueue);
if (!string.IsNullOrEmpty(_errorPumpsQueue))
errorQueuesNames.Add(_errorPumpsQueue);
foreach (var errorQueueName in errorQueuesNames.Distinct())
logger.LogDebug("Starting check rabbitMQ data from hospitals");
var errorQueues = new List<Queue>();
var errorQueuesNames = new List<string>();
try
{
var stats = await advanced.GetQueueStatsAsync(errorQueueName);
errorQueues.Add(new Queue
var services = new ServiceCollection();
services.AddEasyNetQ(_rabbitConnectionString);
var provider = services.BuildServiceProvider();
var bus = provider.GetRequiredService<IBus>();
var advanced = bus.Advanced;
if (!string.IsNullOrEmpty(_errorRecordingQueue))
errorQueuesNames.Add(_errorRecordingQueue);
if (!string.IsNullOrEmpty(_errorPatientsQueue))
errorQueuesNames.Add(_errorPatientsQueue);
if (!string.IsNullOrEmpty(_errorTreatmetsQueue))
errorQueuesNames.Add(_errorTreatmetsQueue);
if (!string.IsNullOrEmpty(_errorObservationsQueue))
errorQueuesNames.Add(_errorObservationsQueue);
if (!string.IsNullOrEmpty(_errorPumpsQueue))
errorQueuesNames.Add(_errorPumpsQueue);
foreach (var errorQueueName in errorQueuesNames.Distinct())
{
Name = errorQueueName,
Messages = stats.MessagesCount,
Consumers = stats.ConsumersCount
});
var stats = await advanced.GetQueueStatsAsync(errorQueueName);
errorQueues.Add(new Queue
{
Name = errorQueueName,
Messages = stats.MessagesCount,
Consumers = stats.ConsumersCount
});
}
}
}
catch (Exception e)
{
logger.LogError("Error checking RabbitMQ queues: {msg} {stack}",
e.Message, e.StackTrace);
catch (Exception e)
{
logger.LogError("Error checking RabbitMQ queues: {msg} {stack}",
e.Message, e.StackTrace);
}
return errorQueues;
}
return errorQueues;
}
/// <summary>
/// Retrieves the CPU consumption of the current process as a Performance measurement.
/// Calculates usage from total processor time divided by processor count, and returns a default Performance instance if the underlying process query fails.
/// </summary>
/// <returns>A Performance object describing the CPU usage percentage, total capacity, and unit. If an error occurs, a default Performance instance is returned and the exception is logged.</returns>
public Performance GetConsumedCpu()
{
Performance performance = new();
try
{
var currentProcess = Process.GetCurrentProcess();
var percentage = currentProcess.TotalProcessorTime.TotalMilliseconds / Environment.ProcessorCount / 10;
performance = new Performance
Performance performance = new();
try
{
Name = "CPU",
PercentageConsumed = percentage,
ValueTotal = 100,
Unit = "%"
};
}
catch (Exception e)
{
logger.LogError("Error check CPU performance: {eMessage} {eStackTrace}", e.Message, e.StackTrace);
var currentProcess = Process.GetCurrentProcess();
var percentage = currentProcess.TotalProcessorTime.TotalMilliseconds / Environment.ProcessorCount / 10;
performance = new Performance
{
Name = "CPU",
PercentageConsumed = percentage,
ValueTotal = 100,
Unit = "%"
};
}
catch (Exception e)
{
logger.LogError("Error check CPU performance: {eMessage} {eStackTrace}", e.Message, e.StackTrace);
}
return performance;
}
return performance;
}
/// <summary>
/// Retrieves the current RAM consumption in gigabytes, returning it as a Performance metric where the total and consumed values are reported as equal with a consumption percentage of 100. If an error occurs while retrieving the memory information, the exception is logged and an empty Performance object is returned.
/// </summary>
/// <returns>A Performance object representing the RAM usage in GB; returns an empty Performance object if the memory retrieval fails.</returns>
public Performance GetConsumedRam()
{
Performance performance = new();
try
{
var totalMemoryBytes = GC.GetTotalMemory(false);
var totalMemoryGb = Math.Round((double)totalMemoryBytes / 1024 / 1024 / 1024, 2);
performance = new Performance
Performance performance = new();
try
{
Name = "RAM",
ValueTotal = totalMemoryGb,
ValueConsumed = totalMemoryGb,
PercentageConsumed = 100,
Unit = "GB"
};
}
catch (Exception e)
{
logger.LogError("Error check RAM performance: {eMessage} {eStackTrace}", e.Message, e.StackTrace);
var totalMemoryBytes = GC.GetTotalMemory(false);
var totalMemoryGb = Math.Round((double)totalMemoryBytes / 1024 / 1024 / 1024, 2);
performance = new Performance
{
Name = "RAM",
ValueTotal = totalMemoryGb,
ValueConsumed = totalMemoryGb,
PercentageConsumed = 100,
Unit = "GB"
};
}
catch (Exception e)
{
logger.LogError("Error check RAM performance: {eMessage} {eStackTrace}", e.Message, e.StackTrace);
}
return performance;
}
return performance;
}
/// <summary>
/// Calculates the consumed storage for the specified drive, returning total and consumed values in gigabytes along with the consumption percentage. If the drive's total size is zero, the percentage is not computed and the total remains zero. Any errors encountered during calculation are logged and an empty <see cref="Performance"/> object is returned.
/// </summary>
/// <param name="drive">The drive whose storage consumption is to be measured.</param>
/// <returns>A <see cref="Performance"/> instance containing the consumed storage, total storage, percentage consumed, and the unit (GB) for the drive.</returns>
public Performance GetConsumedStorage(DriveInfo drive)
{
Performance performance = new();
try
{
double percentage = 0;
//Total storage
double valueTotal = drive.TotalSize;
//Consumed storage
var value = valueTotal - drive.AvailableFreeSpace;
if (valueTotal != 0)
Performance performance = new();
try
{
percentage = Math.Round(value * 100 / valueTotal, 2); //%
valueTotal = Math.Round(valueTotal / 1024 / 1024 / 1024, 2); //Bytes -> GB
double percentage = 0;
//Total storage
double valueTotal = drive.TotalSize;
//Consumed storage
var value = valueTotal - drive.AvailableFreeSpace;
if (valueTotal != 0)
{
percentage = Math.Round(value * 100 / valueTotal, 2); //%
valueTotal = Math.Round(valueTotal / 1024 / 1024 / 1024, 2); //Bytes -> GB
}
value = Math.Round(value / 1024 / 1024 / 1024, 2); //Bytes -> GB
performance = new Performance
{
Name = "STORAGE " + drive.Name,
ValueConsumed = value,
ValueTotal = valueTotal,
PercentageConsumed = percentage,
Unit = "GB"
};
}
value = Math.Round(value / 1024 / 1024 / 1024, 2); //Bytes -> GB
performance = new Performance
catch (Exception e)
{
Name = "STORAGE " + drive.Name,
ValueConsumed = value,
ValueTotal = valueTotal,
PercentageConsumed = percentage,
Unit = "GB"
};
}
catch (Exception e)
{
logger.LogError("Error check Storage performance: {eMessage} {eStackTrace}", e.Message, e.StackTrace);
logger.LogError("Error check Storage performance: {eMessage} {eStackTrace}", e.Message, e.StackTrace);
}
return performance;
}
return performance;
}
/// <summary>
/// Retrieves the consumed storage performance information for all ready drives on the system,
/// skipping any drive that is not ready and logging errors encountered while processing
/// individual drives or the overall enumeration without halting the operation.
/// </summary>
/// <returns>A list of <see cref="Performance"/> entries describing the consumed storage for each
/// successfully processed drive; returns an empty list if no drives yield results or if an
/// error occurs during enumeration.</returns>
private List<Performance> GetConsumedStorages()
{
var list = new List<Performance>();
try
{
foreach (var drive in DriveInfo.GetDrives())
var list = new List<Performance>();
try
{
if (!drive.IsReady) continue;
try
foreach (var drive in DriveInfo.GetDrives())
{
var performance = GetConsumedStorage(drive);
list.Add(performance);
}
catch (Exception e)
{
logger.LogError("Error check Storage drive {drive} in performance: {eMessage}", drive.Name,
e.Message);
if (!drive.IsReady) continue;
try
{
var performance = GetConsumedStorage(drive);
list.Add(performance);
}
catch (Exception e)
{
logger.LogError("Error check Storage drive {drive} in performance: {eMessage}", drive.Name,
e.Message);
}
}
}
}
catch (Exception e)
{
logger.LogError("Error check Storages performance: {eMessage}", e.Message);
catch (Exception e)
{
logger.LogError("Error check Storages performance: {eMessage}", e.Message);
}
return list;
}
return list;
}
/// <summary>
/// Asynchronously retrieves the list of connected API clients (WebSocket clients) from hospitals.
/// Returns an empty list if the underlying call returns null or fails; any exception is caught and logged without being rethrown.
/// </summary>
/// <returns>A task that resolves to the list of connected API clients, which may be empty if no clients were retrieved.</returns>
private async Task<List<ApiClients>> GetApiClientsAsync()
{
logger.LogDebug("Starting check conected clients from hospitals");
var clientsList = new List<ApiClients>();
try
{
var clients = await GetWebSocketClients();
if (clients != null)
clientsList.Add(clients);
}
catch (Exception e)
{
logger.LogError("Error check error conected clients from hospitals: {eMessage} {eStackTrace}",
e.Message, e.StackTrace);
logger.LogDebug("Starting check conected clients from hospitals");
var clientsList = new List<ApiClients>();
try
{
var clients = await GetWebSocketClients();
if (clients != null)
clientsList.Add(clients);
}
catch (Exception e)
{
logger.LogError("Error check error conected clients from hospitals: {eMessage} {eStackTrace}",
e.Message, e.StackTrace);
}
return clientsList;
}
return clientsList;
}
/// <summary>
/// Asynchronously retrieves the list of connected WebSocket API clients, handling any errors by logging them and returning an empty <see cref="ApiClients"/> instance as a fallback.
/// </summary>
/// <returns>A <see cref="Task{TResult}"/> that resolves to an <see cref="ApiClients"/> instance representing the connected WebSocket subscribers.</returns>
private Task<ApiClients?> GetWebSocketClients()
{
ApiClients apiClients = new();
try
{
//TODO new ws apiClients = defaultWebSocketHandler.GetSubscribersConected();
//apiClients = webSocketHandler.GetSubscribersConected();
ApiClients apiClients = new();
try
{
//TODO new ws apiClients = defaultWebSocketHandler.GetSubscribersConected();
//apiClients = webSocketHandler.GetSubscribersConected();
}
catch (Exception e)
{
logger.LogError("Error check WebSocket Clients: {eMessage} {eStackTrace}", e.Message, e.StackTrace);
}
return Task.FromResult<ApiClients?>(apiClients);
}
catch (Exception e)
{
logger.LogError("Error check WebSocket Clients: {eMessage} {eStackTrace}", e.Message, e.StackTrace);
}
return Task.FromResult<ApiClients?>(apiClients);
}
}
@@ -8,6 +8,9 @@ using Microsoft.Extensions.Options;
namespace adas_core.Infrastructure.Utils
{
/// <summary>
/// Provides extension methods for configuring or building cache host instances.
/// </summary>
public static class CacheHostBuilderExtension
{
public static IHostBuilder UseCache(this IHostBuilder hostBuilder)
@@ -4,12 +4,23 @@ using Newtonsoft.Json.Linq;
namespace adas_core.Infrastructure.Utils;
/// <summary>
/// Provides a custom JSON conversion implementation for point of care data, extending the base <see cref="JsonConverter"/> functionality.
/// </summary>
/// <remarks>
/// This converter is designed to handle the serialization and deserialization logic specific to point of care entities, tailoring the behavior inherited from the <see cref="JsonConverter"/> base class.
/// </remarks>
public class CustomPointOfCareConverter : JsonConverter
{
/// <summary>
/// Determines whether the converter can convert the specified type. Returns <c>true</c> only when the supplied type is <see cref="PointOfCare"/>; otherwise, returns <c>false</c>.
/// </summary>
/// <param name="objectType">The type to evaluate for convertibility.</param>
/// <returns><c>true</c> if <paramref name="objectType"/> equals <see cref="PointOfCare"/>; otherwise, <c>false</c>.</returns>
public override bool CanConvert(Type objectType)
{
return objectType == typeof(PointOfCare);
}
{
return objectType == typeof(PointOfCare);
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
@@ -11,54 +11,75 @@ using MongoClient = MongoDB.Driver.MongoClient;
namespace adas_core.Infrastructure.Utils;
/// <summary>
/// Provides extension methods for configuring MongoDB integration on a host builder.
/// </summary>
public static class MongoDbHostBuilderExtension
{
/// <summary>
/// Configures the host builder with the MongoDB conventions and class map registrations required for the application.
/// </summary>
/// <param name="hostBuilder">The host builder to extend with the MongoDB configuration.</param>
/// <returns>The same <see cref="IHostBuilder"/> instance, allowing further fluent configuration.</returns>
public static IHostBuilder UseMongo(this IHostBuilder hostBuilder)
{
ConfigureMongoDbConventions();
ConfigureRegisterMapClass();
return hostBuilder;
}
{
ConfigureMongoDbConventions();
ConfigureRegisterMapClass();
return hostBuilder;
}
/// <summary>
/// Runs all pending MongoDB migrations found in the migrations assembly, updating the database schema to the latest version.
/// </summary>
/// <param name="host">The host whose services are used to resolve the <see cref="IMongoDatabase"/> instance.</param>
/// <returns>The same <see cref="IHost"/> instance, enabling fluent chaining.</returns>
public static IHost RunMongoMigrations(this IHost host)
{
using var scope = host.Services.CreateScope();
var database = scope.ServiceProvider.GetRequiredService<IMongoDatabase>();
var locator = new MigrationLocator();
locator.LookForMigrationsInAssembly(
typeof(adas_core.Infrastructure.Migrations.MongoMigrations.U_0_1_0_UpdateDataPatien).Assembly
);
var runner = new MigrationRunner(
database,
collectionName: "migrations",
migrationLocator: locator
);
runner.UpdateToLatest();
return host;
}
{
using var scope = host.Services.CreateScope();
var database = scope.ServiceProvider.GetRequiredService<IMongoDatabase>();
var locator = new MigrationLocator();
locator.LookForMigrationsInAssembly(
typeof(adas_core.Infrastructure.Migrations.MongoMigrations.U_0_1_0_UpdateDataPatien).Assembly
);
var runner = new MigrationRunner(
database,
collectionName: "migrations",
migrationLocator: locator
);
runner.UpdateToLatest();
return host;
}
/// <summary>
/// Configures and returns a MongoDB database instance using the provided connection settings.
/// Validates that the connection string and database name are provided, and verifies that the database exists.
/// </summary>
/// <param name="dbSettings">The database settings containing the MongoDB connection string and database name.</param>
/// <returns>The configured <see cref="IMongoDatabase"/> instance.</returns>
/// <exception cref="Exception">Thrown when the connection string or database name is null or empty.</exception>
/// <exception cref="Exception">Thrown when the specified database does not exist.</exception>
private static IMongoDatabase ConfigureMongoDbConnection(IOptions<DatabaseSettings> dbSettings)
{
var connectionString = dbSettings.Value.ConnectionString;
var databaseName = dbSettings.Value.DatabaseName;
if (string.IsNullOrEmpty(connectionString) || string.IsNullOrEmpty(databaseName))
throw new Exception("DataBase connection string and name is requiered");
_mongoClient = new MongoClient(connectionString);
var mongoDb = _mongoClient.GetDatabase(databaseName);
if (mongoDb == null) throw new Exception("DataBase doesn't exist");
return mongoDb;
}
{
var connectionString = dbSettings.Value.ConnectionString;
var databaseName = dbSettings.Value.DatabaseName;
if (string.IsNullOrEmpty(connectionString) || string.IsNullOrEmpty(databaseName))
throw new Exception("DataBase connection string and name is requiered");
_mongoClient = new MongoClient(connectionString);
var mongoDb = _mongoClient.GetDatabase(databaseName);
if (mongoDb == null) throw new Exception("DataBase doesn't exist");
return mongoDb;
}
public static void ConfigureRegisterMapClass()
{
@@ -77,24 +98,28 @@ public static class MongoDbHostBuilderExtension
}
}
/// <summary>
/// Registers global MongoDB serialization conventions, including ignoring extra elements in BSON documents
/// and using camelCase for element names, applied to all types in the application.
/// </summary>
public static void ConfigureMongoDbConventions()
{
var pack = new ConventionPack
{
new IgnoreExtraElementsConvention(true),
new CamelCaseElementNameConvention()
};
ConventionRegistry.Register(
"Ignore Extra Elements Convention",
pack,
_ => true);
ConventionRegistry.Register(
"Camel Case Convention",
pack,
_ => true);
}
var pack = new ConventionPack
{
new IgnoreExtraElementsConvention(true),
new CamelCaseElementNameConvention()
};
ConventionRegistry.Register(
"Ignore Extra Elements Convention",
pack,
_ => true);
ConventionRegistry.Register(
"Camel Case Convention",
pack,
_ => true);
}
#region MongoDB
@@ -9,125 +9,132 @@ using MongoDB.Bson.Serialization.Serializers;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents a contributor that provides entity mapping configuration for Alarm entities.
/// </summary>
/// <remarks>Implements the <see cref="IEntityMapContributor"/> interface to participate in the entity mapping process.</remarks>
public class AlarmMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for alarm-related domain types (including alarm configuration, audio, alarm items, patient observation alarms, inactivation states, sources, recording alerts, and performance), configuring enum serialization as strings, null-ignore behavior for optional members, default values where applicable, and unmapping members that should not be persisted. Registration is performed only when a class map for the type is not already registered, ensuring idempotent behavior.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(AlarmConfig)))
BsonClassMap.RegisterClassMap<AlarmConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.Enabled).SetDefaultValue(false);
cm.MapMember(c => c.EndAfter).SetIgnoreIfNull(true);
cm.MapMember(c => c.Recording).SetIgnoreIfNull(true);
cm.MapMember(c => c.Beacon).SetIgnoreIfNull(true);
cm.MapMember(c => c.OpenDoor).SetIgnoreIfNull(true);
cm.MapMember(c => c.Priority).SetIgnoreIfNull(true);
cm.MapMember(c => c.Color).SetIgnoreIfNull(true);
cm.MapMember(c => c.AudioConfig).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(AudioConfig)))
BsonClassMap.RegisterClassMap<AudioConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Type)
.SetDefaultValue(AlarmEnum.AudioAlarmType.Off)
.SetSerializer(new EnumSerializer<AlarmEnum.AudioAlarmType>(BsonType.String));
cm.MapMember(c => c.Path).SetIgnoreIfNull(true);
cm.MapMember(c => c.EndAfter).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(AlarmItem)))
BsonClassMap.RegisterClassMap<AlarmItem>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Enabled).SetDefaultValue(false);
cm.MapMember(c => c.StartBefore).SetIgnoreIfNull(true);
cm.MapMember(c => c.EndAfter).SetIgnoreIfNull(true);
cm.MapMember(c => c.Color).SetIgnoreIfNull(true);
cm.MapMember(c => c.Severity)
.SetDefaultValue(AlarmEnum.Severity.None)
.SetSerializer(new EnumSerializer<AlarmEnum.Severity>(BsonType.String));
cm.MapMember(c => c.BeaconColor)
.SetDefaultValue(AlarmEnum.BeaconColor.None)
.SetSerializer(new EnumSerializer<AlarmEnum.BeaconColor>(BsonType.String));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientObservationAlarm)))
{
BsonClassMap.RegisterClassMap<PatientObservationAlarm>(cm =>
if (!BsonClassMap.IsClassMapRegistered(typeof(AlarmConfig)))
BsonClassMap.RegisterClassMap<AlarmConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.Enabled).SetDefaultValue(false);
cm.MapMember(c => c.EndAfter).SetIgnoreIfNull(true);
cm.MapMember(c => c.Recording).SetIgnoreIfNull(true);
cm.MapMember(c => c.Beacon).SetIgnoreIfNull(true);
cm.MapMember(c => c.OpenDoor).SetIgnoreIfNull(true);
cm.MapMember(c => c.Priority).SetIgnoreIfNull(true);
cm.MapMember(c => c.Color).SetIgnoreIfNull(true);
cm.MapMember(c => c.AudioConfig).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(AudioConfig)))
BsonClassMap.RegisterClassMap<AudioConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Type)
.SetDefaultValue(AlarmEnum.AudioAlarmType.Off)
.SetSerializer(new EnumSerializer<AlarmEnum.AudioAlarmType>(BsonType.String));
cm.MapMember(c => c.Path).SetIgnoreIfNull(true);
cm.MapMember(c => c.EndAfter).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(AlarmItem)))
BsonClassMap.RegisterClassMap<AlarmItem>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Enabled).SetDefaultValue(false);
cm.MapMember(c => c.StartBefore).SetIgnoreIfNull(true);
cm.MapMember(c => c.EndAfter).SetIgnoreIfNull(true);
cm.MapMember(c => c.Color).SetIgnoreIfNull(true);
cm.MapMember(c => c.Severity)
.SetDefaultValue(AlarmEnum.Severity.None)
.SetSerializer(new EnumSerializer<AlarmEnum.Severity>(BsonType.String));
cm.MapMember(c => c.BeaconColor)
.SetDefaultValue(AlarmEnum.BeaconColor.None)
.SetSerializer(new EnumSerializer<AlarmEnum.BeaconColor>(BsonType.String));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientObservationAlarm)))
{
cm.AutoMap();
cm.MapMember(c => c.InactivationState).SetIgnoreIfNull(true);
cm.MapMember(c => c.EventPhase)
.SetDefaultValue(AlarmEnum.EventPhase.Continue)
.SetSerializer(new EnumSerializer<AlarmEnum.EventPhase>(BsonType.String));
cm.MapMember(c => c.Event).SetIgnoreIfNull(true);
cm.MapMember(c => c.EventId).SetIgnoreIfNull(true);
cm.MapMember(c => c.State)
.SetIgnoreIfNull(true)
.SetSerializer(new NullableSerializer<AlarmEnum.ObservationAlarmState>(
new EnumSerializer<AlarmEnum.ObservationAlarmState>(BsonType.String)));
cm.MapMember(c => c.Priority)
.SetIgnoreIfNull(true)
.SetSerializer(new NullableSerializer<AlarmEnum.ObservationAlarmPriority>(
new EnumSerializer<AlarmEnum.ObservationAlarmPriority>(BsonType.String)));
cm.MapMember(c => c.PriorityLevel)
.SetIgnoreIfNull(true)
.SetSerializer(new NullableSerializer<int>(new Int32Serializer()));
cm.UnmapMember(c => c.AlarmConfig);
cm.MapMember(c => c.Type)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<AlarmEnum.ObservationAlarmType>(
new EnumSerializer<AlarmEnum.ObservationAlarmType>(BsonType.String)));
cm.MapMember(c => c.AlertColor).SetIgnoreIfNull(true)
.SetSerializer(new StringSerializer(BsonType.String));
cm.UnmapMember(c => c.MessageTime);
cm.MapMember(c => c.Persist).SetIgnoreIfNull(true);
cm.UnmapMember(c => c.Expired);
cm.MapMember(c => c.Expires).SetIgnoreIfNull(true);
cm.MapMember(c => c.Sources).SetIgnoreIfNull(true);
});
BsonClassMap.RegisterClassMap<InactivationState>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Audio)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<AlarmEnum.AudioVideoState>(
new EnumSerializer<AlarmEnum.AudioVideoState>(BsonType.String)));
cm.MapMember(c => c.Visual)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<AlarmEnum.AudioVideoState>(
new EnumSerializer<AlarmEnum.AudioVideoState>(BsonType.String)));
cm.MapMember(c => c.Acknowledge).SetIgnoreIfNull(true);
});
BsonClassMap.RegisterClassMap<Source>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Code).SetIgnoreIfNull(true);
cm.MapMember(c => c.OriginalName).SetIgnoreIfNull(true);
cm.MapMember(c => c.CodeSystem).SetIgnoreIfNull(true);
cm.MapMember(c => c.Units).SetIgnoreIfNull(true);
cm.MapMember(c => c.Value).SetIgnoreIfNull(true);
cm.MapMember(c => c.Result).SetIgnoreIfNull(true);
});
BsonClassMap.RegisterClassMap<PatientObservationAlarm>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.InactivationState).SetIgnoreIfNull(true);
cm.MapMember(c => c.EventPhase)
.SetDefaultValue(AlarmEnum.EventPhase.Continue)
.SetSerializer(new EnumSerializer<AlarmEnum.EventPhase>(BsonType.String));
cm.MapMember(c => c.Event).SetIgnoreIfNull(true);
cm.MapMember(c => c.EventId).SetIgnoreIfNull(true);
cm.MapMember(c => c.State)
.SetIgnoreIfNull(true)
.SetSerializer(new NullableSerializer<AlarmEnum.ObservationAlarmState>(
new EnumSerializer<AlarmEnum.ObservationAlarmState>(BsonType.String)));
cm.MapMember(c => c.Priority)
.SetIgnoreIfNull(true)
.SetSerializer(new NullableSerializer<AlarmEnum.ObservationAlarmPriority>(
new EnumSerializer<AlarmEnum.ObservationAlarmPriority>(BsonType.String)));
cm.MapMember(c => c.PriorityLevel)
.SetIgnoreIfNull(true)
.SetSerializer(new NullableSerializer<int>(new Int32Serializer()));
cm.UnmapMember(c => c.AlarmConfig);
cm.MapMember(c => c.Type)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<AlarmEnum.ObservationAlarmType>(
new EnumSerializer<AlarmEnum.ObservationAlarmType>(BsonType.String)));
cm.MapMember(c => c.AlertColor).SetIgnoreIfNull(true)
.SetSerializer(new StringSerializer(BsonType.String));
cm.UnmapMember(c => c.MessageTime);
cm.MapMember(c => c.Persist).SetIgnoreIfNull(true);
cm.UnmapMember(c => c.Expired);
cm.MapMember(c => c.Expires).SetIgnoreIfNull(true);
cm.MapMember(c => c.Sources).SetIgnoreIfNull(true);
});
BsonClassMap.RegisterClassMap<InactivationState>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Audio)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<AlarmEnum.AudioVideoState>(
new EnumSerializer<AlarmEnum.AudioVideoState>(BsonType.String)));
cm.MapMember(c => c.Visual)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<AlarmEnum.AudioVideoState>(
new EnumSerializer<AlarmEnum.AudioVideoState>(BsonType.String)));
cm.MapMember(c => c.Acknowledge).SetIgnoreIfNull(true);
});
BsonClassMap.RegisterClassMap<Source>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Code).SetIgnoreIfNull(true);
cm.MapMember(c => c.OriginalName).SetIgnoreIfNull(true);
cm.MapMember(c => c.CodeSystem).SetIgnoreIfNull(true);
cm.MapMember(c => c.Units).SetIgnoreIfNull(true);
cm.MapMember(c => c.Value).SetIgnoreIfNull(true);
cm.MapMember(c => c.Result).SetIgnoreIfNull(true);
});
}
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientRecordingAlert)))
BsonClassMap.RegisterClassMap<PatientRecordingAlert>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.IsRecording);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Performance)))
BsonClassMap.RegisterClassMap<Performance>(cm => { cm.AutoMap(); });
}
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientRecordingAlert)))
BsonClassMap.RegisterClassMap<PatientRecordingAlert>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.IsRecording);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Performance)))
BsonClassMap.RegisterClassMap<Performance>(cm => { cm.AutoMap(); });
}
}
@@ -7,66 +7,72 @@ using MongoDB.Bson.Serialization.Serializers;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents a contributor that provides entity mapping configuration for the <see cref="PatientAppointment"/> type as part of the mapping framework.
/// </summary>
public class AppointmentMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for the <see cref="PatientAppointment"/>, <see cref="PatientAppointmentResourceGroup"/>, and <see cref="Allergies"/> types, customizing the MongoDB serialization for their members. Registration is performed only if a class map for the type has not already been registered, making the call safe to invoke multiple times. Optional members are configured to be ignored when null, enum members are serialized as nullable string enums, and certain computed/action members are unmapped or assigned default collection values.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientAppointment)))
BsonClassMap.RegisterClassMap<PatientAppointment>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.PatientId).SetSerializer(new ObjectIdSerializer(BsonType.String));
cm.MapMember(c => c.Patient).SetIgnoreIfNull(true);
cm.MapMember(c => c.Timings).SetDefaultValue(new List<Timing>());
cm.MapMember(c => c.CreateTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.UpdateTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.PlacerOrder).SetIgnoreIfNull(true);
cm.MapMember(c => c.FillerOrder).SetIgnoreIfNull(true);
cm.MapMember(c => c.EventReason).SetIgnoreIfNull(true);
cm.MapMember(c => c.AppointmentReason).SetIgnoreIfNull(true);
cm.MapMember(c => c.AppointmentType).SetIgnoreIfNull(true);
cm.MapMember(c => c.AppointmentOperationType)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<OperationType>(new EnumSerializer<OperationType>(BsonType.String)));
cm.MapMember(c => c.AppointmentStatus)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<OperationType>(new EnumSerializer<OperationType>(BsonType.String)));
cm.MapMember(c => c.Duration).SetIgnoreIfNull(true);
cm.MapMember(c => c.VisitNumber).SetIgnoreIfNull(true);
cm.MapMember(c => c.PatientClass).SetIgnoreIfNull(true);
cm.MapMember(c => c.EpisodeActivation).SetDefaultValue(true);
cm.MapMember(c => c.PlacerContact).SetIgnoreIfNull(true);
cm.MapMember(c => c.FillerContact).SetIgnoreIfNull(true);
cm.MapMember(c => c.ResourceGroups).SetDefaultValue(new List<PatientAppointmentResourceGroup>());
cm.MapMember(c => c.Allergies).SetDefaultValue(new List<Allergies>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientAppointmentResourceGroup)))
BsonClassMap.RegisterClassMap<PatientAppointmentResourceGroup>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Services).SetIgnoreIfNull(true);
cm.MapMember(c => c.Resources).SetIgnoreIfNull(true);
cm.MapMember(c => c.Locations).SetIgnoreIfNull(true);
cm.MapMember(c => c.Personnel).SetIgnoreIfNull(true);
cm.UnmapMember(c => c.ServicesActions);
cm.UnmapMember(c => c.ResourcesActions);
cm.UnmapMember(c => c.LocationsActions);
cm.UnmapMember(c => c.PersonnelActions);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Allergies)))
BsonClassMap.RegisterClassMap<Allergies>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.AllergenType).SetIgnoreIfNull(true);
cm.MapMember(c => c.Allergen).SetIgnoreIfNull(true);
});
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientAppointment)))
BsonClassMap.RegisterClassMap<PatientAppointment>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.PatientId).SetSerializer(new ObjectIdSerializer(BsonType.String));
cm.MapMember(c => c.Patient).SetIgnoreIfNull(true);
cm.MapMember(c => c.Timings).SetDefaultValue(new List<Timing>());
cm.MapMember(c => c.CreateTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.UpdateTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.PlacerOrder).SetIgnoreIfNull(true);
cm.MapMember(c => c.FillerOrder).SetIgnoreIfNull(true);
cm.MapMember(c => c.EventReason).SetIgnoreIfNull(true);
cm.MapMember(c => c.AppointmentReason).SetIgnoreIfNull(true);
cm.MapMember(c => c.AppointmentType).SetIgnoreIfNull(true);
cm.MapMember(c => c.AppointmentOperationType)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<OperationType>(new EnumSerializer<OperationType>(BsonType.String)));
cm.MapMember(c => c.AppointmentStatus)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<OperationType>(new EnumSerializer<OperationType>(BsonType.String)));
cm.MapMember(c => c.Duration).SetIgnoreIfNull(true);
cm.MapMember(c => c.VisitNumber).SetIgnoreIfNull(true);
cm.MapMember(c => c.PatientClass).SetIgnoreIfNull(true);
cm.MapMember(c => c.EpisodeActivation).SetDefaultValue(true);
cm.MapMember(c => c.PlacerContact).SetIgnoreIfNull(true);
cm.MapMember(c => c.FillerContact).SetIgnoreIfNull(true);
cm.MapMember(c => c.ResourceGroups).SetDefaultValue(new List<PatientAppointmentResourceGroup>());
cm.MapMember(c => c.Allergies).SetDefaultValue(new List<Allergies>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientAppointmentResourceGroup)))
BsonClassMap.RegisterClassMap<PatientAppointmentResourceGroup>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Services).SetIgnoreIfNull(true);
cm.MapMember(c => c.Resources).SetIgnoreIfNull(true);
cm.MapMember(c => c.Locations).SetIgnoreIfNull(true);
cm.MapMember(c => c.Personnel).SetIgnoreIfNull(true);
cm.UnmapMember(c => c.ServicesActions);
cm.UnmapMember(c => c.ResourcesActions);
cm.UnmapMember(c => c.LocationsActions);
cm.UnmapMember(c => c.PersonnelActions);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Allergies)))
BsonClassMap.RegisterClassMap<Allergies>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.AllergenType).SetIgnoreIfNull(true);
cm.MapMember(c => c.Allergen).SetIgnoreIfNull(true);
});
}
}
@@ -4,34 +4,40 @@ using MongoDB.Bson.Serialization;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents an entity map contributor that provides authentication-related mapping logic.
/// </summary>
public class AuthMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for the <see cref="User"/> and <see cref="Authorization"/> types when no class map has been registered yet, configuring field serialization rules such as ignoring null or empty values, mapping the identifier to the "_id" element name, and unmapping navigation properties not intended for persistence.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(User)))
BsonClassMap.RegisterClassMap<User>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Password)
.SetIgnoreIfNull(true)
.SetShouldSerializeMethod(obj => !string.IsNullOrEmpty(((User)obj).Password));
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.LockExpirationDate)
.SetIgnoreIfNull(true);
cm.MapMember(c => c.Authorization).SetElementName("Authorization")
.SetShouldSerializeMethod(_ => false);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Authorization)))
BsonClassMap.RegisterClassMap<Authorization>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.UnitId).SetIgnoreIfNull(true);
cm.UnmapProperty(c => c.User);
cm.UnmapProperty(c => c.Display);
cm.UnmapProperty(c => c.Unit);
});
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(User)))
BsonClassMap.RegisterClassMap<User>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Password)
.SetIgnoreIfNull(true)
.SetShouldSerializeMethod(obj => !string.IsNullOrEmpty(((User)obj).Password));
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.LockExpirationDate)
.SetIgnoreIfNull(true);
cm.MapMember(c => c.Authorization).SetElementName("Authorization")
.SetShouldSerializeMethod(_ => false);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Authorization)))
BsonClassMap.RegisterClassMap<Authorization>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.UnitId).SetIgnoreIfNull(true);
cm.UnmapProperty(c => c.User);
cm.UnmapProperty(c => c.Display);
cm.UnmapProperty(c => c.Unit);
});
}
}
@@ -7,56 +7,65 @@ using MongoDB.Bson.Serialization.Serializers;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Provides mapping contributions for the <c>BannerConfig</c> entity by implementing the <see cref="IEntityMapContributor"/> contract.
/// </summary>
/// <remarks>
/// Used as a contributor that participates in entity map configuration for banner-related data.
/// </remarks>
public class BannerConfigMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for banner-related configuration types, including <see cref="BannerItem"/>, <see cref="BannerItemConfig"/>, <see cref="BannerItemTableConfig"/>, and <see cref="HeaderBannerItemTableConfig"/>. Each map is only registered when no existing registration is found, and applies custom serialization (enum values stored as strings), null-ignoring behavior for optional members, and default values where appropriate.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(BannerItem)))
BsonClassMap.RegisterClassMap<BannerItem>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Type)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<DisplayConfigEnums.BannerType>(
new EnumSerializer<DisplayConfigEnums.BannerType>(BsonType.String)));
cm.MapMember(c => c.GrowPriority).SetDefaultValue(1);
cm.MapMember(c => c.Config).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(BannerItemConfig)))
BsonClassMap.RegisterClassMap<BannerItemConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.BannerItemDialogTableConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.BannerItemTableConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.MedicalStaffConfig).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(BannerItemTableConfig)))
BsonClassMap.RegisterClassMap<BannerItemTableConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Config).SetIgnoreIfNull(true);
cm.MapMember(c => c.BgColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.TextColor).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(HeaderBannerItemTableConfig)))
BsonClassMap.RegisterClassMap<HeaderBannerItemTableConfig>(cm =>
{
cm.MapMember(c => c.Type)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<DisplayConfigEnums.CellType>(
new EnumSerializer<DisplayConfigEnums.CellType>(BsonType.String)));
cm.MapMember(c => c.SubType).SetIgnoreIfNull(true);
cm.MapMember(c => c.Field).SetIgnoreIfNull(true);
cm.MapMember(c => c.GrowPriority).SetIgnoreIfNull(true);
cm.MapMember(c => c.Icon).SetIgnoreIfNull(true);
cm.MapMember(c => c.BgColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.TextColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.Title).SetIgnoreIfNull(true);
});
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(BannerItem)))
BsonClassMap.RegisterClassMap<BannerItem>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Type)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<DisplayConfigEnums.BannerType>(
new EnumSerializer<DisplayConfigEnums.BannerType>(BsonType.String)));
cm.MapMember(c => c.GrowPriority).SetDefaultValue(1);
cm.MapMember(c => c.Config).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(BannerItemConfig)))
BsonClassMap.RegisterClassMap<BannerItemConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.BannerItemDialogTableConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.BannerItemTableConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.MedicalStaffConfig).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(BannerItemTableConfig)))
BsonClassMap.RegisterClassMap<BannerItemTableConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Config).SetIgnoreIfNull(true);
cm.MapMember(c => c.BgColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.TextColor).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(HeaderBannerItemTableConfig)))
BsonClassMap.RegisterClassMap<HeaderBannerItemTableConfig>(cm =>
{
cm.MapMember(c => c.Type)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<DisplayConfigEnums.CellType>(
new EnumSerializer<DisplayConfigEnums.CellType>(BsonType.String)));
cm.MapMember(c => c.SubType).SetIgnoreIfNull(true);
cm.MapMember(c => c.Field).SetIgnoreIfNull(true);
cm.MapMember(c => c.GrowPriority).SetIgnoreIfNull(true);
cm.MapMember(c => c.Icon).SetIgnoreIfNull(true);
cm.MapMember(c => c.BgColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.TextColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.Title).SetIgnoreIfNull(true);
});
}
}
@@ -6,37 +6,48 @@ using LightBeacon = adas_core.Domain.Models.MongoModels.LightBeacon;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents a map contributor that participates in entity mapping operations
/// for beacon-related entities.
/// </summary>
/// <remarks>
/// Implements the <see cref="IEntityMapContributor"/> interface to contribute
/// beacon-specific mapping logic.
/// </remarks>
public class BeaconMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for <see cref="LightBeacon"/>, <see cref="Options"/>, and <see cref="LightBeaconAbstract"/> types to configure their MongoDB serialization. Each registration is skipped if a class map for the type has already been registered, applying default values, element name mappings, and ignore-if-null settings to the respective members.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(LightBeacon)))
BsonClassMap.RegisterClassMap<LightBeacon>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.Type).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Name).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Options).SetDefaultValue(new Options());
cm.MapMember(c => c.InUse).SetIgnoreIfNull(true).SetIsRequired(false);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Options)))
BsonClassMap.RegisterClassMap<Options>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Url).SetIgnoreIfNull(true);
cm.MapMember(c => c.Port).SetIgnoreIfNull(true);
cm.MapMember(c => c.Password).SetIgnoreIfNull(true);
cm.MapMember(c => c.Emulate).SetDefaultValue(false);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(LightBeaconAbstract)))
BsonClassMap.RegisterClassMap<LightBeaconAbstract>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Host);
cm.MapMember(c => c.Password);
});
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(LightBeacon)))
BsonClassMap.RegisterClassMap<LightBeacon>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.Type).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Name).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Options).SetDefaultValue(new Options());
cm.MapMember(c => c.InUse).SetIgnoreIfNull(true).SetIsRequired(false);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Options)))
BsonClassMap.RegisterClassMap<Options>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Url).SetIgnoreIfNull(true);
cm.MapMember(c => c.Port).SetIgnoreIfNull(true);
cm.MapMember(c => c.Password).SetIgnoreIfNull(true);
cm.MapMember(c => c.Emulate).SetDefaultValue(false);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(LightBeaconAbstract)))
BsonClassMap.RegisterClassMap<LightBeaconAbstract>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Host);
cm.MapMember(c => c.Password);
});
}
}
@@ -10,6 +10,9 @@ using Stream = adas_core.Domain.Models.MongoModels.Stream;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Provides configuration mapping contributions for box entities, implementing the <see cref="IEntityMapContributor"/> interface to participate in entity map setup.
/// </summary>
public class BoxConfigMapContributor : IEntityMapContributor
{
public void RegisterMaps()
@@ -5,35 +5,44 @@ using Stream = System.IO.Stream;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents a contributor that participates in entity mapping operations related to camera entities.
/// </summary>
/// <remarks>
/// Implements the <see cref="IEntityMapContributor"/> interface to provide camera-specific mapping behavior within the entity mapping pipeline.
/// </remarks>
public class CameraContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for the <see cref="Camera"/> and <see cref="Domain.Models.MongoModels.Stream"/> types with MongoDB, applying custom mapping conventions such as element name overrides, default values, and ignore-if-null behavior. Registration is performed only once; the method guards against duplicate registration by checking <see cref="BsonClassMap.IsClassMapRegistered(Type)"/> before registering.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(Camera)))
{
BsonClassMap.RegisterClassMap<Camera>(cm =>
if (!BsonClassMap.IsClassMapRegistered(typeof(Camera)))
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.Streams).SetIgnoreIfNull(true);
cm.MapMember(c => c.Name).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Ptz).SetDefaultValue(false);
cm.MapMember(c => c.Driver).SetIgnoreIfNull(true);
cm.MapMember(c => c.Ip).SetIgnoreIfNull(true);
cm.MapMember(c => c.Username).SetIgnoreIfNull(true);
cm.MapMember(c => c.Password).SetIgnoreIfNull(true);
cm.MapMember(c => c.InUse).SetIgnoreIfNull(true).SetIsRequired(false);
});
BsonClassMap.RegisterClassMap<Domain.Models.MongoModels.Stream>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Rtsp).SetIgnoreIfNull(true);
cm.MapMember(c => c.Jpeg).SetIgnoreIfNull(true);
cm.MapMember(c => c.WebRtc).SetIgnoreIfNull(true);
cm.MapMember(c => c.Hls).SetIgnoreIfNull(true);
cm.MapMember(c => c.Mp4).SetIgnoreIfNull(true);
});
BsonClassMap.RegisterClassMap<Camera>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.Streams).SetIgnoreIfNull(true);
cm.MapMember(c => c.Name).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Ptz).SetDefaultValue(false);
cm.MapMember(c => c.Driver).SetIgnoreIfNull(true);
cm.MapMember(c => c.Ip).SetIgnoreIfNull(true);
cm.MapMember(c => c.Username).SetIgnoreIfNull(true);
cm.MapMember(c => c.Password).SetIgnoreIfNull(true);
cm.MapMember(c => c.InUse).SetIgnoreIfNull(true).SetIsRequired(false);
});
BsonClassMap.RegisterClassMap<Domain.Models.MongoModels.Stream>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Rtsp).SetIgnoreIfNull(true);
cm.MapMember(c => c.Jpeg).SetIgnoreIfNull(true);
cm.MapMember(c => c.WebRtc).SetIgnoreIfNull(true);
cm.MapMember(c => c.Hls).SetIgnoreIfNull(true);
cm.MapMember(c => c.Mp4).SetIgnoreIfNull(true);
});
}
}
}
}
@@ -7,87 +7,93 @@ using MongoDB.Bson.Serialization.Serializers;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Provides card-specific mapping contributions by implementing the <see cref="IEntityMapContributor"/> contract within the entity mapping pipeline.
/// </summary>
public class CardMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for the layout and configuration types used by the display configuration domain, including <see cref="SectionBoxLayout"/>, <see cref="CardDetailsConfig"/>, <see cref="CardRotatingLayout"/>, <see cref="Step"/>, and <see cref="HomeConfig"/>. Each map is only registered when no existing map is present, and the configuration establishes default values, enum-as-string serialization, and ignore-if-null behavior for nullable layout properties.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(SectionBoxLayout)))
BsonClassMap.RegisterClassMap<SectionBoxLayout>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Type).SetDefaultValue(DisplayConfigEnums.RowType.Simple)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.RowType>(BsonType.String));
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.Subtitle).SetIgnoreIfNull(true);
cm.MapMember(c => c.Icon).SetIgnoreIfNull(true);
cm.MapMember(c => c.GridColumn).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingTop).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingBottom).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingLeft).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingRight).SetIgnoreIfNull(true);
cm.MapMember(c => c.BorderColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.BorderWidth).SetIgnoreIfNull(true);
cm.MapMember(c => c.BorderStyle).SetIgnoreIfNull(true);
cm.MapMember(c => c.BorderRadius).SetIgnoreIfNull(true);
cm.MapMember(c => c.HProportion).SetIgnoreIfNull(true);
cm.MapMember(c => c.BgColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.SectionUrl).SetIgnoreIfNull(true);
cm.MapMember(c => c.MinHeight).SetIgnoreIfNull(true);
cm.MapMember(c => c.Subtitle).SetIgnoreIfNull(true);
cm.MapMember(c => c.Conditions).SetIgnoreIfNull(true);
cm.MapMember(c => c.Direction).SetDefaultValue(DisplayConfigEnums.DirectionEnum.Row)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.DirectionEnum>(BsonType.String));
cm.MapMember(c => c.Rows).SetDefaultValue(new List<RowBoxLayout>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(CardDetailsConfig)))
BsonClassMap.RegisterClassMap<CardDetailsConfig>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.Header).SetIgnoreIfNull(true);
cm.MapMember(c => c.SmartSections).SetDefaultValue(new List<SectionBoxLayout>());
cm.MapMember(c => c.NurseRows).SetDefaultValue(new List<RowDetailsConfig>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(CardRotatingLayout)))
BsonClassMap.RegisterClassMap<CardRotatingLayout>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.MillisecondsBeforeRotating).SetDefaultValue(3000);
cm.MapMember(c => c.Order).SetIgnoreIfNull(true);
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.Title).SetIgnoreIfNull(true);
cm.MapMember(c => c.Data)
.SetIgnoreIfNull(true)
.SetIsRequired(false);
cm.MapMember(c => c.Type).SetDefaultValue(DisplayConfigEnums.RotatingLayoutType.HomeSection)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.RotatingLayoutType>(BsonType.String));
cm.MapMember(c => c.Mode).SetDefaultValue(DisplayConfigEnums.RotatingLayoutMode.Default)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.RotatingLayoutMode>(BsonType.String));
});
//standard card rotating layout
if (!BsonClassMap.IsClassMapRegistered(typeof(Step)))
BsonClassMap.RegisterClassMap<Step>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Type)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<DisplayConfigEnums.StepType>(
new EnumSerializer<DisplayConfigEnums.StepType>(BsonType.String)));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(HomeConfig)))
BsonClassMap.RegisterClassMap<HomeConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.MinColumnSize).SetDefaultValue("250");
cm.MapMember(c => c.ColumnsPerBreakpoint).SetDefaultValue(1);
cm.MapMember(c => c.BreakpointSize).SetDefaultValue("2000");
cm.MapMember(c => c.CardAspectRatioHeight).SetDefaultValue("700");
cm.MapMember(c => c.CardAspectRatioWidth).SetDefaultValue("500");
});
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(SectionBoxLayout)))
BsonClassMap.RegisterClassMap<SectionBoxLayout>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Type).SetDefaultValue(DisplayConfigEnums.RowType.Simple)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.RowType>(BsonType.String));
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.Subtitle).SetIgnoreIfNull(true);
cm.MapMember(c => c.Icon).SetIgnoreIfNull(true);
cm.MapMember(c => c.GridColumn).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingTop).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingBottom).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingLeft).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingRight).SetIgnoreIfNull(true);
cm.MapMember(c => c.BorderColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.BorderWidth).SetIgnoreIfNull(true);
cm.MapMember(c => c.BorderStyle).SetIgnoreIfNull(true);
cm.MapMember(c => c.BorderRadius).SetIgnoreIfNull(true);
cm.MapMember(c => c.HProportion).SetIgnoreIfNull(true);
cm.MapMember(c => c.BgColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.SectionUrl).SetIgnoreIfNull(true);
cm.MapMember(c => c.MinHeight).SetIgnoreIfNull(true);
cm.MapMember(c => c.Subtitle).SetIgnoreIfNull(true);
cm.MapMember(c => c.Conditions).SetIgnoreIfNull(true);
cm.MapMember(c => c.Direction).SetDefaultValue(DisplayConfigEnums.DirectionEnum.Row)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.DirectionEnum>(BsonType.String));
cm.MapMember(c => c.Rows).SetDefaultValue(new List<RowBoxLayout>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(CardDetailsConfig)))
BsonClassMap.RegisterClassMap<CardDetailsConfig>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.Header).SetIgnoreIfNull(true);
cm.MapMember(c => c.SmartSections).SetDefaultValue(new List<SectionBoxLayout>());
cm.MapMember(c => c.NurseRows).SetDefaultValue(new List<RowDetailsConfig>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(CardRotatingLayout)))
BsonClassMap.RegisterClassMap<CardRotatingLayout>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.MillisecondsBeforeRotating).SetDefaultValue(3000);
cm.MapMember(c => c.Order).SetIgnoreIfNull(true);
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.Title).SetIgnoreIfNull(true);
cm.MapMember(c => c.Data)
.SetIgnoreIfNull(true)
.SetIsRequired(false);
cm.MapMember(c => c.Type).SetDefaultValue(DisplayConfigEnums.RotatingLayoutType.HomeSection)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.RotatingLayoutType>(BsonType.String));
cm.MapMember(c => c.Mode).SetDefaultValue(DisplayConfigEnums.RotatingLayoutMode.Default)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.RotatingLayoutMode>(BsonType.String));
});
//standard card rotating layout
if (!BsonClassMap.IsClassMapRegistered(typeof(Step)))
BsonClassMap.RegisterClassMap<Step>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Type)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<DisplayConfigEnums.StepType>(
new EnumSerializer<DisplayConfigEnums.StepType>(BsonType.String)));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(HomeConfig)))
BsonClassMap.RegisterClassMap<HomeConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.MinColumnSize).SetDefaultValue("250");
cm.MapMember(c => c.ColumnsPerBreakpoint).SetDefaultValue(1);
cm.MapMember(c => c.BreakpointSize).SetDefaultValue("2000");
cm.MapMember(c => c.CardAspectRatioHeight).SetDefaultValue("700");
cm.MapMember(c => c.CardAspectRatioWidth).SetDefaultValue("500");
});
}
}
@@ -7,123 +7,129 @@ using MongoDB.Bson.Serialization.Serializers;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents a contributor that participates in populating a cell-based map by implementing the entity map contribution contract.
/// </summary>
public class CellMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for <see cref="Cell"/>, <see cref="CellDetails"/>, <see cref="IconValueList"/>, <see cref="DialogConfig"/>, and <see cref="MedicalConfig"/> when they have not already been registered. Each registration configures default values, ignore-if-null behavior, and enum serialization for the corresponding type's members to control how instances are persisted in MongoDB.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(Cell)))
BsonClassMap.RegisterClassMap<Cell>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Type)
.SetDefaultValue(DisplayConfigEnums.CellType.Default)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.CellType>(BsonType.String));
cm.MapMember(c => c.ChartSettings).SetIgnoreIfNull(true);
cm.MapMember(c => c.GrowPriority).SetDefaultValue(1.0);
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.IconValueList)
.SetIgnoreIfNull(true);
cm.MapMember(c => c.Border).SetIgnoreIfNull(true);
cm.MapMember(c => c.BorderRadius).SetIgnoreIfNull(true);
cm.MapMember(c => c.Direction)
.SetIgnoreIfNull(true)
.SetSerializer(new NullableSerializer<DisplayConfigEnums.DirectionEnum>(
new EnumSerializer<DisplayConfigEnums.DirectionEnum>(BsonType.String)));
cm.MapMember(c => c.SubType).SetIgnoreIfNull(true);
cm.MapMember(c => c.Size).SetIgnoreIfNull(true);
cm.MapMember(c => c.HideName).SetIgnoreIfNull(true);
cm.MapMember(c => c.IsVisible).SetDefaultValue(true);
cm.MapMember(c => c.IsColumn).SetIgnoreIfNull(true);
cm.MapMember(c => c.BgColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.Title).SetIgnoreIfNull(true);
cm.MapMember(c => c.ValuePathKey).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingTop).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingBottom).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingLeft).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingRight).SetIgnoreIfNull(true);
cm.MapMember(c => c.Color).SetIgnoreIfNull(true);
cm.MapMember(c => c.BackgroundColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.ShowIcon).SetIgnoreIfNull(true);
cm.MapMember(c => c.FlexBasis).SetIgnoreIfNull(true);
cm.MapMember(c => c.Grow).SetIgnoreIfNull(true);
cm.MapMember(c => c.Shrink).SetIgnoreIfNull(true);
cm.MapMember(c => c.ShowTitle).SetIgnoreIfNull(true);
cm.MapMember(c => c.IndicatorHorizontal).SetIgnoreIfNull(true);
cm.MapMember(c => c.DialogConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.ShowIndicator).SetIgnoreIfNull(true);
cm.MapMember(c => c.OnlyNumber).SetIgnoreIfNull(true);
cm.MapMember(c => c.ShowArrow).SetIgnoreIfNull(true);
cm.MapMember(c => c.GraphConf).SetIgnoreIfNull(true);
cm.MapMember(c => c.Names).SetIgnoreIfNull(true);
cm.MapMember(c => c.ObservationName).SetIgnoreIfNull(true);
cm.MapMember(c => c.ObservationTitle).SetIgnoreIfNull(true);
cm.MapMember(c => c.GraphConf).SetIgnoreIfNull(true);
cm.MapMember(c => c.ValuePathNested).SetIgnoreIfNull(true);
cm.MapMember(c => c.SubObs).SetIgnoreIfNull(true);
cm.MapMember(c => c.TextRules).SetIgnoreIfNull(true);
cm.MapMember(c => c.IsStatic).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(CellDetails)))
BsonClassMap.RegisterClassMap<CellDetails>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.TextRules).SetIgnoreIfNull(true);
cm.MapMember(c => c.Type)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.CellType>(BsonType.String))
.SetDefaultValue(DisplayConfigEnums.CellType.Default);
cm.MapMember(c => c.ChartSettings).SetIgnoreIfNull(true);
cm.MapMember(c => c.SubType).SetDefaultValue(string.Empty);
cm.MapMember(c => c.GrowPriority).SetDefaultValue(1.0);
cm.MapMember(c => c.Cells).SetDefaultValue(new List<CellDetails>());
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.Title).SetIgnoreIfNull(true);
cm.MapMember(c => c.IsVisible).SetDefaultValue(true);
cm.MapMember(c => c.ValuePathNested).SetIgnoreIfNull(true);
cm.MapMember(c => c.ValuePathKey).SetIgnoreIfNull(true);
cm.MapMember(c => c.IconValueList).SetIgnoreIfNull(true);
cm.MapMember(c => c.ObservationName).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingTop).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingBottom).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingLeft).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingRight).SetIgnoreIfNull(true);
cm.MapMember(c => c.Color).SetIgnoreIfNull(true);
cm.MapMember(c => c.BackgroundColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.ShowIcon).SetIgnoreIfNull(true);
cm.MapMember(c => c.FlexBasis).SetIgnoreIfNull(true);
cm.MapMember(c => c.FlexDirection).SetIgnoreIfNull(true);
cm.MapMember(c => c.Grow).SetIgnoreIfNull(true);
cm.MapMember(c => c.Shrink).SetIgnoreIfNull(true);
cm.MapMember(c => c.ShowTitle).SetIgnoreIfNull(true);
cm.MapMember(c => c.DialogConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.IsStatic).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(IconValueList)))
BsonClassMap.RegisterClassMap<IconValueList>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.MinValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.MaxValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.Width).SetIgnoreIfNull(true);
cm.MapMember(c => c.Height).SetIgnoreIfNull(true);
cm.MapMember(c => c.IconList).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(DialogConfig)))
BsonClassMap.RegisterClassMap<DialogConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.IsDraggable).SetDefaultValue(false);
cm.MapMember(c => c.MedicalConfig).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(MedicalConfig)))
BsonClassMap.RegisterClassMap<MedicalConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.HasFinalizeTime).SetDefaultValue(true);
cm.MapMember(c => c.HasStartTime).SetDefaultValue(true);
});
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(Cell)))
BsonClassMap.RegisterClassMap<Cell>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Type)
.SetDefaultValue(DisplayConfigEnums.CellType.Default)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.CellType>(BsonType.String));
cm.MapMember(c => c.ChartSettings).SetIgnoreIfNull(true);
cm.MapMember(c => c.GrowPriority).SetDefaultValue(1.0);
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.IconValueList)
.SetIgnoreIfNull(true);
cm.MapMember(c => c.Border).SetIgnoreIfNull(true);
cm.MapMember(c => c.BorderRadius).SetIgnoreIfNull(true);
cm.MapMember(c => c.Direction)
.SetIgnoreIfNull(true)
.SetSerializer(new NullableSerializer<DisplayConfigEnums.DirectionEnum>(
new EnumSerializer<DisplayConfigEnums.DirectionEnum>(BsonType.String)));
cm.MapMember(c => c.SubType).SetIgnoreIfNull(true);
cm.MapMember(c => c.Size).SetIgnoreIfNull(true);
cm.MapMember(c => c.HideName).SetIgnoreIfNull(true);
cm.MapMember(c => c.IsVisible).SetDefaultValue(true);
cm.MapMember(c => c.IsColumn).SetIgnoreIfNull(true);
cm.MapMember(c => c.BgColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.Title).SetIgnoreIfNull(true);
cm.MapMember(c => c.ValuePathKey).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingTop).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingBottom).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingLeft).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingRight).SetIgnoreIfNull(true);
cm.MapMember(c => c.Color).SetIgnoreIfNull(true);
cm.MapMember(c => c.BackgroundColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.ShowIcon).SetIgnoreIfNull(true);
cm.MapMember(c => c.FlexBasis).SetIgnoreIfNull(true);
cm.MapMember(c => c.Grow).SetIgnoreIfNull(true);
cm.MapMember(c => c.Shrink).SetIgnoreIfNull(true);
cm.MapMember(c => c.ShowTitle).SetIgnoreIfNull(true);
cm.MapMember(c => c.IndicatorHorizontal).SetIgnoreIfNull(true);
cm.MapMember(c => c.DialogConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.ShowIndicator).SetIgnoreIfNull(true);
cm.MapMember(c => c.OnlyNumber).SetIgnoreIfNull(true);
cm.MapMember(c => c.ShowArrow).SetIgnoreIfNull(true);
cm.MapMember(c => c.GraphConf).SetIgnoreIfNull(true);
cm.MapMember(c => c.Names).SetIgnoreIfNull(true);
cm.MapMember(c => c.ObservationName).SetIgnoreIfNull(true);
cm.MapMember(c => c.ObservationTitle).SetIgnoreIfNull(true);
cm.MapMember(c => c.GraphConf).SetIgnoreIfNull(true);
cm.MapMember(c => c.ValuePathNested).SetIgnoreIfNull(true);
cm.MapMember(c => c.SubObs).SetIgnoreIfNull(true);
cm.MapMember(c => c.TextRules).SetIgnoreIfNull(true);
cm.MapMember(c => c.IsStatic).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(CellDetails)))
BsonClassMap.RegisterClassMap<CellDetails>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.TextRules).SetIgnoreIfNull(true);
cm.MapMember(c => c.Type)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.CellType>(BsonType.String))
.SetDefaultValue(DisplayConfigEnums.CellType.Default);
cm.MapMember(c => c.ChartSettings).SetIgnoreIfNull(true);
cm.MapMember(c => c.SubType).SetDefaultValue(string.Empty);
cm.MapMember(c => c.GrowPriority).SetDefaultValue(1.0);
cm.MapMember(c => c.Cells).SetDefaultValue(new List<CellDetails>());
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.Title).SetIgnoreIfNull(true);
cm.MapMember(c => c.IsVisible).SetDefaultValue(true);
cm.MapMember(c => c.ValuePathNested).SetIgnoreIfNull(true);
cm.MapMember(c => c.ValuePathKey).SetIgnoreIfNull(true);
cm.MapMember(c => c.IconValueList).SetIgnoreIfNull(true);
cm.MapMember(c => c.ObservationName).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingTop).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingBottom).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingLeft).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingRight).SetIgnoreIfNull(true);
cm.MapMember(c => c.Color).SetIgnoreIfNull(true);
cm.MapMember(c => c.BackgroundColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.ShowIcon).SetIgnoreIfNull(true);
cm.MapMember(c => c.FlexBasis).SetIgnoreIfNull(true);
cm.MapMember(c => c.FlexDirection).SetIgnoreIfNull(true);
cm.MapMember(c => c.Grow).SetIgnoreIfNull(true);
cm.MapMember(c => c.Shrink).SetIgnoreIfNull(true);
cm.MapMember(c => c.ShowTitle).SetIgnoreIfNull(true);
cm.MapMember(c => c.DialogConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.IsStatic).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(IconValueList)))
BsonClassMap.RegisterClassMap<IconValueList>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.MinValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.MaxValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.Width).SetIgnoreIfNull(true);
cm.MapMember(c => c.Height).SetIgnoreIfNull(true);
cm.MapMember(c => c.IconList).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(DialogConfig)))
BsonClassMap.RegisterClassMap<DialogConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.IsDraggable).SetDefaultValue(false);
cm.MapMember(c => c.MedicalConfig).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(MedicalConfig)))
BsonClassMap.RegisterClassMap<MedicalConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.HasFinalizeTime).SetDefaultValue(true);
cm.MapMember(c => c.HasStartTime).SetDefaultValue(true);
});
}
}
@@ -4,331 +4,342 @@ using MongoDB.Bson.Serialization;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents a contributor that supplies color configuration mappings for entities
/// by implementing the <see cref="IEntityMapContributor"/> interface.
/// </summary>
/// <remarks>
/// Used as a pluggable component within the entity mapping pipeline to apply
/// color-related configuration to mapped entities.
/// </remarks>
public class ColorConfigMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for the <see cref="ColorConfig"/> type and its nested configuration types, providing default color and appearance values for properties such as levels, text, arrows, indicators, graphs, status boxes, therapy, tests, and procedures. Each class map is registered only if it has not already been registered, ensuring idempotent initialization of the MongoDB serialization mappings used by the color configuration system.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig)))
BsonClassMap.RegisterClassMap<ColorConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Level).SetDefaultValue(new ColorConfig.LevelColors
{
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig)))
BsonClassMap.RegisterClassMap<ColorConfig>(cm =>
{
Level1 = "#FFFFFF",
Level2 = "#FAFF41",
Level3 = "#F5A623",
Level4 = "#C2510F",
Level5 = "#FF5D6A"
}).SetIgnoreIfNull(true);
cm.MapMember(c => c.Text).SetDefaultValue(new ColorConfig.TextColors
cm.AutoMap();
cm.MapMember(c => c.Level).SetDefaultValue(new ColorConfig.LevelColors
{
Level1 = "#FFFFFF",
Level2 = "#FAFF41",
Level3 = "#F5A623",
Level4 = "#C2510F",
Level5 = "#FF5D6A"
}).SetIgnoreIfNull(true);
cm.MapMember(c => c.Text).SetDefaultValue(new ColorConfig.TextColors
{
Normal = "#FFFFFF",
Warning = "#FFAD26",
Alert = "#FF5D6A",
Improve = "#60D61D",
Expired = "#333333"
}).SetIgnoreIfNull(true);
cm.MapMember(c => c.Arrow).SetDefaultValue(new ColorConfig.ArrowColors
{
Normal = "#FFFFFF",
Warning = "#F5A623",
Alert = "#FF5D6A",
Improve = "#60D61D"
}).SetIgnoreIfNull(true);
cm.MapMember(c => c.Indicator).SetDefaultValue(new ColorConfig.IndicatorColors
{
Empty = "#CCCCCC",
Warning = "#FFAD26",
Normal = "#60D61D",
Alert = "#FF5D6A",
Background = "#000000",
EmptyBackground = "#CCCCCC"
}).SetIgnoreIfNull(true);
cm.MapMember(c => c.Graph).SetDefaultValue(new ColorConfig.GraphColors
{
Alert = "#FF5D6A",
Warning = "#FFAD26",
Normal = "#60D61D"
}).SetIgnoreIfNull(true);
cm.MapMember(c => c.BoxNumber).SetDefaultValue(new ColorConfig.StatusBoxNumberColors
{
Reserved =
new ColorConfig.AppearanceSettings { BackgroundColor = "#FFF84C", TextColor = "#000000" },
InUse = new ColorConfig.AppearanceSettings { BackgroundColor = "#FFF84C", TextColor = "#000000" },
Available = new ColorConfig.AppearanceSettings
{ BackgroundColor = "#FFF84C", TextColor = "#000000" },
Locked = new ColorConfig.AppearanceSettings { BackgroundColor = "#FFF84C", TextColor = "#000000" },
Transferable = new ColorConfig.AppearanceSettings
{ BackgroundColor = "#FFF84C", TextColor = "#000000" },
Exitus = new ColorConfig.AppearanceSettings { BackgroundColor = "#FFF84C", TextColor = "#000000" },
Altable = new ColorConfig.AppearanceSettings { BackgroundColor = "#FFF84C", TextColor = "#000000" }
}).SetIgnoreIfNull(true);
cm.MapMember(c => c.BoxStatusColor).SetDefaultValue(new ColorConfig.StatusBoxNumberColors
{
Reserved =
new ColorConfig.AppearanceSettings { BackgroundColor = "#FFF84C", TextColor = "#000000" },
InUse = new ColorConfig.AppearanceSettings { BackgroundColor = "#FFF84C", TextColor = "#000000" },
Available = new ColorConfig.AppearanceSettings
{ BackgroundColor = "#FFF84C", TextColor = "#000000" },
Locked = new ColorConfig.AppearanceSettings { BackgroundColor = "#FFF84C", TextColor = "#000000" },
Transferable = new ColorConfig.AppearanceSettings
{ BackgroundColor = "#FFF84C", TextColor = "#000000" },
Exitus = new ColorConfig.AppearanceSettings { BackgroundColor = "#FFF84C", TextColor = "#000000" },
Altable = new ColorConfig.AppearanceSettings { BackgroundColor = "#FFF84C", TextColor = "#000000" }
}).SetIgnoreIfNull(true);
cm.MapMember(c => c.Therapy).SetDefaultValue(new ColorConfig.TherapyColors
{
Default = new ColorConfig.AppearanceSettings(),
Finished = new ColorConfig.AppearanceSettings(),
Initialized = new ColorConfig.AppearanceSettings(),
InProgress = new ColorConfig.AppearanceSettings()
}).SetIgnoreIfNull(true);
cm.MapMember(c => c.Test).SetDefaultValue(new ColorConfig.TestColors
{
Default = new ColorConfig.AppearanceSettings
{
BackgroundColor = "#00A4E1",
TextColor = "#FFF"
},
Finished = new ColorConfig.AppearanceSettings
{
BackgroundColor = "#00C49B",
TextColor = "#FFF",
Icon = "assets/icon/light-theme/ic-tick.svg",
IconDefault = "icTick"
},
Initialized = new ColorConfig.AppearanceSettings
{
BackgroundColor = "#ED4965",
TextColor = "#FFF"
},
Expired = new ColorConfig.AppearanceSettings
{
BackgroundColor = "#ED4965",
TextColor = "#FFF",
Icon = "assets/icon/light-theme/ic-clock.svg",
IconDefault = "icClock"
}
}).SetIgnoreIfNull(true);
cm.MapMember(c => c.Procedure).SetDefaultValue(new ColorConfig.ProcedureColors
{
Default = new ColorConfig.AppearanceSettings
{
BackgroundColor = "#00A4E1",
TextColor = "#FFF"
},
Finished = new ColorConfig.AppearanceSettings
{
BackgroundColor = "#00C49B",
TextColor = "#FFF",
Icon = "assets/icon/light-theme/ic-tick.svg",
IconDefault = "icTick"
},
Initialized = new ColorConfig.AppearanceSettings
{
BackgroundColor = "#ED4965",
TextColor = "#FFF"
},
Expired = new ColorConfig.AppearanceSettings
{
BackgroundColor = "#ED4965",
TextColor = "#FFF",
Icon = "assets/icon/light-theme/ic-clock.svg",
IconDefault = "icClock"
}
}).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig.StatusBoxNumberColors)))
BsonClassMap.RegisterClassMap<ColorConfig.StatusBoxNumberColors>(cm =>
{
Normal = "#FFFFFF",
Warning = "#FFAD26",
Alert = "#FF5D6A",
Improve = "#60D61D",
Expired = "#333333"
}).SetIgnoreIfNull(true);
cm.MapMember(c => c.Arrow).SetDefaultValue(new ColorConfig.ArrowColors
cm.AutoMap();
cm.MapMember(c => c.Reserved).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#FFF84C",
TextColor = "#000000"
});
cm.MapMember(c => c.InUse).SetDefaultValue(new ColorConfig.AppearanceSettings());
cm.MapMember(c => c.Available).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#57B812",
TextColor = "#000000"
});
cm.MapMember(c => c.Locked).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#ED4965",
TextColor = "#000000"
});
cm.MapMember(c => c.Transferable).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#57B812",
TextColor = "#000000"
});
cm.MapMember(c => c.Exitus).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#24BFF9",
TextColor = "#000000"
});
cm.MapMember(c => c.Altable).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#24BFF9",
TextColor = "#000000"
});
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig.TherapyColors)))
BsonClassMap.RegisterClassMap<ColorConfig.TherapyColors>(cm =>
{
Normal = "#FFFFFF",
Warning = "#F5A623",
Alert = "#FF5D6A",
Improve = "#60D61D"
}).SetIgnoreIfNull(true);
cm.MapMember(c => c.Indicator).SetDefaultValue(new ColorConfig.IndicatorColors
cm.AutoMap();
cm.MapMember(c => c.Default).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#00A4E1",
TextColor = "#FFF",
IconInvertColor = 1
});
cm.MapMember(c => c.Initialized).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#FFF",
TextColor = "#000"
});
cm.MapMember(c => c.Finished).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#FFF",
TextColor = "#000",
Icon = "assets/icon/light-theme/ic-tick.svg",
IconDefault = "icTick"
});
cm.MapMember(c => c.InProgress).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#FFF",
TextColor = "#000"
});
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig.AppearanceSettings)))
BsonClassMap.RegisterClassMap<ColorConfig.AppearanceSettings>(cm =>
{
Empty = "#CCCCCC",
Warning = "#FFAD26",
Normal = "#60D61D",
Alert = "#FF5D6A",
Background = "#000000",
EmptyBackground = "#CCCCCC"
}).SetIgnoreIfNull(true);
cm.MapMember(c => c.Graph).SetDefaultValue(new ColorConfig.GraphColors
cm.AutoMap();
cm.MapMember(c => c.TextColor).SetDefaultValue("#000000");
cm.MapMember(c => c.BackgroundColor).SetDefaultValue("#FFFFFF");
cm.MapMember(c => c.Icon).SetIgnoreIfNull(true);
cm.MapMember(c => c.IconDefault).SetIgnoreIfNull(true);
cm.MapMember(c => c.IconColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.IconCategory).SetIgnoreIfNull(true);
cm.MapMember(c => c.IconInvertColor).SetDefaultValue(0.0);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig.TestColors)))
BsonClassMap.RegisterClassMap<ColorConfig.TestColors>(cm =>
{
Alert = "#FF5D6A",
Warning = "#FFAD26",
Normal = "#60D61D"
}).SetIgnoreIfNull(true);
cm.MapMember(c => c.BoxNumber).SetDefaultValue(new ColorConfig.StatusBoxNumberColors
{
Reserved =
new ColorConfig.AppearanceSettings { BackgroundColor = "#FFF84C", TextColor = "#000000" },
InUse = new ColorConfig.AppearanceSettings { BackgroundColor = "#FFF84C", TextColor = "#000000" },
Available = new ColorConfig.AppearanceSettings
{ BackgroundColor = "#FFF84C", TextColor = "#000000" },
Locked = new ColorConfig.AppearanceSettings { BackgroundColor = "#FFF84C", TextColor = "#000000" },
Transferable = new ColorConfig.AppearanceSettings
{ BackgroundColor = "#FFF84C", TextColor = "#000000" },
Exitus = new ColorConfig.AppearanceSettings { BackgroundColor = "#FFF84C", TextColor = "#000000" },
Altable = new ColorConfig.AppearanceSettings { BackgroundColor = "#FFF84C", TextColor = "#000000" }
}).SetIgnoreIfNull(true);
cm.MapMember(c => c.BoxStatusColor).SetDefaultValue(new ColorConfig.StatusBoxNumberColors
{
Reserved =
new ColorConfig.AppearanceSettings { BackgroundColor = "#FFF84C", TextColor = "#000000" },
InUse = new ColorConfig.AppearanceSettings { BackgroundColor = "#FFF84C", TextColor = "#000000" },
Available = new ColorConfig.AppearanceSettings
{ BackgroundColor = "#FFF84C", TextColor = "#000000" },
Locked = new ColorConfig.AppearanceSettings { BackgroundColor = "#FFF84C", TextColor = "#000000" },
Transferable = new ColorConfig.AppearanceSettings
{ BackgroundColor = "#FFF84C", TextColor = "#000000" },
Exitus = new ColorConfig.AppearanceSettings { BackgroundColor = "#FFF84C", TextColor = "#000000" },
Altable = new ColorConfig.AppearanceSettings { BackgroundColor = "#FFF84C", TextColor = "#000000" }
}).SetIgnoreIfNull(true);
cm.MapMember(c => c.Therapy).SetDefaultValue(new ColorConfig.TherapyColors
{
Default = new ColorConfig.AppearanceSettings(),
Finished = new ColorConfig.AppearanceSettings(),
Initialized = new ColorConfig.AppearanceSettings(),
InProgress = new ColorConfig.AppearanceSettings()
}).SetIgnoreIfNull(true);
cm.MapMember(c => c.Test).SetDefaultValue(new ColorConfig.TestColors
{
Default = new ColorConfig.AppearanceSettings
cm.AutoMap();
cm.MapMember(c => c.Default).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#00A4E1",
TextColor = "#FFF"
},
Finished = new ColorConfig.AppearanceSettings
});
cm.MapMember(c => c.Initialized).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#ED4965",
TextColor = "#FFF"
});
cm.MapMember(c => c.Finished).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#00C49B",
TextColor = "#FFF",
Icon = "assets/icon/light-theme/ic-tick.svg",
IconDefault = "icTick"
},
Initialized = new ColorConfig.AppearanceSettings
{
BackgroundColor = "#ED4965",
TextColor = "#FFF"
},
Expired = new ColorConfig.AppearanceSettings
});
cm.MapMember(c => c.Expired).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#ED4965",
TextColor = "#FFF",
Icon = "assets/icon/light-theme/ic-clock.svg",
IconDefault = "icClock"
}
}).SetIgnoreIfNull(true);
cm.MapMember(c => c.Procedure).SetDefaultValue(new ColorConfig.ProcedureColors
});
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig.ProcedureColors)))
BsonClassMap.RegisterClassMap<ColorConfig.ProcedureColors>(cm =>
{
Default = new ColorConfig.AppearanceSettings
cm.AutoMap();
cm.MapMember(c => c.Default).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#00A4E1",
TextColor = "#FFF"
},
Finished = new ColorConfig.AppearanceSettings
});
cm.MapMember(c => c.Initialized).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#ED4965",
TextColor = "#FFF"
});
cm.MapMember(c => c.Finished).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#00C49B",
TextColor = "#FFF",
Icon = "assets/icon/light-theme/ic-tick.svg",
IconDefault = "icTick"
},
Initialized = new ColorConfig.AppearanceSettings
{
BackgroundColor = "#ED4965",
TextColor = "#FFF"
},
Expired = new ColorConfig.AppearanceSettings
});
cm.MapMember(c => c.Expired).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#ED4965",
TextColor = "#FFF",
Icon = "assets/icon/light-theme/ic-clock.svg",
IconDefault = "icClock"
}
}).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig.StatusBoxNumberColors)))
BsonClassMap.RegisterClassMap<ColorConfig.StatusBoxNumberColors>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Reserved).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#FFF84C",
TextColor = "#000000"
});
});
cm.MapMember(c => c.InUse).SetDefaultValue(new ColorConfig.AppearanceSettings());
cm.MapMember(c => c.Available).SetDefaultValue(new ColorConfig.AppearanceSettings
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig.LevelColors)))
BsonClassMap.RegisterClassMap<ColorConfig.LevelColors>(cm =>
{
BackgroundColor = "#57B812",
TextColor = "#000000"
cm.AutoMap();
cm.MapMember(c => c.Level1).SetDefaultValue("#FFFFFF");
cm.MapMember(c => c.Level2).SetDefaultValue("#FAFF41");
cm.MapMember(c => c.Level3).SetDefaultValue("#F5A623");
cm.MapMember(c => c.Level4).SetDefaultValue("#C2510F");
cm.MapMember(c => c.Level5).SetDefaultValue("#FF5D6A");
});
cm.MapMember(c => c.Locked).SetDefaultValue(new ColorConfig.AppearanceSettings
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig.TextColors)))
BsonClassMap.RegisterClassMap<ColorConfig.TextColors>(cm =>
{
BackgroundColor = "#ED4965",
TextColor = "#000000"
cm.AutoMap();
cm.MapMember(c => c.Normal).SetDefaultValue("#FFFFFF");
cm.MapMember(c => c.Warning).SetDefaultValue("#FFAD26");
cm.MapMember(c => c.Alert).SetDefaultValue("#FF5D6A");
cm.MapMember(c => c.Improve).SetDefaultValue("#60D61D");
cm.MapMember(c => c.Expired).SetDefaultValue("#333333");
});
cm.MapMember(c => c.Transferable).SetDefaultValue(new ColorConfig.AppearanceSettings
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig.ArrowColors)))
BsonClassMap.RegisterClassMap<ColorConfig.ArrowColors>(cm =>
{
BackgroundColor = "#57B812",
TextColor = "#000000"
cm.AutoMap();
cm.MapMember(c => c.Normal).SetDefaultValue("#FFFFFF");
cm.MapMember(c => c.Warning).SetDefaultValue("#F5A623");
cm.MapMember(c => c.Alert).SetDefaultValue("#FF5D6A");
cm.MapMember(c => c.Improve).SetDefaultValue("#60D61D");
});
cm.MapMember(c => c.Exitus).SetDefaultValue(new ColorConfig.AppearanceSettings
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig.IndicatorColors)))
BsonClassMap.RegisterClassMap<ColorConfig.IndicatorColors>(cm =>
{
BackgroundColor = "#24BFF9",
TextColor = "#000000"
cm.AutoMap();
cm.MapMember(c => c.Empty).SetDefaultValue("#CCCCCC");
cm.MapMember(c => c.Warning).SetDefaultValue("#FFAD26");
cm.MapMember(c => c.Normal).SetDefaultValue("#60D61D");
cm.MapMember(c => c.Alert).SetDefaultValue("#FF5D6A");
cm.MapMember(c => c.Background).SetDefaultValue("#000000");
cm.MapMember(c => c.EmptyBackground).SetDefaultValue("#CCCCCC");
});
cm.MapMember(c => c.Altable).SetDefaultValue(new ColorConfig.AppearanceSettings
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig.GraphColors)))
BsonClassMap.RegisterClassMap<ColorConfig.GraphColors>(cm =>
{
BackgroundColor = "#24BFF9",
TextColor = "#000000"
cm.AutoMap();
cm.MapMember(c => c.Normal).SetDefaultValue("#60D61D");
cm.MapMember(c => c.Warning).SetDefaultValue("#FFAD26");
cm.MapMember(c => c.Alert).SetDefaultValue("#FF5D6A");
});
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig.TherapyColors)))
BsonClassMap.RegisterClassMap<ColorConfig.TherapyColors>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Default).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#00A4E1",
TextColor = "#FFF",
IconInvertColor = 1
});
cm.MapMember(c => c.Initialized).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#FFF",
TextColor = "#000"
});
cm.MapMember(c => c.Finished).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#FFF",
TextColor = "#000",
Icon = "assets/icon/light-theme/ic-tick.svg",
IconDefault = "icTick"
});
cm.MapMember(c => c.InProgress).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#FFF",
TextColor = "#000"
});
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig.AppearanceSettings)))
BsonClassMap.RegisterClassMap<ColorConfig.AppearanceSettings>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.TextColor).SetDefaultValue("#000000");
cm.MapMember(c => c.BackgroundColor).SetDefaultValue("#FFFFFF");
cm.MapMember(c => c.Icon).SetIgnoreIfNull(true);
cm.MapMember(c => c.IconDefault).SetIgnoreIfNull(true);
cm.MapMember(c => c.IconColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.IconCategory).SetIgnoreIfNull(true);
cm.MapMember(c => c.IconInvertColor).SetDefaultValue(0.0);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig.TestColors)))
BsonClassMap.RegisterClassMap<ColorConfig.TestColors>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Default).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#00A4E1",
TextColor = "#FFF"
});
cm.MapMember(c => c.Initialized).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#ED4965",
TextColor = "#FFF"
});
cm.MapMember(c => c.Finished).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#00C49B",
TextColor = "#FFF",
Icon = "assets/icon/light-theme/ic-tick.svg",
IconDefault = "icTick"
});
cm.MapMember(c => c.Expired).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#ED4965",
TextColor = "#FFF",
Icon = "assets/icon/light-theme/ic-clock.svg",
IconDefault = "icClock"
});
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig.ProcedureColors)))
BsonClassMap.RegisterClassMap<ColorConfig.ProcedureColors>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Default).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#00A4E1",
TextColor = "#FFF"
});
cm.MapMember(c => c.Initialized).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#ED4965",
TextColor = "#FFF"
});
cm.MapMember(c => c.Finished).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#00C49B",
TextColor = "#FFF",
Icon = "assets/icon/light-theme/ic-tick.svg",
IconDefault = "icTick"
});
cm.MapMember(c => c.Expired).SetDefaultValue(new ColorConfig.AppearanceSettings
{
BackgroundColor = "#ED4965",
TextColor = "#FFF",
Icon = "assets/icon/light-theme/ic-clock.svg",
IconDefault = "icClock"
});
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig.LevelColors)))
BsonClassMap.RegisterClassMap<ColorConfig.LevelColors>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Level1).SetDefaultValue("#FFFFFF");
cm.MapMember(c => c.Level2).SetDefaultValue("#FAFF41");
cm.MapMember(c => c.Level3).SetDefaultValue("#F5A623");
cm.MapMember(c => c.Level4).SetDefaultValue("#C2510F");
cm.MapMember(c => c.Level5).SetDefaultValue("#FF5D6A");
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig.TextColors)))
BsonClassMap.RegisterClassMap<ColorConfig.TextColors>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Normal).SetDefaultValue("#FFFFFF");
cm.MapMember(c => c.Warning).SetDefaultValue("#FFAD26");
cm.MapMember(c => c.Alert).SetDefaultValue("#FF5D6A");
cm.MapMember(c => c.Improve).SetDefaultValue("#60D61D");
cm.MapMember(c => c.Expired).SetDefaultValue("#333333");
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig.ArrowColors)))
BsonClassMap.RegisterClassMap<ColorConfig.ArrowColors>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Normal).SetDefaultValue("#FFFFFF");
cm.MapMember(c => c.Warning).SetDefaultValue("#F5A623");
cm.MapMember(c => c.Alert).SetDefaultValue("#FF5D6A");
cm.MapMember(c => c.Improve).SetDefaultValue("#60D61D");
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig.IndicatorColors)))
BsonClassMap.RegisterClassMap<ColorConfig.IndicatorColors>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Empty).SetDefaultValue("#CCCCCC");
cm.MapMember(c => c.Warning).SetDefaultValue("#FFAD26");
cm.MapMember(c => c.Normal).SetDefaultValue("#60D61D");
cm.MapMember(c => c.Alert).SetDefaultValue("#FF5D6A");
cm.MapMember(c => c.Background).SetDefaultValue("#000000");
cm.MapMember(c => c.EmptyBackground).SetDefaultValue("#CCCCCC");
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ColorConfig.GraphColors)))
BsonClassMap.RegisterClassMap<ColorConfig.GraphColors>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Normal).SetDefaultValue("#60D61D");
cm.MapMember(c => c.Warning).SetDefaultValue("#FFAD26");
cm.MapMember(c => c.Alert).SetDefaultValue("#FF5D6A");
});
}
}
}
@@ -7,34 +7,40 @@ using MongoDB.Bson.Serialization;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents a map contributor that provides mapping configuration for communication flow entities by implementing the <see cref="IEntityMapContributor"/> interface.
/// </summary>
public class ComunicationFlowMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for the application's MongoDB-serialized types, including <see cref="Message"/>, <see cref="Queue"/>, <see cref="ApiRequest"/>, <see cref="AdmPanelRequest"/>, <see cref="ApiClients"/>, and <see cref="VideoDto"/>, applying custom mapping rules such as unmapping the <c>Operation</c> member on <see cref="Message"/> and ignoring null values for <c>ObsertationData</c> on <see cref="AdmPanelRequest"/>. Each type is only registered if a class map has not already been registered, preventing duplicate registration.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(Message)))
BsonClassMap.RegisterClassMap<Message>(cm =>
{
cm.AutoMap();
cm.UnmapMember(c => c.Operation);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Queue)))
BsonClassMap.RegisterClassMap<Queue>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(ApiRequest)))
BsonClassMap.RegisterClassMap<ApiRequest>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(AdmPanelRequest)))
BsonClassMap.RegisterClassMap<AdmPanelRequest>(cm =>
{
cm.AutoMap();
cm.GetMemberMap(c => c.ObsertationData).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ApiClients)))
BsonClassMap.RegisterClassMap<ApiClients>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(VideoDto)))
BsonClassMap.RegisterClassMap<VideoDto>(cm => { cm.AutoMap(); });
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(Message)))
BsonClassMap.RegisterClassMap<Message>(cm =>
{
cm.AutoMap();
cm.UnmapMember(c => c.Operation);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Queue)))
BsonClassMap.RegisterClassMap<Queue>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(ApiRequest)))
BsonClassMap.RegisterClassMap<ApiRequest>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(AdmPanelRequest)))
BsonClassMap.RegisterClassMap<AdmPanelRequest>(cm =>
{
cm.AutoMap();
cm.GetMemberMap(c => c.ObsertationData).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ApiClients)))
BsonClassMap.RegisterClassMap<ApiClients>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(VideoDto)))
BsonClassMap.RegisterClassMap<VideoDto>(cm => { cm.AutoMap(); });
}
}
@@ -8,74 +8,84 @@ using MongoDB.Bson.Serialization.Serializers;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents a contributor that provides device-related mapping logic for entities.
/// Implements the <see cref="IEntityMapContributor"/> interface to participate in the entity mapping process.
/// </summary>
public class DeviceMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for <see cref="DeviceActionType"/>, <see cref="DeviceAction"/>, <see cref="DeviceSettings"/>, and <see cref="Device"/> types used in MongoDB serialization.
/// </summary>
/// <remarks>
/// Each map is only registered if no class map is already registered for the type. The registration applies auto-mapping and customizes serialization for enum members (stored as strings), applies default values for required properties, and configures nullable members to be ignored when null.
/// </remarks>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(DeviceActionType)))
{
BsonClassMap.RegisterClassMap<DeviceActionType>(cm => cm.AutoMap() );
if (!BsonClassMap.IsClassMapRegistered(typeof(DeviceActionType)))
{
BsonClassMap.RegisterClassMap<DeviceActionType>(cm => cm.AutoMap() );
}
if (!BsonClassMap.IsClassMapRegistered(typeof(DeviceAction)))
{
BsonClassMap.RegisterClassMap<DeviceAction>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Type)
.SetDefaultValue(DeviceActionType.Unknown)
.SetSerializer(new EnumSerializer<DeviceActionType>(BsonType.String));
cm.MapMember(c => c.ConfigObservationId).SetIgnoreIfNull(true);
cm.MapMember(c => c.AlarmName).SetIgnoreIfNull(true);
cm.MapMember(c => c.ValueOnSingleClick)
.SetDefaultValue(new object())
.SetSerializer(new ComplexObjectValueTypeSerializer());
cm.MapMember(c => c.ValueOnDoubleClick)
.SetDefaultValue(new object())
.SetSerializer(new ComplexObjectValueTypeSerializer());
cm.MapMember(c => c.ValueOnHoldClick)
.SetDefaultValue(new object())
.SetSerializer(new ComplexObjectValueTypeSerializer());
}
);
}
if (!BsonClassMap.IsClassMapRegistered(typeof(DeviceSettings)))
{
BsonClassMap.RegisterClassMap<DeviceSettings>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Action).SetDefaultValue(new DeviceAction());
}
);
}
if (!BsonClassMap.IsClassMapRegistered(typeof(Device)))
{
BsonClassMap.RegisterClassMap<Device>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.DeviceType)
.SetDefaultValue(DeviceType.Unknown)
.SetSerializer(new EnumSerializer<DeviceType>(BsonType.String));
cm.MapMember(c => c.Uuid).SetIgnoreIfNull(true);
cm.MapMember(c => c.MacAddr).SetIgnoreIfNull(true);
cm.MapMember(c => c.CreatedAt).SetDefaultValue(DateTime.UtcNow);
cm.MapMember(c => c.UpdatedAt).SetDefaultValue(DateTime.UtcNow);
cm.MapMember(c => c.Key).SetIgnoreIfNull(true);
cm.MapMember(c => c.Color).SetIgnoreIfNull(true);
cm.MapMember(c => c.Battery).SetIgnoreIfNull(true);
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.SerialNumber).SetIgnoreIfNull(true);
cm.MapMember(c => c.PointOfCareIds).SetDefaultValue(new List<ObjectId>());
cm.MapMember(c => c.Connected).SetDefaultValue(false).SetIgnoreIfNull(true);
cm.MapMember(c => c.Ready).SetDefaultValue(false).SetIgnoreIfNull(true);
cm.MapMember(c => c.DeviceType)
.SetSerializer(new EnumSerializer<DeviceType>(BsonType.String));
cm.MapMember(c => c.Settings).SetDefaultValue(new DeviceSettings());
}
);
}
}
if (!BsonClassMap.IsClassMapRegistered(typeof(DeviceAction)))
{
BsonClassMap.RegisterClassMap<DeviceAction>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Type)
.SetDefaultValue(DeviceActionType.Unknown)
.SetSerializer(new EnumSerializer<DeviceActionType>(BsonType.String));
cm.MapMember(c => c.ConfigObservationId).SetIgnoreIfNull(true);
cm.MapMember(c => c.AlarmName).SetIgnoreIfNull(true);
cm.MapMember(c => c.ValueOnSingleClick)
.SetDefaultValue(new object())
.SetSerializer(new ComplexObjectValueTypeSerializer());
cm.MapMember(c => c.ValueOnDoubleClick)
.SetDefaultValue(new object())
.SetSerializer(new ComplexObjectValueTypeSerializer());
cm.MapMember(c => c.ValueOnHoldClick)
.SetDefaultValue(new object())
.SetSerializer(new ComplexObjectValueTypeSerializer());
}
);
}
if (!BsonClassMap.IsClassMapRegistered(typeof(DeviceSettings)))
{
BsonClassMap.RegisterClassMap<DeviceSettings>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Action).SetDefaultValue(new DeviceAction());
}
);
}
if (!BsonClassMap.IsClassMapRegistered(typeof(Device)))
{
BsonClassMap.RegisterClassMap<Device>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.DeviceType)
.SetDefaultValue(DeviceType.Unknown)
.SetSerializer(new EnumSerializer<DeviceType>(BsonType.String));
cm.MapMember(c => c.Uuid).SetIgnoreIfNull(true);
cm.MapMember(c => c.MacAddr).SetIgnoreIfNull(true);
cm.MapMember(c => c.CreatedAt).SetDefaultValue(DateTime.UtcNow);
cm.MapMember(c => c.UpdatedAt).SetDefaultValue(DateTime.UtcNow);
cm.MapMember(c => c.Key).SetIgnoreIfNull(true);
cm.MapMember(c => c.Color).SetIgnoreIfNull(true);
cm.MapMember(c => c.Battery).SetIgnoreIfNull(true);
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.SerialNumber).SetIgnoreIfNull(true);
cm.MapMember(c => c.PointOfCareIds).SetDefaultValue(new List<ObjectId>());
cm.MapMember(c => c.Connected).SetDefaultValue(false).SetIgnoreIfNull(true);
cm.MapMember(c => c.Ready).SetDefaultValue(false).SetIgnoreIfNull(true);
cm.MapMember(c => c.DeviceType)
.SetSerializer(new EnumSerializer<DeviceType>(BsonType.String));
cm.MapMember(c => c.Settings).SetDefaultValue(new DeviceSettings());
}
);
}
}
}
@@ -7,40 +7,46 @@ using MongoDB.Bson.Serialization.Serializers;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents a contributor that supplies entity map configuration related to display home functionality.
/// </summary>
public class DisplayHomeConfigContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for <see cref="CardConfig"/> and <see cref="RowCardConfig"/> types if they are not already registered, configuring MongoDB serialization with custom element names, default values, null-ignore behavior, and string-based enum serialization.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(CardConfig)))
BsonClassMap.RegisterClassMap<CardConfig>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.Rows).SetDefaultValue(new List<RowCardConfig>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(RowCardConfig)))
BsonClassMap.RegisterClassMap<RowCardConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Cells).SetDefaultValue(new List<Cell>());
cm.MapMember(c => c.GrowPriority).SetDefaultValue(1);
cm.MapMember(c => c.Border).SetIgnoreIfNull(true);
cm.MapMember(c => c.BorderRadius).SetIgnoreIfNull(true);
cm.MapMember(c => c.Title).SetIgnoreIfNull(true);
cm.MapMember(c => c.Size).SetIgnoreIfNull(true);
cm.MapMember(c => c.MarginTop).SetIgnoreIfNull(true);
cm.MapMember(c => c.MarginBottom).SetIgnoreIfNull(true);
cm.MapMember(c => c.MarginLeft).SetIgnoreIfNull(true);
cm.MapMember(c => c.MarginRight).SetIgnoreIfNull(true);
cm.MapMember(c => c.BgColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingTop).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingBottom).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingLeft).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingRight).SetIgnoreIfNull(true);
cm.MapMember(c => c.Type)
.SetDefaultValue(DisplayConfigEnums.CellType.Default)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.CellType>(BsonType.String));
});
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(CardConfig)))
BsonClassMap.RegisterClassMap<CardConfig>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.Rows).SetDefaultValue(new List<RowCardConfig>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(RowCardConfig)))
BsonClassMap.RegisterClassMap<RowCardConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Cells).SetDefaultValue(new List<Cell>());
cm.MapMember(c => c.GrowPriority).SetDefaultValue(1);
cm.MapMember(c => c.Border).SetIgnoreIfNull(true);
cm.MapMember(c => c.BorderRadius).SetIgnoreIfNull(true);
cm.MapMember(c => c.Title).SetIgnoreIfNull(true);
cm.MapMember(c => c.Size).SetIgnoreIfNull(true);
cm.MapMember(c => c.MarginTop).SetIgnoreIfNull(true);
cm.MapMember(c => c.MarginBottom).SetIgnoreIfNull(true);
cm.MapMember(c => c.MarginLeft).SetIgnoreIfNull(true);
cm.MapMember(c => c.MarginRight).SetIgnoreIfNull(true);
cm.MapMember(c => c.BgColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingTop).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingBottom).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingLeft).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingRight).SetIgnoreIfNull(true);
cm.MapMember(c => c.Type)
.SetDefaultValue(DisplayConfigEnums.CellType.Default)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.CellType>(BsonType.String));
});
}
}
@@ -8,127 +8,137 @@ using MongoDB.Bson.Serialization.Serializers;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents a contributor responsible for providing display-related mapping logic for entities.
/// </summary>
/// <remarks>
/// Implements the <see cref="IEntityMapContributor"/> interface to participate in the entity mapping pipeline for display scenarios.
/// </remarks>
public class DisplayMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for display-related types (Display, DisplayNurse, StandarDisplay, SmartDisplay, PumpDisplay, DisplayConfig, and GroupedField) used for MongoDB serialization.
/// </summary>
/// <remarks>Configures polymorphic deserialization for DisplayConfig by declaring its known subtypes, sets custom element names for identifiers, applies default values for enum and collection members, omits null or unmapped properties, and uses string-based enum serialization where applicable. Each registration is guarded so it only occurs when the corresponding class map has not already been registered.</remarks>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(Display)))
BsonClassMap.RegisterClassMap<Display>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.UnmapProperty(c => c.Unit);
cm.UnmapProperty(c => c.PointOfCares);
cm.UnmapProperty(c => c.DisplayConfig);
cm.MapMember(c => c.Type)
.SetDefaultValue(DisplayConfigEnums.DisplayType.Unknown)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.DisplayType>(BsonType.String));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(DisplayNurse)))
BsonClassMap.RegisterClassMap<DisplayNurse>(cm =>
{
cm.AutoMap();
});
if (!BsonClassMap.IsClassMapRegistered(typeof(StandarDisplay)))
BsonClassMap.RegisterClassMap<StandarDisplay>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.SectionConfig).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(SmartDisplay)))
BsonClassMap.RegisterClassMap<SmartDisplay>(cm =>
{
cm.AutoMap();
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PumpDisplay)))
BsonClassMap.RegisterClassMap<PumpDisplay>(cm =>
{
cm.AutoMap();
});
if (!BsonClassMap.IsClassMapRegistered(typeof(DisplayConfig)))
{
BsonClassMap.RegisterClassMap<DisplayConfig>(cm =>
if (!BsonClassMap.IsClassMapRegistered(typeof(Display)))
BsonClassMap.RegisterClassMap<Display>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.UnmapProperty(c => c.Unit);
cm.UnmapProperty(c => c.PointOfCares);
cm.UnmapProperty(c => c.DisplayConfig);
cm.MapMember(c => c.Type)
.SetDefaultValue(DisplayConfigEnums.DisplayType.Unknown)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.DisplayType>(BsonType.String));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(DisplayNurse)))
BsonClassMap.RegisterClassMap<DisplayNurse>(cm =>
{
cm.AutoMap();
});
if (!BsonClassMap.IsClassMapRegistered(typeof(StandarDisplay)))
BsonClassMap.RegisterClassMap<StandarDisplay>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.SectionConfig).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(SmartDisplay)))
BsonClassMap.RegisterClassMap<SmartDisplay>(cm =>
{
cm.AutoMap();
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PumpDisplay)))
BsonClassMap.RegisterClassMap<PumpDisplay>(cm =>
{
cm.AutoMap();
});
if (!BsonClassMap.IsClassMapRegistered(typeof(DisplayConfig)))
{
cm.AutoMap();
cm.AddKnownType(typeof(DisplayNurse));
cm.AddKnownType(typeof(StandarDisplay));
cm.AddKnownType(typeof(SmartDisplay));
cm.AddKnownType(typeof(PumpDisplay));
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.Type)
.SetDefaultValue(DisplayConfigEnums.DisplayType.Unknown)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.DisplayType>(BsonType.String));
cm.MapMember(c => c.MediaFolder).SetIgnoreIfNull(true);
cm.MapMember(c => c.CardConfigId).SetIgnoreIfNull(true);
cm.MapMember(c => c.CardConfig)
.SetIgnoreIfNull(true)
.SetIsRequired(false);
cm.MapMember(c => c.DetailConfigId).SetIgnoreIfNull(true);
cm.MapMember(c => c.DetailConfig)
.SetIgnoreIfNull(true)
.SetIsRequired(false);
cm.MapMember(c => c.HomeBanner).SetIgnoreIfNull(true);
cm.MapMember(c => c.FormConfig)
.SetDefaultValue(new FormConfig
{
Admission = new FormItemOverview { Nhc = true },
Demographic = new FormItemOverview { Nhc = true },
Discharge = new FormItemOverview { Nhc = true },
IncomeInfo = new FormItemOverview { Nhc = true }
});
cm.MapMember(c => c.HasCameras).SetIgnoreIfNull(true);
cm.MapMember(c => c.HasSound).SetIgnoreIfNull(true);
cm.MapMember(c => c.IsRotationEnabled).SetIgnoreIfNull(true);
cm.MapMember(c => c.CanChangeCameraMode).SetIgnoreIfNull(true);
cm.MapMember(c => c.CamerasAreActive).SetIgnoreIfNull(true);
cm.MapMember(c => c.CameraStreamType).SetIgnoreIfNull(true);
cm.MapMember(c => c.SensorList).SetIgnoreIfNull(true);
cm.MapMember(c => c.ObservationForIndicator).SetIgnoreIfNull(true);
cm.MapMember(c => c.AlarmFieldList).SetIgnoreIfNull(true);
cm.MapMember(c => c.RequestGroupedFieldList).SetIgnoreIfNull(true);
cm.MapMember(c => c.Pumps).SetIgnoreIfNull(true);
cm.MapMember(c => c.ChartConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.GraphLayout).SetIgnoreIfNull(true);
cm.MapMember(c => c.CardRotatingLayout).SetIgnoreIfNull(true);
cm.MapMember(c => c.ChartConfigIdList).SetIgnoreIfNull(true);
cm.MapMember(c => c.HomeConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.HeaderConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.DisplaySectionIdList).SetDefaultValue(new List<ObjectId>());
cm.MapMember(c => c.Hospital).SetIgnoreIfNull(true);
cm.MapMember(c => c.ColorConfig).SetDefaultValue(new ColorConfig());
cm.MapMember(c => c.FieldList).SetDefaultValue(new List<Field>());
cm.MapMember(c => c.GroupedFieldList).SetDefaultValue(new List<GroupedField>());
cm.UnmapProperty(c => c.DisplaySectionList);
// cm.UnmapProperty(c => c.CardConfig);
});
BsonClassMap.RegisterClassMap<GroupedField>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.Names).SetIgnoreIfNull(true);
cm.MapMember(c => c.Group).SetIgnoreIfNull(true);
cm.MapMember(c => c.StartTimeShift).SetIgnoreIfNull(true);
cm.MapMember(c => c.Max);
cm.MapMember(c => c.Regularity)
.SetIgnoreIfNull(true)
.SetSerializer(new NullableSerializer<GroupedObservationEnum.Regularity>(
new EnumSerializer<GroupedObservationEnum.Regularity>(BsonType.String)));
cm.MapMember(c => c.Since)
.SetDefaultValue(GroupedObservationEnum.Since.Last)
.SetSerializer(new EnumSerializer<GroupedObservationEnum.Since>(BsonType.String));
cm.MapMember(c => c.Result)
.SetIgnoreIfNull(true);
//.SetSerializer(new EnumSerializer<GroupedObservationEnum.Result>(BsonType.Array));
cm.MapMember(c => c.LabelList).SetIgnoreIfNull(true);
});
BsonClassMap.RegisterClassMap<DisplayConfig>(cm =>
{
cm.AutoMap();
cm.AddKnownType(typeof(DisplayNurse));
cm.AddKnownType(typeof(StandarDisplay));
cm.AddKnownType(typeof(SmartDisplay));
cm.AddKnownType(typeof(PumpDisplay));
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.Type)
.SetDefaultValue(DisplayConfigEnums.DisplayType.Unknown)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.DisplayType>(BsonType.String));
cm.MapMember(c => c.MediaFolder).SetIgnoreIfNull(true);
cm.MapMember(c => c.CardConfigId).SetIgnoreIfNull(true);
cm.MapMember(c => c.CardConfig)
.SetIgnoreIfNull(true)
.SetIsRequired(false);
cm.MapMember(c => c.DetailConfigId).SetIgnoreIfNull(true);
cm.MapMember(c => c.DetailConfig)
.SetIgnoreIfNull(true)
.SetIsRequired(false);
cm.MapMember(c => c.HomeBanner).SetIgnoreIfNull(true);
cm.MapMember(c => c.FormConfig)
.SetDefaultValue(new FormConfig
{
Admission = new FormItemOverview { Nhc = true },
Demographic = new FormItemOverview { Nhc = true },
Discharge = new FormItemOverview { Nhc = true },
IncomeInfo = new FormItemOverview { Nhc = true }
});
cm.MapMember(c => c.HasCameras).SetIgnoreIfNull(true);
cm.MapMember(c => c.HasSound).SetIgnoreIfNull(true);
cm.MapMember(c => c.IsRotationEnabled).SetIgnoreIfNull(true);
cm.MapMember(c => c.CanChangeCameraMode).SetIgnoreIfNull(true);
cm.MapMember(c => c.CamerasAreActive).SetIgnoreIfNull(true);
cm.MapMember(c => c.CameraStreamType).SetIgnoreIfNull(true);
cm.MapMember(c => c.SensorList).SetIgnoreIfNull(true);
cm.MapMember(c => c.ObservationForIndicator).SetIgnoreIfNull(true);
cm.MapMember(c => c.AlarmFieldList).SetIgnoreIfNull(true);
cm.MapMember(c => c.RequestGroupedFieldList).SetIgnoreIfNull(true);
cm.MapMember(c => c.Pumps).SetIgnoreIfNull(true);
cm.MapMember(c => c.ChartConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.GraphLayout).SetIgnoreIfNull(true);
cm.MapMember(c => c.CardRotatingLayout).SetIgnoreIfNull(true);
cm.MapMember(c => c.ChartConfigIdList).SetIgnoreIfNull(true);
cm.MapMember(c => c.HomeConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.HeaderConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.DisplaySectionIdList).SetDefaultValue(new List<ObjectId>());
cm.MapMember(c => c.Hospital).SetIgnoreIfNull(true);
cm.MapMember(c => c.ColorConfig).SetDefaultValue(new ColorConfig());
cm.MapMember(c => c.FieldList).SetDefaultValue(new List<Field>());
cm.MapMember(c => c.GroupedFieldList).SetDefaultValue(new List<GroupedField>());
cm.UnmapProperty(c => c.DisplaySectionList);
// cm.UnmapProperty(c => c.CardConfig);
});
BsonClassMap.RegisterClassMap<GroupedField>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.Names).SetIgnoreIfNull(true);
cm.MapMember(c => c.Group).SetIgnoreIfNull(true);
cm.MapMember(c => c.StartTimeShift).SetIgnoreIfNull(true);
cm.MapMember(c => c.Max);
cm.MapMember(c => c.Regularity)
.SetIgnoreIfNull(true)
.SetSerializer(new NullableSerializer<GroupedObservationEnum.Regularity>(
new EnumSerializer<GroupedObservationEnum.Regularity>(BsonType.String)));
cm.MapMember(c => c.Since)
.SetDefaultValue(GroupedObservationEnum.Since.Last)
.SetSerializer(new EnumSerializer<GroupedObservationEnum.Since>(BsonType.String));
cm.MapMember(c => c.Result)
.SetIgnoreIfNull(true);
//.SetSerializer(new EnumSerializer<GroupedObservationEnum.Result>(BsonType.Array));
cm.MapMember(c => c.LabelList).SetIgnoreIfNull(true);
});
}
}
}
}
@@ -4,46 +4,53 @@ using MongoDB.Bson.Serialization;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents a contributor responsible for providing form-related mapping logic for entities.
/// Implements the <see cref="IEntityMapContributor"/> interface to participate in the entity mapping process.
/// </summary>
public class FormMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for <see cref="FormConfig"/> and <see cref="FormItemOverview"/> types used during MongoDB serialization, configuring default values and conditional serialization rules for their members. Each class map is only registered if it has not already been registered, preventing duplicate registrations.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(FormConfig)))
BsonClassMap.RegisterClassMap<FormConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Admission).SetDefaultValue(new FormItemOverview());
cm.MapMember(c => c.Demographic).SetIgnoreIfNull(true);
cm.MapMember(c => c.Discharge).SetIgnoreIfNull(true);
cm.MapMember(c => c.IncomeInfo).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(FormItemOverview)))
BsonClassMap.RegisterClassMap<FormItemOverview>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Nhc).SetDefaultValue(true);
cm.MapMember(c => c.Bed).SetIgnoreIfNull(true);
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.LastName).SetIgnoreIfNull(true);
cm.MapMember(c => c.SecondName).SetIgnoreIfNull(true);
cm.MapMember(c => c.Genre).SetIgnoreIfNull(true);
cm.MapMember(c => c.Birthday).SetIgnoreIfNull(true);
cm.MapMember(c => c.Origin).SetIgnoreIfNull(true);
cm.MapMember(c => c.OriginAux).SetIgnoreIfNull(true);
cm.MapMember(c => c.Diagnostic).SetIgnoreIfNull(true);
cm.MapMember(c => c.DiagnosticAux).SetIgnoreIfNull(true);
cm.MapMember(c => c.Allergy).SetIgnoreIfNull(true);
cm.MapMember(c => c.Language).SetIgnoreIfNull(true);
cm.MapMember(c => c.Insulation).SetIgnoreIfNull(true);
cm.MapMember(c => c.Service).SetIgnoreIfNull(true);
cm.MapMember(c => c.Destination).SetIgnoreIfNull(true);
cm.MapMember(c => c.DestinationAux).SetIgnoreIfNull(true);
cm.MapMember(c => c.AdmDischarge).SetIgnoreIfNull(true);
cm.MapMember(c => c.NurseDischarge).SetIgnoreIfNull(true);
cm.MapMember(c => c.MedicalDischarge).SetIgnoreIfNull(true);
cm.MapMember(c => c.IncomingDate).SetIgnoreIfNull(true);
cm.MapMember(c => c.UciDays).SetIgnoreIfNull(true);
});
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(FormConfig)))
BsonClassMap.RegisterClassMap<FormConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Admission).SetDefaultValue(new FormItemOverview());
cm.MapMember(c => c.Demographic).SetIgnoreIfNull(true);
cm.MapMember(c => c.Discharge).SetIgnoreIfNull(true);
cm.MapMember(c => c.IncomeInfo).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(FormItemOverview)))
BsonClassMap.RegisterClassMap<FormItemOverview>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Nhc).SetDefaultValue(true);
cm.MapMember(c => c.Bed).SetIgnoreIfNull(true);
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.LastName).SetIgnoreIfNull(true);
cm.MapMember(c => c.SecondName).SetIgnoreIfNull(true);
cm.MapMember(c => c.Genre).SetIgnoreIfNull(true);
cm.MapMember(c => c.Birthday).SetIgnoreIfNull(true);
cm.MapMember(c => c.Origin).SetIgnoreIfNull(true);
cm.MapMember(c => c.OriginAux).SetIgnoreIfNull(true);
cm.MapMember(c => c.Diagnostic).SetIgnoreIfNull(true);
cm.MapMember(c => c.DiagnosticAux).SetIgnoreIfNull(true);
cm.MapMember(c => c.Allergy).SetIgnoreIfNull(true);
cm.MapMember(c => c.Language).SetIgnoreIfNull(true);
cm.MapMember(c => c.Insulation).SetIgnoreIfNull(true);
cm.MapMember(c => c.Service).SetIgnoreIfNull(true);
cm.MapMember(c => c.Destination).SetIgnoreIfNull(true);
cm.MapMember(c => c.DestinationAux).SetIgnoreIfNull(true);
cm.MapMember(c => c.AdmDischarge).SetIgnoreIfNull(true);
cm.MapMember(c => c.NurseDischarge).SetIgnoreIfNull(true);
cm.MapMember(c => c.MedicalDischarge).SetIgnoreIfNull(true);
cm.MapMember(c => c.IncomingDate).SetIgnoreIfNull(true);
cm.MapMember(c => c.UciDays).SetIgnoreIfNull(true);
});
}
}
@@ -7,259 +7,268 @@ using MongoDB.Bson.Serialization.Serializers;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents a contributor that provides graph-based mapping logic for entities.
/// </summary>
/// <remarks>
/// Implements the <see cref="IEntityMapContributor"/> contract to participate in entity map configuration within a graph structure.
/// </remarks>
public class GraphMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for the application's chart, graph, and legend configuration types, configuring serialization behavior such as default values, null-ignoring, and enum string serialization. Each registration is guarded by an <see cref="BsonClassMap.IsClassMapRegistered"/> check so the method is idempotent and can be safely called multiple times.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(GraphLayout)))
BsonClassMap.RegisterClassMap<GraphLayout>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Layout).SetIgnoreIfNull(true);
cm.MapMember(c => c.ObservationTitle).SetIgnoreIfNull(true);
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.GraphConf).SetIgnoreIfNull(true);
cm.MapMember(c => c.ObservationName).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ChartSettings)))
BsonClassMap.RegisterClassMap<ChartSettings>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.LegendLayoutConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.LegendGrowPriority).SetIgnoreIfNull(true);
cm.MapMember(c => c.ChartGrowPriority).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ChartConfig)))
{
BsonClassMap.RegisterClassMap<ChartConfig>(cm =>
if (!BsonClassMap.IsClassMapRegistered(typeof(GraphLayout)))
BsonClassMap.RegisterClassMap<GraphLayout>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Layout).SetIgnoreIfNull(true);
cm.MapMember(c => c.ObservationTitle).SetIgnoreIfNull(true);
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.GraphConf).SetIgnoreIfNull(true);
cm.MapMember(c => c.ObservationName).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ChartSettings)))
BsonClassMap.RegisterClassMap<ChartSettings>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.LegendLayoutConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.LegendGrowPriority).SetIgnoreIfNull(true);
cm.MapMember(c => c.ChartGrowPriority).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ChartConfig)))
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.BaseConfig).SetDefaultValue(new ChartBaseConfig());
cm.MapMember(c => c.AxesConfig).SetDefaultValue(new List<AxisConfig>());
cm.MapMember(c => c.SeriesConfig).SetDefaultValue(new List<SeriesConfigBase>());
});
BsonClassMap.RegisterClassMap<ChartBaseConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Title).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Top).SetDefaultValue("7%");
cm.MapMember(c => c.Right).SetDefaultValue("7%");
cm.MapMember(c => c.Bottom).SetDefaultValue("7%");
cm.MapMember(c => c.Left).SetDefaultValue("7%");
cm.MapMember(c => c.Group).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Name).SetDefaultValue(string.Empty);
cm.MapMember(c => c.BorderWidth).SetDefaultValue(1);
cm.MapMember(c => c.BorderColor).SetDefaultValue(string.Empty);
cm.MapMember(c => c.ShowLegend).SetDefaultValue(false);
cm.MapMember(c => c.ShowGrid).SetDefaultValue(false);
cm.MapMember(c => c.NumValues).SetIgnoreIfNull(true);
cm.MapMember(c => c.BaselineOffset).SetIgnoreIfNull(true);
});
BsonClassMap.RegisterClassMap<AxisConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(a => a.Type)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.AxisType>(BsonType.String))
.SetDefaultValue(DisplayConfigEnums.AxisType.Value);
cm.MapMember(a => a.KeyName).SetIgnoreIfNull(true);
cm.MapMember(a => a.Position)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.AxisPosition>(BsonType.String))
.SetDefaultValue(DisplayConfigEnums.AxisPosition.Left);
cm.MapMember(a => a.Min).SetIgnoreIfNull(true);
cm.MapMember(a => a.Max).SetIgnoreIfNull(true);
cm.MapMember(a => a.AxisLine).SetIgnoreIfNull(true);
cm.MapMember(a => a.AxisTick).SetIgnoreIfNull(true);
cm.MapMember(a => a.AxisLabel).SetIgnoreIfNull(true);
cm.MapMember(a => a.Silent).SetDefaultValue(true);
cm.MapMember(a => a.LabelFormat)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.LabelFormat>(BsonType.String))
.SetDefaultValue(DisplayConfigEnums.LabelFormat.Hour);
cm.MapMember(a => a.Offset).SetDefaultValue(0.0);
cm.MapMember(a => a.CustomLabels).SetDefaultValue(new List<string>());
cm.MapMember(a => a.SortLabels).SetDefaultValue(false);
cm.MapMember(a => a.Show).SetDefaultValue(true);
cm.MapMember(a => a.Regularity)
.SetSerializer(new EnumSerializer<GroupedObservationEnum.Regularity>(BsonType.String))
.SetDefaultValue(GroupedObservationEnum.Regularity.Hour);
});
BsonClassMap.RegisterClassMap<AxisTick>(cm =>
{
cm.AutoMap();
cm.MapMember(a => a.Show).SetIgnoreIfNull(true);
cm.MapMember(a => a.Interval).SetIgnoreIfNull(true);
cm.MapMember(a => a.Length).SetIgnoreIfNull(true);
});
BsonClassMap.RegisterClassMap<AxisLabel>(cm =>
{
cm.AutoMap();
cm.MapMember(a => a.Show).SetIgnoreIfNull(true);
cm.MapMember(a => a.Color).SetIgnoreIfNull(true);
cm.MapMember(a => a.Margin).SetIgnoreIfNull(true);
cm.MapMember(a => a.FontSize).SetIgnoreIfNull(true);
cm.MapMember(a => a.Silent).SetIgnoreIfNull(true);
});
BsonClassMap.RegisterClassMap<AxisLineStyle>(cm =>
{
cm.AutoMap();
cm.MapMember(a => a.Color).SetIgnoreIfNull(true);
});
BsonClassMap.RegisterClassMap<AxisLine>(cm =>
{
cm.AutoMap();
cm.MapMember(a => a.Show).SetIgnoreIfNull(true);
cm.MapMember(a => a.LineStyle).SetIgnoreIfNull(true);
});
BsonClassMap.RegisterClassMap<SeriesConfigBase>(cm =>
{
cm.AutoMap();
// cm.SetIsRootClass(true);
// cm.AddKnownType(typeof(CandlestickSeriesConfig));
cm.MapMember(s => s.Key).SetElementName("key");
cm.MapMember(s => s.Color).SetElementName("color");
cm.MapMember(s => s.Type)
.SetDefaultValue(DisplayConfigEnums.SeriesType.Line)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.SeriesType>(BsonType.String));
cm.MapMember(s => s.SourceType)
.SetDefaultValue(DisplayConfigEnums.SourceType.Obs)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.SourceType>(BsonType.String));
cm.MapMember(s => s.AxesNames)
.SetDefaultValue(new List<string>());
cm.MapMember(s => s.ShowSymbol).SetDefaultValue(false);
cm.MapMember(s => s.ShowColorOnLegend).SetDefaultValue(false);
cm.MapMember(s => s.ShowOnLegend).SetDefaultValue(true);
cm.MapMember(s => s.Values)
.SetDefaultValue(GroupedObservationEnum.Result.Last)
.SetSerializer(new EnumSerializer<GroupedObservationEnum.Result>(BsonType.String));
cm.MapMember(s => s.MarkerIcon)
.SetDefaultValue(DisplayConfigEnums.MarkerIcon.None)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.MarkerIcon>(BsonType.String));
cm.MapMember(s => s.VisualMap).SetIgnoreIfNull(true);
cm.MapMember(l => l.LineWidth).SetDefaultValue(2);
cm.MapMember(l => l.LineStyle).SetDefaultValue("solid");
cm.MapMember(v => v.Marker)
.SetDefaultValue(DisplayConfigEnums.MarkerIcon.Kangaroo)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.MarkerIcon>(BsonType.String));
cm.MapMember(a => a.AboveBaselineColor).SetIgnoreIfDefault(true);
cm.MapMember(a => a.BelowBaselineColor).SetIgnoreIfDefault(true);
cm.MapMember(a => a.CandleKeyList).SetIgnoreIfDefault(true);
cm.MapMember(p => p.LineType).SetIgnoreIfNull(true);
});
// BsonClassMap.RegisterClassMap<LineSeriesConfig>(cm =>
// {
// cm.AutoMap();
// cm.MapMember(l => l.LineWidth).SetDefaultValue(2);
// cm.MapMember(l => l.LineStyle).SetDefaultValue("solid");
// });
// BsonClassMap.RegisterClassMap<VerticalMarkerSeriesConfig>(cm =>
// {
// cm.AutoMap();
// cm.MapMember(v => v.Marker)
// .SetDefaultValue(DisplayConfigEnums.MarkerIcon.Kangaroo)
// .SetSerializer(new EnumSerializer<DisplayConfigEnums.MarkerIcon>(BsonType.String));
// });
// BsonClassMap.RegisterClassMap<AreaSeriesConfig>(cm =>
// {
// cm.AutoMap();
// cm.MapMember(a => a.AboveBaselineColor).SetIgnoreIfDefault(true);
// cm.MapMember(a => a.BelowBaselineColor).SetIgnoreIfDefault(true);
// });
// BsonClassMap.RegisterClassMap<CandlestickSeriesConfig>(cm =>
// {
// cm.AutoMap();
// cm.MapMember(a => a.CandleKeyList).SetIgnoreIfDefault(true);
// });
// if (!BsonClassMap.IsClassMapRegistered(typeof(LineSeriesConfig)))
// {
// BsonClassMap.RegisterClassMap<LineSeriesConfig>(cm =>
// {
// cm.AutoMap();
// cm.MapMember(p => p.LineStyle).SetDefaultValue("solid");
// cm.MapMember(p => p.LineWidth).SetDefaultValue(2);
// cm.MapMember(p => p.LineType).SetIgnoreIfNull(true);
// });
// }
BsonClassMap.RegisterClassMap<Candle>(cm =>
{
cm.AutoMap();
cm.MapMember(a => a.Key).SetIgnoreIfDefault(true);
cm.MapMember(a => a.CandleValueType)
.SetSerializer(
new NullableSerializer<CandleValueType>(new EnumSerializer<CandleValueType>(BsonType.String)));
});
BsonClassMap.RegisterClassMap<VisualMap>(cm =>
{
cm.AutoMap();
cm.MapMember(v => v.Show).SetDefaultValue(false);
cm.MapMember(v => v.Dimension).SetDefaultValue(0);
cm.MapMember(v => v.SerieKey).SetIgnoreIfNull(true);
cm.MapMember(v => v.Pieces).SetDefaultValue(new List<Piece>());
});
BsonClassMap.RegisterClassMap<ChartConfig>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.BaseConfig).SetDefaultValue(new ChartBaseConfig());
cm.MapMember(c => c.AxesConfig).SetDefaultValue(new List<AxisConfig>());
cm.MapMember(c => c.SeriesConfig).SetDefaultValue(new List<SeriesConfigBase>());
});
BsonClassMap.RegisterClassMap<ChartBaseConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Title).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Top).SetDefaultValue("7%");
cm.MapMember(c => c.Right).SetDefaultValue("7%");
cm.MapMember(c => c.Bottom).SetDefaultValue("7%");
cm.MapMember(c => c.Left).SetDefaultValue("7%");
cm.MapMember(c => c.Group).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Name).SetDefaultValue(string.Empty);
cm.MapMember(c => c.BorderWidth).SetDefaultValue(1);
cm.MapMember(c => c.BorderColor).SetDefaultValue(string.Empty);
cm.MapMember(c => c.ShowLegend).SetDefaultValue(false);
cm.MapMember(c => c.ShowGrid).SetDefaultValue(false);
cm.MapMember(c => c.NumValues).SetIgnoreIfNull(true);
cm.MapMember(c => c.BaselineOffset).SetIgnoreIfNull(true);
});
BsonClassMap.RegisterClassMap<AxisConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(a => a.Type)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.AxisType>(BsonType.String))
.SetDefaultValue(DisplayConfigEnums.AxisType.Value);
cm.MapMember(a => a.KeyName).SetIgnoreIfNull(true);
cm.MapMember(a => a.Position)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.AxisPosition>(BsonType.String))
.SetDefaultValue(DisplayConfigEnums.AxisPosition.Left);
cm.MapMember(a => a.Min).SetIgnoreIfNull(true);
cm.MapMember(a => a.Max).SetIgnoreIfNull(true);
cm.MapMember(a => a.AxisLine).SetIgnoreIfNull(true);
cm.MapMember(a => a.AxisTick).SetIgnoreIfNull(true);
cm.MapMember(a => a.AxisLabel).SetIgnoreIfNull(true);
cm.MapMember(a => a.Silent).SetDefaultValue(true);
cm.MapMember(a => a.LabelFormat)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.LabelFormat>(BsonType.String))
.SetDefaultValue(DisplayConfigEnums.LabelFormat.Hour);
cm.MapMember(a => a.Offset).SetDefaultValue(0.0);
cm.MapMember(a => a.CustomLabels).SetDefaultValue(new List<string>());
cm.MapMember(a => a.SortLabels).SetDefaultValue(false);
cm.MapMember(a => a.Show).SetDefaultValue(true);
cm.MapMember(a => a.Regularity)
.SetSerializer(new EnumSerializer<GroupedObservationEnum.Regularity>(BsonType.String))
.SetDefaultValue(GroupedObservationEnum.Regularity.Hour);
});
BsonClassMap.RegisterClassMap<AxisTick>(cm =>
{
cm.AutoMap();
cm.MapMember(a => a.Show).SetIgnoreIfNull(true);
cm.MapMember(a => a.Interval).SetIgnoreIfNull(true);
cm.MapMember(a => a.Length).SetIgnoreIfNull(true);
});
BsonClassMap.RegisterClassMap<AxisLabel>(cm =>
{
cm.AutoMap();
cm.MapMember(a => a.Show).SetIgnoreIfNull(true);
cm.MapMember(a => a.Color).SetIgnoreIfNull(true);
cm.MapMember(a => a.Margin).SetIgnoreIfNull(true);
cm.MapMember(a => a.FontSize).SetIgnoreIfNull(true);
cm.MapMember(a => a.Silent).SetIgnoreIfNull(true);
});
BsonClassMap.RegisterClassMap<AxisLineStyle>(cm =>
{
cm.AutoMap();
cm.MapMember(a => a.Color).SetIgnoreIfNull(true);
});
BsonClassMap.RegisterClassMap<AxisLine>(cm =>
{
cm.AutoMap();
cm.MapMember(a => a.Show).SetIgnoreIfNull(true);
cm.MapMember(a => a.LineStyle).SetIgnoreIfNull(true);
});
BsonClassMap.RegisterClassMap<SeriesConfigBase>(cm =>
{
cm.AutoMap();
// cm.SetIsRootClass(true);
// cm.AddKnownType(typeof(CandlestickSeriesConfig));
cm.MapMember(s => s.Key).SetElementName("key");
cm.MapMember(s => s.Color).SetElementName("color");
cm.MapMember(s => s.Type)
.SetDefaultValue(DisplayConfigEnums.SeriesType.Line)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.SeriesType>(BsonType.String));
cm.MapMember(s => s.SourceType)
.SetDefaultValue(DisplayConfigEnums.SourceType.Obs)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.SourceType>(BsonType.String));
cm.MapMember(s => s.AxesNames)
.SetDefaultValue(new List<string>());
cm.MapMember(s => s.ShowSymbol).SetDefaultValue(false);
cm.MapMember(s => s.ShowColorOnLegend).SetDefaultValue(false);
cm.MapMember(s => s.ShowOnLegend).SetDefaultValue(true);
cm.MapMember(s => s.Values)
.SetDefaultValue(GroupedObservationEnum.Result.Last)
.SetSerializer(new EnumSerializer<GroupedObservationEnum.Result>(BsonType.String));
cm.MapMember(s => s.MarkerIcon)
.SetDefaultValue(DisplayConfigEnums.MarkerIcon.None)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.MarkerIcon>(BsonType.String));
cm.MapMember(s => s.VisualMap).SetIgnoreIfNull(true);
cm.MapMember(l => l.LineWidth).SetDefaultValue(2);
cm.MapMember(l => l.LineStyle).SetDefaultValue("solid");
cm.MapMember(v => v.Marker)
.SetDefaultValue(DisplayConfigEnums.MarkerIcon.Kangaroo)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.MarkerIcon>(BsonType.String));
cm.MapMember(a => a.AboveBaselineColor).SetIgnoreIfDefault(true);
cm.MapMember(a => a.BelowBaselineColor).SetIgnoreIfDefault(true);
cm.MapMember(a => a.CandleKeyList).SetIgnoreIfDefault(true);
cm.MapMember(p => p.LineType).SetIgnoreIfNull(true);
});
// BsonClassMap.RegisterClassMap<LineSeriesConfig>(cm =>
// {
// cm.AutoMap();
// cm.MapMember(l => l.LineWidth).SetDefaultValue(2);
// cm.MapMember(l => l.LineStyle).SetDefaultValue("solid");
// });
// BsonClassMap.RegisterClassMap<VerticalMarkerSeriesConfig>(cm =>
// {
// cm.AutoMap();
// cm.MapMember(v => v.Marker)
// .SetDefaultValue(DisplayConfigEnums.MarkerIcon.Kangaroo)
// .SetSerializer(new EnumSerializer<DisplayConfigEnums.MarkerIcon>(BsonType.String));
// });
// BsonClassMap.RegisterClassMap<AreaSeriesConfig>(cm =>
// {
// cm.AutoMap();
// cm.MapMember(a => a.AboveBaselineColor).SetIgnoreIfDefault(true);
// cm.MapMember(a => a.BelowBaselineColor).SetIgnoreIfDefault(true);
// });
// BsonClassMap.RegisterClassMap<CandlestickSeriesConfig>(cm =>
// {
// cm.AutoMap();
// cm.MapMember(a => a.CandleKeyList).SetIgnoreIfDefault(true);
// });
// if (!BsonClassMap.IsClassMapRegistered(typeof(LineSeriesConfig)))
// {
// BsonClassMap.RegisterClassMap<LineSeriesConfig>(cm =>
// {
// cm.AutoMap();
// cm.MapMember(p => p.LineStyle).SetDefaultValue("solid");
// cm.MapMember(p => p.LineWidth).SetDefaultValue(2);
// cm.MapMember(p => p.LineType).SetIgnoreIfNull(true);
// });
// }
BsonClassMap.RegisterClassMap<Candle>(cm =>
{
cm.AutoMap();
cm.MapMember(a => a.Key).SetIgnoreIfDefault(true);
cm.MapMember(a => a.CandleValueType)
.SetSerializer(
new NullableSerializer<CandleValueType>(new EnumSerializer<CandleValueType>(BsonType.String)));
});
BsonClassMap.RegisterClassMap<VisualMap>(cm =>
{
cm.AutoMap();
cm.MapMember(v => v.Show).SetDefaultValue(false);
cm.MapMember(v => v.Dimension).SetDefaultValue(0);
cm.MapMember(v => v.SerieKey).SetIgnoreIfNull(true);
cm.MapMember(v => v.Pieces).SetDefaultValue(new List<Piece>());
});
}
if (!BsonClassMap.IsClassMapRegistered(typeof(LegendLayoutConfig)))
BsonClassMap.RegisterClassMap<LegendLayoutConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Rows).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(LegendLayoutRow)))
BsonClassMap.RegisterClassMap<LegendLayoutRow>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.GrowPriority).SetIgnoreIfNull(true);
cm.MapMember(c => c.Columns).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(LegendLayoutColumn)))
BsonClassMap.RegisterClassMap<LegendLayoutColumn>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Key).SetIgnoreIfNull(true);
cm.MapMember(c => c.GrowPriority).SetIgnoreIfNull(true);
cm.MapMember(c => c.Label).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(LegendLabel)))
BsonClassMap.RegisterClassMap<LegendLabel>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.ColorLabel).SetIgnoreIfNull(true);
cm.MapMember(c => c.ColorIcon).SetIgnoreIfNull(true);
cm.MapMember(c => c.ShowSymbol).SetIgnoreIfNull(true);
cm.MapMember(c => c.IconType).SetIgnoreIfNull(true)
.SetSerializer(new NullableSerializer<DisplayConfigEnums.ELegendIconType>(
new EnumSerializer<DisplayConfigEnums.ELegendIconType>(BsonType.String)));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Piece)))
BsonClassMap.RegisterClassMap<Piece>(cm =>
{
cm.AutoMap();
cm.MapMember(p => p.Opacity).SetIgnoreIfNull(true);
cm.MapMember(p => p.LineType)
.SetDefaultValue(DisplayConfigEnums.LineType.Solid)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.LineType>(BsonType.String));
cm.MapMember(p => p.Color).SetIgnoreIfNull(true);
cm.MapMember(p => p.Symbol).SetIgnoreIfNull(true);
cm.MapMember(p => p.SymbolSize).SetIgnoreIfNull(true);
cm.MapMember(p => p.Eq).SetIgnoreIfNull(true);
cm.MapMember(p => p.Neq).SetIgnoreIfNull(true);
cm.MapMember(p => p.Gt).SetIgnoreIfNull(true);
cm.MapMember(p => p.Lt).SetIgnoreIfNull(true);
cm.MapMember(p => p.Gte).SetIgnoreIfNull(true);
cm.MapMember(p => p.Lte).SetIgnoreIfNull(true);
});
}
if (!BsonClassMap.IsClassMapRegistered(typeof(LegendLayoutConfig)))
BsonClassMap.RegisterClassMap<LegendLayoutConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Rows).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(LegendLayoutRow)))
BsonClassMap.RegisterClassMap<LegendLayoutRow>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.GrowPriority).SetIgnoreIfNull(true);
cm.MapMember(c => c.Columns).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(LegendLayoutColumn)))
BsonClassMap.RegisterClassMap<LegendLayoutColumn>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Key).SetIgnoreIfNull(true);
cm.MapMember(c => c.GrowPriority).SetIgnoreIfNull(true);
cm.MapMember(c => c.Label).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(LegendLabel)))
BsonClassMap.RegisterClassMap<LegendLabel>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.ColorLabel).SetIgnoreIfNull(true);
cm.MapMember(c => c.ColorIcon).SetIgnoreIfNull(true);
cm.MapMember(c => c.ShowSymbol).SetIgnoreIfNull(true);
cm.MapMember(c => c.IconType).SetIgnoreIfNull(true)
.SetSerializer(new NullableSerializer<DisplayConfigEnums.ELegendIconType>(
new EnumSerializer<DisplayConfigEnums.ELegendIconType>(BsonType.String)));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Piece)))
BsonClassMap.RegisterClassMap<Piece>(cm =>
{
cm.AutoMap();
cm.MapMember(p => p.Opacity).SetIgnoreIfNull(true);
cm.MapMember(p => p.LineType)
.SetDefaultValue(DisplayConfigEnums.LineType.Solid)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.LineType>(BsonType.String));
cm.MapMember(p => p.Color).SetIgnoreIfNull(true);
cm.MapMember(p => p.Symbol).SetIgnoreIfNull(true);
cm.MapMember(p => p.SymbolSize).SetIgnoreIfNull(true);
cm.MapMember(p => p.Eq).SetIgnoreIfNull(true);
cm.MapMember(p => p.Neq).SetIgnoreIfNull(true);
cm.MapMember(p => p.Gt).SetIgnoreIfNull(true);
cm.MapMember(p => p.Lt).SetIgnoreIfNull(true);
cm.MapMember(p => p.Gte).SetIgnoreIfNull(true);
cm.MapMember(p => p.Lte).SetIgnoreIfNull(true);
});
}
}
@@ -4,36 +4,45 @@ using MongoDB.Bson.Serialization;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents a contributor that participates in entity mapping, providing header-related mapping logic.
/// </summary>
/// <remarks>
/// Implements the <see cref="IEntityMapContributor"/> interface to contribute mapping behavior within the entity mapping pipeline.
/// </remarks>
public class HeaderMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for the <see cref="HeaderConfig"/> type and its nested <see cref="HeaderConfig.HeaderItem"/> type with the MongoDB BsonClassMap serializer, configuring default values and null-ignore behavior for their members. Registration is performed only if a class map for the corresponding type has not already been registered, preventing duplicate registrations.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(HeaderConfig)))
BsonClassMap.RegisterClassMap<HeaderConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.PartnerLogo).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
cm.MapMember(c => c.CompanyLogo).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
cm.MapMember(c => c.CenterLogo).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
cm.MapMember(c => c.MeddisLogo).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
cm.MapMember(c => c.UnitName).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
cm.MapMember(c => c.Cameras).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
cm.MapMember(c => c.Sensors).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
cm.MapMember(c => c.Fullscreen).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
cm.MapMember(c => c.Sounds).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
cm.MapMember(c => c.Sidebar).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
cm.MapMember(c => c.CurrentDateTime).SetDefaultValue(new HeaderConfig.HeaderItem())
.SetIgnoreIfNull(true);
cm.MapMember(c => c.SectionTitle).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(HeaderConfig.HeaderItem)))
BsonClassMap.RegisterClassMap<HeaderConfig.HeaderItem>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.LogoUrl).SetDefaultValue(() => null)
.SetIgnoreIfNull(true);
cm.MapMember(c => c.IsVisible).SetDefaultValue(true);
});
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(HeaderConfig)))
BsonClassMap.RegisterClassMap<HeaderConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.PartnerLogo).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
cm.MapMember(c => c.CompanyLogo).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
cm.MapMember(c => c.CenterLogo).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
cm.MapMember(c => c.MeddisLogo).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
cm.MapMember(c => c.UnitName).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
cm.MapMember(c => c.Cameras).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
cm.MapMember(c => c.Sensors).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
cm.MapMember(c => c.Fullscreen).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
cm.MapMember(c => c.Sounds).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
cm.MapMember(c => c.Sidebar).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
cm.MapMember(c => c.CurrentDateTime).SetDefaultValue(new HeaderConfig.HeaderItem())
.SetIgnoreIfNull(true);
cm.MapMember(c => c.SectionTitle).SetDefaultValue(new HeaderConfig.HeaderItem()).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(HeaderConfig.HeaderItem)))
BsonClassMap.RegisterClassMap<HeaderConfig.HeaderItem>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.LogoUrl).SetDefaultValue(() => null)
.SetIgnoreIfNull(true);
cm.MapMember(c => c.IsVisible).SetDefaultValue(true);
});
}
}
@@ -5,5 +5,8 @@
/// </summary>
public interface IEntityMapContributor
{
/// <summary>
/// Registers the object-to-object mappings used by the application.
/// </summary>
void RegisterMaps();
}
@@ -7,152 +7,161 @@ using MongoDB.Bson.Serialization.Serializers;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents a map contributor for the master list entity, providing mapping logic through the <see cref="IEntityMapContributor"/> contract.
/// </summary>
/// <remarks>
/// Use this contributor when defining or extending the mapping configuration associated with master list entities.
/// </remarks>
public class MasterListMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for domain types (such as <see cref="Element"/>, <see cref="OptionList"/>, <see cref="MasterList"/>, <see cref="Locale"/>, and their derived option lists) to control their MongoDB serialization. Each registration is performed only when no class map already exists for the type, and applies domain-specific settings including default values, null-ignore rules, discriminator naming, and custom serializers for enums, locale fields, and identifiers.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(Element)))
BsonClassMap.RegisterClassMap<Element>(cm =>
{
cm.AutoMap();
cm.GetMemberMap(c => c.Title).SetDefaultValue(string.Empty);
cm.GetMemberMap(c => c.IsRequired).SetDefaultValue(false);
cm.GetMemberMap(c => c.IsList).SetDefaultValue(false);
cm.GetMemberMap(c => c.ListName).SetDefaultValue(string.Empty);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(OptionListDetails)))
BsonClassMap.RegisterClassMap<OptionListDetails>(cm =>
{
cm.AutoMap();
cm.GetMemberMap(c => c.OptionType).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Name).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.IconDefault).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.IconCategory).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.IconColor).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Color).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.BgColor).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Description).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(MasterList)))
BsonClassMap.RegisterClassMap<MasterList>(cm =>
{
cm.AutoMap();
cm.UnmapMember(c => c.ManualObservationName);
cm.UnmapMember(c => c.AutoObservationName);
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.GetMemberMap(c => c.DefaultLocale).SetIgnoreIfNull(true).SetSerializer(
new NullableSerializer<LocaleEnum>(new EnumSerializer<LocaleEnum>(BsonType.String))
);
cm.GetMemberMap(c => c.Name).SetDefaultValue(string.Empty);
cm.GetMemberMap(c => c.Description).SetDefaultValue(string.Empty);
cm.GetMemberMap(c => c.OptionListDetails).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.CanAddElement).SetDefaultValue(false);
cm.MapMember(c => c.ListType)
.SetSerializer(new EnumSerializer<MasterListType>(BsonType.String));
cm.MapMember(c => c.Options).SetDefaultValue(new List<OptionList>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(LocaleItem)))
BsonClassMap.RegisterClassMap<LocaleItem>(cm =>
{
cm.AutoMap();
cm.GetMemberMap(c => c.Name).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Locale)))
BsonClassMap.RegisterClassMap<Locale>(cm =>
{
cm.AutoMap();
cm.GetMemberMap(c => c.Es).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Pt).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Eng).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Ca).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Zh).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(OptionList)))
BsonClassMap.RegisterClassMap<OptionList>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id").SetSerializer(new NullableSerializer<ObjectId>(new ObjectIdSerializer(BsonType.ObjectId)));;
cm.GetMemberMap(c => c.OptionType).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Name).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.LocaleItems).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.IconDefault).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.IconCategory).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.IconColor).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Color).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.BgColor).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Description).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.InitDate).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.EndDate).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.IsDefault).SetIgnoreIfNull(true);
cm.SetDiscriminator(nameof(OptionList));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(AltableOptionList)))
BsonClassMap.RegisterClassMap<AltableOptionList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(VisitOptionList)))
BsonClassMap.RegisterClassMap<VisitOptionList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(AccessControlList)))
BsonClassMap.RegisterClassMap<AccessControlList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(MobilityOptionList)))
BsonClassMap.RegisterClassMap<MobilityOptionList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(TherapeuticCeilingList)))
BsonClassMap.RegisterClassMap<TherapeuticCeilingList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(PassiveSittingList)))
BsonClassMap.RegisterClassMap<PassiveSittingList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(GenericList)))
BsonClassMap.RegisterClassMap<GenericList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(ProcedureList)))
BsonClassMap.RegisterClassMap<ProcedureList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(TestList)))
BsonClassMap.RegisterClassMap<TestList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(DiagnosisList)))
BsonClassMap.RegisterClassMap<DiagnosisList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(OriginList)))
BsonClassMap.RegisterClassMap<OriginList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(DestinationList)))
BsonClassMap.RegisterClassMap<DestinationList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(InternalDestinationList)))
BsonClassMap.RegisterClassMap<InternalDestinationList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(TreatmentList)))
BsonClassMap.RegisterClassMap<TreatmentList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientStatusList)))
BsonClassMap.RegisterClassMap<PatientStatusList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(ServiceList)))
BsonClassMap.RegisterClassMap<ServiceList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(AllergyList)))
BsonClassMap.RegisterClassMap<AllergyList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(DoctorTypeList)))
BsonClassMap.RegisterClassMap<DoctorTypeList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(DoctorList)))
BsonClassMap.RegisterClassMap<DoctorList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(InsulationList)))
BsonClassMap.RegisterClassMap<InsulationList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(DischargeStatusList)))
BsonClassMap.RegisterClassMap<DischargeStatusList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(LanguageBarrierList)))
BsonClassMap.RegisterClassMap<LanguageBarrierList>(cm => { cm.AutoMap(); });
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(Element)))
BsonClassMap.RegisterClassMap<Element>(cm =>
{
cm.AutoMap();
cm.GetMemberMap(c => c.Title).SetDefaultValue(string.Empty);
cm.GetMemberMap(c => c.IsRequired).SetDefaultValue(false);
cm.GetMemberMap(c => c.IsList).SetDefaultValue(false);
cm.GetMemberMap(c => c.ListName).SetDefaultValue(string.Empty);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(OptionListDetails)))
BsonClassMap.RegisterClassMap<OptionListDetails>(cm =>
{
cm.AutoMap();
cm.GetMemberMap(c => c.OptionType).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Name).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.IconDefault).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.IconCategory).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.IconColor).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Color).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.BgColor).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Description).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(MasterList)))
BsonClassMap.RegisterClassMap<MasterList>(cm =>
{
cm.AutoMap();
cm.UnmapMember(c => c.ManualObservationName);
cm.UnmapMember(c => c.AutoObservationName);
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.GetMemberMap(c => c.DefaultLocale).SetIgnoreIfNull(true).SetSerializer(
new NullableSerializer<LocaleEnum>(new EnumSerializer<LocaleEnum>(BsonType.String))
);
cm.GetMemberMap(c => c.Name).SetDefaultValue(string.Empty);
cm.GetMemberMap(c => c.Description).SetDefaultValue(string.Empty);
cm.GetMemberMap(c => c.OptionListDetails).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.CanAddElement).SetDefaultValue(false);
cm.MapMember(c => c.ListType)
.SetSerializer(new EnumSerializer<MasterListType>(BsonType.String));
cm.MapMember(c => c.Options).SetDefaultValue(new List<OptionList>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(LocaleItem)))
BsonClassMap.RegisterClassMap<LocaleItem>(cm =>
{
cm.AutoMap();
cm.GetMemberMap(c => c.Name).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Locale)))
BsonClassMap.RegisterClassMap<Locale>(cm =>
{
cm.AutoMap();
cm.GetMemberMap(c => c.Es).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Pt).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Eng).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Ca).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Zh).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(OptionList)))
BsonClassMap.RegisterClassMap<OptionList>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id").SetSerializer(new NullableSerializer<ObjectId>(new ObjectIdSerializer(BsonType.ObjectId)));;
cm.GetMemberMap(c => c.OptionType).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Name).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.LocaleItems).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.IconDefault).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.IconCategory).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.IconColor).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Color).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.BgColor).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Description).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.InitDate).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.EndDate).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.IsDefault).SetIgnoreIfNull(true);
cm.SetDiscriminator(nameof(OptionList));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(AltableOptionList)))
BsonClassMap.RegisterClassMap<AltableOptionList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(VisitOptionList)))
BsonClassMap.RegisterClassMap<VisitOptionList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(AccessControlList)))
BsonClassMap.RegisterClassMap<AccessControlList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(MobilityOptionList)))
BsonClassMap.RegisterClassMap<MobilityOptionList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(TherapeuticCeilingList)))
BsonClassMap.RegisterClassMap<TherapeuticCeilingList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(PassiveSittingList)))
BsonClassMap.RegisterClassMap<PassiveSittingList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(GenericList)))
BsonClassMap.RegisterClassMap<GenericList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(ProcedureList)))
BsonClassMap.RegisterClassMap<ProcedureList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(TestList)))
BsonClassMap.RegisterClassMap<TestList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(DiagnosisList)))
BsonClassMap.RegisterClassMap<DiagnosisList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(OriginList)))
BsonClassMap.RegisterClassMap<OriginList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(DestinationList)))
BsonClassMap.RegisterClassMap<DestinationList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(InternalDestinationList)))
BsonClassMap.RegisterClassMap<InternalDestinationList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(TreatmentList)))
BsonClassMap.RegisterClassMap<TreatmentList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientStatusList)))
BsonClassMap.RegisterClassMap<PatientStatusList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(ServiceList)))
BsonClassMap.RegisterClassMap<ServiceList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(AllergyList)))
BsonClassMap.RegisterClassMap<AllergyList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(DoctorTypeList)))
BsonClassMap.RegisterClassMap<DoctorTypeList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(DoctorList)))
BsonClassMap.RegisterClassMap<DoctorList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(InsulationList)))
BsonClassMap.RegisterClassMap<InsulationList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(DischargeStatusList)))
BsonClassMap.RegisterClassMap<DischargeStatusList>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(LanguageBarrierList)))
BsonClassMap.RegisterClassMap<LanguageBarrierList>(cm => { cm.AutoMap(); });
}
}
@@ -4,25 +4,34 @@ using MongoDB.Bson.Serialization;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents a map contributor responsible for contributing mapping logic for notice control entities.
/// </summary>
/// <remarks>
/// Implements the <see cref="IEntityMapContributor"/> interface to participate in the entity mapping process.
/// </remarks>
public class NoticeControlMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for the <see cref="Notice"/>, <see cref="StaffInfo"/>, and <see cref="MedicalStaffConfig"/> types if they have not already been registered, configuring element names, automatic mapping, and null-ignore behavior where applicable.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(Notice)))
BsonClassMap.RegisterClassMap<Notice>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
});
if (!BsonClassMap.IsClassMapRegistered(typeof(StaffInfo)))
BsonClassMap.RegisterClassMap<StaffInfo>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(MedicalStaffConfig)))
BsonClassMap.RegisterClassMap<MedicalStaffConfig>(cm =>
{
cm.MapMember(c => c.HasTeams).SetIgnoreIfNull(true);
cm.MapMember(c => c.StaffAmount).SetIgnoreIfNull(true);
});
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(Notice)))
BsonClassMap.RegisterClassMap<Notice>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
});
if (!BsonClassMap.IsClassMapRegistered(typeof(StaffInfo)))
BsonClassMap.RegisterClassMap<StaffInfo>(cm => { cm.AutoMap(); });
if (!BsonClassMap.IsClassMapRegistered(typeof(MedicalStaffConfig)))
BsonClassMap.RegisterClassMap<MedicalStaffConfig>(cm =>
{
cm.MapMember(c => c.HasTeams).SetIgnoreIfNull(true);
cm.MapMember(c => c.StaffAmount).SetIgnoreIfNull(true);
});
}
}
@@ -4,24 +4,33 @@ using MongoDB.Bson.Serialization;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents a contributor that participates in entity mapping for observation units by implementing the <see cref="IEntityMapContributor"/> contract.
/// </summary>
/// <remarks>
/// Used to register or supply mapping logic specific to observation unit entities within the entity mapping framework.
/// </remarks>
public class ObsUnitMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for <see cref="ConfigUnits"/> and <see cref="ConfigUnitItem"/> types if they have not already been registered, configuring how their members are serialized to MongoDB documents.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(ConfigUnits)))
BsonClassMap.RegisterClassMap<ConfigUnits>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id").SetDefaultValue(string.Empty);
cm.MapMember(c => c.Items).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ConfigUnitItem)))
BsonClassMap.RegisterClassMap<ConfigUnitItem>(cm =>
{
cm.AutoMap();
cm.GetMemberMap(c => c.Code).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Value).SetIgnoreIfNull(true);
});
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(ConfigUnits)))
BsonClassMap.RegisterClassMap<ConfigUnits>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id").SetDefaultValue(string.Empty);
cm.MapMember(c => c.Items).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ConfigUnitItem)))
BsonClassMap.RegisterClassMap<ConfigUnitItem>(cm =>
{
cm.AutoMap();
cm.GetMemberMap(c => c.Code).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Value).SetIgnoreIfNull(true);
});
}
}
@@ -11,279 +11,288 @@ using static adas_core.Domain.Models.GroupedObservation;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents a contributor that provides entity mapping logic for observations.
/// </summary>
/// <remarks>
/// Implements the <see cref="IEntityMapContributor"/> interface to participate in the entity mapping process.
/// </remarks>
public class ObservationMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for the observation-related domain types used by the application. Each registration is guarded by an <see cref="BsonClassMap.IsClassMapRegistered"/> check so the method is safe to invoke multiple times, configuring element names, ignoring null members, unmapping non-persisted members, and supplying custom enum and complex-object serializers where required.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(BasePatientObservation)))
BsonClassMap.RegisterClassMap<BasePatientObservation>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.PatientId).SetElementName("patientid");
cm.MapMember(c => c.UserId).SetIgnoreIfNull(true);
cm.MapMember(c => c.ClinicalEpisode).SetIgnoreIfNull(true);
cm.UnmapMember(c => c.Patient);
cm.MapMember(c => c.SystemId).SetIgnoreIfNull(true);
cm.MapMember(c => c.Code).SetIgnoreIfNull(true);
cm.MapMember(c => c.CodingSystem).SetIgnoreIfNull(true);
cm.MapMember(c => c.ParentData).SetIgnoreIfNull(true);
cm.MapMember(c => c.Time);
cm.MapMember(c => c.EndTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.Units).SetIgnoreIfNull(true);
cm.UnmapMember(c => c.CheckObservations);
cm.UnmapMember(c => c.CreateObservation);
cm.MapMember(c => c.PatientId).SetElementName("patientid");
cm.MapMember(c => c.EndTime).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(BasePatientObservationValue)))
BsonClassMap.RegisterClassMap<BasePatientObservationValue>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Value)
.SetDefaultValue(new object())
.SetSerializer(new ComplexObjectValueTypeSerializer());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientObservation)))
BsonClassMap.RegisterClassMap<PatientObservation>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Max).SetIgnoreIfNull(true);
cm.MapMember(c => c.Min).SetIgnoreIfNull(true);
cm.MapMember(c => c.MaxWarn).SetIgnoreIfNull(true);
cm.MapMember(c => c.MinWarn).SetIgnoreIfNull(true);
cm.MapMember(c => c.WarnColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.AlertColor).SetIgnoreIfNull(true);
cm.UnmapMember(c => c.ShowOnExpired);
cm.MapMember(c => c.InsertMode)
.SetDefaultValue(ObservationEnum.InsertMode.Auto)
.SetSerializer(new EnumSerializer<ObservationEnum.InsertMode>(BsonType.String));
cm.UnmapMember(c => c.Persist);
cm.MapMember(c => c.ColorOnExpired).SetIgnoreIfNull(true);
cm.UnmapMember(c => c.MessageTime);
cm.MapMember(c => c.Result).SetIgnoreIfNull(true);
cm.MapMember(c => c.Status)
.SetDefaultValue(StatusEnum.Type.Ok)
.SetSerializer(new EnumSerializer<StatusEnum.Type>(BsonType.String));
cm.UnmapMember(c => c.Level);
cm.UnmapMember(c => c.Expires);
cm.MapMember(c => c.Expired).SetDefaultValue(false);
cm.UnmapMember(c => c.UiConfiguration);
cm.UnmapMember(c => c.Alarm);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ParentDataClass)))
BsonClassMap.RegisterClassMap<ParentDataClass>(cm =>
{
cm.AutoMap();
cm.GetMemberMap(c => c.Code).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.CodingSystem).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Name).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ObservationData)))
BsonClassMap.RegisterClassMap<ObservationData>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.CodingSystem).SetIgnoreIfNull(true);
cm.MapMember(c => c.Code).SetIgnoreIfNull(true);
cm.MapMember(c => c.Text).SetIgnoreIfNull(true);
cm.MapMember(c => c.Time).SetIgnoreIfNull(true);
cm.MapMember(c => c.Value).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ObservationsRequest)))
BsonClassMap.RegisterClassMap<ObservationsRequest>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.PatientNumber).SetIgnoreIfNull(true);
cm.MapMember(c => c.ObservationNames).SetIgnoreIfNull(true);
cm.MapMember(c => c.StartTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.EndTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.PageNumber).SetDefaultValue(1);
cm.MapMember(c => c.PageSize).SetDefaultValue(10);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(DemoConfig)))
BsonClassMap.RegisterClassMap<DemoConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.ValueOption)
.SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ConfigObservation)))
BsonClassMap.RegisterClassMap<ConfigObservation>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.DemoConfig).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ConfigObservation)))
BsonClassMap.RegisterClassMap<ConfigObservation>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Code).SetIgnoreIfNull(true);
cm.MapMember(c => c.CodingSystem).SetIgnoreIfNull(true);
cm.MapMember(c => c.OriginalName).SetIgnoreIfNull(true);
cm.MapMember(c => c.ParentCode).SetIgnoreIfNull(true);
cm.MapMember(c => c.ParentCodingSystem).SetIgnoreIfNull(true);
cm.MapMember(c => c.ParentName).SetIgnoreIfNull(true);
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.Units).SetIgnoreIfNull(true);
cm.MapMember(c => c.ArrowType)
.SetSerializer(new EnumSerializer<ObservationEnum.ArrowType>(BsonType.String))
.SetDefaultValue(ObservationEnum.ArrowType.Default);
cm.MapMember(c => c.ShowArrow).SetDefaultValue(true);
cm.MapMember(c => c.ShowValue).SetDefaultValue(true);
cm.MapMember(c => c.ForceUnits).SetDefaultValue(false);
cm.MapMember(c => c.MinAlert).SetIgnoreIfNull(true);
cm.MapMember(c => c.MaxAlert).SetIgnoreIfNull(true);
cm.MapMember(c => c.MaxWarn).SetIgnoreIfNull(true);
cm.MapMember(c => c.MinWarn).SetIgnoreIfNull(true);
cm.MapMember(c => c.WarnColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.AlertColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.AlertValues).SetIgnoreIfNull(true);
cm.MapMember(c => c.WarningValues).SetIgnoreIfNull(true);
cm.MapMember(c => c.ForceWarn).SetDefaultValue(false);
cm.MapMember(c => c.ForceAlert).SetDefaultValue(false);
cm.MapMember(c => c.Alert).SetIgnoreIfNull(true);
cm.MapMember(c => c.Expires).SetIgnoreIfNull(true);
cm.MapMember(c => c.ShowOnExpired).SetIgnoreIfDefault(true);
cm.MapMember(c => c.Persist).SetIgnoreIfNull(true);
cm.MapMember(c => c.ColorOnExpired).SetIgnoreIfNull(true);
cm.MapMember(c => c.RetentionPolicy)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<RetentionPolicy>(new EnumSerializer<RetentionPolicy>(BsonType.String)));
cm.MapMember(c => c.RetentionPolicyValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.LevelCondition).SetIgnoreIfNull(true);
cm.MapMember(c => c.Grouped).SetIgnoreIfNull(true);
cm.MapMember(c => c.UiConfiguration).SetIgnoreIfNull(true);
cm.MapMember(c => c.Alarm).SetIgnoreIfNull(true);
cm.MapMember(c => c.Description).SetIgnoreIfNull(true);
cm.MapMember(c => c.RequiredValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.Preconditions).SetIgnoreIfNull(true);
cm.MapMember(c => c.CheckObservations).SetDefaultValue(false);
cm.MapMember(c => c.CreateObservation).SetIgnoreIfNull(true);
cm.MapMember(c => c.InsertMode)
.SetDefaultValue(ObservationEnum.InsertMode.Auto)
.SetSerializer(new EnumSerializer<ObservationEnum.InsertMode>(BsonType.String));
cm.MapMember(c => c.TimeFromMessageTime).SetIgnoreIfDefault(true);
cm.MapMember(c => c.ColorRanges).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ConfigObservation.ColorRange)))
BsonClassMap.RegisterClassMap<ConfigObservation.ColorRange>(cm =>
{
cm.AutoMap();
cm.MapMember(s => s.ValueType)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<ObservationEnum.ValueType>(
new EnumSerializer<ObservationEnum.ValueType>(BsonType.String)));
cm.MapMember(s => s.Min).SetIgnoreIfNull(true);
cm.MapMember(s => s.Max).SetIgnoreIfNull(true);
cm.MapMember(s => s.MatchText).SetIgnoreIfNull(true);
cm.MapMember(s => s.MatchBoolean).SetIgnoreIfNull(true);
cm.MapMember(s => s.MinDate).SetIgnoreIfNull(true);
cm.MapMember(s => s.MaxDate).SetIgnoreIfNull(true);
cm.MapMember(s => s.Color).SetIgnoreIfNull(true);
cm.MapMember(s => s.Label).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(GroupedObservation)))
BsonClassMap.RegisterClassMap<GroupedObservation>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.PatientId);
cm.MapMember(c => c.Name).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Group).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Observations).SetDefaultValue(new List<GroupedObservationObs>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(GroupedObservationObs)))
BsonClassMap.RegisterClassMap<GroupedObservationObs>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Name).SetDefaultValue(string.Empty);
cm.MapMember(c => c.First).SetIgnoreIfNull(true);
cm.MapMember(c => c.Last).SetIgnoreIfNull(true);
cm.MapMember(c => c.Min).SetIgnoreIfNull(true);
cm.MapMember(c => c.Max).SetIgnoreIfNull(true);
cm.MapMember(c => c.MinAlert).SetIgnoreIfNull(true);
cm.MapMember(c => c.MaxAlert).SetIgnoreIfNull(true);
cm.MapMember(c => c.Average).SetIgnoreIfNull(true);
cm.MapMember(c => c.Sum).SetIgnoreIfNull(true);
cm.MapMember(c => c.Count).SetIgnoreIfNull(true);
cm.MapMember(c => c.HalfHour).SetIgnoreIfNull(true);
cm.MapMember(c => c.LastFilled).SetIgnoreIfNull(true);
cm.MapMember(c => c.Time);
cm.MapMember(c => c.Shift).SetIgnoreIfNull(true);
cm.MapMember(c => c.ShiftDate);
cm.MapMember(c => c.IsFilled);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(GroupedObservationObsValue)))
BsonClassMap.RegisterClassMap<GroupedObservationObsValue>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Type)
.SetDefaultValue(StatusEnum.Type.Ok)
.SetSerializer(new EnumSerializer<StatusEnum.Type>(BsonType.String));
cm.MapMember(c => c.Value);
cm.MapMember(c => c.Time).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PredictMedicationObservation)))
BsonClassMap.RegisterClassMap<PredictMedicationObservation>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.MedicineText).SetDefaultValue(string.Empty);
cm.MapMember(c => c.DegreeSimilarity);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ObservationTextRule)))
BsonClassMap.RegisterClassMap<ObservationTextRule>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.ValueType)
.SetDefaultValue(ObservationEnum.ValueType.String)
.SetSerializer(new EnumSerializer<ObservationEnum.ValueType>(BsonType.String));
cm.MapMember(c => c.Value).SetIgnoreIfNull(true);
cm.MapMember(c => c.MatchText).SetIgnoreIfNull(true);
cm.MapMember(c => c.MatchBoolean).SetIgnoreIfNull(true);
cm.MapMember(c => c.MatchNumber).SetIgnoreIfNull(true);
cm.MapMember(c => c.MatchDate).SetIgnoreIfNull(true);
cm.MapMember(c => c.Label).SetIgnoreIfNull(true);
cm.MapMember(c => c.MinDate).SetIgnoreIfNull(true);
cm.MapMember(c => c.MaxDate).SetIgnoreIfNull(true);
cm.MapMember(c => c.MinNum).SetIgnoreIfNull(true);
cm.MapMember(c => c.MaxNum).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ConditionsConfig)))
BsonClassMap.RegisterClassMap<ConditionsConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Condition).SetIgnoreIfNull(true);
cm.MapMember(c => c.FieldCondition).SetIgnoreIfNull(true);
});
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(BasePatientObservation)))
BsonClassMap.RegisterClassMap<BasePatientObservation>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.PatientId).SetElementName("patientid");
cm.MapMember(c => c.UserId).SetIgnoreIfNull(true);
cm.MapMember(c => c.ClinicalEpisode).SetIgnoreIfNull(true);
cm.UnmapMember(c => c.Patient);
cm.MapMember(c => c.SystemId).SetIgnoreIfNull(true);
cm.MapMember(c => c.Code).SetIgnoreIfNull(true);
cm.MapMember(c => c.CodingSystem).SetIgnoreIfNull(true);
cm.MapMember(c => c.ParentData).SetIgnoreIfNull(true);
cm.MapMember(c => c.Time);
cm.MapMember(c => c.EndTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.Units).SetIgnoreIfNull(true);
cm.UnmapMember(c => c.CheckObservations);
cm.UnmapMember(c => c.CreateObservation);
cm.MapMember(c => c.PatientId).SetElementName("patientid");
cm.MapMember(c => c.EndTime).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(BasePatientObservationValue)))
BsonClassMap.RegisterClassMap<BasePatientObservationValue>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Value)
.SetDefaultValue(new object())
.SetSerializer(new ComplexObjectValueTypeSerializer());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientObservation)))
BsonClassMap.RegisterClassMap<PatientObservation>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Max).SetIgnoreIfNull(true);
cm.MapMember(c => c.Min).SetIgnoreIfNull(true);
cm.MapMember(c => c.MaxWarn).SetIgnoreIfNull(true);
cm.MapMember(c => c.MinWarn).SetIgnoreIfNull(true);
cm.MapMember(c => c.WarnColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.AlertColor).SetIgnoreIfNull(true);
cm.UnmapMember(c => c.ShowOnExpired);
cm.MapMember(c => c.InsertMode)
.SetDefaultValue(ObservationEnum.InsertMode.Auto)
.SetSerializer(new EnumSerializer<ObservationEnum.InsertMode>(BsonType.String));
cm.UnmapMember(c => c.Persist);
cm.MapMember(c => c.ColorOnExpired).SetIgnoreIfNull(true);
cm.UnmapMember(c => c.MessageTime);
cm.MapMember(c => c.Result).SetIgnoreIfNull(true);
cm.MapMember(c => c.Status)
.SetDefaultValue(StatusEnum.Type.Ok)
.SetSerializer(new EnumSerializer<StatusEnum.Type>(BsonType.String));
cm.UnmapMember(c => c.Level);
cm.UnmapMember(c => c.Expires);
cm.MapMember(c => c.Expired).SetDefaultValue(false);
cm.UnmapMember(c => c.UiConfiguration);
cm.UnmapMember(c => c.Alarm);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ParentDataClass)))
BsonClassMap.RegisterClassMap<ParentDataClass>(cm =>
{
cm.AutoMap();
cm.GetMemberMap(c => c.Code).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.CodingSystem).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Name).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ObservationData)))
BsonClassMap.RegisterClassMap<ObservationData>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.CodingSystem).SetIgnoreIfNull(true);
cm.MapMember(c => c.Code).SetIgnoreIfNull(true);
cm.MapMember(c => c.Text).SetIgnoreIfNull(true);
cm.MapMember(c => c.Time).SetIgnoreIfNull(true);
cm.MapMember(c => c.Value).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ObservationsRequest)))
BsonClassMap.RegisterClassMap<ObservationsRequest>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.PatientNumber).SetIgnoreIfNull(true);
cm.MapMember(c => c.ObservationNames).SetIgnoreIfNull(true);
cm.MapMember(c => c.StartTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.EndTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.PageNumber).SetDefaultValue(1);
cm.MapMember(c => c.PageSize).SetDefaultValue(10);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(DemoConfig)))
BsonClassMap.RegisterClassMap<DemoConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.ValueOption)
.SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ConfigObservation)))
BsonClassMap.RegisterClassMap<ConfigObservation>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.DemoConfig).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ConfigObservation)))
BsonClassMap.RegisterClassMap<ConfigObservation>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Code).SetIgnoreIfNull(true);
cm.MapMember(c => c.CodingSystem).SetIgnoreIfNull(true);
cm.MapMember(c => c.OriginalName).SetIgnoreIfNull(true);
cm.MapMember(c => c.ParentCode).SetIgnoreIfNull(true);
cm.MapMember(c => c.ParentCodingSystem).SetIgnoreIfNull(true);
cm.MapMember(c => c.ParentName).SetIgnoreIfNull(true);
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.Units).SetIgnoreIfNull(true);
cm.MapMember(c => c.ArrowType)
.SetSerializer(new EnumSerializer<ObservationEnum.ArrowType>(BsonType.String))
.SetDefaultValue(ObservationEnum.ArrowType.Default);
cm.MapMember(c => c.ShowArrow).SetDefaultValue(true);
cm.MapMember(c => c.ShowValue).SetDefaultValue(true);
cm.MapMember(c => c.ForceUnits).SetDefaultValue(false);
cm.MapMember(c => c.MinAlert).SetIgnoreIfNull(true);
cm.MapMember(c => c.MaxAlert).SetIgnoreIfNull(true);
cm.MapMember(c => c.MaxWarn).SetIgnoreIfNull(true);
cm.MapMember(c => c.MinWarn).SetIgnoreIfNull(true);
cm.MapMember(c => c.WarnColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.AlertColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.AlertValues).SetIgnoreIfNull(true);
cm.MapMember(c => c.WarningValues).SetIgnoreIfNull(true);
cm.MapMember(c => c.ForceWarn).SetDefaultValue(false);
cm.MapMember(c => c.ForceAlert).SetDefaultValue(false);
cm.MapMember(c => c.Alert).SetIgnoreIfNull(true);
cm.MapMember(c => c.Expires).SetIgnoreIfNull(true);
cm.MapMember(c => c.ShowOnExpired).SetIgnoreIfDefault(true);
cm.MapMember(c => c.Persist).SetIgnoreIfNull(true);
cm.MapMember(c => c.ColorOnExpired).SetIgnoreIfNull(true);
cm.MapMember(c => c.RetentionPolicy)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<RetentionPolicy>(new EnumSerializer<RetentionPolicy>(BsonType.String)));
cm.MapMember(c => c.RetentionPolicyValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.LevelCondition).SetIgnoreIfNull(true);
cm.MapMember(c => c.Grouped).SetIgnoreIfNull(true);
cm.MapMember(c => c.UiConfiguration).SetIgnoreIfNull(true);
cm.MapMember(c => c.Alarm).SetIgnoreIfNull(true);
cm.MapMember(c => c.Description).SetIgnoreIfNull(true);
cm.MapMember(c => c.RequiredValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.Preconditions).SetIgnoreIfNull(true);
cm.MapMember(c => c.CheckObservations).SetDefaultValue(false);
cm.MapMember(c => c.CreateObservation).SetIgnoreIfNull(true);
cm.MapMember(c => c.InsertMode)
.SetDefaultValue(ObservationEnum.InsertMode.Auto)
.SetSerializer(new EnumSerializer<ObservationEnum.InsertMode>(BsonType.String));
cm.MapMember(c => c.TimeFromMessageTime).SetIgnoreIfDefault(true);
cm.MapMember(c => c.ColorRanges).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ConfigObservation.ColorRange)))
BsonClassMap.RegisterClassMap<ConfigObservation.ColorRange>(cm =>
{
cm.AutoMap();
cm.MapMember(s => s.ValueType)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<ObservationEnum.ValueType>(
new EnumSerializer<ObservationEnum.ValueType>(BsonType.String)));
cm.MapMember(s => s.Min).SetIgnoreIfNull(true);
cm.MapMember(s => s.Max).SetIgnoreIfNull(true);
cm.MapMember(s => s.MatchText).SetIgnoreIfNull(true);
cm.MapMember(s => s.MatchBoolean).SetIgnoreIfNull(true);
cm.MapMember(s => s.MinDate).SetIgnoreIfNull(true);
cm.MapMember(s => s.MaxDate).SetIgnoreIfNull(true);
cm.MapMember(s => s.Color).SetIgnoreIfNull(true);
cm.MapMember(s => s.Label).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(GroupedObservation)))
BsonClassMap.RegisterClassMap<GroupedObservation>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.PatientId);
cm.MapMember(c => c.Name).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Group).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Observations).SetDefaultValue(new List<GroupedObservationObs>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(GroupedObservationObs)))
BsonClassMap.RegisterClassMap<GroupedObservationObs>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Name).SetDefaultValue(string.Empty);
cm.MapMember(c => c.First).SetIgnoreIfNull(true);
cm.MapMember(c => c.Last).SetIgnoreIfNull(true);
cm.MapMember(c => c.Min).SetIgnoreIfNull(true);
cm.MapMember(c => c.Max).SetIgnoreIfNull(true);
cm.MapMember(c => c.MinAlert).SetIgnoreIfNull(true);
cm.MapMember(c => c.MaxAlert).SetIgnoreIfNull(true);
cm.MapMember(c => c.Average).SetIgnoreIfNull(true);
cm.MapMember(c => c.Sum).SetIgnoreIfNull(true);
cm.MapMember(c => c.Count).SetIgnoreIfNull(true);
cm.MapMember(c => c.HalfHour).SetIgnoreIfNull(true);
cm.MapMember(c => c.LastFilled).SetIgnoreIfNull(true);
cm.MapMember(c => c.Time);
cm.MapMember(c => c.Shift).SetIgnoreIfNull(true);
cm.MapMember(c => c.ShiftDate);
cm.MapMember(c => c.IsFilled);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(GroupedObservationObsValue)))
BsonClassMap.RegisterClassMap<GroupedObservationObsValue>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Type)
.SetDefaultValue(StatusEnum.Type.Ok)
.SetSerializer(new EnumSerializer<StatusEnum.Type>(BsonType.String));
cm.MapMember(c => c.Value);
cm.MapMember(c => c.Time).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PredictMedicationObservation)))
BsonClassMap.RegisterClassMap<PredictMedicationObservation>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.MedicineText).SetDefaultValue(string.Empty);
cm.MapMember(c => c.DegreeSimilarity);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ObservationTextRule)))
BsonClassMap.RegisterClassMap<ObservationTextRule>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.ValueType)
.SetDefaultValue(ObservationEnum.ValueType.String)
.SetSerializer(new EnumSerializer<ObservationEnum.ValueType>(BsonType.String));
cm.MapMember(c => c.Value).SetIgnoreIfNull(true);
cm.MapMember(c => c.MatchText).SetIgnoreIfNull(true);
cm.MapMember(c => c.MatchBoolean).SetIgnoreIfNull(true);
cm.MapMember(c => c.MatchNumber).SetIgnoreIfNull(true);
cm.MapMember(c => c.MatchDate).SetIgnoreIfNull(true);
cm.MapMember(c => c.Label).SetIgnoreIfNull(true);
cm.MapMember(c => c.MinDate).SetIgnoreIfNull(true);
cm.MapMember(c => c.MaxDate).SetIgnoreIfNull(true);
cm.MapMember(c => c.MinNum).SetIgnoreIfNull(true);
cm.MapMember(c => c.MaxNum).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ConditionsConfig)))
BsonClassMap.RegisterClassMap<ConditionsConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Condition).SetIgnoreIfNull(true);
cm.MapMember(c => c.FieldCondition).SetIgnoreIfNull(true);
});
}
}
@@ -9,237 +9,243 @@ using MongoDB.Bson.Serialization.Serializers;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents a contributor that participates in the mapping configuration of the <see cref="Patient"/> entity by implementing the <see cref="IEntityMapContributor"/> interface.
/// </summary>
public class PatientMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for the domain model types (Patient, Person, PatientLocation, and related entities) to configure their MongoDB serialization, including auto-mapping, null-value handling, enum string serialization, dictionary serialization, and ID member mapping. Skips registration for types that already have a registered class map.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(HistoricalLocation)))
{
BsonClassMap.RegisterClassMap<HistoricalLocation>(cm =>
if (!BsonClassMap.IsClassMapRegistered(typeof(HistoricalLocation)))
{
cm.AutoMap();
cm.MapMember(c => c.AdmTime);
cm.MapMember(c => c.PatientLocation);
});
BsonClassMap.RegisterClassMap<HistoricalLocation>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.AdmTime);
cm.MapMember(c => c.PatientLocation);
});
}
if (!BsonClassMap.IsClassMapRegistered(typeof(Patient)))
BsonClassMap.RegisterClassMap<Patient>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.GetMemberMap(c => c.PatientNumber).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.PatientId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.AdmTime).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.DischargeStatus).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Altable).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Origin).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.OriginAux).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Diagnosis).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.DiagnosisAux).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Visits).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.AccessControl).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Insulation).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.PatientStatus).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Mobility).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.TherapeuticCeiling).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Procedures).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Tests).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Treatment).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Doctors).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Allergies).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.LanguageBarrier).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.PassiveSitting).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.DisTime).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Person).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.AttendingDoctor).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.LastObservationDate).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.CreationDate).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.UpdateDate).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.ArchiveDate).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.PointOfCareId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.UnitId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.HistoricalLocations).SetIgnoreIfNull(true);
// No mapear estos campos
cm.UnmapMember(c => c.Bed);
cm.UnmapMember(c => c.UnitString);
cm.UnmapMember(c => c.Room);
cm.UnmapMember(c => c.PointOfCare);
cm.UnmapMember(c => c.Location);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Person)))
{
BsonClassMap.RegisterClassMap<Person>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.LastName).SetIgnoreIfNull(true);
cm.MapMember(c => c.FirstName).SetIgnoreIfNull(true);
cm.MapMember(c => c.SecondName).SetIgnoreIfNull(true);
cm.MapMember(c => c.BirthDate).SetIgnoreIfNull(true);
cm.MapMember(c => c.Language).SetIgnoreIfNull(true);
cm.MapMember(c => c.Gender)
.SetDefaultValue(PatientEnum.Gender.Unknown)
.SetSerializer(new EnumSerializer<PatientEnum.Gender>(BsonType.String));
cm.MapMember(c => c.Ids).SetIgnoreIfNull(true);
cm.MapMember(c => c.HistoricalIds)
.SetIgnoreIfNull(true);
});
BsonClassMap.RegisterClassMap<HistoricalId>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Time).SetIgnoreIfNull(true);
cm.MapMember(c => c.PatientIds)
.SetSerializer(
new DictionaryInterfaceImplementerSerializer<Dictionary<string, string>, string, string>(
DictionaryRepresentation.Document))
.SetIgnoreIfNull(true);
});
}
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientLocation)))
BsonClassMap.RegisterClassMap<PatientLocation>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.UnitName).SetIgnoreIfNull(true);
cm.MapMember(c => c.Bed).SetIgnoreIfNull(true);
cm.MapMember(c => c.Room).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientLocationAction)))
BsonClassMap.RegisterClassMap<PatientLocationAction>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Location).SetIgnoreIfNull(true);
cm.MapMember(c => c.Action)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<ActionsEnum.ResourceAction>(
new EnumSerializer<ActionsEnum.ResourceAction>(BsonType.String)));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientIncomeData)))
BsonClassMap.RegisterClassMap<PatientIncomeData>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Diagnosis).SetIgnoreIfNull(true);
cm.MapMember(c => c.DiagnosisAux).SetIgnoreIfNull(true);
cm.MapMember(c => c.Origin).SetIgnoreIfNull(true);
cm.MapMember(c => c.OriginAux).SetIgnoreIfNull(true);
cm.MapMember(c => c.AdmTime).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientIcca)))
BsonClassMap.RegisterClassMap<PatientIcca>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Location).SetDefaultValue(new PatientLocation(string.Empty, string.Empty));
cm.MapMember(c => c.PatientNumber).SetIgnoreIfNull(true);
cm.MapMember(c => c.PatientId).SetIgnoreIfNull(true);
cm.MapMember(c => c.AdmitTime).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientAllergiesValue)))
BsonClassMap.RegisterClassMap<PatientAllergiesValue>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Notes).SetIgnoreIfNull(true);
cm.MapMember(c => c.Type).SetIgnoreIfNull(true);
cm.MapMember(c => c.Value).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientIntravenousLinesValue)))
BsonClassMap.RegisterClassMap<PatientIntravenousLinesValue>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Type).SetIgnoreIfNull(true);
cm.MapMember(c => c.Duration).SetIgnoreIfNull(true);
cm.MapMember(c => c.Action).SetIgnoreIfNull(true);
cm.MapMember(c => c.Location).SetIgnoreIfNull(true);
cm.MapMember(c => c.InsertTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.RemoveTime).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientDiagnosis)))
BsonClassMap.RegisterClassMap<PatientDiagnosis>(cm =>
{
cm.AutoMap();
cm.SetIgnoreExtraElements(true);
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.PatientId);
cm.MapMember(c => c.Time);
cm.MapMember(c => c.UpdateDate).SetIgnoreIfNull(true);
cm.UnmapMember(c => c.MessageTime);
cm.MapMember(c => c.CodingSystem).SetIgnoreIfNull(true);
cm.MapMember(c => c.Description).SetIgnoreIfNull(true);
cm.MapMember(c => c.Label).SetIgnoreIfNull(true);
cm.MapMember(c => c.Code).SetIgnoreIfNull(true);
cm.MapMember(c => c.State).SetIgnoreIfNull(true);
cm.MapMember(c => c.Category).SetIgnoreIfNull(true);
cm.MapMember(c => c.StartTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.EndTime).SetIgnoreIfNull(true);
cm.UnmapMember(c => c.DiagnosisCode);
cm.UnmapMember(c => c.DiagnosisSystem);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientDrainagesValue)))
BsonClassMap.RegisterClassMap<PatientDrainagesValue>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Type).SetIgnoreIfNull(true);
cm.MapMember(c => c.Location).SetIgnoreIfNull(true);
cm.MapMember(c => c.Volume).SetIgnoreIfNull(true);
cm.MapMember(c => c.Height).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientCarePlan)))
BsonClassMap.RegisterClassMap<PatientCarePlan>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.PatientId);
cm.MapMember(c => c.PointOfCareId).SetIgnoreIfNull(true);
cm.MapMember(c => c.PatientNumber).SetIgnoreIfNull(true);
cm.MapMember(c => c.UserId).SetIgnoreIfNull(true);
cm.MapMember(c => c.CarePlan).SetIgnoreIfNull(true);
cm.MapMember(c => c.Description).SetIgnoreIfNull(true);
cm.MapMember(c => c.Time).SetIgnoreIfNull(true);
cm.MapMember(c => c.Action)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<ActionsEnum.CrudAction>(
new EnumSerializer<ActionsEnum.CrudAction>(BsonType.String)));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Admission)))
BsonClassMap.RegisterClassMap<Admission>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.UnmapMember(c => c.PatientLocation);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Discharge)))
BsonClassMap.RegisterClassMap<Discharge>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.UnmapMember(c => c.Patient);
cm.UnmapMember(c => c.PatientLocation);
cm.MapMember(c => c.MedicalDischarge).SetIgnoreIfNull(true);
cm.MapMember(c => c.AdminDischarge).SetIgnoreIfNull(true);
cm.MapMember(c => c.NurseDischarge).SetIgnoreIfNull(true);
});
}
if (!BsonClassMap.IsClassMapRegistered(typeof(Patient)))
BsonClassMap.RegisterClassMap<Patient>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.GetMemberMap(c => c.PatientNumber).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.PatientId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.AdmTime).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.DischargeStatus).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Altable).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Origin).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.OriginAux).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Diagnosis).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.DiagnosisAux).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Visits).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.AccessControl).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Insulation).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.PatientStatus).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Mobility).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.TherapeuticCeiling).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Procedures).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Tests).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Treatment).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Doctors).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Allergies).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.LanguageBarrier).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.PassiveSitting).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.DisTime).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Person).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.AttendingDoctor).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.LastObservationDate).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.CreationDate).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.UpdateDate).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.ArchiveDate).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.PointOfCareId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.UnitId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.HistoricalLocations).SetIgnoreIfNull(true);
// No mapear estos campos
cm.UnmapMember(c => c.Bed);
cm.UnmapMember(c => c.UnitString);
cm.UnmapMember(c => c.Room);
cm.UnmapMember(c => c.PointOfCare);
cm.UnmapMember(c => c.Location);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Person)))
{
BsonClassMap.RegisterClassMap<Person>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.LastName).SetIgnoreIfNull(true);
cm.MapMember(c => c.FirstName).SetIgnoreIfNull(true);
cm.MapMember(c => c.SecondName).SetIgnoreIfNull(true);
cm.MapMember(c => c.BirthDate).SetIgnoreIfNull(true);
cm.MapMember(c => c.Language).SetIgnoreIfNull(true);
cm.MapMember(c => c.Gender)
.SetDefaultValue(PatientEnum.Gender.Unknown)
.SetSerializer(new EnumSerializer<PatientEnum.Gender>(BsonType.String));
cm.MapMember(c => c.Ids).SetIgnoreIfNull(true);
cm.MapMember(c => c.HistoricalIds)
.SetIgnoreIfNull(true);
});
BsonClassMap.RegisterClassMap<HistoricalId>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Time).SetIgnoreIfNull(true);
cm.MapMember(c => c.PatientIds)
.SetSerializer(
new DictionaryInterfaceImplementerSerializer<Dictionary<string, string>, string, string>(
DictionaryRepresentation.Document))
.SetIgnoreIfNull(true);
});
}
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientLocation)))
BsonClassMap.RegisterClassMap<PatientLocation>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.UnitName).SetIgnoreIfNull(true);
cm.MapMember(c => c.Bed).SetIgnoreIfNull(true);
cm.MapMember(c => c.Room).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientLocationAction)))
BsonClassMap.RegisterClassMap<PatientLocationAction>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Location).SetIgnoreIfNull(true);
cm.MapMember(c => c.Action)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<ActionsEnum.ResourceAction>(
new EnumSerializer<ActionsEnum.ResourceAction>(BsonType.String)));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientIncomeData)))
BsonClassMap.RegisterClassMap<PatientIncomeData>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Diagnosis).SetIgnoreIfNull(true);
cm.MapMember(c => c.DiagnosisAux).SetIgnoreIfNull(true);
cm.MapMember(c => c.Origin).SetIgnoreIfNull(true);
cm.MapMember(c => c.OriginAux).SetIgnoreIfNull(true);
cm.MapMember(c => c.AdmTime).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientIcca)))
BsonClassMap.RegisterClassMap<PatientIcca>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Location).SetDefaultValue(new PatientLocation(string.Empty, string.Empty));
cm.MapMember(c => c.PatientNumber).SetIgnoreIfNull(true);
cm.MapMember(c => c.PatientId).SetIgnoreIfNull(true);
cm.MapMember(c => c.AdmitTime).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientAllergiesValue)))
BsonClassMap.RegisterClassMap<PatientAllergiesValue>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Notes).SetIgnoreIfNull(true);
cm.MapMember(c => c.Type).SetIgnoreIfNull(true);
cm.MapMember(c => c.Value).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientIntravenousLinesValue)))
BsonClassMap.RegisterClassMap<PatientIntravenousLinesValue>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Type).SetIgnoreIfNull(true);
cm.MapMember(c => c.Duration).SetIgnoreIfNull(true);
cm.MapMember(c => c.Action).SetIgnoreIfNull(true);
cm.MapMember(c => c.Location).SetIgnoreIfNull(true);
cm.MapMember(c => c.InsertTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.RemoveTime).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientDiagnosis)))
BsonClassMap.RegisterClassMap<PatientDiagnosis>(cm =>
{
cm.AutoMap();
cm.SetIgnoreExtraElements(true);
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.PatientId);
cm.MapMember(c => c.Time);
cm.MapMember(c => c.UpdateDate).SetIgnoreIfNull(true);
cm.UnmapMember(c => c.MessageTime);
cm.MapMember(c => c.CodingSystem).SetIgnoreIfNull(true);
cm.MapMember(c => c.Description).SetIgnoreIfNull(true);
cm.MapMember(c => c.Label).SetIgnoreIfNull(true);
cm.MapMember(c => c.Code).SetIgnoreIfNull(true);
cm.MapMember(c => c.State).SetIgnoreIfNull(true);
cm.MapMember(c => c.Category).SetIgnoreIfNull(true);
cm.MapMember(c => c.StartTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.EndTime).SetIgnoreIfNull(true);
cm.UnmapMember(c => c.DiagnosisCode);
cm.UnmapMember(c => c.DiagnosisSystem);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientDrainagesValue)))
BsonClassMap.RegisterClassMap<PatientDrainagesValue>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Type).SetIgnoreIfNull(true);
cm.MapMember(c => c.Location).SetIgnoreIfNull(true);
cm.MapMember(c => c.Volume).SetIgnoreIfNull(true);
cm.MapMember(c => c.Height).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientCarePlan)))
BsonClassMap.RegisterClassMap<PatientCarePlan>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.PatientId);
cm.MapMember(c => c.PointOfCareId).SetIgnoreIfNull(true);
cm.MapMember(c => c.PatientNumber).SetIgnoreIfNull(true);
cm.MapMember(c => c.UserId).SetIgnoreIfNull(true);
cm.MapMember(c => c.CarePlan).SetIgnoreIfNull(true);
cm.MapMember(c => c.Description).SetIgnoreIfNull(true);
cm.MapMember(c => c.Time).SetIgnoreIfNull(true);
cm.MapMember(c => c.Action)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<ActionsEnum.CrudAction>(
new EnumSerializer<ActionsEnum.CrudAction>(BsonType.String)));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Admission)))
BsonClassMap.RegisterClassMap<Admission>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.UnmapMember(c => c.PatientLocation);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Discharge)))
BsonClassMap.RegisterClassMap<Discharge>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.UnmapMember(c => c.Patient);
cm.UnmapMember(c => c.PatientLocation);
cm.MapMember(c => c.MedicalDischarge).SetIgnoreIfNull(true);
cm.MapMember(c => c.AdminDischarge).SetIgnoreIfNull(true);
cm.MapMember(c => c.NurseDischarge).SetIgnoreIfNull(true);
});
}
}
@@ -9,88 +9,97 @@ using LightBeacon = adas_core.Domain.Models.MongoModels.LightBeacon;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Provides a proof-of-concept implementation of the <see cref="IEntityMapContributor"/> interface for contributing entity mapping logic.
/// </summary>
/// <remarks>
/// This class serves as a demonstrative or experimental implementation of entity map contribution behavior, intended to validate the contract defined by <see cref="IEntityMapContributor"/>.
/// </remarks>
public class PoCMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for PointOfCare-related types (PointOfCare, PointOfCareConfiguration, PoCMapping, PoCMappingItem, and PoCSettings) with the MongoDB driver, configuring serialization options such as element names, default values, null handling, and enum string serialization. Each registration is performed only if a class map for the type has not already been registered.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(PointOfCare)))
BsonClassMap.RegisterClassMap<PointOfCare>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.Room).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Bed).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Hall).SetIgnoreIfNull(true);
cm.MapMember(c => c.UnitId);
cm.MapMember(c => c.Configuration).SetIgnoreIfNull(true);
cm.MapMember(c => c.Status)
.SetSerializer(new EnumSerializer<StatusEnum.PointOfCare>(BsonType.String));
cm.MapMember(c => c.AdmissionId).SetIgnoreIfNull(true);
cm.MapMember(c => c.IsActive).SetIgnoreIfNull(true);
cm.MapMember(c => c.IsVisible).SetIgnoreIfNull(true);
cm.UnmapMember(c => c.UnitName);
cm.UnmapMember(c => c.Unit);
cm.UnmapMember(c => c.Location);
cm.UnmapMember(c => c.Observations);
cm.UnmapMember(c => c.HasPatient);
cm.UnmapMember(c => c.Patientid);
cm.UnmapMember(c => c.Patient);
cm.UnmapMember(c => c.Admission);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PointOfCareConfiguration)))
BsonClassMap.RegisterClassMap<PointOfCareConfiguration>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.BeaconIdList)
.SetDefaultValue(new List<ObjectId>());
cm.MapMember(c => c.CameraIdList)
.SetDefaultValue(new List<ObjectId>());
cm.MapMember(c => c.RelayIdList).SetIgnoreIfNull(true);
cm.MapMember(c => c.Type).SetIgnoreIfNull(true);
cm.MapMember(c => c.Id).SetIgnoreIfNull(true);
cm.MapMember(c => c.BeaconList)
.SetIgnoreIfNull(true)
.SetIsRequired(false);
cm.MapMember(c => c.CameraList)
.SetIgnoreIfNull(true)
.SetIsRequired(false);
cm.MapMember(c => c.RelayList)
.SetIgnoreIfNull(true)
.SetIsRequired(false);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PoCMapping)))
BsonClassMap.RegisterClassMap<PoCMapping>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.PointOfCares).SetDefaultValue(new List<PoCMappingItem>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PoCMappingItem)))
BsonClassMap.RegisterClassMap<PoCMappingItem>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.NewPoC).SetElementName("new");
cm.MapMember(c => c.OriginalPoC).SetElementName("original");
cm.MapMember(c => c.Beds).SetDefaultValue(new List<List<string>>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PoCSettings)))
BsonClassMap.RegisterClassMap<PoCSettings>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.PatientLocation).SetIgnoreIfNull(true);
cm.MapMember(c => c.ManualRelayStatus)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<RelayEnum.Status>(
new EnumSerializer<RelayEnum.Status>(BsonType.String)));
});
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(PointOfCare)))
BsonClassMap.RegisterClassMap<PointOfCare>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.Room).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Bed).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Hall).SetIgnoreIfNull(true);
cm.MapMember(c => c.UnitId);
cm.MapMember(c => c.Configuration).SetIgnoreIfNull(true);
cm.MapMember(c => c.Status)
.SetSerializer(new EnumSerializer<StatusEnum.PointOfCare>(BsonType.String));
cm.MapMember(c => c.AdmissionId).SetIgnoreIfNull(true);
cm.MapMember(c => c.IsActive).SetIgnoreIfNull(true);
cm.MapMember(c => c.IsVisible).SetIgnoreIfNull(true);
cm.UnmapMember(c => c.UnitName);
cm.UnmapMember(c => c.Unit);
cm.UnmapMember(c => c.Location);
cm.UnmapMember(c => c.Observations);
cm.UnmapMember(c => c.HasPatient);
cm.UnmapMember(c => c.Patientid);
cm.UnmapMember(c => c.Patient);
cm.UnmapMember(c => c.Admission);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PointOfCareConfiguration)))
BsonClassMap.RegisterClassMap<PointOfCareConfiguration>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.BeaconIdList)
.SetDefaultValue(new List<ObjectId>());
cm.MapMember(c => c.CameraIdList)
.SetDefaultValue(new List<ObjectId>());
cm.MapMember(c => c.RelayIdList).SetIgnoreIfNull(true);
cm.MapMember(c => c.Type).SetIgnoreIfNull(true);
cm.MapMember(c => c.Id).SetIgnoreIfNull(true);
cm.MapMember(c => c.BeaconList)
.SetIgnoreIfNull(true)
.SetIsRequired(false);
cm.MapMember(c => c.CameraList)
.SetIgnoreIfNull(true)
.SetIsRequired(false);
cm.MapMember(c => c.RelayList)
.SetIgnoreIfNull(true)
.SetIsRequired(false);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PoCMapping)))
BsonClassMap.RegisterClassMap<PoCMapping>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.PointOfCares).SetDefaultValue(new List<PoCMappingItem>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PoCMappingItem)))
BsonClassMap.RegisterClassMap<PoCMappingItem>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.NewPoC).SetElementName("new");
cm.MapMember(c => c.OriginalPoC).SetElementName("original");
cm.MapMember(c => c.Beds).SetDefaultValue(new List<List<string>>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PoCSettings)))
BsonClassMap.RegisterClassMap<PoCSettings>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.PatientLocation).SetIgnoreIfNull(true);
cm.MapMember(c => c.ManualRelayStatus)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<RelayEnum.Status>(
new EnumSerializer<RelayEnum.Status>(BsonType.String)));
});
}
}
@@ -8,6 +8,9 @@ using MongoDB.Bson.Serialization.Serializers;
namespace adas_core.Infrastructure.Utils.MongoMaps
{
/// <summary>
/// Represents a contributor that provides mapping logic for pump-related entities within an entity mapping context.
/// </summary>
public class PumpMapContributor : IEntityMapContributor
{
public void RegisterMaps()
@@ -7,42 +7,49 @@ using MongoDB.Bson.Serialization.Serializers;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Provides an entity map contributor that relays or forwards mapping contributions from an underlying source.
/// Implements the <see cref="IEntityMapContributor"/> contract to participate in entity mapping workflows.
/// </summary>
public class RelayContributor : IEntityMapContributor
{
/// <summary>
/// Registers the BSON class map for the <see cref="Relay"/> type, defining how its properties are serialized and stored in MongoDB. The registration is performed only when no class map is already registered for the type, and it configures default values, string-based enum serialization, and null-ignoring behavior for optional members.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(Relay)))
BsonClassMap.RegisterClassMap<Relay>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.Type)
.SetDefaultValue(RelayEnum.Type.Door)
.SetSerializer(new EnumSerializer<RelayEnum.Type>(BsonType.String));
cm.MapMember(c => c.Driver).SetIgnoreIfNull(true);
cm.MapMember(c => c.Ip).SetIgnoreIfNull(true);
cm.MapMember(c => c.Port).SetDefaultValue(0);
cm.MapMember(c => c.RelayNumber).SetDefaultValue(1);
cm.MapMember(c => c.RelayName).SetIgnoreIfNull(true);
cm.MapMember(c => c.Total).SetDefaultValue(1);
cm.MapMember(c => c.RefreshTime).SetDefaultValue(60);
cm.MapMember(c => c.Open).SetDefaultValue(false);
cm.MapMember(c => c.Status)
.SetDefaultValue(RelayEnum.Status.NotInitialized)
.SetSerializer(new EnumSerializer<RelayEnum.Status>(BsonType.String));
cm.MapMember(c => c.Username).SetIgnoreIfNull(true);
cm.MapMember(c => c.Password).SetIgnoreIfNull(true);
cm.MapMember(c => c.Mode)
.SetDefaultValue(RelayEnum.Mode.OpenedOnClosedOff)
.SetSerializer(new EnumSerializer<RelayEnum.Mode>(BsonType.String));
cm.MapMember(c => c.RebootDelay).SetIgnoreIfNull(true);
cm.MapMember(c => c.Cache).SetDefaultValue(true);
cm.MapMember(c => c.InUse).SetIgnoreIfNull(true).SetIsRequired(false);
cm.MapMember(c => c.ManualRelayStatus)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<RelayEnum.Status>(
new EnumSerializer<RelayEnum.Status>(BsonType.String)));
});
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(Relay)))
BsonClassMap.RegisterClassMap<Relay>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.Type)
.SetDefaultValue(RelayEnum.Type.Door)
.SetSerializer(new EnumSerializer<RelayEnum.Type>(BsonType.String));
cm.MapMember(c => c.Driver).SetIgnoreIfNull(true);
cm.MapMember(c => c.Ip).SetIgnoreIfNull(true);
cm.MapMember(c => c.Port).SetDefaultValue(0);
cm.MapMember(c => c.RelayNumber).SetDefaultValue(1);
cm.MapMember(c => c.RelayName).SetIgnoreIfNull(true);
cm.MapMember(c => c.Total).SetDefaultValue(1);
cm.MapMember(c => c.RefreshTime).SetDefaultValue(60);
cm.MapMember(c => c.Open).SetDefaultValue(false);
cm.MapMember(c => c.Status)
.SetDefaultValue(RelayEnum.Status.NotInitialized)
.SetSerializer(new EnumSerializer<RelayEnum.Status>(BsonType.String));
cm.MapMember(c => c.Username).SetIgnoreIfNull(true);
cm.MapMember(c => c.Password).SetIgnoreIfNull(true);
cm.MapMember(c => c.Mode)
.SetDefaultValue(RelayEnum.Mode.OpenedOnClosedOff)
.SetSerializer(new EnumSerializer<RelayEnum.Mode>(BsonType.String));
cm.MapMember(c => c.RebootDelay).SetIgnoreIfNull(true);
cm.MapMember(c => c.Cache).SetDefaultValue(true);
cm.MapMember(c => c.InUse).SetIgnoreIfNull(true).SetIsRequired(false);
cm.MapMember(c => c.ManualRelayStatus)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<RelayEnum.Status>(
new EnumSerializer<RelayEnum.Status>(BsonType.String)));
});
}
}
@@ -7,85 +7,91 @@ using MongoDB.Bson.Serialization.Serializers;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Provides row-level mapping contributions to the entity mapping process by implementing the IEntityMapContributor interface.
/// </summary>
public class RowMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for <see cref="RowBoxLayout"/>, <see cref="RowDetailsConfig"/>, and <see cref="ObservationRowBoxLayout"/>, configuring default values, enum string serialization, and null-ignoring behavior for nullable members. Each map is registered only if no class map has already been registered for the corresponding type.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(RowBoxLayout)))
BsonClassMap.RegisterClassMap<RowBoxLayout>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.GrowPriority).SetDefaultValue(1D);
cm.MapMember(c => c.Type)
.SetDefaultValue(DisplayConfigEnums.CellType.Default)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.CellType>(BsonType.String));
cm.MapMember(c => c.SubType)
.SetDefaultValue(DisplayConfigEnums.WebDisplayCellSubtype.Default)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.WebDisplayCellSubtype>(BsonType.String));
cm.MapMember(c => c.BgColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingTop).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingBottom).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingLeft).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingRight).SetIgnoreIfNull(true);
cm.MapMember(c => c.Observations)
.SetDefaultValue(new List<ObservationRowBoxLayout>());
cm.MapMember(c => c.Direction)
.SetDefaultValue(DisplayConfigEnums.DirectionEnum.Row)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.DirectionEnum>(BsonType.String));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(RowDetailsConfig)))
BsonClassMap.RegisterClassMap<RowDetailsConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Cells).SetDefaultValue(new List<CellDetails>());
cm.MapMember(c => c.Rows).SetDefaultValue(new List<RowDetailsConfig>());
cm.MapMember(c => c.GrowPriority).SetDefaultValue(1.0);
cm.MapMember(c => c.Title).SetIgnoreIfNull(true);
cm.MapMember(c => c.DialogConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.Type).SetDefaultValue(DisplayConfigEnums.RowType.Demographic)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.RowType>(BsonType.String));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ObservationRowBoxLayout)))
BsonClassMap.RegisterClassMap<ObservationRowBoxLayout>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.TextRules).SetIgnoreIfNull(true);
cm.MapMember(c => c.ChartSettings).SetIgnoreIfNull(true);
cm.MapMember(c => c.ShowTitle).SetIgnoreIfNull(true);
cm.MapMember(c => c.GrowPriority).SetDefaultValue(1D);
cm.MapMember(c => c.Type)
.SetDefaultValue(DisplayConfigEnums.CellType.Default)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.CellType>(BsonType.String));
cm.MapMember(c => c.SubType).SetIgnoreIfNull(true);
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.Icon).SetIgnoreIfNull(true);
cm.MapMember(c => c.Title).SetIgnoreIfNull(true);
cm.MapMember(c => c.GridColumn).SetDefaultValue(1);
cm.MapMember(c => c.IsColumn).SetIgnoreIfNull(true);
cm.MapMember(c => c.IsStatic).SetIgnoreIfNull(true);
cm.MapMember(c => c.GraphConf).SetIgnoreIfNull(true);
cm.MapMember(c => c.Names).SetIgnoreIfNull(true);
cm.MapMember(c => c.ObservationName).SetIgnoreIfNull(true);
cm.MapMember(c => c.ObservationTitle).SetIgnoreIfNull(true);
cm.MapMember(c => c.Observations).SetIgnoreIfNull(true);
cm.MapMember(c => c.BgColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingTop).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingBottom).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingLeft).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingRight).SetIgnoreIfNull(true);
cm.MapMember(c => c.Border).SetIgnoreIfNull(true);
cm.MapMember(c => c.BorderRadius).SetIgnoreIfNull(true);
cm.MapMember(c => c.ValuePathKey).SetIgnoreIfNull(true);
cm.MapMember(c => c.ValuePathNested).SetIgnoreIfNull(true);
cm.MapMember(c => c.Size).SetDefaultValue(15.0);
cm.MapMember(c => c.Format).SetIgnoreIfNull(true);
cm.MapMember(c => c.Length).SetIgnoreIfNull(true);
cm.MapMember(c => c.Direction)
.SetIgnoreIfNull(true)
.SetSerializer(new NullableSerializer<DisplayConfigEnums.DirectionEnum>(
new EnumSerializer<DisplayConfigEnums.DirectionEnum>(BsonType.String)));
});
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(RowBoxLayout)))
BsonClassMap.RegisterClassMap<RowBoxLayout>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.GrowPriority).SetDefaultValue(1D);
cm.MapMember(c => c.Type)
.SetDefaultValue(DisplayConfigEnums.CellType.Default)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.CellType>(BsonType.String));
cm.MapMember(c => c.SubType)
.SetDefaultValue(DisplayConfigEnums.WebDisplayCellSubtype.Default)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.WebDisplayCellSubtype>(BsonType.String));
cm.MapMember(c => c.BgColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingTop).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingBottom).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingLeft).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingRight).SetIgnoreIfNull(true);
cm.MapMember(c => c.Observations)
.SetDefaultValue(new List<ObservationRowBoxLayout>());
cm.MapMember(c => c.Direction)
.SetDefaultValue(DisplayConfigEnums.DirectionEnum.Row)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.DirectionEnum>(BsonType.String));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(RowDetailsConfig)))
BsonClassMap.RegisterClassMap<RowDetailsConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Cells).SetDefaultValue(new List<CellDetails>());
cm.MapMember(c => c.Rows).SetDefaultValue(new List<RowDetailsConfig>());
cm.MapMember(c => c.GrowPriority).SetDefaultValue(1.0);
cm.MapMember(c => c.Title).SetIgnoreIfNull(true);
cm.MapMember(c => c.DialogConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.Type).SetDefaultValue(DisplayConfigEnums.RowType.Demographic)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.RowType>(BsonType.String));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ObservationRowBoxLayout)))
BsonClassMap.RegisterClassMap<ObservationRowBoxLayout>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.TextRules).SetIgnoreIfNull(true);
cm.MapMember(c => c.ChartSettings).SetIgnoreIfNull(true);
cm.MapMember(c => c.ShowTitle).SetIgnoreIfNull(true);
cm.MapMember(c => c.GrowPriority).SetDefaultValue(1D);
cm.MapMember(c => c.Type)
.SetDefaultValue(DisplayConfigEnums.CellType.Default)
.SetSerializer(new EnumSerializer<DisplayConfigEnums.CellType>(BsonType.String));
cm.MapMember(c => c.SubType).SetIgnoreIfNull(true);
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.Icon).SetIgnoreIfNull(true);
cm.MapMember(c => c.Title).SetIgnoreIfNull(true);
cm.MapMember(c => c.GridColumn).SetDefaultValue(1);
cm.MapMember(c => c.IsColumn).SetIgnoreIfNull(true);
cm.MapMember(c => c.IsStatic).SetIgnoreIfNull(true);
cm.MapMember(c => c.GraphConf).SetIgnoreIfNull(true);
cm.MapMember(c => c.Names).SetIgnoreIfNull(true);
cm.MapMember(c => c.ObservationName).SetIgnoreIfNull(true);
cm.MapMember(c => c.ObservationTitle).SetIgnoreIfNull(true);
cm.MapMember(c => c.Observations).SetIgnoreIfNull(true);
cm.MapMember(c => c.BgColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingTop).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingBottom).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingLeft).SetIgnoreIfNull(true);
cm.MapMember(c => c.PaddingRight).SetIgnoreIfNull(true);
cm.MapMember(c => c.Border).SetIgnoreIfNull(true);
cm.MapMember(c => c.BorderRadius).SetIgnoreIfNull(true);
cm.MapMember(c => c.ValuePathKey).SetIgnoreIfNull(true);
cm.MapMember(c => c.ValuePathNested).SetIgnoreIfNull(true);
cm.MapMember(c => c.Size).SetDefaultValue(15.0);
cm.MapMember(c => c.Format).SetIgnoreIfNull(true);
cm.MapMember(c => c.Length).SetIgnoreIfNull(true);
cm.MapMember(c => c.Direction)
.SetIgnoreIfNull(true)
.SetSerializer(new NullableSerializer<DisplayConfigEnums.DirectionEnum>(
new EnumSerializer<DisplayConfigEnums.DirectionEnum>(BsonType.String)));
});
}
}
@@ -8,62 +8,68 @@ using MongoDB.Bson.Serialization.Serializers;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents a contributor that participates in section-based entity mapping by implementing the <see cref="IEntityMapContributor"/> interface.
/// </summary>
public class SectionMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers the BSON class maps for the <see cref="Section"/>, <see cref="Section.SectionItem"/>, <see cref="SectionConfig"/>, and <see cref="MinimalDisplaySection"/> types, configuring their MongoDB serialization behavior such as required fields, default values, null-ignoring members, and custom serializers. Each class map is registered only if it has not already been registered, preventing duplicate registrations.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(Section)))
BsonClassMap.RegisterClassMap<Section>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c._id);
cm.MapMember(c => c.Id)
.SetIsRequired(true)
.SetDefaultValue(string.Empty);
cm.MapMember(c => c.SectionTitle).SetIgnoreIfNull(true);
cm.MapMember(c => c.PointOfCare).SetIgnoreIfNull(true);
cm.MapMember(c => c.LastUpdate).SetIgnoreIfNull(true);
cm.MapMember(c => c.Configuration)
.SetSerializer(new DictionaryBsonConverter());
cm.MapMember(c => c.SectionConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.DesignProperties).SetIgnoreIfNull(true);
cm.MapMember(c => c.PointOfCareList)
.SetDefaultValue(new Dictionary<string, List<PointOfCare>>());
cm.MapMember(c => c.Items).SetDefaultValue(new List<Section.SectionItem>());
cm.MapMember(c => c.Status)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<StatusEnum.Type>(new EnumSerializer<StatusEnum.Type>(BsonType.String)));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Section.SectionItem)))
BsonClassMap.RegisterClassMap<Section.SectionItem>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Group).SetIgnoreIfNull(true);
cm.MapMember(c => c.Boxes).SetDefaultValue(new List<Box>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(SectionConfig)))
BsonClassMap.RegisterClassMap<SectionConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Columns).SetIgnoreIfNull(true);
cm.MapMember(c => c.Rows).SetIgnoreIfNull(true);
cm.MapMember(c => c.RefreshValues).SetIgnoreIfNull(true);
cm.MapMember(c => c.RefreshConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.Steps).SetIgnoreIfNull(true);
cm.MapMember(c => c.DesignProperties).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(MinimalDisplaySection)))
BsonClassMap.RegisterClassMap<MinimalDisplaySection>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetDefaultValue(ObjectId.GenerateNewId());
cm.MapMember(c => c.Name).SetDefaultValue(string.Empty);
cm.MapMember(c => c.IsSelected).SetDefaultValue(false);
});
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(Section)))
BsonClassMap.RegisterClassMap<Section>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c._id);
cm.MapMember(c => c.Id)
.SetIsRequired(true)
.SetDefaultValue(string.Empty);
cm.MapMember(c => c.SectionTitle).SetIgnoreIfNull(true);
cm.MapMember(c => c.PointOfCare).SetIgnoreIfNull(true);
cm.MapMember(c => c.LastUpdate).SetIgnoreIfNull(true);
cm.MapMember(c => c.Configuration)
.SetSerializer(new DictionaryBsonConverter());
cm.MapMember(c => c.SectionConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.DesignProperties).SetIgnoreIfNull(true);
cm.MapMember(c => c.PointOfCareList)
.SetDefaultValue(new Dictionary<string, List<PointOfCare>>());
cm.MapMember(c => c.Items).SetDefaultValue(new List<Section.SectionItem>());
cm.MapMember(c => c.Status)
.SetIgnoreIfNull(true)
.SetSerializer(
new NullableSerializer<StatusEnum.Type>(new EnumSerializer<StatusEnum.Type>(BsonType.String)));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Section.SectionItem)))
BsonClassMap.RegisterClassMap<Section.SectionItem>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Group).SetIgnoreIfNull(true);
cm.MapMember(c => c.Boxes).SetDefaultValue(new List<Box>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(SectionConfig)))
BsonClassMap.RegisterClassMap<SectionConfig>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Columns).SetIgnoreIfNull(true);
cm.MapMember(c => c.Rows).SetIgnoreIfNull(true);
cm.MapMember(c => c.RefreshValues).SetIgnoreIfNull(true);
cm.MapMember(c => c.RefreshConfig).SetIgnoreIfNull(true);
cm.MapMember(c => c.Steps).SetIgnoreIfNull(true);
cm.MapMember(c => c.DesignProperties).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(MinimalDisplaySection)))
BsonClassMap.RegisterClassMap<MinimalDisplaySection>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetDefaultValue(ObjectId.GenerateNewId());
cm.MapMember(c => c.Name).SetDefaultValue(string.Empty);
cm.MapMember(c => c.IsSelected).SetDefaultValue(false);
});
}
}
@@ -4,57 +4,63 @@ using MongoDB.Bson.Serialization;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents an entity map contributor that provides mapping configuration for service configuration entities.
/// </summary>
public class ServiceConfigMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for service configuration-related types, including <see cref="ServiceConfig"/>, <see cref="ServiceConfigService"/>, <see cref="ServiceConfigServiceSection"/>, <see cref="ServiceConfigTheme"/>, and <see cref="ServiceConfigThemeTimetable"/>, configuring their serialization elements, default values, and null-ignoring behavior. Each registration is guarded by a check that ensures the map is only registered if it has not been registered previously.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(ServiceConfig)))
BsonClassMap.RegisterClassMap<ServiceConfig>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.StrId).SetElementName("id")
.SetDefaultValue(string.Empty);
cm.MapMember(c => c.Service).SetDefaultValue(new List<ServiceConfigService>());
cm.MapMember(c => c.Theme).SetIgnoreIfNull(true);
cm.MapMember(c => c.BoxObservations).SetIgnoreIfNull(true);
cm.MapMember(c => c.Score).SetDefaultValue(string.Empty);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ServiceConfigService)))
BsonClassMap.RegisterClassMap<ServiceConfigService>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Screen).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Sections).SetDefaultValue(new List<ServiceConfigServiceSection>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ServiceConfigServiceSection)))
BsonClassMap.RegisterClassMap<ServiceConfigServiceSection>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.BoxId).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Box).SetDefaultValue(string.Empty);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ServiceConfigTheme)))
BsonClassMap.RegisterClassMap<ServiceConfigTheme>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.DefaultTheme).SetElementName("default")
.SetDefaultValue(string.Empty);
cm.MapMember(c => c.Timetables).SetDefaultValue(new List<ServiceConfigThemeTimetable>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ServiceConfigThemeTimetable)))
BsonClassMap.RegisterClassMap<ServiceConfigThemeTimetable>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Theme).SetDefaultValue(string.Empty);
cm.MapMember(c => c.StartDay).SetDefaultValue(string.Empty);
cm.MapMember(c => c.EndDay).SetDefaultValue(string.Empty);
cm.MapMember(c => c.StartHour).SetDefaultValue(string.Empty);
cm.MapMember(c => c.EndHour).SetDefaultValue(string.Empty);
});
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(ServiceConfig)))
BsonClassMap.RegisterClassMap<ServiceConfig>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.StrId).SetElementName("id")
.SetDefaultValue(string.Empty);
cm.MapMember(c => c.Service).SetDefaultValue(new List<ServiceConfigService>());
cm.MapMember(c => c.Theme).SetIgnoreIfNull(true);
cm.MapMember(c => c.BoxObservations).SetIgnoreIfNull(true);
cm.MapMember(c => c.Score).SetDefaultValue(string.Empty);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ServiceConfigService)))
BsonClassMap.RegisterClassMap<ServiceConfigService>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Screen).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Sections).SetDefaultValue(new List<ServiceConfigServiceSection>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ServiceConfigServiceSection)))
BsonClassMap.RegisterClassMap<ServiceConfigServiceSection>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.BoxId).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Box).SetDefaultValue(string.Empty);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ServiceConfigTheme)))
BsonClassMap.RegisterClassMap<ServiceConfigTheme>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.DefaultTheme).SetElementName("default")
.SetDefaultValue(string.Empty);
cm.MapMember(c => c.Timetables).SetDefaultValue(new List<ServiceConfigThemeTimetable>());
});
if (!BsonClassMap.IsClassMapRegistered(typeof(ServiceConfigThemeTimetable)))
BsonClassMap.RegisterClassMap<ServiceConfigThemeTimetable>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Theme).SetDefaultValue(string.Empty);
cm.MapMember(c => c.StartDay).SetDefaultValue(string.Empty);
cm.MapMember(c => c.EndDay).SetDefaultValue(string.Empty);
cm.MapMember(c => c.StartHour).SetDefaultValue(string.Empty);
cm.MapMember(c => c.EndHour).SetDefaultValue(string.Empty);
});
}
}
@@ -4,42 +4,51 @@ using MongoDB.Bson.Serialization;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Provides a standard implementation of the <see cref="IEntityMapContributor"/> interface.
/// </summary>
public class StandardMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for <see cref="HistoricalConfigChanges"/> and <see cref="UiFontData"/> types
/// used during MongoDB serialization, skipping any types that are already registered. For
/// <see cref="UiFontData"/>, several nullable UI configuration members are configured to be ignored
/// when null to avoid persisting default values.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(HistoricalConfigChanges)))
BsonClassMap.RegisterClassMap<HistoricalConfigChanges>(cm =>
{
cm.AutoMap();
//cm.MapMember(c => c.Id)
// .SetSerializer(new ObjectIdSerializer(BsonType.String));
//cm.GetMemberMap(c => c.ConfigType)
// .SetSerializer(new NullableSerializer<ConfigTypes>(new EnumSerializer<ConfigTypes>(BsonType.String)));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(UiFontData)))
BsonClassMap.RegisterClassMap<UiFontData>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.WidthStepBed).SetIgnoreIfNull(true);
cm.MapMember(c => c.HeightStepBed).SetIgnoreIfNull(true);
cm.MapMember(c => c.FontSizeValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.FontSizeMediumValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.FontSizeHeaderValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.FontSizeLittleValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.FontSizeMicroValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.FontSizeNanoValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.FontSizeLittleTextValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.FontSizeName).SetIgnoreIfNull(true);
cm.MapMember(c => c.FontSizeLittleName).SetIgnoreIfNull(true);
cm.MapMember(c => c.FontSizeUnit).SetIgnoreIfNull(true);
cm.MapMember(c => c.BorderColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.BackGroundColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.BoxMarginTop).SetIgnoreIfNull(true);
cm.MapMember(c => c.BoxMarginBot).SetIgnoreIfNull(true);
cm.MapMember(c => c.BoxMarginLeft).SetIgnoreIfNull(true);
cm.MapMember(c => c.BoxMarginRight).SetIgnoreIfNull(true);
});
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(HistoricalConfigChanges)))
BsonClassMap.RegisterClassMap<HistoricalConfigChanges>(cm =>
{
cm.AutoMap();
//cm.MapMember(c => c.Id)
// .SetSerializer(new ObjectIdSerializer(BsonType.String));
//cm.GetMemberMap(c => c.ConfigType)
// .SetSerializer(new NullableSerializer<ConfigTypes>(new EnumSerializer<ConfigTypes>(BsonType.String)));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(UiFontData)))
BsonClassMap.RegisterClassMap<UiFontData>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.WidthStepBed).SetIgnoreIfNull(true);
cm.MapMember(c => c.HeightStepBed).SetIgnoreIfNull(true);
cm.MapMember(c => c.FontSizeValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.FontSizeMediumValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.FontSizeHeaderValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.FontSizeLittleValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.FontSizeMicroValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.FontSizeNanoValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.FontSizeLittleTextValue).SetIgnoreIfNull(true);
cm.MapMember(c => c.FontSizeName).SetIgnoreIfNull(true);
cm.MapMember(c => c.FontSizeLittleName).SetIgnoreIfNull(true);
cm.MapMember(c => c.FontSizeUnit).SetIgnoreIfNull(true);
cm.MapMember(c => c.BorderColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.BackGroundColor).SetIgnoreIfNull(true);
cm.MapMember(c => c.BoxMarginTop).SetIgnoreIfNull(true);
cm.MapMember(c => c.BoxMarginBot).SetIgnoreIfNull(true);
cm.MapMember(c => c.BoxMarginLeft).SetIgnoreIfNull(true);
cm.MapMember(c => c.BoxMarginRight).SetIgnoreIfNull(true);
});
}
}
@@ -8,109 +8,122 @@ using MongoDB.Bson.Serialization.Serializers;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents a contributor that participates in entity mapping for treatment-related entities by implementing the <see cref="IEntityMapContributor"/> contract.
/// </summary>
/// <remarks>
/// This class is intended to be used as a mapping contributor within an entity mapping pipeline, providing configuration logic specific to treatment entities.</remarks>
public class TreatmentMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers the BSON class maps required to serialize and deserialize the patient treatment
/// domain models (such as PatientTreatment, Medicine, Code, CodeStatus, Note, TreatmentRoute,
/// Entity, CodeAction, and PredictMedicationObservation) to and from MongoDB. Each map is only
/// registered when no class map already exists for the corresponding type, and the registrations
/// define custom element names, default values, null-ignoring behavior, and specialized
/// serializers for object identifiers and enum values.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientTreatment)))
BsonClassMap.RegisterClassMap<PatientTreatment>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.PatientId)
.SetSerializer(new ObjectIdSerializer(BsonType.String))
.SetIgnoreIfNull(true);
cm.MapMember(c => c.OrderControl)
.SetSerializer(new EnumSerializer<OrderControlType>(BsonType.String));
cm.MapMember(c => c.PlacerOrder).SetIgnoreIfNull(true);
cm.MapMember(c => c.FillerOrder).SetIgnoreIfNull(true);
cm.MapMember(c => c.OrderStatus).SetDefaultValue(string.Empty);
cm.MapMember(c => c.OrderTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.StartTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.EndTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.RequestedGiveCodes).SetDefaultValue(new List<Code>());
cm.MapMember(c => c.RequestedGiveTreatment).SetDefaultValue(string.Empty);
cm.MapMember(c => c.RequestedGiveCodesStatus).SetDefaultValue(new List<CodeStatus>());
cm.MapMember(c => c.RequestedGiveAmountMinimum).SetIgnoreIfNull(true);
cm.MapMember(c => c.RequestedGiveAmountMaximum).SetIgnoreIfNull(true);
cm.MapMember(c => c.RequestedGiveUnits).SetIgnoreIfNull(true);
cm.MapMember(c => c.RequestedDosageForm).SetIgnoreIfNull(true);
cm.MapMember(c => c.Notes).SetDefaultValue(new List<Note>());
cm.MapMember(c => c.Routes).SetDefaultValue(new List<TreatmentRoute>());
cm.MapMember(c => c.SingleDose).SetDefaultValue(false);
cm.MapMember(c => c.BoloPom).SetDefaultValue(false);
cm.MapMember(c => c.MessageTime);
cm.MapMember(c => c.SystemId).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Entity)))
BsonClassMap.RegisterClassMap<Entity>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.EntityIdentifier).SetIgnoreIfNull(true);
cm.MapMember(c => c.NamespaceId).SetIgnoreIfNull(true);
cm.MapMember(c => c.UniversalId).SetIgnoreIfNull(true);
cm.MapMember(c => c.UniversalIdType).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Code)))
BsonClassMap.RegisterClassMap<Code>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Identifier).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Text).SetDefaultValue(string.Empty);
cm.MapMember(c => c.CodingSystem).SetDefaultValue(string.Empty);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(CodeStatus)))
BsonClassMap.RegisterClassMap<CodeStatus>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Code).SetIgnoreIfNull(true);
cm.MapMember(c => c.Status).SetIgnoreIfNull(true);
cm.MapMember(c => c.AdministrationTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.EndAdministrationTime).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Note)))
BsonClassMap.RegisterClassMap<Note>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.CommentType).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Comment).SetDefaultValue(string.Empty);
cm.MapMember(c => c.EnteredTime).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(TreatmentRoute)))
BsonClassMap.RegisterClassMap<TreatmentRoute>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Route).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Medicine)))
BsonClassMap.RegisterClassMap<Medicine>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.Codes).SetIgnoreIfNull(true);
cm.MapMember(c => c.Notes).SetIgnoreIfNull(true);
cm.MapMember(c => c.Type).SetIgnoreIfNull(true);
cm.MapMember(c => c.Group).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(CodeAction)))
BsonClassMap.RegisterClassMap<CodeAction>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Code).SetIsRequired(true);
cm.MapMember(c => c.Action)
.SetSerializer(new EnumSerializer<ActionsEnum.ResourceAction>(BsonType.String));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PredictMedicationObservation)))
BsonClassMap.RegisterClassMap<PredictMedicationObservation>(cm => { cm.AutoMap(); });
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(PatientTreatment)))
BsonClassMap.RegisterClassMap<PatientTreatment>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.PatientId)
.SetSerializer(new ObjectIdSerializer(BsonType.String))
.SetIgnoreIfNull(true);
cm.MapMember(c => c.OrderControl)
.SetSerializer(new EnumSerializer<OrderControlType>(BsonType.String));
cm.MapMember(c => c.PlacerOrder).SetIgnoreIfNull(true);
cm.MapMember(c => c.FillerOrder).SetIgnoreIfNull(true);
cm.MapMember(c => c.OrderStatus).SetDefaultValue(string.Empty);
cm.MapMember(c => c.OrderTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.StartTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.EndTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.RequestedGiveCodes).SetDefaultValue(new List<Code>());
cm.MapMember(c => c.RequestedGiveTreatment).SetDefaultValue(string.Empty);
cm.MapMember(c => c.RequestedGiveCodesStatus).SetDefaultValue(new List<CodeStatus>());
cm.MapMember(c => c.RequestedGiveAmountMinimum).SetIgnoreIfNull(true);
cm.MapMember(c => c.RequestedGiveAmountMaximum).SetIgnoreIfNull(true);
cm.MapMember(c => c.RequestedGiveUnits).SetIgnoreIfNull(true);
cm.MapMember(c => c.RequestedDosageForm).SetIgnoreIfNull(true);
cm.MapMember(c => c.Notes).SetDefaultValue(new List<Note>());
cm.MapMember(c => c.Routes).SetDefaultValue(new List<TreatmentRoute>());
cm.MapMember(c => c.SingleDose).SetDefaultValue(false);
cm.MapMember(c => c.BoloPom).SetDefaultValue(false);
cm.MapMember(c => c.MessageTime);
cm.MapMember(c => c.SystemId).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Entity)))
BsonClassMap.RegisterClassMap<Entity>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.EntityIdentifier).SetIgnoreIfNull(true);
cm.MapMember(c => c.NamespaceId).SetIgnoreIfNull(true);
cm.MapMember(c => c.UniversalId).SetIgnoreIfNull(true);
cm.MapMember(c => c.UniversalIdType).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Code)))
BsonClassMap.RegisterClassMap<Code>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Identifier).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Text).SetDefaultValue(string.Empty);
cm.MapMember(c => c.CodingSystem).SetDefaultValue(string.Empty);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(CodeStatus)))
BsonClassMap.RegisterClassMap<CodeStatus>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Code).SetIgnoreIfNull(true);
cm.MapMember(c => c.Status).SetIgnoreIfNull(true);
cm.MapMember(c => c.AdministrationTime).SetIgnoreIfNull(true);
cm.MapMember(c => c.EndAdministrationTime).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Note)))
BsonClassMap.RegisterClassMap<Note>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.CommentType).SetDefaultValue(string.Empty);
cm.MapMember(c => c.Comment).SetDefaultValue(string.Empty);
cm.MapMember(c => c.EnteredTime).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(TreatmentRoute)))
BsonClassMap.RegisterClassMap<TreatmentRoute>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Route).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(Medicine)))
BsonClassMap.RegisterClassMap<Medicine>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id");
cm.MapMember(c => c.Name).SetIgnoreIfNull(true);
cm.MapMember(c => c.Codes).SetIgnoreIfNull(true);
cm.MapMember(c => c.Notes).SetIgnoreIfNull(true);
cm.MapMember(c => c.Type).SetIgnoreIfNull(true);
cm.MapMember(c => c.Group).SetIgnoreIfNull(true);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(CodeAction)))
BsonClassMap.RegisterClassMap<CodeAction>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Code).SetIsRequired(true);
cm.MapMember(c => c.Action)
.SetSerializer(new EnumSerializer<ActionsEnum.ResourceAction>(BsonType.String));
});
if (!BsonClassMap.IsClassMapRegistered(typeof(PredictMedicationObservation)))
BsonClassMap.RegisterClassMap<PredictMedicationObservation>(cm => { cm.AutoMap(); });
}
}
@@ -7,88 +7,99 @@ using MongoDB.Bson.Serialization.Serializers;
namespace adas_core.Infrastructure.Utils.MongoMaps;
/// <summary>
/// Represents an entity mapping contributor that provides mapping configuration for units.
/// </summary>
/// <remarks>
/// Implements <see cref="IEntityMapContributor"/> to integrate unit-specific mapping logic into the entity mapping pipeline.
/// </remarks>
public class UnitMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for the <see cref="Unit"/> and <see cref="UnitConfiguration"/> types used in MongoDB serialization.
/// Each registration is performed only if no class map has been previously registered for the target type, and configures
/// field-level mappings, default values, ignored properties, and custom serializers as required by the domain model.
/// </summary>
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(Unit)))
BsonClassMap.RegisterClassMap<Unit>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id").SetIsRequired(true);
cm.GetMemberMap(c => c.Title).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Name).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.LastUpdate).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.PointOfCareIds).SetIgnoreIfNull(true);
cm.UnmapMember(c => c.PocCount);
cm.MapMember(c => c.Status)
.SetDefaultValue(StatusEnum.Type.Ok)
.SetSerializer(
new NullableSerializer<StatusEnum.Type>(new EnumSerializer<StatusEnum.Type>(BsonType.String)));
cm.MapMember(c => c.Configuration).SetDefaultValue(new UnitConfiguration());
cm.GetMemberMap(c => c.AltableOptionListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.AllergyListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.DestinationListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.InternalDestinationListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.DiagnosisListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.DoctorListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.DoctorTypeListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.InsulationListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.MobilityOptionListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.OriginListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.PatientStatusListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.ProcedureListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.TestListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.ServiceListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.TherapeuticCeilingListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.TreatmentListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.VisitOptionListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.AccessControlListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.LanguageBarrierListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.DischargeStatusListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.PassiveSittingListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.GenericListId).SetIgnoreIfNull(true);
// No mapear estos campos
cm.UnmapProperty(c => c.AllergyList);
cm.UnmapProperty(c => c.DestinationList);
cm.UnmapProperty(c => c.DiagnosisList);
cm.UnmapProperty(c => c.DoctorList);
cm.UnmapProperty(c => c.DoctorTypeList);
cm.UnmapProperty(c => c.InsulationList);
cm.UnmapProperty(c => c.MobilityOptionList);
cm.UnmapProperty(c => c.OriginList);
cm.UnmapProperty(c => c.PatientStatusList);
cm.UnmapProperty(c => c.ProcedureList);
cm.UnmapProperty(c => c.TestList);
cm.UnmapProperty(c => c.AltableOptionList);
cm.UnmapProperty(c => c.DischargeStatusList);
cm.UnmapProperty(c => c.ServiceList);
cm.UnmapProperty(c => c.TherapeuticCeilingList);
cm.UnmapProperty(c => c.TreatmentList);
cm.UnmapProperty(c => c.VisitOptionList);
cm.UnmapProperty(c => c.PassiveSittingList);
cm.UnmapProperty(c => c.GenericList);
cm.UnmapProperty(c => c.LanguageBarrierList);
cm.UnmapProperty(c => c.PointOfCares);
cm.UnmapProperty(c => c.InternalDestinationList);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(UnitConfiguration)))
BsonClassMap.RegisterClassMap<UnitConfiguration>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.AutoAdt).SetDefaultValue(false);
cm.MapMember(c => c.ManualDischarge).SetDefaultValue(true);
cm.MapMember(c => c.ManualAdmit).SetDefaultValue(true);
cm.MapMember(c => c.ManualMove).SetDefaultValue(true);
cm.MapMember(c => c.ManualEdit).SetDefaultValue(true);
cm.MapMember(c => c.PlanDisplayConfiguration).SetIgnoreIfNull(true);
cm.MapMember(c => c.SmartDisplayConfiguration).SetIgnoreIfNull(true);
cm.MapMember(c => c.StandarDisplayConfiguration).SetIgnoreIfNull(true);
});
}
{
if (!BsonClassMap.IsClassMapRegistered(typeof(Unit)))
BsonClassMap.RegisterClassMap<Unit>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetElementName("_id").SetIsRequired(true);
cm.GetMemberMap(c => c.Title).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.Name).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.LastUpdate).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.PointOfCareIds).SetIgnoreIfNull(true);
cm.UnmapMember(c => c.PocCount);
cm.MapMember(c => c.Status)
.SetDefaultValue(StatusEnum.Type.Ok)
.SetSerializer(
new NullableSerializer<StatusEnum.Type>(new EnumSerializer<StatusEnum.Type>(BsonType.String)));
cm.MapMember(c => c.Configuration).SetDefaultValue(new UnitConfiguration());
cm.GetMemberMap(c => c.AltableOptionListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.AllergyListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.DestinationListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.InternalDestinationListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.DiagnosisListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.DoctorListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.DoctorTypeListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.InsulationListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.MobilityOptionListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.OriginListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.PatientStatusListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.ProcedureListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.TestListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.ServiceListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.TherapeuticCeilingListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.TreatmentListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.VisitOptionListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.AccessControlListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.LanguageBarrierListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.DischargeStatusListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.PassiveSittingListId).SetIgnoreIfNull(true);
cm.GetMemberMap(c => c.GenericListId).SetIgnoreIfNull(true);
// No mapear estos campos
cm.UnmapProperty(c => c.AllergyList);
cm.UnmapProperty(c => c.DestinationList);
cm.UnmapProperty(c => c.DiagnosisList);
cm.UnmapProperty(c => c.DoctorList);
cm.UnmapProperty(c => c.DoctorTypeList);
cm.UnmapProperty(c => c.InsulationList);
cm.UnmapProperty(c => c.MobilityOptionList);
cm.UnmapProperty(c => c.OriginList);
cm.UnmapProperty(c => c.PatientStatusList);
cm.UnmapProperty(c => c.ProcedureList);
cm.UnmapProperty(c => c.TestList);
cm.UnmapProperty(c => c.AltableOptionList);
cm.UnmapProperty(c => c.DischargeStatusList);
cm.UnmapProperty(c => c.ServiceList);
cm.UnmapProperty(c => c.TherapeuticCeilingList);
cm.UnmapProperty(c => c.TreatmentList);
cm.UnmapProperty(c => c.VisitOptionList);
cm.UnmapProperty(c => c.PassiveSittingList);
cm.UnmapProperty(c => c.GenericList);
cm.UnmapProperty(c => c.LanguageBarrierList);
cm.UnmapProperty(c => c.PointOfCares);
cm.UnmapProperty(c => c.InternalDestinationList);
});
if (!BsonClassMap.IsClassMapRegistered(typeof(UnitConfiguration)))
BsonClassMap.RegisterClassMap<UnitConfiguration>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.AutoAdt).SetDefaultValue(false);
cm.MapMember(c => c.ManualDischarge).SetDefaultValue(true);
cm.MapMember(c => c.ManualAdmit).SetDefaultValue(true);
cm.MapMember(c => c.ManualMove).SetDefaultValue(true);
cm.MapMember(c => c.ManualEdit).SetDefaultValue(true);
cm.MapMember(c => c.PlanDisplayConfiguration).SetIgnoreIfNull(true);
cm.MapMember(c => c.SmartDisplayConfiguration).SetIgnoreIfNull(true);
cm.MapMember(c => c.StandarDisplayConfiguration).SetIgnoreIfNull(true);
});
}
}
@@ -4,6 +4,9 @@ using MongoDB.Driver;
namespace adas_core.Infrastructure.Utils;
/// <summary>
/// Provides utility methods for interacting with MongoDB.
/// </summary>
public class MongoUtils
{
public static async Task EnsureIndexes<TDocument>(IMongoCollection<TDocument> collection,
@@ -7,88 +7,135 @@ using Newtonsoft.Json.Linq;
namespace adas_core.Infrastructure.Utils;
/// <summary>
/// Converts <see cref="PatientObservationAlarm"/> instances to and from JSON representation.
/// </summary>
/// <remarks>
/// Implements <see cref="IBsonSerializer"/> to provide BSON serialization and deserialization support for <see cref="PatientObservationAlarm"/> objects.
/// </remarks>
public class PatientObservationAlarmConverter : JsonConverter<PatientObservationAlarm>, IBsonSerializer
{
/// <summary>
/// Deserializes a BSON value into a .NET object using the supplied deserialization context and arguments.
/// </summary>
/// <param name="context">The BSON deserialization context that provides access to the reader and configuration.</param>
/// <param name="args">The deserialization arguments that influence how the value is read and converted.</param>
/// <returns>The deserialized .NET object produced from the BSON input.</returns>
/// <exception cref="NotImplementedException">Always thrown because the deserialization logic has not been implemented yet.</exception>
public object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
throw new NotImplementedException();
}
{
throw new NotImplementedException();
}
/// <summary>
/// Serializes the specified value to BSON format using the provided serialization context and arguments.
/// </summary>
/// <param name="context">The BSON serialization context that holds the writer and configuration for the serialization operation.</param>
/// <param name="args">The BSON serialization arguments containing additional information that influences the serialization behavior.</param>
/// <param name="value">The object to be serialized into BSON.</param>
/// <exception cref="System.NotImplementedException">Thrown because the method has not yet been implemented.</exception>
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
{
throw new NotImplementedException();
}
{
throw new NotImplementedException();
}
public Type ValueType => typeof(PatientObservationAlarm);
/// <summary>
/// Deserializes a JSON token into a <see cref="PatientObservationAlarm"/> instance. Returns <c>null</c> when the JSON token is <see cref="JsonToken.Null"/>, otherwise extracts the <c>value</c> property from the JSON object and constructs a new alarm, falling back to a default <see cref="object"/> when the value is missing.
/// </summary>
/// <param name="reader">The <see cref="JsonReader"/> positioned at the JSON token to read.</param>
/// <param name="objectType">The type of the object being deserialized.</param>
/// <param name="existingValue">The existing value of the object being deserialized, or <c>null</c> if none exists.</param>
/// <param name="hasExistingValue">Indicates whether <paramref name="existingValue"/> contains a value to be used during deserialization.</param>
/// <param name="serializer">The <see cref="JsonSerializer"/> used for nested object deserialization.</param>
/// <returns>A <see cref="PatientObservationAlarm"/> populated from the JSON, or <c>null</c> if the JSON token is <see cref="JsonToken.Null"/>.</returns>
public override PatientObservationAlarm? ReadJson(JsonReader reader, Type objectType,
PatientObservationAlarm? existingValue,
bool hasExistingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
// Implement your custom deserialization logic here
var jsonObject = JObject.Load(reader);
// Deserialize properties from jsonObject to PatientObservationAlarm object
// Example: Deserialize 'Value' property
var value = jsonObject.GetValue("value")?.ToObject<object>();
return new PatientObservationAlarm
PatientObservationAlarm? existingValue,
bool hasExistingValue, JsonSerializer serializer)
{
Value = value ?? new object()
// Other property assignments...
};
}
public new void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value != null)
{
var jo = JObject.FromObject(value);
// Add [JsonConverter(typeof(StringEnumConverter))] attribute to specified properties
AddStringEnumConverterAttribute(value, "eventPhase");
AddStringEnumConverterAttribute(value, "state");
AddStringEnumConverterAttribute(value, "priority");
AddStringEnumConverterAttribute(value, "type");
jo.Property("messageTime")?.Remove();
jo.Property("expired")?.Remove();
jo.WriteTo(writer);
}
}
private static void AddStringEnumConverterAttribute(object value, string propertyName)
{
var prop = value.GetType().GetProperty(propertyName);
var attr = new JsonConverterAttribute(typeof(StringEnumConverter));
if (prop != null)
{
var attrs = prop.GetCustomAttributes(false);
// Check if the attribute is not already applied
if (Array.Find(attrs, a => a is JsonConverterAttribute) == null)
if (reader.TokenType == JsonToken.Null)
return null;
// Implement your custom deserialization logic here
var jsonObject = JObject.Load(reader);
// Deserialize properties from jsonObject to PatientObservationAlarm object
// Example: Deserialize 'Value' property
var value = jsonObject.GetValue("value")?.ToObject<object>();
return new PatientObservationAlarm
{
// Create a new array that includes the existing attributes and the new one
var newAttrs = new object[attrs.Length + 1];
Array.Copy(attrs, newAttrs, attrs.Length);
newAttrs[attrs.Length] = attr;
Value = value ?? new object()
// Other property assignments...
};
}
// Use reflection to set the new attributes array
var field = typeof(PropertyInfo).GetField("m_customAttributes",
BindingFlags.Instance | BindingFlags.NonPublic);
field?.SetValue(prop, newAttrs);
/// <summary>
/// Serializes the specified object to JSON, applying the <see cref="StringEnumConverter"/> to the <c>eventPhase</c>, <c>state</c>, <c>priority</c>, and <c>type</c> properties, while excluding the <c>messageTime</c> and <c>expired</c> properties from the output. Does nothing when the <paramref name="value"/> is <see langword="null"/>.
/// </summary>
/// <param name="writer">The <see cref="JsonWriter"/> to which the JSON output is written.</param>
/// <param name="value">The object to serialize; if <see langword="null"/>, the method performs no action.</param>
/// <param name="serializer">The <see cref="JsonSerializer"/> used to assist with the conversion.</param>
public new void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value != null)
{
var jo = JObject.FromObject(value);
// Add [JsonConverter(typeof(StringEnumConverter))] attribute to specified properties
AddStringEnumConverterAttribute(value, "eventPhase");
AddStringEnumConverterAttribute(value, "state");
AddStringEnumConverterAttribute(value, "priority");
AddStringEnumConverterAttribute(value, "type");
jo.Property("messageTime")?.Remove();
jo.Property("expired")?.Remove();
jo.WriteTo(writer);
}
}
}
/// <summary>
/// Adds a <see cref="StringEnumConverter"/> JSON converter attribute to the specified property of the given object, if one is not already present. Uses reflection to inject the attribute into the property's internal custom attributes array, avoiding the need to recompile the type with the attribute applied.
/// </summary>
/// <param name="value">The object instance whose type is inspected to locate the target property.</param>
/// <param name="propertyName">The name of the property to which the <see cref="StringEnumConverter"/> attribute will be added.</param>
private static void AddStringEnumConverterAttribute(object value, string propertyName)
{
var prop = value.GetType().GetProperty(propertyName);
var attr = new JsonConverterAttribute(typeof(StringEnumConverter));
if (prop != null)
{
var attrs = prop.GetCustomAttributes(false);
// Check if the attribute is not already applied
if (Array.Find(attrs, a => a is JsonConverterAttribute) == null)
{
// Create a new array that includes the existing attributes and the new one
var newAttrs = new object[attrs.Length + 1];
Array.Copy(attrs, newAttrs, attrs.Length);
newAttrs[attrs.Length] = attr;
// Use reflection to set the new attributes array
var field = typeof(PropertyInfo).GetField("m_customAttributes",
BindingFlags.Instance | BindingFlags.NonPublic);
field?.SetValue(prop, newAttrs);
}
}
}
/// <summary>
/// Writes the JSON representation of a PatientObservationAlarm using the specified writer and serializer.
/// </summary>
/// <param name="writer">The JsonWriter to which the JSON output is written.</param>
/// <param name="value">The PatientObservationAlarm value to serialize.</param>
/// <param name="serializer">The JsonSerializer used during the serialization process.</param>
/// <exception cref="NotImplementedException">The method has not been implemented.</exception>
public override void WriteJson(JsonWriter writer, PatientObservationAlarm? value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
{
throw new NotImplementedException();
}
}
/*How to use it:
@@ -7,62 +7,103 @@ using Newtonsoft.Json.Linq;
namespace adas_core.Infrastructure.Utils;
/// <summary>
/// Provides conversion logic for <see cref="PatientObservation"/> values, supporting JSON serialization through <see cref="JsonConverter{T}"/> and BSON serialization through <see cref="IBsonSerializer"/>.
/// </summary>
/// <remarks>
/// The optional <see cref="Type"/> constructor parameter specifies the target value type used during conversion.
/// </remarks>
public class PatientObservationConverter(Type? valueType) : JsonConverter<PatientObservation>, IBsonSerializer
{
/// <summary>
/// Deserializes a BSON value using the provided deserialization context and arguments.
/// </summary>
/// <param name="context">The deserialization context that provides the BSON reader and configuration.</param>
/// <param name="args">The deserialization arguments containing additional information for the operation.</param>
/// <returns>An object representing the deserialized value.</returns>
/// <exception cref="NotImplementedException">Always thrown because the method is not yet implemented.</exception>
public object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
throw new NotImplementedException();
}
{
throw new NotImplementedException();
}
/// <summary>
/// Serializes the specified value into a BSON format using the provided serialization context and arguments. This implementation is not yet provided and currently throws a <see cref="NotImplementedException"/>.
/// </summary>
/// <param name="context">The BSON serialization context that provides access to the writer and configuration used during serialization.</param>
/// <param name="args">The BSON serialization arguments containing additional information that may influence the serialization process.</param>
/// <param name="value">The object to be serialized into BSON.</param>
/// <exception cref="NotImplementedException">Thrown because the serialization logic has not been implemented.</exception>
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
{
throw new NotImplementedException();
}
{
throw new NotImplementedException();
}
public Type? ValueType { get; } = valueType;
/// <summary>
/// Serializes a <see cref="PatientObservation"/> instance to JSON, applying a string enum converter to the <c>Status</c> property and excluding the <c>MessageTime</c> property from the output. If the supplied value is <c>null</c>, nothing is written to the <paramref name="writer"/>.
/// </summary>
/// <param name="writer">The JSON writer that receives the serialized output.</param>
/// <param name="value">The patient observation to serialize. When <c>null</c>, the method performs no serialization.</param>
/// <param name="serializer">The JSON serializer used during the conversion process.</param>
public override void WriteJson(JsonWriter writer, PatientObservation? value, JsonSerializer serializer)
{
if (value != null)
{
var jo = JObject.FromObject(value);
// Add [JsonConverter(typeof(StringEnumConverter))] attribute to specified properties
AddStringEnumConverterAttribute(value, "Status");
jo.Property("MessageTime")?.Remove();
jo.WriteTo(writer);
}
}
private static void AddStringEnumConverterAttribute(object value, string propertyName)
{
var prop = value.GetType().GetProperty(propertyName);
var attr = new JsonConverterAttribute(typeof(StringEnumConverter));
if (prop != null)
{
var attrs = prop.GetCustomAttributes(false);
// Check if the attribute is not already applied
if (Array.Find(attrs, a => a is JsonConverterAttribute) == null)
if (value != null)
{
// Create a new array that includes the existing attributes and the new one
var newAttrs = new object[attrs.Length + 1];
Array.Copy(attrs, newAttrs, attrs.Length);
newAttrs[attrs.Length] = attr;
// Use reflection to set the new attributes array
var field = typeof(PropertyInfo).GetField("m_customAttributes",
BindingFlags.Instance | BindingFlags.NonPublic);
field?.SetValue(prop, newAttrs);
var jo = JObject.FromObject(value);
// Add [JsonConverter(typeof(StringEnumConverter))] attribute to specified properties
AddStringEnumConverterAttribute(value, "Status");
jo.Property("MessageTime")?.Remove();
jo.WriteTo(writer);
}
}
}
/// <summary>
/// Adds a JsonConverterAttribute for StringEnumConverter to the specified property if one is not already present. If the property cannot be found on the object's type, the method does nothing.
/// </summary>
/// <param name="value">The object instance whose property should be annotated.</param>
/// <param name="propertyName">The name of the property to which the StringEnumConverter attribute should be added.</param>
private static void AddStringEnumConverterAttribute(object value, string propertyName)
{
var prop = value.GetType().GetProperty(propertyName);
var attr = new JsonConverterAttribute(typeof(StringEnumConverter));
if (prop != null)
{
var attrs = prop.GetCustomAttributes(false);
// Check if the attribute is not already applied
if (Array.Find(attrs, a => a is JsonConverterAttribute) == null)
{
// Create a new array that includes the existing attributes and the new one
var newAttrs = new object[attrs.Length + 1];
Array.Copy(attrs, newAttrs, attrs.Length);
newAttrs[attrs.Length] = attr;
// Use reflection to set the new attributes array
var field = typeof(PropertyInfo).GetField("m_customAttributes",
BindingFlags.Instance | BindingFlags.NonPublic);
field?.SetValue(prop, newAttrs);
}
}
}
/// <summary>
/// Deserializes a JSON representation into a <see cref="PatientObservation"/> instance.
/// </summary>
/// <param name="reader">The <see cref="JsonReader"/> to read the JSON from.</param>
/// <param name="objectType">The type of the object being deserialized.</param>
/// <param name="existingValue">The existing value of the object to populate, or <see langword="null"/> if none.</param>
/// <param name="hasExistingValue">Indicates whether <paramref name="existingValue"/> contains a value to reuse.</param>
/// <param name="serializer">The <see cref="JsonSerializer"/> used to read nested values.</param>
/// <returns>A <see cref="PatientObservation"/> instance produced from the JSON.</returns>
/// <exception cref="NotImplementedException">Thrown because the method has not been implemented yet.</exception>
public override PatientObservation ReadJson(JsonReader reader, Type objectType, PatientObservation? existingValue,
bool hasExistingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
bool hasExistingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
@@ -9,73 +9,120 @@ using JsonConverterAttribute = Newtonsoft.Json.JsonConverterAttribute;
namespace adas_core.Infrastructure.Utils;
/// <summary>
/// Provides JSON conversion for <see cref="PumpObservation"/> instances and supports BSON serialization through the <see cref="IBsonSerializer"/> interface.
/// </summary>
public class PatientPumpObservationConverter : JsonConverter<PumpObservation>, IBsonSerializer
{
/// <summary>
/// Deserializes a BSON value into a .NET object using the provided deserialization context and arguments.
/// </summary>
/// <param name="context">The BSON deserialization context that provides access to the reader and configuration.</param>
/// <param name="args">The deserialization arguments containing additional settings for the operation.</param>
/// <returns>The deserialized object.</returns>
/// <exception cref="NotImplementedException">Thrown when the method is invoked, as the deserialization logic has not been implemented.</exception>
public object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
throw new NotImplementedException();
}
{
throw new NotImplementedException();
}
/// <summary>
/// Serializes the specified value into BSON format using the provided serialization context and arguments.
/// This method is not yet implemented and currently throws an exception when invoked.
/// </summary>
/// <param name="context">The BSON serialization context that provides the writer and configuration for the operation.</param>
/// <param name="args">The BSON serialization arguments that influence the serialization behavior.</param>
/// <param name="value">The object to be serialized into BSON.</param>
/// <exception cref="NotImplementedException">Thrown because the method has not been implemented.</exception>
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
{
throw new NotImplementedException();
}
{
throw new NotImplementedException();
}
public Type ValueType => typeof(PatientObservationAlarm);
/// <summary>
/// Deserializes a JSON representation into a <see cref="PumpObservation"/> instance.
/// This implementation is not yet provided and always throws an exception.
/// </summary>
/// <param name="reader">The <see cref="JsonReader"/> to read JSON from.</param>
/// <param name="objectType">The type of the object to deserialize.</param>
/// <param name="existingValue">The existing value of the object being read, or <c>null</c> if there is no existing value.</param>
/// <param name="hasExistingValue">A value indicating whether <paramref name="existingValue"/> has a value.</param>
/// <param name="serializer">The <see cref="JsonSerializer"/> used for deserialization.</param>
/// <returns>A <see cref="PumpObservation"/> instance reconstructed from the JSON data.</returns>
/// <exception cref="NotImplementedException">Thrown because the method has not been implemented yet.</exception>
public override PumpObservation ReadJson(JsonReader reader, Type objectType,
PumpObservation? existingValue,
bool hasExistingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public new void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value != null)
PumpObservation? existingValue,
bool hasExistingValue, JsonSerializer serializer)
{
var jo = JObject.FromObject(value);
// Add [JsonConverter(typeof(StringEnumConverter))] attribute to specified properties
AddStringEnumConverterAttribute(value, "Event");
AddStringEnumConverterAttribute(value, "Status");
AddStringEnumConverterAttribute(value, "PumpMode");
AddStringEnumConverterAttribute(value, "InfusingStatus");
AddStringEnumConverterAttribute(value, "AlarmMode");
jo.WriteTo(writer);
throw new NotImplementedException();
}
}
public override void WriteJson(JsonWriter writer, PumpObservation? value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
private static void AddStringEnumConverterAttribute(object value, string propertyName)
{
var prop = value.GetType().GetProperty(propertyName);
var attr = new JsonConverterAttribute(typeof(StringEnumConverter));
if (prop != null)
/// <summary>
/// Serializes the specified value to JSON, applying string enum conversion to the Event, Status, PumpMode, InfusingStatus, and AlarmMode properties. No output is written when the value is null.
/// </summary>
/// <param name="writer">The JSON writer to which the serialized object is written.</param>
/// <param name="value">The object to serialize; if null, the method does nothing.</param>
/// <param name="serializer">The JSON serializer used as context for the conversion.</param>
public new void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
var attrs = prop.GetCustomAttributes(false);
// Check if the attribute is not already applied
if (Array.Find(attrs, a => a is JsonConverterAttribute) == null)
if (value != null)
{
// Create a new array that includes the existing attributes and the new one
var newAttrs = new object[attrs.Length + 1];
Array.Copy(attrs, newAttrs, attrs.Length);
newAttrs[attrs.Length] = attr;
// Use reflection to set the new attributes array
var field = typeof(PropertyInfo).GetField("m_customAttributes",
BindingFlags.Instance | BindingFlags.NonPublic);
field?.SetValue(prop, newAttrs);
var jo = JObject.FromObject(value);
// Add [JsonConverter(typeof(StringEnumConverter))] attribute to specified properties
AddStringEnumConverterAttribute(value, "Event");
AddStringEnumConverterAttribute(value, "Status");
AddStringEnumConverterAttribute(value, "PumpMode");
AddStringEnumConverterAttribute(value, "InfusingStatus");
AddStringEnumConverterAttribute(value, "AlarmMode");
jo.WriteTo(writer);
}
}
/// <summary>
/// Serializes a <see cref="PumpObservation"/> instance to JSON. This override is not yet implemented and will always throw.
/// </summary>
/// <param name="writer">The JSON writer used to emit the serialized output.</param>
/// <param name="value">The <see cref="PumpObservation"/> to serialize, or <see langword="null"/> if no value is available.</param>
/// <param name="serializer">The <see cref="JsonSerializer"/> providing serialization context and options.</param>
/// <exception cref="NotImplementedException">Always thrown because the method has not been implemented.</exception>
public override void WriteJson(JsonWriter writer, PumpObservation? value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
/// <summary>
/// Adds a <see cref="JsonConverterAttribute"/> of type <see cref="StringEnumConverter"/> to the specified property, ensuring that enum values are serialized as their string names rather than numeric values. If the property already has a <see cref="JsonConverterAttribute"/> applied, no changes are made.
/// </summary>
/// <param name="value">The object instance whose property will be inspected for the attribute.</param>
/// <param name="propertyName">The name of the property to which the converter attribute will be added.</param>
private static void AddStringEnumConverterAttribute(object value, string propertyName)
{
var prop = value.GetType().GetProperty(propertyName);
var attr = new JsonConverterAttribute(typeof(StringEnumConverter));
if (prop != null)
{
var attrs = prop.GetCustomAttributes(false);
// Check if the attribute is not already applied
if (Array.Find(attrs, a => a is JsonConverterAttribute) == null)
{
// Create a new array that includes the existing attributes and the new one
var newAttrs = new object[attrs.Length + 1];
Array.Copy(attrs, newAttrs, attrs.Length);
newAttrs[attrs.Length] = attr;
// Use reflection to set the new attributes array
var field = typeof(PropertyInfo).GetField("m_customAttributes",
BindingFlags.Instance | BindingFlags.NonPublic);
field?.SetValue(prop, newAttrs);
}
}
}
}
}
/*How to use it:
@@ -7,57 +7,98 @@ using Newtonsoft.Json.Linq;
namespace adas_core.Infrastructure.Utils;
/// <summary>
/// Provides JSON conversion for the <see cref="Person"/> type via <see cref="JsonConverter{T}"/> and also implements BSON serialization through <see cref="IBsonSerializer"/>.
/// </summary>
/// <remarks>
/// The primary constructor accepts a <see cref="Type"/> that represents the value type associated with this converter.
/// </remarks>
public class PersonConverter(Type valueType) : JsonConverter<Person>, IBsonSerializer
{
/// <summary>
/// Deserializes a BSON value into a .NET object using the provided context and arguments.
/// </summary>
/// <param name="context">The BSON deserialization context.</param>
/// <param name="args">The BSON deserialization arguments.</param>
/// <returns>The deserialized object.</returns>
/// <exception cref="NotImplementedException">Always thrown because the method is not yet implemented.</exception>
public object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
throw new NotImplementedException();
}
{
throw new NotImplementedException();
}
/// <summary>
/// Serializes the specified object to BSON format using the provided serialization context and arguments. The method is not implemented and always throws an exception.
/// </summary>
/// <param name="context">The BSON serialization context that provides the writer and configuration for the operation.</param>
/// <param name="args">The BSON serialization arguments containing additional settings for the serialization.</param>
/// <param name="value">The object to be serialized to BSON.</param>
/// <exception cref="NotImplementedException">Always thrown because the method has not been implemented yet.</exception>
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
{
throw new NotImplementedException();
}
{
throw new NotImplementedException();
}
public Type ValueType { get; } = valueType;
/// <summary>
/// Serializes a <see cref="Person"/> instance to JSON, ensuring the <c>Gender</c> property is written using string-based enum representation. Does nothing when the provided value is <see langword="null"/>.
/// </summary>
/// <param name="writer">The <see cref="JsonWriter"/> to which the JSON representation is written.</param>
/// <param name="value">The <see cref="Person"/> instance to serialize. If <see langword="null"/>, the method returns without writing anything.</param>
/// <param name="serializer">The <see cref="JsonSerializer"/> used during the conversion to a <see cref="JObject"/>.</param>
public override void WriteJson(JsonWriter writer, Person? value, JsonSerializer serializer)
{
if (value == null) return;
var jo = JObject.FromObject(value);
// Add [JsonConverter(typeof(StringEnumConverter))] attribute to specified properties
AddStringEnumConverterAttribute(value, "Gender");
jo.WriteTo(writer);
}
{
if (value == null) return;
var jo = JObject.FromObject(value);
// Add [JsonConverter(typeof(StringEnumConverter))] attribute to specified properties
AddStringEnumConverterAttribute(value, "Gender");
jo.WriteTo(writer);
}
/// <summary>
/// Adds a StringEnumConverter JSON converter attribute to the specified property if one is not already applied, ensuring enum values are serialized as strings. The method returns silently if the property is not found on the value's type or if a JsonConverterAttribute is already present.
/// </summary>
/// <param name="value">The object instance whose property's attributes will be inspected and modified.</param>
/// <param name="propertyName">The name of the property to which the converter attribute will be added.</param>
private static void AddStringEnumConverterAttribute(object value, string propertyName)
{
var prop = value.GetType().GetProperty(propertyName);
var attr = new JsonConverterAttribute(typeof(StringEnumConverter));
if (prop == null) return;
var attrs = prop.GetCustomAttributes(false);
// Check if the attribute is not already applied
if (Array.Find(attrs, a => a is JsonConverterAttribute) != null) return;
// Create a new array that includes the existing attributes and the new one
var newAttrs = new object[attrs.Length + 1];
Array.Copy(attrs, newAttrs, attrs.Length);
newAttrs[attrs.Length] = attr;
// Use reflection to set the new attributes array
var field = typeof(PropertyInfo).GetField("m_customAttributes",
BindingFlags.Instance | BindingFlags.NonPublic);
field?.SetValue(prop, newAttrs);
}
{
var prop = value.GetType().GetProperty(propertyName);
var attr = new JsonConverterAttribute(typeof(StringEnumConverter));
if (prop == null) return;
var attrs = prop.GetCustomAttributes(false);
// Check if the attribute is not already applied
if (Array.Find(attrs, a => a is JsonConverterAttribute) != null) return;
// Create a new array that includes the existing attributes and the new one
var newAttrs = new object[attrs.Length + 1];
Array.Copy(attrs, newAttrs, attrs.Length);
newAttrs[attrs.Length] = attr;
// Use reflection to set the new attributes array
var field = typeof(PropertyInfo).GetField("m_customAttributes",
BindingFlags.Instance | BindingFlags.NonPublic);
field?.SetValue(prop, newAttrs);
}
/// <summary>
/// Reads the JSON representation of a <see cref="Person"/> object. This override is not yet implemented.
/// </summary>
/// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
/// <param name="objectType">The type of the object to deserialize.</param>
/// <param name="existingValue">The existing value of the object being read, or <c>null</c> if there is no existing value.</param>
/// <param name="hasExistingValue">A value indicating whether <paramref name="existingValue"/> has a value.</param>
/// <param name="serializer">The <see cref="JsonSerializer"/> calling the method.</param>
/// <returns>A <see cref="Person"/> instance deserialized from the JSON.</returns>
/// <exception cref="NotImplementedException">Always thrown because the method has not been implemented.</exception>
public override Person ReadJson(JsonReader reader, Type objectType, Person? existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
@@ -8,34 +8,45 @@ using ILogger = Serilog.ILogger;
namespace adas_core.Infrastructure.Utils;
/// <summary>
/// Handles errors that occur during RabbitMQ message consumption and publishes them using the configured publisher service.
/// </summary>
public class RabbitConsumerErrorHandler(IPublisherService publisherService)
{
private const int MaxRetries = 2;
private static readonly ILogger Logger = Log.ForContext<RabbitConsumerErrorHandler>();
/// <summary>
/// Processes a received message by invoking the next handler in the pipeline, and on failure logs the error,
/// serializes the message body to a string, and delegates to the retry handler using the message properties,
/// received info, and the captured exception.
/// </summary>
/// <param name="message">The deserialized message whose body is inspected or re-serialized for retry handling.</param>
/// <param name="receivedInfo">Information about the message receipt, passed to the retry handler.</param>
/// <param name="next">The asynchronous delegate representing the next step in the processing pipeline.</param>
public async Task HandleAsync<T>(
Message<T> message,
MessageReceivedInfo receivedInfo,
Func<Task> next)
{
try
Message<T> message,
MessageReceivedInfo receivedInfo,
Func<Task> next)
{
await next();
try
{
await next();
}
catch (Exception exception)
{
Logger.Error(exception, "Consumer error {Message}", exception.Message);
var properties = message.Properties;
var body = Encoding.UTF8.GetString(
message.Body is byte[] bytes
? bytes
: Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message.Body)));
HandleRetries(receivedInfo, properties, body, exception);
}
}
catch (Exception exception)
{
Logger.Error(exception, "Consumer error {Message}", exception.Message);
var properties = message.Properties;
var body = Encoding.UTF8.GetString(
message.Body is byte[] bytes
? bytes
: Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message.Body)));
HandleRetries(receivedInfo, properties, body, exception);
}
}
private void HandleRetries(
MessageReceivedInfo receivedInfo,
@@ -102,44 +113,58 @@ public class RabbitConsumerErrorHandler(IPublisherService publisherService)
}
}
/// <summary>
/// Retrieves the retry count from the message properties headers. Returns the deserialized integer value when a "retries" header containing a byte array is present, or 0 when the headers are missing, the key is not found, or the value is not a byte array.
/// </summary>
/// <param name="properties">The message properties whose headers are inspected for the "retries" entry.</param>
/// <returns>The number of retries parsed from the "retries" header, or 0 if the header is absent or not a byte array.</returns>
private int GetRetries(MessageProperties properties)
{
if (properties.Headers != null &&
properties.Headers.TryGetValue("retries", out var retriesObj) &&
retriesObj is byte[] bytes)
{
return BitConverter.ToInt32(bytes, 0);
if (properties.Headers != null &&
properties.Headers.TryGetValue("retries", out var retriesObj) &&
retriesObj is byte[] bytes)
{
return BitConverter.ToInt32(bytes, 0);
}
return 0;
}
return 0;
}
/// <summary>
/// Constructs a new error message that preserves the original message properties and attaches exception details for downstream processing or dead-letter handling.
/// </summary>
/// <param name="receivedInfo">The metadata of the original received message, including exchange, routing key, and queue information used to identify the error source.</param>
/// <param name="originalProperties">The properties of the original message from which delivery mode, content type, correlation id, and message id are copied.</param>
/// <param name="body">The raw body of the original message that caused the error.</param>
/// <param name="exception">The exception that was raised, whose message is included in the error payload.</param>
/// <param name="headers">The headers to associate with the resulting error message.</param>
/// <returns>A <see cref="Message{Error}"/> containing the constructed error and the propagated message properties.</returns>
private static Message<Error> CreateErrorMessage(
MessageReceivedInfo receivedInfo,
MessageProperties originalProperties,
string body,
Exception exception,
Dictionary<string, object> headers)
{
var props = new MessageProperties
MessageReceivedInfo receivedInfo,
MessageProperties originalProperties,
string body,
Exception exception,
Dictionary<string, object> headers)
{
Headers = headers,
DeliveryMode = originalProperties.DeliveryMode,
ContentType = originalProperties.ContentType,
CorrelationId = originalProperties.CorrelationId,
MessageId = originalProperties.MessageId
};
var error = new Error(
body,
exception.Message,
receivedInfo.Exchange,
receivedInfo.RoutingKey,
receivedInfo.Queue,
DateTime.UtcNow,
props
);
return new Message<Error>(error, props);
}
var props = new MessageProperties
{
Headers = headers,
DeliveryMode = originalProperties.DeliveryMode,
ContentType = originalProperties.ContentType,
CorrelationId = originalProperties.CorrelationId,
MessageId = originalProperties.MessageId
};
var error = new Error(
body,
exception.Message,
receivedInfo.Exchange,
receivedInfo.RoutingKey,
receivedInfo.Queue,
DateTime.UtcNow,
props
);
return new Message<Error>(error, props);
}
}
@@ -4,26 +4,43 @@ using Newtonsoft.Json;
namespace adas_core.Infrastructure.Utils;
/// <summary>
/// Provides an implementation of <see cref="IErrorMessageSerializer"/> tailored for use with RabbitMQ, responsible for serializing error messages into a format suitable for transport over RabbitMQ.
/// </summary>
/// <remarks>
/// This class serves as the RabbitMQ-specific concrete realization of the <see cref="IErrorMessageSerializer"/> contract.
/// </remarks>
public class RabbitIErrorMessageSerializer : IErrorMessageSerializer
{
/// <summary>
/// Deserializes a JSON-encoded string by unescaping its JSON representation and converts the result to a UTF-8 byte array.
/// Returns <c>null</c> when the deserialized string is <c>null</c>.
/// </summary>
/// <param name="messageBody">The JSON-encoded string to unescape and convert.</param>
/// <returns>A UTF-8 encoded byte array of the unescaped string, or <c>null</c> if the deserialized value is <c>null</c>.</returns>
public byte[]? Deserialize(string messageBody)
{
var unescapedJsonString = JsonConvert.DeserializeObject<string>(messageBody);
return unescapedJsonString != null ? Encoding.UTF8.GetBytes(unescapedJsonString) : null;
}
{
var unescapedJsonString = JsonConvert.DeserializeObject<string>(messageBody);
return unescapedJsonString != null ? Encoding.UTF8.GetBytes(unescapedJsonString) : null;
}
/// <summary>
/// Attempts to deserialize the UTF-8 decoded message body as a JSON string, returning the original stringified content if deserialization fails.
/// </summary>
/// <param name="messageBody">The raw byte array representing the message body to be deserialized.</param>
/// <returns>The deserialized JSON string if successful; otherwise, the raw UTF-8 decoded string. Returns <see langword="null"/> if the deserialized JSON value is null.</returns>
public string? Serialize(byte[] messageBody)
{
var stringifiedMsgBody = Encoding.UTF8.GetString(messageBody);
try
{
return JsonConvert.DeserializeObject<string>(stringifiedMsgBody);
var stringifiedMsgBody = Encoding.UTF8.GetString(messageBody);
try
{
return JsonConvert.DeserializeObject<string>(stringifiedMsgBody);
}
catch (Exception)
{
return stringifiedMsgBody;
}
}
catch (Exception)
{
return stringifiedMsgBody;
}
}
}
+18 -7
View File
@@ -2,17 +2,28 @@
namespace adas_core.Infrastructure.Utils;
/// <summary>
/// Provides static utility methods for working with types.
/// </summary>
public static class TypesUtils
{
/// <summary>
/// Retrieves the driver <see cref="Type"/> associated with the specified device type and device.
/// Supports "Relay" (mapped to adas-core.module.Relays) and "LightBeacon" (mapped to adas-core.module.LightBeacons) device types.
/// </summary>
/// <param name="deviceType">The category of the device used to determine the appropriate driver type.</param>
/// <param name="device">The specific device identifier used when resolving the driver type.</param>
/// <returns>The <see cref="Type"/> of the driver that corresponds to the given device type.</returns>
/// <exception cref="AdasException">Thrown when the provided <paramref name="deviceType"/> is not a recognized value.</exception>
public static Type GetDriver(string deviceType, string device)
{
return deviceType switch
{
"Relay" => GetType("adas-core.module.Relays", device, deviceType),
"LightBeacon" => GetType("adas-core.module.LightBeacons", device, deviceType),
_ => throw new AdasException($"Device type {deviceType} not found")
};
}
return deviceType switch
{
"Relay" => GetType("adas-core.module.Relays", device, deviceType),
"LightBeacon" => GetType("adas-core.module.LightBeacons", device, deviceType),
_ => throw new AdasException($"Device type {deviceType} not found")
};
}
private static Type GetType(string typeName, string device, string deviceType)
{