diff --git a/.idea/.idea.ProjectLighthouse/.idea/dataSources.xml b/.idea/.idea.ProjectLighthouse/.idea/dataSources.xml index c64bade5..702fe04b 100644 --- a/.idea/.idea.ProjectLighthouse/.idea/dataSources.xml +++ b/.idea/.idea.ProjectLighthouse/.idea/dataSources.xml @@ -4,7 +4,7 @@ mysql.8 true - com.mysql.cj.jdbc.Driver + com.mysql.cj.jdbc.NonRegisteringDriver jdbc:mysql://localhost:3306/lighthouse $ProjectFileDir$ diff --git a/ProjectLighthouse.sln.DotSettings b/ProjectLighthouse.sln.DotSettings index 54a00f4c..385c8365 100644 --- a/ProjectLighthouse.sln.DotSettings +++ b/ProjectLighthouse.sln.DotSettings @@ -128,5 +128,6 @@ True True True + True True True \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/ListController.cs b/ProjectLighthouse/Controllers/ListController.cs index 7b751551..8accb958 100644 --- a/ProjectLighthouse/Controllers/ListController.cs +++ b/ProjectLighthouse/Controllers/ListController.cs @@ -59,19 +59,10 @@ namespace LBPUnion.ProjectLighthouse.Controllers User? user = await this.database.UserFromGameRequest(this.Request); if (user == null) return this.StatusCode(403, ""); - QueuedLevel? queuedLevel = await this.database.QueuedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == id); - if (queuedLevel != null) return this.Ok(); + Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); + if (slot == null) return this.NotFound(); - this.database.QueuedLevels.Add - ( - new QueuedLevel - { - SlotId = id, - UserId = user.UserId, - } - ); - - await this.database.SaveChangesAsync(); + await this.database.QueueLevel(user, slot); return this.Ok(); } @@ -82,10 +73,10 @@ namespace LBPUnion.ProjectLighthouse.Controllers User? user = await this.database.UserFromGameRequest(this.Request); if (user == null) return this.StatusCode(403, ""); - QueuedLevel? queuedLevel = await this.database.QueuedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == id); - if (queuedLevel != null) this.database.QueuedLevels.Remove(queuedLevel); + Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); + if (slot == null) return this.NotFound(); - await this.database.SaveChangesAsync(); + await this.database.UnqueueLevel(user, slot); return this.Ok(); } @@ -140,19 +131,10 @@ namespace LBPUnion.ProjectLighthouse.Controllers User? user = await this.database.UserFromGameRequest(this.Request); if (user == null) return this.StatusCode(403, ""); - HeartedLevel? heartedLevel = await this.database.HeartedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == id); - if (heartedLevel != null) return this.Ok(); + Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); + if (slot == null) return this.NotFound(); - this.database.HeartedLevels.Add - ( - new HeartedLevel - { - SlotId = id, - UserId = user.UserId, - } - ); - - await this.database.SaveChangesAsync(); + await this.database.HeartLevel(user, slot); return this.Ok(); } @@ -163,10 +145,10 @@ namespace LBPUnion.ProjectLighthouse.Controllers User? user = await this.database.UserFromGameRequest(this.Request); if (user == null) return this.StatusCode(403, ""); - HeartedLevel? heartedLevel = await this.database.HeartedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == id); - if (heartedLevel != null) this.database.HeartedLevels.Remove(heartedLevel); + Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); + if (slot == null) return this.NotFound(); - await this.database.SaveChangesAsync(); + await this.database.UnheartLevel(user, slot); return this.Ok(); } @@ -210,20 +192,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); if (heartedUser == null) return this.NotFound(); - HeartedProfile? heartedProfile = await this.database.HeartedProfiles.FirstOrDefaultAsync - (q => q.UserId == user.UserId && q.HeartedUserId == heartedUser.UserId); - if (heartedProfile != null) return this.Ok(); - - this.database.HeartedProfiles.Add - ( - new HeartedProfile - { - HeartedUserId = heartedUser.UserId, - UserId = user.UserId, - } - ); - - await this.database.SaveChangesAsync(); + await this.database.HeartUser(user, heartedUser); return this.Ok(); } @@ -237,11 +206,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); if (heartedUser == null) return this.NotFound(); - HeartedProfile? heartedProfile = await this.database.HeartedProfiles.FirstOrDefaultAsync - (q => q.UserId == user.UserId && q.HeartedUserId == heartedUser.UserId); - if (heartedProfile != null) this.database.HeartedProfiles.Remove(heartedProfile); - - await this.database.SaveChangesAsync(); + await this.database.UnheartUser(user, heartedUser); return this.Ok(); } diff --git a/ProjectLighthouse/Controllers/MatchController.cs b/ProjectLighthouse/Controllers/MatchController.cs index 87bd6e31..4315628e 100644 --- a/ProjectLighthouse/Controllers/MatchController.cs +++ b/ProjectLighthouse/Controllers/MatchController.cs @@ -88,6 +88,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers } lastMatch.Timestamp = TimestampHelper.Timestamp; + lastMatch.GameVersion = gameToken.GameVersion; await this.database.SaveChangesAsync(); diff --git a/ProjectLighthouse/Controllers/MessageController.cs b/ProjectLighthouse/Controllers/MessageController.cs index aff3ddd6..33a23f4e 100644 --- a/ProjectLighthouse/Controllers/MessageController.cs +++ b/ProjectLighthouse/Controllers/MessageController.cs @@ -30,14 +30,19 @@ namespace LBPUnion.ProjectLighthouse.Controllers User user = await this.database.UserFromGameRequest(this.Request, true); if (user == null) return this.StatusCode(403, ""); - return this.Ok - ( - $"Please stay on this screen.\n" + - $"Before continuing, you must approve this session at {ServerSettings.Instance.ExternalUrl}.\n" + - $"Please keep in mind that if the session is denied you may have to wait up to 5-10 minutes to try logging in again.\n" + - $"Once approved, you may press X and continue.\n\n" + - ServerSettings.Instance.EulaText - ); + if (ServerSettings.Instance.UseExternalAuth) + { + return this.Ok + ( + $"Please stay on this screen.\n" + + $"Before continuing, you must approve this session at {ServerSettings.Instance.ExternalUrl}.\n" + + $"Please keep in mind that if the session is denied you may have to wait up to 5-10 minutes to try logging in again.\n" + + $"Once approved, you may press X and continue.\n\n" + + ServerSettings.Instance.EulaText + ); + } + + return this.Ok($"You are now logged in as {user.Username} (id: {user.UserId}).\n\n" + ServerSettings.Instance.EulaText); } [HttpGet("notification")] diff --git a/ProjectLighthouse/Controllers/PublishController.cs b/ProjectLighthouse/Controllers/PublishController.cs index 61444bc0..0f6499dd 100644 --- a/ProjectLighthouse/Controllers/PublishController.cs +++ b/ProjectLighthouse/Controllers/PublishController.cs @@ -75,7 +75,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers GameToken gameToken = userAndToken.Value.Item2; Slot? slot = await this.GetSlotFromBody(); - if (slot == null || slot.Location == null) return this.BadRequest(); + if (slot?.Location == null) return this.BadRequest(); // Republish logic if (slot.SlotId != 0) @@ -93,8 +93,28 @@ namespace LBPUnion.ProjectLighthouse.Controllers slot.CreatorId = oldSlot.CreatorId; slot.LocationId = oldSlot.LocationId; slot.SlotId = oldSlot.SlotId; + + slot.PlaysLBP1 = oldSlot.PlaysLBP1; + slot.PlaysLBP1Complete = oldSlot.PlaysLBP1Complete; + slot.PlaysLBP1Unique = oldSlot.PlaysLBP1Unique; + + slot.PlaysLBP2 = oldSlot.PlaysLBP2; + slot.PlaysLBP2Complete = oldSlot.PlaysLBP2Complete; + slot.PlaysLBP2Unique = oldSlot.PlaysLBP2Unique; + + slot.PlaysLBP3 = oldSlot.PlaysLBP3; + slot.PlaysLBP3Complete = oldSlot.PlaysLBP3Complete; + slot.PlaysLBP3Unique = oldSlot.PlaysLBP3Unique; + + slot.PlaysLBPVita = oldSlot.PlaysLBPVita; + slot.PlaysLBPVitaComplete = oldSlot.PlaysLBPVitaComplete; + slot.PlaysLBPVitaUnique = oldSlot.PlaysLBPVitaUnique; + slot.FirstUploaded = oldSlot.FirstUploaded; slot.LastUpdated = TimeHelper.UnixTimeMilliseconds(); + + slot.TeamPick = oldSlot.TeamPick; + slot.GameVersion = gameToken.GameVersion; this.database.Entry(oldSlot).CurrentValues.SetValues(slot); diff --git a/ProjectLighthouse/Controllers/ResourcesController.cs b/ProjectLighthouse/Controllers/ResourcesController.cs index 24949857..1b645471 100644 --- a/ProjectLighthouse/Controllers/ResourcesController.cs +++ b/ProjectLighthouse/Controllers/ResourcesController.cs @@ -70,9 +70,14 @@ namespace LBPUnion.ProjectLighthouse.Controllers return this.UnprocessableEntity(); } - if (HashHelper.Sha1Hash(file.Data) != hash) + string calculatedHash = HashHelper.Sha1Hash(file.Data).ToLower(); + if (calculatedHash != hash) { - Logger.Log($"File hash does not match the uploaded file! (hash: {hash}, type: {file.FileType})", LoggerLevelResources.Instance); + Logger.Log + ( + $"File hash does not match the uploaded file! (hash: {hash}, calculatedHash: {calculatedHash}, type: {file.FileType})", + LoggerLevelResources.Instance + ); return this.Conflict(); } diff --git a/ProjectLighthouse/Controllers/SlotsController.cs b/ProjectLighthouse/Controllers/SlotsController.cs index 27b7c8ac..6fb7f356 100644 --- a/ProjectLighthouse/Controllers/SlotsController.cs +++ b/ProjectLighthouse/Controllers/SlotsController.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using LBPUnion.ProjectLighthouse.Serialization; +using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Settings; @@ -42,7 +43,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers .Include(s => s.Location) .Where(s => s.Creator!.Username == user.Username) .Skip(pageStart - 1) - .Take(Math.Min(pageSize, ServerStatics.EntitledSlots)), + .Take(Math.Min(pageSize, ServerSettings.Instance.EntitledSlots)), string.Empty, (current, slot) => current + slot.Serialize() ); @@ -56,7 +57,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers new Dictionary { { - "hint_start", pageStart + Math.Min(pageSize, ServerStatics.EntitledSlots) + "hint_start", pageStart + Math.Min(pageSize, ServerSettings.Instance.EntitledSlots) }, { "total", user.UsedSlots @@ -110,7 +111,23 @@ namespace LBPUnion.ProjectLighthouse.Controllers .Take(Math.Min(pageSize, 30)); string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize()); - return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "hint_start", pageStart + Math.Min(pageSize, 30))); + return this.Ok + ( + LbpSerializer.TaggedStringElement + ( + "slots", + response, + new Dictionary + { + { + "hint_start", pageStart + Math.Min(pageSize, ServerSettings.Instance.EntitledSlots) + }, + { + "total", await StatisticsHelper.SlotCount() + }, + } + ) + ); } [HttpGet("slots/mmpicks")] @@ -130,7 +147,23 @@ namespace LBPUnion.ProjectLighthouse.Controllers .Take(Math.Min(pageSize, 30)); string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize()); - return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "hint_start", pageStart + Math.Min(pageSize, 30))); + return this.Ok + ( + LbpSerializer.TaggedStringElement + ( + "slots", + response, + new Dictionary + { + { + "hint_start", pageStart + Math.Min(pageSize, ServerSettings.Instance.EntitledSlots) + }, + { + "total", await StatisticsHelper.MMPicksCount() + }, + } + ) + ); } [HttpGet("slots/lbp2luckydip")] @@ -149,8 +182,24 @@ namespace LBPUnion.ProjectLighthouse.Controllers string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize()); - return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "hint_start", pageStart + Math.Min(pageSize, 30))); + return this.Ok + ( + LbpSerializer.TaggedStringElement + ( + "slots", + response, + new Dictionary + { + { + "hint_start", pageStart + Math.Min(pageSize, ServerSettings.Instance.EntitledSlots) + }, + { + "total", await StatisticsHelper.SlotCount() + }, + } + ) + ); } } -} \ No newline at end of file +} diff --git a/ProjectLighthouse/Controllers/ExternalAuth/AuthenticationController.cs b/ProjectLighthouse/Controllers/Website/ExternalAuth/AuthenticationController.cs similarity index 97% rename from ProjectLighthouse/Controllers/ExternalAuth/AuthenticationController.cs rename to ProjectLighthouse/Controllers/Website/ExternalAuth/AuthenticationController.cs index 323ea19f..045d4cd4 100644 --- a/ProjectLighthouse/Controllers/ExternalAuth/AuthenticationController.cs +++ b/ProjectLighthouse/Controllers/Website/ExternalAuth/AuthenticationController.cs @@ -7,7 +7,7 @@ using LBPUnion.ProjectLighthouse.Types; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -namespace LBPUnion.ProjectLighthouse.Controllers.ExternalAuth +namespace LBPUnion.ProjectLighthouse.Controllers.Website.ExternalAuth { [ApiController] [Route("/authentication")] diff --git a/ProjectLighthouse/Controllers/Website/SlotPageController.cs b/ProjectLighthouse/Controllers/Website/SlotPageController.cs new file mode 100644 index 00000000..ee93b470 --- /dev/null +++ b/ProjectLighthouse/Controllers/Website/SlotPageController.cs @@ -0,0 +1,102 @@ +#nullable enable +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Types; +using LBPUnion.ProjectLighthouse.Types.Levels; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +// I would like to apologize in advance for anyone dealing with this file. +// Theres probably a better way to do this with delegates but I'm tired. +// TODO: Clean up this file +// - jvyden + +namespace LBPUnion.ProjectLighthouse.Controllers.Website +{ + [ApiController] + [Route("slot/{id:int}")] + public class SlotPageController : ControllerBase + { + private readonly Database database; + + public SlotPageController(Database database) + { + this.database = database; + } + + [HttpGet("heart")] + public async Task HeartLevel([FromRoute] int id, [FromQuery] string? callbackUrl) + { + if (string.IsNullOrEmpty(callbackUrl)) + { + callbackUrl = "~/slot/" + id; + } + + User? user = this.database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("~/login"); + + Slot? heartedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); + if (heartedSlot == null) return this.NotFound(); + + await this.database.HeartLevel(user, heartedSlot); + + return this.Redirect(callbackUrl); + } + + [HttpGet("unheart")] + public async Task UnheartLevel([FromRoute] int id, [FromQuery] string? callbackUrl) + { + if (string.IsNullOrEmpty(callbackUrl)) + { + callbackUrl = "~/slot/" + id; + } + + User? user = this.database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("~/login"); + + Slot? heartedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); + if (heartedSlot == null) return this.NotFound(); + + await this.database.UnheartLevel(user, heartedSlot); + + return this.Redirect(callbackUrl); + } + + [HttpGet("queue")] + public async Task QueueLevel([FromRoute] int id, [FromQuery] string? callbackUrl) + { + if (string.IsNullOrEmpty(callbackUrl)) + { + callbackUrl = "~/slot/" + id; + } + + User? user = this.database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("~/login"); + + Slot? queuedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); + if (queuedSlot == null) return this.NotFound(); + + await this.database.QueueLevel(user, queuedSlot); + + return this.Redirect(callbackUrl); + } + + [HttpGet("unqueue")] + public async Task UnqueueLevel([FromRoute] int id, [FromQuery] string? callbackUrl) + { + if (string.IsNullOrEmpty(callbackUrl)) + { + callbackUrl = "~/slot/" + id; + } + + User? user = this.database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("~/login"); + + Slot? queuedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); + if (queuedSlot == null) return this.NotFound(); + + await this.database.UnqueueLevel(user, queuedSlot); + + return this.Redirect(callbackUrl); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/Website/UserPageController.cs b/ProjectLighthouse/Controllers/Website/UserPageController.cs new file mode 100644 index 00000000..43e37725 --- /dev/null +++ b/ProjectLighthouse/Controllers/Website/UserPageController.cs @@ -0,0 +1,48 @@ +#nullable enable +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Types; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Controllers.Website +{ + [ApiController] + [Route("user/{id:int}")] + public class UserPageController : ControllerBase + { + private readonly Database database; + + public UserPageController(Database database) + { + this.database = database; + } + + [HttpGet("heart")] + public async Task HeartUser([FromRoute] int id) + { + User? user = this.database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("~/login"); + + User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); + if (heartedUser == null) return this.NotFound(); + + await this.database.HeartUser(user, heartedUser); + + return this.Redirect("~/user/" + id); + } + + [HttpGet("unheart")] + public async Task UnheartUser([FromRoute] int id) + { + User? user = this.database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("~/login"); + + User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); + if (heartedUser == null) return this.NotFound(); + + await this.database.UnheartUser(user, heartedUser); + + return this.Redirect("~/user/" + id); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Database.cs b/ProjectLighthouse/Database.cs index 0468cb74..e2134e58 100644 --- a/ProjectLighthouse/Database.cs +++ b/ProjectLighthouse/Database.cs @@ -40,7 +40,7 @@ namespace LBPUnion.ProjectLighthouse public async Task CreateUser(string username, string password) { - if (!password.StartsWith("$2a")) throw new ArgumentException(nameof(password) + " is not a BCrypt hash"); + if (!password.StartsWith("$")) throw new ArgumentException(nameof(password) + " is not a BCrypt hash"); User user; if ((user = await this.Users.Where(u => u.Username == username).FirstOrDefaultAsync()) != null) return user; @@ -90,6 +90,87 @@ namespace LBPUnion.ProjectLighthouse return gameToken; } + #region Hearts & Queues + + public async Task HeartUser(User user, User heartedUser) + { + HeartedProfile? heartedProfile = await this.HeartedProfiles.FirstOrDefaultAsync + (q => q.UserId == user.UserId && q.HeartedUserId == heartedUser.UserId); + if (heartedProfile != null) return; + + this.HeartedProfiles.Add + ( + new HeartedProfile + { + HeartedUserId = heartedUser.UserId, + UserId = user.UserId, + } + ); + + await this.SaveChangesAsync(); + } + + public async Task UnheartUser(User user, User heartedUser) + { + HeartedProfile? heartedProfile = await this.HeartedProfiles.FirstOrDefaultAsync + (q => q.UserId == user.UserId && q.HeartedUserId == heartedUser.UserId); + if (heartedProfile != null) this.HeartedProfiles.Remove(heartedProfile); + + await this.SaveChangesAsync(); + } + + public async Task HeartLevel(User user, Slot heartedSlot) + { + HeartedLevel? heartedLevel = await this.HeartedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == heartedSlot.SlotId); + if (heartedLevel != null) return; + + this.HeartedLevels.Add + ( + new HeartedLevel + { + SlotId = heartedSlot.SlotId, + UserId = user.UserId, + } + ); + + await this.SaveChangesAsync(); + } + + public async Task UnheartLevel(User user, Slot heartedSlot) + { + HeartedLevel? heartedLevel = await this.HeartedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == heartedSlot.SlotId); + if (heartedLevel != null) this.HeartedLevels.Remove(heartedLevel); + + await this.SaveChangesAsync(); + } + + public async Task QueueLevel(User user, Slot queuedSlot) + { + QueuedLevel? queuedLevel = await this.QueuedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == queuedSlot.SlotId); + if (queuedLevel != null) return; + + this.QueuedLevels.Add + ( + new QueuedLevel + { + SlotId = queuedSlot.SlotId, + UserId = user.UserId, + } + ); + + await this.SaveChangesAsync(); + } + + public async Task UnqueueLevel(User user, Slot queuedSlot) + { + QueuedLevel? queuedLevel = await this.QueuedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == queuedSlot.SlotId); + if (queuedLevel != null) this.QueuedLevels.Remove(queuedLevel); + + await this.SaveChangesAsync(); + } + + #endregion + #region Game Token Shenanigans public async Task UserFromMMAuth(string authToken, bool allowUnapproved = false) diff --git a/ProjectLighthouse/Helpers/FileHelper.cs b/ProjectLighthouse/Helpers/FileHelper.cs index f85b3113..ce33cff1 100644 --- a/ProjectLighthouse/Helpers/FileHelper.cs +++ b/ProjectLighthouse/Helpers/FileHelper.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Text; using LBPUnion.ProjectLighthouse.Types.Files; +using LBPUnion.ProjectLighthouse.Types.Settings; namespace LBPUnion.ProjectLighthouse.Helpers { @@ -14,6 +15,8 @@ namespace LBPUnion.ProjectLighthouse.Helpers public static bool IsFileSafe(LbpFile file) { + if (!ServerSettings.Instance.CheckForUnsafeFiles) return true; + if (file.FileType == LbpFileType.Unknown) file.FileType = DetermineFileType(file.Data); return file.FileType switch diff --git a/ProjectLighthouse/Helpers/HashHelper.cs b/ProjectLighthouse/Helpers/HashHelper.cs index 838a22a4..88012439 100644 --- a/ProjectLighthouse/Helpers/HashHelper.cs +++ b/ProjectLighthouse/Helpers/HashHelper.cs @@ -67,7 +67,7 @@ namespace LBPUnion.ProjectLighthouse.Helpers public static string Sha256Hash(string str) => Sha256Hash(Encoding.UTF8.GetBytes(str)); - public static string Sha256Hash(byte[] bytes) => BitConverter.ToString(sha256.ComputeHash(bytes)).Replace("-", ""); + public static string Sha256Hash(byte[] bytes) => BitConverter.ToString(sha256.ComputeHash(bytes)).Replace("-", "").ToLower(); public static string Sha1Hash(string str) => Sha1Hash(Encoding.UTF8.GetBytes(str)); diff --git a/ProjectLighthouse/Helpers/MaintenanceHelper.cs b/ProjectLighthouse/Helpers/MaintenanceHelper.cs new file mode 100644 index 00000000..90d9c5b3 --- /dev/null +++ b/ProjectLighthouse/Helpers/MaintenanceHelper.cs @@ -0,0 +1,72 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Maintenance; + +namespace LBPUnion.ProjectLighthouse.Helpers +{ + public static class MaintenanceHelper + { + public static List Commands { get; } + + public static List MaintenanceJobs { get; } + + private static List getListOfInterfaceObjects() where T : class + { + return Assembly.GetExecutingAssembly() + .GetTypes() + .Where(t => t.GetInterfaces().Contains(typeof(T)) && t.GetConstructor(Type.EmptyTypes) != null) + .Select(t => Activator.CreateInstance(t) as T) + .ToList()!; + } + + static MaintenanceHelper() + { + Commands = getListOfInterfaceObjects(); + MaintenanceJobs = getListOfInterfaceObjects(); + } + + public static async Task RunCommand(string[] args) + { + if (args.Length < 1) + { + throw new Exception + ( + "This should never happen. " + + "If it did, its because you tried to run a command before validating that the user actually wants to run one." + ); + } + + string baseCmd = args[0]; + args = args.Skip(1).ToArray(); + + IEnumerable suitableCommands = Commands.Where + (command => command.Aliases().Any(a => a.ToLower() == baseCmd.ToLower())) + .Where(command => args.Length >= command.RequiredArgs()); + foreach (ICommand command in suitableCommands) + { + Console.WriteLine("Running command " + command.Name()); + await command.Run(args); + return; + } + + Console.WriteLine("Command not found."); + } + + public static async Task RunMaintenanceJob(string jobName) + { + IMaintenanceJob? job = MaintenanceJobs.FirstOrDefault(j => j.GetType().Name == jobName); + if (job == null) throw new ArgumentNullException(); + + await RunMaintenanceJob(job); + } + + public static async Task RunMaintenanceJob(IMaintenanceJob job) + { + await job.Run(); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Helpers/GitVersionHelper.cs b/ProjectLighthouse/Helpers/VersionHelper.cs similarity index 87% rename from ProjectLighthouse/Helpers/GitVersionHelper.cs rename to ProjectLighthouse/Helpers/VersionHelper.cs index bc4e8cad..13bae320 100644 --- a/ProjectLighthouse/Helpers/GitVersionHelper.cs +++ b/ProjectLighthouse/Helpers/VersionHelper.cs @@ -6,9 +6,9 @@ using LBPUnion.ProjectLighthouse.Types.Settings; namespace LBPUnion.ProjectLighthouse.Helpers { - public static class GitVersionHelper + public static class VersionHelper { - static GitVersionHelper() + static VersionHelper() { try { @@ -51,8 +51,17 @@ namespace LBPUnion.ProjectLighthouse.Helpers public static string CommitHash { get; set; } public static string Branch { get; set; } - public static string FullVersion => $"{ServerStatics.ServerName} {Branch}@{CommitHash}"; + public static string FullVersion => $"{ServerStatics.ServerName} {Branch}@{CommitHash} {Build}"; public static bool IsDirty => CommitHash.EndsWith("-dirty"); public static bool CanCheckForUpdates { get; set; } + + public const string Build = + #if DEBUG + "Debug"; + #elif RELEASE + "Release"; + #else + "Unknown"; + #endif } } \ No newline at end of file diff --git a/ProjectLighthouse/Maintenance/Commands/MakeUserAdminCommand.cs b/ProjectLighthouse/Maintenance/Commands/MakeUserAdminCommand.cs new file mode 100644 index 00000000..4f0b0cbf --- /dev/null +++ b/ProjectLighthouse/Maintenance/Commands/MakeUserAdminCommand.cs @@ -0,0 +1,47 @@ +#nullable enable +using System; +using System.Threading.Tasks; +using JetBrains.Annotations; +using LBPUnion.ProjectLighthouse.Types; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Maintenance.Commands +{ + [UsedImplicitly] + public class MakeUserAdminCommand : ICommand + { + private readonly Database database = new(); + + public string Name() => "Make User Admin"; + public string[] Aliases() + => new[] + { + "makeAdmin", + }; + public string Arguments() => ""; + public int RequiredArgs() => 1; + + public async Task Run(string[] args) + { + User? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == args[0]); + if (user == null) + { + try + { + user = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == Convert.ToInt32(args[0])); + if (user == null) throw new Exception(); + } + catch + { + Console.WriteLine($"Could not find user by parameter '{args[0]}'"); + return; + } + } + + user.IsAdmin = true; + await this.database.SaveChangesAsync(); + + Console.WriteLine($"The user {user.Username} (id: {user.UserId}) is now an admin."); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Maintenance/Commands/ResetPasswordCommand.cs b/ProjectLighthouse/Maintenance/Commands/ResetPasswordCommand.cs new file mode 100644 index 00000000..91093999 --- /dev/null +++ b/ProjectLighthouse/Maintenance/Commands/ResetPasswordCommand.cs @@ -0,0 +1,51 @@ +#nullable enable +using System; +using System.Threading.Tasks; +using JetBrains.Annotations; +using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Types; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Maintenance.Commands +{ + [UsedImplicitly] + public class ResetPasswordCommand : ICommand + { + private readonly Database database = new(); + public string Name() => "Reset Password"; + public string[] Aliases() + => new[] + { + "setPassword", "resetPassword", "passwd", "password", + }; + public string Arguments() => " "; + public int RequiredArgs() => 2; + + public async Task Run(string[] args) + { + User? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == args[0]); + if (user == null) + { + try + { + user = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == Convert.ToInt32(args[0])); + if (user == null) throw new Exception(); + } + catch + { + Console.WriteLine($"Could not find user by parameter '{args[0]}'"); + return; + } + } + string password = args[1]; + if (password.Length != 64) password = HashHelper.Sha256Hash(password); + + user.Password = HashHelper.BCryptHash(password); + user.PasswordResetRequired = true; + + await this.database.SaveChangesAsync(); + + Console.WriteLine($"The password for user {user.Username} (id: {user.UserId}) has been reset."); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Maintenance/Commands/WipeTokensForUserCommand.cs b/ProjectLighthouse/Maintenance/Commands/WipeTokensForUserCommand.cs new file mode 100644 index 00000000..4e42461a --- /dev/null +++ b/ProjectLighthouse/Maintenance/Commands/WipeTokensForUserCommand.cs @@ -0,0 +1,46 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Types; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Maintenance.Commands +{ + public class WipeTokensForUserCommand : ICommand + { + private readonly Database database = new(); + + public string Name() => "Wipe tokens for user"; + public string[] Aliases() + => new[] + { + "wipeTokens", "wipeToken", "deleteTokens", "deleteToken", "removeTokens", "removeToken", + }; + public string Arguments() => ""; + public int RequiredArgs() => 1; + public async Task Run(string[] args) + { + User? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == args[0]); + if (user == null) + { + try + { + user = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == Convert.ToInt32(args[0])); + if (user == null) throw new Exception(); + } + catch + { + Console.WriteLine($"Could not find user by parameter '{args[0]}'"); + return; + } + } + + this.database.GameTokens.RemoveRange(this.database.GameTokens.Where(t => t.UserId == user.UserId)); + this.database.WebTokens.RemoveRange(this.database.WebTokens.Where(t => t.UserId == user.UserId)); + + await this.database.SaveChangesAsync(); + + Console.WriteLine($"Deleted all tokens for {user.Username} (id: {user.UserId})."); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Maintenance/ICommand.cs b/ProjectLighthouse/Maintenance/ICommand.cs new file mode 100644 index 00000000..3680fcf9 --- /dev/null +++ b/ProjectLighthouse/Maintenance/ICommand.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; + +namespace LBPUnion.ProjectLighthouse.Maintenance +{ + public interface ICommand + { + public Task Run(string[] args); + + public string Name(); + + public string[] Aliases(); + + public string FirstAlias => this.Aliases()[0]; + + public string Arguments(); + + public int RequiredArgs(); + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Maintenance/IMaintenanceJob.cs b/ProjectLighthouse/Maintenance/IMaintenanceJob.cs new file mode 100644 index 00000000..4abd1dcb --- /dev/null +++ b/ProjectLighthouse/Maintenance/IMaintenanceJob.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; + +namespace LBPUnion.ProjectLighthouse.Maintenance +{ + public interface IMaintenanceJob + { + public Task Run(); + + public string Name(); + + public string Description(); + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Maintenance/MaintenanceJobs/CleanupBrokenPhotosMaintenanceJob.cs b/ProjectLighthouse/Maintenance/MaintenanceJobs/CleanupBrokenPhotosMaintenanceJob.cs new file mode 100644 index 00000000..78d45fde --- /dev/null +++ b/ProjectLighthouse/Maintenance/MaintenanceJobs/CleanupBrokenPhotosMaintenanceJob.cs @@ -0,0 +1,71 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Types; +using LBPUnion.ProjectLighthouse.Types.Files; + +namespace LBPUnion.ProjectLighthouse.Maintenance.MaintenanceJobs +{ + public class CleanupBrokenPhotosMaintenanceJob : IMaintenanceJob + { + private readonly Database database = new(); + public string Name() => "Cleanup Broken Photos"; + public string Description() => "Deletes all photos that have missing assets."; + + [SuppressMessage("ReSharper", "LoopCanBePartlyConvertedToQuery")] + public async Task Run() + { + foreach (Photo photo in this.database.Photos) + { + bool hashNullOrEmpty = false; + bool noHashesExist = false; + bool largeHashIsInvalidFile = false; + + hashNullOrEmpty = string.IsNullOrEmpty + (photo.LargeHash) || + string.IsNullOrEmpty(photo.MediumHash) || + string.IsNullOrEmpty(photo.SmallHash) || + string.IsNullOrEmpty(photo.PlanHash); + if (hashNullOrEmpty) goto removePhoto; + + List hashes = new() + { + photo.LargeHash, + photo.MediumHash, + photo.SmallHash, + photo.PlanHash, + }; + + noHashesExist = FileHelper.ResourcesNotUploaded(hashes.ToArray()).Length != 0; + if (noHashesExist) goto removePhoto; + + LbpFile? file = LbpFile.FromHash(photo.LargeHash); +// Console.WriteLine(file.FileType, ); + if (file == null || file.FileType != LbpFileType.Jpeg && file.FileType != LbpFileType.Png) + { + largeHashIsInvalidFile = true; + goto removePhoto; + } + + continue; + + removePhoto: + + Console.WriteLine + ( + $"Removing photo (id: {photo.PhotoId}): " + + $"{nameof(hashNullOrEmpty)}: {hashNullOrEmpty}, " + + $"{nameof(noHashesExist)}: {noHashesExist}, " + + $"{nameof(largeHashIsInvalidFile)}: {largeHashIsInvalidFile}" + ); + + this.database.Photos.Remove(photo); + } + + await this.database.SaveChangesAsync(); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Maintenance/MaintenanceJobs/CleanupUnusedLocationsMaintenanceJob.cs b/ProjectLighthouse/Maintenance/MaintenanceJobs/CleanupUnusedLocationsMaintenanceJob.cs new file mode 100644 index 00000000..5fb8f6ff --- /dev/null +++ b/ProjectLighthouse/Maintenance/MaintenanceJobs/CleanupUnusedLocationsMaintenanceJob.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Types.Profiles; + +namespace LBPUnion.ProjectLighthouse.Maintenance.MaintenanceJobs +{ + public class CleanupUnusedLocationsMaintenanceJob : IMaintenanceJob + { + private readonly Database database = new(); + public string Name() => "Cleanup Unused Locations"; + public string Description() => "Cleanup unused locations in the database."; + + public async Task Run() + { + List usedLocationIds = new(); + + usedLocationIds.AddRange(this.database.Slots.Select(slot => slot.LocationId)); + usedLocationIds.AddRange(this.database.Users.Select(user => user.LocationId)); + + IQueryable locationsToRemove = this.database.Locations.Where(l => !usedLocationIds.Contains(l.Id)); + + foreach (Location location in locationsToRemove) + { + Console.WriteLine("Removing location " + location.Id); + this.database.Locations.Remove(location); + } + + await this.database.SaveChangesAsync(); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Maintenance/MaintenanceJobs/DeleteAllTokensMaintenanceJob.cs b/ProjectLighthouse/Maintenance/MaintenanceJobs/DeleteAllTokensMaintenanceJob.cs new file mode 100644 index 00000000..ca269fec --- /dev/null +++ b/ProjectLighthouse/Maintenance/MaintenanceJobs/DeleteAllTokensMaintenanceJob.cs @@ -0,0 +1,22 @@ +using System; +using System.Threading.Tasks; + +namespace LBPUnion.ProjectLighthouse.Maintenance.MaintenanceJobs +{ + public class DeleteAllTokensMaintenanceJob : IMaintenanceJob + { + private readonly Database database = new(); + + public string Name() => "Delete ALL Tokens"; + public string Description() => "Deletes ALL game tokens and web tokens."; + public async Task Run() + { + this.database.GameTokens.RemoveRange(this.database.GameTokens); + this.database.WebTokens.RemoveRange(this.database.WebTokens); + + await this.database.SaveChangesAsync(); + + Console.WriteLine("Deleted ALL tokens."); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Migrations/20211123224001_AddIsAdminToUser.Designer.cs b/ProjectLighthouse/Migrations/20211123224001_AddIsAdminToUser.Designer.cs new file mode 100644 index 00000000..2ceca95c --- /dev/null +++ b/ProjectLighthouse/Migrations/20211123224001_AddIsAdminToUser.Designer.cs @@ -0,0 +1,713 @@ +// +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(Database))] + [Migration("20211123224001_AddIsAdminToUser")] + partial class AddIsAdminToUser + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.AuthenticationAttempt", b => + { + b.Property("AuthenticationAttemptId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("GameTokenId") + .HasColumnType("int"); + + b.Property("IPAddress") + .HasColumnType("longtext"); + + b.Property("Platform") + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("AuthenticationAttemptId"); + + b.HasIndex("GameTokenId"); + + b.ToTable("AuthenticationAttempts"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.GameToken", b => + { + b.Property("TokenId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("GameVersion") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("UserLocation") + .HasColumnType("longtext"); + + b.Property("UserToken") + .HasColumnType("longtext"); + + b.HasKey("TokenId"); + + b.ToTable("GameTokens"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b => + { + b.Property("HeartedProfileId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("HeartedUserId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("HeartedProfileId"); + + b.HasIndex("HeartedUserId"); + + b.HasIndex("UserId"); + + b.ToTable("HeartedProfiles"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b => + { + b.Property("HeartedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("HeartedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("HeartedLevels"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b => + { + b.Property("QueuedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("QueuedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("QueuedLevels"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.RatedLevel", b => + { + b.Property("RatedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Rating") + .HasColumnType("int"); + + b.Property("RatingLBP1") + .HasColumnType("double"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("RatedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("RatedLevels"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b => + { + b.Property("SlotId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AuthorLabels") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("BackgroundHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatorId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FirstUploaded") + .HasColumnType("bigint"); + + b.Property("GameVersion") + .HasColumnType("int"); + + b.Property("IconHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InitiallyLocked") + .HasColumnType("tinyint(1)"); + + b.Property("LastUpdated") + .HasColumnType("bigint"); + + b.Property("Lbp1Only") + .HasColumnType("tinyint(1)"); + + b.Property("LevelType") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("MaximumPlayers") + .HasColumnType("int"); + + b.Property("MinimumPlayers") + .HasColumnType("int"); + + b.Property("MoveRequired") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PlaysLBP1") + .HasColumnType("int"); + + b.Property("PlaysLBP1Complete") + .HasColumnType("int"); + + b.Property("PlaysLBP1Unique") + .HasColumnType("int"); + + b.Property("PlaysLBP2") + .HasColumnType("int"); + + b.Property("PlaysLBP2Complete") + .HasColumnType("int"); + + b.Property("PlaysLBP2Unique") + .HasColumnType("int"); + + b.Property("PlaysLBP3") + .HasColumnType("int"); + + b.Property("PlaysLBP3Complete") + .HasColumnType("int"); + + b.Property("PlaysLBP3Unique") + .HasColumnType("int"); + + b.Property("PlaysLBPVita") + .HasColumnType("int"); + + b.Property("PlaysLBPVitaComplete") + .HasColumnType("int"); + + b.Property("PlaysLBPVitaUnique") + .HasColumnType("int"); + + b.Property("ResourceCollection") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RootLevel") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Shareable") + .HasColumnType("int"); + + b.Property("SubLevel") + .HasColumnType("tinyint(1)"); + + b.Property("TeamPick") + .HasColumnType("tinyint(1)"); + + b.HasKey("SlotId"); + + b.HasIndex("CreatorId"); + + b.HasIndex("LocationId"); + + b.ToTable("Slots"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.VisitedLevel", b => + { + b.Property("VisitedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("PlaysLBP1") + .HasColumnType("int"); + + b.Property("PlaysLBP2") + .HasColumnType("int"); + + b.Property("PlaysLBP3") + .HasColumnType("int"); + + b.Property("PlaysLBPVita") + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("VisitedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("VisitedLevels"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Photo", b => + { + b.Property("PhotoId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatorId") + .HasColumnType("int"); + + b.Property("LargeHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("MediumHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PhotoSubjectCollection") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PlanHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SmallHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("PhotoId"); + + b.HasIndex("CreatorId"); + + b.ToTable("Photos"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.PhotoSubject", b => + { + b.Property("PhotoSubjectId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Bounds") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("PhotoSubjectId"); + + b.HasIndex("UserId"); + + b.ToTable("PhotoSubjects"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Message") + .HasColumnType("longtext"); + + b.Property("PosterUserId") + .HasColumnType("int"); + + b.Property("TargetUserId") + .HasColumnType("int"); + + b.Property("ThumbsDown") + .HasColumnType("int"); + + b.Property("ThumbsUp") + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("CommentId"); + + b.HasIndex("PosterUserId"); + + b.HasIndex("TargetUserId"); + + b.ToTable("Comments"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.LastMatch", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("UserId"); + + b.ToTable("LastMatches"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Location", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("X") + .HasColumnType("int"); + + b.Property("Y") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Score", b => + { + b.Property("ScoreId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("PlayerIdCollection") + .HasColumnType("longtext"); + + b.Property("Points") + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("ScoreId"); + + b.HasIndex("SlotId"); + + b.ToTable("Scores"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Biography") + .HasColumnType("longtext"); + + b.Property("Game") + .HasColumnType("int"); + + b.Property("IconHash") + .HasColumnType("longtext"); + + b.Property("IsAdmin") + .HasColumnType("tinyint(1)"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("Password") + .HasColumnType("longtext"); + + b.Property("Pins") + .HasColumnType("longtext"); + + b.Property("PlanetHash") + .HasColumnType("longtext"); + + b.Property("Username") + .HasColumnType("longtext"); + + b.HasKey("UserId"); + + b.HasIndex("LocationId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.WebToken", b => + { + b.Property("TokenId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("UserToken") + .HasColumnType("longtext"); + + b.HasKey("TokenId"); + + b.ToTable("WebTokens"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.AuthenticationAttempt", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.GameToken", "GameToken") + .WithMany() + .HasForeignKey("GameTokenId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GameToken"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "HeartedUser") + .WithMany() + .HasForeignKey("HeartedUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HeartedUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.RatedLevel", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Creator") + .WithMany() + .HasForeignKey("CreatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Location"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.VisitedLevel", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Photo", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Creator") + .WithMany() + .HasForeignKey("CreatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.PhotoSubject", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Poster") + .WithMany() + .HasForeignKey("PosterUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Target") + .WithMany() + .HasForeignKey("TargetUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Poster"); + + b.Navigation("Target"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Score", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ProjectLighthouse/Migrations/20211123224001_AddIsAdminToUser.cs b/ProjectLighthouse/Migrations/20211123224001_AddIsAdminToUser.cs new file mode 100644 index 00000000..4678bb64 --- /dev/null +++ b/ProjectLighthouse/Migrations/20211123224001_AddIsAdminToUser.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + public partial class AddIsAdminToUser : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsAdmin", + table: "Users", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsAdmin", + table: "Users"); + } + } +} diff --git a/ProjectLighthouse/Migrations/20211125052035_AddGameVersionToLastMatch.Designer.cs b/ProjectLighthouse/Migrations/20211125052035_AddGameVersionToLastMatch.Designer.cs new file mode 100644 index 00000000..1793ca1b --- /dev/null +++ b/ProjectLighthouse/Migrations/20211125052035_AddGameVersionToLastMatch.Designer.cs @@ -0,0 +1,716 @@ +// +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(Database))] + [Migration("20211125052035_AddGameVersionToLastMatch")] + partial class AddGameVersionToLastMatch + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.AuthenticationAttempt", b => + { + b.Property("AuthenticationAttemptId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("GameTokenId") + .HasColumnType("int"); + + b.Property("IPAddress") + .HasColumnType("longtext"); + + b.Property("Platform") + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("AuthenticationAttemptId"); + + b.HasIndex("GameTokenId"); + + b.ToTable("AuthenticationAttempts"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.GameToken", b => + { + b.Property("TokenId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("GameVersion") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("UserLocation") + .HasColumnType("longtext"); + + b.Property("UserToken") + .HasColumnType("longtext"); + + b.HasKey("TokenId"); + + b.ToTable("GameTokens"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b => + { + b.Property("HeartedProfileId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("HeartedUserId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("HeartedProfileId"); + + b.HasIndex("HeartedUserId"); + + b.HasIndex("UserId"); + + b.ToTable("HeartedProfiles"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b => + { + b.Property("HeartedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("HeartedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("HeartedLevels"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b => + { + b.Property("QueuedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("QueuedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("QueuedLevels"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.RatedLevel", b => + { + b.Property("RatedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Rating") + .HasColumnType("int"); + + b.Property("RatingLBP1") + .HasColumnType("double"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("RatedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("RatedLevels"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b => + { + b.Property("SlotId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AuthorLabels") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("BackgroundHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatorId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FirstUploaded") + .HasColumnType("bigint"); + + b.Property("GameVersion") + .HasColumnType("int"); + + b.Property("IconHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InitiallyLocked") + .HasColumnType("tinyint(1)"); + + b.Property("LastUpdated") + .HasColumnType("bigint"); + + b.Property("Lbp1Only") + .HasColumnType("tinyint(1)"); + + b.Property("LevelType") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("MaximumPlayers") + .HasColumnType("int"); + + b.Property("MinimumPlayers") + .HasColumnType("int"); + + b.Property("MoveRequired") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PlaysLBP1") + .HasColumnType("int"); + + b.Property("PlaysLBP1Complete") + .HasColumnType("int"); + + b.Property("PlaysLBP1Unique") + .HasColumnType("int"); + + b.Property("PlaysLBP2") + .HasColumnType("int"); + + b.Property("PlaysLBP2Complete") + .HasColumnType("int"); + + b.Property("PlaysLBP2Unique") + .HasColumnType("int"); + + b.Property("PlaysLBP3") + .HasColumnType("int"); + + b.Property("PlaysLBP3Complete") + .HasColumnType("int"); + + b.Property("PlaysLBP3Unique") + .HasColumnType("int"); + + b.Property("PlaysLBPVita") + .HasColumnType("int"); + + b.Property("PlaysLBPVitaComplete") + .HasColumnType("int"); + + b.Property("PlaysLBPVitaUnique") + .HasColumnType("int"); + + b.Property("ResourceCollection") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RootLevel") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Shareable") + .HasColumnType("int"); + + b.Property("SubLevel") + .HasColumnType("tinyint(1)"); + + b.Property("TeamPick") + .HasColumnType("tinyint(1)"); + + b.HasKey("SlotId"); + + b.HasIndex("CreatorId"); + + b.HasIndex("LocationId"); + + b.ToTable("Slots"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.VisitedLevel", b => + { + b.Property("VisitedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("PlaysLBP1") + .HasColumnType("int"); + + b.Property("PlaysLBP2") + .HasColumnType("int"); + + b.Property("PlaysLBP3") + .HasColumnType("int"); + + b.Property("PlaysLBPVita") + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("VisitedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("VisitedLevels"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Photo", b => + { + b.Property("PhotoId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatorId") + .HasColumnType("int"); + + b.Property("LargeHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("MediumHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PhotoSubjectCollection") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PlanHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SmallHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("PhotoId"); + + b.HasIndex("CreatorId"); + + b.ToTable("Photos"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.PhotoSubject", b => + { + b.Property("PhotoSubjectId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Bounds") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("PhotoSubjectId"); + + b.HasIndex("UserId"); + + b.ToTable("PhotoSubjects"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Message") + .HasColumnType("longtext"); + + b.Property("PosterUserId") + .HasColumnType("int"); + + b.Property("TargetUserId") + .HasColumnType("int"); + + b.Property("ThumbsDown") + .HasColumnType("int"); + + b.Property("ThumbsUp") + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("CommentId"); + + b.HasIndex("PosterUserId"); + + b.HasIndex("TargetUserId"); + + b.ToTable("Comments"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.LastMatch", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("GameVersion") + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("UserId"); + + b.ToTable("LastMatches"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Location", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("X") + .HasColumnType("int"); + + b.Property("Y") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Score", b => + { + b.Property("ScoreId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("PlayerIdCollection") + .HasColumnType("longtext"); + + b.Property("Points") + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("ScoreId"); + + b.HasIndex("SlotId"); + + b.ToTable("Scores"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Biography") + .HasColumnType("longtext"); + + b.Property("Game") + .HasColumnType("int"); + + b.Property("IconHash") + .HasColumnType("longtext"); + + b.Property("IsAdmin") + .HasColumnType("tinyint(1)"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("Password") + .HasColumnType("longtext"); + + b.Property("Pins") + .HasColumnType("longtext"); + + b.Property("PlanetHash") + .HasColumnType("longtext"); + + b.Property("Username") + .HasColumnType("longtext"); + + b.HasKey("UserId"); + + b.HasIndex("LocationId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.WebToken", b => + { + b.Property("TokenId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("UserToken") + .HasColumnType("longtext"); + + b.HasKey("TokenId"); + + b.ToTable("WebTokens"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.AuthenticationAttempt", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.GameToken", "GameToken") + .WithMany() + .HasForeignKey("GameTokenId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GameToken"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "HeartedUser") + .WithMany() + .HasForeignKey("HeartedUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HeartedUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.RatedLevel", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Creator") + .WithMany() + .HasForeignKey("CreatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Location"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.VisitedLevel", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Photo", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Creator") + .WithMany() + .HasForeignKey("CreatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.PhotoSubject", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Poster") + .WithMany() + .HasForeignKey("PosterUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Target") + .WithMany() + .HasForeignKey("TargetUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Poster"); + + b.Navigation("Target"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Score", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ProjectLighthouse/Migrations/20211125052035_AddGameVersionToLastMatch.cs b/ProjectLighthouse/Migrations/20211125052035_AddGameVersionToLastMatch.cs new file mode 100644 index 00000000..23c6e128 --- /dev/null +++ b/ProjectLighthouse/Migrations/20211125052035_AddGameVersionToLastMatch.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + public partial class AddGameVersionToLastMatch : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "GameVersion", + table: "LastMatches", + type: "int", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "GameVersion", + table: "LastMatches"); + } + } +} diff --git a/ProjectLighthouse/Migrations/20211127201738_AddPasswordResetRequiredToUser.Designer.cs b/ProjectLighthouse/Migrations/20211127201738_AddPasswordResetRequiredToUser.Designer.cs new file mode 100644 index 00000000..1c8e0d20 --- /dev/null +++ b/ProjectLighthouse/Migrations/20211127201738_AddPasswordResetRequiredToUser.Designer.cs @@ -0,0 +1,719 @@ +// +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(Database))] + [Migration("20211127201738_AddPasswordResetRequiredToUser")] + partial class AddPasswordResetRequiredToUser + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.AuthenticationAttempt", b => + { + b.Property("AuthenticationAttemptId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("GameTokenId") + .HasColumnType("int"); + + b.Property("IPAddress") + .HasColumnType("longtext"); + + b.Property("Platform") + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("AuthenticationAttemptId"); + + b.HasIndex("GameTokenId"); + + b.ToTable("AuthenticationAttempts"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.GameToken", b => + { + b.Property("TokenId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("GameVersion") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("UserLocation") + .HasColumnType("longtext"); + + b.Property("UserToken") + .HasColumnType("longtext"); + + b.HasKey("TokenId"); + + b.ToTable("GameTokens"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b => + { + b.Property("HeartedProfileId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("HeartedUserId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("HeartedProfileId"); + + b.HasIndex("HeartedUserId"); + + b.HasIndex("UserId"); + + b.ToTable("HeartedProfiles"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b => + { + b.Property("HeartedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("HeartedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("HeartedLevels"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b => + { + b.Property("QueuedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("QueuedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("QueuedLevels"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.RatedLevel", b => + { + b.Property("RatedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Rating") + .HasColumnType("int"); + + b.Property("RatingLBP1") + .HasColumnType("double"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("RatedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("RatedLevels"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b => + { + b.Property("SlotId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AuthorLabels") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("BackgroundHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatorId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FirstUploaded") + .HasColumnType("bigint"); + + b.Property("GameVersion") + .HasColumnType("int"); + + b.Property("IconHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InitiallyLocked") + .HasColumnType("tinyint(1)"); + + b.Property("LastUpdated") + .HasColumnType("bigint"); + + b.Property("Lbp1Only") + .HasColumnType("tinyint(1)"); + + b.Property("LevelType") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("MaximumPlayers") + .HasColumnType("int"); + + b.Property("MinimumPlayers") + .HasColumnType("int"); + + b.Property("MoveRequired") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PlaysLBP1") + .HasColumnType("int"); + + b.Property("PlaysLBP1Complete") + .HasColumnType("int"); + + b.Property("PlaysLBP1Unique") + .HasColumnType("int"); + + b.Property("PlaysLBP2") + .HasColumnType("int"); + + b.Property("PlaysLBP2Complete") + .HasColumnType("int"); + + b.Property("PlaysLBP2Unique") + .HasColumnType("int"); + + b.Property("PlaysLBP3") + .HasColumnType("int"); + + b.Property("PlaysLBP3Complete") + .HasColumnType("int"); + + b.Property("PlaysLBP3Unique") + .HasColumnType("int"); + + b.Property("PlaysLBPVita") + .HasColumnType("int"); + + b.Property("PlaysLBPVitaComplete") + .HasColumnType("int"); + + b.Property("PlaysLBPVitaUnique") + .HasColumnType("int"); + + b.Property("ResourceCollection") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RootLevel") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Shareable") + .HasColumnType("int"); + + b.Property("SubLevel") + .HasColumnType("tinyint(1)"); + + b.Property("TeamPick") + .HasColumnType("tinyint(1)"); + + b.HasKey("SlotId"); + + b.HasIndex("CreatorId"); + + b.HasIndex("LocationId"); + + b.ToTable("Slots"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.VisitedLevel", b => + { + b.Property("VisitedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("PlaysLBP1") + .HasColumnType("int"); + + b.Property("PlaysLBP2") + .HasColumnType("int"); + + b.Property("PlaysLBP3") + .HasColumnType("int"); + + b.Property("PlaysLBPVita") + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("VisitedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("VisitedLevels"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Photo", b => + { + b.Property("PhotoId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatorId") + .HasColumnType("int"); + + b.Property("LargeHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("MediumHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PhotoSubjectCollection") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PlanHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SmallHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("PhotoId"); + + b.HasIndex("CreatorId"); + + b.ToTable("Photos"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.PhotoSubject", b => + { + b.Property("PhotoSubjectId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Bounds") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("PhotoSubjectId"); + + b.HasIndex("UserId"); + + b.ToTable("PhotoSubjects"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Message") + .HasColumnType("longtext"); + + b.Property("PosterUserId") + .HasColumnType("int"); + + b.Property("TargetUserId") + .HasColumnType("int"); + + b.Property("ThumbsDown") + .HasColumnType("int"); + + b.Property("ThumbsUp") + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("CommentId"); + + b.HasIndex("PosterUserId"); + + b.HasIndex("TargetUserId"); + + b.ToTable("Comments"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.LastMatch", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("GameVersion") + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("UserId"); + + b.ToTable("LastMatches"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Location", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("X") + .HasColumnType("int"); + + b.Property("Y") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Score", b => + { + b.Property("ScoreId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("PlayerIdCollection") + .HasColumnType("longtext"); + + b.Property("Points") + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("ScoreId"); + + b.HasIndex("SlotId"); + + b.ToTable("Scores"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Biography") + .HasColumnType("longtext"); + + b.Property("Game") + .HasColumnType("int"); + + b.Property("IconHash") + .HasColumnType("longtext"); + + b.Property("IsAdmin") + .HasColumnType("tinyint(1)"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("Password") + .HasColumnType("longtext"); + + b.Property("PasswordResetRequired") + .HasColumnType("tinyint(1)"); + + b.Property("Pins") + .HasColumnType("longtext"); + + b.Property("PlanetHash") + .HasColumnType("longtext"); + + b.Property("Username") + .HasColumnType("longtext"); + + b.HasKey("UserId"); + + b.HasIndex("LocationId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.WebToken", b => + { + b.Property("TokenId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("UserToken") + .HasColumnType("longtext"); + + b.HasKey("TokenId"); + + b.ToTable("WebTokens"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.AuthenticationAttempt", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.GameToken", "GameToken") + .WithMany() + .HasForeignKey("GameTokenId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GameToken"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "HeartedUser") + .WithMany() + .HasForeignKey("HeartedUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HeartedUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.RatedLevel", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Creator") + .WithMany() + .HasForeignKey("CreatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Location"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.VisitedLevel", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Photo", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Creator") + .WithMany() + .HasForeignKey("CreatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.PhotoSubject", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Poster") + .WithMany() + .HasForeignKey("PosterUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Target") + .WithMany() + .HasForeignKey("TargetUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Poster"); + + b.Navigation("Target"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Score", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ProjectLighthouse/Migrations/20211127201738_AddPasswordResetRequiredToUser.cs b/ProjectLighthouse/Migrations/20211127201738_AddPasswordResetRequiredToUser.cs new file mode 100644 index 00000000..0fd106d6 --- /dev/null +++ b/ProjectLighthouse/Migrations/20211127201738_AddPasswordResetRequiredToUser.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + public partial class AddPasswordResetRequiredToUser : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "PasswordResetRequired", + table: "Users", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "PasswordResetRequired", + table: "Users"); + } + } +} diff --git a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs index af460ee2..0e83b95a 100644 --- a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs +++ b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs @@ -411,6 +411,9 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + b.Property("GameVersion") + .HasColumnType("int"); + b.Property("Timestamp") .HasColumnType("bigint"); @@ -539,12 +542,18 @@ namespace ProjectLighthouse.Migrations b.Property("IconHash") .HasColumnType("longtext"); + b.Property("IsAdmin") + .HasColumnType("tinyint(1)"); + b.Property("LocationId") .HasColumnType("int"); b.Property("Password") .HasColumnType("longtext"); + b.Property("PasswordResetRequired") + .HasColumnType("tinyint(1)"); + b.Property("Pins") .HasColumnType("longtext"); diff --git a/ProjectLighthouse/Pages/AdminPanelPage.cshtml b/ProjectLighthouse/Pages/AdminPanelPage.cshtml new file mode 100644 index 00000000..f9c92c5c --- /dev/null +++ b/ProjectLighthouse/Pages/AdminPanelPage.cshtml @@ -0,0 +1,55 @@ +@page "/admin" +@using LBPUnion.ProjectLighthouse.Helpers +@using LBPUnion.ProjectLighthouse.Maintenance +@model LBPUnion.ProjectLighthouse.Pages.AdminPanelPage + +@{ + Layout = "Layouts/BaseLayout"; +} + +

