diff --git a/source/backend/api/Controllers/ChesController.cs b/source/backend/api/Controllers/ChesController.cs
new file mode 100644
index 0000000000..893b6fd63c
--- /dev/null
+++ b/source/backend/api/Controllers/ChesController.cs
@@ -0,0 +1,37 @@
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Pims.Api.Models.Ches;
+using Pims.Api.Models.CodeTypes;
+using Pims.Api.Services;
+
+namespace Pims.Api.Controllers
+{
+ [ApiController]
+ [ApiVersion("1.0")]
+ [Route("v{version:apiVersion}/ches/")]
+ [Route("/ches/")]
+ public class ChesController : ControllerBase
+ {
+ private readonly IEmailService _emailService;
+
+ public ChesController(IEmailService emailService)
+ {
+ _emailService = emailService;
+ }
+
+ ///
+ /// Send an email using CHES service.
+ ///
+ [HttpPost("email")]
+ [ProducesResponseType(typeof(EmailResponse), 200)]
+ public async Task SendEmail([FromBody] EmailRequest request)
+ {
+ var result = await _emailService.SendEmailAsync(request);
+ if (result.Status == ExternalResponseStatus.Error)
+ {
+ return StatusCode(500, new { error = result.Message, details = result.Payload });
+ }
+ return Ok(result);
+ }
+ }
+}
diff --git a/source/backend/api/Helpers/Healthchecks/ChesHealthCheck.cs b/source/backend/api/Helpers/Healthchecks/ChesHealthCheck.cs
new file mode 100644
index 0000000000..f09481c05f
--- /dev/null
+++ b/source/backend/api/Helpers/Healthchecks/ChesHealthCheck.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Pims.Api.Repositories.Ches;
+
+namespace Pims.Api.Helpers.Healthchecks
+{
+ ///
+ /// Health check for CHES service connectivity.
+ ///
+ public class ChesHealthCheck : IHealthCheck
+ {
+ private readonly IEmailRepository _repository;
+
+ public ChesHealthCheck(IEmailRepository repository)
+ {
+ _repository = repository;
+ }
+
+ public async Task CheckHealthAsync(
+ HealthCheckContext context, CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ const int maxJitterMilliseconds = 10000;
+ var jitter = Random.Shared.Next(0, maxJitterMilliseconds + 1);
+ if (jitter > 0)
+ {
+ await Task.Delay(TimeSpan.FromMilliseconds(jitter), cancellationToken);
+ }
+
+ var response = await _repository.TryGetHealthAsync();
+ if (response.StatusCode != System.Net.HttpStatusCode.OK)
+ {
+ return new HealthCheckResult(HealthStatus.Degraded, $"CHES health check returned status code: {response.StatusCode}");
+ }
+ }
+ catch (Exception ex)
+ {
+ return new HealthCheckResult(context.Registration.FailureStatus, $"CHES health check failed with exception: {ex.Message}");
+ }
+ return HealthCheckResult.Healthy();
+ }
+ }
+}
diff --git a/source/backend/api/Models/Configuration/ChesConfig.cs b/source/backend/api/Models/Configuration/ChesConfig.cs
new file mode 100644
index 0000000000..a0db8fe797
--- /dev/null
+++ b/source/backend/api/Models/Configuration/ChesConfig.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Pims.Api.Models.Config
+{
+ public class ChesConfig
+ {
+ public Uri AuthEndpoint { get; set; }
+
+ public Uri ChesHost { get; set; }
+
+ public string ServiceClientId { get; set; }
+
+ public string ServiceClientSecret { get; set; }
+
+ public string FromEmail { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/source/backend/api/Repositories/Ches/ChesAuthRepository.cs b/source/backend/api/Repositories/Ches/ChesAuthRepository.cs
new file mode 100644
index 0000000000..69742be482
--- /dev/null
+++ b/source/backend/api/Repositories/Ches/ChesAuthRepository.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Pims.Api.Models.CodeTypes;
+using Pims.Api.Models.Ches;
+using Pims.Api.Models.Requests.Http;
+using Pims.Core.Api.Exceptions;
+using Polly.Registry;
+
+namespace Pims.Api.Repositories.Ches.Auth
+{
+
+ public class ChesAuthRepository : ChesBaseRepository, IEmailAuthRepository
+ {
+ private JwtResponse _currentToken;
+ private DateTime _lastSuccessfulRequest;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Injected Logger Provider.
+ /// Injected Httpclient factory.
+ /// The injected configuration provider.
+ /// The jsonOptions.
+ /// The polly retry policy.
+ public ChesAuthRepository(
+ ILogger logger,
+ IHttpClientFactory httpClientFactory,
+ IConfiguration configuration,
+ IOptions jsonOptions,
+ ResiliencePipelineProvider pollyPipelineProvider)
+ : base(logger, httpClientFactory, configuration, jsonOptions, pollyPipelineProvider)
+ {
+ _currentToken = null;
+ _lastSuccessfulRequest = DateTime.UnixEpoch;
+ }
+
+ public async Task GetTokenAsync()
+ {
+ if (!IsValidToken())
+ {
+ ExternalResponse tokenResult = await TryRequestToken();
+ if (tokenResult.Status == ExternalResponseStatus.Error)
+ {
+ throw new AuthenticationException(tokenResult.Message);
+ }
+
+ _lastSuccessfulRequest = DateTime.UtcNow;
+ _currentToken = tokenResult.Payload;
+ }
+
+ return _currentToken.AccessToken;
+ }
+
+ private bool IsValidToken()
+ {
+ if (_currentToken != null)
+ {
+ DateTime now = DateTime.UtcNow;
+ TimeSpan delta = now - _lastSuccessfulRequest;
+ if (delta.TotalSeconds >= _currentToken.ExpiresIn)
+ {
+ // Revoke token
+ _logger.LogDebug("Authentication Token has expired.");
+ _currentToken = null;
+ return false;
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ private async Task> TryRequestToken()
+ {
+ _logger.LogDebug("Getting authentication token...");
+
+ var requestForm = new Dictionary
+ {
+ { "grant_type", "client_credentials" },
+ { "client_id", _config.ServiceClientId },
+ { "client_secret", _config.ServiceClientSecret },
+ };
+
+ using FormUrlEncodedContent content = new(requestForm);
+ content.Headers.Clear();
+ content.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
+
+ ExternalResponse result = await PostAsync(_config.AuthEndpoint, content);
+ _logger.LogDebug("Token endpoint response: {@Result}", result);
+
+ return result;
+ }
+ }
+}
diff --git a/source/backend/api/Repositories/Ches/ChesBaseRepository.cs b/source/backend/api/Repositories/Ches/ChesBaseRepository.cs
new file mode 100644
index 0000000000..e6a598cae5
--- /dev/null
+++ b/source/backend/api/Repositories/Ches/ChesBaseRepository.cs
@@ -0,0 +1,49 @@
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text.Json;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Pims.Api.Models.Config;
+using Pims.Core.Api.Repositories.Rest;
+using Polly.Registry;
+
+namespace Pims.Api.Repositories.Ches
+{
+ ///
+ /// ChesBaseRepository provides common methods to interact with the Common Health Email Service (CHES) api.
+ ///
+ public abstract class ChesBaseRepository : BaseRestRepository
+ {
+ protected readonly ChesConfig _config;
+ private const string ChesConfigSectionKey = "Ches";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Injected Logger Provider.
+ /// Injected Httpclient factory.
+ /// The injected configuration provider.
+ /// The json options.
+ /// The polly retry policy.
+ protected ChesBaseRepository(
+ ILogger logger,
+ IHttpClientFactory httpClientFactory,
+ IConfiguration configuration,
+ IOptions jsonOptions,
+ ResiliencePipelineProvider pollyPipelineProvider)
+ : base(logger, httpClientFactory, jsonOptions, pollyPipelineProvider)
+ {
+ _config = new ChesConfig();
+ configuration.Bind(ChesConfigSectionKey, _config);
+ }
+
+ public override void AddAuthentication(HttpClient client, string authenticationToken = null)
+ {
+ if (!string.IsNullOrEmpty(authenticationToken))
+ {
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationToken);
+ }
+ }
+ }
+}
diff --git a/source/backend/api/Repositories/Ches/ChesRepository.cs b/source/backend/api/Repositories/Ches/ChesRepository.cs
new file mode 100644
index 0000000000..5cdca65216
--- /dev/null
+++ b/source/backend/api/Repositories/Ches/ChesRepository.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Net.Mime;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Pims.Api.Models.Ches;
+using Pims.Api.Models.CodeTypes;
+using Pims.Api.Models.Requests.Http;
+using Polly.Registry;
+
+namespace Pims.Api.Repositories.Ches
+{
+ ///
+ /// ChesRepository provides email access from the CHES API.
+ ///
+ public class ChesRepository : ChesBaseRepository, IEmailRepository
+ {
+ private readonly HttpClient _client;
+ private readonly IEmailAuthRepository _authRepository;
+ private readonly JsonSerializerOptions _serializeOptions;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Injected Logger Provider.
+ /// Injected Httpclient factory.
+ /// Injected repository that handles authentication.
+ /// The injected configuration provider.
+ /// The jsonOptions.
+ /// The polly retry policy.
+ public ChesRepository(
+ ILogger logger,
+ IHttpClientFactory httpClientFactory,
+ IConfiguration config,
+ IEmailAuthRepository authRepository,
+ IOptions jsonOptions,
+ ResiliencePipelineProvider pollyPipelineProvider)
+ : base(logger, httpClientFactory, config, jsonOptions, pollyPipelineProvider)
+ {
+ _client = httpClientFactory.CreateClient();
+ _client.DefaultRequestHeaders.Accept.Clear();
+ _client.DefaultRequestHeaders.Accept.Add(
+ new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json));
+ _authRepository = authRepository;
+
+ _serializeOptions = new JsonSerializerOptions(jsonOptions.Value)
+ {
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ };
+ }
+
+ public async Task> SendEmailAsync(EmailRequest request)
+ {
+ _logger.LogDebug("Sending Email ...");
+ ExternalResponse result = new ExternalResponse()
+ {
+ Status = ExternalResponseStatus.Error,
+ };
+
+ try
+ {
+ var token = await _authRepository.GetTokenAsync();
+
+ Uri endpoint = new(_config.ChesHost, "/api/v1/email");
+ var jsonContent = JsonSerializer.Serialize(request, _serializeOptions);
+
+ using var content = new StringContent(jsonContent);
+ content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
+
+ using var httpRequest = new HttpRequestMessage(HttpMethod.Post, endpoint);
+ httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
+ httpRequest.Content = content;
+
+ var response = await _client.SendAsync(httpRequest);
+ if (response.IsSuccessStatusCode)
+ {
+ var responseBody = await response.Content.ReadAsStringAsync();
+ result.Status = ExternalResponseStatus.Success;
+ result.Payload = JsonSerializer.Deserialize(responseBody);
+ if (result.Payload == null)
+ {
+ result.Status = ExternalResponseStatus.Error;
+ result.Message = "CHES email send succeeded but response payload was null.";
+ _logger.LogError("CHES email send succeeded but response payload was null.");
+ }
+ }
+ else
+ {
+ var errorBody = await response.Content.ReadAsStringAsync();
+ _logger.LogError("CHES email send failed: {Status} {Reason} {Body}", response.StatusCode, response.ReasonPhrase, errorBody);
+ result.Message = $"CHES email send failed: {response.StatusCode} {response.ReasonPhrase}. Response body: {errorBody}";
+ }
+ }
+ catch (Exception ex)
+ {
+ result.Status = ExternalResponseStatus.Error;
+ result.Message = $"Exception sending CHES email: {ex.Message}";
+ result.Payload = null;
+ result.HttpStatusCode = System.Net.HttpStatusCode.InternalServerError;
+ _logger.LogError(ex, "Exception sending CHES email.");
+ }
+ _logger.LogDebug($"Finished sending email");
+ return result;
+ }
+
+ public async Task TryGetHealthAsync()
+ {
+ _logger.LogDebug("Checking health of CHES service");
+ string authenticationToken = await _authRepository.GetTokenAsync();
+
+ Uri endpoint = new(this._config.ChesHost, "/api/v1/health");
+
+ Task result = GetRawAsync(endpoint, authenticationToken);
+
+ _logger.LogDebug($"Finished checking health of CHES service");
+ return await result;
+ }
+ }
+}
diff --git a/source/backend/api/Repositories/Ches/IEmailAuthRepository.cs b/source/backend/api/Repositories/Ches/IEmailAuthRepository.cs
new file mode 100644
index 0000000000..33be37432d
--- /dev/null
+++ b/source/backend/api/Repositories/Ches/IEmailAuthRepository.cs
@@ -0,0 +1,12 @@
+using System.Threading.Tasks;
+
+namespace Pims.Api.Repositories.Ches
+{
+ ///
+ /// IEmailAuthRepository interface, defines the functionality for a CHES email authentication repository.
+ ///
+ public interface IEmailAuthRepository
+ {
+ Task GetTokenAsync();
+ }
+}
\ No newline at end of file
diff --git a/source/backend/api/Repositories/Ches/IEmailRepository.cs b/source/backend/api/Repositories/Ches/IEmailRepository.cs
new file mode 100644
index 0000000000..86f6cac934
--- /dev/null
+++ b/source/backend/api/Repositories/Ches/IEmailRepository.cs
@@ -0,0 +1,14 @@
+using System.Net.Http;
+using System.Threading.Tasks;
+using Pims.Api.Models.Ches;
+using Pims.Api.Models.Requests.Http;
+
+namespace Pims.Api.Repositories.Ches
+{
+ public interface IEmailRepository
+ {
+ Task> SendEmailAsync(EmailRequest request);
+
+ Task TryGetHealthAsync();
+ }
+}
\ No newline at end of file
diff --git a/source/backend/api/Services/ChesService.cs b/source/backend/api/Services/ChesService.cs
new file mode 100644
index 0000000000..2f5d633710
--- /dev/null
+++ b/source/backend/api/Services/ChesService.cs
@@ -0,0 +1,51 @@
+#nullable enable
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Pims.Api.Models.Ches;
+using Pims.Api.Models.CodeTypes;
+using Pims.Api.Models.Config;
+using Pims.Api.Models.Requests.Http;
+using Pims.Api.Repositories.Ches;
+
+namespace Pims.Api.Services
+///
+/// Default implementation of IEmailService using CHES.
+///
+{
+ public class ChesService : IEmailService
+ {
+ private readonly IEmailRepository _chesRepository;
+ private readonly ILogger _logger;
+ private readonly ChesConfig _chesConfig;
+
+ public ChesService(IEmailRepository chesRepository, ILogger logger, ChesConfig chesConfig)
+ {
+ _chesRepository = chesRepository;
+ _logger = logger;
+ _chesConfig = chesConfig;
+ }
+
+ public async Task> SendEmailAsync(EmailRequest request)
+ {
+ _logger.LogInformation("Email send requested. Recipient count: {recipientCount}.", request.To?.Count ?? 0);
+
+ if (string.IsNullOrEmpty(request.From))
+ {
+ request.From = _chesConfig.FromEmail;
+ }
+
+ ExternalResponse? response = await _chesRepository.SendEmailAsync(request);
+
+ if (response == null || response.Payload == null)
+ {
+ return new ExternalResponse
+ {
+ Status = ExternalResponseStatus.Error,
+ Payload = new EmailResponse(),
+ Message = "Error sending email",
+ };
+ }
+ return response;
+ }
+ }
+}
diff --git a/source/backend/api/Services/IEmailService.cs b/source/backend/api/Services/IEmailService.cs
new file mode 100644
index 0000000000..143e11ce2d
--- /dev/null
+++ b/source/backend/api/Services/IEmailService.cs
@@ -0,0 +1,14 @@
+using System.Threading.Tasks;
+using Pims.Api.Models.Ches;
+using Pims.Api.Models.Requests.Http;
+
+namespace Pims.Api.Services
+{
+ ///
+ /// IEmailService interface, defines the functionality for email service.
+ ///
+ public interface IEmailService
+ {
+ Task> SendEmailAsync(EmailRequest request);
+ }
+}
\ No newline at end of file
diff --git a/source/backend/api/Startup.cs b/source/backend/api/Startup.cs
index cecbd2a0e4..2276523adf 100644
--- a/source/backend/api/Startup.cs
+++ b/source/backend/api/Startup.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using Microsoft.Data.SqlClient;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
@@ -27,6 +26,7 @@
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Versioning;
+using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
@@ -41,6 +41,8 @@
using Pims.Api.Helpers.Middleware;
using Pims.Api.Models.Config;
using Pims.Api.Repositories.Cdogs;
+using Pims.Api.Repositories.Ches;
+using Pims.Api.Repositories.Ches.Auth;
using Pims.Api.Repositories.Mayan;
using Pims.Api.Services;
using Pims.Api.Services.Interfaces;
@@ -372,6 +374,16 @@ public void ConfigureServices(IServiceCollection services)
{ Period = TimeSpan.FromMinutes(allHealthCheckOptions.Cdogs.Period) });
}
+ if (allHealthCheckOptions.Ches.Enabled)
+ {
+ services.AddHealthChecks().Add(new HealthCheckRegistration(
+ "Ches",
+ sp => new ChesHealthCheck(sp.GetService()),
+ null,
+ new string[] { SERVICES, EXTERNAL, SYSTEMCHECK })
+ { Period = TimeSpan.FromMinutes(allHealthCheckOptions.Ches.Period) });
+ }
+
services.AddApiVersioning(options =>
{
options.ReportApiVersions = true;
@@ -521,6 +533,8 @@ private static void AddPimsApiRepositories(IServiceCollection services)
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
services.AddSingleton();
}
@@ -572,6 +586,14 @@ private static void AddPimsApiServices(IServiceCollection services)
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddSingleton(sp =>
+ {
+ var config = sp.GetRequiredService();
+ var chesConfig = new ChesConfig();
+ config.GetSection("Ches").Bind(chesConfig);
+ return chesConfig;
+ });
+ services.AddScoped();
}
///
diff --git a/source/backend/api/appsettings.Development.json b/source/backend/api/appsettings.Development.json
index 82e81cc28e..f924c3dbda 100644
--- a/source/backend/api/appsettings.Development.json
+++ b/source/backend/api/appsettings.Development.json
@@ -16,6 +16,10 @@
"Ltsa": {
"Enabled": false,
"Period": 60
+ },
+ "Ches": {
+ "Enabled": false,
+ "Period": 60
}
},
"Serilog": {
@@ -57,5 +61,13 @@
"Environment": "dev",
"Integration": "4699"
}
+ },
+ "Ches": {
+ "ChesHost": "https://ches-dev.api.gov.bc.ca",
+ "ServiceClientId": "",
+ "ServiceClientSecret": "",
+ "Issuer": "https://dev.loginproxy.gov.bc.ca/auth/realms/comsvcauth",
+ "AuthEndpoint": "https://dev.loginproxy.gov.bc.ca/auth/realms/comsvcauth/protocol/openid-connect/token",
+ "FromEmail": ""
}
}
diff --git a/source/backend/api/appsettings.Local.json b/source/backend/api/appsettings.Local.json
index 58b8148b68..6b9f0fc926 100644
--- a/source/backend/api/appsettings.Local.json
+++ b/source/backend/api/appsettings.Local.json
@@ -26,6 +26,10 @@
"Mayan": {
"Period": 60,
"Enabled": false
+ },
+ "Ches": {
+ "Period": 60,
+ "Enabled": false
}
},
"Logging": {
@@ -80,5 +84,13 @@
"ConnectionUser": "admin",
"ConnectionPassword": "",
"ExposeErrors": "true"
+ },
+ "Ches": {
+ "ChesHost": "https://ches-dev.api.gov.bc.ca",
+ "ServiceClientId": "",
+ "ServiceClientSecret": "",
+ "Issuer": "https://dev.loginproxy.gov.bc.ca/auth/realms/comsvcauth",
+ "AuthEndpoint": "https://dev.loginproxy.gov.bc.ca/auth/realms/comsvcauth/protocol/openid-connect/token",
+ "FromEmail": ""
}
}
diff --git a/source/backend/api/appsettings.Test.json b/source/backend/api/appsettings.Test.json
index 2d163caefb..536d92db4b 100644
--- a/source/backend/api/appsettings.Test.json
+++ b/source/backend/api/appsettings.Test.json
@@ -16,6 +16,10 @@
"Ltsa": {
"Enabled": false,
"Period": 15
+ },
+ "Ches": {
+ "Enabled": false,
+ "Period": 15
}
},
"Serilog": {
diff --git a/source/backend/api/appsettings.Uat.json b/source/backend/api/appsettings.Uat.json
index 6b21492896..381955ae9a 100644
--- a/source/backend/api/appsettings.Uat.json
+++ b/source/backend/api/appsettings.Uat.json
@@ -16,6 +16,10 @@
"Ltsa": {
"Enabled": false,
"Period": 5
+ },
+ "Ches": {
+ "Enabled": false,
+ "Period": 5
}
},
"Serilog": {
diff --git a/source/backend/api/appsettings.json b/source/backend/api/appsettings.json
index 24dab38bca..1646f76200 100644
--- a/source/backend/api/appsettings.json
+++ b/source/backend/api/appsettings.json
@@ -41,6 +41,10 @@
"Cdogs": {
"Enabled": true,
"Period": 1
+ },
+ "Ches": {
+ "Enabled": true,
+ "Period": 1
}
},
"Swagger": {
@@ -153,6 +157,13 @@
"ServiceClientId": "[CLIENT_ID]",
"ServiceClientSecret": "[CLIENT_SECRET]"
},
+ "Ches": {
+ "AuthEndpoint": "[AUTH_ENDPOINT]",
+ "ChesHost": "[CDOGS_HOST]",
+ "ServiceClientId": "[CLIENT_ID]",
+ "ServiceClientSecret": "[CLIENT_SECRET]",
+ "FromEmail": "[FROM_EMAIL]"
+ },
"Polly": {
"MaxRetries": 3,
"DelayInSeconds": 1
diff --git a/source/backend/apimodels/Models/Ches/EmailAttachment.cs b/source/backend/apimodels/Models/Ches/EmailAttachment.cs
new file mode 100644
index 0000000000..f82ff6403a
--- /dev/null
+++ b/source/backend/apimodels/Models/Ches/EmailAttachment.cs
@@ -0,0 +1,34 @@
+
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+#nullable enable
+
+namespace Pims.Api.Models.Ches
+{
+ ///
+ /// Represents an attachment for CHES email.
+ ///
+ public class EmailAttachment : IAttachment
+ {
+ ///
+ /// Buffer or a Stream contents for the attachment.
+ ///
+ [JsonPropertyName("content")]
+ public string? Content { get; set; }
+
+ ///
+ /// Optional content type for the attachment.
+ /// If not set, it will be derived from the filename property.
+ ///
+ [JsonPropertyName("contentType")]
+ public string? ContentType { get; set; }
+
+ ///
+ /// Filename to be reported as the name of the attached file.
+ /// Use of unicode is allowed.
+ ///
+ [JsonPropertyName("filename")]
+ public string? Filename { get; set; }
+ }
+}
diff --git a/source/backend/apimodels/Models/Ches/EmailBodyType.cs b/source/backend/apimodels/Models/Ches/EmailBodyType.cs
new file mode 100644
index 0000000000..27e4a67e4a
--- /dev/null
+++ b/source/backend/apimodels/Models/Ches/EmailBodyType.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Pims.Api.Models.Ches
+{
+ ///
+ /// EmailBodyType enum, provides email body type options for CHES.
+ ///
+ [JsonConverter(typeof(EmailBodyTypeJsonConverter))]
+ public enum EmailBodyType
+ {
+ Html = 0,
+ Text = 1,
+ }
+
+ ///
+ /// Custom JsonConverter for EmailBodyType to serialize as lowercase strings.
+ ///
+ public class EmailBodyTypeJsonConverter : JsonConverter
+ {
+ public override EmailBodyType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var value = reader.GetString();
+ return value?.ToLower() switch
+ {
+ "html" => EmailBodyType.Html,
+ "text" => EmailBodyType.Text,
+ _ => throw new JsonException($"Invalid bodyType value: {value}")
+ };
+ }
+
+ public override void Write(Utf8JsonWriter writer, EmailBodyType value, JsonSerializerOptions options)
+ {
+ var str = value switch
+ {
+ EmailBodyType.Html => "html",
+ EmailBodyType.Text => "text",
+ _ => throw new JsonException($"Invalid bodyType value: {value}")
+ };
+ writer.WriteStringValue(str);
+ }
+ }
+}
diff --git a/source/backend/apimodels/Models/Ches/EmailContext.cs b/source/backend/apimodels/Models/Ches/EmailContext.cs
new file mode 100644
index 0000000000..64676c12e1
--- /dev/null
+++ b/source/backend/apimodels/Models/Ches/EmailContext.cs
@@ -0,0 +1,48 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Pims.Api.Models.Ches
+{
+ ///
+ /// EmailContext provides the template variables for an email.
+ ///
+ public class EmailContext : IEmailContext
+ {
+ ///
+ /// An array of recipients email addresses that will appear on the To: field.
+ ///
+ [JsonPropertyName("to")]
+ public List To { get; set; } = new List();
+
+ ///
+ /// An array of recipients email addresses that will appear on the cc: field.
+ ///
+ [JsonPropertyName("cc")]
+ public List Cc { get; set; } = new List();
+
+ ///
+ /// An array of recipients email addresses that will appear on the bcc: field.
+ ///
+ [JsonPropertyName("bcc")]
+ public List Bcc { get; set; } = new List();
+
+ ///
+ /// A freeform JSON object of key-value pairs.
+ /// All keys must be alphanumeric or underscore.
+ ///
+ [JsonPropertyName("context")]
+ public Dictionary Context { get; set; } = new Dictionary();
+
+ ///
+ /// Desired UTC time for sending the message. 0 = Queue to send immediately
+ ///
+ [JsonPropertyName("delayTS")]
+ public long DelayTS { get; set; }
+
+ ///
+ /// A unique string to be associated with the message
+ ///
+ [JsonPropertyName("tag")]
+ public string Tag { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/source/backend/apimodels/Models/Ches/EmailEncoding.cs b/source/backend/apimodels/Models/Ches/EmailEncoding.cs
new file mode 100644
index 0000000000..7affe88f13
--- /dev/null
+++ b/source/backend/apimodels/Models/Ches/EmailEncoding.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Pims.Api.Models.Ches
+{
+ ///
+ /// EmailEncoding enum, provides encoding options for email content.
+ ///
+ [JsonConverter(typeof(EmailEncodingJsonConverter))]
+ public enum EmailEncoding
+ {
+ Utf8 = 0,
+ Base64 = 1,
+ Binary = 2,
+ Hex = 3,
+ }
+
+ ///
+ /// Custom JsonConverter for EmailEncoding to serialize as lowercase strings.
+ ///
+ public class EmailEncodingJsonConverter : JsonConverter
+ {
+ public override EmailEncoding Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var value = reader.GetString();
+ return value?.ToLower() switch
+ {
+ "utf-8" => EmailEncoding.Utf8,
+ "base64" => EmailEncoding.Base64,
+ "binary" => EmailEncoding.Binary,
+ "hex" => EmailEncoding.Hex,
+ _ => throw new JsonException($"Invalid encoding value: {value}")
+ };
+ }
+
+ public override void Write(Utf8JsonWriter writer, EmailEncoding value, JsonSerializerOptions options)
+ {
+ var str = value switch
+ {
+ EmailEncoding.Utf8 => "utf-8",
+ EmailEncoding.Base64 => "base64",
+ EmailEncoding.Binary => "binary",
+ EmailEncoding.Hex => "hex",
+ _ => throw new JsonException($"Invalid encoding value: {value}")
+ };
+ writer.WriteStringValue(str);
+ }
+ }
+}
diff --git a/source/backend/apimodels/Models/Ches/EmailMergeRequest.cs b/source/backend/apimodels/Models/Ches/EmailMergeRequest.cs
new file mode 100644
index 0000000000..ff818a0e50
--- /dev/null
+++ b/source/backend/apimodels/Models/Ches/EmailMergeRequest.cs
@@ -0,0 +1,67 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Pims.Api.Models.Ches
+{
+
+ ///
+ /// Represents a mail merge request for CHES.
+ ///
+ public class ChesMergeRequest
+ {
+ ///
+ /// The email address of the sender.
+ /// All email addresses can be plain 'sender@server.com' or formatted '"Sender Name" sender@server.com'.
+ ///
+ [JsonPropertyName("from")]
+ [System.ComponentModel.DataAnnotations.Required]
+ public string From { get; set; } = string.Empty;
+
+ ///
+ /// The email subject.
+ ///
+ [JsonPropertyName("subject")]
+ [System.ComponentModel.DataAnnotations.Required]
+ public string Subject { get; set; } = string.Empty;
+
+ ///
+ /// A body template of the message as an Unicode string.
+ /// Refer to https://mozilla.github.io/nunjucks/templating.html for template syntax.
+ ///
+ [JsonPropertyName("body")]
+ [System.ComponentModel.DataAnnotations.Required]
+ public string Body { get; set; } = string.Empty;
+
+ ///
+ /// The email body type (html = content with html, text = plaintext).
+ ///
+ [JsonPropertyName("bodyType")]
+ [System.ComponentModel.DataAnnotations.Required]
+ public EmailBodyType BodyType { get; set; } = EmailBodyType.Html;
+
+ ///
+ /// Identifies encoding for text/html strings (defaults to 'utf-8', other values are 'hex' and 'base64').
+ ///
+ [JsonPropertyName("encoding")]
+ public EmailEncoding Encoding { get; set; } = EmailEncoding.Utf8;
+
+ ///
+ /// Sets message importance headers, either 'high', 'normal' (default) or 'low'..
+ ///
+ [JsonPropertyName("priority")]
+ public EmailPriority Priority { get; set; } = EmailPriority.Normal;
+
+ ///
+ /// An array of Attachment objects.
+ ///
+ [JsonPropertyName("attachments")]
+ public List? Attachments { get; set; }
+
+ ///
+ /// An array of context objects.
+ ///
+ [JsonPropertyName("contexts")]
+ [System.ComponentModel.DataAnnotations.Required]
+ public List Contexts { get; set; } = new();
+ }
+}
\ No newline at end of file
diff --git a/source/backend/apimodels/Models/Ches/EmailPriority.cs b/source/backend/apimodels/Models/Ches/EmailPriority.cs
new file mode 100644
index 0000000000..ae9b42cd91
--- /dev/null
+++ b/source/backend/apimodels/Models/Ches/EmailPriority.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Pims.Api.Models.Ches
+{
+ ///
+ /// EmailPriority enum, provides email priority options for CHES.
+ ///
+ [JsonConverter(typeof(EmailPriorityJsonConverter))]
+ public enum EmailPriority
+ {
+ Low = 0,
+ Normal = 1,
+ High = 2,
+ }
+
+ ///
+ /// Custom JsonConverter for EmailPriority to serialize as lowercase strings.
+ ///
+ public class EmailPriorityJsonConverter : JsonConverter
+ {
+ public override EmailPriority Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var value = reader.GetString();
+ return value?.ToLower() switch
+ {
+ "low" => EmailPriority.Low,
+ "normal" => EmailPriority.Normal,
+ "high" => EmailPriority.High,
+ _ => throw new JsonException($"Invalid priority value: {value}")
+ };
+ }
+
+ public override void Write(Utf8JsonWriter writer, EmailPriority value, JsonSerializerOptions options)
+ {
+ var str = value switch
+ {
+ EmailPriority.Low => "low",
+ EmailPriority.Normal => "normal",
+ EmailPriority.High => "high",
+ _ => throw new JsonException($"Invalid priority value: {value}")
+ };
+ writer.WriteStringValue(str);
+ }
+ }
+}
diff --git a/source/backend/apimodels/Models/Ches/EmailRequest.cs b/source/backend/apimodels/Models/Ches/EmailRequest.cs
new file mode 100644
index 0000000000..d0e6a86220
--- /dev/null
+++ b/source/backend/apimodels/Models/Ches/EmailRequest.cs
@@ -0,0 +1,92 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Pims.Api.Models.Ches
+{
+ ///
+ /// Represents a request to send an email via CHES.
+ ///
+ public class EmailRequest
+ {
+ ///
+ /// An array of recipients email addresses that will appear on the To: field.
+ ///
+ [JsonPropertyName("to")]
+ [System.ComponentModel.DataAnnotations.Required]
+ public List To { get; set; } = new();
+
+ ///
+ /// An array of recipients email addresses that will appear on the CC: field.
+ ///
+ [JsonPropertyName("cc")]
+ public List Cc { get; set; } = new();
+
+ ///
+ /// An array of recipients email addresses that will appear on the BCC: field.
+ ///
+ [JsonPropertyName("bcc")]
+ public List Bcc { get; set; } = new();
+
+ ///
+ /// The email address of the sender.
+ /// All email addresses can be plain 'sender@server.com' or formatted '"Sender Name" sender@server.com'.
+ ///
+ [JsonPropertyName("from")]
+ public string From { get; set; }
+
+ ///
+ /// The email subject.
+ ///
+ [JsonPropertyName("subject")]
+ [System.ComponentModel.DataAnnotations.Required]
+ public string Subject { get; set; } = string.Empty;
+
+ ///
+ /// The body of the message as an Unicode string.
+ ///
+ [JsonPropertyName("body")]
+ [System.ComponentModel.DataAnnotations.Required]
+ public string Body { get; set; } = string.Empty;
+
+ ///
+ /// The email body type (html = content with html, text = plaintext).
+ ///
+ [JsonPropertyName("bodyType")]
+ [JsonConverter(typeof(EmailBodyTypeJsonConverter))]
+ [System.ComponentModel.DataAnnotations.Required]
+ public EmailBodyType BodyType { get; set; } = EmailBodyType.Html;
+
+ ///
+ /// Identifies encoding for text/html strings (defaults to 'utf-8', other values are 'hex' and 'base64').
+ ///
+ [JsonPropertyName("encoding")]
+ [JsonConverter(typeof(EmailEncodingJsonConverter))]
+ public EmailEncoding Encoding { get; set; } = EmailEncoding.Utf8;
+
+ ///
+ /// Sets message importance headers, either 'high', 'normal' (default) or 'low'.
+ ///
+ [JsonPropertyName("priority")]
+ [JsonConverter(typeof(EmailPriorityJsonConverter))]
+ public EmailPriority Priority { get; set; } = EmailPriority.Normal;
+
+ ///
+ /// A unique string which is associated with the message.
+ ///
+ [JsonPropertyName("tag")]
+ public string Tag { get; set; }
+
+ ///
+ /// Desired UTC time for sending the message. 0 = Queue to send immediately.
+ ///
+ [JsonPropertyName("delayTS")]
+ public long? DelayTS { get; set; }
+
+ ///
+ /// An array of Attachment objects.
+ ///
+ [JsonPropertyName("attachments")]
+ public List Attachments { get; set; } = new List();
+ }
+
+}
diff --git a/source/backend/apimodels/Models/Ches/EmailResponse.cs b/source/backend/apimodels/Models/Ches/EmailResponse.cs
new file mode 100644
index 0000000000..423b4518ac
--- /dev/null
+++ b/source/backend/apimodels/Models/Ches/EmailResponse.cs
@@ -0,0 +1,25 @@
+using System.Text.Json.Serialization;
+using System.Collections.Generic;
+
+namespace Pims.Api.Models.Ches
+{
+ ///
+ /// Represents the response from CHES after sending an email.
+ ///
+ public class EmailResponse
+ {
+ ///
+ /// A corresponding transaction uuid.
+ ///
+ [JsonPropertyName("txId")]
+ public string TxId { get; set; } = string.Empty;
+
+ ///
+ /// Array of objects.
+ /// Each object contains msgId, tag and to fields.
+ ///
+ [JsonPropertyName("messages")]
+ public List Messages { get; set; } = new();
+ }
+
+}
diff --git a/source/backend/apimodels/Models/Ches/ErrorModel.cs b/source/backend/apimodels/Models/Ches/ErrorModel.cs
new file mode 100644
index 0000000000..33f9710d0b
--- /dev/null
+++ b/source/backend/apimodels/Models/Ches/ErrorModel.cs
@@ -0,0 +1,22 @@
+using System.Text.Json.Serialization;
+
+namespace Pims.Api.Models.Ches
+{
+ ///
+ /// Represents an error response from the CHES service.
+ ///
+ public class ErrorModel
+ {
+ ///
+ /// The error message for the field.
+ ///
+ [JsonPropertyName("message")]
+ public string Message { get; set; }
+
+ ///
+ /// Contents of the field that was in error.
+ ///
+ [JsonPropertyName("value")]
+ public object Value { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/source/backend/apimodels/Models/Ches/ErrorResponse.cs b/source/backend/apimodels/Models/Ches/ErrorResponse.cs
new file mode 100644
index 0000000000..74d619081f
--- /dev/null
+++ b/source/backend/apimodels/Models/Ches/ErrorResponse.cs
@@ -0,0 +1,41 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Pims.Api.Models.Ches
+{
+ ///
+ /// ErrorResponse class, provides a model that represents an error returned from CHES.
+ ///
+ public class ErrorResponse
+ {
+ ///
+ /// What type of problem, link to explanation of problem.
+ ///
+ [JsonPropertyName("type")]
+ public string Type { get; set; }
+
+ ///
+ /// Title of problem, generally the Http Status Code description.
+ ///
+ [JsonPropertyName("title")]
+ public string Title { get; set; }
+
+ ///
+ /// The Http Status code.
+ ///
+ [JsonPropertyName("status")]
+ public long Status { get; set; }
+
+ ///
+ /// Short description of why this problem was raised.
+ ///
+ [JsonPropertyName("detail")]
+ public string Detail { get; set; }
+
+ ///
+ /// A list of errors associated with the response.
+ ///
+ [JsonPropertyName("errors")]
+ public List Errors { get; set; } = new List();
+ }
+}
\ No newline at end of file
diff --git a/source/backend/apimodels/Models/Ches/IAttachment.cs b/source/backend/apimodels/Models/Ches/IAttachment.cs
new file mode 100644
index 0000000000..2205d7a693
--- /dev/null
+++ b/source/backend/apimodels/Models/Ches/IAttachment.cs
@@ -0,0 +1,16 @@
+#nullable enable
+
+namespace Pims.Api.Models.Ches
+{
+ ///
+ /// Defines the contract for an email attachment.
+ ///
+ public interface IAttachment
+ {
+ string? Content { get; set; }
+
+ string? ContentType { get; set; }
+
+ string? Filename { get; set; }
+ }
+}
diff --git a/source/backend/apimodels/Models/Ches/IEmailContext.cs b/source/backend/apimodels/Models/Ches/IEmailContext.cs
new file mode 100644
index 0000000000..6fa23dd7c8
--- /dev/null
+++ b/source/backend/apimodels/Models/Ches/IEmailContext.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Pims.Api.Models.Ches
+{
+ ///
+ /// Defines the contract for a mail merge context object.
+ ///
+ public interface IEmailContext
+ {
+ [JsonPropertyName("to")]
+ List To { get; set; }
+
+ [JsonPropertyName("cc")]
+ List Cc { get; set; }
+
+ [JsonPropertyName("bcc")]
+ List Bcc { get; set; }
+
+ [JsonPropertyName("context")]
+ Dictionary Context { get; set; }
+
+ [JsonPropertyName("delayTS")]
+ long DelayTS { get; set; }
+
+ [JsonPropertyName("tag")]
+ string Tag { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/source/backend/apimodels/Models/Ches/JwtResponse.cs b/source/backend/apimodels/Models/Ches/JwtResponse.cs
new file mode 100644
index 0000000000..0a7f793c96
--- /dev/null
+++ b/source/backend/apimodels/Models/Ches/JwtResponse.cs
@@ -0,0 +1,58 @@
+using System.Text.Json.Serialization;
+
+namespace Pims.Api.Models.Ches
+{
+ ///
+ /// Represents a JWT (JSON Web Token) response.
+ ///
+ public class JwtResponse
+ {
+ ///
+ /// get/set - The access token.
+ ///
+ [JsonPropertyName("access_token")]
+ public string AccessToken { get; set; }
+
+ ///
+ /// get/set - The token expiration in minutes.
+ ///
+ [JsonPropertyName("expires_in")]
+ public int ExpiresIn { get; set; }
+
+ ///
+ /// get/set - The refresh token expiration in minutes.
+ ///
+ [JsonPropertyName("refresh_expires_in")]
+ public int RefreshExpiresIn { get; set; }
+
+ ///
+ /// get/set - The refresh token.
+ ///
+ [JsonPropertyName("refresh_token")]
+ public string RefreshToken { get; set; }
+
+ ///
+ /// get/set - The token type.
+ ///
+ [JsonPropertyName("token_type")]
+ public string TokenType { get; set; }
+
+ ///
+ /// get/set - The not-before-policy.
+ ///
+ [JsonPropertyName("not-before-policy")]
+ public int NotBeforePolicy { get; set; }
+
+ ///
+ /// get/set - The session state.
+ ///
+ [JsonPropertyName("session_state")]
+ public string SessionState { get; set; }
+
+ ///
+ /// get/set - The scope.
+ ///
+ [JsonPropertyName("scope")]
+ public string Scope { get; set; }
+ }
+}
diff --git a/source/backend/apimodels/Models/Ches/MessageResponse.cs b/source/backend/apimodels/Models/Ches/MessageResponse.cs
new file mode 100644
index 0000000000..3f8c84d861
--- /dev/null
+++ b/source/backend/apimodels/Models/Ches/MessageResponse.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Pims.Api.Models.Ches
+{
+ ///
+ /// MessageResponse class, provides a model that represents the response when a message was added to the CHES queue.
+ ///
+ public class MessageResponse
+ {
+ ///
+ /// A corresponding message uuid.
+ ///
+ [JsonPropertyName("msgId")]
+ public string MsgId { get; set; } = string.Empty;
+
+ ///
+ /// A unique string which is associated with the message.
+ ///
+ [JsonPropertyName("tag")]
+ public string? Tag { get; set; }
+
+ ///
+ /// An array of recipient email addresses that this message will go to.
+ ///
+ [JsonPropertyName("to")]
+ public List To { get; set; } = new();
+ }
+}
diff --git a/source/backend/apimodels/Models/Ches/StatusHistory.cs b/source/backend/apimodels/Models/Ches/StatusHistory.cs
new file mode 100644
index 0000000000..0eb116a1f2
--- /dev/null
+++ b/source/backend/apimodels/Models/Ches/StatusHistory.cs
@@ -0,0 +1,21 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Pims.Api.Models.Ches
+{
+
+ ///
+ /// Represents a status history entry.
+ ///
+ public class ChesStatusHistory
+ {
+ [JsonPropertyName("description")]
+ public string? Description { get; set; }
+
+ [JsonPropertyName("status")]
+ public string? Status { get; set; }
+
+ [JsonPropertyName("timestamp")]
+ public long? Timestamp { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/source/backend/apimodels/Models/Ches/StatusResponse.cs b/source/backend/apimodels/Models/Ches/StatusResponse.cs
new file mode 100644
index 0000000000..7acd26c8b7
--- /dev/null
+++ b/source/backend/apimodels/Models/Ches/StatusResponse.cs
@@ -0,0 +1,60 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Pims.Api.Models.Ches
+{
+
+ ///
+ /// Represents a status response for a message or transaction.
+ ///
+ public class ChesStatusResponse
+ {
+ ///
+ /// A corresponding transaction uuid.
+ ///
+ [JsonPropertyName("txId")]
+ public string? TxId { get; set; }
+
+ ///
+ /// A corresponding message uuid.
+ ///
+ [JsonPropertyName("msgId")]
+ public string? MsgId { get; set; }
+
+ ///
+ /// The current status of the message.
+ ///
+ [JsonPropertyName("status")]
+ public string? Status { get; set; }
+
+ ///
+ /// A unique string which is associated with the message.
+ ///
+ [JsonPropertyName("tag")]
+ public string? Tag { get; set; }
+
+ ///
+ /// UTC time this service first received this message queue request.
+ ///
+ [JsonPropertyName("createdTS")]
+ public long? CreatedTS { get; set; }
+
+ ///
+ /// Desired UTC time for sending the message. 0 = Queue to send immediately.
+ ///
+ [JsonPropertyName("delayTS")]
+ public long? DelayTS { get; set; }
+
+ ///
+ /// UTC time this message queue request was last updated.
+ ///
+ [JsonPropertyName("updatedTS")]
+ public long? UpdatedTS { get; set; }
+
+ ///
+ /// A list of status changes to this message.
+ ///
+ [JsonPropertyName("statusHistory")]
+ public List? StatusHistory { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/source/backend/dal/Options/AllHealthCheckOptions.cs b/source/backend/dal/Options/AllHealthCheckOptions.cs
index 8a61aaf0c2..6e70bf234d 100644
--- a/source/backend/dal/Options/AllHealthCheckOptions.cs
+++ b/source/backend/dal/Options/AllHealthCheckOptions.cs
@@ -30,6 +30,8 @@ public class AllHealthCheckOptions
public PimsBaseHealthCheckOptions Cdogs { get; set; }
public PimsBaseHealthCheckOptions Mayan { get; set; }
+
+ public PimsBaseHealthCheckOptions Ches { get; set; }
#endregion
}
}