Merge branch 'LBPUnion:main' into main

This commit is contained in:
LumaLivy 2021-11-29 17:06:37 -05:00 committed by GitHub
commit e809795ab3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
60 changed files with 3700 additions and 165 deletions

View file

@ -4,7 +4,7 @@
<data-source source="LOCAL" name="lighthouse@localhost" uuid="3bfb85d6-6cc2-427d-9288-06e05ab58c10">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-driver>com.mysql.cj.jdbc.NonRegisteringDriver</jdbc-driver>
<jdbc-url>jdbc:mysql://localhost:3306/lighthouse</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>

View file

@ -128,5 +128,6 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=UCJS/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=UCUS/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unfavourite/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unheart/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unpublish/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=yourthumb/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View file

@ -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();
}

View file

@ -88,6 +88,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers
}
lastMatch.Timestamp = TimestampHelper.Timestamp;
lastMatch.GameVersion = gameToken.GameVersion;
await this.database.SaveChangesAsync();

View file

@ -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")]

View file

@ -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);

View file

@ -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();
}

View file

@ -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<string, object>
{
{
"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<string, object>
{
{
"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<string, object>
{
{
"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<string, object>
{
{
"hint_start", pageStart + Math.Min(pageSize, ServerSettings.Instance.EntitledSlots)
},
{
"total", await StatisticsHelper.SlotCount()
},
}
)
);
}
}
}
}

View file

@ -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")]

View file

@ -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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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);
}
}
}

View file

@ -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<IActionResult> 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<IActionResult> 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);
}
}
}

View file

@ -40,7 +40,7 @@ namespace LBPUnion.ProjectLighthouse
public async Task<User> 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<User?> UserFromMMAuth(string authToken, bool allowUnapproved = false)

View file

@ -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

View file

@ -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));

View file

@ -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<ICommand> Commands { get; }
public static List<IMaintenanceJob> MaintenanceJobs { get; }
private static List<T> getListOfInterfaceObjects<T>() 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<ICommand>();
MaintenanceJobs = getListOfInterfaceObjects<IMaintenanceJob>();
}
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<ICommand> 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();
}
}
}

View file

@ -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
}
}

View file

@ -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() => "<username/userId>";
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.");
}
}
}

View file

@ -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() => "<username/userId> <sha256/plaintext>";
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.");
}
}
}

View file

@ -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() => "<username/userId>";
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}).");
}
}
}

View file

@ -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();
}
}

View file

@ -0,0 +1,13 @@
using System.Threading.Tasks;
namespace LBPUnion.ProjectLighthouse.Maintenance
{
public interface IMaintenanceJob
{
public Task Run();
public string Name();
public string Description();
}
}

View file

@ -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<string> 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();
}
}
}

View file

@ -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<int> usedLocationIds = new();
usedLocationIds.AddRange(this.database.Slots.Select(slot => slot.LocationId));
usedLocationIds.AddRange(this.database.Users.Select(user => user.LocationId));
IQueryable<Location> 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();
}
}
}

View file

@ -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.");
}
}
}

View file

