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