From 602ef4f3cf1f0b52d66ba036ef8d62034fe0703f Mon Sep 17 00:00:00 2001 From: Cody Owens Date: Thu, 12 Feb 2026 18:26:51 -0700 Subject: [PATCH 1/6] building auth branch; following video to set up authentication --- DungeonMasterDashboard.csproj | 1 + Pages/Authentication.razor | 8 -------- Program.cs | 8 ++++++++ Services/ApplicationDbContext.cs | 16 ++++++++++++++++ 4 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 Services/ApplicationDbContext.cs diff --git a/DungeonMasterDashboard.csproj b/DungeonMasterDashboard.csproj index 9fc42bd..2007546 100644 --- a/DungeonMasterDashboard.csproj +++ b/DungeonMasterDashboard.csproj @@ -15,6 +15,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Pages/Authentication.razor b/Pages/Authentication.razor index a773ba0..6c74356 100644 --- a/Pages/Authentication.razor +++ b/Pages/Authentication.razor @@ -2,14 +2,6 @@ @using Microsoft.AspNetCore.Components.WebAssembly.Authentication -@if (Action == "login") -{ -

Log In Page

-} else -{ -

Not Log In Page

-} - @code{ [Parameter] public string? Action { get; set; } } diff --git a/Program.cs b/Program.cs index c845e22..5a4156a 100644 --- a/Program.cs +++ b/Program.cs @@ -1,8 +1,16 @@ using DungeonMasterDashboard; +using DungeonMasterDashboard.Services; +using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; var builder = WebAssemblyHostBuilder.CreateDefault(args); + +builder.Services.AddDbContext(options => +{ + options.UseSqlServer("name=DefaultConnection"); +}); + builder.RootComponents.Add("#app"); builder.RootComponents.Add("head::after"); diff --git a/Services/ApplicationDbContext.cs b/Services/ApplicationDbContext.cs new file mode 100644 index 0000000..9a7aa13 --- /dev/null +++ b/Services/ApplicationDbContext.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace DungeonMasterDashboard.Services +{ + public class ApplicationDbContext : IdentityDbContext + { + public ApplicationDbContext(DbContextOptions options) : base(options) + { + } + + protected ApplicationDbContext() + { + } + } +} From ed39ce9cec7927ba1a1b22e5e00788003586986a Mon Sep 17 00:00:00 2001 From: Cody Owens Date: Thu, 12 Feb 2026 22:46:14 -0700 Subject: [PATCH 2/6] Log in page exists; Built DungeonMasterDashboard.Server API to handle Db Implement custom authentication for Blazor WASM app Replaced OIDC with a custom token-based authentication system using a new CustomAuthenticationStateProvider. Added a custom login form in Authentication.razor and updated navigation to show links only to authorized users. Removed client-side EF Core and related ApplicationDbContext. Updated Program.cs to configure HttpClient and authentication services. Added necessary package references and configuration for the new authentication flow. --- DungeonMasterDashboard.csproj | 2 +- DungeonMasterDashboard.slnx | 1 + Layout/LoginDisplay.razor | 1 + Layout/MainLayout.razor | 1 - Layout/NavMenu.razor | 38 +++++--- Pages/Authentication.razor | 56 ++++++++++- Program.cs | 19 ++-- Services/ApplicationDbContext.cs | 16 ---- Services/CustomAuthenticationStateProvider.cs | 95 +++++++++++++++++++ _Imports.razor | 2 + wwwroot/appsettings.json | 14 +-- 11 files changed, 195 insertions(+), 50 deletions(-) delete mode 100644 Services/ApplicationDbContext.cs create mode 100644 Services/CustomAuthenticationStateProvider.cs diff --git a/DungeonMasterDashboard.csproj b/DungeonMasterDashboard.csproj index 2007546..9657d28 100644 --- a/DungeonMasterDashboard.csproj +++ b/DungeonMasterDashboard.csproj @@ -6,10 +6,10 @@ enable true service-worker-assets.js - true + diff --git a/DungeonMasterDashboard.slnx b/DungeonMasterDashboard.slnx index 5f538c5..c2870be 100644 --- a/DungeonMasterDashboard.slnx +++ b/DungeonMasterDashboard.slnx @@ -1,3 +1,4 @@ + diff --git a/Layout/LoginDisplay.razor b/Layout/LoginDisplay.razor index 7775a52..d5a1534 100644 --- a/Layout/LoginDisplay.razor +++ b/Layout/LoginDisplay.razor @@ -8,6 +8,7 @@ Log in + Register diff --git a/Layout/MainLayout.razor b/Layout/MainLayout.razor index ca781b1..8dceaf1 100644 --- a/Layout/MainLayout.razor +++ b/Layout/MainLayout.razor @@ -7,7 +7,6 @@
- About
diff --git a/Layout/NavMenu.razor b/Layout/NavMenu.razor index 0c48203..fa928a4 100644 --- a/Layout/NavMenu.razor +++ b/Layout/NavMenu.razor @@ -9,21 +9,29 @@ diff --git a/Pages/Authentication.razor b/Pages/Authentication.razor index 6c74356..c5ba14b 100644 --- a/Pages/Authentication.razor +++ b/Pages/Authentication.razor @@ -1,7 +1,61 @@ @page "/authentication/{action}" @using Microsoft.AspNetCore.Components.WebAssembly.Authentication - + +@if (Action == "login") +{ +
+

Login

+
+ + @if (error.Length > 0) + { + + } + +
+ + +
+ +
+ + +
+ +
+
+ +
+
+ Cancel +
+
+
+} + +@inject AuthenticationStateProvider provider +@inject NavigationManager navManager @code{ [Parameter] public string? Action { get; set; } + + public string email = ""; + public string password = ""; + public string error = ""; + + private async Task LoginAsync() + { + var authStateProvider = (CustomAuthenticationStateProvider)provider; + var formResult = await authStateProvider.LoginAsync(email, password); + if (formResult.Success) + { + navManager.NavigateTo("/"); + } else + { + error = formResult.Errors[0]; + } + } } diff --git a/Program.cs b/Program.cs index 5a4156a..24f6f32 100644 --- a/Program.cs +++ b/Program.cs @@ -1,24 +1,25 @@ using DungeonMasterDashboard; using DungeonMasterDashboard.Services; -using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; var builder = WebAssemblyHostBuilder.CreateDefault(args); -builder.Services.AddDbContext(options => -{ - options.UseSqlServer("name=DefaultConnection"); -}); - builder.RootComponents.Add("#app"); builder.RootComponents.Add("head::after"); -builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); +var apiBaseUrl = builder.Configuration["WebApiAddress"] + ?? throw new InvalidOperationException("WebApiAddress is not configured."); -builder.Services.AddOidcAuthentication(options => +builder.Services.AddScoped(_ => new HttpClient { - builder.Configuration.Bind("Auth0", options.ProviderOptions); + BaseAddress = new Uri(apiBaseUrl) }); +builder.Services.AddCascadingAuthenticationState(); +builder.Services.AddAuthorizationCore(); +builder.Services.AddScoped(); + await builder.Build().RunAsync(); + diff --git a/Services/ApplicationDbContext.cs b/Services/ApplicationDbContext.cs deleted file mode 100644 index 9a7aa13..0000000 --- a/Services/ApplicationDbContext.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - -namespace DungeonMasterDashboard.Services -{ - public class ApplicationDbContext : IdentityDbContext - { - public ApplicationDbContext(DbContextOptions options) : base(options) - { - } - - protected ApplicationDbContext() - { - } - } -} diff --git a/Services/CustomAuthenticationStateProvider.cs b/Services/CustomAuthenticationStateProvider.cs new file mode 100644 index 0000000..e162643 --- /dev/null +++ b/Services/CustomAuthenticationStateProvider.cs @@ -0,0 +1,95 @@ +using Microsoft.AspNetCore.Components.Authorization; +using System.Net.Http.Json; +using System.Security.Claims; +using System.Text.Json.Nodes; +using System.Net.Http.Headers; + +namespace DungeonMasterDashboard.Services +{ + public class CustomAuthenticationStateProvider : AuthenticationStateProvider + { + private readonly HttpClient httpClient; + + public CustomAuthenticationStateProvider(HttpClient httpClient) + { + this.httpClient = httpClient; + } + + public override async Task GetAuthenticationStateAsync() + { + /* + //var user = new ClaimsPrincipal(new ClaimsIdentity()); // non-authenticated user + var claims = new List { new Claim(ClaimTypes.Name, "John") }; + var identity = new ClaimsIdentity(claims, "ANY"); + var user = new ClaimsPrincipal(identity); + + return Task.FromResult(new AuthenticationState(user)); + */ + + var user = new ClaimsPrincipal(new ClaimsIdentity()); // non-authenticated user + + try + { + var response = await httpClient.GetAsync("manage/info"); + if (response.IsSuccessStatusCode) + { + var strResponse = await response.Content.ReadAsStringAsync(); + var jsonResponse = JsonNode.Parse(strResponse); + var email = jsonResponse!["email"]!.ToString(); + + var claims = new List + { + new(ClaimTypes.Name, email), + new(ClaimTypes.Email, email) + }; + + // Set the principal + var identity = new ClaimsIdentity(claims, "Token"); + user = new ClaimsPrincipal(identity); + return new AuthenticationState(user); + } + } + catch (Exception ex) + { + + } + + return new AuthenticationState(user); + } + + public async Task LoginAsync(string email, string password) + { + try + { + var response = await httpClient.PostAsJsonAsync("authentication/login", new { email, password }); + if (response.IsSuccessStatusCode) + { + var strResponse = await response.Content.ReadAsStringAsync(); + var jsonResponse = JsonNode.Parse(strResponse); + var accessToken = jsonResponse?["accessToken"]?.ToString(); + var refreshToken = jsonResponse?["refreshToken"]?.ToString(); + + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + // Refresh auth state + NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); + + // Success! + return new FormResult { Success = true }; + } + else + { + return new FormResult { Success = false, Errors = ["Bad Email or Password"] }; + } + } catch { } + + return new FormResult { Success = false, Errors = [ "Connection Error" ] }; + } + } + + public class FormResult + { + public bool Success { get; set; } + public string[] Errors { get; set; } = []; + } +} diff --git a/_Imports.razor b/_Imports.razor index cc17512..5f44a2e 100644 --- a/_Imports.razor +++ b/_Imports.razor @@ -9,3 +9,5 @@ @using Microsoft.JSInterop @using DungeonMasterDashboard @using DungeonMasterDashboard.Layout + +@using DungeonMasterDashboard.Services diff --git a/wwwroot/appsettings.json b/wwwroot/appsettings.json index 6dcdbbe..3872340 100644 --- a/wwwroot/appsettings.json +++ b/wwwroot/appsettings.json @@ -1,10 +1,10 @@ { - "ConnectionStrings": { - "DefaultConnection": "Data Source=(local);Initial Catalog=NewDatabase;Integrated Security=True;Connect Timeout=30;Encrypt=True;Trust Server Certificate=True;Application Intent=ReadWrite;Multi Subnet Failover=False;Command Timeout=30" + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Application": "Warning" + } }, - - "Local": { - "Authority": "https://login.microsoftonline.com/", - "ClientId": "33333333-3333-3333-33333333333333333" - } + "AllowedHosts": "*", + "WebApiAddress": "https://localhost:7090" } From ddcd7c7cbc82eec4a8cee4416cdac4e325ce3312 Mon Sep 17 00:00:00 2001 From: Cody Owens Date: Fri, 13 Feb 2026 11:42:11 -0700 Subject: [PATCH 3/6] Refactor auth, add profile page, remove Weather component - Switched to custom authentication using Blazored.LocalStorage for token management; removed Microsoft.AspNetCore.Components.WebAssembly.Authentication. - Added registration flow and validation to Authentication.razor. - Introduced RegisterDto and UserProfile models. - Added Profile page to display user info; requires authorization. - Updated login/register/profile navigation and routes. - Removed Weather page and its navigation link. - Improved UI and error handling in forms. - Updated README and cleaned up Home page. --- DungeonMasterDashboard.csproj | 2 +- Layout/LoginDisplay.razor | 11 +- Layout/NavMenu.razor | 14 +-- Layout/RedirectToLogin.razor | 5 +- Models/RegisterDto.cs | 8 ++ Models/UserProfile.cs | 11 ++ Pages/Authentication.razor | 102 ++++++++++++++++-- Pages/Home.razor | 4 - Pages/Profile.razor | 54 ++++++++++ Pages/Weather.razor | 57 ---------- Program.cs | 2 + README.md | 4 +- Services/CustomAuthenticationStateProvider.cs | 63 ++++++++++- _Imports.razor | 2 + wwwroot/index.html | 3 +- 15 files changed, 253 insertions(+), 89 deletions(-) create mode 100644 Models/RegisterDto.cs create mode 100644 Models/UserProfile.cs create mode 100644 Pages/Profile.razor delete mode 100644 Pages/Weather.razor diff --git a/DungeonMasterDashboard.csproj b/DungeonMasterDashboard.csproj index 9657d28..1c4242c 100644 --- a/DungeonMasterDashboard.csproj +++ b/DungeonMasterDashboard.csproj @@ -9,10 +9,10 @@ + - diff --git a/Layout/LoginDisplay.razor b/Layout/LoginDisplay.razor index d5a1534..489ea31 100644 --- a/Layout/LoginDisplay.razor +++ b/Layout/LoginDisplay.razor @@ -1,5 +1,6 @@ -@using Microsoft.AspNetCore.Components.WebAssembly.Authentication +@using Microsoft.AspNetCore.Components.Authorization @inject NavigationManager Navigation +@inject AuthenticationStateProvider authStateProvider @@ -7,14 +8,16 @@ - Log in - Register + Log in + Register @code{ public void BeginLogOut() { - Navigation.NavigateToLogout("authentication/logout"); + var custAuthStateProvider = (CustomAuthenticationStateProvider)authStateProvider; + custAuthStateProvider.Logout(); + Navigation.NavigateTo("/"); } } diff --git a/Layout/NavMenu.razor b/Layout/NavMenu.razor index fa928a4..c3ecb72 100644 --- a/Layout/NavMenu.razor +++ b/Layout/NavMenu.razor @@ -21,17 +21,19 @@ Counter -

You're not authorized

- + diff --git a/Layout/RedirectToLogin.razor b/Layout/RedirectToLogin.razor index a1cf400..bf01957 100644 --- a/Layout/RedirectToLogin.razor +++ b/Layout/RedirectToLogin.razor @@ -1,9 +1,8 @@ -@using Microsoft.AspNetCore.Components.WebAssembly.Authentication -@inject NavigationManager Navigation +@inject NavigationManager Navigation @code { protected override void OnInitialized() { - Navigation.NavigateToLogin("authentication/login"); + Navigation.NavigateTo("auth/login"); } } diff --git a/Models/RegisterDto.cs b/Models/RegisterDto.cs new file mode 100644 index 0000000..50ad32a --- /dev/null +++ b/Models/RegisterDto.cs @@ -0,0 +1,8 @@ +namespace DungeonMasterDashboard.Models +{ + public class RegisterDto + { + public string Email { get; set; } = ""; + public string Password { get; set; } = ""; + } +} diff --git a/Models/UserProfile.cs b/Models/UserProfile.cs new file mode 100644 index 0000000..8b4b0d2 --- /dev/null +++ b/Models/UserProfile.cs @@ -0,0 +1,11 @@ +namespace DungeonMasterDashboard.Models +{ + public class UserProfile + { + public string Id { get; set; } = ""; + public string Name { get; set; } = ""; + public string Email { get; set; } = ""; + public string PhoneNumber { get; set; } = ""; + + } +} diff --git a/Pages/Authentication.razor b/Pages/Authentication.razor index c5ba14b..3fe3f1b 100644 --- a/Pages/Authentication.razor +++ b/Pages/Authentication.razor @@ -1,6 +1,6 @@ -@page "/authentication/{action}" -@using Microsoft.AspNetCore.Components.WebAssembly.Authentication +@page "/auth/{action}" +DMD @Action @if (Action == "login") { @@ -16,16 +16,20 @@ } + @*Log In Form*@ + @*EMAIL*@
- +
+ @*PASSWORD*@
- +
+ @*LOG IN BUTTON AND CANCEL*@
@@ -35,6 +39,63 @@
+} else if (Action == "register") +{ +
+
+
+

Register

+
+ + @if (errors.Length > 0) + { + + } + + @*Register Form*@ + @*EMAIL*@ +
+ +
+ +
+
+ + @*PASSWORD*@ +
+ +
+ +
+
+ + @*CONFIRM PASSWORD*@ +
+ +
+ +
+
+ + @*REGISTER BUTTON & CANCEL*@ +
+
+ +
+
+ Cancel +
+
+
+
+
} @inject AuthenticationStateProvider provider @@ -42,9 +103,13 @@ @code{ [Parameter] public string? Action { get; set; } - public string email = ""; - public string password = ""; - public string error = ""; + private string email = ""; + private string password = ""; + private string error = ""; + + // Registration fields + private string[] errors = []; + private string confirmPassword = ""; private async Task LoginAsync() { @@ -58,4 +123,27 @@ error = formResult.Errors[0]; } } + + private async Task RegisterAsync() + { + if (password != confirmPassword) + { + errors = ["Password and Confirm Password do not match!"]; + return; + } + var authStateProvider = (CustomAuthenticationStateProvider)provider; + var registerDto = new RegisterDto + { + Email = email, + Password = password + }; + var formResult = await authStateProvider.RegisterAsync(registerDto); + if (formResult.Success) + { + navManager.NavigateTo("/"); + } else + { + errors = formResult.Errors; + } + } } diff --git a/Pages/Home.razor b/Pages/Home.razor index 798591d..9001e0b 100644 --- a/Pages/Home.razor +++ b/Pages/Home.razor @@ -4,8 +4,4 @@

Hello, world!

- - Welcome to your new app. diff --git a/Pages/Profile.razor b/Pages/Profile.razor new file mode 100644 index 0000000..1b023ba --- /dev/null +++ b/Pages/Profile.razor @@ -0,0 +1,54 @@ +@page "/profile" +@attribute [Authorize] + +DMD Profile + +@if (user == null) +{ +

@message

+} +else +{ +

Profile

+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+} + +@inject HttpClient httpClient +@code { + private UserProfile? user = null; + private string message = ""; + + protected override async Task OnInitializedAsync() + { + try + { + message = "Loading. . ."; + + user = await httpClient.GetFromJsonAsync("api/Account/Profile"); + } + catch + { + message = "Cannot read user profile"; + } + } +} diff --git a/Pages/Weather.razor b/Pages/Weather.razor deleted file mode 100644 index 3ea2b1c..0000000 --- a/Pages/Weather.razor +++ /dev/null @@ -1,57 +0,0 @@ -@page "/weather" -@inject HttpClient Http - -Weather - -

Weather

- -

This component demonstrates fetching data from the server.

- -@if (forecasts == null) -{ -

Loading...

-} -else -{ - - - - - - - - - - - @foreach (var forecast in forecasts) - { - - - - - - - } - -
DateTemp. (C)Temp. (F)Summary
@forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
-} - -@code { - private WeatherForecast[]? forecasts; - - protected override async Task OnInitializedAsync() - { - forecasts = await Http.GetFromJsonAsync("sample-data/weather.json"); - } - - public class WeatherForecast - { - public DateOnly Date { get; set; } - - public int TemperatureC { get; set; } - - public string? Summary { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - } -} diff --git a/Program.cs b/Program.cs index 24f6f32..ac4009c 100644 --- a/Program.cs +++ b/Program.cs @@ -1,3 +1,4 @@ +using Blazored.LocalStorage; using DungeonMasterDashboard; using DungeonMasterDashboard.Services; using Microsoft.AspNetCore.Components.Authorization; @@ -20,6 +21,7 @@ builder.Services.AddCascadingAuthenticationState(); builder.Services.AddAuthorizationCore(); builder.Services.AddScoped(); +builder.Services.AddBlazoredLocalStorage(); await builder.Build().RunAsync(); diff --git a/README.md b/README.md index 8c695a2..46a774e 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -# DungeonMasterDashboard \ No newline at end of file +# DungeonMasterDashboard + +This has saved me https://youtu.be/uYkfk-rMmlM?si=nmG6-jGQuyA-nf9D \ No newline at end of file diff --git a/Services/CustomAuthenticationStateProvider.cs b/Services/CustomAuthenticationStateProvider.cs index e162643..241a989 100644 --- a/Services/CustomAuthenticationStateProvider.cs +++ b/Services/CustomAuthenticationStateProvider.cs @@ -3,16 +3,26 @@ using System.Security.Claims; using System.Text.Json.Nodes; using System.Net.Http.Headers; +using Blazored.LocalStorage; +using DungeonMasterDashboard.Models; namespace DungeonMasterDashboard.Services { public class CustomAuthenticationStateProvider : AuthenticationStateProvider { private readonly HttpClient httpClient; + private readonly ISyncLocalStorageService localStorage; - public CustomAuthenticationStateProvider(HttpClient httpClient) + public CustomAuthenticationStateProvider(HttpClient httpClient, ISyncLocalStorageService localStorage) { this.httpClient = httpClient; + this.localStorage = localStorage; + + var accessToken = localStorage.GetItem("accessToken"); + if (accessToken != null) + { + this.httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + } } public override async Task GetAuthenticationStateAsync() @@ -49,9 +59,9 @@ public override async Task GetAuthenticationStateAsync() return new AuthenticationState(user); } } - catch (Exception ex) + catch { - + } return new AuthenticationState(user); @@ -61,7 +71,7 @@ public async Task LoginAsync(string email, string password) { try { - var response = await httpClient.PostAsJsonAsync("authentication/login", new { email, password }); + var response = await httpClient.PostAsJsonAsync("/login", new { email, password }); if (response.IsSuccessStatusCode) { var strResponse = await response.Content.ReadAsStringAsync(); @@ -69,6 +79,9 @@ public async Task LoginAsync(string email, string password) var accessToken = jsonResponse?["accessToken"]?.ToString(); var refreshToken = jsonResponse?["refreshToken"]?.ToString(); + localStorage.SetItem("accessToken", accessToken); + localStorage.SetItem("refreshToken", refreshToken); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); // Refresh auth state @@ -85,6 +98,48 @@ public async Task LoginAsync(string email, string password) return new FormResult { Success = false, Errors = [ "Connection Error" ] }; } + + public void Logout() + { + // delete tokens from local storage + localStorage.RemoveItem("accessToken"); + localStorage.RemoveItem("refreshToken"); + httpClient.DefaultRequestHeaders.Authorization = null; + NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); + } + + public async Task RegisterAsync(RegisterDto registerDto) + { + try + { + var response = await httpClient.PostAsJsonAsync("/register", registerDto); + if (response.IsSuccessStatusCode) + { + var loginResponse = await LoginAsync(registerDto.Email, registerDto.Password); + return loginResponse; + } + + // Registration Errors + var strResponse = await response.Content.ReadAsStringAsync(); + Console.WriteLine(strResponse); + var jsonResponse = JsonNode.Parse(strResponse); + var errorsObject = jsonResponse!["errors"]!.AsObject(); + var errorsList = new List(); + foreach (var error in errorsObject) + { + errorsList.Add(error.Value![0]!.ToString()); + } + + var formResult = new FormResult + { + Success = false, + Errors = errorsList.ToArray() + }; + + return formResult; + } catch { } + return new FormResult { Success = false, Errors = ["Connection Error"] }; + } } public class FormResult diff --git a/_Imports.razor b/_Imports.razor index 5f44a2e..8ca86ac 100644 --- a/_Imports.razor +++ b/_Imports.razor @@ -11,3 +11,5 @@ @using DungeonMasterDashboard.Layout @using DungeonMasterDashboard.Services +@using DungeonMasterDashboard.Models +@using Microsoft.AspNetCore.Authorization diff --git a/wwwroot/index.html b/wwwroot/index.html index 2a99601..2a22fdd 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -31,8 +31,7 @@ Reload 🗙 - - + From 45da7ca0cb11b8f7d7973e5627367174083e195c Mon Sep 17 00:00:00 2001 From: Cody Owens Date: Fri, 13 Feb 2026 20:03:11 -0700 Subject: [PATCH 4/6] Modifying layout to work with API (Extracting git files to overarching folder) --- .../Controllers/AccountController.cs | 52 ++++ .../DungeonMasterDashboard.Server.csproj | 21 ++ .../20260213031756_FirstMigration.Designer.cs | 279 ++++++++++++++++++ .../20260213031756_FirstMigration.cs | 224 ++++++++++++++ .../ApplicationDbContextModelSnapshot.cs | 276 +++++++++++++++++ .../Models/UserProfile.cs | 11 + DungeonMasterDashboard.Server/Program.cs | 44 +++ .../Properties/launchSettings.json | 24 ++ .../Services/ApplicationDbContext.cs | 16 + .../appsettings.Development.json | 8 + .../appsettings.json | 12 + .../.github}/workflows/gh-pages.yml | 0 App.razor => DungeonMasterDashboard/App.razor | 0 .../DungeonMasterDashboard.csproj | 0 .../DungeonMasterDashboard.slnx | 0 .../Layout}/LoginDisplay.razor | 0 .../Layout}/MainLayout.razor | 0 .../Layout}/MainLayout.razor.css | 154 +++++----- .../Layout}/NavMenu.razor | 0 .../Layout}/NavMenu.razor.css | 166 +++++------ .../Layout}/RedirectToLogin.razor | 0 .../Models}/RegisterDto.cs | 0 .../Models}/UserProfile.cs | 0 .../Pages}/Authentication.razor | 0 .../Pages}/Counter.razor | 0 .../Pages}/Home.razor | 0 .../Pages}/NotFound.razor | 0 .../Pages}/Profile.razor | 0 .../Program.cs | 0 .../Properties}/launchSettings.json | 30 +- .../CustomAuthenticationStateProvider.cs | 0 .../_Imports.razor | 0 .../wwwroot}/appsettings.Development.json | 12 +- .../wwwroot}/appsettings.json | 0 .../wwwroot}/css/app.css | 228 +++++++------- .../wwwroot}/favicon.png | Bin .../wwwroot}/icon-192.png | Bin .../wwwroot}/icon-512.png | Bin .../wwwroot}/index.html | 0 .../lib/bootstrap/dist/css/bootstrap-grid.css | 0 .../bootstrap/dist/css/bootstrap-grid.css.map | 0 .../bootstrap/dist/css/bootstrap-grid.min.css | 0 .../dist/css/bootstrap-grid.min.css.map | 0 .../bootstrap/dist/css/bootstrap-grid.rtl.css | 0 .../dist/css/bootstrap-grid.rtl.css.map | 0 .../dist/css/bootstrap-grid.rtl.min.css | 0 .../dist/css/bootstrap-grid.rtl.min.css.map | 0 .../bootstrap/dist/css/bootstrap-reboot.css | 0 .../dist/css/bootstrap-reboot.css.map | 0 .../dist/css/bootstrap-reboot.min.css | 0 .../dist/css/bootstrap-reboot.min.css.map | 0 .../dist/css/bootstrap-reboot.rtl.css | 0 .../dist/css/bootstrap-reboot.rtl.css.map | 0 .../dist/css/bootstrap-reboot.rtl.min.css | 0 .../dist/css/bootstrap-reboot.rtl.min.css.map | 0 .../dist/css/bootstrap-utilities.css | 0 .../dist/css/bootstrap-utilities.css.map | 0 .../dist/css/bootstrap-utilities.min.css | 0 .../dist/css/bootstrap-utilities.min.css.map | 0 .../dist/css/bootstrap-utilities.rtl.css | 0 .../dist/css/bootstrap-utilities.rtl.css.map | 0 .../dist/css/bootstrap-utilities.rtl.min.css | 0 .../css/bootstrap-utilities.rtl.min.css.map | 0 .../lib/bootstrap/dist/css/bootstrap.css | 0 .../lib/bootstrap/dist/css/bootstrap.css.map | 0 .../lib/bootstrap/dist/css/bootstrap.min.css | 0 .../bootstrap/dist/css/bootstrap.min.css.map | 0 .../lib/bootstrap/dist/css/bootstrap.rtl.css | 0 .../bootstrap/dist/css/bootstrap.rtl.css.map | 0 .../bootstrap/dist/css/bootstrap.rtl.min.css | 0 .../dist/css/bootstrap.rtl.min.css.map | 0 .../lib/bootstrap/dist/js/bootstrap.bundle.js | 0 .../bootstrap/dist/js/bootstrap.bundle.js.map | 0 .../bootstrap/dist/js/bootstrap.bundle.min.js | 0 .../dist/js/bootstrap.bundle.min.js.map | 0 .../lib/bootstrap/dist/js/bootstrap.esm.js | 0 .../bootstrap/dist/js/bootstrap.esm.js.map | 0 .../bootstrap/dist/js/bootstrap.esm.min.js | 0 .../dist/js/bootstrap.esm.min.js.map | 0 .../lib/bootstrap/dist/js/bootstrap.js | 0 .../lib/bootstrap/dist/js/bootstrap.js.map | 0 .../lib/bootstrap/dist/js/bootstrap.min.js | 0 .../bootstrap/dist/js/bootstrap.min.js.map | 0 .../wwwroot}/manifest.webmanifest | 0 .../wwwroot}/sample-data/weather.json | 54 ++-- .../wwwroot}/service-worker.js | 8 +- .../wwwroot}/service-worker.published.js | 110 +++---- 87 files changed, 1348 insertions(+), 381 deletions(-) create mode 100644 DungeonMasterDashboard.Server/Controllers/AccountController.cs create mode 100644 DungeonMasterDashboard.Server/DungeonMasterDashboard.Server.csproj create mode 100644 DungeonMasterDashboard.Server/Migrations/20260213031756_FirstMigration.Designer.cs create mode 100644 DungeonMasterDashboard.Server/Migrations/20260213031756_FirstMigration.cs create mode 100644 DungeonMasterDashboard.Server/Migrations/ApplicationDbContextModelSnapshot.cs create mode 100644 DungeonMasterDashboard.Server/Models/UserProfile.cs create mode 100644 DungeonMasterDashboard.Server/Program.cs create mode 100644 DungeonMasterDashboard.Server/Properties/launchSettings.json create mode 100644 DungeonMasterDashboard.Server/Services/ApplicationDbContext.cs create mode 100644 DungeonMasterDashboard.Server/appsettings.Development.json create mode 100644 DungeonMasterDashboard.Server/appsettings.json rename {.github => DungeonMasterDashboard/.github}/workflows/gh-pages.yml (100%) rename App.razor => DungeonMasterDashboard/App.razor (100%) rename DungeonMasterDashboard.csproj => DungeonMasterDashboard/DungeonMasterDashboard.csproj (100%) rename DungeonMasterDashboard.slnx => DungeonMasterDashboard/DungeonMasterDashboard.slnx (100%) rename {Layout => DungeonMasterDashboard/Layout}/LoginDisplay.razor (100%) rename {Layout => DungeonMasterDashboard/Layout}/MainLayout.razor (100%) rename {Layout => DungeonMasterDashboard/Layout}/MainLayout.razor.css (94%) rename {Layout => DungeonMasterDashboard/Layout}/NavMenu.razor (100%) rename {Layout => DungeonMasterDashboard/Layout}/NavMenu.razor.css (96%) rename {Layout => DungeonMasterDashboard/Layout}/RedirectToLogin.razor (100%) rename {Models => DungeonMasterDashboard/Models}/RegisterDto.cs (100%) rename {Models => DungeonMasterDashboard/Models}/UserProfile.cs (100%) rename {Pages => DungeonMasterDashboard/Pages}/Authentication.razor (100%) rename {Pages => DungeonMasterDashboard/Pages}/Counter.razor (100%) rename {Pages => DungeonMasterDashboard/Pages}/Home.razor (100%) rename {Pages => DungeonMasterDashboard/Pages}/NotFound.razor (100%) rename {Pages => DungeonMasterDashboard/Pages}/Profile.razor (100%) rename Program.cs => DungeonMasterDashboard/Program.cs (100%) rename {Properties => DungeonMasterDashboard/Properties}/launchSettings.json (96%) rename {Services => DungeonMasterDashboard/Services}/CustomAuthenticationStateProvider.cs (100%) rename _Imports.razor => DungeonMasterDashboard/_Imports.razor (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/appsettings.Development.json (95%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/appsettings.json (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/css/app.css (97%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/favicon.png (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/icon-192.png (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/icon-512.png (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/index.html (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-grid.css (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-grid.css.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-grid.min.css (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-grid.min.css.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-grid.rtl.css (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-grid.rtl.css.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-reboot.css (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-reboot.css.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-reboot.min.css (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-utilities.css (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-utilities.css.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-utilities.min.css (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-utilities.min.css.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap.css (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap.css.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap.min.css (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap.min.css.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap.rtl.css (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap.rtl.css.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap.rtl.min.css (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/css/bootstrap.rtl.min.css.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/js/bootstrap.bundle.js (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/js/bootstrap.bundle.js.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/js/bootstrap.bundle.min.js (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/js/bootstrap.esm.js (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/js/bootstrap.esm.js.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/js/bootstrap.esm.min.js (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/js/bootstrap.esm.min.js.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/js/bootstrap.js (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/js/bootstrap.js.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/js/bootstrap.min.js (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/lib/bootstrap/dist/js/bootstrap.min.js.map (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/manifest.webmanifest (100%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/sample-data/weather.json (94%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/service-worker.js (98%) rename {wwwroot => DungeonMasterDashboard/wwwroot}/service-worker.published.js (97%) diff --git a/DungeonMasterDashboard.Server/Controllers/AccountController.cs b/DungeonMasterDashboard.Server/Controllers/AccountController.cs new file mode 100644 index 0000000..2b3ee0a --- /dev/null +++ b/DungeonMasterDashboard.Server/Controllers/AccountController.cs @@ -0,0 +1,52 @@ +using DungeonMasterDashboard.Server.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; + +namespace DungeonMasterDashboard.Server.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class AccountController : ControllerBase + { + private readonly UserManager userManager; + + public AccountController(UserManager userManager) + { + this.userManager = userManager; + } + + [HttpGet] + public IActionResult Welcome() + { + if (User.Identity == null || !User.Identity.IsAuthenticated) + { + return Ok("You are NOT authenticated"); + } + + return Ok("You are authenticated"); + } + + [Authorize] + [HttpGet("Profile")] + public async Task Profile() + { + var currentUser = await userManager.GetUserAsync(User); + if (currentUser == null) + { + return BadRequest(); + } + + var userProfile = new UserProfile + { + Id = currentUser.Id, + Name = currentUser.UserName ?? "", + Email = currentUser.Email ?? "", + PhoneNumber = currentUser.PhoneNumber ?? "" + }; + + return Ok(userProfile); + } + } +} diff --git a/DungeonMasterDashboard.Server/DungeonMasterDashboard.Server.csproj b/DungeonMasterDashboard.Server/DungeonMasterDashboard.Server.csproj new file mode 100644 index 0000000..da45e57 --- /dev/null +++ b/DungeonMasterDashboard.Server/DungeonMasterDashboard.Server.csproj @@ -0,0 +1,21 @@ + + + + net10.0 + enable + enable + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/DungeonMasterDashboard.Server/Migrations/20260213031756_FirstMigration.Designer.cs b/DungeonMasterDashboard.Server/Migrations/20260213031756_FirstMigration.Designer.cs new file mode 100644 index 0000000..6f6f089 --- /dev/null +++ b/DungeonMasterDashboard.Server/Migrations/20260213031756_FirstMigration.Designer.cs @@ -0,0 +1,279 @@ +// +using System; +using DungeonMasterDashboard.Services; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace DungeonMasterDashboard.Server.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260213031756_FirstMigration")] + partial class FirstMigration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DungeonMasterDashboard.Server/Migrations/20260213031756_FirstMigration.cs b/DungeonMasterDashboard.Server/Migrations/20260213031756_FirstMigration.cs new file mode 100644 index 0000000..84ce4d3 --- /dev/null +++ b/DungeonMasterDashboard.Server/Migrations/20260213031756_FirstMigration.cs @@ -0,0 +1,224 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DungeonMasterDashboard.Server.Migrations +{ + /// + public partial class FirstMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/DungeonMasterDashboard.Server/Migrations/ApplicationDbContextModelSnapshot.cs b/DungeonMasterDashboard.Server/Migrations/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 0000000..301eb91 --- /dev/null +++ b/DungeonMasterDashboard.Server/Migrations/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,276 @@ +// +using System; +using DungeonMasterDashboard.Services; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace DungeonMasterDashboard.Server.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DungeonMasterDashboard.Server/Models/UserProfile.cs b/DungeonMasterDashboard.Server/Models/UserProfile.cs new file mode 100644 index 0000000..7ed26f4 --- /dev/null +++ b/DungeonMasterDashboard.Server/Models/UserProfile.cs @@ -0,0 +1,11 @@ +namespace DungeonMasterDashboard.Server.Models +{ + public class UserProfile + { + public string Id { get; set; } = ""; + public string Name { get; set; } = ""; + public string Email { get; set; } = ""; + public string PhoneNumber { get; set; } = ""; + + } +} diff --git a/DungeonMasterDashboard.Server/Program.cs b/DungeonMasterDashboard.Server/Program.cs new file mode 100644 index 0000000..45b3db7 --- /dev/null +++ b/DungeonMasterDashboard.Server/Program.cs @@ -0,0 +1,44 @@ +using DungeonMasterDashboard.Services; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddDbContext(options => +{ + options.UseSqlServer("name=DefaultConnection"); +}); + +builder.Services.AddAuthorization(); +builder.Services.AddIdentityApiEndpoints() + .AddEntityFrameworkStores(); + +builder.Services.AddControllers(); +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); + +var app = builder.Build(); + +app.UseCors(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); + app.UseSwaggerUI(options => + { + options.SwaggerEndpoint("/openapi/v1.json", "api"); + }); +} + +app.MapIdentityApi(); + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/DungeonMasterDashboard.Server/Properties/launchSettings.json b/DungeonMasterDashboard.Server/Properties/launchSettings.json new file mode 100644 index 0000000..6316d96 --- /dev/null +++ b/DungeonMasterDashboard.Server/Properties/launchSettings.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5041", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7090;http://localhost:5041", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/DungeonMasterDashboard.Server/Services/ApplicationDbContext.cs b/DungeonMasterDashboard.Server/Services/ApplicationDbContext.cs new file mode 100644 index 0000000..9a7aa13 --- /dev/null +++ b/DungeonMasterDashboard.Server/Services/ApplicationDbContext.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace DungeonMasterDashboard.Services +{ + public class ApplicationDbContext : IdentityDbContext + { + public ApplicationDbContext(DbContextOptions options) : base(options) + { + } + + protected ApplicationDbContext() + { + } + } +} diff --git a/DungeonMasterDashboard.Server/appsettings.Development.json b/DungeonMasterDashboard.Server/appsettings.Development.json new file mode 100644 index 0000000..ff66ba6 --- /dev/null +++ b/DungeonMasterDashboard.Server/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/DungeonMasterDashboard.Server/appsettings.json b/DungeonMasterDashboard.Server/appsettings.json new file mode 100644 index 0000000..a29e5b5 --- /dev/null +++ b/DungeonMasterDashboard.Server/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=dm_dashboard;Integrated Security=True;TrustServerCertificate=True" + } +} diff --git a/.github/workflows/gh-pages.yml b/DungeonMasterDashboard/.github/workflows/gh-pages.yml similarity index 100% rename from .github/workflows/gh-pages.yml rename to DungeonMasterDashboard/.github/workflows/gh-pages.yml diff --git a/App.razor b/DungeonMasterDashboard/App.razor similarity index 100% rename from App.razor rename to DungeonMasterDashboard/App.razor diff --git a/DungeonMasterDashboard.csproj b/DungeonMasterDashboard/DungeonMasterDashboard.csproj similarity index 100% rename from DungeonMasterDashboard.csproj rename to DungeonMasterDashboard/DungeonMasterDashboard.csproj diff --git a/DungeonMasterDashboard.slnx b/DungeonMasterDashboard/DungeonMasterDashboard.slnx similarity index 100% rename from DungeonMasterDashboard.slnx rename to DungeonMasterDashboard/DungeonMasterDashboard.slnx diff --git a/Layout/LoginDisplay.razor b/DungeonMasterDashboard/Layout/LoginDisplay.razor similarity index 100% rename from Layout/LoginDisplay.razor rename to DungeonMasterDashboard/Layout/LoginDisplay.razor diff --git a/Layout/MainLayout.razor b/DungeonMasterDashboard/Layout/MainLayout.razor similarity index 100% rename from Layout/MainLayout.razor rename to DungeonMasterDashboard/Layout/MainLayout.razor diff --git a/Layout/MainLayout.razor.css b/DungeonMasterDashboard/Layout/MainLayout.razor.css similarity index 94% rename from Layout/MainLayout.razor.css rename to DungeonMasterDashboard/Layout/MainLayout.razor.css index ecf25e5..baef3ee 100644 --- a/Layout/MainLayout.razor.css +++ b/DungeonMasterDashboard/Layout/MainLayout.razor.css @@ -1,77 +1,77 @@ -.page { - position: relative; - display: flex; - flex-direction: column; -} - -main { - flex: 1; -} - -.sidebar { - background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); -} - -.top-row { - background-color: #f7f7f7; - border-bottom: 1px solid #d6d5d5; - justify-content: flex-end; - height: 3.5rem; - display: flex; - align-items: center; -} - - .top-row ::deep a, .top-row ::deep .btn-link { - white-space: nowrap; - margin-left: 1.5rem; - text-decoration: none; - } - - .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { - text-decoration: underline; - } - - .top-row ::deep a:first-child { - overflow: hidden; - text-overflow: ellipsis; - } - -@media (max-width: 640.98px) { - .top-row { - justify-content: space-between; - } - - .top-row ::deep a, .top-row ::deep .btn-link { - margin-left: 0; - } -} - -@media (min-width: 641px) { - .page { - flex-direction: row; - } - - .sidebar { - width: 250px; - height: 100vh; - position: sticky; - top: 0; - } - - .top-row { - position: sticky; - top: 0; - z-index: 1; - } - - .top-row.auth ::deep a:first-child { - flex: 1; - text-align: right; - width: 0; - } - - .top-row, article { - padding-left: 2rem !important; - padding-right: 1.5rem !important; - } -} +.page { + position: relative; + display: flex; + flex-direction: column; +} + +main { + flex: 1; +} + +.sidebar { + background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); +} + +.top-row { + background-color: #f7f7f7; + border-bottom: 1px solid #d6d5d5; + justify-content: flex-end; + height: 3.5rem; + display: flex; + align-items: center; +} + + .top-row ::deep a, .top-row ::deep .btn-link { + white-space: nowrap; + margin-left: 1.5rem; + text-decoration: none; + } + + .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { + text-decoration: underline; + } + + .top-row ::deep a:first-child { + overflow: hidden; + text-overflow: ellipsis; + } + +@media (max-width: 640.98px) { + .top-row { + justify-content: space-between; + } + + .top-row ::deep a, .top-row ::deep .btn-link { + margin-left: 0; + } +} + +@media (min-width: 641px) { + .page { + flex-direction: row; + } + + .sidebar { + width: 250px; + height: 100vh; + position: sticky; + top: 0; + } + + .top-row { + position: sticky; + top: 0; + z-index: 1; + } + + .top-row.auth ::deep a:first-child { + flex: 1; + text-align: right; + width: 0; + } + + .top-row, article { + padding-left: 2rem !important; + padding-right: 1.5rem !important; + } +} diff --git a/Layout/NavMenu.razor b/DungeonMasterDashboard/Layout/NavMenu.razor similarity index 100% rename from Layout/NavMenu.razor rename to DungeonMasterDashboard/Layout/NavMenu.razor diff --git a/Layout/NavMenu.razor.css b/DungeonMasterDashboard/Layout/NavMenu.razor.css similarity index 96% rename from Layout/NavMenu.razor.css rename to DungeonMasterDashboard/Layout/NavMenu.razor.css index 617b89c..6724fd1 100644 --- a/Layout/NavMenu.razor.css +++ b/DungeonMasterDashboard/Layout/NavMenu.razor.css @@ -1,83 +1,83 @@ -.navbar-toggler { - background-color: rgba(255, 255, 255, 0.1); -} - -.top-row { - min-height: 3.5rem; - background-color: rgba(0,0,0,0.4); -} - -.navbar-brand { - font-size: 1.1rem; -} - -.bi { - display: inline-block; - position: relative; - width: 1.25rem; - height: 1.25rem; - margin-right: 0.75rem; - top: -1px; - background-size: cover; -} - -.bi-house-door-fill-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); -} - -.bi-plus-square-fill-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); -} - -.bi-list-nested-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); -} - -.nav-item { - font-size: 0.9rem; - padding-bottom: 0.5rem; -} - - .nav-item:first-of-type { - padding-top: 1rem; - } - - .nav-item:last-of-type { - padding-bottom: 1rem; - } - - .nav-item ::deep a { - color: #d7d7d7; - border-radius: 4px; - height: 3rem; - display: flex; - align-items: center; - line-height: 3rem; - } - -.nav-item ::deep a.active { - background-color: rgba(255,255,255,0.37); - color: white; -} - -.nav-item ::deep a:hover { - background-color: rgba(255,255,255,0.1); - color: white; -} - -@media (min-width: 641px) { - .navbar-toggler { - display: none; - } - - .collapse { - /* Never collapse the sidebar for wide screens */ - display: block; - } - - .nav-scrollable { - /* Allow sidebar to scroll for tall menus */ - height: calc(100vh - 3.5rem); - overflow-y: auto; - } -} +.navbar-toggler { + background-color: rgba(255, 255, 255, 0.1); +} + +.top-row { + min-height: 3.5rem; + background-color: rgba(0,0,0,0.4); +} + +.navbar-brand { + font-size: 1.1rem; +} + +.bi { + display: inline-block; + position: relative; + width: 1.25rem; + height: 1.25rem; + margin-right: 0.75rem; + top: -1px; + background-size: cover; +} + +.bi-house-door-fill-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); +} + +.bi-plus-square-fill-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); +} + +.bi-list-nested-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); +} + +.nav-item { + font-size: 0.9rem; + padding-bottom: 0.5rem; +} + + .nav-item:first-of-type { + padding-top: 1rem; + } + + .nav-item:last-of-type { + padding-bottom: 1rem; + } + + .nav-item ::deep a { + color: #d7d7d7; + border-radius: 4px; + height: 3rem; + display: flex; + align-items: center; + line-height: 3rem; + } + +.nav-item ::deep a.active { + background-color: rgba(255,255,255,0.37); + color: white; +} + +.nav-item ::deep a:hover { + background-color: rgba(255,255,255,0.1); + color: white; +} + +@media (min-width: 641px) { + .navbar-toggler { + display: none; + } + + .collapse { + /* Never collapse the sidebar for wide screens */ + display: block; + } + + .nav-scrollable { + /* Allow sidebar to scroll for tall menus */ + height: calc(100vh - 3.5rem); + overflow-y: auto; + } +} diff --git a/Layout/RedirectToLogin.razor b/DungeonMasterDashboard/Layout/RedirectToLogin.razor similarity index 100% rename from Layout/RedirectToLogin.razor rename to DungeonMasterDashboard/Layout/RedirectToLogin.razor diff --git a/Models/RegisterDto.cs b/DungeonMasterDashboard/Models/RegisterDto.cs similarity index 100% rename from Models/RegisterDto.cs rename to DungeonMasterDashboard/Models/RegisterDto.cs diff --git a/Models/UserProfile.cs b/DungeonMasterDashboard/Models/UserProfile.cs similarity index 100% rename from Models/UserProfile.cs rename to DungeonMasterDashboard/Models/UserProfile.cs diff --git a/Pages/Authentication.razor b/DungeonMasterDashboard/Pages/Authentication.razor similarity index 100% rename from Pages/Authentication.razor rename to DungeonMasterDashboard/Pages/Authentication.razor diff --git a/Pages/Counter.razor b/DungeonMasterDashboard/Pages/Counter.razor similarity index 100% rename from Pages/Counter.razor rename to DungeonMasterDashboard/Pages/Counter.razor diff --git a/Pages/Home.razor b/DungeonMasterDashboard/Pages/Home.razor similarity index 100% rename from Pages/Home.razor rename to DungeonMasterDashboard/Pages/Home.razor diff --git a/Pages/NotFound.razor b/DungeonMasterDashboard/Pages/NotFound.razor similarity index 100% rename from Pages/NotFound.razor rename to DungeonMasterDashboard/Pages/NotFound.razor diff --git a/Pages/Profile.razor b/DungeonMasterDashboard/Pages/Profile.razor similarity index 100% rename from Pages/Profile.razor rename to DungeonMasterDashboard/Pages/Profile.razor diff --git a/Program.cs b/DungeonMasterDashboard/Program.cs similarity index 100% rename from Program.cs rename to DungeonMasterDashboard/Program.cs diff --git a/Properties/launchSettings.json b/DungeonMasterDashboard/Properties/launchSettings.json similarity index 96% rename from Properties/launchSettings.json rename to DungeonMasterDashboard/Properties/launchSettings.json index 4b4f942..c6569c4 100644 --- a/Properties/launchSettings.json +++ b/DungeonMasterDashboard/Properties/launchSettings.json @@ -1,15 +1,15 @@ -{ - "$schema": "https://json.schemastore.org/launchsettings.json", - "profiles": { - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "https://localhost:7277;http://localhost:5095", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "https://localhost:7277;http://localhost:5095", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Services/CustomAuthenticationStateProvider.cs b/DungeonMasterDashboard/Services/CustomAuthenticationStateProvider.cs similarity index 100% rename from Services/CustomAuthenticationStateProvider.cs rename to DungeonMasterDashboard/Services/CustomAuthenticationStateProvider.cs diff --git a/_Imports.razor b/DungeonMasterDashboard/_Imports.razor similarity index 100% rename from _Imports.razor rename to DungeonMasterDashboard/_Imports.razor diff --git a/wwwroot/appsettings.Development.json b/DungeonMasterDashboard/wwwroot/appsettings.Development.json similarity index 95% rename from wwwroot/appsettings.Development.json rename to DungeonMasterDashboard/wwwroot/appsettings.Development.json index fdae94a..5b0f66f 100644 --- a/wwwroot/appsettings.Development.json +++ b/DungeonMasterDashboard/wwwroot/appsettings.Development.json @@ -1,6 +1,6 @@ -{ - "Local": { - "Authority": "https://login.microsoftonline.com/", - "ClientId": "33333333-3333-3333-33333333333333333" - } -} +{ + "Local": { + "Authority": "https://login.microsoftonline.com/", + "ClientId": "33333333-3333-3333-33333333333333333" + } +} diff --git a/wwwroot/appsettings.json b/DungeonMasterDashboard/wwwroot/appsettings.json similarity index 100% rename from wwwroot/appsettings.json rename to DungeonMasterDashboard/wwwroot/appsettings.json diff --git a/wwwroot/css/app.css b/DungeonMasterDashboard/wwwroot/css/app.css similarity index 97% rename from wwwroot/css/app.css rename to DungeonMasterDashboard/wwwroot/css/app.css index fe44b1d..08b5997 100644 --- a/wwwroot/css/app.css +++ b/DungeonMasterDashboard/wwwroot/css/app.css @@ -1,115 +1,115 @@ -html, body { - font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -h1:focus { - outline: none; -} - -a, .btn-link { - color: #0071c1; -} - -.btn-primary { - color: #fff; - background-color: #1b6ec2; - border-color: #1861ac; -} - -.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { - box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; -} - -.content { - padding-top: 1.1rem; -} - -.valid.modified:not([type=checkbox]) { - outline: 1px solid #26b050; -} - -.invalid { - outline: 1px solid red; -} - -.validation-message { - color: red; -} - -#blazor-error-ui { - color-scheme: light only; - background: lightyellow; - bottom: 0; - box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); - box-sizing: border-box; - display: none; - left: 0; - padding: 0.6rem 1.25rem 0.7rem 1.25rem; - position: fixed; - width: 100%; - z-index: 1000; -} - - #blazor-error-ui .dismiss { - cursor: pointer; - position: absolute; - right: 0.75rem; - top: 0.5rem; - } - -.blazor-error-boundary { - background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; - padding: 1rem 1rem 1rem 3.7rem; - color: white; -} - - .blazor-error-boundary::after { - content: "An error has occurred." - } - -.loading-progress { - position: absolute; - display: block; - width: 8rem; - height: 8rem; - inset: 20vh 0 auto 0; - margin: 0 auto 0 auto; -} - - .loading-progress circle { - fill: none; - stroke: #e0e0e0; - stroke-width: 0.6rem; - transform-origin: 50% 50%; - transform: rotate(-90deg); - } - - .loading-progress circle:last-child { - stroke: #1b6ec2; - stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; - transition: stroke-dasharray 0.05s ease-in-out; - } - -.loading-progress-text { - position: absolute; - text-align: center; - font-weight: bold; - inset: calc(20vh + 3.25rem) 0 auto 0.2rem; -} - - .loading-progress-text:after { - content: var(--blazor-load-percentage-text, "Loading"); - } - -code { - color: #c02d76; -} - -.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder { - color: var(--bs-secondary-color); - text-align: end; -} - -.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder { - text-align: start; +html, body { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +h1:focus { + outline: none; +} + +a, .btn-link { + color: #0071c1; +} + +.btn-primary { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { + box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; +} + +.content { + padding-top: 1.1rem; +} + +.valid.modified:not([type=checkbox]) { + outline: 1px solid #26b050; +} + +.invalid { + outline: 1px solid red; +} + +.validation-message { + color: red; +} + +#blazor-error-ui { + color-scheme: light only; + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + box-sizing: border-box; + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::after { + content: "An error has occurred." + } + +.loading-progress { + position: absolute; + display: block; + width: 8rem; + height: 8rem; + inset: 20vh 0 auto 0; + margin: 0 auto 0 auto; +} + + .loading-progress circle { + fill: none; + stroke: #e0e0e0; + stroke-width: 0.6rem; + transform-origin: 50% 50%; + transform: rotate(-90deg); + } + + .loading-progress circle:last-child { + stroke: #1b6ec2; + stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; + transition: stroke-dasharray 0.05s ease-in-out; + } + +.loading-progress-text { + position: absolute; + text-align: center; + font-weight: bold; + inset: calc(20vh + 3.25rem) 0 auto 0.2rem; +} + + .loading-progress-text:after { + content: var(--blazor-load-percentage-text, "Loading"); + } + +code { + color: #c02d76; +} + +.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder { + color: var(--bs-secondary-color); + text-align: end; +} + +.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder { + text-align: start; } \ No newline at end of file diff --git a/wwwroot/favicon.png b/DungeonMasterDashboard/wwwroot/favicon.png similarity index 100% rename from wwwroot/favicon.png rename to DungeonMasterDashboard/wwwroot/favicon.png diff --git a/wwwroot/icon-192.png b/DungeonMasterDashboard/wwwroot/icon-192.png similarity index 100% rename from wwwroot/icon-192.png rename to DungeonMasterDashboard/wwwroot/icon-192.png diff --git a/wwwroot/icon-512.png b/DungeonMasterDashboard/wwwroot/icon-512.png similarity index 100% rename from wwwroot/icon-512.png rename to DungeonMasterDashboard/wwwroot/icon-512.png diff --git a/wwwroot/index.html b/DungeonMasterDashboard/wwwroot/index.html similarity index 100% rename from wwwroot/index.html rename to DungeonMasterDashboard/wwwroot/index.html diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css.map diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css.map diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css.map diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css.map diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css.map diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css.map diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css.map diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css.map diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap.css b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap.css similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap.css rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap.css diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap.css.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap.min.css rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css.map diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css diff --git a/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css.map diff --git a/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js similarity index 100% rename from wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js diff --git a/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map diff --git a/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js similarity index 100% rename from wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js diff --git a/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map diff --git a/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js similarity index 100% rename from wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js diff --git a/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js.map diff --git a/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js similarity index 100% rename from wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js diff --git a/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js.map diff --git a/wwwroot/lib/bootstrap/dist/js/bootstrap.js b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.js similarity index 100% rename from wwwroot/lib/bootstrap/dist/js/bootstrap.js rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.js diff --git a/wwwroot/lib/bootstrap/dist/js/bootstrap.js.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.js.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/js/bootstrap.js.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.js.map diff --git a/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js similarity index 100% rename from wwwroot/lib/bootstrap/dist/js/bootstrap.min.js rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js diff --git a/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map b/DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map similarity index 100% rename from wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map rename to DungeonMasterDashboard/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map diff --git a/wwwroot/manifest.webmanifest b/DungeonMasterDashboard/wwwroot/manifest.webmanifest similarity index 100% rename from wwwroot/manifest.webmanifest rename to DungeonMasterDashboard/wwwroot/manifest.webmanifest diff --git a/wwwroot/sample-data/weather.json b/DungeonMasterDashboard/wwwroot/sample-data/weather.json similarity index 94% rename from wwwroot/sample-data/weather.json rename to DungeonMasterDashboard/wwwroot/sample-data/weather.json index b745973..f0648e7 100644 --- a/wwwroot/sample-data/weather.json +++ b/DungeonMasterDashboard/wwwroot/sample-data/weather.json @@ -1,27 +1,27 @@ -[ - { - "date": "2022-01-06", - "temperatureC": 1, - "summary": "Freezing" - }, - { - "date": "2022-01-07", - "temperatureC": 14, - "summary": "Bracing" - }, - { - "date": "2022-01-08", - "temperatureC": -13, - "summary": "Freezing" - }, - { - "date": "2022-01-09", - "temperatureC": -16, - "summary": "Balmy" - }, - { - "date": "2022-01-10", - "temperatureC": -2, - "summary": "Chilly" - } -] +[ + { + "date": "2022-01-06", + "temperatureC": 1, + "summary": "Freezing" + }, + { + "date": "2022-01-07", + "temperatureC": 14, + "summary": "Bracing" + }, + { + "date": "2022-01-08", + "temperatureC": -13, + "summary": "Freezing" + }, + { + "date": "2022-01-09", + "temperatureC": -16, + "summary": "Balmy" + }, + { + "date": "2022-01-10", + "temperatureC": -2, + "summary": "Chilly" + } +] diff --git a/wwwroot/service-worker.js b/DungeonMasterDashboard/wwwroot/service-worker.js similarity index 98% rename from wwwroot/service-worker.js rename to DungeonMasterDashboard/wwwroot/service-worker.js index fe614da..c6d0085 100644 --- a/wwwroot/service-worker.js +++ b/DungeonMasterDashboard/wwwroot/service-worker.js @@ -1,4 +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', () => { }); +// 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/wwwroot/service-worker.published.js b/DungeonMasterDashboard/wwwroot/service-worker.published.js similarity index 97% rename from wwwroot/service-worker.published.js rename to DungeonMasterDashboard/wwwroot/service-worker.published.js index 51a0e5c..1899cb5 100644 --- a/wwwroot/service-worker.published.js +++ b/DungeonMasterDashboard/wwwroot/service-worker.published.js @@ -1,55 +1,55 @@ -// 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$/, /\.webmanifest$/ ]; -const offlineAssetsExclude = [ /^service-worker\.js$/ ]; - -// Replace with your base path if you are hosting on a subfolder. Ensure there is a trailing '/'. -const base = "/"; -const baseUrl = new URL(base, self.origin); -const manifestUrlList = self.assetsManifest.assets.map(asset => new URL(asset.url, baseUrl).href); - -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, - // unless that request is for an offline resource. - // If you need some URLs to be server-rendered, edit the following check to exclude those URLs - const shouldServeIndexHtml = event.request.mode === 'navigate' - && !manifestUrlList.some(url => url === event.request.url); - - const request = shouldServeIndexHtml ? 'index.html' : event.request; - const cache = await caches.open(cacheName); - cachedResponse = await cache.match(request); - } - - return cachedResponse || fetch(event.request); -} +// 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$/, /\.webmanifest$/ ]; +const offlineAssetsExclude = [ /^service-worker\.js$/ ]; + +// Replace with your base path if you are hosting on a subfolder. Ensure there is a trailing '/'. +const base = "/"; +const baseUrl = new URL(base, self.origin); +const manifestUrlList = self.assetsManifest.assets.map(asset => new URL(asset.url, baseUrl).href); + +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, + // unless that request is for an offline resource. + // If you need some URLs to be server-rendered, edit the following check to exclude those URLs + const shouldServeIndexHtml = event.request.mode === 'navigate' + && !manifestUrlList.some(url => url === event.request.url); + + const request = shouldServeIndexHtml ? 'index.html' : event.request; + const cache = await caches.open(cacheName); + cachedResponse = await cache.match(request); + } + + return cachedResponse || fetch(event.request); +} From d432d27ec00b1902d3fa308d0682b1123e9a189e Mon Sep 17 00:00:00 2001 From: Cody Owens Date: Thu, 19 Feb 2026 11:54:42 -0700 Subject: [PATCH 5/6] ci: add Azure Static Web Apps workflow file on-behalf-of: @Azure opensource@microsoft.com --- ...static-web-apps-lemon-ground-063b78b1e.yml | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/azure-static-web-apps-lemon-ground-063b78b1e.yml diff --git a/.github/workflows/azure-static-web-apps-lemon-ground-063b78b1e.yml b/.github/workflows/azure-static-web-apps-lemon-ground-063b78b1e.yml new file mode 100644 index 0000000..4b258fa --- /dev/null +++ b/.github/workflows/azure-static-web-apps-lemon-ground-063b78b1e.yml @@ -0,0 +1,46 @@ +name: Azure Static Web Apps CI/CD + +on: + push: + branches: + - authentication + pull_request: + types: [opened, synchronize, reopened, closed] + branches: + - authentication + +jobs: + build_and_deploy_job: + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') + runs-on: ubuntu-latest + name: Build and Deploy Job + steps: + - uses: actions/checkout@v3 + with: + submodules: true + lfs: false + - name: Build And Deploy + id: builddeploy + uses: Azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_LEMON_GROUND_063B78B1E }} + repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) + action: "upload" + ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### + # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig + app_location: "Client" # App source code path + api_location: "Api" # Api source code path - optional + output_location: "wwwroot" # Built app content directory - optional + ###### End of Repository/Build Configurations ###### + + close_pull_request_job: + if: github.event_name == 'pull_request' && github.event.action == 'closed' + runs-on: ubuntu-latest + name: Close Pull Request Job + steps: + - name: Close Pull Request + id: closepullrequest + uses: Azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_LEMON_GROUND_063B78B1E }} + action: "close" From 92f15df5f6a0c976165caefa27b99ace884bf743 Mon Sep 17 00:00:00 2001 From: Cody Owens Date: Thu, 19 Feb 2026 17:33:51 -0700 Subject: [PATCH 6/6] Went Nuclear. Attempted to remove authentication in its entirety --- .../DungeonMasterDashboard.Server.csproj | 25 +- .../20260213031756_FirstMigration.Designer.cs | 279 ------------------ .../20260213031756_FirstMigration.cs | 224 -------------- .../ApplicationDbContextModelSnapshot.cs | 276 ----------------- DungeonMasterDashboard.Server/Program.cs | 42 +-- .../Services/ApplicationDbContext.cs | 16 - .../dotnet-tools.json | 13 + DungeonMasterDashboard/App.razor | 20 +- .../DungeonMasterDashboard.csproj | 23 +- .../Layout/LoginDisplay.razor | 21 +- DungeonMasterDashboard/Layout/NavMenu.razor | 4 +- .../Pages/Authentication.razor | 80 ++--- DungeonMasterDashboard/Pages/Counter.razor | 1 - DungeonMasterDashboard/Pages/Profile.razor | 1 - DungeonMasterDashboard/Program.cs | 16 +- DungeonMasterDashboard/_Imports.razor | 2 - 16 files changed, 111 insertions(+), 932 deletions(-) delete mode 100644 DungeonMasterDashboard.Server/Migrations/20260213031756_FirstMigration.Designer.cs delete mode 100644 DungeonMasterDashboard.Server/Migrations/20260213031756_FirstMigration.cs delete mode 100644 DungeonMasterDashboard.Server/Migrations/ApplicationDbContextModelSnapshot.cs delete mode 100644 DungeonMasterDashboard.Server/Services/ApplicationDbContext.cs create mode 100644 DungeonMasterDashboard.Server/dotnet-tools.json diff --git a/DungeonMasterDashboard.Server/DungeonMasterDashboard.Server.csproj b/DungeonMasterDashboard.Server/DungeonMasterDashboard.Server.csproj index da45e57..1e8abfd 100644 --- a/DungeonMasterDashboard.Server/DungeonMasterDashboard.Server.csproj +++ b/DungeonMasterDashboard.Server/DungeonMasterDashboard.Server.csproj @@ -6,16 +6,19 @@ enable - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + + + + + + + + + + diff --git a/DungeonMasterDashboard.Server/Migrations/20260213031756_FirstMigration.Designer.cs b/DungeonMasterDashboard.Server/Migrations/20260213031756_FirstMigration.Designer.cs deleted file mode 100644 index 6f6f089..0000000 --- a/DungeonMasterDashboard.Server/Migrations/20260213031756_FirstMigration.Designer.cs +++ /dev/null @@ -1,279 +0,0 @@ -// -using System; -using DungeonMasterDashboard.Services; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace DungeonMasterDashboard.Server.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("20260213031756_FirstMigration")] - partial class FirstMigration - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DungeonMasterDashboard.Server/Migrations/20260213031756_FirstMigration.cs b/DungeonMasterDashboard.Server/Migrations/20260213031756_FirstMigration.cs deleted file mode 100644 index 84ce4d3..0000000 --- a/DungeonMasterDashboard.Server/Migrations/20260213031756_FirstMigration.cs +++ /dev/null @@ -1,224 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace DungeonMasterDashboard.Server.Migrations -{ - /// - public partial class FirstMigration : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AspNetRoles", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetUsers", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - EmailConfirmed = table.Column(type: "bit", nullable: false), - PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), - SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), - TwoFactorEnabled = table.Column(type: "bit", nullable: false), - LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), - LockoutEnabled = table.Column(type: "bit", nullable: false), - AccessFailedCount = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - RoleId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UserId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetUserClaims_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserLogins", - columns: table => new - { - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), - ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), - UserId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); - table.ForeignKey( - name: "FK_AspNetUserLogins_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserRoles", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - RoleId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserTokens", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(450)", nullable: false), - Value = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AspNetUserTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_AspNetRoleClaims_RoleId", - table: "AspNetRoleClaims", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "RoleNameIndex", - table: "AspNetRoles", - column: "NormalizedName", - unique: true, - filter: "[NormalizedName] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserClaims_UserId", - table: "AspNetUserClaims", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserLogins_UserId", - table: "AspNetUserLogins", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserRoles_RoleId", - table: "AspNetUserRoles", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "EmailIndex", - table: "AspNetUsers", - column: "NormalizedEmail"); - - migrationBuilder.CreateIndex( - name: "UserNameIndex", - table: "AspNetUsers", - column: "NormalizedUserName", - unique: true, - filter: "[NormalizedUserName] IS NOT NULL"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AspNetRoleClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserLogins"); - - migrationBuilder.DropTable( - name: "AspNetUserRoles"); - - migrationBuilder.DropTable( - name: "AspNetUserTokens"); - - migrationBuilder.DropTable( - name: "AspNetRoles"); - - migrationBuilder.DropTable( - name: "AspNetUsers"); - } - } -} diff --git a/DungeonMasterDashboard.Server/Migrations/ApplicationDbContextModelSnapshot.cs b/DungeonMasterDashboard.Server/Migrations/ApplicationDbContextModelSnapshot.cs deleted file mode 100644 index 301eb91..0000000 --- a/DungeonMasterDashboard.Server/Migrations/ApplicationDbContextModelSnapshot.cs +++ /dev/null @@ -1,276 +0,0 @@ -// -using System; -using DungeonMasterDashboard.Services; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace DungeonMasterDashboard.Server.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - partial class ApplicationDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DungeonMasterDashboard.Server/Program.cs b/DungeonMasterDashboard.Server/Program.cs index 45b3db7..596c6bd 100644 --- a/DungeonMasterDashboard.Server/Program.cs +++ b/DungeonMasterDashboard.Server/Program.cs @@ -1,44 +1,26 @@ -using DungeonMasterDashboard.Services; -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.ResponseCompression; -var builder = WebApplication.CreateBuilder(args); - -// Add services to the container. - -builder.Services.AddDbContext(options => +var builder = WebApplication.CreateBuilder(new WebApplicationOptions { - options.UseSqlServer("name=DefaultConnection"); + Args = args, + ContentRootPath = AppContext.BaseDirectory, + WebRootPath = "wwwroot" }); -builder.Services.AddAuthorization(); -builder.Services.AddIdentityApiEndpoints() - .AddEntityFrameworkStores(); - builder.Services.AddControllers(); -// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi -builder.Services.AddOpenApi(); - -var app = builder.Build(); -app.UseCors(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()); +builder.WebHost.UseUrls("http://0.0.0.0:8080"); -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) -{ - app.MapOpenApi(); - app.UseSwaggerUI(options => - { - options.SwaggerEndpoint("/openapi/v1.json", "api"); - }); -} - -app.MapIdentityApi(); +var app = builder.Build(); app.UseHttpsRedirection(); -app.UseAuthorization(); +app.UseBlazorFrameworkFiles(); +app.UseStaticFiles(); + +app.UseRouting(); app.MapControllers(); +app.MapFallbackToFile("index.html"); app.Run(); diff --git a/DungeonMasterDashboard.Server/Services/ApplicationDbContext.cs b/DungeonMasterDashboard.Server/Services/ApplicationDbContext.cs deleted file mode 100644 index 9a7aa13..0000000 --- a/DungeonMasterDashboard.Server/Services/ApplicationDbContext.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - -namespace DungeonMasterDashboard.Services -{ - public class ApplicationDbContext : IdentityDbContext - { - public ApplicationDbContext(DbContextOptions options) : base(options) - { - } - - protected ApplicationDbContext() - { - } - } -} diff --git a/DungeonMasterDashboard.Server/dotnet-tools.json b/DungeonMasterDashboard.Server/dotnet-tools.json new file mode 100644 index 0000000..12789b3 --- /dev/null +++ b/DungeonMasterDashboard.Server/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "10.0.3", + "commands": [ + "dotnet-ef" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/DungeonMasterDashboard/App.razor b/DungeonMasterDashboard/App.razor index 34eb91e..8030f94 100644 --- a/DungeonMasterDashboard/App.razor +++ b/DungeonMasterDashboard/App.razor @@ -1,8 +1,7 @@ - - - - - + + + + @* @if (context.User.Identity?.IsAuthenticated != true) { @@ -11,9 +10,8 @@ {

You are not authorized to access this resource.

} -
-
- -
-
-
+ *@ + + + + diff --git a/DungeonMasterDashboard/DungeonMasterDashboard.csproj b/DungeonMasterDashboard/DungeonMasterDashboard.csproj index 1c4242c..542be95 100644 --- a/DungeonMasterDashboard/DungeonMasterDashboard.csproj +++ b/DungeonMasterDashboard/DungeonMasterDashboard.csproj @@ -8,22 +8,15 @@ service-worker-assets.js - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + + + + - + diff --git a/DungeonMasterDashboard/Layout/LoginDisplay.razor b/DungeonMasterDashboard/Layout/LoginDisplay.razor index 489ea31..0d616bd 100644 --- a/DungeonMasterDashboard/Layout/LoginDisplay.razor +++ b/DungeonMasterDashboard/Layout/LoginDisplay.razor @@ -1,8 +1,7 @@ -@using Microsoft.AspNetCore.Components.Authorization -@inject NavigationManager Navigation -@inject AuthenticationStateProvider authStateProvider +@inject NavigationManager Navigation +@* @inject AuthenticationStateProvider authStateProvider *@ - +@* Hello, @context.User.Identity?.Name! @@ -11,13 +10,13 @@ Log in Register - + *@ @code{ - public void BeginLogOut() - { - var custAuthStateProvider = (CustomAuthenticationStateProvider)authStateProvider; - custAuthStateProvider.Logout(); - Navigation.NavigateTo("/"); - } + // public void BeginLogOut() + // { + // var custAuthStateProvider = (CustomAuthenticationStateProvider)authStateProvider; + // custAuthStateProvider.Logout(); + // Navigation.NavigateTo("/"); + // } } diff --git a/DungeonMasterDashboard/Layout/NavMenu.razor b/DungeonMasterDashboard/Layout/NavMenu.razor index c3ecb72..6e14523 100644 --- a/DungeonMasterDashboard/Layout/NavMenu.razor +++ b/DungeonMasterDashboard/Layout/NavMenu.razor @@ -9,7 +9,7 @@ - @*REGISTER BUTTON & CANCEL*@ + @* REGISTER BUTTON & CANCEL
@@ -92,13 +92,13 @@ -
+
*@ } -@inject AuthenticationStateProvider provider +@* @inject AuthenticationStateProvider provider *@ @inject NavigationManager navManager @code{ [Parameter] public string? Action { get; set; } @@ -111,39 +111,39 @@ private string[] errors = []; private string confirmPassword = ""; - private async Task LoginAsync() - { - var authStateProvider = (CustomAuthenticationStateProvider)provider; - var formResult = await authStateProvider.LoginAsync(email, password); - if (formResult.Success) - { - navManager.NavigateTo("/"); - } else - { - error = formResult.Errors[0]; - } - } - - private async Task RegisterAsync() - { - if (password != confirmPassword) - { - errors = ["Password and Confirm Password do not match!"]; - return; - } - var authStateProvider = (CustomAuthenticationStateProvider)provider; - var registerDto = new RegisterDto - { - Email = email, - Password = password - }; - var formResult = await authStateProvider.RegisterAsync(registerDto); - if (formResult.Success) - { - navManager.NavigateTo("/"); - } else - { - errors = formResult.Errors; - } - } + // private async Task LoginAsync() + // { + // var authStateProvider = (CustomAuthenticationStateProvider)provider; + // var formResult = await authStateProvider.LoginAsync(email, password); + // if (formResult.Success) + // { + // navManager.NavigateTo("/"); + // } else + // { + // error = formResult.Errors[0]; + // } + // } + + // private async Task RegisterAsync() + // { + // if (password != confirmPassword) + // { + // errors = ["Password and Confirm Password do not match!"]; + // return; + // } + // var authStateProvider = (CustomAuthenticationStateProvider)provider; + // var registerDto = new RegisterDto + // { + // Email = email, + // Password = password + // }; + // var formResult = await authStateProvider.RegisterAsync(registerDto); + // if (formResult.Success) + // { + // navManager.NavigateTo("/"); + // } else + // { + // errors = formResult.Errors; + // } + // } } diff --git a/DungeonMasterDashboard/Pages/Counter.razor b/DungeonMasterDashboard/Pages/Counter.razor index 9340be8..625853e 100644 --- a/DungeonMasterDashboard/Pages/Counter.razor +++ b/DungeonMasterDashboard/Pages/Counter.razor @@ -1,6 +1,5 @@ @page "/counter" @using Microsoft.AspNetCore.Authorization -@attribute [Authorize] Counter diff --git a/DungeonMasterDashboard/Pages/Profile.razor b/DungeonMasterDashboard/Pages/Profile.razor index 1b023ba..b6f6acb 100644 --- a/DungeonMasterDashboard/Pages/Profile.razor +++ b/DungeonMasterDashboard/Pages/Profile.razor @@ -1,5 +1,4 @@ @page "/profile" -@attribute [Authorize] DMD Profile diff --git a/DungeonMasterDashboard/Program.cs b/DungeonMasterDashboard/Program.cs index ac4009c..9ea7066 100644 --- a/DungeonMasterDashboard/Program.cs +++ b/DungeonMasterDashboard/Program.cs @@ -1,7 +1,4 @@ -using Blazored.LocalStorage; using DungeonMasterDashboard; -using DungeonMasterDashboard.Services; -using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; @@ -10,18 +7,11 @@ builder.RootComponents.Add("#app"); builder.RootComponents.Add("head::after"); -var apiBaseUrl = builder.Configuration["WebApiAddress"] - ?? throw new InvalidOperationException("WebApiAddress is not configured."); - +// If you don't have an API yet, just point to the same origin. +// This avoids requiring WebApiAddress. builder.Services.AddScoped(_ => new HttpClient { - BaseAddress = new Uri(apiBaseUrl) + BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); -builder.Services.AddCascadingAuthenticationState(); -builder.Services.AddAuthorizationCore(); -builder.Services.AddScoped(); -builder.Services.AddBlazoredLocalStorage(); - await builder.Build().RunAsync(); - diff --git a/DungeonMasterDashboard/_Imports.razor b/DungeonMasterDashboard/_Imports.razor index 8ca86ac..393c71a 100644 --- a/DungeonMasterDashboard/_Imports.razor +++ b/DungeonMasterDashboard/_Imports.razor @@ -1,6 +1,5 @@ @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 @@ -12,4 +11,3 @@ @using DungeonMasterDashboard.Services @using DungeonMasterDashboard.Models -@using Microsoft.AspNetCore.Authorization