Admin Panel

+ +

Commands

+
+ @foreach (ICommand command in MaintenanceHelper.Commands) + { +
+
+

@command.Name()

+
+
+ +


+ + +
+
+
+ } +
+

Maintenance Jobs

+

+ Warning: Interrupting Lighthouse during maintenance may leave the database in an unclean state. +

+ +
+ @foreach (IMaintenanceJob job in MaintenanceHelper.MaintenanceJobs) + { +
+
+

@job.Name()

+

@job.Description()

+
+ + +
+
+
+ } +
\ No newline at end of file diff --git a/ProjectLighthouse/Pages/AdminPanelPage.cshtml.cs b/ProjectLighthouse/Pages/AdminPanelPage.cshtml.cs new file mode 100644 index 00000000..ab4dbe61 --- /dev/null +++ b/ProjectLighthouse/Pages/AdminPanelPage.cshtml.cs @@ -0,0 +1,43 @@ +#nullable enable +using System.Collections.Generic; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Maintenance; +using LBPUnion.ProjectLighthouse.Pages.Layouts; +using LBPUnion.ProjectLighthouse.Types; +using Microsoft.AspNetCore.Mvc; + +namespace LBPUnion.ProjectLighthouse.Pages +{ + public class AdminPanelPage : BaseLayout + { + public AdminPanelPage(Database database) : base(database) + {} + + public List Commands = MaintenanceHelper.Commands; + + public async Task OnGet([FromQuery] string? args, [FromQuery] string? command, [FromQuery] string? maintenanceJob) + { + User? user = this.Database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("~/login"); + if (!user.IsAdmin) return this.NotFound(); + + if (!string.IsNullOrEmpty(command)) + { + args ??= ""; + args = command + " " + args; + string[] split = args.Split(" "); + await MaintenanceHelper.RunCommand(split); + return this.Redirect("~/admin"); + } + + if (!string.IsNullOrEmpty(maintenanceJob)) + { + await MaintenanceHelper.RunMaintenanceJob(maintenanceJob); + return this.Redirect("~/admin"); + } + + return this.Page(); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Pages/LandingPage.cshtml b/ProjectLighthouse/Pages/LandingPage.cshtml index f320a553..312f9d1d 100644 --- a/ProjectLighthouse/Pages/LandingPage.cshtml +++ b/ProjectLighthouse/Pages/LandingPage.cshtml @@ -1,4 +1,5 @@ @page "/" +@using LBPUnion.ProjectLighthouse.Types @model LBPUnion.ProjectLighthouse.Pages.LandingPage @{ @@ -11,15 +12,23 @@

