From f6a7fe6283e52c32940dec51761924b59c91f4f2 Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 17 Sep 2022 14:02:46 -0500 Subject: [PATCH 01/22] User settings, level settings, language and timezone selection and more. (#471) * Initial work for user settings page * Finish user setting and slot setting pages * Don't show slot upload date on home page and fix team pick redirection * Fix upload image button alignment on mobile * Fix image upload on iPhone * Remove unused css and add selected button color * Fix login email check and bump ChromeDriver to 105 * Remove duplicated code and allow users to leave fields empty * Add unpublish button on level settings and move settings button position * Don't show edit button on mini card * Self review bug fixes and users can no longer use an in-use email --- .../LocalizationManager.cs | 2 +- .../Controllers/LoginController.cs | 9 +- .../Admin/ModerationSlotController.cs | 8 +- .../Controllers/SlotPageController.cs | 21 +++ .../Extensions/PartialExtensions.cs | 21 ++- .../Middlewares/HandlePageErrorMiddleware.cs | 3 +- .../UserRequiredRedirectMiddleware.cs | 48 +++++ .../CompleteEmailVerificationPage.cshtml.cs | 7 +- .../ExternalAuth/AuthenticationPage.cshtml | 6 +- .../Pages/LandingPage.cshtml | 5 +- .../Pages/Layouts/BaseLayout.cshtml.cs | 17 +- .../Pages/Moderation/BannedUsersPage.cshtml | 3 + .../Pages/Moderation/CasePage.cshtml | 5 +- .../Pages/Moderation/NewCasePage.cshtml | 1 - .../Pages/Moderation/ReportPage.cshtml | 4 +- .../Pages/Moderation/ReportsPage.cshtml | 4 +- .../Pages/Partials/CommentsPartial.cshtml | 4 +- .../Partials/Links/UserLinkPartial.cshtml | 5 +- .../Partials/ModerationCasePartial.cshtml | 12 +- .../Pages/Partials/PhotoPartial.cshtml | 8 +- .../Pages/Partials/ReportPartial.cshtml | 9 +- .../Pages/Partials/SlotCardPartial.cshtml | 22 ++- .../Pages/Partials/UserCardPartial.cshtml | 5 +- .../Pages/PasswordResetRequiredPage.cshtml | 2 - .../Pages/PhotosPage.cshtml | 4 +- .../Pages/PirateSignupPage.cshtml | 2 +- .../Pages/PirateSignupPage.cshtml.cs | 4 +- .../Pages/RegisterForm.cshtml.cs | 4 +- .../Pages/SendVerificationEmailPage.cshtml | 11 +- .../Pages/SendVerificationEmailPage.cshtml.cs | 36 ++-- .../Pages/SetEmailForm.cshtml | 11 ++ .../Pages/SetEmailForm.cshtml.cs | 13 +- .../Pages/SlotPage.cshtml | 5 +- .../Pages/SlotSettingsPage.cshtml | 168 ++++++++++++++++++ .../Pages/SlotSettingsPage.cshtml.cs | 61 +++++++ .../Pages/UserPage.cshtml | 25 +-- .../Pages/UserSettingsPage.cshtml | 148 +++++++++++++++ .../Pages/UserSettingsPage.cshtml.cs | 86 +++++++++ .../Pages/UsersPage.cshtml | 4 +- .../Startup/WebsiteStartup.cs | 1 + ...rojectLighthouse.Tests.WebsiteTests.csproj | 2 +- ProjectLighthouse/Files/FileHelper.cs | 50 +++++- ProjectLighthouse/Helpers/LabelHelper.cs | 67 ++++++- .../Helpers/SanitizationHelper.cs | 3 +- ProjectLighthouse/Levels/LevelLabels.cs | 2 +- .../Middlewares/MiddlewareDBContext.cs | 21 +++ ...220910190711_AddUserLanguageAndTimezone.cs | 45 +++++ .../20220910190824_RemoveUserIsAPirate.cs | 33 ++++ ProjectLighthouse/PlayerData/Profiles/User.cs | 10 +- .../PlayerData/Profiles/UserStatus.cs | 9 +- ProjectLighthouse/ProjectLighthouse.csproj | 3 +- .../Migrations/DatabaseModelSnapshot.cs | 7 +- ProjectLighthouse/StaticFiles/css/styles.css | 25 ++- 53 files changed, 973 insertions(+), 118 deletions(-) create mode 100644 ProjectLighthouse.Servers.Website/Middlewares/UserRequiredRedirectMiddleware.cs create mode 100644 ProjectLighthouse.Servers.Website/Pages/SlotSettingsPage.cshtml create mode 100644 ProjectLighthouse.Servers.Website/Pages/SlotSettingsPage.cshtml.cs create mode 100644 ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml create mode 100644 ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml.cs create mode 100644 ProjectLighthouse/Middlewares/MiddlewareDBContext.cs create mode 100644 ProjectLighthouse/Migrations/20220910190711_AddUserLanguageAndTimezone.cs create mode 100644 ProjectLighthouse/Migrations/20220910190824_RemoveUserIsAPirate.cs diff --git a/ProjectLighthouse.Localization/LocalizationManager.cs b/ProjectLighthouse.Localization/LocalizationManager.cs index a29fdf16..c3ef79c0 100644 --- a/ProjectLighthouse.Localization/LocalizationManager.cs +++ b/ProjectLighthouse.Localization/LocalizationManager.cs @@ -124,7 +124,7 @@ public static class LocalizationManager .Where(r => r != "resources") .ToList(); - languages.Add(DefaultLang); + languages.Insert(0, DefaultLang); return languages; } diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/LoginController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/LoginController.cs index b41adee2..bd7c0019 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/LoginController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/LoginController.cs @@ -67,8 +67,7 @@ public class LoginController : ControllerBase token = await this.database.AuthenticateUser(npTicket, ipAddress); if (token == null) { - Logger.Warn($"Unable to " + - $"find/generate a token for username {npTicket.Username}", LogArea.Login); + Logger.Warn($"Unable to find/generate a token for username {npTicket.Username}", LogArea.Login); return this.StatusCode(403, ""); // If not, then 403. } } @@ -81,6 +80,12 @@ public class LoginController : ControllerBase return this.StatusCode(403, ""); } + if (ServerConfiguration.Instance.Mail.MailEnabled && (user.EmailAddress == null || !user.EmailAddressVerified)) + { + Logger.Error($"Email address unverified for user {user.Username}", LogArea.Login); + return this.StatusCode(403, ""); + } + if (ServerConfiguration.Instance.Authentication.UseExternalAuth) { if (user.ApprovedIPAddress == ipAddress) diff --git a/ProjectLighthouse.Servers.Website/Controllers/Admin/ModerationSlotController.cs b/ProjectLighthouse.Servers.Website/Controllers/Admin/ModerationSlotController.cs index e67e7bf3..f7cf2ec6 100644 --- a/ProjectLighthouse.Servers.Website/Controllers/Admin/ModerationSlotController.cs +++ b/ProjectLighthouse.Servers.Website/Controllers/Admin/ModerationSlotController.cs @@ -1,8 +1,6 @@ #nullable enable -using LBPUnion.ProjectLighthouse.Administration; using LBPUnion.ProjectLighthouse.Levels; using LBPUnion.ProjectLighthouse.PlayerData.Profiles; -using LBPUnion.ProjectLighthouse.Types; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -30,7 +28,7 @@ public class ModerationSlotController : ControllerBase slot.TeamPick = true; await this.database.SaveChangesAsync(); - return this.Ok(); + return this.Redirect("~/slot/" + id); } [HttpGet("removeTeamPick")] @@ -44,7 +42,7 @@ public class ModerationSlotController : ControllerBase slot.TeamPick = false; await this.database.SaveChangesAsync(); - return this.Ok(); + return this.Redirect("~/slot/" + id); } [HttpGet("delete")] @@ -58,6 +56,6 @@ public class ModerationSlotController : ControllerBase await this.database.RemoveSlot(slot); - return this.Ok(); + return this.Redirect("~/slots/0"); } } \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Controllers/SlotPageController.cs b/ProjectLighthouse.Servers.Website/Controllers/SlotPageController.cs index a2843e46..a57b5d35 100644 --- a/ProjectLighthouse.Servers.Website/Controllers/SlotPageController.cs +++ b/ProjectLighthouse.Servers.Website/Controllers/SlotPageController.cs @@ -25,6 +25,27 @@ public class SlotPageController : ControllerBase this.database = database; } + [HttpGet("unpublish")] + public async Task UnpublishSlot([FromRoute] int id) + { + WebToken? token = this.database.WebTokenFromRequest(this.Request); + if (token == null) return this.Redirect("~/login"); + + Slot? targetSlot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == id); + if (targetSlot == null) return this.Redirect("~/slots/0"); + + if (targetSlot.Location == null) throw new ArgumentNullException(); + + if (targetSlot.CreatorId != token.UserId) return this.Redirect("~/slot/" + id); + + this.database.Locations.Remove(targetSlot.Location); + this.database.Slots.Remove(targetSlot); + + await this.database.SaveChangesAsync(); + + return this.Redirect("~/slots/0"); + } + [HttpGet("rateComment")] public async Task RateComment([FromRoute] int id, [FromQuery] int commentId, [FromQuery] int rating) { diff --git a/ProjectLighthouse.Servers.Website/Extensions/PartialExtensions.cs b/ProjectLighthouse.Servers.Website/Extensions/PartialExtensions.cs index 78306838..6b6ca5ac 100644 --- a/ProjectLighthouse.Servers.Website/Extensions/PartialExtensions.cs +++ b/ProjectLighthouse.Servers.Website/Extensions/PartialExtensions.cs @@ -1,4 +1,3 @@ -using LBPUnion.ProjectLighthouse.Levels; using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using Microsoft.AspNetCore.Html; @@ -9,15 +8,19 @@ namespace LBPUnion.ProjectLighthouse.Servers.Website.Extensions; public static class PartialExtensions { - // ReSharper disable once SuggestBaseTypeForParameter - public static ViewDataDictionary WithLang(this ViewDataDictionary viewData, string language) + + public static ViewDataDictionary WithLang(this ViewDataDictionary viewData, string language) => WithKeyValue(viewData, "Language", language); + + public static ViewDataDictionary WithTime(this ViewDataDictionary viewData, string timeZone) => WithKeyValue(viewData, "TimeZone", timeZone); + + private static ViewDataDictionary WithKeyValue(this ViewDataDictionary viewData, string key, object value) { try { - return new(viewData) + return new ViewDataDictionary(viewData) { { - "Language", language + key, value }, }; } @@ -27,9 +30,9 @@ public static class PartialExtensions } } - public static Task ToLink(this User user, IHtmlHelper helper, ViewDataDictionary viewData, string language) - => helper.PartialAsync("Partials/Links/UserLinkPartial", user, viewData.WithLang(language)); + public static Task ToLink(this User user, IHtmlHelper helper, ViewDataDictionary viewData, string language, string timeZone = "", bool includeStatus = false) + => helper.PartialAsync("Partials/Links/UserLinkPartial", user, viewData.WithLang(language).WithTime(timeZone).WithKeyValue("IncludeStatus", includeStatus)); - public static Task ToHtml(this Photo photo, IHtmlHelper helper, ViewDataDictionary viewData, string language) - => helper.PartialAsync("Partials/PhotoPartial", photo, viewData.WithLang(language)); + public static Task ToHtml(this Photo photo, IHtmlHelper helper, ViewDataDictionary viewData, string language, string timeZone) + => helper.PartialAsync("Partials/PhotoPartial", photo, viewData.WithLang(language).WithTime(timeZone)); } \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Middlewares/HandlePageErrorMiddleware.cs b/ProjectLighthouse.Servers.Website/Middlewares/HandlePageErrorMiddleware.cs index 61405d7a..6e8177bc 100644 --- a/ProjectLighthouse.Servers.Website/Middlewares/HandlePageErrorMiddleware.cs +++ b/ProjectLighthouse.Servers.Website/Middlewares/HandlePageErrorMiddleware.cs @@ -10,7 +10,6 @@ public class HandlePageErrorMiddleware : Middleware public override async Task InvokeAsync(HttpContext ctx) { await this.next(ctx); -// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract if (ctx.Response.StatusCode == 404 && !ctx.Request.Path.StartsWithSegments("/gameAssets")) { try @@ -20,7 +19,7 @@ public class HandlePageErrorMiddleware : Middleware finally { // not much we can do to save us, carry on anyways - await next(ctx); + await this.next(ctx); } } } diff --git a/ProjectLighthouse.Servers.Website/Middlewares/UserRequiredRedirectMiddleware.cs b/ProjectLighthouse.Servers.Website/Middlewares/UserRequiredRedirectMiddleware.cs new file mode 100644 index 00000000..db7cd058 --- /dev/null +++ b/ProjectLighthouse.Servers.Website/Middlewares/UserRequiredRedirectMiddleware.cs @@ -0,0 +1,48 @@ +using LBPUnion.ProjectLighthouse.Configuration; +using LBPUnion.ProjectLighthouse.Middlewares; +using LBPUnion.ProjectLighthouse.PlayerData.Profiles; + +namespace LBPUnion.ProjectLighthouse.Servers.Website.Middlewares; + +public class UserRequiredRedirectMiddleware : MiddlewareDBContext +{ + public UserRequiredRedirectMiddleware(RequestDelegate next) : base(next) + { } + + public override async Task InvokeAsync(HttpContext ctx, Database database) + { + User? user = database.UserFromWebRequest(ctx.Request); + if (user == null || ctx.Request.Path.StartsWithSegments("/logout")) + { + await this.next(ctx); + return; + } + + if (user.PasswordResetRequired && !ctx.Request.Path.StartsWithSegments("/passwordResetRequired") && + !ctx.Request.Path.StartsWithSegments("/passwordReset")) + { + ctx.Response.Redirect("/passwordResetRequired"); + return; + } + + if (ServerConfiguration.Instance.Mail.MailEnabled) + { + // The normal flow is for users to set their email during login so just force them to log out + if (user.EmailAddress == null) + { + ctx.Response.Redirect("/logout"); + return; + } + + if (!user.EmailAddressVerified && + !ctx.Request.Path.StartsWithSegments("/login/sendVerificationEmail") && + !ctx.Request.Path.StartsWithSegments("/verifyEmail")) + { + ctx.Response.Redirect("/login/sendVerificationEmail"); + return; + } + } + + await this.next(ctx); + } +} \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/CompleteEmailVerificationPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/CompleteEmailVerificationPage.cshtml.cs index 5f7e8752..0da6a3e2 100644 --- a/ProjectLighthouse.Servers.Website/Pages/CompleteEmailVerificationPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/CompleteEmailVerificationPage.cshtml.cs @@ -3,7 +3,6 @@ using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles.Email; using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts; -using LBPUnion.ProjectLighthouse.Types; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -30,6 +29,12 @@ public class CompleteEmailVerificationPage : BaseLayout return this.Page(); } + if (DateTime.Now > emailVerifyToken.ExpiresAt) + { + this.Error = "This token has expired"; + return this.Page(); + } + if (emailVerifyToken.UserId != user.UserId) { this.Error = "This token doesn't belong to you!"; diff --git a/ProjectLighthouse.Servers.Website/Pages/ExternalAuth/AuthenticationPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/ExternalAuth/AuthenticationPage.cshtml index 083792b4..ab33c246 100644 --- a/ProjectLighthouse.Servers.Website/Pages/ExternalAuth/AuthenticationPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/ExternalAuth/AuthenticationPage.cshtml @@ -5,6 +5,8 @@ @{ Layout = "Layouts/BaseLayout"; Model.Title = "Authentication"; + string timeZone = Model.GetTimeZone(); + TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone); } @if (Model.AuthenticationAttempts.Count == 0) @@ -41,9 +43,9 @@ else @foreach (AuthenticationAttempt authAttempt in Model.AuthenticationAttempts) { - DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(authAttempt.Timestamp).ToLocalTime(); + DateTimeOffset timestamp = TimeZoneInfo.ConvertTime(DateTimeOffset.FromUnixTimeSeconds(authAttempt.Timestamp), timeZoneInfo);
-

A @authAttempt.Platform authentication request was logged at @timestamp.ToString("MM/dd/yyyy @ h:mm tt") UTC from the IP address @authAttempt.IPAddress.

+

A @authAttempt.Platform authentication request was logged at @timestamp.ToString("M/d/yyyy @ h:mm tt") from the IP address @authAttempt.IPAddress.

} diff --git a/ProjectLighthouse.Servers.Website/Pages/Moderation/CasePage.cshtml b/ProjectLighthouse.Servers.Website/Pages/Moderation/CasePage.cshtml index 77ced934..635b4fcb 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Moderation/CasePage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Moderation/CasePage.cshtml @@ -1,10 +1,13 @@ @page "/moderation/cases/{pageNumber:int}" @using LBPUnion.ProjectLighthouse.Administration +@using LBPUnion.ProjectLighthouse.Servers.Website.Extensions @model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Moderation.CasePage @{ Layout = "Layouts/BaseLayout"; Model.Title = "Cases"; + + string timeZone = Model.GetTimeZone(); }

There are @Model.CaseCount total cases, @Model.DismissedCaseCount of which have been dismissed.

