Skip to content

Commit

Permalink
Merge pull request #178 from bcgov/staging
Browse files Browse the repository at this point in the history
Staging
  • Loading branch information
marzmehr committed Sep 23, 2023
2 parents c5576ea + 7ab73a4 commit 1c0edda
Show file tree
Hide file tree
Showing 36 changed files with 6,151 additions and 333 deletions.
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
1 change: 1 addition & 0 deletions api/api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<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
3 changes: 2 additions & 1 deletion api/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"WebBaseHref": "/sheriff-scheduling/",
"PdfUrl": "http://localhost:5001",
"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
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));

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


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){
var emailBody = GetEmailBody(training);
var emailSent = await ChesEmailService.SendEmail(
emailBody,
"Training Expiry Notice",
training.Sheriff.Email
);
if(emailSent)
await TrainingService.UpdateTraining(training.Id);
}
}
Logger.LogInformation("CronJob Done");
}

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

return emailBody;
}

public Task Execute(IJobExecutionContext context)
{
Logger.LogInformation("___Running CronJob___");
ProcessTrainings();
return Task.CompletedTask;
}
}
}
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
4 changes: 4 additions & 0 deletions api/models/dto/generated/LookupCodeDto.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ public partial class LookupCodeDto
public DateTimeOffset? ExpiryDate { get; set; }
public LocationDto Location { get; set; }
public int? LocationId { get; set; }
public bool Mandatory { get; set; }
public int ValidityPeriod { get; set; }
public string Category { get; set; }
public int AdvanceNotice { get; set; }
public uint ConcurrencyToken { get; set; }
public LookupSortOrderDto SortOrderForLocation { get; set; }
}
Expand Down
1 change: 1 addition & 0 deletions api/models/dto/generated/SheriffTrainingDto.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public partial class SheriffTrainingDto
public int? TrainingTypeId { get; set; }
public DateTimeOffset? TrainingCertificationExpiry { get; set; }
public string Note { get; set; }
public bool FirstNotice { get; set; }
public int Id { get; set; }
public DateTimeOffset StartDate { get; set; }
public DateTimeOffset EndDate { get; set; }
Expand Down
4 changes: 3 additions & 1 deletion api/services/ChesEmailService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public async Task<string> GetEmailServiceToken()
return null;
}

public async Task SendEmail(string body, string subject, string recipientEmail)
public async Task<Boolean> SendEmail(string body, string subject, string recipientEmail)
{
body.ThrowIfNullOrEmpty(nameof(body));
subject.ThrowIfNullOrEmpty(nameof(subject));
Expand Down Expand Up @@ -97,10 +97,12 @@ public async Task SendEmail(string body, string subject, string recipientEmail)
throw new BadRequestException($"While sending email - Received status code: {response.StatusCode} : {contents}");

Logger.LogInformation($"Email sent to {recipientEmail} successfully.");
return true;
}
catch (Exception e)
{
Logger.LogError(e, "Error happened while trying to send email.");
return false;
}
}

Expand Down
49 changes: 49 additions & 0 deletions api/services/usermanagement/TrainingService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using SS.Api.helpers.extensions;
using SS.Api.infrastructure.exceptions;
using SS.Db.models;
using SS.Db.models.sheriff;

namespace SS.Api.services.usermanagement
{

public class TrainingService
{
private SheriffDbContext Db { get; }

public TrainingService(SheriffDbContext db)
{
Db = db;
}


public async Task<List<SheriffTraining>> GetTrainings()
{
var sheriffTrainingQuery = Db.SheriffTraining.AsNoTracking()
.AsSplitQuery()
.Where(t => t.ExpiryDate == null)
.Where(t => t.FirstNotice != true)
.Where(t => t.TrainingType.AdvanceNotice > 0)
.Include(t => t.TrainingType)
.Include(t => t.Sheriff);

return await sheriffTrainingQuery.ToListAsync();
}

public async Task UpdateTraining(int trainingId)
{
var training = await Db.SheriffTraining.FindAsync(trainingId);
training.ThrowBusinessExceptionIfNull(
$"{nameof(SheriffTraining)} with the id: {trainingId} could not be found. ");

if (training.ExpiryDate.HasValue)
throw new BusinessLayerException($"{nameof(SheriffTraining)} with the id: {trainingId} has been expired");

training.FirstNotice = true;
await Db.SaveChangesAsync();
}
}
}
Loading

0 comments on commit 1c0edda

Please sign in to comment.