Merge main into mod-panel

This commit is contained in:
jvyden 2022-06-10 04:01:06 -04:00
commit b2e6f25265
No known key found for this signature in database
GPG key ID: 18BCF2BE0262B278
38 changed files with 468 additions and 139 deletions

View file

@ -3,11 +3,13 @@
<component name="UserContentModel"> <component name="UserContentModel">
<attachedFolders /> <attachedFolders />
<explicitIncludes> <explicitIncludes>
<Path>.config/dotnet-tools.json</Path>
<Path>.github</Path> <Path>.github</Path>
<Path>.gitignore</Path> <Path>.gitignore</Path>
<Path>.idea</Path> <Path>.idea</Path>
<Path>CONTRIBUTING.md</Path> <Path>CONTRIBUTING.md</Path>
<Path>DatabaseMigrations</Path> <Path>DatabaseMigrations</Path>
<Path>LICENSE</Path>
<Path>ProjectLighthouse.sln.DotSettings</Path> <Path>ProjectLighthouse.sln.DotSettings</Path>
<Path>ProjectLighthouse.sln.DotSettings.user</Path> <Path>ProjectLighthouse.sln.DotSettings.user</Path>
<Path>README.md</Path> <Path>README.md</Path>

View file

@ -130,7 +130,7 @@ public class LoginController : ControllerBase
new LoginResult new LoginResult
{ {
AuthTicket = "MM_AUTH=" + token.UserToken, AuthTicket = "MM_AUTH=" + token.UserToken,
LbpEnvVer = ServerStatics.ServerName, ServerBrand = VersionHelper.FullVersion,
}.Serialize() }.Serialize()
); );
} }

View file

@ -86,9 +86,9 @@ public class PublishController : ControllerBase
if (slot.Location == null) return this.BadRequest(); if (slot.Location == null) return this.BadRequest();
if (slot.Description.Length > 200) return this.BadRequest(); if (slot.Description.Length > 500) return this.BadRequest();
if (slot.Name.Length > 100) return this.BadRequest(); if (slot.Name.Length > 64) return this.BadRequest();
if (slot.Resources.Any(resource => !FileHelper.ResourceExists(resource))) if (slot.Resources.Any(resource => !FileHelper.ResourceExists(resource)))
{ {

View file

@ -96,7 +96,7 @@ public class ReviewController : ControllerBase
Review? newReview = await this.getReviewFromBody(); Review? newReview = await this.getReviewFromBody();
if (newReview == null) return this.BadRequest(); if (newReview == null) return this.BadRequest();
if (newReview.Text.Length > 100) return this.BadRequest(); if (newReview.Text.Length > 512) return this.BadRequest();
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == user.UserId); Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == user.UserId);

View file

@ -42,7 +42,7 @@ public class RoomVisualizerController : ControllerBase
#if !DEBUG #if !DEBUG
return this.NotFound(); return this.NotFound();
#else #else
RoomHelper.Rooms.RemoveAll(); lock(RoomHelper.RoomLock) RoomHelper.Rooms.RemoveAll();
return this.Redirect("/debug/roomVisualizer"); return this.Redirect("/debug/roomVisualizer");
#endif #endif
} }

View file

@ -1,14 +1,16 @@
@page "/" @page "/"
@using LBPUnion.ProjectLighthouse.Configuration @using LBPUnion.ProjectLighthouse.Configuration
@using LBPUnion.ProjectLighthouse.Extensions
@using LBPUnion.ProjectLighthouse.PlayerData.Profiles @using LBPUnion.ProjectLighthouse.PlayerData.Profiles
@using LBPUnion.ProjectLighthouse.Types @using LBPUnion.ProjectLighthouse.Levels
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.LandingPage @model LBPUnion.ProjectLighthouse.Servers.Website.Pages.LandingPage
@{ @{
Layout = "Layouts/BaseLayout"; Layout = "Layouts/BaseLayout";
Model.ShowTitleInPage = false; Model.ShowTitleInPage = false;
bool isMobile = this.Request.IsMobile();
} }
<h1>Welcome to <b>Project Lighthouse</b>!</h1> <h1>Welcome to <b>@ServerConfiguration.Instance.Customization.ServerName</b>!</h1>
@if (Model.User != null) @if (Model.User != null)
{ {
@ -42,3 +44,38 @@ else
<a href="/user/@user.UserId" title="@user.Status.ToString()">@user.Username</a> <a href="/user/@user.UserId" title="@user.Status.ToString()">@user.Username</a>
} }
} }
<br>
<div class="@(isMobile ? "" : "ui center aligned grid")">
<div class="eight wide column">
<div class="ui pink segment">
<h1><i class="ribbon icon"></i>Latest Team Picks</h1>
<div class="ui divider"></div>
<div class="ui left aligned segment">
@foreach (Slot slot in Model.LatestTeamPicks!) @* Can't reach a point where this is null *@
{
@await Html.PartialAsync("Partials/SlotCardPartial", slot, Model.GetSlotViewData(slot.SlotId, isMobile))
<br>
}
</div>
</div>
</div>
@if (isMobile)
{
<br>
}
<div class="eight wide column">
<div class="ui blue segment">
<h1><i class="certificate icon"></i>Newest Levels</h1>
<div class="ui divider"></div>
<div class="ui left aligned segment">
@foreach (Slot slot in Model.NewestLevels!) @* Can't reach a point where this is null *@
{
@await Html.PartialAsync("Partials/SlotCardPartial", slot, Model.GetSlotViewData(slot.SlotId, isMobile))
<br>
}
</div>
</div>
</div>
</div>

View file