You are currently logged in as @Model.User.Username.

} -@if (Model.PlayersOnline == 1) +@if (Model.PlayersOnlineCount == 1) { -

There is 1 user currently online.

+

There is 1 user currently online:

+ @foreach (User user in Model.PlayersOnline) + { + @user.Username + } } -else if (Model.PlayersOnline == 0) +else if (Model.PlayersOnlineCount == 0) {

There are no users online. Why not hop on?

} else { -

There are currently @Model.PlayersOnline users online.

+

There are currently @Model.PlayersOnlineCount users online:

+ @foreach (User user in Model.PlayersOnline) + { + @user.Username + } } \ No newline at end of file diff --git a/ProjectLighthouse/Pages/LandingPage.cshtml.cs b/ProjectLighthouse/Pages/LandingPage.cshtml.cs index 1edb223a..b02d3897 100644 --- a/ProjectLighthouse/Pages/LandingPage.cshtml.cs +++ b/ProjectLighthouse/Pages/LandingPage.cshtml.cs @@ -1,9 +1,13 @@ #nullable enable +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Pages.Layouts; +using LBPUnion.ProjectLighthouse.Types; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; namespace LBPUnion.ProjectLighthouse.Pages { @@ -12,12 +16,20 @@ namespace LBPUnion.ProjectLighthouse.Pages public LandingPage(Database database) : base(database) {} - public int PlayersOnline; + public int PlayersOnlineCount; + public List PlayersOnline; [UsedImplicitly] public async Task OnGet() { - this.PlayersOnline = await StatisticsHelper.RecentMatches(); + User? user = this.Database.UserFromWebRequest(this.Request); + if (user != null && user.PasswordResetRequired) return this.Redirect("~/passwordResetRequired"); + + this.PlayersOnlineCount = await StatisticsHelper.RecentMatches(); + + List userIds = await this.Database.LastMatches.Where(l => TimestampHelper.Timestamp - l.Timestamp < 300).Select(l => l.UserId).ToListAsync(); + + this.PlayersOnline = await this.Database.Users.Where(u => userIds.Contains(u.UserId)).ToListAsync(); return this.Page(); } } diff --git a/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml b/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml index ca46ebd3..2e5ee465 100644 --- a/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml +++ b/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml @@ -6,8 +6,11 @@ @{ if (Model!.User == null) { - Model.NavigationItems.Add(new PageNavigationItem("Log in", "/login", "user alternate")); - Model.NavigationItems.Add(new PageNavigationItem("Register", "/register", "user alternate edit")); + Model.NavigationItemsRight.Add(new PageNavigationItem("Log in", "/login", "user alternate")); + if (ServerSettings.Instance.RegistrationEnabled) + { + Model.NavigationItemsRight.Add(new PageNavigationItem("Register", "/register", "user alternate edit")); + } } else { @@ -15,7 +18,13 @@ { Model.NavigationItems.Add(new PageNavigationItem("Authentication", "/authentication", "key")); } - Model.NavigationItems.Add(new PageNavigationItem("Log out", "/logout", "user alternate slash")); // should always be last + 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 } } @@ -27,38 +36,70 @@ -
-
- -
-
- @RenderBody() +
+
+
+ +

