Skip to content
Merged

Dev #1646

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
75e2163
feature/AB#26441-DynamicUrls-FixDomainName
jimmyPasta May 21, 2025
f4ee868
feature/AB#26441-DynamicUrls-Fix popover not available for datatables…
jimmyPasta May 21, 2025
a77a533
feature/AB#26441-DynamicUrls-Initiailizedb
jimmyPasta May 21, 2025
a7caea1
feature/AB#26441-DynamicUrls
jimmyPasta May 28, 2025
fdfc8b5
EndpointManagementAppService
JamesPasta Aug 13, 2025
ff6a48e
feature/AB#26441-DynamicUrls
JamesPasta Aug 18, 2025
2d01305
feature/AB#26441-DynamicUrls-Initial
JamesPasta Aug 20, 2025
b7a4a47
feature/AB#26441-DynamicUrls-Initial-caching
JamesPasta Aug 22, 2025
083b91a
AB#23904: Update EF - Add LinkType Column
aurelio-aot Aug 20, 2025
4d8e712
AB#23904: Change links table to DataTable
aurelio-aot Aug 21, 2025
cd5fd12
AB#23904: Fix error on tab count
aurelio-aot Aug 21, 2025
5bcb4a4
AB#23904: Fix table and data alignment issue
aurelio-aot Aug 21, 2025
9c1b24e
AB#23904: Fix bug on links table refresh
aurelio-aot Aug 21, 2025
0a72067
AB#23904: Transform ApplicationLinksModal dialog
aurelio-aot Aug 26, 2025
250abf9
AB#23904: Fetch details for newly added link item
aurelio-aot Aug 26, 2025
f5e55ac
AB#23904: Confirmation dialog when deleting existing links
aurelio-aot Aug 26, 2025
9a7241d
AB#23904: Fix bug on restored link not updating linkType
aurelio-aot Aug 27, 2025
0378c04
AB#23904: Search by submission number and applicant name
aurelio-aot Aug 27, 2025
73593c8
AB#23904: Potential fix for code scanning alert no. 46: DOM text rein…
aurelio-aot Aug 27, 2025
7ec2ff9
AB#23904: Potential fix for code scanning alert no. 47: DOM text rein…
aurelio-aot Aug 27, 2025
d21f05e
AB#23904: Potential fix for code scanning alert no. 48: DOM text rei…
aurelio-aot Aug 27, 2025
974279f
AB#23904: Fix UI styling
aurelio-aot Aug 28, 2025
01b13f6
feature/AB#26441-DynamicUrls-Sonar
JamesPasta Aug 28, 2025
69e7d71
feature/AB#26441-DynamicUrls-Sonar
JamesPasta Aug 28, 2025
de1ea96
feature/AB#26441-DynamicUrls-remove urls from app settings
JamesPasta Aug 28, 2025
78f2881
Merge pull request #1640 from bcgov/bugfix/AB#29960-FixNull
JamesPasta Aug 28, 2025
768b743
feature/AB#26441-DynamicUrls-Sonar
JamesPasta Aug 28, 2025
920ad99
Merge branch 'dev' into feature/AB#26441-DynamicUrls2
JamesPasta Aug 28, 2025
3cfc659
Update applications/Unity.GrantManager/src/Unity.GrantManager.Applica…
JamesPasta Aug 28, 2025
ccc2709
Update applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Un…
JamesPasta Aug 28, 2025
47f0eec
Update applications/Unity.GrantManager/src/Unity.GrantManager.Domain.…
JamesPasta Aug 28, 2025
8aa6e2d
feature/AB#26441-DynamicUrls-Copilot
JamesPasta Aug 28, 2025
c99723b
Merge branch 'feature/AB#26441-DynamicUrls2' of https://github.com/bc…
JamesPasta Aug 28, 2025
15f2a06
Update applications/Unity.GrantManager/modules/Unity.Payments/src/Uni…
JamesPasta Aug 28, 2025
e5ef8d7
Potential fix for code scanning alert no. 49: Log entries created fro…
JamesPasta Aug 28, 2025
e2c1d4d
Update applications/Unity.GrantManager/src/Unity.GrantManager.EntityF…
JamesPasta Aug 28, 2025
867bc1e
Update applications/Unity.GrantManager/src/Unity.GrantManager.Domain/…
JamesPasta Aug 28, 2025
e0594fd
Update applications/Unity.GrantManager/src/Unity.GrantManager.Applica…
JamesPasta Aug 28, 2025
d6e9d24
Potential fix for code scanning alert no. 51: Log entries created fro…
JamesPasta Aug 28, 2025
33eb5ac
feature/AB#26441-DynamicUrls-Copilot
JamesPasta Aug 28, 2025
fb2e1a6
feature/AB#26441-DynamicUrls-Copilot
JamesPasta Aug 28, 2025
721db33
feature/AB#26441-DynamicUrls-Copilot
JamesPasta Aug 28, 2025
4bfd5b0
Update applications/Unity.GrantManager/src/Unity.GrantManager.Applica…
JamesPasta Aug 28, 2025
9c1e0c4
Potential fix for code scanning alert no. 50: Log entries created fro…
JamesPasta Aug 28, 2025
75825e3
feature/AB#26441-DynamicUrls-Copilot
JamesPasta Aug 28, 2025
6732a04
feature/AB#26441-DynamicUrls-Copilot
JamesPasta Aug 28, 2025
022e24b
feature/AB#26441-DynamicUrls-Copilot
JamesPasta Aug 28, 2025
e9c7dd3
Update applications/Unity.GrantManager/src/Unity.GrantManager.Applica…
JamesPasta Aug 28, 2025
dcb2869
feature/AB#26441-DynamicUrls-Copilot
JamesPasta Aug 28, 2025
ea58682
Merge branch 'feature/AB#26441-DynamicUrls2' of https://github.com/bc…
JamesPasta Aug 28, 2025
c3d105b
Update applications/Unity.GrantManager/src/Unity.GrantManager.Applica…
JamesPasta Aug 28, 2025
990a7ae
feature/AB#26441-DynamicUrls-Copilot
JamesPasta Aug 28, 2025
4509da7
Merge branch 'feature/AB#26441-DynamicUrls2' of https://github.com/bc…
JamesPasta Aug 28, 2025
5a014f1
feature/AB#26441-DynamicUrls-Copilot
JamesPasta Aug 28, 2025
fb99584
Merge pull request #1641 from bcgov/feature/AB#26441-DynamicUrls2
samsaravillo Aug 28, 2025
a1dd907
feature/AB#26441-DynamicUrls-Sonar
JamesPasta Aug 29, 2025
4da293f
AB#23904: Remove CURRENT badge
aurelio-aot Aug 29, 2025
178f0d6
Merge pull request #1642 from bcgov/feature/AB#26441-DynamicUrls2
JamesPasta Aug 29, 2025
dab7756
AB#23904: Fix sonarqube issues
aurelio-aot Sep 2, 2025
53d60da
AB#23904: Fix error on form configuration
aurelio-aot Sep 2, 2025
df2ae46
Revert "AB#23904: Fix error on form configuration"
aurelio-aot Sep 2, 2025
2c0dceb
AB#29825: Fix error on form configuration when FormSchema is empty
aurelio-aot Sep 2, 2025
7164706
Merge pull request #1627 from bcgov/feature/AB#23904-select-type-when…
JamesPasta Sep 3, 2025
7a845d3
Merge pull request #1644 from bcgov/bugfix/AB#29825-form-configuratio…
JamesPasta Sep 3, 2025
b552edb
feature/AB#26441-DynamicUrls-FixBaseUrl
JamesPasta Sep 3, 2025
19424c3
Merge pull request #1645 from bcgov/feature/AB#26441-DynamicUrls2
JamesPasta Sep 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.Text.Json;
using System.Threading.Tasks;
using Unity.Flex.Worksheets.Values;
using Unity.GrantManager.Integration.Geocoder;
using Unity.GrantManager.Integrations.Geocoder;

