diff --git a/LCU.API.Internal.MaidBotApi/.dockerignore b/LCU.API.Internal.MaidBotApi/.dockerignore
new file mode 100644
index 0000000..186c6b6
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/.dockerignore
@@ -0,0 +1,17 @@
+**/*.bat
+**/*.log
+**/*.md
+**/*.yml
+**/.dockerignore
+**/.editorconfig
+**/.git
+**/.gitattributes
+**/.gitignore
+**/.vs
+**/.vscode
+**/bin
+**/dist
+**/dockerfile
+**/node_modules
+**/obj
+**/package-lock.json
diff --git a/LCU.API.Internal.MaidBotApi/.gitignore b/LCU.API.Internal.MaidBotApi/.gitignore
new file mode 100644
index 0000000..3e759b7
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/.gitignore
@@ -0,0 +1,330 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+**/Properties/launchSettings.json
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
diff --git a/LCU.API.Internal.MaidBotApi/Dockerfile b/LCU.API.Internal.MaidBotApi/Dockerfile
new file mode 100644
index 0000000..dffcf56
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/Dockerfile
@@ -0,0 +1,49 @@
+# .NET Core SDK
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+WORKDIR /src
+
+# Copy Projects
+COPY LCU.Api.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi.csproj LCU.Api.Internal.MaidBotApi/
+COPY LCU.Api.Internal.MaidBotLib/LCU.Api.Internal.MaidBotLib.csproj LCU.Api.Internal.MaidBotLib/
+
+# Configure NuGet auth for the Azure DevOps feed during restore.
+ARG PAT=bh2lq3jibktw67b2wdbqyfln7cd3je6lk52kijntpj43v6kimpgq
+RUN mkdir -p /root/.nuget/NuGet && \
+ printf '%s\n' \
+ '' \
+ '' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' ' \
+ '' \
+ > /root/.nuget/NuGet/NuGet.Config
+RUN dotnet restore LCU.Api.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi.csproj
+
+# Copy All Files
+COPY . .
+
+FROM build AS test
+LABEL test=true
+RUN dotnet test -c Release --results-directory /testresults --logger "trx;LogFileName=test_results.trx" LCU.Api.Internal.MaidBotTests/LCU.Api.Internal.MaidBotTests.csproj
+
+# .NET Core Publish
+FROM build AS publish
+RUN dotnet publish LCU.Api.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi.csproj -c Release -o /app/publish
+
+# ASP.NET Core
+FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine3.20
+RUN apk add --no-cache icu-libs && apk add --no-cache tzdata;
+ENV TZ America/Chicago
+ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
+ENV ASPNETCORE_ENVIRONMENT=Development
+
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "LCU.Api.Internal.MaidBotApi.dll"]
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBot.sln b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBot.sln
new file mode 100644
index 0000000..5eaf0b0
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBot.sln
@@ -0,0 +1,54 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29905.134
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.Api.Internal.MaidBotApi", "LCU.API.Internal.MaidBotApi\LCU.Api.Internal.MaidBotApi.csproj", "{1C92A326-91A3-4904-B3EF-B766E10E7191}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.Api.Internal.MaidBotTests", "LCU.Api.Internal.MaidBotTests\LCU.Api.Internal.MaidBotTests.csproj", "{98503299-44F3-410A-B61F-36BC1706F132}"
+ ProjectSection(ProjectDependencies) = postProject
+ {1C92A326-91A3-4904-B3EF-B766E10E7191} = {1C92A326-91A3-4904-B3EF-B766E10E7191}
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.Api.Internal.MaidBotSeqApp", "LCU.Api.Internal.MaidBotSeqApp\LCU.Api.Internal.MaidBotSeqApp.csproj", "{4E8E9B2F-69AD-4326-BD7E-DC53372C2092}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.Api.Internal.MaidBotLib", "LCU.Api.Internal.MaidBotLib\LCU.Api.Internal.MaidBotLib.csproj", "{6FE57F07-EFA9-4769-AE61-95C72787AA9D}"
+EndProject
+Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "LCU.Api.Internal.MaidBotSql", "LCU.Api.Internal.MaidBotSql\LCU.Api.Internal.MaidBotSql.sqlproj", "{ECCE6677-A2F2-4694-964F-F91EBE7AAC00}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1C92A326-91A3-4904-B3EF-B766E10E7191}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1C92A326-91A3-4904-B3EF-B766E10E7191}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1C92A326-91A3-4904-B3EF-B766E10E7191}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1C92A326-91A3-4904-B3EF-B766E10E7191}.Release|Any CPU.Build.0 = Release|Any CPU
+ {98503299-44F3-410A-B61F-36BC1706F132}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {98503299-44F3-410A-B61F-36BC1706F132}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {98503299-44F3-410A-B61F-36BC1706F132}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {98503299-44F3-410A-B61F-36BC1706F132}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4E8E9B2F-69AD-4326-BD7E-DC53372C2092}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4E8E9B2F-69AD-4326-BD7E-DC53372C2092}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4E8E9B2F-69AD-4326-BD7E-DC53372C2092}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4E8E9B2F-69AD-4326-BD7E-DC53372C2092}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6FE57F07-EFA9-4769-AE61-95C72787AA9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6FE57F07-EFA9-4769-AE61-95C72787AA9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6FE57F07-EFA9-4769-AE61-95C72787AA9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6FE57F07-EFA9-4769-AE61-95C72787AA9D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ECCE6677-A2F2-4694-964F-F91EBE7AAC00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ECCE6677-A2F2-4694-964F-F91EBE7AAC00}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ECCE6677-A2F2-4694-964F-F91EBE7AAC00}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+ {ECCE6677-A2F2-4694-964F-F91EBE7AAC00}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ECCE6677-A2F2-4694-964F-F91EBE7AAC00}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ECCE6677-A2F2-4694-964F-F91EBE7AAC00}.Release|Any CPU.Deploy.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {56A51B7E-A90A-4BFF-85D1-19ECB769AF6A}
+ EndGlobalSection
+EndGlobal
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Configurations/ApiConfiguration.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Configurations/ApiConfiguration.cs
new file mode 100644
index 0000000..af497f8
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Configurations/ApiConfiguration.cs
@@ -0,0 +1,14 @@
+namespace LCU.Api.Internal.MaidBotApi.Configurations;
+
+public class ApiConfiguration
+{
+ public string AppName { get; set; }
+ public string SeqUrl { get; set; }
+ public string SeqApiKey { get; set; }
+ public string NotificationsApiEmail { get; set; }
+ public string BatchSchedulerCron { get; set; }
+ public FeatureFlags FeatureFlags { get; set; }
+ public string[] CorsAllowedOrigins { get; set; }
+ public string LogEmailTemplate { get; set; }
+ public string LogEmailRowTemplate { get; set; }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Configurations/FeatureFlags.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Configurations/FeatureFlags.cs
new file mode 100644
index 0000000..0279019
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Configurations/FeatureFlags.cs
@@ -0,0 +1,7 @@
+namespace LCU.Api.Internal.MaidBotApi.Configurations;
+
+public class FeatureFlags
+{
+ public bool LoggingEventConsumer { get; set; }
+ public bool RunScheduler { get; set; }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Consumers/LoggingEventConsumer.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Consumers/LoggingEventConsumer.cs
new file mode 100644
index 0000000..bc62007
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Consumers/LoggingEventConsumer.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using MassTransit;
+using Serilog;
+using LCU.Library.Internal.Exceptions;
+using LCU.Api.Internal.MaidBotLib.Models;
+using LCU.Api.Internal.MaidBotApi.Services;
+namespace LCU.Api.Internal.MaidBotApi.Consumers;
+
+public class LoggingEventConsumer : IConsumer
+{
+ private readonly IServiceScopeFactory _scopeFactory;
+
+ public LoggingEventConsumer(IServiceScopeFactory scopeFactory)
+ {
+ _scopeFactory = scopeFactory;
+ }
+
+ public async Task Consume(ConsumeContext context)
+ {
+ try
+ {
+ using var scope = _scopeFactory.CreateScope();
+ await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().HandleIncomingEventLog(context.Message));
+ }
+ catch (ServiceException serviceException)
+ {
+ Log.Fatal(serviceException, $"Handle Incoming EventLog Failure. " +
+ $"The Handle Incoming EventLog is placed on LCU.MaidBot_error.");
+ throw;
+ }
+ catch (Exception exception)
+ {
+ Log.Fatal(exception, $"Unexpected Handle Incoming EventLog Failure. " +
+ $"The Handle Incoming EventLog is placed on LCU.MaidBot_error.");
+ throw;
+ }
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Controllers/EventLogController.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Controllers/EventLogController.cs
new file mode 100644
index 0000000..e97d563
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Controllers/EventLogController.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Threading.Tasks;
+using System.Linq;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Serilog;
+using LCU.Api.Internal.MaidBotApi.Services;
+using LCU.Api.Internal.MaidBotLib.Models;
+using LCU.Library.Internal.Exceptions;
+namespace LCU.API.Internal.MaidBotApi.Controllers;
+
+[ApiController]
+[Route("api/[controller]")]
+
+public class EventLogController : ControllerBase
+{
+ private readonly IEventLogService _eventLogService;
+
+ public EventLogController(IEventLogService eventLogService)
+ {
+ _eventLogService = eventLogService;
+ }
+
+ [HttpGet]
+ [ProducesResponseType(200)]
+ [ProducesResponseType(204)]
+ [ProducesResponseType(typeof(EventLog), 400)]
+ [ProducesResponseType(500)]
+ public async Task Get()
+ {
+ try
+ {
+ var results = await _eventLogService.GetAsync();
+ if (results == null || !results.Any())
+ {
+ return NoContent();
+ }
+
+ return Ok(results);
+ }
+ catch (ServiceException exception)
+ {
+ return StatusCode(StatusCodes.Status500InternalServerError, exception.Message);
+ }
+ catch (Exception exception)
+ {
+ Log.Error(exception, "Unexpected Exception");
+ return StatusCode(StatusCodes.Status500InternalServerError);
+ }
+ }
+
+ [HttpDelete()]
+ [ProducesResponseType(200)]
+ [ProducesResponseType(500)]
+ public IActionResult DeleteMany([FromBody] List eventLogs)
+ {
+ try
+ {
+ _eventLogService.DeleteMany(eventLogs);
+ return Ok();
+ }
+ catch (ServiceException exception)
+ {
+ return StatusCode(StatusCodes.Status500InternalServerError, exception.Message);
+ }
+ catch (Exception exception)
+ {
+ Log.Error(exception, "Unexpected Exception");
+ return StatusCode(StatusCodes.Status500InternalServerError);
+ }
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Controllers/LoggingMatrixController.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Controllers/LoggingMatrixController.cs
new file mode 100644
index 0000000..ce4023f
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Controllers/LoggingMatrixController.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Serilog;
+using LCU.Api.Internal.MaidBotApi.Exceptions;
+using LCU.Api.Internal.MaidBotApi.Services;
+using LCU.Api.Internal.MaidBotLib.Models;
+using LCU.Library.Internal.Exceptions;
+namespace LCU.API.Internal.MaidBotApi.Controllers;
+
+[ApiController]
+[Route("api/[controller]")]
+
+public class LoggingMatrixController : ControllerBase
+{
+ private readonly ILoggingMatrixService _loggingMatrixService;
+
+ public LoggingMatrixController(ILoggingMatrixService loggingMatrixService)
+ {
+ _loggingMatrixService = loggingMatrixService;
+ }
+
+ [HttpGet(Name = "GetAllLoggingMatrixes")]
+ [ProducesResponseType(typeof(IEnumerable), 200)]
+ [ProducesResponseType(500)]
+ public async Task Get()
+ {
+ try
+ {
+ var result = await _loggingMatrixService.GetAsync();
+ return Ok(result);
+ }
+ catch (ServiceException exception)
+ {
+ return StatusCode(StatusCodes.Status500InternalServerError, exception.Message);
+ }
+ catch (Exception exception)
+ {
+ Log.Error(exception, "Unexpected Exception");
+ return StatusCode(StatusCodes.Status500InternalServerError);
+ }
+ }
+
+ [HttpPost]
+ [ProducesResponseType(typeof(LoggingMatrix), 201)]
+ [ProducesResponseType(typeof(List), 400)]
+ [ProducesResponseType(500)]
+ public async Task Post([FromBody] LoggingMatrix loggingMatrix)
+ {
+ try
+ {
+ var result = await _loggingMatrixService.SaveAsync(loggingMatrix);
+
+ if (result == null)
+ {
+ Log.Error($"Result is null could not create Logging Matrix");
+ return StatusCode((StatusCodes.Status400BadRequest), "Could not create Logging Matrix.");
+ }
+
+ return StatusCode(StatusCodes.Status201Created, result);
+ }
+ catch (ServiceException exception)
+ {
+ return StatusCode(StatusCodes.Status500InternalServerError, exception.Message);
+ }
+ catch (Exception exception)
+ {
+ Log.Error(exception, "Unexpected Exception");
+ return StatusCode(StatusCodes.Status500InternalServerError);
+ }
+ }
+
+ [HttpPut]
+ [ProducesResponseType(typeof(LoggingMatrix), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(404)]
+ [ProducesResponseType(500)]
+ public async Task Put([FromBody] LoggingMatrix loggingMatrix)
+ {
+ try
+ {
+ if (loggingMatrix.LoggingMatrixId <= 0)
+ {
+ return BadRequest("Invalid ID");
+ }
+
+ var result = await _loggingMatrixService.UpdateAsync(loggingMatrix);
+
+ if(result == null)
+ {
+ return NotFound($"The LoggingMatrix with LoggingMatrixId: {loggingMatrix.LoggingMatrixId} " +
+ "could not be found");
+ }
+
+ return Ok(result);
+ }
+ catch (ServiceException exception)
+ {
+ return StatusCode(StatusCodes.Status500InternalServerError, exception.Message);
+ }
+ catch (Exception exception)
+ {
+ Log.Error(exception, "Unexpected Exception");
+ return StatusCode(StatusCodes.Status500InternalServerError);
+ }
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Controllers/QueueEventLogController.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Controllers/QueueEventLogController.cs
new file mode 100644
index 0000000..a425bda
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Controllers/QueueEventLogController.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Serilog;
+using LCU.Api.Internal.MaidBotApi.Services;
+using LCU.Api.Internal.MaidBotLib.Models;
+using LCU.Library.Internal.Exceptions;
+namespace LCU.API.Internal.MaidBotApi.Controllers;
+
+[ApiController]
+[Route("api/[controller]")]
+
+public class QueueEventLogController : ControllerBase
+{
+ private readonly IQueueEventLogService _queueEventLogService;
+
+ public QueueEventLogController(IQueueEventLogService queueEventLogService)
+ {
+ _queueEventLogService = queueEventLogService;
+ }
+
+ [HttpPost]
+ [ProducesResponseType(202)]
+ [ProducesResponseType(typeof(List), 400)]
+ [ProducesResponseType(500)]
+ public async Task Post([FromBody] EventLog EventLog)
+ {
+ try
+ {
+ await _queueEventLogService.QueueEventLog(EventLog);
+ return Accepted();
+ }
+ catch (ServiceException exception)
+ {
+ return StatusCode(StatusCodes.Status500InternalServerError, exception.Message);
+ }
+ catch (Exception exception)
+ {
+ Log.Error(exception, "Unexpected Exception");
+ return StatusCode(StatusCodes.Status500InternalServerError);
+ }
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Controllers/StatusController.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Controllers/StatusController.cs
new file mode 100644
index 0000000..89d71ad
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Controllers/StatusController.cs
@@ -0,0 +1,34 @@
+using System;
+using Microsoft.AspNetCore.Mvc;
+using Serilog;
+using LCU.Api.Internal.MaidBotApi.EnvironmentView;
+namespace LCU.API.Internal.MaidBotApi.Controllers;
+
+[ApiController]
+[Route("api/[controller]")]
+
+public class StatusController : ControllerBase
+{
+ private readonly IEnvironmentView _environmentView;
+
+ public StatusController(IEnvironmentView environmentView)
+ {
+ _environmentView = environmentView;
+ }
+
+ [HttpGet()]
+ [ProducesResponseType(200)]
+ [ProducesResponseType(500)]
+ public IActionResult Get()
+ {
+ try
+ {
+ return Ok(_environmentView);
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Failure to return status");
+ return StatusCode((int)System.Net.HttpStatusCode.InternalServerError);
+ }
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/DataAccesses/INotificationsApiDao.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/DataAccesses/INotificationsApiDao.cs
new file mode 100644
index 0000000..596127a
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/DataAccesses/INotificationsApiDao.cs
@@ -0,0 +1,8 @@
+using System.Threading.Tasks;
+using LCU.Shared.Common.NotificationApiLib.Models;
+namespace LCU.Api.Internal.MaidBotApi.DataAccesses;
+
+public interface INotificationsApiDao
+{
+ Task SendEmailRequest(EmailRequest emailRequest);
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/DataAccesses/NotificationsApiDao.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/DataAccesses/NotificationsApiDao.cs
new file mode 100644
index 0000000..e37352b
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/DataAccesses/NotificationsApiDao.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Threading.Tasks;
+using LCU.Api.Internal.MaidBotApi.Configurations;
+using LCU.Library.Internal.LcuHttpClient.HttpRequest;
+using LCU.Shared.Common.NotificationApiLib.Models;
+namespace LCU.Api.Internal.MaidBotApi.DataAccesses;
+
+public class NotificationsApiDao : INotificationsApiDao
+{
+ private readonly ApiConfiguration _apiConfiguration;
+ private readonly IPostRequest _postRequest;
+
+ public NotificationsApiDao(ApiConfiguration apiConfiguration, IPostRequest postRequest)
+ {
+ _apiConfiguration = apiConfiguration;
+ _postRequest = postRequest;
+ }
+
+ public async Task SendEmailRequest(EmailRequest emailRequest)
+ {
+ await _postRequest.Async(new Uri(_apiConfiguration.NotificationsApiEmail), emailRequest);
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/EnvironmentView/EnvironmentView.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/EnvironmentView/EnvironmentView.cs
new file mode 100644
index 0000000..f1ccfd9
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/EnvironmentView/EnvironmentView.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Reflection;
+using Microsoft.AspNetCore.Hosting;
+namespace LCU.Api.Internal.MaidBotApi.EnvironmentView;
+
+public class EnvironmentView : IEnvironmentView
+{
+ public string Application => _env.ApplicationName;
+ public DateTime CurrentDate => DateTime.Now;
+ public string BuildVersion => AssembleBuildVersion();
+ public DateTime BuildDate => AssemblyBuildDate();
+ public string Environment => _env.EnvironmentName;
+ private readonly IWebHostEnvironment _env;
+
+ public EnvironmentView(IWebHostEnvironment env)
+ {
+ _env = env;
+ }
+
+ public static DateTime AssemblyBuildDate()
+ {
+ //https://stackoverflow.com/a/804310/266594
+ System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
+ System.IO.FileInfo fileInfo = new System.IO.FileInfo(assembly.Location);
+ DateTime lastModified = fileInfo.LastWriteTime;
+
+ return lastModified;
+ }
+
+ public static string AssembleBuildVersion()
+ {
+ var version = Assembly.GetExecutingAssembly().GetName().Version;
+ return version == null ? "-.-.-" : version.ToString();
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/EnvironmentView/IEnvironmentView.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/EnvironmentView/IEnvironmentView.cs
new file mode 100644
index 0000000..3f9fd9b
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/EnvironmentView/IEnvironmentView.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace LCU.Api.Internal.MaidBotApi.EnvironmentView
+{
+ public interface IEnvironmentView
+ {
+ string Application { get; }
+ DateTime CurrentDate { get; }
+ string BuildVersion { get; }
+ DateTime BuildDate { get; }
+ string Environment { get; }
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Exceptions/NotFoundException.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Exceptions/NotFoundException.cs
new file mode 100644
index 0000000..368148e
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Exceptions/NotFoundException.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace LCU.Api.Internal.MaidBotApi.Exceptions
+{
+ /**
+ * If an entity cannot be found, that specific error can allow for a 404 or 400 with more detail to be
+ * returned. So specify that to the API layer.
+ */
+ public class NotFoundException : ApplicationException
+ {
+ public NotFoundException(string message) : base(message)
+ {
+ }
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Exceptions/ServerErrorException.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Exceptions/ServerErrorException.cs
new file mode 100644
index 0000000..46427f4
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Exceptions/ServerErrorException.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace LCU.Api.Internal.MaidBotApi.Exceptions
+{
+ /**
+ * Do not want to expose database and related exceptions to the controllers / web layer.
+ * The lower layers log what happened for developers, then return a general error; unless
+ * a more specific error can be returned in a way to allow the client to act (such as NotFound).
+ */
+ public class ServerErrorException : Exception
+ {
+ public ServerErrorException() : base("A server error occured, and a response could not be produced.") { }
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/HostedService/SchedulerHostedService.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/HostedService/SchedulerHostedService.cs
new file mode 100644
index 0000000..7d4125f
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/HostedService/SchedulerHostedService.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Serilog;
+using NCrontab;
+using LCU.Api.Internal.MaidBotApi.Services;
+using LCU.Api.Internal.MaidBotApi.Configurations;
+namespace LCU.API.Internal.MaidBotApi.HostedService;
+
+public class SchedulerHostedService : BackgroundService
+{
+ private readonly ApiConfiguration _apiConfiguration;
+ private readonly IServiceScopeFactory _scopeFactory;
+
+ private readonly CrontabSchedule _schedule;
+ private DateTime _nextScheduleRun;
+ public const string DefaultScheduleCronPattern = "*/5 * * * *";
+
+ private CancellationToken _cancellationToken;
+
+ public SchedulerHostedService(IServiceScopeFactory scopeFactory,
+ ApiConfiguration apiConfiguration)
+ {
+ _scopeFactory = scopeFactory;
+ _apiConfiguration = apiConfiguration;
+
+ var cron = (_apiConfiguration?.BatchSchedulerCron) ?? DefaultScheduleCronPattern;
+ _schedule = CrontabSchedule.Parse(cron);
+ _nextScheduleRun = _schedule.GetNextOccurrence(DateTime.Now);
+
+ _cancellationToken = new CancellationToken();
+ }
+
+ protected override async Task ExecuteAsync(CancellationToken cancellationToken)
+ {
+ Log.Information("MaidBot Scheduler Hosted Service is starting.");
+ _cancellationToken = cancellationToken;
+ try
+ {
+ do
+ {
+ if (_apiConfiguration.FeatureFlags.RunScheduler && DateTime.Now > _nextScheduleRun)
+ {
+ using var scope = _scopeFactory.CreateScope();
+ await Task.Run(async () => await scope.ServiceProvider.GetRequiredService().HandleLogDistribution());
+
+ _nextScheduleRun = _schedule.GetNextOccurrence(DateTime.Now);
+ }
+
+ await Task.Delay(5000, cancellationToken); //5 seconds delay
+ } while (!cancellationToken.IsCancellationRequested);
+ }
+ catch (TaskCanceledException taskCanceledException) when (_cancellationToken.IsCancellationRequested)
+ {
+ Log.Information(taskCanceledException, "MaidBot Scheduler Hosted Service was shut down normally.");
+ }
+ catch (Exception exception)
+ {
+ Log.Error(exception, "Error trying to startup main thread for MaidBot Scheduler Hosted Service");
+ }
+
+ Log.Information("MaidBot Scheduler Hosted Service is stopping.");
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi.csproj b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi.csproj
new file mode 100644
index 0000000..a075066
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi.csproj
@@ -0,0 +1,47 @@
+
+
+
+ net8.0
+ InProcess
+ 1.0.0
+ 1.0.0
+
+
+
+ ..\LCU.API.Internal.MaidBotApi\LCU.Api.Internal.MaidBotApi.xml
+ 1701;1702;1591
+
+
+
+ ..\LCU.API.Internal.MaidBotApi\LCU.Api.Internal.MaidBotApi.xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+ Always
+
+
+
+
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi.xml b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi.xml
new file mode 100644
index 0000000..d9c5851
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi.xml
@@ -0,0 +1,17 @@
+
+
+
+ LCU.Api.Internal.MaidBotApi
+
+
+
+ If an entity cannot be found, that specific error can allow for a 404 or 400 with more detail to be
+ returned. So specify that to the API layer.
+
+
+ Do not want to expose database and related exceptions to the controllers / web layer.
+ The lower layers log what happened for developers, then return a general error; unless
+ a more specific error can be returned in a way to allow the client to act (such as NotFound).
+
+
+
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Program.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Program.cs
new file mode 100644
index 0000000..1156807
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Program.cs
@@ -0,0 +1,207 @@
+using System;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.OpenApi.Models;
+using Microsoft.Extensions.Hosting;
+using Newtonsoft.Json;
+using MassTransit;
+using Serilog;
+using LCU.API.Internal.MaidBotApi.HostedService;
+using LCU.Api.Internal.MaidBotApi.Services;
+using LCU.Api.Internal.MaidBotApi.EnvironmentView;
+using LCU.Api.Internal.MaidBotLib.Repositories;
+using LCU.Api.Internal.MaidBotLib.Persistence;
+using LCU.Library.Internal.LcuMassTransitModels;
+using LCU.Library.Internal.LcuMassTransit;
+using LCU.Api.Internal.MaidBotApi.Consumers;
+using LCU.Api.Internal.MaidBotApi.Configurations;
+using LCU.Library.Internal.Logging.Models;
+using LCU.Library.Internal.Logging;
+using LCU.Library.Internal.Logging.Middleware;
+using LCU.Library.Internal.LcuHttpClient;
+using LCU.Api.Internal.MaidBotApi.DataAccesses;
+namespace LCU.API.Internal.MaidBot;
+
+public class Program
+{
+ static string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
+
+ public static void Main(string[] args)
+ {
+ var builder = BuildBuilder(args);
+ BuildServicesAndConfigurations(builder);
+ BuildAppAndRun(builder);
+ }
+
+ private static WebApplicationBuilder BuildBuilder(string[] args)
+ {
+ var builder = WebApplication.CreateBuilder(args);
+ builder.Host.UseSerilog();
+ builder.Configuration.AddJsonFile("/app/config/environment-appsettings/appsettings.json", optional: true, reloadOnChange: true);
+ builder.Configuration.AddJsonFile("/app/config/workload-appsettings/appsettings.json", optional: true, reloadOnChange: true);
+ builder.Configuration.AddJsonFile("/vault/secrets/maidbot-api-seq-api-key.json", optional: true, reloadOnChange: true);
+ builder.Configuration.AddJsonFile("/vault/secrets/maidbot-api-connection-configuration-password.json", optional: true, reloadOnChange: true);
+ builder.Configuration.AddJsonFile("/vault/secrets/maidbot-api-connection-configuration-username.json", optional: true, reloadOnChange: true);
+ builder.Configuration.AddJsonFile("/vault/secrets/maidbot-database-connection.json", optional: true, reloadOnChange: true);
+ return builder;
+ }
+
+ static void BuildServicesAndConfigurations(WebApplicationBuilder builder)
+ {
+ var services = builder.Services;
+ var configuration = builder.Configuration;
+ services.AddControllers();
+ // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+ services.AddEndpointsApiExplorer();
+ services.AddSwaggerGen();
+
+ services.AddDbContext(options =>
+ {
+ options.UseSqlServer(configuration.GetSection("Connections").GetSection("SQL").GetSection("MaidBotDB").Value);
+ });
+
+ services.AddHttpClient();
+ services.AddLCUHttpServices();
+
+ //Configurations
+ var apiConfiguration = new ApiConfiguration();
+ configuration.GetSection("Primary").Bind(apiConfiguration);
+ services.AddSingleton(apiConfiguration);
+ //debugging
+ Console.WriteLine(JsonConvert.SerializeObject(apiConfiguration));
+
+ var connectionConfiguration = MassTransitConfigurationSetUp.AddConnectionConfiguration(ref services, configuration);
+ //debugging
+ Console.WriteLine(JsonConvert.SerializeObject(connectionConfiguration));
+
+ var receiveEndpointConfiguration = new ReceiveEndpointConfiguration();
+ receiveEndpointConfiguration = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, "ReceiveEndpoint");
+ services.AddSingleton(receiveEndpointConfiguration);
+ //debugging
+ Console.WriteLine(JsonConvert.SerializeObject(receiveEndpointConfiguration));
+
+ //EnvironmentView
+ services.AddScoped();
+
+ var loggerConfiguration = new LoggingConfiguration(apiConfiguration.AppName,
+ apiConfiguration.SeqApiKey,
+ apiConfiguration.SeqUrl);
+ var lcuLogging = new LcuLogging(loggerConfiguration);
+ lcuLogging.UseLcuLogging();
+
+ services.AddCors(options =>
+ {
+ options.AddPolicy(MyAllowSpecificOrigins,
+ builder =>
+ {
+ builder.WithOrigins(
+ apiConfiguration.CorsAllowedOrigins
+ ).AllowAnyHeader().AllowAnyMethod();
+ });
+ });
+
+ AddMaidBotServices(ref services);
+ AddMaidBotLib(ref services);
+ AddMaidBotDataAccesses(ref services);
+ AddMassTransitServices(ref services, connectionConfiguration, receiveEndpointConfiguration, apiConfiguration);
+
+ // The Hosted
+ services.AddHostedService();
+
+ // Register the Swagger generator, defining 1 or more Swagger documents
+ services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = $"{apiConfiguration.AppName}", Version = "v1" }); });
+
+ services.Configure(options =>
+ {
+ options.SuppressModelStateInvalidFilter = true;
+ });
+ }
+
+ static void AddMaidBotServices(ref IServiceCollection services)
+ {
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ }
+
+ static void AddMaidBotLib(ref IServiceCollection services)
+ {
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ }
+
+ static void AddMaidBotDataAccesses(ref IServiceCollection services)
+ {
+ services.AddScoped();
+ }
+
+ static void AddMassTransitServices(ref IServiceCollection services,
+ ConnectionConfiguration connectionConfiguration,
+ ReceiveEndpointConfiguration receiveEndpointConfiguration,
+ ApiConfiguration apiConfiguration)
+ {
+ services.AddMassTransit(x =>
+ {
+ x.AddConsumer();
+
+ x.UsingRabbitMq((context, cfg) =>
+ {
+ cfg.Host(connectionConfiguration.HostUrl, "/", host =>
+ {
+ host.Username(connectionConfiguration.UserName);
+ host.Password(connectionConfiguration.Password);
+ });
+ cfg.UseTimeout(t => t.Timeout = TimeSpan.FromSeconds(420));
+
+ if (apiConfiguration.FeatureFlags.LoggingEventConsumer)
+ {
+ cfg.ReceiveEndpoint(receiveEndpointConfiguration.ReceiveEndpoint, e =>
+ {
+ e.ConfigureConsumer(context);
+ e.SetQuorumQueue();
+ e.PrefetchCount = receiveEndpointConfiguration.PrefetchLimit;
+ e.UseTimeout(t => t.Timeout = TimeSpan.FromSeconds(420));
+ });
+ }
+ });
+ });
+ services.AddMassTransitHostedService();
+ }
+
+ static void BuildAppAndRun(WebApplicationBuilder builder)
+ {
+ var app = builder.Build();
+
+ // Configure the HTTP request pipeline.
+ if (app.Environment.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ }
+
+ app.UseSwagger();
+ app.UseSwaggerUI(c => c.SwaggerEndpoint("v1/swagger.json", "LCU.Api.Internal.MaidBotApi v1"));
+ app.UseMiddleware();
+ app.UseSerilogRequestLogging(options =>
+ {
+ options.EnrichDiagnosticContext = RequestResponseLoggingMiddleware.RequestDataParser.EnrichFromRequest;
+ });
+
+ app.UseRouting();
+ app.UseCors(MyAllowSpecificOrigins);
+ app.UseHttpsRedirection();
+ app.UseAuthorization();
+ app.MapControllers();
+ app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
+
+ app.Run();
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Security/AzureActiveDirectoryOptions.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Security/AzureActiveDirectoryOptions.cs
new file mode 100644
index 0000000..cd17897
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Security/AzureActiveDirectoryOptions.cs
@@ -0,0 +1,13 @@
+
+namespace LCU.API.Internal.MaidBot.Security
+{
+ public class AzureActiveDirectoryOptions
+ {
+ public string ClientId { get; set; }
+ public string ClientSecret { get; set; }
+ public string Instance { get; set; }
+ public string Domain { get; set; }
+ public string TenantId { get; set; }
+ public string ValidAudience { get; set; }
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Security/AzureAdServiceCollectionExtensions.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Security/AzureAdServiceCollectionExtensions.cs
new file mode 100644
index 0000000..28a2232
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Security/AzureAdServiceCollectionExtensions.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+
+namespace LCU.API.Internal.MaidBot.Security
+{
+ public static class AzureAdServiceCollectionExtensions
+ {
+ public static AuthenticationBuilder AddAzureAdBearer(this AuthenticationBuilder builder)
+ => builder.AddAzureAdBearer(_ => { });
+
+ public static AuthenticationBuilder AddAzureAdBearer(this AuthenticationBuilder builder, Action configureOptions)
+ {
+ builder.Services.Configure(configureOptions);
+ builder.Services.AddSingleton, ConfigureAzureOptions>();
+ builder.AddJwtBearer();
+ return builder;
+ }
+
+ private class ConfigureAzureOptions : IConfigureNamedOptions
+ {
+ private readonly AzureActiveDirectoryOptions _azureOptions;
+
+ public ConfigureAzureOptions(IOptions azureOptions)
+ {
+ _azureOptions = azureOptions.Value;
+ }
+
+ public void Configure(string name, JwtBearerOptions options)
+ {
+ options.Audience = _azureOptions.ClientId;
+ options.Authority = $"{_azureOptions.Instance}{_azureOptions.TenantId}";
+
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidAudience = _azureOptions.ValidAudience
+ };
+ }
+
+ public void Configure(JwtBearerOptions options)
+ {
+ Configure(Options.DefaultName, options);
+ }
+ }
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/DebugEventLogService.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/DebugEventLogService.cs
new file mode 100644
index 0000000..1970ac2
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/DebugEventLogService.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Threading.Tasks;
+using Serilog;
+using LCU.Api.Internal.MaidBotApi.Exceptions;
+using LCU.Api.Internal.MaidBotLib.Models;
+using LCU.Api.Internal.MaidBotLib.Repositories;
+using LCU.Library.Internal.Exceptions;
+
+namespace LCU.Api.Internal.MaidBotApi.Services;
+
+public class DebugEventLogService : IDebugEventLogService
+{
+ private readonly IDebugEventLogRepository _debugEventLogRepository;
+ private readonly IUnitOfWork _unitOfWork;
+
+ public DebugEventLogService(IUnitOfWork unitOfWork,
+ IDebugEventLogRepository debugEventLogRepository)
+ {
+ _unitOfWork = unitOfWork;
+ _debugEventLogRepository = debugEventLogRepository;
+ }
+
+ public async Task DebugEventLog(EventLog eventLog)
+ {
+ if (!string.IsNullOrEmpty(eventLog.DebugData))
+ {
+ var debugEventLog = new DebugEventLog(eventLog);
+ await SaveAsync(debugEventLog);
+ }
+ }
+
+ public async Task SaveAsync(DebugEventLog debugEventLog)
+ {
+ try
+ {
+ var result = await _debugEventLogRepository.AddAsync(debugEventLog);
+ await _unitOfWork.CompleteAsync();
+ return result;
+ }
+ catch (Exception exception)
+ {
+ Log.Error(exception, $"Failure saving DebugEventLog.");
+ throw new ServiceException($"Failure saving DebugEventLog.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/EventLogService.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/EventLogService.cs
new file mode 100644
index 0000000..0e9d131
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/EventLogService.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Serilog;
+using Newtonsoft.Json;
+using LCU.Api.Internal.MaidBotLib.Models;
+using LCU.Api.Internal.MaidBotLib.Repositories;
+using LCU.Library.Internal.Exceptions;
+namespace LCU.Api.Internal.MaidBotApi.Services;
+
+public class EventLogService : IEventLogService
+{
+ private readonly IEventLogRepository _eventLogRepository;
+ private readonly IDebugEventLogService _debugEventLogService;
+ private readonly ILoggingMatrixService _loggingMatrixService;
+ private readonly IUnitOfWork _unitOfWork;
+
+ public EventLogService(IEventLogRepository eventLogRepository,
+ IDebugEventLogService debugEventLogService,
+ ILoggingMatrixService loggingMatrixService,
+ IUnitOfWork unitOfWork)
+ {
+ _eventLogRepository = eventLogRepository;
+ _debugEventLogService = debugEventLogService;
+ _loggingMatrixService = loggingMatrixService;
+ _unitOfWork = unitOfWork;
+ }
+
+ public async Task HandleIncomingEventLog(EventLog eventLog)
+ {
+ var loggingMatrixes = await _loggingMatrixService.GetByApplicationLevelAndGroupAsync(eventLog.Application,
+ eventLog.Level,
+ eventLog.Group);
+ if (loggingMatrixes.Any())
+ {
+ await SaveAsync(eventLog);
+ }
+
+ await _debugEventLogService.DebugEventLog(eventLog);
+ }
+
+ public async Task> GetAsync()
+ {
+ try
+ {
+ return await _eventLogRepository.ListAsync();
+ }
+ catch (Exception exception)
+ {
+ var message = "Failure retrieving EventLogs.";
+ Log.Error(exception, message);
+ throw new ServiceException($"{message} Exception Message: {exception.Message}. ");
+ }
+ }
+
+ public async Task SaveAsync(EventLog eventLog)
+ {
+ try
+ {
+ var result = await _eventLogRepository.AddAsync(eventLog);
+ await _unitOfWork.CompleteAsync();
+ return result;
+ }
+ catch (Exception exception)
+ {
+ var message = $"Failure saving EventLog. EventLog: { JsonConvert.SerializeObject(eventLog)}";
+ Log.Error(exception, message);
+ throw new ServiceException($"{message} Exception Message: {exception.Message}.");
+ }
+ }
+
+ public void DeleteMany(List eventLogs)
+ {
+ try
+ {
+ _eventLogRepository.RemoveRange(eventLogs);
+ _unitOfWork.Complete();
+ }
+ catch (Exception exception)
+ {
+ var message = $"Failure to delete EventLogs.";
+ Log.Error(exception, message);
+ throw new ServiceException($"{message} Exception Message: {exception.Message}.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/IDebugEventLogService.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/IDebugEventLogService.cs
new file mode 100644
index 0000000..af9c550
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/IDebugEventLogService.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+using LCU.Api.Internal.MaidBotLib.Models;
+namespace LCU.Api.Internal.MaidBotApi.Services;
+
+public interface IDebugEventLogService
+{
+ Task DebugEventLog(EventLog eventLog);
+ Task SaveAsync(DebugEventLog debugEventLog);
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/IEventLogService.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/IEventLogService.cs
new file mode 100644
index 0000000..f80b0d6
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/IEventLogService.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using LCU.Api.Internal.MaidBotLib.Models;
+namespace LCU.Api.Internal.MaidBotApi.Services;
+
+public interface IEventLogService
+{
+ Task HandleIncomingEventLog(EventLog eventLog);
+ Task> GetAsync();
+ Task SaveAsync(EventLog eventLog);
+ void DeleteMany(List eventLogs);
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/ILogDistributionService.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/ILogDistributionService.cs
new file mode 100644
index 0000000..029d1ad
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/ILogDistributionService.cs
@@ -0,0 +1,7 @@
+using System.Threading.Tasks;
+namespace LCU.Api.Internal.MaidBotApi.Services;
+
+public interface ILogDistributionService
+{
+ Task HandleLogDistribution();
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/ILoggingMatrixService.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/ILoggingMatrixService.cs
new file mode 100644
index 0000000..2a57fcc
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/ILoggingMatrixService.cs
@@ -0,0 +1,17 @@
+using LCU.Api.Internal.MaidBotLib.Models;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace LCU.Api.Internal.MaidBotApi.Services
+{
+ public interface ILoggingMatrixService
+ {
+ Task> GetAsync();
+ Task> GetActiveAsync();
+ Task GetByIdAsync(int id);
+ Task> GetByApplicationLevelAndGroupAsync(string application, string level, string group);
+ Task> GetByApplicationAndLevelAsync(string application, string level);
+ Task SaveAsync(LoggingMatrix loggingMatrix);
+ Task UpdateAsync(LoggingMatrix loggingMatrix);
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/INotificationsApiService.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/INotificationsApiService.cs
new file mode 100644
index 0000000..ce7bf37
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/INotificationsApiService.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using LCU.Api.Internal.MaidBotLib.Models;
+namespace LCU.Api.Internal.MaidBotApi.Services;
+
+public interface INotificationsApiService
+{
+ Task SendNotificationAsync(LoggingMatrix loggingMatrix, List eventLogs);
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/IQueueEventLogService.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/IQueueEventLogService.cs
new file mode 100644
index 0000000..9ec6885
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/IQueueEventLogService.cs
@@ -0,0 +1,8 @@
+using System.Threading.Tasks;
+using LCU.Api.Internal.MaidBotLib.Models;
+namespace LCU.Api.Internal.MaidBotApi.Services;
+
+public interface IQueueEventLogService
+{
+ Task QueueEventLog(EventLog request);
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/LogDistributionService.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/LogDistributionService.cs
new file mode 100644
index 0000000..318b6a8
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/LogDistributionService.cs
@@ -0,0 +1,59 @@
+using Serilog;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+namespace LCU.Api.Internal.MaidBotApi.Services;
+
+public class LogDistributionService : ILogDistributionService
+{
+ private readonly IEventLogService _eventLogService;
+ private readonly ILoggingMatrixService _loggingMatrixService;
+ private readonly INotificationsApiService _notificationsApiService;
+
+ public LogDistributionService(IEventLogService eventLogService,
+ ILoggingMatrixService loggingMatrixService,
+ INotificationsApiService notificationsApiService)
+ {
+ _eventLogService = eventLogService;
+ _loggingMatrixService = loggingMatrixService;
+ _notificationsApiService = notificationsApiService;
+ }
+
+ public async Task HandleLogDistribution()
+ {
+ try
+ {
+ var eventLogs = await _eventLogService.GetAsync();
+ if(eventLogs == null || !eventLogs.Any())
+ {
+ return;
+ }
+
+ var loggingMatrixs = await _loggingMatrixService.GetActiveAsync();
+ if (loggingMatrixs == null || !loggingMatrixs.Any())
+ {
+ return;
+ }
+
+ foreach (var loggingMatrix in loggingMatrixs)
+ {
+ var matrixEventLogs = eventLogs.Where(eventLog => eventLog.Application == loggingMatrix.Application &&
+ eventLog.Level == loggingMatrix.Level &&
+ eventLog.Group == loggingMatrix.Group).ToList();
+
+ if (!matrixEventLogs.Any())
+ {
+ continue;
+ }
+
+ await _notificationsApiService.SendNotificationAsync(loggingMatrix, matrixEventLogs);
+ }
+
+ _eventLogService.DeleteMany(eventLogs.ToList());
+ }
+ catch (Exception exception)
+ {
+ Log.Error(exception, "Failure to Handle Log Distribution.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/LoggingMatrixService.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/LoggingMatrixService.cs
new file mode 100644
index 0000000..9c649af
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/LoggingMatrixService.cs
@@ -0,0 +1,140 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+using Serilog;
+using LCU.Api.Internal.MaidBotLib.Models;
+using LCU.Api.Internal.MaidBotLib.Repositories;
+using LCU.Library.Internal.Exceptions;
+namespace LCU.Api.Internal.MaidBotApi.Services;
+
+public class LoggingMatrixService : ILoggingMatrixService
+{
+ private readonly ILoggingMatrixRepository _loggingMatrixRepository;
+ private readonly IUnitOfWork _unitOfWork;
+
+ public LoggingMatrixService(IUnitOfWork unitOfWork,
+ ILoggingMatrixRepository loggingMatrixRepository)
+ {
+ _unitOfWork = unitOfWork;
+ _loggingMatrixRepository = loggingMatrixRepository;
+ }
+
+ public async Task> GetAsync()
+ {
+ try
+ {
+ return await _loggingMatrixRepository.ListAsync();
+ }
+ catch (Exception exception)
+ {
+ var message = "Failure retrieving Logging Matrixes.";
+ Log.Error(exception, message);
+ throw new ServiceException($"{message} Exception Message: {exception.Message}. ");
+ }
+ }
+
+ public async Task> GetActiveAsync()
+ {
+ try
+ {
+ return await _loggingMatrixRepository.ListActiveAsync();
+ }
+ catch (Exception exception)
+ {
+ var message = "Failure retrieving active Logging Matrixes.";
+ Log.Error(exception, message);
+ throw new ServiceException($"{message} Exception Message: {exception.Message}. ");
+ }
+ }
+
+ public async Task> GetByApplicationLevelAndGroupAsync(string application, string level, string group)
+ {
+ try
+ {
+ return await _loggingMatrixRepository.RetrieveByApplicationLevelAndGroup(application, level, group);
+ }
+ catch (Exception exception)
+ {
+ var message = $"Failure retrieving Logging Matrixes with application {application} and level {level}.";
+ Log.Error(exception, message);
+ throw new ServiceException($"{message} Exception Message: {exception.Message}. ");
+ }
+ }
+
+ public async Task> GetByApplicationAndLevelAsync(string application, string level)
+ {
+ try
+ {
+ return await _loggingMatrixRepository.RetrieveByApplciationAndLevel(application, level);
+ }
+ catch (Exception exception)
+ {
+ var message = $"Failure retrieving Logging Matrixes with application {application} and level {level}.";
+ Log.Error(exception, message);
+ throw new ServiceException($"{message} Exception Message: {exception.Message}. ");
+ }
+ }
+
+ public async Task GetByIdAsync(int id)
+ {
+ LoggingMatrix loggingMatrix = null;
+ try
+ {
+ loggingMatrix = await _loggingMatrixRepository.FindByIdAsync(id);
+ return loggingMatrix;
+ }
+ catch (Exception exception)
+ {
+ var message = $"Failure retrieving Logging Matrix with id {id}.";
+ Log.Error(exception, message);
+ throw new ServiceException($"{message} Exception Message: {exception.Message}.");
+ }
+ }
+
+ public async Task SaveAsync(LoggingMatrix loggingMatrix)
+ {
+ try
+ {
+ var result = await _loggingMatrixRepository.AddAsync(loggingMatrix);
+ await _unitOfWork.CompleteAsync();
+
+ return result;
+ }
+ catch (Exception exception)
+ {
+ var message = $"Failure saving Logging Matrix. LoggingMatrix: { JsonConvert.SerializeObject(loggingMatrix)}";
+ Log.Error(exception, message);
+ throw new ServiceException($"{message} Exception Message: {exception.Message}.");
+ }
+ }
+
+ public async Task UpdateAsync(LoggingMatrix loggingMatrix)
+ {
+ try
+ {
+ var existing = await GetByIdAsync(loggingMatrix.LoggingMatrixId);
+ if (existing == null)
+ {
+ return null;
+ }
+
+ existing.Application = loggingMatrix.Application;
+ existing.Level = loggingMatrix.Level;
+ existing.Group = loggingMatrix.Group;
+ existing.Email = loggingMatrix.Email;
+ existing.Disabled = loggingMatrix.Disabled;
+
+ _loggingMatrixRepository.Update(existing);
+ await _unitOfWork.CompleteAsync();
+
+ return existing;
+ }
+ catch (Exception exception)
+ {
+ var message = $"Failure updating Logging Matrix. LoggingMatrix: { JsonConvert.SerializeObject(loggingMatrix)}";
+ Log.Error(exception, message);
+ throw new ServiceException($"{message} Exception Message: {exception.Message}.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/NotificationsApiService.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/NotificationsApiService.cs
new file mode 100644
index 0000000..abc6286
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/NotificationsApiService.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using System.Text;
+using System.IO;
+using Serilog;
+using LCU.Api.Internal.MaidBotApi.DataAccesses;
+using LCU.Api.Internal.MaidBotLib.Models;
+using LCU.Library.Internal.Exceptions;
+using LCU.Shared.Common.NotificationApiLib.Models;
+using LCU.Api.Internal.MaidBotApi.Configurations;
+namespace LCU.Api.Internal.MaidBotApi.Services;
+
+public class NotificationsApiService : INotificationsApiService
+{
+ private readonly ApiConfiguration _apiConfiguration;
+ private readonly INotificationsApiDao _notificationsApiDao;
+ private const string TableRowsTag = "{TableRows}";
+ private const string ApplicationTag = "{Application}";
+ private const string LevelTag = "{Level}";
+
+ public NotificationsApiService(ApiConfiguration apiConfiguration,
+ INotificationsApiDao notificationsApiDao)
+ {
+ _apiConfiguration = apiConfiguration;
+ _notificationsApiDao = notificationsApiDao;
+ }
+
+ public async Task SendNotificationAsync(LoggingMatrix loggingMatrix, List eventLogs)
+ {
+ try
+ {
+ var emailRequest = new EmailRequest
+ {
+ Recipient = new List { loggingMatrix.Email },
+ Subject = $"{loggingMatrix.Application} - {loggingMatrix.Level}",
+ Body = CreateEmailBody(loggingMatrix, eventLogs)
+ };
+ await _notificationsApiDao.SendEmailRequest(emailRequest);
+ }
+ catch (Exception exception)
+ {
+ var message = $"Failure to Send Maidbot Email.";
+ Log.Error(exception, message);
+ throw new ServiceException($"{message} Exception Message: {exception.Message}");
+ }
+ }
+
+ public string CreateEmailBody(LoggingMatrix loggingMatrix, List eventLogs)
+ {
+ var errorRows = new StringBuilder();
+ foreach(var eventLog in eventLogs)
+ {
+ errorRows.Append(CreateEventLogRow(eventLog));
+ }
+
+ var logEmailTemplateReader = new StreamReader(_apiConfiguration.LogEmailTemplate);
+ string logEmailTemplateOutput = logEmailTemplateReader.ReadToEnd();
+ logEmailTemplateOutput = logEmailTemplateOutput.Replace(TableRowsTag, errorRows.ToString());
+ logEmailTemplateOutput = logEmailTemplateOutput.Replace(ApplicationTag, loggingMatrix.Level);
+ return logEmailTemplateOutput.Replace(LevelTag, loggingMatrix.Application);
+ }
+
+ public string CreateEventLogRow(EventLog eventLog)
+ {
+ var errorRowReader = new StreamReader(_apiConfiguration.LogEmailRowTemplate);
+ string errorRowOutput = errorRowReader.ReadToEnd();
+
+ foreach (var property in eventLog.GetType().GetProperties())
+ {
+ string value = property.GetValue(eventLog) == null ? string.Empty :
+ property.GetValue(eventLog).ToString();
+ errorRowOutput = errorRowOutput.Replace($"{{{property.Name}}}", value);
+ }
+
+ return errorRowOutput;
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/QueueEventLogService.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/QueueEventLogService.cs
new file mode 100644
index 0000000..52fdc8b
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Services/QueueEventLogService.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using MassTransit;
+using Serilog;
+using LCU.Api.Internal.MaidBotLib.Models;
+using LCU.Library.Internal.Exceptions;
+using LCU.Library.Internal.LcuMassTransitModels;
+namespace LCU.Api.Internal.MaidBotApi.Services;
+
+public class QueueEventLogService : IQueueEventLogService
+{
+ private readonly ISendEndpoint _sendEndpoint;
+
+ public QueueEventLogService(IBusControl busControl,
+ ReceiveEndpointConfiguration receiveEndpointConfiguration)
+ {
+ _sendEndpoint = busControl.GetSendEndpoint(receiveEndpointConfiguration.ReceiveEndpointUri).Result;
+ }
+
+ public async Task QueueEventLog(EventLog request)
+ {
+ try
+ {
+ var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
+ await _sendEndpoint.Send(request, cancellationTokenSource.Token);
+ }
+ catch (Exception exception)
+ {
+ Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
+ Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
+ .Fatal(exception, "Failed to Queue EventLog Request.");
+ throw new ServiceException("Failed to Queue EventLog Request.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Templates/LogEmailRowTemplate.html b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Templates/LogEmailRowTemplate.html
new file mode 100644
index 0000000..12f04f4
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Templates/LogEmailRowTemplate.html
@@ -0,0 +1,4 @@
+
+ | {LogTimeStamp} |
+ {Message} |
+
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Templates/LogEmailTemplate.html b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Templates/LogEmailTemplate.html
new file mode 100644
index 0000000..b7491cb
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/Templates/LogEmailTemplate.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+ MaidBot: {Application} Event Logs
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/appsettings.json b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/appsettings.json
new file mode 100644
index 0000000..819b651
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotApi/appsettings.json
@@ -0,0 +1,38 @@
+{
+ "Primary": {
+ "AppName": "MaidBot Api",
+ "SeqUrl": "http://localhost:5080",
+ "SeqApiKey": "VALUE-IN-KEYVAULT",
+ "NotificationsApiEmail": "https://localhost:50353/api/Email",
+ "FeatureFlags": {
+ "LoggingEventConsumer": true,
+ "RunScheduler": true
+ },
+ "BatchSchedulerCron": "*/5 * * * *",
+ "CorsAllowedOrigins": [
+ "http://localhost:3000",
+ "http://lcu-internal.com",
+ "https://lcu-internal.com",
+ "https://qa-api.lcu-internal.com",
+ "https://uat-api.lcu-internal.com",
+ "https://api.lcu-internal.com"
+ ],
+ "LogEmailTemplate": "./Templates/LogEmailTemplate.html",
+ "LogEmailRowTemplate": "./Templates/LogEmailRowTemplate.html"
+ },
+ "ConnectionConfiguration": {
+ "HostUrl": "localhost",
+ "UserName": "guest",
+ "Password": "guest"
+ },
+ "ReceiveEndpoint": {
+ "ReceiveEndpoint": "LCU.MaidBot",
+ "ReceiveEndpointType": "exchange",
+ "PrefetchLimit": 1
+ },
+ "Connections": {
+ "SQL": {
+ "MaidBotDB": "data source=localhost;initial catalog=MaidBot;Integrated Security=SSPI;MultipleActiveResultSets=True;App=EntityFramework"
+ }
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/LCU.Api.Internal.MaidBotLib.csproj b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/LCU.Api.Internal.MaidBotLib.csproj
new file mode 100644
index 0000000..0c94c02
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/LCU.Api.Internal.MaidBotLib.csproj
@@ -0,0 +1,25 @@
+
+
+
+ netstandard2.0
+ true
+ 1.0.2
+
+ 1.0.0 - Initial Creation
+ 1.0.1 - Add BusinessOperations Group Enum
+ 1.0.2 - Add IndirectLending Group Enum
+
+
+ 1.0.0 - Initial Creation
+ 1.0.1 - Add BusinessOperations Group Enum
+ 1.0.2 - Add IndirectLending Group Enum
+
+
+
+
+
+
+
+
+
+
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Models/DebugEventLog.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Models/DebugEventLog.cs
new file mode 100644
index 0000000..ed66bb0
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Models/DebugEventLog.cs
@@ -0,0 +1,20 @@
+namespace LCU.Api.Internal.MaidBotLib.Models
+{
+ public class DebugEventLog
+ {
+ public const int DebugMaxLength = 3000;
+
+ public DebugEventLog()
+ {
+
+ }
+
+ public DebugEventLog(EventLog eventLog)
+ {
+ RawLog = eventLog.DebugData.Length > DebugMaxLength ? eventLog.DebugData.Substring(0, DebugMaxLength) : eventLog.DebugData;
+ }
+
+ public int DebugEventLogId { get; set; }
+ public string RawLog { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Models/EventLog.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Models/EventLog.cs
new file mode 100644
index 0000000..d4c2fd4
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Models/EventLog.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace LCU.Api.Internal.MaidBotLib.Models
+{
+ public class EventLog
+ {
+ public int EventLogId { get; set; }
+ public string Application { get; set; }
+ public string Level { get; set; }
+ public string Group { get; set; }
+ public DateTime LogTimeStamp { get; set; }
+ public string Message { get; set; }
+ public string Environment { get; set; }
+ public string DebugData { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Models/GroupEnum.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Models/GroupEnum.cs
new file mode 100644
index 0000000..c0790da
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Models/GroupEnum.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace LCU.Api.Internal.MaidBotLib.Models
+{
+ public enum GroupEnum
+ {
+ Testing,
+ ITDevelopment,
+ BusinessOperations,
+ IndirectLending
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Models/LoggingMatrix.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Models/LoggingMatrix.cs
new file mode 100644
index 0000000..1b29cae
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Models/LoggingMatrix.cs
@@ -0,0 +1,18 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace LCU.Api.Internal.MaidBotLib.Models
+{
+ public class LoggingMatrix
+ {
+ public int LoggingMatrixId { get; set; }
+ [StringLength(200, MinimumLength = 1, ErrorMessage = "Application field must have a length between 1 and 200 characters")]
+ public string Application { get; set; }
+ [StringLength(50, MinimumLength = 1, ErrorMessage = "Level field must have a length between 1 and 50 characters")]
+ public string Level { get; set; }
+ [StringLength(100, MinimumLength = 1, ErrorMessage = "Groups field must have a length between 1 and 100 characters")]
+ public string Group { get; set; }
+ [StringLength(100, MinimumLength = 1, ErrorMessage = "Emails field must have a length between 1 and 100 characters")]
+ public string Email { get; set; }
+ public bool Disabled { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Models/PropertyEnum.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Models/PropertyEnum.cs
new file mode 100644
index 0000000..e8d816c
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Models/PropertyEnum.cs
@@ -0,0 +1,7 @@
+namespace LCU.Api.Internal.MaidBotLib.Models
+{
+ public enum PropertyEnum
+ {
+ Group
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Persistence/AppDbContext.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Persistence/AppDbContext.cs
new file mode 100644
index 0000000..cc2f8e4
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Persistence/AppDbContext.cs
@@ -0,0 +1,23 @@
+using LCU.Api.Internal.MaidBotLib.Models;
+using Microsoft.EntityFrameworkCore;
+
+namespace LCU.Api.Internal.MaidBotLib.Persistence
+{
+ public class AppDbContext : DbContext
+ {
+ public DbSet LoggingMatrixs { get; set; }
+ public DbSet EventLogs { get; set; }
+ public DbSet DebugEventLogs { get; set; }
+
+ public AppDbContext(DbContextOptions options) : base(options) { }
+
+ protected override void OnModelCreating(ModelBuilder builder)
+ {
+ base.OnModelCreating(builder);
+
+ builder.Entity().ToTable("LoggingMatrix");
+ builder.Entity().ToTable("EventLog");
+ builder.Entity().ToTable("DebugEventLog");
+ }
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/BaseRepository.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/BaseRepository.cs
new file mode 100644
index 0000000..aa18938
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/BaseRepository.cs
@@ -0,0 +1,17 @@
+using LCU.Api.Internal.MaidBotLib.Persistence;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace LCU.Api.Internal.MaidBotLib.Repositories
+{
+ public abstract class BaseRepository
+ {
+ protected readonly AppDbContext _context;
+
+ public BaseRepository(AppDbContext context)
+ {
+ _context = context;
+ }
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/DebugEventLogRepository.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/DebugEventLogRepository.cs
new file mode 100644
index 0000000..c75bbae
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/DebugEventLogRepository.cs
@@ -0,0 +1,20 @@
+using LCU.Api.Internal.MaidBotLib.Models;
+using LCU.Api.Internal.MaidBotLib.Persistence;
+using Microsoft.EntityFrameworkCore;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace LCU.Api.Internal.MaidBotLib.Repositories
+{
+ public class DebugEventLogRepository : BaseRepository, IDebugEventLogRepository
+ {
+ public DebugEventLogRepository(AppDbContext context) : base(context) { }
+
+ public async Task AddAsync(DebugEventLog debugEventLog)
+ {
+ var result = await _context.DebugEventLogs.AddAsync(debugEventLog);
+ return result.Entity;
+ }
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/EventLogRepository.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/EventLogRepository.cs
new file mode 100644
index 0000000..72bd32f
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/EventLogRepository.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using LCU.Api.Internal.MaidBotLib.Models;
+using LCU.Api.Internal.MaidBotLib.Persistence;
+
+namespace LCU.Api.Internal.MaidBotLib.Repositories
+{
+ public class EventLogRepository : BaseRepository, IEventLogRepository
+ {
+ public EventLogRepository(AppDbContext context) : base(context) { }
+
+ public async Task> ListAsync()
+ {
+ return await _context.EventLogs.ToListAsync();
+ }
+
+ public async Task AddAsync(EventLog eventLog)
+ {
+ var result = await _context.EventLogs.AddAsync(eventLog);
+ return result.Entity;
+ }
+
+ public void RemoveRange(List eventLog)
+ {
+ _context.EventLogs.RemoveRange(eventLog);
+ }
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/IDebugEventLogRepository.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/IDebugEventLogRepository.cs
new file mode 100644
index 0000000..fb59f6b
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/IDebugEventLogRepository.cs
@@ -0,0 +1,13 @@
+using LCU.Api.Internal.MaidBotLib.Models;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LCU.Api.Internal.MaidBotLib.Repositories
+{
+ public interface IDebugEventLogRepository
+ {
+ Task AddAsync(DebugEventLog debugEventLog);
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/IEventLogRepository.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/IEventLogRepository.cs
new file mode 100644
index 0000000..61c5941
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/IEventLogRepository.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using LCU.Api.Internal.MaidBotLib.Models;
+
+namespace LCU.Api.Internal.MaidBotLib.Repositories
+{
+ public interface IEventLogRepository
+ {
+ Task> ListAsync();
+ Task AddAsync(EventLog eventLog);
+ void RemoveRange(List eventLog);
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/ILoggingMatrixRepository.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/ILoggingMatrixRepository.cs
new file mode 100644
index 0000000..b100338
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/ILoggingMatrixRepository.cs
@@ -0,0 +1,19 @@
+using LCU.Api.Internal.MaidBotLib.Models;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LCU.Api.Internal.MaidBotLib.Repositories
+{
+ public interface ILoggingMatrixRepository
+ {
+ Task> ListAsync();
+ Task> ListActiveAsync();
+ Task FindByIdAsync(int id);
+ Task> RetrieveByApplicationLevelAndGroup(string application, string level, string group);
+ Task> RetrieveByApplciationAndLevel(string application, string level);
+ Task AddAsync(LoggingMatrix loggingMatrix);
+ void Update(LoggingMatrix loggingMatrix);
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/IUnitOfWork.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/IUnitOfWork.cs
new file mode 100644
index 0000000..971d35d
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/IUnitOfWork.cs
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+
+namespace LCU.Api.Internal.MaidBotLib.Repositories
+{
+ public interface IUnitOfWork
+ {
+ Task CompleteAsync();
+ void Complete();
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/LoggingMatrixRepository.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/LoggingMatrixRepository.cs
new file mode 100644
index 0000000..67cc084
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/LoggingMatrixRepository.cs
@@ -0,0 +1,59 @@
+using LCU.Api.Internal.MaidBotLib.Models;
+using LCU.Api.Internal.MaidBotLib.Persistence;
+using Microsoft.EntityFrameworkCore;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace LCU.Api.Internal.MaidBotLib.Repositories
+{
+ public class LoggingMatrixRepository : BaseRepository, ILoggingMatrixRepository
+ {
+ public LoggingMatrixRepository(AppDbContext context) : base(context) { }
+
+ public async Task> ListAsync()
+ {
+ return await _context.LoggingMatrixs.ToListAsync();
+ }
+
+ public async Task> ListActiveAsync()
+ {
+ return await _context.LoggingMatrixs.Where(loggingMatrix => !loggingMatrix.Disabled).ToListAsync();
+ }
+
+ public async Task FindByIdAsync(int id)
+ {
+ return await _context.LoggingMatrixs.Where(loggingMatrix => loggingMatrix.LoggingMatrixId == id).FirstOrDefaultAsync();
+ }
+
+ public async Task> RetrieveByApplicationLevelAndGroup(string application, string level, string group)
+ {
+ return await _context.LoggingMatrixs
+ .Where(matrix => matrix.Application == application &&
+ matrix.Level == level &&
+ matrix.Group == group &&
+ !matrix.Disabled)
+ .ToListAsync();
+ }
+
+ public async Task> RetrieveByApplciationAndLevel(string application, string level)
+ {
+ return await _context.LoggingMatrixs
+ .Where(matrix => matrix.Application == application &&
+ matrix.Level == level &&
+ !matrix.Disabled)
+ .ToListAsync();
+ }
+
+ public async Task AddAsync(LoggingMatrix loggingMatrix)
+ {
+ var result = await _context.LoggingMatrixs.AddAsync(loggingMatrix);
+ return result.Entity;
+ }
+
+ public void Update(LoggingMatrix loggingMatrix)
+ {
+ _context.LoggingMatrixs.Update(loggingMatrix);
+ }
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/UnitOfWork.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/UnitOfWork.cs
new file mode 100644
index 0000000..dbc8666
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotLib/Repositories/UnitOfWork.cs
@@ -0,0 +1,25 @@
+using LCU.Api.Internal.MaidBotLib.Persistence;
+using System.Threading.Tasks;
+
+namespace LCU.Api.Internal.MaidBotLib.Repositories
+{
+ public class UnitOfWork : IUnitOfWork
+ {
+ private readonly AppDbContext _context;
+
+ public UnitOfWork(AppDbContext context)
+ {
+ _context = context;
+ }
+
+ public async Task CompleteAsync()
+ {
+ await _context.SaveChangesAsync();
+ }
+
+ public void Complete()
+ {
+ _context.SaveChanges();
+ }
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotSeqApp/LCU.Api.Internal.MaidBotSeqApp.csproj b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotSeqApp/LCU.Api.Internal.MaidBotSeqApp.csproj
new file mode 100644
index 0000000..ca15570
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotSeqApp/LCU.Api.Internal.MaidBotSeqApp.csproj
@@ -0,0 +1,42 @@
+
+
+
+ netstandard2.0
+ true
+ LCU.Api.Internal.MaidBotSeqApp
+ 1.0.0
+ Always
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage
+
+
+
+
+
+ <_ReferenceCopyLocalPaths Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')->WithMetadataValue('PrivateAssets', 'All'))" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotSeqApp/SeqToRabbit.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotSeqApp/SeqToRabbit.cs
new file mode 100644
index 0000000..56eaff1
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotSeqApp/SeqToRabbit.cs
@@ -0,0 +1,100 @@
+using LCU.Api.Internal.MaidBotLib.Models;
+using Newtonsoft.Json;
+using Seq.Apps;
+using Seq.Apps.LogEvents;
+using System;
+using System.Net.Http;
+using System.Text;
+
+namespace LCU.Api.Internal.MaidBotSeqApp
+{
+ ///
+ /// Queues LogEvent.
+ ///
+ [SeqApp("QueueLogs", Description = "This will queue logs based on a signal event")]
+ public class SeqToRabbit : SeqApp, ISubscribeTo
+ {
+ private static readonly HttpClient _client = new HttpClient();
+
+ [SeqAppSetting(DisplayName = "Url", HelpText = "Url of MaidBot")]
+ public string Url { get; set; }
+ [SeqAppSetting(DisplayName = "DebugMode", HelpText = "Setting this will set DebugData field to the events serialized object and save to our DebugEventLog database table.")]
+ public bool DebugMode { get; set; }
+
+ protected override void OnAttached()
+ {
+ base.OnAttached();
+ }
+
+ // Called by Seq whenever an event is sent to the app
+ public void On(Event evt)
+ {
+ EventLog logRequest = CreateLogRequest(evt);
+
+ if(logRequest == null)
+ {
+ return;
+ }
+
+ QueueLogEvent(logRequest);
+ }
+
+ public bool MissingRequiredFields(Event evt)
+ {
+ return evt == null ||
+ evt.Data == null ||
+ evt.Data.RenderedMessage == null ||
+ evt.Data.Properties == null ||
+ !evt.Data.Properties.ContainsKey("Application") ||
+ !evt.Data.Properties.ContainsKey("Group");
+ }
+
+ public EventLog CreateLogRequest(Event evt)
+ {
+ var eventLog = new EventLog();
+
+ try
+ {
+ if (MissingRequiredFields(evt))
+ {
+ Log.Debug("Seq LogEventData missing required fields to be logged by MaidBot.");
+ return null;
+ }
+
+ eventLog.Application = evt.Data.Properties["Application"].ToString();
+ eventLog.Level = Enum.GetName(typeof(LogEventLevel), evt.Data.Level);
+ eventLog.LogTimeStamp = evt.TimestampUtc;
+ eventLog.Message = evt.Data.RenderedMessage;
+ eventLog.Group = evt.Data.Properties["Group"].ToString();
+ eventLog.Environment = evt.Data.Properties.ContainsKey("Environment") ? evt.Data.Properties["Environment"].ToString() : "";
+ eventLog.DebugData = DebugMode ? JsonConvert.SerializeObject(evt) : "";
+
+ return eventLog;
+ }
+ catch(Exception ex)
+ {
+ Log.Debug(ex, "[Error] MaidBot: Error Creating Event Log.");
+ return null;
+ }
+ }
+
+ public void QueueLogEvent(EventLog eventLog)
+ {
+ try
+ {
+ var encodedContent = JsonConvert.SerializeObject(eventLog);
+ var content = new StringContent(encodedContent, Encoding.UTF8, "application/json");
+ HttpResponseMessage response = _client.PostAsync(Url, content).Result;
+
+ if (!response.IsSuccessStatusCode)
+ {
+ Log.Debug("[Error] Failure to send Event Log to MaidBot.");
+ }
+ }
+ catch(Exception ex)
+ {
+ Log.Debug(ex, "[Error] MaidBot: Failure to send Event Log to MaidBot.");
+ }
+ }
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotSql/DebugEventLog.sql b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotSql/DebugEventLog.sql
new file mode 100644
index 0000000..fa0f260
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotSql/DebugEventLog.sql
@@ -0,0 +1,5 @@
+CREATE TABLE [dbo].[DebugEventLog]
+(
+ [DebugEventLogId] INT NOT NULL PRIMARY KEY IDENTITY,
+ [RawLog] VARCHAR(3000) NULL
+)
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotSql/EventLog.sql b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotSql/EventLog.sql
new file mode 100644
index 0000000..10f9024
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotSql/EventLog.sql
@@ -0,0 +1,11 @@
+CREATE TABLE [dbo].[EventLog]
+(
+ [EventLogId] INT NOT NULL PRIMARY KEY IDENTITY,
+ [Application] VARCHAR(500) NOT NULL,
+ [Level] VARCHAR(100) NOT NULL,
+ [Group] VARCHAR(200) NOT NULL,
+ [LogTimeStamp] DATETIME NOT NULL,
+ [Message] VARCHAR(MAX) NOT NULL,
+ [Environment] VARCHAR(100) NOT NULL,
+ [DebugData] VARCHAR(MAX) NULL
+)
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotSql/LCU.Api.Internal.MaidBotSql.refactorlog b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotSql/LCU.Api.Internal.MaidBotSql.refactorlog
new file mode 100644
index 0000000..ef19c82
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotSql/LCU.Api.Internal.MaidBotSql.refactorlog
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotSql/LCU.Api.Internal.MaidBotSql.sqlproj b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotSql/LCU.Api.Internal.MaidBotSql.sqlproj
new file mode 100644
index 0000000..e6a9124
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotSql/LCU.Api.Internal.MaidBotSql.sqlproj
@@ -0,0 +1,68 @@
+
+
+
+ Debug
+ AnyCPU
+ LCU.Api.Internal.MaidBotSql
+ 2.0
+ 4.1
+ {ecce6677-a2f2-4694-964f-f91ebe7aac00}
+ Microsoft.Data.Tools.Schema.Sql.Sql130DatabaseSchemaProvider
+ Database
+
+
+ LCU.Api.Internal.MaidBotSql
+ LCU.Api.Internal.MaidBotSql
+ 1033, CI
+ BySchemaAndSchemaType
+ True
+ v4.5
+ CS
+ Properties
+ False
+ True
+ True
+
+
+ bin\Release\
+ $(MSBuildProjectName).sql
+ False
+ pdbonly
+ true
+ false
+ true
+ prompt
+ 4
+
+
+ bin\Debug\
+ $(MSBuildProjectName).sql
+ false
+ true
+ full
+ false
+ true
+ true
+ prompt
+ 4
+
+
+ 11.0
+
+ True
+ 11.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotSql/LoggingMatrix.sql b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotSql/LoggingMatrix.sql
new file mode 100644
index 0000000..195e850
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotSql/LoggingMatrix.sql
@@ -0,0 +1,9 @@
+CREATE TABLE [dbo].[LoggingMatrix]
+(
+ [LoggingMatrixId] INT NOT NULL PRIMARY KEY IDENTITY,
+ [Application] VARCHAR(200) NOT NULL,
+ [Level] VARCHAR(50) NOT NULL,
+ [Group] VARCHAR(100) NOT NULL,
+ [Email] VARCHAR(100) NOT NULL,
+ [Disabled] BIT NULL
+)
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/ControllerTests/LoggingMatrixControllerTest.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/ControllerTests/LoggingMatrixControllerTest.cs
new file mode 100644
index 0000000..5659785
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/ControllerTests/LoggingMatrixControllerTest.cs
@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Moq;
+using Xunit;
+using LCU.Api.Internal.MaidBotApi.Services;
+using LCU.Api.Internal.MaidBotLib.Models;
+using LCU.API.Internal.MaidBotApi.Controllers;
+namespace LCU.Api.Internal.MaidBotTests.ControllerTests;
+
+public class LoggingMatrixControllerTest
+{
+ private readonly LoggingMatrixController _loggingMatrixController;
+ private readonly Mock _mockLoggingMatrixService;
+
+ public LoggingMatrixControllerTest()
+ {
+ _mockLoggingMatrixService = new Mock();
+ _loggingMatrixController = new LoggingMatrixController(_mockLoggingMatrixService.Object);
+ }
+
+ [Fact]
+ public async void GetAllLoggingMatrix_Success_200()
+ {
+ // Arrange
+ _mockLoggingMatrixService.Setup(service => service.GetAsync()).ReturnsAsync(new List
+ {
+ new LoggingMatrix(),
+ new LoggingMatrix()
+ });
+
+ // Act
+ var result = await _loggingMatrixController.Get();
+
+ // Assert
+ Assert.NotNull(result);
+
+ var okResult = result as OkObjectResult;
+ Assert.NotNull(okResult);
+ Assert.NotNull(okResult.Value);
+
+ var extractedValue = okResult.Value as IEnumerable;
+ Assert.NotNull(extractedValue);
+ Assert.Equal(2, extractedValue.Count());
+ }
+
+ [Fact]
+ public async void GetAllLoggingMatrix_BsaCorrectionServiceThrowsError_500()
+ {
+ // Arrange
+ _mockLoggingMatrixService.Setup(service => service.GetAsync()).ThrowsAsync(new Exception());
+
+ // Act
+ var result = await _loggingMatrixController.Get();
+
+ // Assert
+ Assert.NotNull(result);
+
+ var okResult = result as StatusCodeResult;
+ Assert.NotNull(okResult);
+ Assert.Equal(StatusCodes.Status500InternalServerError, okResult.StatusCode);
+ }
+
+ [Fact]
+ public async void PostLoggingMatrix_LoggingMatrixReturnsNull_BadRequest400()
+ {
+ var loggingMatrix = new LoggingMatrix() { };
+ _mockLoggingMatrixService.Setup(service => service.SaveAsync(loggingMatrix)).ReturnsAsync(() => null);
+
+ // Act
+ var result = await _loggingMatrixController.Post(loggingMatrix);
+
+ // Assert
+ Assert.NotNull(result);
+
+ var okResult = result as ObjectResult;
+ Assert.NotNull(okResult);
+ Assert.Equal(StatusCodes.Status400BadRequest, okResult.StatusCode);
+ }
+
+ [Fact]
+ public async void PostLoggingMatrix_throwsException_500()
+ {
+ var loggingMatrix = new LoggingMatrix() { };
+ _mockLoggingMatrixService.Setup(service => service.SaveAsync(loggingMatrix)).Throws(new Exception("Exception from unit test"));
+
+ // Act
+ var result = await _loggingMatrixController.Post(loggingMatrix);
+
+ // Assert
+ Assert.NotNull(result);
+
+ var okResult = result as StatusCodeResult;
+ Assert.NotNull(okResult);
+ Assert.Equal(StatusCodes.Status500InternalServerError, okResult.StatusCode);
+ }
+
+ [Fact]
+ public async void PutLoggingMatrix_BadRequestIdEqualZero_400()
+ {
+ var loggingMatrix = new LoggingMatrix
+ {
+ LoggingMatrixId = 0
+ };
+
+ // Act
+ var result = await _loggingMatrixController.Put(loggingMatrix);
+
+ // Assert
+ Assert.NotNull(result);
+
+ var returnedResult = result as BadRequestObjectResult;
+ Assert.NotNull(returnedResult);
+ Assert.Equal(StatusCodes.Status400BadRequest, returnedResult.StatusCode);
+ }
+
+ [Fact]
+ public async void PutLoggingMatrix_ThrowsException_500()
+ {
+ var loggingMatrix = new LoggingMatrix
+ {
+ LoggingMatrixId = 1
+ };
+ _mockLoggingMatrixService.Setup(service => service.UpdateAsync(loggingMatrix)).Throws(new Exception("Exception from update unit test"));
+
+ // Act
+ var result = await _loggingMatrixController.Put(loggingMatrix);
+
+ // Assert
+ Assert.NotNull(result);
+
+ var okResult = result as StatusCodeResult;
+ Assert.NotNull(okResult);
+ Assert.Equal(StatusCodes.Status500InternalServerError, okResult.StatusCode);
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/ControllerTests/QueueEventLogControllerTest.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/ControllerTests/QueueEventLogControllerTest.cs
new file mode 100644
index 0000000..4737e5a
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/ControllerTests/QueueEventLogControllerTest.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Moq;
+using Xunit;
+using LCU.Api.Internal.MaidBotApi.Services;
+using LCU.Api.Internal.MaidBotLib.Models;
+using LCU.API.Internal.MaidBotApi.Controllers;
+namespace LCU.Api.Internal.MaidBotTests.ControllerTests;
+
+public class QueueEventLogControllerTest
+{
+ private readonly QueueEventLogController _queueEventLogController;
+ private readonly Mock _mockQueueEventLogService;
+
+ public QueueEventLogControllerTest()
+ {
+ _mockQueueEventLogService = new Mock();
+ _queueEventLogController = new QueueEventLogController(_mockQueueEventLogService.Object);
+ }
+
+ [Fact]
+ public async Task PostLoggingMatrix_throwsException_500()
+ {
+ var eventLog = new EventLog() { };
+ _mockQueueEventLogService.Setup(service => service.QueueEventLog(eventLog)).Throws(new Exception("Exception from unit test"));
+
+ // Act
+ var result = await _queueEventLogController.Post(eventLog);
+
+ // Assert
+ Assert.NotNull(result);
+
+ var okResult = result as StatusCodeResult;
+ Assert.NotNull(okResult);
+ Assert.Equal(StatusCodes.Status500InternalServerError, okResult.StatusCode);
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/LCU.Api.Internal.MaidBotTests.csproj b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/LCU.Api.Internal.MaidBotTests.csproj
new file mode 100644
index 0000000..2b96559
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/LCU.Api.Internal.MaidBotTests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net8.0
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+
+
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/ModelTest/DebugEventLogTest.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/ModelTest/DebugEventLogTest.cs
new file mode 100644
index 0000000..ce1de53
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/ModelTest/DebugEventLogTest.cs
@@ -0,0 +1,21 @@
+using LCU.Api.Internal.MaidBotLib.Models;
+using Xunit;
+
+namespace LCU.Api.Internal.MaidBotTests.ModelTest
+{
+ public class DebugEventLogTests
+ {
+ [Fact]
+ public void CreateDebugEventLog_RawLog_LengthToLong_TrimToMax()
+ {
+ // Arrange / Act
+ var testEventLog = new EventLog();
+ testEventLog.DebugData = "TESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETETESTTESTETSETETSETSETSETSETSETSETSETETESTSETSETSETSETSETESTESTESTSETSETSETSETSTESTETEETE";
+ var debugEventLog = new DebugEventLog(testEventLog);
+
+ // Assert
+ Assert.NotNull(debugEventLog);
+ Assert.Equal(DebugEventLog.DebugMaxLength, debugEventLog.RawLog.Length);
+ }
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/RepositoryTests/DbContextFromJson.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/RepositoryTests/DbContextFromJson.cs
new file mode 100644
index 0000000..353cae7
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/RepositoryTests/DbContextFromJson.cs
@@ -0,0 +1,70 @@
+using LCU.Api.Internal.MaidBotLib.Models;
+using LCU.Api.Internal.MaidBotLib.Persistence;
+using Microsoft.EntityFrameworkCore;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace LCU.Api.Internal.MaidBotTests.RepositoryTests
+{
+ public class DbContextTestSet
+ {
+ public IReadOnlyCollection LoggingMatrixSet { get; set; }
+ }
+
+ public abstract class DbContextFromJsonTest
+ {
+ public DbContextTestSet GetTestAccounts(string filePath)
+ {
+ var testSet = new DbContextTestSet();
+
+ // Get the absolute path to the JSON file
+ var path = Path.IsPathRooted(filePath)
+ ? filePath
+ : Path.GetRelativePath(Directory.GetCurrentDirectory(), filePath);
+
+ if (!File.Exists(path))
+ {
+ throw new ArgumentException($"Could not find file at path: {path}");
+ }
+
+ var fileData = File.ReadAllText(filePath);
+ var allData = JObject.Parse(fileData);
+
+ var runInfo = allData["LoggingMatrixs"];
+ testSet.LoggingMatrixSet = runInfo?.ToObject>();
+
+ return testSet;
+ }
+
+ public AppDbContext GetBlankContext()
+ {
+ var options = new DbContextOptionsBuilder()
+ .UseInMemoryDatabase(Guid.NewGuid().ToString())
+ .Options;
+ return new AppDbContext(options);
+ }
+
+ public AppDbContext GetContextWithData(string jsonFileName)
+ {
+ var options = new DbContextOptionsBuilder()
+ .UseInMemoryDatabase(Guid.NewGuid().ToString())
+ .Options;
+ var context = new AppDbContext(options);
+
+ var testData = GetTestAccounts(jsonFileName);
+ AddTo(context.LoggingMatrixs, testData.LoggingMatrixSet);
+
+ context.SaveChanges();
+
+ return context;
+ }
+
+ private static void AddTo(DbSet collection, IEnumerable range) where T : class
+ {
+ if (range != null) collection.AddRange(range);
+ }
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/RepositoryTests/LoggingMatrixRepositoryTest.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/RepositoryTests/LoggingMatrixRepositoryTest.cs
new file mode 100644
index 0000000..8709a49
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/RepositoryTests/LoggingMatrixRepositoryTest.cs
@@ -0,0 +1,66 @@
+using LCU.Api.Internal.MaidBotLib.Models;
+using LCU.Api.Internal.MaidBotLib.Repositories;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Xunit;
+
+namespace LCU.Api.Internal.MaidBotTests.RepositoryTests
+{
+ public class LoggingMatrixRepositoryTest : DbContextFromJsonTest
+ {
+ private readonly LoggingMatrixRepository _loggingMatrixRepository;
+ private const string JsonTestDataFilePath = "RepositoryTests\\LoggingMatrixTestData.json";
+
+ public LoggingMatrixRepositoryTest()
+ {
+ var dbContext = GetContextWithData(JsonTestDataFilePath);
+ _loggingMatrixRepository = new LoggingMatrixRepository(dbContext);
+ }
+
+ //TODO: These UnitTest have been commented out since there is version mismatch and at this time
+ // we are not going to undergo the work that is required to upgrade. The work to upgrade
+ // the .net version will need to be done in a separate Story:
+ // 89988 Maidbot API > Library Modernization
+ // https://landmarkcu.visualstudio.com/LMCU/_boards/board/t/Development/Stories?workitem=89988.
+ // This decision was made during this PR:
+ // https://landmarkcu.visualstudio.com/Platform/_git/LCU.API.Internal.MaidBotApi/pullrequest/17634
+ //[Fact]
+ //public void ListAsync_Test()
+ //{
+ // var loggingMatrixes = _loggingMatrixRepository.ListAsync().Result;
+ // Assert.NotNull(loggingMatrixes);
+ // Assert.Equal(6, loggingMatrixes.Count());
+ //}
+
+ //[Fact]
+ //public async void FindById_Test()
+ //{
+ // var loggingMatrix = await _loggingMatrixRepository.FindByIdAsync(1);
+ // Assert.NotNull(loggingMatrix);
+ // Assert.Equal(1, loggingMatrix.LoggingMatrixId);
+ //}
+
+ //[Fact]
+ //public void RetrieveByApplicationLevelAndGroup_Test()
+ //{
+ // var application = "Test App 1";
+ // var group = "Test Group 1";
+ // var level = "Test Level 1";
+ // var loggingMatrixes = _loggingMatrixRepository.RetrieveByApplicationLevelAndGroup(application, level, group ).Result;
+ // Assert.NotNull(loggingMatrixes);
+ // Assert.Equal(2, loggingMatrixes.Count());
+ //}
+
+ //[Fact]
+ //public void RetrieveByApplciationAndLevel_Test()
+ //{
+ // var application = "Test App 1";
+ // var level = "Test Level 1";
+ // var loggingMatrixes = _loggingMatrixRepository.RetrieveByApplciationAndLevel(application, level).Result;
+ // Assert.NotNull(loggingMatrixes);
+ // Assert.Equal(3, loggingMatrixes.Count());
+ //}
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/RepositoryTests/LoggingMatrixTestData.json b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/RepositoryTests/LoggingMatrixTestData.json
new file mode 100644
index 0000000..d52898d
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/RepositoryTests/LoggingMatrixTestData.json
@@ -0,0 +1,52 @@
+{
+ "LoggingMatrixs": [
+ {
+ "LoggingmatrixId": 1,
+ "Application": "Test App 1",
+ "Level": "Test Level 1",
+ "Group": "Test Group 1",
+ "Email": "TestEmail1@Landmarkcu.com",
+ "Disabled": false
+ },
+ {
+ "LoggingmatrixId": 2,
+ "Application": "Test App 2",
+ "Level": "Test Level 2",
+ "Group": "Test Group 2",
+ "Email": "TestEmail2@Landmarkcu.com",
+ "Disabled": false
+ },
+ {
+ "LoggingmatrixId": 3,
+ "Application": "Test App 3",
+ "Level": "Test Level 3",
+ "Group": "Test Group 3",
+ "Email": "TestEmail3@Landmarkcu.com",
+ "Disabled": true
+ },
+ {
+ "LoggingmatrixId": 4,
+ "Application": "Test App 1",
+ "Level": "Test Level 1",
+ "Group": "Test Group 1",
+ "Email": "TestEmail4@Landmarkcu.com",
+ "Disabled": false
+ },
+ {
+ "LoggingmatrixId": 5,
+ "Application": "Test App 1",
+ "Level": "Test Level 1",
+ "Group": "Test Group 1",
+ "Email": "TestEmail5@Landmarkcu.com",
+ "Disabled": true
+ },
+ {
+ "LoggingmatrixId": 6,
+ "Application": "Test App 1",
+ "Level": "Test Level 1",
+ "Group": "Test Group 6",
+ "Email": "TestEmail6@Landmarkcu.com",
+ "Disabled": false
+ }
+ ]
+}
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/ServiceTests/DebugEventLogServiceTest.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/ServiceTests/DebugEventLogServiceTest.cs
new file mode 100644
index 0000000..e413c2d
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/ServiceTests/DebugEventLogServiceTest.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Threading.Tasks;
+using Moq;
+using Xunit;
+using LCU.Api.Internal.MaidBotApi.Exceptions;
+using LCU.Api.Internal.MaidBotApi.Services;
+using LCU.Api.Internal.MaidBotLib.Models;
+using LCU.Api.Internal.MaidBotLib.Repositories;
+using LCU.Library.Internal.Exceptions;
+
+namespace LCU.Api.Internal.MaidBotTests.ServiceTests;
+
+public class DebugEventLogServiceTest
+{
+ private readonly DebugEventLogService _debugEventLogService;
+ private readonly Mock _mockDebugEventLogRepository;
+
+ public DebugEventLogServiceTest()
+ {
+ var mockUnitOfWork = new Mock();
+ _mockDebugEventLogRepository = new Mock();
+ _debugEventLogService = new DebugEventLogService(mockUnitOfWork.Object, _mockDebugEventLogRepository.Object);
+ }
+
+ [Fact]
+ public async Task SaveAsync_Success()
+ {
+ // Arrange
+ _mockDebugEventLogRepository.Setup(repo => repo.AddAsync(It.IsAny())).ReturnsAsync(new DebugEventLog());
+
+ // Act
+ var result = await _debugEventLogService.SaveAsync(new DebugEventLog());
+
+ // Assert
+ Assert.NotNull(result);
+ }
+
+ [Fact]
+ public async Task SaveAsync_Exception_Throws_ServerError()
+ {
+ // Arrange
+ _mockDebugEventLogRepository.Setup(repo => repo.AddAsync(It.IsAny())).ThrowsAsync(new Exception());
+
+ // Act / Assert
+ await Assert.ThrowsAsync(async () =>
+ await _debugEventLogService.SaveAsync(new DebugEventLog()));
+ }
+
+ [Fact]
+ public async Task DebugEventLog_Verify_AddAsyncCalled_Once()
+ {
+ // Arrange
+ var testEventLog = new EventLog()
+ {
+ DebugData = "Test Debug Data"
+ };
+ var testDebugEventLog = new DebugEventLog()
+ {
+ RawLog = "Test Debug Data"
+ };
+
+ _mockDebugEventLogRepository.Setup(repo => repo.AddAsync(testDebugEventLog)).ReturnsAsync(testDebugEventLog);
+
+ //Act
+ await _debugEventLogService.DebugEventLog(testEventLog);
+
+ //Assert
+ _mockDebugEventLogRepository.Verify(x => x.AddAsync(It.IsAny()), Times.Once);
+ }
+
+ [Fact]
+ public async Task DebugEventLog_Verify_AddAsyncCalled_Never()
+ {
+ // Arrange
+ var testEventLog = new EventLog() { };
+ _mockDebugEventLogRepository.Setup(repo => repo.AddAsync(It.IsAny())).ReturnsAsync(new DebugEventLog());
+ var testDebugEventLog = new DebugEventLog();
+
+ //Act
+ await _debugEventLogService.DebugEventLog(testEventLog);
+
+ //Assert
+ _mockDebugEventLogRepository.Verify(x => x.AddAsync(testDebugEventLog), Times.Never);
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/ServiceTests/EventLogServiceTest.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/ServiceTests/EventLogServiceTest.cs
new file mode 100644
index 0000000..ade374a
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/ServiceTests/EventLogServiceTest.cs
@@ -0,0 +1,170 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Moq;
+using Xunit;
+using LCU.Api.Internal.MaidBotApi.Services;
+using LCU.Api.Internal.MaidBotLib.Models;
+using LCU.Api.Internal.MaidBotLib.Repositories;
+namespace LCU.Api.Internal.MaidBotTests.ServiceTests;
+
+public class EventLogServiceTest
+{
+ private EventLogService _eventLogService;
+ private readonly Mock _mockEventLogRepository;
+ private readonly Mock _mockDebugEventLogService;
+ private readonly Mock _mockLoggingMatrixService;
+ private readonly Mock _mockUnitOfWork;
+
+ public EventLogServiceTest()
+ {
+ _mockEventLogRepository = new Mock();
+ _mockDebugEventLogService = new Mock();
+ _mockLoggingMatrixService = new Mock();
+ _mockUnitOfWork = new Mock();
+
+ _eventLogService = new EventLogService(_mockEventLogRepository.Object,
+ _mockDebugEventLogService.Object,
+ _mockLoggingMatrixService.Object,
+ _mockUnitOfWork.Object);
+ }
+
+ [Fact]
+ public async Task HandleEventLog_AllMethods_Called_Once()
+ {
+ // Arrange
+ var testEventLog = new EventLog
+ {
+ Application = "Test App",
+ Level = "Test Level",
+ Group = "Test Group",
+ LogTimeStamp = Convert.ToDateTime("1/1/2020"),
+ Message = "Test Message",
+ Environment = "Test",
+ DebugData = "Test Debug Data"
+ };
+
+ var testLoggingMatrix = new List
+ {
+ new LoggingMatrix
+ {
+ LoggingMatrixId = 1,
+ Application = "Test App",
+ Level = "Test Level",
+ Group = "Test Group",
+ Email = "TestEmail@Landmarkcu.com",
+ Disabled = false
+ }
+ };
+
+ _mockLoggingMatrixService.Setup(x => x.GetByApplicationLevelAndGroupAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(testLoggingMatrix);
+
+ var eventLogResult = new EventLog
+ {
+ EventLogId = 1,
+ Application = "Test App",
+ Level = "Test Level",
+ Group = "Test Group",
+ LogTimeStamp = Convert.ToDateTime("1/1/2020"),
+ Message = "Test Message",
+ Environment = "Test",
+ DebugData = "Test Debug Data"
+ };
+ _mockEventLogRepository.Setup(x => x.AddAsync(It.IsAny())).ReturnsAsync(eventLogResult);
+ _mockDebugEventLogService.Setup(x => x.DebugEventLog(It.IsAny()));
+
+ //Act
+ await _eventLogService.HandleIncomingEventLog(testEventLog);
+
+ //Assert
+ _mockLoggingMatrixService.Verify(x => x.GetByApplicationLevelAndGroupAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once);
+ _mockEventLogRepository.Verify(x => x.AddAsync(It.IsAny()), Times.Once);
+ _mockDebugEventLogService.Verify(x => x.DebugEventLog(It.IsAny()), Times.Once);
+ }
+
+ [Fact]
+ public async Task HandleEventLog_NoMatrixFound_EventLogNotSaved()
+ {
+ // Arrange
+ var testEventLog = new EventLog()
+ {
+ Application = "Test App",
+ Level = "Test Level",
+ Group = "Test Group",
+ LogTimeStamp = Convert.ToDateTime("1/1/2020"),
+ Message = "Test Message",
+ Environment = "Test",
+ DebugData = null
+ };
+
+ var testLoggingMatrix = new List();
+ _mockLoggingMatrixService.Setup(x => x.GetByApplicationLevelAndGroupAsync(It.IsAny(),
+ It.IsAny(),
+ It.IsAny())).ReturnsAsync(testLoggingMatrix);
+
+ var eventLogResult = new EventLog
+ {
+ EventLogId = 1,
+ Application = "Test App",
+ Level = "Test Level",
+ Group = "Test Group",
+ LogTimeStamp = Convert.ToDateTime("1/1/2020"),
+ Message = "Test Message",
+ Environment = "Test",
+ DebugData = null
+ };
+ _mockEventLogRepository.Setup(x => x.AddAsync(It.IsAny())).ReturnsAsync(eventLogResult);
+ _mockDebugEventLogService.Setup(x => x.DebugEventLog(It.IsAny()));
+
+ //Act
+ await _eventLogService.HandleIncomingEventLog(testEventLog);
+
+ //Assert
+ _mockLoggingMatrixService.Verify(x => x.GetByApplicationLevelAndGroupAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once);
+ _mockEventLogRepository.Verify(x => x.AddAsync(It.IsAny()), Times.Never);
+ _mockDebugEventLogService.Verify(x => x.DebugEventLog(It.IsAny()), Times.Once);
+ }
+
+ [Fact]
+ public async Task HandleEventLog_NoMatrixFound_EventLogNotSaved_DebugSaved()
+ {
+ // Arrange
+ var testEventLog = new EventLog()
+ {
+ Application = "Test App",
+ Level = "Test Level",
+ Group = "Test Group",
+ LogTimeStamp = Convert.ToDateTime("1/1/2020"),
+ Message = "Test Message",
+ Environment = "Test",
+ DebugData = "Test Debug Data"
+ };
+
+ var testLoggingMatrix = new List();
+ _mockLoggingMatrixService.Setup(x => x.GetByApplicationLevelAndGroupAsync(It.IsAny(),
+ It.IsAny(),
+ It.IsAny())).ReturnsAsync(testLoggingMatrix);
+
+ var eventLogResult = new EventLog
+ {
+ EventLogId = 1,
+ Application = "Test App",
+ Level = "Test Level",
+ Group = "Test Group",
+ LogTimeStamp = Convert.ToDateTime("1/1/2020"),
+ Message = "Test Message",
+ Environment = "Test",
+ DebugData = "Test Debug Data"
+ };
+ _mockEventLogRepository.Setup(x => x.AddAsync(It.IsAny())).ReturnsAsync(eventLogResult);
+ _mockDebugEventLogService.Setup(x => x.DebugEventLog(It.IsAny()));
+
+ //Act
+ await _eventLogService.HandleIncomingEventLog(testEventLog);
+
+ //Assert
+ _mockLoggingMatrixService.Verify(x => x.GetByApplicationLevelAndGroupAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once);
+ _mockEventLogRepository.Verify(x => x.AddAsync(It.IsAny()), Times.Never);
+ _mockDebugEventLogService.Verify(x => x.DebugEventLog(It.IsAny()), Times.Once);
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/ServiceTests/LogDistributionServiceTest.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/ServiceTests/LogDistributionServiceTest.cs
new file mode 100644
index 0000000..1b81ee1
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/ServiceTests/LogDistributionServiceTest.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Moq;
+using Xunit;
+using LCU.Api.Internal.MaidBotApi.Services;
+using LCU.Api.Internal.MaidBotLib.Models;
+namespace LCU.Api.Internal.MaidBotTests.ServiceTests;
+
+public class LogDistributionServiceTest
+{
+ private LogDistributionService _logDistributionService;
+ private readonly Mock _mockEventLogService;
+ private readonly Mock _mockLoggingMatrixService;
+ private readonly Mock _notificationsApiService;
+
+ public LogDistributionServiceTest()
+ {
+ _mockEventLogService = new Mock();
+ _mockLoggingMatrixService = new Mock();
+ _notificationsApiService = new Mock();
+
+ _logDistributionService = new LogDistributionService(_mockEventLogService.Object,
+ _mockLoggingMatrixService.Object,
+ _notificationsApiService.Object);
+ }
+
+ [Fact]
+ public async Task HandleLogDistribution_NoEventLogs()
+ {
+ var eventLogs = new List();
+ _mockEventLogService.Setup(x => x.GetAsync()).ReturnsAsync(eventLogs);
+
+ await _logDistributionService.HandleLogDistribution();
+
+ _mockEventLogService.Verify(x => x.GetAsync(), Times.Once);
+ _mockLoggingMatrixService.Verify(x => x.GetActiveAsync(), Times.Never);
+ _notificationsApiService.Verify(x => x.SendNotificationAsync(It.IsAny(), It.IsAny>()), Times.Never);
+ _mockEventLogService.Verify(x => x.DeleteMany(It.IsAny>()), Times.Never);
+ }
+
+ [Fact]
+ public async Task HandleLogDistribution_EventLogs_NoMatrix()
+ {
+ var eventLogs = new List
+ {
+ new EventLog
+ {
+ Application = "Test App",
+ Level = "Test Level",
+ Group = "Test Group",
+ LogTimeStamp = Convert.ToDateTime("1/1/2022"),
+ Message = "Test Message 1",
+ Environment = "Test",
+ DebugData = "Test Debug Data"
+ },
+ new EventLog
+ {
+ Application = "Test App",
+ Level = "Test Level",
+ Group = "Test Group",
+ LogTimeStamp = Convert.ToDateTime("1/1/2022"),
+ Message = "Test Message 2",
+ Environment = "Test",
+ DebugData = "Test Debug Data"
+ }
+ };
+ _mockEventLogService.Setup(x => x.GetAsync()).ReturnsAsync(eventLogs);
+
+ var loggingMatrixs = new List();
+ _mockLoggingMatrixService.Setup(x => x.GetActiveAsync()).ReturnsAsync(loggingMatrixs);
+
+ await _logDistributionService.HandleLogDistribution();
+
+ _mockEventLogService.Verify(x => x.GetAsync(), Times.Once);
+ _mockLoggingMatrixService.Verify(x => x.GetActiveAsync(), Times.Once);
+ _notificationsApiService.Verify(x => x.SendNotificationAsync(It.IsAny(), It.IsAny>()), Times.Never);
+ _mockEventLogService.Verify(x => x.DeleteMany(It.IsAny>()), Times.Never);
+ }
+
+ [Fact]
+ public async Task HandleLogDistribution_EventLogs_LoggingMatrix_SendEmail()
+ {
+ var eventLogs = new List
+ {
+ new EventLog
+ {
+ Application = "Test App",
+ Level = "Test Level",
+ Group = "Test Group",
+ LogTimeStamp = Convert.ToDateTime("1/1/2022"),
+ Message = "Test Message 1",
+ Environment = "Test",
+ DebugData = "Test Debug Data"
+ },
+ new EventLog
+ {
+ Application = "Test App",
+ Level = "Test Level",
+ Group = "Test Group",
+ LogTimeStamp = Convert.ToDateTime("1/1/2022"),
+ Message = "Test Message 2",
+ Environment = "Test",
+ DebugData = "Test Debug Data"
+ }
+ };
+ _mockEventLogService.Setup(x => x.GetAsync()).ReturnsAsync(eventLogs);
+
+ var loggingMatrixs = new List
+ {
+ new LoggingMatrix
+ {
+ LoggingMatrixId = 1,
+ Application = "Test App",
+ Level = "Test Level",
+ Group = "Test Group",
+ Email = "TestEmail@Landmarkcu.com",
+ Disabled = false
+ }
+ };
+ _mockLoggingMatrixService.Setup(x => x.GetActiveAsync()).ReturnsAsync(loggingMatrixs);
+
+ await _logDistributionService.HandleLogDistribution();
+
+ _mockEventLogService.Verify(x => x.GetAsync(), Times.Once);
+ _mockLoggingMatrixService.Verify(x => x.GetActiveAsync(), Times.Once);
+ _notificationsApiService.Verify(x => x.SendNotificationAsync(It.IsAny(), It.IsAny>()), Times.Once);
+ _mockEventLogService.Verify(x => x.DeleteMany(It.IsAny>()), Times.Once);
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/ServiceTests/LoggingMatrixServiceTest.cs b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/ServiceTests/LoggingMatrixServiceTest.cs
new file mode 100644
index 0000000..a616de6
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/ServiceTests/LoggingMatrixServiceTest.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Moq;
+using Xunit;
+using LCU.Api.Internal.MaidBotApi.Exceptions;
+using LCU.Api.Internal.MaidBotApi.Services;
+using LCU.Api.Internal.MaidBotLib.Models;
+using LCU.Api.Internal.MaidBotLib.Repositories;
+using LCU.Library.Internal.Exceptions;
+
+namespace LCU.Api.Internal.MaidBotTests.ServiceTests;
+
+public class LoggingMatrixServiceTest
+{
+ private readonly LoggingMatrixService _loggingMatrixService;
+ private readonly Mock _mockLoggingMatrixRepository;
+
+ public LoggingMatrixServiceTest()
+ {
+ var mockUnitOfWork = new Mock();
+ _mockLoggingMatrixRepository = new Mock();
+ _loggingMatrixService = new LoggingMatrixService(mockUnitOfWork.Object, _mockLoggingMatrixRepository.Object);
+ }
+
+ [Fact]
+ public async Task GetAsync_Success()
+ {
+ // Arrange
+ _mockLoggingMatrixRepository.Setup(repo => repo.ListAsync()).ReturnsAsync(new List()
+ {
+ new LoggingMatrix(),
+ new LoggingMatrix()
+ });
+
+ // Act
+ var result = await _loggingMatrixService.GetAsync();
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(2, result.Count());
+ }
+
+ [Fact]
+ public async Task GetAsync_Exception_Throws_ServerError()
+ {
+ // Arrange
+ _mockLoggingMatrixRepository.Setup(repo => repo.ListAsync()).ThrowsAsync(new Exception());
+
+ // Act / Assert
+ await Assert.ThrowsAsync(async () => await _loggingMatrixService.GetAsync());
+ }
+
+ [Fact]
+ public async Task SaveAsync_Success()
+ {
+ // Arrange
+ _mockLoggingMatrixRepository.Setup(repo => repo.AddAsync(It.IsAny())).ReturnsAsync(new LoggingMatrix());
+
+ // Act
+ var result = await _loggingMatrixService.SaveAsync(new LoggingMatrix());
+
+ // Assert
+ Assert.NotNull(result);
+ }
+
+ [Fact]
+ public async Task SaveAsync_Exception_Throws_ServerError()
+ {
+ // Arrange
+ _mockLoggingMatrixRepository.Setup(repo => repo.AddAsync(It.IsAny())).ThrowsAsync(new Exception());
+
+ // Act / Assert
+ await Assert.ThrowsAsync(async () =>
+ await _loggingMatrixService.SaveAsync(new LoggingMatrix()));
+ }
+
+ [Fact]
+ public async Task UpdateAsync_Success_Updates_Fields()
+ {
+ // Arrange
+ var testingLoggingMatrix = new LoggingMatrix()
+ {
+ LoggingMatrixId = 1,
+ Application = "Test Application",
+ Level = "Test Level",
+ Group = "Test Group",
+ Email = "TESTEmail@Landmarkcu.com",
+ Disabled = false
+ };
+ _mockLoggingMatrixRepository.Setup(repo => repo.FindByIdAsync(It.IsAny())).ReturnsAsync(testingLoggingMatrix);
+
+ // Act
+ var result = await _loggingMatrixService.UpdateAsync(testingLoggingMatrix);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(testingLoggingMatrix.LoggingMatrixId, result.LoggingMatrixId);
+ Assert.Equal(testingLoggingMatrix.Application, result.Application);
+ Assert.Equal(testingLoggingMatrix.Level, result.Level);
+ Assert.Equal(testingLoggingMatrix.Group, result.Group);
+ Assert.Equal(testingLoggingMatrix.Email, result.Email);
+ Assert.Equal(testingLoggingMatrix.Disabled, result.Disabled);
+ }
+}
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/additional_test_args.runsettings b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/additional_test_args.runsettings
new file mode 100644
index 0000000..16d6a41
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/LCU.Api.Internal.MaidBotTests/additional_test_args.runsettings
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .*microsoft.*
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LCU.API.Internal.MaidBotApi/README.md b/LCU.API.Internal.MaidBotApi/README.md
new file mode 100644
index 0000000..1bc32ad
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/README.md
@@ -0,0 +1,41 @@
+# Introduction
+Maidbot was created to give us the ability to get notifications on seq and other loggings. It has an api endpoint that can be used to queue a log. Once the log is consumed it queries our LoggingMatrix for the Application, Level, and Group. If a matrix is found the email is used to send a notification with the message of the log.
+
+# Seq Debugging
+Since seq app can only be tested on seq, there is no way to debug locally. This adds a pain to testing the application. So if there is a need to test follow the following steps.
+
+Local MaidBotApi Steps.
+1. Create your own test queue in QA Rabbit. Use the same exchange LCU.Logging.Direct and routing key for binding.
+2. Change appsettings to use your test queue.
+
+Seq App Steps
+1. Make code adjustments to SeqToRabbit.
+2. Build
+3. Grab nuget package from bin.
+4. Change name of nuget to indicate it is for debugging.
+5. Go to QA server and to the seq nuget folder. (Currently C:\Program Files\Seq\Nuget)
+6. Add nuget file
+7. Go to QA seq --> settings --> Apps --> Install From Nuget
+8. Select "LocalMachine" as Feed.
+9. Add your test nuget package name and put as Package Id. (Doesn't require package version).
+10. Install.
+11. Add Instance
+12. Create a Title that indicates that it is Debuging/Testing.
+13. Add Stream that you wish to use to test. NOTE: Do not use information or other logging that occurs very frequently. If there is an issue with your app it will flood seq with errors.
+14. Add QA MaidBotApi url
+15. Select DebugMode to indicate that the raw log will be saved to our MaidBot DebugEventLog table.
+16. DO NOT select "Run the app on existing events" as this can cause a flood logs. (Unless fully aware what you are pulling in).
+17. Save Changes
+
+
+#App Integration Steps
+- Pull LCU.Api.Internal.MaidBotLib Nuget into project.
+- Add Context of desired property such as Group.
+ - Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
+ Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
+ .Fatal("Failure return status, message: {0} {1}", e.Message, e.StackTrace);
+- Use MaidBotApi swagger to create matrix to follow your app, desired level, and group.
+- In Seq create signal that will capture this log(If not already created).
+- Create instance that will watch this signal.
+
+
diff --git a/LCU.API.Internal.MaidBotApi/azdo_pipelines/azure-k8s-pipeline.yaml b/LCU.API.Internal.MaidBotApi/azdo_pipelines/azure-k8s-pipeline.yaml
new file mode 100644
index 0000000..246e782
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/azdo_pipelines/azure-k8s-pipeline.yaml
@@ -0,0 +1,74 @@
+parameters:
+- name: target_environment
+ displayName: Target Environment
+ type: string
+ default: qa
+ values:
+ - qa
+ - uat
+ - prd
+- name: k8s_namespace
+ displayName: K8s Namespace to deploy to
+ type: string
+ default: landmarkcu
+ values:
+ - landmarkcu
+
+variables:
+- template: ./config-files/${{ parameters.target_environment }}-pvars.yaml
+- name: source_branch_name
+ value: $[replace(replace(variables['Build.SourceBranch'], 'refs/heads/', ''), '/', '-')]
+- name: docker_container_repository_name
+ value: landmarkcu/lcu.api.internal.maidbot-maidbot
+
+resources:
+ repositories:
+ - repository: azdo_pipeline_templates
+ type: git
+ name: Infrastructure Platform/azdo-pipeline-templates
+ ref: main
+
+stages:
+# DOTNET BUILD AND NUGET PUSH
+- template: dotnet-nuget-build-push.yaml@azdo_pipeline_templates
+ parameters:
+ azdo_agent_pool: $(pvar_azdo_agent_pool_nuget)
+ azure_artifacts_feed: 'LCU-Internal'
+ branches_to_push:
+ - develop
+ - main
+
+# DACPAC BUILD AND PUSH
+- template: db-build-deploy.yaml@azdo_pipeline_templates
+ parameters:
+ azdo_agent_pool: $(pvar_azdo_agent_pool_db)
+ azdo_deployment_environment: $(pvar_azdo_deployment_environment_db)
+ sql_project_path: './LCU.API.Internal.MaidBotSql/LCU.API.Internal.MaidBotSql.sqlproj'
+ built_sql_dacpac_file_path: './LCU.API.Internal.MaidBotSql/bin/Release/LCU.API.Internal.MaidBotSql.dacpac'
+ name_of_artifact: LCU_API_Internal_MaidBotSql_dacpac
+ db_name: $(pvar_LCU_Api_Internal_MaidBotSql_db_name)
+ sql_server_name: $(pvar_LCU_Api_Internal_MaidBotSql_sql_server_name)
+
+# DOCKER BUILD AND PUSH
+- template: docker-build-push.yaml@azdo_pipeline_templates
+ parameters:
+ azdo_agent_pool: $(pvar_azdo_agent_pool)
+ docker_registry_service_connection: $(pvar_docker_registry_service_connection)
+ docker_container_repository_name: $(docker_container_repository_name)
+ dockerfile_path: ./Dockerfile
+ docker_image_tags: |
+ build-$(Build.BuildId)
+ $(source_branch_name)
+
+# K8S DEPLOY
+- template: k8s-deploy.yaml@azdo_pipeline_templates
+ parameters:
+ dependsOn: CI
+ condition: or(eq(variables['Build.SourceBranchName'], 'develop'), eq(variables['Build.SourceBranchName'], 'main'))
+ azdo_agent_pool: $(pvar_azdo_agent_pool)
+ kubectl_version: $(pvar_kubectl_version)
+ k8s_service_connection: $(pvar_k8s_service_connection)
+ k8s_namespace: ${{ parameters.k8s_namespace }}
+ k8s_kustomization_path: ./kustomize/overlays/${{ parameters.target_environment }}
+ docker_registry_service_connection: $(pvar_docker_registry_service_connection)
+ docker_image_to_deploy: $(docker_container_repository_name):build-$(Build.BuildId)
diff --git a/LCU.API.Internal.MaidBotApi/azdo_pipelines/azure-pipelines-feature-branches.yaml b/LCU.API.Internal.MaidBotApi/azdo_pipelines/azure-pipelines-feature-branches.yaml
new file mode 100644
index 0000000..ee44aef
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/azdo_pipelines/azure-pipelines-feature-branches.yaml
@@ -0,0 +1,10 @@
+trigger:
+ branches:
+ include:
+ - '*'
+ exclude:
+ - 'main'
+ - 'develop'
+extends:
+ template: azure-k8s-pipeline.yaml
+
diff --git a/LCU.API.Internal.MaidBotApi/azdo_pipelines/azure-pipelines-prd.yaml b/LCU.API.Internal.MaidBotApi/azdo_pipelines/azure-pipelines-prd.yaml
new file mode 100644
index 0000000..7d13684
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/azdo_pipelines/azure-pipelines-prd.yaml
@@ -0,0 +1,9 @@
+trigger:
+ branches:
+ include:
+ - 'main'
+extends:
+ template: azure-k8s-pipeline.yaml
+ parameters:
+ target_environment: prd
+ k8s_namespace: landmarkcu
diff --git a/LCU.API.Internal.MaidBotApi/azdo_pipelines/azure-pipelines-qa.yaml b/LCU.API.Internal.MaidBotApi/azdo_pipelines/azure-pipelines-qa.yaml
new file mode 100644
index 0000000..f581dd4
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/azdo_pipelines/azure-pipelines-qa.yaml
@@ -0,0 +1,9 @@
+trigger:
+ branches:
+ include:
+ - 'develop'
+extends:
+ template: azure-k8s-pipeline.yaml
+ parameters:
+ target_environment: qa
+ k8s_namespace: landmarkcu
diff --git a/LCU.API.Internal.MaidBotApi/azdo_pipelines/azure-pipelines-uat.yaml b/LCU.API.Internal.MaidBotApi/azdo_pipelines/azure-pipelines-uat.yaml
new file mode 100644
index 0000000..4747234
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/azdo_pipelines/azure-pipelines-uat.yaml
@@ -0,0 +1,9 @@
+trigger:
+ branches:
+ include:
+ - 'main'
+extends:
+ template: azure-k8s-pipeline.yaml
+ parameters:
+ target_environment: uat
+ k8s_namespace: landmarkcu
diff --git a/LCU.API.Internal.MaidBotApi/azdo_pipelines/config-files/prd-pvars.yaml b/LCU.API.Internal.MaidBotApi/azdo_pipelines/config-files/prd-pvars.yaml
new file mode 100644
index 0000000..e6fe16b
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/azdo_pipelines/config-files/prd-pvars.yaml
@@ -0,0 +1,12 @@
+---
+variables:
+ pvar_azdo_agent_pool: vmss-sub-df-lcu-apps-prd-lz-uscz
+ pvar_docker_registry_service_connection: Docker Registry LMCU
+ pvar_k8s_service_connection: svcon-sub-df-lcu-apps-prd-aks-k8s-uscz
+ pvar_kubectl_version: 1.25.9
+ pvar_azdo_agent_pool_nuget: LCUOnPrem
+ pvar_azdo_agent_pool_db: LCUOnPrem
+ pvar_azdo_deployment_environment_db: DB-PRD
+
+ pvar_LCU_Api_Internal_MaidBotSql_db_name: MaidBot
+ pvar_LCU_Api_Internal_MaidBotSql_sql_server_name: D1CLS01P
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/azdo_pipelines/config-files/qa-pvars.yaml b/LCU.API.Internal.MaidBotApi/azdo_pipelines/config-files/qa-pvars.yaml
new file mode 100644
index 0000000..afbe508
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/azdo_pipelines/config-files/qa-pvars.yaml
@@ -0,0 +1,12 @@
+---
+variables:
+ pvar_azdo_agent_pool: vmss-sub-df-lcu-apps-qa-lz-uscz
+ pvar_docker_registry_service_connection: Docker Registry LMCU
+ pvar_k8s_service_connection: svcon-sub-df-lcu-apps-qa-aks-k8s-uscz
+ pvar_kubectl_version: 1.25.9
+ pvar_azdo_agent_pool_nuget: LCUOnPrem
+ pvar_azdo_agent_pool_db: LCUOnPrem
+ pvar_azdo_deployment_environment_db: DB-QA
+
+ pvar_LCU_Api_Internal_MaidBotSql_db_name: MaidBot
+ pvar_LCU_Api_Internal_MaidBotSql_sql_server_name: DAZCDTBWEB01Q
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/azdo_pipelines/config-files/uat-pvars.yaml b/LCU.API.Internal.MaidBotApi/azdo_pipelines/config-files/uat-pvars.yaml
new file mode 100644
index 0000000..ff3441c
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/azdo_pipelines/config-files/uat-pvars.yaml
@@ -0,0 +1,12 @@
+---
+variables:
+ pvar_azdo_agent_pool: vmss-sub-df-lcu-apps-uat-lz-uscz
+ pvar_docker_registry_service_connection: Docker Registry LMCU
+ pvar_k8s_service_connection: svcon-sub-df-lcu-apps-uat-aks-k8s-uscz
+ pvar_kubectl_version: 1.25.9
+ pvar_azdo_agent_pool_nuget: LCUOnPrem
+ pvar_azdo_agent_pool_db: LCUOnPrem
+ pvar_azdo_deployment_environment_db: DB-UAT
+
+ pvar_LCU_Api_Internal_MaidBotSql_db_name: MaidBot
+ pvar_LCU_Api_Internal_MaidBotSql_sql_server_name: D1CLS01U
\ No newline at end of file
diff --git a/LCU.API.Internal.MaidBotApi/azure-pipelines.yml b/LCU.API.Internal.MaidBotApi/azure-pipelines.yml
new file mode 100644
index 0000000..a33cdae
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/azure-pipelines.yml
@@ -0,0 +1,79 @@
+# ASP.NET Core
+
+name: 'MaidBotAPI.$(Build.SourceBranchName).$(Date:yyyyMMdd)$(Rev:.r)'
+trigger:
+ branches:
+ include:
+ - '*'
+
+pool:
+ name: LCUOnPrem
+
+variables:
+ buildConfiguration: 'Release'
+
+steps:
+- task: DotNetCoreCLI@2
+ inputs:
+ command: 'restore'
+ feedsToUse: 'select'
+ vstsFeed: '58ebbac4-8e98-4281-bb3f-58003852f4a7'
+
+- task: DotNetCoreCLI@2
+ inputs:
+ command: 'build'
+ projects: '**/*.csproj'
+ arguments: '--configuration $(buildConfiguration)'
+
+- task: DotNetCoreCLI@2
+ displayName: Test
+ inputs:
+ command: test
+ projects: '**/*.Tests.csproj'
+ arguments: '--configuration $(BuildConfiguration) --collect "Code coverage" --settings additional_test_args.runsettings'
+
+- task: DotNetCoreCLI@2
+ displayName: 'dotnet publish $(buildConfiguration)'
+ inputs:
+ command: 'publish'
+ publishWebProjects: false
+ projects: '**/*.csproj'
+ arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
+
+- task: CopyFiles@2
+ displayName: Copy Nuget Package to artifact staging directory
+ inputs:
+ SourceFolder: './LCU.Api.Internal.MaidBotSeqApp/bin/$(BuildConfiguration)/'
+ Contents: '*.nupkg'
+ TargetFolder: '$(build.artifactstagingdirectory)'
+ OverWrite: true
+
+- task: NuGetCommand@2
+ condition: and (succeeded(), or( eq(variables['Build.SourceBranchName'], 'develop'), eq(variables['Build.SourceBranchName'], 'main')) )
+ displayName: 'NuGet push'
+ inputs:
+ command: push
+ packagesToPush: 'LCU.Api.Internal.MaidBotLib/bin/Release/*.nupkg'
+ publishVstsFeed: '58ebbac4-8e98-4281-bb3f-58003852f4a7'
+ allowPackageConflicts: true
+
+- task: MSBuild@1
+ displayName: Build sql project
+ inputs:
+ solution: './LCU.Api.Internal.MaidBotSql/LCU.Api.Internal.MaidBotSql.sqlproj'
+ configuration: 'Release'
+
+- task: CopyFiles@2
+ displayName: Copy dacpac to artifact staging directory
+ inputs:
+ SourceFolder: './LCU.Api.Internal.MaidBotSql/bin/$(BuildConfiguration)/'
+ Contents: 'LCU.Api.Internal.MaidBotSql.dacpac'
+ TargetFolder: '$(build.artifactstagingdirectory)'
+ OverWrite: true
+
+- task: PublishBuildArtifacts@1
+ condition: and (succeeded(), or( eq(variables['Build.SourceBranchName'], 'develop'), eq(variables['Build.SourceBranchName'], 'main')) )
+ displayName: 'Publish Artifact: dist'
+ inputs:
+ PathtoPublish: '$(build.artifactstagingdirectory)'
+ ArtifactName: 'dist'
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/base/Deployment.yaml b/LCU.API.Internal.MaidBotApi/kustomize/base/Deployment.yaml
new file mode 100644
index 0000000..3ed59bb
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/base/Deployment.yaml
@@ -0,0 +1,46 @@
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: maidbot-api
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: maidbot-api
+ strategy:
+ rollingUpdate:
+ maxSurge: 25%
+ maxUnavailable: 25%
+ type: RollingUpdate
+ template:
+ metadata:
+ labels:
+ app: maidbot-api
+ spec:
+ volumes: []
+ containers:
+ - name: maidbot-api
+ env: []
+ volumeMounts: []
+ image: maidbot-api
+ resources:
+ requests:
+ cpu: 50m
+ memory: 512Mi
+ limits:
+ memory: 512Mi
+ ports:
+ - containerPort: 8080
+ livenessProbe:
+ initialDelaySeconds: 5
+ periodSeconds: 5
+ httpGet:
+ path: /api/status
+ port: 8080
+ readinessProbe:
+ initialDelaySeconds: 5
+ periodSeconds: 5
+ httpGet:
+ path: /api/status
+ port: 8080
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/base/Service.yaml b/LCU.API.Internal.MaidBotApi/kustomize/base/Service.yaml
new file mode 100644
index 0000000..05e2845
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/base/Service.yaml
@@ -0,0 +1,11 @@
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: maidbot-api
+spec:
+ selector:
+ app: maidbot-api
+ ports:
+ - port: 8080
+ targetPort: 8080
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/base/kustomization.yaml b/LCU.API.Internal.MaidBotApi/kustomize/base/kustomization.yaml
new file mode 100644
index 0000000..28e1ae0
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/base/kustomization.yaml
@@ -0,0 +1,9 @@
+resources:
+ - Deployment.yaml
+ - Service.yaml
+ - virtual-service.yaml
+ - secrets-provider-class.yaml
+
+images:
+- name: maidbot-api
+ newName: landmarkcu/lcu.api.internal.maidbot-maidbot
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/base/secrets-provider-class.yaml b/LCU.API.Internal.MaidBotApi/kustomize/base/secrets-provider-class.yaml
new file mode 100644
index 0000000..e71a6dc
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/base/secrets-provider-class.yaml
@@ -0,0 +1,11 @@
+---
+apiVersion: secrets-store.csi.x-k8s.io/v1
+kind: SecretProviderClass
+metadata:
+ name: maidbot-api-csi-key-vault-secrets-provider
+spec:
+ provider: azure
+ parameters:
+ usePodIdentity: "false"
+ useVMManagedIdentity: "true"
+ tenantId: 63cd1554-2783-4a86-ae50-b9e6c21c9499
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/base/virtual-service.yaml b/LCU.API.Internal.MaidBotApi/kustomize/base/virtual-service.yaml
new file mode 100644
index 0000000..21a360b
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/base/virtual-service.yaml
@@ -0,0 +1,19 @@
+---
+apiVersion: networking.istio.io/v1beta1
+kind: VirtualService
+metadata:
+ name: maidbot-api
+spec:
+ hosts: []
+ gateways:
+ - istio-system/ingress-gateway
+ http:
+ - name: maidbot-api
+ match:
+ - uri:
+ prefix: /
+ route:
+ - destination:
+ host: maidbot-api
+ port:
+ number: 8080
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/components/keyvaults/normal/kustomization.yaml b/LCU.API.Internal.MaidBotApi/kustomize/components/keyvaults/normal/kustomization.yaml
new file mode 100644
index 0000000..0ae1552
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/components/keyvaults/normal/kustomization.yaml
@@ -0,0 +1,10 @@
+apiVersion: kustomize.config.k8s.io/v1alpha1
+kind: Component
+
+patches:
+ - target:
+ kind: Deployment
+ path: set_secrets_deployment.yaml
+ - target:
+ kind: SecretProviderClass
+ path: set_secrets_provider.yaml
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/components/keyvaults/normal/set_secrets_deployment.yaml b/LCU.API.Internal.MaidBotApi/kustomize/components/keyvaults/normal/set_secrets_deployment.yaml
new file mode 100644
index 0000000..fe741bf
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/components/keyvaults/normal/set_secrets_deployment.yaml
@@ -0,0 +1,41 @@
+- op: add
+ path: /spec/template/spec/volumes/-
+ value:
+ name: maidbot-api-secrets
+ csi:
+ driver: secrets-store.csi.k8s.io
+ readOnly: true
+ volumeAttributes:
+ secretProviderClass: normal-maidbot-api-csi-key-vault-secrets-provider
+
+- op: add
+ path: /spec/template/spec/containers/0/volumeMounts/-
+ value:
+ name: maidbot-api-secrets
+ mountPath: /vault/secrets/maidbot-api-seq-api-key.json
+ readOnly: true
+ subPath: maidbot-api-seq-api-key
+
+- op: add
+ path: /spec/template/spec/containers/0/volumeMounts/-
+ value:
+ name: maidbot-api-secrets
+ mountPath: /vault/secrets/maidbot-api-connection-configuration-password.json
+ readOnly: true
+ subPath: maidbot-api-connection-configuration-password
+
+- op: add
+ path: /spec/template/spec/containers/0/volumeMounts/-
+ value:
+ name: maidbot-api-secrets
+ mountPath: /vault/secrets/maidbot-api-connection-configuration-username.json
+ readOnly: true
+ subPath: maidbot-api-connection-configuration-username
+
+- op: add
+ path: /spec/template/spec/containers/0/volumeMounts/-
+ value:
+ name: maidbot-api-secrets
+ mountPath: /vault/secrets/maidbot-database-connection.json
+ readOnly: true
+ subPath: maidbot-database-connection
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/components/keyvaults/normal/set_secrets_provider.yaml b/LCU.API.Internal.MaidBotApi/kustomize/components/keyvaults/normal/set_secrets_provider.yaml
new file mode 100644
index 0000000..a76ffb7
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/components/keyvaults/normal/set_secrets_provider.yaml
@@ -0,0 +1,20 @@
+- op: add
+ path: /spec/parameters/objects
+ value: |
+ array:
+ - |
+ objectName: maidbot-api-seq-api-key
+ objectType: secret
+ objectVersion: ""
+ - |
+ objectName: maidbot-api-connection-configuration-password
+ objectType: secret
+ objectVersion: ""
+ - |
+ objectName: maidbot-api-connection-configuration-username
+ objectType: secret
+ objectVersion: ""
+ - |
+ objectName: maidbot-database-connection
+ objectType: secret
+ objectVersion: ""
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/components/workloads/normal/appsettings/appsettings.json b/LCU.API.Internal.MaidBotApi/kustomize/components/workloads/normal/appsettings/appsettings.json
new file mode 100644
index 0000000..2c63c08
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/components/workloads/normal/appsettings/appsettings.json
@@ -0,0 +1,2 @@
+{
+}
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/components/workloads/normal/kustomization.yaml b/LCU.API.Internal.MaidBotApi/kustomize/components/workloads/normal/kustomization.yaml
new file mode 100644
index 0000000..43a2aa5
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/components/workloads/normal/kustomization.yaml
@@ -0,0 +1,40 @@
+apiVersion: kustomize.config.k8s.io/v1alpha1
+kind: Component
+
+#Test istio process
+namePrefix: normal-
+
+configMapGenerator:
+ - name: maidbot-api-appsettings
+ files:
+ - appsettings/appsettings.json
+generatorOptions:
+ disableNameSuffixHash: true
+
+patches:
+ - target:
+ kind: Deployment
+ path: set_appsettings_deployment.yaml
+
+replacements:
+ - source:
+ kind: Deployment
+ fieldPath: metadata.name
+ targets:
+ - select:
+ kind: Deployment
+ fieldPaths:
+ - spec.selector.matchLabels.app
+ - spec.template.metadata.labels.app
+ - spec.template.spec.containers.*.name
+
+ - select:
+ kind: Service
+ fieldPaths:
+ - spec.selector.app
+
+ - select:
+ kind: VirtualService
+ fieldPaths:
+ - spec.http.*.route.*.destination.host
+ - spec.http.*.name
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/components/workloads/normal/set_appsettings_deployment.yaml b/LCU.API.Internal.MaidBotApi/kustomize/components/workloads/normal/set_appsettings_deployment.yaml
new file mode 100644
index 0000000..c70222c
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/components/workloads/normal/set_appsettings_deployment.yaml
@@ -0,0 +1,12 @@
+- op: add
+ path: /spec/template/spec/volumes/-
+ value:
+ name: volume-appsettings
+ configMap:
+ name: maidbot-api-appsettings
+
+- op: add
+ path: /spec/template/spec/containers/0/volumeMounts/-
+ value:
+ name: volume-appsettings
+ mountPath: /app/config/workload-appsettings
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/local/common/kustomization.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/local/common/kustomization.yaml
new file mode 100644
index 0000000..c70db7d
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/local/common/kustomization.yaml
@@ -0,0 +1,8 @@
+resources:
+ - ../../../base
+patchesStrategicMerge:
+ - set_appsettings_deployment.yaml
+ - set_secrets_provider_class.yaml
+images:
+- name: maidbot-api
+ newName: landmarkcu/lcu.api.internal.maidbot-maidbot
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/local/common/set_appsettings_deployment.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/local/common/set_appsettings_deployment.yaml
new file mode 100644
index 0000000..1edd47a
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/local/common/set_appsettings_deployment.yaml
@@ -0,0 +1,12 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: maidbot-api
+spec:
+ template:
+ spec:
+ containers:
+ - name: maidbot-api
+ env:
+ - name: ASPNETCORE_ENVIRONMENT
+ value: "Development"
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/local/common/set_secrets_provider_class.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/local/common/set_secrets_provider_class.yaml
new file mode 100644
index 0000000..2afb5ab
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/local/common/set_secrets_provider_class.yaml
@@ -0,0 +1,9 @@
+---
+apiVersion: secrets-store.csi.x-k8s.io/v1
+kind: SecretProviderClass
+metadata:
+ name: maidbot-api-csi-key-vault-secrets-provider
+spec:
+ parameters:
+ userAssignedIdentityID: 3286ed9a-f85c-4c50-b742-dbd3c1c3fe1f
+ keyvaultName: kv-lz-apps-qa-uscz
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/local/kustomization.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/local/kustomization.yaml
new file mode 100644
index 0000000..25a65b6
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/local/kustomization.yaml
@@ -0,0 +1,2 @@
+resources:
+ - normal
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/local/normal/configmap.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/local/normal/configmap.yaml
new file mode 100644
index 0000000..d640e51
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/local/normal/configmap.yaml
@@ -0,0 +1,19 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: maidbot-configmap
+data:
+ primary.json: |
+ {
+ "Primary": {
+ "SeqUrl": "https://qa-seq.lcu-internal.com",
+ "CorsAllowedOrigins": [
+ "http://localhost:3000",
+ "http://lcu-internal.com",
+ "https://lcu-internal.com",
+ "https://qa-api.lcu-internal.com",
+ "https://uat-api.lcu-internal.com",
+ "https://api.lcu-internal.com"
+ ]
+ }
+ }
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/local/normal/kustomization.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/local/normal/kustomization.yaml
new file mode 100644
index 0000000..6dd0c3b
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/local/normal/kustomization.yaml
@@ -0,0 +1,5 @@
+resources:
+ - configmap.yaml
+ - ../common
+patchesStrategicMerge:
+ - set_virtual_service.yaml
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/local/normal/set_virtual_service.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/local/normal/set_virtual_service.yaml
new file mode 100644
index 0000000..4a1ea98
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/local/normal/set_virtual_service.yaml
@@ -0,0 +1,8 @@
+---
+apiVersion: networking.istio.io/v1beta1
+kind: VirtualService
+metadata:
+ name: maidbot-api
+spec:
+ hosts:
+ - qa-maidbot-api.apps.azure.lcu-internal.com
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/prd/appsettings/appsettings.json b/LCU.API.Internal.MaidBotApi/kustomize/overlays/prd/appsettings/appsettings.json
new file mode 100644
index 0000000..420714f
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/prd/appsettings/appsettings.json
@@ -0,0 +1,27 @@
+{
+ "Primary": {
+ "AppName": "MaidBot Api",
+ "SeqUrl": "https://seq.lcu-internal.com",
+ "NotificationsApiEmail": "https://notificationsapi.lcu-internal.com/api/Email",
+ "FeatureFlags": {
+ "LoggingEventConsumer": true,
+ "RunScheduler": true
+ },
+ "BatchSchedulerCron": "*/5 * * * *",
+ "CorsAllowedOrigins": [
+ "http://lcu-internal.com",
+ "https://lcu-internal.com",
+ "https://api.lcu-internal.com"
+ ],
+ "LogEmailTemplate": "./Templates/LogEmailTemplate.html",
+ "LogEmailRowTemplate": "./Templates/LogEmailRowTemplate.html"
+ },
+ "ConnectionConfiguration": {
+ "HostUrl": "rabbitmq.lcu-internal.com"
+ },
+ "ReceiveEndpoint": {
+ "ReceiveEndpoint": "LCU.MaidBot",
+ "ReceiveEndpointType": "exchange",
+ "PrefetchLimit": 1
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/prd/component/kustomization.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/prd/component/kustomization.yaml
new file mode 100644
index 0000000..6e732a6
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/prd/component/kustomization.yaml
@@ -0,0 +1,7 @@
+apiVersion: kustomize.config.k8s.io/v1alpha1
+kind: Component
+
+patches:
+ - target:
+ kind: SecretProviderClass
+ path: set_secrets_provider_class.yaml
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/prd/component/set_secrets_provider_class.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/prd/component/set_secrets_provider_class.yaml
new file mode 100644
index 0000000..8719a77
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/prd/component/set_secrets_provider_class.yaml
@@ -0,0 +1,7 @@
+- op: add
+ path: /spec/parameters/userAssignedIdentityID
+ value: 3286ed9a-f85c-4c50-b742-dbd3c1c3fe1f
+
+- op: add
+ path: /spec/parameters/keyvaultName
+ value: kv-lz-apps-qa-uscz
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/prd/kustomization.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/prd/kustomization.yaml
new file mode 100644
index 0000000..651b146
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/prd/kustomization.yaml
@@ -0,0 +1,14 @@
+resources:
+ - normal
+
+configMapGenerator:
+ - name: maidbot-api-environment-appsettings
+ files:
+ - appsettings/appsettings.json
+generatorOptions:
+ disableNameSuffixHash: true
+
+patches:
+ - target:
+ kind: Deployment
+ path: set_configmap_deployment.yaml
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/prd/normal/kustomization.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/prd/normal/kustomization.yaml
new file mode 100644
index 0000000..a1e6b06
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/prd/normal/kustomization.yaml
@@ -0,0 +1,12 @@
+resources:
+ - ../../../base
+
+components:
+ - ../component
+ - ../../../components/keyvaults/normal
+ - ../../../components/workloads/normal
+
+patches:
+ - target:
+ kind: VirtualService
+ path: set_virtual_service.yaml
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/prd/normal/set_virtual_service.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/prd/normal/set_virtual_service.yaml
new file mode 100644
index 0000000..701049b
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/prd/normal/set_virtual_service.yaml
@@ -0,0 +1,3 @@
+- op: add
+ path: /spec/hosts/-
+ value: prd-maidbot-api.apps.azure.lcu-internal.com
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/prd/set_configmap_deployment.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/prd/set_configmap_deployment.yaml
new file mode 100644
index 0000000..e37f2cd
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/prd/set_configmap_deployment.yaml
@@ -0,0 +1,12 @@
+- op: add
+ path: /spec/template/spec/volumes/-
+ value:
+ name: volume-environment-appsettings
+ configMap:
+ name: maidbot-api-environment-appsettings
+
+- op: add
+ path: /spec/template/spec/containers/0/volumeMounts/-
+ value:
+ name: volume-environment-appsettings
+ mountPath: /app/config/environment-appsettings
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/qa/appsettings/appsettings.json b/LCU.API.Internal.MaidBotApi/kustomize/overlays/qa/appsettings/appsettings.json
new file mode 100644
index 0000000..8baa159
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/qa/appsettings/appsettings.json
@@ -0,0 +1,27 @@
+{
+ "Primary": {
+ "AppName": "MaidBot Api",
+ "SeqUrl": "https://qa-seq.lcu-internal.com",
+ "NotificationsApiEmail": "https://qa-notificationsapi.lcu-internal.com/api/Email",
+ "FeatureFlags": {
+ "LoggingEventConsumer": true,
+ "RunScheduler": true
+ },
+ "BatchSchedulerCron": "*/5 * * * *",
+ "CorsAllowedOrigins": [
+ "http://lcu-internal.com",
+ "https://lcu-internal.com",
+ "https://qa-api.lcu-internal.com"
+ ],
+ "LogEmailTemplate": "./Templates/LogEmailTemplate.html",
+ "LogEmailRowTemplate": "./Templates/LogEmailRowTemplate.html"
+ },
+ "ConnectionConfiguration": {
+ "HostUrl": "qa-rabbitmq.lcu-internal.com"
+ },
+ "ReceiveEndpoint": {
+ "ReceiveEndpoint": "LCU.MaidBot",
+ "ReceiveEndpointType": "exchange",
+ "PrefetchLimit": 1
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/qa/component/kustomization.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/qa/component/kustomization.yaml
new file mode 100644
index 0000000..6e732a6
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/qa/component/kustomization.yaml
@@ -0,0 +1,7 @@
+apiVersion: kustomize.config.k8s.io/v1alpha1
+kind: Component
+
+patches:
+ - target:
+ kind: SecretProviderClass
+ path: set_secrets_provider_class.yaml
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/qa/component/set_secrets_provider_class.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/qa/component/set_secrets_provider_class.yaml
new file mode 100644
index 0000000..8719a77
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/qa/component/set_secrets_provider_class.yaml
@@ -0,0 +1,7 @@
+- op: add
+ path: /spec/parameters/userAssignedIdentityID
+ value: 3286ed9a-f85c-4c50-b742-dbd3c1c3fe1f
+
+- op: add
+ path: /spec/parameters/keyvaultName
+ value: kv-lz-apps-qa-uscz
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/qa/kustomization.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/qa/kustomization.yaml
new file mode 100644
index 0000000..651b146
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/qa/kustomization.yaml
@@ -0,0 +1,14 @@
+resources:
+ - normal
+
+configMapGenerator:
+ - name: maidbot-api-environment-appsettings
+ files:
+ - appsettings/appsettings.json
+generatorOptions:
+ disableNameSuffixHash: true
+
+patches:
+ - target:
+ kind: Deployment
+ path: set_configmap_deployment.yaml
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/qa/normal/kustomization.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/qa/normal/kustomization.yaml
new file mode 100644
index 0000000..a1e6b06
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/qa/normal/kustomization.yaml
@@ -0,0 +1,12 @@
+resources:
+ - ../../../base
+
+components:
+ - ../component
+ - ../../../components/keyvaults/normal
+ - ../../../components/workloads/normal
+
+patches:
+ - target:
+ kind: VirtualService
+ path: set_virtual_service.yaml
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/qa/normal/set_virtual_service.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/qa/normal/set_virtual_service.yaml
new file mode 100644
index 0000000..fc8ef51
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/qa/normal/set_virtual_service.yaml
@@ -0,0 +1,3 @@
+- op: add
+ path: /spec/hosts/-
+ value: qa-maidbot-api.apps.azure.lcu-internal.com
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/qa/set_configmap_deployment.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/qa/set_configmap_deployment.yaml
new file mode 100644
index 0000000..e37f2cd
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/qa/set_configmap_deployment.yaml
@@ -0,0 +1,12 @@
+- op: add
+ path: /spec/template/spec/volumes/-
+ value:
+ name: volume-environment-appsettings
+ configMap:
+ name: maidbot-api-environment-appsettings
+
+- op: add
+ path: /spec/template/spec/containers/0/volumeMounts/-
+ value:
+ name: volume-environment-appsettings
+ mountPath: /app/config/environment-appsettings
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/uat/appsettings/appsettings.json b/LCU.API.Internal.MaidBotApi/kustomize/overlays/uat/appsettings/appsettings.json
new file mode 100644
index 0000000..63065c7
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/uat/appsettings/appsettings.json
@@ -0,0 +1,27 @@
+{
+ "Primary": {
+ "AppName": "MaidBot Api",
+ "SeqUrl": "https://uat-seq.lcu-internal.com",
+ "NotificationsApiEmail": "https://uat-notificationsapi.lcu-internal.com/api/Email",
+ "FeatureFlags": {
+ "LoggingEventConsumer": true,
+ "RunScheduler": true
+ },
+ "BatchSchedulerCron": "*/5 * * * *",
+ "CorsAllowedOrigins": [
+ "http://lcu-internal.com",
+ "https://lcu-internal.com",
+ "https://uat-api.lcu-internal.com"
+ ],
+ "LogEmailTemplate": "./Templates/LogEmailTemplate.html",
+ "LogEmailRowTemplate": "./Templates/LogEmailRowTemplate.html"
+ },
+ "ConnectionConfiguration": {
+ "HostUrl": "uat-rabbitmq.lcu-internal.com"
+ },
+ "ReceiveEndpoint": {
+ "ReceiveEndpoint": "LCU.MaidBot",
+ "ReceiveEndpointType": "exchange",
+ "PrefetchLimit": 1
+ }
+}
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/uat/component/kustomization.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/uat/component/kustomization.yaml
new file mode 100644
index 0000000..6e732a6
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/uat/component/kustomization.yaml
@@ -0,0 +1,7 @@
+apiVersion: kustomize.config.k8s.io/v1alpha1
+kind: Component
+
+patches:
+ - target:
+ kind: SecretProviderClass
+ path: set_secrets_provider_class.yaml
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/uat/component/set_secrets_provider_class.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/uat/component/set_secrets_provider_class.yaml
new file mode 100644
index 0000000..8719a77
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/uat/component/set_secrets_provider_class.yaml
@@ -0,0 +1,7 @@
+- op: add
+ path: /spec/parameters/userAssignedIdentityID
+ value: 3286ed9a-f85c-4c50-b742-dbd3c1c3fe1f
+
+- op: add
+ path: /spec/parameters/keyvaultName
+ value: kv-lz-apps-qa-uscz
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/uat/kustomization.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/uat/kustomization.yaml
new file mode 100644
index 0000000..651b146
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/uat/kustomization.yaml
@@ -0,0 +1,14 @@
+resources:
+ - normal
+
+configMapGenerator:
+ - name: maidbot-api-environment-appsettings
+ files:
+ - appsettings/appsettings.json
+generatorOptions:
+ disableNameSuffixHash: true
+
+patches:
+ - target:
+ kind: Deployment
+ path: set_configmap_deployment.yaml
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/uat/normal/kustomization.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/uat/normal/kustomization.yaml
new file mode 100644
index 0000000..a1e6b06
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/uat/normal/kustomization.yaml
@@ -0,0 +1,12 @@
+resources:
+ - ../../../base
+
+components:
+ - ../component
+ - ../../../components/keyvaults/normal
+ - ../../../components/workloads/normal
+
+patches:
+ - target:
+ kind: VirtualService
+ path: set_virtual_service.yaml
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/uat/normal/set_virtual_service.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/uat/normal/set_virtual_service.yaml
new file mode 100644
index 0000000..637a66c
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/uat/normal/set_virtual_service.yaml
@@ -0,0 +1,3 @@
+- op: add
+ path: /spec/hosts/-
+ value: uat-maidbot-api.apps.azure.lcu-internal.com
diff --git a/LCU.API.Internal.MaidBotApi/kustomize/overlays/uat/set_configmap_deployment.yaml b/LCU.API.Internal.MaidBotApi/kustomize/overlays/uat/set_configmap_deployment.yaml
new file mode 100644
index 0000000..e37f2cd
--- /dev/null
+++ b/LCU.API.Internal.MaidBotApi/kustomize/overlays/uat/set_configmap_deployment.yaml
@@ -0,0 +1,12 @@
+- op: add
+ path: /spec/template/spec/volumes/-
+ value:
+ name: volume-environment-appsettings
+ configMap:
+ name: maidbot-api-environment-appsettings
+
+- op: add
+ path: /spec/template/spec/containers/0/volumeMounts/-
+ value:
+ name: volume-environment-appsettings
+ mountPath: /app/config/environment-appsettings