From 345c39793c881d3ce932e9b340ff2beb376245e4 Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Sun, 5 Nov 2023 14:48:47 +0100 Subject: [PATCH 01/22] refactor: Remove unnecessary service --- .../Controllers/DoorRequestController.cs | 8 +++---- API/DoorRequest.API/Program.cs | 3 +-- .../Services/DoorRequestService.cs | 21 ------------------- ...BrixelOpenDoorClient.cs => DoorService.cs} | 8 +++++-- .../Services/IBrixelOpenDoorClient.cs | 8 ------- ...IDoorRequestService.cs => IDoorService.cs} | 2 +- 6 files changed, 12 insertions(+), 38 deletions(-) delete mode 100644 API/DoorRequest.API/Services/DoorRequestService.cs rename API/DoorRequest.API/Services/{BrixelOpenDoorClient.cs => DoorService.cs} (84%) delete mode 100644 API/DoorRequest.API/Services/IBrixelOpenDoorClient.cs rename API/DoorRequest.API/Services/{IDoorRequestService.cs => IDoorService.cs} (73%) diff --git a/API/DoorRequest.API/Controllers/DoorRequestController.cs b/API/DoorRequest.API/Controllers/DoorRequestController.cs index 3b508a6..5c60f23 100644 --- a/API/DoorRequest.API/Controllers/DoorRequestController.cs +++ b/API/DoorRequest.API/Controllers/DoorRequestController.cs @@ -12,12 +12,12 @@ namespace DoorRequest.API.Controllers; [Authorize] public class DoorRequestController : ControllerBase { - private readonly IDoorRequestService _doorRequestService; + private readonly IDoorService _doorService; private readonly LockConfiguration _lockConfiguration; - public DoorRequestController(IDoorRequestService doorRequestService, IOptions lockConfiguration) + public DoorRequestController(IDoorService doorService, IOptions lockConfiguration) { - _doorRequestService = doorRequestService; + _doorService = doorService ?? throw new System.ArgumentNullException(nameof(doorService)); _lockConfiguration = lockConfiguration.Value; } @@ -25,7 +25,7 @@ public DoorRequestController(IDoorRequestService doorRequestService, IOptions OpenDoorRequest() { - return await _doorRequestService.OpenDoor(); + return await _doorService.OpenDoor(); } [HttpGet("code")] diff --git a/API/DoorRequest.API/Program.cs b/API/DoorRequest.API/Program.cs index ca06168..b9d6534 100644 --- a/API/DoorRequest.API/Program.cs +++ b/API/DoorRequest.API/Program.cs @@ -82,8 +82,7 @@ .ValidateDataAnnotations() .ValidateOnStart(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.Configure(builder.Configuration.GetSection(nameof(LockConfiguration))); var app = builder.Build(); diff --git a/API/DoorRequest.API/Services/DoorRequestService.cs b/API/DoorRequest.API/Services/DoorRequestService.cs deleted file mode 100644 index 16c3878..0000000 --- a/API/DoorRequest.API/Services/DoorRequestService.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.Extensions.Logging; -using System.Threading.Tasks; - -namespace DoorRequest.API.Services; - -public class DoorRequestService : IDoorRequestService -{ - private readonly IBrixelOpenDoorClient _brixelOpenDoorClient; - private readonly ILogger _logger; - - public DoorRequestService(IBrixelOpenDoorClient brixelOpenDoorClient, ILogger logger) - { - _brixelOpenDoorClient = brixelOpenDoorClient; - _logger = logger; - } - public async Task OpenDoor() - { - _logger.LogInformation("Sending request to open the door via MQTT"); - return await _brixelOpenDoorClient.OpenDoor(); - } -} diff --git a/API/DoorRequest.API/Services/BrixelOpenDoorClient.cs b/API/DoorRequest.API/Services/DoorService.cs similarity index 84% rename from API/DoorRequest.API/Services/BrixelOpenDoorClient.cs rename to API/DoorRequest.API/Services/DoorService.cs index 4d1d8c7..b178b50 100644 --- a/API/DoorRequest.API/Services/BrixelOpenDoorClient.cs +++ b/API/DoorRequest.API/Services/DoorService.cs @@ -1,5 +1,6 @@  using DoorRequest.API.Config; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MQTTnet; using MQTTnet.Client; @@ -11,12 +12,13 @@ namespace DoorRequest.API.Services; -public class BrixelOpenDoorClient : IBrixelOpenDoorClient +public class DoorService : IDoorService { private readonly string _topic; private readonly MqttClientOptions _options; + private ILogger _logger; - public BrixelOpenDoorClient(IOptions options) + public DoorService(IOptions options, ILogger logger) { _topic = options.Value.Topic; var optionsBuilder = new MqttClientOptionsBuilder() @@ -44,10 +46,12 @@ public BrixelOpenDoorClient(IOptions options) } _options = optionsBuilder.Build(); + _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); } public async Task OpenDoor() { + _logger.LogInformation("Sending request to open the door via MQTT"); using var mqttClient = new MqttFactory().CreateMqttClient(); await mqttClient.ConnectAsync(_options, CancellationToken.None); var message = new MqttApplicationMessageBuilder() diff --git a/API/DoorRequest.API/Services/IBrixelOpenDoorClient.cs b/API/DoorRequest.API/Services/IBrixelOpenDoorClient.cs deleted file mode 100644 index 3dcfc9b..0000000 --- a/API/DoorRequest.API/Services/IBrixelOpenDoorClient.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Threading.Tasks; - -namespace DoorRequest.API.Services; - -public interface IBrixelOpenDoorClient -{ - Task OpenDoor(); -} \ No newline at end of file diff --git a/API/DoorRequest.API/Services/IDoorRequestService.cs b/API/DoorRequest.API/Services/IDoorService.cs similarity index 73% rename from API/DoorRequest.API/Services/IDoorRequestService.cs rename to API/DoorRequest.API/Services/IDoorService.cs index 27dc5af..ce39028 100644 --- a/API/DoorRequest.API/Services/IDoorRequestService.cs +++ b/API/DoorRequest.API/Services/IDoorService.cs @@ -2,7 +2,7 @@ namespace DoorRequest.API.Services; -public interface IDoorRequestService +public interface IDoorService { Task OpenDoor(); } \ No newline at end of file From 00e012f53b0eb4af18915974c3441facc78cd2ed Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Sun, 5 Nov 2023 15:19:46 +0100 Subject: [PATCH 02/22] fix: Add readonly modified to logger --- API/DoorRequest.API/Services/DoorService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/DoorRequest.API/Services/DoorService.cs b/API/DoorRequest.API/Services/DoorService.cs index b178b50..c4ce5ad 100644 --- a/API/DoorRequest.API/Services/DoorService.cs +++ b/API/DoorRequest.API/Services/DoorService.cs @@ -16,7 +16,7 @@ public class DoorService : IDoorService { private readonly string _topic; private readonly MqttClientOptions _options; - private ILogger _logger; + private readonly ILogger _logger; public DoorService(IOptions options, ILogger logger) { From 958fccb45a2f4d2bf37c9446174eb7a03149ed3f Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Sun, 5 Nov 2023 18:05:49 +0100 Subject: [PATCH 03/22] chore: Update .dockeringnore and move .editorconfig to repo root --- .dockerignore | 21 +++++++----- .../.editorconfig => .editorconfig | 32 +++++++++++++++++-- 2 files changed, 43 insertions(+), 10 deletions(-) rename WebApp/doorrequest/.editorconfig => .editorconfig (88%) diff --git a/.dockerignore b/.dockerignore index 2d15e48..3729ff0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,20 +1,25 @@ +**/.classpath **/.dockerignore **/.env **/.git **/.gitignore +**/.project +**/.settings +**/.toolstarget **/.vs **/.vscode **/*.*proj.user +**/*.dbmdl +**/*.jfm **/azds.yaml -**/charts **/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log **/obj -**/Dockerfile -**/Dockerfile.develop -**/docker-compose.yml -**/docker-compose.*.yml -**/*.dbmdl -**/*.jfm **/secrets.dev.yaml **/values.dev.yaml -**/.toolstarget \ No newline at end of file +LICENSE +README.md \ No newline at end of file diff --git a/WebApp/doorrequest/.editorconfig b/.editorconfig similarity index 88% rename from WebApp/doorrequest/.editorconfig rename to .editorconfig index f43e844..d3b8cfc 100644 --- a/WebApp/doorrequest/.editorconfig +++ b/.editorconfig @@ -119,13 +119,13 @@ csharp_prefer_static_local_function = true:warning csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async # Code-block preferences -csharp_prefer_simple_using_statement = true +csharp_prefer_simple_using_statement = true:suggestion csharp_prefer_braces = when_multiline:warning # Expression-level preferences csharp_prefer_simple_default_expression = true:warning csharp_style_deconstructed_variable_declaration = true -csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion csharp_style_inlined_variable_declaration = false csharp_style_pattern_local_over_anonymous_function = true:warning csharp_style_prefer_index_operator = true:warning @@ -264,3 +264,31 @@ dotnet_naming_style.generic_parameter_style.required_prefix = T dotnet_naming_style.generic_parameter_style.required_suffix = dotnet_naming_style.generic_parameter_style.word_separator = dotnet_naming_style.generic_parameter_style.capitalization = pascal_case +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion + +[*.{cs,vb}] +dotnet_style_coalesce_expression = true:warning +dotnet_style_null_propagation = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_object_initializer = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_prefer_simplified_boolean_expressions = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion +dotnet_style_explicit_tuple_names = true:warning +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:warning +dotnet_style_namespace_match_folder = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf \ No newline at end of file From 03d453f361a7d4b4b3ab63607352a914ea907973 Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Sun, 5 Nov 2023 18:07:13 +0100 Subject: [PATCH 04/22] feat: (wip) Added Blazor WASM Web project with PWA support, authentication and authorization --- API/DoorRequest.API/DoorRequest.API.csproj | 6 +- DoorRequest.sln | 19 ++++- .../Authorization/CustomClaims.cs | 2 +- .../Authorization/Roles.cs | 2 +- Shared/Shared.csproj | 9 ++ Web/App.razor | 25 ++++++ Web/Authorization/CustomUserFactory.cs | 48 +++++++++++ Web/Pages/About.razor | 78 ++++++++++++++++++ Web/Pages/Authentication.razor | 7 ++ Web/Pages/DoorService.razor | 10 +++ Web/Program.cs | 30 +++++++ Web/Properties/launchSettings.json | 30 +++++++ Web/Shared/LoginDisplay.razor | 21 +++++ Web/Shared/MainLayout.razor | 66 +++++++++++++++ Web/Shared/MainLayout.razor.css | 0 Web/Shared/RedirectToLogin.razor | 9 ++ Web/Shared/SurveyPrompt.razor | 16 ++++ Web/Web.csproj | 41 +++++++++ Web/_Imports.razor | 12 +++ Web/wwwroot/appsettings.Development.json | 7 ++ Web/wwwroot/appsettings.json | 6 ++ Web/wwwroot/css/app.css | 0 Web/wwwroot/favicon.png | Bin 0 -> 1148 bytes Web/wwwroot/icon-192.png | Bin 0 -> 2626 bytes Web/wwwroot/icon-512.png | Bin 0 -> 6311 bytes Web/wwwroot/index.html | 39 +++++++++ Web/wwwroot/manifest.json | 21 +++++ Web/wwwroot/service-worker.js | 4 + Web/wwwroot/service-worker.published.js | 48 +++++++++++ 29 files changed, 552 insertions(+), 4 deletions(-) rename {API/DoorRequest.API => Shared}/Authorization/CustomClaims.cs (65%) rename {API/DoorRequest.API => Shared}/Authorization/Roles.cs (81%) create mode 100644 Shared/Shared.csproj create mode 100644 Web/App.razor create mode 100644 Web/Authorization/CustomUserFactory.cs create mode 100644 Web/Pages/About.razor create mode 100644 Web/Pages/Authentication.razor create mode 100644 Web/Pages/DoorService.razor create mode 100644 Web/Program.cs create mode 100644 Web/Properties/launchSettings.json create mode 100644 Web/Shared/LoginDisplay.razor create mode 100644 Web/Shared/MainLayout.razor create mode 100644 Web/Shared/MainLayout.razor.css create mode 100644 Web/Shared/RedirectToLogin.razor create mode 100644 Web/Shared/SurveyPrompt.razor create mode 100644 Web/Web.csproj create mode 100644 Web/_Imports.razor create mode 100644 Web/wwwroot/appsettings.Development.json create mode 100644 Web/wwwroot/appsettings.json create mode 100644 Web/wwwroot/css/app.css create mode 100644 Web/wwwroot/favicon.png create mode 100644 Web/wwwroot/icon-192.png create mode 100644 Web/wwwroot/icon-512.png create mode 100644 Web/wwwroot/index.html create mode 100644 Web/wwwroot/manifest.json create mode 100644 Web/wwwroot/service-worker.js create mode 100644 Web/wwwroot/service-worker.published.js diff --git a/API/DoorRequest.API/DoorRequest.API.csproj b/API/DoorRequest.API/DoorRequest.API.csproj index 83bdbc7..9d71524 100644 --- a/API/DoorRequest.API/DoorRequest.API.csproj +++ b/API/DoorRequest.API/DoorRequest.API.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 a32e9851-ef6a-48c6-803f-7ea1cb26c158 Linux ..\.. @@ -22,6 +22,10 @@ + + + + diff --git a/DoorRequest.sln b/DoorRequest.sln index 41a3feb..4356be7 100644 --- a/DoorRequest.sln +++ b/DoorRequest.sln @@ -1,12 +1,15 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.6.33723.286 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DoorRequest.API", "API\DoorRequest.API\DoorRequest.API.csproj", "{263FC65F-673F-42AF-BBDC-7D194BCF58AC}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "Web\Web.csproj", "{D21DF4B7-5113-482B-9FD4-7541B2E2F8E9}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution items", "Solution items", "{CC45F04F-0025-46C0-B8D9-5072C68FD717}" ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig docker-compose.yml = docker-compose.yml EndProjectSection EndProject @@ -22,6 +25,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mosquitto", "Mosquitto", "{ Infrastructure\Mosquitto\config\mosquitto.conf = Infrastructure\Mosquitto\config\mosquitto.conf EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared", "Shared\Shared.csproj", "{51C4CCF5-F70A-45CF-BF76-9DF83B92E731}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -32,10 +37,22 @@ Global {263FC65F-673F-42AF-BBDC-7D194BCF58AC}.Debug|Any CPU.Build.0 = Debug|Any CPU {263FC65F-673F-42AF-BBDC-7D194BCF58AC}.Release|Any CPU.ActiveCfg = Release|Any CPU {263FC65F-673F-42AF-BBDC-7D194BCF58AC}.Release|Any CPU.Build.0 = Release|Any CPU + {D21DF4B7-5113-482B-9FD4-7541B2E2F8E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D21DF4B7-5113-482B-9FD4-7541B2E2F8E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D21DF4B7-5113-482B-9FD4-7541B2E2F8E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D21DF4B7-5113-482B-9FD4-7541B2E2F8E9}.Release|Any CPU.Build.0 = Release|Any CPU + {51C4CCF5-F70A-45CF-BF76-9DF83B92E731}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51C4CCF5-F70A-45CF-BF76-9DF83B92E731}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51C4CCF5-F70A-45CF-BF76-9DF83B92E731}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51C4CCF5-F70A-45CF-BF76-9DF83B92E731}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {C695FD5A-7CC3-4226-A8C4-7C89EA489FA6} = {1CD813E1-F654-4340-8DCA-0935FBBA9EE2} + {13A6DFE6-8ADB-4493-91C3-48803473E3E1} = {1CD813E1-F654-4340-8DCA-0935FBBA9EE2} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3BEE30D4-3A4A-4C6F-BA05-29AF80363B10} EndGlobalSection diff --git a/API/DoorRequest.API/Authorization/CustomClaims.cs b/Shared/Authorization/CustomClaims.cs similarity index 65% rename from API/DoorRequest.API/Authorization/CustomClaims.cs rename to Shared/Authorization/CustomClaims.cs index b1dbf0b..11b1def 100644 --- a/API/DoorRequest.API/Authorization/CustomClaims.cs +++ b/Shared/Authorization/CustomClaims.cs @@ -1,4 +1,4 @@ -namespace DoorRequest.API.Authorization; +namespace Shared.Authorization; public static class CustomClaims { diff --git a/API/DoorRequest.API/Authorization/Roles.cs b/Shared/Authorization/Roles.cs similarity index 81% rename from API/DoorRequest.API/Authorization/Roles.cs rename to Shared/Authorization/Roles.cs index 89458cd..83a25c9 100644 --- a/API/DoorRequest.API/Authorization/Roles.cs +++ b/Shared/Authorization/Roles.cs @@ -1,4 +1,4 @@ -namespace DoorRequest.API.Authorization; +namespace Shared.Authorization; public static class Roles { diff --git a/Shared/Shared.csproj b/Shared/Shared.csproj new file mode 100644 index 0000000..cfadb03 --- /dev/null +++ b/Shared/Shared.csproj @@ -0,0 +1,9 @@ + + + + net7.0 + enable + enable + + + diff --git a/Web/App.razor b/Web/App.razor new file mode 100644 index 0000000..d07039a --- /dev/null +++ b/Web/App.razor @@ -0,0 +1,25 @@ + + + + + + @if (context.User.Identity?.IsAuthenticated != true) + { + + } + else + { +

You are not authorized to access this resource.

+ } +
+
+ +
+ + Not found + +

Sorry, there's nothing at this address.

+
+
+
+
diff --git a/Web/Authorization/CustomUserFactory.cs b/Web/Authorization/CustomUserFactory.cs new file mode 100644 index 0000000..0074f8a --- /dev/null +++ b/Web/Authorization/CustomUserFactory.cs @@ -0,0 +1,48 @@ +using System.Security.Claims; +using System.Text.Json; + +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal; + +namespace Web.Authorization; + +public class CustomUserFactory : AccountClaimsPrincipalFactory +{ + public CustomUserFactory(IAccessTokenProviderAccessor accessor) + : base(accessor) + { + } + + public async override ValueTask CreateUserAsync( + RemoteUserAccount account, + RemoteAuthenticationUserOptions options) + { + var user = await base.CreateUserAsync(account, options); + var claimsIdentity = (ClaimsIdentity)(user.Identity + ?? throw new Exception("Could not get user's Identity property")); + + if (account != null) + { + MapArrayClaimsToMultipleSeparateClaims(account, claimsIdentity); + } + + return user; + } + + private void MapArrayClaimsToMultipleSeparateClaims(RemoteUserAccount account, ClaimsIdentity claimsIdentity) + { + foreach (var prop in account.AdditionalProperties) + { + var key = prop.Key; + var value = prop.Value; + if (value != null && + (value is JsonElement element && element.ValueKind == JsonValueKind.Array)) + { + claimsIdentity.RemoveClaim(claimsIdentity.FindFirst(prop.Key)); + var claims = element.EnumerateArray() + .Select(x => new Claim(prop.Key, x.ToString())); + claimsIdentity.AddClaims(claims); + } + } + } +} \ No newline at end of file diff --git a/Web/Pages/About.razor b/Web/Pages/About.razor new file mode 100644 index 0000000..0382eff --- /dev/null +++ b/Web/Pages/About.razor @@ -0,0 +1,78 @@ +@page "/about" +@using System.Security.Claims +@using Microsoft.AspNetCore.Components.Authorization +@using System.Globalization +@using global::Shared.Authorization; +@inject AuthenticationStateProvider AuthenticationStateProvider + +

About

+ +

@authMessage

+ +

+ Current culture: @CultureInfo.CurrentUICulture +

+ +

+ + Logout + +

+ +

Claims

+ +

+ @if (claims.Count() > 0) + { +

    + @foreach (var claim in claims) + { +
  • @claim.Type: @claim.Value
  • + } +
+ } +

+ +

Roles

+ +

+ @if (roles.Count() > 0) + { +

    + @foreach (var role in roles) + { +
  • @role
  • + } +
+ } + else + { + No roles found. + } +

+ +@code { + private string? authMessage; + private IEnumerable claims = Enumerable.Empty(); + private IEnumerable roles = Enumerable.Empty(); + + protected override async Task OnInitializedAsync() + { + var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); + var user = authState.User; + + if (user?.Identity?.IsAuthenticated ?? false) + { + authMessage = $"{user.Identity.Name} is authenticated."; + claims = user.Claims; + roles = user.Claims.Where(c => c.Type == CustomClaims.Roles).Select(c => c.Value); + } + else + { + authMessage = "The user is NOT authenticated."; + } + } +} \ No newline at end of file diff --git a/Web/Pages/Authentication.razor b/Web/Pages/Authentication.razor new file mode 100644 index 0000000..6c74356 --- /dev/null +++ b/Web/Pages/Authentication.razor @@ -0,0 +1,7 @@ +@page "/authentication/{action}" +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication + + +@code{ + [Parameter] public string? Action { get; set; } +} diff --git a/Web/Pages/DoorService.razor b/Web/Pages/DoorService.razor new file mode 100644 index 0000000..405e3bf --- /dev/null +++ b/Web/Pages/DoorService.razor @@ -0,0 +1,10 @@ +@page "/" +@using Microsoft.AspNetCore.Authorization; +@using global::Shared.Authorization; +@attribute [Authorize(Roles = Roles.TwentyFourSevenAccess)] + +

DoorService

+ +@code { + +} diff --git a/Web/Program.cs b/Web/Program.cs new file mode 100644 index 0000000..8ee1a17 --- /dev/null +++ b/Web/Program.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; + +using MudBlazor.Services; + +using Shared.Authorization; + +using Web; +using Web.Authorization; + +var builder = WebAssemblyHostBuilder.CreateDefault(args); +builder.RootComponents.Add("#app"); +builder.RootComponents.Add("head::after"); + +builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + +builder.Services.AddOidcAuthentication(options => +{ + // Configure your authentication provider options here. + // For more information, see https://aka.ms/blazor-standalone-auth + builder.Configuration.Bind("Local", options.ProviderOptions); + options.UserOptions.RoleClaim = CustomClaims.Roles; +}); + +builder.Services.AddApiAuthorization() + .AddAccountClaimsPrincipalFactory(); + +builder.Services.AddMudServices(); + +await builder.Build().RunAsync(); diff --git a/Web/Properties/launchSettings.json b/Web/Properties/launchSettings.json new file mode 100644 index 0000000..efccf95 --- /dev/null +++ b/Web/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:13524", + "sslPort": 44322 + } + }, + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "https://localhost:7104;http://localhost:5204", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Web/Shared/LoginDisplay.razor b/Web/Shared/LoginDisplay.razor new file mode 100644 index 0000000..69940b1 --- /dev/null +++ b/Web/Shared/LoginDisplay.razor @@ -0,0 +1,21 @@ +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication +@inject NavigationManager Navigation + + + + Hello, @context.User.Identity?.Name! + Log out + + + + Log in + + + +@code { + private void BeginLogOut() + { + Navigation.NavigateToLogout("authentication/logout"); + } +} diff --git a/Web/Shared/MainLayout.razor b/Web/Shared/MainLayout.razor new file mode 100644 index 0000000..b53de06 --- /dev/null +++ b/Web/Shared/MainLayout.razor @@ -0,0 +1,66 @@ +@inherits LayoutComponentBase + + + + + + + DoorApp + + + + + + @Body + + + +@code { + private bool _isDarkMode; + + private MudThemeProvider _mudThemeProvider = default!; + + private readonly MudTheme _currentTheme = new() + { + Palette = new PaletteLight + { + Primary = "#0A7BCF", + Secondary = "#4CAF50", + Info = "#64a7e2", + Success = "#2ECC40", + Warning = "#FFC107", + Error = "#FF0000", + AppbarBackground = "#212121", + TextPrimary = "#0A7BCF", + TextSecondary = "#4CAF50", + // more color properties + }, + PaletteDark = new PaletteDark + { + Primary = "00BFFF", + Secondary = "#800080", + Info = "#4169E1", + Success = "#32CD32", + Warning = "#FFA500", + Error = "#FF0000", + AppbarBackground = "#121212", + TextPrimary = "#E0E0E0", + TextSecondary = "#BDBDBD", + // more color properties + } + }; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + _isDarkMode = await _mudThemeProvider.GetSystemPreference(); + StateHasChanged(); + } + } + + void ThemeToggle() => _isDarkMode = !_isDarkMode; +} \ No newline at end of file diff --git a/Web/Shared/MainLayout.razor.css b/Web/Shared/MainLayout.razor.css new file mode 100644 index 0000000..e69de29 diff --git a/Web/Shared/RedirectToLogin.razor b/Web/Shared/RedirectToLogin.razor new file mode 100644 index 0000000..a1cf400 --- /dev/null +++ b/Web/Shared/RedirectToLogin.razor @@ -0,0 +1,9 @@ +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication +@inject NavigationManager Navigation + +@code { + protected override void OnInitialized() + { + Navigation.NavigateToLogin("authentication/login"); + } +} diff --git a/Web/Shared/SurveyPrompt.razor b/Web/Shared/SurveyPrompt.razor new file mode 100644 index 0000000..67b6b62 --- /dev/null +++ b/Web/Shared/SurveyPrompt.razor @@ -0,0 +1,16 @@ +
+ + @Title + + + Please take our + brief survey + + and tell us what you think. +
+ +@code { + // Demonstrates how a parent component can supply parameters + [Parameter] + public string? Title { get; set; } +} diff --git a/Web/Web.csproj b/Web/Web.csproj new file mode 100644 index 0000000..b24d3e3 --- /dev/null +++ b/Web/Web.csproj @@ -0,0 +1,41 @@ + + + + net7.0 + enable + enable + service-worker-assets.js + True + + + + False + True + + + + False + True + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Web/_Imports.razor b/Web/_Imports.razor new file mode 100644 index 0000000..252a741 --- /dev/null +++ b/Web/_Imports.razor @@ -0,0 +1,12 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.AspNetCore.Components.WebAssembly.Http +@using Microsoft.JSInterop +@using Web +@using Web.Shared +@using MudBlazor \ No newline at end of file diff --git a/Web/wwwroot/appsettings.Development.json b/Web/wwwroot/appsettings.Development.json new file mode 100644 index 0000000..5c980a9 --- /dev/null +++ b/Web/wwwroot/appsettings.Development.json @@ -0,0 +1,7 @@ +{ + "Local": { + "Authority": "http://localhost:5302/realms/DevRealm", + "ClientId": "door-request-webapp", + "ResponseType": "code" + } +} diff --git a/Web/wwwroot/appsettings.json b/Web/wwwroot/appsettings.json new file mode 100644 index 0000000..c0d95df --- /dev/null +++ b/Web/wwwroot/appsettings.json @@ -0,0 +1,6 @@ +{ + "Local": { + "Authority": "", + "ClientId": "" + } +} diff --git a/Web/wwwroot/css/app.css b/Web/wwwroot/css/app.css new file mode 100644 index 0000000..e69de29 diff --git a/Web/wwwroot/favicon.png b/Web/wwwroot/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8422b59695935d180d11d5dbe99653e711097819 GIT binary patch literal 1148 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 literal 0 HcmV?d00001 diff --git a/Web/wwwroot/icon-512.png b/Web/wwwroot/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..c2dd4842dc93df73c322218ee03eca142a19a338 GIT binary patch literal 6311 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 literal 0 HcmV?d00001 diff --git a/Web/wwwroot/index.html b/Web/wwwroot/index.html new file mode 100644 index 0000000..d20aadd --- /dev/null +++ b/Web/wwwroot/index.html @@ -0,0 +1,39 @@ + + + + + + + Web + + + + + + + + + + + + +
+ + + + +
+
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
+ + + + + + + diff --git a/Web/wwwroot/manifest.json b/Web/wwwroot/manifest.json new file mode 100644 index 0000000..a8fa3da --- /dev/null +++ b/Web/wwwroot/manifest.json @@ -0,0 +1,21 @@ +{ + "name": "Web", + "short_name": "Web", + "start_url": "./", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#03173d", + "prefer_related_applications": false, + "icons": [ + { + "src": "icon-512.png", + "type": "image/png", + "sizes": "512x512" + }, + { + "src": "icon-192.png", + "type": "image/png", + "sizes": "192x192" + } + ] +} diff --git a/Web/wwwroot/service-worker.js b/Web/wwwroot/service-worker.js new file mode 100644 index 0000000..fe614da --- /dev/null +++ b/Web/wwwroot/service-worker.js @@ -0,0 +1,4 @@ +// In development, always fetch from the network and do not enable offline support. +// This is because caching would make development more difficult (changes would not +// be reflected on the first load after each change). +self.addEventListener('fetch', () => { }); diff --git a/Web/wwwroot/service-worker.published.js b/Web/wwwroot/service-worker.published.js new file mode 100644 index 0000000..0d9986f --- /dev/null +++ b/Web/wwwroot/service-worker.published.js @@ -0,0 +1,48 @@ +// Caution! Be sure you understand the caveats before publishing an application with +// offline support. See https://aka.ms/blazor-offline-considerations + +self.importScripts('./service-worker-assets.js'); +self.addEventListener('install', event => event.waitUntil(onInstall(event))); +self.addEventListener('activate', event => event.waitUntil(onActivate(event))); +self.addEventListener('fetch', event => event.respondWith(onFetch(event))); + +const cacheNamePrefix = 'offline-cache-'; +const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`; +const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/ ]; +const offlineAssetsExclude = [ /^service-worker\.js$/ ]; + +async function onInstall(event) { + console.info('Service worker: Install'); + + // Fetch and cache all matching items from the assets manifest + const assetsRequests = self.assetsManifest.assets + .filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url))) + .filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url))) + .map(asset => new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' })); + await caches.open(cacheName).then(cache => cache.addAll(assetsRequests)); +} + +async function onActivate(event) { + console.info('Service worker: Activate'); + + // Delete unused caches + const cacheKeys = await caches.keys(); + await Promise.all(cacheKeys + .filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName) + .map(key => caches.delete(key))); +} + +async function onFetch(event) { + let cachedResponse = null; + if (event.request.method === 'GET') { + // For all navigation requests, try to serve index.html from cache + // If you need some URLs to be server-rendered, edit the following check to exclude those URLs + const shouldServeIndexHtml = event.request.mode === 'navigate'; + + const request = shouldServeIndexHtml ? 'index.html' : event.request; + const cache = await caches.open(cacheName); + cachedResponse = await cache.match(request); + } + + return cachedResponse || fetch(event.request); +} From 415b2c8711d8a34726fb63ab3b17b02f098d043a Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Sun, 5 Nov 2023 23:54:08 +0100 Subject: [PATCH 05/22] feat: (wip) Implemented API call to DoorRequest API --- .../Controllers/DoorRequestController.cs | 12 ++++--- API/DoorRequest.API/Program.cs | 10 ++++-- .../Properties/launchSettings.json | 26 ++++++--------- DoorRequest.sln | 2 +- Web/Authorization/CustomUserFactory.cs | 2 +- Web/Extensions/DoorServiceExtensions.cs | 30 +++++++++++++++++ Web/Extensions/MudBlazorExtensions.cs | 15 +++++++++ Web/Pages/DoorControl.razor | 24 ++++++++++++++ Web/Pages/DoorService.razor | 10 ------ Web/Program.cs | 10 +++--- Web/Properties/launchSettings.json | 32 +++++++------------ Web/Services/DoorService.cs | 22 +++++++++++++ Web/Shared/MainLayout.razor | 2 ++ Web/Web.csproj | 1 + 14 files changed, 136 insertions(+), 62 deletions(-) create mode 100644 Web/Extensions/DoorServiceExtensions.cs create mode 100644 Web/Extensions/MudBlazorExtensions.cs create mode 100644 Web/Pages/DoorControl.razor delete mode 100644 Web/Pages/DoorService.razor create mode 100644 Web/Services/DoorService.cs diff --git a/API/DoorRequest.API/Controllers/DoorRequestController.cs b/API/DoorRequest.API/Controllers/DoorRequestController.cs index 5c60f23..bed6cca 100644 --- a/API/DoorRequest.API/Controllers/DoorRequestController.cs +++ b/API/DoorRequest.API/Controllers/DoorRequestController.cs @@ -1,9 +1,13 @@ -using DoorRequest.API.Config; +using System.Threading.Tasks; + +using DoorRequest.API.Config; using DoorRequest.API.Services; + using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; -using System.Threading.Tasks; + +using Shared.Authorization; namespace DoorRequest.API.Controllers; @@ -22,14 +26,14 @@ public DoorRequestController(IDoorService doorService, IOptions OpenDoorRequest() { return await _doorService.OpenDoor(); } [HttpGet("code")] - [Authorize(Roles = Authorization.Roles.KeyVaultCodeAccess)] + [Authorize(Roles = Roles.KeyVaultCodeAccess)] public int GetLockCode() { return _lockConfiguration.Code; diff --git a/API/DoorRequest.API/Program.cs b/API/DoorRequest.API/Program.cs index b9d6534..64bc71a 100644 --- a/API/DoorRequest.API/Program.cs +++ b/API/DoorRequest.API/Program.cs @@ -1,6 +1,9 @@ -using DoorRequest.API.Authorization; +using System; +using System.IdentityModel.Tokens.Jwt; + using DoorRequest.API.Config; using DoorRequest.API.Services; + using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; @@ -9,10 +12,11 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.IdentityModel.Tokens; + using Serilog; using Serilog.Events; -using System; -using System.IdentityModel.Tokens.Jwt; + +using Shared.Authorization; Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() diff --git a/API/DoorRequest.API/Properties/launchSettings.json b/API/DoorRequest.API/Properties/launchSettings.json index a328f99..c733f44 100644 --- a/API/DoorRequest.API/Properties/launchSettings.json +++ b/API/DoorRequest.API/Properties/launchSettings.json @@ -1,22 +1,5 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:63559", - "sslPort": 44300 - } - }, - "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "api/values", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, "DoorRequest.API": { "commandName": "Project", "launchUrl": "api/values", @@ -37,5 +20,14 @@ "useSSL": true, "sslPort": 44301 } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:63559", + "sslPort": 44300 + } } } \ No newline at end of file diff --git a/DoorRequest.sln b/DoorRequest.sln index 4356be7..3b50b2c 100644 --- a/DoorRequest.sln +++ b/DoorRequest.sln @@ -25,7 +25,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mosquitto", "Mosquitto", "{ Infrastructure\Mosquitto\config\mosquitto.conf = Infrastructure\Mosquitto\config\mosquitto.conf EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared", "Shared\Shared.csproj", "{51C4CCF5-F70A-45CF-BF76-9DF83B92E731}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "Shared\Shared.csproj", "{51C4CCF5-F70A-45CF-BF76-9DF83B92E731}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Web/Authorization/CustomUserFactory.cs b/Web/Authorization/CustomUserFactory.cs index 0074f8a..e584bfa 100644 --- a/Web/Authorization/CustomUserFactory.cs +++ b/Web/Authorization/CustomUserFactory.cs @@ -29,7 +29,7 @@ public async override ValueTask CreateUserAsync( return user; } - private void MapArrayClaimsToMultipleSeparateClaims(RemoteUserAccount account, ClaimsIdentity claimsIdentity) + private static void MapArrayClaimsToMultipleSeparateClaims(RemoteUserAccount account, ClaimsIdentity claimsIdentity) { foreach (var prop in account.AdditionalProperties) { diff --git a/Web/Extensions/DoorServiceExtensions.cs b/Web/Extensions/DoorServiceExtensions.cs new file mode 100644 index 0000000..0a503fb --- /dev/null +++ b/Web/Extensions/DoorServiceExtensions.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; + +using Web.Services; + +namespace Web.Extensions; + +public static class DoorServiceExtensions +{ + public static void AddDoorService(this IServiceCollection services) + { + services.AddScoped(); + + services.AddTransient(); + + services.AddHttpClient(options => + { + options.BaseAddress = new Uri("https://localhost:5001/api/DoorRequest/"); + }).AddHttpMessageHandler(); + } +} + +public class DoorRequestApiAuthorizationMessageHandler : AuthorizationMessageHandler +{ + public DoorRequestApiAuthorizationMessageHandler(IAccessTokenProvider provider, NavigationManager navigation) : base(provider, navigation) + { + ConfigureHandler( + authorizedUrls: new[] { "https://localhost:5001/api/DoorRequest/" }); + } +} \ No newline at end of file diff --git a/Web/Extensions/MudBlazorExtensions.cs b/Web/Extensions/MudBlazorExtensions.cs new file mode 100644 index 0000000..bf03892 --- /dev/null +++ b/Web/Extensions/MudBlazorExtensions.cs @@ -0,0 +1,15 @@ +using MudBlazor; +using MudBlazor.Services; + +namespace Web.Extensions; + +public static class MudBlazorExtensions +{ + public static IServiceCollection AddMudBlazor(this IServiceCollection services) + { + return services.AddMudServices(config => + { + config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomCenter; + }); + } +} diff --git a/Web/Pages/DoorControl.razor b/Web/Pages/DoorControl.razor new file mode 100644 index 0000000..b46dca5 --- /dev/null +++ b/Web/Pages/DoorControl.razor @@ -0,0 +1,24 @@ +@page "/" +@using Microsoft.AspNetCore.Authorization; +@using Web.Services; +@using global::Shared.Authorization; +@attribute [Authorize(Roles = Roles.TwentyFourSevenAccess)] + +

DoorService

+ +Buzz me in! + +@code { + [Inject] + private IDoorService DoorService { get; set; } = default!; + + [Inject] + private ISnackbar Snackbar { get; set; } = default!; + + public async Task OpenDoor() + { + await DoorService.OpenDoor(CancellationToken.None); + Snackbar.Add("Door buzzer triggered successfully!", Severity.Success); + } + +} diff --git a/Web/Pages/DoorService.razor b/Web/Pages/DoorService.razor deleted file mode 100644 index 405e3bf..0000000 --- a/Web/Pages/DoorService.razor +++ /dev/null @@ -1,10 +0,0 @@ -@page "/" -@using Microsoft.AspNetCore.Authorization; -@using global::Shared.Authorization; -@attribute [Authorize(Roles = Roles.TwentyFourSevenAccess)] - -

DoorService

- -@code { - -} diff --git a/Web/Program.cs b/Web/Program.cs index 8ee1a17..5466504 100644 --- a/Web/Program.cs +++ b/Web/Program.cs @@ -1,12 +1,11 @@ using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using MudBlazor.Services; - using Shared.Authorization; using Web; using Web.Authorization; +using Web.Extensions; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); @@ -20,11 +19,10 @@ // For more information, see https://aka.ms/blazor-standalone-auth builder.Configuration.Bind("Local", options.ProviderOptions); options.UserOptions.RoleClaim = CustomClaims.Roles; -}); +}).AddAccountClaimsPrincipalFactory(); -builder.Services.AddApiAuthorization() - .AddAccountClaimsPrincipalFactory(); +builder.Services.AddMudBlazor(); -builder.Services.AddMudServices(); +builder.Services.AddDoorService(); await builder.Build().RunAsync(); diff --git a/Web/Properties/launchSettings.json b/Web/Properties/launchSettings.json index efccf95..3afd294 100644 --- a/Web/Properties/launchSettings.json +++ b/Web/Properties/launchSettings.json @@ -1,30 +1,22 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:13524", - "sslPort": 44322 - } - }, "profiles": { "https": { "commandName": "Project", - "dotnetRunMessages": true, "launchBrowser": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "https://localhost:7104;http://localhost:5204", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, + }, + "dotnetRunMessages": true, "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } + "applicationUrl": "https://localhost:7104" + } + }, + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:13524", + "sslPort": 44322 } } -} +} \ No newline at end of file diff --git a/Web/Services/DoorService.cs b/Web/Services/DoorService.cs new file mode 100644 index 0000000..accaa21 --- /dev/null +++ b/Web/Services/DoorService.cs @@ -0,0 +1,22 @@ +namespace Web.Services; + +public interface IDoorService +{ + Task OpenDoor(CancellationToken ct); +} + +public class DoorService : IDoorService +{ + private readonly HttpClient _httpClient; + + public DoorService(HttpClient httpClient) + { + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + } + + public async Task OpenDoor(CancellationToken ct) + { + var response = await _httpClient.PostAsync("open", null, ct); + response.EnsureSuccessStatusCode(); + } +} diff --git a/Web/Shared/MainLayout.razor b/Web/Shared/MainLayout.razor index b53de06..75071f2 100644 --- a/Web/Shared/MainLayout.razor +++ b/Web/Shared/MainLayout.razor @@ -2,6 +2,8 @@ + + diff --git a/Web/Web.csproj b/Web/Web.csproj index b24d3e3..da774f1 100644 --- a/Web/Web.csproj +++ b/Web/Web.csproj @@ -22,6 +22,7 @@ + From 539d784f0bc0553fafe0a9e58ce4bc50644ca8a9 Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Wed, 8 Nov 2023 21:25:58 +0100 Subject: [PATCH 06/22] chore: Add non-0 lock code for local debug config --- API/DoorRequest.API/appsettings.Development.json | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/API/DoorRequest.API/appsettings.Development.json b/API/DoorRequest.API/appsettings.Development.json index e4846b9..49ccd61 100644 --- a/API/DoorRequest.API/appsettings.Development.json +++ b/API/DoorRequest.API/appsettings.Development.json @@ -21,10 +21,13 @@ "Authentication": { "Authority": "http://localhost:5302/realms/DevRealm" }, - "MQTTDoorConfiguration": { - "Server": "localhost", - "Port": 5303, - "UseSSL": false, - "Topic": "some/topic" - } + "MQTTDoorConfiguration": { + "Server": "localhost", + "Port": 5303, + "UseSSL": false, + "Topic": "some/topic" + }, + "LockConfiguration": { + "Code": 123 + } } From 292da19bd32ff2208aeb6ef3ed7cd787dcb0163d Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Wed, 8 Nov 2023 21:26:43 +0100 Subject: [PATCH 07/22] feat(Web): Get API url from configuration --- Web/Configuration/ApiConfiguration.cs | 11 ++++++++++ Web/Extensions/DoorServiceExtensions.cs | 28 +++++++++++++++++------- Web/wwwroot/appsettings.Development.json | 3 +++ 3 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 Web/Configuration/ApiConfiguration.cs diff --git a/Web/Configuration/ApiConfiguration.cs b/Web/Configuration/ApiConfiguration.cs new file mode 100644 index 0000000..554f7b7 --- /dev/null +++ b/Web/Configuration/ApiConfiguration.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; + +namespace Web.Configuration; + +public class ApiConfiguration +{ + public const string SectionName = "ApiConfiguration"; + + [Url, Required(AllowEmptyStrings = false)] + public string BaseUrl { get; set; } = default!; +} diff --git a/Web/Extensions/DoorServiceExtensions.cs b/Web/Extensions/DoorServiceExtensions.cs index 0a503fb..a08dca0 100644 --- a/Web/Extensions/DoorServiceExtensions.cs +++ b/Web/Extensions/DoorServiceExtensions.cs @@ -1,30 +1,42 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.WebAssembly.Authentication; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using Web.Configuration; using Web.Services; namespace Web.Extensions; public static class DoorServiceExtensions { - public static void AddDoorService(this IServiceCollection services) + public static void AddDoorService(this IServiceCollection services, IConfiguration configuration) { + services.AddOptions() + .Bind(configuration.GetSection(ApiConfiguration.SectionName)) + .ValidateDataAnnotations(); + services.AddScoped(); - services.AddTransient(); + services.TryAddTransient(); - services.AddHttpClient(options => + services.AddHttpClient((provider, options) => { - options.BaseAddress = new Uri("https://localhost:5001/api/DoorRequest/"); - }).AddHttpMessageHandler(); + var configuration = provider.GetRequiredService>().Value; + + options.BaseAddress = new Uri($"{configuration.BaseUrl}/doorrequest/"); + }).AddHttpMessageHandler(); } } -public class DoorRequestApiAuthorizationMessageHandler : AuthorizationMessageHandler +public class ApiAuthorizationMessageHandler : AuthorizationMessageHandler { - public DoorRequestApiAuthorizationMessageHandler(IAccessTokenProvider provider, NavigationManager navigation) : base(provider, navigation) + public ApiAuthorizationMessageHandler( + IAccessTokenProvider provider, + NavigationManager navigation, + IOptions configuration) : base(provider, navigation) { ConfigureHandler( - authorizedUrls: new[] { "https://localhost:5001/api/DoorRequest/" }); + authorizedUrls: new[] { configuration.Value.BaseUrl }); } } \ No newline at end of file diff --git a/Web/wwwroot/appsettings.Development.json b/Web/wwwroot/appsettings.Development.json index 5c980a9..b8fc793 100644 --- a/Web/wwwroot/appsettings.Development.json +++ b/Web/wwwroot/appsettings.Development.json @@ -3,5 +3,8 @@ "Authority": "http://localhost:5302/realms/DevRealm", "ClientId": "door-request-webapp", "ResponseType": "code" + }, + "ApiConfiguration": { + "BaseUrl": "https://localhost:5001/api" } } From 9eae0e46a8a6db5aab647744d370dbec4a45c114 Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Wed, 8 Nov 2023 21:27:56 +0100 Subject: [PATCH 08/22] feat: Get app title from configuration --- Web/Shared/MainLayout.razor | 16 ++++++++++------ Web/wwwroot/appsettings.json | 9 +++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Web/Shared/MainLayout.razor b/Web/Shared/MainLayout.razor index 75071f2..a6a6649 100644 --- a/Web/Shared/MainLayout.razor +++ b/Web/Shared/MainLayout.razor @@ -1,13 +1,14 @@ @inherits LayoutComponentBase - - DoorApp + + @Title + @code { + private string Title = "DoorApp"; private bool _isDarkMode; private MudThemeProvider _mudThemeProvider = default!; + [Inject] + private IConfiguration Configuration { get; set; } = default!; + private readonly MudTheme _currentTheme = new() { Palette = new PaletteLight @@ -37,8 +42,7 @@ Error = "#FF0000", AppbarBackground = "#212121", TextPrimary = "#0A7BCF", - TextSecondary = "#4CAF50", - // more color properties + TextSecondary = "#4CAF50" }, PaletteDark = new PaletteDark { @@ -50,13 +54,13 @@ Error = "#FF0000", AppbarBackground = "#121212", TextPrimary = "#E0E0E0", - TextSecondary = "#BDBDBD", - // more color properties + TextSecondary = "#BDBDBD" } }; protected override async Task OnAfterRenderAsync(bool firstRender) { + Title = Configuration.GetValue("AppName") ?? "DoorApp"; if (firstRender) { _isDarkMode = await _mudThemeProvider.GetSystemPreference(); diff --git a/Web/wwwroot/appsettings.json b/Web/wwwroot/appsettings.json index c0d95df..4bbc06d 100644 --- a/Web/wwwroot/appsettings.json +++ b/Web/wwwroot/appsettings.json @@ -1,6 +1,7 @@ { - "Local": { - "Authority": "", - "ClientId": "" - } + "Local": { + "Authority": "", + "ClientId": "" + }, + "AppName": "HCKSPC DoorApp" } From e6aa168cfe8d9d63044031c160ce0bb784bdffba Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Wed, 8 Nov 2023 21:28:48 +0100 Subject: [PATCH 09/22] feat(Web): Show collapsed user menu on small screens --- Web/Shared/LoginDisplay.razor | 37 ++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/Web/Shared/LoginDisplay.razor b/Web/Shared/LoginDisplay.razor index 69940b1..7227b00 100644 --- a/Web/Shared/LoginDisplay.razor +++ b/Web/Shared/LoginDisplay.razor @@ -2,16 +2,35 @@ @using Microsoft.AspNetCore.Components.WebAssembly.Authentication @inject NavigationManager Navigation - - - Hello, @context.User.Identity?.Name! - Log out + + + + + Hello, @context.User.Identity?.Name! + + Log out + + + + + Log in + + + + + + + + + Hello, @context.User.Identity?.Name! + Log out - - - Log in - - + + + Log in + + + @code { private void BeginLogOut() From 3983ea2036d5ade2433aee600cb7c3ff6c253baa Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Wed, 8 Nov 2023 21:30:51 +0100 Subject: [PATCH 10/22] feat(Web): Show loading spinner while app is loading --- Web/wwwroot/css/app.css | 70 +++++++++++++++++++++++++++++++++++++++++ Web/wwwroot/index.html | 9 +++--- 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/Web/wwwroot/css/app.css b/Web/wwwroot/css/app.css index e69de29..8c45091 100644 --- a/Web/wwwroot/css/app.css +++ b/Web/wwwroot/css/app.css @@ -0,0 +1,70 @@ +div#app { + height: 100vh; +} + +div#loader { + background-color: rgba(50,51,61,1); + display: flex; + justify-content: center; + padding-top: 4rem; + height: 100vh; +} + +.loader { + width: 48px; + height: 48px; + border-radius: 50%; + display: inline-block; + position: relative; + border: 3px solid; + border-color: #FFF #FFF transparent transparent; + box-sizing: border-box; + animation: rotation 1s linear infinite; +} + + .loader::after, + .loader::before { + content: ''; + box-sizing: border-box; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + margin: auto; + border: 3px solid; + border-color: transparent transparent #FF3D00 #FF3D00; + width: 40px; + height: 40px; + border-radius: 50%; + box-sizing: border-box; + animation: rotationBack 0.5s linear infinite; + transform-origin: center center; + } + + .loader::before { + width: 32px; + height: 32px; + border-color: #FFF #FFF transparent transparent; + animation: rotation 1.5s linear infinite; + } + +@keyframes rotation { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +@keyframes rotationBack { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(-360deg); + } +} diff --git a/Web/wwwroot/index.html b/Web/wwwroot/index.html index d20aadd..999024d 100644 --- a/Web/wwwroot/index.html +++ b/Web/wwwroot/index.html @@ -18,11 +18,9 @@
- - - - -
+
+ +
@@ -34,6 +32,7 @@ + From fdc21b2b98200f5c385823467574eb128c25e30f Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Wed, 8 Nov 2023 21:32:25 +0100 Subject: [PATCH 11/22] fix: Add Api configuration --- Web/Program.cs | 2 +- Web/Web.csproj | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Web/Program.cs b/Web/Program.cs index 5466504..35e4aee 100644 --- a/Web/Program.cs +++ b/Web/Program.cs @@ -23,6 +23,6 @@ builder.Services.AddMudBlazor(); -builder.Services.AddDoorService(); +builder.Services.AddDoorService(builder.Configuration); await builder.Build().RunAsync(); diff --git a/Web/Web.csproj b/Web/Web.csproj index da774f1..15c7969 100644 --- a/Web/Web.csproj +++ b/Web/Web.csproj @@ -6,15 +6,16 @@ enable service-worker-assets.js True + $(MSBuildProjectName) - False + True True - False + True True @@ -23,8 +24,10 @@ + + - + From 71b7c190a4cdf3686ee04af2696f26accf378f34 Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Wed, 8 Nov 2023 21:33:28 +0100 Subject: [PATCH 12/22] feat(Web): Finalized DoorControl page with online/offline UX and key vault code display --- Web/Pages/DoorControl.razor | 84 +++++++++++++++++++++++++++++++++---- Web/Services/DoorService.cs | 8 +++- Web/wwwroot/web.js | 23 ++++++++++ 3 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 Web/wwwroot/web.js diff --git a/Web/Pages/DoorControl.razor b/Web/Pages/DoorControl.razor index b46dca5..fc079bb 100644 --- a/Web/Pages/DoorControl.razor +++ b/Web/Pages/DoorControl.razor @@ -1,24 +1,94 @@ @page "/" + @using Microsoft.AspNetCore.Authorization; @using Web.Services; @using global::Shared.Authorization; -@attribute [Authorize(Roles = Roles.TwentyFourSevenAccess)] -

DoorService

+@implements IDisposable; + +@inject IDoorService DoorService; +@inject ISnackbar Snackbar; +@inject IJSRuntime JsRuntime; + +@attribute [Authorize(Roles = Roles.TwentyFourSevenAccess)] -Buzz me in! +
+ + @if (OpeningDoor) + { + + Opening door... + } + else + { + Buzz me in! + } + +
+
+ + + + + Key vault code + + + + @if (KeyVaultCode is null) + { + + } + else + { + @KeyVaultCode + } + + + +
@code { - [Inject] - private IDoorService DoorService { get; set; } = default!; + private int? KeyVaultCode { get; set; } - [Inject] - private ISnackbar Snackbar { get; set; } = default!; + private bool OpeningDoor; + private bool IsOnline; public async Task OpenDoor() { + OpeningDoor = true; await DoorService.OpenDoor(CancellationToken.None); Snackbar.Add("Door buzzer triggered successfully!", Severity.Success); + OpeningDoor = false; } + protected override async Task OnInitializedAsync() + { + KeyVaultCode = await DoorService.GetCode(CancellationToken.None); + } + + [JSInvokable("Connection.StatusChanged")] + public void OnConnectionStatusChanged(bool isOnline) + { + if (IsOnline != isOnline) + { + IsOnline = isOnline; + } + + if (!IsOnline) + Snackbar.Add("Lost network connection", Severity.Error); + + StateHasChanged(); + } + + protected override void OnInitialized() + { + base.OnInitialized(); + + JsRuntime.InvokeVoidAsync("Connection.Initialize", DotNetObjectReference.Create(this)); + } + + public void Dispose() + { + JsRuntime.InvokeVoidAsync("Connection.Dispose"); + } } diff --git a/Web/Services/DoorService.cs b/Web/Services/DoorService.cs index accaa21..c017a74 100644 --- a/Web/Services/DoorService.cs +++ b/Web/Services/DoorService.cs @@ -1,7 +1,10 @@ -namespace Web.Services; +using System.Net.Http.Json; + +namespace Web.Services; public interface IDoorService { + Task GetCode(CancellationToken ct); Task OpenDoor(CancellationToken ct); } @@ -19,4 +22,7 @@ public async Task OpenDoor(CancellationToken ct) var response = await _httpClient.PostAsync("open", null, ct); response.EnsureSuccessStatusCode(); } + + public Task GetCode(CancellationToken ct) + => _httpClient.GetFromJsonAsync("code", ct); } diff --git a/Web/wwwroot/web.js b/Web/wwwroot/web.js new file mode 100644 index 0000000..8a9809a --- /dev/null +++ b/Web/wwwroot/web.js @@ -0,0 +1,23 @@ +let handler; + +window.Connection = { + Initialize: function (interop) { + + handler = function () { + interop.invokeMethodAsync("Connection.StatusChanged", navigator.onLine); + } + + window.addEventListener("online", handler); + window.addEventListener("offline", handler); + + handler(navigator.onLine); + }, + Dispose: function () { + + if (handler != null) { + + window.removeEventListener("online", handler); + window.removeEventListener("offline", handler); + } + } +}; From 02d4c89451262c57b2d4c723c67721cecb4091f1 Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Wed, 8 Nov 2023 21:33:44 +0100 Subject: [PATCH 13/22] feat(web): Show user that login is required, instead of just redirecting to login --- Web/App.razor | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Web/App.razor b/Web/App.razor index d07039a..5110f3e 100644 --- a/Web/App.razor +++ b/Web/App.razor @@ -5,7 +5,8 @@ @if (context.User.Identity?.IsAuthenticated != true) { - + + Please log in } else { From 6e4b5b27450f796fee5fcd0cd17de842ec15693f Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Wed, 8 Nov 2023 23:52:29 +0100 Subject: [PATCH 14/22] feat(Web): Set page title to app title --- Web/Shared/MainLayout.razor | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Web/Shared/MainLayout.razor b/Web/Shared/MainLayout.razor index a6a6649..47ac04c 100644 --- a/Web/Shared/MainLayout.razor +++ b/Web/Shared/MainLayout.razor @@ -1,5 +1,7 @@ @inherits LayoutComponentBase +@Title + From 1603c130c96051140126f2a67f9d383df6b9b6e2 Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Wed, 8 Nov 2023 23:52:46 +0100 Subject: [PATCH 15/22] ci(Web): Added Dockerfile and nginx config --- Web/Dockerfile | 17 +++++++++++++++++ Web/nginx.conf | 15 +++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 Web/Dockerfile create mode 100644 Web/nginx.conf diff --git a/Web/Dockerfile b/Web/Dockerfile new file mode 100644 index 0000000..efda937 --- /dev/null +++ b/Web/Dockerfile @@ -0,0 +1,17 @@ +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build-env +#RUN apk add nodejs +#RUN apk add npm +RUN apt-get update && apt-get install -y python3 +WORKDIR /app +COPY . ./ +RUN dotnet workload restore +#RUN npm --prefix Web install +RUN dotnet build "Web/Web.csproj" -c Release +RUN dotnet publish "Web/Web.csproj" -c Release -o output +RUN rm output/wwwroot/appsettings.json.br && rm output/wwwroot/appsettings.json.gz + +FROM nginx:alpine +WORKDIR /usr/share/nginx/html +COPY --from=build-env /app/output/wwwroot . +COPY Web/nginx.conf /etc/nginx/nginx.conf +EXPOSE 80 \ No newline at end of file diff --git a/Web/nginx.conf b/Web/nginx.conf new file mode 100644 index 0000000..a5d350d --- /dev/null +++ b/Web/nginx.conf @@ -0,0 +1,15 @@ +events { } +http { + include mime.types; + types { + application/wasm; + } + server { + listen 80; + index index.html; + location / { + root /usr/share/nginx/html; + try_files $uri $uri/ /index.html =404; + } + } +} \ No newline at end of file From 5d3c031bc74dd61ce58c3f1d2b6a2365be69d92b Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Fri, 10 Nov 2023 21:17:15 +0100 Subject: [PATCH 16/22] feat: Added health check endpoint to API --- API/DoorRequest.API/Health.http | 6 ++++++ API/DoorRequest.API/Program.cs | 2 ++ 2 files changed, 8 insertions(+) create mode 100644 API/DoorRequest.API/Health.http diff --git a/API/DoorRequest.API/Health.http b/API/DoorRequest.API/Health.http new file mode 100644 index 0000000..488bb30 --- /dev/null +++ b/API/DoorRequest.API/Health.http @@ -0,0 +1,6 @@ +# For more info on HTTP files go to https://aka.ms/vs/httpfile +@rootUrl=https://localhost:5001 + +HEAD {{rootUrl}}/healthz + +# HEAD {{rootUrl}}/healthz \ No newline at end of file diff --git a/API/DoorRequest.API/Program.cs b/API/DoorRequest.API/Program.cs index 64bc71a..fec6cea 100644 --- a/API/DoorRequest.API/Program.cs +++ b/API/DoorRequest.API/Program.cs @@ -37,6 +37,7 @@ builder.Services.AddSwaggerGen(); builder.Services.AddControllers(); + builder.Services.AddHealthChecks(); var authOptions = builder.Configuration.GetSection(AuthenticationConfiguration.SectionName).Get(); @@ -105,6 +106,7 @@ app.UseAuthorization(); app.MapControllers(); + app.MapHealthChecks("/healthz"); app.Run(); } From 2e9bc3998d50df8b7b5e15161ebda5c3b7ffad14 Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Fri, 10 Nov 2023 21:25:53 +0100 Subject: [PATCH 17/22] feat: Replaced connection check with regular pings to API health check endpoint --- Web/App.razor | 12 ++++- Web/Extensions/ApiOptionsExtensions.cs | 15 ++++++ Web/Extensions/ApiServiceExtensions.cs | 21 +++++++++ Web/Extensions/DoorServiceExtensions.cs | 8 +--- Web/Pages/DoorControl.razor | 52 ++++++++------------- Web/Program.cs | 6 ++- Web/Services/ApiService.cs | 29 ++++++++++++ Web/Services/ConnectionStatusService.cs | 59 ++++++++++++++++++++++++ Web/Services/IConnectionStatusService.cs | 10 ++++ Web/wwwroot/appsettings.Development.json | 2 +- 10 files changed, 173 insertions(+), 41 deletions(-) create mode 100644 Web/Extensions/ApiOptionsExtensions.cs create mode 100644 Web/Extensions/ApiServiceExtensions.cs create mode 100644 Web/Services/ApiService.cs create mode 100644 Web/Services/ConnectionStatusService.cs create mode 100644 Web/Services/IConnectionStatusService.cs diff --git a/Web/App.razor b/Web/App.razor index 5110f3e..e5794c4 100644 --- a/Web/App.razor +++ b/Web/App.razor @@ -1,4 +1,7 @@ - +@using Web.Services; +@inject IConnectionStatusService ConnectionStatusService; + + @@ -24,3 +27,10 @@ + +@code { + protected override async Task OnInitializedAsync() + { + await ConnectionStatusService.StartPeriodicChecks(CancellationToken.None); + } +} \ No newline at end of file diff --git a/Web/Extensions/ApiOptionsExtensions.cs b/Web/Extensions/ApiOptionsExtensions.cs new file mode 100644 index 0000000..0ea38e5 --- /dev/null +++ b/Web/Extensions/ApiOptionsExtensions.cs @@ -0,0 +1,15 @@ +using Web.Configuration; + +namespace Web.Extensions; + +public static class ApiOptionsExtensions +{ + public static IServiceCollection ConfigureApiOptions(this IServiceCollection services, IConfiguration configuration) + { + services.AddOptions() + .Bind(configuration.GetSection(ApiConfiguration.SectionName)) + .ValidateDataAnnotations(); + + return services; + } +} diff --git a/Web/Extensions/ApiServiceExtensions.cs b/Web/Extensions/ApiServiceExtensions.cs new file mode 100644 index 0000000..5fa3f14 --- /dev/null +++ b/Web/Extensions/ApiServiceExtensions.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.Options; + +using Web.Configuration; +using Web.Services; + +namespace Web.Extensions; + +public static class ApiServiceExtensions +{ + public static void AddApiService(this IServiceCollection services) + { + services.AddScoped(); + + services.AddHttpClient((provider, options) => + { + var configuration = provider.GetRequiredService>().Value; + + options.BaseAddress = new Uri(configuration.BaseUrl); + }); + } +} \ No newline at end of file diff --git a/Web/Extensions/DoorServiceExtensions.cs b/Web/Extensions/DoorServiceExtensions.cs index a08dca0..a5dc9dd 100644 --- a/Web/Extensions/DoorServiceExtensions.cs +++ b/Web/Extensions/DoorServiceExtensions.cs @@ -10,12 +10,8 @@ namespace Web.Extensions; public static class DoorServiceExtensions { - public static void AddDoorService(this IServiceCollection services, IConfiguration configuration) + public static void AddDoorService(this IServiceCollection services) { - services.AddOptions() - .Bind(configuration.GetSection(ApiConfiguration.SectionName)) - .ValidateDataAnnotations(); - services.AddScoped(); services.TryAddTransient(); @@ -24,7 +20,7 @@ public static void AddDoorService(this IServiceCollection services, IConfigurati { var configuration = provider.GetRequiredService>().Value; - options.BaseAddress = new Uri($"{configuration.BaseUrl}/doorrequest/"); + options.BaseAddress = new Uri($"{configuration.BaseUrl}/api/doorrequest/"); }).AddHttpMessageHandler(); } } diff --git a/Web/Pages/DoorControl.razor b/Web/Pages/DoorControl.razor index fc079bb..302f447 100644 --- a/Web/Pages/DoorControl.razor +++ b/Web/Pages/DoorControl.razor @@ -4,11 +4,9 @@ @using Web.Services; @using global::Shared.Authorization; -@implements IDisposable; - @inject IDoorService DoorService; @inject ISnackbar Snackbar; -@inject IJSRuntime JsRuntime; +@inject IConnectionStatusService ConnectionStatusService; @attribute [Authorize(Roles = Roles.TwentyFourSevenAccess)] @@ -34,13 +32,13 @@ - @if (KeyVaultCode is null) + @if (KeyVaultCode is not null) { - + @KeyVaultCode } else { - @KeyVaultCode + } @@ -51,7 +49,8 @@ private int? KeyVaultCode { get; set; } private bool OpeningDoor; - private bool IsOnline; + + private bool IsOnline => ConnectionStatusService.ApiReachable; public async Task OpenDoor() { @@ -63,32 +62,21 @@ protected override async Task OnInitializedAsync() { - KeyVaultCode = await DoorService.GetCode(CancellationToken.None); - } - - [JSInvokable("Connection.StatusChanged")] - public void OnConnectionStatusChanged(bool isOnline) - { - if (IsOnline != isOnline) + ConnectionStatusService.AddOnDisconnectedAction((ct) => { - IsOnline = isOnline; - } + Snackbar.Clear(); + Snackbar.Add("Can not connect to API service", Severity.Error); + StateHasChanged(); + return Task.CompletedTask; + }); - if (!IsOnline) - Snackbar.Add("Lost network connection", Severity.Error); - - StateHasChanged(); - } - - protected override void OnInitialized() - { - base.OnInitialized(); - - JsRuntime.InvokeVoidAsync("Connection.Initialize", DotNetObjectReference.Create(this)); - } - - public void Dispose() - { - JsRuntime.InvokeVoidAsync("Connection.Dispose"); + ConnectionStatusService.AddOnConnectedAction((ct) => + { + Snackbar.Clear(); + Snackbar.Add("API Service reachable", Severity.Success); + StateHasChanged(); + return Task.CompletedTask; + }); + KeyVaultCode = await DoorService.GetCode(CancellationToken.None); } } diff --git a/Web/Program.cs b/Web/Program.cs index 35e4aee..76be470 100644 --- a/Web/Program.cs +++ b/Web/Program.cs @@ -6,6 +6,7 @@ using Web; using Web.Authorization; using Web.Extensions; +using Web.Services; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); @@ -23,6 +24,9 @@ builder.Services.AddMudBlazor(); -builder.Services.AddDoorService(builder.Configuration); +builder.Services.ConfigureApiOptions(builder.Configuration); +builder.Services.AddDoorService(); +builder.Services.AddApiService(); +builder.Services.AddSingleton(); await builder.Build().RunAsync(); diff --git a/Web/Services/ApiService.cs b/Web/Services/ApiService.cs new file mode 100644 index 0000000..4b10e6d --- /dev/null +++ b/Web/Services/ApiService.cs @@ -0,0 +1,29 @@ +namespace Web.Services; + +public interface IApiService +{ + Task IsHealthy(CancellationToken ct); +} + +public class ApiService : IApiService +{ + private readonly HttpClient _httpClient; + + public ApiService(HttpClient httpClient) + { + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + } + + public async Task IsHealthy(CancellationToken ct) + { + try + { + var response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "/healthz"), ct); + return response.IsSuccessStatusCode; + } + catch (HttpRequestException) + { + return false; + } + } +} diff --git a/Web/Services/ConnectionStatusService.cs b/Web/Services/ConnectionStatusService.cs new file mode 100644 index 0000000..fd30ee5 --- /dev/null +++ b/Web/Services/ConnectionStatusService.cs @@ -0,0 +1,59 @@ +namespace Web.Services; + +public class ConnectionStatusService : IConnectionStatusService +{ + private readonly IApiService _apiService; + public bool ApiReachable { get; private set; } = true; + + private readonly List> _onDisconnectedActions = new(); + private readonly List> _onConnectedActions = new(); + + public ConnectionStatusService(IApiService apiService) + { + _apiService = apiService ?? throw new ArgumentNullException(nameof(apiService)); + } + + public async Task StartPeriodicChecks(CancellationToken ct) + { + var timer = new PeriodicTimer(TimeSpan.FromSeconds(1)); + while (!ct.IsCancellationRequested + && await timer.WaitForNextTickAsync(ct)) + { + var reachable = await _apiService.IsHealthy(ct); + if (reachable != ApiReachable) + { + ApiReachable = reachable; + if (reachable) + await OnConnected(ct); + else + await OnDisconnected(ct); + } + } + } + + public void AddOnDisconnectedAction(Func action) + { + _onDisconnectedActions.Add(action); + } + + public void AddOnConnectedAction(Func action) + { + _onConnectedActions.Add(action); + } + + private async Task OnDisconnected(CancellationToken ct) + { + foreach (var task in _onDisconnectedActions) + { + await task(ct); + } + } + + private async Task OnConnected(CancellationToken ct) + { + foreach (var task in _onConnectedActions) + { + await task(ct); + } + } +} \ No newline at end of file diff --git a/Web/Services/IConnectionStatusService.cs b/Web/Services/IConnectionStatusService.cs new file mode 100644 index 0000000..66c40a7 --- /dev/null +++ b/Web/Services/IConnectionStatusService.cs @@ -0,0 +1,10 @@ +namespace Web.Services; + +public interface IConnectionStatusService +{ + bool ApiReachable { get; } + + void AddOnConnectedAction(Func action); + void AddOnDisconnectedAction(Func action); + Task StartPeriodicChecks(CancellationToken ct); +} \ No newline at end of file diff --git a/Web/wwwroot/appsettings.Development.json b/Web/wwwroot/appsettings.Development.json index b8fc793..3fce7f4 100644 --- a/Web/wwwroot/appsettings.Development.json +++ b/Web/wwwroot/appsettings.Development.json @@ -5,6 +5,6 @@ "ResponseType": "code" }, "ApiConfiguration": { - "BaseUrl": "https://localhost:5001/api" + "BaseUrl": "https://localhost:5001" } } From 3abe09aeea423889f7b6cfe1a4068c5a8b73b81a Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Fri, 10 Nov 2023 22:35:56 +0100 Subject: [PATCH 18/22] fix: Revert API and Shared project back to .NET6 --- API/DoorRequest.API/DoorRequest.API.csproj | 2 +- DoorRequest.sln | 1 + Shared/Shared.csproj | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/API/DoorRequest.API/DoorRequest.API.csproj b/API/DoorRequest.API/DoorRequest.API.csproj index 9d71524..19236bb 100644 --- a/API/DoorRequest.API/DoorRequest.API.csproj +++ b/API/DoorRequest.API/DoorRequest.API.csproj @@ -1,7 +1,7 @@  - net7.0 + net6.0 a32e9851-ef6a-48c6-803f-7ea1cb26c158 Linux ..\.. diff --git a/DoorRequest.sln b/DoorRequest.sln index 3b50b2c..aedccff 100644 --- a/DoorRequest.sln +++ b/DoorRequest.sln @@ -11,6 +11,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution items", "Solution ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig docker-compose.yml = docker-compose.yml + GitVersion.yml = GitVersion.yml EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{1CD813E1-F654-4340-8DCA-0935FBBA9EE2}" diff --git a/Shared/Shared.csproj b/Shared/Shared.csproj index cfadb03..132c02c 100644 --- a/Shared/Shared.csproj +++ b/Shared/Shared.csproj @@ -1,7 +1,7 @@ - net7.0 + net6.0 enable enable From 621a88f96a485057bf1318a3cf55594dcbf3d4dc Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Fri, 10 Nov 2023 22:41:51 +0100 Subject: [PATCH 19/22] ci: Add web image build/push to github registry --- .github/workflows/docker-image.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 6961638..411bb8e 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -1,7 +1,7 @@ name: Publish on: push: - branches: [master] + branches: [master, blazor-webapp] env: REGISTRY: ghcr.io @@ -34,10 +34,12 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: ${{ env.REGISTRY }}/brixel/door-request-api + images: | + ${{ env.REGISTRY }}/brixel/door-request-api + ${{ env.REGISTRY }}/brixel/door-request-web tags: type=semver,pattern={{version}},value=${{ steps.gitversion.outputs.semVer }} - - name: Build and push - id: docker_build + - name: Build and push API + id: docker_build_api uses: docker/build-push-action@v5 with: context: . @@ -45,3 +47,12 @@ jobs: file: API/DoorRequest.API/Dockerfile tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + - name: Build and push Web + id: docker_build_web + uses: docker/build-push-action@v5 + with: + context: . + push: true + file: Web/Dockerfile + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} From a67295e1aedacf91f7e4d49065d92d0395386e7a Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Fri, 10 Nov 2023 23:43:00 +0100 Subject: [PATCH 20/22] fix: Simplify CORS config --- API/DoorRequest.API/Program.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/API/DoorRequest.API/Program.cs b/API/DoorRequest.API/Program.cs index fec6cea..9b3fcf6 100644 --- a/API/DoorRequest.API/Program.cs +++ b/API/DoorRequest.API/Program.cs @@ -73,15 +73,6 @@ .Build(); }); - builder.Services.AddCors(options => - { - //TODO: Fix CORS - options.AddPolicy("CorsPolicy", builder => - builder.AllowAnyOrigin() - .AllowAnyMethod() - .AllowAnyHeader()); - }); - builder.Services.AddOptions() .Bind(builder.Configuration.GetSection(DoorConfiguration.SectionName)) .ValidateDataAnnotations() @@ -100,7 +91,11 @@ } app.UseHttpsRedirection(); - app.UseCors("CorsPolicy"); + app.UseCors(builder => + //TODO: Fix CORS + builder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader()); app.UseAuthentication(); app.UseAuthorization(); From 76269d85fba8eff24605bb7a1d1698c031a980a4 Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Sat, 11 Nov 2023 21:38:39 +0100 Subject: [PATCH 21/22] ci: Split up api and web docker image build --- .github/workflows/docker-image.yml | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 411bb8e..dc43516 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -3,15 +3,20 @@ on: push: branches: [master, blazor-webapp] -env: - REGISTRY: ghcr.io - jobs: publish: runs-on: ubuntu-latest permissions: contents: write packages: write + strategy: + fail-fast: false + matrix: + include: + - dockerfile: ./API/DoorRequest.API/Dockerfile + image: ghcr.io/brixel/door-request-api + - dockerfile: ./Web/Dockerfile + image: ghcr.io/brixel/door-request-web steps: - name: Checkout uses: actions/checkout@v4 @@ -20,7 +25,7 @@ jobs: - name: Log in to the Container registry uses: docker/login-action@v3 with: - registry: ${{ env.REGISTRY }} + registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Install GitVersion @@ -34,25 +39,14 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: | - ${{ env.REGISTRY }}/brixel/door-request-api - ${{ env.REGISTRY }}/brixel/door-request-web + images: ${{ matrix.image }} tags: type=semver,pattern={{version}},value=${{ steps.gitversion.outputs.semVer }} - - name: Build and push API + - name: Build and push docker image id: docker_build_api uses: docker/build-push-action@v5 with: context: . push: true - file: API/DoorRequest.API/Dockerfile - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - name: Build and push Web - id: docker_build_web - uses: docker/build-push-action@v5 - with: - context: . - push: true - file: Web/Dockerfile + file: ${{ matrix.dockerfile }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} From 20312d5143437ef2e9c544fc7bbdf57b75fd49fd Mon Sep 17 00:00:00 2001 From: Alex Goris Date: Sat, 11 Nov 2023 22:30:40 +0100 Subject: [PATCH 22/22] ci: Use user-friendly name for build jobs, and only build on master --- .github/workflows/docker-image.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index dc43516..d316a40 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -1,10 +1,11 @@ name: Publish on: push: - branches: [master, blazor-webapp] + branches: [master] jobs: publish: + name: Publish ${{ matrix.image }} runs-on: ubuntu-latest permissions: contents: write