Skip to content

Commit

Permalink
Merge branch 'master' into dependabot/npm_and_yarn/web/moment-timezon…
Browse files Browse the repository at this point in the history
…e-0.5.35
  • Loading branch information
marzmehr committed Feb 12, 2024
2 parents 4bce8c5 + 8b72f8e commit f2bab56
Show file tree
Hide file tree
Showing 144 changed files with 23,110 additions and 1,246 deletions.
2 changes: 1 addition & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"mapster.tool": {
"version": "6.5.0",
"version": "8.0.0",
"commands": [
"dotnet-mapster"
]
Expand Down
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# the repo. Unless a later match takes precedence,
# @global-owner1 and @global-owner2 will be requested for
# review when someone opens a pull request.
* @devinleighsmith @WadeBarnes @seeker25 @marzmehr
* @devinleighsmith @WadeBarnes @marzmehr

# Order is important; the last matching pattern takes the most
# precedence. When someone opens a pull request that only
Expand Down
8 changes: 8 additions & 0 deletions api/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
using SS.Api.services.ef;
using SS.Db.models;
using Microsoft.Extensions.Logging;
using Quartz;
using SS.Api.cronjobs;

namespace SS.Api
{
Expand Down Expand Up @@ -137,6 +139,12 @@ public void ConfigureServices(IServiceCollection services)
});

services.AddSwaggerGenNewtonsoftSupport();

services.AddQuartz(q =>
{
q.AddJobAndTrigger<TrainingNotification>(Configuration);
});
services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
Expand Down
11 changes: 6 additions & 5 deletions api/api.csproj
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>SS.Api</RootNamespace>
<UserSecretsId>de959767-ede6-4f8a-b6b9-d36aed70339C</UserSecretsId>
<ProjectGuid>6224c484-3e23-4f06-a749-195c1e478110</ProjectGuid>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>bin\$(Configuration)\netcoreapp3.1\api.xml</DocumentationFile>
<DocumentationFile>bin\$(Configuration)\net5.0\api.xml</DocumentationFile>
<NoWarn>1701;1702;1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<NoWarn>1701;1702;1591</NoWarn>
<DocumentationFile>bin\$(Configuration)\netcoreapp3.1\api.xml</DocumentationFile>
<DocumentationFile>bin\$(Configuration)\net5.0\api.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FlexLabs.EntityFrameworkCore.Upsert" Version="3.1.0" />
<PackageReference Include="IdentityModel" Version="4.4.0" />
<PackageReference Include="Mapster" Version="6.5.0" />
<PackageReference Include="Mapster" Version="7.3.0" />
<PackageReference Include="Mapster.DependencyInjection" Version="1.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.18" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="3.1.9" />
Expand All @@ -34,10 +33,12 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="7.0.0" />
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="5.2.8" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.8.0" />
<PackageReference Include="NodaTime" Version="3.0.3" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="5.0.1" />
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.7.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="5.6.3" />
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="5.6.3" />
Expand Down
2 changes: 2 additions & 0 deletions api/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"WebBaseHref": "/sheriff-scheduling/",
"PdfUrl": "http://localhost:5001",
"TrainingNotification": "0 30 5 * * ?",
// Hint: Override these in secrets when doing local development. ByPassAuthAndUseImpersonatedUser - only works in development mode.
"ByPassAuthAndUseImpersonatedUser": "true",
"ImpersonateUser": {
Expand Down
26 changes: 24 additions & 2 deletions api/controllers/scheduling/DistributeScheduleController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using SS.Api.helpers;
using SS.Api.helpers.extensions;
using SS.Api.infrastructure.authorization;
using SS.Api.models.dto;
using SS.Api.models.dto.generated;
using SS.Api.services.scheduling;
using SS.Common.helpers.extensions;
Expand All @@ -17,14 +18,15 @@
namespace SS.Api.controllers.scheduling
{
[Route("api/[controller]")]
[ApiController]
public class DistributeScheduleController : ControllerBase
{
private DistributeScheduleService DistributeScheduleService { get; }
private ShiftService ShiftService { get; }
private SheriffDbContext Db { get; }
private IConfiguration Configuration { get; }

public DistributeScheduleController(DistributeScheduleService distributeSchedule, ShiftService shiftService, SheriffDbContext db, IConfiguration configuration)
public DistributeScheduleController(DistributeScheduleService distributeSchedule, ShiftService shiftService, SheriffDbContext db, IConfiguration configuration)
{
DistributeScheduleService = distributeSchedule;
ShiftService = shiftService;
Expand Down Expand Up @@ -73,5 +75,25 @@ public async Task<ActionResult<List<ShiftAvailabilityDto>>> GetDistributeSchedul

return Ok(shiftsWithDuties.Adapt<List<ShiftAvailabilityDto>>());
}

[HttpPost("print")]
[PermissionClaimAuthorize(perm: Permission.ViewDistributeSchedule)]
public async Task<FileContentResult> Print(PdfHtml pdfhtml)
{
var pdfContent = await DistributeScheduleService.PrintService(pdfhtml.html);
return new FileContentResult(pdfContent, "application/pdf");
}

[HttpPost("email")]
[PermissionClaimAuthorize(perm: Permission.ViewDistributeSchedule)]
public async Task<ActionResult> Email(PdfHtml pdfhtml)
{
var pdfContent = await DistributeScheduleService.PrintService(pdfhtml.html);

var senderEmail = $"{User.Email()}";
await DistributeScheduleService.EmailService(senderEmail, pdfhtml.recipients, pdfhtml.emailSubject, pdfhtml.emailContent, pdfContent);

return Ok("Email Sent.");
}
}
}
}
35 changes: 34 additions & 1 deletion api/controllers/usermanagement/SheriffController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,17 @@ public class SheriffController : UserController
private ShiftService ShiftService { get; }
private DutyRosterService DutyRosterService { get; }
private SheriffDbContext Db { get; }
private TrainingService TrainingService { get; }

