From a440be4df759c7d317c1574ce1132fab807494f2 Mon Sep 17 00:00:00 2001 From: Jake Mannens Date: Sun, 17 Sep 2023 22:51:50 +1000 Subject: Consolidated user session state and made it persistent across all scopes pertaining to a session --- Pages/Component/NsfwSwitch.razor | 4 +- Pages/Component/TagSelectDialog.razor | 10 ++-- Pages/Gallery.razor | 10 ++-- Pages/TagDefinitions.razor | 10 ++-- Program.cs | 3 ++ Services/UserService.cs | 86 +++++++++++++++++++++-------------- 6 files changed, 71 insertions(+), 52 deletions(-) diff --git a/Pages/Component/NsfwSwitch.razor b/Pages/Component/NsfwSwitch.razor index d0f499d..b96606d 100644 --- a/Pages/Component/NsfwSwitch.razor +++ b/Pages/Component/NsfwSwitch.razor @@ -1,8 +1,8 @@ @inject IUserService userService - + @code { private void ToggleNsfw(bool showNsfw) => - userService.ShowNsfw = showNsfw; + userService.UserSessionState.ShowNsfw = showNsfw; } \ No newline at end of file diff --git a/Pages/Component/TagSelectDialog.razor b/Pages/Component/TagSelectDialog.razor index 7be5c43..20be31e 100644 --- a/Pages/Component/TagSelectDialog.razor +++ b/Pages/Component/TagSelectDialog.razor @@ -71,7 +71,7 @@ public void Hide() => Visible = false; protected override void OnInitialized() { - userService.ShowNsfwChanged += ShowNsfwChanged; + userService.UserSessionState.OnStateChange += ShowNsfwChanged; LoadTags(); } @@ -81,7 +81,7 @@ var selected = SelectedTags.Select(td => td.Guid); int[] nsfwTags = Array.Empty(); - if(!userService.ShowNsfw) + if(!userService.UserSessionState.ShowNsfw) nsfwTags = tagService.TagsThatImply(HBContext.NsfwTag) .Select(td => td.ObjectId) .ToArray(); @@ -91,7 +91,7 @@ .Where(td => td.Source == TagSource.UserTag) .OrderBy(td => td.Name) .AsEnumerable() - .Where(td => userService.ShowNsfw || !td.ImplicitTags + .Where(td => userService.UserSessionState.ShowNsfw || !td.ImplicitTags .IntersectBy(nsfwTags, td => td.ObjectId) .Any()) .Select(td => new Tuple( @@ -170,7 +170,7 @@ StateHasChanged(); } - public async void ShowNsfwChanged(object? sender, bool showNsfw) => + public async void ShowNsfwChanged(UserSessionState userSessionState) => await InvokeAsync(() => { LoadTags(); StateHasChanged(); @@ -178,6 +178,6 @@ public void Dispose() { db.Dispose(); - userService.ShowNsfwChanged -= ShowNsfwChanged; + userService.UserSessionState.OnStateChange -= ShowNsfwChanged; } } diff --git a/Pages/Gallery.razor b/Pages/Gallery.razor index d473c28..762ef7f 100644 --- a/Pages/Gallery.razor +++ b/Pages/Gallery.razor @@ -10,7 +10,7 @@ @Title -@if(Ingest && !userService.ShowNsfw) { +@if(Ingest && !userService.UserSessionState.ShowNsfw) {

Ingest feed is not available unless NSFW mode is enabled!

You must enable NSFW mode to continue...

@@ -68,7 +68,7 @@ private IEnumerator mediaEnumerator; protected override void OnInitialized() => - userService.ShowNsfwChanged += ShowNsfwChanged; + userService.UserSessionState.OnStateChange += ShowNsfwChanged; protected override void OnParametersSet() => LoadMedia(); @@ -124,7 +124,7 @@ break; Media? m = enumerator.Current; - if(!userService.ShowNsfw) + if(!userService.UserSessionState.ShowNsfw) if(m.Tags.Select(t => t.TagDefinitionId).Intersect(nsfwTags).Any() || m.IsIngest) continue; @@ -135,7 +135,7 @@ } } - private async void ShowNsfwChanged(object? sender, bool showNsfw) { + private async void ShowNsfwChanged(UserSessionState userSessionState) { await InvokeAsync(() => { LoadMedia(); StateHasChanged(); @@ -144,6 +144,6 @@ public void Dispose() { mediaEnumerator.Dispose(); - userService.ShowNsfwChanged -= ShowNsfwChanged; + userService.UserSessionState.OnStateChange -= ShowNsfwChanged; } } diff --git a/Pages/TagDefinitions.razor b/Pages/TagDefinitions.razor index f728631..3840c6d 100644 --- a/Pages/TagDefinitions.razor +++ b/Pages/TagDefinitions.razor @@ -94,14 +94,14 @@ private string?[] tagNamespaces; protected override void OnInitialized() => - userService.ShowNsfwChanged += ShowNsfwChanged; + userService.UserSessionState.OnStateChange += ShowNsfwChanged; protected override void OnParametersSet() => LoadTags(); private void LoadTags() { int[] nsfwTags = Array.Empty(); - if(!userService.ShowNsfw) + if(!userService.UserSessionState.ShowNsfw) nsfwTags = tagService.TagsThatImply(HBContext.NsfwTag) .Select(td => td.ObjectId) .ToArray(); @@ -112,7 +112,7 @@ .OrderBy(td => td.Namespace) .ThenBy(td => td.Name) .AsEnumerable() - .Where(td => userService.ShowNsfw || !td.ImplicitTags + .Where(td => userService.UserSessionState.ShowNsfw || !td.ImplicitTags .IntersectBy(nsfwTags, td => td.ObjectId) .Any()) .ToArray(); @@ -176,12 +176,12 @@ StateHasChanged(); } - private async void ShowNsfwChanged(object? sender, bool showNsfw) => + private async void ShowNsfwChanged(UserSessionState userSessionState) => await InvokeAsync(() => { LoadTags(); StateHasChanged(); }); public void Dispose() => - userService.ShowNsfwChanged -= ShowNsfwChanged; + userService.UserSessionState.OnStateChange -= ShowNsfwChanged; } diff --git a/Program.cs b/Program.cs index e78f0d4..79fe8fa 100644 --- a/Program.cs +++ b/Program.cs @@ -8,6 +8,7 @@ namespace HyperBooru; public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); + builder.Services.AddSession(); builder.Services.AddHttpContextAccessor(); builder.Services.AddAuthentication( CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(); @@ -24,6 +25,7 @@ public class Program { builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddHostedService(); @@ -34,6 +36,7 @@ public class Program { using var db = scope.ServiceProvider.GetRequiredService(); db.Database.Migrate(); + app.UseSession(); app.UseHsts(); app.UseHttpsRedirection(); app.UseStaticFiles(); diff --git a/Services/UserService.cs b/Services/UserService.cs index 1a2bd8f..39b1963 100644 --- a/Services/UserService.cs +++ b/Services/UserService.cs @@ -4,50 +4,30 @@ using Microsoft.EntityFrameworkCore; namespace HyperBooru.Services; public interface IUserService { - public bool ShowNsfw { get; set; } - - public event EventHandler ShowNsfwChanged; - - public User User { get; } + public UserSessionState UserSessionState { get; } } public class UserService : IUserService { - public bool ShowNsfw { - get => showNsfw; - set { - showNsfw = value; - ShowNsfwChanged?.Invoke(this, value); - } - } + public UserSessionState UserSessionState => + globalUserService.GetSessionState(httpContext.Session.Id); - public User User { - get { - if(user is not null) - return user; - using var db = dbFactory.CreateDbContext(); - int id = int.Parse(httpContextAccessor - .HttpContext!.User.Claims - .First(c => c.Type == "ObjectId") - .Value); - return user = db.Users.Find(id)!; - } - } - - public event EventHandler ShowNsfwChanged; - - private bool showNsfw = false; - - private User? user; - - private IDbContextFactory dbFactory; private IHttpContextAccessor httpContextAccessor; + private IGlobalUserService globalUserService; + + private HttpContext httpContext => + httpContextAccessor.HttpContext!; public UserService( - IDbContextFactory dbFactory, - IHttpContextAccessor httpContextAccessor) { + IHttpContextAccessor httpContextAccessor, + IGlobalUserService globalUserService) { - this.dbFactory = dbFactory; this.httpContextAccessor = httpContextAccessor; + this.globalUserService = globalUserService; + + // HTTP context session states are discarded if no values + // are set. Set a dummy value so that the session state + // will not be discarded later when we actually need it. + httpContext.Session.SetInt32("Persist", 1); } public static string HashPassword(string password) => @@ -59,3 +39,39 @@ public class UserService : IUserService { 100_000, 512 / 8)); } + +public interface IGlobalUserService { + public UserSessionState GetSessionState(string id); +} + +public class GlobalUserService : IGlobalUserService { + // TODO: prune this list periodically + private Dictionary sessionStates = new(); + + public UserSessionState GetSessionState(string id) { + sessionStates.TryGetValue(id, out var state); + + if(state is null) { + state = new(); + sessionStates[id] = state; + } + + return state; + } +} + +public record UserSessionState { + public event UserSessionStateChange OnStateChange; + + public bool ShowNsfw { + get => showNsfw; + set { + showNsfw = value; + OnStateChange.Invoke(this); + } + } + + private bool showNsfw = false; +} + +public delegate void UserSessionStateChange(UserSessionState sessionState); -- cgit v1.3