@ -1,23 +1,27 @@
#nullable enable #nullable enable
using JetBrains.Annotations; using JetBrains.Annotations;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts; using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages; namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
public class LandingPage : BaseLayout public class LandingPage : BaseLayout
{ {
public LandingPage(Database database) : base(database)
{}
public int AuthenticationAttemptsCount; public int AuthenticationAttemptsCount;
public List<User> PlayersOnline = new(); public List<User> PlayersOnline = new();
public int PlayersOnlineCount; public int PlayersOnlineCount;
public LandingPage(Database database) : base(database)
{} public List<Slot>? LatestTeamPicks;
public List<Slot>? NewestLevels;
[UsedImplicitly] [UsedImplicitly]
public async Task<IActionResult> OnGet() public async Task<IActionResult> OnGet()
@ -35,6 +39,38 @@ public class LandingPage : BaseLayout
List<int> userIds = await this.Database.LastContacts.Where(l => TimeHelper.Timestamp - l.Timestamp < 300).Select(l => l.UserId).ToListAsync(); List<int> userIds = await this.Database.LastContacts.Where(l => TimeHelper.Timestamp - l.Timestamp < 300).Select(l => l.UserId).ToListAsync();
this.PlayersOnline = await this.Database.Users.Where(u => userIds.Contains(u.UserId)).ToListAsync(); this.PlayersOnline = await this.Database.Users.Where(u => userIds.Contains(u.UserId)).ToListAsync();
const int maxShownLevels = 5;
this.LatestTeamPicks = await this.Database.Slots.Where
(s => s.TeamPick)
.OrderByDescending(s => s.FirstUploaded)
.Take(maxShownLevels)
.Include(s => s.Creator)
.ToListAsync();
this.NewestLevels = await this.Database.Slots.OrderByDescending(s => s.FirstUploaded).Take(maxShownLevels).Include(s => s.Creator).ToListAsync();
return this.Page(); return this.Page();
} }
public ViewDataDictionary GetSlotViewData(int slotId, bool isMobile = false)
=> new(ViewData)
{
{
"User", this.User
},
{
"CallbackUrl", $"~/slot/{slotId}"
},
{
"ShowLink", true
},
{
"IsMini", true
},
{
"IsMobile", isMobile
},
};
} }

View file

@ -33,11 +33,11 @@
<head> <head>
@if (Model.Title == string.Empty) @if (Model.Title == string.Empty)
{ {
<title>Project Lighthouse</title> <title>@ServerConfiguration.Instance.Customization.ServerName</title>
} }
else else
{ {
<title>Project Lighthouse - @Model.Title</title> <title>@ServerConfiguration.Instance.Customization.ServerName - @Model.Title</title>
} }
<link rel="stylesheet" type="text/css" href="~/css/styles.css"> <link rel="stylesheet" type="text/css" href="~/css/styles.css">
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/fomantic-ui@2.8.8/dist/semantic.min.css"> <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/fomantic-ui@2.8.8/dist/semantic.min.css">
@ -52,7 +52,7 @@
@* Embed Stuff *@ @* Embed Stuff *@
<meta name="theme-color" data-react-helmet="true" content="#008cff"> <meta name="theme-color" data-react-helmet="true" content="#008cff">
<meta content="Project Lighthouse - @Model.Title" property="og:title"> <meta content="@ServerConfiguration.Instance.Customization.ServerName - @Model.Title" property="og:title">
@if (!string.IsNullOrEmpty(Model.Description)) @if (!string.IsNullOrEmpty(Model.Description))
{ {
<meta content="@Model.Description" property="og:description"> <meta content="@Model.Description" property="og:description">

View file

@ -2,7 +2,6 @@
@using LBPUnion.ProjectLighthouse.Configuration @using LBPUnion.ProjectLighthouse.Configuration
@using LBPUnion.ProjectLighthouse.PlayerData @using LBPUnion.ProjectLighthouse.PlayerData
@using LBPUnion.ProjectLighthouse.PlayerData.Profiles @using LBPUnion.ProjectLighthouse.PlayerData.Profiles
@using LBPUnion.ProjectLighthouse.Types
@using Microsoft.EntityFrameworkCore @using Microsoft.EntityFrameworkCore
@model LBPUnion.ProjectLighthouse.Levels.Slot @model LBPUnion.ProjectLighthouse.Levels.Slot
@ -12,7 +11,9 @@
await using Database database = new(); await using Database database = new();
string slotName = string.IsNullOrEmpty(Model.Name) ? "Unnamed Level" : Model.Name; string slotName = string.IsNullOrEmpty(Model.Name) ? "Unnamed Level" : Model.Name;
bool isMobile = (bool?)ViewData["IsMobile"] ?? false; bool isMobile = (bool?)ViewData["IsMobile"] ?? false;
bool mini = (bool?)ViewData["IsMini"] ?? false;
bool isQueued = false; bool isQueued = false;
bool isHearted = false; bool isHearted = false;
@ -31,13 +32,15 @@
} }
<div class="card"> <div class="card">
@{ @{
int size = isMobile ? 50 : 100; int size = isMobile || mini ? 50 : 100;
} }
<div> <div>
<img src="~/assets/slotCardOverlay.png" style="min-width: @(size)px; width: @(size)px; height: @(size)px; pointer-events: none; position: absolute"> <img src="~/assets/slotCardOverlay.png" style="min-width: @(size)px; width: @(size)px; height: @(size)px; pointer-events: none; position: absolute">
<img class="cardIcon slotCardIcon" src="/gameAssets/@iconHash" style="min-width: @(size)px; width: @(size)px; height: @(size)px"> <img class="cardIcon slotCardIcon" src="/gameAssets/@iconHash" style="min-width: @(size)px; width: @(size)px; height: @(size)px">
</div> </div>
<div class="cardStats"> <div class="cardStats">
@if (!mini)
{
@if (showLink) @if (showLink)
{ {
<h2> <h2>
@ -50,6 +53,23 @@
@slotName @slotName
</h1> </h1>
} }
}
else
{
@if (showLink)
{
<h3>
<a href="~/slot/@Model.SlotId">@slotName</a>
</h3>
}
else
{
<h3>
@slotName
</h3>
}
}
<div class="cardStatsUnderTitle"> <div class="cardStatsUnderTitle">
<i class="pink heart icon" title="Hearts"></i> <span>@Model.Hearts</span> <i class="pink heart icon" title="Hearts"></i> <span>@Model.Hearts</span>
<i class="blue play icon" title="Plays"></i> <span>@Model.PlaysUnique</span> <i class="blue play icon" title="Plays"></i> <span>@Model.PlaysUnique</span>
@ -68,7 +88,7 @@
</div> </div>
<div class="cardButtons"> <div class="cardButtons">
<br> <br>
@if (user != null) @if (user != null && !mini)
{ {
if (isHearted) if (isHearted)
{ {

View file

@ -1,5 +1,6 @@
#nullable enable #nullable enable
using JetBrains.Annotations; using JetBrains.Annotations;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts; using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
@ -38,7 +39,8 @@ public class PasswordResetPage : BaseLayout
await this.Database.SaveChangesAsync(); await this.Database.SaveChangesAsync();
if (!user.EmailAddressVerified) return this.Redirect("~/login/sendVerificationEmail"); if (!user.EmailAddressVerified && ServerConfiguration.Instance.Mail.MailEnabled)
return this.Redirect("~/login/sendVerificationEmail");
return this.Redirect("~/"); return this.Redirect("~/");
} }

View file

@ -2,6 +2,7 @@ using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.Tests; using LBPUnion.ProjectLighthouse.Tests;
using Xunit; using Xunit;
@ -25,7 +26,7 @@ public class AuthenticationTests : LighthouseServerTest
Assert.True(response.IsSuccessStatusCode); Assert.True(response.IsSuccessStatusCode);
string responseContent = await response.Content.ReadAsStringAsync(); string responseContent = await response.Content.ReadAsStringAsync();
Assert.Contains("MM_AUTH=", responseContent); Assert.Contains("MM_AUTH=", responseContent);
Assert.Contains(ServerStatics.ServerName, responseContent); Assert.Contains(VersionHelper.FullVersion, responseContent);
} }
[DatabaseFact] [DatabaseFact]
@ -35,10 +36,10 @@ public class AuthenticationTests : LighthouseServerTest
Assert.NotNull(loginResult); Assert.NotNull(loginResult);
Assert.NotNull(loginResult.AuthTicket); Assert.NotNull(loginResult.AuthTicket);
Assert.NotNull(loginResult.LbpEnvVer); Assert.NotNull(loginResult.ServerBrand);
Assert.Contains("MM_AUTH=", loginResult.AuthTicket); Assert.Contains("MM_AUTH=", loginResult.AuthTicket);
Assert.Equal(ServerStatics.ServerName, loginResult.LbpEnvVer); Assert.Equal(VersionHelper.FullVersion, loginResult.ServerBrand);
} }
[DatabaseFact] [DatabaseFact]

View file

@ -15,7 +15,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Selenium.WebDriver" Version="4.1.1" /> <PackageReference Include="Selenium.WebDriver" Version="4.2.0" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="101.0.4951.4100" /> <PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="101.0.4951.4100" />
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">

View file

@ -115,6 +115,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=dpadrate/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=dpadrate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ezoiar/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=ezoiar/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=farc/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=farc/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=FLUSHALL/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=friendscores/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=friendscores/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ingame/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Ingame/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Kettu/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Kettu/@EntryIndexedValue">True</s:Boolean>

View file

@ -0,0 +1,25 @@
using System;
using System.ComponentModel.DataAnnotations;
using LBPUnion.ProjectLighthouse.Administration.Maintenance;
namespace LBPUnion.ProjectLighthouse.Administration;
/// <summary>
/// A record of the completion of a <see cref="IMigrationTask"/>.
/// </summary>
public class CompletedMigration
{
/// <summary>
/// The name of the migration.
/// </summary>
/// <remarks>
/// Do not use the user-friendly name when setting this.
/// </remarks>
[Key]
public string MigrationName { get; set; }
/// <summary>
/// The moment the migration was ran.
/// </summary>
public DateTime RanAt { get; set; }
}

View file

@ -0,0 +1,20 @@
using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.StorableLists;
namespace LBPUnion.ProjectLighthouse.Administration.Maintenance.Commands;
public class FlushRedisCommand : ICommand
{
public string Name() => "Flush Redis";
public string[] Aliases() => new[] {
"flush", "flush-redis",
};
public string Arguments() => "";
public int RequiredArgs() => 0;
public async Task Run(string[] args, Logger logger)
{
await RedisDatabase.FlushAll();
}
}

View file

@ -1,7 +1,9 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations;
namespace LBPUnion.ProjectLighthouse.Administration.Maintenance; namespace LBPUnion.ProjectLighthouse.Administration.Maintenance;
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
public interface IMaintenanceJob public interface IMaintenanceJob
{ {
public Task Run(); public Task Run();

View file

@ -0,0 +1,20 @@
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace LBPUnion.ProjectLighthouse.Administration.Maintenance;
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
public interface IMigrationTask
{
/// <summary>
/// The user-friendly name of a migration.
/// </summary>
public string Name();
/// <summary>
/// Performs the migration.
/// </summary>
/// <param name="database">The Lighthouse database.</param>
/// <returns>True if successful, false if not.</returns>
internal Task<bool> Run(Database database);
}

View file

@ -1,9 +1,11 @@
#nullable enable #nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Logging.Loggers; using LBPUnion.ProjectLighthouse.Logging.Loggers;
@ -11,24 +13,16 @@ namespace LBPUnion.ProjectLighthouse.Administration.Maintenance;
public static class MaintenanceHelper public static class MaintenanceHelper
{ {
static MaintenanceHelper() static MaintenanceHelper()
{ {
Commands = getListOfInterfaceObjects<ICommand>(); Commands = getListOfInterfaceObjects<ICommand>();
MaintenanceJobs = getListOfInterfaceObjects<IMaintenanceJob>(); MaintenanceJobs = getListOfInterfaceObjects<IMaintenanceJob>();
MigrationTasks = getListOfInterfaceObjects<IMigrationTask>();
} }
public static List<ICommand> Commands { get; } public static List<ICommand> Commands { get; }
public static List<IMaintenanceJob> MaintenanceJobs { get; } public static List<IMaintenanceJob> MaintenanceJobs { get; }
public static List<IMigrationTask> MigrationTasks { 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()!;
}
public static async Task<List<LogLine>> RunCommand(string[] args) public static async Task<List<LogLine>> RunCommand(string[] args)
{ {
@ -66,11 +60,57 @@ public static class MaintenanceHelper
IMaintenanceJob? job = MaintenanceJobs.FirstOrDefault(j => j.GetType().Name == jobName); IMaintenanceJob? job = MaintenanceJobs.FirstOrDefault(j => j.GetType().Name == jobName);
if (job == null) throw new ArgumentNullException(); if (job == null) throw new ArgumentNullException();
await RunMaintenanceJob(job);
}
public static async Task RunMaintenanceJob(IMaintenanceJob job)
{
await job.Run(); await job.Run();
} }
public static async Task RunMigration(IMigrationTask migrationTask, Database? database = null)
{
database ??= new Database();
// Migrations should never be run twice.
Debug.Assert(!await database.CompletedMigrations.Has(m => m.MigrationName == migrationTask.GetType().Name));
Logger.Info($"Running migration task {migrationTask.Name()}", LogArea.Database);
bool success;
Exception? exception = null;
try
{
success = await migrationTask.Run(database);
}
catch(Exception e)
{
success = false;
exception = e;
}
if(!success)
{
Logger.Error($"Could not run migration {migrationTask.Name()}", LogArea.Database);
if (exception != null) Logger.Error(exception.ToDetailedException(), LogArea.Database);
return;
}
Logger.Success($"Successfully completed migration {migrationTask.Name()}", LogArea.Database);
CompletedMigration completedMigration = new()
{
MigrationName = migrationTask.GetType().Name,
RanAt = DateTime.Now,
};
database.CompletedMigrations.Add(completedMigration);
await database.SaveChangesAsync();
}
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()!;
}
} }

View file

@ -25,7 +25,7 @@ public class CleanupBrokenPhotosMaintenanceJob : IMaintenanceJob
bool largeHashIsInvalidFile = false; bool largeHashIsInvalidFile = false;
bool tooManyPhotoSubjects = false; bool tooManyPhotoSubjects = false;
bool duplicatePhotoSubjects = false; bool duplicatePhotoSubjects = false;
bool takenInTheFuture = true; bool takenInTheFuture = false;
// Checks should generally be ordered in least computationally expensive to most. // Checks should generally be ordered in least computationally expensive to most.

View file

@ -1,36 +0,0 @@
#nullable enable
using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Administration.Reports;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.PlayerData.Reviews;
namespace LBPUnion.ProjectLighthouse.Administration.Maintenance.MaintenanceJobs;
public class CleanupXmlInjection : IMaintenanceJob
{
private readonly Database database = new();
public string Name() => "Sanitize user content";
public string Description() => "Sanitizes all user-generated strings in levels, reviews, comments, users, and scores to prevent XML injection. Only needs to be run once.";
public async Task Run()
{
foreach (Slot slot in this.database.Slots) SanitizationHelper.SanitizeStringsInClass(slot);
foreach (Review review in this.database.Reviews) SanitizationHelper.SanitizeStringsInClass(review);
foreach (Comment comment in this.database.Comments) SanitizationHelper.SanitizeStringsInClass(comment);
foreach (Score score in this.database.Scores) SanitizationHelper.SanitizeStringsInClass(score);
foreach (User user in this.database.Users) SanitizationHelper.SanitizeStringsInClass(user);
foreach (Photo photo in this.database.Photos) SanitizationHelper.SanitizeStringsInClass(photo);
foreach (GriefReport report in this.database.Reports) SanitizationHelper.SanitizeStringsInClass(report);
await this.database.SaveChangesAsync();
}
}

View file

@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Helpers;
namespace LBPUnion.ProjectLighthouse.Administration.Maintenance.MigrationTasks;
public class CleanupXmlInjectionMigration : IMigrationTask
{
public string Name() => "Cleanup XML injections";
// Weird, but required. Thanks, hejlsberg.
async Task<bool> IMigrationTask.Run(Database database)
{
List<object> objsToBeSanitized = new();
// Store all the objects we need to sanitize in a list.
// The alternative here is to loop through every table, but thats a ton of code...
objsToBeSanitized.AddRange(database.Slots);
objsToBeSanitized.AddRange(database.Reviews);
objsToBeSanitized.AddRange(database.Comments);
objsToBeSanitized.AddRange(database.Scores);
objsToBeSanitized.AddRange(database.Users);
objsToBeSanitized.AddRange(database.Photos);
objsToBeSanitized.AddRange(database.Reports);
foreach (object obj in objsToBeSanitized) SanitizationHelper.SanitizeStringsInClass(obj);
await database.SaveChangesAsync();
return true;
}
}

View file

@ -0,0 +1,6 @@
namespace LBPUnion.ProjectLighthouse.Configuration.ConfigurationCategories;
public class CustomizationConfiguration
{
public string ServerName { get; set; } = "Project Lighthouse";
}

View file

@ -23,7 +23,7 @@ public class ServerConfiguration
// You can use an ObsoleteAttribute instead. Make sure you set it to error, though. // You can use an ObsoleteAttribute instead. Make sure you set it to error, though.
// //
// Thanks for listening~ // Thanks for listening~
public const int CurrentConfigVersion = 4; public const int CurrentConfigVersion = 5;
#region Meta #region Meta
@ -163,6 +163,9 @@ public class ServerConfiguration
#endregion #endregion
// TODO: Find a way to properly remove config options
// YamlDotNet hates that and it's fucking annoying.
// This seriously sucks. /rant
[Obsolete("Obsolete. Use the Website/GameApi/Api listen URLS instead.")] [Obsolete("Obsolete. Use the Website/GameApi/Api listen URLS instead.")]
public string ListenUrl { get; set; } = "http://localhost:10060"; public string ListenUrl { get; set; } = "http://localhost:10060";
@ -193,5 +196,5 @@ public class ServerConfiguration
public MailConfiguration Mail { get; set; } = new(); public MailConfiguration Mail { get; set; } = new();
public UserGeneratedContentLimitConfiguration UserGeneratedContentLimits { get; set; } = new(); public UserGeneratedContentLimitConfiguration UserGeneratedContentLimits { get; set; } = new();
public WebsiteConfiguration WebsiteConfiguration { get; set; } = new(); public WebsiteConfiguration WebsiteConfiguration { get; set; } = new();
public CustomizationConfiguration Customization { get; set; } = new();
} }

