mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-07-31 09:18:38 +00:00
Merge master into ..i'm not typing that.
This commit is contained in:
commit
b3b5354c68
334 changed files with 5045 additions and 2956 deletions
|
@ -0,0 +1,20 @@
|
|||
@page "/admin/user/{id:int}/ban"
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Admin.AdminBanUserPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = "Ban " + Model.TargetedUser!.Username + "?";
|
||||
}
|
||||
|
||||
<p>Are you sure you want to ban this user?</p>
|
||||
|
||||
<form method="post">
|
||||
@Html.AntiForgeryToken()
|
||||
|
||||
<div class="ui left labeled input">
|
||||
<label for="text" class="ui blue label">Reason: </label>
|
||||
<input type="text" name="reason" id="text">
|
||||
</div><br><br>
|
||||
|
||||
<input type="submit" value="Yes, ban @Model.TargetedUser.Username!" id="submit" class="ui red button"><br>
|
||||
</form>
|
|
@ -0,0 +1,48 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Admin;
|
||||
|
||||
public class AdminBanUserPage : BaseLayout
|
||||
{
|
||||
|
||||
public User? TargetedUser;
|
||||
public AdminBanUserPage(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public async Task<IActionResult> OnGet([FromRoute] int id)
|
||||
{
|
||||
User? user = this.Database.UserFromWebRequest(this.Request);
|
||||
if (user == null || !user.IsAdmin) return this.NotFound();
|
||||
|
||||
this.TargetedUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == id);
|
||||
if (this.TargetedUser == null) return this.NotFound();
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPost([FromRoute] int id, string reason)
|
||||
{
|
||||
User? user = this.Database.UserFromWebRequest(this.Request);
|
||||
if (user == null || !user.IsAdmin) return this.NotFound();
|
||||
|
||||
this.TargetedUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == id);
|
||||
if (this.TargetedUser == null) return this.NotFound();
|
||||
|
||||
this.TargetedUser.Banned = true;
|
||||
this.TargetedUser.BannedReason = reason;
|
||||
|
||||
// invalidate all currently active gametokens
|
||||
this.Database.GameTokens.RemoveRange(this.Database.GameTokens.Where(t => t.UserId == this.TargetedUser.UserId));
|
||||
|
||||
// invalidate all currently active webtokens
|
||||
this.Database.WebTokens.RemoveRange(this.Database.WebTokens.Where(t => t.UserId == this.TargetedUser.UserId));
|
||||
|
||||
await this.Database.SaveChangesAsync();
|
||||
return this.Redirect($"/user/{this.TargetedUser.UserId}");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
@page "/admin"
|
||||
@using LBPUnion.ProjectLighthouse.Administration
|
||||
@using LBPUnion.ProjectLighthouse.Administration.Maintenance
|
||||
@using LBPUnion.ProjectLighthouse.Extensions
|
||||
@using LBPUnion.ProjectLighthouse.Helpers
|
||||
@using LBPUnion.ProjectLighthouse.Types
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Admin.AdminPanelPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = "Admin Panel";
|
||||
}
|
||||
|
||||
@if (Model.Log != null)
|
||||
{
|
||||
<div class="ui bottom attached message">
|
||||
<h2>Command Output</h2>
|
||||
@foreach (string line in Model.Log.Split("\n"))
|
||||
{
|
||||
<code>@line.TrimEnd()</code><br>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!this.Request.IsMobile())
|
||||
{
|
||||
<div class="ui center aligned grid">
|
||||
@foreach (AdminPanelStatistic statistic in Model.Statistics)
|
||||
{
|
||||
@await Html.PartialAsync("Partials/AdminPanelStatisticPartial", statistic)
|
||||
}
|
||||
</div>
|
||||
<br>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (AdminPanelStatistic statistic in Model.Statistics)
|
||||
{
|
||||
@await Html.PartialAsync("Partials/AdminPanelStatisticPartial", statistic)
|
||||
<br>
|
||||
}
|
||||
}
|
||||
|
||||
<h2>Commands</h2>
|
||||
@foreach (ICommand command in MaintenanceHelper.Commands)
|
||||
{
|
||||
<div class="ui blue segment">
|
||||
<h3>@command.Name()</h3>
|
||||
<form>
|
||||
<input type="text" name="command" style="display: none;" value="@command.FirstAlias">
|
||||
@if (command.RequiredArgs() > 0)
|
||||
{
|
||||
<div class="ui left action input" style="width: 100%">
|
||||
<button type="submit" class="ui green button">
|
||||
<i class="play icon"></i>
|
||||
Execute
|
||||
</button>
|
||||
<input type="text" name="args" placeholder="@command.Arguments()">
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="submit" class="ui green button">
|
||||
<i class="play icon"></i>
|
||||
Execute
|
||||
</button>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
<h2>Maintenance Jobs</h2>
|
||||
<p>
|
||||
<b>Warning: Interrupting Lighthouse during maintenance may leave the database in an unclean state.</b>
|
||||
</p>
|
||||
|
||||
@foreach (IMaintenanceJob job in MaintenanceHelper.MaintenanceJobs)
|
||||
{
|
||||
<div class="ui red segment">
|
||||
<h3>@job.Name()</h3>
|
||||
<p>@job.Description()</p>
|
||||
<form>
|
||||
<input type="text" name="maintenanceJob" style="display: none;" value="@job.GetType().Name">
|
||||
<button type="submit" class="ui green button">
|
||||
<i class="play icon"></i>
|
||||
Run Job
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Administration;
|
||||
using LBPUnion.ProjectLighthouse.Administration.Maintenance;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Admin;
|
||||
|
||||
public class AdminPanelPage : BaseLayout
|
||||
{
|
||||
public List<ICommand> Commands = MaintenanceHelper.Commands;
|
||||
public AdminPanelPage(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public List<AdminPanelStatistic> Statistics = new();
|
||||
|
||||
public string? Log;
|
||||
|
||||
public async Task<IActionResult> OnGet([FromQuery] string? args, [FromQuery] string? command, [FromQuery] string? maintenanceJob, [FromQuery] string? log)
|
||||
{
|
||||
User? user = this.Database.UserFromWebRequest(this.Request);
|
||||
if (user == null) return this.Redirect("~/login");
|
||||
if (!user.IsAdmin) return this.NotFound();
|
||||
|
||||
this.Statistics.Add(new AdminPanelStatistic("Users", await StatisticsHelper.UserCount(), "users"));
|
||||
this.Statistics.Add(new AdminPanelStatistic("Slots", await StatisticsHelper.SlotCount()));
|
||||
this.Statistics.Add(new AdminPanelStatistic("Photos", await StatisticsHelper.PhotoCount()));
|
||||
this.Statistics.Add(new AdminPanelStatistic("Reports", await StatisticsHelper.ReportCount(), "reports/0"));
|
||||
|
||||
if (!string.IsNullOrEmpty(command))
|
||||
{
|
||||
args ??= "";
|
||||
args = command + " " + args;
|
||||
string[] split = args.Split(" ");
|
||||
|
||||
List<LogLine> runCommand = await MaintenanceHelper.RunCommand(split);
|
||||
return this.Redirect($"~/admin?log={CryptoHelper.ToBase64(runCommand.ToLogString())}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(maintenanceJob))
|
||||
{
|
||||
await MaintenanceHelper.RunMaintenanceJob(maintenanceJob);
|
||||
return this.Redirect("~/admin");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(log))
|
||||
{
|
||||
this.Log = CryptoHelper.FromBase64(log);
|
||||
}
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
@page "/admin/users"
|
||||
@using LBPUnion.ProjectLighthouse.PlayerData.Profiles
|
||||
@using LBPUnion.ProjectLighthouse.Types
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Admin.AdminPanelUsersPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = "Users";
|
||||
}
|
||||
|
||||
<p>There are currently @Model.UserCount users registered to your instance.</p>
|
||||
<p><b>Note:</b> Users are ordered by most-recent-first.</p>
|
||||
|
||||
<div class="ui grid">
|
||||
@foreach (User user in Model.Users)
|
||||
{
|
||||
string color = "blue";
|
||||
string subtitle = "User";
|
||||
if (user.IsAdmin)
|
||||
{
|
||||
color = "yellow";
|
||||
subtitle = "Admin";
|
||||
}
|
||||
if (user.Banned)
|
||||
{
|
||||
color = "red";
|
||||
subtitle = $"Banned user! Reason: {user.BannedReason}";
|
||||
}
|
||||
|
||||
subtitle += $" (id: {user.UserId})";
|
||||
|
||||
<div class="eight wide column">
|
||||
<div class="ui @color segment">
|
||||
<h2>
|
||||
<a href="/user/@user.UserId">@user.Username</a>
|
||||
</h2>
|
||||
<h3>@subtitle</h3>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
|
@ -0,0 +1,29 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Admin;
|
||||
|
||||
public class AdminPanelUsersPage : BaseLayout
|
||||
{
|
||||
public int UserCount;
|
||||
|
||||
public List<User> Users = new();
|
||||
public AdminPanelUsersPage(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public async Task<IActionResult> OnGet()
|
||||
{
|
||||
User? user = this.Database.UserFromWebRequest(this.Request);
|
||||
if (user == null) return this.Redirect("~/login");
|
||||
if (!user.IsAdmin) return this.NotFound();
|
||||
|
||||
this.Users = await this.Database.Users.OrderByDescending(u => u.UserId).ToListAsync();
|
||||
this.UserCount = this.Users.Count;
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
@page "/admin/user/{id:int}/setGrantedSlots"
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Admin.AdminSetGrantedSlotsPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = "Set granted slots for " + Model.TargetedUser!.Username;
|
||||
}
|
||||
|
||||
@await Html.PartialAsync("Partials/AdminSetGrantedSlotsFormPartial", Model.TargetedUser)
|
|
@ -0,0 +1,41 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Admin;
|
||||
|
||||
public class AdminSetGrantedSlotsPage : BaseLayout
|
||||
{
|
||||
public AdminSetGrantedSlotsPage(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public User? TargetedUser;
|
||||
|
||||
public async Task<IActionResult> OnGet([FromRoute] int id)
|
||||
{
|
||||
User? user = this.Database.UserFromWebRequest(this.Request);
|
||||
if (user == null || !user.IsAdmin) return this.NotFound();
|
||||
|
||||
this.TargetedUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == id);
|
||||
if (this.TargetedUser == null) return this.NotFound();
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPost([FromRoute] int id, int grantedSlotCount)
|
||||
{
|
||||
User? user = this.Database.UserFromWebRequest(this.Request);
|
||||
if (user == null || !user.IsAdmin) return this.NotFound();
|
||||
|
||||
this.TargetedUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == id);
|
||||
if (this.TargetedUser == null) return this.NotFound();
|
||||
|
||||
this.TargetedUser.AdminGrantedSlots = grantedSlotCount;
|
||||
|
||||
await this.Database.SaveChangesAsync();
|
||||
return this.Redirect($"/user/{this.TargetedUser.UserId}");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
@page "/verifyEmail"
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.CompleteEmailVerificationPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
|
||||
if (Model.Error == null)
|
||||
{
|
||||
Model.Title = "Email Address Verified";
|
||||
}
|
||||
else
|
||||
{
|
||||
Model.Title = "Couldn't verify email address";
|
||||
}
|
||||
}
|
||||
|
||||
@if (Model.Error != null)
|
||||
{
|
||||
<p>
|
||||
<b>Reason:</b> @Model.Error
|
||||
</p>
|
||||
<p>Please try again. If the error persists, please contact the instance administrator.</p>
|
||||
<a href="/login/sendVerificationEmail">
|
||||
<div class="ui blue button">Resend email</div>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>Your email has been successfully verified. You may now close this tab.</p>
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
#nullable enable
|
||||
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;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
|
||||
public class CompleteEmailVerificationPage : BaseLayout
|
||||
{
|
||||
public CompleteEmailVerificationPage(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public string? Error;
|
||||
|
||||
public async Task<IActionResult> OnGet(string token)
|
||||
{
|
||||
if (!ServerConfiguration.Instance.Mail.MailEnabled) return this.NotFound();
|
||||
|
||||
User? user = this.Database.UserFromWebRequest(this.Request);
|
||||
if (user == null) return this.Redirect("~/login");
|
||||
|
||||
EmailVerificationToken? emailVerifyToken = await this.Database.EmailVerificationTokens.FirstOrDefaultAsync(e => e.EmailToken == token);
|
||||
if (emailVerifyToken == null)
|
||||
{
|
||||
this.Error = "Invalid verification token";
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
if (emailVerifyToken.UserId != user.UserId)
|
||||
{
|
||||
this.Error = "This token doesn't belong to you!";
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
this.Database.EmailVerificationTokens.Remove(emailVerifyToken);
|
||||
|
||||
user.EmailAddressVerified = true;
|
||||
|
||||
await this.Database.SaveChangesAsync();
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
@page "/debug/filter"
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Debug.FilterTestPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = "Debug - Filter Test";
|
||||
}
|
||||
|
||||
@if (Model.FilteredText != null)
|
||||
{
|
||||
<p>@Model.FilteredText</p>
|
||||
}
|
||||
|
||||
<form>
|
||||
<div class="ui right action input">
|
||||
<input type="text" name="text" placeholder="Text to be filtered" value="@(Model.Text ?? "")">
|
||||
<button type="submit" class="ui blue button">
|
||||
<i class="pen icon"></i>
|
||||
Filter
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
|
@ -0,0 +1,27 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Debug;
|
||||
|
||||
public class FilterTestPage : BaseLayout
|
||||
{
|
||||
public FilterTestPage(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public string? FilteredText;
|
||||
public string? Text;
|
||||
|
||||
public IActionResult OnGet(string? text = null)
|
||||
{
|
||||
#if !DEBUG
|
||||
return this.NotFound();
|
||||
#endif
|
||||
|
||||
if (text != null) this.FilteredText = CensorHelper.ScanMessage(text);
|
||||
this.Text = text;
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
@page "/debug/roomVisualizer"
|
||||
@using LBPUnion.ProjectLighthouse.Extensions
|
||||
@using LBPUnion.ProjectLighthouse.Helpers
|
||||
@using LBPUnion.ProjectLighthouse.Match.Rooms
|
||||
@using LBPUnion.ProjectLighthouse.PlayerData
|
||||
@using LBPUnion.ProjectLighthouse.PlayerData.Profiles
|
||||
@using LBPUnion.ProjectLighthouse.Types
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Debug.RoomVisualizerPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = "Debug - Room Visualizer";
|
||||
|
||||
const int refreshSeconds = 5;
|
||||
}
|
||||
|
||||
<script>
|
||||
let shouldRefresh = true;
|
||||
|
||||
setTimeout(() => {
|
||||
if (shouldRefresh) window.location.reload();
|
||||
}, @(refreshSeconds * 1000));
|
||||
|
||||
function stopRefreshing() {
|
||||
shouldRefresh = false;
|
||||
console.log("Stopped refresh");
|
||||
|
||||
const stopRefreshButton = document.getElementById("stop-refresh-button");
|
||||
stopRefreshButton.parentElement.removeChild(stopRefreshButton);
|
||||
console.log("Removed stop refresh button");
|
||||
}
|
||||
</script>
|
||||
|
||||
<p>This page will automatically refresh every @refreshSeconds seconds.</p>
|
||||
@* workaround for users w/o js*@
|
||||
<noscript>
|
||||
<b>You will not be able to disable auto-refresh without JavaScript. Please enable JavaScript for this functionality.</b><br>
|
||||
<meta http-equiv="refresh" content="@refreshSeconds">
|
||||
</noscript>
|
||||
|
||||
<p>@RoomHelper.Rooms.Count() rooms</p>
|
||||
|
||||
<a href="/debug/roomVisualizer/createFakeRoom">
|
||||
<div class="ui blue button">Create Fake Room</div>
|
||||
</a>
|
||||
|
||||
<a href="/debug/roomVisualizer/deleteRooms">
|
||||
<div class="ui red button">Nuke all rooms</div>
|
||||
</a>
|
||||
|
||||
<button class="ui blue button" onclick="stopRefreshing()" id="stop-refresh-button">Stop refreshing</button>
|
||||
|
||||
<h2>Best rooms for each game version</h2>
|
||||
@foreach (GameVersion version in Enum.GetValues<GameVersion>())
|
||||
{
|
||||
#nullable enable
|
||||
if (version == GameVersion.LittleBigPlanet1 || version == GameVersion.LittleBigPlanetPSP || version == GameVersion.Unknown) continue;
|
||||
|
||||
FindBestRoomResponse? response = RoomHelper.FindBestRoom(null, version, null, null, null);
|
||||
string text = response == null ? "No room found." : "Room " + response.RoomId;
|
||||
|
||||
<p><b>Best room for @version.ToPrettyString()</b>: @text</p>
|
||||
}
|
||||
|
||||
<h2>Room display</h2>
|
||||
|
||||
@foreach (Room room in RoomHelper.Rooms)
|
||||
{
|
||||
bool userInRoom = room.PlayerIds.Contains(Model.User?.UserId ?? -1);
|
||||
string color = userInRoom ? "green" : "blue";
|
||||
<div class="ui @color inverted segment">
|
||||
<h3>Room @room.RoomId</h3>
|
||||
@if (userInRoom)
|
||||
{
|
||||
<p>
|
||||
<b>You are currently in this room.</b>
|
||||
</p>
|
||||
}
|
||||
<p>@room.PlayerIds.Count players, state is @room.State, version is @room.RoomVersion.ToPrettyString() on platform @room.RoomPlatform</p>
|
||||
<p>Slot type: @room.Slot.SlotType, slot id: @room.Slot.SlotId</p>
|
||||
@foreach (User player in room.GetPlayers(Model.Database))
|
||||
{
|
||||
<div class="ui segment">@player.Username</div>
|
||||
}
|
||||
</div>
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
#nullable enable
|
||||
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
#if !DEBUG
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
#endif
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Debug;
|
||||
|
||||
public class RoomVisualizerPage : BaseLayout
|
||||
{
|
||||
public RoomVisualizerPage(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public IActionResult OnGet()
|
||||
{
|
||||
#if !DEBUG
|
||||
User? user = this.Database.UserFromWebRequest(this.Request);
|
||||
if (user == null || !user.IsAdmin) return this.NotFound();
|
||||
#endif
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
@page "/debug/version"
|
||||
@using LBPUnion.ProjectLighthouse.Helpers
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Debug.VersionInfoPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = "Debug - Version Information";
|
||||
Model.Description = VersionHelper.FullVersion;
|
||||
}
|
||||
|
||||
<h2>Build specific information</h2>
|
||||
<p><b>FullVersion</b>: @VersionHelper.FullVersion</p>
|
||||
<br>
|
||||
<p><b>Branch</b>: @VersionHelper.Branch</p>
|
||||
<p><b>Build</b>: @VersionHelper.Build</p>
|
||||
<p><b>CommitHash</b>: @VersionHelper.CommitHash</p>
|
||||
<p><b>IsDirty</b>: @VersionHelper.IsDirty</p>
|
||||
<p><b>CanCheckForUpdates</b>: @VersionHelper.CanCheckForUpdates</p>
|
||||
<p><b>CommitsOutOfDate</b>: @VersionHelper.CommitsOutOfDate</p>
|
||||
|
||||
<h2>Remotes</h2>
|
||||
@foreach (string remote in VersionHelper.Remotes)
|
||||
{
|
||||
<p>@remote</p>
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using JetBrains.Annotations;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Debug;
|
||||
|
||||
public class VersionInfoPage : BaseLayout
|
||||
{
|
||||
public VersionInfoPage([NotNull] Database database) : base(database)
|
||||
{}
|
||||
public IActionResult OnGet() => this.Page();
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
@page "/authentication"
|
||||
@using LBPUnion.ProjectLighthouse.PlayerData
|
||||
@using LBPUnion.ProjectLighthouse.Types
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.ExternalAuth.AuthenticationPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = "Authentication";
|
||||
}
|
||||
|
||||
@if (Model.AuthenticationAttempts.Count == 0)
|
||||
{
|
||||
<p>You have no pending authentication attempts.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>You have @Model.AuthenticationAttempts.Count authentication attempts pending.</p>
|
||||
@if (Model.IpAddress != null)
|
||||
{
|
||||
<p>This device's IP address is <b>@(Model.IpAddress.ToString())</b>. If this matches with an authentication attempt below, then it's safe to assume the authentication attempt came from the same network as this device.</p>
|
||||
}
|
||||
}
|
||||
|
||||
<a href="/authentication/autoApprovals">
|
||||
<button class="ui small blue button">
|
||||
<i class="cog icon"></i>
|
||||
<span>Manage automatically approved IP addresses</span>
|
||||
</button>
|
||||
</a>
|
||||
<a href="/authentication/denyAll">
|
||||
<button class="ui small red button">
|
||||
<i class="x icon"></i>
|
||||
<span>Deny all</span>
|
||||
</button>
|
||||
</a>
|
||||
|
||||
@foreach (AuthenticationAttempt authAttempt in Model.AuthenticationAttempts)
|
||||
{
|
||||
DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(authAttempt.Timestamp);
|
||||
<div class="ui red segment">
|
||||
<p>A <b>@authAttempt.Platform</b> authentication request was logged at <b>@timestamp.ToString("MM/dd/yyyy @ h:mm tt") UTC</b> from the IP address <b>@authAttempt.IPAddress</b>.</p>
|
||||
<div>
|
||||
<a href="/authentication/autoApprove/@authAttempt.AuthenticationAttemptId">
|
||||
<button class="ui tiny green button">
|
||||
<i class="check icon"></i>
|
||||
<span>Automatically approve every time</span>
|
||||
</button>
|
||||
</a>
|
||||
<a href="/authentication/approve/@authAttempt.AuthenticationAttemptId">
|
||||
<button class="ui tiny yellow button">
|
||||
<i class="check icon"></i>
|
||||
<span>Approve this time</span>
|
||||
</button>
|
||||
</a>
|
||||
<a href="/authentication/deny/@authAttempt.AuthenticationAttemptId">
|
||||
<button class="ui tiny red button">
|
||||
<i class="x icon"></i>
|
||||
<span>Deny</span>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
#nullable enable
|
||||
using System.Net;
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.ExternalAuth;
|
||||
|
||||
public class AuthenticationPage : BaseLayout
|
||||
{
|
||||
|
||||
public List<AuthenticationAttempt> AuthenticationAttempts = new();
|
||||
|
||||
public IPAddress? IpAddress;
|
||||
public AuthenticationPage(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public IActionResult OnGet()
|
||||
{
|
||||
if (!ServerConfiguration.Instance.Authentication.UseExternalAuth) return this.NotFound();
|
||||
if (this.User == null) return this.StatusCode(403, "");
|
||||
|
||||
this.IpAddress = this.HttpContext.Connection.RemoteIpAddress;
|
||||
|
||||
this.AuthenticationAttempts = this.Database.AuthenticationAttempts.Include
|
||||
(a => a.GameToken)
|
||||
.Where(a => a.GameToken.UserId == this.User.UserId)
|
||||
.OrderByDescending(a => a.Timestamp)
|
||||
.ToList();
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
@page "/authentication/autoApprovals"
|
||||
@using LBPUnion.ProjectLighthouse.PlayerData.Profiles
|
||||
@using LBPUnion.ProjectLighthouse.Types
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.ExternalAuth.ManageUserApprovedIpAddressesPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = "Automatically approved IP addresses";
|
||||
}
|
||||
|
||||
|
||||
@foreach (UserApprovedIpAddress approvedIpAddress in Model.ApprovedIpAddresses)
|
||||
{
|
||||
<div class="ui blue segment">
|
||||
<p>@approvedIpAddress.IpAddress</p>
|
||||
<a href="/authentication/revokeAutoApproval/@approvedIpAddress.UserApprovedIpAddressId">
|
||||
<button class="ui red button">
|
||||
<i class="trash icon"></i>
|
||||
<span>Revoke</span>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.ExternalAuth;
|
||||
|
||||
public class ManageUserApprovedIpAddressesPage : BaseLayout
|
||||
{
|
||||
public List<UserApprovedIpAddress> ApprovedIpAddresses = new();
|
||||
|
||||
public ManageUserApprovedIpAddressesPage(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public async Task<IActionResult> OnGet()
|
||||
{
|
||||
User? user = this.Database.UserFromWebRequest(this.Request);
|
||||
if (user == null) return this.Redirect("/login");
|
||||
|
||||
this.ApprovedIpAddresses = await this.Database.UserApprovedIpAddresses.Where(a => a.UserId == user.UserId).ToListAsync();
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
}
|
81
ProjectLighthouse.Servers.Website/Pages/LandingPage.cshtml
Normal file
81
ProjectLighthouse.Servers.Website/Pages/LandingPage.cshtml
Normal file
|
@ -0,0 +1,81 @@
|
|||
@page "/"
|
||||
@using LBPUnion.ProjectLighthouse.Configuration
|
||||
@using LBPUnion.ProjectLighthouse.Extensions
|
||||
@using LBPUnion.ProjectLighthouse.PlayerData.Profiles
|
||||
@using LBPUnion.ProjectLighthouse.Levels
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.LandingPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.ShowTitleInPage = false;
|
||||
bool isMobile = this.Request.IsMobile();
|
||||
}
|
||||
<h1>Welcome to <b>@ServerConfiguration.Instance.Customization.ServerName</b>!</h1>
|
||||
|
||||
@if (Model.User != null)
|
||||
{
|
||||
<p>You are currently logged in as <b>@Model.User.Username</b>.</p>
|
||||
if (ServerConfiguration.Instance.Authentication.UseExternalAuth && Model.AuthenticationAttemptsCount > 0)
|
||||
{
|
||||
<p>
|
||||
<b>You have @Model.AuthenticationAttemptsCount authentication attempts pending. Click <a href="/authentication">here</a> to view them.</b>
|
||||
</p>
|
||||
}
|
||||
}
|
||||
|
||||
@if (Model.PlayersOnlineCount == 1)
|
||||
{
|
||||
<p>There is 1 user currently online:</p>
|
||||
@foreach (User user in Model.PlayersOnline)
|
||||
{
|
||||
<a href="/user/@user.UserId" title="@user.Status.ToString()">@user.Username</a>
|
||||
}
|
||||
}
|
||||
|
||||
else if (Model.PlayersOnlineCount == 0)
|
||||
{
|
||||
<p>There are no users online. Why not hop on?</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>There are currently @Model.PlayersOnlineCount users online:</p>
|
||||
@foreach (User user in Model.PlayersOnline)
|
||||
{
|
||||
<a href="/user/@user.UserId" title="@user.Status.ToString()">@user.Username</a>
|
||||
}
|
||||
}
|
||||
|
||||
<br>
|
||||
|
||||
<div class="@(isMobile ? "" : "ui center aligned grid")">
|
||||
<div class="eight wide column">
|
||||
<div class="ui pink segment">
|
||||
<h1><i class="ribbon icon"></i>Latest Team Picks</h1>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui left aligned segment">
|
||||
@foreach (Slot slot in Model.LatestTeamPicks!) @* Can't reach a point where this is null *@
|
||||
{
|
||||
@await Html.PartialAsync("Partials/SlotCardPartial", slot, Model.GetSlotViewData(slot.SlotId, isMobile))
|
||||
<br>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (isMobile)
|
||||
{
|
||||
<br>
|
||||
}
|
||||
<div class="eight wide column">
|
||||
<div class="ui blue segment">
|
||||
<h1><i class="certificate icon"></i>Newest Levels</h1>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui left aligned segment">
|
||||
@foreach (Slot slot in Model.NewestLevels!) @* Can't reach a point where this is null *@
|
||||
{
|
||||
@await Html.PartialAsync("Partials/SlotCardPartial", slot, Model.GetSlotViewData(slot.SlotId, isMobile))
|
||||
<br>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,76 @@
|
|||
#nullable enable
|
||||
using JetBrains.Annotations;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Levels;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
|
||||
public class LandingPage : BaseLayout
|
||||
{
|
||||
public LandingPage(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public int AuthenticationAttemptsCount;
|
||||
public List<User> PlayersOnline = new();
|
||||
|
||||
public int PlayersOnlineCount;
|
||||
|
||||
public List<Slot>? LatestTeamPicks;
|
||||
public List<Slot>? NewestLevels;
|
||||
|
||||
[UsedImplicitly]
|
||||
public async Task<IActionResult> OnGet()
|
||||
{
|
||||
User? user = this.Database.UserFromWebRequest(this.Request);
|
||||
if (user != null && user.PasswordResetRequired) return this.Redirect("~/passwordResetRequired");
|
||||
|
||||
this.PlayersOnlineCount = await StatisticsHelper.RecentMatches();
|
||||
|
||||
if (user != null)
|
||||
this.AuthenticationAttemptsCount = await this.Database.AuthenticationAttempts.Include
|
||||
(a => a.GameToken)
|
||||
.CountAsync(a => a.GameToken.UserId == user.UserId);
|
||||
|
||||
List<int> userIds = await this.Database.LastContacts.Where(l => TimeHelper.Timestamp - l.Timestamp < 300).Select(l => l.UserId).ToListAsync();
|
||||
|
||||
this.PlayersOnline = await this.Database.Users.Where(u => userIds.Contains(u.UserId)).ToListAsync();
|
||||
|
||||
const int maxShownLevels = 5;
|
||||
|
||||
this.LatestTeamPicks = await this.Database.Slots.Where
|
||||
(s => s.TeamPick)
|
||||
.OrderByDescending(s => s.FirstUploaded)
|
||||
.Take(maxShownLevels)
|
||||
.Include(s => s.Creator)
|
||||
.ToListAsync();
|
||||
|
||||
this.NewestLevels = await this.Database.Slots.OrderByDescending(s => s.FirstUploaded).Take(maxShownLevels).Include(s => s.Creator).ToListAsync();
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
public ViewDataDictionary GetSlotViewData(int slotId, bool isMobile = false)
|
||||
=> new(ViewData)
|
||||
{
|
||||
{
|
||||
"User", this.User
|
||||
},
|
||||
{
|
||||
"CallbackUrl", $"~/slot/{slotId}"
|
||||
},
|
||||
{
|
||||
"ShowLink", true
|
||||
},
|
||||
{
|
||||
"IsMini", true
|
||||
},
|
||||
{
|
||||
"IsMobile", isMobile
|
||||
},
|
||||
};
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
@using LBPUnion.ProjectLighthouse.Configuration
|
||||
@using LBPUnion.ProjectLighthouse.Extensions
|
||||
@using LBPUnion.ProjectLighthouse.Helpers
|
||||
@using LBPUnion.ProjectLighthouse.Types
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts.BaseLayout
|
||||
|
||||
@{
|
||||
if (Model!.User == null)
|
||||
{
|
||||
Model.NavigationItemsRight.Add(new PageNavigationItem("Login / Register", "/login", "sign in"));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ServerConfiguration.Instance.Authentication.UseExternalAuth)
|
||||
{
|
||||
Model.NavigationItems.Add(new PageNavigationItem("Authentication", "/authentication", "key"));
|
||||
}
|
||||
Model.NavigationItemsRight.Add(new PageNavigationItem("Profile", "/user/" + Model.User.UserId, "user alternate"));
|
||||
|
||||
@if (Model.User.IsAdmin)
|
||||
{
|
||||
Model.NavigationItemsRight.Add(new PageNavigationItem("Admin Panel", "/admin", "cogs"));
|
||||
}
|
||||
Model.NavigationItemsRight.Add(new PageNavigationItem("Log out", "/logout", "user alternate slash")); // should always be last
|
||||
}
|
||||
|
||||
Model.IsMobile = Model.Request.IsMobile();
|
||||
}
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
@if (Model.Title == string.Empty)
|
||||
{
|
||||
<title>@ServerConfiguration.Instance.Customization.ServerName</title>
|
||||
}
|
||||
else
|
||||
{
|
||||
<title>@ServerConfiguration.Instance.Customization.ServerName - @Model.Title</title>
|
||||
}
|
||||
<link rel="stylesheet" type="text/css" href="~/css/styles.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/fomantic-ui@2.8.8/dist/semantic.min.css">
|
||||
|
||||
@* Favicon *@
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="manifest" href="/site.webmanifest">
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#008cff">
|
||||
<meta name="msapplication-TileColor" content="#008cff">
|
||||
|
||||
@* Embed Stuff *@
|
||||
<meta name="theme-color" data-react-helmet="true" content="#008cff">
|
||||
<meta content="@ServerConfiguration.Instance.Customization.ServerName - @Model.Title" property="og:title">
|
||||
@if (!string.IsNullOrEmpty(Model.Description))
|
||||
{
|
||||
<meta content="@Model.Description" property="og:description">
|
||||
}
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
@* Google Analytics *@
|
||||
@if (ServerConfiguration.Instance.GoogleAnalytics.AnalyticsEnabled)
|
||||
{
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=@ServerConfiguration.Instance.GoogleAnalytics.Id"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', '@ServerConfiguration.Instance.GoogleAnalytics.Id');
|
||||
</script>
|
||||
}
|
||||
</head>
|
||||
<body>
|
||||
<div class="pageContainer">
|
||||
<header class="lighthouse-header">
|
||||
<div class="ui attached menu">
|
||||
<div class="ui container">
|
||||
@{
|
||||
string mobileIconStyle = Model.IsMobile ? "margin-right: 0;" : "";
|
||||
}
|
||||
@foreach (PageNavigationItem navigationItem in Model!.NavigationItems)
|
||||
{
|
||||
<a class="item" href="@navigationItem.Url">
|
||||
@if (navigationItem.Icon != null)
|
||||
{
|
||||
<i class="@navigationItem.Icon icon" style="@mobileIconStyle"></i>
|
||||
}
|
||||
|
||||
@if (!Model.IsMobile)
|
||||
{
|
||||
@navigationItem.Name
|
||||
}
|
||||
</a>
|
||||
}
|
||||
<div class="right menu">
|
||||
@foreach (PageNavigationItem navigationItem in Model!.NavigationItemsRight)
|
||||
{
|
||||
<a class="item" href="@navigationItem.Url">
|
||||
@if (navigationItem.Icon != null)
|
||||
{
|
||||
<i class="@navigationItem.Icon icon" style="@mobileIconStyle"></i>
|
||||
}
|
||||
|
||||
@if (!Model.IsMobile)
|
||||
{
|
||||
@navigationItem.Name
|
||||
}
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<noscript>
|
||||
<div class="ui bottom attached yellow message small">
|
||||
<div class="ui container">
|
||||
<div style="display: flex; align-items: center; font-size: 1.2rem;">
|
||||
<i class="warning icon"></i>
|
||||
<span style="font-size: 1.2rem;">JavaScript not enabled</span>
|
||||
</div>
|
||||
<p>
|
||||
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.
|
||||
It's not <i>too</i> bloated, we promise.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</noscript>
|
||||
@if (!ServerStatics.IsDebug && VersionHelper.IsDirty)
|
||||
{
|
||||
<div class="ui bottom attached red message large">
|
||||
<div class="ui container">
|
||||
<i class="warning icon"></i>
|
||||
<span style="font-size: 1.2rem;">Potential License Violation</span>
|
||||
<p>This instance is a public-facing instance that has been modified without the changes published. You may be in violation of the <a href="https://github.com/LBPUnion/project-lighthouse/blob/main/LICENSE">GNU Affero General Public License v3.0</a>.</p>
|
||||
<p>If you believe this is an error, please create an issue with the output of <code>git status</code> ran from the root of the server source code in the description on our <a href="https://github.com/LBPUnion/project-lighthouse/issues">issue tracker</a>.</p>
|
||||
<p>If not, please publish the source code somewhere accessible to your users.</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</header>
|
||||
<div class="main">
|
||||
<div class="ui container">
|
||||
<br>
|
||||
@if (Model.ShowTitleInPage)
|
||||
{
|
||||
<h1>@Model.Title</h1>
|
||||
}
|
||||
@RenderBody()
|
||||
<div style="height: 50px;"></div> @* makes it look nicer *@
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<div class="ui black attached inverted segment">
|
||||
<div class="ui container">
|
||||
|
||||
<p>Page generated by @VersionHelper.FullVersion.</p>
|
||||
@if (VersionHelper.IsDirty)
|
||||
{
|
||||
<p>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.</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@if (ServerStatics.IsDebug)
|
||||
{
|
||||
<div class="ui red attached inverted segment">
|
||||
<div class="ui container">
|
||||
<button type="button" class="ui inverted button collapsible">
|
||||
<b>Show/Hide Debug Info</b>
|
||||
</button>
|
||||
|
||||
<div style="display:none" id="lighthouse-debug-info">
|
||||
<br>
|
||||
<p>Model.IsMobile: @Model.IsMobile</p>
|
||||
<p>Model.Title: @Model.Title</p>
|
||||
<p>Model.Description: @Model.Description</p>
|
||||
<p>Model.User.UserId: @(Model.User?.UserId.ToString() ?? "(not logged in)")</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const collapsible = document.getElementsByClassName("collapsible");
|
||||
|
||||
for (let i = 0; i < collapsible.length; i++)
|
||||
{
|
||||
collapsible[i].addEventListener("click", function()
|
||||
{
|
||||
this.classList.toggle("active");
|
||||
const content = this.nextElementSibling;
|
||||
if (content.style.display === "block")
|
||||
{
|
||||
content.style.display = "none";
|
||||
}
|
||||
else
|
||||
{
|
||||
content.style.display = "block";
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
}
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,44 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
|
||||
public class BaseLayout : PageModel
|
||||
{
|
||||
|
||||
public readonly Database Database;
|
||||
|
||||
public readonly List<PageNavigationItem> NavigationItems = new()
|
||||
{
|
||||
new PageNavigationItem("Home", "/", "home"),
|
||||
new PageNavigationItem("Users", "/users/0", "user friends"),
|
||||
new PageNavigationItem("Photos", "/photos/0", "camera"),
|
||||
new PageNavigationItem("Levels", "/slots/0", "certificate"),
|
||||
};
|
||||
|
||||
public readonly List<PageNavigationItem> NavigationItemsRight = new();
|
||||
public string Description = string.Empty;
|
||||
|
||||
public bool IsMobile;
|
||||
|
||||
public bool ShowTitleInPage = true;
|
||||
|
||||
public string Title = string.Empty;
|
||||
|
||||
private User? user;
|
||||
public BaseLayout(Database database)
|
||||
{
|
||||
this.Database = database;
|
||||
}
|
||||
|
||||
public new User? User {
|
||||
get {
|
||||
if (this.user != null) return this.user;
|
||||
|
||||
return this.user = this.Database.UserFromWebRequest(this.Request);
|
||||
}
|
||||
set => this.user = value;
|
||||
}
|
||||
}
|
68
ProjectLighthouse.Servers.Website/Pages/LoginForm.cshtml
Normal file
68
ProjectLighthouse.Servers.Website/Pages/LoginForm.cshtml
Normal file
|
@ -0,0 +1,68 @@
|
|||
@page "/login"
|
||||
@using LBPUnion.ProjectLighthouse.Configuration
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.LoginForm
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = "Log in";
|
||||
}
|
||||
|
||||
<script src="https://geraintluff.github.io/sha256/sha256.min.js"></script>
|
||||
|
||||
<script>
|
||||
function onSubmit(form) {
|
||||
const passwordInput = document.getElementById("password");
|
||||
const passwordSubmit = document.getElementById("password-submit");
|
||||
|
||||
passwordSubmit.value = sha256(passwordInput.value);
|
||||
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Model.Error))
|
||||
{
|
||||
<div class="ui negative message">
|
||||
<div class="header">
|
||||
Uh oh!
|
||||
</div>
|
||||
<p style="white-space: pre-line">@Model.Error</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
<form class="ui form" onsubmit="return onSubmit(this)" method="post">
|
||||
@Html.AntiForgeryToken()
|
||||
|
||||
<div class="field">
|
||||
<label>Username</label>
|
||||
<div class="ui left icon input">
|
||||
<input type="text" name="username" id="text" placeholder="Username">
|
||||
<i class="user icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Password</label>
|
||||
<div class="ui left icon input">
|
||||
<input type="password" id="password" placeholder="Password">
|
||||
<input type="hidden" id="password-submit" name="password">
|
||||
<i class="lock icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (ServerConfiguration.Instance.Captcha.CaptchaEnabled)
|
||||
{
|
||||
@await Html.PartialAsync("Partials/CaptchaPartial")
|
||||
}
|
||||
|
||||
<input type="submit" value="Log in" id="submit" class="ui blue button">
|
||||
@if (ServerConfiguration.Instance.Authentication.RegistrationEnabled)
|
||||
{
|
||||
<a href="/register">
|
||||
<div class="ui button">
|
||||
<i class="user alternate add icon"></i>
|
||||
Register
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
</form>
|
113
ProjectLighthouse.Servers.Website/Pages/LoginForm.cshtml.cs
Normal file
113
ProjectLighthouse.Servers.Website/Pages/LoginForm.cshtml.cs
Normal file
|
@ -0,0 +1,113 @@
|
|||
#nullable enable
|
||||
using JetBrains.Annotations;
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
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;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
|
||||
public class LoginForm : BaseLayout
|
||||
{
|
||||
public LoginForm(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public string? Error { get; private set; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public async Task<IActionResult> OnPost(string username, string password)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
this.Error = "The username field is required.";
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
this.Error = "The password field is required.";
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
if (!await this.Request.CheckCaptchaValidity())
|
||||
{
|
||||
this.Error = "You must complete the captcha correctly.";
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
User? user = await this.Database.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
if (user == null)
|
||||
{
|
||||
Logger.Warn($"User {username} failed to login on web due to invalid username", LogArea.Login);
|
||||
this.Error = "The username or password you entered is invalid.";
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
if (!BCrypt.Net.BCrypt.Verify(password, user.Password))
|
||||
{
|
||||
Logger.Warn($"User {user.Username} (id: {user.UserId}) failed to login on web due to invalid password", LogArea.Login);
|
||||
this.Error = "The username or password you entered is invalid.";
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
if (user.Banned)
|
||||
{
|
||||
Logger.Warn($"User {user.Username} (id: {user.UserId}) failed to login on web due to being banned", LogArea.Login);
|
||||
this.Error = "You have been banned. Please contact an administrator for more information.\nReason: " + user.BannedReason;
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
if (user.EmailAddress == null && ServerConfiguration.Instance.Mail.MailEnabled)
|
||||
{
|
||||
Logger.Warn($"User {user.Username} (id: {user.UserId}) failed to login; email not set", LogArea.Login);
|
||||
|
||||
EmailSetToken emailSetToken = new()
|
||||
{
|
||||
UserId = user.UserId,
|
||||
User = user,
|
||||
EmailToken = CryptoHelper.GenerateAuthToken(),
|
||||
};
|
||||
|
||||
this.Database.EmailSetTokens.Add(emailSetToken);
|
||||
await this.Database.SaveChangesAsync();
|
||||
|
||||
return this.Redirect("/login/setEmail?token=" + emailSetToken.EmailToken);
|
||||
}
|
||||
|
||||
WebToken webToken = new()
|
||||
{
|
||||
UserId = user.UserId,
|
||||
UserToken = CryptoHelper.GenerateAuthToken(),
|
||||
};
|
||||
|
||||
this.Database.WebTokens.Add(webToken);
|
||||
await this.Database.SaveChangesAsync();
|
||||
|
||||
this.Response.Cookies.Append
|
||||
(
|
||||
"LighthouseToken",
|
||||
webToken.UserToken,
|
||||
new CookieOptions
|
||||
{
|
||||
Expires = DateTimeOffset.Now.AddDays(7),
|
||||
}
|
||||
);
|
||||
|
||||
Logger.Success($"User {user.Username} (id: {user.UserId}) successfully logged in on web", LogArea.Login);
|
||||
|
||||
if (user.PasswordResetRequired) return this.Redirect("~/passwordResetRequired");
|
||||
if (ServerConfiguration.Instance.Mail.MailEnabled && !user.EmailAddressVerified) return this.Redirect("~/login/sendVerificationEmail");
|
||||
|
||||
return this.RedirectToPage(nameof(LandingPage));
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public IActionResult OnGet() => this.Page();
|
||||
}
|
10
ProjectLighthouse.Servers.Website/Pages/LogoutPage.cshtml
Normal file
10
ProjectLighthouse.Servers.Website/Pages/LogoutPage.cshtml
Normal file
|
@ -0,0 +1,10 @@
|
|||
@page "/logout"
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.LogoutPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = "Logged out";
|
||||
}
|
||||
|
||||
<p>You have been successfully logged out. You will be redirected in 5 seconds, or you may click <a href="/">here</a> to do so manually.</p>
|
||||
<meta http-equiv="refresh" content="5; url=/"/>
|
25
ProjectLighthouse.Servers.Website/Pages/LogoutPage.cshtml.cs
Normal file
25
ProjectLighthouse.Servers.Website/Pages/LogoutPage.cshtml.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
|
||||
public class LogoutPage : BaseLayout
|
||||
{
|
||||
public LogoutPage(Database database) : base(database)
|
||||
{}
|
||||
public async Task<IActionResult> OnGet()
|
||||
{
|
||||
WebToken? token = this.Database.WebTokenFromRequest(this.Request);
|
||||
if (token == null) return this.BadRequest();
|
||||
|
||||
this.Database.WebTokens.Remove(token);
|
||||
await this.Database.SaveChangesAsync();
|
||||
|
||||
this.Response.Cookies.Delete("LighthouseToken");
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
@model LBPUnion.ProjectLighthouse.Administration.AdminPanelStatistic
|
||||
|
||||
<div class="three wide column">
|
||||
<div class="ui center aligned blue segment">
|
||||
@if (Model.ViewAllEndpoint != null)
|
||||
{
|
||||
<h2>
|
||||
<a href="/admin/@Model.ViewAllEndpoint">@Model.StatisticNamePlural</a>
|
||||
</h2>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h2>@Model.StatisticNamePlural</h2>
|
||||
}
|
||||
<h3>@Model.Count</h3>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,12 @@
|
|||
@model LBPUnion.ProjectLighthouse.PlayerData.Profiles.User
|
||||
|
||||
<form method="post" action="/admin/user/@Model.UserId/setGrantedSlots">
|
||||
@Html.AntiForgeryToken()
|
||||
<div class="ui left action input">
|
||||
<button type="submit" class="ui blue button">
|
||||
<i class="pencil icon"></i>
|
||||
<span>Set Granted Slots</span>
|
||||
</button>
|
||||
<input type="text" name="grantedSlotCount" placeholder="Granted Slots" value="@Model.AdminGrantedSlots">
|
||||
</div>
|
||||
</form>
|
|
@ -0,0 +1,6 @@
|
|||
@using LBPUnion.ProjectLighthouse.Configuration
|
||||
@if (ServerConfiguration.Instance.Captcha.CaptchaEnabled)
|
||||
{
|
||||
<div class="h-captcha" data-sitekey="@ServerConfiguration.Instance.Captcha.SiteKey"></div>
|
||||
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
@using System.Web
|
||||
@using LBPUnion.ProjectLighthouse.PlayerData.Profiles
|
||||
<div class="ui yellow segment" id="comments">
|
||||
<h2>Comments</h2>
|
||||
@if (Model.Comments.Count == 0 && Model.CommentsEnabled)
|
||||
{
|
||||
<p>There are no comments.</p>
|
||||
}
|
||||
else if (!Model.CommentsEnabled)
|
||||
{
|
||||
<b>
|
||||
<i>Comments are disabled.</i>
|
||||
</b>
|
||||
}
|
||||
else
|
||||
{
|
||||
int count = Model.Comments.Count;
|
||||
<p>There @(count == 1 ? "is" : "are") @count comment@(count == 1 ? "" : "s").</p>
|
||||
}
|
||||
|
||||
@if (Model.CommentsEnabled && Model.User != null)
|
||||
{
|
||||
<div class="ui divider"></div>
|
||||
<form class="ui reply form" action="@Url.RouteUrl(ViewContext.RouteData.Values)/postComment" method="post">
|
||||
<div class="field">
|
||||
<textarea style="min-height: 70px; height: 70px; max-height:120px" name="msg"></textarea>
|
||||
</div>
|
||||
<input type="submit" class="ui blue button">
|
||||
</form>
|
||||
@if (Model.Comments.Count > 0)
|
||||
{
|
||||
<div class="ui divider"></div>
|
||||
}
|
||||
}
|
||||
|
||||
@for(int i = 0; i < Model.Comments.Count; i++)
|
||||
{
|
||||
Comment comment = Model.Comments[i];
|
||||
DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(comment.Timestamp / 1000);
|
||||
StringWriter messageWriter = new();
|
||||
HttpUtility.HtmlDecode(comment.getComment(), messageWriter);
|
||||
|
||||
string decodedMessage = messageWriter.ToString();
|
||||
string? url = Url.RouteUrl(ViewContext.RouteData.Values);
|
||||
if (url == null) continue;
|
||||
|
||||
int rating = comment.ThumbsUp - comment.ThumbsDown;
|
||||
|
||||
<div style="display: flex" id="@comment.CommentId">
|
||||
<div class="voting">
|
||||
<a href="@url/rateComment?commentId=@(comment.CommentId)&rating=@(comment.YourThumb == 1 ? 0 : 1)">
|
||||
<i class="fitted @(comment.YourThumb == 1 ? "green" : "grey") arrow up link icon" style="display: block"></i>
|
||||
</a>
|
||||
<span style="text-align: center; margin: auto; @(rating < 0 ? "margin-left: -5px" : "")">@(rating)</span>
|
||||
<a href="@url/rateComment?commentId=@(comment.CommentId)&rating=@(comment.YourThumb == -1 ? 0 : -1)">
|
||||
<i class="fitted @(comment.YourThumb == -1 ? "red" : "grey") arrow down link icon" style="display: block"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="comment">
|
||||
<b><a href="/user/@comment.PosterUserId">@comment.Poster.Username</a>: </b>
|
||||
@if (comment.Deleted)
|
||||
{
|
||||
<i>
|
||||
<span>@decodedMessage</span>
|
||||
</i>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@decodedMessage</span>
|
||||
}
|
||||
<p>
|
||||
<i>@timestamp.ToString("MM/dd/yyyy @ h:mm tt") UTC</i>
|
||||
</p>
|
||||
@if (i != Model.Comments.Count - 1)
|
||||
{
|
||||
<div class="ui divider"></div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
|
@ -0,0 +1,127 @@
|
|||
@using LBPUnion.ProjectLighthouse.PlayerData
|
||||
@using LBPUnion.ProjectLighthouse.Types
|
||||
@model LBPUnion.ProjectLighthouse.PlayerData.Photo
|
||||
|
||||
|
||||
<div style="position: relative">
|
||||
<canvas class="hide-subjects" id="canvas-subjects-@Model.PhotoId" width="1920" height="1080"
|
||||
style="position: absolute; transform: rotate(180deg)">
|
||||
</canvas>
|
||||
<img id="game-image-@Model.PhotoId" src="/gameAssets/@Model.LargeHash"
|
||||
style="width: 100%; height: auto; border-radius: .28571429rem;">
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<p>
|
||||
<i>
|
||||
Taken by
|
||||
<b>
|
||||
<a href="/user/@Model.Creator?.UserId">@Model.Creator?.Username</a>
|
||||
</b>
|
||||
</i>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>Photo contains @Model.Subjects.Count @(Model.Subjects.Count == 1 ? "person" : "people"):</b>
|
||||
</p>
|
||||
<div id="hover-subjects-@Model.PhotoId">
|
||||
@foreach (PhotoSubject subject in Model.Subjects)
|
||||
{
|
||||
<a href="/user/@subject.UserId">@subject.User.Username</a>
|
||||
}
|
||||
</div>
|
||||
|
||||
@{
|
||||
PhotoSubject[] subjects = Model.Subjects.ToArray();
|
||||
foreach (PhotoSubject subject in subjects) subject.Username = subject.User.Username;
|
||||
}
|
||||
|
||||
<script>
|
||||
// render the page first so that image heights have been calculated
|
||||
window.addEventListener("load", function () {
|
||||
const canvas = document.getElementById("canvas-subjects-@Model.PhotoId");
|
||||
const hoverer = document.getElementById("hover-subjects-@Model.PhotoId");
|
||||
const image = document.getElementById("game-image-@Model.PhotoId");
|
||||
|
||||
hoverer.addEventListener('mouseenter', function () {
|
||||
canvas.className = "photo-subjects";
|
||||
});
|
||||
hoverer.addEventListener('mouseleave', function () {
|
||||
canvas.className = "photo-subjects hide-subjects";
|
||||
});
|
||||
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
const subjects = @Html.Raw(Json.Serialize(subjects.ToArray()));
|
||||
|
||||
canvas.width = image.offsetWidth;
|
||||
canvas.height = image.clientHeight; // space for names to hang off
|
||||
|
||||
const w = canvas.width;
|
||||
const h = canvas.height;
|
||||
|
||||
// halfwidth, halfheight
|
||||
const hw = w / 2;
|
||||
const hh = h / 2;
|
||||
|
||||
const colours = ["#96dd3c", "#ceb424", "#cc0a1d", "#c800cc"];
|
||||
|
||||
subjects.forEach((s, si) => {
|
||||
const colour = colours[si % 4];
|
||||
|
||||
// Bounding box
|
||||
const bounds = s.bounds.split(",").map(parseFloat);
|
||||
|
||||
const [x1, y1, x2, y2] = bounds.map(n => Math.min(Math.max(n, -1), 1));
|
||||
|
||||
const bx = hw - (x2 * hw);
|
||||
const by = hh - (y2 * hh);
|
||||
const bw = (x2 - x1) * hw;
|
||||
const bh = (y2 - y1) * hh;
|
||||
|
||||
context.beginPath();
|
||||
context.lineWidth = 3;
|
||||
context.strokeStyle = colour;
|
||||
context.rect(bx, by, bw, bh);
|
||||
context.stroke();
|
||||
|
||||
// Move into relative coordinates from bounding box
|
||||
context.translate(bx, by);
|
||||
|
||||
// Username label
|
||||
context.font = "16px Lato";
|
||||
context.fillStyle = colour;
|
||||
|
||||
// Text width/height for the label background
|
||||
const tw = context.measureText(s.username).width;
|
||||
const th = 24;
|
||||
|
||||
// Check if the label will flow off the bottom of the frame
|
||||
const overflowBottom = (bounds[3] * hh) > (hh - 24);
|
||||
// Check if the label will flow off the left of the frame
|
||||
const overflowLeft = (bounds[2] * hw - tw) < (-hw);
|
||||
|
||||
// Set alignment
|
||||
context.textAlign = overflowLeft ? "start" : "end";
|
||||
|
||||
// Text x / y
|
||||
const lx = overflowLeft ? -bw + 6 : -6;
|
||||
const ly = overflowBottom ? -bh - 6 : 16;
|
||||
|
||||
// Label background x / y
|
||||
const lbx = overflowLeft ? bw - tw - 12 : -2;
|
||||
const lby = overflowBottom ? bh : -24;
|
||||
|
||||
// Draw background
|
||||
context.fillRect(lbx, lby, tw + 16, th);
|
||||
|
||||
// Draw text, rotated back upright (canvas draws rotated 180deg)
|
||||
context.fillStyle = "white";
|
||||
context.rotate(Math.PI);
|
||||
context.fillText(s.username, lx, ly);
|
||||
|
||||
// reset transform
|
||||
context.setTransform(1, 0, 0, 1, 0, 0);
|
||||
})
|
||||
}, false);
|
||||
</script>
|
|
@ -0,0 +1,120 @@
|
|||
@using LBPUnion.ProjectLighthouse
|
||||
@using LBPUnion.ProjectLighthouse.Configuration
|
||||
@using LBPUnion.ProjectLighthouse.PlayerData
|
||||
@using LBPUnion.ProjectLighthouse.PlayerData.Profiles
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@model LBPUnion.ProjectLighthouse.Levels.Slot
|
||||
|
||||
@{
|
||||
User? user = (User?)ViewData["User"];
|
||||
|
||||
await using Database database = new();
|
||||
|
||||
string slotName = string.IsNullOrEmpty(Model.Name) ? "Unnamed Level" : Model.Name;
|
||||
|
||||
bool isMobile = (bool?)ViewData["IsMobile"] ?? false;
|
||||
bool mini = (bool?)ViewData["IsMini"] ?? false;
|
||||
|
||||
bool isQueued = false;
|
||||
bool isHearted = false;
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
isQueued = await database.QueuedLevels.FirstOrDefaultAsync(h => h.SlotId == Model.SlotId && h.UserId == user.UserId) != null;
|
||||
isHearted = await database.HeartedLevels.FirstOrDefaultAsync(h => h.SlotId == Model.SlotId && h.UserId == user.UserId) != null;
|
||||
}
|
||||
|
||||
string callbackUrl = (string)ViewData["CallbackUrl"]!;
|
||||
bool showLink = (bool?)ViewData["ShowLink"] ?? false;
|
||||
|
||||
string iconHash = Model.IconHash;
|
||||
if (string.IsNullOrWhiteSpace(iconHash) || iconHash.StartsWith('g')) iconHash = ServerConfiguration.Instance.WebsiteConfiguration.MissingIconHash;
|
||||
}
|
||||
<div class="card">
|
||||
@{
|
||||
int size = isMobile || mini ? 50 : 100;
|
||||
}
|
||||
<div>
|
||||
<img src="~/assets/slotCardOverlay.png" style="min-width: @(size)px; width: @(size)px; height: @(size)px; pointer-events: none; position: absolute">
|
||||
<img class="cardIcon slotCardIcon" src="/gameAssets/@iconHash" style="min-width: @(size)px; width: @(size)px; height: @(size)px">
|
||||
</div>
|
||||
<div class="cardStats">
|
||||
@if (!mini)
|
||||
{
|
||||
@if (showLink)
|
||||
{
|
||||
<h2>
|
||||
<a href="~/slot/@Model.SlotId">@slotName</a>
|
||||
</h2>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h1>
|
||||
@slotName
|
||||
</h1>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (showLink)
|
||||
{
|
||||
<h3>
|
||||
<a href="~/slot/@Model.SlotId">@slotName</a>
|
||||
</h3>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h3>
|
||||
@slotName
|
||||
</h3>
|
||||
}
|
||||
}
|
||||
|
||||
<div class="cardStatsUnderTitle">
|
||||
<i class="pink heart icon" title="Hearts"></i> <span>@Model.Hearts</span>
|
||||
<i class="blue play icon" title="Plays"></i> <span>@Model.PlaysUnique</span>
|
||||
<i class="green thumbs up icon" title="Yays"></i> <span>@Model.Thumbsup</span>
|
||||
<i class="red thumbs down icon" title="Boos"></i> <span>@Model.Thumbsdown</span>
|
||||
|
||||
@if (Model.GameVersion == GameVersion.LittleBigPlanet1)
|
||||
{
|
||||
<i class="yellow star icon" title="LBP1 Stars"></i>
|
||||
<span>@Model.RatingLBP1</span>
|
||||
}
|
||||
</div>
|
||||
<p>
|
||||
<i>Created by <a href="/user/@Model.Creator?.UserId">@Model.Creator?.Username</a> for @Model.GameVersion.ToPrettyString()</i>
|
||||
</p>
|
||||
</div>
|
||||
<div class="cardButtons">
|
||||
<br>
|
||||
@if (user != null && !mini)
|
||||
{
|
||||
if (isHearted)
|
||||
{
|
||||
<a class="ui pink tiny button" href="/slot/@Model.SlotId/unheart?callbackUrl=@callbackUrl" title="Unheart">
|
||||
<i class="broken heart icon" style="margin: 0"></i>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a class="ui pink tiny button" href="/slot/@Model.SlotId/heart?callbackUrl=@callbackUrl" title="Heart">
|
||||
<i class="heart icon" style="margin: 0"></i>
|
||||
</a>
|
||||
}
|
||||
|
||||
if (isQueued)
|
||||
{
|
||||
<a class="ui yellow tiny button" href="/slot/@Model.SlotId/unqueue?callbackUrl=@callbackUrl" title="Unqueue">
|
||||
<i class="bell slash icon" style="margin: 0"></i>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a class="ui yellow tiny button" href="/slot/@Model.SlotId/queue?callbackUrl=@callbackUrl" title="Queue">
|
||||
<i class="bell icon" style="margin: 0"></i>
|
||||
</a>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,37 @@
|
|||
@model LBPUnion.ProjectLighthouse.PlayerData.Profiles.User
|
||||
|
||||
@{
|
||||
bool showLink = (bool?)ViewData["ShowLink"] ?? false;
|
||||
bool isMobile = (bool?)ViewData["IsMobile"] ?? false;
|
||||
}
|
||||
|
||||
<div class="card">
|
||||
@{
|
||||
int size = isMobile ? 50 : 100;
|
||||
}
|
||||
<div class="cardIcon userCardIcon" style="background-image: url('/gameAssets/@Model.WebsiteAvatarHash'); min-width: @(size)px; width: @(size)px; height: @(size)px">
|
||||
</div>
|
||||
<div class="cardStats">
|
||||
@if (showLink)
|
||||
{
|
||||
<h2 style="margin-bottom: 2px;">
|
||||
<a href="~/user/@Model.UserId">@Model.Username</a>
|
||||
</h2>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h1 style="margin-bottom: 2px;">
|
||||
@Model.Username
|
||||
</h1>
|
||||
}
|
||||
<p>
|
||||
<i>@Model.Status</i>
|
||||
</p>
|
||||
<div class="cardStatsUnderTitle">
|
||||
<i class="pink heart icon" title="Hearts"></i> <span>@Model.Hearts</span>
|
||||
<i class="blue comment icon" title="Comments"></i> <span>@Model.Comments</span>
|
||||
<i class="green upload icon" title="Uploaded Levels"></i><span>@Model.UsedSlots</span>
|
||||
<i class="purple camera icon" title="Uploaded Photos"></i><span>@Model.PhotosByMe</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,51 @@
|
|||
@page "/passwordReset"
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.PasswordResetPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = "Password Reset";
|
||||
}
|
||||
|
||||
<script src="https://geraintluff.github.io/sha256/sha256.min.js"></script>
|
||||
|
||||
<script>
|
||||
function onSubmit(form) {
|
||||
const passwordInput = document.getElementById("password");
|
||||
const confirmPasswordInput = document.getElementById("confirmPassword");
|
||||
const passwordSubmit = document.getElementById("password-submit");
|
||||
const confirmPasswordSubmit = document.getElementById("confirmPassword-submit");
|
||||
|
||||
passwordSubmit.value = sha256(passwordInput.value);
|
||||
confirmPasswordSubmit.value = sha256(confirmPasswordInput.value);
|
||||
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Model.Error))
|
||||
{
|
||||
<div class="ui negative message">
|
||||
<div class="header">
|
||||
Uh oh!
|
||||
</div>
|
||||
<p>@Model.Error</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
<form onsubmit="return onSubmit(this)" method="post">
|
||||
@Html.AntiForgeryToken()
|
||||
|
||||
<div class="ui left labeled input">
|
||||
<label for="password" class="ui blue label">Password: </label>
|
||||
<input type="password" id="password">
|
||||
<input type="hidden" id="password-submit" name="password">
|
||||
</div><br><br>
|
||||
|
||||
<div class="ui left labeled input">
|
||||
<label for="password" class="ui blue label">Confirm Password: </label>
|
||||
<input type="password" id="confirmPassword">
|
||||
<input type="hidden" id="confirmPassword-submit" name="confirmPassword">
|
||||
</div><br><br><br>
|
||||
|
||||
<input type="submit" value="Reset password and continue" id="submit" class="ui green button"><br>
|
||||
</form>
|
|
@ -0,0 +1,56 @@
|
|||
#nullable enable
|
||||
using JetBrains.Annotations;
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
|
||||
public class PasswordResetPage : BaseLayout
|
||||
{
|
||||
public PasswordResetPage(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public string? Error { get; private set; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public async Task<IActionResult> OnPost(string password, string confirmPassword)
|
||||
{
|
||||
User? user = this.Database.UserFromWebRequest(this.Request);
|
||||
if (user == null) return this.Redirect("~/login");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
this.Error = "The password field is required.";
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
if (password != confirmPassword)
|
||||
{
|
||||
this.Error = "Passwords do not match!";
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
user.Password = CryptoHelper.BCryptHash(password);
|
||||
user.PasswordResetRequired = false;
|
||||
|
||||
await this.Database.SaveChangesAsync();
|
||||
|
||||
if (!user.EmailAddressVerified && ServerConfiguration.Instance.Mail.MailEnabled)
|
||||
return this.Redirect("~/login/sendVerificationEmail");
|
||||
|
||||
return this.Redirect("~/");
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public IActionResult OnGet()
|
||||
{
|
||||
User? user = this.Database.UserFromWebRequest(this.Request);
|
||||
if (user == null) return this.Redirect("~/login");
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
@page "/passwordResetRequired"
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.PasswordResetRequiredPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = "Password Reset Required";
|
||||
}
|
||||
|
||||
<p>An administrator has deemed it necessary that you reset your password. Please do so.</p>
|
||||
|
||||
<a href="/passwordReset">
|
||||
<div class="ui blue button">Reset Password</div>
|
||||
</a>
|
|
@ -0,0 +1,24 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
|
||||
public class PasswordResetRequiredPage : BaseLayout
|
||||
{
|
||||
public PasswordResetRequiredPage(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public bool WasResetRequest { get; private set; }
|
||||
|
||||
public IActionResult OnGet()
|
||||
{
|
||||
User? user = this.Database.UserFromWebRequest(this.Request);
|
||||
if (user == null) return this.Redirect("~/login");
|
||||
if (!user.PasswordResetRequired) return this.Redirect("~/passwordReset");
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
}
|
36
ProjectLighthouse.Servers.Website/Pages/PhotosPage.cshtml
Normal file
36
ProjectLighthouse.Servers.Website/Pages/PhotosPage.cshtml
Normal file
|
@ -0,0 +1,36 @@
|
|||
@page "/photos/{pageNumber:int}"
|
||||
@using LBPUnion.ProjectLighthouse.PlayerData
|
||||
@using LBPUnion.ProjectLighthouse.Types
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.PhotosPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = "Photos";
|
||||
}
|
||||
|
||||
<p>There are @Model.PhotoCount total photos!</p>
|
||||
|
||||
<form action="/photos/0">
|
||||
<div class="ui icon input">
|
||||
<input type="text" name="name" placeholder="Search photos..." value="@Model.SearchValue">
|
||||
<i class="search icon"></i>
|
||||
</div>
|
||||
</form>
|
||||
<div class="ui divider"></div>
|
||||
|
||||
@foreach (Photo photo in Model.Photos)
|
||||
{
|
||||
<div class="ui segment">
|
||||
@await Html.PartialAsync("Partials/PhotoPartial", photo)
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Model.PageNumber != 0)
|
||||
{
|
||||
<a href="/photos/@(Model.PageNumber - 1)@(Model.SearchValue?.Length == 0 ? "" : "?name=" + Model.SearchValue)">Previous Page</a>
|
||||
}
|
||||
@(Model.PageNumber + 1) / @(Model.PageAmount)
|
||||
@if (Model.PageNumber < Model.PageAmount - 1)
|
||||
{
|
||||
<a href="/photos/@(Model.PageNumber + 1)@(Model.SearchValue?.Length == 0 ? "" : "?name=" + Model.SearchValue)">Next Page</a>
|
||||
}
|
51
ProjectLighthouse.Servers.Website/Pages/PhotosPage.cshtml.cs
Normal file
51
ProjectLighthouse.Servers.Website/Pages/PhotosPage.cshtml.cs
Normal file
|
@ -0,0 +1,51 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
|
||||
public class PhotosPage : BaseLayout
|
||||
{
|
||||
|
||||
public int PageAmount;
|
||||
|
||||
public int PageNumber;
|
||||
|
||||
public int PhotoCount;
|
||||
|
||||
public List<Photo> Photos = new();
|
||||
|
||||
public string? SearchValue;
|
||||
public PhotosPage(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public async Task<IActionResult> OnGet([FromRoute] int pageNumber, [FromQuery] string? name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name)) name = "";
|
||||
|
||||
this.SearchValue = name.Replace(" ", string.Empty);
|
||||
|
||||
this.PhotoCount = await this.Database.Photos.Include
|
||||
(p => p.Creator)
|
||||
.CountAsync(p => p.Creator!.Username.Contains(this.SearchValue) || p.PhotoSubjectCollection.Contains(this.SearchValue));
|
||||
|
||||
this.PageNumber = pageNumber;
|
||||
this.PageAmount = Math.Max(1, (int)Math.Ceiling((double)this.PhotoCount / ServerStatics.PageSize));
|
||||
|
||||
if (this.PageNumber < 0 || this.PageNumber >= this.PageAmount) return this.Redirect($"/photos/{Math.Clamp(this.PageNumber, 0, this.PageAmount - 1)}");
|
||||
|
||||
this.Photos = await this.Database.Photos.Include
|
||||
(p => p.Creator)
|
||||
.Where(p => p.Creator!.Username.Contains(this.SearchValue) || p.PhotoSubjectCollection.Contains(this.SearchValue))
|
||||
.OrderByDescending(p => p.Timestamp)
|
||||
.Skip(pageNumber * ServerStatics.PageSize)
|
||||
.Take(ServerStatics.PageSize)
|
||||
.ToListAsync();
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
}
|
108
ProjectLighthouse.Servers.Website/Pages/RegisterForm.cshtml
Normal file
108
ProjectLighthouse.Servers.Website/Pages/RegisterForm.cshtml
Normal file
|
@ -0,0 +1,108 @@
|
|||
@page "/register"
|
||||
@using LBPUnion.ProjectLighthouse.Configuration
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.RegisterForm
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = "Register";
|
||||
}
|
||||
|
||||
<script src="https://geraintluff.github.io/sha256/sha256.min.js"></script>
|
||||
|
||||
<script>
|
||||
function onSubmit(form) {
|
||||
const passwordInput = document.getElementById("password");
|
||||
const confirmPasswordInput = document.getElementById("confirmPassword");
|
||||
const passwordSubmit = document.getElementById("password-submit");
|
||||
const confirmPasswordSubmit = document.getElementById("confirm-submit");
|
||||
passwordSubmit.value = sha256(passwordInput.value);
|
||||
confirmPasswordSubmit.value = sha256(confirmPasswordInput.value);
|
||||
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Model.Error))
|
||||
{
|
||||
<div class="ui negative message">
|
||||
<div class="header">
|
||||
Uh oh!
|
||||
</div>
|
||||
<p>@Model.Error</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
<form class="ui form" onsubmit="return onSubmit(this)" method="post">
|
||||
@Html.AntiForgeryToken()
|
||||
|
||||
<div class="field">
|
||||
<label>Username</label>
|
||||
<div class="ui left icon input">
|
||||
<input type="text" name="username" id="text" placeholder="Username" pattern="^[a-zA-Z0-9_.-]*$" minlength="3" maxlength="16">
|
||||
<i class="user icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (ServerConfiguration.Instance.Mail.MailEnabled)
|
||||
{
|
||||
<div class="field">
|
||||
<label>Email address</label>
|
||||
<div class="ui left icon input">
|
||||
<input type="email" name="emailAddress" id="emailAddress" placeholder="Email Address">
|
||||
<i class="mail icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="field">
|
||||
<label>Password</label>
|
||||
<div class="ui left icon input">
|
||||
<input type="password" id="password" placeholder="Password">
|
||||
<input type="hidden" name="password" id="password-submit">
|
||||
<i class="lock icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Confirm Password</label>
|
||||
<div class="ui left icon input">
|
||||
<input type="password" id="confirmPassword" placeholder="Confirm Password">
|
||||
<input type="hidden" name="confirmPassword" id="confirm-submit">
|
||||
<i class="lock icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (ServerConfiguration.Instance.Captcha.CaptchaEnabled)
|
||||
{
|
||||
@await Html.PartialAsync("Partials/CaptchaPartial")
|
||||
}
|
||||
|
||||
<input type="submit" value="Register" id="submit" class="ui green button">
|
||||
</form>
|
||||
|
||||
<br><br>
|
||||
|
||||
@if (ServerStatics.IsDebug)
|
||||
{
|
||||
<button class="ui red button" onclick="fill()">DEBUG: Fill with everything but email</button>
|
||||
|
||||
<script>
|
||||
const usernameField = document.getElementById("text");
|
||||
const emailField = document.getElementById("emailAddress");
|
||||
const passwordField = document.getElementById("password");
|
||||
const confirmPasswordField = document.getElementById("confirmPassword");
|
||||
|
||||
function fill() {
|
||||
const min = 100;
|
||||
const max = 99999999;
|
||||
|
||||
const rand = Math.floor(Math.random() * (max - min + 1) + min);
|
||||
|
||||
usernameField.value = rand.toString();
|
||||
passwordField.value = rand.toString();
|
||||
confirmPasswordField.value = rand.toString();
|
||||
|
||||
emailField.focus();
|
||||
}
|
||||
</script>
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using JetBrains.Annotations;
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
|
||||
public class RegisterForm : BaseLayout
|
||||
{
|
||||
public RegisterForm(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public string? Error { get; private set; }
|
||||
|
||||
[UsedImplicitly]
|
||||
[SuppressMessage("ReSharper", "SpecifyStringComparison")]
|
||||
public async Task<IActionResult> OnPost(string username, string password, string confirmPassword, string emailAddress)
|
||||
{
|
||||
if (!ServerConfiguration.Instance.Authentication.RegistrationEnabled) return this.NotFound();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
this.Error = "The username field is blank.";
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
this.Error = "Password field is required.";
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(emailAddress) && ServerConfiguration.Instance.Mail.MailEnabled)
|
||||
{
|
||||
this.Error = "Email address field is required.";
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
if (password != confirmPassword)
|
||||
{
|
||||
this.Error = "Passwords do not match!";
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
if (await this.Database.Users.FirstOrDefaultAsync(u => u.Username.ToLower() == username.ToLower()) != null)
|
||||
{
|
||||
this.Error = "The username you've chosen is already taken.";
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
if (ServerConfiguration.Instance.Mail.MailEnabled &&
|
||||
await this.Database.Users.FirstOrDefaultAsync(u => u.EmailAddress != null && u.EmailAddress.ToLower() == emailAddress.ToLower()) != null)
|
||||
{
|
||||
this.Error = "The email address you've chosen is already taken.";
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
if (!await this.Request.CheckCaptchaValidity())
|
||||
{
|
||||
this.Error = "You must complete the captcha correctly.";
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
User user = await this.Database.CreateUser(username, CryptoHelper.BCryptHash(password), emailAddress);
|
||||
|
||||
WebToken webToken = new()
|
||||
{
|
||||
UserId = user.UserId,
|
||||
UserToken = CryptoHelper.GenerateAuthToken(),
|
||||
};
|
||||
|
||||
this.Database.WebTokens.Add(webToken);
|
||||
await this.Database.SaveChangesAsync();
|
||||
|
||||
this.Response.Cookies.Append("LighthouseToken", webToken.UserToken);
|
||||
|
||||
if (ServerConfiguration.Instance.Mail.MailEnabled) return this.Redirect("~/login/sendVerificationEmail");
|
||||
|
||||
return this.RedirectToPage(nameof(LandingPage));
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
[SuppressMessage("ReSharper", "SpecifyStringComparison")]
|
||||
public IActionResult OnGet()
|
||||
{
|
||||
this.Error = string.Empty;
|
||||
if (!ServerConfiguration.Instance.Authentication.RegistrationEnabled) return this.NotFound();
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
}
|
248
ProjectLighthouse.Servers.Website/Pages/ReportsPage.cshtml
Normal file
248
ProjectLighthouse.Servers.Website/Pages/ReportsPage.cshtml
Normal file
|
@ -0,0 +1,248 @@
|
|||
@page "/admin/reports/{pageNumber:int}"
|
||||
@using LBPUnion.ProjectLighthouse.Administration.Reports
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.ReportsPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = "Reports";
|
||||
}
|
||||
|
||||
<p>There are @Model.ReportCount total reports!</p>
|
||||
|
||||
<form action="/admin/reports/0">
|
||||
<div class="ui icon input">
|
||||
<input type="text" name="name" placeholder="Search reports..." value="@Model.SearchValue">
|
||||
<i class="search icon"></i>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
|
||||
<script>
|
||||
let subjects = [];
|
||||
let bounds = [];
|
||||
let canvases = [];
|
||||
let ctx = [];
|
||||
let images = [];
|
||||
</script>
|
||||
|
||||
@foreach (GriefReport report in Model.Reports)
|
||||
{
|
||||
<div class="ui segment">
|
||||
<div>
|
||||
<canvas class="hide-subjects" id="canvas-subjects-@report.ReportId" width="1920" height="1080"
|
||||
style="position: absolute; transform: rotate(180deg)">
|
||||
</canvas>
|
||||
<img class="hover-region" id="game-image-@report.ReportId" src="/gameAssets/@report.JpegHash" alt="Grief report picture" style="width: 100%; height: auto; border-radius: .28571429rem;">
|
||||
</div>
|
||||
<p>
|
||||
<i>
|
||||
Report submitted by
|
||||
<b>
|
||||
<a href="/user/@report.ReportingPlayerId">@report.ReportingPlayer.Username</a>
|
||||
</b>
|
||||
</i>
|
||||
</p>
|
||||
<b class="hover-players" id="hover-subjects-2-@report.ReportId">Report contains @report.XmlPlayers.Length @(report.XmlPlayers.Length == 1 ? "player" : "players")</b>
|
||||
@foreach (ReportPlayer player in report.XmlPlayers)
|
||||
{
|
||||
<div id="hover-subjects-@report.ReportId" class="hover-players">
|
||||
<a href="/">@player.Name</a>
|
||||
</div>
|
||||
}
|
||||
|
||||
<br>
|
||||
<div>
|
||||
<b>Report time: </b>@(DateTimeOffset.FromUnixTimeMilliseconds(report.Timestamp).ToString("R"))
|
||||
</div>
|
||||
<div>
|
||||
<b>Report reason: </b>@report.Type
|
||||
</div>
|
||||
<div>
|
||||
<b>Level ID:</b> @report.LevelId
|
||||
</div>
|
||||
<div>
|
||||
<b>Level type:</b> @report.LevelType
|
||||
</div>
|
||||
<div>
|
||||
<b>Level owner:</b> @report.LevelOwner
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<a class="ui green small button" href="/admin/report/@report.ReportId/dismiss">
|
||||
<i class="checkmark icon"></i>
|
||||
<span>Dismiss</span>
|
||||
</a>
|
||||
<a class="ui red small button" href="/admin/report/@report.ReportId/remove">
|
||||
<i class="trash icon"></i>
|
||||
<span>Remove all related assets</span>
|
||||
</a>
|
||||
</div>
|
||||
<script>
|
||||
subjects[@report.ReportId] = @Html.Raw(report.Players)
|
||||
bounds[@report.ReportId] = @Html.Raw(report.Bounds)
|
||||
images[@report.ReportId] = document.getElementById("game-image-@report.ReportId")
|
||||
canvases[@report.ReportId] = document.getElementById("canvas-subjects-@report.ReportId")
|
||||
canvases[@report.ReportId].width = images[@report.ReportId].offsetWidth;
|
||||
canvases[@report.ReportId].height = images[@report.ReportId].clientHeight;
|
||||
ctx[@report.ReportId] = canvases[@report.ReportId].getContext('2d');
|
||||
</script>
|
||||
}
|
||||
|
||||
<script>
|
||||
function getReportId(name){
|
||||
let split = name.split("-");
|
||||
return split[split.length-1];
|
||||
}
|
||||
const colours = ["#96dd3c", "#ceb424", "#cc0a1d", "#c800cc"];
|
||||
let displayType;
|
||||
window.addEventListener("load", function () {
|
||||
document.querySelectorAll(".hover-players").forEach(item => {
|
||||
item.addEventListener('mouseenter', function () {
|
||||
let reportId = getReportId(item.id);
|
||||
const canvas = canvases[reportId];
|
||||
displayType = 1;
|
||||
|
||||
canvas.className = "photo-subjects";
|
||||
|
||||
redraw(reportId);
|
||||
});
|
||||
});
|
||||
document.querySelectorAll(".hover-region").forEach(item => {
|
||||
item.addEventListener('mouseenter', function () {
|
||||
let reportId = getReportId(item.id);
|
||||
const canvas = canvases[reportId];
|
||||
const image = document.getElementById("game-image-" + reportId.toString());
|
||||
displayType = 0;
|
||||
|
||||
canvas.className = "photo-subjects";
|
||||
|
||||
canvas.width = image.offsetWidth;
|
||||
canvas.height = image.clientHeight; // space for names to hang off
|
||||
|
||||
redraw(reportId);
|
||||
});
|
||||
});
|
||||
document.querySelectorAll(".hover-region, .hover-players").forEach(item => {
|
||||
item.addEventListener('mouseleave', function () {
|
||||
canvases[getReportId(item.id)].className = "photo-subjects hide-subjects";
|
||||
});
|
||||
});
|
||||
}, false);
|
||||
|
||||
function redraw(reportId){
|
||||
let context = ctx[reportId];
|
||||
let canvas = canvases[reportId];
|
||||
let image = images[reportId];
|
||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||
let w = canvas.width;
|
||||
let h = canvas.height;
|
||||
|
||||
// halfwidth, halfheight
|
||||
const hw = w / 2;
|
||||
const hh = h / 2;
|
||||
switch (displayType){
|
||||
case 0: {
|
||||
let imageBounds = bounds[reportId];
|
||||
const x1 = imageBounds.Left;
|
||||
const x2 = imageBounds.Right;
|
||||
const y1 = imageBounds.Top;
|
||||
const y2 = imageBounds.Bottom;
|
||||
const scaleX = image.naturalWidth / canvas.width;
|
||||
const scaleY = image.naturalHeight / canvas.height;
|
||||
const bx = canvas.width-(x2/scaleX);
|
||||
const by = canvas.height-(y2/scaleY);
|
||||
const bw = (x2 - x1) / scaleX;
|
||||
const bh = (y2 - y1) / scaleY;
|
||||
context.beginPath();
|
||||
context.globalAlpha = 0.6;
|
||||
context.fillStyle = "black";
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
context.clearRect(bx, by, bw, bh);
|
||||
context.beginPath();
|
||||
context.lineWidth = 2;
|
||||
context.strokeStyle = "#957a24";
|
||||
context.rect(bx, by, bw, bh);
|
||||
context.stroke();
|
||||
context.globalAlpha = 1.0;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
let subject = subjects[reportId];
|
||||
subject.forEach((s, si) => {
|
||||
const colour = colours[si % 4];
|
||||
|
||||
// Bounding box
|
||||
const x1 = s.Location.Left;
|
||||
const x2 = s.Location.Right;
|
||||
const y1 = s.Location.Top;
|
||||
const y2 = s.Location.Bottom;
|
||||
|
||||
const scaleX = image.naturalWidth / canvas.width;
|
||||
const scaleY = image.naturalHeight / canvas.height;
|
||||
|
||||
|
||||
const bx = canvas.width-(x2/scaleX);
|
||||
const by = canvas.height-(y2/scaleY);
|
||||
const bw = (x2 - x1) / scaleX;
|
||||
const bh = (y2 - y1) / scaleY;
|
||||
|
||||
context.beginPath();
|
||||
context.lineWidth = 3;
|
||||
context.strokeStyle = colour;
|
||||
context.rect(bx, by, bw, bh);
|
||||
context.stroke();
|
||||
|
||||
// Move into relative coordinates from bounding box
|
||||
context.translate(bx, by);
|
||||
|
||||
// Username label
|
||||
context.font = "16px Lato";
|
||||
context.fillStyle = colour;
|
||||
|
||||
// Text width/height for the label background
|
||||
const tw = context.measureText(s.Name).width;
|
||||
const th = 24;
|
||||
|
||||
// Check if the label will flow off the bottom of the frame
|
||||
const overflowBottom = (y2+tw - 24) > (canvas.width);
|
||||
// Check if the label will flow off the left of the frame
|
||||
const overflowLeft = (x2) < (24);
|
||||
|
||||
// Set alignment
|
||||
context.textAlign = overflowLeft ? "start" : "end";
|
||||
|
||||
// Text x / y
|
||||
const lx = overflowLeft ? -bw + 6 : -6;
|
||||
const ly = overflowBottom ? -bh - 6 : 16;
|
||||
|
||||
// Label background x / y
|
||||
const lbx = overflowLeft ? bw - tw - 12 : 0;
|
||||
const lby = overflowBottom ? bh : -24;
|
||||
|
||||
// Draw background
|
||||
context.fillRect(lbx, lby, tw+8, th);
|
||||
|
||||
// Draw text, rotated back upright (canvas draws rotated 180deg)
|
||||
context.fillStyle = "white";
|
||||
context.rotate(Math.PI);
|
||||
context.fillText(s.Name, lx, ly);
|
||||
|
||||
// reset transform
|
||||
context.setTransform(1, 0, 0, 1, 0, 0);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@if (Model.PageNumber != 0)
|
||||
{
|
||||
<a href="/admin/reports/@(Model.PageNumber - 1)@(Model.SearchValue.Length == 0 ? "" : "?name=" + Model.SearchValue)">Previous Page</a>
|
||||
}
|
||||
@(Model.PageNumber + 1) / @(Model.PageAmount)
|
||||
@if (Model.PageNumber < Model.PageAmount - 1)
|
||||
{
|
||||
<a href="/admin/reports/@(Model.PageNumber + 1)@(Model.SearchValue.Length == 0 ? "" : "?name=" + Model.SearchValue)">Next Page</a>
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
#nullable enable
|
||||
using System.Text.Json;
|
||||
using LBPUnion.ProjectLighthouse.Administration.Reports;
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
|
||||
public class ReportsPage : BaseLayout
|
||||
{
|
||||
|
||||
public int PageAmount;
|
||||
|
||||
public int PageNumber;
|
||||
|
||||
public int ReportCount;
|
||||
|
||||
public List<GriefReport> Reports = new();
|
||||
|
||||
public string SearchValue = "";
|
||||
|
||||
public ReportsPage(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public async Task<IActionResult> OnGet([FromRoute] int pageNumber, [FromQuery] string? name)
|
||||
{
|
||||
User? user = this.Database.UserFromWebRequest(this.Request);
|
||||
if (user == null) return this.Redirect("~/login");
|
||||
if (!user.IsAdmin) return this.NotFound();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(name)) name = "";
|
||||
|
||||
this.SearchValue = name.Replace(" ", string.Empty);
|
||||
|
||||
this.ReportCount = await this.Database.Reports.Include(r => r.ReportingPlayer).CountAsync(r => r.ReportingPlayer.Username.Contains(this.SearchValue));
|
||||
|
||||
this.PageNumber = pageNumber;
|
||||
this.PageAmount = Math.Max(1, (int)Math.Ceiling((double)this.ReportCount / ServerStatics.PageSize));
|
||||
|
||||
if (this.PageNumber < 0 || this.PageNumber >= this.PageAmount)
|
||||
return this.Redirect($"/admin/reports/{Math.Clamp(this.PageNumber, 0, this.PageAmount - 1)}");
|
||||
|
||||
this.Reports = await this.Database.Reports.Include(r => r.ReportingPlayer)
|
||||
.Where(r => r.ReportingPlayer.Username.Contains(this.SearchValue))
|
||||
.OrderByDescending(r => r.Timestamp)
|
||||
.Skip(pageNumber * ServerStatics.PageSize)
|
||||
.Take(ServerStatics.PageSize)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (GriefReport r in this.Reports)
|
||||
{
|
||||
r.XmlPlayers = (ReportPlayer[])JsonSerializer.Deserialize(r.Players, typeof(ReportPlayer[]))!;
|
||||
|
||||
r.XmlBounds = new Marqee()
|
||||
{
|
||||
Rect = (Rectangle)JsonSerializer.Deserialize(r.Bounds, typeof(Rectangle))!,
|
||||
};
|
||||
}
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
@page "/login/sendVerificationEmail"
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.SendVerificationEmailPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = "Verify Email Address";
|
||||
}
|
||||
|
||||
<p>An email address on your account has been set, but hasn't been verified yet.</p>
|
||||
<p>To verify it, check the email sent to <a href="mailto:@Model.User?.EmailAddress">@Model.User?.EmailAddress</a> and click the link in the email.</p>
|
||||
|
||||
<a href="/login/sendVerificationEmail">
|
||||
<div class="ui blue button">Resend email</div>
|
||||
</a>
|
|
@ -0,0 +1,61 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
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;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
|
||||
public class SendVerificationEmailPage : BaseLayout
|
||||
{
|
||||
public SendVerificationEmailPage(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public async Task<IActionResult> OnGet()
|
||||
{
|
||||
if (!ServerConfiguration.Instance.Mail.MailEnabled) return this.NotFound();
|
||||
|
||||
User? user = this.Database.UserFromWebRequest(this.Request);
|
||||
if (user == null) return this.Redirect("/login");
|
||||
|
||||
// `using` weirdness here. I tried to fix it, but I couldn't.
|
||||
// The user should never see this page once they've been verified, so assert here.
|
||||
System.Diagnostics.Debug.Assert(!user.EmailAddressVerified);
|
||||
|
||||
// Othewise, on a release build, just silently redirect them to the landing page.
|
||||
#if !DEBUG
|
||||
if (user.EmailAddressVerified)
|
||||
{
|
||||
return this.Redirect("/");
|
||||
}
|
||||
#endif
|
||||
|
||||
EmailVerificationToken verifyToken = new()
|
||||
{
|
||||
UserId = user.UserId,
|
||||
User = user,
|
||||
EmailToken = CryptoHelper.GenerateAuthToken(),
|
||||
};
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
29
ProjectLighthouse.Servers.Website/Pages/SetEmailForm.cshtml
Normal file
29
ProjectLighthouse.Servers.Website/Pages/SetEmailForm.cshtml
Normal file
|
@ -0,0 +1,29 @@
|
|||
@page "/login/setEmail"
|
||||
@using LBPUnion.ProjectLighthouse.Configuration
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.SetEmailForm
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = "Set an Email Address";
|
||||
}
|
||||
|
||||
<p>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.</p>
|
||||
|
||||
<form class="ui form" onsubmit="return onSubmit(this)" method="post">
|
||||
@Html.AntiForgeryToken()
|
||||
|
||||
@if (ServerConfiguration.Instance.Mail.MailEnabled)
|
||||
{
|
||||
<div class="field">
|
||||
<label>Please type a valid email address and verify it:</label>
|
||||
<div class="ui left icon input">
|
||||
<input type="email" name="emailAddress" id="emailAddress" placeholder="Email Address">
|
||||
<i class="mail icon"></i>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="token" id="token" value="@Model.EmailToken?.EmailToken">
|
||||
</div>
|
||||
}
|
||||
|
||||
<input type="submit" value="Verify Email Address" id="submit" class="ui blue button">
|
||||
</form>
|
|
@ -0,0 +1,80 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
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;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
|
||||
public class SetEmailForm : BaseLayout
|
||||
{
|
||||
public SetEmailForm(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public EmailSetToken? EmailToken;
|
||||
|
||||
public async Task<IActionResult> OnGet(string? token = null)
|
||||
{
|
||||
if (!ServerConfiguration.Instance.Mail.MailEnabled) return this.NotFound();
|
||||
if (token == null) return this.Redirect("/login");
|
||||
|
||||
EmailSetToken? emailToken = await this.Database.EmailSetTokens.FirstOrDefaultAsync(t => t.EmailToken == token);
|
||||
if (emailToken == null) return this.Redirect("/login");
|
||||
|
||||
this.EmailToken = emailToken;
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPost(string emailAddress, string token)
|
||||
{
|
||||
if (!ServerConfiguration.Instance.Mail.MailEnabled) return this.NotFound();
|
||||
|
||||
EmailSetToken? emailToken = await this.Database.EmailSetTokens.Include(t => t.User).FirstOrDefaultAsync(t => t.EmailToken == token);
|
||||
if (emailToken == null) return this.Redirect("/login");
|
||||
|
||||
emailToken.User.EmailAddress = emailAddress;
|
||||
this.Database.EmailSetTokens.Remove(emailToken);
|
||||
|
||||
User user = emailToken.User;
|
||||
|
||||
EmailVerificationToken emailVerifyToken = new()
|
||||
{
|
||||
UserId = user.UserId,
|
||||
User = user,
|
||||
EmailToken = CryptoHelper.GenerateAuthToken(),
|
||||
};
|
||||
|
||||
this.Database.EmailVerificationTokens.Add(emailVerifyToken);
|
||||
|
||||
// The user just set their email address. Now, let's grant them a token to proceed with verifying the email.
|
||||
WebToken webToken = new()
|
||||
{
|
||||
UserId = user.UserId,
|
||||
UserToken = CryptoHelper.GenerateAuthToken(),
|
||||
};
|
||||
|
||||
this.Response.Cookies.Append
|
||||
(
|
||||
"LighthouseToken",
|
||||
webToken.UserToken,
|
||||
new CookieOptions
|
||||
{
|
||||
Expires = DateTimeOffset.Now.AddDays(7),
|
||||
}
|
||||
);
|
||||
|
||||
Logger.Success($"User {user.Username} (id: {user.UserId}) successfully logged in on web after setting an email address", LogArea.Login);
|
||||
|
||||
this.Database.WebTokens.Add(webToken);
|
||||
await this.Database.SaveChangesAsync();
|
||||
|
||||
return this.Redirect("/login/sendVerificationEmail");
|
||||
}
|
||||
}
|
199
ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml
Normal file
199
ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml
Normal file
|
@ -0,0 +1,199 @@
|
|||
@page "/slot/{id:int}"
|
||||
@using System.Web
|
||||
@using LBPUnion.ProjectLighthouse.Administration
|
||||
@using LBPUnion.ProjectLighthouse.Configuration
|
||||
@using LBPUnion.ProjectLighthouse.Extensions
|
||||
@using LBPUnion.ProjectLighthouse.PlayerData.Reviews
|
||||
@using LBPUnion.ProjectLighthouse.Types
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.SlotPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.ShowTitleInPage = false;
|
||||
|
||||
Model.Title = Model.Slot?.Name ?? "";
|
||||
Model.Description = Model.Slot?.Description ?? "";
|
||||
|
||||
bool isMobile = this.Request.IsMobile();
|
||||
}
|
||||
|
||||
@await Html.PartialAsync("Partials/SlotCardPartial", Model.Slot, new ViewDataDictionary(ViewData)
|
||||
{
|
||||
{
|
||||
"User", Model.User
|
||||
},
|
||||
{
|
||||
"CallbackUrl", $"~/slot/{Model.Slot?.SlotId}"
|
||||
},
|
||||
{
|
||||
"ShowLink", false
|
||||
},
|
||||
{
|
||||
"IsMobile", Model.Request.IsMobile()
|
||||
},
|
||||
})
|
||||
<br>
|
||||
|
||||
<div class="ui grid">
|
||||
<div class="eight wide column">
|
||||
<div class="ui blue segment">
|
||||
<h2>Description</h2>
|
||||
<p>@HttpUtility.HtmlDecode(string.IsNullOrEmpty(Model.Slot?.Description) ? "This level has no description." : Model.Slot.Description)</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="eight wide column">
|
||||
<div class="ui red segment">
|
||||
<h2>Tags</h2>
|
||||
@{
|
||||
string[] authorLabels = Model.Slot?.AuthorLabels.Split(",") ?? new string[]
|
||||
{};
|
||||
if (authorLabels.Length == 1) // ..?? ok c#
|
||||
{
|
||||
<p>This level has no tags.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (string label in authorLabels.Where(label => !string.IsNullOrEmpty(label)))
|
||||
{
|
||||
<div class="ui blue label">@label.Replace("LABEL_", "")</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="eight wide column">
|
||||
@await Html.PartialAsync("Partials/CommentsPartial")
|
||||
</div>
|
||||
<div class="eight wide column">
|
||||
<div class="ui purple segment">
|
||||
<h2>Reviews</h2>
|
||||
@if (Model.Reviews.Count == 0 && Model.ReviewsEnabled)
|
||||
{
|
||||
<p>There are no reviews.</p>
|
||||
}
|
||||
else if (!Model.ReviewsEnabled)
|
||||
{
|
||||
<b>
|
||||
<i>Reviews are disabled on this level.</i>
|
||||
</b>
|
||||
}
|
||||
else
|
||||
{
|
||||
int count = Model.Reviews.Count;
|
||||
<p>There @(count == 1 ? "is" : "are") @count review@(count == 1 ? "" : "s").</p>
|
||||
<div class="ui divider"></div>
|
||||
}
|
||||
|
||||
|
||||
@for(int i = 0; i < Model.Reviews.Count; i++)
|
||||
{
|
||||
Review review = Model.Reviews[i];
|
||||
string faceHash = (review.Thumb switch {
|
||||
-1 => review.Reviewer?.BooHash,
|
||||
0 => review.Reviewer?.MehHash,
|
||||
1 => review.Reviewer?.YayHash,
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
}) ?? "";
|
||||
|
||||
if (string.IsNullOrWhiteSpace(faceHash))
|
||||
{
|
||||
faceHash = ServerConfiguration.Instance.WebsiteConfiguration.MissingIconHash;
|
||||
}
|
||||
|
||||
string faceAlt = review.Thumb switch {
|
||||
-1 => "Boo!",
|
||||
0 => "Meh.",
|
||||
1 => "Yay!",
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
|
||||
int size = isMobile ? 50 : 100;
|
||||
|
||||
<div class="card">
|
||||
<div>
|
||||
<img class="cardIcon slotCardIcon" src="@ServerConfiguration.Instance.ExternalUrl/gameAssets/@faceHash" alt="@faceAlt" title="@faceAlt" style="min-width: @(size)px; width: @(size)px; height: @(size)px">
|
||||
</div>
|
||||
<div class="cardStats">
|
||||
<h3 style="margin-bottom: 5px;">@review.Reviewer?.Username</h3>
|
||||
@if (review.Deleted)
|
||||
{
|
||||
if (review.DeletedBy == DeletedBy.LevelAuthor)
|
||||
{
|
||||
<p>
|
||||
<i>This review has been deleted by the level author.</i>
|
||||
</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>
|
||||
<i>This review has been deleted by a moderator.</i>
|
||||
</p>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (review.Labels.Length > 1)
|
||||
{
|
||||
@foreach (string reviewLabel in review.Labels)
|
||||
{
|
||||
<div class="ui blue label">@reviewLabel.Replace("LABEL_", "")</div>
|
||||
}
|
||||
}
|
||||
@if (string.IsNullOrWhiteSpace(review.Text))
|
||||
{
|
||||
<p>
|
||||
<i>This review contains no text.</i>
|
||||
</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
<p>@HttpUtility.HtmlDecode(review.Text)</p>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@if (i != Model.Reviews.Count - 1)
|
||||
{
|
||||
<div class="ui divider"></div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.User != null && Model.User.IsAdmin)
|
||||
{
|
||||
<div class="ui yellow segment">
|
||||
<h2>Admin Options</h2>
|
||||
|
||||
@if (Model.Slot?.TeamPick ?? false)
|
||||
{
|
||||
<a href="/admin/slot/@Model.Slot.SlotId/removeTeamPick">
|
||||
<div class="ui pink button">
|
||||
<i class="ribbon icon"></i>
|
||||
<span>Remove Team Pick</span>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a href="/admin/slot/@Model.Slot?.SlotId/teamPick">
|
||||
<div class="ui pink button">
|
||||
<i class="ribbon icon"></i>
|
||||
<span>Team Pick</span>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
|
||||
<a href="/admin/slot/@Model.Slot?.SlotId/delete">
|
||||
<div class="ui red button">
|
||||
<i class="trash icon"></i>
|
||||
<span>Delete</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
}
|
70
ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml.cs
Normal file
70
ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml.cs
Normal file
|
@ -0,0 +1,70 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.Levels;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Reviews;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
|
||||
public class SlotPage : BaseLayout
|
||||
{
|
||||
public List<Comment> Comments = new();
|
||||
public List<Review> Reviews = new();
|
||||
|
||||
public readonly bool CommentsEnabled = ServerConfiguration.Instance.UserGeneratedContentLimits.LevelCommentsEnabled;
|
||||
public readonly bool ReviewsEnabled = ServerConfiguration.Instance.UserGeneratedContentLimits.LevelReviewsEnabled;
|
||||
|
||||
public Slot? Slot;
|
||||
public SlotPage(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public async Task<IActionResult> OnGet([FromRoute] int id)
|
||||
{
|
||||
Slot? slot = await this.Database.Slots.Include(s => s.Creator).FirstOrDefaultAsync(s => s.SlotId == id);
|
||||
if (slot == null) return this.NotFound();
|
||||
|
||||
this.Slot = slot;
|
||||
|
||||
if (this.CommentsEnabled)
|
||||
{
|
||||
this.Comments = await this.Database.Comments.Include(p => p.Poster)
|
||||
.OrderByDescending(p => p.Timestamp)
|
||||
.Where(c => c.TargetId == id && c.Type == CommentType.Level)
|
||||
.Take(50)
|
||||
.ToListAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Comments = new List<Comment>();
|
||||
}
|
||||
|
||||
if (this.ReviewsEnabled)
|
||||
{
|
||||
this.Reviews = await this.Database.Reviews.Include(r => r.Reviewer)
|
||||
.OrderByDescending(r => r.ThumbsUp - r.ThumbsDown)
|
||||
.ThenByDescending(r => r.Timestamp)
|
||||
.Where(r => r.SlotId == id)
|
||||
.Take(50)
|
||||
.ToListAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Reviews = new List<Review>();
|
||||
}
|
||||
|
||||
if (this.User == null) return this.Page();
|
||||
|
||||
foreach (Comment c in this.Comments)
|
||||
{
|
||||
Reaction? reaction = await this.Database.Reactions.FirstOrDefaultAsync(r => r.UserId == this.User.UserId && r.TargetId == c.CommentId);
|
||||
if (reaction != null) c.YourThumb = reaction.Rating;
|
||||
}
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
}
|
51
ProjectLighthouse.Servers.Website/Pages/SlotsPage.cshtml
Normal file
51
ProjectLighthouse.Servers.Website/Pages/SlotsPage.cshtml
Normal file
|
@ -0,0 +1,51 @@
|
|||
@page "/slots/{pageNumber:int}"
|
||||
@using LBPUnion.ProjectLighthouse.Extensions
|
||||
@using LBPUnion.ProjectLighthouse.Levels
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.SlotsPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = "Levels";
|
||||
}
|
||||
|
||||
<p>There are @Model.SlotCount total levels!</p>
|
||||
|
||||
<form action="/slots/0">
|
||||
<div class="ui icon input">
|
||||
<input type="text" name="name" placeholder="Search levels..." value="@Model.SearchValue">
|
||||
<i class="search icon"></i>
|
||||
</div>
|
||||
</form>
|
||||
<div class="ui divider"></div>
|
||||
|
||||
@foreach (Slot slot in Model.Slots)
|
||||
{
|
||||
bool isMobile = Model.Request.IsMobile();
|
||||
<div class="ui segment">
|
||||
@await Html.PartialAsync("Partials/SlotCardPartial", slot, new ViewDataDictionary(ViewData)
|
||||
{
|
||||
{
|
||||
"User", Model.User
|
||||
},
|
||||
{
|
||||
"CallbackUrl", $"~/slots/{Model.PageNumber}"
|
||||
},
|
||||
{
|
||||
"ShowLink", true
|
||||
},
|
||||
{
|
||||
"IsMobile", isMobile
|
||||
},
|
||||
})
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Model.PageNumber != 0)
|
||||
{
|
||||
<a href="/slots/@(Model.PageNumber - 1)@(Model.SearchValue?.Length == 0 ? "" : "?name=" + Model.SearchValue)">Previous Page</a>
|
||||
}
|
||||
@(Model.PageNumber + 1) / @(Model.PageAmount)
|
||||
@if (Model.PageNumber < Model.PageAmount - 1)
|
||||
{
|
||||
<a href="/slots/@(Model.PageNumber + 1)@(Model.SearchValue?.Length == 0 ? "" : "?name=" + Model.SearchValue)">Next Page</a>
|
||||
}
|
79
ProjectLighthouse.Servers.Website/Pages/SlotsPage.cshtml.cs
Normal file
79
ProjectLighthouse.Servers.Website/Pages/SlotsPage.cshtml.cs
Normal file
|
@ -0,0 +1,79 @@
|
|||
#nullable enable
|
||||
using System.Text;
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.Levels;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
|
||||
public class SlotsPage : BaseLayout
|
||||
{
|
||||
|
||||
public int PageAmount;
|
||||
|
||||
public int PageNumber;
|
||||
|
||||
public int SlotCount;
|
||||
|
||||
public List<Slot> Slots = new();
|
||||
|
||||
public string? SearchValue;
|
||||
|
||||
public SlotsPage(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public async Task<IActionResult> OnGet([FromRoute] int pageNumber, [FromQuery] string? name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name)) name = "";
|
||||
|
||||
string? targetAuthor = null;
|
||||
GameVersion? targetGame = null;
|
||||
StringBuilder finalSearch = new();
|
||||
foreach (string part in name.Split(" "))
|
||||
{
|
||||
if (part.Contains("by:"))
|
||||
{
|
||||
targetAuthor = part.Replace("by:", "");
|
||||
}
|
||||
else if (part.Contains("game:"))
|
||||
{
|
||||
if (part.Contains('1')) targetGame = GameVersion.LittleBigPlanet1;
|
||||
else if (part.Contains('2')) targetGame = GameVersion.LittleBigPlanet2;
|
||||
else if (part.Contains('3')) targetGame = GameVersion.LittleBigPlanet3;
|
||||
else if (part.Contains('v')) targetGame = GameVersion.LittleBigPlanetVita;
|
||||
}
|
||||
else
|
||||
{
|
||||
finalSearch.Append(part);
|
||||
}
|
||||
}
|
||||
|
||||
this.SearchValue = name.Trim();
|
||||
|
||||
this.SlotCount = await this.Database.Slots.Include(p => p.Creator)
|
||||
.Where(p => p.Name.Contains(finalSearch.ToString()))
|
||||
.Where(p => p.Creator != null && (targetAuthor == null || string.Equals(p.Creator.Username.ToLower(), targetAuthor.ToLower())))
|
||||
.Where(p => targetGame == null || p.GameVersion == targetGame)
|
||||
.CountAsync();
|
||||
|
||||
this.PageNumber = pageNumber;
|
||||
this.PageAmount = Math.Max(1, (int)Math.Ceiling((double)this.SlotCount / ServerStatics.PageSize));
|
||||
|
||||
if (this.PageNumber < 0 || this.PageNumber >= this.PageAmount) return this.Redirect($"/slots/{Math.Clamp(this.PageNumber, 0, this.PageAmount - 1)}");
|
||||
|
||||
this.Slots = await this.Database.Slots.Include(p => p.Creator)
|
||||
.Where(p => p.Name.Contains(finalSearch.ToString()))
|
||||
.Where(p => p.Creator != null && (targetAuthor == null || string.Equals(p.Creator.Username.ToLower(), targetAuthor.ToLower())))
|
||||
.Where(p => targetGame == null || p.GameVersion == targetGame)
|
||||
.OrderByDescending(p => p.FirstUploaded)
|
||||
.Skip(pageNumber * ServerStatics.PageSize)
|
||||
.Take(ServerStatics.PageSize)
|
||||
.ToListAsync();
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
}
|
144
ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml
Normal file
144
ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml
Normal file
|
@ -0,0 +1,144 @@
|
|||
@page "/user/{userId:int}"
|
||||
@using System.Web
|
||||
@using LBPUnion.ProjectLighthouse.Extensions
|
||||
@using LBPUnion.ProjectLighthouse.PlayerData
|
||||
@using LBPUnion.ProjectLighthouse.Types
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.UserPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.ShowTitleInPage = false;
|
||||
|
||||
Model.Title = Model.ProfileUser!.Username + "'s user page";
|
||||
Model.Description = Model.ProfileUser!.Biography;
|
||||
}
|
||||
|
||||
@if (Model.ProfileUser.Banned)
|
||||
{
|
||||
<div class="ui inverted red segment">
|
||||
<h2>User is currently banned!</h2>
|
||||
@if (Model.User != null && Model.User.IsAdmin)
|
||||
{
|
||||
<b>Reason:</b>
|
||||
<span>"@Model.ProfileUser.BannedReason"</span>
|
||||
<p>
|
||||
<i>Note: Only you and other admins may view the ban reason.</i>
|
||||
</p>
|
||||
<a class="ui inverted button" href="/admin/user/@Model.ProfileUser.UserId/unban">
|
||||
<i class="ban icon"></i>
|
||||
<span>Unban User</span>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>For shame...</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="ui grid">
|
||||
<div class="eight wide column">
|
||||
@await Html.PartialAsync("Partials/UserCardPartial", Model.ProfileUser, new ViewDataDictionary(ViewData)
|
||||
{
|
||||
{
|
||||
"ShowLink", false
|
||||
},
|
||||
{
|
||||
"IsMobile", Model.Request.IsMobile()
|
||||
},
|
||||
})
|
||||
</div>
|
||||
<div class="eight wide right aligned column">
|
||||
<br>
|
||||
@if (Model.ProfileUser != Model.User && Model.User != null)
|
||||
{
|
||||
if (!Model.IsProfileUserHearted)
|
||||
{
|
||||
<a class="ui pink button" href="/user/@Model.ProfileUser.UserId/heart">
|
||||
<i class="heart icon"></i>
|
||||
<span>Heart</span>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a class="ui pink button" href="/user/@Model.ProfileUser.UserId/unheart">
|
||||
<i class="heart broken icon"></i>
|
||||
<span>Unheart</span>
|
||||
</a>
|
||||
}
|
||||
}
|
||||
@if (Model.ProfileUser == Model.User)
|
||||
{
|
||||
<a class="ui blue button" href="/passwordReset">
|
||||
<i class="key icon"></i>
|
||||
<span>Reset Password</span>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<div class="eight wide column">
|
||||
<div class="ui blue segment">
|
||||
<h2>Biography</h2>
|
||||
@if (string.IsNullOrWhiteSpace(Model.ProfileUser.Biography))
|
||||
{
|
||||
<p>@Model.ProfileUser.Username hasn't introduced themselves yet</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>@HttpUtility.HtmlDecode(Model.ProfileUser.Biography)</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="eight wide column">
|
||||
<div class="ui red segment">
|
||||
<h2>Recent Activity</h2>
|
||||
<p>Coming soon!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@if (Model.Photos != null && Model.Photos.Count != 0)
|
||||
{
|
||||
<div class="ui purple segment">
|
||||
<h2>Most recent photos</h2>
|
||||
|
||||
<div class="ui center aligned grid">
|
||||
@foreach (Photo photo in Model.Photos)
|
||||
{
|
||||
<div class="eight wide column">
|
||||
@await Html.PartialAsync("Partials/PhotoPartial", photo)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@await Html.PartialAsync("Partials/CommentsPartial")
|
||||
|
||||
@if (Model.User != null && Model.User.IsAdmin)
|
||||
{
|
||||
<div class="ui yellow segment">
|
||||
<h2>Admin Options</h2>
|
||||
|
||||
@if (!Model.ProfileUser.Banned)
|
||||
{
|
||||
<div>
|
||||
<a class="ui red button" href="/admin/user/@Model.ProfileUser.UserId/ban">
|
||||
<i class="ban icon"></i>
|
||||
<span>Ban User</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="ui fitted hidden divider"></div>
|
||||
}
|
||||
|
||||
<div>
|
||||
<a class="ui red button" href="/admin/user/@Model.ProfileUser.UserId/wipePlanets">
|
||||
<i class="trash alternate icon"></i>
|
||||
<span>Wipe user's earth decorations</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="ui fitted hidden divider"></div>
|
||||
|
||||
@await Html.PartialAsync("Partials/AdminSetGrantedSlotsFormPartial", Model.ProfileUser)
|
||||
</div>
|
||||
}
|
58
ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml.cs
Normal file
58
ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml.cs
Normal file
|
@ -0,0 +1,58 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
|
||||
public class UserPage : BaseLayout
|
||||
{
|
||||
public List<Comment>? Comments;
|
||||
|
||||
public bool CommentsEnabled = ServerConfiguration.Instance.UserGeneratedContentLimits.ProfileCommentsEnabled;
|
||||
|
||||
public bool IsProfileUserHearted;
|
||||
|
||||
public List<Photo>? Photos;
|
||||
|
||||
public User? ProfileUser;
|
||||
public UserPage(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public async Task<IActionResult> OnGet([FromRoute] int userId)
|
||||
{
|
||||
this.ProfileUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == userId);
|
||||
if (this.ProfileUser == null) return this.NotFound();
|
||||
|
||||
this.Photos = await this.Database.Photos.OrderByDescending(p => p.Timestamp).Where(p => p.CreatorId == userId).Take(6).ToListAsync();
|
||||
if (this.CommentsEnabled)
|
||||
{
|
||||
this.Comments = await this.Database.Comments.Include(p => p.Poster)
|
||||
.OrderByDescending(p => p.Timestamp)
|
||||
.Where(p => p.TargetId == userId && p.Type == CommentType.Profile)
|
||||
.Take(50)
|
||||
.ToListAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Comments = new List<Comment>();
|
||||
}
|
||||
|
||||
if (this.User == null) return this.Page();
|
||||
|
||||
foreach (Comment c in this.Comments)
|
||||
{
|
||||
Reaction? reaction = await this.Database.Reactions.FirstOrDefaultAsync(r => r.UserId == this.User.UserId && r.TargetId == c.CommentId);
|
||||
if (reaction != null) c.YourThumb = reaction.Rating;
|
||||
}
|
||||
this.IsProfileUserHearted = await this.Database.HeartedProfiles.FirstOrDefaultAsync
|
||||
(u => u.UserId == this.User.UserId && u.HeartedUserId == this.ProfileUser.UserId) !=
|
||||
null;
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
}
|
46
ProjectLighthouse.Servers.Website/Pages/UsersPage.cshtml
Normal file
46
ProjectLighthouse.Servers.Website/Pages/UsersPage.cshtml
Normal file
|
@ -0,0 +1,46 @@
|
|||
@page "/users/{pageNumber:int}"
|
||||
@using LBPUnion.ProjectLighthouse.Extensions
|
||||
@using LBPUnion.ProjectLighthouse.PlayerData.Profiles
|
||||
@using LBPUnion.ProjectLighthouse.Types
|
||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.UsersPage
|
||||
|
||||
@{
|
||||
Layout = "Layouts/BaseLayout";
|
||||
Model.Title = "Users";
|
||||
}
|
||||
|
||||
<p>There are @Model.UserCount total users.</p>
|
||||
|
||||
<form action="/users/0">
|
||||
<div class="ui icon input">
|
||||
<input type="text" name="name" placeholder="Search users..." value="@Model.SearchValue">
|
||||
<i class="search icon"></i>
|
||||
</div>
|
||||
</form>
|
||||
<div class="ui divider"></div>
|
||||
|
||||
@foreach (User user in Model.Users)
|
||||
{
|
||||
bool isMobile = Model.Request.IsMobile();
|
||||
<div class="ui segment">
|
||||
@await Html.PartialAsync("Partials/UserCardPartial", user, new ViewDataDictionary(ViewData)
|
||||
{
|
||||
{
|
||||
"ShowLink", true
|
||||
},
|
||||
{
|
||||
"IsMobile", isMobile
|
||||
},
|
||||
})
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Model.PageNumber != 0)
|
||||
{
|
||||
<a href="/users/@(Model.PageNumber - 1)@(Model.SearchValue?.Length == 0 ? "" : "?name=" + Model.SearchValue)">Previous Page</a>
|
||||
}
|
||||
@(Model.PageNumber + 1) / @(Model.PageAmount)
|
||||
@if (Model.PageNumber < Model.PageAmount - 1)
|
||||
{
|
||||
<a href="/users/@(Model.PageNumber + 1)@(Model.SearchValue?.Length == 0 ? "" : "?name=" + Model.SearchValue)">Next Page</a>
|
||||
}
|
47
ProjectLighthouse.Servers.Website/Pages/UsersPage.cshtml.cs
Normal file
47
ProjectLighthouse.Servers.Website/Pages/UsersPage.cshtml.cs
Normal file
|
@ -0,0 +1,47 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||
|
||||
public class UsersPage : BaseLayout
|
||||
{
|
||||
public int PageAmount;
|
||||
|
||||
public int PageNumber;
|
||||
|
||||
public int UserCount;
|
||||
|
||||
public List<User> Users = new();
|
||||
|
||||
public string? SearchValue;
|
||||
|
||||
public UsersPage(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public async Task<IActionResult> OnGet([FromRoute] int pageNumber, [FromQuery] string? name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name)) name = "";
|
||||
|
||||
this.SearchValue = name.Replace(" ", string.Empty);
|
||||
|
||||
this.UserCount = await this.Database.Users.CountAsync(u => !u.Banned && u.Username.Contains(this.SearchValue));
|
||||
|
||||
this.PageNumber = pageNumber;
|
||||
this.PageAmount = Math.Max(1, (int)Math.Ceiling((double)this.UserCount / ServerStatics.PageSize));
|
||||
|
||||
if (this.PageNumber < 0 || this.PageNumber >= this.PageAmount) return this.Redirect($"/users/{Math.Clamp(this.PageNumber, 0, this.PageAmount - 1)}");
|
||||
|
||||
this.Users = await this.Database.Users.Where(u => !u.Banned && u.Username.Contains(this.SearchValue))
|
||||
.OrderByDescending(b => b.UserId)
|
||||
.Skip(pageNumber * ServerStatics.PageSize)
|
||||
.Take(ServerStatics.PageSize)
|
||||
.ToListAsync();
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue