diff --git a/ProjectLighthouse.Servers.Website/Extensions/SlugExtensions.cs b/ProjectLighthouse.Servers.Website/Extensions/SlugExtensions.cs new file mode 100644 index 000000000..eaef5e257 --- /dev/null +++ b/ProjectLighthouse.Servers.Website/Extensions/SlugExtensions.cs @@ -0,0 +1,33 @@ +using System.Text.RegularExpressions; +using System.Web; +using LBPUnion.ProjectLighthouse.Types.Entities.Level; +using LBPUnion.ProjectLighthouse.Types.Entities.Profile; + +namespace LBPUnion.ProjectLighthouse.Servers.Website.Extensions; + +public static partial class SlugExtensions +{ + [GeneratedRegex("[^a-zA-Z0-9 ]")] + private static partial Regex ValidSlugCharactersRegex(); + + [GeneratedRegex(@"[\s]{2,}")] + private static partial Regex WhitespaceRegex(); + + /// + /// Generates a URL slug that only contains alphanumeric characters + /// with spaces replaced with dashes + /// + /// The slot to generate the slug for + /// A string containing the url slug for this slot + public static string GenerateSlug(this SlotEntity slot) => + slot.Name.Length == 0 + ? "unnamed-level" + : WhitespaceRegex().Replace(ValidSlugCharactersRegex().Replace(HttpUtility.HtmlDecode(slot.Name), ""), " ").Replace(" ", "-").ToLower(); + + /// + /// Generates a URL slug for the given user + /// + /// The user to generate the slug for + /// A string containing the url slug for this user + public static string GenerateSlug(this UserEntity user) => user.Username.ToLower(); +} \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/Partials/Links/UserLinkPartial.cshtml b/ProjectLighthouse.Servers.Website/Pages/Partials/Links/UserLinkPartial.cshtml index 4d889b336..d43791bed 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Partials/Links/UserLinkPartial.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Partials/Links/UserLinkPartial.cshtml @@ -12,7 +12,7 @@ string userStatus = includeStatus ? Model.GetStatus(Database).ToTranslatedString(language, timeZone) : ""; } - + @if (Model.IsModerator) diff --git a/ProjectLighthouse.Servers.Website/Pages/Partials/SlotCardPartial.cshtml b/ProjectLighthouse.Servers.Website/Pages/Partials/SlotCardPartial.cshtml index 43ac34237..0b6940cbe 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Partials/SlotCardPartial.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Partials/SlotCardPartial.cshtml @@ -53,7 +53,7 @@ @if (showLink) {

- @slotName + @slotName

} else @@ -68,7 +68,7 @@ @if (showLink) {

- @slotName + @slotName

} else diff --git a/ProjectLighthouse.Servers.Website/Pages/Partials/UserCardPartial.cshtml b/ProjectLighthouse.Servers.Website/Pages/Partials/UserCardPartial.cshtml index 683c69c3f..14c800a3d 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Partials/UserCardPartial.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Partials/UserCardPartial.cshtml @@ -22,7 +22,7 @@ @if (showLink) {

- @Model.Username + @Model.Username @if (Model.IsModerator) { diff --git a/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml index d6e6ca2d5..d42517e6a 100644 --- a/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml @@ -1,4 +1,4 @@ -@page "/slot/{id:int}" +@page "/slot/{id:int}/{slug?}" @using System.Web @using LBPUnion.ProjectLighthouse.Database @using LBPUnion.ProjectLighthouse.Extensions diff --git a/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml.cs index e228c82bc..bed130780 100644 --- a/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml.cs @@ -1,6 +1,7 @@ #nullable enable using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Servers.Website.Extensions; using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts; using LBPUnion.ProjectLighthouse.Types.Entities.Interaction; using LBPUnion.ProjectLighthouse.Types.Entities.Level; @@ -28,7 +29,7 @@ public class SlotPage : BaseLayout public SlotPage(DatabaseContext database) : base(database) {} - public async Task OnGet([FromRoute] int id) + public async Task OnGet([FromRoute] int id, string? slug) { SlotEntity? slot = await this.Database.Slots.Include(s => s.Creator) .Where(s => s.Type == SlotType.User || (this.User != null && this.User.PermissionLevel >= PermissionLevel.Moderator)) @@ -45,6 +46,12 @@ public async Task OnGet([FromRoute] int id) if ((slot.Hidden || slot.SubLevel && (this.User == null && this.User != slot.Creator)) && !(this.User?.IsModerator ?? false)) return this.NotFound(); + string slotSlug = slot.GenerateSlug(); + if (slug == null || slotSlug != slug) + { + return this.Redirect($"~/slot/{id}/{slotSlug}"); + } + this.Slot = slot; List blockedUsers = this.User == null diff --git a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml index 0e78f3d32..a24a3ddb8 100644 --- a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml @@ -1,4 +1,4 @@ -@page "/user/{userId:int}" +@page "/user/{userId:int}/{slug?}" @using System.Web @using LBPUnion.ProjectLighthouse.Extensions @using LBPUnion.ProjectLighthouse.Localization.StringLists diff --git a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml.cs index ee85fc87b..670c8801e 100644 --- a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml.cs @@ -1,6 +1,7 @@ #nullable enable using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Servers.Website.Extensions; using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts; using LBPUnion.ProjectLighthouse.Types.Entities.Interaction; using LBPUnion.ProjectLighthouse.Types.Entities.Level; @@ -38,11 +39,17 @@ public class UserPage : BaseLayout public UserPage(DatabaseContext database) : base(database) { } - public async Task OnGet([FromRoute] int userId) + public async Task OnGet([FromRoute] int userId, string? slug) { this.ProfileUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == userId); if (this.ProfileUser == null) return this.NotFound(); + string userSlug = this.ProfileUser.GenerateSlug(); + if (slug == null || userSlug != slug) + { + return this.Redirect($"~/user/{userId}/{userSlug}"); + } + bool isAuthenticated = this.User != null; bool isOwner = this.ProfileUser == this.User || this.User != null && this.User.IsModerator;