@@ -20,5 +23,5 @@ @foreach (ModerationCase @case in Model.Cases) { - @(await Html.PartialAsync("Partials/ModerationCasePartial", @case)) + @(await Html.PartialAsync("Partials/ModerationCasePartial", @case, ViewData.WithTime(timeZone))) } \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/Moderation/NewCasePage.cshtml b/ProjectLighthouse.Servers.Website/Pages/Moderation/NewCasePage.cshtml index 6985ebad..ec666361 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Moderation/NewCasePage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Moderation/NewCasePage.cshtml @@ -1,5 +1,4 @@ @page "/moderation/newCase" -@using LBPUnion.ProjectLighthouse.Administration @model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Moderation.NewCasePage @{ diff --git a/ProjectLighthouse.Servers.Website/Pages/Moderation/ReportPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/Moderation/ReportPage.cshtml index 53b0fe32..492c5a59 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Moderation/ReportPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Moderation/ReportPage.cshtml @@ -1,9 +1,11 @@ @page "/moderation/report/{reportId:int}" +@using LBPUnion.ProjectLighthouse.Servers.Website.Extensions @model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Moderation.ReportPage @{ Layout = "Layouts/BaseLayout"; Model.Title = $"Report {Model.Report.ReportId}"; + string timeZone = Model.GetTimeZone(); } -@await Html.PartialAsync("Partials/ReportPartial", Model.Report) +@await Html.PartialAsync("Partials/ReportPartial", Model.Report, ViewData.WithTime(timeZone)) @await Html.PartialAsync("Partials/RenderReportBoundsPartial") \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/Moderation/ReportsPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/Moderation/ReportsPage.cshtml index 0efde1ed..806ba3f6 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Moderation/ReportsPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Moderation/ReportsPage.cshtml @@ -1,10 +1,12 @@ @page "/moderation/reports/{pageNumber:int}" @using LBPUnion.ProjectLighthouse.Administration.Reports +@using LBPUnion.ProjectLighthouse.Servers.Website.Extensions @model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Moderation.ReportsPage @{ Layout = "Layouts/BaseLayout"; Model.Title = "Reports"; + string timeZone = Model.GetTimeZone(); }

There are @Model.ReportCount total reports.

@@ -28,7 +30,7 @@ @foreach (GriefReport report in Model.Reports) { - @await Html.PartialAsync("Partials/ReportPartial", report) + @await Html.PartialAsync("Partials/ReportPartial", report, ViewData.WithTime(timeZone)) } @await Html.PartialAsync("Partials/RenderReportBoundsPartial") diff --git a/ProjectLighthouse.Servers.Website/Pages/Partials/CommentsPartial.cshtml b/ProjectLighthouse.Servers.Website/Pages/Partials/CommentsPartial.cshtml index 4cb549c4..0a8c24a3 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Partials/CommentsPartial.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Partials/CommentsPartial.cshtml @@ -6,6 +6,8 @@ @{ string language = (string?)ViewData["Language"] ?? LocalizationManager.DefaultLang; + string timeZone = (string?)ViewData["TimeZone"] ?? TimeZoneInfo.Local.Id; + TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone); }
@@ -85,7 +87,7 @@ @decodedMessage }

- @timestamp.ToString("MM/dd/yyyy @ h:mm tt") UTC + @TimeZoneInfo.ConvertTime(timestamp, timeZoneInfo).ToString("M/d/yyyy @ h:mm:ss tt")

@if (i != Model.Comments.Count - 1) { diff --git a/ProjectLighthouse.Servers.Website/Pages/Partials/Links/UserLinkPartial.cshtml b/ProjectLighthouse.Servers.Website/Pages/Partials/Links/UserLinkPartial.cshtml index 42d4a332..7aed4e31 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Partials/Links/UserLinkPartial.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Partials/Links/UserLinkPartial.cshtml @@ -3,9 +3,12 @@ @{ string language = (string?)ViewData["Language"] ?? LocalizationManager.DefaultLang; + string timeZone = (string?)ViewData["TimeZone"] ?? TimeZoneInfo.Local.Id; + bool includeStatus = (bool?)ViewData["IncludeStatus"] ?? false; + string userStatus = includeStatus ? Model.Status.ToTranslatedString(language, timeZone) : ""; } -
+ @Model.Username \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/Partials/ModerationCasePartial.cshtml b/ProjectLighthouse.Servers.Website/Pages/Partials/ModerationCasePartial.cshtml index b34d1b95..09b3773f 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Partials/ModerationCasePartial.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Partials/ModerationCasePartial.cshtml @@ -1,7 +1,6 @@ @using System.Diagnostics @using LBPUnion.ProjectLighthouse @using LBPUnion.ProjectLighthouse.Administration -@using LBPUnion.ProjectLighthouse.Configuration @using LBPUnion.ProjectLighthouse.Levels @using LBPUnion.ProjectLighthouse.PlayerData.Profiles @model LBPUnion.ProjectLighthouse.Administration.ModerationCase @@ -10,6 +9,9 @@ Database database = new(); string color = "blue"; + string timeZone = (string?)ViewData["TimeZone"] ?? TimeZoneInfo.Local.Id; + TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone); + if (Model.Expired) color = "yellow"; if (Model.Dismissed) color = "green"; } @@ -23,19 +25,19 @@ Debug.Assert(Model.DismissedAt != null);

- This case was dismissed by @Model.Dismisser.Username on @Model.DismissedAt.Value.ToString("MM/dd/yyyy @ h:mm tt"). + This case was dismissed by @Model.Dismisser.Username on @TimeZoneInfo.ConvertTime(Model.DismissedAt.Value, timeZoneInfo).ToString("M/d/yyyy @ h:mm tt").

} - else if (Model.Expired) + else if (Model.Expired && Model.ExpiresAt != null) {

- This case expired on @Model.ExpiresAt!.Value.ToString("MM/dd/yyyy @ h:mm tt"). + This case expired on @TimeZoneInfo.ConvertTime(Model.ExpiresAt.Value, timeZoneInfo).ToString("M/d/yyyy @ h:mm tt").

} Case created by @Model.Creator.Username - on @Model.CreatedAt.ToString("MM/dd/yyyy @ h:mm tt") + on @TimeZoneInfo.ConvertTime(Model.CreatedAt, timeZoneInfo).ToString("M/d/yyyy @ h:mm tt")
@if (Model.Type.AffectsLevel()) diff --git a/ProjectLighthouse.Servers.Website/Pages/Partials/PhotoPartial.cshtml b/ProjectLighthouse.Servers.Website/Pages/Partials/PhotoPartial.cshtml index 5e0252e8..3031af5e 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Partials/PhotoPartial.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Partials/PhotoPartial.cshtml @@ -8,6 +8,8 @@ @{ string language = (string?)ViewData["Language"] ?? LocalizationManager.DefaultLang; + string timeZone = (string?)ViewData["TimeZone"] ?? TimeZoneInfo.Local.Id; + TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone); }
@@ -26,7 +28,7 @@ { by - @await Model.Creator.ToLink(Html, ViewData, language) + @await Model.Creator.ToLink(Html, ViewData, language, timeZone) } @if (Model.Slot != null) @@ -49,7 +51,7 @@ break; } } - at @DateTime.UnixEpoch.AddSeconds(Model.Timestamp).ToString(CultureInfo.CurrentCulture) + at @TimeZoneInfo.ConvertTime(DateTime.UnixEpoch.AddSeconds(Model.Timestamp), timeZoneInfo).ToString("M/d/yyyy h:mm:ss tt")

@@ -62,7 +64,7 @@
@foreach (PhotoSubject subject in Model.Subjects) { - @await subject.User.ToLink(Html, ViewData, language) + @await subject.User.ToLink(Html, ViewData, language, timeZone) }
diff --git a/ProjectLighthouse.Servers.Website/Pages/Partials/ReportPartial.cshtml b/ProjectLighthouse.Servers.Website/Pages/Partials/ReportPartial.cshtml index b8ad6a90..a07343ef 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Partials/ReportPartial.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Partials/ReportPartial.cshtml @@ -1,7 +1,12 @@ @using LBPUnion.ProjectLighthouse.Administration.Reports @model LBPUnion.ProjectLighthouse.Administration.Reports.GriefReport -
+@{ + string timeZone = (string?)ViewData["TimeZone"] ?? TimeZoneInfo.Local.Id; + TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone); +} + +
@@ -26,7 +31,7 @@
- Report time: @(DateTimeOffset.FromUnixTimeMilliseconds(Model.Timestamp).ToLocalTime().ToString("R")) + Report time: @(TimeZoneInfo.ConvertTime(DateTimeOffset.FromUnixTimeMilliseconds(Model.Timestamp), timeZoneInfo).ToString("M/d/yyyy h:mm:ss tt"))
Report reason: @Model.Type diff --git a/ProjectLighthouse.Servers.Website/Pages/Partials/SlotCardPartial.cshtml b/ProjectLighthouse.Servers.Website/Pages/Partials/SlotCardPartial.cshtml index 843f2b03..8608e537 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Partials/SlotCardPartial.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Partials/SlotCardPartial.cshtml @@ -18,6 +18,8 @@ bool isMobile = (bool?)ViewData["IsMobile"] ?? false; bool mini = (bool?)ViewData["IsMini"] ?? false; string language = (string?)ViewData["Language"] ?? LocalizationManager.DefaultLang; + string timeZone = (string?)ViewData["TimeZone"] ?? TimeZoneInfo.Local.Id; + TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone); bool isQueued = false; bool isHearted = false; @@ -39,9 +41,9 @@ int size = isMobile || mini ? 50 : 100; }
- - - + +
@@ -90,12 +92,24 @@
@if (Model.Creator != null) { + string date = ""; + if(!mini) + date = " on " + TimeZoneInfo.ConvertTime(DateTimeOffset.FromUnixTimeMilliseconds(Model.FirstUploaded), timeZoneInfo).DateTime.ToShortDateString();

- Created by @await Model.Creator.ToLink(Html, ViewData, language) on @Model.GameVersion.ToPrettyString() + Created by @await Model.Creator.ToLink(Html, ViewData, language) in @Model.GameVersion.ToPrettyString()@date

}
+
+ @if (user != null && !mini && (user.IsModerator || user.UserId == Model.CreatorId)) + { + + + + } +
+

@if (user != null && !mini) { diff --git a/ProjectLighthouse.Servers.Website/Pages/Partials/UserCardPartial.cshtml b/ProjectLighthouse.Servers.Website/Pages/Partials/UserCardPartial.cshtml index 32311fb6..99f9c265 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Partials/UserCardPartial.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Partials/UserCardPartial.cshtml @@ -6,13 +6,14 @@ bool showLink = (bool?)ViewData["ShowLink"] ?? false; bool isMobile = (bool?)ViewData["IsMobile"] ?? false; string language = (string?)ViewData["Language"] ?? LocalizationManager.DefaultLang; + string timeZone = (string?)ViewData["TimeZone"] ?? TimeZoneInfo.Local.Id; }
@{ int size = isMobile ? 50 : 100; } -
+
@if (showLink) @@ -28,7 +29,7 @@ } - @Model.Status.ToTranslatedString(language) + @Model.Status.ToTranslatedString(language, timeZone)
@Model.Hearts diff --git a/ProjectLighthouse.Servers.Website/Pages/PasswordResetRequiredPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/PasswordResetRequiredPage.cshtml index e62de0c3..6724cc96 100644 --- a/ProjectLighthouse.Servers.Website/Pages/PasswordResetRequiredPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/PasswordResetRequiredPage.cshtml @@ -1,6 +1,4 @@ @page "/passwordResetRequired" -@using LBPUnion.ProjectLighthouse.Localization.StringLists -@using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts @model LBPUnion.ProjectLighthouse.Servers.Website.Pages.PasswordResetRequiredPage @{ diff --git a/ProjectLighthouse.Servers.Website/Pages/PhotosPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/PhotosPage.cshtml index 1a189049..0af93f44 100644 --- a/ProjectLighthouse.Servers.Website/Pages/PhotosPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/PhotosPage.cshtml @@ -2,7 +2,6 @@ @using LBPUnion.ProjectLighthouse.Localization.StringLists @using LBPUnion.ProjectLighthouse.PlayerData @using LBPUnion.ProjectLighthouse.Servers.Website.Extensions -@using LBPUnion.ProjectLighthouse.Types @model LBPUnion.ProjectLighthouse.Servers.Website.Pages.PhotosPage @{ @@ -10,6 +9,7 @@ Model.Title = Model.Translate(BaseLayoutStrings.HeaderPhotos); string language = Model.GetLanguage(); + string timeZone = Model.GetTimeZone(); }

There are @Model.PhotoCount total photos!

@@ -25,7 +25,7 @@ @foreach (Photo photo in Model.Photos) {
- @await photo.ToHtml(Html, ViewData, language) + @await photo.ToHtml(Html, ViewData, language, timeZone)
} diff --git a/ProjectLighthouse.Servers.Website/Pages/PirateSignupPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/PirateSignupPage.cshtml index 9d9b183e..f2b3e9b9 100644 --- a/ProjectLighthouse.Servers.Website/Pages/PirateSignupPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/PirateSignupPage.cshtml @@ -7,7 +7,7 @@ } -@if (!Model.User!.IsAPirate) +@if (Model.User!.Language != "en-PT") {

So, ye wanna be a pirate? Well, ye came to the right place!

Just click this 'ere button, and welcome aboard!

diff --git a/ProjectLighthouse.Servers.Website/Pages/PirateSignupPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/PirateSignupPage.cshtml.cs index 17dff379..a65827f4 100644 --- a/ProjectLighthouse.Servers.Website/Pages/PirateSignupPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/PirateSignupPage.cshtml.cs @@ -1,8 +1,6 @@ using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages; @@ -24,7 +22,7 @@ public class PirateSignupPage : BaseLayout User? user = this.Database.UserFromWebRequest(this.Request); if (user == null) return this.Redirect("/login"); - user.IsAPirate = !user.IsAPirate; + user.Language = user.Language == "en-PT" ? "en" : "en-PT"; await this.Database.SaveChangesAsync(); return this.Redirect("/"); diff --git a/ProjectLighthouse.Servers.Website/Pages/RegisterForm.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/RegisterForm.cshtml.cs index 6f6a7619..3f8260d0 100644 --- a/ProjectLighthouse.Servers.Website/Pages/RegisterForm.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/RegisterForm.cshtml.cs @@ -72,7 +72,7 @@ public class RegisterForm : BaseLayout } if (ServerConfiguration.Instance.Mail.MailEnabled && - await this.Database.Users.FirstOrDefaultAsync(u => u.EmailAddress != null && u.EmailAddress.ToLower() == emailAddress.ToLower()) != null) + await this.Database.Users.AnyAsync(u => u.EmailAddress != null && u.EmailAddress.ToLower() == emailAddress.ToLower())) { this.Error = this.Translate(ErrorStrings.EmailTaken); return this.Page(); @@ -86,7 +86,7 @@ public class RegisterForm : BaseLayout if (this.Request.Query.ContainsKey("token")) { - await Database.RemoveRegistrationToken(this.Request.Query["token"]); + await this.Database.RemoveRegistrationToken(this.Request.Query["token"]); } User user = await this.Database.CreateUser(username, CryptoHelper.BCryptHash(password), emailAddress); diff --git a/ProjectLighthouse.Servers.Website/Pages/SendVerificationEmailPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/SendVerificationEmailPage.cshtml index cae13524..0a3fa0f6 100644 --- a/ProjectLighthouse.Servers.Website/Pages/SendVerificationEmailPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/SendVerificationEmailPage.cshtml @@ -6,8 +6,15 @@ Model.Title = "Verify Email Address"; } -

An email address on your account has been set, but hasn't been verified yet.

-

To verify it, check the email sent to @Model.User?.EmailAddress and click the link in the email.

+@if (Model.Success) +{ +

An email address on your account has been set, but hasn't been verified yet.

+

To verify it, check the email sent to @Model.User?.EmailAddress and click the link in the email.

+} +else +{ +

Failed to send email, please try again later

+}
Resend email
diff --git a/ProjectLighthouse.Servers.Website/Pages/SendVerificationEmailPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/SendVerificationEmailPage.cshtml.cs index fae0a1d4..7431acc4 100644 --- a/ProjectLighthouse.Servers.Website/Pages/SendVerificationEmailPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/SendVerificationEmailPage.cshtml.cs @@ -4,8 +4,8 @@ using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles.Email; using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts; -using LBPUnion.ProjectLighthouse.Types; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages; @@ -14,6 +14,8 @@ public class SendVerificationEmailPage : BaseLayout public SendVerificationEmailPage(Database database) : base(database) {} + public bool Success { get; set; } + public async Task OnGet() { if (!ServerConfiguration.Instance.Mail.MailEnabled) return this.NotFound(); @@ -33,29 +35,29 @@ public class SendVerificationEmailPage : BaseLayout } #endif - EmailVerificationToken verifyToken = new() + EmailVerificationToken? verifyToken = await this.Database.EmailVerificationTokens.FirstOrDefaultAsync(v => v.UserId == user.UserId); + // If user doesn't have a token or it is expired then regenerate + if (verifyToken == null || DateTime.Now > verifyToken.ExpiresAt) { - UserId = user.UserId, - User = user, - EmailToken = CryptoHelper.GenerateAuthToken(), - }; + verifyToken = new EmailVerificationToken + { + UserId = user.UserId, + User = user, + EmailToken = CryptoHelper.GenerateAuthToken(), + ExpiresAt = DateTime.Now.AddHours(6), + }; - this.Database.EmailVerificationTokens.Add(verifyToken); - - await this.Database.SaveChangesAsync(); + this.Database.EmailVerificationTokens.Add(verifyToken); + await this.Database.SaveChangesAsync(); + } string body = "Hello,\n\n" + $"This email is a request to verify this email for your (likely new!) Project Lighthouse account ({user.Username}).\n\n" + $"To verify your account, click the following link: {ServerConfiguration.Instance.ExternalUrl}/verifyEmail?token={verifyToken.EmailToken}\n\n\n" + "If this wasn't you, feel free to ignore this email."; - if (SMTPHelper.SendEmail(user.EmailAddress, "Project Lighthouse Email Verification", body)) - { - return this.Page(); - } - else - { - throw new Exception("failed to send email"); - } + this.Success = SMTPHelper.SendEmail(user.EmailAddress, "Project Lighthouse Email Verification", body); + + return this.Page(); } } \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/SetEmailForm.cshtml b/ProjectLighthouse.Servers.Website/Pages/SetEmailForm.cshtml index 38782a30..0bf88c98 100644 --- a/ProjectLighthouse.Servers.Website/Pages/SetEmailForm.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/SetEmailForm.cshtml @@ -1,5 +1,6 @@ @page "/login/setEmail" @using LBPUnion.ProjectLighthouse.Configuration +@using LBPUnion.ProjectLighthouse.Localization.StringLists @model LBPUnion.ProjectLighthouse.Servers.Website.Pages.SetEmailForm @{ @@ -9,6 +10,16 @@

