From f732578fb50941a9793662f1467caeff5a8828e7 Mon Sep 17 00:00:00 2001 From: Tudor Stanciu Date: Sat, 28 Jan 2023 22:31:54 +0000 Subject: [PATCH] Merged PR 69: Network resurrector notification sistem - Added messaging - Removed Netmash.Application.DataContracts dependency - removed local CorrelationManager - release notes - agent GetSystemVersion query update - Server SystemController - Network resurrector notification sistem - NotificationService update - NotificationContext: rename MachineStatus to ActionStatus - added readme file - config update - configs - release notes update --- NetworkResurrector.sln | 1 + README.md | 79 ++++++++++ ReleaseNotes.xml | 10 +- dependencies.props | 5 +- ...etworkResurrector.Agent.Application.csproj | 1 - .../Queries/GetSystemVersion.cs | 35 +++-- .../Commands/Cancel.cs | 4 +- .../Commands/Lock.cs | 4 +- .../Commands/Logout.cs | 4 +- .../Commands/Restart.cs | 4 +- .../Commands/Shutdown.cs | 4 +- .../Commands/Sleep.cs | 4 +- ...Resurrector.Agent.PublishedLanguage.csproj | 2 +- .../NetworkResurrector.Agent.csproj | 1 - .../CommandHandlers/ShutdownMachineHandler.cs | 13 +- .../CommandHandlers/WakeMachineHandler.cs | 11 +- .../DependencyInjectionExtensions.cs | 3 +- .../Extensions/EntityExtensions.cs | 20 +++ .../NetworkResurrector.Api.Application.csproj | 3 +- .../Queries/GetMachines.cs | 6 +- .../Queries/GetSystemVersion.cs | 8 +- .../Abstractions/INotificationService.cs | 14 ++ .../Services/CorrelationManager.cs | 14 -- .../Services/NotificationService.cs | 138 ++++++++++++++++++ .../Constants/Notifications.cs | 22 +++ .../Models/Notifications/Notification.cs | 9 ++ .../Notifications/NotificationContext.cs | 12 ++ .../Notifications/NotificationTemplate.cs | 9 ++ .../Commands/CancelAction.cs | 4 +- .../Commands/LockMachine.cs | 4 +- .../Commands/LogoutUser.cs | 4 +- .../Commands/PingMachine.cs | 4 +- .../Commands/RestartMachine.cs | 4 +- .../Commands/ShutdownMachine.cs | 4 +- .../Commands/SleepMachine.cs | 4 +- .../Commands/WakeMachine.cs | 4 +- ...rkResurrector.Api.PublishedLanguage.csproj | 6 +- .../Controllers/ErrorsController.cs | 15 +- .../Extensions/DataTypeExtensions.cs | 8 + .../Extensions/LoggingExtensions.cs | 41 ++++++ .../Extensions/MessagingExtensions.cs | 20 +++ .../Extensions/StartupExtensions.cs | 12 +- .../NetworkResurrector.Api.csproj | 5 +- src/api/NetworkResurrector.Api/Program.cs | 23 +-- .../Services/EmptyMessageBusPublisher.cs | 23 +++ .../NetworkResurrector.Api/appsettings.json | 56 ++++++- .../CommandHandlers/PingMachineHandler.cs | 5 +- .../CommandHandlers/ShutdownMachineHandler.cs | 5 +- .../CommandHandlers/WakeMachineHandler.cs | 5 +- .../Queries/GetServiceVersion.cs | 40 ++++- .../Commands/PingMachine.cs | 10 +- .../Commands/ShutdownMachine.cs | 10 +- .../Commands/WakeMachine.cs | 10 +- ...esurrector.Server.PublishedLanguage.csproj | 6 +- .../Controllers/SystemController.cs | 36 +++++ .../NetworkResurrector.Server.csproj | 1 - 56 files changed, 631 insertions(+), 178 deletions(-) create mode 100644 README.md create mode 100644 src/api/NetworkResurrector.Api.Application/Extensions/EntityExtensions.cs create mode 100644 src/api/NetworkResurrector.Api.Application/Services/Abstractions/INotificationService.cs delete mode 100644 src/api/NetworkResurrector.Api.Application/Services/CorrelationManager.cs create mode 100644 src/api/NetworkResurrector.Api.Application/Services/NotificationService.cs create mode 100644 src/api/NetworkResurrector.Api.Domain/Constants/Notifications.cs create mode 100644 src/api/NetworkResurrector.Api.Domain/Models/Notifications/Notification.cs create mode 100644 src/api/NetworkResurrector.Api.Domain/Models/Notifications/NotificationContext.cs create mode 100644 src/api/NetworkResurrector.Api.Domain/Models/Notifications/NotificationTemplate.cs create mode 100644 src/api/NetworkResurrector.Api/Extensions/DataTypeExtensions.cs create mode 100644 src/api/NetworkResurrector.Api/Extensions/LoggingExtensions.cs create mode 100644 src/api/NetworkResurrector.Api/Extensions/MessagingExtensions.cs create mode 100644 src/api/NetworkResurrector.Api/Services/EmptyMessageBusPublisher.cs create mode 100644 src/server/NetworkResurrector.Server/Controllers/SystemController.cs diff --git a/NetworkResurrector.sln b/NetworkResurrector.sln index 0d251b8..3c8d0f4 100644 --- a/NetworkResurrector.sln +++ b/NetworkResurrector.sln @@ -55,6 +55,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution items", "solution dependencies.props = dependencies.props Directory.Build.props = Directory.Build.props NuGet.config = NuGet.config + README.md = README.md ReleaseNotes.xml = ReleaseNotes.xml EndProjectSection EndProject diff --git a/README.md b/README.md new file mode 100644 index 0000000..4f5692d --- /dev/null +++ b/README.md @@ -0,0 +1,79 @@ +# Network resurrector + +Everything must be able to be managed remotely. Even the powered off servers. That's how Network resurrector appeared, the tool I wrote specifically to be able to wake up my machines that I don't need to be powered on all the time. + +Network Resurrector is a system that comprises of five essential services which allow for the execution of its core functionality. To enable various additional features, such as the notification mechanism, supplementary components may be added to the system as an option. + +## Main components + +### Frontend +* The frontend component is a web application written in React JS that has the role of providing the user with a friendly visual interface through which to interact with the system. + +### API +* The API component is a .NET 6 REST API that has the role of mediating the exchange of data and commands between the frontend component and the database or server component. + +### Server +* The server component is a .NET 6 service specialized in executing 'WakeOnLAN', 'Ping' and 'Shutdown' actions for the machines in its network. + +### Agent +* The agent is a .NET 6 service specialized in executing 'Shutdown', 'Restart', 'Sleep', 'Logout' and 'Lock' actions on the host machine on which it is installed. Each action can be executed at the time of launch or with a certain delay. If an action is requested with a delay and later the user changes his mind, he can cancel the action by executing the separate 'Cancel' type action. +* The need for the agent appeared after I realized that the server cannot perform any action on a machine, being outside of it. +* The agent solves this problem because it is installed directly on the targeted machine. Later, the API component can delegate the resolution of an action directly to the agent. +* Most of the time, the execution flow of an action is realized in the following way: + * The user initiates an action, such as starting or stopping a machine, by pressing the corresponding button in the user interface. + * The frontend component sends the command to the API. + * The API checks who is configured as performer for the respective action for the machine and sends the command to it. + * The performer of the action (Agent or Server) executes the command and responds on the flow with status. + * In most cases, the Server component handles the machine startup action while the Agent component manages the machine shutdown action. +* As is probably already obvious, the agent can be installed on as many machines as desired. + +### Gundo +* Gundo is my personal identity server. It manages user authentication within the application and authorizes requests made by it. Further information about Gundo can be found on its dedicated page. + +All communication between the five main components is done exclusively through HTTP. + +## Secondary components + +* NATS Streaming - Used to publish notifications about executed actions. +* Correo - Used to send by email the notifications generated by the system. +* Seq - Used to collect and view system logs. + +## Notification system + +The notification system is focused on system actions (Wake, Shutdown, etc), and the configuration of notifications is done in structures of the following form: + +``` +{ + "To": [ "user@homelab.com" ], + "Subject": "Network resurrector: Machine {MACHINE_NAME} has been waked. Status: {ACTION_STATUS}", + "Body": "Hello,{NEWLINE:2}Network resurrector processed a command to start machine {MACHINE_FULLNAME} with IP {MACHINE_IP} at {SYSTEM_DATETIME}.{NEWLINE}The performer who was delegated for this action was {ACTION_PERFORMER}.{NEWLINE}Action status: {ACTION_STATUS}{NEWLINE:2}Have a nice day,{NEWLINE}Network resurrector notification system." +} +``` + +The texts can be written at the user's free choice, and the following placeholders can be used in their composition: ```{MACHINE_NAME}```, ```{MACHINE_FULLNAME}```, ```{MACHINE_IP}```, ```{ACTION_STATUS}```, ```{ACTION_PERFORMER}```, ```{SYSTEM_DATETIME}```, ```{ERROR_MESSGE}```, ```{NEWLINE}```, ```{NEWLINE:2}``` + +```{NEWLINE:x}``` is a dynamic placeholder. Any number can be written in place of the 'x' character and the notification system will add that many new lines. + +## Database +Currently, the database server supported by the system is only Microsoft SQL Server. In the following versions, the system will also be compatible with PostgreSQL and SQLite. + +## Logging +The logging functionality is managed with Serilog, and its configuration is done in the ```appsettings.json``` file. In addition to its standard configuration, Network resurrector also has a preconfigured area where two destinations for logs are available: SqlServer database and Seq. Each of the destinations can be activated or not. If logging in the console is sufficient, all additional logging destinations can be disabled. +This configuration area is: + +``` +"Logs": { + "SqlServer": { + "Enabled": false, + "Connection": "Server=;Database=;User Id=;Password=;" + }, + "Seq": { + "Enabled": false, + "Url": "", + "ApiKey": "" + } +} +``` + +## Hosting +All the components of the system are written in cross-platform technologies, so its host can be any environment. \ No newline at end of file diff --git a/ReleaseNotes.xml b/ReleaseNotes.xml index 75ee6d7..4366ef1 100644 --- a/ReleaseNotes.xml +++ b/ReleaseNotes.xml @@ -41,7 +41,13 @@ 1.1.0 - .NET 6 upgrade - + Massive improvements + • .NET 6 upgrade + • Nuget packages upgrade + • Added Seq logging + • Added messaging and published notifications from command handlers + • Refactoring and code cleanup + • Added README.md file + \ No newline at end of file diff --git a/dependencies.props b/dependencies.props index 7c62508..f1f36c4 100644 --- a/dependencies.props +++ b/dependencies.props @@ -4,15 +4,16 @@ 6.0.1 4.1.0 3.1.0 - 4.0.1 + 5.2.2 5.6.1 10.1.1 8.1.1 9.0.0 + 6.0.30 6.0.1 1.0.6 - 1.0.1 1.0.8 1.2.0 + 1.0.1 \ No newline at end of file diff --git a/src/agent/NetworkResurrector.Agent.Application/NetworkResurrector.Agent.Application.csproj b/src/agent/NetworkResurrector.Agent.Application/NetworkResurrector.Agent.Application.csproj index 4bcb0f3..6f35d26 100644 --- a/src/agent/NetworkResurrector.Agent.Application/NetworkResurrector.Agent.Application.csproj +++ b/src/agent/NetworkResurrector.Agent.Application/NetworkResurrector.Agent.Application.csproj @@ -10,7 +10,6 @@ - diff --git a/src/agent/NetworkResurrector.Agent.Application/Queries/GetSystemVersion.cs b/src/agent/NetworkResurrector.Agent.Application/Queries/GetSystemVersion.cs index 0a38be8..eba394e 100644 --- a/src/agent/NetworkResurrector.Agent.Application/Queries/GetSystemVersion.cs +++ b/src/agent/NetworkResurrector.Agent.Application/Queries/GetSystemVersion.cs @@ -1,6 +1,7 @@ using MediatR; -using Netmash.Application.DataContracts; using System; +using System.IO; +using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -8,31 +9,29 @@ namespace NetworkResurrector.Agent.Application.Queries { public class GetSystemVersion { - public class Query : Query - { - public Query() { } - } + public class Query : IRequest { } - public class Model - { - public string Version { get; set; } - public DateTime LastUpdateDate { get; set; } - } + public record Model(string Version, DateTime LastUpdateDate); public class QueryHandler : IRequestHandler { - public QueryHandler() - { - } + public QueryHandler() { } public async Task Handle(Query request, CancellationToken cancellationToken) { - var result = new Model() - { - Version = "1.0.0", - LastUpdateDate = DateTime.Now - }; + var version = Environment.GetEnvironmentVariable("APP_VERSION"); + var appDate = Environment.GetEnvironmentVariable("APP_DATE"); + if (string.IsNullOrEmpty(version)) + version = Assembly.GetEntryAssembly().GetCustomAttribute().InformationalVersion; + + if (!DateTime.TryParse(appDate, out var lastUpdateDate)) + { + var location = Assembly.GetExecutingAssembly().Location; + lastUpdateDate = File.GetLastWriteTime(location); + } + + var result = new Model(version, lastUpdateDate); return await Task.FromResult(result); } } diff --git a/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Cancel.cs b/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Cancel.cs index 6a0b252..a48b7c1 100644 --- a/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Cancel.cs +++ b/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Cancel.cs @@ -1,10 +1,10 @@ -using Netmash.Application.DataContracts; +using MediatR; using NetworkResurrector.Agent.PublishedLanguage.Dto; using NetworkResurrector.Agent.PublishedLanguage.Events; namespace NetworkResurrector.Agent.PublishedLanguage.Commands { - public class Cancel : Command + public class Cancel : IRequest { public ActionOwner Owner { get; set; } } diff --git a/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Lock.cs b/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Lock.cs index 68c41f6..aae1a6f 100644 --- a/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Lock.cs +++ b/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Lock.cs @@ -1,10 +1,10 @@ -using Netmash.Application.DataContracts; +using MediatR; using NetworkResurrector.Agent.PublishedLanguage.Dto; using NetworkResurrector.Agent.PublishedLanguage.Events; namespace NetworkResurrector.Agent.PublishedLanguage.Commands { - public class Lock : Command + public class Lock : IRequest { public ActionOwner Owner { get; set; } } diff --git a/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Logout.cs b/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Logout.cs index 29eada9..d91c950 100644 --- a/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Logout.cs +++ b/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Logout.cs @@ -1,10 +1,10 @@ -using Netmash.Application.DataContracts; +using MediatR; using NetworkResurrector.Agent.PublishedLanguage.Dto; using NetworkResurrector.Agent.PublishedLanguage.Events; namespace NetworkResurrector.Agent.PublishedLanguage.Commands { - public class Logout : Command + public class Logout : IRequest { public ActionOwner Owner { get; set; } } diff --git a/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Restart.cs b/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Restart.cs index e680b1c..a79b6ed 100644 --- a/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Restart.cs +++ b/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Restart.cs @@ -1,10 +1,10 @@ -using Netmash.Application.DataContracts; +using MediatR; using NetworkResurrector.Agent.PublishedLanguage.Dto; using NetworkResurrector.Agent.PublishedLanguage.Events; namespace NetworkResurrector.Agent.PublishedLanguage.Commands { - public class Restart : Command + public class Restart : IRequest { public ActionOwner Owner { get; set; } public ActionOptions Options { get; set; } diff --git a/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Shutdown.cs b/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Shutdown.cs index 465442a..77c07a9 100644 --- a/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Shutdown.cs +++ b/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Shutdown.cs @@ -1,10 +1,10 @@ -using Netmash.Application.DataContracts; +using MediatR; using NetworkResurrector.Agent.PublishedLanguage.Dto; using NetworkResurrector.Agent.PublishedLanguage.Events; namespace NetworkResurrector.Agent.PublishedLanguage.Commands { - public class Shutdown : Command + public class Shutdown : IRequest { public ActionOwner Owner { get; set; } public ActionOptions Options { get; set; } diff --git a/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Sleep.cs b/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Sleep.cs index 531580a..21e8bdf 100644 --- a/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Sleep.cs +++ b/src/agent/NetworkResurrector.Agent.PublishedLanguage/Commands/Sleep.cs @@ -1,10 +1,10 @@ -using Netmash.Application.DataContracts; +using MediatR; using NetworkResurrector.Agent.PublishedLanguage.Dto; using NetworkResurrector.Agent.PublishedLanguage.Events; namespace NetworkResurrector.Agent.PublishedLanguage.Commands { - public class Sleep : Command + public class Sleep : IRequest { public ActionOwner Owner { get; set; } public ActionOptions Options { get; set; } diff --git a/src/agent/NetworkResurrector.Agent.PublishedLanguage/NetworkResurrector.Agent.PublishedLanguage.csproj b/src/agent/NetworkResurrector.Agent.PublishedLanguage/NetworkResurrector.Agent.PublishedLanguage.csproj index 75ad389..965177b 100644 --- a/src/agent/NetworkResurrector.Agent.PublishedLanguage/NetworkResurrector.Agent.PublishedLanguage.csproj +++ b/src/agent/NetworkResurrector.Agent.PublishedLanguage/NetworkResurrector.Agent.PublishedLanguage.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/agent/NetworkResurrector.Agent/NetworkResurrector.Agent.csproj b/src/agent/NetworkResurrector.Agent/NetworkResurrector.Agent.csproj index 3d7b801..28f5391 100644 --- a/src/agent/NetworkResurrector.Agent/NetworkResurrector.Agent.csproj +++ b/src/agent/NetworkResurrector.Agent/NetworkResurrector.Agent.csproj @@ -14,7 +14,6 @@ - diff --git a/src/api/NetworkResurrector.Api.Application/CommandHandlers/ShutdownMachineHandler.cs b/src/api/NetworkResurrector.Api.Application/CommandHandlers/ShutdownMachineHandler.cs index e160075..ff558f0 100644 --- a/src/api/NetworkResurrector.Api.Application/CommandHandlers/ShutdownMachineHandler.cs +++ b/src/api/NetworkResurrector.Api.Application/CommandHandlers/ShutdownMachineHandler.cs @@ -3,6 +3,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using NetworkResurrector.Agent.PublishedLanguage.Dto; using NetworkResurrector.Agent.Wrapper.Services; +using NetworkResurrector.Api.Application.Extensions; +using NetworkResurrector.Api.Application.Services.Abstractions; using NetworkResurrector.Api.Domain.Constants; using NetworkResurrector.Api.Domain.Repositories; using NetworkResurrector.Api.PublishedLanguage.Commands; @@ -21,14 +23,16 @@ namespace NetworkResurrector.Api.Application.CommandHandlers private readonly INetworkRepository _repository; private readonly IResurrectorAgentService _resurrectorAgentService; private readonly IConfiguration _configuration; + private readonly INotificationService _notificationService; - public ShutdownMachineHandler(ILogger logger, IResurrectorService resurrectorService, INetworkRepository repository, IResurrectorAgentService resurrectorAgentService, IConfiguration configuration) + public ShutdownMachineHandler(ILogger logger, IResurrectorService resurrectorService, INetworkRepository repository, IResurrectorAgentService resurrectorAgentService, IConfiguration configuration, INotificationService notificationService) { _logger=logger; _resurrectorService=resurrectorService; _repository=repository; _resurrectorAgentService=resurrectorAgentService; _configuration=configuration; + _notificationService=notificationService; } public async Task Handle(ShutdownMachine command, CancellationToken cancellationToken) @@ -42,7 +46,8 @@ namespace NetworkResurrector.Api.Application.CommandHandlers var ipAddressOrMachineName = machine.IPv4Address ?? machine.MachineName; MachineShutdown result; - switch (powerConfiguration.Performer.PerformerCode) + var performer = powerConfiguration.Performer.PerformerCode; + switch (performer) { case PowerActionPerformers.NETWORK_RESURRECTOR_SERVER: if (command.Delay.HasValue || command.Force) @@ -64,10 +69,12 @@ namespace NetworkResurrector.Api.Application.CommandHandlers break; default: - throw new Exception($"Power action performer {powerConfiguration.Performer.PerformerCode} is not implemented."); + throw new Exception($"Power action performer {performer} is not implemented."); } _logger.LogDebug($"Machine {command.MachineId} shutdown finished. Success: {result.Success}; Status: {result.Status}"); + var notificationContext = machine.ToNotificationContext(result.Status, performer); + await _notificationService.Notify(NotificationType.Shutdown, notificationContext, cancellationToken); return result; } diff --git a/src/api/NetworkResurrector.Api.Application/CommandHandlers/WakeMachineHandler.cs b/src/api/NetworkResurrector.Api.Application/CommandHandlers/WakeMachineHandler.cs index c783e2f..d873516 100644 --- a/src/api/NetworkResurrector.Api.Application/CommandHandlers/WakeMachineHandler.cs +++ b/src/api/NetworkResurrector.Api.Application/CommandHandlers/WakeMachineHandler.cs @@ -1,5 +1,7 @@ using MediatR; using Microsoft.Extensions.Logging; +using NetworkResurrector.Api.Application.Extensions; +using NetworkResurrector.Api.Application.Services.Abstractions; using NetworkResurrector.Api.Domain.Constants; using NetworkResurrector.Api.Domain.Repositories; using NetworkResurrector.Api.PublishedLanguage.Commands; @@ -16,12 +18,14 @@ namespace NetworkResurrector.Api.Application.CommandHandlers private readonly ILogger _logger; private readonly IResurrectorService _resurrectorService; private readonly INetworkRepository _repository; + private readonly INotificationService _notificationService; - public WakeMachineHandler(ILogger logger, IResurrectorService resurrectorService, INetworkRepository repository) + public WakeMachineHandler(ILogger logger, IResurrectorService resurrectorService, INetworkRepository repository, INotificationService notificationService) { _logger=logger; _resurrectorService=resurrectorService; _repository=repository; + _notificationService=notificationService; } public async Task Handle(WakeMachine command, CancellationToken cancellationToken) @@ -33,7 +37,8 @@ namespace NetworkResurrector.Api.Application.CommandHandlers //log activity MachineWaked result; - switch (powerConfiguration.Performer.PerformerCode) + var performer = powerConfiguration.Performer.PerformerCode; + switch (performer) { case PowerActionPerformers.NETWORK_RESURRECTOR_SERVER: var wakeResult = await _resurrectorService.Wake(machine.MACAddress); @@ -45,6 +50,8 @@ namespace NetworkResurrector.Api.Application.CommandHandlers throw new Exception($"Power action performer {powerConfiguration.Performer.PerformerCode} is not implemented."); } + var notificationContext = machine.ToNotificationContext(result.Status, performer); + await _notificationService.Notify(NotificationType.Wake, notificationContext, cancellationToken); return result; } } diff --git a/src/api/NetworkResurrector.Api.Application/DependencyInjectionExtensions.cs b/src/api/NetworkResurrector.Api.Application/DependencyInjectionExtensions.cs index f308c48..8d235f9 100644 --- a/src/api/NetworkResurrector.Api.Application/DependencyInjectionExtensions.cs +++ b/src/api/NetworkResurrector.Api.Application/DependencyInjectionExtensions.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using NetworkResurrector.Api.Application.Services; +using NetworkResurrector.Api.Application.Services.Abstractions; namespace NetworkResurrector.Api.Application { @@ -7,7 +8,7 @@ namespace NetworkResurrector.Api.Application { public static void AddApplicationServices(this IServiceCollection services) { - services.AddScoped(); + services.AddSingleton(); } } } diff --git a/src/api/NetworkResurrector.Api.Application/Extensions/EntityExtensions.cs b/src/api/NetworkResurrector.Api.Application/Extensions/EntityExtensions.cs new file mode 100644 index 0000000..352f8dc --- /dev/null +++ b/src/api/NetworkResurrector.Api.Application/Extensions/EntityExtensions.cs @@ -0,0 +1,20 @@ +using NetworkResurrector.Api.Domain.Entities; +using NetworkResurrector.Api.Domain.Models.Notifications; + +namespace NetworkResurrector.Api.Application.Extensions +{ + internal static class EntityExtensions + { + public static NotificationContext ToNotificationContext(this Machine machine, string actionStatus = null, string actionPerformer = null) + { + return new NotificationContext() + { + MachineName = machine.MachineName, + MachineFullName = machine.FullMachineName, + MachineIP = machine.IPv4Address, + ActionStatus = actionStatus, + ActionPerformer = actionPerformer + }; + } + } +} diff --git a/src/api/NetworkResurrector.Api.Application/NetworkResurrector.Api.Application.csproj b/src/api/NetworkResurrector.Api.Application/NetworkResurrector.Api.Application.csproj index f982787..c446cf4 100644 --- a/src/api/NetworkResurrector.Api.Application/NetworkResurrector.Api.Application.csproj +++ b/src/api/NetworkResurrector.Api.Application/NetworkResurrector.Api.Application.csproj @@ -6,11 +6,12 @@ + - + diff --git a/src/api/NetworkResurrector.Api.Application/Queries/GetMachines.cs b/src/api/NetworkResurrector.Api.Application/Queries/GetMachines.cs index 6ffe8b3..275e361 100644 --- a/src/api/NetworkResurrector.Api.Application/Queries/GetMachines.cs +++ b/src/api/NetworkResurrector.Api.Application/Queries/GetMachines.cs @@ -1,6 +1,5 @@ using AutoMapper; using MediatR; -using Netmash.Application.DataContracts; using NetworkResurrector.Api.Domain.Repositories; using System.Threading; using System.Threading.Tasks; @@ -9,10 +8,7 @@ namespace NetworkResurrector.Api.Application.Queries { public class GetMachines { - public class Query : Query - { - public Query() { } - } + public class Query : IRequest { } public class Model { diff --git a/src/api/NetworkResurrector.Api.Application/Queries/GetSystemVersion.cs b/src/api/NetworkResurrector.Api.Application/Queries/GetSystemVersion.cs index 07cbe48..19d6bfc 100644 --- a/src/api/NetworkResurrector.Api.Application/Queries/GetSystemVersion.cs +++ b/src/api/NetworkResurrector.Api.Application/Queries/GetSystemVersion.cs @@ -1,5 +1,4 @@ using MediatR; -using Netmash.Application.DataContracts; using System; using System.IO; using System.Reflection; @@ -10,12 +9,7 @@ namespace NetworkResurrector.Api.Application.Queries { public class GetSystemVersion { - public class Query : Query - { - public Query() - { - } - } + public class Query : IRequest { } public record Model(string Version, DateTime LastUpdateDate); diff --git a/src/api/NetworkResurrector.Api.Application/Services/Abstractions/INotificationService.cs b/src/api/NetworkResurrector.Api.Application/Services/Abstractions/INotificationService.cs new file mode 100644 index 0000000..74d4905 --- /dev/null +++ b/src/api/NetworkResurrector.Api.Application/Services/Abstractions/INotificationService.cs @@ -0,0 +1,14 @@ +using NetworkResurrector.Api.Domain.Constants; +using NetworkResurrector.Api.Domain.Models.Notifications; +using System.Threading; +using System.Threading.Tasks; + +namespace NetworkResurrector.Api.Application.Services.Abstractions +{ + public interface INotificationService + { + Task Notify(NotificationType type, NotificationContext context, CancellationToken cancellationToken = default); + Task Notify(NotificationType type, string machineName, string machineFullName, string machineIP, string actionStatus, string actionPerformer, string errorMessage, CancellationToken cancellationToken = default); + Task NotifyError(string errorMessage, CancellationToken cancellationToken = default); + } +} diff --git a/src/api/NetworkResurrector.Api.Application/Services/CorrelationManager.cs b/src/api/NetworkResurrector.Api.Application/Services/CorrelationManager.cs deleted file mode 100644 index d4b6ef1..0000000 --- a/src/api/NetworkResurrector.Api.Application/Services/CorrelationManager.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace NetworkResurrector.Api.Application.Services -{ - public class CorrelationManager - { - public Guid CorrelationId { get; } - - public CorrelationManager() - { - CorrelationId=Guid.NewGuid(); - } - } -} diff --git a/src/api/NetworkResurrector.Api.Application/Services/NotificationService.cs b/src/api/NetworkResurrector.Api.Application/Services/NotificationService.cs new file mode 100644 index 0000000..ba33630 --- /dev/null +++ b/src/api/NetworkResurrector.Api.Application/Services/NotificationService.cs @@ -0,0 +1,138 @@ +using Correo.PublishedLanguage.Commands; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using NBB.Messaging.Abstractions; +using NetworkResurrector.Api.Application.Services.Abstractions; +using NetworkResurrector.Api.Domain.Constants; +using NetworkResurrector.Api.Domain.Models.Notifications; +using System; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + +namespace NetworkResurrector.Api.Application.Services +{ + internal class NotificationService : INotificationService + { + private readonly IMessageBusPublisher _messageBusPublisher; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + + public NotificationService(IMessageBusPublisher messageBusPublisher, IConfiguration configuration, ILogger logger) + { + _messageBusPublisher=messageBusPublisher; + _configuration=configuration; + _logger=logger; + } + + private Notification GetNotification(NotificationType type, NotificationContext context) + { + var template = _configuration.GetSection($"Notifications:{type}").Get(); + var notification = new Notification() + { + To = template.To, + Subject = ReplacePlaceholders(template.Subject, context), + Body = ReplacePlaceholders(template.Body, context) + }; + + return notification; + } + + private string ReplacePlaceholders(string text, NotificationContext context) + { + var regex = new Regex(@"\{[A-Z_:0-9]+\}"); + MatchCollection matches = regex.Matches(text); + var distinctMatches = matches.OfType().Select(m => m.Value).Distinct(); + foreach (var placeholder in distinctMatches) + { + var placeholderValue = GetPlaceholderValue(placeholder, context); + if (string.IsNullOrEmpty(placeholderValue)) + _logger.LogWarning($"The placeholder '{placeholder}' was not recognized and was ignored."); + else + text = text.Replace(placeholder, placeholderValue); + } + return text; + } + + private string GetPlaceholderValue(string placeholder, NotificationContext context) + { + return placeholder switch + { + NotificationPlaceholder.MACHINE_NAME => context.MachineName, + NotificationPlaceholder.MACHINE_FULLNAME => context.MachineFullName, + NotificationPlaceholder.MACHINE_IP => context.MachineIP, + NotificationPlaceholder.ACTION_STATUS => context.ActionStatus, + NotificationPlaceholder.ACTION_PERFORMER => context.ActionPerformer, + NotificationPlaceholder.SYSTEM_DATETIME => DateTime.Now.ToString(), + NotificationPlaceholder.ERROR_MESSGE => context.ErrorMessage, + NotificationPlaceholder.NEWLINE => Environment.NewLine, + _ => GetRegexPlaceholderValue(placeholder), + }; + } + + private string GetRegexPlaceholderValue(string placeholder) + { + var placeholderValue = GetNewLinesPlaceholderValue(placeholder); + // check here for other regex placeholders + return placeholderValue; + } + + private string GetNewLinesPlaceholderValue(string placeholder) + { + var regex = new Regex(@"\{NEWLINE(?::\d+)?\}"); + var match = regex.Match(placeholder); + if (!match.Success) + return null; + + var linesCountRegex = new Regex(@"(?<=:)[0-9]+"); + var linesCountMatch = linesCountRegex.Match(placeholder); + if (!linesCountMatch.Success) + return null; + + var linesCount = Convert.ToInt32(linesCountMatch.Value); + var placeHolderValue = string.Concat(Enumerable.Repeat(Environment.NewLine, linesCount)); + + return placeHolderValue; + } + + public async Task Notify(NotificationType type, NotificationContext context, CancellationToken cancellationToken = default) + { + var notification = GetNotification(type, context); + var cmd = new SendEmail() + { + Subject = notification.Subject, + Body = notification.Body, + IsBodyHtml = false, + To = notification.To.Select(z => new SendEmail.MailAddress(z)) + }; + + await _messageBusPublisher.PublishAsync(cmd, cancellationToken); + } + + public async Task NotifyError(string errorMessage, CancellationToken cancellationToken = default) + { + var context = new NotificationContext() + { + ErrorMessage = errorMessage + }; + + await Notify(NotificationType.Error, context, cancellationToken); + } + + public async Task Notify(NotificationType type, string machineName, string machineFullName, string machineIP, string actionStatus, string actionPerformer, string errorMessage, CancellationToken cancellationToken = default) + { + var context = new NotificationContext() + { + MachineName = machineName, + MachineFullName = machineFullName, + MachineIP = machineIP, + ActionStatus = actionStatus, + ActionPerformer = actionPerformer, + ErrorMessage= errorMessage, + }; + + await Notify(type, context, cancellationToken); + } + } +} diff --git a/src/api/NetworkResurrector.Api.Domain/Constants/Notifications.cs b/src/api/NetworkResurrector.Api.Domain/Constants/Notifications.cs new file mode 100644 index 0000000..e6810d6 --- /dev/null +++ b/src/api/NetworkResurrector.Api.Domain/Constants/Notifications.cs @@ -0,0 +1,22 @@ +namespace NetworkResurrector.Api.Domain.Constants +{ + public enum NotificationType + { + Wake, + Shutdown, + Error + } + + public struct NotificationPlaceholder + { + public const string + MACHINE_NAME = "{MACHINE_NAME}", + MACHINE_FULLNAME = "{MACHINE_FULLNAME}", + MACHINE_IP = "{MACHINE_IP}", + ACTION_STATUS = "{ACTION_STATUS}", + ACTION_PERFORMER = "{ACTION_PERFORMER}", + SYSTEM_DATETIME = "{SYSTEM_DATETIME}", + ERROR_MESSGE = "{ERROR_MESSGE}", + NEWLINE = "{NEWLINE}"; + } +} diff --git a/src/api/NetworkResurrector.Api.Domain/Models/Notifications/Notification.cs b/src/api/NetworkResurrector.Api.Domain/Models/Notifications/Notification.cs new file mode 100644 index 0000000..3cd0a23 --- /dev/null +++ b/src/api/NetworkResurrector.Api.Domain/Models/Notifications/Notification.cs @@ -0,0 +1,9 @@ +namespace NetworkResurrector.Api.Domain.Models.Notifications +{ + public record Notification + { + public string[] To { get; init; } + public string Subject { get; init; } + public string Body { get; init; } + } +} diff --git a/src/api/NetworkResurrector.Api.Domain/Models/Notifications/NotificationContext.cs b/src/api/NetworkResurrector.Api.Domain/Models/Notifications/NotificationContext.cs new file mode 100644 index 0000000..c7bc78f --- /dev/null +++ b/src/api/NetworkResurrector.Api.Domain/Models/Notifications/NotificationContext.cs @@ -0,0 +1,12 @@ +namespace NetworkResurrector.Api.Domain.Models.Notifications +{ + public record NotificationContext + { + public string MachineName { get; set; } + public string MachineFullName { get; set; } + public string MachineIP { get; set; } + public string ActionStatus { get; set; } + public string ActionPerformer { get; set; } + public string ErrorMessage { get; set; } + } +} diff --git a/src/api/NetworkResurrector.Api.Domain/Models/Notifications/NotificationTemplate.cs b/src/api/NetworkResurrector.Api.Domain/Models/Notifications/NotificationTemplate.cs new file mode 100644 index 0000000..9ba4f6b --- /dev/null +++ b/src/api/NetworkResurrector.Api.Domain/Models/Notifications/NotificationTemplate.cs @@ -0,0 +1,9 @@ +namespace NetworkResurrector.Api.Domain.Models.Notifications +{ + public record NotificationTemplate + { + public string[] To { get; init; } + public string Subject { get; init; } + public string Body { get; init; } + } +} diff --git a/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/CancelAction.cs b/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/CancelAction.cs index e96e957..2573421 100644 --- a/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/CancelAction.cs +++ b/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/CancelAction.cs @@ -1,9 +1,9 @@ -using Netmash.Application.DataContracts; +using MediatR; using NetworkResurrector.Api.PublishedLanguage.Events; namespace NetworkResurrector.Api.PublishedLanguage.Commands { - public class CancelAction : Command + public class CancelAction : IRequest { public int MachineId { get; set; } public int ActionId { get; set; } diff --git a/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/LockMachine.cs b/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/LockMachine.cs index e687623..02db0fa 100644 --- a/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/LockMachine.cs +++ b/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/LockMachine.cs @@ -1,9 +1,9 @@ -using Netmash.Application.DataContracts; +using MediatR; using NetworkResurrector.Api.PublishedLanguage.Events; namespace NetworkResurrector.Api.PublishedLanguage.Commands { - public class LockMachine : Command + public class LockMachine : IRequest { public int MachineId { get; set; } } diff --git a/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/LogoutUser.cs b/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/LogoutUser.cs index f300718..dbaebfa 100644 --- a/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/LogoutUser.cs +++ b/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/LogoutUser.cs @@ -1,9 +1,9 @@ -using Netmash.Application.DataContracts; +using MediatR; using NetworkResurrector.Api.PublishedLanguage.Events; namespace NetworkResurrector.Api.PublishedLanguage.Commands { - public class LogoutUser : Command + public class LogoutUser : IRequest { public int MachineId { get; set; } } diff --git a/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/PingMachine.cs b/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/PingMachine.cs index 5d52157..235f951 100644 --- a/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/PingMachine.cs +++ b/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/PingMachine.cs @@ -1,9 +1,9 @@ -using Netmash.Application.DataContracts; +using MediatR; using NetworkResurrector.Api.PublishedLanguage.Events; namespace NetworkResurrector.Api.PublishedLanguage.Commands { - public class PingMachine : Command + public class PingMachine : IRequest { public int MachineId { get; set; } } diff --git a/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/RestartMachine.cs b/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/RestartMachine.cs index d9c12b2..1181bb6 100644 --- a/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/RestartMachine.cs +++ b/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/RestartMachine.cs @@ -1,9 +1,9 @@ -using Netmash.Application.DataContracts; +using MediatR; using NetworkResurrector.Api.PublishedLanguage.Events; namespace NetworkResurrector.Api.PublishedLanguage.Commands { - public class RestartMachine : Command + public class RestartMachine : IRequest { public int MachineId { get; set; } public int? Delay { get; set; } diff --git a/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/ShutdownMachine.cs b/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/ShutdownMachine.cs index 87dd6ca..05d0f85 100644 --- a/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/ShutdownMachine.cs +++ b/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/ShutdownMachine.cs @@ -1,9 +1,9 @@ -using Netmash.Application.DataContracts; +using MediatR; using NetworkResurrector.Api.PublishedLanguage.Events; namespace NetworkResurrector.Api.PublishedLanguage.Commands { - public class ShutdownMachine : Command + public class ShutdownMachine : IRequest { public int MachineId { get; set; } public int? Delay { get; set; } diff --git a/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/SleepMachine.cs b/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/SleepMachine.cs index 2f80137..672695d 100644 --- a/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/SleepMachine.cs +++ b/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/SleepMachine.cs @@ -1,9 +1,9 @@ -using Netmash.Application.DataContracts; +using MediatR; using NetworkResurrector.Api.PublishedLanguage.Events; namespace NetworkResurrector.Api.PublishedLanguage.Commands { - public class SleepMachine : Command + public class SleepMachine : IRequest { public int MachineId { get; set; } public int? Delay { get; set; } diff --git a/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/WakeMachine.cs b/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/WakeMachine.cs index 44b21f5..b3af7c8 100644 --- a/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/WakeMachine.cs +++ b/src/api/NetworkResurrector.Api.PublishedLanguage/Commands/WakeMachine.cs @@ -1,9 +1,9 @@ -using Netmash.Application.DataContracts; +using MediatR; using NetworkResurrector.Api.PublishedLanguage.Events; namespace NetworkResurrector.Api.PublishedLanguage.Commands { - public class WakeMachine : Command + public class WakeMachine : IRequest { public int MachineId { get; set; } } diff --git a/src/api/NetworkResurrector.Api.PublishedLanguage/NetworkResurrector.Api.PublishedLanguage.csproj b/src/api/NetworkResurrector.Api.PublishedLanguage/NetworkResurrector.Api.PublishedLanguage.csproj index ea960cf..1feb30b 100644 --- a/src/api/NetworkResurrector.Api.PublishedLanguage/NetworkResurrector.Api.PublishedLanguage.csproj +++ b/src/api/NetworkResurrector.Api.PublishedLanguage/NetworkResurrector.Api.PublishedLanguage.csproj @@ -4,8 +4,8 @@ net6.0 - - - + + + diff --git a/src/api/NetworkResurrector.Api/Controllers/ErrorsController.cs b/src/api/NetworkResurrector.Api/Controllers/ErrorsController.cs index 0658724..f56fd38 100644 --- a/src/api/NetworkResurrector.Api/Controllers/ErrorsController.cs +++ b/src/api/NetworkResurrector.Api/Controllers/ErrorsController.cs @@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using NetworkResurrector.Api.Application.Services; using System; using System.ComponentModel.DataAnnotations; @@ -12,27 +11,23 @@ namespace NetworkResurrector.Api.Controllers [ApiExplorerSettings(IgnoreApi = true)] public class ErrorsController : ControllerBase { - private readonly CorrelationManager _correlationManager; - - public ErrorsController(CorrelationManager correlationManager) + public ErrorsController() { - _correlationManager=correlationManager; } - internal record Error(int Status, string Title, Guid CorrelationId, string Message = null); + internal record Error(int Status, string Title, string Message = null); [Route("error")] public IActionResult HandleErrors() { - var correlationId = _correlationManager.CorrelationId; var context = HttpContext.Features.Get(); var exception = context.Error; return exception switch { - ValidationException => StatusCode(StatusCodes.Status404NotFound, new Error(StatusCodes.Status404NotFound, "Internal server error", correlationId, exception.Message)), - UnauthorizedAccessException => StatusCode(StatusCodes.Status401Unauthorized, new Error(StatusCodes.Status401Unauthorized, "Internal server error", correlationId)), - _ => StatusCode(StatusCodes.Status500InternalServerError, new Error(StatusCodes.Status500InternalServerError, "Internal server error", correlationId)), + ValidationException => StatusCode(StatusCodes.Status404NotFound, new Error(StatusCodes.Status404NotFound, "Internal server error", exception.Message)), + UnauthorizedAccessException => StatusCode(StatusCodes.Status401Unauthorized, new Error(StatusCodes.Status401Unauthorized, "Internal server error")), + _ => StatusCode(StatusCodes.Status500InternalServerError, new Error(StatusCodes.Status500InternalServerError, "Internal server error")), }; } } diff --git a/src/api/NetworkResurrector.Api/Extensions/DataTypeExtensions.cs b/src/api/NetworkResurrector.Api/Extensions/DataTypeExtensions.cs new file mode 100644 index 0000000..0e249de --- /dev/null +++ b/src/api/NetworkResurrector.Api/Extensions/DataTypeExtensions.cs @@ -0,0 +1,8 @@ +namespace NetworkResurrector.Api.Extensions +{ + internal static class DataTypeExtensions + { + public static string Nullify(this string value) + => string.IsNullOrWhiteSpace(value) ? null : value; + } +} diff --git a/src/api/NetworkResurrector.Api/Extensions/LoggingExtensions.cs b/src/api/NetworkResurrector.Api/Extensions/LoggingExtensions.cs new file mode 100644 index 0000000..6d79789 --- /dev/null +++ b/src/api/NetworkResurrector.Api/Extensions/LoggingExtensions.cs @@ -0,0 +1,41 @@ +using Microsoft.Extensions.Configuration; +using Serilog; +using Serilog.Configuration; +using Serilog.Sinks.MSSqlServer; +using System; + +namespace NetworkResurrector.Api.Extensions +{ + internal static class LoggingExtensions + { + internal static LoggerSinkConfiguration ConfiguredDestinations(this LoggerSinkConfiguration writeTo, IConfiguration configuration) + { + var sqlServerEnabled = configuration.GetValue("Logs:SqlServer:Enabled"); + var sqlServerConnection = configuration.GetValue("Logs:SqlServer:Connection"); + var seqEnabled = configuration.GetValue("Logs:Seq:Enabled"); + var seqUrl = configuration.GetValue("Logs:Seq:Url"); + var seqApiKey = configuration.GetValue("Logs:Seq:ApiKey"); + + if (sqlServerEnabled && string.IsNullOrWhiteSpace(sqlServerConnection)) + throw new Exception("If SqlServer logging is enabled, the SqlServer connection must be configured."); + if (seqEnabled && string.IsNullOrWhiteSpace(seqUrl)) + throw new Exception("If Seq logging is enabled, the Seq URL must be configured."); + + if (sqlServerEnabled) + { + var columnOptions = new ColumnOptions(); + columnOptions.Store.Remove(StandardColumn.Properties); + columnOptions.Store.Remove(StandardColumn.MessageTemplate); + columnOptions.Store.Add(StandardColumn.LogEvent); + var mssqlSinkOptions = new MSSqlServerSinkOptions() { AutoCreateSqlTable = true, TableName = "__Logs" }; + + writeTo.MSSqlServer(sqlServerConnection, mssqlSinkOptions, columnOptions: columnOptions); + } + + if (seqEnabled) + writeTo.Seq(seqUrl, apiKey: seqApiKey.Nullify()); + + return writeTo; + } + } +} diff --git a/src/api/NetworkResurrector.Api/Extensions/MessagingExtensions.cs b/src/api/NetworkResurrector.Api/Extensions/MessagingExtensions.cs new file mode 100644 index 0000000..1015d49 --- /dev/null +++ b/src/api/NetworkResurrector.Api/Extensions/MessagingExtensions.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using NBB.Messaging.Abstractions; +using NetworkResurrector.Api.Services; + +namespace NetworkResurrector.Api.Extensions +{ + + public static class MessagingExtensions + { + public static void AddMessageBus(this IServiceCollection services, IConfiguration configuration) + { + var enabled = configuration.GetValue("Messaging:Enabled", false); + if (enabled) + services.AddMessageBus().AddNatsTransport(configuration); + else + services.AddSingleton(); + } + } +} diff --git a/src/api/NetworkResurrector.Api/Extensions/StartupExtensions.cs b/src/api/NetworkResurrector.Api/Extensions/StartupExtensions.cs index 0a5cf04..b464889 100644 --- a/src/api/NetworkResurrector.Api/Extensions/StartupExtensions.cs +++ b/src/api/NetworkResurrector.Api/Extensions/StartupExtensions.cs @@ -15,7 +15,6 @@ using NetworkResurrector.Api.Domain.Data; using NetworkResurrector.Api.Services; using NetworkResurrector.Server.Wrapper; using Newtonsoft.Json; -using System.Reflection; namespace NetworkResurrector.Api.Extensions { @@ -33,7 +32,7 @@ namespace NetworkResurrector.Api.Extensions services.AddScoped(); // MediatR - services.AddMediatR(GetMediatRAssemblies()); + services.AddMediatR(typeof(Application.Queries.GetMachines).Assembly); services.AddScoped(typeof(IPipelineBehavior<,>), typeof(RequestPreProcessorBehavior<,>)); services.AddScoped(typeof(IPipelineBehavior<,>), typeof(RequestPostProcessorBehavior<,>)); @@ -53,14 +52,11 @@ namespace NetworkResurrector.Api.Extensions services.AddMigration(DatabaseType.SQLServer, MetadataLocation.Database); services.AddDataAccess(); - // Application + // Application services services.AddApplicationServices(); - } - private static Assembly[] GetMediatRAssemblies() - { - var assembly = typeof(Application.Queries.GetMachines).Assembly; - return new Assembly[] { assembly }; + // Messaging + services.AddMessageBus(configuration); } public static void Configure(this IApplicationBuilder app) diff --git a/src/api/NetworkResurrector.Api/NetworkResurrector.Api.csproj b/src/api/NetworkResurrector.Api/NetworkResurrector.Api.csproj index 0d47cfe..28c65a6 100644 --- a/src/api/NetworkResurrector.Api/NetworkResurrector.Api.csproj +++ b/src/api/NetworkResurrector.Api/NetworkResurrector.Api.csproj @@ -11,13 +11,14 @@ + - + - + diff --git a/src/api/NetworkResurrector.Api/Program.cs b/src/api/NetworkResurrector.Api/Program.cs index 2fe2685..9d755a2 100644 --- a/src/api/NetworkResurrector.Api/Program.cs +++ b/src/api/NetworkResurrector.Api/Program.cs @@ -4,9 +4,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using NetworkResurrector.Api.Extensions; using Serilog; -using Serilog.Core; -using Serilog.Events; -using Serilog.Sinks.MSSqlServer; using System; namespace NetworkResurrector.Api @@ -19,27 +16,11 @@ namespace NetworkResurrector.Api builder.Host.UseSerilog((_, lc) => { - var connectionString = builder.Configuration.GetConnectionString("DatabaseConnection"); - var loggingLevelParam = builder.Configuration.GetValue("Logging:LogLevel:Default"); - - var loggingLevelOk = Enum.TryParse(loggingLevelParam, out LogEventLevel loggingLevel); - if (!loggingLevelOk) - throw new Exception($"Logging level '{loggingLevelParam}' is not valid."); - - var loggingLevelSwitch = new LoggingLevelSwitch(loggingLevel); - - var columnOptions = new ColumnOptions(); - columnOptions.Store.Remove(StandardColumn.Properties); - columnOptions.Store.Remove(StandardColumn.MessageTemplate); - columnOptions.Store.Add(StandardColumn.LogEvent); - var mssqlSinkOptions = new MSSqlServerSinkOptions() { AutoCreateSqlTable = true, TableName = "__Logs" }; - lc - .MinimumLevel.ControlledBy(loggingLevelSwitch) - .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + .ReadFrom.Configuration(builder.Configuration) .Enrich.FromLogContext() .WriteTo.Console() - .WriteTo.MSSqlServer(connectionString, mssqlSinkOptions, columnOptions: columnOptions); + .WriteTo.ConfiguredDestinations(builder.Configuration); }); builder.Services.ConfigureServices(builder.Configuration); diff --git a/src/api/NetworkResurrector.Api/Services/EmptyMessageBusPublisher.cs b/src/api/NetworkResurrector.Api/Services/EmptyMessageBusPublisher.cs new file mode 100644 index 0000000..fa33b66 --- /dev/null +++ b/src/api/NetworkResurrector.Api/Services/EmptyMessageBusPublisher.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.Logging; +using NBB.Messaging.Abstractions; +using System.Threading; +using System.Threading.Tasks; + +namespace NetworkResurrector.Api.Services +{ + internal class EmptyMessageBusPublisher : IMessageBusPublisher + { + private readonly ILogger _logger; + + public EmptyMessageBusPublisher(ILogger logger) + { + _logger=logger; + } + + public Task PublishAsync(T message, MessagingPublisherOptions publisherOptions = null, CancellationToken cancellationToken = default) + { + _logger.LogDebug($"EmptyMessageBusPublisher: Message {message.GetType().FullName} was published."); + return Task.CompletedTask; + } + } +} diff --git a/src/api/NetworkResurrector.Api/appsettings.json b/src/api/NetworkResurrector.Api/appsettings.json index b7d230d..5833c4d 100644 --- a/src/api/NetworkResurrector.Api/appsettings.json +++ b/src/api/NetworkResurrector.Api/appsettings.json @@ -3,11 +3,23 @@ "ConnectionStrings": { "DatabaseConnection": "Server=#########;Database=#########;User Id=#########;Password=#########;MultipleActiveResultSets=true" }, - "Logging": { - "LogLevel": { - "Default": "Debug", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Information" + } + } + }, + "Logs": { + "SqlServer": { + "Enabled": false, + "Connection": "Server=#########;Database=#########;User Id=#########;Password=#########;MultipleActiveResultSets=true" + }, + "Seq": { + "Enabled": false, + "Url": "", + "ApiKey": "" } }, "AllowedHosts": "*", @@ -15,10 +27,38 @@ "Code": "NETWORK_RESURRECTOR_API" }, "IdentityServer": { - //"BaseAddress": "http://localhost:5063/" - "BaseAddress": "https://lab.code-rove.com/identity-server-api/" + "BaseAddress": "http://:/api/" }, "NetworkResurrectorServer": { - "BaseAddress": "http://localhost:5062" + "BaseAddress": "http://:" + }, + "Messaging": { + "Enabled": false, + "TopicPrefix": "HomeLab.", + "Source": "NetworkResurrector.Api", + "Nats": { + "NatsUrl": "nats://:4222", + "Cluster": "", + "ClientId": "NetworkResurrector_Api", + "QGroup": "NetworkResurrector.Api", + "DurableName": "durable" + } + }, + "Notifications": { + "Wake": { + "To": [ "user@homelab.com" ], + "Subject": "Network resurrector: Machine {MACHINE_NAME} has been waked. Status: {ACTION_STATUS}", + "Body": "Hello,{NEWLINE:2}Network resurrector processed a command to start machine {MACHINE_FULLNAME} with IP {MACHINE_IP} at {SYSTEM_DATETIME}.{NEWLINE}The performer who was delegated for this action was {ACTION_PERFORMER}.{NEWLINE}Action status: {ACTION_STATUS}{NEWLINE:2}Have a nice day,{NEWLINE}Network resurrector notification system." + }, + "Shutdown": { + "To": [ "user@homelab.com" ], + "Subject": "Network resurrector: Machine {MACHINE_NAME} has been shut down. Status: {ACTION_STATUS}", + "Body": "Hello,{NEWLINE:2}Network resurrector processed a command to shut down machine {MACHINE_FULLNAME} with IP {MACHINE_IP} at {SYSTEM_DATETIME}.{NEWLINE}The performer who was delegated for this action was {ACTION_PERFORMER}.{NEWLINE}Action status: {ACTION_STATUS}{NEWLINE:2}Have a nice day,{NEWLINE}Network resurrector notification system." + }, + "Error": { + "To": [ "user@homelab.com" ], + "Subject": "Network resurrector: An unexpected error has occurred", + "Body": "Hello,{NEWLINE:2}Network resurrector encountered an unexpected error: {ERROR_MESSGE}.{NEWLINE}For more details about the error, check the logging system.{NEWLINE:2}Have a nice day,{NEWLINE}Network resurrector notification system." + } } } diff --git a/src/server/NetworkResurrector.Server.Application/CommandHandlers/PingMachineHandler.cs b/src/server/NetworkResurrector.Server.Application/CommandHandlers/PingMachineHandler.cs index 079ad87..9ab3be3 100644 --- a/src/server/NetworkResurrector.Server.Application/CommandHandlers/PingMachineHandler.cs +++ b/src/server/NetworkResurrector.Server.Application/CommandHandlers/PingMachineHandler.cs @@ -31,9 +31,8 @@ namespace NetworkResurrector.Server.Application.CommandHandlers } catch (Exception ex) { - var correlationIdMsg = $"CorrelationId: {command.Metadata.CorrelationId}"; - _logger.LogError(ex, $"An unexpected error has occurred. {correlationIdMsg}"); - return new MachinePinged(false, $"{ex.Message} {correlationIdMsg}"); + _logger.LogError(ex, "An unexpected error has occurred."); + return new MachinePinged(false, ex.Message); } } } diff --git a/src/server/NetworkResurrector.Server.Application/CommandHandlers/ShutdownMachineHandler.cs b/src/server/NetworkResurrector.Server.Application/CommandHandlers/ShutdownMachineHandler.cs index 5580607..5403b8e 100644 --- a/src/server/NetworkResurrector.Server.Application/CommandHandlers/ShutdownMachineHandler.cs +++ b/src/server/NetworkResurrector.Server.Application/CommandHandlers/ShutdownMachineHandler.cs @@ -36,9 +36,8 @@ namespace NetworkResurrector.Server.Application.CommandHandlers } catch (Exception ex) { - var correlationIdMsg = $"CorrelationId: {command.Metadata.CorrelationId}"; - _logger.LogError(ex, $"An unexpected error has occurred. {correlationIdMsg}"); - return new MachineShutdown(false, $"{ex.Message} {correlationIdMsg}"); + _logger.LogError(ex, "An unexpected error has occurred."); + return new MachineShutdown(false, ex.Message); } } } diff --git a/src/server/NetworkResurrector.Server.Application/CommandHandlers/WakeMachineHandler.cs b/src/server/NetworkResurrector.Server.Application/CommandHandlers/WakeMachineHandler.cs index 7e676ae..81a37e0 100644 --- a/src/server/NetworkResurrector.Server.Application/CommandHandlers/WakeMachineHandler.cs +++ b/src/server/NetworkResurrector.Server.Application/CommandHandlers/WakeMachineHandler.cs @@ -29,9 +29,8 @@ namespace NetworkResurrector.Server.Application.CommandHandlers } catch (Exception ex) { - var correlationIdMsg = $"CorrelationId: {command.Metadata.CorrelationId}"; - _logger.LogError(ex, $"An unexpected error has occurred. {correlationIdMsg}"); - return new MachineWaked(false, $"{ex.Message} {correlationIdMsg}"); + _logger.LogError(ex, "An unexpected error has occurred."); + return new MachineWaked(false, ex.Message); } } } diff --git a/src/server/NetworkResurrector.Server.Application/Queries/GetServiceVersion.cs b/src/server/NetworkResurrector.Server.Application/Queries/GetServiceVersion.cs index d8acaeb..a8476e6 100644 --- a/src/server/NetworkResurrector.Server.Application/Queries/GetServiceVersion.cs +++ b/src/server/NetworkResurrector.Server.Application/Queries/GetServiceVersion.cs @@ -1,7 +1,41 @@ -namespace NetworkResurrector.Server.Application.Queries +using MediatR; +using System; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace NetworkResurrector.Server.Application.Queries { - class GetServiceVersion + public class GetSystemVersion { - //TO DO + public class Query : IRequest { } + + public record Model(string Version, DateTime LastUpdateDate); + + public class QueryHandler : IRequestHandler + { + public QueryHandler() + { + } + + public async Task Handle(Query request, CancellationToken cancellationToken) + { + var version = Environment.GetEnvironmentVariable("APP_VERSION"); + var appDate = Environment.GetEnvironmentVariable("APP_DATE"); + + if (string.IsNullOrEmpty(version)) + version = Assembly.GetEntryAssembly().GetCustomAttribute().InformationalVersion; + + if (!DateTime.TryParse(appDate, out var lastUpdateDate)) + { + var location = Assembly.GetExecutingAssembly().Location; + lastUpdateDate = File.GetLastWriteTime(location); + } + + var result = new Model(version, lastUpdateDate); + return await Task.FromResult(result); + } + } } } diff --git a/src/server/NetworkResurrector.Server.PublishedLanguage/Commands/PingMachine.cs b/src/server/NetworkResurrector.Server.PublishedLanguage/Commands/PingMachine.cs index edf630d..f39b765 100644 --- a/src/server/NetworkResurrector.Server.PublishedLanguage/Commands/PingMachine.cs +++ b/src/server/NetworkResurrector.Server.PublishedLanguage/Commands/PingMachine.cs @@ -1,16 +1,10 @@ -using Netmash.Application.DataContracts; +using MediatR; using NetworkResurrector.Server.PublishedLanguage.Events; -using System; namespace NetworkResurrector.Server.PublishedLanguage.Commands { - public class PingMachine : Command + public class PingMachine : IRequest { public string IpAddressOrMachineName { get; set; } - - public PingMachine(string ipAddressOrMachineName) : base(new Metadata() { CorrelationId = Guid.NewGuid() }) - { - IpAddressOrMachineName = ipAddressOrMachineName; - } } } diff --git a/src/server/NetworkResurrector.Server.PublishedLanguage/Commands/ShutdownMachine.cs b/src/server/NetworkResurrector.Server.PublishedLanguage/Commands/ShutdownMachine.cs index 62ac586..d7f959c 100644 --- a/src/server/NetworkResurrector.Server.PublishedLanguage/Commands/ShutdownMachine.cs +++ b/src/server/NetworkResurrector.Server.PublishedLanguage/Commands/ShutdownMachine.cs @@ -1,16 +1,10 @@ -using Netmash.Application.DataContracts; +using MediatR; using NetworkResurrector.Server.PublishedLanguage.Events; -using System; namespace NetworkResurrector.Server.PublishedLanguage.Commands { - public class ShutdownMachine : Command + public class ShutdownMachine : IRequest { public string IPAddressOrMachineName { get; set; } - - public ShutdownMachine(string ipAddressOrMachineName) : base(new Metadata() { CorrelationId = Guid.NewGuid() }) - { - IPAddressOrMachineName = ipAddressOrMachineName; - } } } diff --git a/src/server/NetworkResurrector.Server.PublishedLanguage/Commands/WakeMachine.cs b/src/server/NetworkResurrector.Server.PublishedLanguage/Commands/WakeMachine.cs index 728afea..a1cb83c 100644 --- a/src/server/NetworkResurrector.Server.PublishedLanguage/Commands/WakeMachine.cs +++ b/src/server/NetworkResurrector.Server.PublishedLanguage/Commands/WakeMachine.cs @@ -1,16 +1,10 @@ -using Netmash.Application.DataContracts; +using MediatR; using NetworkResurrector.Server.PublishedLanguage.Events; -using System; namespace NetworkResurrector.Server.PublishedLanguage.Commands { - public class WakeMachine : Command + public class WakeMachine : IRequest { public string MacAddress { get; set; } - - public WakeMachine(string macAddress) : base(new Metadata() { CorrelationId = Guid.NewGuid() }) - { - MacAddress = macAddress; - } } } \ No newline at end of file diff --git a/src/server/NetworkResurrector.Server.PublishedLanguage/NetworkResurrector.Server.PublishedLanguage.csproj b/src/server/NetworkResurrector.Server.PublishedLanguage/NetworkResurrector.Server.PublishedLanguage.csproj index d42e72a..bd66b4d 100644 --- a/src/server/NetworkResurrector.Server.PublishedLanguage/NetworkResurrector.Server.PublishedLanguage.csproj +++ b/src/server/NetworkResurrector.Server.PublishedLanguage/NetworkResurrector.Server.PublishedLanguage.csproj @@ -9,7 +9,7 @@ 1.0.3 - - - + + + diff --git a/src/server/NetworkResurrector.Server/Controllers/SystemController.cs b/src/server/NetworkResurrector.Server/Controllers/SystemController.cs new file mode 100644 index 0000000..2116b30 --- /dev/null +++ b/src/server/NetworkResurrector.Server/Controllers/SystemController.cs @@ -0,0 +1,36 @@ +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using NetworkResurrector.Server.Application.Queries; +using System; +using System.Threading.Tasks; + +namespace NetworkResurrector.Server.Controllers +{ + [Authorize] + [ApiController] + [Route("system")] + public class SystemController : ControllerBase + { + private readonly IMediator _mediator; + + public SystemController(IMediator mediator) + { + _mediator = mediator; + } + + [AllowAnonymous] + [HttpGet("ping")] + public IActionResult Ping() + { + return Ok($"Ping success. System datetime: {DateTime.Now}"); + } + + [HttpGet("version")] + public async Task GetSystemVersion([FromRoute] GetSystemVersion.Query query) + { + var result = await _mediator.Send(query); + return Ok(result); + } + } +} diff --git a/src/server/NetworkResurrector.Server/NetworkResurrector.Server.csproj b/src/server/NetworkResurrector.Server/NetworkResurrector.Server.csproj index e9b7bbe..fcf6f87 100644 --- a/src/server/NetworkResurrector.Server/NetworkResurrector.Server.csproj +++ b/src/server/NetworkResurrector.Server/NetworkResurrector.Server.csproj @@ -14,7 +14,6 @@ -