Page generated by @VersionHelper.FullVersion.

+ @if (VersionHelper.IsDirty) + { +

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.

+ } +
+
+
-
-
-
- -

Page generated by @GitVersionHelper.FullVersion.

- @if (GitVersionHelper.IsDirty) - { -

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.

- } -
-
-
\ No newline at end of file diff --git a/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml.cs b/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml.cs index 3d3fbcc2..e77b3f11 100644 --- a/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml.cs +++ b/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml.cs @@ -29,7 +29,11 @@ namespace LBPUnion.ProjectLighthouse.Pages.Layouts { new PageNavigationItem("Home", "/", "home"), new PageNavigationItem("Photos", "/photos/0", "camera"), + new PageNavigationItem("Levels", "/slots/0", "certificate"), }; + public readonly List NavigationItemsRight = new() + {}; + } } \ No newline at end of file diff --git a/ProjectLighthouse/Pages/LoginForm.cshtml b/ProjectLighthouse/Pages/LoginForm.cshtml index 83d3dbf0..b5b4b82d 100644 --- a/ProjectLighthouse/Pages/LoginForm.cshtml +++ b/ProjectLighthouse/Pages/LoginForm.cshtml @@ -20,14 +20,14 @@

Log in

- + -

- -
- -


-
+
+ + +



+ +
\ No newline at end of file diff --git a/ProjectLighthouse/Pages/LoginForm.cshtml.cs b/ProjectLighthouse/Pages/LoginForm.cshtml.cs index 674eb803..332f1448 100644 --- a/ProjectLighthouse/Pages/LoginForm.cshtml.cs +++ b/ProjectLighthouse/Pages/LoginForm.cshtml.cs @@ -39,6 +39,8 @@ namespace LBPUnion.ProjectLighthouse.Pages this.Response.Cookies.Append("LighthouseToken", webToken.UserToken); + if (user.PasswordResetRequired) return this.Redirect("~/passwordResetRequired"); + return this.RedirectToPage(nameof(LandingPage)); } diff --git a/ProjectLighthouse/Pages/PasswordResetPage.cshtml b/ProjectLighthouse/Pages/PasswordResetPage.cshtml new file mode 100644 index 00000000..e069274e --- /dev/null +++ b/ProjectLighthouse/Pages/PasswordResetPage.cshtml @@ -0,0 +1,36 @@ +@page "/passwordReset" +@model LBPUnion.ProjectLighthouse.Pages.PasswordResetPage + +@{ + Layout = "Layouts/BaseLayout"; +} + + + + + +

Password Reset

+ +
+
+ + +


+ +
+ + +