// ReSharper disable once InconsistentNaming
private readonly long _uploadPhotoSizeLimitKB;

public SheriffController(SheriffService sheriffService, DutyRosterService dutyRosterService, ShiftService shiftService, UserService userUserService, IConfiguration configuration, SheriffDbContext db) : base(userUserService)
public SheriffController(SheriffService sheriffService, DutyRosterService dutyRosterService, ShiftService shiftService, UserService userUserService,TrainingService trainingService, IConfiguration configuration, SheriffDbContext db) : base(userUserService)
{
SheriffService = sheriffService;
ShiftService = shiftService;
DutyRosterService = dutyRosterService;
TrainingService = trainingService;
Db = db;
_uploadPhotoSizeLimitKB = Convert.ToInt32(configuration.GetNonEmptyValue("UploadPhotoSizeLimitKB"));
}
Expand Down Expand Up @@ -155,6 +157,15 @@ public async Task<ActionResult<SheriffDto>> UploadPhoto(Guid? id, string badgeNu
return Ok(sheriff.Adapt<SheriffDto>());
}

[HttpPut]
[Route("updateExcused")]
[PermissionClaimAuthorize(perm: Permission.GenerateReports)]
public async Task<ActionResult<SheriffDto>> UpdateExcused(Sheriff excusedSheriff)
{
var sheriff = await SheriffService.UpdateSheriffExcused(excusedSheriff);
return Ok(sheriff.Adapt<SheriffDto>());
}

#endregion Sheriff

#region SheriffAwayLocation
Expand Down Expand Up @@ -268,6 +279,28 @@ public async Task<ActionResult> RemoveSheriffLeave(int id, string expiryReason)

#endregion SheriffLeave

#region SheriffTrainingReports

[HttpPost]
[Route("training/reports")]
[PermissionClaimAuthorize(perm: Permission.GenerateReports)]
public async Task<ActionResult<TrainingReportDto>> GetSheriffsTrainingReports(TrainingReportSearchDto trainingReportSearch)
{
var sheriffs = await TrainingService.GetSheriffsTrainingReports(trainingReportSearch);
return Ok(sheriffs.Adapt<List<TrainingReportDto>>());
}

[HttpGet]
[Route("training/adjust-expiry")]
[PermissionClaimAuthorize(perm: Permission.AdjustTrainingExpiry)]
public async Task<ActionResult> TrainingExpiryAdjustment()
{
await TrainingService.TrainingExpiryAdjustment();
return Ok(new { result = "success"});
}

#endregion SheriffTrainingReports

#region SheriffTraining

[HttpGet]
Expand Down
34 changes: 34 additions & 0 deletions api/cronjobs/QuartzConfigService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using Microsoft.Extensions.Configuration;
using Quartz;

namespace SS.Api.cronjobs
{
public static class QuartzConfigService
{
public static void AddJobAndTrigger<T>(
this IServiceCollectionQuartzConfigurator quartz,
IConfiguration config)
where T : IJob
{

string jobName = typeof(T).Name;
var configKey = $"{jobName}";
var cronSchedule = config[configKey];

if (string.IsNullOrEmpty(cronSchedule))
{
throw new Exception($"No Quartz.NET Cron schedule found for job in configuration at {configKey}");
}

var jobKey = new JobKey(jobName);
quartz.AddJob<T>(opts => opts.WithIdentity(jobKey));

quartz.AddTrigger(opts => opts
.ForJob(jobKey)
.WithIdentity(jobName + "-trigger")
.WithCronSchedule(cronSchedule));

}
}
}
89 changes: 89 additions & 0 deletions api/cronjobs/TrainingNotification.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@


using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Quartz;
using SS.Api.services;
using SS.Api.services.usermanagement;
using SS.Db.models.sheriff;