View file

@ -2,13 +2,12 @@
using System; using System;
using System.Linq; using System.Linq;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Types;
namespace LBPUnion.ProjectLighthouse.Configuration; namespace LBPUnion.ProjectLighthouse.Configuration;
public static class ServerStatics public static class ServerStatics
{ {
public const string ServerName = "ProjectLighthouse";
public const int PageSize = 20; public const int PageSize = 20;
public static bool DbConnected { public static bool DbConnected {
@ -25,11 +24,18 @@ public static class ServerStatics
} }
} }
// FIXME: This needs to go at some point.
public static bool IsUnitTesting => AppDomain.CurrentDomain.GetAssemblies().Any(assembly => assembly.FullName!.StartsWith("xunit")); public static bool IsUnitTesting => AppDomain.CurrentDomain.GetAssemblies().Any(assembly => assembly.FullName!.StartsWith("xunit"));
#if DEBUG #if DEBUG
public static readonly bool IsDebug = true; public const bool IsDebug = true;
#else #else
public static readonly bool IsDebug = false; public const bool IsDebug = false;
#endif #endif
/// <summary>
/// The servertype, determined on startup. Shouldn't be null unless very very early in startup.
/// </summary>
// The way of doing this is kinda weird, but it works.
public static ServerType ServerType;
} }

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Administration;
using LBPUnion.ProjectLighthouse.Administration.Reports; using LBPUnion.ProjectLighthouse.Administration.Reports;
using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
@ -21,6 +22,7 @@ namespace LBPUnion.ProjectLighthouse;
public class Database : DbContext public class Database : DbContext
{ {
public DbSet<CompletedMigration> CompletedMigrations { get; set; }
public DbSet<User> Users { get; set; } public DbSet<User> Users { get; set; }
public DbSet<Location> Locations { get; set; } public DbSet<Location> Locations { get; set; }
public DbSet<Slot> Slots { get; set; } public DbSet<Slot> Slots { get; set; }

View file

@ -1,4 +1,7 @@
using System;
using System.Linq; using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Levels; using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Reviews; using LBPUnion.ProjectLighthouse.PlayerData.Reviews;
@ -52,4 +55,7 @@ public static class DatabaseExtensions
return query; return query;
} }
public static async Task<bool> Has<T>(this IQueryable<T> queryable, Expression<Func<T, bool>> predicate) =>
await queryable.FirstOrDefaultAsync(predicate) != null;
} }

View file