+ +
+
\ No newline at end of file diff --git a/ProjectLighthouse/Pages/PasswordResetPage.cshtml.cs b/ProjectLighthouse/Pages/PasswordResetPage.cshtml.cs new file mode 100644 index 00000000..e0c473d3 --- /dev/null +++ b/ProjectLighthouse/Pages/PasswordResetPage.cshtml.cs @@ -0,0 +1,38 @@ +#nullable enable +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Pages.Layouts; +using LBPUnion.ProjectLighthouse.Types; +using Microsoft.AspNetCore.Mvc; + +namespace LBPUnion.ProjectLighthouse.Pages +{ + public class PasswordResetPage : BaseLayout + { + public PasswordResetPage(Database database) : base(database) + {} + + public bool WasResetRequest { get; private set; } + public async Task OnGet([FromQuery] string password, [FromQuery] string confirmPassword) + { + User? user = this.Database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("~/login"); + + this.WasResetRequest = !string.IsNullOrEmpty(password) && !string.IsNullOrEmpty(confirmPassword); + + if (this.WasResetRequest) + { + if (password != confirmPassword) return this.BadRequest(); + + user.Password = HashHelper.BCryptHash(password); + user.PasswordResetRequired = false; + + await this.Database.SaveChangesAsync(); + + return this.Redirect("~/"); + } + + return this.Page(); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Pages/PasswordResetRequiredPage.cshtml b/ProjectLighthouse/Pages/PasswordResetRequiredPage.cshtml new file mode 100644 index 00000000..5dc62ef7 --- /dev/null +++ b/ProjectLighthouse/Pages/PasswordResetRequiredPage.cshtml @@ -0,0 +1,13 @@ +@page "/passwordResetRequired" +@model LBPUnion.ProjectLighthouse.Pages.PasswordResetRequiredPage + +@{ + Layout = "Layouts/BaseLayout"; +} + +

Password Reset Required

+

An admin has deemed it necessary that you reset your password. Please do so.

+ + +
Reset Password
+
\ No newline at end of file diff --git a/ProjectLighthouse/Pages/PasswordResetRequiredPage.cshtml.cs b/ProjectLighthouse/Pages/PasswordResetRequiredPage.cshtml.cs new file mode 100644 index 00000000..55c07595 --- /dev/null +++ b/ProjectLighthouse/Pages/PasswordResetRequiredPage.cshtml.cs @@ -0,0 +1,26 @@ +#nullable enable +using System.Threading.Tasks; +using JetBrains.Annotations; +using LBPUnion.ProjectLighthouse.Pages.Layouts; +using LBPUnion.ProjectLighthouse.Types; +using Microsoft.AspNetCore.Mvc; + +namespace LBPUnion.ProjectLighthouse.Pages +{ + public class PasswordResetRequiredPage : BaseLayout + { + public PasswordResetRequiredPage([NotNull] Database database) : base(database) + {} + + public bool WasResetRequest { get; private set; } + + public async Task 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(); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Pages/PhotosPage.cshtml b/ProjectLighthouse/Pages/PhotosPage.cshtml index 98f4e12d..9cd07040 100644 --- a/ProjectLighthouse/Pages/PhotosPage.cshtml +++ b/ProjectLighthouse/Pages/PhotosPage.cshtml @@ -19,7 +19,7 @@ Taken by - @photo.Creator.Username + @photo.Creator.Username

@@ -29,7 +29,7 @@

@foreach (PhotoSubject subject in photo.Subjects) { - @subject.User.Username + @subject.User.Username } } @@ -38,6 +38,4 @@ { Previous Page } -Next Page - -
@* solves quirk with footer *@ \ No newline at end of file +Next Page \ No newline at end of file diff --git a/ProjectLighthouse/Pages/PhotosPage.cshtml.cs b/ProjectLighthouse/Pages/PhotosPage.cshtml.cs index f7be042a..e9e94007 100644 --- a/ProjectLighthouse/Pages/PhotosPage.cshtml.cs +++ b/ProjectLighthouse/Pages/PhotosPage.cshtml.cs @@ -5,6 +5,7 @@ using JetBrains.Annotations; using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Pages.Layouts; using LBPUnion.ProjectLighthouse.Types; +using LBPUnion.ProjectLighthouse.Types.Settings; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -23,7 +24,6 @@ namespace LBPUnion.ProjectLighthouse.Pages public async Task OnGet([FromRoute] int pageNumber) { - const int pageSize = 20; this.PhotoCount = await StatisticsHelper.PhotoCount(); this.PageNumber = pageNumber; @@ -31,8 +31,8 @@ namespace LBPUnion.ProjectLighthouse.Pages this.Photos = await this.Database.Photos.Include (p => p.Creator) .OrderByDescending(p => p.Timestamp) - .Skip(pageNumber * pageSize) - .Take(pageSize) + .Skip(pageNumber * ServerStatics.PageSize) + .Take(ServerStatics.PageSize) .ToListAsync(); return this.Page(); diff --git a/ProjectLighthouse/Pages/RegisterForm.cshtml b/ProjectLighthouse/Pages/RegisterForm.cshtml index b5385f17..175e6741 100644 --- a/ProjectLighthouse/Pages/RegisterForm.cshtml +++ b/ProjectLighthouse/Pages/RegisterForm.cshtml @@ -19,20 +19,23 @@ } -

Register

+
- + -

-
- - -

-
- -


-
+ +
+ + +


+ +
+ + +



+ +
\ No newline at end of file diff --git a/ProjectLighthouse/Pages/RegisterForm.cshtml.cs b/ProjectLighthouse/Pages/RegisterForm.cshtml.cs index 5e5347ca..91a22719 100644 --- a/ProjectLighthouse/Pages/RegisterForm.cshtml.cs +++ b/ProjectLighthouse/Pages/RegisterForm.cshtml.cs @@ -4,6 +4,7 @@ using JetBrains.Annotations; using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Pages.Layouts; using LBPUnion.ProjectLighthouse.Types; +using LBPUnion.ProjectLighthouse.Types.Settings; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -20,6 +21,8 @@ namespace LBPUnion.ProjectLighthouse.Pages [SuppressMessage("ReSharper", "SpecifyStringComparison")] public async Task OnGet([FromQuery] string username, [FromQuery] string password, [FromQuery] string confirmPassword) { + if (!ServerSettings.Instance.RegistrationEnabled) return this.NotFound(); + this.WasRegisterRequest = !string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password) && !string.IsNullOrEmpty(confirmPassword); if (this.WasRegisterRequest) diff --git a/ProjectLighthouse/Pages/SlotsPage.cshtml b/ProjectLighthouse/Pages/SlotsPage.cshtml new file mode 100644 index 00000000..3c2cef23 --- /dev/null +++ b/ProjectLighthouse/Pages/SlotsPage.cshtml @@ -0,0 +1,86 @@ +@page "/slots/{pageNumber:int}" +@using LBPUnion.ProjectLighthouse.Types +@using LBPUnion.ProjectLighthouse.Types.Levels +@using Microsoft.EntityFrameworkCore +@model LBPUnion.ProjectLighthouse.Pages.SlotsPage + +@{ + Layout = "Layouts/BaseLayout"; +} + +

Levels

+

There are @Model.SlotCount total levels!

