From 3c1e27ede56b6b65415b681b7bc5846731ac2265 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 15 Jul 2023 02:19:11 +0200 Subject: [PATCH 001/117] Initial blazor implementation --- Modix.Web/App.razor | 12 ++++ Modix.Web/Data/WeatherForecast.cs | 12 ++++ Modix.Web/Data/WeatherForecastService.cs | 19 +++++++ Modix.Web/Modix.Web.csproj | 13 +++++ Modix.Web/Pages/Commands.razor | 24 ++++++++ Modix.Web/Pages/Error.cshtml | 42 ++++++++++++++ Modix.Web/Pages/Error.cshtml.cs | 25 +++++++++ Modix.Web/Pages/FetchData.razor | 47 ++++++++++++++++ Modix.Web/Pages/Index.razor | 22 ++++++++ Modix.Web/Pages/Logs.razor | 22 ++++++++ Modix.Web/Pages/Promotions.razor | 23 ++++++++ Modix.Web/Pages/Stats.razor | 23 ++++++++ Modix.Web/Pages/Tags.razor | 23 ++++++++ Modix.Web/Pages/UserLookup.razor | 23 ++++++++ Modix.Web/Pages/_Host.cshtml | 34 ++++++++++++ Modix.Web/Program.cs | 65 ++++++++++++++++++++++ Modix.Web/Properties/launchSettings.json | 37 +++++++++++++ Modix.Web/Shared/MainLayout.razor | 35 ++++++++++++ Modix.Web/Shared/MainLayout.razor.css | 70 ++++++++++++++++++++++++ Modix.Web/Shared/NavMenu.razor | 59 ++++++++++++++++++++ Modix.Web/Shared/NavMenu.razor.css | 68 +++++++++++++++++++++++ Modix.Web/_Imports.razor | 10 ++++ Modix.Web/appsettings.Development.json | 9 +++ Modix.Web/appsettings.json | 9 +++ Modix.sln | 16 +++++- 25 files changed, 741 insertions(+), 1 deletion(-) create mode 100644 Modix.Web/App.razor create mode 100644 Modix.Web/Data/WeatherForecast.cs create mode 100644 Modix.Web/Data/WeatherForecastService.cs create mode 100644 Modix.Web/Modix.Web.csproj create mode 100644 Modix.Web/Pages/Commands.razor create mode 100644 Modix.Web/Pages/Error.cshtml create mode 100644 Modix.Web/Pages/Error.cshtml.cs create mode 100644 Modix.Web/Pages/FetchData.razor create mode 100644 Modix.Web/Pages/Index.razor create mode 100644 Modix.Web/Pages/Logs.razor create mode 100644 Modix.Web/Pages/Promotions.razor create mode 100644 Modix.Web/Pages/Stats.razor create mode 100644 Modix.Web/Pages/Tags.razor create mode 100644 Modix.Web/Pages/UserLookup.razor create mode 100644 Modix.Web/Pages/_Host.cshtml create mode 100644 Modix.Web/Program.cs create mode 100644 Modix.Web/Properties/launchSettings.json create mode 100644 Modix.Web/Shared/MainLayout.razor create mode 100644 Modix.Web/Shared/MainLayout.razor.css create mode 100644 Modix.Web/Shared/NavMenu.razor create mode 100644 Modix.Web/Shared/NavMenu.razor.css create mode 100644 Modix.Web/_Imports.razor create mode 100644 Modix.Web/appsettings.Development.json create mode 100644 Modix.Web/appsettings.json diff --git a/Modix.Web/App.razor b/Modix.Web/App.razor new file mode 100644 index 000000000..6fd3ed1b5 --- /dev/null +++ b/Modix.Web/App.razor @@ -0,0 +1,12 @@ + + + + + + + Not found + +

Sorry, there's nothing at this address.

+
+
+
diff --git a/Modix.Web/Data/WeatherForecast.cs b/Modix.Web/Data/WeatherForecast.cs new file mode 100644 index 000000000..34a5ceb6c --- /dev/null +++ b/Modix.Web/Data/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace Modix.Web.Data; + +public class WeatherForecast +{ + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} diff --git a/Modix.Web/Data/WeatherForecastService.cs b/Modix.Web/Data/WeatherForecastService.cs new file mode 100644 index 000000000..c05c3d2d2 --- /dev/null +++ b/Modix.Web/Data/WeatherForecastService.cs @@ -0,0 +1,19 @@ +namespace Modix.Web.Data; + +public class WeatherForecastService +{ + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + public Task GetForecastAsync(DateOnly startDate) + { + return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = startDate.AddDays(index), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }).ToArray()); + } +} diff --git a/Modix.Web/Modix.Web.csproj b/Modix.Web/Modix.Web.csproj new file mode 100644 index 000000000..8c6eb5769 --- /dev/null +++ b/Modix.Web/Modix.Web.csproj @@ -0,0 +1,13 @@ + + + + net7.0 + enable + enable + + + + + + + diff --git a/Modix.Web/Pages/Commands.razor b/Modix.Web/Pages/Commands.razor new file mode 100644 index 000000000..74cf154f7 --- /dev/null +++ b/Modix.Web/Pages/Commands.razor @@ -0,0 +1,24 @@ +@page "/commands" + + + + + Commands + +

Commands

+ +

Current count: @currentCount

+ + +
+
+
+ +@code { + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} diff --git a/Modix.Web/Pages/Error.cshtml b/Modix.Web/Pages/Error.cshtml new file mode 100644 index 000000000..3ff7e625d --- /dev/null +++ b/Modix.Web/Pages/Error.cshtml @@ -0,0 +1,42 @@ +@page +@model Modix.Web.Pages.ErrorModel + + + + + + + + Error + + + + + +
+
+

Error.

+

An error occurred while processing your request.

+ + @if (Model.ShowRequestId) + { +

+ Request ID: @Model.RequestId +

+ } + +

Development Mode

+

+ Swapping to the Development environment displays detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

+
+
+ + + diff --git a/Modix.Web/Pages/Error.cshtml.cs b/Modix.Web/Pages/Error.cshtml.cs new file mode 100644 index 000000000..04d92f1ec --- /dev/null +++ b/Modix.Web/Pages/Error.cshtml.cs @@ -0,0 +1,25 @@ +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Modix.Web.Pages; +[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] +[IgnoreAntiforgeryToken] +public class ErrorModel : PageModel +{ + public string? RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + private readonly ILogger _logger; + + public ErrorModel(ILogger logger) + { + _logger = logger; + } + + public void OnGet() + { + RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; + } +} diff --git a/Modix.Web/Pages/FetchData.razor b/Modix.Web/Pages/FetchData.razor new file mode 100644 index 000000000..6d81339e7 --- /dev/null +++ b/Modix.Web/Pages/FetchData.razor @@ -0,0 +1,47 @@ +@page "/fetchdata" +@using Modix.Web.Data +@inject WeatherForecastService ForecastService + +Weather forecast + +

Weather forecast

+ +

This component demonstrates fetching data from a service.

+ +@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 ForecastService.GetForecastAsync(DateOnly.FromDateTime(DateTime.Now)); + } +} diff --git a/Modix.Web/Pages/Index.razor b/Modix.Web/Pages/Index.razor new file mode 100644 index 000000000..ee4654dc4 --- /dev/null +++ b/Modix.Web/Pages/Index.razor @@ -0,0 +1,22 @@ +@page "/" + +Modix - Home + + +
+

+ Modix (stylized MODiX) is a general purpose Discord bot written for use on the + C# Discord Server + by + developers like you. + It includes administrative features, a C# compiler / eval module, documentation search, and more. +

+

+ Modix is constantly in development - if you'd like to contribute, stop by the server, + and check out our GitHub repo! +

+ + + + +
diff --git a/Modix.Web/Pages/Logs.razor b/Modix.Web/Pages/Logs.razor new file mode 100644 index 000000000..b3819b6ae --- /dev/null +++ b/Modix.Web/Pages/Logs.razor @@ -0,0 +1,22 @@ +@page "/logs" + + + + + Logs +

Logs

+

Current count: @currentCount

+ + +
+
+
+ +@code { + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor new file mode 100644 index 000000000..b436a0407 --- /dev/null +++ b/Modix.Web/Pages/Promotions.razor @@ -0,0 +1,23 @@ +@page "/promotions" + + + + + + Promotions +

Promotion Campaigns

+

Current count: @currentCount

+ + +
+
+
+ +@code { + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} diff --git a/Modix.Web/Pages/Stats.razor b/Modix.Web/Pages/Stats.razor new file mode 100644 index 000000000..6630f932c --- /dev/null +++ b/Modix.Web/Pages/Stats.razor @@ -0,0 +1,23 @@ +@page "/stats" + + + + + + Stats +

Statistics for C#

+

Current count: @currentCount

+ + +
+
+
+ +@code { + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} diff --git a/Modix.Web/Pages/Tags.razor b/Modix.Web/Pages/Tags.razor new file mode 100644 index 000000000..ae7120e22 --- /dev/null +++ b/Modix.Web/Pages/Tags.razor @@ -0,0 +1,23 @@ +@page "/tags" + + + + + + Tags +

Tags

+

Current count: @currentCount

+ + +
+
+
+ +@code { + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} diff --git a/Modix.Web/Pages/UserLookup.razor b/Modix.Web/Pages/UserLookup.razor new file mode 100644 index 000000000..496f2da1e --- /dev/null +++ b/Modix.Web/Pages/UserLookup.razor @@ -0,0 +1,23 @@ +@page "/userlookup" + + + + + + UserLookup +

User Lookup

+

Current count: @currentCount

+ + +
+
+
+ +@code { + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} diff --git a/Modix.Web/Pages/_Host.cshtml b/Modix.Web/Pages/_Host.cshtml new file mode 100644 index 000000000..020d5cef6 --- /dev/null +++ b/Modix.Web/Pages/_Host.cshtml @@ -0,0 +1,34 @@ +@page "/" +@using Microsoft.AspNetCore.Components.Web +@namespace Modix.Web.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + + + + + + + + + + + + + + + + +
+ + An error has occurred. This application may no longer respond until reloaded. + + + An unhandled exception has occurred. See browser dev tools for details. + + Reload + 🗙 +
+ + + + diff --git a/Modix.Web/Program.cs b/Modix.Web/Program.cs new file mode 100644 index 000000000..8826d0de2 --- /dev/null +++ b/Modix.Web/Program.cs @@ -0,0 +1,65 @@ +using System.Security.Claims; +using AspNet.Security.OAuth.Discord; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Modix.Web.Data; + +namespace Modix.Web; +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + // Add services to the container. + builder.Services.AddRazorPages(); + builder.Services.AddServerSideBlazor(); + builder.Services.AddSingleton(); + + builder.Services + .AddAuthentication(options => + { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = DiscordAuthenticationDefaults.AuthenticationScheme; + }) + .AddCookie() + .AddDiscord(options => + { + options.ClientId = ""; + options.ClientSecret = ""; + options.SaveTokens = true; + options.AccessDeniedPath = "/"; + + options.Scope.Add("identify"); + + options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id", ClaimValueTypes.UInteger64); + options.ClaimActions.MapJsonKey(ClaimTypes.Name, "username", ClaimValueTypes.String); + }); + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + if (!app.Environment.IsDevelopment()) + { + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseStaticFiles(); + + app.UseRouting(); + + app.UseAuthentication(); + app.UseAuthorization(); + + app.MapGet("/login", async (context) => await context.ChallengeAsync(DiscordAuthenticationDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = "/" })); + app.MapGet("/logout", async (context) => await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = "/" })); + + app.MapBlazorHub(); + app.MapFallbackToPage("/_Host"); + + app.Run(); + } +} diff --git a/Modix.Web/Properties/launchSettings.json b/Modix.Web/Properties/launchSettings.json new file mode 100644 index 000000000..df0728cc5 --- /dev/null +++ b/Modix.Web/Properties/launchSettings.json @@ -0,0 +1,37 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:6749", + "sslPort": 44391 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5029", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7246;http://localhost:5029", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Modix.Web/Shared/MainLayout.razor b/Modix.Web/Shared/MainLayout.razor new file mode 100644 index 000000000..f80db34b1 --- /dev/null +++ b/Modix.Web/Shared/MainLayout.razor @@ -0,0 +1,35 @@ +@inherits LayoutComponentBase + +Modix.Web + + +
+ + + + + + + +
+ + +
+ Logout +
+
+ +
+ Login +
+
+
+ +
+ @Body +
+
+
+
diff --git a/Modix.Web/Shared/MainLayout.razor.css b/Modix.Web/Shared/MainLayout.razor.css new file mode 100644 index 000000000..551e4b276 --- /dev/null +++ b/Modix.Web/Shared/MainLayout.razor.css @@ -0,0 +1,70 @@ +.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 .btn-link { + white-space: nowrap; + margin-left: 1.5rem; + } + + .top-row a:first-child { + overflow: hidden; + text-overflow: ellipsis; + } + +@media (max-width: 640.98px) { + .top-row:not(.auth) { + display: none; + } + + .top-row.auth { + justify-content: space-between; + } + + .top-row a, .top-row .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, article { + padding-left: 2rem !important; + padding-right: 1.5rem !important; + } +} diff --git a/Modix.Web/Shared/NavMenu.razor b/Modix.Web/Shared/NavMenu.razor new file mode 100644 index 000000000..12ae0d429 --- /dev/null +++ b/Modix.Web/Shared/NavMenu.razor @@ -0,0 +1,59 @@ + + + + +@code { + private bool collapseNavMenu = true; + + private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; + + private void ToggleNavMenu() + { + collapseNavMenu = !collapseNavMenu; + } +} diff --git a/Modix.Web/Shared/NavMenu.razor.css b/Modix.Web/Shared/NavMenu.razor.css new file mode 100644 index 000000000..604b7a1a1 --- /dev/null +++ b/Modix.Web/Shared/NavMenu.razor.css @@ -0,0 +1,68 @@ +.navbar-toggler { + background-color: rgba(255, 255, 255, 0.1); +} + +.top-row { + height: 3.5rem; + background-color: rgba(0,0,0,0.4); +} + +.navbar-brand { + font-size: 1.1rem; +} + +.oi { + width: 2rem; + font-size: 1.1rem; + vertical-align: text-top; + top: -2px; +} + +.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.25); + 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/Modix.Web/_Imports.razor b/Modix.Web/_Imports.razor new file mode 100644 index 000000000..d4ac1b7ad --- /dev/null +++ b/Modix.Web/_Imports.razor @@ -0,0 +1,10 @@ +@using System.Net.Http +@using Microsoft.AspNetCore.Authorization +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using Modix.Web +@using Modix.Web.Shared diff --git a/Modix.Web/appsettings.Development.json b/Modix.Web/appsettings.Development.json new file mode 100644 index 000000000..770d3e931 --- /dev/null +++ b/Modix.Web/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "DetailedErrors": true, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Modix.Web/appsettings.json b/Modix.Web/appsettings.json new file mode 100644 index 000000000..10f68b8c8 --- /dev/null +++ b/Modix.Web/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Modix.sln b/Modix.sln index 1d4efdd26..8dfec23ea 100644 --- a/Modix.sln +++ b/Modix.sln @@ -59,7 +59,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "wiki", "wiki", "{092916B2-3 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modix.Analyzers", "Modix.Analyzers\Modix.Analyzers.csproj", "{6009AEDD-BE3B-40BF-B9C4-4C3796F58609}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modix.Analyzers.Test", "Modix.Analyzers.Test\Modix.Analyzers.Test.csproj", "{9A28A475-067B-4CBD-94BC-CA31C0D1555A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modix.Analyzers.Test", "Modix.Analyzers.Test\Modix.Analyzers.Test.csproj", "{9A28A475-067B-4CBD-94BC-CA31C0D1555A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modix.Web", "Modix.Web\Modix.Web.csproj", "{2280A9D0-358E-4668-8855-6832725C740A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -203,6 +205,18 @@ Global {9A28A475-067B-4CBD-94BC-CA31C0D1555A}.Release|x64.Build.0 = Release|Any CPU {9A28A475-067B-4CBD-94BC-CA31C0D1555A}.Release|x86.ActiveCfg = Release|Any CPU {9A28A475-067B-4CBD-94BC-CA31C0D1555A}.Release|x86.Build.0 = Release|Any CPU + {2280A9D0-358E-4668-8855-6832725C740A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2280A9D0-358E-4668-8855-6832725C740A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2280A9D0-358E-4668-8855-6832725C740A}.Debug|x64.ActiveCfg = Debug|Any CPU + {2280A9D0-358E-4668-8855-6832725C740A}.Debug|x64.Build.0 = Debug|Any CPU + {2280A9D0-358E-4668-8855-6832725C740A}.Debug|x86.ActiveCfg = Debug|Any CPU + {2280A9D0-358E-4668-8855-6832725C740A}.Debug|x86.Build.0 = Debug|Any CPU + {2280A9D0-358E-4668-8855-6832725C740A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2280A9D0-358E-4668-8855-6832725C740A}.Release|Any CPU.Build.0 = Release|Any CPU + {2280A9D0-358E-4668-8855-6832725C740A}.Release|x64.ActiveCfg = Release|Any CPU + {2280A9D0-358E-4668-8855-6832725C740A}.Release|x64.Build.0 = Release|Any CPU + {2280A9D0-358E-4668-8855-6832725C740A}.Release|x86.ActiveCfg = Release|Any CPU + {2280A9D0-358E-4668-8855-6832725C740A}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From b5c8382d40d8d635953c2470d88bb556b399ce3e Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 29 Jul 2023 22:23:25 +0200 Subject: [PATCH 002/117] Remove template files --- Modix.Web/Data/WeatherForecast.cs | 12 ------ Modix.Web/Data/WeatherForecastService.cs | 19 ---------- Modix.Web/Modix.Web.csproj | 20 ++++++++-- Modix.Web/Pages/FetchData.razor | 47 ------------------------ 4 files changed, 17 insertions(+), 81 deletions(-) delete mode 100644 Modix.Web/Data/WeatherForecast.cs delete mode 100644 Modix.Web/Data/WeatherForecastService.cs delete mode 100644 Modix.Web/Pages/FetchData.razor diff --git a/Modix.Web/Data/WeatherForecast.cs b/Modix.Web/Data/WeatherForecast.cs deleted file mode 100644 index 34a5ceb6c..000000000 --- a/Modix.Web/Data/WeatherForecast.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Modix.Web.Data; - -public class WeatherForecast -{ - public DateOnly Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string? Summary { get; set; } -} diff --git a/Modix.Web/Data/WeatherForecastService.cs b/Modix.Web/Data/WeatherForecastService.cs deleted file mode 100644 index c05c3d2d2..000000000 --- a/Modix.Web/Data/WeatherForecastService.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Modix.Web.Data; - -public class WeatherForecastService -{ - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - public Task GetForecastAsync(DateOnly startDate) - { - return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = startDate.AddDays(index), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = Summaries[Random.Shared.Next(Summaries.Length)] - }).ToArray()); - } -} diff --git a/Modix.Web/Modix.Web.csproj b/Modix.Web/Modix.Web.csproj index 8c6eb5769..7e2bb08eb 100644 --- a/Modix.Web/Modix.Web.csproj +++ b/Modix.Web/Modix.Web.csproj @@ -1,13 +1,27 @@ - + - net7.0 enable enable - + + + + + + + + + + + + + + + + diff --git a/Modix.Web/Pages/FetchData.razor b/Modix.Web/Pages/FetchData.razor deleted file mode 100644 index 6d81339e7..000000000 --- a/Modix.Web/Pages/FetchData.razor +++ /dev/null @@ -1,47 +0,0 @@ -@page "/fetchdata" -@using Modix.Web.Data -@inject WeatherForecastService ForecastService - -Weather forecast - -

Weather forecast

- -

This component demonstrates fetching data from a service.

- -@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 ForecastService.GetForecastAsync(DateOnly.FromDateTime(DateTime.Now)); - } -} From d0510cbcb5414ced6ffe643c6fb1ffdcc18b8df0 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 29 Jul 2023 22:36:09 +0200 Subject: [PATCH 003/117] Add developmentSettings to gitignore and allow wwwroot in blazor project --- .gitignore | 5 +++-- Modix.Web/wwwroot/css/site.css | 3 +++ Modix.Web/wwwroot/favicon.png | Bin 0 -> 1148 bytes Modix.sln | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 Modix.Web/wwwroot/css/site.css create mode 100644 Modix.Web/wwwroot/favicon.png diff --git a/.gitignore b/.gitignore index 0639821a0..ecf9e659a 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ bld/ .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot wwwroot/ +!Modix.Web/wwwroot/ dataprotection/ # VS Code @@ -261,5 +262,5 @@ Modix/config /Modix/Properties/launchSettings.json *.DotSettings -Modix/developmentSettings\.json -Modix/logs/* +**/developmentSettings\.json +**/logs/* diff --git a/Modix.Web/wwwroot/css/site.css b/Modix.Web/wwwroot/css/site.css new file mode 100644 index 000000000..cb1ebbfeb --- /dev/null +++ b/Modix.Web/wwwroot/css/site.css @@ -0,0 +1,3 @@ +.lookup-text-item { + margin-left: 10px !important; +} diff --git a/Modix.Web/wwwroot/favicon.png b/Modix.Web/wwwroot/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8422b59695935d180d11d5dbe99653e711097819 GIT binary patch literal 1148 zcmV-?1cUpDP)9h26h2-Cs%i*@Moc3?#6qJID|D#|3|2Hn7gTIYEkr|%Xjp);YgvFmB&0#2E2b=| zkVr)lMv9=KqwN&%obTp-$<51T%rx*NCwceh-E+=&e(oLO`@Z~7gybJ#U|^tB2Pai} zRN@5%1qsZ1e@R(XC8n~)nU1S0QdzEYlWPdUpH{wJ2Pd4V8kI3BM=)sG^IkUXF2-j{ zrPTYA6sxpQ`Q1c6mtar~gG~#;lt=s^6_OccmRd>o{*=>)KS=lM zZ!)iG|8G0-9s3VLm`bsa6e ze*TlRxAjXtm^F8V`M1%s5d@tYS>&+_ga#xKGb|!oUBx3uc@mj1%=MaH4GR0tPBG_& z9OZE;->dO@`Q)nr<%dHAsEZRKl zedN6+3+uGHejJp;Q==pskSAcRcyh@6mjm2z-uG;s%dM-u0*u##7OxI7wwyCGpS?4U zBFAr(%GBv5j$jS@@t@iI8?ZqE36I^4t+P^J9D^ELbS5KMtZ z{Qn#JnSd$15nJ$ggkF%I4yUQC+BjDF^}AtB7w348EL>7#sAsLWs}ndp8^DsAcOIL9 zTOO!!0!k2`9BLk25)NeZp7ev>I1Mn={cWI3Yhx2Q#DnAo4IphoV~R^c0x&nw*MoIV zPthX?{6{u}sMS(MxD*dmd5rU(YazQE59b|TsB5Tm)I4a!VaN@HYOR)DwH1U5y(E)z zQqQU*B%MwtRQ$%x&;1p%ANmc|PkoFJZ%<-uq%PX&C!c-7ypis=eP+FCeuv+B@h#{4 zGx1m0PjS~FJt}3mdt4c!lel`1;4W|03kcZRG+DzkTy|7-F~eDsV2Tx!73dM0H0CTh zl)F-YUkE1zEzEW(;JXc|KR5{ox%YTh{$%F$a36JP6Nb<0%#NbSh$dMYF-{ z1_x(Vx)}fs?5_|!5xBTWiiIQHG<%)*e=45Fhjw_tlnmlixq;mUdC$R8v#j( zhQ$9YR-o%i5Uc`S?6EC51!bTRK=Xkyb<18FkCKnS2;o*qlij1YA@-nRpq#OMTX&RbL<^2q@0qja!uIvI;j$6>~k@IMwD42=8$$!+R^@5o6HX(*n~ Date: Sat, 29 Jul 2023 22:37:01 +0200 Subject: [PATCH 004/117] Add MudBlazor to _Host.cshtml --- Modix.Web/Pages/_Host.cshtml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Modix.Web/Pages/_Host.cshtml b/Modix.Web/Pages/_Host.cshtml index 020d5cef6..edd87d5e8 100644 --- a/Modix.Web/Pages/_Host.cshtml +++ b/Modix.Web/Pages/_Host.cshtml @@ -9,10 +9,14 @@ - + + + + + @@ -30,5 +34,7 @@ + + From f3f135c0505893a5ee384a007cac179d425a6305 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 29 Jul 2023 22:37:48 +0200 Subject: [PATCH 005/117] Initial landing page implementation --- Modix.Web/Pages/Index.razor | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/Modix.Web/Pages/Index.razor b/Modix.Web/Pages/Index.razor index ee4654dc4..b71802aa0 100644 --- a/Modix.Web/Pages/Index.razor +++ b/Modix.Web/Pages/Index.razor @@ -1,22 +1,25 @@ @page "/" +@using MudBlazor Modix - Home -
-

- Modix (stylized MODiX) is a general purpose Discord bot written for use on the - C# Discord Server - by - developers like you. - It includes administrative features, a C# compiler / eval module, documentation search, and more. -

-

- Modix is constantly in development - if you'd like to contribute, stop by the server, - and check out our GitHub repo! -

+ +
+ + Modix (stylized MODiX) is a general purpose Discord bot written for use on the + C# Discord Server + by + developers like you. + It includes administrative features, a C# compiler / eval module, documentation search, and more. + + + Modix is constantly in development - if you'd like to contribute, stop by the server, + and check out our GitHub repo! + - - - -
+ + + +
+ From 68b903bffbff4d88d809406a1f8ce5e097dc81cc Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 29 Jul 2023 22:48:40 +0200 Subject: [PATCH 006/117] Migrate Modix startup to Modix.Web Add Authorization policies based on AuthorizationClaim enum Add crude implementation of DiscordUserService --- .../Extensions/ServiceCollectionExtensions.cs | 186 ++++++++++++++ Modix.Web/Models/ModixUser.cs | 31 +++ Modix.Web/Program.cs | 239 +++++++++++++++--- .../Services/ClaimsTransformationService.cs | 43 ++++ Modix.Web/Services/DiscordUserService.cs | 63 +++++ 5 files changed, 525 insertions(+), 37 deletions(-) create mode 100644 Modix.Web/Extensions/ServiceCollectionExtensions.cs create mode 100644 Modix.Web/Models/ModixUser.cs create mode 100644 Modix.Web/Services/ClaimsTransformationService.cs create mode 100644 Modix.Web/Services/DiscordUserService.cs diff --git a/Modix.Web/Extensions/ServiceCollectionExtensions.cs b/Modix.Web/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..0ad55a812 --- /dev/null +++ b/Modix.Web/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,186 @@ +using System; +using System.Net; +using System.Net.Http; + +using Discord; +using Discord.Commands; +using Discord.Interactions; +using Discord.Rest; +using Discord.WebSocket; + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; + +using Modix; +using Modix.Behaviors; +using Modix.Bot; +using Modix.Bot.Behaviors; +using Modix.Common; +using Modix.Common.Messaging; +using Modix.Data.Models.Core; +using Modix.Data.Repositories; +using Modix.Services; +using Modix.Services.AutoRemoveMessage; +using Modix.Services.CodePaste; +using Modix.Services.CommandHelp; +using Modix.Services.Core; +using Modix.Services.Csharp; +using Modix.Services.EmojiStats; +using Modix.Services.GuildStats; +using Modix.Services.Images; +using Modix.Services.Moderation; +using Modix.Services.Promotions; +using Modix.Services.Quote; +using Modix.Services.StackExchange; +using Modix.Services.Starboard; +using Modix.Services.Tags; +using Modix.Services.Utilities; +using Modix.Services.Wikipedia; + +using Polly; +using Polly.Extensions.Http; + +namespace Microsoft.Extensions.DependencyInjection +{ + internal static class ServiceCollectionExtensions + { + public static IServiceCollection AddModixHttpClients(this IServiceCollection services) + { + services.AddHttpClient(); + + services.AddHttpClient(HttpClientNames.RetryOnTransientErrorPolicy) + .AddPolicyHandler(HttpPolicyExtensions.HandleTransientHttpError() + .WaitAndRetryAsync(2, retryAttempt => TimeSpan.FromSeconds(5))); + + services.AddHttpClient(HttpClientNames.TimeoutFiveSeconds) + .ConfigureHttpClient(client => + { + client.Timeout = TimeSpan.FromSeconds(5); + }); + + services.AddHttpClient(HttpClientNames.Timeout300ms) + .ConfigureHttpClient(client => + { + client.Timeout = TimeSpan.FromMilliseconds(300); + }); + + services.AddHttpClient(HttpClientNames.AutomaticGZipDecompression) + .ConfigurePrimaryHttpMessageHandler(() => + new HttpClientHandler() + { + AutomaticDecompression = DecompressionMethods.GZip, + }); + + return services; + } + + public static IServiceCollection AddModix( + this IServiceCollection services, + IConfiguration configuration) + { + services + .AddSingleton( + provider => new DiscordSocketClient(config: new DiscordSocketConfig + { + AlwaysDownloadUsers = true, + GatewayIntents = + GatewayIntents.GuildBans | // GUILD_BAN_ADD, GUILD_BAN_REMOVE + GatewayIntents.GuildMembers | // GUILD_MEMBER_ADD, GUILD_MEMBER_UPDATE, GUILD_MEMBER_REMOVE + GatewayIntents.GuildMessageReactions | // MESSAGE_REACTION_ADD, MESSAGE_REACTION_REMOVE, + // MESSAGE_REACTION_REMOVE_ALL, MESSAGE_REACTION_REMOVE_EMOJI + GatewayIntents.GuildMessages | // MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, MESSAGE_DELETE_BULK + GatewayIntents.Guilds | // GUILD_CREATE, GUILD_UPDATE, GUILD_DELETE, GUILD_ROLE_CREATE, + // GUILD_ROLE_UPDATE, GUILD_ROLE_DELETE, CHANNEL_CREATE, + // CHANNEL_UPDATE, CHANNEL_DELETE, CHANNEL_PINS_UPDATE + GatewayIntents.MessageContent, // MESSAGE_CONTENT + LogLevel = LogSeverity.Debug, + MessageCacheSize = provider + .GetRequiredService>() + .Value + .MessageCacheSize //needed to log deletions + })) + .AddSingleton(provider => provider.GetRequiredService()); + + services + .AddSingleton( + provider => new DiscordRestClient(config: new DiscordRestConfig + { + LogLevel = LogSeverity.Debug, + })); + + services.AddSingleton(_ => + { + var service = new CommandService( + new CommandServiceConfig + { + LogLevel = LogSeverity.Debug, + DefaultRunMode = Discord.Commands.RunMode.Sync, + CaseSensitiveCommands = false, + SeparatorChar = ' ' + }); + + service.AddTypeReader(new EmoteTypeReader()); + service.AddTypeReader(new UserEntityTypeReader()); + service.AddTypeReader>(new AnyGuildMessageTypeReader()); + service.AddTypeReader(new TimeSpanTypeReader(), true); + service.AddTypeReader(new UserOrMessageAuthorEntityTypeReader()); + service.AddTypeReader(new UriTypeReader()); + + return service; + }) + .AddScoped, CommandListeningBehavior>(); + + services.AddSingleton(provider => + { + var socketClient = provider.GetRequiredService(); + var service = new InteractionService(socketClient, new() + { + LogLevel = LogSeverity.Debug, + DefaultRunMode = Discord.Interactions.RunMode.Sync, + UseCompiledLambda = true, + AutoServiceScopes = false, + }); + + service.AddTypeConverter(new EmoteTypeConverter()); + service.AddTypeConverter(new Modix.Bot.TypeConverters.UriTypeConverter()); + + return service; + }) + .AddScoped, InteractionListeningBehavior>(); + + services.AddSingleton(); + + services + .AddModixCommon(configuration) + .AddModixServices(configuration) + .AddModixBot(configuration) + .AddModixCore() + .AddModixModeration() + .AddModixPromotions() + .AddCodePaste() + .AddCommandHelp() + .AddGuildStats() + .AddModixTags() + .AddStarboard() + .AddAutoRemoveMessage() + .AddEmojiStats() + .AddImages(); + + services.AddScoped(); + services.AddSingleton(); + services.AddMemoryCache(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped, PromotionLoggingHandler>(); + + services.AddHostedService(); + + return services; + } + } +} diff --git a/Modix.Web/Models/ModixUser.cs b/Modix.Web/Models/ModixUser.cs new file mode 100644 index 000000000..e5daa19ce --- /dev/null +++ b/Modix.Web/Models/ModixUser.cs @@ -0,0 +1,31 @@ +using Discord; +using Modix.Services.Utilities; + +namespace Modix.Web.Models; + +public class ModixUser +{ + public string Name { get; set; } + public ulong UserId { get; set; } + public string AvatarUrl { get; set; } + + public static ModixUser FromIGuildUser(IGuildUser user) + { + return new() + { + Name = user.GetDisplayName(), + UserId = user.Id, + AvatarUrl = user.GetDisplayAvatarUrl() ?? user.GetDefaultAvatarUrl() + }; + } + + public static ModixUser FromNonGuildUser(IUser user) + { + return new() + { + Name = user.GetDisplayName(), + UserId = user.Id, + AvatarUrl = user.GetAvatarUrl() ?? user.GetDefaultAvatarUrl() + }; + } +} diff --git a/Modix.Web/Program.cs b/Modix.Web/Program.cs index 8826d0de2..69b5cbeef 100644 --- a/Modix.Web/Program.cs +++ b/Modix.Web/Program.cs @@ -1,65 +1,230 @@ -using System.Security.Claims; +using System.Diagnostics; +using System.Reflection; +using System.Security.Claims; using AspNet.Security.OAuth.Discord; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; -using Modix.Web.Data; +using Microsoft.AspNetCore.DataProtection; +using Modix.Data; +using Modix.Data.Models.Core; +using Modix.Services.CodePaste; +using Modix.Services.Utilities; +using Modix.Web.Services; +using MudBlazor; +using MudBlazor.Services; +using Serilog; +using Serilog.Events; +using Serilog.Formatting.Compact; namespace Modix.Web; + public class Program { - public static void Main(string[] args) + public static int Main(string[] args) { var builder = WebApplication.CreateBuilder(args); - // Add services to the container. - builder.Services.AddRazorPages(); - builder.Services.AddServerSideBlazor(); - builder.Services.AddSingleton(); + var configBuilder = builder.Configuration + .AddEnvironmentVariables("MODIX_") + .AddJsonFile("developmentSettings.json", optional: true, reloadOnChange: false) + .AddKeyPerFile("/run/secrets", true); - builder.Services - .AddAuthentication(options => + if (builder.Environment.IsDevelopment()) + { + configBuilder.AddUserSecrets(); + } + + var builtConfig = configBuilder.Build(); + var config = new ModixConfig(); + builtConfig.Bind(config); + + ConfigureServices(builder, builtConfig, config); + + var loggerConfig = new LoggerConfiguration() + .MinimumLevel.Verbose() + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .MinimumLevel.Override("Modix.DiscordSerilogAdapter", LogEventLevel.Information) + .Enrich.FromLogContext() + .WriteTo.Logger(subLoggerConfig => subLoggerConfig + .MinimumLevel.Information() + // .MinimumLevel.Override() is not supported for sub-loggers, even though the docs don't specify this. See https://github.com/serilog/serilog/pull/1033 + .Filter.ByExcluding("SourceContext like 'Microsoft.%' and @l in ['Information', 'Debug', 'Verbose']") + .WriteTo.Console() + .WriteTo.File(Path.Combine("logs", "{Date}.log"), rollingInterval: RollingInterval.Day)) + .WriteTo.File( + new RenderedCompactJsonFormatter(), + Path.Combine("logs", "{Date}.clef"), + rollingInterval: RollingInterval.Day, + retainedFileCountLimit: 2); + + var seqEndpoint = config.SeqEndpoint; + var seqKey = config.SeqKey; + + if (seqEndpoint != null && seqKey == null) // seq is enabled without a key { - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = DiscordAuthenticationDefaults.AuthenticationScheme; - }) - .AddCookie() - .AddDiscord(options => + loggerConfig = loggerConfig.WriteTo.Seq(seqEndpoint); + } + else if (seqEndpoint != null && seqKey != null) //seq is enabled with a key { - options.ClientId = ""; - options.ClientSecret = ""; - options.SaveTokens = true; - options.AccessDeniedPath = "/"; + loggerConfig = loggerConfig.WriteTo.Seq(seqEndpoint, apiKey: seqKey); + } - options.Scope.Add("identify"); + var webhookId = config.LogWebhookId; + var webhookToken = config.LogWebhookToken; - options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id", ClaimValueTypes.UInteger64); - options.ClaimActions.MapJsonKey(ClaimTypes.Name, "username", ClaimValueTypes.String); - }); + //.ConfigureWebHostDefaults(webBuilder => + //{ + // webBuilder + // .UseConfiguration(builtConfig) + // .UseStartup(); + //}) + //.UseSerilog(); - var app = builder.Build(); + var host = builder.Build(); - // Configure the HTTP request pipeline. - if (!app.Environment.IsDevelopment()) + if (!host.Environment.IsDevelopment()) { - app.UseExceptionHandler("/Error"); + host.UseExceptionHandler("/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); + host.UseHsts(); } - app.UseHttpsRedirection(); - app.UseStaticFiles(); + host.UseHttpsRedirection(); + host.UseStaticFiles(); + + host.UseRouting(); - app.UseRouting(); + host.UseRequestLocalization("en-US"); - app.UseAuthentication(); - app.UseAuthorization(); + host.UseResponseCompression(); + host.UseAuthentication(); + host.UseAuthorization(); + + host.MapGet("/login", async (context) => await context.ChallengeAsync(DiscordAuthenticationDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = "/" })); + host.MapGet("/logout", async (context) => await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = "/" })); + + host.Map("/invite", builder => + { + builder.Run(handler => + { + //TODO: Maybe un-hardcode this? + //handler.Response.StatusCode = StatusCodes + + handler.Response.Redirect("https://aka.ms/csharp-discord"); + return Task.CompletedTask; + }); + }); + + // TODO: Is this important? Possibly remove + //host.UseMvcWithDefaultRoute(); + host.MapBlazorHub(); + host.MapFallbackToPage("/_Host"); + + if (webhookId.HasValue && webhookToken != null) + { + loggerConfig = loggerConfig + .WriteTo.DiscordWebhookSink(webhookId.Value, webhookToken, LogEventLevel.Error, host.Services.GetRequiredService()); + } + + Log.Logger = loggerConfig.CreateLogger(); + + try + { + host.Run(); + return 0; + } + catch (Exception ex) + { + Log.ForContext() + .Fatal(ex, "Host terminated unexpectedly."); - app.MapGet("/login", async (context) => await context.ChallengeAsync(DiscordAuthenticationDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = "/" })); - app.MapGet("/logout", async (context) => await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = "/" })); + if (Debugger.IsAttached && Environment.UserInteractive) + { + Console.WriteLine(Environment.NewLine + "Press any key to exit..."); + Console.ReadKey(true); + } - app.MapBlazorHub(); - app.MapFallbackToPage("/_Host"); + return ex.HResult; + } + finally + { + Log.CloseAndFlush(); + } + } + + private static void ConfigureServices(WebApplicationBuilder builder, IConfiguration configuration, ModixConfig modixConfig) + { + builder.Services.AddScoped(); + builder.Services.AddMudServices(); + builder.Services.AddMudMarkdownServices(); + builder.Services.AddRazorPages(config => + { + config.Conventions.AuthorizePage("/Promotions", nameof(AuthorizationClaim.PromotionsRead)); + }); + builder.Services.AddServerSideBlazor(); + + builder.Services.AddServices(Assembly.GetExecutingAssembly(), configuration); + + builder.Services.Configure(configuration); + + builder.Services.AddDataProtection() + .PersistKeysToFileSystem(new DirectoryInfo(@"dataprotection")); + + builder.Services + .AddAuthentication(options => + { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = DiscordAuthenticationDefaults.AuthenticationScheme; + }) + .AddCookie() + .AddDiscord(options => + { + options.ClientId = modixConfig.DiscordClientId!; + options.ClientSecret = modixConfig.DiscordClientSecret!; + options.SaveTokens = true; + options.AccessDeniedPath = "/"; + + options.Scope.Add("identify"); + + options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id", ClaimValueTypes.UInteger64); + options.ClaimActions.MapJsonKey(ClaimTypes.Name, "username", ClaimValueTypes.String); + }); + + builder.Services.AddAuthorization(config => + { + var claims = Enum.GetValues(); + foreach (var claim in claims) + { + config.AddPolicy(claim.ToString(), builder => builder.RequireClaim(ClaimTypes.Role, claim.ToString())); + } + }); + + builder.Services.AddScoped(); + + //services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) + // .AddCookie(options => + // { + // options.LoginPath = "/api/unauthorized"; + // //options.LogoutPath = "/logout"; + // options.ExpireTimeSpan = new TimeSpan(7, 0, 0, 0); + // }) + // .AddDiscordAuthentication(); + + builder.Services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN"); + builder.Services.AddResponseCompression(); + + // TODO: Fix this + //services.AddTransient, StaticFilesConfiguration>(); + //services.AddTransient(); + + builder.Services + .AddServices(typeof(ModixContext).Assembly, configuration) + .AddNpgsql(configuration.GetValue(nameof(ModixConfig.DbConnection))); + + builder.Services + .AddModixHttpClients() + .AddModix(configuration); - app.Run(); + builder.Services.AddMvc(d => d.EnableEndpointRouting = false); } } diff --git a/Modix.Web/Services/ClaimsTransformationService.cs b/Modix.Web/Services/ClaimsTransformationService.cs new file mode 100644 index 000000000..63b444650 --- /dev/null +++ b/Modix.Web/Services/ClaimsTransformationService.cs @@ -0,0 +1,43 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Authentication; +using Modix.Services.Core; + +namespace Modix.Web.Services; + +public class ClaimsTransformationService : IClaimsTransformation +{ + private readonly IAuthorizationService _authorizationService; + private readonly DiscordUserService _discordUserService; + + public ClaimsTransformationService(IAuthorizationService authorizationService, DiscordUserService discordUserService) + { + _authorizationService = authorizationService; + _discordUserService = discordUserService; + } + + public async Task TransformAsync(ClaimsPrincipal principal) + { + if (!principal.Identity?.IsAuthenticated ?? false) + return principal; + + var userId = principal.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value; + if (!ulong.TryParse(userId, out var userSnowflake)) + return principal; + + var newPrincipal = principal.Clone(); + if (newPrincipal.Identity is not ClaimsIdentity claimsIdentity) + return principal; + + // TODO: Get selected guild from cookie + var currentGuild = _discordUserService.GetUserGuild(); + var currentUser = currentGuild.GetUser(userSnowflake); + await _authorizationService.OnAuthenticatedAsync(currentUser.Id, currentGuild.Id, currentUser.Roles.Select(x => x.Id).ToList()); + + var claims = (await _authorizationService.GetGuildUserClaimsAsync(currentUser)) + .Select(d => new Claim(ClaimTypes.Role, d.ToString())); + + claimsIdentity.AddClaims(claims); + + return newPrincipal; + } +} diff --git a/Modix.Web/Services/DiscordUserService.cs b/Modix.Web/Services/DiscordUserService.cs new file mode 100644 index 000000000..7bd763d43 --- /dev/null +++ b/Modix.Web/Services/DiscordUserService.cs @@ -0,0 +1,63 @@ +using System.Security.Claims; +using Discord.WebSocket; +using Microsoft.AspNetCore.Components.Authorization; +using Modix.Services.Core; +using Modix.Web.Models; + +namespace Modix.Web.Services; + +public class DiscordUserService +{ + private readonly DiscordSocketClient _client; + private readonly AuthenticationStateProvider _authenticationStateProvider; + private readonly IUserService _userService; + + public DiscordUserService(DiscordSocketClient client, AuthenticationStateProvider authenticationStateProvider, IUserService userService) + { + _client = client; + _authenticationStateProvider = authenticationStateProvider; + _userService = userService; + } + + public SocketGuild GetUserGuild() => _client.Guilds.First(); + + public async Task GetCurrentUserAsync() + { + var authState = await _authenticationStateProvider.GetAuthenticationStateAsync(); + if (!authState.User.Identity?.IsAuthenticated ?? false) + return null; + + var userId = authState.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value; + if (!ulong.TryParse(userId, out var userSnowflake)) + return null; + + var currentGuild = GetUserGuild(); + return currentGuild.GetUser(userSnowflake); + } + + public async Task> AutoCompleteAsync(string query) + { + var userGuild = GetUserGuild(); + + if (userGuild?.Users is null) + return Array.Empty(); + + var result = userGuild.Users + .Where(d => d.Username.Contains(query, StringComparison.OrdinalIgnoreCase) || d.Id.ToString() == query) + .Take(10) + .Select(ModixUser.FromIGuildUser); + + if (!result.Any() && ulong.TryParse(query, out var userId)) + { + var user = await _userService.GetUserInformationAsync(userGuild.Id, userId); + + if (user is not null) + { + result = result.Append(ModixUser.FromNonGuildUser(user)); + } + } + + return result; + } + +} From 7e982e4b90ec2fa4a5dda448b332a9cdfaf9a6ca Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 29 Jul 2023 22:54:51 +0200 Subject: [PATCH 007/117] Implement navbar with separate views depending on authorization state --- Modix.Web/App.razor | 35 +++++++---- Modix.Web/Shared/MainLayout.razor | 92 ++++++++++++++++++++-------- Modix.Web/Shared/MainLayout.razor.cs | 29 +++++++++ 3 files changed, 118 insertions(+), 38 deletions(-) create mode 100644 Modix.Web/Shared/MainLayout.razor.cs diff --git a/Modix.Web/App.razor b/Modix.Web/App.razor index 6fd3ed1b5..211bbcbcd 100644 --- a/Modix.Web/App.razor +++ b/Modix.Web/App.razor @@ -1,12 +1,23 @@ - - - - - - - Not found - -

Sorry, there's nothing at this address.

-
-
-
+@using MudBlazor + + + + + + + Sorry, you don't have access to that page. + + + Please wait... + + + + + + Not found + + Sorry, there's nothing at this address. + + + + \ No newline at end of file diff --git a/Modix.Web/Shared/MainLayout.razor b/Modix.Web/Shared/MainLayout.razor index f80db34b1..875130c44 100644 --- a/Modix.Web/Shared/MainLayout.razor +++ b/Modix.Web/Shared/MainLayout.razor @@ -1,35 +1,75 @@ -@inherits LayoutComponentBase +@using Modix.Data.Models.Core; +@using MudBlazor +@inherits LayoutComponentBase + + + + Modix.Web - -
- - - - - - +
- - -
- Logout -
-
- -
- Login -
-
-
+ + + + +
+ Home + Stats + Commands + User Lookup + Tags + + Promotions + + + Logs + +
+ + +
+ @if (AvatarUrl is not null && Username is not null) + { + + @Username + } + +
+
+ +
+
+ Home + Commands +
+
+ +
+
+
+
+
-
- @Body -
+ + @Body + +
+ +@code { + private MudTheme _theme = new() + { + Typography = new() + { + Default = new() + { + LetterSpacing = "0" + } + } + }; +} diff --git a/Modix.Web/Shared/MainLayout.razor.cs b/Modix.Web/Shared/MainLayout.razor.cs new file mode 100644 index 000000000..72ff55361 --- /dev/null +++ b/Modix.Web/Shared/MainLayout.razor.cs @@ -0,0 +1,29 @@ +using System.Security.Claims; +using AspNet.Security.OAuth.Discord; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Authorization; + +namespace Modix.Web.Shared; + +public partial class MainLayout : LayoutComponentBase +{ + //TODO: Move all of this into NavBar? + [Inject] + public AuthenticationStateProvider AuthenticationStateProvider { get; set; } = null!; + + public string? AvatarUrl { get; private set; } + public string? Username { get; private set; } + + protected override async Task OnInitializedAsync() + { + var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); + if (!authState.User.Identity?.IsAuthenticated ?? false) + return; + + var avatarHash = authState.User.Claims.FirstOrDefault(x => x.Type == DiscordAuthenticationConstants.Claims.AvatarHash)?.Value; + var userId = authState.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value; + + AvatarUrl = $"https://cdn.discordapp.com/avatars/{userId}/{avatarHash}.png"; + Username = authState.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Name)?.Value; + } +} From 5651a4976faccc59cefce7878f4d961d2934c4ff Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 29 Jul 2023 22:59:35 +0200 Subject: [PATCH 008/117] Initial implementation of UserLookup page --- Modix.Web/Components/UserLookupField.razor | 43 ++++ .../Components/UserSearchAutoComplete.razor | 37 ++++ Modix.Web/Pages/UserLookup.razor | 207 +++++++++++++++++- 3 files changed, 281 insertions(+), 6 deletions(-) create mode 100644 Modix.Web/Components/UserLookupField.razor create mode 100644 Modix.Web/Components/UserSearchAutoComplete.razor diff --git a/Modix.Web/Components/UserLookupField.razor b/Modix.Web/Components/UserLookupField.razor new file mode 100644 index 000000000..654b95513 --- /dev/null +++ b/Modix.Web/Components/UserLookupField.razor @@ -0,0 +1,43 @@ +@using MudBlazor +@typeparam T + +
+ @Label + @if(ChildContent is not null) + { + @ChildContent + } + else if(Value is not null) + { + @Value + } + else + { + @Default + } +
+ + + +@code { + + [Parameter] + public string? Label { get; set; } + + [Parameter] + public T? Value { get; set; } + + [Parameter] + public T? Default { get; set; } + + [Parameter] + public RenderFragment? ChildContent { get; set; } +} \ No newline at end of file diff --git a/Modix.Web/Components/UserSearchAutoComplete.razor b/Modix.Web/Components/UserSearchAutoComplete.razor new file mode 100644 index 000000000..a0a6af3df --- /dev/null +++ b/Modix.Web/Components/UserSearchAutoComplete.razor @@ -0,0 +1,37 @@ +@using Modix.Web.Models; +@using Modix.Web.Services; +@using MudBlazor + +Tell us their username + + + + @user.Name + + + + +@code { + [Inject] + public DiscordUserService DiscordUserService { get; set; } = null!; + + [Parameter] + public EventCallback SelectedUserChanged { get; set; } + + + private async Task> Search(string user) + { + return await DiscordUserService.AutoCompleteAsync(user); + } +} diff --git a/Modix.Web/Pages/UserLookup.razor b/Modix.Web/Pages/UserLookup.razor index 496f2da1e..a5ca3f1f9 100644 --- a/Modix.Web/Pages/UserLookup.razor +++ b/Modix.Web/Pages/UserLookup.razor @@ -1,23 +1,218 @@ @page "/userlookup" +@using Discord; +@using Modix.Data.Repositories; +@using Modix.Services.Core; +@using Modix.Services.Utilities; +@using Modix.Web.Components +@using Modix.Web.Models; +@using Modix.Web.Services +@using MudBlazor +@using Discord.WebSocket +@using System.Linq.Expressions; +@using MudBlazor.Charts +@using System.Globalization; +@using Humanizer; UserLookup -

User Lookup

-

Current count: @currentCount

- + + + User Lookup@(userInformation is null ? null : $" - {userInformation.Username + (userInformation.Discriminator == "0000" ? "" : "#" + userInformation.Discriminator)}") + + + + + @if (userInformation is not null) + { + User Information +
+ + + + + +
+ +
+
+ + Guild Participation + + + + + + + + + Member Information + + + + + + + +
+ @if(!userInformation.Roles.Any()) + { + No roles assigned + } + else + { + @foreach (var role in userInformation.Roles) + { + var roleName = $"@{role.Name}"; + var roleColorStyle = $"border: 1px solid {role.Color}"; + + @* @roleName *@ + @roleName + } + } +
+
+
+ + Messages by Channel + + + + @foreach (var channel in userInformation.MessageCountsPerChannel) + { + var channelColorStyle = $"border: 1px solid {channel.Color}"; + + @($"{channel.ChannelName} ({channel.Count})") + + } + + + + + } + +
@code { - private int currentCount = 0; + List messageCountsPerChannelView = null; + UserInformation? userInformation = null; + + [Inject] + public DiscordUserService DiscordUserService { get; set; } = null!; + + [Inject] + public IUserService UserService { get; set; } = null!; + + [Inject] + public IMessageRepository MessageRepository { get; set; } = null!; - private void IncrementCount() + private void SelectedChannelsChanged(MudChip[] chips) { - currentCount++; + var channels = chips.Select(x => x.Value).Cast(); + messageCountsPerChannelView = userInformation!.MessageCountsPerChannel + .Where(x => channels.Contains(x.ChannelName)) + .ToList(); } + + private async Task SelectedUserChanged(ModixUser user) + { + var currentGuild = DiscordUserService.GetUserGuild(); + + // if (!ulong.TryParse(userIdString, out var userId)) + // return Ok(null); + + var ephemeralUser = await UserService.GetUserInformationAsync(currentGuild.Id, user.UserId); + + // if (userInformation is null) + // return Ok(null); + + var userRank = await MessageRepository.GetGuildUserParticipationStatistics(currentGuild.Id, user.UserId); + var messages7 = await MessageRepository.GetGuildUserMessageCountByDate(currentGuild.Id, user.UserId, TimeSpan.FromDays(7)); + var messages30 = await MessageRepository.GetGuildUserMessageCountByDate(currentGuild.Id, user.UserId, TimeSpan.FromDays(30)); + + var roles = ephemeralUser.RoleIds + .Select(x => currentGuild.GetRole(x)) + .OrderByDescending(x => x.IsHoisted) + .ThenByDescending(x => x.Position) + .ToArray(); + + var timespan = DateTimeOffset.UtcNow - DateTimeOffset.MinValue; + var result = await MessageRepository.GetGuildUserMessageCountByChannel(currentGuild.Id, user.UserId, timespan); + var colors = ColorUtils.GetRainbowColors(result.Count); + + var mapped = result + .Select((x, i) => new MessageCountPerChannelInformation(x.ChannelName, x.MessageCount, colors[i++].ToString())) + .OrderByDescending(x => x.Count) + .ToList(); + + userInformation = new UserInformation( + ephemeralUser.Id.ToString(), + ephemeralUser.Username, + ephemeralUser.Nickname, + ephemeralUser.Discriminator, + ephemeralUser.AvatarId != null ? ephemeralUser.GetAvatarUrl(ImageFormat.Auto, 256) : ephemeralUser.GetDefaultAvatarUrl(), + ephemeralUser.CreatedAt, + ephemeralUser.JoinedAt, + ephemeralUser.FirstSeen, + ephemeralUser.LastSeen, + userRank.Rank, + messages7.Sum(x => x.MessageCount), + messages30.Sum(x => x.MessageCount), + userRank.AveragePerDay, + userRank.Percentile, + roles + .Where(x => !x.IsEveryone) + .Select(x => new RoleInformation(x.Id, x.Name, x.Color.ToString())), + ephemeralUser.IsBanned, + ephemeralUser.BanReason, + ephemeralUser.GuildId != default, + mapped + ); + + messageCountsPerChannelView = userInformation.MessageCountsPerChannel.ToList(); + + } + + public record RoleInformation(ulong Id, string Name, string Color); + + public record MessageCountPerChannelInformation(string ChannelName, double Count, string Color); + + public record UserInformation( + string Id, + string? Username, + string? Nickname, + string? Discriminator, + string? AvatarUrl, + DateTimeOffset CreatedAt, + DateTimeOffset? JoinedAt, + DateTimeOffset? FirstSeen, + DateTimeOffset? LastSeen, + int Rank, + int Last7DaysMessages, + int Last30DaysMessages, + decimal AverageMessagesPerDay, + int Percentile, + IEnumerable Roles, + bool IsBanned, + string? BanReason, + bool IsGuildMember, + IReadOnlyList MessageCountsPerChannel + ); } From 39f3da4874a8a960672a8b0b7275265a0c6ca025 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 29 Jul 2023 23:00:33 +0200 Subject: [PATCH 009/117] Initial implementation of Commands page --- Modix.Web/Pages/Commands.razor | 173 ++++++++++++++++++++++++++++++--- 1 file changed, 160 insertions(+), 13 deletions(-) diff --git a/Modix.Web/Pages/Commands.razor b/Modix.Web/Pages/Commands.razor index 74cf154f7..7c3a6eb8f 100644 --- a/Modix.Web/Pages/Commands.razor +++ b/Modix.Web/Pages/Commands.razor @@ -1,24 +1,171 @@ @page "/commands" +@using Modix.Services.CommandHelp; +@using Modix.Services.Utilities; +@using MudBlazor; - - - - Commands +Commands -

Commands

+ + + + @foreach (var module in Modules.OrderBy(m => m.Name)) + { + + @module.Name + + } + + -

Current count: @currentCount

+
+ Commands + @foreach (var module in Modules.OrderBy(m => m.Name)) + { + + @module.Name + + @module.Summary + + foreach (var command in module.Commands) + { + + + @foreach (var alias in command.Aliases) + { +
+ @(command.IsSlashCommand ? '/' : '!')@alias.ToLower() + @if (alias == command.Aliases.First()) + { + @command.Summary + @foreach (var parameter in command.Parameters) + { + @*TODO: Add pointer styling?*@ + +
+ @parameter.Name + + @if (parameter.Summary is not null || parameter.Options.Count > 0) + { + var description = $"{parameter.Summary} {string.Join(", ", parameter.Options)}"; + + … + + } + + @parameter.Type + + @if (parameter.IsOptional) + { + + + ? + + } +
+ } + } +
+ } +
+
+ + } + } +
+
- -
-
-
+ @code { - private int currentCount = 0; - private void IncrementCount() + record Command(string Name, string Summary, IReadOnlyCollection Aliases, IReadOnlyCollection Parameters, bool IsSlashCommand); + + record Module(string Name, string Summary, IEnumerable Commands); + + [Inject] + ICommandHelpService CommandHelpService { get; set; } = null!; + + IReadOnlyCollection Modules = null!; + + protected override void OnInitialized() { - currentCount++; + var modules = CommandHelpService.GetModuleHelpData(); + + Modules = modules.Select(m => + { + var commands = m.Commands.Select(c => new Command(c.Name, c.Summary, FormatUtilities.CollapsePlurals(c.Aliases), c.Parameters, c.IsSlashCommand)); + return new Module(m.Name, m.Summary, commands); + }).ToArray(); } } From 1c350c42d8c4e91305c8f160f688e4611a8f1fc8 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 29 Jul 2023 23:03:40 +0200 Subject: [PATCH 010/117] Initial implementation of Stats page --- Modix.Web/Pages/Stats.razor | 119 +++++++++++++++++++++++++++++++++--- 1 file changed, 112 insertions(+), 7 deletions(-) diff --git a/Modix.Web/Pages/Stats.razor b/Modix.Web/Pages/Stats.razor index 6630f932c..1798758f4 100644 --- a/Modix.Web/Pages/Stats.razor +++ b/Modix.Web/Pages/Stats.razor @@ -1,23 +1,128 @@ @page "/stats" +@using Modix.Data.Models.Core; +@using Modix.Services.GuildStats; +@using Modix.Web.Services; +@using MudBlazor - Stats -

Statistics for C#

-

Current count: @currentCount

- + @if(Data is not null) + { + + Statistics for C# + + + + + Role Distribution + + + + + + @foreach (var role in Data.GuildRoleCounts) + { + var channelColorStyle = $"border: 1px solid {role.Color}"; + + @($"{role.Name} ({role.Count})") + + } + + + + + + + + + + + Most Active Users + of the last 30 days + + + @foreach (var stat in Data.TopUserMessageCounts) + { + var rankSymbol = stat.Rank switch + { + 1 => "🥇", + 2 => "🥈", + 3 => "🥉", + _ => null + }; + + var username = stat.Username; + username += stat.Discriminator == "0000" ? string.Empty : $"#{stat.Discriminator}"; + + + @($"{rankSymbol ?? $"{stat.Rank}."} {username}") + + @($"{stat.MessageCount} messages") + + } + + + + + + + + }
+ + @code { - private int currentCount = 0; - private void IncrementCount() + public record GuildStatData(string GuildName, List GuildRoleCounts, IReadOnlyCollection TopUserMessageCounts); + + GuildStatData Data { get; set;} = null!; + List GuildRoleCountView { get; set; } = null!; + + [Inject] + IGuildStatService GuildStatService { get; set; } = null!; + + [Inject] + DiscordUserService DiscordUserService { get; set; } = null!; + + protected override async Task OnInitializedAsync() + { + var userGuild = DiscordUserService.GetUserGuild(); + var currentUser = await DiscordUserService.GetCurrentUserAsync(); + + if (currentUser is null) + throw new InvalidOperationException("Could not find currently logged in user."); + + var roleCounts = await GuildStatService.GetGuildMemberDistributionAsync(userGuild); + var messageCounts = await GuildStatService.GetTopMessageCounts(userGuild, currentUser.Id); + + Data = new GuildStatData(userGuild.Name, roleCounts, messageCounts); + GuildRoleCountView = roleCounts; + } + + private void SelectedChannelsChanged(MudChip[] chips) { - currentCount++; + var roles = chips.Select(x => x.Value).Cast(); + GuildRoleCountView = Data.GuildRoleCounts.Where(x => roles.Contains(x.Name)).ToList(); } } From 16d7f8c8d37150e51e46df9af5483e86b9966675 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 29 Jul 2023 23:05:47 +0200 Subject: [PATCH 011/117] Initial implementation of Tags page --- Modix.Web/Pages/Tags.razor | 164 +++++++++++++++++++++++++++++++++++-- 1 file changed, 158 insertions(+), 6 deletions(-) diff --git a/Modix.Web/Pages/Tags.razor b/Modix.Web/Pages/Tags.razor index ae7120e22..ee4ebded9 100644 --- a/Modix.Web/Pages/Tags.razor +++ b/Modix.Web/Pages/Tags.razor @@ -1,23 +1,175 @@ @page "/tags" +@using Modix.Data.Models.Core; +@using Modix.Data.Models.Tags; +@using Modix.Services.Tags; +@using Modix.Web.Services; +@using MudBlazor +@using System.Globalization; +@using Modix.Data.Models.Core; Tags -

Tags

-

Current count: @currentCount

- + + Tags + @if (Data is not null) + { + + + Create Tag + + + + + Preview + + + +
+ Save +
+ Cancel +
+
+ + + + + Create + + Refresh + + + + + Name + Last Modified + Owner + Content + Uses + + + @tag.Name + @tag.Created.ToString("dd/MM/yy, h:MM:ss tt") + @tag.OwnerName + + + + @tag.Uses + + + + + + } +
@code { - private int currentCount = 0; - private void IncrementCount() + public record TagData( + string Name, + DateTimeOffset Created, + bool IsOwnedByRole, + GuildUserBrief? OwnerUser, + GuildRoleBrief? OwnerRole, + string? OwnerName, + string Content, + uint Uses, + bool CanMaintain, + TagSummary TagSummary); + + + [Inject] + ITagService TagService { get; set; } = null!; + + [Inject] + DiscordUserService DiscordUserService { get; set; } = null!; + + [Inject] + IDialogService DialogService { get; set; } = null!; + + TagData[]? Data { get; set; } + string? query; + string? _tagNameValue; + string? _tagContentValue; + bool _createDialogVisible; + + protected override async Task OnInitializedAsync() => await FetchData(); + + private async Task FetchData() + { + var currentGuild = DiscordUserService.GetUserGuild(); + + var summaries = await TagService.GetSummariesAsync(new TagSearchCriteria + { + GuildId = currentGuild.Id, + }); + + Data = summaries + .Select(x => CreateTagData(x)) + .ToArray(); + + // foreach (var tag in data) + // { + // // TODO Revisit this functionality + // tag.CanMaintain = false; + // } + } + + private bool FilterFunction(TagData tag) + { + if (string.IsNullOrWhiteSpace(query)) + return true; + + if (tag.OwnerUser is not null && (tag.OwnerUser.Username.Contains(query) || tag.OwnerUser.Id.ToString() == query)) + return true; + + if (tag.OwnerRole?.Name.Contains(query) ?? false) + return true; + + if (tag.Name.Contains(query)) + return true; + + if (tag.Content.Contains(query)) + return true; + + return false; + } + + private async Task SaveTag() + { + var currentGuild = DiscordUserService.GetUserGuild(); + var currentUser = await DiscordUserService.GetCurrentUserAsync(); + await TagService.CreateTagAsync(currentGuild.Id, currentUser.Id, _tagNameValue, _tagContentValue); + var createdTag = await TagService.GetTagAsync(currentGuild.Id, _tagNameValue); + Data = Data!.Append(CreateTagData(createdTag)).ToArray(); + + _createDialogVisible = false; + } + + private TagData CreateTagData(TagSummary summary) + { + return new TagData( + summary.Name, + summary.CreateAction.Created, + summary.OwnerRole is not null, + summary.OwnerUser, + summary.OwnerRole, + summary.OwnerRole?.Name ?? summary.OwnerUser?.Username, + summary.Content, + summary.Uses, + false, + summary); + } + + private void ToggleDialog() { - currentCount++; + _createDialogVisible = !_createDialogVisible; } } From b02cb4e2becfb9dd165c1dee2badeff829077eaa Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 29 Jul 2023 23:06:17 +0200 Subject: [PATCH 012/117] Initial implementation of Promotions page --- .../EditPromotionCommentDialog.razor | 43 ++++ Modix.Web/Pages/CreatePromotion.razor | 131 +++++++++++ Modix.Web/Pages/Promotions.razor | 214 +++++++++++++++++- 3 files changed, 377 insertions(+), 11 deletions(-) create mode 100644 Modix.Web/Components/EditPromotionCommentDialog.razor create mode 100644 Modix.Web/Pages/CreatePromotion.razor diff --git a/Modix.Web/Components/EditPromotionCommentDialog.razor b/Modix.Web/Components/EditPromotionCommentDialog.razor new file mode 100644 index 000000000..2b5c1638d --- /dev/null +++ b/Modix.Web/Components/EditPromotionCommentDialog.razor @@ -0,0 +1,43 @@ +@using Modix.Data.Models.Promotions; +@using MudBlazor + + + + Edit Comment + + +
+ + + + + + + + + +
+
+ +
+ Update +
+ Cancel +
+
+ + + +@code { + [CascadingParameter] + MudDialogInstance MudDialog { get; set; } + + [Parameter] + public PromotionSentiment PromotionSentiment { get; set; } + + [Parameter] + public required string Content { get; set; } + + void Submit() => MudDialog.Close(DialogResult.Ok((PromotionSentiment, Content))); + void Cancel() => MudDialog.Cancel(); +} diff --git a/Modix.Web/Pages/CreatePromotion.razor b/Modix.Web/Pages/CreatePromotion.razor new file mode 100644 index 000000000..fdc98ab10 --- /dev/null +++ b/Modix.Web/Pages/CreatePromotion.razor @@ -0,0 +1,131 @@ +@page "/promotions/create" +@using Modix.Data.Models.Core; +@using Modix.Services.Promotions; +@using Modix.Web.Components +@using Modix.Web.Models; +@using Modix.Web.Services; +@using MudBlazor + +@attribute [Authorize(Policy = nameof(AuthorizationClaim.PromotionsCreateCampaign))] + + + + + + Start a Campaign + + + +

Feel like someone deserves recognition? Start a promotion campaign for them - even if that person is yourself!

+
+ +

Once a campaign is started, users can anonymously comment, voicing their opinions for or against the individual up for promotion

+
+ +

Staff will periodically review campaigns. If approved, the user will be immediately promoted! If not, they may be permanently denied, or further looked into as the campaign runs its course.

+
+
+ + + + + @if(_selectedUser is not null && _nextRank is not null) + { +
+ @_selectedUser.Name can be promoted to this rank + @_nextRank.Name +
+ +
+ Finally, say a few words on their behalf + + + +
+ + + Submit + + } + +
+ +
+
+ +
+
+
+ + + + +@code { + public record NextRank(string? Name, string Color); + + [Inject] + public IPromotionsService PromotionsService { get; set; } = null!; + + [Inject] + public DiscordUserService DiscordUserService { get; set; } = null!; + + [Inject] + public ISnackbar Snackbar { get; set; } = null!; + + [Inject] + public NavigationManager NavigationManager { get; set; } = null!; + + private ModixUser? _selectedUser; + private string? _promotionComment; + + private NextRank? _nextRank; + + private async Task SelectedUserChanged(ModixUser user) + { + if(user != _selectedUser) + { + _nextRank = null; + _promotionComment = null; + } + + _selectedUser = user; + if (user is null) + return; + + var nextRank = await PromotionsService.GetNextRankRoleForUserAsync(user.UserId); + var currentGuild = DiscordUserService.GetUserGuild(); + + _nextRank = new NextRank(nextRank?.Name, currentGuild.Roles.First(x => x.Id == nextRank?.Id).Color.ToString()); + } + + private async Task CreateCampaign() + { + try + { + await PromotionsService.CreateCampaignAsync(_selectedUser!.UserId, _promotionComment); + } + catch (InvalidOperationException ex) + { + Snackbar.Configuration.PositionClass = Defaults.Classes.Position.BottomCenter; + Snackbar.Add(ex.Message, Severity.Error); + return; + } + + NavigationManager.NavigateTo("/promotions"); + } +} diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index b436a0407..7761badd4 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -1,23 +1,215 @@ @page "/promotions" +@using Modix.Data.Models.Core; +@using Modix.Data.Models.Promotions; +@using Modix.Services.Promotions; +@using Modix.Web.Components; +@using Modix.Web.Services; +@using MudBlazor + +@attribute [Authorize(Policy = nameof(AuthorizationClaim.PromotionsRead))] - - - Promotions -

Promotion Campaigns

-

Current count: @currentCount

- + Promotions + + + Promotion Campaigns +
+ + Start One +
+ + @foreach(var campaign in Campaigns.Where(x => _showInactive ? true : (x.Outcome is null)).OrderByDescending(x => x.Outcome is null).ThenByDescending(x => x.CreateAction.Created)) + { + var isCurrentUserCampaign = CurrentUserId == campaign.Subject.Id; + + var icon = campaign.Outcome switch + { + PromotionCampaignOutcome.Accepted => Icons.Material.Filled.Check, + PromotionCampaignOutcome.Rejected => Icons.Material.Filled.NotInterested, + PromotionCampaignOutcome.Failed => Icons.Material.Filled.Error, + _ => Icons.Material.Filled.HowToVote + }; + + var sentimentRatio = isCurrentUserCampaign ? 0d : (double)campaign.ApproveCount / (campaign.ApproveCount + campaign.OpposeCount); + var sentimentColor = sentimentRatio switch + { + _ when isCurrentUserCampaign => Color.Transparent, + _ when sentimentRatio > 0.67 => Color.Success, + _ when sentimentRatio > 0.33 => Color.Warning, + _ => Color.Error + }; + + + +
+
+ + + + + @($"{campaign.Subject.Username + (campaign.Subject.Discriminator == "0000" ? "" : "#" + campaign.Subject.Discriminator)}") + + ➥ @campaign.TargetRole.Name +
+
+
+
+ + @(isCurrentUserCampaign ? "?" : campaign.ApproveCount.ToString()) +
+
+ + @(isCurrentUserCampaign ? "?" : campaign.OpposeCount.ToString()) +
+
+ +
+
+
+ + Campaign started @campaign.CreateAction.Created.ToString("dd/MM/yy, h:MM:ss tt") + + @if(campaign.Subject.Id == CurrentUserId) + { + Sorry, you aren't allowed to see comments on your own campaign. + } + else if(!campaignCommentData.ContainsKey(campaign.Id)) + { + + } + else + { + foreach(var comment in campaignCommentData[campaign.Id]) + { + var sentimentIcon = comment.PromotionSentiment == PromotionSentiment.Approve ? Icons.Material.Filled.ThumbUp : Icons.Material.Filled.ThumbDown; +
+ + @comment.Content + @if (comment.IsFromCurrentUser) + { + Edit + } + @comment.CreatedAt.ToString("dd/MM/yy, h:MM:ss tt") +
+ + } + } +
+
+ } +
+
-
-
+ + @code { - private int currentCount = 0; - private void IncrementCount() + public record CampaignCommentData(long Id, PromotionSentiment PromotionSentiment, string Content, DateTimeOffset CreatedAt, bool IsFromCurrentUser); + + + [Inject] + public DiscordUserService DiscordUserService { get; set; } = null!; + + [Inject] + public IPromotionsService PromotionsService { get; set; } = null!; + + [Inject] + public IDialogService DialogService { get; set; } = null!; + + private ulong CurrentUserId { get; set; } + + private IReadOnlyCollection Campaigns = Array.Empty(); + private Dictionary RoleColors = new Dictionary(); + private Dictionary> campaignCommentData = new Dictionary>(); + + private bool _showInactive; + + protected override async Task OnInitializedAsync() + { + var currentGuild = DiscordUserService.GetUserGuild(); + RoleColors = currentGuild.Roles.ToDictionary(x => x.Id, x => x.Color.ToString()); + + Campaigns = await PromotionsService.SearchCampaignsAsync(new PromotionCampaignSearchCriteria + { + GuildId = currentGuild.Id + }); + + var currentUser = await DiscordUserService.GetCurrentUserAsync(); + CurrentUserId = currentUser!.Id; + } + + protected override void OnAfterRender(bool firstRender) { - currentCount++; + base.OnAfterRender(firstRender); + } + + private async Task CampaignExpanded(bool wasExpanded, long campaignId, ulong userId) + { + if (!wasExpanded) + return; + + if (CurrentUserId == userId) + return; + + if (campaignCommentData.ContainsKey(campaignId)) + return; + + var result = await PromotionsService.GetCampaignDetailsAsync(campaignId); + + if (result is null) // TODO: What to do here? + return; + + //TODO: Fix IsFromCurrentUser + campaignCommentData[campaignId] = result.Comments + .Where(x => x.ModifyAction is null) + .Select(c => new CampaignCommentData(c.Id, c.Sentiment, c.Content, c.CreateAction.Created, c.CreateAction.CreatedBy.Id == CurrentUserId)) + .ToList(); + + StateHasChanged(); + + + // { + // c.Id, + // c.Sentiment, + // c.Content, + // CreateAction = new { c.CreateAction.Id, c.CreateAction.Created }, + // IsModified = !(c.ModifyAction is null), + // IsFromCurrentUser = c.CreateAction.CreatedBy.Id == ModixAuth.CurrentUserId, + // })); + } + + private async Task ToggleEditDialog(long commentId, PromotionSentiment oldPromotionSentiment, string oldContent) + { + var dialogParams = new DialogParameters + { + { nameof(EditPromotionCommentDialog.PromotionSentiment), oldPromotionSentiment }, + { nameof(EditPromotionCommentDialog.Content), oldContent} + }; + + var dialog = DialogService.Show("", dialogParams); + var result = await dialog.Result; + + if (result.Canceled) + return; + + var (newPromotionSentiment, newContent) = ((PromotionSentiment, string))result.Data; + + await PromotionsService.UpdateCommentAsync(commentId, newPromotionSentiment, newContent); } } From 8ff568ff76d2bc5a00651d93a19812595457e40e Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Mon, 31 Jul 2023 00:53:08 +0200 Subject: [PATCH 013/117] Run AuthorizationService.OnAuthenticatedAsync in MainLayout instead ClaimsTransformation uses a different service scope from the blazor circuit - which leads to AuthorizationService properties not being set properly for the authenticated user Also remove redundant authorization setup in startup --- Modix.Web/Program.cs | 5 +---- .../Services/ClaimsTransformationService.cs | 1 - Modix.Web/Shared/MainLayout.razor.cs | 16 ++++++++++++++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Modix.Web/Program.cs b/Modix.Web/Program.cs index 69b5cbeef..6868c54b4 100644 --- a/Modix.Web/Program.cs +++ b/Modix.Web/Program.cs @@ -157,10 +157,7 @@ private static void ConfigureServices(WebApplicationBuilder builder, IConfigurat builder.Services.AddScoped(); builder.Services.AddMudServices(); builder.Services.AddMudMarkdownServices(); - builder.Services.AddRazorPages(config => - { - config.Conventions.AuthorizePage("/Promotions", nameof(AuthorizationClaim.PromotionsRead)); - }); + builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); builder.Services.AddServices(Assembly.GetExecutingAssembly(), configuration); diff --git a/Modix.Web/Services/ClaimsTransformationService.cs b/Modix.Web/Services/ClaimsTransformationService.cs index 63b444650..f2eb560aa 100644 --- a/Modix.Web/Services/ClaimsTransformationService.cs +++ b/Modix.Web/Services/ClaimsTransformationService.cs @@ -31,7 +31,6 @@ public async Task TransformAsync(ClaimsPrincipal principal) // TODO: Get selected guild from cookie var currentGuild = _discordUserService.GetUserGuild(); var currentUser = currentGuild.GetUser(userSnowflake); - await _authorizationService.OnAuthenticatedAsync(currentUser.Id, currentGuild.Id, currentUser.Roles.Select(x => x.Id).ToList()); var claims = (await _authorizationService.GetGuildUserClaimsAsync(currentUser)) .Select(d => new Claim(ClaimTypes.Role, d.ToString())); diff --git a/Modix.Web/Shared/MainLayout.razor.cs b/Modix.Web/Shared/MainLayout.razor.cs index 72ff55361..ebc48bfd8 100644 --- a/Modix.Web/Shared/MainLayout.razor.cs +++ b/Modix.Web/Shared/MainLayout.razor.cs @@ -2,6 +2,8 @@ using AspNet.Security.OAuth.Discord; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; +using Modix.Services.Core; +using Modix.Web.Services; namespace Modix.Web.Shared; @@ -11,6 +13,12 @@ public partial class MainLayout : LayoutComponentBase [Inject] public AuthenticationStateProvider AuthenticationStateProvider { get; set; } = null!; + [Inject] + public IAuthorizationService AuthorizationService { get; set; } = null!; + + [Inject] + public DiscordUserService DiscordUserService { get; set; } = null!; + public string? AvatarUrl { get; private set; } public string? Username { get; private set; } @@ -25,5 +33,13 @@ protected override async Task OnInitializedAsync() AvatarUrl = $"https://cdn.discordapp.com/avatars/{userId}/{avatarHash}.png"; Username = authState.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Name)?.Value; + + if (!ulong.TryParse(userId, out var userSnowflake)) + return; + + var currentGuild = DiscordUserService.GetUserGuild(); + var currentUser = currentGuild.GetUser(userSnowflake); + + await AuthorizationService.OnAuthenticatedAsync(currentUser.Id, currentGuild.Id, currentUser.Roles.Select(x => x.Id).ToList()); } } From a26a118f945367847fcd149a8e73c4edb1fea6f1 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Mon, 31 Jul 2023 00:56:08 +0200 Subject: [PATCH 014/117] Initial implementation of Logs/Infractions page --- Modix.Web/Components/ConfirmationDialog.razor | 30 ++ Modix.Web/Components/DeletedMessages.razor | 5 + Modix.Web/Components/Infractions.razor | 447 ++++++++++++++++++ .../Components/UserSearchAutoComplete.razor | 4 +- Modix.Web/Pages/Logs.razor | 49 +- 5 files changed, 518 insertions(+), 17 deletions(-) create mode 100644 Modix.Web/Components/ConfirmationDialog.razor create mode 100644 Modix.Web/Components/DeletedMessages.razor create mode 100644 Modix.Web/Components/Infractions.razor diff --git a/Modix.Web/Components/ConfirmationDialog.razor b/Modix.Web/Components/ConfirmationDialog.razor new file mode 100644 index 000000000..514af5323 --- /dev/null +++ b/Modix.Web/Components/ConfirmationDialog.razor @@ -0,0 +1,30 @@ +@using Modix.Data.Models.Promotions; +@using MudBlazor + + + + Confirmation + + + @Content + + +
+ Confirm +
+ Cancel +
+
+ + + +@code { + [CascadingParameter] + MudDialogInstance MudDialog { get; set; } + + [Parameter] + public required string Content { get; set; } + + void Submit() => MudDialog.Close(); + void Cancel() => MudDialog.Cancel(); +} diff --git a/Modix.Web/Components/DeletedMessages.razor b/Modix.Web/Components/DeletedMessages.razor new file mode 100644 index 000000000..3e7fea3df --- /dev/null +++ b/Modix.Web/Components/DeletedMessages.razor @@ -0,0 +1,5 @@ +

DeletedMessages

+ +@code { + +} diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor new file mode 100644 index 000000000..05b02784e --- /dev/null +++ b/Modix.Web/Components/Infractions.razor @@ -0,0 +1,447 @@ +@using Modix.Data.Models; +@using Modix.Data.Models.Core; +@using Modix.Data.Models.Moderation; +@using Modix.Services.Moderation; +@using Modix.Web.Models; +@using Modix.Web.Services; +@using MudBlazor; +@using System.Security.Claims; + + + + + + Create Infraction + + + + Infraction +
+
+ + + + + + + + + + + + + + +
+ +
+ + @if(_infractionType == InfractionType.Mute) + { + Duration +
+ + + + + +
+ } +
+ +
+ Save +
+ Cancel +
+
+ + + + +
+
+ Create + Refresh +
+
+ + +
+
+
+ + + Id + + + + Type + + + Created On + + Subject + + + + Creator + + + Reason + @if(_showState) + { + State + } + @if (_canDeleteInfractions || _canRescind) + { + Actions + } + + + @infraction.Id + @infraction.Type + @infraction.CreateAction.Created.ToString("dd/MM/yy, h:MM:ss tt") + @(GetUsername(infraction.Subject)) + @(GetUsername(infraction.CreateAction.CreatedBy)) + @infraction.Reason + @if (_showState) + { + @(infraction.RescindAction != null ? "Rescinded" : infraction.DeleteAction != null ? "Deleted" : "Active") + } + @if(_canDeleteInfractions || _canRescind) + { + +
+ @if (infraction.CanBeDeleted) + { + Delete + } + @if (infraction.CanBeRescind) + { + Rescind + } +
+
+ } +
+ + + +
+
+
+@code { + + [Inject] + public IModerationService ModerationService { get; set; } = null!; + + [Inject] + public DiscordUserService DiscordUserService { get; set; } = null!; + + [Inject] + public ISnackbar Snackbar { get; set; } = null!; + + [CascadingParameter] + private Task? AuthState { get; set; } + + [Inject] + public IDialogService DialogService { get; set; } = null!; + + private MudTable TableRef; + + private bool _showState; + + private HashSet _selectedInfractions; + private bool _canRescind; + private bool _canDeleteInfractions; + + private ModixUser? _selectedUser; + private InfractionType _infractionType = InfractionType.Notice; + private string? _infractionReason; + private bool _createDialogVisible; + private int? _newInfractionMonths; + private int? _newInfractionDays; + private int? _newInfractionHours; + private int? _newInfractionMinutes; + private int? _newInfractionSeconds; + + private class TableFilter + { + public string? Id { get; set; } + public string? Type { get; set; } + public string? Subject { get; set; } + public string? Creator { get; set; } + public bool ShowDeleted { get; set; } + } + TableFilter _tableFilter = new(); + + protected override async Task OnInitializedAsync() + { + if (AuthState is null) + return; + + var auth = await AuthState; + _canRescind = auth.User.HasClaim(ClaimTypes.Role, nameof(AuthorizationClaim.ModerationRescind)); + _canDeleteInfractions = auth.User.HasClaim(ClaimTypes.Role, nameof(AuthorizationClaim.ModerationDeleteInfraction)); + } + + private void ToggleDialog() + { + _createDialogVisible = !_createDialogVisible; + } + + private void SelectedUserChanged(ModixUser user) + { + _selectedUser = user; + } + + private async Task FilterChanged(Action filterSetter) + { + filterSetter(); + await TableRef.ReloadServerData(); + } + + private static string GetUsername(GuildUserBrief userBrief) + { + return $"{userBrief.Username}{(userBrief.Discriminator == "0000" ? "" : "#" + userBrief.Discriminator)}"; + } + + private async Task RescindInfraction(InfractionData infraction) + { + try + { + var dialogParams = new DialogParameters + { + { nameof(ConfirmationDialog.Content), $"Are you sure you want to rescind infraction #{infraction.Id}?"} + }; + + var dialog = DialogService.Show("", dialogParams); + var result = await dialog.Result; + + if (result.Canceled) + { + Snackbar.Add("Action was cancelled", Severity.Info); + return; + } + + await ModerationService.RescindInfractionAsync(infraction.Id); + await Refresh(); + } + catch (Exception ex) + { + Snackbar.Add(ex.Message, Severity.Error); + } + } + + private async Task DeleteInfraction(InfractionData infraction) + { + try + { + var dialogParams = new DialogParameters + { + { nameof(ConfirmationDialog.Content), $"Are you sure you want to delete infraction #{infraction.Id}?"} + }; + + var dialog = DialogService.Show("", dialogParams); + var result = await dialog.Result; + + if (result.Canceled) + { + Snackbar.Add("Action was cancelled", Severity.Info); + return; + } + + await ModerationService.DeleteInfractionAsync(infraction.Id); + await Refresh(); + } + catch (Exception ex) + { + Snackbar.Add(ex.Message, Severity.Error); + } + } + + private async Task SaveInfraction() + { + _createDialogVisible = false; + + var duration = GetTimeSpan( + _newInfractionMonths, + _newInfractionDays, + _newInfractionHours, + _newInfractionMinutes, + _newInfractionSeconds); + + try + { + var currentUser = await DiscordUserService.GetCurrentUserAsync(); + await ModerationService.CreateInfractionAsync(currentUser!.Guild.Id, currentUser.Id, _infractionType, _selectedUser!.UserId, _infractionReason!, duration); + } + catch (InvalidOperationException ex) + { + Snackbar.Add(ex.Message, Severity.Error); + return; + } + + + Snackbar.Add($"Added infraction for user {_selectedUser!.Name}", Severity.Success); + + _selectedUser = null; + _newInfractionMonths = null; + _newInfractionDays = null; + _newInfractionHours = null; + _newInfractionMinutes = null; + _newInfractionSeconds = null; + _infractionReason = null; + + await TableRef.ReloadServerData(); + + TimeSpan? GetTimeSpan(int? months, int? days, int? hours, int? minutes, int? seconds) + { + if (months is null + && days is null + && hours is null + && minutes is null + && seconds is null) + return null; + + var now = DateTimeOffset.UtcNow; + var daysInMonth = DateTime.DaysInMonth(now.Year, now.Month); + + var monthSpan = months is null + ? TimeSpan.Zero + : TimeSpan.FromDays(months.Value * daysInMonth); + + var daySpan = days is null + ? TimeSpan.Zero + : TimeSpan.FromDays(days.Value); + + var hourSpan = hours is null + ? TimeSpan.Zero + : TimeSpan.FromHours(hours.Value); + + var minuteSpan = minutes is null + ? TimeSpan.Zero + : TimeSpan.FromMinutes(minutes.Value); + + var secondSpan = seconds is null + ? TimeSpan.Zero + : TimeSpan.FromSeconds(seconds.Value); + + return monthSpan + daySpan + hourSpan + minuteSpan + secondSpan; + } + } + + private async Task Refresh() => await TableRef.ReloadServerData(); + + private async Task> LoadInfractions(TableState tableState) + { + var currentGuild = DiscordUserService.GetUserGuild(); + var currentUser = await DiscordUserService.GetCurrentUserAsync(); + + var sortingCriteria = new[] + { + new SortingCriteria() + { + PropertyName = tableState.SortLabel ?? nameof(InfractionData.Id), + Direction = tableState.SortDirection == MudBlazor.SortDirection.Ascending + ? Data.Models.SortDirection.Ascending + : Data.Models.SortDirection.Descending, + } + }; + + var Id = long.TryParse(_tableFilter.Id, out var id) ? id : (long?)null; + var Types = Enum.TryParse(_tableFilter.Type, out var parsed) ? new[] { parsed } : null; + var Subject = ulong.TryParse(_tableFilter.Subject, out _) ? null : _tableFilter.Subject; + var SubjectId = ulong.TryParse(_tableFilter.Subject, out var subjectSnowflake) ? subjectSnowflake : (ulong?)null; + + var Creator = ulong.TryParse(_tableFilter.Creator, out _) ? null : _tableFilter.Creator; + var CreatedById = ulong.TryParse(_tableFilter.Creator, out var createdById) ? createdById : (ulong?)null; + + var searchCriteria = new InfractionSearchCriteria + { + GuildId = currentGuild.Id, + Id = Id, + Types = Types, + Subject = Subject, + SubjectId = SubjectId, + Creator = Creator, + CreatedById = CreatedById, + IsDeleted = _tableFilter.ShowDeleted ? null : false + }; + + var pagingCriteria = new PagingCriteria + { + FirstRecordIndex = tableState.Page * tableState.PageSize, + PageSize = tableState.PageSize, + }; + + var result = await ModerationService.SearchInfractionsAsync( + searchCriteria, + sortingCriteria, + pagingCriteria); + + var outranksValues = new Dictionary(); + + foreach (var (guildId, subjectId) in result.Records + .Select(x => (guildId: x.GuildId, subjectId: x.Subject.Id)) + .Distinct()) + { + outranksValues[subjectId] + = await ModerationService.DoesModeratorOutrankUserAsync(guildId, currentUser.Id, subjectId); + } + + var mapped = result.Records.Select( + x => new InfractionData( + x.Id, + x.GuildId, + x.Type, + x.Reason, + x.Duration, + x.Subject, + + x.CreateAction, + x.RescindAction, + x.DeleteAction, + + x.RescindAction is null + && x.DeleteAction is null + && (x.Type == InfractionType.Mute || x.Type == InfractionType.Ban) + && outranksValues[x.Subject.Id], + + x.DeleteAction is null + && outranksValues[x.Subject.Id] + ) + ).ToArray(); + + return new TableData + { + Items = mapped, + TotalItems = mapped.Length + }; + } + + public record InfractionData( + long Id, + ulong GuildId, + InfractionType Type, + string Reason, + TimeSpan? Duration, + GuildUserBrief Subject, + ModerationActionBrief CreateAction, + ModerationActionBrief? RescindAction, + ModerationActionBrief? DeleteAction, + bool CanBeRescind, + bool CanBeDeleted + ); +} diff --git a/Modix.Web/Components/UserSearchAutoComplete.razor b/Modix.Web/Components/UserSearchAutoComplete.razor index a0a6af3df..292819df1 100644 --- a/Modix.Web/Components/UserSearchAutoComplete.razor +++ b/Modix.Web/Components/UserSearchAutoComplete.razor @@ -2,7 +2,7 @@ @using Modix.Web.Services; @using MudBlazor -Tell us their username +@Title SelectedUserChanged { get; set; } + [Parameter] + public string? Title { get; set; } = "Tell us their username"; private async Task> Search(string user) { diff --git a/Modix.Web/Pages/Logs.razor b/Modix.Web/Pages/Logs.razor index b3819b6ae..f94db0e79 100644 --- a/Modix.Web/Pages/Logs.razor +++ b/Modix.Web/Pages/Logs.razor @@ -1,22 +1,39 @@ -@page "/logs" - - - +@page "/logs/{SubPage}" +@page "/logs" +@using Modix.Data.Models.Core; +@using Modix.Web.Components +@using MudBlazor +@attribute [Authorize(Policy = nameof(AuthorizationClaim.ModerationRead))] - Logs -

Logs

-

Current count: @currentCount

- + + Logs + Logs -
-
+ + + + Deletions + Infractions + + +
+ @if(SubPage == "infractions") + { + + + + } + else if(SubPage == "deletedMessages") + { + + + + } +
+
@code { - private int currentCount = 0; - - private void IncrementCount() - { - currentCount++; - } + [Parameter] + public string SubPage { get; set; } = "infractions"; } From e25d0f7a28bd9b9551fc72afa3b32b46f8c80933 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Mon, 31 Jul 2023 01:13:15 +0200 Subject: [PATCH 015/117] Use dropdown for 'Type' in infractions grid --- Modix.Web/Components/Infractions.razor | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor index 05b02784e..ca737a7a1 100644 --- a/Modix.Web/Components/Infractions.razor +++ b/Modix.Web/Components/Infractions.razor @@ -87,7 +87,13 @@ Type - + + @foreach(var infractionType in Enum.GetValues()) + { + + } + + @* *@ Created On @@ -179,7 +185,7 @@ private class TableFilter { public string? Id { get; set; } - public string? Type { get; set; } + public InfractionType? Type { get; set; } public string? Subject { get; set; } public string? Creator { get; set; } public bool ShowDeleted { get; set; } @@ -361,7 +367,7 @@ }; var Id = long.TryParse(_tableFilter.Id, out var id) ? id : (long?)null; - var Types = Enum.TryParse(_tableFilter.Type, out var parsed) ? new[] { parsed } : null; + var Types = _tableFilter.Type is not null ? new[] { _tableFilter.Type.Value } : null; var Subject = ulong.TryParse(_tableFilter.Subject, out _) ? null : _tableFilter.Subject; var SubjectId = ulong.TryParse(_tableFilter.Subject, out var subjectSnowflake) ? subjectSnowflake : (ulong?)null; From 356387288dc1ae92f161fea56ccbece8e4938c7c Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Mon, 31 Jul 2023 01:19:13 +0200 Subject: [PATCH 016/117] Remove unnecessary comments --- Modix.Web/Components/Infractions.razor | 1 - Modix.Web/Pages/Promotions.razor | 11 ----------- 2 files changed, 12 deletions(-) diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor index ca737a7a1..2a0f5b2ad 100644 --- a/Modix.Web/Components/Infractions.razor +++ b/Modix.Web/Components/Infractions.razor @@ -93,7 +93,6 @@ } - @* *@ Created On diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index 7761badd4..3a6d7e837 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -175,23 +175,12 @@ if (result is null) // TODO: What to do here? return; - //TODO: Fix IsFromCurrentUser campaignCommentData[campaignId] = result.Comments .Where(x => x.ModifyAction is null) .Select(c => new CampaignCommentData(c.Id, c.Sentiment, c.Content, c.CreateAction.Created, c.CreateAction.CreatedBy.Id == CurrentUserId)) .ToList(); StateHasChanged(); - - - // { - // c.Id, - // c.Sentiment, - // c.Content, - // CreateAction = new { c.CreateAction.Id, c.CreateAction.Created }, - // IsModified = !(c.ModifyAction is null), - // IsFromCurrentUser = c.CreateAction.CreatedBy.Id == ModixAuth.CurrentUserId, - // })); } private async Task ToggleEditDialog(long commentId, PromotionSentiment oldPromotionSentiment, string oldContent) From 7d28fbb029c6b639581c30dc4d8916cabaaeadbd Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Mon, 31 Jul 2023 01:29:30 +0200 Subject: [PATCH 017/117] Move navbar from MainLayout to separate component --- Modix.Web/Shared/MainLayout.razor | 40 +---------- Modix.Web/Shared/MainLayout.razor.cs | 1 - Modix.Web/Shared/NavMenu.razor | 102 +++++++++++++-------------- 3 files changed, 49 insertions(+), 94 deletions(-) diff --git a/Modix.Web/Shared/MainLayout.razor b/Modix.Web/Shared/MainLayout.razor index 875130c44..21940fe56 100644 --- a/Modix.Web/Shared/MainLayout.razor +++ b/Modix.Web/Shared/MainLayout.razor @@ -12,46 +12,8 @@
- - - -
- Home - Stats - Commands - User Lookup - Tags - - Promotions - - - Logs - -
- -
- @if (AvatarUrl is not null && Username is not null) - { - - @Username - } - -
-
- -
-
- Home - Commands -
-
- -
-
-
-
-
+ @Body diff --git a/Modix.Web/Shared/MainLayout.razor.cs b/Modix.Web/Shared/MainLayout.razor.cs index ebc48bfd8..ad5c87d85 100644 --- a/Modix.Web/Shared/MainLayout.razor.cs +++ b/Modix.Web/Shared/MainLayout.razor.cs @@ -9,7 +9,6 @@ namespace Modix.Web.Shared; public partial class MainLayout : LayoutComponentBase { - //TODO: Move all of this into NavBar? [Inject] public AuthenticationStateProvider AuthenticationStateProvider { get; set; } = null!; diff --git a/Modix.Web/Shared/NavMenu.razor b/Modix.Web/Shared/NavMenu.razor index 12ae0d429..45c29c58d 100644 --- a/Modix.Web/Shared/NavMenu.razor +++ b/Modix.Web/Shared/NavMenu.razor @@ -1,59 +1,53 @@ - +@using Modix.Data.Models.Core; +@using MudBlazor; - + + + + +
+ Home + Stats + Commands + User Lookup + Tags + + Promotions + + + Logs + +
+ -@code { - private bool collapseNavMenu = true; +
+ @if (AvatarUrl is not null && Username is not null) + { + + @Username + } + +
+
+ +
+
+ Home + Commands +
+
+ +
+
+
+
+
+
- private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; +@code { + [Parameter] + public string? AvatarUrl { get; set; } - private void ToggleNavMenu() - { - collapseNavMenu = !collapseNavMenu; - } + [Parameter] + public string? Username { get; set; } } From a6cb9ccdb1001aa705c7cc6377fdd8d5fa992efc Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Mon, 31 Jul 2023 01:42:13 +0200 Subject: [PATCH 018/117] Change page titles --- Modix.Web/Components/DeletedMessages.razor | 4 +++- Modix.Web/Components/Infractions.razor | 2 ++ Modix.Web/Pages/Commands.razor | 2 +- Modix.Web/Pages/CreatePromotion.razor | 2 +- Modix.Web/Pages/Index.razor | 1 - Modix.Web/Pages/Logs.razor | 3 ++- Modix.Web/Pages/Promotions.razor | 4 ++-- Modix.Web/Pages/Stats.razor | 3 ++- Modix.Web/Pages/Tags.razor | 4 ++-- Modix.Web/Pages/UserLookup.razor | 3 ++- Modix.Web/Shared/MainLayout.razor | 1 - 11 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Modix.Web/Components/DeletedMessages.razor b/Modix.Web/Components/DeletedMessages.razor index 3e7fea3df..f2170892a 100644 --- a/Modix.Web/Components/DeletedMessages.razor +++ b/Modix.Web/Components/DeletedMessages.razor @@ -1,4 +1,6 @@ -

DeletedMessages

+Modix - Deletions + +

DeletedMessages

@code { diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor index 2a0f5b2ad..add4537af 100644 --- a/Modix.Web/Components/Infractions.razor +++ b/Modix.Web/Components/Infractions.razor @@ -7,6 +7,8 @@ @using MudBlazor; @using System.Security.Claims; +Modix - Infractions + diff --git a/Modix.Web/Pages/Commands.razor b/Modix.Web/Pages/Commands.razor index 7c3a6eb8f..30342a84d 100644 --- a/Modix.Web/Pages/Commands.razor +++ b/Modix.Web/Pages/Commands.razor @@ -3,7 +3,7 @@ @using Modix.Services.Utilities; @using MudBlazor; -Commands +Modix - Commands diff --git a/Modix.Web/Pages/CreatePromotion.razor b/Modix.Web/Pages/CreatePromotion.razor index fdc98ab10..13318da28 100644 --- a/Modix.Web/Pages/CreatePromotion.razor +++ b/Modix.Web/Pages/CreatePromotion.razor @@ -8,7 +8,7 @@ @attribute [Authorize(Policy = nameof(AuthorizationClaim.PromotionsCreateCampaign))] - +Modix - Start A Campaign diff --git a/Modix.Web/Pages/Index.razor b/Modix.Web/Pages/Index.razor index b71802aa0..aa8ce3518 100644 --- a/Modix.Web/Pages/Index.razor +++ b/Modix.Web/Pages/Index.razor @@ -3,7 +3,6 @@ Modix - Home -
diff --git a/Modix.Web/Pages/Logs.razor b/Modix.Web/Pages/Logs.razor index f94db0e79..be209b10f 100644 --- a/Modix.Web/Pages/Logs.razor +++ b/Modix.Web/Pages/Logs.razor @@ -5,8 +5,9 @@ @using MudBlazor @attribute [Authorize(Policy = nameof(AuthorizationClaim.ModerationRead))] +Modix - Logs + - Logs Logs diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index 3a6d7e837..6f5a94d42 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -8,9 +8,9 @@ @attribute [Authorize(Policy = nameof(AuthorizationClaim.PromotionsRead))] - +Modix - Promotions - Promotions + Promotion Campaigns diff --git a/Modix.Web/Pages/Stats.razor b/Modix.Web/Pages/Stats.razor index 1798758f4..c0a4cc4ee 100644 --- a/Modix.Web/Pages/Stats.razor +++ b/Modix.Web/Pages/Stats.razor @@ -4,10 +4,11 @@ @using Modix.Web.Services; @using MudBlazor +Modix - Stats + - Stats @if(Data is not null) { diff --git a/Modix.Web/Pages/Tags.razor b/Modix.Web/Pages/Tags.razor index ee4ebded9..148e53ee1 100644 --- a/Modix.Web/Pages/Tags.razor +++ b/Modix.Web/Pages/Tags.razor @@ -5,13 +5,13 @@ @using Modix.Web.Services; @using MudBlazor @using System.Globalization; -@using Modix.Data.Models.Core; + +Modix - Tags - Tags Tags @if (Data is not null) diff --git a/Modix.Web/Pages/UserLookup.razor b/Modix.Web/Pages/UserLookup.razor index a5ca3f1f9..9f0694ca2 100644 --- a/Modix.Web/Pages/UserLookup.razor +++ b/Modix.Web/Pages/UserLookup.razor @@ -13,11 +13,12 @@ @using System.Globalization; @using Humanizer; +Modix - User Lookup + - UserLookup User Lookup@(userInformation is null ? null : $" - {userInformation.Username + (userInformation.Discriminator == "0000" ? "" : "#" + userInformation.Discriminator)}") diff --git a/Modix.Web/Shared/MainLayout.razor b/Modix.Web/Shared/MainLayout.razor index 21940fe56..e9dffe29a 100644 --- a/Modix.Web/Shared/MainLayout.razor +++ b/Modix.Web/Shared/MainLayout.razor @@ -6,7 +6,6 @@ -Modix.Web
From 0080f304a86794a79dbb4b78a551b1003fd862d2 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Tue, 1 Aug 2023 02:23:27 +0200 Subject: [PATCH 019/117] Get SocketGuild from SocketGuildUser instead --- Modix.Web/Components/Infractions.razor | 3 +-- Modix.Web/Pages/Promotions.razor | 14 ++++---------- Modix.Web/Pages/Stats.razor | 10 +++------- Modix.Web/Pages/Tags.razor | 5 ++--- 4 files changed, 10 insertions(+), 22 deletions(-) diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor index add4537af..4360bed9b 100644 --- a/Modix.Web/Components/Infractions.razor +++ b/Modix.Web/Components/Infractions.razor @@ -353,7 +353,6 @@ private async Task> LoadInfractions(TableState tableState) { - var currentGuild = DiscordUserService.GetUserGuild(); var currentUser = await DiscordUserService.GetCurrentUserAsync(); var sortingCriteria = new[] @@ -377,7 +376,7 @@ var searchCriteria = new InfractionSearchCriteria { - GuildId = currentGuild.Id, + GuildId = currentUser!.Guild.Id, Id = Id, Types = Types, Subject = Subject, diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index 6f5a94d42..9468744be 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -142,21 +142,15 @@ protected override async Task OnInitializedAsync() { - var currentGuild = DiscordUserService.GetUserGuild(); - RoleColors = currentGuild.Roles.ToDictionary(x => x.Id, x => x.Color.ToString()); + var currentUser = await DiscordUserService.GetCurrentUserAsync(); + RoleColors = currentUser!.Guild.Roles.ToDictionary(x => x.Id, x => x.Color.ToString()); Campaigns = await PromotionsService.SearchCampaignsAsync(new PromotionCampaignSearchCriteria { - GuildId = currentGuild.Id + GuildId = currentUser.Guild.Id }); - var currentUser = await DiscordUserService.GetCurrentUserAsync(); - CurrentUserId = currentUser!.Id; - } - - protected override void OnAfterRender(bool firstRender) - { - base.OnAfterRender(firstRender); + CurrentUserId = currentUser.Id; } private async Task CampaignExpanded(bool wasExpanded, long campaignId, ulong userId) diff --git a/Modix.Web/Pages/Stats.razor b/Modix.Web/Pages/Stats.razor index c0a4cc4ee..05c4ffb11 100644 --- a/Modix.Web/Pages/Stats.razor +++ b/Modix.Web/Pages/Stats.razor @@ -108,16 +108,12 @@ protected override async Task OnInitializedAsync() { - var userGuild = DiscordUserService.GetUserGuild(); var currentUser = await DiscordUserService.GetCurrentUserAsync(); - if (currentUser is null) - throw new InvalidOperationException("Could not find currently logged in user."); + var roleCounts = await GuildStatService.GetGuildMemberDistributionAsync(currentUser!.Guild); + var messageCounts = await GuildStatService.GetTopMessageCounts(currentUser.Guild, currentUser.Id); - var roleCounts = await GuildStatService.GetGuildMemberDistributionAsync(userGuild); - var messageCounts = await GuildStatService.GetTopMessageCounts(userGuild, currentUser.Id); - - Data = new GuildStatData(userGuild.Name, roleCounts, messageCounts); + Data = new GuildStatData(currentUser.Guild.Name, roleCounts, messageCounts); GuildRoleCountView = roleCounts; } diff --git a/Modix.Web/Pages/Tags.razor b/Modix.Web/Pages/Tags.razor index 148e53ee1..ed522b8eb 100644 --- a/Modix.Web/Pages/Tags.razor +++ b/Modix.Web/Pages/Tags.razor @@ -144,10 +144,9 @@ private async Task SaveTag() { - var currentGuild = DiscordUserService.GetUserGuild(); var currentUser = await DiscordUserService.GetCurrentUserAsync(); - await TagService.CreateTagAsync(currentGuild.Id, currentUser.Id, _tagNameValue, _tagContentValue); - var createdTag = await TagService.GetTagAsync(currentGuild.Id, _tagNameValue); + await TagService.CreateTagAsync(currentUser!.Guild.Id, currentUser.Id, _tagNameValue, _tagContentValue); + var createdTag = await TagService.GetTagAsync(currentUser.Guild.Id, _tagNameValue); Data = Data!.Append(CreateTagData(createdTag)).ToArray(); _createDialogVisible = false; From c91ce5cc97adf73e14172ba8c3ee49be738531e5 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Tue, 1 Aug 2023 02:24:28 +0200 Subject: [PATCH 020/117] Use OnAfterRenderAsync instead to avoid duplicate DB calls --- Modix.Web/Pages/Stats.razor | 7 ++++++- Modix.Web/Pages/Tags.razor | 10 +++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Modix.Web/Pages/Stats.razor b/Modix.Web/Pages/Stats.razor index 05c4ffb11..217a51d16 100644 --- a/Modix.Web/Pages/Stats.razor +++ b/Modix.Web/Pages/Stats.razor @@ -106,8 +106,11 @@ [Inject] DiscordUserService DiscordUserService { get; set; } = null!; - protected override async Task OnInitializedAsync() + protected override async Task OnAfterRenderAsync(bool firstRender) { + if (!firstRender) + return; + var currentUser = await DiscordUserService.GetCurrentUserAsync(); var roleCounts = await GuildStatService.GetGuildMemberDistributionAsync(currentUser!.Guild); @@ -115,6 +118,8 @@ Data = new GuildStatData(currentUser.Guild.Name, roleCounts, messageCounts); GuildRoleCountView = roleCounts; + + StateHasChanged(); } private void SelectedChannelsChanged(MudChip[] chips) diff --git a/Modix.Web/Pages/Tags.razor b/Modix.Web/Pages/Tags.razor index ed522b8eb..2d9eceee3 100644 --- a/Modix.Web/Pages/Tags.razor +++ b/Modix.Web/Pages/Tags.razor @@ -100,7 +100,15 @@ string? _tagContentValue; bool _createDialogVisible; - protected override async Task OnInitializedAsync() => await FetchData(); + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (!firstRender) + return; + + await FetchData(); + + StateHasChanged(); + } private async Task FetchData() { From 06c21b209e38974b68cb104deaa9d386c1573150 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Tue, 1 Aug 2023 02:35:38 +0200 Subject: [PATCH 021/117] Implement guild selection Remove ClaimsTransformationService Overall improvements to navbar Move logic for adding claims to App.razor Read selected guild from cookie and pass it down the chain --- Modix.Web/App.razor | 55 +++++++++++- Modix.Web/Models/SessionState.cs | 6 ++ Modix.Web/Pages/Tags.razor | 8 +- Modix.Web/Pages/_Host.cshtml | 3 +- Modix.Web/Program.cs | 5 +- .../Services/ClaimsTransformationService.cs | 42 --------- Modix.Web/Services/DiscordUserService.cs | 30 ++++++- Modix.Web/Services/LocalStorageService.cs | 26 ++++++ Modix.Web/Shared/MainLayout.razor | 4 +- Modix.Web/Shared/MainLayout.razor.cs | 44 --------- Modix.Web/Shared/NavMenu.razor | 89 ++++++++++++++++--- 11 files changed, 201 insertions(+), 111 deletions(-) create mode 100644 Modix.Web/Models/SessionState.cs delete mode 100644 Modix.Web/Services/ClaimsTransformationService.cs create mode 100644 Modix.Web/Services/LocalStorageService.cs delete mode 100644 Modix.Web/Shared/MainLayout.razor.cs diff --git a/Modix.Web/App.razor b/Modix.Web/App.razor index 211bbcbcd..18126ce63 100644 --- a/Modix.Web/App.razor +++ b/Modix.Web/App.razor @@ -1,4 +1,8 @@ -@using MudBlazor +@using Discord.WebSocket; +@using Modix.Web.Models; +@using MudBlazor +@using System.Security.Claims; +@using Modix.Services.Core; @@ -20,4 +24,51 @@ - \ No newline at end of file + + +@code { + [Parameter] + public string? SelectedGuild { get; set; } + + [Inject] + public SessionState SessionState { get; set; } = null!; + + [Inject] + public DiscordSocketClient DiscordClient { get; set; } = null!; + + [Inject] + public AuthenticationStateProvider AuthenticationStateProvider { get; set; } = null!; + + [Inject] + public Modix.Services.Core.IAuthorizationService AuthorizationService { get; set; } = null!; + + protected override async Task OnInitializedAsync() + { + var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); + if (!authState.User.Identity?.IsAuthenticated ?? false) + return; + + var userId = authState.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value; + + if (!ulong.TryParse(userId, out var userSnowflake)) + return; + + if (!ulong.TryParse(SelectedGuild, out var selectedGuildId)) + return; + + SessionState.SelectedGuild = selectedGuildId; + + var currentGuild = DiscordClient.GetGuild(SessionState.SelectedGuild) ?? DiscordClient.Guilds.First(); + var currentUser = currentGuild.GetUser(userSnowflake); + + if (authState.User.Identity is not ClaimsIdentity claimsIdentity) + return; + + var claims = (await AuthorizationService.GetGuildUserClaimsAsync(currentUser)) + .Select(d => new Claim(ClaimTypes.Role, d.ToString())); + + claimsIdentity.AddClaims(claims); + + await AuthorizationService.OnAuthenticatedAsync(currentUser.Id, currentGuild.Id, currentUser.Roles.Select(x => x.Id).ToList()); + } +} \ No newline at end of file diff --git a/Modix.Web/Models/SessionState.cs b/Modix.Web/Models/SessionState.cs new file mode 100644 index 000000000..1cc64d0f6 --- /dev/null +++ b/Modix.Web/Models/SessionState.cs @@ -0,0 +1,6 @@ +namespace Modix.Web.Models; + +public class SessionState +{ + public ulong SelectedGuild { get; set; } +} diff --git a/Modix.Web/Pages/Tags.razor b/Modix.Web/Pages/Tags.razor index 2d9eceee3..ba08287c4 100644 --- a/Modix.Web/Pages/Tags.razor +++ b/Modix.Web/Pages/Tags.razor @@ -2,6 +2,7 @@ @using Modix.Data.Models.Core; @using Modix.Data.Models.Tags; @using Modix.Services.Tags; +@using Modix.Web.Models; @using Modix.Web.Services; @using MudBlazor @using System.Globalization; @@ -91,6 +92,9 @@ [Inject] DiscordUserService DiscordUserService { get; set; } = null!; + [Inject] + SessionState SessionState { get; set; } = null!; + [Inject] IDialogService DialogService { get; set; } = null!; @@ -112,11 +116,11 @@ private async Task FetchData() { - var currentGuild = DiscordUserService.GetUserGuild(); + var currentGuild = SessionState.SelectedGuild; var summaries = await TagService.GetSummariesAsync(new TagSearchCriteria { - GuildId = currentGuild.Id, + GuildId = currentGuild, }); Data = summaries diff --git a/Modix.Web/Pages/_Host.cshtml b/Modix.Web/Pages/_Host.cshtml index edd87d5e8..dd7047b3f 100644 --- a/Modix.Web/Pages/_Host.cshtml +++ b/Modix.Web/Pages/_Host.cshtml @@ -1,5 +1,6 @@ @page "/" @using Microsoft.AspNetCore.Components.Web +@using Modix.Web.Services; @namespace Modix.Web.Pages @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @@ -20,7 +21,7 @@ - +
diff --git a/Modix.Web/Program.cs b/Modix.Web/Program.cs index 6868c54b4..d321488f3 100644 --- a/Modix.Web/Program.cs +++ b/Modix.Web/Program.cs @@ -9,6 +9,7 @@ using Modix.Data.Models.Core; using Modix.Services.CodePaste; using Modix.Services.Utilities; +using Modix.Web.Models; using Modix.Web.Services; using MudBlazor; using MudBlazor.Services; @@ -155,6 +156,8 @@ public static int Main(string[] args) private static void ConfigureServices(WebApplicationBuilder builder, IConfiguration configuration, ModixConfig modixConfig) { builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddMudServices(); builder.Services.AddMudMarkdownServices(); builder.Services.AddRazorPages(); @@ -196,8 +199,6 @@ private static void ConfigureServices(WebApplicationBuilder builder, IConfigurat } }); - builder.Services.AddScoped(); - //services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) // .AddCookie(options => // { diff --git a/Modix.Web/Services/ClaimsTransformationService.cs b/Modix.Web/Services/ClaimsTransformationService.cs deleted file mode 100644 index f2eb560aa..000000000 --- a/Modix.Web/Services/ClaimsTransformationService.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Security.Claims; -using Microsoft.AspNetCore.Authentication; -using Modix.Services.Core; - -namespace Modix.Web.Services; - -public class ClaimsTransformationService : IClaimsTransformation -{ - private readonly IAuthorizationService _authorizationService; - private readonly DiscordUserService _discordUserService; - - public ClaimsTransformationService(IAuthorizationService authorizationService, DiscordUserService discordUserService) - { - _authorizationService = authorizationService; - _discordUserService = discordUserService; - } - - public async Task TransformAsync(ClaimsPrincipal principal) - { - if (!principal.Identity?.IsAuthenticated ?? false) - return principal; - - var userId = principal.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value; - if (!ulong.TryParse(userId, out var userSnowflake)) - return principal; - - var newPrincipal = principal.Clone(); - if (newPrincipal.Identity is not ClaimsIdentity claimsIdentity) - return principal; - - // TODO: Get selected guild from cookie - var currentGuild = _discordUserService.GetUserGuild(); - var currentUser = currentGuild.GetUser(userSnowflake); - - var claims = (await _authorizationService.GetGuildUserClaimsAsync(currentUser)) - .Select(d => new Claim(ClaimTypes.Role, d.ToString())); - - claimsIdentity.AddClaims(claims); - - return newPrincipal; - } -} diff --git a/Modix.Web/Services/DiscordUserService.cs b/Modix.Web/Services/DiscordUserService.cs index 7bd763d43..702c814ce 100644 --- a/Modix.Web/Services/DiscordUserService.cs +++ b/Modix.Web/Services/DiscordUserService.cs @@ -11,15 +11,41 @@ public class DiscordUserService private readonly DiscordSocketClient _client; private readonly AuthenticationStateProvider _authenticationStateProvider; private readonly IUserService _userService; + private readonly SessionState _sessionState; - public DiscordUserService(DiscordSocketClient client, AuthenticationStateProvider authenticationStateProvider, IUserService userService) + public DiscordUserService( + DiscordSocketClient client, + AuthenticationStateProvider authenticationStateProvider, + IUserService userService, + SessionState sessionState) { _client = client; _authenticationStateProvider = authenticationStateProvider; _userService = userService; + _sessionState = sessionState; } - public SocketGuild GetUserGuild() => _client.Guilds.First(); + public SocketGuild GetUserGuild() + { + if (_sessionState.SelectedGuild != 0) + return _client.GetGuild(_sessionState.SelectedGuild); + + return _client.Guilds.First(); + } + + public record GuildOption(ulong Id, string Name, string IconUrl); + + public async Task> GetGuildOptionsAsync() + { + var currentUser = await GetCurrentUserAsync(); + if (currentUser is null) + return Array.Empty(); + + return _client + .Guilds + .Where(d => d.GetUser(currentUser.Id) != null) + .Select(d => new GuildOption(d.Id, d.Name, d.IconUrl)); + } public async Task GetCurrentUserAsync() { diff --git a/Modix.Web/Services/LocalStorageService.cs b/Modix.Web/Services/LocalStorageService.cs new file mode 100644 index 000000000..2b2734ab4 --- /dev/null +++ b/Modix.Web/Services/LocalStorageService.cs @@ -0,0 +1,26 @@ +using Discord.WebSocket; +using Microsoft.JSInterop; +using Modix.Web.Models; + +namespace Modix.Web.Services; + +public class LocalStorageService +{ + private readonly IJSRuntime _jsRuntime; + private readonly DiscordSocketClient _client; + private readonly SessionState _sessionState; + public const string GuildCookieKey = "SelectedGuild"; + + public LocalStorageService(IJSRuntime jsRuntime, DiscordSocketClient client, SessionState sessionState) + { + _jsRuntime = jsRuntime; + _client = client; + _sessionState = sessionState; + } + + public async Task SetSelectedGuildAsync(ulong guildId) + { + await _jsRuntime.InvokeVoidAsync("eval", $"document.cookie = \"{GuildCookieKey}={guildId}\""); + _sessionState.SelectedGuild = guildId; + } +} diff --git a/Modix.Web/Shared/MainLayout.razor b/Modix.Web/Shared/MainLayout.razor index e9dffe29a..4a0b42111 100644 --- a/Modix.Web/Shared/MainLayout.razor +++ b/Modix.Web/Shared/MainLayout.razor @@ -7,12 +7,11 @@ -
- + @Body @@ -23,6 +22,7 @@ @code { + private MudTheme _theme = new() { Typography = new() diff --git a/Modix.Web/Shared/MainLayout.razor.cs b/Modix.Web/Shared/MainLayout.razor.cs deleted file mode 100644 index ad5c87d85..000000000 --- a/Modix.Web/Shared/MainLayout.razor.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Security.Claims; -using AspNet.Security.OAuth.Discord; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Authorization; -using Modix.Services.Core; -using Modix.Web.Services; - -namespace Modix.Web.Shared; - -public partial class MainLayout : LayoutComponentBase -{ - [Inject] - public AuthenticationStateProvider AuthenticationStateProvider { get; set; } = null!; - - [Inject] - public IAuthorizationService AuthorizationService { get; set; } = null!; - - [Inject] - public DiscordUserService DiscordUserService { get; set; } = null!; - - public string? AvatarUrl { get; private set; } - public string? Username { get; private set; } - - protected override async Task OnInitializedAsync() - { - var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); - if (!authState.User.Identity?.IsAuthenticated ?? false) - return; - - var avatarHash = authState.User.Claims.FirstOrDefault(x => x.Type == DiscordAuthenticationConstants.Claims.AvatarHash)?.Value; - var userId = authState.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value; - - AvatarUrl = $"https://cdn.discordapp.com/avatars/{userId}/{avatarHash}.png"; - Username = authState.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Name)?.Value; - - if (!ulong.TryParse(userId, out var userSnowflake)) - return; - - var currentGuild = DiscordUserService.GetUserGuild(); - var currentUser = currentGuild.GetUser(userSnowflake); - - await AuthorizationService.OnAuthenticatedAsync(currentUser.Id, currentGuild.Id, currentUser.Roles.Select(x => x.Id).ToList()); - } -} diff --git a/Modix.Web/Shared/NavMenu.razor b/Modix.Web/Shared/NavMenu.razor index 45c29c58d..9f70d6c3d 100644 --- a/Modix.Web/Shared/NavMenu.razor +++ b/Modix.Web/Shared/NavMenu.razor @@ -1,21 +1,26 @@ -@using Modix.Data.Models.Core; +@using AspNet.Security.OAuth.Discord; +@using Discord.WebSocket; +@using Modix.Data.Models.Core; +@using Modix.Web.Models; +@using Modix.Web.Services; @using MudBlazor; +@using System.Security.Claims; - +
- Home - Stats - Commands - User Lookup - Tags + Home + Stats + Commands + User Lookup + Tags - Promotions + Promotions - Logs + Logs
@@ -26,7 +31,25 @@ @Username } - + + +
+ + + +
+
+ + @foreach (var guildOption in GuildOptions) + { + + + @guildOption.Name + + } + +
+
@@ -45,9 +68,47 @@
@code { - [Parameter] - public string? AvatarUrl { get; set; } - [Parameter] - public string? Username { get; set; } + [CascadingParameter] + public Task? AuthenticationState { get; set; } = null!; + + [Inject] + public DiscordUserService DiscordUserService { get; set; } = null!; + + [Inject] + public LocalStorageService LocalStorage { get; set; } = null!; + + [Inject] + public NavigationManager NavigationManager { get; set; } = null!; + + private string? AvatarUrl { get; set; } + private string? Username { get; set; } + + private IEnumerable GuildOptions { get; set; } = Array.Empty(); + private SocketGuild? SelectedGuild { get; set; } + + protected override async Task OnInitializedAsync() + { + if (AuthenticationState is null) + return; + + var authState = await AuthenticationState; + if (!authState.User.Identity?.IsAuthenticated ?? false) + return; + + var avatarHash = authState.User.Claims.FirstOrDefault(x => x.Type == DiscordAuthenticationConstants.Claims.AvatarHash)?.Value; + var userId = authState.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value; + + AvatarUrl = $"https://cdn.discordapp.com/avatars/{userId}/{avatarHash}.png"; + Username = authState.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Name)?.Value; + + GuildOptions = await DiscordUserService.GetGuildOptionsAsync(); + SelectedGuild = DiscordUserService.GetUserGuild(); + } + + private async Task SelectGuild(ulong guildId) + { + await LocalStorage.SetSelectedGuildAsync(guildId); + NavigationManager.NavigateTo(NavigationManager.Uri, true); + } } From 2a3d85073a59a69506275da0784656edd0700765 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Tue, 1 Aug 2023 02:41:52 +0200 Subject: [PATCH 022/117] Remove etc in favor of Authorize attribute --- Modix.Web/Pages/CreatePromotion.razor | 111 ++++++++++++-------------- 1 file changed, 53 insertions(+), 58 deletions(-) diff --git a/Modix.Web/Pages/CreatePromotion.razor b/Modix.Web/Pages/CreatePromotion.razor index 13318da28..2b4c180af 100644 --- a/Modix.Web/Pages/CreatePromotion.razor +++ b/Modix.Web/Pages/CreatePromotion.razor @@ -9,64 +9,59 @@ @attribute [Authorize(Policy = nameof(AuthorizationClaim.PromotionsCreateCampaign))] Modix - Start A Campaign - - - - Start a Campaign - - - -

Feel like someone deserves recognition? Start a promotion campaign for them - even if that person is yourself!

-
- -

Once a campaign is started, users can anonymously comment, voicing their opinions for or against the individual up for promotion

-
- -

Staff will periodically review campaigns. If approved, the user will be immediately promoted! If not, they may be permanently denied, or further looked into as the campaign runs its course.

-
-
- - - - - @if(_selectedUser is not null && _nextRank is not null) - { -
- @_selectedUser.Name can be promoted to this rank - @_nextRank.Name -
- -
- Finally, say a few words on their behalf - - - -
- - - Submit - - } - -
- -
-
- -
-
- + + + Start a Campaign + + + +

Feel like someone deserves recognition? Start a promotion campaign for them - even if that person is yourself!

+
+ +

Once a campaign is started, users can anonymously comment, voicing their opinions for or against the individual up for promotion

+
+ +

Staff will periodically review campaigns. If approved, the user will be immediately promoted! If not, they may be permanently denied, or further looked into as the campaign runs its course.

+
+
+ + + + + @if(_selectedUser is not null && _nextRank is not null) + { +
+ @_selectedUser.Name can be promoted to this rank + @_nextRank.Name +
+ +
+ Finally, say a few words on their behalf + + + +
+ + + Submit + + } + +
+ +
+
@code { - - record Command(string Name, string Summary, IReadOnlyCollection Aliases, IReadOnlyCollection Parameters, bool IsSlashCommand); - - record Module(string Name, string Summary, IEnumerable Commands); - [Inject] ICommandHelpService CommandHelpService { get; set; } = null!; diff --git a/Modix.Web/Pages/CreatePromotion.razor b/Modix.Web/Pages/CreatePromotion.razor index 3c3b6ad6f..4ebfdfdf4 100644 --- a/Modix.Web/Pages/CreatePromotion.razor +++ b/Modix.Web/Pages/CreatePromotion.razor @@ -4,6 +4,7 @@ @using Modix.Services.Promotions; @using Modix.Web.Components @using Modix.Web.Models; +@using Modix.Web.Models.Promotions; @using Modix.Web.Services; @using MudBlazor @@ -70,7 +71,6 @@ @code { - public record NextRank(string? Name, string Color); [Inject] public IPromotionsService PromotionsService { get; set; } = null!; diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index 77e1f5b5b..3576ee523 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -4,6 +4,7 @@ @using Modix.Data.Models.Promotions; @using Modix.Services.Promotions; @using Modix.Web.Components; +@using Modix.Web.Models.Promotions; @using Modix.Web.Services; @using MudBlazor @@ -118,10 +119,6 @@ @code { - - public record CampaignCommentData(long Id, PromotionSentiment PromotionSentiment, string Content, DateTimeOffset CreatedAt, bool IsFromCurrentUser); - - [Inject] public DiscordUserService DiscordUserService { get; set; } = null!; diff --git a/Modix.Web/Pages/Stats.razor b/Modix.Web/Pages/Stats.razor index 8de58186a..fdd1f25ed 100644 --- a/Modix.Web/Pages/Stats.razor +++ b/Modix.Web/Pages/Stats.razor @@ -2,6 +2,7 @@ @attribute [Authorize] @using Modix.Data.Models.Core; @using Modix.Services.GuildStats; +@using Modix.Web.Models.Stats; @using Modix.Web.Services; @using MudBlazor @@ -88,9 +89,6 @@ @code { - - public record GuildStatData(string GuildName, List GuildRoleCounts, IReadOnlyCollection TopUserMessageCounts); - GuildStatData Data { get; set;} = null!; List GuildRoleCountView { get; set; } = null!; diff --git a/Modix.Web/Pages/Tags.razor b/Modix.Web/Pages/Tags.razor index e7c6386a9..629cbb791 100644 --- a/Modix.Web/Pages/Tags.razor +++ b/Modix.Web/Pages/Tags.razor @@ -4,6 +4,7 @@ @using Modix.Data.Models.Tags; @using Modix.Services.Tags; @using Modix.Web.Models; +@using Modix.Web.Models.Tags; @using Modix.Web.Services; @using MudBlazor @using System.Globalization; @@ -12,77 +13,63 @@ - - Tags - @if (Data is not null) - { - - - Create Tag - - - - - Preview - - - -
- Save -
- Cancel -
-
- - - - - Create - - Refresh - - - - - Name - Last Modified - Owner - Content - Uses - - - @tag.Name - @tag.Created.ToString("dd/MM/yy, h:MM:ss tt") - @tag.OwnerName - - - - @tag.Uses - - - - - - } -
+ + Tags + @if (Data is not null) + { + + + Create Tag + + + + + Preview + + + +
+ Save +
+ Cancel +
+
+ + + + + Create + + Refresh + + + + + Name + Last Modified + Owner + Content + Uses + + + @tag.Name + @tag.Created.ToString("dd/MM/yy, h:MM:ss tt") + @tag.OwnerName + + + + @tag.Uses + + + + + + } +
@code { - - public record TagData( - string Name, - DateTimeOffset Created, - bool IsOwnedByRole, - GuildUserBrief? OwnerUser, - GuildRoleBrief? OwnerRole, - string? OwnerName, - string Content, - uint Uses, - bool CanMaintain, - TagSummary TagSummary); - - [Inject] ITagService TagService { get; set; } = null!; @@ -121,7 +108,7 @@ }); Data = summaries - .Select(x => CreateTagData(x)) + .Select(TagData.CreateFromSummary) .ToArray(); // foreach (var tag in data) @@ -156,26 +143,11 @@ var currentUser = await DiscordUserService.GetCurrentUserAsync(); await TagService.CreateTagAsync(currentUser!.Guild.Id, currentUser.Id, _tagNameValue, _tagContentValue); var createdTag = await TagService.GetTagAsync(currentUser.Guild.Id, _tagNameValue); - Data = Data!.Append(CreateTagData(createdTag)).ToArray(); + Data = Data!.Append(TagData.CreateFromSummary(createdTag)).ToArray(); _createDialogVisible = false; } - private TagData CreateTagData(TagSummary summary) - { - return new TagData( - summary.Name, - summary.CreateAction.Created, - summary.OwnerRole is not null, - summary.OwnerUser, - summary.OwnerRole, - summary.OwnerRole?.Name ?? summary.OwnerUser?.Username, - summary.Content, - summary.Uses, - false, - summary); - } - private void ToggleDialog() { _createDialogVisible = !_createDialogVisible; diff --git a/Modix.Web/Pages/UserLookup.razor b/Modix.Web/Pages/UserLookup.razor index ecbc2794e..35c958e74 100644 --- a/Modix.Web/Pages/UserLookup.razor +++ b/Modix.Web/Pages/UserLookup.razor @@ -6,6 +6,7 @@ @using Modix.Services.Utilities; @using Modix.Web.Components @using Modix.Web.Models; +@using Modix.Web.Models.UserLookup; @using Modix.Web.Services @using MudBlazor @using Discord.WebSocket @@ -160,61 +161,13 @@ var result = await MessageRepository.GetGuildUserMessageCountByChannel(currentGuild.Id, user.UserId, timespan); var colors = ColorUtils.GetRainbowColors(result.Count); - var mapped = result + var messageCountsPerChannel = result .Select((x, i) => new MessageCountPerChannelInformation(x.ChannelName, x.MessageCount, colors[i++].ToString())) .OrderByDescending(x => x.Count) .ToList(); - userInformation = new UserInformation( - ephemeralUser.Id.ToString(), - ephemeralUser.Username, - ephemeralUser.Nickname, - ephemeralUser.Discriminator, - ephemeralUser.AvatarId != null ? ephemeralUser.GetAvatarUrl(ImageFormat.Auto, 256) : ephemeralUser.GetDefaultAvatarUrl(), - ephemeralUser.CreatedAt, - ephemeralUser.JoinedAt, - ephemeralUser.FirstSeen, - ephemeralUser.LastSeen, - userRank.Rank, - messages7.Sum(x => x.MessageCount), - messages30.Sum(x => x.MessageCount), - userRank.AveragePerDay, - userRank.Percentile, - roles - .Where(x => !x.IsEveryone) - .Select(x => new RoleInformation(x.Id, x.Name, x.Color.ToString())), - ephemeralUser.IsBanned, - ephemeralUser.BanReason, - ephemeralUser.GuildId != default, - mapped - ); + userInformation = UserInformation.FromEphemeralUser(ephemeralUser, userRank, messages7, messages30, roles, messageCountsPerChannel); messageCountsPerChannelView = userInformation.MessageCountsPerChannel.ToArray(); } - - public record RoleInformation(ulong Id, string Name, string Color); - - public record MessageCountPerChannelInformation(string ChannelName, double Count, string Color); - - public record UserInformation( - string Id, - string? Username, - string? Nickname, - string? Discriminator, - string? AvatarUrl, - DateTimeOffset CreatedAt, - DateTimeOffset? JoinedAt, - DateTimeOffset? FirstSeen, - DateTimeOffset? LastSeen, - int Rank, - int Last7DaysMessages, - int Last30DaysMessages, - decimal AverageMessagesPerDay, - int Percentile, - IEnumerable Roles, - bool IsBanned, - string? BanReason, - bool IsGuildMember, - IReadOnlyList MessageCountsPerChannel - ); } diff --git a/Modix.Web/Services/DiscordUserService.cs b/Modix.Web/Services/DiscordUserService.cs index 702c814ce..2559f4b61 100644 --- a/Modix.Web/Services/DiscordUserService.cs +++ b/Modix.Web/Services/DiscordUserService.cs @@ -33,8 +33,6 @@ public SocketGuild GetUserGuild() return _client.Guilds.First(); } - public record GuildOption(ulong Id, string Name, string IconUrl); - public async Task> GetGuildOptionsAsync() { var currentUser = await GetCurrentUserAsync(); diff --git a/Modix.Web/Shared/NavMenu.razor b/Modix.Web/Shared/NavMenu.razor index 15af2f736..ff0ab9bef 100644 --- a/Modix.Web/Shared/NavMenu.razor +++ b/Modix.Web/Shared/NavMenu.razor @@ -93,7 +93,7 @@ private string? AvatarUrl { get; set; } private string? Username { get; set; } - private IEnumerable GuildOptions { get; set; } = Array.Empty(); + private IEnumerable GuildOptions { get; set; } = Array.Empty(); private SocketGuild? SelectedGuild { get; set; } protected override async Task OnInitializedAsync() From fd451a6f21f5ea8c10d3816f55672d6ac50a5cec Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Tue, 1 Aug 2023 21:34:50 +0200 Subject: [PATCH 031/117] Propagate CurrentUserId down to components via SessionState to avoid reading from user claims constantly --- Modix.Web/App.razor | 8 +++++++- Modix.Web/Components/Infractions.razor | 4 ++-- Modix.Web/Models/SessionState.cs | 1 + Modix.Web/Pages/Promotions.razor | 2 +- Modix.Web/Pages/Stats.razor | 2 +- Modix.Web/Pages/Tags.razor | 2 +- Modix.Web/Pages/UserLookup.razor | 2 +- Modix.Web/Services/DiscordUserService.cs | 23 +++++------------------ Modix.Web/Shared/NavMenu.razor | 8 ++++---- 9 files changed, 23 insertions(+), 29 deletions(-) diff --git a/Modix.Web/App.razor b/Modix.Web/App.razor index c46b50bdd..a1ca9a507 100644 --- a/Modix.Web/App.razor +++ b/Modix.Web/App.razor @@ -52,9 +52,15 @@ if (!ulong.TryParse(SelectedGuild, out var selectedGuildId)) return; + var userId = authState.User.FindFirst(x => x.Type == ClaimTypes.NameIdentifier)?.Value; + if (!ulong.TryParse(userId, out var userSnowflake)) + return; + SessionState.SelectedGuild = selectedGuildId; + SessionState.CurrentUserId = userSnowflake; + + var currentUser = DiscordUserService.GetCurrentUser(); - var currentUser = await DiscordUserService.GetCurrentUserAsync(); await AuthorizationService.OnAuthenticatedAsync(currentUser!.Id, currentUser.Guild.Id, currentUser.Roles.Select(x => x.Id).ToList()); } } \ No newline at end of file diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor index b641419ba..3172cf33c 100644 --- a/Modix.Web/Components/Infractions.razor +++ b/Modix.Web/Components/Infractions.razor @@ -286,7 +286,7 @@ try { - var currentUser = await DiscordUserService.GetCurrentUserAsync(); + var currentUser = DiscordUserService.GetCurrentUser(); await ModerationService.CreateInfractionAsync(currentUser!.Guild.Id, currentUser.Id, _infractionType, _selectedUser!.UserId, _infractionReason!, duration); } catch (InvalidOperationException ex) @@ -348,7 +348,7 @@ private async Task> LoadInfractions(TableState tableState) { - var currentUser = await DiscordUserService.GetCurrentUserAsync(); + var currentUser = DiscordUserService.GetCurrentUser(); var sortingCriteria = new[] { diff --git a/Modix.Web/Models/SessionState.cs b/Modix.Web/Models/SessionState.cs index 1cc64d0f6..f9cb03a67 100644 --- a/Modix.Web/Models/SessionState.cs +++ b/Modix.Web/Models/SessionState.cs @@ -3,4 +3,5 @@ public class SessionState { public ulong SelectedGuild { get; set; } + public ulong CurrentUserId { get; set; } } diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index 3576ee523..03a7671c0 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -141,7 +141,7 @@ if (!firstRender) return; - var currentUser = await DiscordUserService.GetCurrentUserAsync(); + var currentUser = DiscordUserService.GetCurrentUser(); RoleColors = currentUser!.Guild.Roles.ToDictionary(x => x.Id, x => x.Color.ToString()); Campaigns = await PromotionsService.SearchCampaignsAsync(new PromotionCampaignSearchCriteria diff --git a/Modix.Web/Pages/Stats.razor b/Modix.Web/Pages/Stats.razor index fdd1f25ed..b36649819 100644 --- a/Modix.Web/Pages/Stats.razor +++ b/Modix.Web/Pages/Stats.razor @@ -103,7 +103,7 @@ if (!firstRender) return; - var currentUser = await DiscordUserService.GetCurrentUserAsync(); + var currentUser = DiscordUserService.GetCurrentUser(); var roleCounts = await GuildStatService.GetGuildMemberDistributionAsync(currentUser!.Guild); var messageCounts = await GuildStatService.GetTopMessageCounts(currentUser.Guild, currentUser.Id); diff --git a/Modix.Web/Pages/Tags.razor b/Modix.Web/Pages/Tags.razor index 629cbb791..781041bd8 100644 --- a/Modix.Web/Pages/Tags.razor +++ b/Modix.Web/Pages/Tags.razor @@ -140,7 +140,7 @@ private async Task SaveTag() { - var currentUser = await DiscordUserService.GetCurrentUserAsync(); + var currentUser = DiscordUserService.GetCurrentUser(); await TagService.CreateTagAsync(currentUser!.Guild.Id, currentUser.Id, _tagNameValue, _tagContentValue); var createdTag = await TagService.GetTagAsync(currentUser.Guild.Id, _tagNameValue); Data = Data!.Append(TagData.CreateFromSummary(createdTag)).ToArray(); diff --git a/Modix.Web/Pages/UserLookup.razor b/Modix.Web/Pages/UserLookup.razor index 35c958e74..e119a0334 100644 --- a/Modix.Web/Pages/UserLookup.razor +++ b/Modix.Web/Pages/UserLookup.razor @@ -124,7 +124,7 @@ if(!firstRender) return; - var currentUser = await DiscordUserService.GetCurrentUserAsync(); + var currentUser = DiscordUserService.GetCurrentUser(); await SelectedUserChanged(ModixUser.FromIGuildUser(currentUser!)); StateHasChanged(); diff --git a/Modix.Web/Services/DiscordUserService.cs b/Modix.Web/Services/DiscordUserService.cs index 2559f4b61..de5db2d75 100644 --- a/Modix.Web/Services/DiscordUserService.cs +++ b/Modix.Web/Services/DiscordUserService.cs @@ -1,6 +1,4 @@ -using System.Security.Claims; -using Discord.WebSocket; -using Microsoft.AspNetCore.Components.Authorization; +using Discord.WebSocket; using Modix.Services.Core; using Modix.Web.Models; @@ -9,18 +7,15 @@ namespace Modix.Web.Services; public class DiscordUserService { private readonly DiscordSocketClient _client; - private readonly AuthenticationStateProvider _authenticationStateProvider; private readonly IUserService _userService; private readonly SessionState _sessionState; public DiscordUserService( DiscordSocketClient client, - AuthenticationStateProvider authenticationStateProvider, IUserService userService, SessionState sessionState) { _client = client; - _authenticationStateProvider = authenticationStateProvider; _userService = userService; _sessionState = sessionState; } @@ -33,9 +28,9 @@ public SocketGuild GetUserGuild() return _client.Guilds.First(); } - public async Task> GetGuildOptionsAsync() + public IEnumerable GetGuildOptionsAsync() { - var currentUser = await GetCurrentUserAsync(); + var currentUser = GetCurrentUser(); if (currentUser is null) return Array.Empty(); @@ -45,18 +40,10 @@ public async Task> GetGuildOptionsAsync() .Select(d => new GuildOption(d.Id, d.Name, d.IconUrl)); } - public async Task GetCurrentUserAsync() + public SocketGuildUser? GetCurrentUser() { - var authState = await _authenticationStateProvider.GetAuthenticationStateAsync(); - if (!authState.User.Identity?.IsAuthenticated ?? false) - return null; - - var userId = authState.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value; - if (!ulong.TryParse(userId, out var userSnowflake)) - return null; - var currentGuild = GetUserGuild(); - return currentGuild.GetUser(userSnowflake); + return currentGuild.GetUser(_sessionState.CurrentUserId); } public async Task> AutoCompleteAsync(string query) diff --git a/Modix.Web/Shared/NavMenu.razor b/Modix.Web/Shared/NavMenu.razor index ff0ab9bef..ba8c4012a 100644 --- a/Modix.Web/Shared/NavMenu.razor +++ b/Modix.Web/Shared/NavMenu.razor @@ -106,13 +106,13 @@ return; var avatarHash = authState.User.FindFirst(x => x.Type == DiscordAuthenticationConstants.Claims.AvatarHash)?.Value; - var userId = authState.User.FindFirst(x => x.Type == ClaimTypes.NameIdentifier)?.Value; + var user = DiscordUserService.GetCurrentUser(); - AvatarUrl = $"https://cdn.discordapp.com/avatars/{userId}/{avatarHash}.png"; + AvatarUrl = $"https://cdn.discordapp.com/avatars/{user!.Id}/{avatarHash}.png"; Username = authState.User.Identity?.Name; - GuildOptions = await DiscordUserService.GetGuildOptionsAsync(); - SelectedGuild = DiscordUserService.GetUserGuild(); + GuildOptions = DiscordUserService.GetGuildOptionsAsync(); + SelectedGuild = user.Guild; } private async Task SelectGuild(ulong guildId) From 35e965121f7a32b13170f4a6e644335d089a7cab Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Tue, 1 Aug 2023 21:41:26 +0200 Subject: [PATCH 032/117] Fix issue with filters not resetting properly --- Modix.Web/Models/Infractions/TableFilter.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Modix.Web/Models/Infractions/TableFilter.cs b/Modix.Web/Models/Infractions/TableFilter.cs index 404606ea6..fc7e112ab 100644 --- a/Modix.Web/Models/Infractions/TableFilter.cs +++ b/Modix.Web/Models/Infractions/TableFilter.cs @@ -16,7 +16,12 @@ public string? Subject get => _subject; set { - if (ulong.TryParse(value, out var subjectId)) + if (string.IsNullOrWhiteSpace(value)) + { + _subject = null; + SubjectId = null; + } + else if (ulong.TryParse(value, out var subjectId)) { SubjectId = subjectId; } @@ -35,7 +40,12 @@ public string? Creator get => _creator; set { - if (ulong.TryParse(value, out var createdById)) + if (string.IsNullOrWhiteSpace(value)) + { + _creator = null; + CreatedById = null; + } + else if (ulong.TryParse(value, out var createdById)) { CreatedById = createdById; } From 13169b38103a70428e088260fdc29e4242efb708 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Tue, 1 Aug 2023 23:45:50 +0200 Subject: [PATCH 033/117] Initially select nothing in the dropdown for InfractionType (nothing was actually initially selected, it just appeared to be) --- Modix.Web/Components/Infractions.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor index 3172cf33c..4f5c9ae38 100644 --- a/Modix.Web/Components/Infractions.razor +++ b/Modix.Web/Components/Infractions.razor @@ -90,10 +90,10 @@ Type - + @foreach(var infractionType in Enum.GetValues()) { - + } From b648bcab18cd8b82737bd9a7763d7bc4751c3940 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Tue, 1 Aug 2023 23:47:01 +0200 Subject: [PATCH 034/117] Force cookie to be created on root path --- Modix.Web/Services/CookieService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modix.Web/Services/CookieService.cs b/Modix.Web/Services/CookieService.cs index 5fe4badc7..992a8b153 100644 --- a/Modix.Web/Services/CookieService.cs +++ b/Modix.Web/Services/CookieService.cs @@ -17,7 +17,7 @@ public CookieService(IJSRuntime jsRuntime, SessionState sessionState) public async Task SetSelectedGuildAsync(ulong guildId) { - await _jsRuntime.InvokeVoidAsync("eval", $"document.cookie = \"{GuildCookieKey}={guildId}\""); + await _jsRuntime.InvokeVoidAsync("eval", $"document.cookie = \"{GuildCookieKey}={guildId}; path=/\";"); _sessionState.SelectedGuild = guildId; } } From 4ee8dfc7ed71f45a697a65efe6ac6d326e5d60a9 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Tue, 1 Aug 2023 23:47:37 +0200 Subject: [PATCH 035/117] Continue with first found guild if no guild cookie was found --- Modix.Web/App.razor | 10 ++++------ Modix.Web/Security/ClaimsMiddleware.cs | 6 +----- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/Modix.Web/App.razor b/Modix.Web/App.razor index a1ca9a507..36ec50fb2 100644 --- a/Modix.Web/App.razor +++ b/Modix.Web/App.razor @@ -49,15 +49,13 @@ if (!authState.User.Identity?.IsAuthenticated ?? false) return; - if (!ulong.TryParse(SelectedGuild, out var selectedGuildId)) - return; - var userId = authState.User.FindFirst(x => x.Type == ClaimTypes.NameIdentifier)?.Value; - if (!ulong.TryParse(userId, out var userSnowflake)) - return; - SessionState.SelectedGuild = selectedGuildId; + _ = ulong.TryParse(userId, out var userSnowflake); + _ = ulong.TryParse(SelectedGuild, out var selectedGuildId); + SessionState.CurrentUserId = userSnowflake; + SessionState.SelectedGuild = selectedGuildId; var currentUser = DiscordUserService.GetCurrentUser(); diff --git a/Modix.Web/Security/ClaimsMiddleware.cs b/Modix.Web/Security/ClaimsMiddleware.cs index 6154e659a..f79cf4eaa 100644 --- a/Modix.Web/Security/ClaimsMiddleware.cs +++ b/Modix.Web/Security/ClaimsMiddleware.cs @@ -24,11 +24,7 @@ public async Task InvokeAsync(HttpContext context, IAuthorizationService authori } var selectedGuild = context.Request.Cookies[CookieService.GuildCookieKey]; - if (!ulong.TryParse(selectedGuild, out var selectedGuildId)) - { - await _next(context); - return; - } + _ = ulong.TryParse(selectedGuild, out var selectedGuildId); if (context.User.Identity is not ClaimsIdentity claimsIdentity) { From 8547ab1a1bd321cecdd10bced13ebe16887a7aae Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Wed, 2 Aug 2023 00:06:17 +0200 Subject: [PATCH 036/117] Case-insensitive filtering on Tags page --- Modix.Web/Pages/Tags.razor | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Modix.Web/Pages/Tags.razor b/Modix.Web/Pages/Tags.razor index 781041bd8..88ec13ed9 100644 --- a/Modix.Web/Pages/Tags.razor +++ b/Modix.Web/Pages/Tags.razor @@ -123,16 +123,16 @@ if (string.IsNullOrWhiteSpace(query)) return true; - if (tag.OwnerUser is not null && (tag.OwnerUser.Username.Contains(query) || tag.OwnerUser.Id.ToString() == query)) + if (tag.OwnerUser is not null && (tag.OwnerUser.Username.Contains(query, StringComparison.OrdinalIgnoreCase) || tag.OwnerUser.Id.ToString() == query)) return true; - if (tag.OwnerRole?.Name.Contains(query) ?? false) + if (tag.OwnerRole is not null && (tag.OwnerRole.Name.Contains(query, StringComparison.OrdinalIgnoreCase) || tag.OwnerRole.Id.ToString() == query)) return true; - if (tag.Name.Contains(query)) + if (tag.Name.Contains(query, StringComparison.OrdinalIgnoreCase)) return true; - if (tag.Content.Contains(query)) + if (tag.Content.Contains(query, StringComparison.OrdinalIgnoreCase)) return true; return false; From 7c51c233e168b624339d8cfbe201f706ccc09101 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Wed, 2 Aug 2023 00:52:58 +0200 Subject: [PATCH 037/117] Improve feedback when Tag creation fails (or succeeds) --- Modix.Web/Pages/Tags.razor | 50 ++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/Modix.Web/Pages/Tags.razor b/Modix.Web/Pages/Tags.razor index 88ec13ed9..1fa6d73a2 100644 --- a/Modix.Web/Pages/Tags.razor +++ b/Modix.Web/Pages/Tags.razor @@ -28,10 +28,16 @@ -
- Save -
- Cancel + + Save + + + Cancel
@@ -71,16 +77,19 @@ @code { [Inject] - ITagService TagService { get; set; } = null!; + private ITagService TagService { get; set; } = null!; [Inject] - DiscordUserService DiscordUserService { get; set; } = null!; + private DiscordUserService DiscordUserService { get; set; } = null!; [Inject] - SessionState SessionState { get; set; } = null!; + private SessionState SessionState { get; set; } = null!; [Inject] - IDialogService DialogService { get; set; } = null!; + private IDialogService DialogService { get; set; } = null!; + + [Inject] + private ISnackbar Snackbar { get; set; } = null!; TagData[]? Data { get; set; } string? query; @@ -140,12 +149,27 @@ private async Task SaveTag() { - var currentUser = DiscordUserService.GetCurrentUser(); - await TagService.CreateTagAsync(currentUser!.Guild.Id, currentUser.Id, _tagNameValue, _tagContentValue); - var createdTag = await TagService.GetTagAsync(currentUser.Guild.Id, _tagNameValue); - Data = Data!.Append(TagData.CreateFromSummary(createdTag)).ToArray(); + try + { + var currentUser = DiscordUserService.GetCurrentUser(); + + await TagService.CreateTagAsync(currentUser!.Guild.Id, currentUser.Id, _tagNameValue, _tagContentValue); + var createdTag = await TagService.GetTagAsync(currentUser.Guild.Id, _tagNameValue); + + Data = Data!.Append(TagData.CreateFromSummary(createdTag)).ToArray(); + Snackbar.Add($"Tag '{_tagNameValue}' created.", Severity.Success); + } + catch (Exception ex) + { + Snackbar.Add(ex.Message, Severity.Error); + } + finally + { + _tagNameValue = null; + _tagContentValue = null; - _createDialogVisible = false; + _createDialogVisible = false; + } } private void ToggleDialog() From 0708b77b2923edbbc904a15d55edb0cd405e58ab Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Wed, 2 Aug 2023 00:58:48 +0200 Subject: [PATCH 038/117] Allow null/empty comments for promotions Use MudSpacer instead of manual flex-grow --- Modix.Web/Components/ConfirmationDialog.razor | 5 ++--- Modix.Web/Components/EditPromotionCommentDialog.razor | 9 ++++----- Modix.Web/Pages/CreatePromotion.razor | 9 +-------- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/Modix.Web/Components/ConfirmationDialog.razor b/Modix.Web/Components/ConfirmationDialog.razor index 514af5323..e8304f28e 100644 --- a/Modix.Web/Components/ConfirmationDialog.razor +++ b/Modix.Web/Components/ConfirmationDialog.razor @@ -9,9 +9,8 @@ @Content -
- Confirm -
+ Confirm + Cancel
diff --git a/Modix.Web/Components/EditPromotionCommentDialog.razor b/Modix.Web/Components/EditPromotionCommentDialog.razor index 2b5c1638d..69bef32f1 100644 --- a/Modix.Web/Components/EditPromotionCommentDialog.razor +++ b/Modix.Web/Components/EditPromotionCommentDialog.razor @@ -15,14 +15,13 @@ - +
-
- Update -
- Cancel + Update + + Cancel
diff --git a/Modix.Web/Pages/CreatePromotion.razor b/Modix.Web/Pages/CreatePromotion.razor index 4ebfdfdf4..58a782845 100644 --- a/Modix.Web/Pages/CreatePromotion.razor +++ b/Modix.Web/Pages/CreatePromotion.razor @@ -48,14 +48,7 @@
- - Submit - + Submit } From da919b4466a1ea8ff76b1a711734f82ecac37e68 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Wed, 2 Aug 2023 01:00:35 +0200 Subject: [PATCH 039/117] Disable editing comments for closed campaigns Update UI after editing comment Better feedback after editing comment --- Modix.Web/Pages/Promotions.razor | 36 +++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index 03a7671c0..96bfde552 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -80,15 +80,22 @@ } else { - foreach(var comment in campaignCommentData[campaign.Id]) + foreach(var comment in campaignCommentData[campaign.Id].Values) { var sentimentIcon = comment.PromotionSentiment == PromotionSentiment.Approve ? Icons.Material.Filled.ThumbUp : Icons.Material.Filled.ThumbDown;
@comment.Content - @if (comment.IsFromCurrentUser) + @if (comment.IsFromCurrentUser && campaign.CloseAction is null) { - Edit + + Edit + } @comment.CreatedAt.ToString("dd/MM/yy, h:MM:ss tt")
@@ -128,11 +135,14 @@ [Inject] public IDialogService DialogService { get; set; } = null!; + [Inject] + public ISnackbar Snackbar { get; set; } = null!; + private ulong CurrentUserId { get; set; } private IReadOnlyCollection Campaigns = Array.Empty(); private Dictionary RoleColors = new Dictionary(); - private Dictionary> campaignCommentData = new Dictionary>(); + private Dictionary> campaignCommentData = new Dictionary>(); private bool _showInactive; @@ -173,12 +183,12 @@ campaignCommentData[campaignId] = result.Comments .Where(x => x.ModifyAction is null) .Select(c => new CampaignCommentData(c.Id, c.Sentiment, c.Content, c.CreateAction.Created, c.CreateAction.CreatedBy.Id == CurrentUserId)) - .ToList(); + .ToDictionary(x => x.Id, x => x); StateHasChanged(); } - private async Task ToggleEditDialog(long commentId, PromotionSentiment oldPromotionSentiment, string oldContent) + private async Task ToggleEditDialog(long campaignId, long commentId, PromotionSentiment oldPromotionSentiment, string oldContent) { var dialogParams = new DialogParameters { @@ -194,6 +204,18 @@ var (newPromotionSentiment, newContent) = ((PromotionSentiment, string))result.Data; - await PromotionsService.UpdateCommentAsync(commentId, newPromotionSentiment, newContent); + try + { + await PromotionsService.UpdateCommentAsync(commentId, newPromotionSentiment, newContent); + var campaignVoteData = campaignCommentData[campaignId][commentId]; + campaignCommentData[campaignId][commentId] = campaignVoteData with { Content = newContent, PromotionSentiment = newPromotionSentiment }; + } + catch (InvalidOperationException ex) + { + Snackbar.Add(ex.Message, Severity.Error); + return; + } + + Snackbar.Add("Campaign vote was updated.", Severity.Success); } } From c0e738d12e21da0dbd6bf2297f580a83f731985a Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Thu, 3 Aug 2023 02:42:53 +0200 Subject: [PATCH 040/117] Initial implementation of Configuration (Claims) --- .../Components/Configuration/Channels.razor | 5 + .../Components/Configuration/Claims.razor | 168 ++++++++++++++++++ .../Components/Configuration/Roles.razor | 5 + Modix.Web/Pages/Configuration.razor | 56 ++++++ Modix.Web/Shared/NavMenu.razor | 8 +- 5 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 Modix.Web/Components/Configuration/Channels.razor create mode 100644 Modix.Web/Components/Configuration/Claims.razor create mode 100644 Modix.Web/Components/Configuration/Roles.razor create mode 100644 Modix.Web/Pages/Configuration.razor diff --git a/Modix.Web/Components/Configuration/Channels.razor b/Modix.Web/Components/Configuration/Channels.razor new file mode 100644 index 000000000..6a5a3ca7b --- /dev/null +++ b/Modix.Web/Components/Configuration/Channels.razor @@ -0,0 +1,5 @@ +

Channels

+ +@code { + +} diff --git a/Modix.Web/Components/Configuration/Claims.razor b/Modix.Web/Components/Configuration/Claims.razor new file mode 100644 index 000000000..43478013a --- /dev/null +++ b/Modix.Web/Components/Configuration/Claims.razor @@ -0,0 +1,168 @@ +@using Modix.Data.Models.Core; +@using Modix.Data.Repositories; +@using Modix.Data.Utilities; +@using Modix.Models.Core; +@using Modix.Web.Models.UserLookup; +@using Modix.Web.Services; +@using MudBlazor +@using System.Reflection; +@using Humanizer; + +Modix - Claims +Claim Assignments + +@if (ClaimData is not null && MappedClaims is not null && Roles is not null && _selectedRole is not null) +{ + + + + + @foreach(var role in Roles.Values) + { + + + } + + + + + + @foreach (var claimData in ClaimData.OrderBy(x => x.Value.Name).GroupBy(x => x.Value.Category)) + { + @claimData.Key.ToString().Titleize() + foreach (var groupedClaimData in claimData) + { + MappedClaims.TryGetValue((_selectedRole, groupedClaimData.Key), out var mappedClaimForRole); + + +
+ @groupedClaimData.Value.Name.Titleize() + + + + X + + + + – + + + + ✓ + + +
+ @groupedClaimData.Value.Description + +
+ } + } +
+
+} + +@code { + [Inject] + public IClaimMappingRepository ClaimMappingRepository { get; set; } = null!; + + [Inject] + public DiscordUserService DiscordUserService { get; set; } = null!; + + [Inject] + public Modix.Services.Core.IAuthorizationService AuthorizationService { get; set; } = null!; + + [Inject] + public ISnackbar Snackbar { get; set; } = null!; + + private Dictionary? ClaimData { get; set; } + private Dictionary<(ulong?, AuthorizationClaim), ClaimMappingBrief>? MappedClaims { get; set; } + private Dictionary? Roles { get; set; } + + private ulong? _selectedRole; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (!firstRender) + return; + + ClaimData = typeof(AuthorizationClaim).GetFields(BindingFlags.Public | BindingFlags.Static).ToDictionary + ( + d => (AuthorizationClaim)d.GetValue(null)!, + d => + { + var claimInfo = (ClaimInfoAttribute)d.GetCustomAttributes(typeof(ClaimInfoAttribute), true).First()!; + + return new ClaimInfoData + { + Name = d.Name, + Description = claimInfo.Description, + Category = claimInfo.Category + }; + } + ); + + var currentGuild = DiscordUserService.GetUserGuild(); + var mappedClaims = await ClaimMappingRepository.SearchBriefsAsync(new ClaimMappingSearchCriteria + { + IsDeleted = false, + GuildId = currentGuild.Id + }); + + MappedClaims = mappedClaims.ToDictionary(x => (x.RoleId, x.Claim), x => x); + + Roles = currentGuild.Roles + .Select(d => new RoleInformation(d.Id, d.Name, d.Color.ToString())) + .OrderBy(x => x.Name) + .ToDictionary(x => x.Id, x => x); + + _selectedRole = Roles.First().Key; + + StateHasChanged(); + } + + private async Task ModifyMapping(ulong roleId, AuthorizationClaim authorizationClaim, ClaimMappingType? claimMappingType) + { + var key = (roleId, authorizationClaim); + if (MappedClaims!.TryGetValue(key, out var claimMapping) && claimMapping.Type == claimMappingType) + return; + + await AuthorizationService.ModifyClaimMappingAsync(roleId, authorizationClaim, claimMappingType); + if(claimMappingType is ClaimMappingType.Denied or ClaimMappingType.Granted) + { + Snackbar.Add($"Claim '{authorizationClaim}' for '{Roles![roleId].Name}' was changed to '{claimMappingType}'", Severity.Success); + if (claimMapping is null) + { + MappedClaims[key] = new ClaimMappingBrief + { + Claim = authorizationClaim, + RoleId = roleId, + Type = claimMappingType.Value + }; + } + else + { + claimMapping.Type = claimMappingType.Value; + } + } + else + { + Snackbar.Add($"Claim '{authorizationClaim}' for '{Roles![roleId].Name}' was removed.", Severity.Success); + MappedClaims.Remove(key); + } + } +} diff --git a/Modix.Web/Components/Configuration/Roles.razor b/Modix.Web/Components/Configuration/Roles.razor new file mode 100644 index 000000000..dda3b94f9 --- /dev/null +++ b/Modix.Web/Components/Configuration/Roles.razor @@ -0,0 +1,5 @@ +

Roles

+ +@code { + +} diff --git a/Modix.Web/Pages/Configuration.razor b/Modix.Web/Pages/Configuration.razor new file mode 100644 index 000000000..613bb41b3 --- /dev/null +++ b/Modix.Web/Pages/Configuration.razor @@ -0,0 +1,56 @@ +@page "/config" +@page "/config/{SubPage}" + +@attribute [Authorize( + Roles = $@" + {nameof(AuthorizationClaim.DesignatedRoleMappingRead)}, + {nameof(AuthorizationClaim.DesignatedChannelMappingRead)}, + {nameof(AuthorizationClaim.AuthorizationConfigure)}")] + +@using Modix.Data.Models.Core; +@using Modix.Web.Components +@using Modix.Web.Components.Configuration +@using MudBlazor + +Modix - Configuration + + + Logs + + + + + Roles + Channels + Claims + + +
+ @if (SubPage == "roles") + { + + + + } + else if (SubPage == "channels") + { + + + + } + else if (SubPage == "claims") + { + + + + } +
+
+
+ +@code { + + [Parameter] + public string? SubPage { get; set; } + +} diff --git a/Modix.Web/Shared/NavMenu.razor b/Modix.Web/Shared/NavMenu.razor index ba8c4012a..aa9f8c1f9 100644 --- a/Modix.Web/Shared/NavMenu.razor +++ b/Modix.Web/Shared/NavMenu.razor @@ -16,11 +16,17 @@ Commands User Lookup Tags + Promotions + - Logs + Logs + + + + Config
From 53c79d7decef5e0118971a21087129418dd78f3c Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Thu, 3 Aug 2023 03:29:45 +0200 Subject: [PATCH 041/117] Small touchup --- Modix.Web/Pages/Configuration.razor | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Modix.Web/Pages/Configuration.razor b/Modix.Web/Pages/Configuration.razor index 613bb41b3..0b5fbfcd1 100644 --- a/Modix.Web/Pages/Configuration.razor +++ b/Modix.Web/Pages/Configuration.razor @@ -15,10 +15,9 @@ Modix - Configuration - Logs - + Configuration - + Roles Channels From edc9ee8ab07d3cb50546992efe1f02dd932e8694 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Thu, 3 Aug 2023 03:30:09 +0200 Subject: [PATCH 042/117] Remove unnecessary stuff in MainLayout --- Modix.Web/Shared/MainLayout.razor | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Modix.Web/Shared/MainLayout.razor b/Modix.Web/Shared/MainLayout.razor index 4a0b42111..60cc2f672 100644 --- a/Modix.Web/Shared/MainLayout.razor +++ b/Modix.Web/Shared/MainLayout.razor @@ -7,18 +7,15 @@ -
-
- + - + - - @Body - - -
-
+ + @Body + + +
@code { From f4fa55d9c0c4036cbb5bf4eb005fb5706d84cec8 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Thu, 3 Aug 2023 16:41:30 +0200 Subject: [PATCH 043/117] Modify DesignatedRoleService API slightly to return the id of the created entity --- Modix.Services/Core/DesignatedRoleService.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Modix.Services/Core/DesignatedRoleService.cs b/Modix.Services/Core/DesignatedRoleService.cs index d5a12e415..0df61133e 100644 --- a/Modix.Services/Core/DesignatedRoleService.cs +++ b/Modix.Services/Core/DesignatedRoleService.cs @@ -21,7 +21,7 @@ public interface IDesignatedRoleService /// The Discord snowflake ID of the role being designated /// The type of designation to be made /// A that will complete when the operation has completed. - Task AddDesignatedRoleAsync(ulong guildId, ulong roleId, DesignatedRoleType type); + Task AddDesignatedRoleAsync(ulong guildId, ulong roleId, DesignatedRoleType type); /// /// Unassigns a role's previously given designation. @@ -95,7 +95,7 @@ public DesignatedRoleService(IAuthorizationService authorizationService, IDesign } /// - public async Task AddDesignatedRoleAsync(ulong guildId, ulong roleId, DesignatedRoleType type) + public async Task AddDesignatedRoleAsync(ulong guildId, ulong roleId, DesignatedRoleType type) { AuthorizationService.RequireAuthenticatedUser(); AuthorizationService.RequireClaims(AuthorizationClaim.DesignatedRoleMappingCreate); @@ -111,7 +111,7 @@ public async Task AddDesignatedRoleAsync(ulong guildId, ulong roleId, Designated }, default)) throw new InvalidOperationException($"Role {roleId} already has a {type} designation"); - await DesignatedRoleMappingRepository.CreateAsync(new DesignatedRoleMappingCreationData() + var entityId = await DesignatedRoleMappingRepository.CreateAsync(new DesignatedRoleMappingCreationData() { GuildId = guildId, RoleId = roleId, @@ -120,6 +120,8 @@ await DesignatedRoleMappingRepository.CreateAsync(new DesignatedRoleMappingCreat }); transaction.Commit(); + + return entityId; } } From 02e5392c322284ba31c94d40135a81f5ccbc0e7f Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Thu, 3 Aug 2023 16:43:46 +0200 Subject: [PATCH 044/117] Initial implementation of Configuration (Roles) --- .../Configuration/IndividualDesignation.razor | 70 ++++++++ .../Components/Configuration/Roles.razor | 156 +++++++++++++++++- .../Components/RoleSearchAutoComplete.razor | 36 ++++ .../Configuration/DesignatedRoleData.cs | 5 + Modix.Web/Services/DiscordUserService.cs | 21 ++- 5 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 Modix.Web/Components/Configuration/IndividualDesignation.razor create mode 100644 Modix.Web/Components/RoleSearchAutoComplete.razor create mode 100644 Modix.Web/Models/Configuration/DesignatedRoleData.cs diff --git a/Modix.Web/Components/Configuration/IndividualDesignation.razor b/Modix.Web/Components/Configuration/IndividualDesignation.razor new file mode 100644 index 000000000..c0c051ca8 --- /dev/null +++ b/Modix.Web/Components/Configuration/IndividualDesignation.razor @@ -0,0 +1,70 @@ +@using Modix.Data.Models.Core; +@using Modix.Web.Models.Configuration; +@using MudBlazor + + +@if (DesignatedRoleMapping is not null) +{ + + + @($"@{DesignatedRoleMapping.Name}") + + + + @if (!_showConfirm) + { + + X + + } + else + { + Remove Designation? + + + Yes + + + No + + } + + +} + + +@code { + + [Parameter, EditorRequired] + public DesignatedRoleData? DesignatedRoleMapping { get; set; } + + [Parameter, EditorRequired] + public EventCallback RemoveDesignation { get; set; } + + private bool _showConfirm; +} diff --git a/Modix.Web/Components/Configuration/Roles.razor b/Modix.Web/Components/Configuration/Roles.razor index dda3b94f9..13f46f89f 100644 --- a/Modix.Web/Components/Configuration/Roles.razor +++ b/Modix.Web/Components/Configuration/Roles.razor @@ -1,5 +1,159 @@ -

Roles

+@using Modix.Data.Models.Core; +@using Modix.Services.Core; +@using Modix.Web.Models.Configuration; +@using Modix.Web.Models.UserLookup; +@using Modix.Web.Services; +@using MudBlazor +@using Humanizer; +@using System.Security.Claims; + +Modix - Roles +Role Designations + + +@if(DesignatedRoleMappings is not null && DesignatedRoleTypes is not null) +{ + + + Assign a Role + + + + Designation + + @foreach (var designation in DesignatedRoleTypes) + { + + } + + + + + Assign + + + Cancel + + + + + + @foreach(var designatedRoleType in DesignatedRoleTypes.OrderBy(x => x.ToString())) + { + +
+
+ @designatedRoleType.ToString().Titleize() + @if (!DesignatedRoleMappings.ContainsKey(designatedRoleType)) + { + None Assigned + } + else + { + @foreach (var designatedRoleMapping in DesignatedRoleMappings[designatedRoleType]) + { + + } + } +
+ +
+ + + +
+
+
+ + } +
+
+} +
@code { + [Inject] + public DiscordUserService DiscordUserService { get; set; } = null!; + + [Inject] + public IDesignatedRoleService DesignatedRoleService { get; set; } = null!; + + [Inject] + public ISnackbar Snackbar { get; set; } = null!; + + private Dictionary>? DesignatedRoleMappings { get; set; } + private DesignatedRoleType[]? DesignatedRoleTypes { get; set; } + + private bool _createDialogVisible; + private DesignatedRoleType? _selectedDesignatedRoleType; + private RoleInformation? _selectedRole; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (!firstRender) + return; + + var currentGuild = DiscordUserService.GetUserGuild(); + var designatedRoles = await DesignatedRoleService.GetDesignatedRolesAsync(currentGuild.Id); + + DesignatedRoleMappings = designatedRoles + .Select(d => new DesignatedRoleData(d.Id, d.Role.Id, d.Type, currentGuild?.GetRole(d.Role.Id)?.Name ?? d.Role.Name)) + .ToLookup(x => x.RoleDesignation, x => x) + .ToDictionary(x => x.Key, x => x.ToList()); + + DesignatedRoleTypes = Enum.GetValues(); + + StateHasChanged(); + } + + public void ToggleCreateDialog() + { + _createDialogVisible = !_createDialogVisible; + if(_createDialogVisible) + { + _selectedRole = null; + _selectedDesignatedRoleType = null; + } + } + + private void SelectedRoleChanged(RoleInformation role) + { + _selectedRole = role; + } + + public async Task SaveDesignation() + { + var currentGuild = DiscordUserService.GetUserGuild(); + + var id = await DesignatedRoleService.AddDesignatedRoleAsync(currentGuild.Id, _selectedRole!.Id, _selectedDesignatedRoleType!.Value); + + _createDialogVisible = false; + + if (!DesignatedRoleMappings!.ContainsKey(_selectedDesignatedRoleType.Value)) + { + DesignatedRoleMappings[_selectedDesignatedRoleType.Value] = new List(); + } + + DesignatedRoleMappings[_selectedDesignatedRoleType.Value].Add(new DesignatedRoleData(id, _selectedRole.Id, _selectedDesignatedRoleType.Value, _selectedRole.Name)); + + Snackbar.Add($"Added designation '{_selectedDesignatedRoleType}' to role '{_selectedRole.Name}'", Severity.Success); + } + + public async Task RemoveDesignation(long id, DesignatedRoleType designatedRoleType) + { + await DesignatedRoleService.RemoveDesignatedRoleByIdAsync(id); + + var roleMappingsWithType = DesignatedRoleMappings![designatedRoleType]; + var removedRoleMapping = roleMappingsWithType.First(x => x.Id == id); + + roleMappingsWithType.Remove(removedRoleMapping); + Snackbar.Add($"Removed designation '{designatedRoleType}' from role '{removedRoleMapping.Name}'", Severity.Success); + } } diff --git a/Modix.Web/Components/RoleSearchAutoComplete.razor b/Modix.Web/Components/RoleSearchAutoComplete.razor new file mode 100644 index 000000000..2891465e2 --- /dev/null +++ b/Modix.Web/Components/RoleSearchAutoComplete.razor @@ -0,0 +1,36 @@ +@using Modix.Web.Models; +@using Modix.Web.Models.UserLookup; +@using Modix.Web.Services; +@using MudBlazor + +@Title + + + + +@code { + [Inject] + public DiscordUserService DiscordUserService { get; set; } = null!; + + [Parameter] + public EventCallback SelectedRoleChanged { get; set; } + + [Parameter] + public string? Title { get; set; } = "Role Name"; + + private Task> Search(string query) + { + return Task.FromResult(DiscordUserService.AutoCompleteRoles(query)); + } +} diff --git a/Modix.Web/Models/Configuration/DesignatedRoleData.cs b/Modix.Web/Models/Configuration/DesignatedRoleData.cs new file mode 100644 index 000000000..169c92c19 --- /dev/null +++ b/Modix.Web/Models/Configuration/DesignatedRoleData.cs @@ -0,0 +1,5 @@ +using Modix.Data.Models.Core; + +namespace Modix.Web.Models.Configuration; + +public record DesignatedRoleData(long Id, ulong RoleId, DesignatedRoleType RoleDesignation, string Name); diff --git a/Modix.Web/Services/DiscordUserService.cs b/Modix.Web/Services/DiscordUserService.cs index de5db2d75..bca610097 100644 --- a/Modix.Web/Services/DiscordUserService.cs +++ b/Modix.Web/Services/DiscordUserService.cs @@ -1,6 +1,8 @@ -using Discord.WebSocket; +using Discord; +using Discord.WebSocket; using Modix.Services.Core; using Modix.Web.Models; +using Modix.Web.Models.UserLookup; namespace Modix.Web.Services; @@ -71,4 +73,21 @@ public async Task> AutoCompleteAsync(string query) return result; } + public IEnumerable AutoCompleteRoles(string query) + { + if (query.StartsWith('@')) + { + query = query[1..]; + } + + var currentGuild = GetUserGuild(); + IEnumerable result = currentGuild.Roles; + + if (!string.IsNullOrWhiteSpace(query)) + { + result = result.Where(d => d.Name.Contains(query, StringComparison.OrdinalIgnoreCase)); + } + + return result.Take(10).Select(d => new RoleInformation(d.Id, d.Name, d.Color.ToString())); + } } From acc05cbad619deba41e08ca79644a913c7314f7a Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Thu, 3 Aug 2023 16:51:27 +0200 Subject: [PATCH 045/117] Slight improvement to styling of menu for Configuration and Logs --- Modix.Web/Pages/Configuration.razor | 29 +++++++++++++++++------------ Modix.Web/Pages/Logs.razor | 25 ++++++++++++++----------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/Modix.Web/Pages/Configuration.razor b/Modix.Web/Pages/Configuration.razor index 0b5fbfcd1..f65a3aeac 100644 --- a/Modix.Web/Pages/Configuration.razor +++ b/Modix.Web/Pages/Configuration.razor @@ -15,16 +15,21 @@ Modix - Configuration - Configuration - - - - Roles - Channels - Claims - - -
+ + + + Configuration + + + + + + + + + + + @if (SubPage == "roles") { @@ -43,8 +48,8 @@ } -
-
+ +
@code { diff --git a/Modix.Web/Pages/Logs.razor b/Modix.Web/Pages/Logs.razor index 771f3a87f..6799d9806 100644 --- a/Modix.Web/Pages/Logs.razor +++ b/Modix.Web/Pages/Logs.razor @@ -8,16 +8,19 @@ Modix - Logs - Logs - - - - Deletions - Infractions - - -
+ + + Logs + + + + + + + + + @if(SubPage == "infractions") { @@ -30,8 +33,8 @@ } -
-
+ +
@code { From 2142bf3beb7c0bbebc4570082f915d93b2393057 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Thu, 3 Aug 2023 17:29:18 +0200 Subject: [PATCH 046/117] Modify DesignatedChannelService API slightly to return the id of the created entity --- .../Core/DesignatedChannelService.cs | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/Modix.Services/Core/DesignatedChannelService.cs b/Modix.Services/Core/DesignatedChannelService.cs index 1bf3965a7..cc7f62129 100644 --- a/Modix.Services/Core/DesignatedChannelService.cs +++ b/Modix.Services/Core/DesignatedChannelService.cs @@ -1,15 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; - using Discord; - -using Serilog; - using Modix.Data.Models.Core; using Modix.Data.Repositories; -using System.Threading; +using Serilog; namespace Modix.Services.Core { @@ -25,7 +22,7 @@ public interface IDesignatedChannelService /// The channel to be assigned. /// The type of designation to be assigned. /// A that will complete when the operation has completed. - Task AddDesignatedChannelAsync(IGuild guild, IMessageChannel channel, DesignatedChannelType type); + Task AddDesignatedChannelAsync(IGuild guild, IMessageChannel channel, DesignatedChannelType type); /// /// Unassigns a channel's previously given designation, for a given guild. @@ -117,7 +114,7 @@ public DesignatedChannelService(IDesignatedChannelMappingRepository designatedCh } /// - public async Task AddDesignatedChannelAsync(IGuild guild, IMessageChannel logChannel, DesignatedChannelType type) + public async Task AddDesignatedChannelAsync(IGuild guild, IMessageChannel logChannel, DesignatedChannelType type) { AuthorizationService.RequireAuthenticatedUser(); AuthorizationService.RequireClaims(AuthorizationClaim.DesignatedChannelMappingCreate); @@ -135,7 +132,7 @@ public async Task AddDesignatedChannelAsync(IGuild guild, IMessageChannel logCha throw new InvalidOperationException($"{logChannel.Name} in {guild.Name} is already assigned to {type}"); } - await DesignatedChannelMappingRepository.CreateAsync(new DesignatedChannelMappingCreationData() + var id = await DesignatedChannelMappingRepository.CreateAsync(new DesignatedChannelMappingCreationData() { GuildId = guild.Id, ChannelId = logChannel.Id, @@ -144,6 +141,8 @@ await DesignatedChannelMappingRepository.CreateAsync(new DesignatedChannelMappin }); transaction.Commit(); + + return id; } } @@ -205,11 +204,11 @@ public Task AnyDesignatedChannelAsync(ulong guildId, DesignatedChannelType /// public Task> GetDesignatedChannelIdsAsync(ulong guildId, DesignatedChannelType type) => DesignatedChannelMappingRepository.SearchChannelIdsAsync(new DesignatedChannelMappingSearchCriteria() - { - GuildId = guildId, - Type = type, - IsDeleted = false - }); + { + GuildId = guildId, + Type = type, + IsDeleted = false + }); /// public async Task> GetDesignatedChannelsAsync(IGuild guild, DesignatedChannelType type) From ff7774508e803089d8046db13f378194bcf272e6 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Thu, 3 Aug 2023 17:29:57 +0200 Subject: [PATCH 047/117] Make IndividualDesignation component generic --- .../Configuration/IndividualDesignation.razor | 22 +++++++++++-------- .../Components/Configuration/Roles.razor | 5 ++++- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Modix.Web/Components/Configuration/IndividualDesignation.razor b/Modix.Web/Components/Configuration/IndividualDesignation.razor index c0c051ca8..65b458815 100644 --- a/Modix.Web/Components/Configuration/IndividualDesignation.razor +++ b/Modix.Web/Components/Configuration/IndividualDesignation.razor @@ -3,8 +3,6 @@ @using MudBlazor -@if (DesignatedRoleMapping is not null) -{ - @($"@{DesignatedRoleMapping.Name}") + @NamePrefix@Name - - + @if (!_showConfirm) { Remove Designation? -} @code { + [Parameter, EditorRequired] + public EventCallback RemoveDesignation { get; set; } [Parameter, EditorRequired] - public DesignatedRoleData? DesignatedRoleMapping { get; set; } + public string? AuthorizationRoleForDelete { get; set; } [Parameter, EditorRequired] - public EventCallback RemoveDesignation { get; set; } + public string? NamePrefix { get; set; } + + [Parameter, EditorRequired] + public string? Name { get; set; } + + [Parameter, EditorRequired] + public long Id { get; set; } private bool _showConfirm; } diff --git a/Modix.Web/Components/Configuration/Roles.razor b/Modix.Web/Components/Configuration/Roles.razor index 13f46f89f..31c013b50 100644 --- a/Modix.Web/Components/Configuration/Roles.razor +++ b/Modix.Web/Components/Configuration/Roles.razor @@ -56,7 +56,10 @@ @foreach (var designatedRoleMapping in DesignatedRoleMappings[designatedRoleType]) { } From 189ef63c18c01aeca8b0aa43ddd15bd07f8189cb Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Thu, 3 Aug 2023 17:30:52 +0200 Subject: [PATCH 048/117] Initial implementation of Configuration (Channels) --- .../ChannelSearchAutoComplete.razor | 37 ++++ .../Components/Configuration/Channels.razor | 160 +++++++++++++++++- Modix.Web/Models/Common/ChannelInformation.cs | 3 + .../Configuration/DesignatedChannelData.cs | 5 + Modix.Web/Services/DiscordUserService.cs | 16 ++ 5 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 Modix.Web/Components/ChannelSearchAutoComplete.razor create mode 100644 Modix.Web/Models/Common/ChannelInformation.cs create mode 100644 Modix.Web/Models/Configuration/DesignatedChannelData.cs diff --git a/Modix.Web/Components/ChannelSearchAutoComplete.razor b/Modix.Web/Components/ChannelSearchAutoComplete.razor new file mode 100644 index 000000000..8a2dbd609 --- /dev/null +++ b/Modix.Web/Components/ChannelSearchAutoComplete.razor @@ -0,0 +1,37 @@ +@using Modix.Web.Models; +@using Modix.Web.Models.Common; +@using Modix.Web.Models.UserLookup; +@using Modix.Web.Services; +@using MudBlazor + +@Title + + + + +@code { + [Inject] + public DiscordUserService DiscordUserService { get; set; } = null!; + + [Parameter] + public EventCallback SelectedChannelChanged { get; set; } + + [Parameter] + public string? Title { get; set; } = "Channel Name"; + + private Task> Search(string query) + { + return Task.FromResult(DiscordUserService.AutocompleteChannels(query)); + } +} diff --git a/Modix.Web/Components/Configuration/Channels.razor b/Modix.Web/Components/Configuration/Channels.razor index 6a5a3ca7b..00eb201f4 100644 --- a/Modix.Web/Components/Configuration/Channels.razor +++ b/Modix.Web/Components/Configuration/Channels.razor @@ -1,5 +1,163 @@ -

Channels

+@using Modix.Data.Models.Core; +@using Modix.Services.Core; +@using Modix.Web.Models.Common; +@using Modix.Web.Models.Configuration; +@using Modix.Web.Models.UserLookup; +@using Modix.Web.Services; +@using MudBlazor +@using Humanizer; +@using System.Security.Claims; + +Modix - Channels +Channel Designations + + + @if (DesignatedChannelMappings is not null && DesignatedChannelTypes is not null) + { + + + Assign a Channel + + + + Designation + + @foreach (var designation in DesignatedChannelTypes) + { + + } + + + + + Assign + + + Cancel + + + + + + @foreach (var designatedChannelType in DesignatedChannelTypes.OrderBy(x => x.ToString())) + { + +
+
+ @designatedChannelType.ToString().Titleize() + @if (!DesignatedChannelMappings.ContainsKey(designatedChannelType)) + { + None Assigned + } + else + { + @foreach (var designatedChannelMapping in DesignatedChannelMappings[designatedChannelType]) + { + + } + } +
+ +
+ + + +
+
+
+ + } +
+
+ } +
@code { + [Inject] + public DiscordUserService DiscordUserService { get; set; } = null!; + + [Inject] + public IDesignatedChannelService DesignatedChannelService { get; set; } = null!; + + [Inject] + public ISnackbar Snackbar { get; set; } = null!; + + private Dictionary>? DesignatedChannelMappings { get; set; } + private DesignatedChannelType[]? DesignatedChannelTypes { get; set; } + + private bool _createDialogVisible; + private DesignatedChannelType? _selectedDesignatedChannelType; + private ChannelInformation? _selectedChannel; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (!firstRender) + return; + + var currentGuild = DiscordUserService.GetUserGuild(); + var designatedChannels = await DesignatedChannelService.GetDesignatedChannelsAsync(currentGuild.Id); + + DesignatedChannelMappings = designatedChannels + .Select(d => new DesignatedChannelData(d.Id, d.Channel.Id, d.Type, currentGuild?.GetChannel(d.Channel.Id)?.Name ?? d.Channel.Name)) + .ToLookup(x => x.ChannelDesignation, x => x) + .ToDictionary(x => x.Key, x => x.ToList()); + + DesignatedChannelTypes = Enum.GetValues(); + + StateHasChanged(); + } + + public void ToggleCreateDialog() + { + _createDialogVisible = !_createDialogVisible; + if (_createDialogVisible) + { + _selectedChannel = null; + _selectedDesignatedChannelType = null; + } + } + + private void SelectedChannelChanged(ChannelInformation channel) + { + _selectedChannel = channel; + } + + public async Task SaveDesignation() + { + var currentGuild = DiscordUserService.GetUserGuild(); + var channel = (Discord.IMessageChannel)currentGuild.GetChannel(_selectedChannel!.Id); + + var id = await DesignatedChannelService.AddDesignatedChannelAsync(currentGuild, channel, _selectedDesignatedChannelType!.Value); + + _createDialogVisible = false; + + if (!DesignatedChannelMappings!.ContainsKey(_selectedDesignatedChannelType.Value)) + { + DesignatedChannelMappings[_selectedDesignatedChannelType.Value] = new List(); + } + + DesignatedChannelMappings[_selectedDesignatedChannelType.Value].Add(new DesignatedChannelData(id, _selectedChannel.Id, _selectedDesignatedChannelType.Value, _selectedChannel.Name)); + + Snackbar.Add($"Added designation '{_selectedDesignatedChannelType}' to channel '{_selectedChannel.Name}'", Severity.Success); + } + + public async Task RemoveDesignation(long id, DesignatedChannelType designatedChannelType) + { + await DesignatedChannelService.RemoveDesignatedChannelByIdAsync(id); + + var channelMappingsWithType = DesignatedChannelMappings![designatedChannelType]; + var removedChannelMapping = channelMappingsWithType.First(x => x.Id == id); + + channelMappingsWithType.Remove(removedChannelMapping); + Snackbar.Add($"Removed designation '{designatedChannelType}' from channel '{removedChannelMapping.Name}'", Severity.Success); + } } diff --git a/Modix.Web/Models/Common/ChannelInformation.cs b/Modix.Web/Models/Common/ChannelInformation.cs new file mode 100644 index 000000000..4862b3094 --- /dev/null +++ b/Modix.Web/Models/Common/ChannelInformation.cs @@ -0,0 +1,3 @@ +namespace Modix.Web.Models.Common; + +public record ChannelInformation(ulong Id, string Name); diff --git a/Modix.Web/Models/Configuration/DesignatedChannelData.cs b/Modix.Web/Models/Configuration/DesignatedChannelData.cs new file mode 100644 index 000000000..d2e8cbdbd --- /dev/null +++ b/Modix.Web/Models/Configuration/DesignatedChannelData.cs @@ -0,0 +1,5 @@ +using Modix.Data.Models.Core; + +namespace Modix.Web.Models.Configuration; + +public record DesignatedChannelData(long Id, ulong RoleId, DesignatedChannelType ChannelDesignation, string Name); diff --git a/Modix.Web/Services/DiscordUserService.cs b/Modix.Web/Services/DiscordUserService.cs index bca610097..95a7fe118 100644 --- a/Modix.Web/Services/DiscordUserService.cs +++ b/Modix.Web/Services/DiscordUserService.cs @@ -2,6 +2,7 @@ using Discord.WebSocket; using Modix.Services.Core; using Modix.Web.Models; +using Modix.Web.Models.Common; using Modix.Web.Models.UserLookup; namespace Modix.Web.Services; @@ -90,4 +91,19 @@ public IEnumerable AutoCompleteRoles(string query) return result.Take(10).Select(d => new RoleInformation(d.Id, d.Name, d.Color.ToString())); } + + public IEnumerable AutocompleteChannels(string query) + { + if (query.StartsWith('#')) + { + query = query[1..]; + } + + var currentGuild = GetUserGuild(); + return currentGuild.Channels + .Where(d => d is SocketTextChannel) + .Where(d => d.Name.Contains(query, StringComparison.OrdinalIgnoreCase)) + .Take(10) + .Select(d => new ChannelInformation(d.Id, d.Name)); + } } From 7af84ef6756a0e011d1460da6586c73b92df5f86 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Thu, 3 Aug 2023 18:17:18 +0200 Subject: [PATCH 049/117] Slight improvements to Channels/Roles configuration pages --- Modix.Web/Components/Configuration/Channels.razor | 4 ++-- Modix.Web/Components/Configuration/Roles.razor | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Modix.Web/Components/Configuration/Channels.razor b/Modix.Web/Components/Configuration/Channels.razor index 00eb201f4..cdc46e2ce 100644 --- a/Modix.Web/Components/Configuration/Channels.razor +++ b/Modix.Web/Components/Configuration/Channels.razor @@ -48,13 +48,13 @@
@designatedChannelType.ToString().Titleize() - @if (!DesignatedChannelMappings.ContainsKey(designatedChannelType)) + @if (!DesignatedChannelMappings.TryGetValue(designatedChannelType, out var channelDesignations) || !channelDesignations.Any()) { None Assigned } else { - @foreach (var designatedChannelMapping in DesignatedChannelMappings[designatedChannelType]) + @foreach (var designatedChannelMapping in channelDesignations) {
@designatedRoleType.ToString().Titleize() - @if (!DesignatedRoleMappings.ContainsKey(designatedRoleType)) + @if (!DesignatedRoleMappings.TryGetValue(designatedRoleType, out var roleDesignations) || !roleDesignations.Any()) { None Assigned } else { - @foreach (var designatedRoleMapping in DesignatedRoleMappings[designatedRoleType]) + @foreach (var designatedRoleMapping in roleDesignations) { Date: Thu, 3 Aug 2023 18:31:54 +0200 Subject: [PATCH 050/117] Make Autocomplete component generic --- Modix.Web/Components/AutoComplete.razor | 21 ++++++++++ Modix.Web/Components/AutoComplete.razor.cs | 27 +++++++++++++ .../ChannelSearchAutoComplete.razor | 37 ------------------ .../Components/Configuration/Channels.razor | 8 +++- .../Components/Configuration/Roles.razor | 9 ++++- Modix.Web/Components/Infractions.razor | 13 ++++++- .../Components/RoleSearchAutoComplete.razor | 36 ----------------- .../Components/UserSearchAutoComplete.razor | 39 ------------------- Modix.Web/Models/Common/ChannelInformation.cs | 2 +- Modix.Web/Models/Common/IAutoCompleteItem.cs | 6 +++ .../Models/UserLookup/RoleInformation.cs | 6 ++- Modix.Web/Pages/CreatePromotion.razor | 12 +++++- Modix.Web/Pages/UserLookup.razor | 12 +++++- 13 files changed, 108 insertions(+), 120 deletions(-) create mode 100644 Modix.Web/Components/AutoComplete.razor create mode 100644 Modix.Web/Components/AutoComplete.razor.cs delete mode 100644 Modix.Web/Components/ChannelSearchAutoComplete.razor delete mode 100644 Modix.Web/Components/RoleSearchAutoComplete.razor delete mode 100644 Modix.Web/Components/UserSearchAutoComplete.razor create mode 100644 Modix.Web/Models/Common/IAutoCompleteItem.cs diff --git a/Modix.Web/Components/AutoComplete.razor b/Modix.Web/Components/AutoComplete.razor new file mode 100644 index 000000000..09012fe0b --- /dev/null +++ b/Modix.Web/Components/AutoComplete.razor @@ -0,0 +1,21 @@ +@using Modix.Web.Models; +@using Modix.Web.Services; +@using MudBlazor + +@typeparam T + +@Title + + diff --git a/Modix.Web/Components/AutoComplete.razor.cs b/Modix.Web/Components/AutoComplete.razor.cs new file mode 100644 index 000000000..9442bf785 --- /dev/null +++ b/Modix.Web/Components/AutoComplete.razor.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Components; +using Modix.Web.Models.Common; +using Modix.Web.Services; + +namespace Modix.Web.Components +{ + public partial class AutoComplete where T : IAutoCompleteItem + { + [Inject] + public DiscordUserService DiscordUserService { get; set; } = null!; + + [Parameter] + public RenderFragment ItemTemplate { get; set; } + + [Parameter] + public string? Placeholder { get; set; } + + [Parameter] + public EventCallback SelectedItemChanged { get; set; } + + [Parameter, EditorRequired] + public string? Title { get; set; } + + [Parameter, EditorRequired] + public Func>> SearchFunc { get; set; } = null!; + } +} diff --git a/Modix.Web/Components/ChannelSearchAutoComplete.razor b/Modix.Web/Components/ChannelSearchAutoComplete.razor deleted file mode 100644 index 8a2dbd609..000000000 --- a/Modix.Web/Components/ChannelSearchAutoComplete.razor +++ /dev/null @@ -1,37 +0,0 @@ -@using Modix.Web.Models; -@using Modix.Web.Models.Common; -@using Modix.Web.Models.UserLookup; -@using Modix.Web.Services; -@using MudBlazor - -@Title - - - - -@code { - [Inject] - public DiscordUserService DiscordUserService { get; set; } = null!; - - [Parameter] - public EventCallback SelectedChannelChanged { get; set; } - - [Parameter] - public string? Title { get; set; } = "Channel Name"; - - private Task> Search(string query) - { - return Task.FromResult(DiscordUserService.AutocompleteChannels(query)); - } -} diff --git a/Modix.Web/Components/Configuration/Channels.razor b/Modix.Web/Components/Configuration/Channels.razor index cdc46e2ce..7009074c0 100644 --- a/Modix.Web/Components/Configuration/Channels.razor +++ b/Modix.Web/Components/Configuration/Channels.razor @@ -19,7 +19,13 @@ Assign a Channel - + Designation @foreach (var designation in DesignatedChannelTypes) diff --git a/Modix.Web/Components/Configuration/Roles.razor b/Modix.Web/Components/Configuration/Roles.razor index 4f3c9d8af..540757e20 100644 --- a/Modix.Web/Components/Configuration/Roles.razor +++ b/Modix.Web/Components/Configuration/Roles.razor @@ -18,7 +18,14 @@ Assign a Role - + + Designation @foreach (var designation in DesignatedRoleTypes) diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor index 4f5c9ae38..1fd548112 100644 --- a/Modix.Web/Components/Infractions.razor +++ b/Modix.Web/Components/Infractions.razor @@ -17,7 +17,18 @@ Create Infraction - + + + + @user.Name + + + Infraction
diff --git a/Modix.Web/Components/RoleSearchAutoComplete.razor b/Modix.Web/Components/RoleSearchAutoComplete.razor deleted file mode 100644 index 2891465e2..000000000 --- a/Modix.Web/Components/RoleSearchAutoComplete.razor +++ /dev/null @@ -1,36 +0,0 @@ -@using Modix.Web.Models; -@using Modix.Web.Models.UserLookup; -@using Modix.Web.Services; -@using MudBlazor - -@Title - - - - -@code { - [Inject] - public DiscordUserService DiscordUserService { get; set; } = null!; - - [Parameter] - public EventCallback SelectedRoleChanged { get; set; } - - [Parameter] - public string? Title { get; set; } = "Role Name"; - - private Task> Search(string query) - { - return Task.FromResult(DiscordUserService.AutoCompleteRoles(query)); - } -} diff --git a/Modix.Web/Components/UserSearchAutoComplete.razor b/Modix.Web/Components/UserSearchAutoComplete.razor deleted file mode 100644 index 292819df1..000000000 --- a/Modix.Web/Components/UserSearchAutoComplete.razor +++ /dev/null @@ -1,39 +0,0 @@ -@using Modix.Web.Models; -@using Modix.Web.Services; -@using MudBlazor - -@Title - - - - @user.Name - - - - -@code { - [Inject] - public DiscordUserService DiscordUserService { get; set; } = null!; - - [Parameter] - public EventCallback SelectedUserChanged { get; set; } - - [Parameter] - public string? Title { get; set; } = "Tell us their username"; - - private async Task> Search(string user) - { - return await DiscordUserService.AutoCompleteAsync(user); - } -} diff --git a/Modix.Web/Models/Common/ChannelInformation.cs b/Modix.Web/Models/Common/ChannelInformation.cs index 4862b3094..871dd49cf 100644 --- a/Modix.Web/Models/Common/ChannelInformation.cs +++ b/Modix.Web/Models/Common/ChannelInformation.cs @@ -1,3 +1,3 @@ namespace Modix.Web.Models.Common; -public record ChannelInformation(ulong Id, string Name); +public record ChannelInformation(ulong Id, string Name) : IAutoCompleteItem; diff --git a/Modix.Web/Models/Common/IAutoCompleteItem.cs b/Modix.Web/Models/Common/IAutoCompleteItem.cs new file mode 100644 index 000000000..a85da01c5 --- /dev/null +++ b/Modix.Web/Models/Common/IAutoCompleteItem.cs @@ -0,0 +1,6 @@ +namespace Modix.Web.Models.Common; + +public interface IAutoCompleteItem +{ + public string Name { get; } +} diff --git a/Modix.Web/Models/UserLookup/RoleInformation.cs b/Modix.Web/Models/UserLookup/RoleInformation.cs index 5c7ff4575..3e469a8f9 100644 --- a/Modix.Web/Models/UserLookup/RoleInformation.cs +++ b/Modix.Web/Models/UserLookup/RoleInformation.cs @@ -1,4 +1,6 @@ -namespace Modix.Web.Models.UserLookup; +using Modix.Web.Models.Common; -public record RoleInformation(ulong Id, string Name, string Color); +namespace Modix.Web.Models.UserLookup; + +public record RoleInformation(ulong Id, string Name, string Color) : IAutoCompleteItem; diff --git a/Modix.Web/Pages/CreatePromotion.razor b/Modix.Web/Pages/CreatePromotion.razor index 58a782845..c7224e945 100644 --- a/Modix.Web/Pages/CreatePromotion.razor +++ b/Modix.Web/Pages/CreatePromotion.razor @@ -26,7 +26,17 @@ - + + + + @user.Name + + @if(_selectedUser is not null && _nextRank is not null) { diff --git a/Modix.Web/Pages/UserLookup.razor b/Modix.Web/Pages/UserLookup.razor index e119a0334..61cff6f10 100644 --- a/Modix.Web/Pages/UserLookup.razor +++ b/Modix.Web/Pages/UserLookup.razor @@ -23,7 +23,17 @@
- + + + + @user.Name + +
@if (userInformation is not null) From c4e91aa5500f50f6c1da1ef6eaf7d6377f37b5a7 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Thu, 3 Aug 2023 18:35:28 +0200 Subject: [PATCH 051/117] Move ModixUser and RoleInformation into Common --- Modix.Web/Components/Configuration/Claims.razor | 1 + Modix.Web/Components/Configuration/Roles.razor | 1 + Modix.Web/Components/Infractions.razor | 1 + Modix.Web/Models/{ => Common}/ModixUser.cs | 4 ++-- Modix.Web/Models/{UserLookup => Common}/RoleInformation.cs | 4 +--- Modix.Web/Models/UserLookup/UserInformation.cs | 1 + Modix.Web/Pages/CreatePromotion.razor | 1 + Modix.Web/Pages/UserLookup.razor | 1 + Modix.Web/Services/DiscordUserService.cs | 1 - 9 files changed, 9 insertions(+), 6 deletions(-) rename Modix.Web/Models/{ => Common}/ModixUser.cs (89%) rename Modix.Web/Models/{UserLookup => Common}/RoleInformation.cs (54%) diff --git a/Modix.Web/Components/Configuration/Claims.razor b/Modix.Web/Components/Configuration/Claims.razor index 43478013a..934a528ff 100644 --- a/Modix.Web/Components/Configuration/Claims.razor +++ b/Modix.Web/Components/Configuration/Claims.razor @@ -7,6 +7,7 @@ @using MudBlazor @using System.Reflection; @using Humanizer; +@using Modix.Web.Models.Common; Modix - Claims Claim Assignments diff --git a/Modix.Web/Components/Configuration/Roles.razor b/Modix.Web/Components/Configuration/Roles.razor index 540757e20..402105563 100644 --- a/Modix.Web/Components/Configuration/Roles.razor +++ b/Modix.Web/Components/Configuration/Roles.razor @@ -6,6 +6,7 @@ @using MudBlazor @using Humanizer; @using System.Security.Claims; +@using Modix.Web.Models.Common; Modix - Roles Role Designations diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor index 1fd548112..20e6920ca 100644 --- a/Modix.Web/Components/Infractions.razor +++ b/Modix.Web/Components/Infractions.razor @@ -7,6 +7,7 @@ @using Modix.Web.Services; @using MudBlazor; @using System.Security.Claims; +@using Modix.Web.Models.Common; Modix - Infractions diff --git a/Modix.Web/Models/ModixUser.cs b/Modix.Web/Models/Common/ModixUser.cs similarity index 89% rename from Modix.Web/Models/ModixUser.cs rename to Modix.Web/Models/Common/ModixUser.cs index e5daa19ce..df1a8dd74 100644 --- a/Modix.Web/Models/ModixUser.cs +++ b/Modix.Web/Models/Common/ModixUser.cs @@ -1,9 +1,9 @@ using Discord; using Modix.Services.Utilities; -namespace Modix.Web.Models; +namespace Modix.Web.Models.Common; -public class ModixUser +public class ModixUser : IAutoCompleteItem { public string Name { get; set; } public ulong UserId { get; set; } diff --git a/Modix.Web/Models/UserLookup/RoleInformation.cs b/Modix.Web/Models/Common/RoleInformation.cs similarity index 54% rename from Modix.Web/Models/UserLookup/RoleInformation.cs rename to Modix.Web/Models/Common/RoleInformation.cs index 3e469a8f9..3c00f5f46 100644 --- a/Modix.Web/Models/UserLookup/RoleInformation.cs +++ b/Modix.Web/Models/Common/RoleInformation.cs @@ -1,6 +1,4 @@ -using Modix.Web.Models.Common; - -namespace Modix.Web.Models.UserLookup; +namespace Modix.Web.Models.Common; public record RoleInformation(ulong Id, string Name, string Color) : IAutoCompleteItem; diff --git a/Modix.Web/Models/UserLookup/UserInformation.cs b/Modix.Web/Models/UserLookup/UserInformation.cs index 85c451a8d..b3bcdcea6 100644 --- a/Modix.Web/Models/UserLookup/UserInformation.cs +++ b/Modix.Web/Models/UserLookup/UserInformation.cs @@ -1,6 +1,7 @@ using Discord; using Discord.WebSocket; using Modix.Data.Models.Core; +using Modix.Web.Models.Common; namespace Modix.Web.Models.UserLookup; diff --git a/Modix.Web/Pages/CreatePromotion.razor b/Modix.Web/Pages/CreatePromotion.razor index c7224e945..501300c80 100644 --- a/Modix.Web/Pages/CreatePromotion.razor +++ b/Modix.Web/Pages/CreatePromotion.razor @@ -4,6 +4,7 @@ @using Modix.Services.Promotions; @using Modix.Web.Components @using Modix.Web.Models; +@using Modix.Web.Models.Common; @using Modix.Web.Models.Promotions; @using Modix.Web.Services; @using MudBlazor diff --git a/Modix.Web/Pages/UserLookup.razor b/Modix.Web/Pages/UserLookup.razor index 61cff6f10..18cbd2f17 100644 --- a/Modix.Web/Pages/UserLookup.razor +++ b/Modix.Web/Pages/UserLookup.razor @@ -14,6 +14,7 @@ @using MudBlazor.Charts @using System.Globalization; @using Humanizer; +@using Modix.Web.Models.Common; Modix - User Lookup diff --git a/Modix.Web/Services/DiscordUserService.cs b/Modix.Web/Services/DiscordUserService.cs index 95a7fe118..5e2d63958 100644 --- a/Modix.Web/Services/DiscordUserService.cs +++ b/Modix.Web/Services/DiscordUserService.cs @@ -3,7 +3,6 @@ using Modix.Services.Core; using Modix.Web.Models; using Modix.Web.Models.Common; -using Modix.Web.Models.UserLookup; namespace Modix.Web.Services; From fb96f852c2d66d9d1d7f6f138f93e0036edd05c4 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Thu, 3 Aug 2023 21:02:00 +0200 Subject: [PATCH 052/117] Modify interface of PromotionsService to return the PromotionActionSummary after updating a comment Fix bug in Promotions UI where you could edit a comment and inadvertently have two active ones as a result --- .../Promotions/PromotionsService.cs | 6 ++- Modix.Web/Pages/Promotions.razor | 39 ++++++++++--------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/Modix.Services/Promotions/PromotionsService.cs b/Modix.Services/Promotions/PromotionsService.cs index 4af6c2796..394ab5072 100644 --- a/Modix.Services/Promotions/PromotionsService.cs +++ b/Modix.Services/Promotions/PromotionsService.cs @@ -59,7 +59,7 @@ public interface IPromotionsService /// The value of the updated comment. /// The value of the updated comment. /// A that will complete when the operation has completed. - Task UpdateCommentAsync(long commentId, PromotionSentiment newSentiment, string? newContent); + Task UpdateCommentAsync(long commentId, PromotionSentiment newSentiment, string? newContent); Task AddOrUpdateCommentAsync(long campaignId, Optional sentiment, Optional comment = default); @@ -237,7 +237,7 @@ public async Task AddCommentAsync(long campaignId, PromotionSentiment sentiment, } /// - public async Task UpdateCommentAsync(long commentId, PromotionSentiment newSentiment, string? newContent) + public async Task UpdateCommentAsync(long commentId, PromotionSentiment newSentiment, string? newContent) { AuthorizationService.RequireAuthenticatedUser(); AuthorizationService.RequireClaims(AuthorizationClaim.PromotionsComment); @@ -263,6 +263,8 @@ public async Task UpdateCommentAsync(long commentId, PromotionSentiment newSenti } PublishActionNotificationAsync(resultAction); + + return resultAction; } public async Task AddOrUpdateCommentAsync(long campaignId, Optional sentiment, Optional content = default) diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index 96bfde552..479c44cd7 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -80,24 +80,25 @@ } else { - foreach(var comment in campaignCommentData[campaign.Id].Values) + foreach(var comment in campaignCommentData[campaign.Id].Values.OrderByDescending(x => x.CreatedAt)) { var sentimentIcon = comment.PromotionSentiment == PromotionSentiment.Approve ? Icons.Material.Filled.ThumbUp : Icons.Material.Filled.ThumbDown; -
+
- @comment.Content - @if (comment.IsFromCurrentUser && campaign.CloseAction is null) - { - - Edit - - } - @comment.CreatedAt.ToString("dd/MM/yy, h:MM:ss tt") + @comment.Content + + @if (comment.IsFromCurrentUser && campaign.CloseAction is null) + { + + Edit + + } + @comment.CreatedAt.ToString("dd/MM/yy, h:MM:ss tt")
} @@ -206,9 +207,11 @@ try { - await PromotionsService.UpdateCommentAsync(commentId, newPromotionSentiment, newContent); - var campaignVoteData = campaignCommentData[campaignId][commentId]; - campaignCommentData[campaignId][commentId] = campaignVoteData with { Content = newContent, PromotionSentiment = newPromotionSentiment }; + var promotionActionSummary = await PromotionsService.UpdateCommentAsync(commentId, newPromotionSentiment, newContent); + var newComment = promotionActionSummary.NewComment; + + campaignCommentData[campaignId].Remove(commentId); + campaignCommentData[campaignId][newComment!.Id] = new CampaignCommentData(newComment.Id, newComment.Sentiment, newComment.Content, promotionActionSummary.Created, true); } catch (InvalidOperationException ex) { From 0b2a80f39bf590c98b8a84c0967a1c0a3ca003fc Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Fri, 4 Aug 2023 02:12:14 +0200 Subject: [PATCH 053/117] Implement possibility of passing query parameters to infractions page, such as ?id= and ?subject= Also redirect from /infractions to /logs/infractions --- Modix.Web/Components/Infractions.razor | 16 ++++++++++++++-- Modix.Web/Pages/Logs.razor | 26 ++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor index 20e6920ca..6ec895001 100644 --- a/Modix.Web/Components/Infractions.razor +++ b/Modix.Web/Components/Infractions.razor @@ -98,7 +98,7 @@ Id - + Type @@ -112,7 +112,7 @@ Created On Subject - + Creator @@ -163,6 +163,12 @@ @code { + [Parameter] + public string? Subject { get; set; } + + [Parameter] + public string? Id { get; set; } + [Inject] public IModerationService ModerationService { get; set; } = null!; @@ -197,6 +203,12 @@ private TableFilter _tableFilter = new(); + protected override void OnParametersSet() + { + _tableFilter.Subject = Subject; + _tableFilter.IdString = Id; + } + protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender) diff --git a/Modix.Web/Pages/Logs.razor b/Modix.Web/Pages/Logs.razor index 6799d9806..0f1aaab01 100644 --- a/Modix.Web/Pages/Logs.razor +++ b/Modix.Web/Pages/Logs.razor @@ -1,5 +1,6 @@ @page "/logs/{SubPage}" @page "/logs" +@page "/infractions" @attribute [Authorize(Policy = nameof(AuthorizationClaim.ModerationRead))] @using Modix.Data.Models.Core; @using Modix.Web.Components @@ -24,7 +25,7 @@ @if(SubPage == "infractions") { - + } else if(SubPage == "deletedMessages") @@ -39,5 +40,26 @@ @code { [Parameter] - public string SubPage { get; set; } = "infractions"; + public string SubPage { get; set; } + + [Inject] + public NavigationManager NavigationManager { get; set; } = null!; + + [Parameter] + [SupplyParameterFromQuery] + public string? Subject { get; set; } + + [Parameter] + [SupplyParameterFromQuery] + public string? Id { get; set; } + + protected override void OnAfterRender(bool firstRender) + { + var relativePath = NavigationManager.ToBaseRelativePath(NavigationManager.Uri); + + if (relativePath.StartsWith("infractions")) + { + NavigationManager.NavigateTo($"/logs/{relativePath}", new NavigationOptions { ReplaceHistoryEntry = true, ForceLoad = false }); + } + } } From 1d81a81575e9994c462b39a1e71cc5a35e8ce3f2 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Fri, 4 Aug 2023 02:13:52 +0200 Subject: [PATCH 054/117] Add possibility of accepting/rejecting/forcing a campaign as well as link to the infractions page for the campaign subject --- Modix.Web/Pages/Promotions.razor | 90 +++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index 479c44cd7..45e92b6d9 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -1,12 +1,16 @@ @page "/promotions" + @attribute [Authorize(Policy = nameof(AuthorizationClaim.PromotionsRead))] + @using Modix.Data.Models.Core; @using Modix.Data.Models.Promotions; +@using Modix.Data.Utilities; @using Modix.Services.Promotions; @using Modix.Web.Components; @using Modix.Web.Models.Promotions; @using Modix.Web.Services; @using MudBlazor +@using Humanizer; Modix - Promotions @@ -48,10 +52,29 @@ - @($"{campaign.Subject.Username + (campaign.Subject.Discriminator == "0000" ? "" : "#" + campaign.Subject.Discriminator)}") + @GetUsername(campaign.Subject) ➥ @campaign.TargetRole.Name
+ + @if (campaign.Outcome is null) + { + + + Accept + Reject + + + } + +
@@ -165,6 +188,13 @@ StateHasChanged(); } + private string GetUsername(GuildUserBrief guildUser) + { + var username = guildUser.Username; + var discriminator = guildUser.Discriminator == "0000" ? string.Empty : $"#{guildUser.Discriminator}"; + return $"{username}{discriminator}"; + } + private async Task CampaignExpanded(bool wasExpanded, long campaignId, ulong userId) { if (!wasExpanded) @@ -221,4 +251,62 @@ Snackbar.Add("Campaign vote was updated.", Severity.Success); } + + private async Task AcceptCampaign(PromotionCampaignSummary campaign) + { + var timeSince = DateTime.UtcNow - campaign.CreateAction.Created; + + var username = GetUsername(campaign.Subject); + bool force = false; + if (timeSince < PromotionCampaignEntityExtensions.CampaignAcceptCooldown) + { + var timeLeftHumanized = campaign.GetTimeUntilCampaignCanBeClosed().Humanize(3); + var dialogParams = new DialogParameters + { + { nameof(ConfirmationDialog.Content), $"There is {timeLeftHumanized} left on the campaign. Do you want to force accept the campaign for {username}?" } + }; + + var dialog = DialogService.Show("", dialogParams); + var confirmationResult = await dialog.Result; + + if (confirmationResult.Canceled) + { + Snackbar.Add("Action was cancelled", Severity.Info); + return; + } + + force = true; + } + + try + { + await PromotionsService.AcceptCampaignAsync(campaign.Id, force); + } + catch (InvalidOperationException ex) + { + Snackbar.Add(ex.Message, Severity.Error); + return; + } + + campaign.Outcome = PromotionCampaignOutcome.Accepted; + Snackbar.Add($"Campaign for '{username}' was accepted.", Severity.Success); + } + + private async Task RejectCampaign(PromotionCampaignSummary campaign) + { + try + { + await PromotionsService.RejectCampaignAsync(campaign.Id); + + } + catch (InvalidOperationException ex) + { + Snackbar.Add(ex.Message, Severity.Error); + return; + } + + var username = GetUsername(campaign.Subject); + campaign.Outcome = PromotionCampaignOutcome.Rejected; + Snackbar.Add($"Campaign for '{username}' was rejected.", Severity.Success); + } } From f3a53aedf270dadad365c89306388ce9cc9d7e32 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Fri, 4 Aug 2023 02:20:51 +0200 Subject: [PATCH 055/117] Simplify styling on Infractions page --- Modix.Web/Components/Infractions.razor | 39 ++++++++++++-------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor index 6ec895001..4cfb1b426 100644 --- a/Modix.Web/Components/Infractions.razor +++ b/Modix.Web/Components/Infractions.razor @@ -70,13 +70,15 @@ } -
Save -
+ OnClick="SaveInfraction" + > + Save + + Cancel
@@ -84,48 +86,43 @@ -
-
Create - Refresh -
-
- - -
-
+ Refresh + + +
- + Id - + Type - + @foreach(var infractionType in Enum.GetValues()) { } - Created On - + Created On + Subject - + Creator - Reason + Reason @if(_showState) { - State + State } @if (_canDeleteInfractions || _canRescind) { - Actions + Actions } From 635b4bae519b382d53dbc0eaf2777ebdf8539f1a Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Fri, 4 Aug 2023 12:54:24 +0200 Subject: [PATCH 056/117] Use iconbuttons instead for accept/reject --- Modix.Web/Pages/Promotions.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index 45e92b6d9..f307a7c75 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -61,8 +61,8 @@ { - Accept - Reject + + } From cd5cd8fb439f8f066b6386e8f5d952fe00235a9c Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 5 Aug 2023 00:37:24 +0200 Subject: [PATCH 057/117] Re-use existing extension method for getting full username --- Modix.Web/Pages/Promotions.razor | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index f307a7c75..29d06bbc3 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -11,6 +11,7 @@ @using Modix.Web.Services; @using MudBlazor @using Humanizer; +@using Modix.Services.Utilities; Modix - Promotions @@ -52,7 +53,7 @@ - @GetUsername(campaign.Subject) + @campaign.Subject.GetFullUsername() ➥ @campaign.TargetRole.Name
@@ -188,13 +189,6 @@ StateHasChanged(); } - private string GetUsername(GuildUserBrief guildUser) - { - var username = guildUser.Username; - var discriminator = guildUser.Discriminator == "0000" ? string.Empty : $"#{guildUser.Discriminator}"; - return $"{username}{discriminator}"; - } - private async Task CampaignExpanded(bool wasExpanded, long campaignId, ulong userId) { if (!wasExpanded) @@ -256,7 +250,7 @@ { var timeSince = DateTime.UtcNow - campaign.CreateAction.Created; - var username = GetUsername(campaign.Subject); + var username = campaign.Subject.GetFullUsername(); bool force = false; if (timeSince < PromotionCampaignEntityExtensions.CampaignAcceptCooldown) { @@ -305,7 +299,7 @@ return; } - var username = GetUsername(campaign.Subject); + var username = campaign.Subject.GetFullUsername(); campaign.Outcome = PromotionCampaignOutcome.Rejected; Snackbar.Add($"Campaign for '{username}' was rejected.", Severity.Success); } From 3f734e9d5ae8231a5d96f05ae0b6c15e86ccc774 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 5 Aug 2023 16:39:35 +0200 Subject: [PATCH 058/117] Initial implementation of Logs/DeletedMessages page --- Modix.Web/Components/DeletedMessages.razor | 267 +++++++++++++++++- .../DeletedMessageInformation.cs | 27 ++ .../Models/DeletedMessages/TableFilter.cs | 92 ++++++ 3 files changed, 384 insertions(+), 2 deletions(-) create mode 100644 Modix.Web/Models/DeletedMessages/DeletedMessageInformation.cs create mode 100644 Modix.Web/Models/DeletedMessages/TableFilter.cs diff --git a/Modix.Web/Components/DeletedMessages.razor b/Modix.Web/Components/DeletedMessages.razor index f2170892a..f51ce6c41 100644 --- a/Modix.Web/Components/DeletedMessages.razor +++ b/Modix.Web/Components/DeletedMessages.razor @@ -1,7 +1,270 @@ -Modix - Deletions +@using Discord.WebSocket; +@using Discord; +@using Modix.Data.Models.Moderation; +@using Modix.Data.Models; +@using Modix.Services.Moderation; +@using Modix.Web.Models.DeletedMessages; +@using Modix.Web.Services; +@using MudBlazor +@using Modix.Services.Utilities -

DeletedMessages

+Modix - Deletions + + + +
+ Batch Deletion Context + + +
+
+ + @if(!DeletedMessagesContext.TryGetValue(_currentContext, out var deletedMessageContext)) + { +
+ +
+ } + else if (!deletedMessageContext.Any()) + { + No messages + } + else + { + Starting + @deletedMessageContext.First().SentTime?.ToLocalTime().ToString("MM/dd/yy, h:mm:ss tt") + + + + + @foreach(var item in deletedMessageContext) + { + var wasDeleted = item.SentTime is null; + var styling = wasDeleted ? "background-color: #f5f5f5; border-top: 1px solid #fff" : ""; + var title = wasDeleted ? "This was deleted" : item.SentTime!.Value.ToLocalTime().ToString(); + +
+
+ @if(wasDeleted) + { + 🚫 + } + else + { + @item.SentTime!.Value.ToLocalTime().ToString("hh:mm") + } + + @item.Username +
+ @if(string.IsNullOrWhiteSpace(item.Content)) + { + No Content + } + else + { + + } +
+ } + } + +
+
+ + + + + Refresh + + + + Channel + + + + Author + + + + Deleted On + + + Deleted By + + + + Content + + + + Reason + + + + Batch ID + + + Actions + + + #@deletedMessage.Channel.Name + @deletedMessage.Author.GetFullUsername() + @deletedMessage.Created + @deletedMessage.CreatedBy.GetFullUsername() + + + + @deletedMessage.Reason + @deletedMessage.BatchId + + Context + + + + + + + + + + @code { + [Inject] + public IModerationService ModerationService { get; set; } = null!; + + [Inject] + public DiscordUserService DiscordUserService { get; set; } = null!; + + [Inject] + public ISnackbar Snackbar { get; set; } = null!; + + private MudTable TableRef; + private Dictionary> DeletedMessagesContext { get; } = new Dictionary>(); + private bool _deletedMessagesContextDialogVisible; + private long _currentContext; + + private TableFilter _tableFilter = new(); + + private async Task RefreshTable() => await TableRef.ReloadServerData(); + + private async Task FilterChanged(Action filterSetter) + { + filterSetter(); + await RefreshTable(); + } + + private async Task OpenDialog(long? batchId) + { + if (batchId is null) + return; + + _currentContext = batchId.Value; + _deletedMessagesContextDialogVisible = true; + + await GetDeletionContext(_currentContext); + } + + private void CloseDialog() => _deletedMessagesContextDialogVisible = false; + + private async Task> LoadDeletedMessages(TableState tableState) + { + var currentGuild = DiscordUserService.GetUserGuild(); + + var searchCriteria = new DeletedMessageSearchCriteria + { + GuildId = currentGuild.Id, + Channel = _tableFilter.Channel, + ChannelId = _tableFilter.ChannelId, + Author = _tableFilter.Author, + AuthorId = _tableFilter.AuthorId, + CreatedBy = _tableFilter.CreatedBy, + CreatedById = _tableFilter.CreatedById, + Content = _tableFilter.Content, + Reason = _tableFilter.Reason, + BatchId = _tableFilter.BatchId + }; + + var result = await ModerationService.SearchDeletedMessagesAsync(searchCriteria, + new[] + { + new SortingCriteria + { + PropertyName = tableState.SortLabel ?? nameof(DeletedMessageSummary.Created), + Direction = tableState.SortDirection == MudBlazor.SortDirection.Ascending + ? Data.Models.SortDirection.Ascending + : Data.Models.SortDirection.Descending + } + }, + new PagingCriteria + { + FirstRecordIndex = tableState.Page * tableState.PageSize, + PageSize = tableState.PageSize, + } + ); + + return new TableData + { + TotalItems = (int)result.FilteredRecordCount, + Items = result.Records + }; + } + + private async Task GetDeletionContext(long batchId) + { + if (DeletedMessagesContext.ContainsKey(batchId)) + return; + + _currentContext = batchId; + + var deletedMessages = await ModerationService.SearchDeletedMessagesAsync(new DeletedMessageSearchCriteria + { + BatchId = batchId + }, + new SortingCriteria[] + { + //Sort ascending, so the earliest message is first + new SortingCriteria { PropertyName = nameof(DeletedMessageSummary.MessageId), Direction = Data.Models.SortDirection.Ascending } + }, + new PagingCriteria() + ); + + var firstMessage = deletedMessages.Records.FirstOrDefault(); + + if (firstMessage is null) + { + Snackbar.Add($"Couldn't find messages for batch id {batchId}", Severity.Error); + return; + } + + var currentUser = DiscordUserService.GetCurrentUser(); + var batchChannelId = deletedMessages.Records.First().Channel.Id; + if (currentUser!.Guild.GetChannel(batchChannelId) is not ISocketMessageChannel foundChannel) + { + Snackbar.Add($"Couldn't recreate context - text channel with id {batchChannelId} not found", Severity.Error); + return; + } + + if (currentUser.GetPermissions(foundChannel as IGuildChannel).ReadMessageHistory == false) + { + Snackbar.Add($"You don't have read permissions for the channel this batch was deleted in (#{foundChannel.Name})", Severity.Error); + return; + } + + var beforeMessages = await foundChannel.GetMessagesAsync(firstMessage.MessageId, Discord.Direction.Before, 25).FlattenAsync(); + var afterMessages = await foundChannel.GetMessagesAsync(firstMessage.MessageId, Discord.Direction.After, 25 + (int)deletedMessages.FilteredRecordCount).FlattenAsync(); + + var allMessages = new List(); + allMessages.AddRange(deletedMessages.Records.Select(d => new DeletedMessageInformation(d.MessageId, null, null, d.Author.GetFullUsername(), d.Content))); + allMessages.AddRange(beforeMessages.Select(d => DeletedMessageInformation.FromIMessage(d))); + allMessages.AddRange(afterMessages.Select(d => DeletedMessageInformation.FromIMessage(d))); + + DeletedMessagesContext[batchId] = allMessages.OrderBy(d => d.MessageId).ToList(); + } + } diff --git a/Modix.Web/Models/DeletedMessages/DeletedMessageInformation.cs b/Modix.Web/Models/DeletedMessages/DeletedMessageInformation.cs new file mode 100644 index 000000000..1fdd0f979 --- /dev/null +++ b/Modix.Web/Models/DeletedMessages/DeletedMessageInformation.cs @@ -0,0 +1,27 @@ +using Discord; +using Humanizer.Bytes; +using Modix.Services.Utilities; + +namespace Modix.Web.Models.DeletedMessages; + +public record DeletedMessageInformation(ulong MessageId, DateTimeOffset? SentTime, string? Url, string Username, string Content) +{ + public static DeletedMessageInformation FromIMessage(IMessage message) + { + var content = message.Content; + + if (string.IsNullOrWhiteSpace(content)) + { + if (message.Embeds.Any()) + { + content = $"Embed: {message.Embeds.First().Title}: {message.Embeds.First().Description}"; + } + else if (message.Attachments.Any()) + { + content = $"Attachment: {message.Attachments.First().Filename} {ByteSize.FromBytes(message.Attachments.First().Size)}"; + } + } + + return new DeletedMessageInformation(message.Id, message.CreatedAt, message.GetJumpUrl(), message.Author.GetDisplayName(), content); + } +} diff --git a/Modix.Web/Models/DeletedMessages/TableFilter.cs b/Modix.Web/Models/DeletedMessages/TableFilter.cs new file mode 100644 index 000000000..2ab8420ba --- /dev/null +++ b/Modix.Web/Models/DeletedMessages/TableFilter.cs @@ -0,0 +1,92 @@ +namespace Modix.Web.Models.DeletedMessages; + +public class TableFilter +{ + private string? _author; + public string? Author + { + get => _author; + set + { + if (string.IsNullOrWhiteSpace(value)) + { + _author = null; + AuthorId = null; + } + else if (ulong.TryParse(value, out var subjectId)) + { + AuthorId = subjectId; + } + else + { + _author = value; + } + } + } + + public ulong? AuthorId { get; private set; } + + private string? _createdBy; + public string? CreatedBy + { + get => _createdBy; + set + { + if (string.IsNullOrWhiteSpace(value)) + { + _createdBy = null; + CreatedById = null; + } + else if (ulong.TryParse(value, out var createdById)) + { + CreatedById = createdById; + } + else + { + _createdBy = value; + } + } + } + + public ulong? CreatedById { get; private set; } + + private string? _channel; + public string? Channel + { + get => _channel; + set + { + if (string.IsNullOrWhiteSpace(value)) + { + _channel = null; + ChannelId = null; + } + else if (ulong.TryParse(value, out var channelId)) + { + ChannelId = channelId; + } + else + { + _channel = value; + } + } + } + + public long? BatchId { get; set; } + + public ulong? ChannelId { get; private set; } + public string? Content { get; set; } + public string? Reason { get; set; } + + public void SetBatchId(string? batchId) + { + if (!long.TryParse(batchId, out var id)) + { + BatchId = null; + return; + } + + BatchId = id; + } + +} From f1f4e6f9e2f6d81b67932840644b5c066660bba0 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 5 Aug 2023 18:02:45 +0200 Subject: [PATCH 059/117] Fix bug with filtering DeletedMessageEntity - When Batch is set, use that to filter on CreatedBy(Id) - Move AsExpandable() to sourceQuery --- .../Models/Moderation/DeletedMessageSearchCriteria.cs | 10 ++++++++-- Modix.Data/Repositories/DeletedMessageRepository.cs | 3 +-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Modix.Data/Models/Moderation/DeletedMessageSearchCriteria.cs b/Modix.Data/Models/Moderation/DeletedMessageSearchCriteria.cs index 072306b3d..00adfbd63 100644 --- a/Modix.Data/Models/Moderation/DeletedMessageSearchCriteria.cs +++ b/Modix.Data/Models/Moderation/DeletedMessageSearchCriteria.cs @@ -132,10 +132,16 @@ public static IQueryable FilterBy(this IQueryable ReusableQueries.StringContainsUser.Invoke(x.Author, criteria!.Author!), !string.IsNullOrWhiteSpace(criteria?.Author)) .FilterBy( - x => x.CreateAction.CreatedById == criteria!.CreatedById, + x => x.Batch == null + ? x.CreateAction.CreatedById == criteria!.CreatedById + : x.Batch.CreateAction.CreatedById == criteria!.CreatedById, criteria?.CreatedById != null) .FilterBy( - x => ReusableQueries.StringContainsUser.Invoke(x.CreateAction.CreatedBy!, criteria!.CreatedBy!), + x => ReusableQueries.StringContainsUser.Invoke( + x.Batch == null + ? x.CreateAction.CreatedBy! + : x.Batch.CreateAction.CreatedBy, + criteria!.CreatedBy!), !string.IsNullOrWhiteSpace(criteria?.CreatedBy)) .FilterBy( x => ReusableQueries.DbCaseInsensitiveContains.Invoke(x.Content, criteria!.Content!), diff --git a/Modix.Data/Repositories/DeletedMessageRepository.cs b/Modix.Data/Repositories/DeletedMessageRepository.cs index 9515409cb..770de2bc0 100644 --- a/Modix.Data/Repositories/DeletedMessageRepository.cs +++ b/Modix.Data/Repositories/DeletedMessageRepository.cs @@ -93,13 +93,12 @@ public async Task CreateAsync( public async Task> SearchSummariesPagedAsync( DeletedMessageSearchCriteria searchCriteria, IEnumerable sortingCriteria, PagingCriteria pagingCriteria) { - var sourceQuery = ModixContext.Set().AsNoTracking(); + var sourceQuery = ModixContext.Set().AsNoTracking().AsExpandable(); var filteredQuery = sourceQuery .FilterBy(searchCriteria); var pagedQuery = filteredQuery - .AsExpandable() .Select(DeletedMessageSummary.FromEntityProjection) .SortBy(sortingCriteria, DeletedMessageSummary.SortablePropertyMap) .OrderThenBy(x => x.MessageId, SortDirection.Ascending) From aefc94a55402fcee6b77ed99b06ed2f59388f029 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 5 Aug 2023 18:10:42 +0200 Subject: [PATCH 060/117] Set _currentContext before potential early bail Close dialog on error --- Modix.Web/Components/DeletedMessages.razor | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Modix.Web/Components/DeletedMessages.razor b/Modix.Web/Components/DeletedMessages.razor index f51ce6c41..95504dd05 100644 --- a/Modix.Web/Components/DeletedMessages.razor +++ b/Modix.Web/Components/DeletedMessages.razor @@ -217,11 +217,11 @@ private async Task GetDeletionContext(long batchId) { + _currentContext = batchId; + if (DeletedMessagesContext.ContainsKey(batchId)) return; - _currentContext = batchId; - var deletedMessages = await ModerationService.SearchDeletedMessagesAsync(new DeletedMessageSearchCriteria { BatchId = batchId @@ -238,6 +238,7 @@ if (firstMessage is null) { + CloseDialog(); Snackbar.Add($"Couldn't find messages for batch id {batchId}", Severity.Error); return; } @@ -246,12 +247,14 @@ var batchChannelId = deletedMessages.Records.First().Channel.Id; if (currentUser!.Guild.GetChannel(batchChannelId) is not ISocketMessageChannel foundChannel) { + CloseDialog(); Snackbar.Add($"Couldn't recreate context - text channel with id {batchChannelId} not found", Severity.Error); return; } if (currentUser.GetPermissions(foundChannel as IGuildChannel).ReadMessageHistory == false) { + CloseDialog(); Snackbar.Add($"You don't have read permissions for the channel this batch was deleted in (#{foundChannel.Name})", Severity.Error); return; } From 8935577a94761634f0ddbaff1039b32c9459318b Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 5 Aug 2023 18:37:23 +0200 Subject: [PATCH 061/117] Show role color if relevant in Tags grid --- Modix.Web/Pages/Tags.razor | 44 ++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/Modix.Web/Pages/Tags.razor b/Modix.Web/Pages/Tags.razor index 1fa6d73a2..c02adf0b1 100644 --- a/Modix.Web/Pages/Tags.razor +++ b/Modix.Web/Pages/Tags.razor @@ -4,6 +4,7 @@ @using Modix.Data.Models.Tags; @using Modix.Services.Tags; @using Modix.Web.Models; +@using Modix.Web.Models.Common; @using Modix.Web.Models.Tags; @using Modix.Web.Services; @using MudBlazor @@ -15,7 +16,7 @@ Tags - @if (Data is not null) + @if (Data is not null && Roles is not null) { @@ -44,9 +45,9 @@ - Create + Create - Refresh + Refresh @@ -60,7 +61,15 @@ @tag.Name @tag.Created.ToString("dd/MM/yy, h:MM:ss tt") - @tag.OwnerName + @if(tag.OwnerRole is not null) + { + var roleColor = Roles[tag.OwnerRole.Id].Color; + @@@tag.OwnerName + } + else + { + @tag.OwnerName + } @@ -82,20 +91,19 @@ [Inject] private DiscordUserService DiscordUserService { get; set; } = null!; - [Inject] - private SessionState SessionState { get; set; } = null!; - [Inject] private IDialogService DialogService { get; set; } = null!; [Inject] private ISnackbar Snackbar { get; set; } = null!; - TagData[]? Data { get; set; } - string? query; - string? _tagNameValue; - string? _tagContentValue; - bool _createDialogVisible; + private Dictionary? Roles { get; set; } + private TagData[]? Data { get; set; } + + private string? query; + private string? _tagNameValue; + private string? _tagContentValue; + private bool _createDialogVisible; protected override async Task OnAfterRenderAsync(bool firstRender) { @@ -109,22 +117,20 @@ private async Task FetchData() { - var currentGuild = SessionState.SelectedGuild; + var currentGuild = DiscordUserService.GetUserGuild(); var summaries = await TagService.GetSummariesAsync(new TagSearchCriteria { - GuildId = currentGuild, + GuildId = currentGuild.Id, }); Data = summaries .Select(TagData.CreateFromSummary) .ToArray(); - // foreach (var tag in data) - // { - // // TODO Revisit this functionality - // tag.CanMaintain = false; - // } + Roles = currentGuild.Roles + .Select(x => new RoleInformation(x.Id, x.Name, x.Color.ToString())) + .ToDictionary(x => x.Id, x => x); } private bool FilterFunction(TagData tag) From 84da6978534d970629f09fe052ba560a3215329e Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 5 Aug 2023 18:41:22 +0200 Subject: [PATCH 062/117] Enable query parameter for Tags page --- Modix.Web/Pages/Tags.razor | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Modix.Web/Pages/Tags.razor b/Modix.Web/Pages/Tags.razor index c02adf0b1..b4a48e09b 100644 --- a/Modix.Web/Pages/Tags.razor +++ b/Modix.Web/Pages/Tags.razor @@ -49,7 +49,7 @@ Refresh - + Name @@ -97,10 +97,13 @@ [Inject] private ISnackbar Snackbar { get; set; } = null!; + [Parameter] + [SupplyParameterFromQuery] + public string? Query { get; set; } + private Dictionary? Roles { get; set; } private TagData[]? Data { get; set; } - private string? query; private string? _tagNameValue; private string? _tagContentValue; private bool _createDialogVisible; @@ -135,19 +138,19 @@ private bool FilterFunction(TagData tag) { - if (string.IsNullOrWhiteSpace(query)) + if (string.IsNullOrWhiteSpace(Query)) return true; - if (tag.OwnerUser is not null && (tag.OwnerUser.Username.Contains(query, StringComparison.OrdinalIgnoreCase) || tag.OwnerUser.Id.ToString() == query)) + if (tag.OwnerUser is not null && (tag.OwnerUser.Username.Contains(Query, StringComparison.OrdinalIgnoreCase) || tag.OwnerUser.Id.ToString() == Query)) return true; - if (tag.OwnerRole is not null && (tag.OwnerRole.Name.Contains(query, StringComparison.OrdinalIgnoreCase) || tag.OwnerRole.Id.ToString() == query)) + if (tag.OwnerRole is not null && (tag.OwnerRole.Name.Contains(Query, StringComparison.OrdinalIgnoreCase) || tag.OwnerRole.Id.ToString() == Query)) return true; - if (tag.Name.Contains(query, StringComparison.OrdinalIgnoreCase)) + if (tag.Name.Contains(Query, StringComparison.OrdinalIgnoreCase)) return true; - if (tag.Content.Contains(query, StringComparison.OrdinalIgnoreCase)) + if (tag.Content.Contains(Query, StringComparison.OrdinalIgnoreCase)) return true; return false; From d139371c13f1e0cc5b3102f9b2d7cf64d7d9eb13 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 5 Aug 2023 18:47:25 +0200 Subject: [PATCH 063/117] Use generic version of DialogParameters when instantiating Dialogs --- Modix.Web/Components/Infractions.razor | 8 ++++---- Modix.Web/Pages/Promotions.razor | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor index 4cfb1b426..2cbdde08d 100644 --- a/Modix.Web/Components/Infractions.razor +++ b/Modix.Web/Components/Infractions.razor @@ -244,9 +244,9 @@ { try { - var dialogParams = new DialogParameters + var dialogParams = new DialogParameters { - { nameof(ConfirmationDialog.Content), $"Are you sure you want to rescind infraction #{infraction.Id}?"} + { x => x.Content, $"Are you sure you want to rescind infraction #{infraction.Id}?"} }; var dialog = DialogService.Show("", dialogParams); @@ -271,9 +271,9 @@ { try { - var dialogParams = new DialogParameters + var dialogParams = new DialogParameters { - { nameof(ConfirmationDialog.Content), $"Are you sure you want to delete infraction #{infraction.Id}?"} + { x => x.Content, $"Are you sure you want to delete infraction #{infraction.Id}?"} }; var dialog = DialogService.Show("", dialogParams); diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index 29d06bbc3..4a63f2af8 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -215,10 +215,10 @@ private async Task ToggleEditDialog(long campaignId, long commentId, PromotionSentiment oldPromotionSentiment, string oldContent) { - var dialogParams = new DialogParameters + var dialogParams = new DialogParameters { - { nameof(EditPromotionCommentDialog.PromotionSentiment), oldPromotionSentiment }, - { nameof(EditPromotionCommentDialog.Content), oldContent} + { x => x.PromotionSentiment, oldPromotionSentiment }, + { x => x.Content, oldContent} }; var dialog = DialogService.Show("", dialogParams); @@ -255,9 +255,9 @@ if (timeSince < PromotionCampaignEntityExtensions.CampaignAcceptCooldown) { var timeLeftHumanized = campaign.GetTimeUntilCampaignCanBeClosed().Humanize(3); - var dialogParams = new DialogParameters + var dialogParams = new DialogParameters { - { nameof(ConfirmationDialog.Content), $"There is {timeLeftHumanized} left on the campaign. Do you want to force accept the campaign for {username}?" } + { x => x.Content, $"There is {timeLeftHumanized} left on the campaign. Do you want to force accept the campaign for {username}?" } }; var dialog = DialogService.Show("", dialogParams); From 063d0bf8f8203e3aa898d6673907dd8f8bbef9e0 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 5 Aug 2023 18:51:02 +0200 Subject: [PATCH 064/117] Persist infraction table settings to local storage Move cookie constants into separate class --- Modix.Web/Components/Infractions.razor | 34 ++++++++++++++++++----- Modix.Web/Models/CookieConstants.cs | 8 ++++++ Modix.Web/Pages/_Host.cshtml | 4 +-- Modix.Web/Program.cs | 1 + Modix.Web/Security/ClaimsMiddleware.cs | 4 +-- Modix.Web/Services/CookieService.cs | 3 +- Modix.Web/Services/LocalStorageService.cs | 24 ++++++++++++++++ 7 files changed, 65 insertions(+), 13 deletions(-) create mode 100644 Modix.Web/Models/CookieConstants.cs create mode 100644 Modix.Web/Services/LocalStorageService.cs diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor index 2cbdde08d..c487df3ce 100644 --- a/Modix.Web/Components/Infractions.razor +++ b/Modix.Web/Components/Infractions.razor @@ -70,10 +70,10 @@ } - Save @@ -86,11 +86,11 @@ - Create + Create Refresh - - + + @@ -178,6 +178,9 @@ [Inject] public IDialogService DialogService { get; set; } = null!; + [Inject] + public LocalStorageService LocalStorage { get; set; } = null!; + [CascadingParameter] private Task? AuthState { get; set; } @@ -217,6 +220,23 @@ var auth = await AuthState; _canRescind = auth.User.HasClaim(ClaimTypes.Role, nameof(AuthorizationClaim.ModerationRescind)); _canDeleteInfractions = auth.User.HasClaim(ClaimTypes.Role, nameof(AuthorizationClaim.ModerationDeleteInfraction)); + + _tableFilter.ShowDeleted = await LocalStorage.GetBoolAsync("showDeleted"); + _showState = await LocalStorage.GetBoolAsync("showState"); + + StateHasChanged(); + } + + private async Task ShowStateChanged(bool showState) + { + _showState = showState; + await LocalStorage.SetBoolAsync(CookieConstants.ShowState, showState); + } + + private async Task ShowDeletedChanged(bool showDeleted) + { + await FilterChanged(() => _tableFilter.ShowDeleted = showDeleted); + await LocalStorage.SetBoolAsync(CookieConstants.ShowDeleted, showDeleted); } private void ToggleDialog() diff --git a/Modix.Web/Models/CookieConstants.cs b/Modix.Web/Models/CookieConstants.cs new file mode 100644 index 000000000..faa69e5ca --- /dev/null +++ b/Modix.Web/Models/CookieConstants.cs @@ -0,0 +1,8 @@ +namespace Modix.Web.Models; + +public static class CookieConstants +{ + public const string SelectedGuild = nameof(SelectedGuild); + public const string ShowState = nameof(ShowState); + public const string ShowDeleted = nameof(ShowDeleted); +} diff --git a/Modix.Web/Pages/_Host.cshtml b/Modix.Web/Pages/_Host.cshtml index bdb98050e..af7e1d625 100644 --- a/Modix.Web/Pages/_Host.cshtml +++ b/Modix.Web/Pages/_Host.cshtml @@ -1,6 +1,6 @@ @page "/" @using Microsoft.AspNetCore.Components.Web -@using Modix.Web.Services; +@using Modix.Web.Models; @namespace Modix.Web.Pages @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @@ -21,7 +21,7 @@ - +
diff --git a/Modix.Web/Program.cs b/Modix.Web/Program.cs index 389dcd6a6..cc69b1744 100644 --- a/Modix.Web/Program.cs +++ b/Modix.Web/Program.cs @@ -161,6 +161,7 @@ private static void ConfigureServices(WebApplicationBuilder builder, IConfigurat builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddMudServices(); builder.Services.AddMudMarkdownServices(); builder.Services.AddRazorPages(); diff --git a/Modix.Web/Security/ClaimsMiddleware.cs b/Modix.Web/Security/ClaimsMiddleware.cs index f79cf4eaa..908c22eff 100644 --- a/Modix.Web/Security/ClaimsMiddleware.cs +++ b/Modix.Web/Security/ClaimsMiddleware.cs @@ -1,7 +1,7 @@ using System.Security.Claims; using Discord.WebSocket; using Modix.Services.Core; -using Modix.Web.Services; +using Modix.Web.Models; namespace Modix.Web.Security; @@ -23,7 +23,7 @@ public async Task InvokeAsync(HttpContext context, IAuthorizationService authori return; } - var selectedGuild = context.Request.Cookies[CookieService.GuildCookieKey]; + var selectedGuild = context.Request.Cookies[CookieConstants.SelectedGuild]; _ = ulong.TryParse(selectedGuild, out var selectedGuildId); if (context.User.Identity is not ClaimsIdentity claimsIdentity) diff --git a/Modix.Web/Services/CookieService.cs b/Modix.Web/Services/CookieService.cs index 992a8b153..68dc585c7 100644 --- a/Modix.Web/Services/CookieService.cs +++ b/Modix.Web/Services/CookieService.cs @@ -7,7 +7,6 @@ public class CookieService { private readonly IJSRuntime _jsRuntime; private readonly SessionState _sessionState; - public const string GuildCookieKey = "SelectedGuild"; public CookieService(IJSRuntime jsRuntime, SessionState sessionState) { @@ -17,7 +16,7 @@ public CookieService(IJSRuntime jsRuntime, SessionState sessionState) public async Task SetSelectedGuildAsync(ulong guildId) { - await _jsRuntime.InvokeVoidAsync("eval", $"document.cookie = \"{GuildCookieKey}={guildId}; path=/\";"); + await _jsRuntime.InvokeVoidAsync("eval", $"document.cookie = \"{CookieConstants.SelectedGuild}={guildId}; path=/\";"); _sessionState.SelectedGuild = guildId; } } diff --git a/Modix.Web/Services/LocalStorageService.cs b/Modix.Web/Services/LocalStorageService.cs new file mode 100644 index 000000000..08ddd21f2 --- /dev/null +++ b/Modix.Web/Services/LocalStorageService.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage; + +namespace Modix.Web.Services; + +public class LocalStorageService +{ + private readonly ProtectedLocalStorage _localStorage; + + public LocalStorageService(ProtectedLocalStorage localStorage) + { + _localStorage = localStorage; + } + + public async Task GetBoolAsync(string key) + { + var result = await _localStorage.GetAsync(key); + return result.Value; + } + + public async Task SetBoolAsync(string key, bool value) + { + await _localStorage.SetAsync(key, value); + } +} From eb48e5e57e4835c1151f2b8314a837a6dcf0c093 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 5 Aug 2023 19:25:08 +0200 Subject: [PATCH 065/117] Slight styling improvements to stats page --- Modix.Web/Pages/Stats.razor | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/Modix.Web/Pages/Stats.razor b/Modix.Web/Pages/Stats.razor index b36649819..1b8859ede 100644 --- a/Modix.Web/Pages/Stats.razor +++ b/Modix.Web/Pages/Stats.razor @@ -10,18 +10,18 @@ @if(Data is not null) { - + Statistics for C# - - + + - + Role Distribution - + - + @foreach (var role in Data.GuildRoleCounts) { var channelColorStyle = $"border: 1px solid {role.Color}"; @@ -47,9 +47,9 @@ - + - + Most Active Users of the last 30 days @@ -70,7 +70,7 @@ @($"{rankSymbol ?? $"{stat.Rank}."} {username}") - @($"{stat.MessageCount} messages") + @stat.MessageCount messages } @@ -82,12 +82,6 @@ } - - @code { GuildStatData Data { get; set;} = null!; List GuildRoleCountView { get; set; } = null!; From b2af708eb427e70c77f85a178d32c04e7bb1ba0d Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 5 Aug 2023 21:25:34 +0200 Subject: [PATCH 066/117] Fix/consolidate date string formatting --- Modix.Web/Components/Infractions.razor | 2 +- Modix.Web/Pages/Promotions.razor | 4 ++-- Modix.Web/Pages/Tags.razor | 2 +- Modix.Web/Pages/UserLookup.razor | 14 +++++++------- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor index c487df3ce..64ec7c093 100644 --- a/Modix.Web/Components/Infractions.razor +++ b/Modix.Web/Components/Infractions.razor @@ -128,7 +128,7 @@ @infraction.Id @infraction.Type - @infraction.CreateAction.Created.ToString("dd/MM/yy, h:MM:ss tt") + @infraction.CreateAction.Created.ToString("MM/dd/yy, h:mm:ss tt") @(GetUsername(infraction.Subject)) @(GetUsername(infraction.CreateAction.CreatedBy)) @infraction.Reason diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index 4a63f2af8..96be2b663 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -92,7 +92,7 @@
- Campaign started @campaign.CreateAction.Created.ToString("dd/MM/yy, h:MM:ss tt") + Campaign started @campaign.CreateAction.Created.ToString("MM/dd/yy, h:mm:ss tt") @if(campaign.Subject.Id == CurrentUserId) { @@ -122,7 +122,7 @@ Edit } - @comment.CreatedAt.ToString("dd/MM/yy, h:MM:ss tt") + @comment.CreatedAt.ToString("MM/dd/yy, h:mm:ss tt")
} diff --git a/Modix.Web/Pages/Tags.razor b/Modix.Web/Pages/Tags.razor index b4a48e09b..530ded19b 100644 --- a/Modix.Web/Pages/Tags.razor +++ b/Modix.Web/Pages/Tags.razor @@ -60,7 +60,7 @@ @tag.Name - @tag.Created.ToString("dd/MM/yy, h:MM:ss tt") + @tag.Created.ToString("MM/dd/yy, h:mm:ss tt") @if(tag.OwnerRole is not null) { var roleColor = Roles[tag.OwnerRole.Id].Color; diff --git a/Modix.Web/Pages/UserLookup.razor b/Modix.Web/Pages/UserLookup.razor index 18cbd2f17..b2c51cf97 100644 --- a/Modix.Web/Pages/UserLookup.razor +++ b/Modix.Web/Pages/UserLookup.razor @@ -41,11 +41,11 @@ { User Information
- - - - - + + + + +
@@ -64,8 +64,8 @@ - - + +
From 96ded79a03082536d91e4cc2dccfb53fc771d32a Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sun, 6 Aug 2023 00:58:27 +0200 Subject: [PATCH 067/117] Responsive navmenu --- Modix.Web/Services/DiscordUserService.cs | 2 +- Modix.Web/Shared/MiniUser.razor | 93 +++++++++++++++ Modix.Web/Shared/NavMenu.razor | 145 ++++++----------------- Modix.Web/Shared/NavMenuLinks.razor | 52 ++++++++ 4 files changed, 179 insertions(+), 113 deletions(-) create mode 100644 Modix.Web/Shared/MiniUser.razor create mode 100644 Modix.Web/Shared/NavMenuLinks.razor diff --git a/Modix.Web/Services/DiscordUserService.cs b/Modix.Web/Services/DiscordUserService.cs index 5e2d63958..be3265fa3 100644 --- a/Modix.Web/Services/DiscordUserService.cs +++ b/Modix.Web/Services/DiscordUserService.cs @@ -30,7 +30,7 @@ public SocketGuild GetUserGuild() return _client.Guilds.First(); } - public IEnumerable GetGuildOptionsAsync() + public IEnumerable GetGuildOptions() { var currentUser = GetCurrentUser(); if (currentUser is null) diff --git a/Modix.Web/Shared/MiniUser.razor b/Modix.Web/Shared/MiniUser.razor new file mode 100644 index 000000000..bb1761eb8 --- /dev/null +++ b/Modix.Web/Shared/MiniUser.razor @@ -0,0 +1,93 @@ +@using AspNet.Security.OAuth.Discord; +@using Discord.WebSocket; +@using Modix.Web.Models; +@using Modix.Web.Services; +@using MudBlazor + +
+ @if (AvatarUrl is not null && Username is not null) + { + + +
+ + @Username + +
+
+ + Log Out + +
+ } + + + +
+ + +
+
+ + @foreach (var guildOption in GuildOptions) + { + + + @guildOption.Name + + } + +
+
+ + + + +@code { + [CascadingParameter] + public Task? AuthenticationState { get; set; } = null!; + + [Inject] + public DiscordUserService DiscordUserService { get; set; } = null!; + + private string? AvatarUrl { get; set; } + private string? Username { get; set; } + + private IEnumerable GuildOptions { get; set; } = Array.Empty(); + private SocketGuild? SelectedGuild { get; set; } + + [Inject] + public CookieService CookieService { get; set; } = null!; + + [Inject] + public NavigationManager NavigationManager { get; set; } = null!; + + protected override async Task OnInitializedAsync() + { + if (AuthenticationState is null) + return; + + var authState = await AuthenticationState; + if (!authState.User.Identity?.IsAuthenticated ?? false) + return; + + var avatarHash = authState.User.FindFirst(x => x.Type == DiscordAuthenticationConstants.Claims.AvatarHash)?.Value; + var user = DiscordUserService.GetCurrentUser(); + + AvatarUrl = $"https://cdn.discordapp.com/avatars/{user!.Id}/{avatarHash}.png"; + Username = authState.User.Identity?.Name; + + GuildOptions = DiscordUserService.GetGuildOptions(); + SelectedGuild = user.Guild; + } + + private async Task SelectGuild(ulong guildId) + { + await CookieService.SetSelectedGuildAsync(guildId); + NavigationManager.NavigateTo(NavigationManager.Uri, true); + } +} diff --git a/Modix.Web/Shared/NavMenu.razor b/Modix.Web/Shared/NavMenu.razor index aa9f8c1f9..e21d0f6bd 100644 --- a/Modix.Web/Shared/NavMenu.razor +++ b/Modix.Web/Shared/NavMenu.razor @@ -7,123 +7,44 @@ @using System.Security.Claims; - - - -
- Home - Stats - Commands - User Lookup - Tags - - Promotions - - - - Logs - - - - Config - -
- - - -
- @if (AvatarUrl is not null && Username is not null) - { - - -
- - @Username - -
-
- - Log Out - -
- } - - - -
- - -
-
- - @foreach (var guildOption in GuildOptions) - { - - - @guildOption.Name - - } - -
-
-
- -
- Home - Commands -
+
+ + + + + + + + + + + + +
+ +
+ + + + -
- Log In -
- -
-
+ + + +
-@code { - - [CascadingParameter] - public Task? AuthenticationState { get; set; } = null!; - - [Inject] - public DiscordUserService DiscordUserService { get; set; } = null!; - - [Inject] - public CookieService CookieService { get; set; } = null!; - - [Inject] - public NavigationManager NavigationManager { get; set; } = null!; - - private string? AvatarUrl { get; set; } - private string? Username { get; set; } - - private IEnumerable GuildOptions { get; set; } = Array.Empty(); - private SocketGuild? SelectedGuild { get; set; } - - protected override async Task OnInitializedAsync() - { - if (AuthenticationState is null) - return; - - var authState = await AuthenticationState; - if (!authState.User.Identity?.IsAuthenticated ?? false) - return; - - var avatarHash = authState.User.FindFirst(x => x.Type == DiscordAuthenticationConstants.Claims.AvatarHash)?.Value; - var user = DiscordUserService.GetCurrentUser(); + - AvatarUrl = $"https://cdn.discordapp.com/avatars/{user!.Id}/{avatarHash}.png"; - Username = authState.User.Identity?.Name; +@code { - GuildOptions = DiscordUserService.GetGuildOptionsAsync(); - SelectedGuild = user.Guild; - } + private bool _drawerVisible; - private async Task SelectGuild(ulong guildId) - { - await CookieService.SetSelectedGuildAsync(guildId); - NavigationManager.NavigateTo(NavigationManager.Uri, true); - } + private void ToggleDrawer() => _drawerVisible = !_drawerVisible; } diff --git a/Modix.Web/Shared/NavMenuLinks.razor b/Modix.Web/Shared/NavMenuLinks.razor new file mode 100644 index 000000000..c4ab36364 --- /dev/null +++ b/Modix.Web/Shared/NavMenuLinks.razor @@ -0,0 +1,52 @@ +@using AspNet.Security.OAuth.Discord; +@using Discord.WebSocket; +@using Modix.Data.Models.Core; +@using Modix.Web.Models; +@using Modix.Web.Services; +@using MudBlazor + + + + + Home + Stats + Commands + User Lookup + Tags + + + Promotions + + + + Logs + + + + Config + + + + +
+ Home + Commands +
+ +
+ Log In +
+
+
+
+ + + +@code { + +} From 4015fad91d071ea779a3821b08dbacd4a4aff700 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sun, 6 Aug 2023 01:49:02 +0200 Subject: [PATCH 068/117] Responsive commands page --- Modix.Web/Pages/Commands.razor | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Modix.Web/Pages/Commands.razor b/Modix.Web/Pages/Commands.razor index 025f564d3..092e989a2 100644 --- a/Modix.Web/Pages/Commands.razor +++ b/Modix.Web/Pages/Commands.razor @@ -6,8 +6,7 @@ Modix - Commands - - + @foreach (var module in Modules.OrderBy(m => m.Name)) { @@ -37,11 +36,12 @@ @foreach (var alias in command.Aliases) { -
+
@(command.IsSlashCommand ? '/' : '!')@alias.ToLower() @if (alias == command.Aliases.First()) { @command.Summary +
@foreach (var parameter in command.Parameters) { @*TODO: Add pointer styling?*@ @@ -78,6 +78,8 @@ }
} +
+ }
} @@ -85,9 +87,10 @@ } + } +
- @code { + [Inject] + public SessionState SessionState { get; set; } = null!; + + [Inject] + public CookieService CookieService { get; set; } = null!; + [Inject] public DiscordHelper DiscordHelper { get; set; } = null!; @@ -171,6 +178,11 @@ private bool _showInactive; + protected override void OnInitialized() + { + _showInactive = SessionState.ShowInactivePromotions; + } + protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender) @@ -189,6 +201,12 @@ StateHasChanged(); } + private async Task ShowInactiveChanged(bool showInactive) + { + _showInactive = showInactive; + await CookieService.SetShowInactivePromotionsAsync(showInactive); + } + private async Task CampaignExpanded(bool wasExpanded, long campaignId, ulong userId) { if (!wasExpanded) diff --git a/Modix.Web/Pages/_Host.cshtml b/Modix.Web/Pages/_Host.cshtml index af7e1d625..8d7c1f541 100644 --- a/Modix.Web/Pages/_Host.cshtml +++ b/Modix.Web/Pages/_Host.cshtml @@ -21,7 +21,12 @@ - +
diff --git a/Modix.Web/Services/CookieService.cs b/Modix.Web/Services/CookieService.cs index 68dc585c7..0658b8a3f 100644 --- a/Modix.Web/Services/CookieService.cs +++ b/Modix.Web/Services/CookieService.cs @@ -16,7 +16,30 @@ public CookieService(IJSRuntime jsRuntime, SessionState sessionState) public async Task SetSelectedGuildAsync(ulong guildId) { - await _jsRuntime.InvokeVoidAsync("eval", $"document.cookie = \"{CookieConstants.SelectedGuild}={guildId}; path=/\";"); + await SetCookieAsync(CookieConstants.SelectedGuild, guildId); _sessionState.SelectedGuild = guildId; } + + public async Task SetShowDeletedInfractionsAsync(bool showDeleted) + { + await SetCookieAsync(CookieConstants.ShowDeletedInfractions, showDeleted); + _sessionState.ShowDeletedInfractions = showDeleted; + } + + public async Task SetShowInfractionStateAsync(bool showInfractionState) + { + await SetCookieAsync(CookieConstants.ShowInfractionState, showInfractionState); + _sessionState.ShowInfractionState = showInfractionState; + } + + public async Task SetShowInactivePromotionsAsync(bool showInactivePromotions) + { + await SetCookieAsync(CookieConstants.ShowInactivePromotions, showInactivePromotions); + _sessionState.ShowInactivePromotions = showInactivePromotions; + } + + private async Task SetCookieAsync(string key, T value) + { + await _jsRuntime.InvokeVoidAsync("eval", $"document.cookie = \"{key}={value}; path=/\";"); + } } diff --git a/Modix.Web/Services/LocalStorageService.cs b/Modix.Web/Services/LocalStorageService.cs deleted file mode 100644 index 08ddd21f2..000000000 --- a/Modix.Web/Services/LocalStorageService.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage; - -namespace Modix.Web.Services; - -public class LocalStorageService -{ - private readonly ProtectedLocalStorage _localStorage; - - public LocalStorageService(ProtectedLocalStorage localStorage) - { - _localStorage = localStorage; - } - - public async Task GetBoolAsync(string key) - { - var result = await _localStorage.GetAsync(key); - return result.Value; - } - - public async Task SetBoolAsync(string key, bool value) - { - await _localStorage.SetAsync(key, value); - } -} diff --git a/Modix.Web/Setup.cs b/Modix.Web/Setup.cs index fbeb2ba85..fe57d27a1 100644 --- a/Modix.Web/Setup.cs +++ b/Modix.Web/Setup.cs @@ -45,7 +45,6 @@ public static IServiceCollection ConfigureBlazorServices(this IServiceCollection services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddMudServices(); services.AddMudMarkdownServices(); From 3e45eb1224393c2f30cbf505d7fc8d693f57bed8 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sun, 6 Aug 2023 21:22:57 +0200 Subject: [PATCH 083/117] Remove unused package/project references --- Modix.Web/Modix.Web.csproj | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Modix.Web/Modix.Web.csproj b/Modix.Web/Modix.Web.csproj index 379c5fa64..8b04da70f 100644 --- a/Modix.Web/Modix.Web.csproj +++ b/Modix.Web/Modix.Web.csproj @@ -10,19 +10,11 @@ - - - - - - - - From 4e0f930754274cb8532d9a0269a71809731704fa Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sun, 6 Aug 2023 21:30:24 +0200 Subject: [PATCH 084/117] Add favicon --- Modix.Web/Pages/_Host.cshtml | 2 +- Modix.Web/wwwroot/favicon.ico | Bin 0 -> 1150 bytes Modix.Web/wwwroot/favicon.png | Bin 1148 -> 0 bytes 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 Modix.Web/wwwroot/favicon.ico delete mode 100644 Modix.Web/wwwroot/favicon.png diff --git a/Modix.Web/Pages/_Host.cshtml b/Modix.Web/Pages/_Host.cshtml index 8d7c1f541..086a84c6d 100644 --- a/Modix.Web/Pages/_Host.cshtml +++ b/Modix.Web/Pages/_Host.cshtml @@ -12,7 +12,7 @@ - + diff --git a/Modix.Web/wwwroot/favicon.ico b/Modix.Web/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..80429cc0b24fab0731c95da6f2062ec4657629cb GIT binary patch literal 1150 zcmZ{iKWGzS7{=dAE_caYE|=?Fb4^0hh@g^6)gUM?R#0n^(pC^b5C<1mLHx6{!J&Vg z9EymmI=P6GlV}GOI(2XmTcoI+MC_KW{@$bwt+p4QyYGA7=Xu`u`|^qy{PKC>U6Y%p zNI^sn0YXGNaXupCLbrqV{-0N&ph}TUCgWR{bqb8ywmsz?Ng;4^L*#*#uk zlF6i;A0F;{oMTN~(wR&><2VO(4UVQ#soAXO`N~hFtOoj3`gj(GVaoGzU40bw%CSbn zFyV97Ax%(o8r;=&7)JULeCF|3<7@-jY_^`u<&1KWqUs)HtAiXqVAVf@ye*XnoZdOQkLUDL5_qyzV&e2{cXc1paII zpZU&NmVE)vPuBAr8ym7UFYxpX`^f44^qu`#+fLWG(<}JhAlCpV$t{8(^mQB$8JoD& zuD#7?i2OIOq<-+ETJ2BZah2FA_{I8~*7kMaw>?2mPkTr0VR&o!-_rnlouA)^kyzAR z9LEVg_ij&q(-ZA|@QOQ%Ir{>5!Tgt1(_gFYChf-PTkm2&eLkiCQG8x9o4tCL<&~8# zZ?A>U8*^(jXCq(?uOU2SaPY`pk{xmFuTY4kudirwM?U|*%}DIvY~Qp_i~Q*qdFP9K dnh}|M-@z>3ra@eh6j=r9wnz)EYFbkr`~~2jXl(!h literal 0 HcmV?d00001 diff --git a/Modix.Web/wwwroot/favicon.png b/Modix.Web/wwwroot/favicon.png deleted file mode 100644 index 8422b59695935d180d11d5dbe99653e711097819..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1148 zcmV-?1cUpDP)9h26h2-Cs%i*@Moc3?#6qJID|D#|3|2Hn7gTIYEkr|%Xjp);YgvFmB&0#2E2b=| zkVr)lMv9=KqwN&%obTp-$<51T%rx*NCwceh-E+=&e(oLO`@Z~7gybJ#U|^tB2Pai} zRN@5%1qsZ1e@R(XC8n~)nU1S0QdzEYlWPdUpH{wJ2Pd4V8kI3BM=)sG^IkUXF2-j{ zrPTYA6sxpQ`Q1c6mtar~gG~#;lt=s^6_OccmRd>o{*=>)KS=lM zZ!)iG|8G0-9s3VLm`bsa6e ze*TlRxAjXtm^F8V`M1%s5d@tYS>&+_ga#xKGb|!oUBx3uc@mj1%=MaH4GR0tPBG_& z9OZE;->dO@`Q)nr<%dHAsEZRKl zedN6+3+uGHejJp;Q==pskSAcRcyh@6mjm2z-uG;s%dM-u0*u##7OxI7wwyCGpS?4U zBFAr(%GBv5j$jS@@t@iI8?ZqE36I^4t+P^J9D^ELbS5KMtZ z{Qn#JnSd$15nJ$ggkF%I4yUQC+BjDF^}AtB7w348EL>7#sAsLWs}ndp8^DsAcOIL9 zTOO!!0!k2`9BLk25)NeZp7ev>I1Mn={cWI3Yhx2Q#DnAo4IphoV~R^c0x&nw*MoIV zPthX?{6{u}sMS(MxD*dmd5rU(YazQE59b|TsB5Tm)I4a!VaN@HYOR)DwH1U5y(E)z zQqQU*B%MwtRQ$%x&;1p%ANmc|PkoFJZ%<-uq%PX&C!c-7ypis=eP+FCeuv+B@h#{4 zGx1m0PjS~FJt}3mdt4c!lel`1;4W|03kcZRG+DzkTy|7-F~eDsV2Tx!73dM0H0CTh zl)F-YUkE1zEzEW(;JXc|KR5{ox%YTh{$%F$a36JP6Nb<0%#NbSh$dMYF-{ z1_x(Vx)}fs?5_|!5xBTWiiIQHG<%)*e=45Fhjw_tlnmlixq;mUdC$R8v#j( zhQ$9YR-o%i5Uc`S?6EC51!bTRK=Xkyb<18FkCKnS2;o*qlij1YA@-nRpq#OMTX&RbL<^2q@0qja!uIvI;j$6>~k@IMwD42=8$$!+R^@5o6HX(*n~ Date: Sun, 6 Aug 2023 21:38:04 +0200 Subject: [PATCH 085/117] Use Roles instead of Policy authorization --- Modix.Web/Components/Infractions.razor | 8 ++++---- Modix.Web/Pages/Configuration.razor | 6 +++--- Modix.Web/Pages/CreatePromotion.razor | 2 +- Modix.Web/Pages/Logs.razor | 6 +++--- Modix.Web/Pages/Promotions.razor | 2 +- Modix.Web/Pages/Tags.razor | 2 +- Modix.Web/Setup.cs | 13 +------------ Modix.Web/Shared/NavMenuLinks.razor | 4 ++-- 8 files changed, 16 insertions(+), 27 deletions(-) diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor index 5daa1e0e3..3178a1c7e 100644 --- a/Modix.Web/Components/Infractions.razor +++ b/Modix.Web/Components/Infractions.razor @@ -34,16 +34,16 @@
- + - + - + - + diff --git a/Modix.Web/Pages/Configuration.razor b/Modix.Web/Pages/Configuration.razor index 69ba6f04f..80f35e3d9 100644 --- a/Modix.Web/Pages/Configuration.razor +++ b/Modix.Web/Pages/Configuration.razor @@ -32,19 +32,19 @@ @if (SubPage == "roles") { - + } else if (SubPage == "channels") { - + } else if (SubPage == "claims") { - + } diff --git a/Modix.Web/Pages/CreatePromotion.razor b/Modix.Web/Pages/CreatePromotion.razor index 357381d33..a5854743b 100644 --- a/Modix.Web/Pages/CreatePromotion.razor +++ b/Modix.Web/Pages/CreatePromotion.razor @@ -1,5 +1,5 @@ @page "/promotions/create" -@attribute [Authorize(Policy = nameof(AuthorizationClaim.PromotionsCreateCampaign))] +@attribute [Authorize(Roles = nameof(AuthorizationClaim.PromotionsCreateCampaign))] @using Modix.Data.Models.Core; @using Modix.Services.Promotions; @using Modix.Web.Components diff --git a/Modix.Web/Pages/Logs.razor b/Modix.Web/Pages/Logs.razor index 49f5174af..79f8082bd 100644 --- a/Modix.Web/Pages/Logs.razor +++ b/Modix.Web/Pages/Logs.razor @@ -1,7 +1,7 @@ @page "/logs/{SubPage}" @page "/logs" @page "/infractions" -@attribute [Authorize(Policy = nameof(AuthorizationClaim.ModerationRead))] +@attribute [Authorize(Roles = nameof(AuthorizationClaim.ModerationRead))] @using Modix.Data.Models.Core; @using Modix.Web.Components @using MudBlazor @@ -24,13 +24,13 @@ @if(SubPage == "infractions") { - + } else if(SubPage == "deletedMessages") { - + } diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index f31a31b9f..fff4f0d12 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -1,6 +1,6 @@ @page "/promotions" -@attribute [Authorize(Policy = nameof(AuthorizationClaim.PromotionsRead))] +@attribute [Authorize(Roles = nameof(AuthorizationClaim.PromotionsRead))] @using Modix.Data.Models.Core; @using Modix.Data.Models.Promotions; diff --git a/Modix.Web/Pages/Tags.razor b/Modix.Web/Pages/Tags.razor index 25bd37652..04f6aa944 100644 --- a/Modix.Web/Pages/Tags.razor +++ b/Modix.Web/Pages/Tags.razor @@ -44,7 +44,7 @@ - + Create Refresh diff --git a/Modix.Web/Setup.cs b/Modix.Web/Setup.cs index fe57d27a1..d428412bc 100644 --- a/Modix.Web/Setup.cs +++ b/Modix.Web/Setup.cs @@ -1,8 +1,6 @@ -using System.Security.Claims; -using AspNet.Security.OAuth.Discord; +using AspNet.Security.OAuth.Discord; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; -using Modix.Data.Models.Core; using Modix.Web.Models; using Modix.Web.Security; using Modix.Web.Services; @@ -51,15 +49,6 @@ public static IServiceCollection ConfigureBlazorServices(this IServiceCollection services.AddRazorPages(); services.AddServerSideBlazor(); - services.AddAuthorization(config => - { - var claims = Enum.GetValues(); - foreach (var claim in claims) - { - config.AddPolicy(claim.ToString(), builder => builder.RequireClaim(ClaimTypes.Role, claim.ToString())); - } - }); - return services; } } diff --git a/Modix.Web/Shared/NavMenuLinks.razor b/Modix.Web/Shared/NavMenuLinks.razor index c4ab36364..52d182c2a 100644 --- a/Modix.Web/Shared/NavMenuLinks.razor +++ b/Modix.Web/Shared/NavMenuLinks.razor @@ -14,11 +14,11 @@ User Lookup Tags - + Promotions - + Logs From f32b7ad324e170041b8a819f35ea369802b25780 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sun, 6 Aug 2023 22:17:00 +0200 Subject: [PATCH 086/117] Improve grid size for bigger screens --- Modix.Web/Pages/Commands.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modix.Web/Pages/Commands.razor b/Modix.Web/Pages/Commands.razor index 0e2056b71..692d89d5f 100644 --- a/Modix.Web/Pages/Commands.razor +++ b/Modix.Web/Pages/Commands.razor @@ -13,7 +13,7 @@ @if (Modules is not null) { - + @foreach (var module in Modules.OrderBy(x => x.Name)) { @@ -24,7 +24,7 @@ - +
@foreach (var module in Modules.OrderBy(m => m.Name)) { From fc99cbc01cff34b6de31503a2303eb56b9ea3343 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:40:00 +0200 Subject: [PATCH 087/117] Implement comment creation box to show when the user has yet to vote on a campaign --- .../Promotions/PromotionsService.cs | 6 ++- .../Components/CreateCampaignComment.razor | 40 +++++++++++++++++++ Modix.Web/Pages/Promotions.razor | 24 +++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 Modix.Web/Components/CreateCampaignComment.razor diff --git a/Modix.Services/Promotions/PromotionsService.cs b/Modix.Services/Promotions/PromotionsService.cs index 394ab5072..b0f45dff9 100644 --- a/Modix.Services/Promotions/PromotionsService.cs +++ b/Modix.Services/Promotions/PromotionsService.cs @@ -50,7 +50,7 @@ public interface IPromotionsService /// The value to use for the new comment. /// The value to use for the new comment. /// A that will complete when the operation has completed. - Task AddCommentAsync(long campaignId, PromotionSentiment sentiment, string? content); + Task AddCommentAsync(long campaignId, PromotionSentiment sentiment, string? content); /// /// Updates an existing comment on a promotion campaign by deleting the comment and adding a new one. @@ -187,7 +187,7 @@ public async Task CreateCampaignAsync(ulong subjectId, string? comment = null) } /// - public async Task AddCommentAsync(long campaignId, PromotionSentiment sentiment, string? content) + public async Task AddCommentAsync(long campaignId, PromotionSentiment sentiment, string? content) { AuthorizationService.RequireAuthenticatedGuild(); AuthorizationService.RequireAuthenticatedUser(); @@ -234,6 +234,8 @@ public async Task AddCommentAsync(long campaignId, PromotionSentiment sentiment, } PublishActionNotificationAsync(resultAction); + + return resultAction; } /// diff --git a/Modix.Web/Components/CreateCampaignComment.razor b/Modix.Web/Components/CreateCampaignComment.razor new file mode 100644 index 000000000..1d520e2cd --- /dev/null +++ b/Modix.Web/Components/CreateCampaignComment.razor @@ -0,0 +1,40 @@ +@using Modix.Data.Models.Promotions; +@using MudBlazor; + +
+ +
+ + + +
+
+ + + + + + + + +
+ + + Create +
+
+ +@code { + + [Parameter, EditorRequired] + public EventCallback<(PromotionSentiment PromotionSentiment, string? Content)> OnCampaignCommentCreation { get; set; } + + private PromotionSentiment PromotionSentiment { get; set; } = PromotionSentiment.Approve; + private string? Content { get; set; } + + private async Task Submit() + { + await OnCampaignCommentCreation.InvokeAsync((PromotionSentiment, Content)); + } + +} diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index fff4f0d12..c7e799943 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -127,6 +127,11 @@
} + + if(campaign.CloseAction is null && !campaignCommentData[campaign.Id].Any(x => x.Value.IsFromCurrentUser)) + { + + } } @@ -231,6 +236,25 @@ StateHasChanged(); } + private async Task OnCampaignCommentCreation(long campaignId, GuildUserBrief campaignSubject, PromotionSentiment sentiment, string? content) + { + try + { + var promotionActionSummary = await PromotionsService.AddCommentAsync(campaignId, sentiment, content); + var newComment = promotionActionSummary.NewComment; + + campaignCommentData[campaignId][newComment!.Id] = new CampaignCommentData(newComment.Id, newComment.Sentiment, newComment.Content, promotionActionSummary.Created, true); + } + catch (InvalidOperationException ex) + { + Snackbar.Add(ex.Message, Severity.Error); + return; + } + + var username = campaignSubject.GetFullUsername(); + Snackbar.Add($"Added comment to campaign for user {username}.", Severity.Success); + } + private async Task ToggleEditDialog(long campaignId, long commentId, PromotionSentiment oldPromotionSentiment, string oldContent) { var dialogParams = new DialogParameters From c7b927811d5100f2436dac9376ffa50fedb1a3e0 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:40:25 +0200 Subject: [PATCH 088/117] Fit content to screen width on Tags page --- Modix.Web/Pages/Tags.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modix.Web/Pages/Tags.razor b/Modix.Web/Pages/Tags.razor index 04f6aa944..58656fe69 100644 --- a/Modix.Web/Pages/Tags.razor +++ b/Modix.Web/Pages/Tags.razor @@ -14,7 +14,7 @@ - + Tags @if (Data is not null && Roles is not null) { From aadacedb72c82fcd449e2f4ae5f0fa9f166474a2 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Mon, 11 Sep 2023 20:29:06 +0200 Subject: [PATCH 089/117] Minor touchups --- Modix.Web/Components/AutoComplete.razor | 9 +++-- Modix.Web/Components/AutoComplete.razor.cs | 27 +++++++-------- Modix.Web/Models/Common/IAutoCompleteItem.cs | 2 +- Modix.Web/Models/Common/ModixUser.cs | 34 ++++++++----------- Modix.Web/Models/Common/RoleInformation.cs | 1 - .../DeletedMessageInformation.cs | 4 +-- .../Models/DeletedMessages/TableFilter.cs | 1 - Modix.Web/Models/Tags/TagData.cs | 1 - Modix.Web/Pages/Error.cshtml.cs | 13 ++----- Modix.Web/Pages/Promotions.razor | 4 +-- Modix.Web/Security/ClaimsMiddleware.cs | 15 +++----- Modix.Web/Services/CookieService.cs | 23 ++++--------- Modix.Web/Services/DiscordHelper.cs | 32 +++++------------ 13 files changed, 60 insertions(+), 106 deletions(-) diff --git a/Modix.Web/Components/AutoComplete.razor b/Modix.Web/Components/AutoComplete.razor index 09012fe0b..5370d0ebd 100644 --- a/Modix.Web/Components/AutoComplete.razor +++ b/Modix.Web/Components/AutoComplete.razor @@ -16,6 +16,11 @@ ToStringFunc="item => item?.Name" Clearable="true" Adornment="Adornment.Start" - SelectValueOnTab="true" - ItemTemplate="@ItemTemplate"> + SelectValueOnTab="true"> + + @if(ItemTemplate is not null) + { + @ItemTemplate + } + diff --git a/Modix.Web/Components/AutoComplete.razor.cs b/Modix.Web/Components/AutoComplete.razor.cs index c6572f0e3..46ee31008 100644 --- a/Modix.Web/Components/AutoComplete.razor.cs +++ b/Modix.Web/Components/AutoComplete.razor.cs @@ -1,23 +1,22 @@ using Microsoft.AspNetCore.Components; using Modix.Web.Models.Common; -namespace Modix.Web.Components +namespace Modix.Web.Components; + +public partial class AutoComplete where T : IAutoCompleteItem { - public partial class AutoComplete where T : IAutoCompleteItem - { - [Parameter] - public RenderFragment ItemTemplate { get; set; } + [Parameter] + public RenderFragment? ItemTemplate { get; set; } - [Parameter] - public string? Placeholder { get; set; } + [Parameter] + public string? Placeholder { get; set; } - [Parameter] - public EventCallback SelectedItemChanged { get; set; } + [Parameter] + public EventCallback SelectedItemChanged { get; set; } - [Parameter, EditorRequired] - public string? Title { get; set; } + [Parameter, EditorRequired] + public string? Title { get; set; } - [Parameter, EditorRequired] - public Func>> SearchFunc { get; set; } = null!; - } + [Parameter, EditorRequired] + public Func>> SearchFunc { get; set; } = null!; } diff --git a/Modix.Web/Models/Common/IAutoCompleteItem.cs b/Modix.Web/Models/Common/IAutoCompleteItem.cs index a85da01c5..6e28089cc 100644 --- a/Modix.Web/Models/Common/IAutoCompleteItem.cs +++ b/Modix.Web/Models/Common/IAutoCompleteItem.cs @@ -2,5 +2,5 @@ public interface IAutoCompleteItem { - public string Name { get; } + public string? Name { get; } } diff --git a/Modix.Web/Models/Common/ModixUser.cs b/Modix.Web/Models/Common/ModixUser.cs index df1a8dd74..b110ab35c 100644 --- a/Modix.Web/Models/Common/ModixUser.cs +++ b/Modix.Web/Models/Common/ModixUser.cs @@ -3,29 +3,23 @@ namespace Modix.Web.Models.Common; -public class ModixUser : IAutoCompleteItem +public sealed class ModixUser : IAutoCompleteItem { - public string Name { get; set; } - public ulong UserId { get; set; } - public string AvatarUrl { get; set; } + public string? Name { get; init; } + public ulong UserId { get; init; } + public string? AvatarUrl { get; init; } - public static ModixUser FromIGuildUser(IGuildUser user) + public static ModixUser FromIGuildUser(IGuildUser user) => new() { - return new() - { - Name = user.GetDisplayName(), - UserId = user.Id, - AvatarUrl = user.GetDisplayAvatarUrl() ?? user.GetDefaultAvatarUrl() - }; - } + Name = user.GetDisplayName(), + UserId = user.Id, + AvatarUrl = user.GetDisplayAvatarUrl() ?? user.GetDefaultAvatarUrl() + }; - public static ModixUser FromNonGuildUser(IUser user) + public static ModixUser FromNonGuildUser(IUser user) => new() { - return new() - { - Name = user.GetDisplayName(), - UserId = user.Id, - AvatarUrl = user.GetAvatarUrl() ?? user.GetDefaultAvatarUrl() - }; - } + Name = user.GetDisplayName(), + UserId = user.Id, + AvatarUrl = user.GetAvatarUrl() ?? user.GetDefaultAvatarUrl() + }; } diff --git a/Modix.Web/Models/Common/RoleInformation.cs b/Modix.Web/Models/Common/RoleInformation.cs index 3c00f5f46..d5a36484c 100644 --- a/Modix.Web/Models/Common/RoleInformation.cs +++ b/Modix.Web/Models/Common/RoleInformation.cs @@ -1,4 +1,3 @@ namespace Modix.Web.Models.Common; public record RoleInformation(ulong Id, string Name, string Color) : IAutoCompleteItem; - diff --git a/Modix.Web/Models/DeletedMessages/DeletedMessageInformation.cs b/Modix.Web/Models/DeletedMessages/DeletedMessageInformation.cs index 1fdd0f979..e87f73a0f 100644 --- a/Modix.Web/Models/DeletedMessages/DeletedMessageInformation.cs +++ b/Modix.Web/Models/DeletedMessages/DeletedMessageInformation.cs @@ -12,11 +12,11 @@ public static DeletedMessageInformation FromIMessage(IMessage message) if (string.IsNullOrWhiteSpace(content)) { - if (message.Embeds.Any()) + if (message.Embeds.Count > 0) { content = $"Embed: {message.Embeds.First().Title}: {message.Embeds.First().Description}"; } - else if (message.Attachments.Any()) + else if (message.Attachments.Count > 0) { content = $"Attachment: {message.Attachments.First().Filename} {ByteSize.FromBytes(message.Attachments.First().Size)}"; } diff --git a/Modix.Web/Models/DeletedMessages/TableFilter.cs b/Modix.Web/Models/DeletedMessages/TableFilter.cs index 2ab8420ba..48157fbfe 100644 --- a/Modix.Web/Models/DeletedMessages/TableFilter.cs +++ b/Modix.Web/Models/DeletedMessages/TableFilter.cs @@ -88,5 +88,4 @@ public void SetBatchId(string? batchId) BatchId = id; } - } diff --git a/Modix.Web/Models/Tags/TagData.cs b/Modix.Web/Models/Tags/TagData.cs index 1243d955e..7e6fa3d57 100644 --- a/Modix.Web/Models/Tags/TagData.cs +++ b/Modix.Web/Models/Tags/TagData.cs @@ -15,7 +15,6 @@ public record TagData( bool CanMaintain, TagSummary TagSummary) { - public static TagData CreateFromSummary(TagSummary summary) { return new TagData( diff --git a/Modix.Web/Pages/Error.cshtml.cs b/Modix.Web/Pages/Error.cshtml.cs index 04d92f1ec..ad6c2eb22 100644 --- a/Modix.Web/Pages/Error.cshtml.cs +++ b/Modix.Web/Pages/Error.cshtml.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages; namespace Modix.Web.Pages; + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] [IgnoreAntiforgeryToken] public class ErrorModel : PageModel @@ -11,15 +12,5 @@ public class ErrorModel : PageModel public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - private readonly ILogger _logger; - - public ErrorModel(ILogger logger) - { - _logger = logger; - } - - public void OnGet() - { - RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; - } + public void OnGet() => RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; } diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index c7e799943..317456d1a 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -41,8 +41,8 @@ var sentimentColor = sentimentRatio switch { _ when isCurrentUserCampaign => Color.Transparent, - _ when sentimentRatio > 0.67 => Color.Success, - _ when sentimentRatio > 0.33 => Color.Warning, + > 0.67 => Color.Success, + > 0.33 => Color.Warning, _ => Color.Error }; diff --git a/Modix.Web/Security/ClaimsMiddleware.cs b/Modix.Web/Security/ClaimsMiddleware.cs index 908c22eff..20b89069b 100644 --- a/Modix.Web/Security/ClaimsMiddleware.cs +++ b/Modix.Web/Security/ClaimsMiddleware.cs @@ -5,21 +5,14 @@ namespace Modix.Web.Security; -public class ClaimsMiddleware +public class ClaimsMiddleware(RequestDelegate next) { - private readonly RequestDelegate _next; - - public ClaimsMiddleware(RequestDelegate next) - { - _next = next; - } - public async Task InvokeAsync(HttpContext context, IAuthorizationService authorizationService, DiscordSocketClient discordClient) { var userId = context.User.FindFirst(x => x.Type == ClaimTypes.NameIdentifier)?.Value; if (!ulong.TryParse(userId, out var userSnowflake)) { - await _next(context); + await next(context); return; } @@ -28,7 +21,7 @@ public async Task InvokeAsync(HttpContext context, IAuthorizationService authori if (context.User.Identity is not ClaimsIdentity claimsIdentity) { - await _next(context); + await next(context); return; } @@ -40,6 +33,6 @@ public async Task InvokeAsync(HttpContext context, IAuthorizationService authori claimsIdentity.AddClaims(claims); - await _next(context); + await next(context); } } diff --git a/Modix.Web/Services/CookieService.cs b/Modix.Web/Services/CookieService.cs index 0658b8a3f..fe60141be 100644 --- a/Modix.Web/Services/CookieService.cs +++ b/Modix.Web/Services/CookieService.cs @@ -3,43 +3,32 @@ namespace Modix.Web.Services; -public class CookieService +public class CookieService(IJSRuntime jsRuntime, SessionState sessionState) { - private readonly IJSRuntime _jsRuntime; - private readonly SessionState _sessionState; - - public CookieService(IJSRuntime jsRuntime, SessionState sessionState) - { - _jsRuntime = jsRuntime; - _sessionState = sessionState; - } - public async Task SetSelectedGuildAsync(ulong guildId) { await SetCookieAsync(CookieConstants.SelectedGuild, guildId); - _sessionState.SelectedGuild = guildId; + sessionState.SelectedGuild = guildId; } public async Task SetShowDeletedInfractionsAsync(bool showDeleted) { await SetCookieAsync(CookieConstants.ShowDeletedInfractions, showDeleted); - _sessionState.ShowDeletedInfractions = showDeleted; + sessionState.ShowDeletedInfractions = showDeleted; } public async Task SetShowInfractionStateAsync(bool showInfractionState) { await SetCookieAsync(CookieConstants.ShowInfractionState, showInfractionState); - _sessionState.ShowInfractionState = showInfractionState; + sessionState.ShowInfractionState = showInfractionState; } public async Task SetShowInactivePromotionsAsync(bool showInactivePromotions) { await SetCookieAsync(CookieConstants.ShowInactivePromotions, showInactivePromotions); - _sessionState.ShowInactivePromotions = showInactivePromotions; + sessionState.ShowInactivePromotions = showInactivePromotions; } private async Task SetCookieAsync(string key, T value) - { - await _jsRuntime.InvokeVoidAsync("eval", $"document.cookie = \"{key}={value}; path=/\";"); - } + => await jsRuntime.InvokeVoidAsync("eval", $"document.cookie = \"{key}={value}; path=/\";"); } diff --git a/Modix.Web/Services/DiscordHelper.cs b/Modix.Web/Services/DiscordHelper.cs index 340b42034..462a8408b 100644 --- a/Modix.Web/Services/DiscordHelper.cs +++ b/Modix.Web/Services/DiscordHelper.cs @@ -6,28 +6,14 @@ namespace Modix.Web.Services; -public class DiscordHelper +public class DiscordHelper(DiscordSocketClient client, IUserService userService, SessionState sessionState) { - private readonly DiscordSocketClient _client; - private readonly IUserService _userService; - private readonly SessionState _sessionState; - - public DiscordHelper( - DiscordSocketClient client, - IUserService userService, - SessionState sessionState) - { - _client = client; - _userService = userService; - _sessionState = sessionState; - } - public SocketGuild GetUserGuild() { - if (_sessionState.SelectedGuild != 0) - return _client.GetGuild(_sessionState.SelectedGuild); + if (sessionState.SelectedGuild != 0) + return client.GetGuild(sessionState.SelectedGuild); - return _client.Guilds.First(); + return client.Guilds.First(); } public IEnumerable GetGuildOptions() @@ -36,7 +22,7 @@ public IEnumerable GetGuildOptions() if (currentUser is null) return Array.Empty(); - return _client + return client .Guilds .Where(d => d.GetUser(currentUser.Id) != null) .Select(d => new GuildOption(d.Id, d.Name, d.IconUrl)); @@ -45,7 +31,7 @@ public IEnumerable GetGuildOptions() public SocketGuildUser? GetCurrentUser() { var currentGuild = GetUserGuild(); - return currentGuild.GetUser(_sessionState.CurrentUserId); + return currentGuild.GetUser(sessionState.CurrentUserId); } public async Task> AutoCompleteAsync(string query) @@ -62,7 +48,7 @@ public async Task> AutoCompleteAsync(string query) if (!result.Any() && ulong.TryParse(query, out var userId)) { - var user = await _userService.GetUserInformationAsync(userGuild.Id, userId); + var user = await userService.GetUserInformationAsync(userGuild.Id, userId); if (user is not null) { @@ -100,8 +86,8 @@ public IEnumerable AutocompleteChannels(string query) var currentGuild = GetUserGuild(); return currentGuild.Channels - .Where(d => d is SocketTextChannel) - .Where(d => d.Name.Contains(query, StringComparison.OrdinalIgnoreCase)) + .Where(d => d is SocketTextChannel + && d.Name.Contains(query, StringComparison.OrdinalIgnoreCase)) .Take(10) .Select(d => new ChannelInformation(d.Id, d.Name)); } From 12dc264244167efab51a7097c386ab858ebed826 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Mon, 11 Sep 2023 20:29:28 +0200 Subject: [PATCH 090/117] Remove unused css --- Modix.Web/Shared/MainLayout.razor.css | 70 --------------------------- Modix.Web/Shared/NavMenu.razor | 7 --- Modix.Web/Shared/NavMenu.razor.css | 68 -------------------------- 3 files changed, 145 deletions(-) delete mode 100644 Modix.Web/Shared/MainLayout.razor.css delete mode 100644 Modix.Web/Shared/NavMenu.razor.css diff --git a/Modix.Web/Shared/MainLayout.razor.css b/Modix.Web/Shared/MainLayout.razor.css deleted file mode 100644 index 551e4b276..000000000 --- a/Modix.Web/Shared/MainLayout.razor.css +++ /dev/null @@ -1,70 +0,0 @@ -.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 .btn-link { - white-space: nowrap; - margin-left: 1.5rem; - } - - .top-row a:first-child { - overflow: hidden; - text-overflow: ellipsis; - } - -@media (max-width: 640.98px) { - .top-row:not(.auth) { - display: none; - } - - .top-row.auth { - justify-content: space-between; - } - - .top-row a, .top-row .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, article { - padding-left: 2rem !important; - padding-right: 1.5rem !important; - } -} diff --git a/Modix.Web/Shared/NavMenu.razor b/Modix.Web/Shared/NavMenu.razor index 73daf37bc..885869498 100644 --- a/Modix.Web/Shared/NavMenu.razor +++ b/Modix.Web/Shared/NavMenu.razor @@ -35,13 +35,6 @@
- - @code { private bool _drawerVisible; diff --git a/Modix.Web/Shared/NavMenu.razor.css b/Modix.Web/Shared/NavMenu.razor.css deleted file mode 100644 index 604b7a1a1..000000000 --- a/Modix.Web/Shared/NavMenu.razor.css +++ /dev/null @@ -1,68 +0,0 @@ -.navbar-toggler { - background-color: rgba(255, 255, 255, 0.1); -} - -.top-row { - height: 3.5rem; - background-color: rgba(0,0,0,0.4); -} - -.navbar-brand { - font-size: 1.1rem; -} - -.oi { - width: 2rem; - font-size: 1.1rem; - vertical-align: text-top; - top: -2px; -} - -.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.25); - 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; - } -} From 445b2bdd44957ff8808bf1d1753be994a2f74e31 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Mon, 11 Sep 2023 20:35:43 +0200 Subject: [PATCH 091/117] Fix more nullability stuff Some of it might technically be impossible scenarios, but "meh" :) --- Modix.Web/Components/ConfirmationDialog.razor | 6 +++--- Modix.Web/Components/DeletedMessages.razor | 10 ++++++++-- .../EditPromotionCommentDialog.razor | 6 +++--- Modix.Web/Components/Infractions.razor | 20 ++++++++++++------- Modix.Web/Pages/Logs.razor | 2 +- 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/Modix.Web/Components/ConfirmationDialog.razor b/Modix.Web/Components/ConfirmationDialog.razor index e8304f28e..5ca697464 100644 --- a/Modix.Web/Components/ConfirmationDialog.razor +++ b/Modix.Web/Components/ConfirmationDialog.razor @@ -19,11 +19,11 @@ @code { [CascadingParameter] - MudDialogInstance MudDialog { get; set; } + MudDialogInstance? MudDialog { get; set; } [Parameter] public required string Content { get; set; } - void Submit() => MudDialog.Close(); - void Cancel() => MudDialog.Cancel(); + void Submit() => MudDialog?.Close(); + void Cancel() => MudDialog?.Cancel(); } diff --git a/Modix.Web/Components/DeletedMessages.razor b/Modix.Web/Components/DeletedMessages.razor index d038e3663..731defa51 100644 --- a/Modix.Web/Components/DeletedMessages.razor +++ b/Modix.Web/Components/DeletedMessages.razor @@ -144,14 +144,20 @@ [Inject] public ISnackbar Snackbar { get; set; } = null!; - private MudTable TableRef; + private MudTable? TableRef; private Dictionary> DeletedMessagesContext { get; } = new Dictionary>(); private bool _deletedMessagesContextDialogVisible; private long _currentContext; private TableFilter _tableFilter = new(); - private async Task RefreshTable() => await TableRef.ReloadServerData(); + private async Task RefreshTable() + { + if(TableRef is null) + return; + + await TableRef.ReloadServerData(); + } private async Task FilterChanged(Action filterSetter) { diff --git a/Modix.Web/Components/EditPromotionCommentDialog.razor b/Modix.Web/Components/EditPromotionCommentDialog.razor index 69bef32f1..969334f46 100644 --- a/Modix.Web/Components/EditPromotionCommentDialog.razor +++ b/Modix.Web/Components/EditPromotionCommentDialog.razor @@ -29,7 +29,7 @@ @code { [CascadingParameter] - MudDialogInstance MudDialog { get; set; } + MudDialogInstance? MudDialog { get; set; } [Parameter] public PromotionSentiment PromotionSentiment { get; set; } @@ -37,6 +37,6 @@ [Parameter] public required string Content { get; set; } - void Submit() => MudDialog.Close(DialogResult.Ok((PromotionSentiment, Content))); - void Cancel() => MudDialog.Cancel(); + void Submit() => MudDialog?.Close(DialogResult.Ok((PromotionSentiment, Content))); + void Cancel() => MudDialog?.Cancel(); } diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor index 3178a1c7e..76d767e65 100644 --- a/Modix.Web/Components/Infractions.razor +++ b/Modix.Web/Components/Infractions.razor @@ -90,7 +90,7 @@ Create - Refresh + Refresh @@ -199,7 +199,7 @@ [CascadingParameter] private Task? AuthState { get; set; } - private MudTable TableRef; + private MudTable? TableRef; private bool _showState; @@ -262,7 +262,7 @@ private async Task FilterChanged(Action filterSetter) { filterSetter(); - await TableRef.ReloadServerData(); + await RefreshTable(); } private static string GetUsername(GuildUserBrief userBrief) @@ -289,7 +289,7 @@ } await ModerationService.RescindInfractionAsync(infraction.Id); - await Refresh(); + await RefreshTable(); } catch (Exception ex) { @@ -316,7 +316,7 @@ } await ModerationService.DeleteInfractionAsync(infraction.Id); - await Refresh(); + await RefreshTable(); } catch (Exception ex) { @@ -357,7 +357,7 @@ _newInfractionSeconds = null; _infractionReason = null; - await TableRef.ReloadServerData(); + await RefreshTable(); TimeSpan? GetTimeSpan(int? months, int? days, int? hours, int? minutes, int? seconds) { @@ -395,7 +395,13 @@ } } - private async Task Refresh() => await TableRef.ReloadServerData(); + private async Task RefreshTable() + { + if(TableRef is null) + return; + + await TableRef.ReloadServerData(); + } private async Task> LoadInfractions(TableState tableState) { diff --git a/Modix.Web/Pages/Logs.razor b/Modix.Web/Pages/Logs.razor index 79f8082bd..8edca64bf 100644 --- a/Modix.Web/Pages/Logs.razor +++ b/Modix.Web/Pages/Logs.razor @@ -40,7 +40,7 @@ @code { [Parameter] - public string SubPage { get; set; } + public string? SubPage { get; set; } [Inject] public NavigationManager NavigationManager { get; set; } = null!; From 97b832580f5ac656131e5eff1a1ec6741faaa67f Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Mon, 11 Sep 2023 21:02:42 +0200 Subject: [PATCH 092/117] Fixed AutoComplete after breaking it by not invoking the RenderFragment after fixing nullability --- Modix.Web/Components/AutoComplete.razor | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Modix.Web/Components/AutoComplete.razor b/Modix.Web/Components/AutoComplete.razor index 5370d0ebd..cd991288e 100644 --- a/Modix.Web/Components/AutoComplete.razor +++ b/Modix.Web/Components/AutoComplete.razor @@ -17,10 +17,7 @@ Clearable="true" Adornment="Adornment.Start" SelectValueOnTab="true"> - - @if(ItemTemplate is not null) - { - @ItemTemplate - } + + @ItemTemplate?.Invoke(item) From 263cd8326c54ac3da1f25f09f6ae0a1c41508660 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Mon, 11 Sep 2023 21:03:09 +0200 Subject: [PATCH 093/117] Disable button/comment box on promotion creation page if no "next rank" is available --- Modix.Web/Pages/CreatePromotion.razor | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Modix.Web/Pages/CreatePromotion.razor b/Modix.Web/Pages/CreatePromotion.razor index a5854743b..f5111106c 100644 --- a/Modix.Web/Pages/CreatePromotion.razor +++ b/Modix.Web/Pages/CreatePromotion.razor @@ -49,7 +49,7 @@
Finally, say a few words on their behalf -
- Submit + Submit } @@ -108,7 +108,14 @@ var nextRank = await PromotionsService.GetNextRankRoleForUserAsync(user.UserId); var currentGuild = DiscordHelper.GetUserGuild(); - _nextRank = new NextRank(nextRank?.Name, currentGuild.Roles.First(x => x.Id == nextRank?.Id).Color.ToString()); + if(nextRank is null) + { + _nextRank = new NextRank("None", "#607d8b"); + } + else + { + _nextRank = new NextRank(nextRank.Name, currentGuild.Roles.First(x => x.Id == nextRank.Id).Color.ToString()); + } } private async Task CreateCampaign() From f923f73a1be38a8edf8982e8156aeb12535fb334 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Thu, 21 Dec 2023 15:25:10 +0100 Subject: [PATCH 094/117] Fix compilation issue --- Modix.Data.Test/Assertions/DbContextSequenceExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modix.Data.Test/Assertions/DbContextSequenceExtensions.cs b/Modix.Data.Test/Assertions/DbContextSequenceExtensions.cs index 11632aa84..a9684e353 100644 --- a/Modix.Data.Test/Assertions/DbContextSequenceExtensions.cs +++ b/Modix.Data.Test/Assertions/DbContextSequenceExtensions.cs @@ -86,8 +86,8 @@ private static ResettableSequenceValueGenerator GetGetResettableSeque .GetOrAdd(property, entity, valueGeneratorConstructor); } - private static readonly Dictionary> _valueGeneratorConstructorsByValueType - = new Dictionary>() + private static readonly Dictionary> _valueGeneratorConstructorsByValueType + = new Dictionary>() { [typeof(long)] = (p, e) => new ResettableInt64SequenceValueGenerator() }; From 2b8e9c63efc9f59f0cde10b5e9f3e4583b030448 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Thu, 21 Dec 2023 15:25:23 +0100 Subject: [PATCH 095/117] Upgrade packages to stable version to fix startup issue --- Directory.Build.targets | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index f93092695..5aba2432f 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -14,10 +14,10 @@ - - - - + + + + From b0cb57d8ecf6beab57e773a65a3ea5bfe14cd507 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Thu, 21 Dec 2023 16:38:57 +0100 Subject: [PATCH 096/117] Fix ItemTemplate issue causing preview to be empty --- Modix.Web/Components/AutoComplete.razor | 6 ++---- Modix.Web/Components/AutoComplete.razor.cs | 5 ++++- Modix.Web/Components/Configuration/Channels.razor | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Modix.Web/Components/AutoComplete.razor b/Modix.Web/Components/AutoComplete.razor index cd991288e..0975d4d54 100644 --- a/Modix.Web/Components/AutoComplete.razor +++ b/Modix.Web/Components/AutoComplete.razor @@ -16,8 +16,6 @@ ToStringFunc="item => item?.Name" Clearable="true" Adornment="Adornment.Start" - SelectValueOnTab="true"> - - @ItemTemplate?.Invoke(item) - + SelectValueOnTab="true" + ItemTemplate="ItemTemplate"> diff --git a/Modix.Web/Components/AutoComplete.razor.cs b/Modix.Web/Components/AutoComplete.razor.cs index 46ee31008..b15656217 100644 --- a/Modix.Web/Components/AutoComplete.razor.cs +++ b/Modix.Web/Components/AutoComplete.razor.cs @@ -6,7 +6,10 @@ namespace Modix.Web.Components; public partial class AutoComplete where T : IAutoCompleteItem { [Parameter] - public RenderFragment? ItemTemplate { get; set; } + // Nullability mismatch between MudBlazor, this value is checked for null in the MudBlazor component and changes behavior based on that + // This is to get around the annoying warning when we assign a 'RenderFragment?' to 'RenderFragment' + // In theory the nullable variant is "more" correct, but alas, here we are + public RenderFragment ItemTemplate { get; set; } = null!; [Parameter] public string? Placeholder { get; set; } diff --git a/Modix.Web/Components/Configuration/Channels.razor b/Modix.Web/Components/Configuration/Channels.razor index a247f8620..9543ebc6d 100644 --- a/Modix.Web/Components/Configuration/Channels.razor +++ b/Modix.Web/Components/Configuration/Channels.razor @@ -65,7 +65,7 @@ } From 8d3d176a1b43dbcaf840e878c3e01a22622d423b Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Thu, 21 Dec 2023 16:44:47 +0100 Subject: [PATCH 097/117] Remove TODO comments, add error message when fetching of campaign details fails --- Modix.Web/Pages/Commands.razor | 2 -- Modix.Web/Pages/Promotions.razor | 6 ++++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Modix.Web/Pages/Commands.razor b/Modix.Web/Pages/Commands.razor index 692d89d5f..b244863f9 100644 --- a/Modix.Web/Pages/Commands.razor +++ b/Modix.Web/Pages/Commands.razor @@ -47,8 +47,6 @@
@foreach (var parameter in command.Parameters) { - @*TODO: Add pointer styling?*@ -
@parameter.Name diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index 317456d1a..4567cf64d 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -224,9 +224,11 @@ return; var result = await PromotionsService.GetCampaignDetailsAsync(campaignId); - - if (result is null) // TODO: What to do here? + if (result is null) + { + Snackbar.Add($"Unable to load campaign details for campaign id {campaignId}.", Severity.Error); return; + } campaignCommentData[campaignId] = result.Comments .Where(x => x.ModifyAction is null) From a96e3cc75575fdd43e9fc55aefaea83cf61f6633 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Thu, 21 Dec 2023 16:52:00 +0100 Subject: [PATCH 098/117] Make Title an optional parameter on the AutoComplete component and remove the usage of it on the UserLookup page --- Modix.Web/Components/AutoComplete.razor | 5 ++++- Modix.Web/Components/AutoComplete.razor.cs | 2 +- Modix.Web/Pages/UserLookup.razor | 1 - 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Modix.Web/Components/AutoComplete.razor b/Modix.Web/Components/AutoComplete.razor index 0975d4d54..6554267a6 100644 --- a/Modix.Web/Components/AutoComplete.razor +++ b/Modix.Web/Components/AutoComplete.razor @@ -4,7 +4,10 @@ @typeparam T -@Title +@if (Title is not null) +{ + @Title +} where T : IAutoCompleteItem [Parameter] public EventCallback SelectedItemChanged { get; set; } - [Parameter, EditorRequired] + [Parameter] public string? Title { get; set; } [Parameter, EditorRequired] diff --git a/Modix.Web/Pages/UserLookup.razor b/Modix.Web/Pages/UserLookup.razor index dd4a264f1..b6acde89c 100644 --- a/Modix.Web/Pages/UserLookup.razor +++ b/Modix.Web/Pages/UserLookup.razor @@ -28,7 +28,6 @@ SearchFunc="(query) => DiscordHelper.AutoCompleteAsync(query)" Placeholder="Username or ID" SelectedItemChanged="SelectedUserChanged" - Title="Tell us their username" > From cfb447ee9624c31e9c386dd9e5b7b6047b1e82dc Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Thu, 21 Dec 2023 17:04:39 +0100 Subject: [PATCH 099/117] Remove background color from UserLookup --- Modix.Web/Pages/UserLookup.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modix.Web/Pages/UserLookup.razor b/Modix.Web/Pages/UserLookup.razor index b6acde89c..173729ea9 100644 --- a/Modix.Web/Pages/UserLookup.razor +++ b/Modix.Web/Pages/UserLookup.razor @@ -18,7 +18,7 @@ Modix - User Lookup - + User Lookup@(userInformation is null ? null : $" - {userInformation.Username + (userInformation.Discriminator == "0000" ? "" : "#" + userInformation.Discriminator)}") From c5ae0d92fcef770f7c8cad850e4fa89374970f8a Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Thu, 21 Dec 2023 17:18:41 +0100 Subject: [PATCH 100/117] Apply small gap rule on small screen sizes (that aren't quite xs yet) to avoid overlapping between elements --- Modix.Web/Components/Infractions.razor | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor index 76d767e65..ece64eb56 100644 --- a/Modix.Web/Components/Infractions.razor +++ b/Modix.Web/Components/Infractions.razor @@ -85,20 +85,20 @@ - - + + Create - + Refresh - + - + From e9a93dc1d73d66c99d03674f48dc7ec81cee34a2 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Thu, 21 Dec 2023 19:05:31 +0100 Subject: [PATCH 101/117] Improved styling for toolbar on Infractions page --- Modix.Web/Components/Infractions.razor | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor index ece64eb56..1709e7513 100644 --- a/Modix.Web/Components/Infractions.razor +++ b/Modix.Web/Components/Infractions.razor @@ -85,23 +85,16 @@ - - + +
+
Create - - Refresh - - - - - - - - - - - +
+ + + +
From 79aac749260e5cbe18f6c90591469ad2fde0a991 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Thu, 21 Dec 2023 19:05:43 +0100 Subject: [PATCH 102/117] Improved styling for toolbar on Tags page --- Modix.Web/Pages/Tags.razor | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Modix.Web/Pages/Tags.razor b/Modix.Web/Pages/Tags.razor index 58656fe69..bcfbda692 100644 --- a/Modix.Web/Pages/Tags.razor +++ b/Modix.Web/Pages/Tags.razor @@ -42,15 +42,18 @@ - - +
+
Create Refresh - - - +
+ + +
+ + Name Last Modified From 5d966f2042c3b80c8c437e77ada2c21f1dab40b3 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Fri, 22 Dec 2023 22:24:22 +0100 Subject: [PATCH 103/117] Fix css not being served from the correct folder --- Modix.Web/Setup.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Modix.Web/Setup.cs b/Modix.Web/Setup.cs index d428412bc..5c42e2ca1 100644 --- a/Modix.Web/Setup.cs +++ b/Modix.Web/Setup.cs @@ -1,6 +1,7 @@ using AspNet.Security.OAuth.Discord; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.Extensions.FileProviders; using Modix.Web.Models; using Modix.Web.Security; using Modix.Web.Services; @@ -21,6 +22,14 @@ public static WebApplication ConfigureBlazorApplication(this WebApplication app) } app.UseHttpsRedirection(); + + var wwwrootCssFileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "..", "Modix.Web", "wwwroot", "css")); + app.UseStaticFiles(new StaticFileOptions + { + FileProvider = wwwrootCssFileProvider, + RequestPath = "/css", + }); + app.UseStaticFiles(); app.UseRouting(); From 18cc9ae2235fa2648f21d28e35586bf0dd6bbc43 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Fri, 22 Dec 2023 22:29:43 +0100 Subject: [PATCH 104/117] Improve styling for Promotions page on small resolutions --- Modix.Web/Pages/Promotions.razor | 58 +++++++++++++++++--------------- Modix.Web/wwwroot/css/site.css | 6 ++++ 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index 4567cf64d..790e14044 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -20,9 +20,9 @@ Promotion Campaigns -
+
- Start One + Start One
@foreach(var campaign in Campaigns.Where(x => _showInactive ? true : (x.Outcome is null)).OrderByDescending(x => x.Outcome is null).ThenByDescending(x => x.CreateAction.Created)) @@ -48,9 +48,9 @@ -
-
- +
+
+ @@ -59,36 +59,38 @@ ➥ @campaign.TargetRole.Name
- @if (campaign.Outcome is null) - { - - - - - - - } - - -
-
+
+ @if (campaign.Outcome is null) + { + + + + + + + } + +
+ +
+
- @(isCurrentUserCampaign ? "?" : campaign.ApproveCount.ToString()) + @(isCurrentUserCampaign ? "?" : campaign.ApproveCount.ToString())
- @(isCurrentUserCampaign ? "?" : campaign.OpposeCount.ToString()) + @(isCurrentUserCampaign ? "?" : campaign.OpposeCount.ToString())
- +
diff --git a/Modix.Web/wwwroot/css/site.css b/Modix.Web/wwwroot/css/site.css index cb1ebbfeb..ddcaced31 100644 --- a/Modix.Web/wwwroot/css/site.css +++ b/Modix.Web/wwwroot/css/site.css @@ -1,3 +1,9 @@ .lookup-text-item { margin-left: 10px !important; } + +@media (max-width: 600px) { + .width-sm { + width: 100%; + } +} \ No newline at end of file From b60925e17a5f505edc3886a3ed64145f64b29cf8 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Fri, 22 Dec 2023 22:36:53 +0100 Subject: [PATCH 105/117] Update NuGet packages and change from deprecated APIs/properties --- Modix.Web/Components/Infractions.razor | 4 ++-- Modix.Web/Modix.Web.csproj | 4 ++-- Modix.Web/Pages/Promotions.razor | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor index 1709e7513..df8a30bc1 100644 --- a/Modix.Web/Components/Infractions.razor +++ b/Modix.Web/Components/Infractions.razor @@ -92,8 +92,8 @@ Refresh
- - + +
diff --git a/Modix.Web/Modix.Web.csproj b/Modix.Web/Modix.Web.csproj index 8b04da70f..02a9f2313 100644 --- a/Modix.Web/Modix.Web.csproj +++ b/Modix.Web/Modix.Web.csproj @@ -10,8 +10,8 @@ - - + + diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index 790e14044..c1cbba657 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -21,7 +21,7 @@ Promotion Campaigns
- + Start One
From bde3adf44e1410732c84a0a4ba2d9efcb0c1085b Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Fri, 22 Dec 2023 23:55:45 +0100 Subject: [PATCH 106/117] Remove UseStaticFiles call in favor of linking to static files correctly in the _Host file --- Modix.Web/Pages/_Host.cshtml | 6 +++--- Modix.Web/Setup.cs | 8 -------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/Modix.Web/Pages/_Host.cshtml b/Modix.Web/Pages/_Host.cshtml index 086a84c6d..39448a326 100644 --- a/Modix.Web/Pages/_Host.cshtml +++ b/Modix.Web/Pages/_Host.cshtml @@ -10,9 +10,9 @@ - - - + + + diff --git a/Modix.Web/Setup.cs b/Modix.Web/Setup.cs index 5c42e2ca1..2a619ac92 100644 --- a/Modix.Web/Setup.cs +++ b/Modix.Web/Setup.cs @@ -1,7 +1,6 @@ using AspNet.Security.OAuth.Discord; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.Extensions.FileProviders; using Modix.Web.Models; using Modix.Web.Security; using Modix.Web.Services; @@ -23,13 +22,6 @@ public static WebApplication ConfigureBlazorApplication(this WebApplication app) app.UseHttpsRedirection(); - var wwwrootCssFileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "..", "Modix.Web", "wwwroot", "css")); - app.UseStaticFiles(new StaticFileOptions - { - FileProvider = wwwrootCssFileProvider, - RequestPath = "/css", - }); - app.UseStaticFiles(); app.UseRouting(); From fb80fd19d3b2d511ee58628b9367c4998712fd39 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 23 Dec 2023 09:01:09 +0100 Subject: [PATCH 107/117] Update Dockerfile to use .net8 instead of .net8-preview image --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 48a295687..24f922501 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ -FROM mcr.microsoft.com/dotnet/aspnet:8.0-preview AS base +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app -FROM mcr.microsoft.com/dotnet/sdk:8.0-preview AS dotnet-build-base +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS dotnet-build-base WORKDIR /src RUN printf 'Package: nodejs\nPin: origin deb.nodesource.com\nPin-Priority: 600\n' > /etc/apt/preferences.d/nodesource RUN apt-get update && apt-get install curl -y \ From aab1e42ac35a395168f56d0dac0a126fde76dd56 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 23 Dec 2023 09:17:36 +0100 Subject: [PATCH 108/117] Update more NuGet packages to .NET pinned versions and cleanup some of the version references in projects --- Directory.Build.targets | 39 +++++++++---------- Modix.Bot.Test/Modix.Bot.Test.csproj | 2 +- .../Modix.Services.Test.csproj | 2 +- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index 5aba2432f..b430fc594 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -21,21 +21,21 @@ - + - - - - - - - - - - - - - + + + + + + + + + + + + + @@ -44,13 +44,12 @@ - - - - - + + + + - + diff --git a/Modix.Bot.Test/Modix.Bot.Test.csproj b/Modix.Bot.Test/Modix.Bot.Test.csproj index dcaac6542..ad98b49e2 100644 --- a/Modix.Bot.Test/Modix.Bot.Test.csproj +++ b/Modix.Bot.Test/Modix.Bot.Test.csproj @@ -6,7 +6,7 @@ - + diff --git a/Modix.Services.Test/Modix.Services.Test.csproj b/Modix.Services.Test/Modix.Services.Test.csproj index 4ea6f08f8..d80e63abf 100644 --- a/Modix.Services.Test/Modix.Services.Test.csproj +++ b/Modix.Services.Test/Modix.Services.Test.csproj @@ -6,7 +6,7 @@ - + From a64e032577267a7d317bce31d75dc017bc9abf3c Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 23 Dec 2023 09:57:37 +0100 Subject: [PATCH 109/117] Use maxcpucount:1 in Dockerfile to avoid files being used by other processes error --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 24f922501..d660ecede 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,13 +15,13 @@ RUN dotnet restore Modix.sln COPY . . FROM dotnet-build-base AS dotnet-build -RUN dotnet build -c Release --no-restore Modix.sln +RUN dotnet build -maxcpucount:1 -c Release --no-restore Modix.sln FROM dotnet-build as dotnet-test RUN dotnet test -c Release --no-build --no-restore Modix.sln FROM dotnet-build AS publish -RUN dotnet publish -c Release --no-build --no-restore -o /app Modix/Modix.csproj +RUN dotnet publish -maxcpucount:1 -c Release --no-build --no-restore -o /app Modix/Modix.csproj FROM base AS final COPY --from=publish /app . From ab96419b082b48f706d5ed32e2a14c7cd25240a7 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Sat, 23 Dec 2023 10:29:20 +0100 Subject: [PATCH 110/117] Slight mobile styling tweaks to Infractions and Tags page --- Modix.Web/Components/Infractions.razor | 8 +++++--- Modix.Web/Pages/Tags.razor | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor index df8a30bc1..3a48a6f8c 100644 --- a/Modix.Web/Components/Infractions.razor +++ b/Modix.Web/Components/Infractions.razor @@ -86,14 +86,16 @@ -
+
Create Refresh
- - +
+ + +
diff --git a/Modix.Web/Pages/Tags.razor b/Modix.Web/Pages/Tags.razor index bcfbda692..5d2869c46 100644 --- a/Modix.Web/Pages/Tags.razor +++ b/Modix.Web/Pages/Tags.razor @@ -43,7 +43,7 @@
-
+
Create From 4f09bf67b3f1249d25640f3d4103487ddd479363 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Wed, 27 Dec 2023 15:36:29 +0100 Subject: [PATCH 111/117] Reduce reliance on inline styling --- .../Components/Configuration/Channels.razor | 8 ++- .../Components/Configuration/Claims.razor | 4 +- .../Configuration/IndividualDesignation.razor | 50 ++++++++----------- .../Components/Configuration/Roles.razor | 8 ++- Modix.Web/Components/DeletedMessages.razor | 20 ++++---- .../EditPromotionCommentDialog.razor | 2 +- Modix.Web/Components/Infractions.razor | 30 +++++------ Modix.Web/Components/UserLookupField.razor | 20 +++----- Modix.Web/Pages/Commands.razor | 42 ++++++++-------- Modix.Web/Pages/CreatePromotion.razor | 25 ++++------ Modix.Web/Pages/Index.razor | 2 +- Modix.Web/Pages/Promotions.razor | 28 ++++++----- Modix.Web/Pages/Tags.razor | 10 ++-- Modix.Web/Pages/UserLookup.razor | 6 +-- Modix.Web/Shared/MiniUser.razor | 16 +++--- Modix.Web/wwwroot/css/site.css | 16 ++++-- 16 files changed, 143 insertions(+), 144 deletions(-) diff --git a/Modix.Web/Components/Configuration/Channels.razor b/Modix.Web/Components/Configuration/Channels.razor index 9543ebc6d..cec6a2bd1 100644 --- a/Modix.Web/Components/Configuration/Channels.razor +++ b/Modix.Web/Components/Configuration/Channels.razor @@ -53,10 +53,14 @@
- @designatedChannelType.ToString().Titleize() + + @designatedChannelType.ToString().Titleize() + @if (!DesignatedChannelMappings.TryGetValue(designatedChannelType, out var channelDesignations) || !channelDesignations.Any()) { - None Assigned + + NONE ASSIGNED + } else { diff --git a/Modix.Web/Components/Configuration/Claims.razor b/Modix.Web/Components/Configuration/Claims.razor index 66c35d140..64955c3d1 100644 --- a/Modix.Web/Components/Configuration/Claims.razor +++ b/Modix.Web/Components/Configuration/Claims.razor @@ -37,7 +37,9 @@
- @groupedClaimData.Value.Name.Titleize() + + @groupedClaimData.Value.Name.Titleize() + - + @NamePrefix@Name @if (!_showConfirm) { - + X } @@ -31,22 +27,18 @@ { Remove Designation? - + Yes - + No } diff --git a/Modix.Web/Components/Configuration/Roles.razor b/Modix.Web/Components/Configuration/Roles.razor index eec829b6f..ca1670a52 100644 --- a/Modix.Web/Components/Configuration/Roles.razor +++ b/Modix.Web/Components/Configuration/Roles.razor @@ -54,10 +54,14 @@
- @designatedRoleType.ToString().Titleize() + + @designatedRoleType.ToString().Titleize() + @if (!DesignatedRoleMappings.TryGetValue(designatedRoleType, out var roleDesignations) || !roleDesignations.Any()) { - None Assigned + + NONE ASSIGNED + } else { diff --git a/Modix.Web/Components/DeletedMessages.razor b/Modix.Web/Components/DeletedMessages.razor index 731defa51..5a4144796 100644 --- a/Modix.Web/Components/DeletedMessages.razor +++ b/Modix.Web/Components/DeletedMessages.razor @@ -31,7 +31,7 @@ } else { - Starting + Starting @deletedMessageContext.First().SentTime?.ToLocalTime().ToString("MM/dd/yy, h:mm:ss tt") @@ -54,7 +54,7 @@ @item.SentTime!.Value.ToLocalTime().ToString("hh:mm") } - @item.Username + @item.Username
@if(string.IsNullOrWhiteSpace(item.Content)) { @@ -77,34 +77,34 @@ Refresh - + Channel - + Author - + Deleted On - + Deleted By - + Content - + Reason - + Batch ID - Actions + Actions #@deletedMessage.Channel.Name diff --git a/Modix.Web/Components/EditPromotionCommentDialog.razor b/Modix.Web/Components/EditPromotionCommentDialog.razor index 969334f46..d5c853845 100644 --- a/Modix.Web/Components/EditPromotionCommentDialog.razor +++ b/Modix.Web/Components/EditPromotionCommentDialog.razor @@ -15,7 +15,7 @@ - +
diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor index 3a48a6f8c..cb04075a5 100644 --- a/Modix.Web/Components/Infractions.razor +++ b/Modix.Web/Components/Infractions.razor @@ -25,13 +25,13 @@ Title="Subject" > - + @user.Name - Infraction -
+ Infraction +
@@ -59,8 +59,8 @@ @if(_infractionType == InfractionType.Mute) { - Duration -
+ Duration +
@@ -100,11 +100,11 @@ - + Id - + Type @foreach(var infractionType in Enum.GetValues()) @@ -113,23 +113,25 @@ } - Created On - + + Created On + + Subject - + Creator - Reason + Reason @if(_showState) { - State + State } @if (_canDeleteInfractions || _canRescind) { - Actions + Actions } @@ -146,7 +148,7 @@ @if(_canDeleteInfractions || _canRescind) { -
+
@if (infraction.CanBeDeleted) { Delete diff --git a/Modix.Web/Components/UserLookupField.razor b/Modix.Web/Components/UserLookupField.razor index 654b95513..2f774f5e4 100644 --- a/Modix.Web/Components/UserLookupField.razor +++ b/Modix.Web/Components/UserLookupField.razor @@ -1,32 +1,24 @@ @using MudBlazor @typeparam T -
- @Label +
+ + @Label + @if(ChildContent is not null) { @ChildContent } else if(Value is not null) { - @Value + @Value } else { - @Default + @Default }
- - @code { [Parameter] diff --git a/Modix.Web/Pages/Commands.razor b/Modix.Web/Pages/Commands.razor index b244863f9..9950a57c2 100644 --- a/Modix.Web/Pages/Commands.razor +++ b/Modix.Web/Pages/Commands.razor @@ -88,7 +88,7 @@ } - + }
@@ -121,26 +121,26 @@ flex-wrap: nowrap; } - .tags .tag { - border-radius: 4px; - display: inline-flex; - height: 2em; - margin: 0.16em 0 0.16em 0 !important; - align-items: center; - padding-left: .75em; - padding-right: .75em; - font-size: .75rem; - } - - .tags .tag.is-dark { - background-color: #363636; - color: #fff - } - - .tags .tag.is-info { - background-color: #3298dc; - color: #fff - } + .tags .tag { + border-radius: 4px; + display: inline-flex; + height: 2em; + margin: 0.16em 0 0.16em 0 !important; + align-items: center; + padding-left: .75em; + padding-right: .75em; + font-size: .75rem; + } + + .tags .tag.is-dark { + background-color: #363636; + color: #fff + } + + .tags .tag.is-info { + background-color: #3298dc; + color: #fff + } .tag:not(:last-child) { border-bottom-right-radius: 0; diff --git a/Modix.Web/Pages/CreatePromotion.razor b/Modix.Web/Pages/CreatePromotion.razor index f5111106c..cc5a2e320 100644 --- a/Modix.Web/Pages/CreatePromotion.razor +++ b/Modix.Web/Pages/CreatePromotion.razor @@ -15,18 +15,18 @@ Start a Campaign - +

Feel like someone deserves recognition? Start a promotion campaign for them - even if that person is yourself!

- +

Once a campaign is started, users can anonymously comment, voicing their opinions for or against the individual up for promotion

- +

Staff will periodically review campaigns. If approved, the user will be immediately promoted! If not, they may be permanently denied, or further looked into as the campaign runs its course.

- + - + @user.Name @if(_selectedUser is not null && _nextRank is not null) { -
+
@_selectedUser.Name can be promoted to this rank @_nextRank.Name
-
+
Finally, say a few words on their behalf - + - - - @code { [Inject] diff --git a/Modix.Web/Pages/Index.razor b/Modix.Web/Pages/Index.razor index 174892895..cde1708fb 100644 --- a/Modix.Web/Pages/Index.razor +++ b/Modix.Web/Pages/Index.razor @@ -17,7 +17,7 @@ and check out our GitHub repo! - + diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index c1cbba657..ddca91f25 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -50,13 +50,14 @@
- + - + @campaign.Subject.GetFullUsername() - ➥ @campaign.TargetRole.Name + + @campaign.TargetRole.Name
@@ -99,7 +100,9 @@ @if(campaign.Subject.Id == CurrentUserId) { - Sorry, you aren't allowed to see comments on your own campaign. + + Sorry, you aren't allowed to see comments on your own campaign. + } else if(!campaignCommentData.ContainsKey(campaign.Id)) { @@ -145,16 +148,15 @@ diff --git a/Modix.Web/Pages/Tags.razor b/Modix.Web/Pages/Tags.razor index 5d2869c46..7a12defb7 100644 --- a/Modix.Web/Pages/Tags.razor +++ b/Modix.Web/Pages/Tags.razor @@ -55,11 +55,11 @@ - Name - Last Modified - Owner - Content - Uses + Name + Last Modified + Owner + Content + Uses @tag.Name diff --git a/Modix.Web/Pages/UserLookup.razor b/Modix.Web/Pages/UserLookup.razor index 173729ea9..0e199fc5b 100644 --- a/Modix.Web/Pages/UserLookup.razor +++ b/Modix.Web/Pages/UserLookup.razor @@ -30,7 +30,7 @@ SelectedItemChanged="SelectedUserChanged" > - + @user.Name @@ -70,7 +70,7 @@
@if(!userInformation.Roles.Any()) { - No roles assigned + No roles assigned } else { @@ -79,7 +79,7 @@ var roleName = $"@{role.Name}"; var roleColorStyle = $"border: 1px solid {role.Color}"; - @roleName + @roleName } }
diff --git a/Modix.Web/Shared/MiniUser.razor b/Modix.Web/Shared/MiniUser.razor index 2f533dc07..eda3fc76f 100644 --- a/Modix.Web/Shared/MiniUser.razor +++ b/Modix.Web/Shared/MiniUser.razor @@ -4,14 +4,14 @@ @using Modix.Web.Services; @using MudBlazor -
+
@if (AvatarUrl is not null && Username is not null) { -
- - @Username +
+ + @Username
@@ -20,11 +20,11 @@ } - + -
- +
+
@@ -32,7 +32,7 @@ @foreach (var guildOption in GuildOptions) { - + @guildOption.Name } diff --git a/Modix.Web/wwwroot/css/site.css b/Modix.Web/wwwroot/css/site.css index ddcaced31..366ccbe10 100644 --- a/Modix.Web/wwwroot/css/site.css +++ b/Modix.Web/wwwroot/css/site.css @@ -1,9 +1,17 @@ -.lookup-text-item { - margin-left: 10px !important; -} - @media (max-width: 600px) { .width-sm { width: 100%; } +} + +.center-text { + text-align: center !important; +} + +.vertical-top { + vertical-align: top !important; +} + +.vertical-bottom { + vertical-align: bottom; } \ No newline at end of file From 8b1a69123a5047121476afcb9250bf6b64a6bc5f Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Wed, 27 Dec 2023 18:20:32 +0100 Subject: [PATCH 112/117] Update default value for UseBlazor setting in deployment configuration files --- docker-stack.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-stack.yml b/docker-stack.yml index 4d88c0bfb..b377ed770 100644 --- a/docker-stack.yml +++ b/docker-stack.yml @@ -77,6 +77,7 @@ services: MODIX_ReplUrl: http://repl:31337/eval MODIX_IlUrl: http://repl:31337/il MODIX_SeqEndpoint: http://seq:5341 + MODIX_UseBlazor: 'True' deploy: mode: replicated replicas: 1 From 9bf0fa4c227611f390db8bc7847091bb250ae650 Mon Sep 17 00:00:00 2001 From: calledude <22471295+calledude@users.noreply.github.com> Date: Wed, 27 Dec 2023 18:44:26 +0100 Subject: [PATCH 113/117] Format code --- Modix.Web/Components/AutoComplete.razor | 24 +-- .../Components/Configuration/Channels.razor | 22 ++- .../Components/Configuration/Claims.razor | 46 +++--- .../Components/Configuration/Roles.razor | 145 +++++++++--------- Modix.Web/Components/ConfirmationDialog.razor | 2 +- Modix.Web/Components/DeletedMessages.razor | 28 ++-- .../EditPromotionCommentDialog.razor | 4 +- Modix.Web/Components/Infractions.razor | 70 ++++----- Modix.Web/Components/UserLookupField.razor | 4 +- Modix.Web/Pages/Configuration.razor | 6 +- Modix.Web/Pages/CreatePromotion.razor | 21 ++- Modix.Web/Pages/Index.razor | 2 +- Modix.Web/Pages/Logs.razor | 12 +- Modix.Web/Pages/Promotions.razor | 68 ++++---- Modix.Web/Pages/Stats.razor | 26 ++-- Modix.Web/Pages/Tags.razor | 20 ++- Modix.Web/Pages/UserLookup.razor | 43 +++--- Modix.Web/Pages/_Host.cshtml | 12 +- Modix.Web/Shared/MainLayout.razor | 6 +- Modix.Web/Shared/MiniUser.razor | 18 +-- Modix.Web/Shared/NavMenu.razor | 14 +- Modix.Web/Shared/NavMenuLinks.razor | 2 +- 22 files changed, 287 insertions(+), 308 deletions(-) diff --git a/Modix.Web/Components/AutoComplete.razor b/Modix.Web/Components/AutoComplete.razor index 6554267a6..ae430f952 100644 --- a/Modix.Web/Components/AutoComplete.razor +++ b/Modix.Web/Components/AutoComplete.razor @@ -9,16 +9,16 @@ @Title } + Placeholder="@Placeholder" + AdornmentIcon="@Icons.Material.Filled.Search" + SearchFunc="SearchFunc" + MinCharacters="3" + Immediate="false" + ResetValueOnEmptyText="true" + ValueChanged="SelectedItemChanged" + ToStringFunc="item => item?.Name" + Clearable="true" + Adornment="Adornment.Start" + SelectValueOnTab="true" + ItemTemplate="ItemTemplate"> diff --git a/Modix.Web/Components/Configuration/Channels.razor b/Modix.Web/Components/Configuration/Channels.razor index cec6a2bd1..2a57d855b 100644 --- a/Modix.Web/Components/Configuration/Channels.razor +++ b/Modix.Web/Components/Configuration/Channels.razor @@ -19,12 +19,11 @@ Assign a Channel - Designation @@ -66,12 +65,11 @@ { @foreach (var designatedChannelMapping in channelDesignations) { - + } }
diff --git a/Modix.Web/Components/Configuration/Claims.razor b/Modix.Web/Components/Configuration/Claims.razor index 64955c3d1..82ff30fc7 100644 --- a/Modix.Web/Components/Configuration/Claims.razor +++ b/Modix.Web/Components/Configuration/Claims.razor @@ -18,10 +18,10 @@ - @foreach(var role in Roles.Values) + @foreach (var role in Roles.Values) { - + } @@ -40,41 +40,35 @@ @groupedClaimData.Value.Name.Titleize() - + - - X + + X - - – + + – - - ✓ + + ✓
@groupedClaimData.Value.Description - } } + } } @@ -145,7 +139,7 @@ return; await AuthorizationService.ModifyClaimMappingAsync(roleId, authorizationClaim, claimMappingType); - if(claimMappingType is ClaimMappingType.Denied or ClaimMappingType.Granted) + if (claimMappingType is ClaimMappingType.Denied or ClaimMappingType.Granted) { Snackbar.Add($"Claim '{authorizationClaim}' for '{Roles![roleId].Name}' was changed to '{claimMappingType}'", Severity.Success); if (claimMapping is null) diff --git a/Modix.Web/Components/Configuration/Roles.razor b/Modix.Web/Components/Configuration/Roles.razor index ca1670a52..b7241a06e 100644 --- a/Modix.Web/Components/Configuration/Roles.razor +++ b/Modix.Web/Components/Configuration/Roles.razor @@ -12,84 +12,81 @@ Role Designations -@if(DesignatedRoleMappings is not null && DesignatedRoleTypes is not null) -{ - - - Assign a Role - - - - - Designation - - @foreach (var designation in DesignatedRoleTypes) + @if (DesignatedRoleMappings is not null && DesignatedRoleTypes is not null) + { + + + Assign a Role + + + + + Designation + + @foreach (var designation in DesignatedRoleTypes) + { + + } + + + + + Assign + + + Cancel + + + + + + @foreach (var designatedRoleType in DesignatedRoleTypes.OrderBy(x => x.ToString())) { - - } - - - - - Assign - - - Cancel - - - - - - @foreach(var designatedRoleType in DesignatedRoleTypes.OrderBy(x => x.ToString())) - { - -
-
- - @designatedRoleType.ToString().Titleize() - - @if (!DesignatedRoleMappings.TryGetValue(designatedRoleType, out var roleDesignations) || !roleDesignations.Any()) - { - - NONE ASSIGNED + +
+
+ + @designatedRoleType.ToString().Titleize() - } - else - { - @foreach (var designatedRoleMapping in roleDesignations) + @if (!DesignatedRoleMappings.TryGetValue(designatedRoleType, out var roleDesignations) || !roleDesignations.Any()) { - + + NONE ASSIGNED + } - } -
- -
- - - + else + { + @foreach (var designatedRoleMapping in roleDesignations) + { + + } + } +
+ +
+ + + +
-
- - - } - - -} + + + } + + + } @code { @@ -130,7 +127,7 @@ public void ToggleCreateDialog() { _createDialogVisible = !_createDialogVisible; - if(_createDialogVisible) + if (_createDialogVisible) { _selectedRole = null; _selectedDesignatedRoleType = null; diff --git a/Modix.Web/Components/ConfirmationDialog.razor b/Modix.Web/Components/ConfirmationDialog.razor index 5ca697464..4fd7aba01 100644 --- a/Modix.Web/Components/ConfirmationDialog.razor +++ b/Modix.Web/Components/ConfirmationDialog.razor @@ -10,7 +10,7 @@ Confirm - + Cancel diff --git a/Modix.Web/Components/DeletedMessages.razor b/Modix.Web/Components/DeletedMessages.razor index 5a4144796..21392c0ba 100644 --- a/Modix.Web/Components/DeletedMessages.razor +++ b/Modix.Web/Components/DeletedMessages.razor @@ -14,15 +14,15 @@
Batch Deletion Context - +
- @if(!DeletedMessagesContext.TryGetValue(_currentContext, out var deletedMessageContext)) + @if (!DeletedMessagesContext.TryGetValue(_currentContext, out var deletedMessageContext)) {
- +
} else if (!deletedMessageContext.Any()) @@ -31,13 +31,14 @@ } else { - Starting + + Starting @deletedMessageContext.First().SentTime?.ToLocalTime().ToString("MM/dd/yy, h:mm:ss tt") - + - @foreach(var item in deletedMessageContext) + @foreach (var item in deletedMessageContext) { var wasDeleted = item.SentTime is null; var styling = wasDeleted ? "background-color: #f5f5f5; border-top: 1px solid #fff" : ""; @@ -45,7 +46,7 @@
- @if(wasDeleted) + @if (wasDeleted) { 🚫 } @@ -56,13 +57,13 @@ @item.Username
- @if(string.IsNullOrWhiteSpace(item.Content)) + @if (string.IsNullOrWhiteSpace(item.Content)) { No Content } else { - + }
} @@ -112,7 +113,7 @@ @deletedMessage.Created @deletedMessage.CreatedBy.GetFullUsername() - + @deletedMessage.Reason @deletedMessage.BatchId @@ -121,7 +122,7 @@ - + @@ -153,7 +154,7 @@ private async Task RefreshTable() { - if(TableRef is null) + if (TableRef is null) return; await TableRef.ReloadServerData(); @@ -228,7 +229,8 @@ if (DeletedMessagesContext.ContainsKey(batchId)) return; - var deletedMessages = await ModerationService.SearchDeletedMessagesAsync(new DeletedMessageSearchCriteria + var deletedMessages = await ModerationService.SearchDeletedMessagesAsync( + new DeletedMessageSearchCriteria { BatchId = batchId }, diff --git a/Modix.Web/Components/EditPromotionCommentDialog.razor b/Modix.Web/Components/EditPromotionCommentDialog.razor index d5c853845..33a7f599f 100644 --- a/Modix.Web/Components/EditPromotionCommentDialog.razor +++ b/Modix.Web/Components/EditPromotionCommentDialog.razor @@ -15,12 +15,12 @@ - +
Update - + Cancel diff --git a/Modix.Web/Components/Infractions.razor b/Modix.Web/Components/Infractions.razor index cb04075a5..abd1102b2 100644 --- a/Modix.Web/Components/Infractions.razor +++ b/Modix.Web/Components/Infractions.razor @@ -22,8 +22,7 @@ SearchFunc="(query) => DiscordHelper.AutoCompleteAsync(query)" Placeholder="Username or ID" SelectedItemChanged="SelectedUserChanged" - Title="Subject" - > + Title="Subject"> @user.Name @@ -35,50 +34,47 @@
- + - + - + - +
- +
- @if(_infractionType == InfractionType.Mute) - { - Duration -
- - - - - -
- } + @if (_infractionType == InfractionType.Mute) + { + Duration +
+ + + + + +
+ } - + Save - + Cancel @@ -91,7 +87,7 @@ Create Refresh
- +
@@ -107,9 +103,9 @@ Type - @foreach(var infractionType in Enum.GetValues()) + @foreach (var infractionType in Enum.GetValues()) { - + } @@ -125,7 +121,7 @@ Reason - @if(_showState) + @if (_showState) { State } @@ -145,7 +141,7 @@ { @(infraction.RescindAction != null ? "Rescinded" : infraction.DeleteAction != null ? "Deleted" : "Active") } - @if(_canDeleteInfractions || _canRescind) + @if (_canDeleteInfractions || _canRescind) {
@@ -394,7 +390,7 @@ private async Task RefreshTable() { - if(TableRef is null) + if (TableRef is null) return; await TableRef.ReloadServerData(); diff --git a/Modix.Web/Components/UserLookupField.razor b/Modix.Web/Components/UserLookupField.razor index 2f774f5e4..37fd58a49 100644 --- a/Modix.Web/Components/UserLookupField.razor +++ b/Modix.Web/Components/UserLookupField.razor @@ -5,11 +5,11 @@ @Label - @if(ChildContent is not null) + @if (ChildContent is not null) { @ChildContent } - else if(Value is not null) + else if (Value is not null) { @Value } diff --git a/Modix.Web/Pages/Configuration.razor b/Modix.Web/Pages/Configuration.razor index 80f35e3d9..47d9380a6 100644 --- a/Modix.Web/Pages/Configuration.razor +++ b/Modix.Web/Pages/Configuration.razor @@ -21,10 +21,10 @@ Configuration - - + + - + diff --git a/Modix.Web/Pages/CreatePromotion.razor b/Modix.Web/Pages/CreatePromotion.razor index cc5a2e320..856e2c883 100644 --- a/Modix.Web/Pages/CreatePromotion.razor +++ b/Modix.Web/Pages/CreatePromotion.razor @@ -31,15 +31,14 @@ SearchFunc="(query) => DiscordHelper.AutoCompleteAsync(query)" Placeholder="Username or ID" SelectedItemChanged="SelectedUserChanged" - Title="Tell us their username" - > + Title="Tell us their username"> @user.Name - @if(_selectedUser is not null && _nextRank is not null) + @if (_selectedUser is not null && _nextRank is not null) {
@_selectedUser.Name can be promoted to this rank @@ -50,12 +49,12 @@ Finally, say a few words on their behalf + Class="pa-3" + Immediate="true" + @bind-Value="_promotionComment" + Lines="5" + Margin="Margin.Dense" + Placeholder="They should be promoted because..." />
@@ -88,7 +87,7 @@ private async Task SelectedUserChanged(ModixUser user) { - if(user != _selectedUser) + if (user != _selectedUser) { _nextRank = null; _promotionComment = null; @@ -101,7 +100,7 @@ var nextRank = await PromotionsService.GetNextRankRoleForUserAsync(user.UserId); var currentGuild = DiscordHelper.GetUserGuild(); - if(nextRank is null) + if (nextRank is null) { _nextRank = new NextRank("None", "#607d8b"); } diff --git a/Modix.Web/Pages/Index.razor b/Modix.Web/Pages/Index.razor index cde1708fb..6cf3cd4e6 100644 --- a/Modix.Web/Pages/Index.razor +++ b/Modix.Web/Pages/Index.razor @@ -18,6 +18,6 @@ - + diff --git a/Modix.Web/Pages/Logs.razor b/Modix.Web/Pages/Logs.razor index 8edca64bf..d4c250eea 100644 --- a/Modix.Web/Pages/Logs.razor +++ b/Modix.Web/Pages/Logs.razor @@ -15,23 +15,23 @@ Logs - - - + + + - @if(SubPage == "infractions") + @if (SubPage == "infractions") { } - else if(SubPage == "deletedMessages") + else if (SubPage == "deletedMessages") { - + } diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index ddca91f25..54dd1b213 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -25,7 +25,7 @@ Start One
- @foreach(var campaign in Campaigns.Where(x => _showInactive ? true : (x.Outcome is null)).OrderByDescending(x => x.Outcome is null).ThenByDescending(x => x.CreateAction.Created)) + @foreach (var campaign in Campaigns.Where(x => _showInactive ? true : (x.Outcome is null)).OrderByDescending(x => x.Outcome is null).ThenByDescending(x => x.CreateAction.Created)) { var isCurrentUserCampaign = CurrentUserId == campaign.Subject.Id; @@ -51,7 +51,7 @@
- + @campaign.Subject.GetFullUsername() @@ -65,29 +65,27 @@ { - - + + } - +
- + @(isCurrentUserCampaign ? "?" : campaign.ApproveCount.ToString())
- + @(isCurrentUserCampaign ? "?" : campaign.OpposeCount.ToString())
@@ -97,45 +95,43 @@ Campaign started @campaign.CreateAction.Created.ToString("MM/dd/yy, h:mm:ss tt") - - @if(campaign.Subject.Id == CurrentUserId) + + @if (campaign.Subject.Id == CurrentUserId) { Sorry, you aren't allowed to see comments on your own campaign. } - else if(!campaignCommentData.ContainsKey(campaign.Id)) + else if (!campaignCommentData.ContainsKey(campaign.Id)) { - + } else { - foreach(var comment in campaignCommentData[campaign.Id].Values.OrderByDescending(x => x.CreatedAt)) + foreach (var comment in campaignCommentData[campaign.Id].Values.OrderByDescending(x => x.CreatedAt)) { var sentimentIcon = comment.PromotionSentiment == PromotionSentiment.Approve ? Icons.Material.Filled.ThumbUp : Icons.Material.Filled.ThumbDown;
@comment.Content - - @if (comment.IsFromCurrentUser && campaign.CloseAction is null) - { - + + @if (comment.IsFromCurrentUser && campaign.CloseAction is null) + { + Edit - - } + + } @comment.CreatedAt.ToString("MM/dd/yy, h:mm:ss tt") -
- +
+ } - if(campaign.CloseAction is null && !campaignCommentData[campaign.Id].Any(x => x.Value.IsFromCurrentUser)) + if (campaign.CloseAction is null && !campaignCommentData[campaign.Id].Any(x => x.Value.IsFromCurrentUser)) { - + } } @@ -147,14 +143,14 @@