namespace Unity.Flex.Worksheets.Collectors
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
Expand All @@ -23,49 +21,31 @@
using Volo.Abp.Domain.Entities;
using Volo.Abp.Features;
using Volo.Abp.SettingManagement;
using Microsoft.AspNetCore.Http;
using Volo.Abp.Users;
using Unity.GrantManager.Notifications;

namespace Unity.Notifications.EmailNotifications;


[Dependency(ReplaceServices = false)]
[ExposeServices(typeof(EmailNotificationService), typeof(IEmailNotificationService))]
public class EmailNotificationService : ApplicationService, IEmailNotificationService
{
private readonly IChesClientService _chesClientService;
private readonly IConfiguration _configuration;
private readonly EmailQueueService _emailQueueService;
private readonly IEmailLogsRepository _emailLogsRepository;
private readonly IExternalUserLookupServiceProvider _externalUserLookupServiceProvider;
private readonly ISettingManager _settingManager;
private readonly IFeatureChecker _featureChecker;
private readonly IHttpContextAccessor _httpContextAccessor;

public EmailNotificationService(
#pragma warning disable S107 // Methods should not have too many parameters
public class EmailNotificationService(
INotificationsAppService notificationAppService,
IEmailLogsRepository emailLogsRepository,
IConfiguration configuration,
IChesClientService chesClientService,
EmailQueueService emailQueueService,
IExternalUserLookupServiceProvider externalUserLookupServiceProvider,
ISettingManager settingManager,
IFeatureChecker featureChecker,
IHttpContextAccessor httpContextAccessor
)
{
_emailLogsRepository = emailLogsRepository;
_configuration = configuration;
_chesClientService = chesClientService;
_emailQueueService = emailQueueService;
_externalUserLookupServiceProvider = externalUserLookupServiceProvider;
_settingManager = settingManager;
_featureChecker = featureChecker;
_httpContextAccessor = httpContextAccessor;
}
IHttpContextAccessor httpContextAccessor) : ApplicationService, IEmailNotificationService
#pragma warning restore S107 // Methods should not have too many parameters
{

public async Task DeleteEmail(Guid id)
{
await _emailLogsRepository.DeleteAsync(id);
}
await emailLogsRepository.DeleteAsync(id);
}

public async Task<int> GetEmailsChesWithNoResponseCountAsync()
{
Expand All @@ -77,7 +57,7 @@ public async Task<int> GetEmailsChesWithNoResponseCountAsync()
(x.Status == EmailStatus.Initialized && x.CreationTime.AddMinutes(10) < dbNow);

// Fetch all email logs and apply the filter using LINQ
var allEmailLogs = await _emailLogsRepository.GetListAsync();
var allEmailLogs = await emailLogsRepository.GetListAsync();
var emailLogs = allEmailLogs.Where(filter.Compile()).ToList();

// Ensure we're returning 0 if no logs are found
Expand All @@ -90,16 +70,16 @@ public async Task<int> GetEmailsChesWithNoResponseCountAsync()
{
return null;
}

var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, "html", emailTemplateName, emailCC, emailBCC);
EmailLog emailLog = await _emailLogsRepository.GetAsync(emailId);
var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, "html", emailTemplateName);
EmailLog emailLog = await emailLogsRepository.GetAsync(emailId);
emailLog = UpdateMappedEmailLog(emailLog, emailObject);
emailLog.ApplicationId = applicationId;
emailLog.Id = emailId;
emailLog.Status = status ?? EmailStatus.Initialized;