This instance requires email verification. As your account was created before this was a requirement, you must now set an email for your account before continuing.

+@if (!string.IsNullOrWhiteSpace(Model.Error)) +{ +
+
+ @Model.Translate(GeneralStrings.Error) +
+

@Model.Error

+
+} +
@Html.AntiForgeryToken() diff --git a/ProjectLighthouse.Servers.Website/Pages/SetEmailForm.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/SetEmailForm.cshtml.cs index 01ca1262..d16eb5a9 100644 --- a/ProjectLighthouse.Servers.Website/Pages/SetEmailForm.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/SetEmailForm.cshtml.cs @@ -1,12 +1,13 @@ #nullable enable +using System.Diagnostics.CodeAnalysis; using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Localization.StringLists; using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles.Email; using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts; -using LBPUnion.ProjectLighthouse.Types; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -19,6 +20,8 @@ public class SetEmailForm : BaseLayout public EmailSetToken? EmailToken; + public string? Error { get; private set; } + public async Task OnGet(string? token = null) { if (!ServerConfiguration.Instance.Mail.MailEnabled) return this.NotFound(); @@ -32,6 +35,7 @@ public class SetEmailForm : BaseLayout return this.Page(); } + [SuppressMessage("ReSharper", "SpecifyStringComparison")] public async Task OnPost(string emailAddress, string token) { if (!ServerConfiguration.Instance.Mail.MailEnabled) return this.NotFound(); @@ -39,6 +43,13 @@ public class SetEmailForm : BaseLayout EmailSetToken? emailToken = await this.Database.EmailSetTokens.Include(t => t.User).FirstOrDefaultAsync(t => t.EmailToken == token); if (emailToken == null) return this.Redirect("/login"); + if (await this.Database.Users.AnyAsync(u => u.EmailAddress != null && u.EmailAddress.ToLower() == emailAddress.ToLower())) + { + this.Error = this.Translate(ErrorStrings.EmailTaken); + this.EmailToken = emailToken; + return this.Page(); + } + emailToken.User.EmailAddress = emailAddress; this.Database.EmailSetTokens.Remove(emailToken); diff --git a/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml index e25df416..83e3dd77 100644 --- a/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml @@ -19,6 +19,7 @@ bool isMobile = this.Request.IsMobile(); string language = Model.GetLanguage(); + string timeZone = Model.GetTimeZone(); } @if (Model.Slot!.Hidden) @@ -96,7 +97,7 @@
}
- @await Html.PartialAsync("Partials/CommentsPartial", ViewData.WithLang(language)) + @await Html.PartialAsync("Partials/CommentsPartial", ViewData.WithLang(language).WithTime(timeZone))
@if (isMobile) { @@ -216,7 +217,7 @@ @foreach (Photo photo in Model.Photos) {
- @await photo.ToHtml(Html, ViewData, language) + @await photo.ToHtml(Html, ViewData, language, timeZone)
}
diff --git a/ProjectLighthouse.Servers.Website/Pages/SlotSettingsPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/SlotSettingsPage.cshtml new file mode 100644 index 00000000..08fec3e4 --- /dev/null +++ b/ProjectLighthouse.Servers.Website/Pages/SlotSettingsPage.cshtml @@ -0,0 +1,168 @@ +@page "/slot/{slotId:int}/settings" +@using System.Web +@using LBPUnion.ProjectLighthouse.Configuration +@using LBPUnion.ProjectLighthouse.Extensions +@using LBPUnion.ProjectLighthouse.Helpers +@using LBPUnion.ProjectLighthouse.Levels +@using LBPUnion.ProjectLighthouse.Localization.StringLists +@using LBPUnion.ProjectLighthouse.PlayerData +@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.SlotSettingsPage + +@{ + Layout = "Layouts/BaseLayout"; + Model.ShowTitleInPage = false; + + Model.Title = HttpUtility.HtmlDecode(Model.Slot?.Name ?? ""); + + bool isMobile = Request.IsMobile(); + + int size = isMobile ? 100 : 200; +} + + + + + + \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/SlotSettingsPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/SlotSettingsPage.cshtml.cs new file mode 100644 index 00000000..744d0389 --- /dev/null +++ b/ProjectLighthouse.Servers.Website/Pages/SlotSettingsPage.cshtml.cs @@ -0,0 +1,61 @@ +#nullable enable +using LBPUnion.ProjectLighthouse.Files; +using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Levels; +using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages; + +public class SlotSettingsPage : BaseLayout +{ + + public Slot? Slot; + public SlotSettingsPage(Database database) : base(database) + {} + + public async Task OnPost([FromRoute] int slotId, [FromForm] string? avatar, [FromForm] string? name, [FromForm] string? description, string? labels) + { + this.Slot = await this.Database.Slots.FirstOrDefaultAsync(u => u.SlotId == slotId); + if (this.Slot == null) return this.NotFound(); + + if (this.User == null) return this.Redirect("~/slot/" + slotId); + + if (!this.User.IsModerator && this.User != this.Slot.Creator) return this.Redirect("~/slot/" + slotId); + + string? avatarHash = await FileHelper.ParseBase64Image(avatar); + + if (avatarHash != null) this.Slot.IconHash = avatarHash; + + name = SanitizationHelper.SanitizeString(name); + if (this.Slot.Name != name) this.Slot.Name = name; + + description = SanitizationHelper.SanitizeString(description); + if (this.Slot.Description != description) this.Slot.Description = description; + + labels = LabelHelper.RemoveInvalidLabels(SanitizationHelper.SanitizeString(labels)); + if (this.Slot.AuthorLabels != labels) this.Slot.AuthorLabels = labels; + + // ReSharper disable once InvertIf + if (this.Database.ChangeTracker.HasChanges()) + { + this.Slot.LastUpdated = TimeHelper.TimestampMillis; + await this.Database.SaveChangesAsync(); + } + + return this.Redirect("~/slot/" + slotId); + } + + public async Task OnGet([FromRoute] int slotId) + { + this.Slot = await this.Database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId); + if (this.Slot == null) return this.NotFound(); + + if (this.User == null) return this.Redirect("~/slot/" + slotId); + + if(!this.User.IsModerator && this.User.UserId != this.Slot.CreatorId) return this.Redirect("~/slot/" + slotId); + + return this.Page(); + } +} \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml index f2085172..7612b8bd 100644 --- a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml @@ -5,7 +5,6 @@ @using LBPUnion.ProjectLighthouse.Localization.StringLists @using LBPUnion.ProjectLighthouse.PlayerData @using LBPUnion.ProjectLighthouse.Servers.Website.Extensions -@using LBPUnion.ProjectLighthouse.Types @model LBPUnion.ProjectLighthouse.Servers.Website.Pages.UserPage @{ @@ -15,8 +14,9 @@ Model.Title = Model.Translate(ProfileStrings.Title, Model.ProfileUser!.Username); Model.Description = Model.ProfileUser!.Biography; - bool isMobile = this.Request.IsMobile(); + bool isMobile = Request.IsMobile(); string language = Model.GetLanguage(); + string timeZone = Model.GetTimeZone(); } @if (Model.ProfileUser.IsBanned) @@ -50,7 +50,10 @@ }, { "Language", Model.GetLanguage() - } + }, + { + "TimeZone", Model.GetTimeZone() + }, })
@@ -140,7 +145,7 @@ } } -@await Html.PartialAsync("Partials/CommentsPartial", ViewData.WithLang(language)) +@await Html.PartialAsync("Partials/CommentsPartial", ViewData.WithLang(language).WithTime(timeZone)) @if (Model.User != null && Model.User.IsModerator) { diff --git a/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml new file mode 100644 index 00000000..a81f2d37 --- /dev/null +++ b/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml @@ -0,0 +1,148 @@ +@page "/user/{userId:int}/settings" +@using System.Globalization +@using System.Web +@using LBPUnion.ProjectLighthouse.Configuration +@using LBPUnion.ProjectLighthouse.Extensions +@using LBPUnion.ProjectLighthouse.Localization +@using LBPUnion.ProjectLighthouse.Localization.StringLists +@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.UserSettingsPage + +@{ + Layout = "Layouts/BaseLayout"; + Model.ShowTitleInPage = false; + + Model.Title = Model.Translate(ProfileStrings.Title, Model.ProfileUser!.Username); + + bool isMobile = Request.IsMobile(); + + int size = isMobile ? 100 : 200; +} + + + + + + \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml.cs new file mode 100644 index 00000000..16ab05fe --- /dev/null +++ b/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml.cs @@ -0,0 +1,86 @@ +#nullable enable +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using LBPUnion.ProjectLighthouse.Configuration; +using LBPUnion.ProjectLighthouse.Files; +using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Localization; +using LBPUnion.ProjectLighthouse.PlayerData.Profiles; +using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages; + +public class UserSettingsPage : BaseLayout +{ + + public User? ProfileUser; + public UserSettingsPage(Database database) : base(database) + {} + + private static bool IsValidEmail(string? email) => !string.IsNullOrWhiteSpace(email) && new EmailAddressAttribute().IsValid(email); + + [SuppressMessage("ReSharper", "SpecifyStringComparison")] + public async Task OnPost([FromRoute] int userId, [FromForm] string? avatar, [FromForm] string? username, [FromForm] string? email, [FromForm] string? biography, [FromForm] string? timeZone, [FromForm] string? language) + { + this.ProfileUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == userId); + if (this.ProfileUser == null) return this.NotFound(); + + if (this.User == null) return this.Redirect("~/user/" + userId); + + if (!this.User.IsModerator && this.User != this.ProfileUser) return this.Redirect("~/user/" + userId); + + string? avatarHash = await FileHelper.ParseBase64Image(avatar); + + if (avatarHash != null) this.ProfileUser.IconHash = avatarHash; + + biography = SanitizationHelper.SanitizeString(biography); + + if (this.ProfileUser.Biography != biography) this.ProfileUser.Biography = biography; + + if (ServerConfiguration.Instance.Mail.MailEnabled && IsValidEmail(email) && (this.User == this.ProfileUser || this.User.IsAdmin)) + { + // if email hasn't already been used + if (!await this.Database.Users.AnyAsync(u => u.EmailAddress != null && u.EmailAddress.ToLower() == email!.ToLower())) + { + if (this.ProfileUser.EmailAddress != email) + { + this.ProfileUser.EmailAddress = email; + this.ProfileUser.EmailAddressVerified = false; + } + } + } + + if (this.ProfileUser == this.User) + { + if (!string.IsNullOrWhiteSpace(language) && this.ProfileUser.Language != language) + { + if (LocalizationManager.GetAvailableLanguages().Contains(language)) + this.ProfileUser.Language = language; + } + + if (!string.IsNullOrWhiteSpace(timeZone) && this.ProfileUser.TimeZone != timeZone) + { + HashSet timeZoneIds = TimeZoneInfo.GetSystemTimeZones().Select(t => t.Id).ToHashSet(); + if (timeZoneIds.Contains(timeZone)) this.ProfileUser.TimeZone = timeZone; + } + } + + + await this.Database.SaveChangesAsync(); + return this.Redirect("~/user/" + userId); + } + + public async Task OnGet([FromRoute] int userId) + { + this.ProfileUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == userId); + if (this.ProfileUser == null) return this.NotFound(); + + if (this.User == null) return this.Redirect("~/user/" + userId); + + if(!this.User.IsModerator && this.User != this.ProfileUser) return this.Redirect("~/user/" + userId); + + return this.Page(); + } +} \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/UsersPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/UsersPage.cshtml index e2f083e9..97c0940f 100644 --- a/ProjectLighthouse.Servers.Website/Pages/UsersPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/UsersPage.cshtml @@ -2,7 +2,6 @@ @using LBPUnion.ProjectLighthouse.Extensions @using LBPUnion.ProjectLighthouse.Localization.StringLists @using LBPUnion.ProjectLighthouse.PlayerData.Profiles -@using LBPUnion.ProjectLighthouse.Types @model LBPUnion.ProjectLighthouse.Servers.Website.Pages.UsersPage @{ @@ -35,6 +34,9 @@ { "Language", Model.GetLanguage() }, + { + "TimeZone", Model.GetTimeZone() + }, })
} diff --git a/ProjectLighthouse.Servers.Website/Startup/WebsiteStartup.cs b/ProjectLighthouse.Servers.Website/Startup/WebsiteStartup.cs index 6e757fa7..159b4abb 100644 --- a/ProjectLighthouse.Servers.Website/Startup/WebsiteStartup.cs +++ b/ProjectLighthouse.Servers.Website/Startup/WebsiteStartup.cs @@ -80,6 +80,7 @@ public class WebsiteStartup app.UseMiddleware(); app.UseMiddleware(); + app.UseMiddleware(); app.UseRouting(); diff --git a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj index 9495735a..03535d62 100644 --- a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj +++ b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj @@ -16,7 +16,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/ProjectLighthouse/Files/FileHelper.cs b/ProjectLighthouse/Files/FileHelper.cs index a922b25c..de4c0b98 100644 --- a/ProjectLighthouse/Files/FileHelper.cs +++ b/ProjectLighthouse/Files/FileHelper.cs @@ -6,14 +6,15 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using DDSReader; using ICSharpCode.SharpZipLib.Zip.Compression; using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.PlayerData; +using Pfim; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; @@ -240,6 +241,34 @@ public static class FileHelper if (!Directory.Exists(path)) Directory.CreateDirectory(path ?? throw new ArgumentNullException(nameof(path))); } + private static readonly Regex base64Regex = new(@"data:([^\/]+)\/([^;]+);base64,(.*)", RegexOptions.Compiled); + + public static async Task ParseBase64Image(string? image) + { + if (string.IsNullOrWhiteSpace(image)) return null; + + System.Text.RegularExpressions.Match match = base64Regex.Match(image); + + if (!match.Success) return null; + + if (match.Groups.Count != 4) return null; + + byte[] data = Convert.FromBase64String(match.Groups[3].Value); + + LbpFile file = new(data); + + if (file.FileType is not (LbpFileType.Jpeg or LbpFileType.Png)) return null; + + if (ResourceExists(file.Hash)) return file.Hash; + + string assetsDirectory = ResourcePath; + string path = GetResourcePath(file.Hash); + + EnsureDirectoryCreated(assetsDirectory); + await File.WriteAllBytesAsync(path, file.Data); + return file.Hash; + } + public static string[] ResourcesNotUploaded(params string[] hashes) => hashes.Where(hash => !ResourceExists(hash)).ToArray(); public static void ConvertAllTexturesToPng() @@ -284,7 +313,7 @@ public static class FileHelper public static bool LbpFileToPNG(LbpFile file) => LbpFileToPNG(file.Data, file.Hash, file.FileType); - public static bool LbpFileToPNG(byte[] data, string hash, LbpFileType type) + private static bool LbpFileToPNG(byte[] data, string hash, LbpFileType type) { if (type != LbpFileType.Jpeg && type != LbpFileType.Png && type != LbpFileType.Texture) return false; @@ -342,6 +371,7 @@ public static class FileHelper if (compressed[i] == decompressed[i]) { writer.Write(deflatedData); + continue; } Inflater inflater = new(); @@ -357,19 +387,25 @@ public static class FileHelper private static bool DDSToPNG(string hash, byte[] data) { - using MemoryStream stream = new(); - DDSImage image = new(data); + Dds ddsImage = Dds.Create(data, new PfimConfig()); + if(ddsImage.Compressed) + ddsImage.Decompress(); - image.SaveAsPng(stream); + // ReSharper disable once SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault + Image image = ddsImage.Format switch + { + ImageFormat.Rgba32 => Image.LoadPixelData(ddsImage.Data, ddsImage.Width, ddsImage.Height), + _ => throw new ArgumentOutOfRangeException($"ddsImage.Format is not supported: {ddsImage.Format}") + }; Directory.CreateDirectory("png"); - File.WriteAllBytes($"png/{hash}.png", stream.ToArray()); + image.SaveAsPngAsync($"png/{hash}.png"); return true; } private static bool JPGToPNG(string hash, byte[] data) { - using Image image = Image.Load(data); + using Image image = Image.Load(data); using MemoryStream ms = new(); image.SaveAsPng(ms); diff --git a/ProjectLighthouse/Helpers/LabelHelper.cs b/ProjectLighthouse/Helpers/LabelHelper.cs index 5bbb7d13..b88dddc9 100644 --- a/ProjectLighthouse/Helpers/LabelHelper.cs +++ b/ProjectLighthouse/Helpers/LabelHelper.cs @@ -1,32 +1,84 @@ using System; using System.Collections.Generic; using LBPUnion.ProjectLighthouse.Levels; +using LBPUnion.ProjectLighthouse.PlayerData; namespace LBPUnion.ProjectLighthouse.Helpers; public static class LabelHelper { + private static readonly List lbp3Labels = new() + { + "LABEL_SINGLE_PLAYER", + "LABEL_RPG", + "LABEL_TOP_DOWN", + "LABEL_CO_OP", + "LABEL_1st_Person", + "LABEL_3rd_Person", + "LABEL_Sci_Fi", + "LABEL_Social", + "LABEL_Arcade_Game", + "LABEL_Board_Game", + "LABEL_Card_Game", + "LABEL_Mini_Game", + "LABEL_Party_Game", + "LABEL_Defence", + "LABEL_Driving", + "LABEL_Hangout", + "LABEL_Hide_And_Seek", + "LABEL_Prop_Hunt", + "LABEL_Music_Gallery", + "LABEL_Costume_Gallery", + "LABEL_Sticker_Gallery", + "LABEL_Movie", + "LABEL_Pinball", + "LABEL_Technology", + "LABEL_Homage", + "LABEL_8_Bit", + "LABEL_16_Bit", + "LABEL_Seasonal", + "LABEL_Time_Trial", + "LABEL_INTERACTIVE_STREAM", + "LABEL_QUESTS", + "LABEL_SACKPOCKET", + "LABEL_SPRINGINATOR", + "LABEL_HOVERBOARD_NAME", + "LABEL_FLOATY_FLUID_NAME", + "LABEL_ODDSOCK", + "LABEL_TOGGLE", + "LABEL_SWOOP", + "LABEL_SACKBOY", + "LABEL_CREATED_CHARACTERS", + }; + private static readonly Dictionary translationTable = new() { + {"Label_SinglePlayer", "Single Player"}, + {"LABEL_Quick", "Short"}, + {"LABEL_Competitive", "Versus"}, + {"LABEL_Puzzle", "Puzzler"}, + {"LABEL_Platform", "Platformer"}, + {"LABEL_Race", "Racer"}, + {"LABEL_SurvivalChallenge", "Survival Challenge"}, {"LABEL_DirectControl", "Controllinator"}, {"LABEL_GrapplingHook", "Grappling Hook"}, {"LABEL_JumpPads", "Bounce Pads"}, {"LABEL_MagicBag", "Creatinator"}, {"LABEL_LowGravity", "Low Gravity"}, - {"LABEL_PowerGlove", "Grabinator"}, + {"LABEL_PowerGlove", "Grabinators"}, {"LABEL_ATTRACT_GEL", "Attract-o-Gel"}, {"LABEL_ATTRACT_TWEAK", "Attract-o-Tweaker"}, {"LABEL_HEROCAPE", "Hero Cape"}, {"LABEL_MEMORISER", "Memorizer"}, {"LABEL_WALLJUMP", "Wall Jump"}, {"LABEL_SINGLE_PLAYER", "Single Player"}, - {"LABEL_SurvivalChallenge", "Survival Challenge"}, {"LABEL_TOP_DOWN", "Top Down"}, {"LABEL_CO_OP", "Co-Op"}, {"LABEL_Sci_Fi", "Sci-Fi"}, {"LABEL_INTERACTIVE_STREAM", "Interactive Stream"}, {"LABEL_QUESTS", "Quests"}, + {"LABEL_Mini_Game", "Mini-Game"}, {"8_Bit", "8-bit"}, {"16_Bit", "16-bit"}, {"LABEL_SACKPOCKET", "Sackpocket"}, @@ -40,6 +92,17 @@ public static class LabelHelper {"LABEL_CREATED_CHARACTERS", "Created Characters"}, }; + public static bool isValidForGame(string label, GameVersion gameVersion) + { + return gameVersion switch + { + GameVersion.LittleBigPlanet1 => IsValidTag(label), + GameVersion.LittleBigPlanet2 => IsValidLabel(label) && !lbp3Labels.Contains(label), + GameVersion.LittleBigPlanet3 => IsValidLabel(label), + _ => false, + }; + } + public static bool IsValidTag(string tag) => Enum.IsDefined(typeof(LevelTags), tag.Replace("TAG_", "").Replace("-", "_")); public static bool IsValidLabel(string label) => Enum.IsDefined(typeof(LevelLabels), label); diff --git a/ProjectLighthouse/Helpers/SanitizationHelper.cs b/ProjectLighthouse/Helpers/SanitizationHelper.cs index 06882f02..3df81756 100644 --- a/ProjectLighthouse/Helpers/SanitizationHelper.cs +++ b/ProjectLighthouse/Helpers/SanitizationHelper.cs @@ -30,8 +30,9 @@ public static class SanitizationHelper } } - public static string SanitizeString(string input) + public static string SanitizeString(string? input) { + if (input == null) return ""; foreach ((string? key, string? value) in charsToReplace) { diff --git a/ProjectLighthouse/Levels/LevelLabels.cs b/ProjectLighthouse/Levels/LevelLabels.cs index 8d19d8f7..3fd15517 100644 --- a/ProjectLighthouse/Levels/LevelLabels.cs +++ b/ProjectLighthouse/Levels/LevelLabels.cs @@ -31,6 +31,7 @@ public enum LevelLabels LABEL_Strategy, LABEL_SurvivalChallenge, LABEL_Tutorial, + LABEL_Retro, LABEL_Collectables, LABEL_DirectControl, LABEL_Explosives, @@ -52,7 +53,6 @@ public enum LevelLabels LABEL_HEROCAPE, LABEL_MEMORISER, LABEL_WALLJUMP, - LABEL_Retro, LABEL_SINGLE_PLAYER, LABEL_RPG, LABEL_TOP_DOWN, diff --git a/ProjectLighthouse/Middlewares/MiddlewareDBContext.cs b/ProjectLighthouse/Middlewares/MiddlewareDBContext.cs new file mode 100644 index 00000000..77de227c --- /dev/null +++ b/ProjectLighthouse/Middlewares/MiddlewareDBContext.cs @@ -0,0 +1,21 @@ +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Http; + +namespace LBPUnion.ProjectLighthouse.Middlewares; + +public abstract class MiddlewareDBContext +{ + // this makes it consistent with typical middleware usage + [SuppressMessage("ReSharper", "InconsistentNaming")] + protected RequestDelegate next { get; } + + protected MiddlewareDBContext(RequestDelegate next) + { + this.next = next; + } + + [UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)] + public abstract Task InvokeAsync(HttpContext ctx, Database db); +} \ No newline at end of file diff --git a/ProjectLighthouse/Migrations/20220910190711_AddUserLanguageAndTimezone.cs b/ProjectLighthouse/Migrations/20220910190711_AddUserLanguageAndTimezone.cs new file mode 100644 index 00000000..a574128a --- /dev/null +++ b/ProjectLighthouse/Migrations/20220910190711_AddUserLanguageAndTimezone.cs @@ -0,0 +1,45 @@ +using System; +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + + [DbContext(typeof(Database))] + [Migration("20220910190711_AddUserLanguageAndTimezone")] + public partial class AddUserLanguageAndTimezone : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Language", + table: "Users", + type: "longtext", + defaultValue: "en", + nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AddColumn( + name: "TimeZone", + table: "Users", + type: "longtext", + defaultValue: TimeZoneInfo.Local.Id, + nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Language", + table: "Users"); + + migrationBuilder.DropColumn( + name: "TimeZone", + table: "Users"); + } + } +} diff --git a/ProjectLighthouse/Migrations/20220910190824_RemoveUserIsAPirate.cs b/ProjectLighthouse/Migrations/20220910190824_RemoveUserIsAPirate.cs new file mode 100644 index 00000000..11f81031 --- /dev/null +++ b/ProjectLighthouse/Migrations/20220910190824_RemoveUserIsAPirate.cs @@ -0,0 +1,33 @@ +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + + [DbContext(typeof(Database))] + [Migration("20220910190824_RemoveUserIsAPirate")] + public partial class RemoveUserIsAPirate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("UPDATE Users SET Language = \"en-PT\" WHERE isAPirate"); + + migrationBuilder.DropColumn( + name: "IsAPirate", + table: "Users"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsAPirate", + table: "Users", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + } + } +} diff --git a/ProjectLighthouse/PlayerData/Profiles/User.cs b/ProjectLighthouse/PlayerData/Profiles/User.cs index cfaaa6c3..73d9d3bb 100644 --- a/ProjectLighthouse/PlayerData/Profiles/User.cs +++ b/ProjectLighthouse/PlayerData/Profiles/User.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Diagnostics.CodeAnalysis; @@ -179,11 +180,10 @@ public class User [JsonIgnore] public string? ApprovedIPAddress { get; set; } #nullable disable - - /// - /// ARRR! Forces the user to see Pirate English translations on the website. - /// - public bool IsAPirate { get; set; } + + public string Language { get; set; } = "en"; + + public string TimeZone { get; set; } = TimeZoneInfo.Local.Id; public PrivacyType LevelVisibility { get; set; } = PrivacyType.All; diff --git a/ProjectLighthouse/PlayerData/Profiles/UserStatus.cs b/ProjectLighthouse/PlayerData/Profiles/UserStatus.cs index cfac696d..af718db3 100644 --- a/ProjectLighthouse/PlayerData/Profiles/UserStatus.cs +++ b/ProjectLighthouse/PlayerData/Profiles/UserStatus.cs @@ -47,7 +47,7 @@ public class UserStatus this.CurrentRoom = RoomHelper.FindRoomByUserId(userId); } - private string FormatOfflineTimestamp(string language) + private string FormatOfflineTimestamp(string language, string timeZone) { if (this.LastLogout <= 0 && this.LastLogin <= 0) { @@ -56,11 +56,12 @@ public class UserStatus long timestamp = this.LastLogout; if (timestamp <= 0) timestamp = this.LastLogin; - string formattedTime = DateTimeOffset.FromUnixTimeMilliseconds(timestamp).ToLocalTime().ToString("M/d/yyyy h:mm:ss tt"); + TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone); + string formattedTime = TimeZoneInfo.ConvertTime(DateTimeOffset.FromUnixTimeMilliseconds(timestamp), timeZoneInfo).ToString("M/d/yyyy h:mm:ss tt"); return StatusStrings.LastOnline.Translate(language, formattedTime); } - public string ToTranslatedString(string language) + public string ToTranslatedString(string language, string timeZone) { this.CurrentVersion ??= GameVersion.Unknown; this.CurrentPlatform ??= Platform.Unknown; @@ -69,7 +70,7 @@ public class UserStatus { StatusType.Online => StatusStrings.CurrentlyOnline.Translate(language, ((GameVersion)this.CurrentVersion).ToPrettyString(), (Platform)this.CurrentPlatform), - StatusType.Offline => this.FormatOfflineTimestamp(language), + StatusType.Offline => this.FormatOfflineTimestamp(language, timeZone), _ => GeneralStrings.Unknown.Translate(language), }; } diff --git a/ProjectLighthouse/ProjectLighthouse.csproj b/ProjectLighthouse/ProjectLighthouse.csproj index 01203ae1..1854fc34 100644 --- a/ProjectLighthouse/ProjectLighthouse.csproj +++ b/ProjectLighthouse/ProjectLighthouse.csproj @@ -10,7 +10,8 @@ - + + diff --git a/ProjectLighthouse/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs b/ProjectLighthouse/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs index 1071afdc..1ce63adf 100644 --- a/ProjectLighthouse/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs +++ b/ProjectLighthouse/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs @@ -730,8 +730,8 @@ namespace ProjectLighthouse.Migrations b.Property("IconHash") .HasColumnType("longtext"); - b.Property("IsAPirate") - .HasColumnType("tinyint(1)"); + b.Property("Language") + .HasColumnType("longtext"); b.Property("LastLogin") .HasColumnType("bigint"); @@ -772,6 +772,9 @@ namespace ProjectLighthouse.Migrations b.Property("ProfileVisibility") .HasColumnType("int"); + b.Property("TimeZone") + .HasColumnType("longtext"); + b.Property("Username") .IsRequired() .HasColumnType("longtext"); diff --git a/ProjectLighthouse/StaticFiles/css/styles.css b/ProjectLighthouse/StaticFiles/css/styles.css index c50eeac6..f9f7f285 100644 --- a/ProjectLighthouse/StaticFiles/css/styles.css +++ b/ProjectLighthouse/StaticFiles/css/styles.css @@ -159,4 +159,27 @@ div.cardStatsUnderTitle > span { margin-right: 0.5em; } -/*#endregion Comments*/ \ No newline at end of file +/*#endregion Comments*/ + +/*#region Slot labels */ + +.selected { + color: #fff !important; + background-color: #0e91f5 !important; +} + +.selected:hover { + background-color: #0084ea !important; +} + +.skew { + --scale: scale(1, 1); + --skew: rotate(0deg); + transition: transform 0.25s !important; + transform: var(--skew) var(--scale); +} +.skew:hover { + --scale: scale(1.2, 1.2); +} + +/*#endregion Slot labels */ \ No newline at end of file From db29a6b4583c460c805c78a5198c3f0abb029cf5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 17 Sep 2022 14:09:52 -0500 Subject: [PATCH 02/22] Update the localization files (#478) [skip ci] Update the localization files Co-authored-by: Crowdin Bot --- .../BaseLayout.lang-de-DE.resx | 2 +- .../BaseLayout.lang-ru-RU.resx | 18 +++++++++--------- .../Error.lang-ru-RU.resx | 8 ++++---- .../General.lang-ru-RU.resx | 4 ++-- .../LandingPage.lang-de-DE.resx | 8 ++++---- .../LandingPage.lang-ru-RU.resx | 8 ++++---- .../ModPanel.lang-ru-RU.resx | 8 ++++---- .../Profile.lang-ru-RU.resx | 6 +++--- .../Register.lang-ru-RU.resx | 2 +- .../Status.lang-de-DE.resx | 2 +- .../Status.lang-es-ES.resx | 2 +- .../Status.lang-pl-PL.resx | 2 +- .../Status.lang-ru-RU.resx | 4 ++-- 13 files changed, 37 insertions(+), 37 deletions(-) diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-de-DE.resx b/ProjectLighthouse.Localization/BaseLayout.lang-de-DE.resx index c505621d..c14abc8f 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-de-DE.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-de-DE.resx @@ -50,7 +50,7 @@ A quick shortcut on the header to take you to your profile if logged in. - Admin + Administrationsmenü A header link that takes you to the admin panel if available. diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-ru-RU.resx b/ProjectLighthouse.Localization/BaseLayout.lang-ru-RU.resx index 54a2c94b..0a804fcf 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-ru-RU.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-ru-RU.resx @@ -50,7 +50,7 @@ A quick shortcut on the header to take you to your profile if logged in. - Admin + Администратор A header link that takes you to the admin panel if available. @@ -58,30 +58,30 @@ A shortcut to log you out of your account. - Mod Panel + Панель модов - Page generated by {0}. + Страница создана {0} - This page was generated using a modified version of Project Lighthouse. Please make sure you are properly disclosing the source code to any users who may be using this instance. + Эта страница была создана с помощью модифицированной версии Project Lighthouse. Пожалуйста, убедитесь, что вы правильно раскрываете исходный код всем пользователям, которые могут использовать этот экземпляр. - While we intend to have as little JavaScript as possible, we can not guarantee everything will work without it. We recommend that you whitelist JavaScript for Project Lighthouse. + Хотя мы намерены использовать как можно меньше JavaScript, мы не можем гарантировать, что все будет работать без него. Мы рекомендуем включить JavaScript в белый список для освещения проекта Lighthouse. JavaScript не включен - Potential License Violation + Потенциальное нарушение лицензии - This instance is a public-facing instance that has been modified without the changes published. You may be in violation of the {0}. + Этот экземпляр является общедоступным экземпляром, который был изменен без публикации изменений. Вы могли нарушить {0}. - If you believe this is an error, please create an issue with the output of {0} ran from the root of the server source code in the description on our {1}issue tracker{2}. + Если вы считаете, что это ошибка, пожалуйста, создайте проблему с выводом {0} выполненным из корня исходного кода сервера в описании на нашем {1}трекера задач{2}. - If not, please publish the source code somewhere accessible to your users. + Если нет, пожалуйста, опубликуйте исходный код в доступном для ваших пользователей месте. \ No newline at end of file diff --git a/ProjectLighthouse.Localization/Error.lang-ru-RU.resx b/ProjectLighthouse.Localization/Error.lang-ru-RU.resx index da414456..10eec104 100644 --- a/ProjectLighthouse.Localization/Error.lang-ru-RU.resx +++ b/ProjectLighthouse.Localization/Error.lang-ru-RU.resx @@ -18,13 +18,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - The username field is blank. + Поле имени пользователя пустое. Выбранное имя пользователя уже занято. - Password field is required. + Требуется ввести пароль. Пароли не совпадают! @@ -36,10 +36,10 @@ Вы должны правильно завершить капчу. - The email address you've chosen is already taken. + Выбранный адрес электронной почты уже занят. - Email address field is required. + Введите адрес электронной почты. Вы были заблокированы. Пожалуйста, свяжитесь с администратором для получения дополнительной информации.\nПричина: {0} diff --git a/ProjectLighthouse.Localization/General.lang-ru-RU.resx b/ProjectLighthouse.Localization/General.lang-ru-RU.resx index 8c79526c..4717a55d 100644 --- a/ProjectLighthouse.Localization/General.lang-ru-RU.resx +++ b/ProjectLighthouse.Localization/General.lang-ru-RU.resx @@ -45,12 +45,12 @@ Сбросить пароль - Recent Activity + Недавняя активность Скоро будет! - Most recent photos + Самые последние фото \ No newline at end of file diff --git a/ProjectLighthouse.Localization/LandingPage.lang-de-DE.resx b/ProjectLighthouse.Localization/LandingPage.lang-de-DE.resx index d9a2625c..65c8b3b2 100644 --- a/ProjectLighthouse.Localization/LandingPage.lang-de-DE.resx +++ b/ProjectLighthouse.Localization/LandingPage.lang-de-DE.resx @@ -26,19 +26,19 @@ A greeting on the main page of the website. - Greetings, {0}. + Willkommen, {0}. A greeting on the main page of the website. - There are no people online. Why not hop on? + Es ist niemand online. Lust auf ein Spiel? A greeting on the main page of the website. - There is 1 person currently online: + Gerade ist ein Spieler online: A greeting on the main page of the website. - There are currently {0} people online: + Gerade sind {0} Spieler online: A greeting on the main page of the website. diff --git a/ProjectLighthouse.Localization/LandingPage.lang-ru-RU.resx b/ProjectLighthouse.Localization/LandingPage.lang-ru-RU.resx index b72e6b71..6ebf741d 100644 --- a/ProjectLighthouse.Localization/LandingPage.lang-ru-RU.resx +++ b/ProjectLighthouse.Localization/LandingPage.lang-ru-RU.resx @@ -26,19 +26,19 @@ A greeting on the main page of the website. - Greetings, {0}. + Приветствуем, {0}. A greeting on the main page of the website. - There are no people online. Why not hop on? + Нет людей в сети. Почему бы не продолжить играть? A greeting on the main page of the website. - There is 1 person currently online: + Сейчас в сети 1 пользователь: A greeting on the main page of the website. - There are currently {0} people online: + В настоящее время {0} человек в сети: A greeting on the main page of the website. diff --git a/ProjectLighthouse.Localization/ModPanel.lang-ru-RU.resx b/ProjectLighthouse.Localization/ModPanel.lang-ru-RU.resx index 0471ed47..fc305894 100644 --- a/ProjectLighthouse.Localization/ModPanel.lang-ru-RU.resx +++ b/ProjectLighthouse.Localization/ModPanel.lang-ru-RU.resx @@ -18,15 +18,15 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Moderation Panel + Панель модерации - Welcome to the moderation panel, {0}! + Добро пожаловать на панель модерации, {0}! - Banned Users + Заблокированные пользователи - Hidden Levels + Скрытые уровни \ No newline at end of file diff --git a/ProjectLighthouse.Localization/Profile.lang-ru-RU.resx b/ProjectLighthouse.Localization/Profile.lang-ru-RU.resx index e3252b0b..83d6633d 100644 --- a/ProjectLighthouse.Localization/Profile.lang-ru-RU.resx +++ b/ProjectLighthouse.Localization/Profile.lang-ru-RU.resx @@ -18,12 +18,12 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Biography + Биография - {0} hasn't introduced themselves yet. + {0} Ещё не представился. - {0}'s user page + Страница пользователя {0} \ No newline at end of file diff --git a/ProjectLighthouse.Localization/Register.lang-ru-RU.resx b/ProjectLighthouse.Localization/Register.lang-ru-RU.resx index abd741d2..d19ca775 100644 --- a/ProjectLighthouse.Localization/Register.lang-ru-RU.resx +++ b/ProjectLighthouse.Localization/Register.lang-ru-RU.resx @@ -18,6 +18,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Caution: Your username MUST match your PSN/RPCN username in order to be able to sign in from in-game. + Внимание: Ваше имя пользователя ДОЛЖНО соответствует вашему логину PSN/RPCN для того, чтобы иметь возможность войти в игру. \ No newline at end of file diff --git a/ProjectLighthouse.Localization/Status.lang-de-DE.resx b/ProjectLighthouse.Localization/Status.lang-de-DE.resx index abf5a0ae..6bb4b095 100644 --- a/ProjectLighthouse.Localization/Status.lang-de-DE.resx +++ b/ProjectLighthouse.Localization/Status.lang-de-DE.resx @@ -18,7 +18,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Currently playing {0} on {1} + Spielt gerade {0} auf {1} Offline diff --git a/ProjectLighthouse.Localization/Status.lang-es-ES.resx b/ProjectLighthouse.Localization/Status.lang-es-ES.resx index 55669b55..1c1858fe 100644 --- a/ProjectLighthouse.Localization/Status.lang-es-ES.resx +++ b/ProjectLighthouse.Localization/Status.lang-es-ES.resx @@ -24,6 +24,6 @@ Desconectado - Desconectado desde hace {0} + Desconectado desde el {0} \ No newline at end of file diff --git a/ProjectLighthouse.Localization/Status.lang-pl-PL.resx b/ProjectLighthouse.Localization/Status.lang-pl-PL.resx index d06737e7..d04e261c 100644 --- a/ProjectLighthouse.Localization/Status.lang-pl-PL.resx +++ b/ProjectLighthouse.Localization/Status.lang-pl-PL.resx @@ -18,7 +18,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Currently playing {0} on {1} + Online w {0} na {1} Offline diff --git a/ProjectLighthouse.Localization/Status.lang-ru-RU.resx b/ProjectLighthouse.Localization/Status.lang-ru-RU.resx index cd181c0b..b81904eb 100644 --- a/ProjectLighthouse.Localization/Status.lang-ru-RU.resx +++ b/ProjectLighthouse.Localization/Status.lang-ru-RU.resx @@ -18,12 +18,12 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Currently playing {0} on {1} + Сейчас играют {0} на {1} Не в сети - Offline since {0} + Последний раз был(а) в сети {0} \ No newline at end of file From 63c9a75c31742fd9b4ac5e031f8d113cc07db727 Mon Sep 17 00:00:00 2001 From: Slendy Date: Sat, 17 Sep 2022 14:37:47 -0500 Subject: [PATCH 03/22] Update EntityFramework and ASP.NET to 6.0.9 --- .config/dotnet-tools.json | 2 +- .../ProjectLighthouse.Servers.Website.csproj | 2 +- .../ProjectLighthouse.Tests.GameApiTests.csproj | 4 ++-- .../ProjectLighthouse.Tests.WebsiteTests.csproj | 4 ++-- ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj | 4 ++-- ProjectLighthouse/ProjectLighthouse.csproj | 6 +++--- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index b42ef99b..5660a96c 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-ef": { - "version": "6.0.8", + "version": "6.0.9", "commands": [ "dotnet-ef" ] diff --git a/ProjectLighthouse.Servers.Website/ProjectLighthouse.Servers.Website.csproj b/ProjectLighthouse.Servers.Website/ProjectLighthouse.Servers.Website.csproj index cf72bd1f..4c421f7b 100644 --- a/ProjectLighthouse.Servers.Website/ProjectLighthouse.Servers.Website.csproj +++ b/ProjectLighthouse.Servers.Website/ProjectLighthouse.Servers.Website.csproj @@ -33,7 +33,7 @@ - + diff --git a/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj b/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj index 3ba83d83..660e2cb4 100644 --- a/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj +++ b/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj @@ -9,8 +9,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj index 03535d62..33af481e 100644 --- a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj +++ b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj @@ -9,8 +9,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj index 9886d57e..b8c96bab 100644 --- a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj +++ b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj @@ -14,8 +14,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/ProjectLighthouse/ProjectLighthouse.csproj b/ProjectLighthouse/ProjectLighthouse.csproj index 1854fc34..9257197b 100644 --- a/ProjectLighthouse/ProjectLighthouse.csproj +++ b/ProjectLighthouse/ProjectLighthouse.csproj @@ -15,9 +15,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive From 098066292525faf5d84767e3d2a8d2afe74e6ce9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 17 Sep 2022 18:40:10 -0500 Subject: [PATCH 04/22] Bump Discord.Net.Webhook from 3.8.0 to 3.8.1 (#480) Bumps [Discord.Net.Webhook](https://github.com/Discord-Net/Discord.Net) from 3.8.0 to 3.8.1. - [Release notes](https://github.com/Discord-Net/Discord.Net/releases) - [Changelog](https://github.com/discord-net/Discord.Net/blob/dev/CHANGELOG.md) - [Commits](https://github.com/Discord-Net/Discord.Net/compare/3.8.0...3.8.1) --- updated-dependencies: - dependency-name: Discord.Net.Webhook dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- ProjectLighthouse/ProjectLighthouse.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectLighthouse/ProjectLighthouse.csproj b/ProjectLighthouse/ProjectLighthouse.csproj index 9257197b..68bc16dd 100644 --- a/ProjectLighthouse/ProjectLighthouse.csproj +++ b/ProjectLighthouse/ProjectLighthouse.csproj @@ -12,7 +12,7 @@ - + From 7249248e45b2ef9472f8a575ab5cd6771a83aac0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 17 Sep 2022 18:43:18 -0500 Subject: [PATCH 05/22] Bump Redis.OM from 0.2.1 to 0.2.2 (#479) Bumps [Redis.OM](https://github.com/redis/redis-om-dotnet) from 0.2.1 to 0.2.2. - [Release notes](https://github.com/redis/redis-om-dotnet/releases) - [Commits](https://github.com/redis/redis-om-dotnet/compare/v0.2.1...v0.2.2) --- updated-dependencies: - dependency-name: Redis.OM dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- ProjectLighthouse/ProjectLighthouse.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectLighthouse/ProjectLighthouse.csproj b/ProjectLighthouse/ProjectLighthouse.csproj index 68bc16dd..09cab326 100644 --- a/ProjectLighthouse/ProjectLighthouse.csproj +++ b/ProjectLighthouse/ProjectLighthouse.csproj @@ -22,7 +22,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + From 40d75626604c19276a88339c0cecfea6614e5ab0 Mon Sep 17 00:00:00 2001 From: Slendy Date: Sat, 17 Sep 2022 21:10:48 -0500 Subject: [PATCH 06/22] Fix total queued levels count on Vita --- .../Controllers/Slots/ListController.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ListController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ListController.cs index 84744a7c..3e1d051b 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ListController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ListController.cs @@ -53,8 +53,11 @@ public class ListController : ControllerBase return this.Ok ( - LbpSerializer.TaggedStringElement - ("slots", response, "total", this.database.QueuedLevels.Include(q => q.User).Count(q => q.User.Username == username)) + LbpSerializer.TaggedStringElement("slots", response, new Dictionary + { + { "total", await this.database.QueuedLevels.CountAsync(q => q.UserId == token.UserId) }, + { "hint_start", pageStart + Math.Min(pageSize, 30) }, + }) ); } @@ -136,7 +139,7 @@ public class ListController : ControllerBase ( LbpSerializer.TaggedStringElement("favouriteSlots", response, new Dictionary { - { "total", this.database.HeartedLevels.Count(q => q.UserId == targetUser.UserId) }, + { "total", await this.database.HeartedLevels.CountAsync(q => q.UserId == targetUser.UserId) }, { "hint_start", pageStart + Math.Min(pageSize, 30) }, }) ); @@ -225,7 +228,7 @@ public class ListController : ControllerBase ( LbpSerializer.TaggedStringElement("favouriteUsers", response, new Dictionary { - { "total", this.database.HeartedProfiles.Count(q => q.UserId == targetUser.UserId) }, + { "total", await this.database.HeartedProfiles.CountAsync(q => q.UserId == targetUser.UserId) }, { "hint_start", pageStart + Math.Min(pageSize, 30) }, }) ); From 2a44e85a3089ffe570bba7d6885cc303745597e9 Mon Sep 17 00:00:00 2001 From: Slendy Date: Sun, 18 Sep 2022 15:27:54 -0500 Subject: [PATCH 07/22] Limit text length and number of level labels --- .../Pages/SlotSettingsPage.cshtml.cs | 4 ++-- .../Pages/UserSettingsPage.cshtml.cs | 2 +- ProjectLighthouse/Helpers/LabelHelper.cs | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ProjectLighthouse.Servers.Website/Pages/SlotSettingsPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/SlotSettingsPage.cshtml.cs index 744d0389..a2ceedf4 100644 --- a/ProjectLighthouse.Servers.Website/Pages/SlotSettingsPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/SlotSettingsPage.cshtml.cs @@ -29,10 +29,10 @@ public class SlotSettingsPage : BaseLayout if (avatarHash != null) this.Slot.IconHash = avatarHash; name = SanitizationHelper.SanitizeString(name); - if (this.Slot.Name != name) this.Slot.Name = name; + if (this.Slot.Name != name && name.Length <= 64) this.Slot.Name = name; description = SanitizationHelper.SanitizeString(description); - if (this.Slot.Description != description) this.Slot.Description = description; + if (this.Slot.Description != description && description.Length <= 512) this.Slot.Description = description; labels = LabelHelper.RemoveInvalidLabels(SanitizationHelper.SanitizeString(labels)); if (this.Slot.AuthorLabels != labels) this.Slot.AuthorLabels = labels; diff --git a/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml.cs index 16ab05fe..78796aef 100644 --- a/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml.cs @@ -37,7 +37,7 @@ public class UserSettingsPage : BaseLayout biography = SanitizationHelper.SanitizeString(biography); - if (this.ProfileUser.Biography != biography) this.ProfileUser.Biography = biography; + if (this.ProfileUser.Biography != biography && biography.Length <= 512) this.ProfileUser.Biography = biography; if (ServerConfiguration.Instance.Mail.MailEnabled && IsValidEmail(email) && (this.User == this.ProfileUser || this.User.IsAdmin)) { diff --git a/ProjectLighthouse/Helpers/LabelHelper.cs b/ProjectLighthouse/Helpers/LabelHelper.cs index b88dddc9..7a9fddbd 100644 --- a/ProjectLighthouse/Helpers/LabelHelper.cs +++ b/ProjectLighthouse/Helpers/LabelHelper.cs @@ -110,6 +110,8 @@ public static class LabelHelper public static string RemoveInvalidLabels(string authorLabels) { List labels = new(authorLabels.Split(",")); + if (labels.Count > 5) labels = labels.GetRange(0, 5); + for (int i = labels.Count - 1; i >= 0; i--) { if (!IsValidLabel(labels[i])) labels.Remove(labels[i]); From 91441e67bc0e839ed78cceece293a27686d36520 Mon Sep 17 00:00:00 2001 From: Slendy Date: Sun, 18 Sep 2022 15:45:13 -0500 Subject: [PATCH 08/22] Fix language display name for chinese --- .../Pages/UserSettingsPage.cshtml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml index a81f2d37..5fe1bfc3 100644 --- a/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml @@ -88,7 +88,13 @@ function onSubmit(e){ selected = " selected=\"selected\""; } string langName = new CultureInfo(lang).DisplayName; - if (lang == "en-PT") langName = "Pirate Speak (The Seven Seas)"; + langName = lang switch + { + "en-PT" => "Pirate Speak (The Seven Seas)", + "zh-CN" => "Simplified Chinese", + "zh-TW" => "Traditional Chinese", + _ => langName, + }; } From de44991ea485ee9b00251a50b49ee971b2e2a972 Mon Sep 17 00:00:00 2001 From: Slendy Date: Sun, 18 Sep 2022 15:48:56 -0500 Subject: [PATCH 09/22] Fix word wrap on user biographies --- ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml index 7612b8bd..47e4fd90 100644 --- a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml @@ -103,7 +103,7 @@ } else { -

@HttpUtility.HtmlDecode(Model.ProfileUser.Biography)

+

@HttpUtility.HtmlDecode(Model.ProfileUser.Biography)

}
From f1eae89506ec86d50dd13cf633903e739a7f4580 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 18 Sep 2022 16:28:52 -0500 Subject: [PATCH 10/22] Update the localization files (#481) [skip ci] Update the localization files Co-authored-by: Crowdin Bot --- ProjectLighthouse.Localization/Status.lang-ru-RU.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectLighthouse.Localization/Status.lang-ru-RU.resx b/ProjectLighthouse.Localization/Status.lang-ru-RU.resx index b81904eb..f08be300 100644 --- a/ProjectLighthouse.Localization/Status.lang-ru-RU.resx +++ b/ProjectLighthouse.Localization/Status.lang-ru-RU.resx @@ -18,7 +18,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Сейчас играют {0} на {1} + Сейчас играет {0} на {1} Не в сети From abaae48552209d74c856cf90c09bdeb58329c3a2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 19 Sep 2022 21:10:57 -0500 Subject: [PATCH 11/22] Update the localization files (#485) [skip ci] Update the localization files Co-authored-by: Crowdin Bot --- ProjectLighthouse.Localization/BaseLayout.lang-es-ES.resx | 2 +- ProjectLighthouse.Localization/General.lang-es-ES.resx | 2 +- ProjectLighthouse.Localization/Status.lang-es-MX.resx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ProjectLighthouse.Localization/BaseLayout.lang-es-ES.resx b/ProjectLighthouse.Localization/BaseLayout.lang-es-ES.resx index 9d5bce4d..91d0115f 100644 --- a/ProjectLighthouse.Localization/BaseLayout.lang-es-ES.resx +++ b/ProjectLighthouse.Localization/BaseLayout.lang-es-ES.resx @@ -64,7 +64,7 @@ Página generada por {0}. - Esta página fue generada usando una versión modificada de Project Lighthouse. Por favor asegúrese de que está revelando correctamente el código principal a los usuarios que están usando está instancia + Esta página fue generada usando una versión modificada de Project Lighthouse. Por favor, asegúrese de que está revelando correctamente el código fuente a los usuarios que están usando está instancia Aunque intentemos tener el mínimo de JavaScript posible, no podemos garantizar que todo funcione sin él. Le recomendamos que actives JavaScript para Project Lighthouse. diff --git a/ProjectLighthouse.Localization/General.lang-es-ES.resx b/ProjectLighthouse.Localization/General.lang-es-ES.resx index bfb2f43f..aa1692e4 100644 --- a/ProjectLighthouse.Localization/General.lang-es-ES.resx +++ b/ProjectLighthouse.Localization/General.lang-es-ES.resx @@ -18,7 +18,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Nombre de usuario + Usuario Contraseña diff --git a/ProjectLighthouse.Localization/Status.lang-es-MX.resx b/ProjectLighthouse.Localization/Status.lang-es-MX.resx index 94d4150f..1c1858fe 100644 --- a/ProjectLighthouse.Localization/Status.lang-es-MX.resx +++ b/ProjectLighthouse.Localization/Status.lang-es-MX.resx @@ -24,6 +24,6 @@ Desconectado - Desconectado desde {0} + Desconectado desde el {0} \ No newline at end of file From 39a66b9cab38bd3b6f21a172569e8a32d5987277 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Sep 2022 14:24:54 -0500 Subject: [PATCH 12/22] Bump Redis.OM from 0.2.2 to 0.2.3 (#487) Bumps [Redis.OM](https://github.com/redis/redis-om-dotnet) from 0.2.2 to 0.2.3. - [Release notes](https://github.com/redis/redis-om-dotnet/releases) - [Commits](https://github.com/redis/redis-om-dotnet/compare/v0.2.2...v0.2.3) --- updated-dependencies: - dependency-name: Redis.OM dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- ProjectLighthouse/ProjectLighthouse.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectLighthouse/ProjectLighthouse.csproj b/ProjectLighthouse/ProjectLighthouse.csproj index 09cab326..c87a7fcd 100644 --- a/ProjectLighthouse/ProjectLighthouse.csproj +++ b/ProjectLighthouse/ProjectLighthouse.csproj @@ -22,7 +22,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + From e4bfab234f4f8e76bc1c1076b84ca54f4737c0d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Sep 2022 14:32:41 -0500 Subject: [PATCH 13/22] Bump YamlDotNet from 12.0.0 to 12.0.1 (#483) Bumps [YamlDotNet](https://github.com/aaubry/YamlDotNet) from 12.0.0 to 12.0.1. - [Release notes](https://github.com/aaubry/YamlDotNet/releases) - [Commits](https://github.com/aaubry/YamlDotNet/compare/v12.0.0...v12.0.1) --- updated-dependencies: - dependency-name: YamlDotNet dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- ProjectLighthouse/ProjectLighthouse.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectLighthouse/ProjectLighthouse.csproj b/ProjectLighthouse/ProjectLighthouse.csproj index c87a7fcd..74cd8d65 100644 --- a/ProjectLighthouse/ProjectLighthouse.csproj +++ b/ProjectLighthouse/ProjectLighthouse.csproj @@ -25,7 +25,7 @@ - + From 602f0c63d580c0dd1ab34f4b698672e053a4d43d Mon Sep 17 00:00:00 2001 From: Slendy Date: Tue, 20 Sep 2022 15:18:27 -0500 Subject: [PATCH 14/22] Don't redirect for StaticFiles --- .../Middlewares/UserRequiredRedirectMiddleware.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ProjectLighthouse.Servers.Website/Middlewares/UserRequiredRedirectMiddleware.cs b/ProjectLighthouse.Servers.Website/Middlewares/UserRequiredRedirectMiddleware.cs index db7cd058..7e6ba83a 100644 --- a/ProjectLighthouse.Servers.Website/Middlewares/UserRequiredRedirectMiddleware.cs +++ b/ProjectLighthouse.Servers.Website/Middlewares/UserRequiredRedirectMiddleware.cs @@ -18,6 +18,13 @@ public class UserRequiredRedirectMiddleware : MiddlewareDBContext return; } + // Request ends with a path (e.g. /css/style.css) + if (!string.IsNullOrEmpty(Path.GetExtension(ctx.Request.Path))) + { + await this.next(ctx); + return; + } + if (user.PasswordResetRequired && !ctx.Request.Path.StartsWithSegments("/passwordResetRequired") && !ctx.Request.Path.StartsWithSegments("/passwordReset")) { From b7c4f172985536c58470dfb44154cb59664ad553 Mon Sep 17 00:00:00 2001 From: Slendy Date: Tue, 20 Sep 2022 15:31:48 -0500 Subject: [PATCH 15/22] Fix bug in user redirection middleware --- .../Middlewares/UserRequiredRedirectMiddleware.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ProjectLighthouse.Servers.Website/Middlewares/UserRequiredRedirectMiddleware.cs b/ProjectLighthouse.Servers.Website/Middlewares/UserRequiredRedirectMiddleware.cs index 7e6ba83a..c571d833 100644 --- a/ProjectLighthouse.Servers.Website/Middlewares/UserRequiredRedirectMiddleware.cs +++ b/ProjectLighthouse.Servers.Website/Middlewares/UserRequiredRedirectMiddleware.cs @@ -25,10 +25,16 @@ public class UserRequiredRedirectMiddleware : MiddlewareDBContext return; } - if (user.PasswordResetRequired && !ctx.Request.Path.StartsWithSegments("/passwordResetRequired") && - !ctx.Request.Path.StartsWithSegments("/passwordReset")) + if (user.PasswordResetRequired) { - ctx.Response.Redirect("/passwordResetRequired"); + if (!ctx.Request.Path.StartsWithSegments("/passwordResetRequired") && + !ctx.Request.Path.StartsWithSegments("/passwordReset")) + { + ctx.Response.Redirect("/passwordResetRequired"); + return; + } + + await this.next(ctx); return; } From 972faeea841d277b9546a9fe9d7d647f0fbc50a3 Mon Sep 17 00:00:00 2001 From: Slendy Date: Tue, 20 Sep 2022 15:38:55 -0500 Subject: [PATCH 16/22] Add support for LBPV labels on the website --- ProjectLighthouse/Helpers/LabelHelper.cs | 30 +++++++++++++++++++++--- ProjectLighthouse/Levels/LevelLabels.cs | 17 ++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/ProjectLighthouse/Helpers/LabelHelper.cs b/ProjectLighthouse/Helpers/LabelHelper.cs index 7a9fddbd..8718f9ca 100644 --- a/ProjectLighthouse/Helpers/LabelHelper.cs +++ b/ProjectLighthouse/Helpers/LabelHelper.cs @@ -8,6 +8,24 @@ namespace LBPUnion.ProjectLighthouse.Helpers; public static class LabelHelper { + private static readonly List lbpVitaLabels = new() + { + "LABEL_Arcade", + "LABEL_Co_op", + "LABEL_Precision", + "LABEL_Controlinator", + "LABEL_Flick", + "LABEL_Memoriser", + "LABEL_MultiLevel", + "LABEL_Portrait", + "LABEL_RearTouch", + "LABEL_SharedScreen", + "LABEL_Swipe", + "LABEL_Tap", + "LABEL_Tilt", + "LABEL_Touch", + }; + private static readonly List lbp3Labels = new() { "LABEL_SINGLE_PLAYER", @@ -72,6 +90,11 @@ public static class LabelHelper {"LABEL_HEROCAPE", "Hero Cape"}, {"LABEL_MEMORISER", "Memorizer"}, {"LABEL_WALLJUMP", "Wall Jump"}, + {"Label_Controlinator", "Controllinator"}, + {"LABEL_MultiLevel", "Multi Level"}, + {"LABEL_Portrait", "Portrait View"}, + {"LABEL_RearTouch", "Rear touch pad"}, + {"LABEL_SharedScreen", "Shared Screen"}, {"LABEL_SINGLE_PLAYER", "Single Player"}, {"LABEL_TOP_DOWN", "Top Down"}, {"LABEL_CO_OP", "Co-Op"}, @@ -97,15 +120,16 @@ public static class LabelHelper return gameVersion switch { GameVersion.LittleBigPlanet1 => IsValidTag(label), - GameVersion.LittleBigPlanet2 => IsValidLabel(label) && !lbp3Labels.Contains(label), - GameVersion.LittleBigPlanet3 => IsValidLabel(label), + GameVersion.LittleBigPlanet2 => IsValidLabel(label) && !lbp3Labels.Contains(label) && !lbpVitaLabels.Contains(label), + GameVersion.LittleBigPlanetVita => IsValidLabel(label) && !lbp3Labels.Contains(label), + GameVersion.LittleBigPlanet3 => IsValidLabel(label) && !lbpVitaLabels.Contains(label), _ => false, }; } public static bool IsValidTag(string tag) => Enum.IsDefined(typeof(LevelTags), tag.Replace("TAG_", "").Replace("-", "_")); - public static bool IsValidLabel(string label) => Enum.IsDefined(typeof(LevelLabels), label); + private static bool IsValidLabel(string label) => Enum.IsDefined(typeof(LevelLabels), label.Replace("-", "_")); public static string RemoveInvalidLabels(string authorLabels) { diff --git a/ProjectLighthouse/Levels/LevelLabels.cs b/ProjectLighthouse/Levels/LevelLabels.cs index 3fd15517..186743f6 100644 --- a/ProjectLighthouse/Levels/LevelLabels.cs +++ b/ProjectLighthouse/Levels/LevelLabels.cs @@ -7,6 +7,7 @@ namespace LBPUnion.ProjectLighthouse.Levels; // I would remove the LABEL prefix, but some of the tags start with numbers and won't compile public enum LevelLabels { + // Start LBP2 Labels LABEL_SinglePlayer, LABEL_Multiplayer, LABEL_Quick, @@ -53,6 +54,22 @@ public enum LevelLabels LABEL_HEROCAPE, LABEL_MEMORISER, LABEL_WALLJUMP, + // Start LBP Vita Labels + LABEL_Arcade, + LABEL_Co_op, + LABEL_Precision, + LABEL_Controlinator, + LABEL_Flick, + LABEL_Memoriser, + LABEL_MultiLevel, + LABEL_Portrait, + LABEL_RearTouch, + LABEL_SharedScreen, + LABEL_Swipe, + LABEL_Tap, + LABEL_Tilt, + LABEL_Touch, + // Start LBP3 Labels LABEL_SINGLE_PLAYER, LABEL_RPG, LABEL_TOP_DOWN, From 83a905c8a293259fb0d1bc9da21237dce0c57b50 Mon Sep 17 00:00:00 2001 From: Slendy Date: Tue, 20 Sep 2022 15:56:42 -0500 Subject: [PATCH 17/22] Don't redirect for gameAssets either --- .../Middlewares/UserRequiredRedirectMiddleware.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectLighthouse.Servers.Website/Middlewares/UserRequiredRedirectMiddleware.cs b/ProjectLighthouse.Servers.Website/Middlewares/UserRequiredRedirectMiddleware.cs index c571d833..92d81793 100644 --- a/ProjectLighthouse.Servers.Website/Middlewares/UserRequiredRedirectMiddleware.cs +++ b/ProjectLighthouse.Servers.Website/Middlewares/UserRequiredRedirectMiddleware.cs @@ -19,7 +19,7 @@ public class UserRequiredRedirectMiddleware : MiddlewareDBContext } // Request ends with a path (e.g. /css/style.css) - if (!string.IsNullOrEmpty(Path.GetExtension(ctx.Request.Path))) + if (!string.IsNullOrEmpty(Path.GetExtension(ctx.Request.Path)) || ctx.Request.Path.StartsWithSegments("/gameAssets")) { await this.next(ctx); return; From dfd1d9b748cef4dd460a2751c42ac8e0d8231ba9 Mon Sep 17 00:00:00 2001 From: Dagg <32235163+daggintosh@users.noreply.github.com> Date: Tue, 20 Sep 2022 14:08:02 -0700 Subject: [PATCH 18/22] LittleBigPlanet 3 Adventure Support (#477) * Baseline LBP3 Adventure slot support VERY unsafe and hacky to use as of now, this is just testing the waters. * ADC file type checking * Refactor & trimming This might need to be adjusted if any feature is found to be missing * isAdventure added to API * Prototype Adventure icons for Website I am not an artist, please make this more in line with the originals. * Override border radius for LBP3 Adventures * Cleaning * Remove WriteLine and unused property * Remove unused libraries * Handle tracking and submitting of Adventure scores * Check for null instead of 0 Non-adventure slots will report null, not 0 * Score for adventure slot instead of main slot * Tweaks for PR * Identify levels for photos by level resource Verify this doesn't break anything. * SlotCardPartial merge with main changes * PR resolution 2 * probably not what was wanted Use variables for style extension * Internal slots already properly identified * Return line breaks to end of Slot.cs * Remove line break added by Github thanks * Github. * Make this a one-liner * Reduce to two lines * This can also be one line * This can *also* be one line * Update ProjectLighthouse.Servers.Website/Pages/Partials/SlotCardPartial.cshtml Co-authored-by: Josh * PR changes * Update ProjectLighthouse/Migrations/20220916141401_ScoreboardAdvSlot.cs Co-authored-by: Josh Co-authored-by: Josh --- .../Responses/MinimalSlot.cs | 2 ++ .../Controllers/Resources/PhotosController.cs | 8 +++-- .../Controllers/Slots/PublishController.cs | 22 ++++++++++--- .../Controllers/Slots/ScoreController.cs | 29 +++++++++++------- .../Pages/Partials/PhotoPartial.cshtml | 3 +- .../Pages/Partials/SlotCardPartial.cshtml | 8 +++-- ProjectLighthouse/Files/FileHelper.cs | 3 ++ ProjectLighthouse/Files/LbpFileType.cs | 1 + ProjectLighthouse/Levels/Slot.cs | 4 +++ .../20220916141401_ScoreboardAdvSlot.cs | 23 ++++++++++++++ .../20220918154500_AddIsAdventureColumn.cs | 23 ++++++++++++++ ProjectLighthouse/PlayerData/Score.cs | 3 ++ .../StaticFiles/assets/advSlotCardMask.png | Bin 0 -> 10278 bytes .../StaticFiles/assets/advSlotCardOverlay.png | Bin 0 -> 11745 bytes 14 files changed, 107 insertions(+), 22 deletions(-) create mode 100644 ProjectLighthouse/Migrations/20220916141401_ScoreboardAdvSlot.cs create mode 100644 ProjectLighthouse/Migrations/20220918154500_AddIsAdventureColumn.cs create mode 100644 ProjectLighthouse/StaticFiles/assets/advSlotCardMask.png create mode 100644 ProjectLighthouse/StaticFiles/assets/advSlotCardOverlay.png diff --git a/ProjectLighthouse.Servers.API/Responses/MinimalSlot.cs b/ProjectLighthouse.Servers.API/Responses/MinimalSlot.cs index f1d39c66..326b9265 100644 --- a/ProjectLighthouse.Servers.API/Responses/MinimalSlot.cs +++ b/ProjectLighthouse.Servers.API/Responses/MinimalSlot.cs @@ -10,6 +10,7 @@ public struct MinimalSlot public string Name { get; set; } public string IconHash { get; set; } public bool TeamPick { get; set; } + public bool IsAdventure { get; set; } public GameVersion GameVersion { get; set; } #if DEBUG public long FirstUploaded { get; set; } @@ -22,6 +23,7 @@ public struct MinimalSlot Name = slot.Name, IconHash = slot.IconHash, TeamPick = slot.TeamPick, + IsAdventure = slot.IsAdventurePlanet, GameVersion = slot.GameVersion, #if DEBUG FirstUploaded = slot.FirstUploaded, diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs index e19c432c..044236ee 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs @@ -63,8 +63,12 @@ public class PhotosController : ControllerBase { case SlotType.User: { - Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.Type == SlotType.User && s.SlotId == photoSlot.SlotId); - if (slot != null && !string.IsNullOrEmpty(slot.RootLevel)) validLevel = true; + // We'll grab the slot by the RootLevel and see what happens from here. + Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.Type == SlotType.User && s.ResourceCollection.Contains(photoSlot.RootLevel)); + if(slot == null) break; + + if (!string.IsNullOrEmpty(slot!.RootLevel)) validLevel = true; + if (slot.IsAdventurePlanet) photoSlot.SlotId = slot.SlotId; break; } case SlotType.Pod: diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/PublishController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/PublishController.cs index d462cfdd..f8f91830 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/PublishController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/PublishController.cs @@ -40,7 +40,8 @@ public class PublishController : ControllerBase GameToken gameToken = userAndToken.Value.Item2; Slot? slot = await this.getSlotFromBody(); - if (slot == null) { + if (slot == null) + { Logger.Warn("Rejecting level upload, slot is null", LogArea.Publish); return this.BadRequest(); // if the level cant be parsed then it obviously cant be uploaded } @@ -135,10 +136,21 @@ public class PublishController : ControllerBase return this.BadRequest(); } - if (rootLevel.FileType != LbpFileType.Level) + if (!slot.IsAdventurePlanet) { - Logger.Warn("Rejecting level upload, rootLevel is not a level", LogArea.Publish); - return this.BadRequest(); + if (rootLevel.FileType != LbpFileType.Level) + { + Logger.Warn("Rejecting level upload, rootLevel is not a level", LogArea.Publish); + return this.BadRequest(); + } + } + else + { + if (rootLevel.FileType != LbpFileType.Adventure) + { + Logger.Warn("Rejecting level upload, rootLevel is not a LBP 3 Adventure", LogArea.Publish); + return this.BadRequest(); + } } GameVersion slotVersion = FileHelper.ParseLevelVersion(rootLevel); @@ -232,7 +244,7 @@ public class PublishController : ControllerBase this.database.Slots.Add(slot); await this.database.SaveChangesAsync(); - + if (user.LevelVisibility == PrivacyType.All) { await WebhookHelper.SendWebhook("New level published!", diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ScoreController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ScoreController.cs index f25f4d57..f10f10c2 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ScoreController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ScoreController.cs @@ -26,7 +26,8 @@ public class ScoreController : ControllerBase } [HttpPost("scoreboard/{slotType}/{id:int}")] - public async Task SubmitScore(string slotType, int id, [FromQuery] bool lbp1 = false, [FromQuery] bool lbp2 = false, [FromQuery] bool lbp3 = false) + [HttpPost("scoreboard/{slotType}/{id:int}/{childId:int}")] + public async Task SubmitScore(string slotType, int id, int? childId, [FromQuery] bool lbp1 = false, [FromQuery] bool lbp2 = false, [FromQuery] bool lbp3 = false) { GameToken? token = await this.database.GameTokenFromRequest(this.Request); if (token == null) return this.StatusCode(403, ""); @@ -78,6 +79,7 @@ public class ScoreController : ControllerBase if (slotType == "developer") id = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer); score.SlotId = id; + score.ChildSlotId = childId; Slot? slot = this.database.Slots.FirstOrDefault(s => s.SlotId == score.SlotId); if (slot == null) @@ -116,12 +118,13 @@ public class ScoreController : ControllerBase Type = score.Type, Points = score.Points, SlotId = score.SlotId, + ChildSlotId = score.ChildSlotId }; IQueryable existingScore = this.database.Scores.Where(s => s.SlotId == playerScore.SlotId) - .Where(s => s.PlayerIdCollection == playerScore.PlayerIdCollection) - .Where(s => s.Type == playerScore.Type); - + .Where(s => childId != 0 || s.ChildSlotId == childId) + .Where(s => s.PlayerIdCollection == playerScore.PlayerIdCollection) + .Where(s => s.Type == playerScore.Type); if (existingScore.Any()) { Score first = existingScore.First(s => s.SlotId == playerScore.SlotId); @@ -137,13 +140,14 @@ public class ScoreController : ControllerBase await this.database.SaveChangesAsync(); - string myRanking = this.getScores(score.SlotId, score.Type, username, -1, 5, "scoreboardSegment"); + string myRanking = this.getScores(score.SlotId, score.Type, username, -1, 5, "scoreboardSegment", childId: score.ChildSlotId); return this.Ok(myRanking); } [HttpGet("friendscores/{slotType}/{slotId:int}/{type:int}")] - public async Task FriendScores(string slotType, int slotId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5) + [HttpGet("friendscores/{slotType}/{slotId:int}/{childId:int}/{type:int}")] + public async Task FriendScores(string slotType, int slotId, int? childId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5) { GameToken? token = await this.database.GameTokenFromRequest(this.Request); if (token == null) return this.StatusCode(403, ""); @@ -169,12 +173,13 @@ public class ScoreController : ControllerBase if (friendUsername != null) friendNames.Add(friendUsername); } - return this.Ok(this.getScores(slotId, type, username, pageStart, pageSize, "scores", friendNames.ToArray())); + return this.Ok(this.getScores(slotId, type, username, pageStart, pageSize, "scores", friendNames.ToArray(), childId)); } [HttpGet("topscores/{slotType}/{slotId:int}/{type:int}")] + [HttpGet("topscores/{slotType}/{slotId:int}/{childId:int}/{type:int}")] [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] - public async Task TopScores(string slotType, int slotId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5) + public async Task TopScores(string slotType, int slotId, int? childId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5) { GameToken? token = await this.database.GameTokenFromRequest(this.Request); if (token == null) return this.StatusCode(403, ""); @@ -187,7 +192,7 @@ public class ScoreController : ControllerBase if (slotType == "developer") slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer); - return this.Ok(this.getScores(slotId, type, username, pageStart, pageSize)); + return this.Ok(this.getScores(slotId, type, username, pageStart, pageSize, childId: childId)); } [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] @@ -199,7 +204,8 @@ public class ScoreController : ControllerBase int pageStart = -1, int pageSize = 5, string rootName = "scores", - string[]? playerIds = null + string[]? playerIds = null, + int? childId = 0 ) { @@ -207,8 +213,9 @@ public class ScoreController : ControllerBase // var needed for Anonymous type returned from SELECT var rankedScores = this.database.Scores .Where(s => s.SlotId == slotId && s.Type == type) - .AsEnumerable() + .Where(s => s.ChildSlotId == null || s.ChildSlotId == childId) .Where(s => playerIds == null || playerIds.Any(id => s.PlayerIdCollection.Contains(id))) + .AsEnumerable() .OrderByDescending(s => s.Points) .ThenBy(s => s.ScoreId) .ToList() diff --git a/ProjectLighthouse.Servers.Website/Pages/Partials/PhotoPartial.cshtml b/ProjectLighthouse.Servers.Website/Pages/Partials/PhotoPartial.cshtml index 3031af5e..89de6ad1 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Partials/PhotoPartial.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Partials/PhotoPartial.cshtml @@ -37,7 +37,8 @@ { case SlotType.User: - in level @HttpUtility.HtmlDecode(Model.Slot.Name) + @(Model.Slot.IsAdventurePlanet ? "on an adventure in" : "in level") + @HttpUtility.HtmlDecode(Model.Slot.Name) break; case SlotType.Developer: diff --git a/ProjectLighthouse.Servers.Website/Pages/Partials/SlotCardPartial.cshtml b/ProjectLighthouse.Servers.Website/Pages/Partials/SlotCardPartial.cshtml index 8608e537..aa166760 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Partials/SlotCardPartial.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Partials/SlotCardPartial.cshtml @@ -39,11 +39,13 @@
@{ int size = isMobile || mini ? 50 : 100; + bool isAdventure = Model.IsAdventurePlanet; + string advenStyleExt = isAdventure ? "-webkit-mask-image: url(/assets/advSlotCardMask.png); -webkit-mask-size: contain; border-radius: 0%;" : ""; }
- - - + +
diff --git a/ProjectLighthouse/Files/FileHelper.cs b/ProjectLighthouse/Files/FileHelper.cs index de4c0b98..b0906b51 100644 --- a/ProjectLighthouse/Files/FileHelper.cs +++ b/ProjectLighthouse/Files/FileHelper.cs @@ -64,6 +64,7 @@ public static class FileHelper LbpFileType.Texture => true, LbpFileType.Script => false, LbpFileType.Level => true, + LbpFileType.Adventure => true, LbpFileType.Voice => true, LbpFileType.Quest => true, LbpFileType.Plan => true, @@ -190,6 +191,8 @@ public static class FileHelper "FSHb" => LbpFileType.Script, "VOPb" => LbpFileType.Voice, "LVLb" => LbpFileType.Level, + "ADCb" => LbpFileType.Adventure, + "ADSb" => LbpFileType.Adventure, "PLNb" => LbpFileType.Plan, "QSTb" => LbpFileType.Quest, _ => readAlternateHeader(reader), diff --git a/ProjectLighthouse/Files/LbpFileType.cs b/ProjectLighthouse/Files/LbpFileType.cs index 7e4bdf03..4f9a3c2a 100644 --- a/ProjectLighthouse/Files/LbpFileType.cs +++ b/ProjectLighthouse/Files/LbpFileType.cs @@ -5,6 +5,7 @@ public enum LbpFileType Script, // .ff, FSH Texture, // TEX Level, // LVL + Adventure, // ADC, ADS CrossLevel, // PRF, Cross controller level FileArchive, // .farc, (ends with FARC) Plan, // PLN, uploaded with levels diff --git a/ProjectLighthouse/Levels/Slot.cs b/ProjectLighthouse/Levels/Slot.cs index 7171f473..cf981518 100644 --- a/ProjectLighthouse/Levels/Slot.cs +++ b/ProjectLighthouse/Levels/Slot.cs @@ -60,6 +60,9 @@ public class Slot [XmlElement("icon")] public string IconHash { get; set; } = ""; + [XmlElement("isAdventurePlanet")] + public bool IsAdventurePlanet { get; set; } + [XmlElement("rootLevel")] [JsonIgnore] public string RootLevel { get; set; } = ""; @@ -301,6 +304,7 @@ public class Slot LbpSerializer.StringElement("initiallyLocked", this.InitiallyLocked) + LbpSerializer.StringElement("isSubLevel", this.SubLevel) + LbpSerializer.StringElement("isLBP1Only", this.Lbp1Only) + + LbpSerializer.StringElement("isAdventurePlanet", this.IsAdventurePlanet) + LbpSerializer.StringElement("background", this.BackgroundHash) + LbpSerializer.StringElement("shareable", this.Shareable) + LbpSerializer.StringElement("authorLabels", this.AuthorLabels) + diff --git a/ProjectLighthouse/Migrations/20220916141401_ScoreboardAdvSlot.cs b/ProjectLighthouse/Migrations/20220916141401_ScoreboardAdvSlot.cs new file mode 100644 index 00000000..9c5cb567 --- /dev/null +++ b/ProjectLighthouse/Migrations/20220916141401_ScoreboardAdvSlot.cs @@ -0,0 +1,23 @@ +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(Database))] + [Migration("20220916141401_ScoreboardAdvSlot")] + public partial class CreateScoreboardAdvSlot : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ChildSlotId", + table: "Scores", + type: "int", + nullable: false, + defaultValue: 0); + } + } +} diff --git a/ProjectLighthouse/Migrations/20220918154500_AddIsAdventureColumn.cs b/ProjectLighthouse/Migrations/20220918154500_AddIsAdventureColumn.cs new file mode 100644 index 00000000..aecbffb1 --- /dev/null +++ b/ProjectLighthouse/Migrations/20220918154500_AddIsAdventureColumn.cs @@ -0,0 +1,23 @@ +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(Database))] + [Migration("20220918154500_AddIsAdventureColumn")] + public partial class AddisAdventureColumn : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsAdventurePlanet", + table: "Slots", + type: "bool", + nullable: false, + defaultValue: false); + } + } +} diff --git a/ProjectLighthouse/PlayerData/Score.cs b/ProjectLighthouse/PlayerData/Score.cs index 67ad2601..b584b43f 100644 --- a/ProjectLighthouse/PlayerData/Score.cs +++ b/ProjectLighthouse/PlayerData/Score.cs @@ -21,6 +21,9 @@ public class Score [ForeignKey(nameof(SlotId))] public Slot Slot { get; set; } + [XmlIgnore] + public int? ChildSlotId { get; set; } + [XmlElement("type")] public int Type { get; set; } diff --git a/ProjectLighthouse/StaticFiles/assets/advSlotCardMask.png b/ProjectLighthouse/StaticFiles/assets/advSlotCardMask.png new file mode 100644 index 0000000000000000000000000000000000000000..1a2e85bb7ad84565579456688d9c54c738dd96eb GIT binary patch literal 10278 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 JtaD0e0stfVg5LlD literal 0 HcmV?d00001 diff --git a/ProjectLighthouse/StaticFiles/assets/advSlotCardOverlay.png b/ProjectLighthouse/StaticFiles/assets/advSlotCardOverlay.png new file mode 100644 index 0000000000000000000000000000000000000000..f4223d5a0874530303f38838ebd24b1edd7e424f GIT binary patch literal 11745 zcmeAS@N?(olHy`uVBq!ia0y~yU}6Aa4mJh`hA$OYelajGa29w(7Bet#3xhBt!>lE`?vDlV*SK*YwXX2 zUP+YVF}v}@XZNbr%WBsYO+DDaC>!+o;oXaZ(i?hR(%zlvdA?oj^vp>Uos>S;{;z(2 zLy-IT@82Gimh93gvt{s{WKzk-;5kVvg<*oqN~clgXgG`}g3-({S`yH@M6_+F|9icD zFUKAR8-{m`-UmKD`+t)ykFmV*|Exa(s-BmEEm+quooD>}EV;bF_JKwL%a3RG|GmAR z=OE0`u%GEa_YFp|S@x&Tey&v#xsxkbZ`LU9G)cvFFYkl+4u*oB4q z#op*iDzcX(7wPlQdhYzFLH0x4XA9GRcOU(aRt`G0^I}i*Bo*6c z-A7mcM}4m8d~rKf;77xY_qzU3><`{P)1TH`zw>9rEY}~#JdBf6N?*zpp1yB%@x|pD z+Y^on{1vY~>wkJOKB#`+1~Rk6GeO?sVomes#S(i}JgugDezN#~;^&Pex8u2(%sck| zn;{>?`9aDsdqd8jr(avY@W$6}+vq)MiF3u&7l&&<+3dA@tFVDL=Kn(d56+Aa5J z?^f}&YPsI|Z+725@y_P2MjSQ__kM5G|CG#_&_2T=!0eyxr@4&k4WdbGi&Q*k_2@2o zIA7uX4}anB!2%WCH4Ohx;v- zi^QipK9IY(zy69pYlCgl%LAJ}zBjGk)@jRhDUxBk*ff>Q8nKh^|5qOotlQoo{u};3TW)Y}$oXTd#N)uyU{~;w!M;Vs(`rVy%l_ERdEaN*rwZ)gzEd+x|C7xC1{4%Pu>Ruhx$2GA?){HD79p|`9B9Pc$(c+A=MLvmyKMitL0tLGj6 zW}4mep8ejDkI8uvzx=JuEDi4)rO!UT@?(2vu+VRptjJ@V878U79ud1}f4$@0E{8RH zgV`FTUF7Yyq~AUGzRGyTrjN@HEPfUS{2V#2eqG^e(~}}{me3f8;ZZ)8&_Uq z_`tBkZTH9Jl3mUZSkAFdP9-Q;v$27%>@a+mV{yqtGb zgr|>jeeZ*6&q*fhxO!M!iU*{wf3Hqo}H9FAh;;p z>C1V$w+aRGcYMuPaT8_u)}Xybx_*~zfwYC+pMdBmsywQmL0^s*A369*tMXX=tr~sS z#?vnQWjg~|89JUezQ36F$M#Van+t;kgSh(-TUF0lrQ)4x^UZF(HmyIUP$0g;@>IUn zDFy?Ue{+vZ|F`?-yhdobd7R>XLj}*Ev%g&8ZM^2_)_-|npUYgc^5=~cJw^-+e@K%&;}We?O>Pl;7T27s=LGyJ&si9Sf!d^-S|G=l!w$YWR}>5&LfMB{Nw+-E6p={OjK&72nO5 zpWOa;>D12h&bpocUry+E)LDr==3-#juUoYDtX2Iq;Y1vuLMgMtc zJx{H_qn@{Nzx$Vq8zs{jezet`Px^JRtn2+wua<_*$ui9Rf>eqOxu-;5>( zm;Zm#<}^#BGcfG%`gicscdyM-igk?o46~Ev+EqL+Cro}({j_ZV-e<+B0y~V}HTGCD z++niIkz0N&zO8Y~^N%|X`<*8($t>ur`7*!kkN5XK%N0HdR{GA}C|SiJjnXdnWp;bAFf?SlY|@mj|CM+-vO)O3PtIQVNlQGf{J-q9 zdwccYDTNK(G4ro9sEIPjF}%N!_hIwe{OB>s#PfM)@c;7@^LzcT{|gqV zXn!$X*l;BS!%uc?>HiBPovpYxaQ(?@N}i;Wd8f{G&DVIjkIUEA2Mc(xpAt4c$*{pW zhD$^Gzh0%^fvC@gpp5eDq}av#*D}8EHR4#}{S>5z;e%bl{BQaT{MS_SeC)R1`jd9( z@g$W@nL6h$TTMRKcfP1jm9KI5a=dd^3BwI#F>?*+`dK_1ozL`?#4>nZvWZamb+h3` z{>xeaCzJ{7@PVjf_%UP0(SvI+xyI4k|j_0?*`#mR)&sjIp44{=`AdWUr3&0o{*{ovi-+MqXN~LbkKa4CGK6&md|+U1P|4i3 zM)+^`%opjaUFx-)4@`HFf8#Tqk>SDpPLU9^e~Ja{6*p@Bq$Vt0&FD3$ghATn{wu-S zH`a^RPjyV_-|_AK%5}^Krk|ZJsw8-q@!h0yH?M^cLc*D+sJvwGn>f4n)yDEn_Qor} zt*(|bF#NDBly6^Q^G#@zbvVS($Wg!NJb)PC8`@d#(&{?NT3=Jx_nq`=9=Q->_YLzU-I@ zD?`WM3Ewl!{}oL=VD~|$VBg}8o|8hX_~$76x;){|b_MoE+eQ0*r)F|9d~YnzF#jj} z=+;TQkKS?unckC@tjiTV`=90FaAKab>ISBKfrw)Bn7` z#K)9BIiGQk)@2C>1vO^71$Te^K60%qGqz#zf%Utk7O1Su+OPI2!0K0gmfUp52aF%; z!kPbQUY24gYc$U^|7ZJ2-*hhPhdu_+An{8V|8o{y237b$^0U1zOED;j`Rs7t{ZW6i zIM*xT*;+9#vKl5W(TY`=Rd026zt8U%_5Cm6<;zVEEH^yMn7|RU?Cy`tx?sJ| z_D{e5y|VaQ$G)xqU&PNdyDZ1R@Q($ODdPWmeg0AX;HjURy638+r(O2H6#SdMwK9gS zaec==)30fY3=VeOphoHgv%=Y~{T^x3pp0g6-K@jzvhuHUxr_LxIx3uvoz1yfh{2A1 zpGw_D=SSl8%YWVxGE^*J1?4pxvktrBIWMdQYG0ULoF3L;_q9@ev2{x%C#bF{3(}IV zk4m?^EB3?t!IdZNYM!%_mv{V|B=)!Q1*rMy{bl7$LvO|h>krfmB>VN&&yFZ6Y_woC zW-zmsoU4)jPKfd$_+>^Y(UdK4+T}sKM zC6&dqUTFV)_S5cS`6-1B4qukfG|Fat5S^5|bkoQ8lfH9U_$@J<&M{4;_fX`b^)l+~ zf6fp4} zd}V*xWB<*@myhwBPvn@OB708kqWiRydsWXedpIWazi>Cs3}Ze}`{3@g!mK~mXFn^> zW1XO4JNwfM``lNP`#=@Nbch;;1EooyTQ`0D-pMIB;s3+9xqmLGcpAS6{nV)9694it z9}|D4oyq1jONJcg>lvVg9Cs%B$VzEYO~IGc{X#x_%Wp49mUR=aGtKbaEW@Cn7H_lQ zFaH(wx^*S9o@soj0+nrNr(EycH&IVt->m<%-RFiE_Qtu_m=EX`{&v0j<8s}Z-3K_6 zYSTa_&O0V{(cDjlrE}Yx>(=;LOE4nhx3=& zI}P+16XbXJ%$cVtU4KjafyKs|hwYhP$MOVBGTC~q^WO*Vq-XU}ujPwCHO85LllL9a znKhk(;e-2w*PxKk)NK%bB>$m0$v@q5(xiByzlohM#4~vtj5un1zs%fe=+DTYGCt@FnZH&6Cw4pJbQN=TH?hEd2m-#zfzx0ZJ;@j-JX#bh1mD~*T1vTGt6z+N^ zRxo={;o+#yJA-9={U@2k`U%!1_U<`&K6g?58`Jr}XT8ubPQAy(aNvDMr_}w4()CfA z2DblIC;m|J{CZ2_mt)qS%%%Ik{IOm%|IfPBjD44F*c#S1`gdkquKc+Csg!ufZR=kL z($p7ER`KojT_nF|()Wos)z}+V7tOEvdt|@u<7b8p4tJLC^w|CJd)IO81KkH+p5*tP zWWwqvT$|8(=TG&P<^xI>{hwXh!`4vGAP*{1pUgL(%W7f$bD`&?Wd|ZZU9@+p_c;A~ znZgG{h&qOjt+N*1{ZV}+=iHXS3)}~0cJ}*By0X;R<*&c=Kc189-KhdQe%-h~NBOf1 z!#-xa47ugU_B;QZdAW8Ts2(Z3skC00`J#AP274oahn>;(XO;{Mdt6m^Zu+?V$aeob z_kYS1tm{7LIZ5rEOyKwFC-*M6U!%v`7`v$c%+^YNhWpIspmy4Olk@FQHZwd|w>R+& zVm~*z|KH_hcGg>(5AZ_OFC`U&&?El2X_mkHcZ z`*rNjYDEScmUS2N{$w_}SBf3j$uP;}{-v$|g`X^&|H~27)!%3MJ&S`uV0p3Jg}gt^ zkDmJ%Gxjm9FE_ICytHGl-<6+Rw*42n$bL%ULqpBYig#Cq7%sK+ZTk4$?0k8riN%4< z43kXOZ|kty?>6VvedSm6k}Q{weAK!k#K2&uqE!N+@ zP0mm6{1wmE_`cIFuy+>I0lAHHlW+dGtgCSQ$yLVlf!E)=PI`7zDe(H`RrQ(2>Tg-u zu{NG|nZLp!ASL2KfPUQ?=Rl_CBNT7V2{f_-3?M(Ss5Da z8Sh`p`@@{1^g!@Yc~O7GL61pk*S%eA<5gZ&&-xI{)|lV9FJP_|GsA(|Pf8iDb%-D1 z+xR-TYGclWNCwYYp|4$J{n<mDdW9?Nd;g0_21mZenrq)MurF5kNElDpD6vm;Rf@pXFpF% zFsWo-%Mtimy|Z$k!!&(KhT|Q0$-XS$WYa)#rp`)rA;5-GnbzzX1Uhb zyN*r6bLtI+S8t6jet17g|KvKM(yIOyw|{Z6HO&7e`a$yTvHJcUtf`F^7kL{lgPL(# zg|qIwxtx3B`w7Pc)*o{qN|%4G?-Y*&6)uee%NNBl`IQ=3dCsa|-eEQS-5#&)|E4-V zko(Ynl0Vl>l40IC^8>!Odh6T&{Ek+Qo01M{l%3tb*=25Q?mYcT>3^3i6ii-my^4#i zVR`w|pZPa`T%OA)pnpcZ;VdXSJ-e$G7=L>4|EJFixtJ!r-ZHO=@xjZxY<5|4%a6@J zbii(-gzNl>SwWLbte$uLs+zOsalF+pf5!*Pi~L?|N;2?0)9N?>S5(_zSD3#_(P5I? zW0}D4%Zuz&@9H)mxVnzbVl@u~dvX7b;9WJ0)26fd3FrrH=bpCYqKEtc?6aTU`~Q9K z+*PxF^2M_T%m)s}s>ju&KK4IZ;;~2Jdc$W>EBo2;%0~yZOn-$WE3!ArF4BA5z{9}) zy}@Jl1nK%&4Mr;SSq&IGE^W5-zw^DmX~lZ(1A1>2KIjEbFLhvRI9%CYad2vHy~7@h z%P|ef2eM;%{3bm+knJL?yZ$?W274pBi|sW<2?oA&_KMkMkMFC-E!llUwSm!(ZOW3v zR_<3WntlrW4>Hf}r-KiB3WLR-?~T8=NY~Gg=(bwxTg&ppH=KFO63MU9i|UrC-k9F; zD~hf0w~OpGMM(y}eEy2bWsmPKt`$pY-JzWDkbPg9%F5iv6|=RUEdF2nX<3dY$DW%t zKJ6(C6=w4VW(Q2^-S59I%`{_P{F|(XNhZm96Sn{I-TrIoviVI1Vi)~;B`Lu$@2tQM zpWPq%XWQRCGV7{)$ILaS?LU~RcqU3#w62)F`hathuWmD_$5O?{){y>PbK{?sn?I&^ zoYhnadLuNCb;6SQMH5#{Z+`Z(=<^;PrtO`(R;Q;hNc4I?c^$m++i7!0K9+4h0k3qE@~*j+Ss^fNk4syks7s9x=I zHzpU!78vE5$O;|9j7@&$L|4M$dCr*z%5DH}vhc`MmRAx$Ix!p~thT*%~hX zk}jAWaYD)TpX?_mht)^A|C~AEuI739B~PGu`0VH0PhS5!efINgm)ffwk_>L|xqhXA;VFq^;-PEGf+P$I92$E<^(VhG5@b0C3aVG-qfg6giRvbIRU|`4J zP!6gQ-%p5t11jBv=7Rdmi{zGZ)~5ivoImfi5>*xQtyWEQ{UYD3O4B^lLg96aJKS zy6T)dw%>ipomeH-^`Pd+=lt#!<=HNOrG>t3;9&}%bw2Y955we-f`!`NH-G#Fbz08O z2Y0@Yy;s_K&GE{9ySEAj(^pKN#lUBzTb9|d+}69h1}KaI1Lzl&MiN3ZCdK%cqQ{cj-T&rW|;Kstm~p{OBer_3w(b{ z;e%|Tx~U>t!^xZKm)^>~JvRRudxWal(I4swdz(LcUb0vtT>6%E(Yd9!7b${9QS9E? zKYX9s2mut^Ka-HR7moz2fdWfidCJ zCDV1AKE7w%-g!H|$|dO@Xt=^eN!rEswcyvcW*~P=c;=CLh=*bFch*9&1v!7pXPws- z%&5pL%ChWZv3s9VGS!%&BYj*!9soXVIzR=eISp$ZW=Ukuyp0|PVgHjuV=jQauE8?}E7|*tq<7Wz2vo}#?Yj{`Vy{016 z?4PWXsQv`?6La3CluRnANblIS?UqfvlK%VIO$XdvWJBC}817cB2Gu{+Pg)Is9q@c~ zR>gDk1D?Y3{~Pvvh_pJP*?b_n|F5s_6vhS7Pl387<&nxx4brThvqYm^?nY(L z(-$gT%fs}3vby7HWwwTQdwo?NZTh%;tN8W_IsJ2p3Y?(`A3lqo|_lj{PLguYl-OpAW%-Iel>$J!D~Xa_a8bm7!Dl*uWAzs4 z|3#nE8zVwra}+FG&FFPWSx=xeb7|#0Wxt=(9UsUA-ZydOVYr;i5wqy-4@Eu3oXOky z?fmZrI8WNO$>qvo#*MG;oBjN@UxcOX#O(#yT@nn-&M$w$Y83SN{lw|#- zMumZfx2l$DrZCKio8D=D$!I4iGjy2WRhoFfmRlk7QqbE-mAv=6U46bqE51*ebuKN- zOOj#P_2&oTJ8y1X`SHG5e0p@YUHPiTA3axH*Ie{(%|!j+dup$)#C_hmzVW=3)cko( zj0wB`PMaa{L-uXtR_?RUe;>)*(d*}?zS2$F<*w)IX?hdX)!z$$eZ?2J-{dF{!{xW@ zkG#35TJ-q7rd|D-pFco#z{;T6E_W@%YI53TSf+LCp83z}=fal?JPeoLYgJ00RZ-3D zxSb!z;^$uxGwD_FtQF?X&nm5qXD-P-yWXcF#)^gcz?a|b9|A$M;Ic}o`V-3UTU|f- zLgnfcoyzh{tc&=TzO4%u_|dTVmRIoBi;yXYskCn?6y(rG@>eniFL zECcg_Hx;vYoZpu7=VjdO+DEhQ6iYCv^xhK-jK4H#zw@U!RZWg1vc9IuJPey3S3iio z)muN^mn8!y}4|7%P00kgpQCeAzzo1ZEc^p-uopZm|6#ZO@Q3ma~Q%#dv-rb}F& zwr}OTB?<+-EBt3EvNhZ+X0CA9{gMB~XQp2V0v~+!b5qZpx5w|wP8 zH7Tu!G2z=Aq3x{GdhKJoZ>#Dt@)X1}c%Ijj{kvXpx;%J(yrR3tVUc7K!wg?}!O6L& zj?KUJsO`o?Q2v;F(Q_8Nc!$-dOE&)16Q?*nsD5!U<6+AX*X zSSuT?p5e#!ZIE~d=eMIfbbc6sUugqZzH{j_!07R~l_MitpBu0@Oe zPyhLr!QN=?B73RFh~dmSa7V>nWxdc2x13ua?Hj-L9n;ACwubpX(;k;If9@}?{c>M9 z@Aqz(eYye0Cm0NF|6{4~-u>}==WWJ*f&44)JNNEZeVMt3<&N?>g>?e#!pyfEA4D(u zm(eE4kal17QSZ!aFYKJo@cx)qA*?Udd2chrq`Fg98yv6nR=ddFRAp~0x&ME5EN{T# zWQG~F|M`DNU;GhlwBOY(cDfo1xbMC>xp{^7@sra<^94&&*Z;g@ejwgTYQEbE1_Svz zcb^kGee?cg_BOC8ZQb9a=x~Yuw!$ly`75NkU1U8SADC^td_RA;)B?BD39#^E#GBO|dv%|~hmZo%l|F>td`b^8!ZS*`Zzc&e#XFL13 zf(l^k*rvHT?W+RvEb9la51_(GLDUJkAYERxqExyzkq5KDi(P z387Ng;$sX3_3tKrf9BU~fA&dhgz5(W91)e3dC^Z?j|5%5zf_jxp6aWvXFZGwcCpN$ zDG|+w{GI0C3Li{8$?tn|I{_JABvm7)jvj3-@AZT{J+|?)jZ@prvtD#MI;>jTkSr0Z{;&7Qu4#o)lnhM?YU9pC>;1@gDK)M_*zh~6SD zb{P~R+xa_1;?4fqZCoVo!f3&?=i`y!HQ6&@AD*3DZTd%NR49*=R$u-$gQH*2Svad(Qkx z&EEEw;{&lk{SZcG2U|VC>t9ZRmO#lUPh+lSj(DF^GU-_6+=_a8|2d2vn}45H*x)Yn zceM~(L)9PFAA3M+VEQxuF+cippP7f@l1-HQz5A!1m^w_$U~klKurD{XC_T&&F#YtA zmHRe*d@nq?uB_wH2EH87D2(!U$35#WoBp%jBF^kx&K~=`p4F`TK=4>l7 z-RD&I-}wr2=m~~^<+l&~KKod%@a=z@Lfe2#HCA4G&c~~<|K;zjTEHdA@an5sVeSWw z4_t*=7c3Po{dpz${&RT8*@kbOvj0OgnH~1NV|*{Tob~cEg-Oq%qrUw%DQI57ylM*L zg1qaUum7ok*s5?fVPeJpe4+RCp*e~5_ig1>JXiI)*G^IiJ;9*h8Pv+dKtA)ue`ekw WpM`f@o`F_yGkCiCxvX Date: Tue, 20 Sep 2022 16:11:27 -0500 Subject: [PATCH 19/22] Bump SharpZipLib from 1.3.3 to 1.4.0 (#484) Bumps [SharpZipLib](https://github.com/icsharpcode/SharpZipLib) from 1.3.3 to 1.4.0. - [Release notes](https://github.com/icsharpcode/SharpZipLib/releases) - [Changelog](https://github.com/icsharpcode/SharpZipLib/blob/master/docs/Changes.txt) - [Commits](https://github.com/icsharpcode/SharpZipLib/compare/v1.3.3...v1.4.0) --- updated-dependencies: - dependency-name: SharpZipLib dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- ProjectLighthouse/ProjectLighthouse.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectLighthouse/ProjectLighthouse.csproj b/ProjectLighthouse/ProjectLighthouse.csproj index 74cd8d65..fd7a2b22 100644 --- a/ProjectLighthouse/ProjectLighthouse.csproj +++ b/ProjectLighthouse/ProjectLighthouse.csproj @@ -23,7 +23,7 @@ - + From a6c17b0e1632356d17dddd9191e469019bdc2bdb Mon Sep 17 00:00:00 2001 From: Slendy Date: Tue, 20 Sep 2022 16:55:56 -0500 Subject: [PATCH 20/22] Fix leaderboards for non-adventure levels --- .../Controllers/Slots/ScoreController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ScoreController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ScoreController.cs index f10f10c2..6a611f80 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ScoreController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ScoreController.cs @@ -118,11 +118,11 @@ public class ScoreController : ControllerBase Type = score.Type, Points = score.Points, SlotId = score.SlotId, - ChildSlotId = score.ChildSlotId + ChildSlotId = score.ChildSlotId, }; IQueryable existingScore = this.database.Scores.Where(s => s.SlotId == playerScore.SlotId) - .Where(s => childId != 0 || s.ChildSlotId == childId) + .Where(s => s.ChildSlotId == 0 || s.ChildSlotId == childId) .Where(s => s.PlayerIdCollection == playerScore.PlayerIdCollection) .Where(s => s.Type == playerScore.Type); if (existingScore.Any()) @@ -213,7 +213,7 @@ public class ScoreController : ControllerBase // var needed for Anonymous type returned from SELECT var rankedScores = this.database.Scores .Where(s => s.SlotId == slotId && s.Type == type) - .Where(s => s.ChildSlotId == null || s.ChildSlotId == childId) + .Where(s => s.ChildSlotId == 0 || s.ChildSlotId == childId) .Where(s => playerIds == null || playerIds.Any(id => s.PlayerIdCollection.Contains(id))) .AsEnumerable() .OrderByDescending(s => s.Points) From b26d96bacdf6fc31be4267cb2180fec57de3cfe3 Mon Sep 17 00:00:00 2001 From: Slendy Date: Tue, 20 Sep 2022 17:09:35 -0500 Subject: [PATCH 21/22] Make childId not nullable in SubmitScore --- .../Controllers/Slots/ScoreController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ScoreController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ScoreController.cs index 6a611f80..78f1305a 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ScoreController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ScoreController.cs @@ -27,7 +27,7 @@ public class ScoreController : ControllerBase [HttpPost("scoreboard/{slotType}/{id:int}")] [HttpPost("scoreboard/{slotType}/{id:int}/{childId:int}")] - public async Task SubmitScore(string slotType, int id, int? childId, [FromQuery] bool lbp1 = false, [FromQuery] bool lbp2 = false, [FromQuery] bool lbp3 = false) + public async Task SubmitScore(string slotType, int id, int childId, [FromQuery] bool lbp1 = false, [FromQuery] bool lbp2 = false, [FromQuery] bool lbp3 = false) { GameToken? token = await this.database.GameTokenFromRequest(this.Request); if (token == null) return this.StatusCode(403, ""); From 2cf2e6622a6e070124b80e7a4859f833fe0a4d35 Mon Sep 17 00:00:00 2001 From: Slendy Date: Thu, 22 Sep 2022 17:11:17 -0500 Subject: [PATCH 22/22] Prevent directory traversal attacks --- .../Resources/ResourcesController.cs | 6 ++++++ .../Controllers/ResourcesController.cs | 19 ++++++++++--------- ProjectLighthouse/Files/FileHelper.cs | 4 ++++ 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs index 43ec1d48..d1caa841 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs @@ -56,6 +56,12 @@ public class ResourcesController : ControllerBase string path = FileHelper.GetResourcePath(hash); + string fullPath = Path.GetFullPath(path); + string basePath = Path.GetFullPath(FileHelper.ResourcePath); + + // Prevent directory traversal attacks + if (!fullPath.StartsWith(basePath)) return this.BadRequest(); + if (FileHelper.ResourceExists(hash)) return this.File(IOFile.OpenRead(path), "application/octet-stream"); return this.NotFound(); diff --git a/ProjectLighthouse.Servers.Website/Controllers/ResourcesController.cs b/ProjectLighthouse.Servers.Website/Controllers/ResourcesController.cs index 3edafd84..cdef6071 100644 --- a/ProjectLighthouse.Servers.Website/Controllers/ResourcesController.cs +++ b/ProjectLighthouse.Servers.Website/Controllers/ResourcesController.cs @@ -11,18 +11,19 @@ public class ResourcesController : ControllerBase [HttpGet("/gameAssets/{hash}")] public IActionResult GetGameImage(string hash) { - string path = Path.Combine("png", $"{hash}.png"); + string path = FileHelper.GetImagePath($"{hash}.png"); - if (IOFile.Exists(path)) - { - return this.File(IOFile.OpenRead(path), "image/png"); - } + string fullPath = Path.GetFullPath(path); + string basePath = Path.GetFullPath(FileHelper.ImagePath); + + // Prevent directory traversal attacks + if (!fullPath.StartsWith(basePath)) return this.BadRequest(); + + if (IOFile.Exists(path)) return this.File(IOFile.OpenRead(path), "image/png"); LbpFile? file = LbpFile.FromHash(hash); - if (file != null && FileHelper.LbpFileToPNG(file)) - { - return this.File(IOFile.OpenRead(path), "image/png"); - } + if (file != null && FileHelper.LbpFileToPNG(file)) return this.File(IOFile.OpenRead(path), "image/png"); + return this.NotFound(); } } \ No newline at end of file diff --git a/ProjectLighthouse/Files/FileHelper.cs b/ProjectLighthouse/Files/FileHelper.cs index b0906b51..9e2d9452 100644 --- a/ProjectLighthouse/Files/FileHelper.cs +++ b/ProjectLighthouse/Files/FileHelper.cs @@ -24,8 +24,12 @@ public static class FileHelper { public static readonly string ResourcePath = Path.Combine(Environment.CurrentDirectory, "r"); + public static readonly string ImagePath = Path.Combine(Environment.CurrentDirectory, "png"); + public static string GetResourcePath(string hash) => Path.Combine(ResourcePath, hash); + public static string GetImagePath(string hash) => Path.Combine(ImagePath, hash); + public static bool AreDependenciesSafe(LbpFile file) { // recursively check if dependencies are safe