@ -31,8 +31,8 @@ public static class VersionHelper
{ {
Logger.Error Logger.Error
( (
"Project Lighthouse was built incorrectly. Please make sure git is available when building. " + "Project Lighthouse was built incorrectly. Please make sure git is available when building.",
"Because of this, you will not be notified of updates.", // "Because of this, you will not be notified of updates.",
LogArea.Startup LogArea.Startup
); );
CommitHash = "invalid"; CommitHash = "invalid";
@ -54,7 +54,7 @@ public static class VersionHelper
public static string CommitHash { get; set; } public static string CommitHash { get; set; }
public static string Branch { get; set; } public static string Branch { get; set; }
public static string FullVersion => $"{ServerStatics.ServerName} {Branch}@{CommitHash} {Build}"; public static string FullVersion => $"Project Lighthouse {Branch}@{CommitHash} {Build} ({ServerConfiguration.Instance.Customization.ServerName})";
public static bool IsDirty => CommitHash.EndsWith("-dirty") || CommitsOutOfDate != 1 || CommitHash == "invalid" || Branch == "invalid"; public static bool IsDirty => CommitHash.EndsWith("-dirty") || CommitsOutOfDate != 1 || CommitHash == "invalid" || Branch == "invalid";
public static int CommitsOutOfDate { get; set; } public static int CommitsOutOfDate { get; set; }
public static bool CanCheckForUpdates { get; set; } public static bool CanCheckForUpdates { get; set; }

View file

@ -11,7 +11,8 @@ public class ConsoleLogger : ILogger
foreach (string line in logLine.Message.Split('\n')) foreach (string line in logLine.Message.Split('\n'))
{ {
// The following is scuffed. Beware~ // The following is scuffed.
// Beware~
// Write the level! [Success] // Write the level! [Success]
Console.ForegroundColor = ConsoleColor.White; Console.ForegroundColor = ConsoleColor.White;

View file

@ -1,11 +1,11 @@
using System; using System;
using System.IO; using System.IO;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Files; using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Helpers;
namespace LBPUnion.ProjectLighthouse.Logging.Loggers; namespace LBPUnion.ProjectLighthouse.Logging.Loggers;
public class LighthouseFileLogger : ILogger public class FileLogger : ILogger
{ {
private static readonly string logsDirectory = Path.Combine(Environment.CurrentDirectory, "logs"); private static readonly string logsDirectory = Path.Combine(Environment.CurrentDirectory, "logs");
@ -13,8 +13,8 @@ public class LighthouseFileLogger : ILogger
{ {
FileHelper.EnsureDirectoryCreated(logsDirectory); FileHelper.EnsureDirectoryCreated(logsDirectory);
string contentFile = $"[{line.Level}] <{line.Trace.Name}:{line.Trace.Section}> {line.Message}\n"; string contentFile = $"[{ServerStatics.ServerType}] [{line.Level}] <{line.Trace.Name}:{line.Trace.Section}> {line.Message}\n";
string contentAll = $"[{line.Area}:{line.Level}] <{line.Trace.Name}:{line.Trace.Section}> {line.Message}\n"; string contentAll = $"[{ServerStatics.ServerType}] [{line.Area}:{line.Level}] <{line.Trace.Name}:{line.Trace.Section}> {line.Message}\n";
try try
{ {

View file

@ -16,7 +16,8 @@ namespace LBPUnion.ProjectLighthouse.Match.Rooms;
public class RoomHelper public class RoomHelper
{ {
public static readonly StorableList<Room> Rooms = RoomStore.GetRooms(); public static readonly object RoomLock = new();
public static StorableList<Room> Rooms => RoomStore.GetRooms();
public static void StartCleanupThread() public static void StartCleanupThread()
{ {
@ -162,7 +163,7 @@ public class RoomHelper
}; };
CleanupRooms(room.HostId, room); CleanupRooms(room.HostId, room);
lock(Rooms) Rooms.Add(room); lock(RoomLock) Rooms.Add(room);
Logger.Info($"Created room (id: {room.RoomId}) for host {room.HostId}", LogArea.Match); Logger.Info($"Created room (id: {room.RoomId}) for host {room.HostId}", LogArea.Match);
return room; return room;
@ -193,7 +194,7 @@ public class RoomHelper
[SuppressMessage("ReSharper", "InvertIf")] [SuppressMessage("ReSharper", "InvertIf")]
public static void CleanupRooms(int? hostId = null, Room? newRoom = null, Database? database = null) public static void CleanupRooms(int? hostId = null, Room? newRoom = null, Database? database = null)
{ {
lock(Rooms) lock(RoomLock)
{ {
int roomCountBeforeCleanup = Rooms.Count(); int roomCountBeforeCleanup = Rooms.Count();

View file

@ -0,0 +1,37 @@
using System;
using LBPUnion.ProjectLighthouse;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ProjectLighthouse.Migrations
{
[DbContext(typeof(Database))]
[Migration("20220610061641_AddCompletedMigrations")]
public class AddCompletedMigrations : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "CompletedMigrations",
columns: table => new
{
MigrationName = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
RanAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_CompletedMigrations", x => x.MigrationName);
})
.Annotation("MySql:CharSet", "utf8mb4");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "CompletedMigrations");
}
}
}

View file

@ -1,4 +1,5 @@
// <auto-generated /> // <auto-generated />
using System;
using LBPUnion.ProjectLighthouse; using LBPUnion.ProjectLighthouse;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
@ -18,6 +19,19 @@ namespace ProjectLighthouse.Migrations
.HasAnnotation("ProductVersion", "6.0.5") .HasAnnotation("ProductVersion", "6.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 64); .HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Administration.CompletedMigration", b =>
{
b.Property<string>("MigrationName")
.HasColumnType("varchar(255)");
b.Property<DateTime>("RanAt")
.HasColumnType("datetime(6)");
b.HasKey("MigrationName");
b.ToTable("CompletedMigrations");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Administration.Reports.GriefReport", b => modelBuilder.Entity("LBPUnion.ProjectLighthouse.Administration.Reports.GriefReport", b =>
{ {
b.Property<int>("ReportId") b.Property<int>("ReportId")
@ -581,15 +595,16 @@ namespace ProjectLighthouse.Migrations
b.Property<int>("AdminGrantedSlots") b.Property<int>("AdminGrantedSlots")
.HasColumnType("int"); .HasColumnType("int");
b.Property<bool>("Banned")
.HasColumnType("tinyint(1)");
b.Property<string>("BannedReason") b.Property<string>("BannedReason")
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<string>("Biography") b.Property<string>("Biography")
.IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<string>("BooHash") b.Property<string>("BooHash")
.IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<string>("EmailAddress") b.Property<string>("EmailAddress")
@ -602,48 +617,39 @@ namespace ProjectLighthouse.Migrations
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("IconHash") b.Property<string>("IconHash")
.IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<bool>("IsAdmin")
.HasColumnType("tinyint(1)");
b.Property<int>("LocationId") b.Property<int>("LocationId")
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("MehHash") b.Property<string>("MehHash")
.IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<string>("Password") b.Property<string>("Password")
.IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<bool>("PasswordResetRequired") b.Property<bool>("PasswordResetRequired")
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
b.Property<int>("PermissionLevel")
.HasColumnType("int");
b.Property<string>("Pins") b.Property<string>("Pins")
.IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<string>("PlanetHashLBP2") b.Property<string>("PlanetHashLBP2")
.IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<string>("PlanetHashLBP3") b.Property<string>("PlanetHashLBP3")
.IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<string>("PlanetHashLBPVita") b.Property<string>("PlanetHashLBPVita")
.IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<string>("Username") b.Property<string>("Username")
.IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<string>("YayHash") b.Property<string>("YayHash")
.IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext");
b.HasKey("UserId"); b.HasKey("UserId");

View file

@ -15,9 +15,12 @@ public class LoginResult
public string AuthTicket { get; set; } public string AuthTicket { get; set; }
[XmlElement("lbpEnvVer")] [XmlElement("lbpEnvVer")]
public string LbpEnvVer { get; set; } public string ServerBrand { get; set; }
public string Serialize() public string Serialize()
=> LbpSerializer.Elements => LbpSerializer.Elements
(new KeyValuePair<string, object>("authTicket", this.AuthTicket), new KeyValuePair<string, object>("lbpEnvVer", this.LbpEnvVer)); (
new KeyValuePair<string, object>("authTicket", this.AuthTicket),
new KeyValuePair<string, object>("lbpEnvVer", this.ServerBrand)
);
} }

View file

@ -11,7 +11,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" /> <PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="DDSReader" Version="1.0.8-pre" /> <PackageReference Include="DDSReader" Version="1.0.8-pre" />
<PackageReference Include="Discord.Net.Webhook" Version="3.6.1" /> <PackageReference Include="Discord.Net.Webhook" Version="3.7.2" />
<PackageReference Include="InfluxDB.Client" Version="4.2.0" /> <PackageReference Include="InfluxDB.Client" Version="4.2.0" />
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" /> <PackageReference Include="JetBrains.Annotations" Version="2022.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.5" /> <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.5" />
@ -50,10 +50,6 @@
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Remove="Migrations\20220522192158_SwitchToPermissionLevels.Designer.cs" />
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent"> <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="git describe --long --always --dirty --exclude=\* --abbrev=8 &gt; &quot;$(ProjectDir)/gitVersion.txt&quot;" /> <Exec Command="git describe --long --always --dirty --exclude=\* --abbrev=8 &gt; &quot;$(ProjectDir)/gitVersion.txt&quot;" />
<Exec Command="git branch --show-current &gt; &quot;$(ProjectDir)/gitBranch.txt&quot;" /> <Exec Command="git branch --show-current &gt; &quot;$(ProjectDir)/gitBranch.txt&quot;" />

View file

@ -20,7 +20,6 @@ public class DebugWarmupLifetime : IHostLifetime
private CancellationTokenRegistration applicationStartedRegistration; private CancellationTokenRegistration applicationStartedRegistration;
private readonly ConsoleLifetime consoleLifetime; private readonly ConsoleLifetime consoleLifetime;
public static ServerType ServerType;
public DebugWarmupLifetime public DebugWarmupLifetime
( (
@ -39,7 +38,7 @@ public class DebugWarmupLifetime : IHostLifetime
{ {
using HttpClient client = new(); using HttpClient client = new();
string url = ServerType switch string url = ServerStatics.ServerType switch
{ {
ServerType.GameServer => ServerConfiguration.Instance.GameApiListenUrl, ServerType.GameServer => ServerConfiguration.Instance.GameApiListenUrl,
ServerType.Website => ServerConfiguration.Instance.WebsiteListenUrl, ServerType.Website => ServerConfiguration.Instance.WebsiteListenUrl,

View file

@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using LBPUnion.ProjectLighthouse.Administration;
using LBPUnion.ProjectLighthouse.Administration.Maintenance; using LBPUnion.ProjectLighthouse.Administration.Maintenance;
using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Extensions;
@ -9,6 +11,7 @@ using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Logging.Loggers; using LBPUnion.ProjectLighthouse.Logging.Loggers;
using LBPUnion.ProjectLighthouse.Match.Rooms; using LBPUnion.ProjectLighthouse.Match.Rooms;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Startup; using LBPUnion.ProjectLighthouse.Startup;
using LBPUnion.ProjectLighthouse.StorableLists; using LBPUnion.ProjectLighthouse.StorableLists;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
@ -24,18 +27,16 @@ public static class StartupTasks
Stopwatch stopwatch = new(); Stopwatch stopwatch = new();
stopwatch.Start(); stopwatch.Start();
#if DEBUG ServerStatics.ServerType = serverType;
DebugWarmupLifetime.ServerType = serverType;
#endif
// Setup logging // Setup logging
Logger.Instance.AddLogger(new ConsoleLogger()); Logger.Instance.AddLogger(new ConsoleLogger());
Logger.Instance.AddLogger(new LighthouseFileLogger()); Logger.Instance.AddLogger(new FileLogger());
Logger.Info($"Welcome to the Project Lighthouse {serverType.ToString()}!", LogArea.Startup); Logger.Info($"Welcome to the Project Lighthouse {serverType.ToString()}!", LogArea.Startup);
Logger.Info($"You are running version {VersionHelper.FullVersion}", LogArea.Startup); Logger.Info($"You are running version {VersionHelper.FullVersion}", LogArea.Startup);
// Referencing ServerSettings.Instance here loads the config, see ServerSettings.cs for more information // Referencing ServerConfiguration.Instance here loads the config, see ServerConfiguration.cs for more information
Logger.Success("Loaded config file version " + ServerConfiguration.Instance.ConfigVersion, LogArea.Startup); Logger.Success("Loaded config file version " + ServerConfiguration.Instance.ConfigVersion, LogArea.Startup);
Logger.Info("Connecting to the database...", LogArea.Startup); Logger.Info("Connecting to the database...", LogArea.Startup);
@ -46,13 +47,15 @@ public static class StartupTasks
} }
else else
{ {
Logger.Success("Connected!", LogArea.Startup); Logger.Success("Connected to the database!", LogArea.Startup);
} }
if (!dbConnected) Environment.Exit(1); if (!dbConnected) Environment.Exit(1);
using Database database = new(); using Database database = new();
Logger.Info("Migrating database...", LogArea.Database); #if !DEBUG
if(serverType == ServerType.GameServer)
#endif
migrateDatabase(database); migrateDatabase(database);
if (ServerConfiguration.Instance.InfluxDB.InfluxEnabled) if (ServerConfiguration.Instance.InfluxDB.InfluxEnabled)
@ -84,24 +87,65 @@ public static class StartupTasks
FileHelper.ConvertAllTexturesToPng(); FileHelper.ConvertAllTexturesToPng();
} }
Logger.Info("Starting room cleanup thread...", LogArea.Startup);
RoomHelper.StartCleanupThread();
Logger.Info("Initializing Redis...", LogArea.Startup); Logger.Info("Initializing Redis...", LogArea.Startup);
RedisDatabase.Initialize().Wait(); RedisDatabase.Initialize().Wait();
if (serverType == ServerType.GameServer)
{
Logger.Info("Starting room cleanup thread...", LogArea.Startup);
RoomHelper.StartCleanupThread();
}
// Create admin user if no users exist
if (serverType == ServerType.Website && database.Users.CountAsync().Result == 0)
{
const string passwordClear = "lighthouse";
string password = CryptoHelper.BCryptHash(CryptoHelper.Sha256Hash(passwordClear));
User admin = database.CreateUser("admin", password).Result;
admin.IsAdmin = true;
admin.PasswordResetRequired = true;
database.SaveChanges();
Logger.Success("No users were found, so an admin user was created. " +
$"The username is 'admin' and the password is '{passwordClear}'.", LogArea.Startup);
}
stopwatch.Stop(); stopwatch.Stop();
Logger.Success($"Ready! Startup took {stopwatch.ElapsedMilliseconds}ms. Passing off control to ASP.NET...", LogArea.Startup); Logger.Success($"Ready! Startup took {stopwatch.ElapsedMilliseconds}ms. Passing off control to ASP.NET...", LogArea.Startup);
} }
private static void migrateDatabase(Database database) private static void migrateDatabase(Database database)
{ {
Logger.Info("Migrating database...", LogArea.Database);
Stopwatch totalStopwatch = new();
Stopwatch stopwatch = new(); Stopwatch stopwatch = new();
totalStopwatch.Start();
stopwatch.Start(); stopwatch.Start();
database.Database.MigrateAsync().Wait(); database.Database.MigrateAsync().Wait();
stopwatch.Stop();
Logger.Success($"Structure migration took {stopwatch.ElapsedMilliseconds}ms.", LogArea.Database);
stopwatch.Reset();
stopwatch.Start();
List<CompletedMigration> completedMigrations = database.CompletedMigrations.ToList();
List<IMigrationTask> migrationsToRun = MaintenanceHelper.MigrationTasks
.Where(migrationTask => !completedMigrations
.Select(m => m.MigrationName)
.Contains(migrationTask.GetType().Name)
).ToList();
foreach (IMigrationTask migrationTask in migrationsToRun)
{
MaintenanceHelper.RunMigration(migrationTask, database).Wait();
}
stopwatch.Stop(); stopwatch.Stop();
Logger.Success($"Migration took {stopwatch.ElapsedMilliseconds}ms.", LogArea.Database); totalStopwatch.Stop();
Logger.Success($"Extra migration tasks took {stopwatch.ElapsedMilliseconds}ms.", LogArea.Database);
Logger.Success($"Total migration took {totalStopwatch.ElapsedMilliseconds}ms.", LogArea.Database);
} }
} }

View file

@ -39,8 +39,7 @@ public static class RedisDatabase
return; return;
} }
await connection.RecreateIndexAsync(typeof(Room)); await createIndexes(connection);
await connection.RecreateIndexAsync(typeof(UserFriendData));
} }
catch(Exception e) catch(Exception e)
{ {
@ -52,6 +51,20 @@ public static class RedisDatabase
Logger.Success("Initialized Redis.", LogArea.Redis); Logger.Success("Initialized Redis.", LogArea.Redis);
} }
public static async Task FlushAll()
{
IRedisConnection connection = getConnection();
await connection.ExecuteAsync("FLUSHALL");
await createIndexes(connection);
}
private static async Task createIndexes(IRedisConnection connection)
{
await connection.RecreateIndexAsync(typeof(Room));
await connection.RecreateIndexAsync(typeof(UserFriendData));
}
private static IRedisConnection getConnection() private static IRedisConnection getConnection()
{ {
Logger.Debug("Getting a Redis connection", LogArea.Redis); Logger.Debug("Getting a Redis connection", LogArea.Redis);

View file

@ -1,4 +1,9 @@
#!/bin/bash #!/bin/bash
# Developer script to create EntityFramework database migrations
#
# $1: Name of the migration, e.g. SwitchToPermissionLevels
# Invoked manually
export LIGHTHOUSE_DB_CONNECTION_STRING='server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse' export LIGHTHOUSE_DB_CONNECTION_STRING='server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse'
dotnet ef migrations add "$1" --project ../ProjectLighthouse dotnet ef migrations add "$1" --project ../ProjectLighthouse