Skip to content

Commit

Permalink
feat: Az sender for sending task owner report
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonathanio123 committed Nov 12, 2024
1 parent 75c7441 commit 16a7ea2
Show file tree
Hide file tree
Showing 17 changed files with 451 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
namespace Fusion.Resources.Functions.Common.ApiClients.ApiModels;

public class SendEmailRequest
{
public required string[] Recipients { get; set; }
public required string Subject { get; set; }
public required string Body { get; set; }
public string? FromDisplayName { get; set; }
}

public class SendEmailWithTemplateRequest
{
public required string Subject { get; set; }

public required string[] Recipients { get; set; }

/// <summary>
/// Specify the content that is to be displayed in the mail
/// </summary>
public required MailBody MailBody { get; set; }
}

public class MailBody
{
/// <summary>
/// The main content in the mail placed between the header and footer
/// </summary>
public required string HtmlContent { get; set; }

/// <summary>
/// Optional. If not specified, the footer template will be used
/// </summary>
public string? HtmlFooter { get; set; }

/// <summary>
/// Optional. A text that is displayed inside the header. Will default to 'Mail from Fusion'
/// </summary>
public string? HeaderTitle { get; set; }
}
10 changes: 10 additions & 0 deletions src/Fusion.Resources.Functions.Common/ApiClients/IMailApiClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Fusion.Resources.Functions.Common.ApiClients.ApiModels;

namespace Fusion.Resources.Functions.Common.ApiClients;