// When being called here the current tenant is in context - verified by looking at the tenant id
EmailLog loggedEmail = await _emailLogsRepository.UpdateAsync(emailLog, autoSave: true);
EmailLog loggedEmail = await emailLogsRepository.UpdateAsync(emailLog, autoSave: true);
return loggedEmail;
}

Expand All @@ -122,7 +102,7 @@ public async Task<int> GetEmailsChesWithNoResponseCountAsync()
emailLog.Status = status ?? EmailStatus.Initialized;

// When being called here the current tenant is in context - verified by looking at the tenant id
EmailLog loggedEmail = await _emailLogsRepository.InsertAsync(emailLog, autoSave: true);
EmailLog loggedEmail = await emailLogsRepository.InsertAsync(emailLog, autoSave: true);
return loggedEmail;
}

Expand All @@ -131,21 +111,19 @@ protected virtual async Task NotifyTeamsChannel(string chesEmailError)
string? envInfo = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
string activityTitle = "CHES Email error: " + chesEmailError;
string activitySubtitle = "Environment: " + envInfo;
string teamsChannel = _configuration["Notifications:TeamsNotificationsWebhook"] ?? "";
List<Fact> facts = new() { };
await TeamsNotificationService.PostToTeamsAsync(teamsChannel, activityTitle, activitySubtitle, facts);
await notificationAppService.PostToTeamsAsync(activityTitle, activitySubtitle);
}