namespace SS.Api.cronjobs
{
[DisallowConcurrentExecution]
public class TrainingNotification: IJob
{
private readonly ILogger<TrainingNotification> Logger;
public IServiceProvider Services { get; }

public TrainingNotification(ILogger<TrainingNotification> logger, IServiceProvider services, ManageTypesService manageTypesService)
{
Logger = logger;
Services = services;
}

public async void ProcessTrainings()
{
using var scope = Services.CreateScope();
var TrainingService = scope.ServiceProvider.GetRequiredService<TrainingService>();
var ChesEmailService = scope.ServiceProvider.GetRequiredService<ChesEmailService>();

var trainings = await TrainingService.GetTrainings();
foreach(var training in trainings)
{
var noticeDate = DateTimeOffset.UtcNow.AddDays(training.TrainingType.AdvanceNotice);

Logger.LogInformation(training.TrainingCertificationExpiry.ToString());
Logger.LogInformation((training.TrainingCertificationExpiry < noticeDate).ToString());
Logger.LogInformation(training.Sheriff.Email);

if(training.TrainingCertificationExpiry < noticeDate){

bool rotatingTraining = TrainingService.IsRotatingTraining(training.TrainingType);
var emailBody = rotatingTraining? GetEmailRotatingBody(training) : GetEmailEndOfYearBody(training);
var emailTitle = rotatingTraining? "Training Expiry Notice" : "Training Requalification Notice";

var emailSent = await ChesEmailService.SendEmail(
emailBody,
emailTitle,
training.Sheriff.Email
);
if(emailSent)
await TrainingService.UpdateTraining(training.Id);
}
}
Logger.LogInformation("CronJob Done");
}

public string GetEmailRotatingBody(SheriffTraining training)
{
var expiryDate = training.TrainingCertificationExpiry.Value.ToString("MMMM dd, yyyy");
var emailBody =
$"Dear {training.Sheriff.FirstName} {training.Sheriff.LastName}, \n\n"+
$"Your \'{training.TrainingType.Code}\' certification will expire on \'{expiryDate}\'. \n"+
"Please ensure your certification is renewed before this date.";

return emailBody;
}

public string GetEmailEndOfYearBody(SheriffTraining training)
{
var expiryYear = training.TrainingCertificationExpiry.Value.AddDays(-1).AddYears(1).ToString("yyyy");
var emailBody =
$"Dear {training.Sheriff.FirstName} {training.Sheriff.LastName}, \n\n"+
$"Your \'{training.TrainingType.Code}\' certification will require renewal for the calendar year \'{expiryYear}\'. \n"+
$"Please ensure you renew your certification between January 1st and December 31, {expiryYear}. \n\n" +
"It is recommended to schedule training early in the year to ensure end of year compliance.";

return emailBody;
}

public Task Execute(IJobExecutionContext context)
{
Logger.LogInformation("___Running CronJob___");
ProcessTrainings();
return Task.CompletedTask;
}
}
}
8 changes: 8 additions & 0 deletions api/helpers/ConfigurationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,13 @@ public static string GetNonEmptyValue(this IConfiguration configuration, string
? throw new ConfigurationException($"Configuration '{key}' is invalid or missing.")
: configurationValue;
}

public static string GetBoolValue(this IConfiguration configuration, string key)
{
var configurationValue = configuration.GetValue<string>(key);
return string.IsNullOrEmpty(configurationValue)
? "false"
: configurationValue;
}
}
}
10 changes: 6 additions & 4 deletions api/helpers/extensions/ClaimExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public static class ClaimExtensions
public static string GetValueByType(this IEnumerable<Claim> claims, string type) =>
claims.FirstOrDefault(c => c.Type == type)?.Value;

public static string GetIdirUserName(this IEnumerable<Claim> claims) =>
public static string GetIdirUserName(this IEnumerable<Claim> claims) =>
claims.GetValueByType(CustomClaimTypes.IdirUserName).Replace("@idir", "").ToLower();

public static Guid GetIdirId(this IEnumerable<Claim> claims) =>
Expand Down Expand Up @@ -42,8 +42,11 @@ public static int HomeLocationId(this ClaimsPrincipal user)
public static string IdirId(this ClaimsPrincipal user) =>
user.FindFirstValue(CustomClaimTypes.IdirId);

public static string Email(this ClaimsPrincipal user) =>
user.FindFirstValue(ClaimTypes.Email);

public static string IdirUserName(this ClaimsPrincipal user) =>
user.FindFirstValue(CustomClaimTypes.IdirUserName).Replace("@idir","");
user.FindFirstValue(CustomClaimTypes.IdirUserName).Replace("@idir", "");

public static Guid CurrentUserId(this ClaimsPrincipal user)
{
Expand All @@ -52,6 +55,5 @@ public static Guid CurrentUserId(this ClaimsPrincipal user)
throw new InvalidOperationException("Missing UserId Guid from claims.");
return userId;
}

}
}
}
1 change: 1 addition & 0 deletions api/infrastructure/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public static IServiceCollection AddSSServices(this IServiceCollection services,
services.AddScoped<UserService>();
services.AddScoped<SheriffService>();
services.AddScoped<ShiftService>();
services.AddScoped<TrainingService>();
services.AddScoped<DutyRosterService>();
services.AddScoped<AssignmentService>();
services.AddScoped<DistributeScheduleService>();
Expand Down
Loading

0 comments on commit f2bab56

Please sign in to comment.