public interface IMailApiClient
{
public Task SendEmailAsync(SendEmailRequest request, CancellationToken cancellationToken = default);

public Task SendEmailWithTemplate(SendEmailWithTemplateRequest request, string? templateName = "default", CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
namespace Fusion.Resources.Functions.Common.ApiClients;
using Fusion.Integration.Profile;
using Fusion.Integration.Profile.ApiClient;

namespace Fusion.Resources.Functions.Common.ApiClients;

public interface IPeopleApiClient
{
Task<string> GetPersonFullDepartmentAsync(Guid? personAzureUniqueId);
Task<ICollection<ApiEnsuredProfileV2>> ResolvePersonsAsync(IEnumerable<PersonIdentifier> personAzureUniqueIds, CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ public Task PutDepartmentAsync(ApiResourceOwnerDepartment departments,
public Task<ApiWeeklySummaryReport?> GetLatestWeeklyReportAsync(string departmentSapId,
CancellationToken cancellationToken = default);

/// <exception cref="SummaryApiError"></exception>
public Task<ApiWeeklyTaskOwnerReport?> GetLatestWeeklyTaskOwnerReportAsync(Guid projectId,
CancellationToken cancellationToken = default);

/// <exception cref="SummaryApiError"></exception>
public Task PutWeeklySummaryReportAsync(string departmentSapId, ApiWeeklySummaryReport report,
CancellationToken cancellationToken = default);
Expand Down Expand Up @@ -103,4 +107,43 @@ public class ApiProject
public Guid[] AssignedAdminsAzureUniqueId { get; set; } = [];
}

public class ApiWeeklyTaskOwnerReport
{
public required Guid Id { get; set; }
public required Guid ProjectId { get; set; }
public required DateTime PeriodStart { get; set; }
public required DateTime PeriodEnd { get; set; }

public required int ActionsAwaitingTaskOwnerAction { get; set; }
public required ApiAdminAccessExpiring[] AdminAccessExpiringInLessThanThreeMonths { get; set; }
public required ApiPositionAllocationEnding[] PositionAllocationsEndingInNextThreeMonths { get; set; }
public required ApiTBNPositionStartingSoon[] TBNPositionsStartingInLessThanThreeMonths { get; set; }
}

public class ApiAdminAccessExpiring
{
public required Guid AzureUniqueId { get; set; }
public required string FullName { get; set; }
public required DateTime Expires { get; set; }
}

public class ApiPositionAllocationEnding
{
public required string PositionExternalId { get; set; }

public required string PositionName { get; set; }

public required string PositionNameDetailed { get; set; }

public required DateTime PositionAppliesTo { get; set; }
}

public class ApiTBNPositionStartingSoon
{
public required string PositionExternalId { get; set; }
public required string PositionName { get; set; }
public required string PositionNameDetailed { get; set; }
public required DateTime PositionAppliesFrom { get; set; }
}

#endregion
48 changes: 48 additions & 0 deletions src/Fusion.Resources.Functions.Common/ApiClients/MailApiClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System.Text;
using Fusion.Resources.Functions.Common.ApiClients.ApiModels;
using Fusion.Resources.Functions.Common.Extensions;
using Fusion.Resources.Functions.Common.Integration.Errors;
using Fusion.Resources.Functions.Common.Integration.Http;
using Newtonsoft.Json;

namespace Fusion.Resources.Functions.Common.ApiClients;

public class MailApiClient : IMailApiClient
{
private readonly HttpClient mailClient;

public MailApiClient(IHttpClientFactory httpClientFactory)
{
mailClient = httpClientFactory.CreateClient(HttpClientNames.Application.Mail);
mailClient.Timeout = TimeSpan.FromMinutes(2);
}

public async Task SendEmailAsync(SendEmailRequest request, CancellationToken cancellationToken = default)
{
var json = JsonConvert.SerializeObject(request);
var content = new StringContent(json, Encoding.UTF8, "application/json");

using var response = await mailClient.PostAsync("/mails", content, cancellationToken);

await ThrowIfNotSuccess(response);
}

public async Task SendEmailWithTemplate(SendEmailWithTemplateRequest request, string? templateName = "default", CancellationToken cancellationToken = default)
{
var json = JsonConvert.SerializeObject(request);
var content = new StringContent(json, Encoding.UTF8, "application/json");

using var response = await mailClient.PostAsync($"templates/{templateName}/mails", content, cancellationToken);

await ThrowIfNotSuccess(response);
}

private async Task ThrowIfNotSuccess(HttpResponseMessage response)
{
if (!response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync();
throw new ApiError(response.RequestMessage!.RequestUri!.ToString(), response.StatusCode, body, "Response from API call indicates error");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Fusion.Resources.Functions.Common.Integration.Http;
using Fusion.Integration.Profile;
using Fusion.Integration.Profile.ApiClient;
using Fusion.Resources.Functions.Common.Integration.Http;

namespace Fusion.Resources.Functions.Common.ApiClients;

Expand All @@ -19,4 +21,15 @@ public async Task<string> GetPersonFullDepartmentAsync(Guid? personAzureUniqueId

return data.FullDepartment;
}

public async Task<ICollection<ApiEnsuredProfileV2>> ResolvePersonsAsync(IEnumerable<PersonIdentifier> personAzureUniqueIds, CancellationToken cancellationToken = default)
{
var resp = await peopleClient
.PostAsJsonAsync<ICollection<ApiEnsuredProfileV2>>($"/persons/ensure?api-version=3.0", new
{
personIdentifiers = personAzureUniqueIds.Select(p => p.ToString())
}, cancellationToken);

return resp;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,25 @@ public async Task<ApiProject> PutProjectAsync(ApiProject project, CancellationTo
cancellationToken: cancellationToken))?.Items?.FirstOrDefault();
}

public async Task<ApiWeeklyTaskOwnerReport?> GetLatestWeeklyTaskOwnerReportAsync(Guid projectId, CancellationToken cancellationToken = default)
{
var lastMonday = DateTime.UtcNow.GetPreviousWeeksMondayDate();

var queryString = $"/projects/{projectId}/task-owners-summary-reports/weekly?$filter=PeriodStart eq '{lastMonday.Date:O}'&$top=1";


using var response = await summaryClient.GetAsync(queryString, cancellationToken);

await ThrowIfUnsuccessfulAsync(response);


await using var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken);

return (await JsonSerializer.DeserializeAsync<ApiCollection<ApiWeeklyTaskOwnerReport>>(contentStream,
jsonSerializerOptions,
cancellationToken: cancellationToken))?.Items?.FirstOrDefault();
}

public async Task PutWeeklySummaryReportAsync(string departmentSapId, ApiWeeklySummaryReport report,
CancellationToken cancellationToken = default)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ public static IServiceCollection AddHttpClients(this IServiceCollection services
builder.AddRolesClient();
services.AddScoped<IRolesApiClient, RolesApiClient>();

builder.AddMailClient();
services.AddScoped<IMailApiClient, MailApiClient>();

return services;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<ItemGroup>
<PackageReference Include="Fusion.Services.Org.ApiModels" Version="8.0.5"/>
<PackageReference Include="AdaptiveCards" Version="3.1.0" />
<PackageReference Include="AdaptiveCards" Version="2.7.3"/>
<PackageReference Include="Fusion.ApiClients.Org" Version="8.0.4"/>
<PackageReference Include="Fusion.Integration" Version="8.0.8"/>
<PackageReference Include="Fusion.Events.Azure.Functions.Extensions" Version="6.0.5"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Fusion.Resources.Functions.Common.Integration.Authentication;
using Fusion.Resources.Functions.Common.Integration.ServiceDiscovery;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Fusion.Resources.Functions.Common.Integration.Http.Handlers;

public class MailHttpHandler : FunctionHttpMessageHandler
{
private readonly IOptions<HttpClientsOptions> options;

public MailHttpHandler(ILoggerFactory loggerFactory, ITokenProvider tokenProvider, IServiceDiscovery serviceDiscovery, IOptions<HttpClientsOptions> options)
: base(loggerFactory.CreateLogger<MailHttpHandler>(), tokenProvider, serviceDiscovery)
{
this.options = options;
}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
await SetEndpointUriForRequestAsync(request, ServiceEndpoint.Mail);
await AddAuthHeaderForRequestAsync(request, options.Value.Fusion);

return await base.SendAsync(request, cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Fusion.Resources.Functions.Common.Integration.Errors;
using System.Text;
using Fusion.Resources.Functions.Common.Integration.Errors;
using Newtonsoft.Json;

namespace Fusion.Resources.Functions.Common.Integration.Http
Expand All @@ -17,6 +18,23 @@ public static async Task<T> GetAsJsonAsync<T>(this HttpClient client, string url
return deserialized;
}


public static async Task<TResponse> PostAsJsonAsync<TResponse>(this HttpClient client, string url, object data, CancellationToken cancellationToken = default)
{
var json = JsonConvert.SerializeObject(data);
var content = new StringContent(json, Encoding.UTF8, "application/json");

var response = await client.PostAsync(url, content, cancellationToken);

var body = await response.Content.ReadAsStringAsync(cancellationToken);

if (!response.IsSuccessStatusCode)
throw new ApiError(response.RequestMessage!.RequestUri!.ToString(), response.StatusCode, body, "Response from API call indicates error");

TResponse deserialized = JsonConvert.DeserializeObject<TResponse>(body);
return deserialized;
}

public static async Task<IEnumerable<string>> OptionsAsync(this HttpClient client, string url, CancellationToken cancellationToken = default)
{
var message = new HttpRequestMessage(HttpMethod.Options, url);
Expand All @@ -26,6 +44,5 @@ public static async Task<IEnumerable<string>> OptionsAsync(this HttpClient clien

return allowHeaders;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public HttpClientFactoryBuilder AddRolesClient()
services.AddTransient<RolesHttpHandler>();
services.AddHttpClient(HttpClientNames.Application.Roles, client =>
{
client.BaseAddress = new Uri("https://fusion-notifications");
client.BaseAddress = new Uri("https://fusion-roles");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
})
.AddHttpMessageHandler<RolesHttpHandler>()
Expand All @@ -122,6 +122,20 @@ public HttpClientFactoryBuilder AddRolesClient()
return this;
}

public HttpClientFactoryBuilder AddMailClient()
{
services.AddTransient<MailHttpHandler>();
services.AddHttpClient(HttpClientNames.Application.Mail, client =>
{
client.BaseAddress = new Uri("https://fusion-mail");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
})
.AddHttpMessageHandler<MailHttpHandler>()
.AddTransientHttpErrorPolicy(DefaultRetryPolicy());

return this;
}

private readonly TimeSpan[] DefaultSleepDurations = new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10) };

private Func<PolicyBuilder<HttpResponseMessage>, IAsyncPolicy<HttpResponseMessage>> DefaultRetryPolicy(TimeSpan[] sleepDurations = null) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public static class Application
public const string Context = "App.Context";
public const string LineOrg = "App.LineOrg";
public const string Roles = "App.Roles";
public const string Mail = "App.Mail";
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ public sealed class ServiceEndpoint
public static ServiceEndpoint Context = new ServiceEndpoint { Key = "context" };
public static ServiceEndpoint LineOrg = new ServiceEndpoint { Key = "lineorg" };
public static ServiceEndpoint Roles = new ServiceEndpoint { Key = "roles" };
public static ServiceEndpoint Mail = new ServiceEndpoint { Key = "mail" };
}
}
Loading

0 comments on commit 16a7ea2

Please sign in to comment.