public async Task<HttpResponseMessage> SendCommentNotification(EmailCommentDto input)
{
HttpResponseMessage res = new();
try
{
if (await _featureChecker.IsEnabledAsync("Unity.Notifications"))
if (await featureChecker.IsEnabledAsync("Unity.Notifications"))
{
var defaultFromAddress = await SettingProvider.GetOrNullAsync(NotificationsSettings.Mailing.DefaultFromAddress);
var scheme = "https";
var request = _httpContextAccessor.HttpContext?.Request;
var request = httpContextAccessor.HttpContext?.Request;

if (request == null)
{
Expand Down Expand Up @@ -211,7 +189,6 @@ public async Task<HttpResponseMessage> SendCommentNotification(EmailCommentDto i
return res;
}


/// <summary>
/// Send Email Notfication
/// </summary>
Expand All @@ -238,8 +215,8 @@ public async Task<HttpResponseMessage> SendEmailNotification(string emailTo, str

}
// Send the email using the CHES client service
var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, emailBodyType, emailTemplateName, emailCC, emailBCC);
var response = await _chesClientService.SendAsync(emailObject);
var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, emailBodyType, emailTemplateName);
var response = await chesClientService.SendAsync(emailObject);

// Assuming SendAsync returns a HttpResponseMessage or equivalent:
return response;
Expand All @@ -259,7 +236,7 @@ public async Task<HttpResponseMessage> SendEmailNotification(string emailTo, str
EmailLog emailLog = new EmailLog();
try
{
emailLog = await _emailLogsRepository.GetAsync(id);
emailLog = await emailLogsRepository.GetAsync(id);
}
catch (EntityNotFoundException ex)
{
Expand All @@ -272,7 +249,7 @@ public async Task<HttpResponseMessage> SendEmailNotification(string emailTo, str
[Authorize]
public virtual async Task<List<EmailHistoryDto>> GetHistoryByApplicationId(Guid applicationId)
{
var entityList = await _emailLogsRepository.GetByApplicationIdAsync(applicationId);
var entityList = await emailLogsRepository.GetByApplicationIdAsync(applicationId);
var dtoList = ObjectMapper.Map<List<EmailLog>, List<EmailHistoryDto>>(entityList);

var sentByUserIds = dtoList
Expand All @@ -284,7 +261,7 @@ public virtual async Task<List<EmailHistoryDto>> GetHistoryByApplicationId(Guid

foreach (var userId in sentByUserIds)
{
var userInfo = await _externalUserLookupServiceProvider.FindByIdAsync(userId);
var userInfo = await externalUserLookupServiceProvider.FindByIdAsync(userId);
if (userInfo != null)
{
userDictionary[userId] = ObjectMapper.Map<IUserData, EmailHistoryUserDto>(userInfo);
Expand Down Expand Up @@ -313,10 +290,18 @@ public async Task SendEmailToQueue(EmailLog emailLog)
emailNotificationEvent.Id = emailLog.Id;
emailNotificationEvent.TenantId = emailLog.TenantId;
emailNotificationEvent.RetryAttempts = emailLog.RetryAttempts;
await _emailQueueService.SendToEmailEventQueueAsync(emailNotificationEvent);
await emailQueueService.SendToEmailEventQueueAsync(emailNotificationEvent);
}

protected virtual async Task<dynamic> GetEmailObjectAsync(string emailTo, string body, string subject, string? emailFrom, string? emailBodyType, string? emailTemplateName, string? emailCC = null, string? emailBCC = null)
protected virtual async Task<dynamic> GetEmailObjectAsync(
string emailTo,
string body,
string subject,
string? emailFrom,
string? emailBodyType,
string? emailTemplateName,
string? emailCC = null,
string? emailBCC = null)
{
var toList = emailTo.ParseEmailList() ?? [];
var ccList = emailCC.ParseEmailList();
Expand Down Expand Up @@ -365,7 +350,7 @@ private async Task UpdateTenantSettings(string settingKey, string valueString)
{
if (!valueString.IsNullOrWhiteSpace())
{
await _settingManager.SetForCurrentTenantAsync(settingKey, valueString);
await settingManager.SetForCurrentTenantAsync(settingKey, valueString);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ internal class EmailNotificationHandler(
IEmailNotificationService emailNotificationService,
IFeatureChecker featureChecker) : ILocalEventHandler<EmailNotificationEvent>, ITransientDependency
{
private const string GRANT_APPLICATION_UPDATE_SUBJECT = "Grant Application Update";
private const string FAILED_PAYMENTS_SUBJECT = "CAS Payment Failure Notification";

public async Task HandleEventAsync(EmailNotificationEvent eventData)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
{
public class ChesClientOptions
{
public string ChesUrl { get; set; } = string.Empty;
public string ChesTokenUrl { get; set; } = string.Empty;
public string ChesClientId { get; set; } = string.Empty;
public string ChesClientSecret { get; set; } = string.Empty;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,54 +1,58 @@
using Volo.Abp;
using System.Threading.Tasks;
using System.Text.Json;
using Unity.Modules.Shared.Integrations;
using Unity.Modules.Shared.Http;
using Volo.Abp.Application.Services;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using System.Net.Http;
using Volo.Abp.Caching;
using Unity.GrantManager.Integrations;

namespace Unity.Notifications.Integrations.Ches
{
[IntegrationService]
[RemoteService(false, Name = "Ches")]
[ExposeServices(typeof(ChesClientService), typeof(IChesClientService))]
public class ChesClientService : ApplicationService, IChesClientService
public class ChesClientService(
IDistributedCache<TokenValidationResponse, string> chesTokenCache,
IResilientHttpRequest resilientHttpRequest,
IEndpointManagementAppService endpointManagementAppService,
IHttpClientFactory httpClientFactory,
IOptions<ChesClientOptions> chesClientOptions
) : ApplicationService, IChesClientService
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IResilientHttpRequest _resilientRestClient;
private readonly IOptions<ChesClientOptions> _chesClientOptions;
private readonly IDistributedCache<TokenValidationResponse, string> _chesTokenCache;

public ChesClientService(
IDistributedCache<TokenValidationResponse, string> chesTokenCache,
IResilientHttpRequest resilientHttpRequest,
IHttpClientFactory httpClientFactory,
IOptions<ChesClientOptions> chesClientOptions)
public async Task<HttpResponseMessage?> SendAsync(object emailRequest)
{
_resilientRestClient = resilientHttpRequest;
_chesClientOptions = chesClientOptions;
_httpClientFactory = httpClientFactory;
_chesTokenCache = chesTokenCache;
string authToken = await GetAuthTokenAsync();
string notificationsApiUrl = await endpointManagementAppService.GetUgmUrlByKeyNameAsync(DynamicUrlKeyNames.NOTIFICATION_API_BASE);
var resource = $"{notificationsApiUrl}/email";

// Pass the object directly; ResilientHttpRequest will serialize it to JSON
var response = await resilientHttpRequest.HttpAsync(
HttpMethod.Post,
resource,
emailRequest,
authToken
);

return response;
}

public async Task<HttpResponseMessage?> SendAsync(object emailRequest)
private async Task<string> GetAuthTokenAsync()
{
ClientOptions clientOptions = new ClientOptions
string notificationsAuthUrl = await endpointManagementAppService.GetUgmUrlByKeyNameAsync(DynamicUrlKeyNames.NOTIFICATION_AUTH);

ClientOptions clientOptions = new()
{
Url = _chesClientOptions.Value.ChesTokenUrl,
ClientId = _chesClientOptions.Value.ChesClientId,
ClientSecret = _chesClientOptions.Value.ChesClientSecret,
Url = notificationsAuthUrl,
ClientId = chesClientOptions.Value.ChesClientId,
ClientSecret = chesClientOptions.Value.ChesClientSecret,
ApiKey = "ChesApiKey"
};

TokenService tokenService = new(_httpClientFactory, _chesTokenCache, Logger);
var authToken = await tokenService.GetAuthTokenAsync(clientOptions);
var resource = $"{_chesClientOptions.Value.ChesUrl}/email";
string jsonString = JsonSerializer.Serialize(emailRequest);
var response = await _resilientRestClient.HttpAsyncWithBody(HttpMethod.Post, resource, jsonString, authToken);
return response;
TokenService tokenService = new(httpClientFactory, chesTokenCache, Logger);
return await tokenService.GetAuthTokenAsync(clientOptions);
}
}
}
Loading