Adding MaidBotApi

This commit is contained in:
Zeeshaun
2026-03-11 13:21:47 -05:00
parent f3cb730f3b
commit f004c8205a
122 changed files with 4227 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
**/*.bat
**/*.log
**/*.md
**/*.yml
**/.dockerignore
**/.editorconfig
**/.git
**/.gitattributes
**/.gitignore
**/.vs
**/.vscode
**/bin
**/dist
**/dockerfile
**/node_modules
**/obj
**/package-lock.json
+330
View File
@@ -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/
+49
View File
@@ -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' \
'<?xml version="1.0" encoding="utf-8"?>' \
'<configuration>' \
' <packageSources>' \
' <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />' \
' <add key="LCU-Internal" value="https://landmarkcu.pkgs.visualstudio.com/_packaging/LCU-Internal/nuget/v3/index.json" />' \
' </packageSources>' \
' <packageSourceCredentials>' \
' <LCU-Internal>' \
' <add key="Username" value="AzureDevOps" />' \
' <add key="ClearTextPassword" value="'"${PAT}"'" />' \
' </LCU-Internal>' \
' </packageSourceCredentials>' \
'</configuration>' \
> /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"]
@@ -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
@@ -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; }
}
@@ -0,0 +1,7 @@
namespace LCU.Api.Internal.MaidBotApi.Configurations;
public class FeatureFlags
{
public bool LoggingEventConsumer { get; set; }
public bool RunScheduler { get; set; }
}
@@ -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<EventLog>
{
private readonly IServiceScopeFactory _scopeFactory;
public LoggingEventConsumer(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public async Task Consume(ConsumeContext<EventLog> context)
{
try
{
using var scope = _scopeFactory.CreateScope();
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IEventLogService>().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;
}
}
}
@@ -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<IActionResult> 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<EventLog> 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);
}
}
}
@@ -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<LoggingMatrix>), 200)]
[ProducesResponseType(500)]
public async Task<ActionResult> 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<string>), 400)]
[ProducesResponseType(500)]
public async Task<ActionResult> 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<IActionResult> 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);
}
}
}
@@ -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<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> 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);
}
}
}
@@ -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);
}
}
}
@@ -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);
}
@@ -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);
}
}
@@ -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();
}
}
@@ -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; }
}
}
@@ -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)
{
}
}
}
@@ -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.") { }
}
}
@@ -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<ILogDistributionService>().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.");
}
}
@@ -0,0 +1,47 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
<AssemblyVersion>1.0.0</AssemblyVersion>
<FileVersion>1.0.0</FileVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>..\LCU.API.Internal.MaidBotApi\LCU.Api.Internal.MaidBotApi.xml</DocumentationFile>
<NoWarn>1701;1702;1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DocumentationFile>..\LCU.API.Internal.MaidBotApi\LCU.Api.Internal.MaidBotApi.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LCU.Library.Internal.Exceptions" Version="1.0.0" />
<PackageReference Include="LCU.Library.Internal.LcuHttpClient" Version="2.0.1" />
<PackageReference Include="LCU.Library.Internal.LcuMassTransit" Version="1.0.0" />
<PackageReference Include="LCU.Library.Internal.LcuMassTransitModels" Version="1.1.0" />
<PackageReference Include="LCU.Library.Internal.Logging" Version="2.1.0" />
<PackageReference Include="LCU.Shared.Common.NotificationApiLib" Version="1.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.14" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.14" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" />
<PackageReference Include="ncrontab" Version="3.3.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LCU.Api.Internal.MaidBotLib\LCU.Api.Internal.MaidBotLib.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Templates\LogEmailRowTemplate.html">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Templates\LogEmailTemplate.html">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>LCU.Api.Internal.MaidBotApi</name>
</assembly>
<members>
<member name="T:LCU.Api.Internal.MaidBotApi.Exceptions.NotFoundException">
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.
</member>
<member name="T:LCU.Api.Internal.MaidBotApi.Exceptions.ServerErrorException">
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).
</member>
</members>
</doc>
@@ -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<AppDbContext>(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<IEnvironmentView, EnvironmentView>();
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<SchedulerHostedService>();
// 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<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
}
static void AddMaidBotServices(ref IServiceCollection services)
{
services.AddScoped<ILogDistributionService, LogDistributionService>();
services.AddScoped<IQueueEventLogService, QueueEventLogService>();
services.AddScoped<INotificationsApiService, NotificationsApiService>();
services.AddScoped<ILoggingMatrixService, LoggingMatrixService>();
services.AddScoped<IEventLogService, EventLogService>();
services.AddScoped<IDebugEventLogService, DebugEventLogService>();
}
static void AddMaidBotLib(ref IServiceCollection services)
{
services.AddScoped<ILoggingMatrixRepository, LoggingMatrixRepository>();
services.AddScoped<IEventLogRepository, EventLogRepository>();
services.AddScoped<IDebugEventLogRepository, DebugEventLogRepository>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
}
static void AddMaidBotDataAccesses(ref IServiceCollection services)
{
services.AddScoped<INotificationsApiDao, NotificationsApiDao>();
}
static void AddMassTransitServices(ref IServiceCollection services,
ConnectionConfiguration connectionConfiguration,
ReceiveEndpointConfiguration receiveEndpointConfiguration,
ApiConfiguration apiConfiguration)
{
services.AddMassTransit(x =>
{
x.AddConsumer<LoggingEventConsumer>();
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<LoggingEventConsumer>(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<RequestResponseLoggingMiddleware>();
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();
}
}
@@ -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; }
}
}
@@ -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<AzureActiveDirectoryOptions> configureOptions)
{
builder.Services.Configure(configureOptions);
builder.Services.AddSingleton<IConfigureOptions<JwtBearerOptions>, ConfigureAzureOptions>();
builder.AddJwtBearer();
return builder;
}
private class ConfigureAzureOptions : IConfigureNamedOptions<JwtBearerOptions>
{
private readonly AzureActiveDirectoryOptions _azureOptions;
public ConfigureAzureOptions(IOptions<AzureActiveDirectoryOptions> 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);
}
}
}
}
@@ -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<DebugEventLog> 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.");
}
}
}
@@ -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<IEnumerable<EventLog>> 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<EventLog> 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<EventLog> 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}.");
}
}
}
@@ -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<DebugEventLog> SaveAsync(DebugEventLog debugEventLog);
}
@@ -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<IEnumerable<EventLog>> GetAsync();
Task<EventLog> SaveAsync(EventLog eventLog);
void DeleteMany(List<EventLog> eventLogs);
}
@@ -0,0 +1,7 @@
using System.Threading.Tasks;
namespace LCU.Api.Internal.MaidBotApi.Services;
public interface ILogDistributionService
{
Task HandleLogDistribution();
}
@@ -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<IEnumerable<LoggingMatrix>> GetAsync();
Task<IEnumerable<LoggingMatrix>> GetActiveAsync();
Task<LoggingMatrix> GetByIdAsync(int id);
Task<IEnumerable<LoggingMatrix>> GetByApplicationLevelAndGroupAsync(string application, string level, string group);
Task<IEnumerable<LoggingMatrix>> GetByApplicationAndLevelAsync(string application, string level);
Task<LoggingMatrix> SaveAsync(LoggingMatrix loggingMatrix);
Task<LoggingMatrix> UpdateAsync(LoggingMatrix loggingMatrix);
}
}
@@ -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<EventLog> eventLogs);
}
@@ -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);
}
@@ -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.");
}
}
}
@@ -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<IEnumerable<LoggingMatrix>> 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<IEnumerable<LoggingMatrix>> 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<IEnumerable<LoggingMatrix>> 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<IEnumerable<LoggingMatrix>> 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<LoggingMatrix> 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<LoggingMatrix> 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<LoggingMatrix> 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}.");
}
}
}
@@ -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<EventLog> eventLogs)
{
try
{
var emailRequest = new EmailRequest
{
Recipient = new List<string> { 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<EventLog> 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;
}
}
@@ -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.");
}
}
}
@@ -0,0 +1,4 @@
<tr id="errorRow" style="height: 22px;">
<td style="max-width: 50px;">{LogTimeStamp}</td>
<td style="max-width: 300px;">{Message}</td>
</tr>
@@ -0,0 +1,56 @@
<!DOCTYPE html>
<html>
<head>
<style>
#errors {
font-family: Arial, Helvetica, sans-serif;
border-collapse: collapse;
width: 100%;
}
#errors td, #errors th {
border: 1px solid #ddd;
padding: 8px;
}
#errors tr:nth-child(even) {
background-color: #f2f2f2;
}
#errors th {
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
background-color: #4CAF50;
color: white;
}
#emailHeader {
color: #0054a2;
}
#errorTableHeader {
background-color: #0054a2;
color: white;
}
</style>
<meta charset="utf-8">
<title>MaidBot: {Application} Event Logs</title>
</head>
<body>
<h1><span id="emailHeader">MaidBot: {Application} {Level} Event Logs</span></h1>
<table id="errors">
<thead id="errorTableHeader">
<tr style="height: 17px;">
<td style="max-width: 50px;font-weight: bolder;">Timestamp</td>
<td style="max-width: 300px; font-weight: bolder;">Log Details</td>
</tr>
</thead>
<tbody id="errorRows">
{TableRows}
</tbody>
</table>
</body>
</html>
@@ -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"
}
}
}
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.0.2</Version>
<Description>
1.0.0 - Initial Creation
1.0.1 - Add BusinessOperations Group Enum
1.0.2 - Add IndirectLending Group Enum
</Description>
<PackageReleaseNotes>
1.0.0 - Initial Creation
1.0.1 - Add BusinessOperations Group Enum
1.0.2 - Add IndirectLending Group Enum
</PackageReleaseNotes>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.10" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />
</ItemGroup>
</Project>
@@ -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; }
}
}
@@ -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; }
}
}
@@ -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
}
}
@@ -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; }
}
}
@@ -0,0 +1,7 @@
namespace LCU.Api.Internal.MaidBotLib.Models
{
public enum PropertyEnum
{
Group
}
}
@@ -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<LoggingMatrix> LoggingMatrixs { get; set; }
public DbSet<EventLog> EventLogs { get; set; }
public DbSet<DebugEventLog> DebugEventLogs { get; set; }
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<LoggingMatrix>().ToTable("LoggingMatrix");
builder.Entity<EventLog>().ToTable("EventLog");
builder.Entity<DebugEventLog>().ToTable("DebugEventLog");
}
}
}
@@ -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;
}
}
}
@@ -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<DebugEventLog> AddAsync(DebugEventLog debugEventLog)
{
var result = await _context.DebugEventLogs.AddAsync(debugEventLog);
return result.Entity;
}
}
}
@@ -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<IEnumerable<EventLog>> ListAsync()
{
return await _context.EventLogs.ToListAsync();
}
public async Task<EventLog> AddAsync(EventLog eventLog)
{
var result = await _context.EventLogs.AddAsync(eventLog);
return result.Entity;
}
public void RemoveRange(List<EventLog> eventLog)
{
_context.EventLogs.RemoveRange(eventLog);
}
}
}
@@ -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<DebugEventLog> AddAsync(DebugEventLog debugEventLog);
}
}
@@ -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<IEnumerable<EventLog>> ListAsync();
Task<EventLog> AddAsync(EventLog eventLog);
void RemoveRange(List<EventLog> eventLog);
}
}
@@ -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<IEnumerable<LoggingMatrix>> ListAsync();
Task<IEnumerable<LoggingMatrix>> ListActiveAsync();
Task<LoggingMatrix> FindByIdAsync(int id);
Task<IEnumerable<LoggingMatrix>> RetrieveByApplicationLevelAndGroup(string application, string level, string group);
Task<IEnumerable<LoggingMatrix>> RetrieveByApplciationAndLevel(string application, string level);
Task<LoggingMatrix> AddAsync(LoggingMatrix loggingMatrix);
void Update(LoggingMatrix loggingMatrix);
}
}
@@ -0,0 +1,10 @@
using System.Threading.Tasks;
namespace LCU.Api.Internal.MaidBotLib.Repositories
{
public interface IUnitOfWork
{
Task CompleteAsync();
void Complete();
}
}
@@ -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<IEnumerable<LoggingMatrix>> ListAsync()
{
return await _context.LoggingMatrixs.ToListAsync();
}
public async Task<IEnumerable<LoggingMatrix>> ListActiveAsync()
{
return await _context.LoggingMatrixs.Where(loggingMatrix => !loggingMatrix.Disabled).ToListAsync();
}
public async Task<LoggingMatrix> FindByIdAsync(int id)
{
return await _context.LoggingMatrixs.Where(loggingMatrix => loggingMatrix.LoggingMatrixId == id).FirstOrDefaultAsync();
}
public async Task<IEnumerable<LoggingMatrix>> 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<IEnumerable<LoggingMatrix>> RetrieveByApplciationAndLevel(string application, string level)
{
return await _context.LoggingMatrixs
.Where(matrix => matrix.Application == application &&
matrix.Level == level &&
!matrix.Disabled)
.ToListAsync();
}
public async Task<LoggingMatrix> AddAsync(LoggingMatrix loggingMatrix)
{
var result = await _context.LoggingMatrixs.AddAsync(loggingMatrix);
return result.Entity;
}
public void Update(LoggingMatrix loggingMatrix)
{
_context.LoggingMatrixs.Update(loggingMatrix);
}
}
}
@@ -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();
}
}
}
@@ -0,0 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Description>LCU.Api.Internal.MaidBotSeqApp</Description>
<Version>1.0.0</Version>
<RunPostBuildEvent>Always</RunPostBuildEvent>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.9" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="Seq.Apps" Version="5.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LCU.Api.Internal.MaidBotLib\LCU.Api.Internal.MaidBotLib.csproj" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup>
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
</PropertyGroup>
<Target Name="CopyProjectReferencesToPackage" DependsOnTargets="BuildOnlySettings;ResolveReferences">
<ItemGroup>
<!-- Filter out unnecessary files -->
<_ReferenceCopyLocalPaths Include="@(ReferenceCopyLocalPaths-&gt;WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')-&gt;WithMetadataValue('PrivateAssets', 'All'))" />
</ItemGroup>
<!-- Print batches for debug purposes -->
<Message Text="Batch for .nupkg: ReferenceCopyLocalPaths = @(_ReferenceCopyLocalPaths), ReferenceCopyLocalPaths.DestinationSubDirectory = %(_ReferenceCopyLocalPaths.DestinationSubDirectory) Filename = %(_ReferenceCopyLocalPaths.Filename) Extension = %(_ReferenceCopyLocalPaths.Extension)" Importance="High" Condition="'@(_ReferenceCopyLocalPaths)' != ''" />
<ItemGroup>
<!-- Add file to package with consideration of sub folder. If empty, the root folder is chosen. -->
<BuildOutputInPackage Include="@(_ReferenceCopyLocalPaths)" TargetPath="%(_ReferenceCopyLocalPaths.DestinationSubDirectory)" />
</ItemGroup>
</Target>
</Project>
@@ -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
{
/// <summary>
/// Queues LogEvent.
/// </summary>
[SeqApp("QueueLogs", Description = "This will queue logs based on a signal event")]
public class SeqToRabbit : SeqApp, ISubscribeTo<LogEventData>
{
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<LogEventData> evt)
{
EventLog logRequest = CreateLogRequest(evt);
if(logRequest == null)
{
return;
}
QueueLogEvent(logRequest);
}
public bool MissingRequiredFields(Event<LogEventData> 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<LogEventData> 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.");
}
}
}
}
@@ -0,0 +1,5 @@
CREATE TABLE [dbo].[DebugEventLog]
(
[DebugEventLogId] INT NOT NULL PRIMARY KEY IDENTITY,
[RawLog] VARCHAR(3000) NULL
)
@@ -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
)
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<Operations Version="1.0" xmlns="http://schemas.microsoft.com/sqlserver/dac/Serialization/2012/02">
<Operation Name="Rename Refactor" Key="f8dee825-d461-43d7-a087-e525edf88ec9" ChangeDateTime="10/19/2020 20:57:13">
<Property Name="ElementName" Value="[dbo].[LoggingMatrix].[Id]" />
<Property Name="ElementType" Value="SqlSimpleColumn" />
<Property Name="ParentElementName" Value="[dbo].[LoggingMatrix]" />
<Property Name="ParentElementType" Value="SqlTable" />
<Property Name="NewName" Value="LoggingMatrixId" />
</Operation>
<Operation Name="Rename Refactor" Key="640cc9fc-bf7e-4fc8-9e7a-d2be37aed364" ChangeDateTime="10/19/2020 20:58:44">
<Property Name="ElementName" Value="[dbo].[LoggingMatrix].[Group]" />
<Property Name="ElementType" Value="SqlSimpleColumn" />
<Property Name="ParentElementName" Value="[dbo].[LoggingMatrix]" />
<Property Name="ParentElementType" Value="SqlTable" />
<Property Name="NewName" Value="Groups" />
</Operation>
<Operation Name="Rename Refactor" Key="76e86eb1-05aa-4265-82bf-d15789e45591" ChangeDateTime="10/20/2020 17:47:01">
<Property Name="ElementName" Value="[dbo].[LoggingMatrix].[Groups]" />
<Property Name="ElementType" Value="SqlSimpleColumn" />
<Property Name="ParentElementName" Value="[dbo].[LoggingMatrix]" />
<Property Name="ParentElementType" Value="SqlTable" />
<Property Name="NewName" Value="Group" />
</Operation>
<Operation Name="Rename Refactor" Key="af161f35-aff3-44c9-a19a-2f4f4959965d" ChangeDateTime="10/20/2020 17:47:03">
<Property Name="ElementName" Value="[dbo].[LoggingMatrix].[Emails]" />
<Property Name="ElementType" Value="SqlSimpleColumn" />
<Property Name="ParentElementName" Value="[dbo].[LoggingMatrix]" />
<Property Name="ParentElementType" Value="SqlTable" />
<Property Name="NewName" Value="Email" />
</Operation>
<Operation Name="Rename Refactor" Key="6122a340-340c-4e01-a932-b1cafcccd65c" ChangeDateTime="10/23/2020 13:36:33">
<Property Name="ElementName" Value="[dbo].[DebugEventLogs].[Id]" />
<Property Name="ElementType" Value="SqlSimpleColumn" />
<Property Name="ParentElementName" Value="[dbo].[DebugEventLogs]" />
<Property Name="ParentElementType" Value="SqlTable" />
<Property Name="NewName" Value="DebugEventLogId" />
</Operation>
<Operation Name="Rename Refactor" Key="8d26ed1b-e4e8-4636-ab40-ac133403cdb4" ChangeDateTime="07/11/2022 23:59:06">
<Property Name="ElementName" Value="[dbo].[EventLog].[Id]" />
<Property Name="ElementType" Value="SqlSimpleColumn" />
<Property Name="ParentElementName" Value="[dbo].[EventLog]" />
<Property Name="ParentElementType" Value="SqlTable" />
<Property Name="NewName" Value="EventLogId" />
</Operation>
</Operations>
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<Name>LCU.Api.Internal.MaidBotSql</Name>
<SchemaVersion>2.0</SchemaVersion>
<ProjectVersion>4.1</ProjectVersion>
<ProjectGuid>{ecce6677-a2f2-4694-964f-f91ebe7aac00}</ProjectGuid>
<DSP>Microsoft.Data.Tools.Schema.Sql.Sql130DatabaseSchemaProvider</DSP>
<OutputType>Database</OutputType>
<RootPath>
</RootPath>
<RootNamespace>LCU.Api.Internal.MaidBotSql</RootNamespace>
<AssemblyName>LCU.Api.Internal.MaidBotSql</AssemblyName>
<ModelCollation>1033, CI</ModelCollation>
<DefaultFileStructure>BySchemaAndSchemaType</DefaultFileStructure>
<DeployToDatabase>True</DeployToDatabase>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<TargetLanguage>CS</TargetLanguage>
<AppDesignerFolder>Properties</AppDesignerFolder>
<SqlServerVerification>False</SqlServerVerification>
<IncludeCompositeObjects>True</IncludeCompositeObjects>
<TargetDatabaseSet>True</TargetDatabaseSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<OutputPath>bin\Release\</OutputPath>
<BuildScriptName>$(MSBuildProjectName).sql</BuildScriptName>
<TreatWarningsAsErrors>False</TreatWarningsAsErrors>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<DefineDebug>false</DefineDebug>
<DefineTrace>true</DefineTrace>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<OutputPath>bin\Debug\</OutputPath>
<BuildScriptName>$(MSBuildProjectName).sql</BuildScriptName>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<DefineDebug>true</DefineDebug>
<DefineTrace>true</DefineTrace>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">11.0</VisualStudioVersion>
<!-- Default to the v11.0 targets path if the targets file for the current VS version is not found -->
<SSDTExists Condition="Exists('$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets')">True</SSDTExists>
<VisualStudioVersion Condition="'$(SSDTExists)' == ''">11.0</VisualStudioVersion>
</PropertyGroup>
<Import Condition="'$(SQLDBExtensionsRefPath)' != ''" Project="$(SQLDBExtensionsRefPath)\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
<Import Condition="'$(SQLDBExtensionsRefPath)' == ''" Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
<ItemGroup>
<Folder Include="Properties" />
</ItemGroup>
<ItemGroup>
<Build Include="LoggingMatrix.sql" />
<Build Include="DebugEventLog.sql" />
<Build Include="EventLog.sql" />
</ItemGroup>
<ItemGroup>
<RefactorLog Include="LCU.Api.Internal.MaidBotSql.refactorlog" />
</ItemGroup>
</Project>
@@ -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
)
@@ -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<ILoggingMatrixService> _mockLoggingMatrixService;
public LoggingMatrixControllerTest()
{
_mockLoggingMatrixService = new Mock<ILoggingMatrixService>();
_loggingMatrixController = new LoggingMatrixController(_mockLoggingMatrixService.Object);
}
[Fact]
public async void GetAllLoggingMatrix_Success_200()
{
// Arrange
_mockLoggingMatrixService.Setup(service => service.GetAsync()).ReturnsAsync(new List<LoggingMatrix>
{
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<LoggingMatrix>;
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);
}
}
@@ -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<IQueueEventLogService> _mockQueueEventLogService;
public QueueEventLogControllerTest()
{
_mockQueueEventLogService = new Mock<IQueueEventLogService>();
_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);
}
}
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.6" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LCU.API.Internal.MaidBotApi\LCU.Api.Internal.MaidBotApi.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="RepositoryTests\LoggingMatrixTestData.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -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);
}
}
}
@@ -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<LoggingMatrix> 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<List<LoggingMatrix>>();
return testSet;
}
public AppDbContext GetBlankContext()
{
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
return new AppDbContext(options);
}
public AppDbContext GetContextWithData(string jsonFileName)
{
var options = new DbContextOptionsBuilder<AppDbContext>()
.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<T>(DbSet<T> collection, IEnumerable<T> range) where T : class
{
if (range != null) collection.AddRange(range);
}
}
}
@@ -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());
//}
}
}
@@ -0,0 +1,52 @@
{
"LoggingMatrixs": [
{
"LoggingmatrixId": 1,
"Application": "Test App 1",
"Level": "Test Level 1",
"Group": "Test Group 1",
"Email": "[email protected]",
"Disabled": false
},
{
"LoggingmatrixId": 2,
"Application": "Test App 2",
"Level": "Test Level 2",
"Group": "Test Group 2",
"Email": "[email protected]",
"Disabled": false
},
{
"LoggingmatrixId": 3,
"Application": "Test App 3",
"Level": "Test Level 3",
"Group": "Test Group 3",
"Email": "[email protected]",
"Disabled": true
},
{
"LoggingmatrixId": 4,
"Application": "Test App 1",
"Level": "Test Level 1",
"Group": "Test Group 1",
"Email": "[email protected]",
"Disabled": false
},
{
"LoggingmatrixId": 5,
"Application": "Test App 1",
"Level": "Test Level 1",
"Group": "Test Group 1",
"Email": "[email protected]",
"Disabled": true
},
{
"LoggingmatrixId": 6,
"Application": "Test App 1",
"Level": "Test Level 1",
"Group": "Test Group 6",
"Email": "[email protected]",
"Disabled": false
}
]
}
@@ -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<IDebugEventLogRepository> _mockDebugEventLogRepository;
public DebugEventLogServiceTest()
{
var mockUnitOfWork = new Mock<IUnitOfWork>();
_mockDebugEventLogRepository = new Mock<IDebugEventLogRepository>();
_debugEventLogService = new DebugEventLogService(mockUnitOfWork.Object, _mockDebugEventLogRepository.Object);
}
[Fact]
public async Task SaveAsync_Success()
{
// Arrange
_mockDebugEventLogRepository.Setup(repo => repo.AddAsync(It.IsAny<DebugEventLog>())).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<DebugEventLog>())).ThrowsAsync(new Exception());
// Act / Assert
await Assert.ThrowsAsync<ServiceException>(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<DebugEventLog>()), Times.Once);
}
[Fact]
public async Task DebugEventLog_Verify_AddAsyncCalled_Never()
{
// Arrange
var testEventLog = new EventLog() { };
_mockDebugEventLogRepository.Setup(repo => repo.AddAsync(It.IsAny<DebugEventLog>())).ReturnsAsync(new DebugEventLog());
var testDebugEventLog = new DebugEventLog();
//Act
await _debugEventLogService.DebugEventLog(testEventLog);
//Assert
_mockDebugEventLogRepository.Verify(x => x.AddAsync(testDebugEventLog), Times.Never);
}
}
@@ -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<IEventLogRepository> _mockEventLogRepository;
private readonly Mock<IDebugEventLogService> _mockDebugEventLogService;
private readonly Mock<ILoggingMatrixService> _mockLoggingMatrixService;
private readonly Mock<IUnitOfWork> _mockUnitOfWork;
public EventLogServiceTest()
{
_mockEventLogRepository = new Mock<IEventLogRepository>();
_mockDebugEventLogService = new Mock<IDebugEventLogService>();
_mockLoggingMatrixService = new Mock<ILoggingMatrixService>();
_mockUnitOfWork = new Mock<IUnitOfWork>();
_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<LoggingMatrix>
{
new LoggingMatrix
{
LoggingMatrixId = 1,
Application = "Test App",
Level = "Test Level",
Group = "Test Group",
Email = "[email protected]",
Disabled = false
}
};
_mockLoggingMatrixService.Setup(x => x.GetByApplicationLevelAndGroupAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).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<EventLog>())).ReturnsAsync(eventLogResult);
_mockDebugEventLogService.Setup(x => x.DebugEventLog(It.IsAny<EventLog>()));
//Act
await _eventLogService.HandleIncomingEventLog(testEventLog);
//Assert
_mockLoggingMatrixService.Verify(x => x.GetByApplicationLevelAndGroupAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once);
_mockEventLogRepository.Verify(x => x.AddAsync(It.IsAny<EventLog>()), Times.Once);
_mockDebugEventLogService.Verify(x => x.DebugEventLog(It.IsAny<EventLog>()), 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<LoggingMatrix>();
_mockLoggingMatrixService.Setup(x => x.GetByApplicationLevelAndGroupAsync(It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>())).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<EventLog>())).ReturnsAsync(eventLogResult);
_mockDebugEventLogService.Setup(x => x.DebugEventLog(It.IsAny<EventLog>()));
//Act
await _eventLogService.HandleIncomingEventLog(testEventLog);
//Assert
_mockLoggingMatrixService.Verify(x => x.GetByApplicationLevelAndGroupAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once);
_mockEventLogRepository.Verify(x => x.AddAsync(It.IsAny<EventLog>()), Times.Never);
_mockDebugEventLogService.Verify(x => x.DebugEventLog(It.IsAny<EventLog>()), 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<LoggingMatrix>();
_mockLoggingMatrixService.Setup(x => x.GetByApplicationLevelAndGroupAsync(It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>())).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<EventLog>())).ReturnsAsync(eventLogResult);
_mockDebugEventLogService.Setup(x => x.DebugEventLog(It.IsAny<EventLog>()));
//Act
await _eventLogService.HandleIncomingEventLog(testEventLog);
//Assert
_mockLoggingMatrixService.Verify(x => x.GetByApplicationLevelAndGroupAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once);
_mockEventLogRepository.Verify(x => x.AddAsync(It.IsAny<EventLog>()), Times.Never);
_mockDebugEventLogService.Verify(x => x.DebugEventLog(It.IsAny<EventLog>()), Times.Once);
}
}
@@ -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<IEventLogService> _mockEventLogService;
private readonly Mock<ILoggingMatrixService> _mockLoggingMatrixService;
private readonly Mock<INotificationsApiService> _notificationsApiService;
public LogDistributionServiceTest()
{
_mockEventLogService = new Mock<IEventLogService>();
_mockLoggingMatrixService = new Mock<ILoggingMatrixService>();
_notificationsApiService = new Mock<INotificationsApiService>();
_logDistributionService = new LogDistributionService(_mockEventLogService.Object,
_mockLoggingMatrixService.Object,
_notificationsApiService.Object);
}
[Fact]
public async Task HandleLogDistribution_NoEventLogs()
{
var eventLogs = new List<EventLog>();
_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<LoggingMatrix>(), It.IsAny<List<EventLog>>()), Times.Never);
_mockEventLogService.Verify(x => x.DeleteMany(It.IsAny<List<EventLog>>()), Times.Never);
}
[Fact]
public async Task HandleLogDistribution_EventLogs_NoMatrix()
{
var eventLogs = new List<EventLog>
{
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<LoggingMatrix>();
_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<LoggingMatrix>(), It.IsAny<List<EventLog>>()), Times.Never);
_mockEventLogService.Verify(x => x.DeleteMany(It.IsAny<List<EventLog>>()), Times.Never);
}
[Fact]
public async Task HandleLogDistribution_EventLogs_LoggingMatrix_SendEmail()
{
var eventLogs = new List<EventLog>
{
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<LoggingMatrix>
{
new LoggingMatrix
{
LoggingMatrixId = 1,
Application = "Test App",
Level = "Test Level",
Group = "Test Group",
Email = "[email protected]",
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<LoggingMatrix>(), It.IsAny<List<EventLog>>()), Times.Once);
_mockEventLogService.Verify(x => x.DeleteMany(It.IsAny<List<EventLog>>()), Times.Once);
}
}
@@ -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<ILoggingMatrixRepository> _mockLoggingMatrixRepository;
public LoggingMatrixServiceTest()
{
var mockUnitOfWork = new Mock<IUnitOfWork>();
_mockLoggingMatrixRepository = new Mock<ILoggingMatrixRepository>();
_loggingMatrixService = new LoggingMatrixService(mockUnitOfWork.Object, _mockLoggingMatrixRepository.Object);
}
[Fact]
public async Task GetAsync_Success()
{
// Arrange
_mockLoggingMatrixRepository.Setup(repo => repo.ListAsync()).ReturnsAsync(new List<LoggingMatrix>()
{
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<ServiceException>(async () => await _loggingMatrixService.GetAsync());
}
[Fact]
public async Task SaveAsync_Success()
{
// Arrange
_mockLoggingMatrixRepository.Setup(repo => repo.AddAsync(It.IsAny<LoggingMatrix>())).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<LoggingMatrix>())).ThrowsAsync(new Exception());
// Act / Assert
await Assert.ThrowsAsync<ServiceException>(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 = "[email protected]",
Disabled = false
};
_mockLoggingMatrixRepository.Setup(repo => repo.FindByIdAsync(It.IsAny<int>())).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);
}
}
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- additional settings used during the dotnet test execution step in the azure dev ops pipeline -->
<RunSettings>
<DataCollectionRunSettings>
<DataCollectors>
<DataCollector friendlyName="Code Coverage" uri="datacollector://Microsoft/CodeCoverage/2.0" assemblyQualifiedName="Microsoft.VisualStudio.Coverage.DynamicCoverageDataCollector, Microsoft.VisualStudio.TraceCollector, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<Configuration>
<CodeCoverage>
<CompanyNames>
<Exclude>
<CompanyName>.*microsoft.*</CompanyName>
</Exclude>
</CompanyNames>
</CodeCoverage>
</Configuration>
</DataCollector>
</DataCollectors>
</DataCollectionRunSettings>
</RunSettings>
+41
View File
@@ -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.
@@ -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)
@@ -0,0 +1,10 @@
trigger:
branches:
include:
- '*'
exclude:
- 'main'
- 'develop'
extends:
template: azure-k8s-pipeline.yaml
@@ -0,0 +1,9 @@
trigger:
branches:
include:
- 'main'
extends:
template: azure-k8s-pipeline.yaml
parameters:
target_environment: prd
k8s_namespace: landmarkcu
@@ -0,0 +1,9 @@
trigger:
branches:
include:
- 'develop'
extends:
template: azure-k8s-pipeline.yaml
parameters:
target_environment: qa
k8s_namespace: landmarkcu
@@ -0,0 +1,9 @@
trigger:
branches:
include:
- 'main'
extends:
template: azure-k8s-pipeline.yaml
parameters:
target_environment: uat
k8s_namespace: landmarkcu
@@ -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
@@ -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
@@ -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
@@ -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'
@@ -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
@@ -0,0 +1,11 @@
---
apiVersion: v1
kind: Service
metadata:
name: maidbot-api
spec:
selector:
app: maidbot-api
ports:
- port: 8080
targetPort: 8080
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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: ""
@@ -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
@@ -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
@@ -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
@@ -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"
@@ -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
@@ -0,0 +1,2 @@
resources:
- normal
@@ -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"
]
}
}
@@ -0,0 +1,5 @@
resources:
- configmap.yaml
- ../common
patchesStrategicMerge:
- set_virtual_service.yaml

Some files were not shown because too many files have changed in this diff Show More