Created Windows Service
This commit is contained in:
parent
35690df362
commit
794b616293
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
.vs
|
||||
bin
|
||||
obj
|
||||
TestResults
|
||||
TestResults
|
||||
Properties
|
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Worker">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
@ -8,6 +8,9 @@
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
|
||||
<Configurations>Debug;Release;Staging</Configurations>
|
||||
<OutputType>exe</OutputType>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
@ -26,12 +29,15 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autofac.Extras.Quartz" Version="10.0.0" />
|
||||
<PackageReference Include="Dapper" Version="2.1.28" />
|
||||
<PackageReference Include="Dapper.Contrib" Version="2.0.78" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.0" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="NLog" Version="5.2.8" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.8" />
|
||||
<PackageReference Include="Quartz" Version="3.8.1" />
|
||||
|
@ -4,5 +4,6 @@
|
||||
<Controller_SelectedScaffolderID>ApiControllerEmptyScaffolder</Controller_SelectedScaffolderID>
|
||||
<Controller_SelectedScaffolderCategoryPath>root/Common/Api</Controller_SelectedScaffolderCategoryPath>
|
||||
<NameOfLastUsedPublishProfile>C:\Users\tuckerc\FabApprovalWorkerService\FabApprovalWorkerService\Properties\PublishProfiles\Staging.pubxml</NameOfLastUsedPublishProfile>
|
||||
<_LastSelectedProfileId>C:\Users\tuckerc\FabApprovalWorkerService\FabApprovalWorkerService\Properties\PublishProfiles\FolderProfile.pubxml</_LastSelectedProfileId>
|
||||
</PropertyGroup>
|
||||
</Project>
|
@ -1,10 +1,14 @@
|
||||
namespace FabApprovalWorkerService.Models;
|
||||
using Dapper.Contrib.Extensions;
|
||||
|
||||
namespace FabApprovalWorkerService.Models;
|
||||
|
||||
[Table("ECN")]
|
||||
public class ECN {
|
||||
[Key]
|
||||
public required int ECNNumber { get; set; }
|
||||
public bool IsTECN { get; set; } = false;
|
||||
public DateTime ExpirationDate { get; set; }
|
||||
public DateTime? ExtensionDate { get; set; }
|
||||
public DateTime ExtensionDate { get; set; } = DateTime.MinValue;
|
||||
public required int OriginatorID { get; set; }
|
||||
public required string Title { get; set; }
|
||||
}
|
||||
|
12
FabApprovalWorkerService/Models/TrainingAssignment.cs
Normal file
12
FabApprovalWorkerService/Models/TrainingAssignment.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using Dapper.Contrib.Extensions;
|
||||
|
||||
namespace FabApprovalWorkerService.Models;
|
||||
[Table("TrainingAssignment")]
|
||||
public class TrainingAssignment {
|
||||
[Key]
|
||||
public int ID { get; set; }
|
||||
public int TrainingID { get; set; }
|
||||
public bool Status { get; set; } = false;
|
||||
public bool Deleted { get; set; } = false;
|
||||
public DateTime DeletedDate { get; set; }
|
||||
}
|
@ -8,7 +8,7 @@ using Quartz;
|
||||
|
||||
using System.Net.Mail;
|
||||
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
|
||||
|
||||
builder.Logging.ClearProviders();
|
||||
builder.Logging.SetMinimumLevel(LogLevel.Trace);
|
||||
@ -36,11 +36,7 @@ builder.Services.AddQuartz(q => {
|
||||
q.AddTrigger(opts => opts
|
||||
.ForJob(pendingOOOStatusJob)
|
||||
.WithIdentity("Pending OOO status trigger")
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithIntervalInMinutes(10)
|
||||
.RepeatForever()
|
||||
)
|
||||
.StartNow()
|
||||
.WithCronSchedule(CronScheduleBuilder.DailyAtHourAndMinute(0, 0))
|
||||
);
|
||||
|
||||
JobKey expiredOOOStatusJob = new JobKey("Expired OOO status job");
|
||||
@ -50,6 +46,16 @@ builder.Services.AddQuartz(q => {
|
||||
q.AddTrigger(opts => opts
|
||||
.ForJob(expiredOOOStatusJob)
|
||||
.WithIdentity("Expired OOO status trigger")
|
||||
.WithCronSchedule(CronScheduleBuilder.DailyAtHourAndMinute(0, 0))
|
||||
);
|
||||
|
||||
JobKey expiringTECNJob = new JobKey("Expiring TECN job");
|
||||
q.AddJob<ExpiringTECNWorker>(opts => opts
|
||||
.WithIdentity(expiringTECNJob)
|
||||
);
|
||||
q.AddTrigger(opts => opts
|
||||
.ForJob(expiringTECNJob)
|
||||
.WithIdentity("Expiring TECN trigger")
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithIntervalInMinutes(10)
|
||||
.RepeatForever()
|
||||
@ -62,6 +68,11 @@ builder.Services.AddQuartzHostedService(opt => {
|
||||
opt.WaitForJobsToComplete = true;
|
||||
});
|
||||
|
||||
WebApplication app = builder.Build();
|
||||
builder.Services.AddWindowsService(options => {
|
||||
options.ServiceName = "Fab Approval Worker Service";
|
||||
});
|
||||
builder.Services.AddHostedService<WindowsService>();
|
||||
|
||||
IHost app = builder.Build();
|
||||
|
||||
app.Run();
|
||||
|
@ -32,13 +32,13 @@ public class ECNService : IECNService {
|
||||
queryBuilder.Append($"ExpirationDate between '{yesterday}' ");
|
||||
queryBuilder.Append($"and '{today}'");
|
||||
|
||||
IEnumerable<ECN> expiredTecns = (await _dalService.QueryAsync<ECN>(queryBuilder.ToString()))
|
||||
.Where(e => e.ExtensionDate is null || e.ExtensionDate < DateTime.Now)
|
||||
.ToList();
|
||||
IEnumerable<ECN> expiredTecns = (await _dalService.QueryAsync<ECN>(queryBuilder.ToString()));
|
||||
|
||||
_logger.LogInformation($"Found {expiredTecns.Count()} expired TECNs");
|
||||
IEnumerable<ECN> expiredTecnsNotExtended = expiredTecns
|
||||
.Where(e => e.ExtensionDate < DateTime.Now)
|
||||
.ToList();
|
||||
|
||||
return expiredTecns;
|
||||
return expiredTecnsNotExtended;
|
||||
} catch (Exception ex) {
|
||||
StringBuilder errMsgBuilder = new();
|
||||
errMsgBuilder.Append("An exception occurred when attempting to get all TECNs expired in the last day. ");
|
||||
@ -56,18 +56,18 @@ public class ECNService : IECNService {
|
||||
string fiveDaysFromToday = DateTime.Now.AddDays(5).ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
StringBuilder queryBuilder = new StringBuilder();
|
||||
queryBuilder.Append("select ECNNumber, IsTECN, ExpirationDate, ExtensionDate, OriginatorID, Title ");
|
||||
queryBuilder.Append($"from ECN where IsTECN = 1 and ");
|
||||
queryBuilder.Append("select * from ECN ");
|
||||
queryBuilder.Append($"where IsTECN = 1 and ");
|
||||
queryBuilder.Append($"ExpirationDate between '{today}' ");
|
||||
queryBuilder.Append($"and '{fiveDaysFromToday}'");
|
||||
queryBuilder.Append($"and '{fiveDaysFromToday}';");
|
||||
|
||||
IEnumerable<ECN> expiringTecns = (await _dalService.QueryAsync<ECN>(queryBuilder.ToString()))
|
||||
.Where(e => e.ExtensionDate is null || e.ExtensionDate <= DateTime.Now.AddDays(5))
|
||||
IEnumerable<ECN> expiringTecns = (await _dalService.QueryAsync<ECN>(queryBuilder.ToString()));
|
||||
|
||||
IEnumerable<ECN> expiringTecnsNotExtended = expiringTecns
|
||||
.Where(e => e.ExtensionDate <= DateTime.Now.AddDays(5))
|
||||
.ToList();
|
||||
|
||||
_logger.LogInformation($"Found {expiringTecns.Count()} expiring TECNs");
|
||||
|
||||
return expiringTecns;
|
||||
return expiringTecnsNotExtended;
|
||||
} catch (Exception ex) {
|
||||
StringBuilder errMsgBuilder = new();
|
||||
errMsgBuilder.Append("An exception occurred when attempting to get all TECNs expiring in the next five days. ");
|
||||
|
102
FabApprovalWorkerService/Services/TrainingService.cs
Normal file
102
FabApprovalWorkerService/Services/TrainingService.cs
Normal file
@ -0,0 +1,102 @@
|
||||
using FabApprovalWorkerService.Models;
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace FabApprovalWorkerService.Services;
|
||||
|
||||
public interface ITrainingService {
|
||||
Task<IEnumerable<int>> GetTrainingIdsForECN(int ecnNumber);
|
||||
Task DeleteTrainingAssignment(int trainingId);
|
||||
Task<IEnumerable<int>> GetTrainingAssignmentIdsForTraining(int trainingId);
|
||||
Task DeleteDocAssignment(int trainingAssignmentId);
|
||||
}
|
||||
|
||||
public class TrainingService : ITrainingService {
|
||||
private ILogger<TrainingService> _logger;
|
||||
private IDalService _dalService;
|
||||
|
||||
public TrainingService(ILogger<TrainingService> logger, IDalService dalService) {
|
||||
_logger = logger ??
|
||||
throw new ArgumentNullException("ILogger not injected");
|
||||
_dalService = dalService ??
|
||||
throw new ArgumentNullException("IDalService not injected");
|
||||
}
|
||||
|
||||
public async Task DeleteDocAssignment(int trainingAssignmentId) {
|
||||
if (trainingAssignmentId <= 0) throw new ArgumentException($"Invalid training assignment id: {trainingAssignmentId}");
|
||||
|
||||
try {
|
||||
_logger.LogInformation($"Attempting to delete training doc assignments for training assignment {trainingAssignmentId}");
|
||||
|
||||
StringBuilder queryBuilder = new();
|
||||
queryBuilder.Append($"update TrainingDocAcks set Deleted = 1, ");
|
||||
queryBuilder.Append($"DeletedDate = {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} ");
|
||||
queryBuilder.Append($"where TrainingAssignmentID = {trainingAssignmentId} and Reviewed = 0;");
|
||||
|
||||
await _dalService.ExecuteAsync(queryBuilder.ToString());
|
||||
} catch (Exception ex) {
|
||||
StringBuilder errMsgBuilder = new();
|
||||
errMsgBuilder.Append($"An exception occurred when attempting to delete training doc assignment ");
|
||||
errMsgBuilder.Append($"{trainingAssignmentId}. Exception: {ex.Message}");
|
||||
_logger.LogError(errMsgBuilder.ToString());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteTrainingAssignment(int trainingId) {
|
||||
if (trainingId <= 0) throw new ArgumentException($"Invalid training id: {trainingId}");
|
||||
|
||||
try {
|
||||
_logger.LogInformation($"Attempting to delete training assignment {trainingId}");
|
||||
|
||||
StringBuilder queryBuilder = new();
|
||||
queryBuilder.Append($"update TrainingAssignments set Deleted = 1, ");
|
||||
queryBuilder.Append($"DeletedDate = {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} ");
|
||||
queryBuilder.Append($"where TrainingID = {trainingId} and status = 0;");
|
||||
|
||||
await _dalService.ExecuteAsync(queryBuilder.ToString());
|
||||
} catch (Exception ex) {
|
||||
StringBuilder errMsgBuilder = new();
|
||||
errMsgBuilder.Append($"An exception occurred when attempting to delete training assignment ");
|
||||
errMsgBuilder.Append($"{trainingId}. Exception: {ex.Message}");
|
||||
_logger.LogError(errMsgBuilder.ToString());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<int>> GetTrainingAssignmentIdsForTraining(int trainingId) {
|
||||
if (trainingId <= 0) throw new ArgumentException($"Invalid trainingID: {trainingId}");
|
||||
|
||||
try {
|
||||
_logger.LogInformation($"Attempting to get training assignment ids for training id {trainingId}");
|
||||
|
||||
string sql = $"select ID from TrainingAssignments where TrainingID = {trainingId};";
|
||||
|
||||
return await _dalService.QueryAsync<int>(sql);
|
||||
} catch (Exception ex) {
|
||||
StringBuilder errMsgBuilder = new();
|
||||
errMsgBuilder.Append($"An exception occurred when attempting to get training assignment ids ");
|
||||
errMsgBuilder.Append($"for training id {trainingId}. Exception: {ex.Message}");
|
||||
_logger.LogError(errMsgBuilder.ToString());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<int>> GetTrainingIdsForECN(int ecnNumber) {
|
||||
if (ecnNumber <= 0) throw new ArgumentException($"Invalid ecnNumber: {ecnNumber}");
|
||||
|
||||
try {
|
||||
_logger.LogInformation($"Attempting to get training ids for ecn {ecnNumber}");
|
||||
|
||||
string sql = $"select TrainingID from Training where ECN = {ecnNumber};";
|
||||
|
||||
return await _dalService.QueryAsync<int>(sql);
|
||||
} catch (Exception ex) {
|
||||
StringBuilder errMsgBuilder = new();
|
||||
errMsgBuilder.Append($"An exception occurred when attempting to get training ids ");
|
||||
errMsgBuilder.Append($"for ECN {ecnNumber}. Exception: {ex.Message}");
|
||||
_logger.LogError(errMsgBuilder.ToString());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
40
FabApprovalWorkerService/Services/WindowsService.cs
Normal file
40
FabApprovalWorkerService/Services/WindowsService.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using FabApprovalWorkerService.Models;
|
||||
|
||||
namespace FabApprovalWorkerService.Services;
|
||||
|
||||
public class WindowsService : BackgroundService {
|
||||
private readonly ILogger<WindowsService> _logger;
|
||||
private readonly IMonInWorkerClient _monInClient;
|
||||
|
||||
public WindowsService(ILogger<WindowsService> logger,
|
||||
IServiceProvider serviceProvider) {
|
||||
_logger = logger ??
|
||||
throw new ArgumentNullException("ILogger not injected");
|
||||
using (IServiceScope scope = serviceProvider.CreateScope()) {
|
||||
_monInClient = scope.ServiceProvider.GetService<IMonInWorkerClient>() ??
|
||||
throw new ArgumentNullException("IMonInWorkerClient not injected");
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
|
||||
_logger.LogInformation("Starting Windows service");
|
||||
|
||||
try {
|
||||
while (!stoppingToken.IsCancellationRequested) {
|
||||
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
|
||||
|
||||
_monInClient.PostStatus("WindowsService", StatusValue.Ok);
|
||||
}
|
||||
} catch (OperationCanceledException) {
|
||||
_logger.LogError("The Windows service has been stopped");
|
||||
|
||||
_monInClient.PostStatus("WindowsService", StatusValue.Critical);
|
||||
} catch (Exception ex) {
|
||||
_logger.LogError($"An exception occurred when running Windows Service. Exception: {ex.Message}");
|
||||
|
||||
_monInClient.PostStatus("WindowsService", StatusValue.Critical);
|
||||
|
||||
Environment.Exit(1);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +1,16 @@
|
||||
drop table if exists ECN;
|
||||
|
||||
create table ECN (
|
||||
ECNID integer primary key,
|
||||
ECNNumber integer not null,
|
||||
ECNNumber integer primary key not null,
|
||||
IsTECN integer default 0,
|
||||
ExpirationDate text not null,
|
||||
ExpirationDate text,
|
||||
ExtensionDate text,
|
||||
OriginatorID integer not null,
|
||||
Title text not null
|
||||
);
|
||||
|
||||
insert into ECN (ECNNumber, IsTECN, ExpirationDate, ExtensionDate, OriginatorID, Title)
|
||||
values (1, 0, '2024-03-30 00:00:00', '', 1, 'title1'),
|
||||
(2, 1, '2024-04-01 00:00:00', '', 6, 'title2'),
|
||||
(3, 1, '2024-06-01 00:00:00', '', 4, 'title3'),
|
||||
(4, 1, '2024-04-01 00:00:00', '2024-06-01 00:00:00', 3, 'title4')
|
||||
values (1, 0, '2024-04-06 00:00:00', null, 1, 'title1'),
|
||||
(2, 1, '2024-04-04 00:00:00', null, 6, 'title2'),
|
||||
(3, 1, '2024-06-01 00:00:00', null, 4, 'title3'),
|
||||
(4, 1, '2024-04-03 00:00:00', '2024-06-01 00:00:00', 3, 'title4')
|
@ -1,9 +1,9 @@
|
||||
drop table if exists TECNNotificationUsers;
|
||||
drop table if exists TECNNotificationsUsers;
|
||||
|
||||
create table TECNNotificationUsers (
|
||||
create table TECNNotificationsUsers (
|
||||
Id integer primary key,
|
||||
UserId integer not null
|
||||
);
|
||||
|
||||
insert into TECNNotificationUsers (UserId)
|
||||
insert into TECNNotificationsUsers (UserId)
|
||||
values (1), (2), (3);
|
@ -43,6 +43,8 @@ public class ExpiringTECNWorker : IJob {
|
||||
|
||||
IEnumerable<ECN> expiringTECNs = await _ecnService.GetExpiringTECNs();
|
||||
|
||||
_logger.LogInformation($"There are {expiringTECNs.Count()} TECNs expiring in the next 5 days");
|
||||
|
||||
foreach (ECN eCN in expiringTECNs) {
|
||||
string recipientEmail = await _userService.GetUserEmail(eCN.OriginatorID);
|
||||
MailAddress recipientAddress = new MailAddress(recipientEmail);
|
||||
@ -55,9 +57,8 @@ public class ExpiringTECNWorker : IJob {
|
||||
}
|
||||
|
||||
StringBuilder bodyBuilder = new();
|
||||
bodyBuilder.Append($"Good day, TECN# {eCN.ECNNumber} will be expiring in ");
|
||||
bodyBuilder.Append($"{(eCN.ExpirationDate - DateTime.Now).Days} ");
|
||||
bodyBuilder.Append($"on {eCN.ExpirationDate.ToString("MMMM dd, yyyy")}. ");
|
||||
bodyBuilder.Append($"Good day, TECN# {eCN.ECNNumber} will be expire on ");
|
||||
bodyBuilder.Append($"{eCN.ExpirationDate.ToString("MMMM dd, yyyy")}. ");
|
||||
bodyBuilder.Append($"<br /> Review TECN <a href='https://mesaapproval.mes.com/ECN/Edit?IssueID={eCN.ECNNumber}'> ");
|
||||
bodyBuilder.Append("here </a>");
|
||||
|
||||
|
@ -112,6 +112,9 @@ internal class SmtpServiceTests {
|
||||
|
||||
Assert.True(await _smtpService.SendEmail(ADDRESS_LIST, ADDRESS_LIST, "subject", "body"));
|
||||
|
||||
_mockSmtpClient.Verify(s => s.Send(It.IsAny<MailMessage>()));
|
||||
string? env = Environment.GetEnvironmentVariable("FabApprovalEnvironmentName");
|
||||
|
||||
if (env is not null && !env.ToLower().Equals("development"))
|
||||
_mockSmtpClient.Verify(s => s.Send(It.IsAny<MailMessage>()));
|
||||
}
|
||||
}
|
||||
|
29
FabApprovalWorkerServiceTests/TrainingServiceTests.cs
Normal file
29
FabApprovalWorkerServiceTests/TrainingServiceTests.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using FabApprovalWorkerService.Services;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Moq;
|
||||
|
||||
namespace FabApprovalWorkerServiceTests;
|
||||
public class TrainingServiceTests {
|
||||
private Mock<ILogger<TrainingService>> _mockLogger;
|
||||
private Mock<IDalService> _mockDalService;
|
||||
|
||||
private TrainingService _trainingService;
|
||||
|
||||
[SetUp]
|
||||
public void Setup() {
|
||||
_mockLogger = new Mock<ILogger<TrainingService>>();
|
||||
_mockDalService = new Mock<IDalService>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TrainingServiceWithNullLoggerShouldThrowException() {
|
||||
Assert.Throws<ArgumentNullException>(() => new TrainingService(null, _mockDalService.Object));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TrainingServiceWithNullDalServiceShouldThrowException() {
|
||||
Assert.Throws<ArgumentNullException>(() => new TrainingService(_mockLogger.Object, null));
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user