@ -0,0 +1,713 @@
// <auto-generated />
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<int>("AuthenticationAttemptId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("GameTokenId")
.HasColumnType("int");
b.Property<string>("IPAddress")
.HasColumnType("longtext");
b.Property<int>("Platform")
.HasColumnType("int");
b.Property<long>("Timestamp")
.HasColumnType("bigint");
b.HasKey("AuthenticationAttemptId");
b.HasIndex("GameTokenId");
b.ToTable("AuthenticationAttempts");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.GameToken", b =>
{
b.Property<int>("TokenId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<bool>("Approved")
.HasColumnType("tinyint(1)");
b.Property<int>("GameVersion")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.Property<string>("UserLocation")
.HasColumnType("longtext");
b.Property<string>("UserToken")
.HasColumnType("longtext");
b.HasKey("TokenId");
b.ToTable("GameTokens");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b =>
{
b.Property<int>("HeartedProfileId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("HeartedUserId")
.HasColumnType("int");
b.Property<int>("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<int>("HeartedLevelId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("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<int>("QueuedLevelId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("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<int>("RatedLevelId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("Rating")
.HasColumnType("int");
b.Property<double>("RatingLBP1")
.HasColumnType("double");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("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<int>("SlotId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("AuthorLabels")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("BackgroundHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("CreatorId")
.HasColumnType("int");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.Property<long>("FirstUploaded")
.HasColumnType("bigint");
b.Property<int>("GameVersion")
.HasColumnType("int");
b.Property<string>("IconHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("InitiallyLocked")
.HasColumnType("tinyint(1)");
b.Property<long>("LastUpdated")
.HasColumnType("bigint");
b.Property<bool>("Lbp1Only")
.HasColumnType("tinyint(1)");
b.Property<string>("LevelType")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("LocationId")
.HasColumnType("int");
b.Property<int>("MaximumPlayers")
.HasColumnType("int");
b.Property<int>("MinimumPlayers")
.HasColumnType("int");
b.Property<bool>("MoveRequired")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("PlaysLBP1")
.HasColumnType("int");
b.Property<int>("PlaysLBP1Complete")
.HasColumnType("int");
b.Property<int>("PlaysLBP1Unique")
.HasColumnType("int");
b.Property<int>("PlaysLBP2")
.HasColumnType("int");
b.Property<int>("PlaysLBP2Complete")
.HasColumnType("int");
b.Property<int>("PlaysLBP2Unique")
.HasColumnType("int");
b.Property<int>("PlaysLBP3")
.HasColumnType("int");
b.Property<int>("PlaysLBP3Complete")
.HasColumnType("int");
b.Property<int>("PlaysLBP3Unique")
.HasColumnType("int");
b.Property<int>("PlaysLBPVita")
.HasColumnType("int");
b.Property<int>("PlaysLBPVitaComplete")
.HasColumnType("int");
b.Property<int>("PlaysLBPVitaUnique")
.HasColumnType("int");
b.Property<string>("ResourceCollection")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("RootLevel")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Shareable")
.HasColumnType("int");
b.Property<bool>("SubLevel")
.HasColumnType("tinyint(1)");
b.Property<bool>("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<int>("VisitedLevelId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("PlaysLBP1")
.HasColumnType("int");
b.Property<int>("PlaysLBP2")
.HasColumnType("int");
b.Property<int>("PlaysLBP3")
.HasColumnType("int");
b.Property<int>("PlaysLBPVita")
.HasColumnType("int");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("VisitedLevelId");
b.HasIndex("SlotId");
b.HasIndex("UserId");
b.ToTable("VisitedLevels");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Photo", b =>
{
b.Property<int>("PhotoId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("CreatorId")
.HasColumnType("int");
b.Property<string>("LargeHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("MediumHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("PhotoSubjectCollection")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("PlanHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("SmallHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<long>("Timestamp")
.HasColumnType("bigint");
b.HasKey("PhotoId");
b.HasIndex("CreatorId");
b.ToTable("Photos");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.PhotoSubject", b =>
{
b.Property<int>("PhotoSubjectId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Bounds")
.HasColumnType("longtext");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("PhotoSubjectId");
b.HasIndex("UserId");
b.ToTable("PhotoSubjects");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b =>
{
b.Property<int>("CommentId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Message")
.HasColumnType("longtext");
b.Property<int>("PosterUserId")
.HasColumnType("int");
b.Property<int>("TargetUserId")
.HasColumnType("int");
b.Property<int>("ThumbsDown")
.HasColumnType("int");
b.Property<int>("ThumbsUp")
.HasColumnType("int");
b.Property<long>("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<int>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<long>("Timestamp")
.HasColumnType("bigint");
b.HasKey("UserId");
b.ToTable("LastMatches");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Location", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("X")
.HasColumnType("int");
b.Property<int>("Y")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("Locations");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Score", b =>
{
b.Property<int>("ScoreId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("PlayerIdCollection")
.HasColumnType("longtext");
b.Property<int>("Points")
.HasColumnType("int");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("ScoreId");
b.HasIndex("SlotId");
b.ToTable("Scores");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b =>
{
b.Property<int>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Biography")
.HasColumnType("longtext");
b.Property<int>("Game")
.HasColumnType("int");
b.Property<string>("IconHash")
.HasColumnType("longtext");
b.Property<bool>("IsAdmin")
.HasColumnType("tinyint(1)");
b.Property<int>("LocationId")
.HasColumnType("int");
b.Property<string>("Password")
.HasColumnType("longtext");
b.Property<string>("Pins")
.HasColumnType("longtext");
b.Property<string>("PlanetHash")
.HasColumnType("longtext");
b.Property<string>("Username")
.HasColumnType("longtext");
b.HasKey("UserId");
b.HasIndex("LocationId");
b.ToTable("Users");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.WebToken", b =>
{
b.Property<int>("TokenId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.Property<string>("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
}
}
}

View file

@ -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<bool>(
name: "IsAdmin",
table: "Users",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsAdmin",
table: "Users");
}
}
}

View file

@ -0,0 +1,716 @@
// <auto-generated />
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<int>("AuthenticationAttemptId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("GameTokenId")
.HasColumnType("int");
b.Property<string>("IPAddress")
.HasColumnType("longtext");
b.Property<int>("Platform")
.HasColumnType("int");
b.Property<long>("Timestamp")
.HasColumnType("bigint");
b.HasKey("AuthenticationAttemptId");
b.HasIndex("GameTokenId");
b.ToTable("AuthenticationAttempts");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.GameToken", b =>
{
b.Property<int>("TokenId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<bool>("Approved")
.HasColumnType("tinyint(1)");
b.Property<int>("GameVersion")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.Property<string>("UserLocation")
.HasColumnType("longtext");
b.Property<string>("UserToken")
.HasColumnType("longtext");
b.HasKey("TokenId");
b.ToTable("GameTokens");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b =>
{
b.Property<int>("HeartedProfileId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("HeartedUserId")
.HasColumnType("int");
b.Property<int>("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<int>("HeartedLevelId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("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<int>("QueuedLevelId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("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<int>("RatedLevelId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("Rating")
.HasColumnType("int");
b.Property<double>("RatingLBP1")
.HasColumnType("double");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("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<int>("SlotId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("AuthorLabels")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("BackgroundHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("CreatorId")
.HasColumnType("int");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.Property<long>("FirstUploaded")
.HasColumnType("bigint");
b.Property<int>("GameVersion")
.HasColumnType("int");
b.Property<string>("IconHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("InitiallyLocked")
.HasColumnType("tinyint(1)");
b.Property<long>("LastUpdated")
.HasColumnType("bigint");
b.Property<bool>("Lbp1Only")
.HasColumnType("tinyint(1)");
b.Property<string>("LevelType")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("LocationId")
.HasColumnType("int");
b.Property<int>("MaximumPlayers")
.HasColumnType("int");
b.Property<int>("MinimumPlayers")
.HasColumnType("int");
b.Property<bool>("MoveRequired")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("PlaysLBP1")
.HasColumnType("int");
b.Property<int>("PlaysLBP1Complete")
.HasColumnType("int");
b.Property<int>("PlaysLBP1Unique")
.HasColumnType("int");
b.Property<int>("PlaysLBP2")
.HasColumnType("int");
b.Property<int>("PlaysLBP2Complete")
.HasColumnType("int");
b.Property<int>("PlaysLBP2Unique")
.HasColumnType("int");
b.Property<int>("PlaysLBP3")
.HasColumnType("int");
b.Property<int>("PlaysLBP3Complete")
.HasColumnType("int");
b.Property<int>("PlaysLBP3Unique")
.HasColumnType("int");
b.Property<int>("PlaysLBPVita")
.HasColumnType("int");
b.Property<int>("PlaysLBPVitaComplete")
.HasColumnType("int");
b.Property<int>("PlaysLBPVitaUnique")
.HasColumnType("int");
b.Property<string>("ResourceCollection")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("RootLevel")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Shareable")
.HasColumnType("int");
b.Property<bool>("SubLevel")
.HasColumnType("tinyint(1)");
b.Property<bool>("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<int>("VisitedLevelId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("PlaysLBP1")
.HasColumnType("int");
b.Property<int>("PlaysLBP2")
.HasColumnType("int");
b.Property<int>("PlaysLBP3")
.HasColumnType("int");
b.Property<int>("PlaysLBPVita")
.HasColumnType("int");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("VisitedLevelId");
b.HasIndex("SlotId");
b.HasIndex("UserId");
b.ToTable("VisitedLevels");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Photo", b =>
{
b.Property<int>("PhotoId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("CreatorId")
.HasColumnType("int");
b.Property<string>("LargeHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("MediumHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("PhotoSubjectCollection")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("PlanHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("SmallHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<long>("Timestamp")
.HasColumnType("bigint");
b.HasKey("PhotoId");
b.HasIndex("CreatorId");
b.ToTable("Photos");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.PhotoSubject", b =>
{
b.Property<int>("PhotoSubjectId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Bounds")
.HasColumnType("longtext");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("PhotoSubjectId");
b.HasIndex("UserId");
b.ToTable("PhotoSubjects");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b =>
{
b.Property<int>("CommentId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Message")
.HasColumnType("longtext");
b.Property<int>("PosterUserId")
.HasColumnType("int");
b.Property<int>("TargetUserId")
.HasColumnType("int");
b.Property<int>("ThumbsDown")
.HasColumnType("int");
b.Property<int>("ThumbsUp")
.HasColumnType("int");
b.Property<long>("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<int>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("GameVersion")
.HasColumnType("int");
b.Property<long>("Timestamp")
.HasColumnType("bigint");
b.HasKey("UserId");
b.ToTable("LastMatches");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Location", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("X")
.HasColumnType("int");
b.Property<int>("Y")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("Locations");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Score", b =>
{
b.Property<int>("ScoreId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("PlayerIdCollection")
.HasColumnType("longtext");
b.Property<int>("Points")
.HasColumnType("int");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("ScoreId");
b.HasIndex("SlotId");
b.ToTable("Scores");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b =>
{
b.Property<int>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Biography")
.HasColumnType("longtext");
b.Property<int>("Game")
.HasColumnType("int");
b.Property<string>("IconHash")
.HasColumnType("longtext");
b.Property<bool>("IsAdmin")
.HasColumnType("tinyint(1)");
b.Property<int>("LocationId")
.HasColumnType("int");
b.Property<string>("Password")
.HasColumnType("longtext");
b.Property<string>("Pins")
.HasColumnType("longtext");
b.Property<string>("PlanetHash")
.HasColumnType("longtext");
b.Property<string>("Username")
.HasColumnType("longtext");
b.HasKey("UserId");
b.HasIndex("LocationId");
b.ToTable("Users");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.WebToken", b =>
{
b.Property<int>("TokenId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.Property<string>("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
}
}
}

View file

@ -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<int>(
name: "GameVersion",
table: "LastMatches",
type: "int",
nullable: false,
defaultValue: 0);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "GameVersion",
table: "LastMatches");
}
}
}

View file

@ -0,0 +1,719 @@
// <auto-generated />
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<int>("AuthenticationAttemptId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("GameTokenId")
.HasColumnType("int");
b.Property<string>("IPAddress")
.HasColumnType("longtext");
b.Property<int>("Platform")
.HasColumnType("int");
b.Property<long>("Timestamp")
.HasColumnType("bigint");
b.HasKey("AuthenticationAttemptId");
b.HasIndex("GameTokenId");
b.ToTable("AuthenticationAttempts");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.GameToken", b =>
{
b.Property<int>("TokenId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<bool>("Approved")
.HasColumnType("tinyint(1)");
b.Property<int>("GameVersion")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.Property<string>("UserLocation")
.HasColumnType("longtext");
b.Property<string>("UserToken")
.HasColumnType("longtext");
b.HasKey("TokenId");
b.ToTable("GameTokens");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b =>
{
b.Property<int>("HeartedProfileId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("HeartedUserId")
.HasColumnType("int");
b.Property<int>("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<int>("HeartedLevelId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("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<int>("QueuedLevelId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("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<int>("RatedLevelId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("Rating")
.HasColumnType("int");
b.Property<double>("RatingLBP1")
.HasColumnType("double");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("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<int>("SlotId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("AuthorLabels")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("BackgroundHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("CreatorId")
.HasColumnType("int");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.Property<long>("FirstUploaded")
.HasColumnType("bigint");
b.Property<int>("GameVersion")
.HasColumnType("int");
b.Property<string>("IconHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("InitiallyLocked")
.HasColumnType("tinyint(1)");
b.Property<long>("LastUpdated")
.HasColumnType("bigint");
b.Property<bool>("Lbp1Only")
.HasColumnType("tinyint(1)");
b.Property<string>("LevelType")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("LocationId")
.HasColumnType("int");
b.Property<int>("MaximumPlayers")
.HasColumnType("int");
b.Property<int>("MinimumPlayers")
.HasColumnType("int");
b.Property<bool>("MoveRequired")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("PlaysLBP1")
.HasColumnType("int");
b.Property<int>("PlaysLBP1Complete")
.HasColumnType("int");
b.Property<int>("PlaysLBP1Unique")
.HasColumnType("int");
b.Property<int>("PlaysLBP2")
.HasColumnType("int");
b.Property<int>("PlaysLBP2Complete")
.HasColumnType("int");
b.Property<int>("PlaysLBP2Unique")
.HasColumnType("int");
b.Property<int>("PlaysLBP3")
.HasColumnType("int");
b.Property<int>("PlaysLBP3Complete")
.HasColumnType("int");
b.Property<int>("PlaysLBP3Unique")
.HasColumnType("int");
b.Property<int>("PlaysLBPVita")
.HasColumnType("int");
b.Property<int>("PlaysLBPVitaComplete")
.HasColumnType("int");
b.Property<int>("PlaysLBPVitaUnique")
.HasColumnType("int");
b.Property<string>("ResourceCollection")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("RootLevel")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Shareable")
.HasColumnType("int");
b.Property<bool>("SubLevel")
.HasColumnType("tinyint(1)");
b.Property<bool>("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<int>("VisitedLevelId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("PlaysLBP1")
.HasColumnType("int");
b.Property<int>("PlaysLBP2")
.HasColumnType("int");
b.Property<int>("PlaysLBP3")
.HasColumnType("int");
b.Property<int>("PlaysLBPVita")
.HasColumnType("int");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("VisitedLevelId");
b.HasIndex("SlotId");
b.HasIndex("UserId");
b.ToTable("VisitedLevels");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Photo", b =>
{
b.Property<int>("PhotoId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("CreatorId")
.HasColumnType("int");
b.Property<string>("LargeHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("MediumHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("PhotoSubjectCollection")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("PlanHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("SmallHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<long>("Timestamp")
.HasColumnType("bigint");
b.HasKey("PhotoId");
b.HasIndex("CreatorId");
b.ToTable("Photos");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.PhotoSubject", b =>
{
b.Property<int>("PhotoSubjectId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Bounds")
.HasColumnType("longtext");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("PhotoSubjectId");
b.HasIndex("UserId");
b.ToTable("PhotoSubjects");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b =>
{
b.Property<int>("CommentId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Message")
.HasColumnType("longtext");
b.Property<int>("PosterUserId")
.HasColumnType("int");
b.Property<int>("TargetUserId")
.HasColumnType("int");
b.Property<int>("ThumbsDown")
.HasColumnType("int");
b.Property<int>("ThumbsUp")
.HasColumnType("int");
b.Property<long>("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<int>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("GameVersion")
.HasColumnType("int");
b.Property<long>("Timestamp")
.HasColumnType("bigint");
b.HasKey("UserId");
b.ToTable("LastMatches");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Location", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("X")
.HasColumnType("int");
b.Property<int>("Y")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("Locations");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Score", b =>
{
b.Property<int>("ScoreId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("PlayerIdCollection")
.HasColumnType("longtext");
b.Property<int>("Points")
.HasColumnType("int");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("ScoreId");
b.HasIndex("SlotId");
b.ToTable("Scores");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b =>
{
b.Property<int>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Biography")
.HasColumnType("longtext");
b.Property<int>("Game")
.HasColumnType("int");
b.Property<string>("IconHash")
.HasColumnType("longtext");
b.Property<bool>("IsAdmin")
.HasColumnType("tinyint(1)");
b.Property<int>("LocationId")
.HasColumnType("int");
b.Property<string>("Password")
.HasColumnType("longtext");
b.Property<bool>("PasswordResetRequired")
.HasColumnType("tinyint(1)");
b.Property<string>("Pins")
.HasColumnType("longtext");
b.Property<string>("PlanetHash")
.HasColumnType("longtext");
b.Property<string>("Username")
.HasColumnType("longtext");
b.HasKey("UserId");
b.HasIndex("LocationId");
b.ToTable("Users");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.WebToken", b =>
{
b.Property<int>("TokenId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.Property<string>("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
}
}
}

View file

@ -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<bool>(
name: "PasswordResetRequired",
table: "Users",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "PasswordResetRequired",
table: "Users");
}
}
}

View file

@ -411,6 +411,9 @@ namespace ProjectLighthouse.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("GameVersion")
.HasColumnType("int");
b.Property<long>("Timestamp")
.HasColumnType("bigint");
@ -539,12 +542,18 @@ namespace ProjectLighthouse.Migrations
b.Property<string>("IconHash")
.HasColumnType("longtext");
b.Property<bool>("IsAdmin")
.HasColumnType("tinyint(1)");
b.Property<int>("LocationId")
.HasColumnType("int");
b.Property<string>("Password")
.HasColumnType("longtext");
b.Property<bool>("PasswordResetRequired")
.HasColumnType("tinyint(1)");
b.Property<string>("Pins")
.HasColumnType("longtext");

View file

@ -0,0 +1,55 @@
@page "/admin"
@using LBPUnion.ProjectLighthouse.Helpers
@using LBPUnion.ProjectLighthouse.Maintenance
@model LBPUnion.ProjectLighthouse.Pages.AdminPanelPage
@{
Layout = "Layouts/BaseLayout";
}
<h1>Admin Panel</h1>
<h2>Commands</h2>
<div class="ui grid">
@foreach (ICommand command in MaintenanceHelper.Commands)
{
<div class="four wide column">
<div class="ui blue segment">
<h3>@command.Name()</h3>
<form>
<div class="ui input" style="width: 100%;">
<input type="text" name="args" placeholder="@command.Arguments()">
</div><br><br>
<input type="text" name="command" style="display: none;" value="@command.FirstAlias">
<button type="submit" class="ui green button" style="width: 100%;">
<i class="play icon"></i>
Execute
</button>
</form>
</div>
</div>
}
</div>
<h2>Maintenance Jobs</h2>
<p>
<b>Warning: Interrupting Lighthouse during maintenance may leave the database in an unclean state.</b>
</p>
<div class="ui grid">
@foreach (IMaintenanceJob job in MaintenanceHelper.MaintenanceJobs)
{
<div class="four wide column">
<div class="ui red segment">
<h3>@job.Name()</h3>
<p>@job.Description()</p>
<form>
<input type="text" name="maintenanceJob" style="display: none;" value="@job.GetType().Name">
<button type="submit" class="ui green button" style="width: 100%;">
<i class="play icon"></i>
Execute
</button>
</form>
</div>
</div>
}
</div>

View file

@ -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<ICommand> Commands = MaintenanceHelper.Commands;
public async Task<IActionResult> 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();
}
}
}

View file

@ -1,4 +1,5 @@
@page "/"
@using LBPUnion.ProjectLighthouse.Types
@model LBPUnion.ProjectLighthouse.Pages.LandingPage
@{
@ -11,15 +12,23 @@
<p>You are currently logged in as <b>@Model.User.Username</b>.</p>
}
@if (Model.PlayersOnline == 1)
@if (Model.PlayersOnlineCount == 1)
{
<p>There is 1 user currently online.</p>
<p>There is 1 user currently online:</p>
@foreach (User user in Model.PlayersOnline)
{
<a href="/user/@user.UserId">@user.Username</a>
}
}
else if (Model.PlayersOnline == 0)
else if (Model.PlayersOnlineCount == 0)
{
<p>There are no users online. Why not hop on?</p>
}
else
{
<p>There are currently @Model.PlayersOnline users online.</p>
<p>There are currently @Model.PlayersOnlineCount users online:</p>
@foreach (User user in Model.PlayersOnline)
{
<a href="/user/@user.UserId">@user.Username</a>
}
}

View file

@ -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<User> PlayersOnline;
[UsedImplicitly]
public async Task<IActionResult> 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<int> 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();
}
}

View file

@ -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 @@
<link rel="stylesheet" type="text/css" href="~/css/styles.css">
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/fomantic-ui@2.8.8/dist/semantic.min.css">
</head>
<header class="lighthouse-header">
<div class="ui attached menu">
<div class="ui container">
@foreach (PageNavigationItem navigationItem in Model!.NavigationItems)
{
<a class="item" href="@navigationItem.Url">
@if (navigationItem.Icon != null)
<body>
<div class="pageContainer">
<header class="lighthouse-header">
<div class="ui attached menu">
<div class="ui container">
@foreach (PageNavigationItem navigationItem in Model!.NavigationItems)
{
<a class="item" href="@navigationItem.Url">
@if (navigationItem.Icon != null)
{
<i class="@navigationItem.Icon icon"></i>
}
@navigationItem.Name
</a>
}
<div class="right menu">
@foreach (PageNavigationItem navigationItem in Model!.NavigationItemsRight)
{
<i class="@navigationItem.Icon icon"></i>
<a class="item" href="@navigationItem.Url">
@if (navigationItem.Icon != null)
{
<i class="@navigationItem.Icon icon"></i>
}
@navigationItem.Name
</a>
}
@navigationItem.Name
</a>
}
</div>
</div>
</div>
<noscript>
<div class="ui bottom attached yellow message small">
<div class="ui container">
<div style="display: flex; align-items: center; font-size: 1.2rem;">
<i class="warning icon"></i>
<span style="font-size: 1.2rem;">JavaScript not enabled</span>
</div>
<p>
While we intend to have as little JavaScript as possible, we can not
guarantee everything will work without it. We recommend that you whitelist JavaScript for Project Lighthouse.
It's not <i>too</i> bloated, we promise.
</p>
</div>
</div>
</noscript>
</header>
<div class="main">
<div class="ui container">
<br>
@RenderBody()
<div style="height: 50px;"></div> @* makes it look nicer *@
</div>
</div>
</header>
<body>
<div class="ui container">
<br>
@RenderBody()
<footer>
<div class="ui black attached inverted segment">
<div class="ui container">
<p>Page generated by @VersionHelper.FullVersion.</p>
@if (VersionHelper.IsDirty)
{
<p>This page was generated using a modified version of Project Lighthouse. Please make sure you are properly disclosing the source code to any users who may be using this instance.</p>
}
</div>
</div>
</footer>
</div>
</body>
<footer class="lighthouse-footer">
<div class="ui black attached inverted segment">
<div class="ui container">
<p>Page generated by @GitVersionHelper.FullVersion.</p>
@if (GitVersionHelper.IsDirty)
{
<p>This page was generated using a modified version of Project Lighthouse. Please make sure you are properly disclosing the source code to any users who may be using this instance.</p>
}
</div>
</div>
</footer>
</html>

View file

@ -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<PageNavigationItem> NavigationItemsRight = new()
{};
}
}

View file

@ -20,14 +20,14 @@
<h1>Log in</h1>
<form onsubmit="return onSubmit(this)">
<div class="ui left labeled input">
<label for="text" class="ui label">Username: </label>
<label for="text" class="ui blue label">Username: </label>
<input type="text" name="username" id="text">
</div><br>
<div class="ui left labeled input">
<label for="password" class="ui label">Password: </label>
<input type="password" name="password" id="password">
</div><br><br>
<input type="submit" value="Log in" id="submit" class="ui button"><br>
<div class="ui left labeled input">
<label for="password" class="ui blue label">Password: </label>
<input type="password" name="password" id="password">
</div><br><br><br>
<input type="submit" value="Log in" id="submit" class="ui green button"><br>
</form>

View file

@ -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));
}

View file

@ -0,0 +1,36 @@
@page "/passwordReset"
@model LBPUnion.ProjectLighthouse.Pages.PasswordResetPage
@{
Layout = "Layouts/BaseLayout";
}
<script src="https://geraintluff.github.io/sha256/sha256.min.js"></script>
<script>
function onSubmit(form) {
const password = form['password'];
const confirmPassword = form['confirmPassword'];
password.value = sha256(password.value);
confirmPassword.value = sha256(confirmPassword.value);
return true;
}
</script>
<h1>Password Reset</h1>
<form onsubmit="return onSubmit(this)">
<div class="ui left labeled input">
<label for="password" class="ui blue label">Password: </label>
<input type="password" name="password" id="password">
</div><br><br>
<div class="ui left labeled input">
<label for="password" class="ui blue label">Confirm Password: </label>
<input type="password" name="confirmPassword" id="confirmPassword">
</div><br><br><br>
<input type="submit" value="Reset password and continue" id="submit" class="ui green button"><br>
</form>

View file

@ -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<IActionResult> 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();
}
}
}

View file

@ -0,0 +1,13 @@
@page "/passwordResetRequired"
@model LBPUnion.ProjectLighthouse.Pages.PasswordResetRequiredPage
@{
Layout = "Layouts/BaseLayout";
}
<h1>Password Reset Required</h1>
<p>An admin has deemed it necessary that you reset your password. Please do so.</p>
<a href="/passwordReset">
<div class="ui blue button">Reset Password</div>
</a>

View file

@ -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<IActionResult> OnGet()
{
User? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login");
if (!user.PasswordResetRequired) return this.Redirect("~/passwordReset");
return this.Page();
}
}
}

View file

@ -19,7 +19,7 @@
<i>
Taken by
<b>
<a href="/users/@photo.Creator!.UserId">@photo.Creator.Username</a>
<a href="/user/@photo.Creator!.UserId">@photo.Creator.Username</a>
</b>
</i>
</p>
@ -29,7 +29,7 @@
</p>
@foreach (PhotoSubject subject in photo.Subjects)
{
<a href="/users/@subject.UserId">@subject.User.Username</a>
<a href="/user/@subject.UserId">@subject.User.Username</a>
}
</div>
}
@ -38,6 +38,4 @@
{
<a href="/photos/@(Model.PageNumber - 1)">Previous Page</a>
}
<a href="/photos/@(Model.PageNumber + 1)">Next Page</a>
<div style="height: 100px; width: 1px;"></div> @* solves quirk with footer *@
<a href="/photos/@(Model.PageNumber + 1)">Next Page</a>

View file

@ -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<IActionResult> 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();

View file

@ -19,20 +19,23 @@
}
</script>
<h1>Register</h1>
<form onsubmit="return onSubmit(this)">
<div class="ui left labeled input">
<label for="text" class="ui label">Username: </label>
<label for="text" class="ui blue label">Username: </label>
<input type="text" name="username" id="text">
</div><br>
<div class="ui left labeled input">
<label for="password" class="ui label">Password: </label>
<input type="password" name="password" id="password">
</div><br>
<div class="ui left labeled input">
<label for="password" class="ui label">Confirm Password: </label>
<input type="password" name="confirmPassword" id="confirmPassword">
</div><br><br>
<input type="submit" value="Register" id="submit" class="ui button"><br>
<div class="ui left labeled input">
<label for="password" class="ui blue label">Password: </label>
<input type="password" name="password" id="password">
</div><br><br>
<div class="ui left labeled input">
<label for="password" class="ui blue label">Confirm Password: </label>
<input type="password" name="confirmPassword" id="confirmPassword">
</div><br><br><br>
<input type="submit" value="Register" id="submit" class="ui green button"><br>
</form>

View file

@ -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<IActionResult> 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)

View file

@ -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";
}
<h1>Levels</h1>
<p>There are @Model.SlotCount total levels!</p>
@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;
}
<div class="ui segment">
<div class="ui grid">
<div class="eight wide column">
<h2 style="margin-bottom: 2px;">@slotName</h2>
<div class="statsUnderTitle" style="margin-bottom: 10px;">
<i class="pink heart icon" title="Hearts"></i> <span>@slot.Hearts</span>
<i class="blue play icon" title="Plays"></i> <span>@slot.Plays</span>
<i class="green thumbs up icon" title="Yays"></i> <span>@slot.Thumbsup</span>
<i class="red thumbs down icon" title="Boos"></i> <span>@slot.Thumbsdown</span>
@if (slot.GameVersion == GameVersion.LittleBigPlanet1)
{
<i class="yellow star icon" title="LBP1 Stars"></i>
<span>@slot.RatingLBP1</span>
}
</div>
<p>
<i>Created by <a href="/user/@slot.Creator?.UserId">@slot.Creator?.Username</a></i>
</p>
</div>
<div class="eight wide right aligned column">
@if (Model.User != null)
{
if (isHearted)
{
<a class="ui pink tiny button" href="/slot/@slot.SlotId/unheart?callbackUrl=~/slots/@Model.PageNumber" title="Unheart">
<i class="broken heart icon" style="margin: 0"></i>
</a>
}
else
{
<a class="ui pink tiny button" href="/slot/@slot.SlotId/heart?callbackUrl=~/slots/@Model.PageNumber" title="Heart">
<i class="heart icon" style="margin: 0"></i>
</a>
}
if (isQueued)
{
<a class="ui yellow tiny button" href="/slot/@slot.SlotId/unqueue?callbackUrl=~/slots/@Model.PageNumber" title="Unqueue">
<i class="bell slash icon" style="margin: 0"></i>
</a>
}
else
{
<a class="ui yellow tiny button" href="/slot/@slot.SlotId/queue?callbackUrl=~/slots/@Model.PageNumber" title="Queue">
<i class="bell icon" style="margin: 0"></i>
</a>
}
}
</div>
</div>
</div>
}
@if (Model.PageNumber != 0)
{
<a href="/slots/@(Model.PageNumber - 1)">Previous Page</a>
}
<a href="/slots/@(Model.PageNumber + 1)">Next Page</a>

View file

@ -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<Slot> Slots;
public int PageNumber;
public async Task<IActionResult> 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();
}
}
}

View file

@ -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";
}
<div class="ui grid">
<div class="eight wide column">
<h1>@Model.ProfileUser!.Username's user page</h1>
<p>
<i>@Model.ProfileUser.Status</i>
</p>
<div class="statsUnderTitle">
<i class="pink heart icon" title="Hearts"></i> <span>@Model.ProfileUser.Hearts</span>
<i class="blue comment icon" title="Comments"></i> <span>@Model.ProfileUser.Comments</span>
<i class="green upload icon" title="Uploaded Levels"></i><span>@Model.ProfileUser.UsedSlots / @ServerSettings.Instance.EntitledSlots</span>
<i class="purple camera icon" title="Uploaded Photos"></i><span>@Model.ProfileUser.PhotosByMe</span>
</div>
</div>
<div class="eight wide right aligned column">
<br>
@if (Model.ProfileUser != Model.User && Model.User != null)
{
if (!Model.IsProfileUserHearted)
{
<a class="ui pink button" href="/user/@Model.ProfileUser.UserId/heart">
<i class="heart icon"></i>
<span>Heart</span>
</a>
}
else
{
<a class="ui pink button" href="/user/@Model.ProfileUser.UserId/unheart">
<i class="heart broken icon"></i>
<span>Unheart</span>
</a>
}
}
@if (Model.ProfileUser == Model.User)
{
<a class="ui blue button" href="/passwordReset">
<i class="key icon"></i>
<span>Reset Password</span>
</a>
}
</div>
<div class="eight wide column">
<div class="ui blue segment">
<h2>Biography</h2>
<p>@Model.ProfileUser.Biography</p>
</div>
</div>
<div class="eight wide column">
<div class="ui red segment">
<h2>Recent Activity</h2>
<p>Coming soon!</p>
</div>
</div>
</div>
@if (Model.Photos != null && Model.Photos.Count != 0)
{
<div class="ui purple segment">
<h2>Most recent photos</h2>
<div class="ui center aligned grid">
@foreach (Photo photo in Model.Photos)
{
<div class="eight wide column">
<img src="/gameAssets/@photo.LargeHash" style="width: 100%; height: auto; border-radius: .28571429rem;">
<br>
<p>
<b>Photo contains @photo.Subjects.Count @(photo.Subjects.Count == 1 ? "person" : "people"):</b>
</p>
@foreach (PhotoSubject subject in photo.Subjects)
{
<a href="/user/@subject.UserId">@subject.User.Username</a>
}
</div>
}
</div>
</div>
}
@if (Model.ProfileUser.Comments > 0)
{
<div class="ui yellow segment">
<h1>Comments</h1>
@foreach (Comment comment in Model.Comments!)
{
DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(comment.Timestamp / 1000);
<div>
<b><a href="/user/@comment.PosterUserId">@comment.Poster.Username</a>: </b>
<span>@comment.Message</span>
<p>
<i>@timestamp.ToString("MM/dd/yyyy @ h:mm tt") UTC</i>
</p>
<div class="ui divider"></div>
</div>
}
</div>
}

View file

@ -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<Photo>? Photos;
public List<Comment>? Comments;
public bool IsProfileUserHearted;
public async Task<IActionResult> OnGet([FromRoute] int userId)
{
this.ProfileUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == userId);
if (this.ProfileUser == null) return this.NotFound();
this.Photos = await this.Database.Photos.OrderByDescending(p => p.Timestamp).Where(p => p.CreatorId == userId).Take(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();
}
}
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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);
}
}
}

View file

@ -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 ");
}
}

View file

@ -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;
}
}

View file

@ -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;
/// <summary>
/// The maximum amount of slots allowed on users' earth
/// </summary>
public int EntitledSlots { get; set; } = 50;
public int ListsQuota { get; set; } = 50;
}
}

View file

@ -8,13 +8,6 @@ namespace LBPUnion.ProjectLighthouse.Types.Settings
{
public static class ServerStatics
{
/// <summary>
/// The maximum amount of slots allowed on users' earth
/// </summary>
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;
}
}

View file

@ -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
/// <summary>
/// The number of slots remaining on the earth
/// </summary>
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);

View file

@ -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.