+ +@foreach (Slot slot in Model.Slots) +{ + string slotName = string.IsNullOrEmpty(slot.Name) ? "Unnamed Level" : slot.Name; + + bool isQueued = false; + bool isHearted = false; + + if (Model.User != null) + { + isQueued = await Model.Database.QueuedLevels.FirstOrDefaultAsync(h => h.SlotId == slot.SlotId && h.UserId == Model.User.UserId) != null; + + isHearted = await Model.Database.HeartedLevels.FirstOrDefaultAsync(h => h.SlotId == slot.SlotId && h.UserId == Model.User.UserId) != null; + } + +
+
+
+

@slotName

+
+ @slot.Hearts + @slot.Plays + @slot.Thumbsup + @slot.Thumbsdown + + @if (slot.GameVersion == GameVersion.LittleBigPlanet1) + { + + @slot.RatingLBP1 + } +
+

+ Created by @slot.Creator?.Username +

+
+
+ @if (Model.User != null) + { + if (isHearted) + { + + + + } + else + { + + + + } + + if (isQueued) + { + + + + } + else + { + + + + } + } +
+
+
+} + +@if (Model.PageNumber != 0) +{ + Previous Page +} +Next Page \ No newline at end of file diff --git a/ProjectLighthouse/Pages/SlotsPage.cshtml.cs b/ProjectLighthouse/Pages/SlotsPage.cshtml.cs new file mode 100644 index 00000000..dc7c6248 --- /dev/null +++ b/ProjectLighthouse/Pages/SlotsPage.cshtml.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using JetBrains.Annotations; +using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Pages.Layouts; +using LBPUnion.ProjectLighthouse.Types.Levels; +using LBPUnion.ProjectLighthouse.Types.Settings; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Pages +{ + public class SlotsPage : BaseLayout + { + public SlotsPage([NotNull] Database database) : base(database) + {} + + public int SlotCount; + + public List Slots; + + public int PageNumber; + + public async Task OnGet([FromRoute] int pageNumber) + { + this.SlotCount = await StatisticsHelper.SlotCount(); + + this.PageNumber = pageNumber; + + this.Slots = await this.Database.Slots.Include + (p => p.Creator) + .OrderByDescending(p => p.FirstUploaded) + .Skip(pageNumber * ServerStatics.PageSize) + .Take(ServerStatics.PageSize) + .ToListAsync(); + + return this.Page(); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Pages/UserPage.cshtml b/ProjectLighthouse/Pages/UserPage.cshtml new file mode 100644 index 00000000..d9e127e3 --- /dev/null +++ b/ProjectLighthouse/Pages/UserPage.cshtml @@ -0,0 +1,108 @@ +@page "/user/{userId:int}" +@using LBPUnion.ProjectLighthouse.Types +@using LBPUnion.ProjectLighthouse.Types.Profiles +@using LBPUnion.ProjectLighthouse.Types.Settings +@model LBPUnion.ProjectLighthouse.Pages.UserPage + +@{ + Layout = "Layouts/BaseLayout"; +} + +
+
+

@Model.ProfileUser!.Username's user page

+

+ @Model.ProfileUser.Status +

+
+ @Model.ProfileUser.Hearts + @Model.ProfileUser.Comments + @Model.ProfileUser.UsedSlots / @ServerSettings.Instance.EntitledSlots + @Model.ProfileUser.PhotosByMe +
+
+
+
+ @if (Model.ProfileUser != Model.User && Model.User != null) + { + if (!Model.IsProfileUserHearted) + { + + + Heart + + } + else + { + + + Unheart + + } + } + @if (Model.ProfileUser == Model.User) + { + + + Reset Password + + } +
+
+
+

Biography

+

@Model.ProfileUser.Biography

+
+
+
+
+

Recent Activity

+

Coming soon!

+
+
+
+ + +@if (Model.Photos != null && Model.Photos.Count != 0) +{ +
+

Most recent photos

+ +
+ @foreach (Photo photo in Model.Photos) + { +
+ +
+ +

+ Photo contains @photo.Subjects.Count @(photo.Subjects.Count == 1 ? "person" : "people"): +

+ @foreach (PhotoSubject subject in photo.Subjects) + { + @subject.User.Username + } +
+ } +
+
+} + +@if (Model.ProfileUser.Comments > 0) +{ +
+

Comments

+ @foreach (Comment comment in Model.Comments!) + { + DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(comment.Timestamp / 1000); +
+ @comment.Poster.Username: + @comment.Message +

+ @timestamp.ToString("MM/dd/yyyy @ h:mm tt") UTC +

+
+
+ } +
+} \ No newline at end of file diff --git a/ProjectLighthouse/Pages/UserPage.cshtml.cs b/ProjectLighthouse/Pages/UserPage.cshtml.cs new file mode 100644 index 00000000..cfde4ff7 --- /dev/null +++ b/ProjectLighthouse/Pages/UserPage.cshtml.cs @@ -0,0 +1,50 @@ +#nullable enable +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Pages.Layouts; +using LBPUnion.ProjectLighthouse.Types; +using LBPUnion.ProjectLighthouse.Types.Profiles; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Pages +{ + public class UserPage : BaseLayout + { + public UserPage(Database database) : base(database) + {} + + public User? ProfileUser; + + public List? Photos; + public List? Comments; + + public bool IsProfileUserHearted; + + public async Task OnGet([FromRoute] int userId) + { + this.ProfileUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == userId); + if (this.ProfileUser == null) return this.NotFound(); + + this.Photos = await this.Database.Photos.OrderByDescending(p => p.Timestamp).Where(p => p.CreatorId == userId).Take(5).ToListAsync(); + this.Comments = await this.Database.Comments.Include + (p => p.Poster) + .Include(p => p.Target) + .OrderByDescending(p => p.Timestamp) + .Where(p => p.TargetUserId == userId) + .Take(50) + .ToListAsync(); + + if (this.User != null) + { + + this.IsProfileUserHearted = (await this.Database.HeartedProfiles.FirstOrDefaultAsync + (u => u.UserId == this.User.UserId && u.HeartedUserId == this.ProfileUser.UserId)) != + null; + } + + return this.Page(); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Program.cs b/ProjectLighthouse/Program.cs index 65c6ffac..1ef82df1 100644 --- a/ProjectLighthouse/Program.cs +++ b/ProjectLighthouse/Program.cs @@ -24,12 +24,13 @@ namespace LBPUnion.ProjectLighthouse // Setup logging Logger.StartLogging(); + Logger.UpdateRate /= 2; LoggerLine.LogFormat = "[{0}] {1}"; Logger.AddLogger(new ConsoleLogger()); Logger.AddLogger(new LighthouseFileLogger()); Logger.Log("Welcome to Project Lighthouse!", LoggerLevelStartup.Instance); - Logger.Log($"Running {GitVersionHelper.FullVersion}", LoggerLevelStartup.Instance); + Logger.Log($"Running {VersionHelper.FullVersion}", LoggerLevelStartup.Instance); // This loads the config, see ServerSettings.cs for more information Logger.Log("Loaded config file version " + ServerSettings.Instance.ConfigVersion, LoggerLevelStartup.Instance); @@ -47,9 +48,7 @@ namespace LBPUnion.ProjectLighthouse if (ServerSettings.Instance.InfluxEnabled) { Logger.Log("Influx logging is enabled. Starting influx logging...", LoggerLevelStartup.Instance); - #pragma warning disable CS4014 - InfluxHelper.StartLogging(); - #pragma warning restore CS4014 + InfluxHelper.StartLogging().Wait(); if (ServerSettings.Instance.InfluxLoggingEnabled) Logger.AddLogger(new InfluxLogger()); } @@ -64,6 +63,12 @@ namespace LBPUnion.ProjectLighthouse Logger.Log("You can do so by running any dotnet command with the flag: \"-c Release\". ", LoggerLevelStartup.Instance); #endif + if (args.Length != 0) + { + MaintenanceHelper.RunCommand(args).Wait(); + return; + } + stopwatch.Stop(); Logger.Log($"Ready! Startup took {stopwatch.ElapsedMilliseconds}ms. Passing off control to ASP.NET...", LoggerLevelStartup.Instance); @@ -75,7 +80,7 @@ namespace LBPUnion.ProjectLighthouse Stopwatch stopwatch = new(); stopwatch.Start(); - database.Database.Migrate(); + database.Database.MigrateAsync().Wait(); stopwatch.Stop(); Logger.Log($"Migration took {stopwatch.ElapsedMilliseconds}ms.", LoggerLevelDatabase.Instance); diff --git a/ProjectLighthouse/StaticFiles/css/styles.css b/ProjectLighthouse/StaticFiles/css/styles.css index 2b1738bf..53cbfaa5 100644 --- a/ProjectLighthouse/StaticFiles/css/styles.css +++ b/ProjectLighthouse/StaticFiles/css/styles.css @@ -1,5 +1,18 @@ -footer.lighthouse-footer { - width: 100%; - bottom: 0; - position: fixed; +div.pageContainer { + display: flex; + flex-direction: column; + min-height: 100vh; } + +div.main { + flex: 1; +} + +div.statsUnderTitle > i { + margin-right: 2px; +} + +div.statsUnderTitle > span { + margin-right: 5px; +} + diff --git a/ProjectLighthouse/Types/Files/LbpFile.cs b/ProjectLighthouse/Types/Files/LbpFile.cs index b226276c..d0c98314 100644 --- a/ProjectLighthouse/Types/Files/LbpFile.cs +++ b/ProjectLighthouse/Types/Files/LbpFile.cs @@ -1,3 +1,5 @@ +#nullable enable +using System.IO; using LBPUnion.ProjectLighthouse.Helpers; namespace LBPUnion.ProjectLighthouse.Types.Files @@ -20,5 +22,15 @@ namespace LBPUnion.ProjectLighthouse.Types.Files this.Data = data; this.FileType = FileHelper.DetermineFileType(this.Data); } + + public static LbpFile? FromHash(string hash) + { + string path = FileHelper.GetResourcePath(hash); + if (!File.Exists(path)) return null; + + byte[] data = File.ReadAllBytes(path); + + return new LbpFile(data); + } } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/GameVersion.cs b/ProjectLighthouse/Types/GameVersion.cs index c0530d9c..2ad35019 100644 --- a/ProjectLighthouse/Types/GameVersion.cs +++ b/ProjectLighthouse/Types/GameVersion.cs @@ -9,4 +9,9 @@ namespace LBPUnion.ProjectLighthouse.Types LittleBigPlanetPSP = 4, Unknown = -1, } + + public static class GameVersionExtensions + { + public static string ToPrettyString(this GameVersion gameVersion) => gameVersion.ToString().Replace("LittleBigPlanet", "LittleBigPlanet "); + } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Profiles/LastMatch.cs b/ProjectLighthouse/Types/Profiles/LastMatch.cs index ec637c14..832430cd 100644 --- a/ProjectLighthouse/Types/Profiles/LastMatch.cs +++ b/ProjectLighthouse/Types/Profiles/LastMatch.cs @@ -8,5 +8,7 @@ namespace LBPUnion.ProjectLighthouse.Types.Profiles public int UserId { get; set; } public long Timestamp { get; set; } + + public GameVersion GameVersion { get; set; } = GameVersion.Unknown; } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Settings/ServerSettings.cs b/ProjectLighthouse/Types/Settings/ServerSettings.cs index b23a61dc..eabf41c4 100644 --- a/ProjectLighthouse/Types/Settings/ServerSettings.cs +++ b/ProjectLighthouse/Types/Settings/ServerSettings.cs @@ -63,13 +63,13 @@ namespace LBPUnion.ProjectLighthouse.Types.Settings } } + public const int CurrentConfigVersion = 10; // MUST BE INCREMENTED FOR EVERY CONFIG CHANGE! + #region Meta [NotNull] public static ServerSettings Instance; - public const int CurrentConfigVersion = 7; - [JsonPropertyName("ConfigVersionDoNotModifyOrYouWillBeSlapped")] public int ConfigVersion { get; set; } = CurrentConfigVersion; @@ -91,5 +91,16 @@ namespace LBPUnion.ProjectLighthouse.Types.Settings public string ExternalUrl { get; set; } = "http://localhost:10060"; public string ServerDigestKey { get; set; } public bool UseExternalAuth { get; set; } + + public bool CheckForUnsafeFiles { get; set; } = true; + + public bool RegistrationEnabled { get; set; } = true; + + /// + /// The maximum amount of slots allowed on users' earth + /// + public int EntitledSlots { get; set; } = 50; + + public int ListsQuota { get; set; } = 50; } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Settings/ServerStatics.cs b/ProjectLighthouse/Types/Settings/ServerStatics.cs index a6b29548..1bb8227c 100644 --- a/ProjectLighthouse/Types/Settings/ServerStatics.cs +++ b/ProjectLighthouse/Types/Settings/ServerStatics.cs @@ -8,13 +8,6 @@ namespace LBPUnion.ProjectLighthouse.Types.Settings { public static class ServerStatics { - /// - /// The maximum amount of slots allowed on users' earth - /// - public const int EntitledSlots = 50; - - public const int ListsQuota = 50; - public const string ServerName = "ProjectLighthouse"; public static bool DbConnected { @@ -32,5 +25,7 @@ namespace LBPUnion.ProjectLighthouse.Types.Settings } public static bool IsUnitTesting => AppDomain.CurrentDomain.GetAssemblies().Any(assembly => assembly.FullName.StartsWith("xunit")); + + public const int PageSize = 20; } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/User.cs b/ProjectLighthouse/Types/User.cs index 42f1a853..dcd8dd97 100644 --- a/ProjectLighthouse/Types/User.cs +++ b/ProjectLighthouse/Types/User.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations.Schema; using System.Diagnostics.CodeAnalysis; using System.Linq; +using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Types.Profiles; using LBPUnion.ProjectLighthouse.Types.Settings; @@ -100,13 +101,34 @@ namespace LBPUnion.ProjectLighthouse.Types } } + public bool IsAdmin { get; set; } = false; + + public bool PasswordResetRequired { get; set; } + + #nullable enable + [NotMapped] + public string Status { + get { + using Database database = new(); + LastMatch? lastMatch = database.LastMatches.Where + (l => l.UserId == this.UserId) + .FirstOrDefault(l => TimestampHelper.Timestamp - l.Timestamp < 300); + + if (lastMatch == null) return "Offline"; + + return "Currently online on " + lastMatch.GameVersion.ToPrettyString(); + } + } + #nullable disable + public string Serialize(GameVersion gameVersion = GameVersion.LittleBigPlanet1) { string user = LbpSerializer.TaggedStringElement("npHandle", this.Username, "icon", this.IconHash) + LbpSerializer.StringElement("game", this.Game) + this.SerializeSlots(gameVersion == GameVersion.LittleBigPlanetVita) + LbpSerializer.StringElement("lists", this.Lists) + - LbpSerializer.StringElement("lists_quota", ServerStatics.ListsQuota) + // technically not a part of the user but LBP expects it + LbpSerializer.StringElement + ("lists_quota", ServerSettings.Instance.ListsQuota) + // technically not a part of the user but LBP expects it LbpSerializer.StringElement("biography", this.Biography) + LbpSerializer.StringElement("reviewCount", this.Reviews) + LbpSerializer.StringElement("commentCount", this.Comments) + @@ -148,7 +170,7 @@ namespace LBPUnion.ProjectLighthouse.Types /// /// The number of slots remaining on the earth /// - public int FreeSlots => ServerStatics.EntitledSlots - this.UsedSlots; + public int FreeSlots => ServerSettings.Instance.EntitledSlots - this.UsedSlots; private static readonly string[] slotTypes = { @@ -178,12 +200,12 @@ namespace LBPUnion.ProjectLighthouse.Types slotTypesLocal = slotTypes; } - slots += LbpSerializer.StringElement("entitledSlots", ServerStatics.EntitledSlots); + slots += LbpSerializer.StringElement("entitledSlots", ServerSettings.Instance.EntitledSlots); slots += LbpSerializer.StringElement("freeSlots", this.FreeSlots); foreach (string slotType in slotTypesLocal) { - slots += LbpSerializer.StringElement(slotType + "EntitledSlots", ServerStatics.EntitledSlots); + slots += LbpSerializer.StringElement(slotType + "EntitledSlots", ServerSettings.Instance.EntitledSlots); // ReSharper disable once StringLiteralTypo slots += LbpSerializer.StringElement(slotType + slotType == "crossControl" ? "PurchsedSlots" : "PurchasedSlots", 0); slots += LbpSerializer.StringElement(slotType + "FreeSlots", this.FreeSlots); diff --git a/README.md b/README.md index f3ff44b4..d7f6ad58 100644 --- a/README.md +++ b/README.md @@ -80,11 +80,15 @@ Keep in mind while running database tests you need to have `LIGHTHOUSE_DB_CONNEC ## Compatibility across games and platforms -| Game | Console (PS3/Vita) | Emulator (RPCS3) | Next-Gen (PS4/PS5) | -|----------|-------------------------------------|------------------------------------------------|--------------------| -| LBP1 | Compatible | Incompatible, crashes on entering pod computer | N/A | -| LBP2 | Compatible | Compatible with patched RPCS3 | N/A | -| LBP3 | Somewhat compatible | Somewhat compatible with workaround | Incompatible | -| LBP Vita | Compatible | N/A | N/A | +| Game | Console (PS3/Vita/PSP) | Emulator (RPCS3/Vita3k/PPSSPP) | Next-Gen (PS4/PS5) | +|----------|---------------------------------------|----------------------------------------------------------|------------------------| +| LBP1 | Compatible | Incompatible, crashes on entering pod computer | N/A | +| LBP2 | Compatible | Compatible with patched RPCS3 | N/A | +| LBP3 | Somewhat compatible, frequent crashes | Somewhat compatible with patched RPCS3, frequent crashes | Incompatible | +| LBP Vita | Compatible | Incompatible, marked as "bootable" on Vita3k | N/A | +| LBP PSP | Potentially compatible | Incompatible, PSN not supported on PPSSPP | Potentially Compatible | -Project Lighthouse is still a heavy work in progress, so this is subject to change at any point. +While LBP Vita and LBP PSP can be supported, they are not properly seperated from the mainline games at this time. +We recommend you run seperate instances for these games to avoid problems. + +Project Lighthouse is still a heavy work in progress, so this chart is subject to change at any point.