Implement property dependency injection for the website (#806)

* Remove most non DI usages of DbContext

* Optimize website queries and refactor startup to use top level statements

* Remove unused functions in UserEntity and SlotEntity

* Optimize LBP1 LevelTags
This commit is contained in:
Josh 2023-06-20 00:02:24 -05:00 committed by GitHub
parent 727dd4e903
commit e43397ac6a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 333 additions and 430 deletions

View file

@ -1,36 +1,22 @@
using LBPUnion.ProjectLighthouse;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Logging.Loggers.AspNet;
using LBPUnion.ProjectLighthouse.Servers.API.Startup;
using LBPUnion.ProjectLighthouse.Types.Misc;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace LBPUnion.ProjectLighthouse.Servers.API;
await StartupTasks.Run(ServerType.Api);
public static class Program
IHostBuilder builder = Host.CreateDefaultBuilder();
builder.ConfigureWebHostDefaults(webBuilder =>
{
public static void Main(string[] args)
{
StartupTasks.Run(args, ServerType.Api);
webBuilder.UseStartup<ApiStartup>();
webBuilder.UseUrls(ServerConfiguration.Instance.ApiListenUrl);
});
CreateHostBuilder(args).Build().Run();
}
builder.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddProvider(new AspNetToLighthouseLoggerProvider());
});
public static IHostBuilder CreateHostBuilder(string[] args)
=> Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults
(
webBuilder =>
{
webBuilder.UseStartup<ApiStartup>();
webBuilder.UseUrls(ServerConfiguration.Instance.ApiListenUrl);
}
)
.ConfigureLogging
(
logging =>
{
logging.ClearProviders();
logging.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, AspNetToLighthouseLoggerProvider>());
}
);
}
await builder.Build().RunAsync();

View file

@ -68,7 +68,7 @@ public struct ApiSlot
PlaysUnique = slot.PlaysUnique,
PlaysComplete = slot.PlaysComplete,
CommentsEnabled = slot.CommentsEnabled,
AverageRating = slot.RatingLBP1,
AverageRating = context.RatedLevels.Where(r => r.SlotId == slot.SlotId).Average(r => (double?)r.RatingLBP1) ?? 3.0,
LevelType = slot.LevelType,
};
}

View file

@ -98,17 +98,22 @@ public class MatchController : ControllerBase
case FindBestRoom diveInData when MatchHelper.UserLocations.Count > 1:
#endif
{
FindBestRoomResponse? response = RoomHelper.FindBestRoom
(user, token.GameVersion, diveInData.RoomSlot, token.Platform, token.UserLocation);
FindBestRoomResponse? response = RoomHelper.FindBestRoom(this.database,
user,
token.GameVersion,
diveInData.RoomSlot,
token.Platform,
token.UserLocation);
if (response == null) return this.NotFound();
string serialized = JsonSerializer.Serialize(response, typeof(FindBestRoomResponse));
foreach (Player player in response.Players) MatchHelper.AddUserRecentlyDivedIn(user.UserId, player.User.UserId);
foreach (Player player in response.Players)
MatchHelper.AddUserRecentlyDivedIn(user.UserId, player.User.UserId);
return this.Ok($"[{{\"StatusCode\":200}},{serialized}]");
}
case CreateRoom createRoom when MatchHelper.UserLocations.Count >= 1:
case CreateRoom createRoom when !MatchHelper.UserLocations.IsEmpty:
{
List<int> users = new();
foreach (string playerUsername in createRoom.Players)
@ -139,9 +144,8 @@ public class MatchController : ControllerBase
}
room.PlayerIds = users.Select(u => u.UserId).ToList();
await RoomHelper.CleanupRooms(null, room);
await RoomHelper.CleanupRooms(this.database, null, room);
}
break;
}
}

View file

@ -34,17 +34,17 @@ public class PhotosController : ControllerBase
[HttpPost("uploadPhoto")]
public async Task<IActionResult> UploadPhoto()
{
UserEntity? user = await this.database.UserFromGameToken(this.GetToken());
if (user == null) return this.Forbid();
GameTokenEntity token = this.GetToken();
if (user.GetUploadedPhotoCount(this.database) >= ServerConfiguration.Instance.UserGeneratedContentLimits.PhotosQuota) return this.BadRequest();
int photoCount = await this.database.Photos.CountAsync(p => p.CreatorId == token.UserId);
if (photoCount >= ServerConfiguration.Instance.UserGeneratedContentLimits.PhotosQuota) return this.BadRequest();
GamePhoto? photo = await this.DeserializeBody<GamePhoto>();
if (photo == null) return this.BadRequest();
foreach (PhotoEntity p in this.database.Photos.Where(p => p.CreatorId == user.UserId))
foreach (PhotoEntity p in this.database.Photos.Where(p => p.CreatorId == token.UserId))
{
if (p.LargeHash == photo.LargeHash) return this.Ok(); // photo already uplaoded
if (p.LargeHash == photo.LargeHash) return this.Ok(); // photo already uploaded
if (p.MediumHash == photo.MediumHash) return this.Ok();
if (p.SmallHash == photo.SmallHash) return this.Ok();
if (p.PlanHash == photo.PlanHash) return this.Ok();
@ -52,8 +52,7 @@ public class PhotosController : ControllerBase
PhotoEntity photoEntity = new()
{
CreatorId = user.UserId,
Creator = user,
CreatorId = token.UserId,
SmallHash = photo.SmallHash,
MediumHash = photo.MediumHash,
LargeHash = photo.LargeHash,
@ -145,12 +144,14 @@ public class PhotosController : ControllerBase
await this.database.SaveChangesAsync();
string username = await this.database.UsernameFromGameToken(token);
await WebhookHelper.SendWebhook
(
new EmbedBuilder
{
Title = "New photo uploaded!",
Description = $"{user.Username} uploaded a new photo.",
Description = $"{username} uploaded a new photo.",
ImageUrl = $"{ServerConfiguration.Instance.ExternalUrl}/gameAssets/{photo.LargeHash}",
Color = WebhookHelper.GetEmbedColor(),
}

View file

@ -12,7 +12,6 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Filter;
using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Matchmaking.Rooms;
using LBPUnion.ProjectLighthouse.Types.Misc;
using LBPUnion.ProjectLighthouse.Types.Serialization;
using LBPUnion.ProjectLighthouse.Types.Users;

View file

@ -1,36 +1,22 @@
using LBPUnion.ProjectLighthouse;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Logging.Loggers.AspNet;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Startup;
using LBPUnion.ProjectLighthouse.Types.Misc;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer;
await StartupTasks.Run(ServerType.GameServer);
public static class Program
IHostBuilder builder = Host.CreateDefaultBuilder();
builder.ConfigureWebHostDefaults(webBuilder =>
{
public static void Main(string[] args)
{
StartupTasks.Run(args, ServerType.GameServer);
webBuilder.UseStartup<GameServerStartup>();
webBuilder.UseUrls(ServerConfiguration.Instance.GameApiListenUrl);
});
CreateHostBuilder(args).Build().Run();
}
builder.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddProvider(new AspNetToLighthouseLoggerProvider());
});
public static IHostBuilder CreateHostBuilder(string[] args)
=> Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults
(
webBuilder =>
{
webBuilder.UseStartup<GameServerStartup>();
webBuilder.UseUrls(ServerConfiguration.Instance.GameApiListenUrl);
}
)
.ConfigureLogging
(
logging =>
{
logging.ClearProviders();
logging.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, AspNetToLighthouseLoggerProvider>());
}
);
}
await builder.Build().RunAsync();

View file

@ -113,6 +113,5 @@ public class GameServerStartup
app.UseAuthorization();
app.UseEndpoints(endpoints => endpoints.MapControllers());
app.UseEndpoints(endpoints => endpoints.MapRazorPages());
}
}

View file

@ -8,14 +8,12 @@ using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
using LBPUnion.ProjectLighthouse.Servers.Website.Types;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Logging;
using LBPUnion.ProjectLighthouse.Types.Maintenance;
using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Admin;
public class AdminPanelPage : BaseLayout
{
public List<ICommand> Commands = MaintenanceHelper.Commands;
public AdminPanelPage(DatabaseContext database) : base(database)
{ }
@ -40,7 +38,7 @@ public class AdminPanelPage : BaseLayout
args = command + " " + args;
string[] split = args.Split(" ");
List<LogLine> runCommand = await MaintenanceHelper.RunCommand(split);
List<LogLine> runCommand = await MaintenanceHelper.RunCommand(this.HttpContext.RequestServices, split);
return this.Redirect($"~/admin?log={CryptoHelper.ToBase64(runCommand.ToLogString())}");
}

View file

@ -1,10 +1,12 @@
@page "/debug/roomVisualizer"
@using LBPUnion.ProjectLighthouse.Database
@using LBPUnion.ProjectLighthouse.Extensions
@using LBPUnion.ProjectLighthouse.Helpers
@using LBPUnion.ProjectLighthouse.Types.Entities.Profile
@using LBPUnion.ProjectLighthouse.Types.Matchmaking.Rooms
@using LBPUnion.ProjectLighthouse.Types.Users
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Debug.RoomVisualizerPage
@inject DatabaseContext Database
@{
Layout = "Layouts/BaseLayout";
@ -59,7 +61,7 @@
#nullable enable
if (version == GameVersion.LittleBigPlanet1 || version == GameVersion.LittleBigPlanetPSP || version == GameVersion.Unknown) continue;
FindBestRoomResponse? response = RoomHelper.FindBestRoom(null, version, null, null, null);
FindBestRoomResponse? response = RoomHelper.FindBestRoom(Database, null, version, null, null, null);
string text = response == null ? "No room found." : "Room " + response.RoomId;
<p><b>Best room for @version.ToPrettyString()</b>: @text</p>

View file

@ -29,7 +29,7 @@ public class BaseLayout : PageModel
public BaseLayout(DatabaseContext database)
{
this.Database = database;
this.NavigationItems.Add(new PageNavigationItem(BaseLayoutStrings.HeaderUsers, "/users/0", "user friends"));
this.NavigationItems.Add(new PageNavigationItem(BaseLayoutStrings.HeaderPhotos, "/photos/0", "camera"));
this.NavigationItems.Add(new PageNavigationItem(BaseLayoutStrings.HeaderSlots, "/slots/0", "globe americas"));

View file

@ -1,9 +1,11 @@
@using System.Web
@using System.IO
@using LBPUnion.ProjectLighthouse.Database
@using LBPUnion.ProjectLighthouse.Localization
@using LBPUnion.ProjectLighthouse.Servers.Website.Extensions
@using LBPUnion.ProjectLighthouse.Types.Entities.Interaction
@using LBPUnion.ProjectLighthouse.Types.Entities.Profile
@inject DatabaseContext Database
@{
string language = (string?)ViewData["Language"] ?? LocalizationManager.DefaultLang;
@ -50,7 +52,7 @@
int yourThumb = commentAndReaction.Value?.Rating ?? 0;
DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(comment.Timestamp / 1000).ToLocalTime();
StringWriter messageWriter = new();
HttpUtility.HtmlDecode(comment.GetCommentMessage(), messageWriter);
HttpUtility.HtmlDecode(comment.GetCommentMessage(Database), messageWriter);
string decodedMessage = messageWriter.ToString();
string? url = Url.RouteUrl(ViewContext.RouteData.Values);

View file

@ -1,14 +1,15 @@
@using LBPUnion.ProjectLighthouse.Database
@using LBPUnion.ProjectLighthouse.Localization
@using LBPUnion.ProjectLighthouse.Servers.Website.Extensions
@model LBPUnion.ProjectLighthouse.Types.Entities.Profile.UserEntity
@using LBPUnion.ProjectLighthouse.Types.Entities.Profile
@model UserEntity
@inject DatabaseContext Database
@{
string language = (string?)ViewData["Language"] ?? LocalizationManager.DefaultLang;
string timeZone = (string?)ViewData["TimeZone"] ?? TimeZoneInfo.Local.Id;
bool includeStatus = (bool?)ViewData["IncludeStatus"] ?? false;
await using DatabaseContext database = DatabaseContext.CreateNewInstance();
string userStatus = includeStatus ? Model.GetStatus(database).ToTranslatedString(language, timeZone) : "";
string userStatus = includeStatus ? Model.GetStatus(Database).ToTranslatedString(language, timeZone) : "";
}
<a href="/user/@Model.UserId" title="@userStatus" class="user-link">

View file

@ -4,9 +4,9 @@
@using LBPUnion.ProjectLighthouse.Types.Entities.Profile
@using LBPUnion.ProjectLighthouse.Types.Moderation.Cases
@model LBPUnion.ProjectLighthouse.Types.Entities.Moderation.ModerationCaseEntity
@inject DatabaseContext Database
@{
DatabaseContext database = DatabaseContext.CreateNewInstance();
string color = "blue";
string timeZone = (string?)ViewData["TimeZone"] ?? TimeZoneInfo.Local.Id;
@ -70,7 +70,7 @@
@if (Model.Type.AffectsLevel())
{
SlotEntity? slot = await Model.GetSlotAsync(database);
SlotEntity? slot = await Model.GetSlotAsync(Database);
if (slot != null)
{
<p><strong>Affected level:</strong> <a href="/slot/@slot.SlotId">@slot.Name</a></p>
@ -78,7 +78,7 @@
}
else if (Model.Type.AffectsUser())
{
UserEntity? user = await Model.GetUserAsync(database);
UserEntity? user = await Model.GetUserAsync(Database);
if (user != null)
{
<p><strong>Affected user:</strong> <a href="/user/@user.UserId">@user.Username</a></p>

View file

@ -7,12 +7,11 @@
@using LBPUnion.ProjectLighthouse.Types.Users
@using Microsoft.EntityFrameworkCore
@model LBPUnion.ProjectLighthouse.Types.Entities.Level.SlotEntity
@inject DatabaseContext Database
@{
UserEntity? user = (UserEntity?)ViewData["User"];
await using DatabaseContext database = DatabaseContext.CreateNewInstance();
string slotName = HttpUtility.HtmlDecode(string.IsNullOrEmpty(Model!.Name) ? "Unnamed Level" : Model.Name);
bool isMobile = (bool?)ViewData["IsMobile"] ?? false;
@ -26,8 +25,8 @@
if (user != null)
{
isQueued = await database.QueuedLevels.FirstOrDefaultAsync(h => h.SlotId == Model.SlotId && h.UserId == user.UserId) != null;
isHearted = await database.HeartedLevels.FirstOrDefaultAsync(h => h.SlotId == Model.SlotId && h.UserId == user.UserId) != null;
isQueued = await Database.QueuedLevels.AnyAsync(h => h.SlotId == Model.SlotId && h.UserId == user.UserId);
isHearted = await Database.HeartedLevels.AnyAsync(h => h.SlotId == Model.SlotId && h.UserId == user.UserId);
}
string callbackUrl = (string)ViewData["CallbackUrl"]!;
@ -81,15 +80,24 @@
}
<div class="cardStatsUnderTitle">
<i class="pink heart icon" title="Hearts"></i> <span>@Model.Hearts</span>
@{
var slotStats = await Database.Slots.Where(s => s.SlotId == Model.SlotId).Select(_ => new
{
HeartCount = Database.HeartedLevels.Count(h => h.SlotId == Model.SlotId),
ThumbsUp = Database.RatedLevels.Count(r => r.SlotId == Model.SlotId && r.Rating == 1),
ThumbsDown = Database.RatedLevels.Count(r => r.SlotId == Model.SlotId && r.Rating == -1),
RatingLbp1 = Database.RatedLevels.Where(r => r.SlotId == Model.SlotId).Average(r => (double?)r.RatingLBP1) ?? 3.0,
}).OrderBy(_ => 1).FirstAsync();
}
<i class="pink heart icon" title="Hearts"></i> <span>@slotStats.HeartCount</span>
<i class="blue play icon" title="Plays"></i> <span>@Model.PlaysUnique</span>
<i class="green thumbs up icon" title="Yays"></i> <span>@Model.Thumbsup</span>
<i class="red thumbs down icon" title="Boos"></i> <span>@Model.Thumbsdown</span>
<i class="green thumbs up icon" title="Yays"></i> <span>@slotStats.ThumbsUp</span>
<i class="red thumbs down icon" title="Boos"></i> <span>@slotStats.ThumbsDown</span>
@if (Model.GameVersion == GameVersion.LittleBigPlanet1)
{
<i class="yellow star icon" title="Star Rating"></i>
<span>@(Math.Round(Model.RatingLBP1 * 10) / 10)</span>
<span>@(Math.Round(slotStats.RatingLbp1 * 10) / 10)</span>
}
</div>
@if (Model.Creator != null)

View file

@ -1,7 +1,9 @@
@using LBPUnion.ProjectLighthouse.Database
@using LBPUnion.ProjectLighthouse.Localization
@using LBPUnion.ProjectLighthouse.Servers.Website.Extensions;
@using Microsoft.EntityFrameworkCore
@model LBPUnion.ProjectLighthouse.Types.Entities.Profile.UserEntity
@inject DatabaseContext Database
@{
bool showLink = (bool?)ViewData["ShowLink"] ?? false;
@ -42,21 +44,22 @@
</h1>
}
@{
await using DatabaseContext context = DatabaseContext.CreateNewInstance();
int hearts = Model.GetHeartCount(context);
int comments = Model.GetCommentCount(context);
int levels = Model.GetUsedSlotCount(context);
int photos = Model.GetUploadedPhotoCount(context);
var stats = await Database.Users.Where(u => u.UserId == Model.UserId).Select(_ => new
{
HeartCount = Database.HeartedProfiles.Count(hp => hp.HeartedUserId == Model.UserId),
CommentCount = Database.Comments.Count(c => c.PosterUserId == Model.UserId),
LevelCount = Database.Slots.Count(s => s.CreatorId == Model.UserId),
PhotoCount = Database.Photos.Count(p => p.CreatorId == Model.UserId),
}).OrderBy(_ => 1).FirstAsync();
}
<span>
<i>@Model.GetStatus(context).ToTranslatedString(language, timeZone)</i>
<i>@Model.GetStatus(Database).ToTranslatedString(language, timeZone)</i>
</span>
<div class="cardStatsUnderTitle">
<i class="pink heart icon" title="Hearts"></i> <span>@hearts</span>
<i class="blue comment icon" title="Comments"></i> <span>@comments</span>
<i class="green upload icon" title="Uploaded Levels"></i><span>@levels</span>
<i class="purple camera icon" title="Uploaded Photos"></i><span>@photos</span>
<i class="pink heart icon" title="Hearts"></i> <span>@stats.HeartCount</span>
<i class="blue comment icon" title="Comments"></i> <span>@stats.CommentCount</span>
<i class="green upload icon" title="Uploaded Levels"></i><span>@stats.LevelCount</span>
<i class="purple camera icon" title="Uploaded Photos"></i><span>@stats.PhotoCount</span>
</div>
</div>
</div>

View file

@ -10,7 +10,6 @@ namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
public class PhotosPage : BaseLayout
{
public int PageAmount;
public int PageNumber;

View file

@ -9,6 +9,7 @@
@using LBPUnion.ProjectLighthouse.Types.Moderation.Cases
@using LBPUnion.ProjectLighthouse.Types.Users
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.SlotPage
@inject DatabaseContext Database
@{
Layout = "Layouts/BaseLayout";
@ -55,7 +56,7 @@
string[] authorLabels;
if (Model.Slot?.GameVersion == GameVersion.LittleBigPlanet1)
{
authorLabels = Model.Slot.LevelTags(DatabaseContext.CreateNewInstance());
authorLabels = Model.Slot.LevelTags(Database);
}
else
{

View file

@ -1,38 +1,22 @@
#nullable enable
using LBPUnion.ProjectLighthouse;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Logging.Loggers.AspNet;
using LBPUnion.ProjectLighthouse.Servers.Website.Startup;
using LBPUnion.ProjectLighthouse.Types.Misc;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace LBPUnion.ProjectLighthouse.Servers.Website;
await StartupTasks.Run(ServerType.Website);
public static class Program
IHostBuilder builder = Host.CreateDefaultBuilder();
builder.ConfigureWebHostDefaults(webBuilder =>
{
public static void Main(string[] args)
{
StartupTasks.Run(args, ServerType.Website);
webBuilder.UseStartup<WebsiteStartup>();
webBuilder.UseUrls(ServerConfiguration.Instance.WebsiteListenUrl);
webBuilder.UseWebRoot("StaticFiles");
});
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args)
=> Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults
(
webBuilder =>
{
webBuilder.UseStartup<WebsiteStartup>();
webBuilder.UseWebRoot("StaticFiles");
webBuilder.UseUrls(ServerConfiguration.Instance.WebsiteListenUrl);
}
)
.ConfigureLogging
(
logging =>
{
logging.ClearProviders();
logging.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, AspNetToLighthouseLoggerProvider>());
}
);
}
builder.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddProvider(new AspNetToLighthouseLoggerProvider());
});
await builder.Build().RunAsync();

View file

@ -42,7 +42,7 @@ public class WebsiteStartup
{
// jank but works
string projectDir = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", ".."));
options.FileProviders.Clear();
options.FileProviders.Add(new PhysicalFileProvider(projectDir));
});
@ -131,7 +131,10 @@ public class WebsiteStartup
app.UseRequestLocalization();
app.UseEndpoints(endpoints => endpoints.MapControllers());
app.UseEndpoints(endpoints => endpoints.MapRazorPages());
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapRazorPages();
});
}
}

View file

@ -1,12 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using LBPUnion.ProjectLighthouse.Filter;
using LBPUnion.ProjectLighthouse.Filter;
using LBPUnion.ProjectLighthouse.Filter.Filters;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Extensions;
using LBPUnion.ProjectLighthouse.Tests.Helpers;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Filter;
using LBPUnion.ProjectLighthouse.Types.Users;
using Microsoft.AspNetCore.Http;
using Xunit;

View file

@ -8,9 +8,12 @@ namespace LBPUnion.ProjectLighthouse.Tests.Helpers;
public static class IntegrationHelper
{
private static readonly Lazy<bool> dbConnected = new(IsDbConnected);
private static readonly Lazy<bool> dbConnected = new(() =>
{
using DatabaseContext database = DatabaseContext.CreateNewInstance();
return database.Database.CanConnect();
});
private static bool IsDbConnected() => ServerStatics.DbConnected;
/// <summary>
/// Resets the database to a clean state and returns a new DatabaseContext.

View file

@ -1,23 +0,0 @@
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Database;
using Microsoft.EntityFrameworkCore;
using Xunit;
namespace LBPUnion.ProjectLighthouse.Tests.Integration;
public sealed class DatabaseFactAttribute : FactAttribute
{
private static readonly object migrateLock = new();
public DatabaseFactAttribute()
{
ServerConfiguration.Instance.DbConnectionString = "server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse";
if (!ServerStatics.DbConnected) this.Skip = "Database not available";
else
lock (migrateLock)
{
using DatabaseContext database = DatabaseContext.CreateNewInstance();
database.Database.Migrate();
}
}
}

View file

@ -6,31 +6,34 @@ using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Logging;
using LBPUnion.ProjectLighthouse.Types.Maintenance;
using Microsoft.Extensions.DependencyInjection;
namespace LBPUnion.ProjectLighthouse.Administration.Maintenance.Commands
namespace LBPUnion.ProjectLighthouse.Administration.Maintenance.Commands;
public class CreateApiKeyCommand : ICommand
{
public class CreateApiKeyCommand : ICommand
{
public string Name() => "Create API Key";
public string[] Aliases() => new[] { "createAPIKey", };
public string Arguments() => "<description>";
public int RequiredArgs() => 1;
public async Task Run(string[] args, Logger logger)
public string Name() => "Create API Key";
public string[] Aliases() =>
new[]
{
ApiKeyEntity key = new() { Description = args[0], };
if (string.IsNullOrWhiteSpace(key.Description))
{
key.Description = "<no description specified>";
}
key.Key = CryptoHelper.GenerateAuthToken();
key.Created = DateTime.Now;
DatabaseContext database = DatabaseContext.CreateNewInstance();
await database.APIKeys.AddAsync(key);
await database.SaveChangesAsync();
logger.LogSuccess($"The API key has been created (id: {key.Id}), however for security the token will only be shown once.", LogArea.Command);
logger.LogInfo($"Key: {key.Key}", LogArea.Command);
}
}
}
"createAPIKey",
};
public string Arguments() => "<description>";
public int RequiredArgs() => 1;
public async Task Run(IServiceProvider provider, string[] args, Logger logger)
{
ApiKeyEntity key = new() { Description = args[0], };
if (string.IsNullOrWhiteSpace(key.Description))
{
key.Description = "<no description specified>";
}
key.Key = CryptoHelper.GenerateAuthToken();
key.Created = DateTime.Now;
DatabaseContext database = provider.GetRequiredService<DatabaseContext>();
await database.APIKeys.AddAsync(key);
await database.SaveChangesAsync();
logger.LogSuccess($"The API key has been created (id: {key.Id}), however for security the token will only be shown once.", LogArea.Command);
logger.LogInfo($"Key: {key.Key}", LogArea.Command);
}
}

View file

@ -1,4 +1,5 @@
#nullable enable
using System;
using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Helpers;
@ -7,47 +8,46 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Logging;
using LBPUnion.ProjectLighthouse.Types.Maintenance;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace LBPUnion.ProjectLighthouse.Administration.Maintenance.Commands;
public class CreateUserCommand : ICommand
{
private readonly DatabaseContext _database = DatabaseContext.CreateNewInstance();
public string Name() => "Create New User";
public string[] Aliases() =>
new[]
{
"useradd", "adduser", "newuser", "createUser",
};
public string Arguments() => "<OnlineID> <Password>";
public int RequiredArgs() => 2;
public async Task Run(string[] args, Logger logger)
public async Task Run(IServiceProvider provider, string[] args, Logger logger)
{
DatabaseContext database = provider.GetRequiredService<DatabaseContext>();
string onlineId = args[0];
string password = args[1];
password = CryptoHelper.Sha256Hash(password);
UserEntity? user = await this._database.Users.FirstOrDefaultAsync(u => u.Username == onlineId);
UserEntity? user = await database.Users.FirstOrDefaultAsync(u => u.Username == onlineId);
if (user == null)
{
user = await this._database.CreateUser(onlineId, CryptoHelper.BCryptHash(password));
logger.LogSuccess($"Created user {user.UserId} with online ID (username) {user.Username} and the specified password.", LogArea.Command);
user.PasswordResetRequired = true;
logger.LogInfo("This user will need to reset their password when they log in.", LogArea.Command);
await this._database.SaveChangesAsync();
logger.LogInfo("Database changes saved.", LogArea.Command);
}
else
{
logger.LogError("A user with this username already exists.", LogArea.Command);
return;
}
user = await database.CreateUser(onlineId, CryptoHelper.BCryptHash(password));
logger.LogSuccess(
$"Created user {user.UserId} with online ID (username) {user.Username} and the specified password.",
LogArea.Command);
user.PasswordResetRequired = true;
logger.LogInfo("This user will need to reset their password when they log in.", LogArea.Command);
await database.SaveChangesAsync();
logger.LogInfo("Database changes saved.", LogArea.Command);
}
public string Name() => "Create New User";
public string[] Aliases()
=> new[]
{
"useradd", "adduser", "newuser", "createUser",
};
public string Arguments() => "<OnlineID> <Password>";
public int RequiredArgs() => 2;
}

View file

@ -7,12 +7,12 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Logging;
using LBPUnion.ProjectLighthouse.Types.Maintenance;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace LBPUnion.ProjectLighthouse.Administration.Maintenance.Commands;
public class DeleteUserCommand : ICommand
{
private readonly DatabaseContext database = DatabaseContext.CreateNewInstance();
public string Name() => "Delete User";
public string[] Aliases()
=> new[]
@ -21,22 +21,22 @@ public class DeleteUserCommand : ICommand
};
public string Arguments() => "<username/userId>";
public int RequiredArgs() => 1;
public async Task Run(string[] args, Logger logger)
public async Task Run(IServiceProvider provider, string[] args, Logger logger)
{
UserEntity? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username.Length > 0 && u.Username == args[0]);
DatabaseContext database = provider.GetRequiredService<DatabaseContext>();
UserEntity? user = await database.Users.FirstOrDefaultAsync(u => u.Username.Length > 0 && 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
{
user = await database.Users.FirstOrDefaultAsync(u => u.UserId == Convert.ToInt32(args[0]));
if (user == null)
{
logger.LogError($"Could not find user by parameter '{args[0]}'", LogArea.Command);
return;
}
}
await this.database.RemoveUser(user);
await database.RemoveUser(user);
logger.LogSuccess($"Successfully deleted user {user.Username}", LogArea.Command);
}
}

View file

@ -1,3 +1,4 @@
using System;
using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.StorableLists;
@ -8,14 +9,14 @@ namespace LBPUnion.ProjectLighthouse.Administration.Maintenance.Commands;
public class FlushRedisCommand : ICommand
{
public string Name() => "Flush Redis";
public string[] Aliases() => new[] {
"flush", "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();
}
public async Task Run(IServiceProvider provider, string[] args, Logger logger) => await RedisDatabase.FlushAll();
}

View file

@ -7,32 +7,12 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Logging;
using LBPUnion.ProjectLighthouse.Types.Maintenance;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace LBPUnion.ProjectLighthouse.Administration.Maintenance.Commands;
public class RenameUserCommand : ICommand
{
private readonly DatabaseContext database = DatabaseContext.CreateNewInstance();
public async Task Run(string[] args, Logger logger)
{
UserEntity? 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
{
logger.LogError($"Could not find user by parameter '{args[0]}'", LogArea.Command);
return;
}
user.Username = args[1];
await this.database.SaveChangesAsync();
logger.LogSuccess($"The username for user {user.Username} (id: {user.UserId}) has been changed.", LogArea.Command);
}
public string Name() => "Rename User";
public string[] Aliases()
=> new[]
@ -41,4 +21,26 @@ public class RenameUserCommand : ICommand
};
public string Arguments() => "<username/userId> <newUsername>";
public int RequiredArgs() => 2;
public async Task Run(IServiceProvider provider, string[] args, Logger logger)
{
DatabaseContext database = provider.GetRequiredService<DatabaseContext>();
UserEntity? user = await database.Users.FirstOrDefaultAsync(u => u.Username == args[0]);
if (user == null)
{
_ = int.TryParse(args[0], out int userId);
user = await database.Users.FirstOrDefaultAsync(u => u.UserId == userId);
if (user == null)
{
logger.LogError($"Could not find user by parameter '{args[0]}'", LogArea.Command);
return;
}
}
user.Username = args[1];
await database.SaveChangesAsync();
logger.LogSuccess($"The username for user {user.Username} (id: {user.UserId}) has been changed.",
LogArea.Command);
}
}

View file

@ -8,12 +8,12 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Logging;
using LBPUnion.ProjectLighthouse.Types.Maintenance;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace LBPUnion.ProjectLighthouse.Administration.Maintenance.Commands;
public class ResetPasswordCommand : ICommand
{
private readonly DatabaseContext database = DatabaseContext.CreateNewInstance();
public string Name() => "Reset Password";
public string[] Aliases()
=> new[]
@ -23,28 +23,28 @@ public class ResetPasswordCommand : ICommand
public string Arguments() => "<username/userId> <sha256/plaintext>";
public int RequiredArgs() => 2;
public async Task Run(string[] args, Logger logger)
public async Task Run(IServiceProvider provider, string[] args, Logger logger)
{
UserEntity? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == args[0]);
DatabaseContext database = provider.GetRequiredService<DatabaseContext>();
UserEntity? user = await 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
{
_ = int.TryParse(args[0], out int userId);
user = await database.Users.FirstOrDefaultAsync(u => u.UserId == userId);
if (user == null)
{
logger.LogError($"Could not find user by parameter '{args[0]}'", LogArea.Command);
return;
}
}
string password = args[1];
if (password.Length != 64) password = CryptoHelper.Sha256Hash(password);
user.Password = CryptoHelper.BCryptHash(password);
user.PasswordResetRequired = true;
await this.database.SaveChangesAsync();
await database.SaveChangesAsync();
logger.LogSuccess($"The password for user {user.Username} (id: {user.UserId}) has been reset.", LogArea.Command);
}

View file

@ -1,3 +1,4 @@
using System;
using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging;
@ -7,10 +8,6 @@ namespace LBPUnion.ProjectLighthouse.Administration.Maintenance.Commands;
public class TestWebhookCommand : ICommand
{
public async Task Run(string[] args, Logger logger)
{
await WebhookHelper.SendWebhook("Testing 123", "Someone is testing the Discord webhook from the admin panel.");
}
public string Name() => "Test Discord Webhook";
public string[] Aliases()
=> new[]
@ -19,4 +16,7 @@ public class TestWebhookCommand : ICommand
};
public string Arguments() => "";
public int RequiredArgs() => 0;
public async Task Run(IServiceProvider provider, string[] args, Logger logger) =>
await WebhookHelper.SendWebhook("Testing 123", "Someone is testing the Discord webhook from the admin panel.");
}

View file

@ -1,19 +1,18 @@
#nullable enable
using System;
using System.Linq;
using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Maintenance;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace LBPUnion.ProjectLighthouse.Administration.Maintenance.Commands;
public class WipeTokensForUserCommand : ICommand
{
private readonly DatabaseContext database = DatabaseContext.CreateNewInstance();
public string Name() => "Wipe tokens for user";
public string[] Aliases()
=> new[]
@ -22,25 +21,24 @@ public class WipeTokensForUserCommand : ICommand
};
public string Arguments() => "<username/userId>";
public int RequiredArgs() => 1;
public async Task Run(string[] args, Logger logger)
public async Task Run(IServiceProvider provider, string[] args, Logger logger)
{
UserEntity? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == args[0]);
DatabaseContext database = provider.GetRequiredService<DatabaseContext>();
UserEntity? user = await 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
{
_ = int.TryParse(args[0], out int userId);
user = await database.Users.FirstOrDefaultAsync(u => u.UserId == userId);
if (user == null)
{
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();
await database.GameTokens.RemoveWhere(t => t.UserId == user.UserId);
await database.WebTokens.RemoveWhere(t => t.UserId == user.UserId);
Console.WriteLine(@$"Deleted all tokens for {user.Username} (id: {user.UserId}).");
}

View file

@ -30,11 +30,11 @@ public static class MaintenanceHelper
public static List<IMigrationTask> MigrationTasks { get; }
public static List<IRepeatingTask> RepeatingTasks { get; }
public static async Task<List<LogLine>> RunCommand(string[] args)
public static async Task<List<LogLine>> RunCommand(IServiceProvider provider, 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.");
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();
@ -44,21 +44,32 @@ public static class MaintenanceHelper
InMemoryLogger memoryLogger = new();
logger.AddLogger(memoryLogger);
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)
ICommand? command = Commands
.Where(command =>
command.Aliases().Any(a => string.Equals(a, baseCmd, StringComparison.CurrentCultureIgnoreCase)))
.FirstOrDefault(command => args.Length >= command.RequiredArgs());
if (command == null)
{
logger.LogInfo("Running command " + command.Name(), LogArea.Command);
await command.Run(args, logger);
logger.LogError("Failed to find command", LogArea.Command);
logger.Flush();
return memoryLogger.Lines;
}
try
{
logger.LogInfo("Running command " + command.Name(), LogArea.Command);
logger.LogError("Command not found.", LogArea.Command);
logger.Flush();
return memoryLogger.Lines;
await command.Run(provider, args, logger);
logger.Flush();
return memoryLogger.Lines;
}
catch(Exception e)
{
logger.LogError($"Failed to run command: {e.Message}", LogArea.Command);
logger.LogError(e.ToDetailedException(), LogArea.Command);
logger.Flush();
return memoryLogger.Lines;
}
}
public static async Task RunMaintenanceJob(string jobName)
@ -69,9 +80,8 @@ public static class MaintenanceHelper
await job.Run();
}
public static async Task RunMigration(IMigrationTask migrationTask, DatabaseContext? database = null)
public static async Task RunMigration(DatabaseContext database, IMigrationTask migrationTask)
{
database ??= DatabaseContext.CreateNewInstance();
// Migrations should never be run twice.
Debug.Assert(!await database.CompletedMigrations.Has(m => m.MigrationName == migrationTask.GetType().Name));

View file

@ -14,14 +14,15 @@ namespace LBPUnion.ProjectLighthouse.Administration.Maintenance.MaintenanceJobs;
public class CleanupBrokenPhotosMaintenanceJob : IMaintenanceJob
{
private readonly DatabaseContext database = DatabaseContext.CreateNewInstance();
public string Name() => "Cleanup Broken Photos";
public string Description() => "Deletes all photos that have missing assets or invalid photo subjects.";
[SuppressMessage("ReSharper", "LoopCanBePartlyConvertedToQuery")]
public async Task Run()
{
foreach (PhotoEntity photo in this.database.Photos)
await using DatabaseContext database = DatabaseContext.CreateNewInstance();
foreach (PhotoEntity photo in database.Photos)
{
bool hashNullOrEmpty = false;
bool noHashesExist = false;
@ -44,8 +45,7 @@ public class CleanupBrokenPhotosMaintenanceJob : IMaintenanceJob
goto removePhoto;
}
hashNullOrEmpty = string.IsNullOrEmpty
(photo.LargeHash) ||
hashNullOrEmpty = string.IsNullOrEmpty(photo.LargeHash) ||
string.IsNullOrEmpty(photo.MediumHash) ||
string.IsNullOrEmpty(photo.SmallHash) ||
string.IsNullOrEmpty(photo.PlanHash);
@ -86,18 +86,18 @@ public class CleanupBrokenPhotosMaintenanceJob : IMaintenanceJob
Console.WriteLine
(
$"Removing photo (id: {photo.PhotoId}): " +
$"{nameof(hashNullOrEmpty)}: {hashNullOrEmpty}, " +
$"{nameof(noHashesExist)}: {noHashesExist}, " +
$"{nameof(largeHashIsInvalidFile)}: {largeHashIsInvalidFile}, " +
$"{nameof(tooManyPhotoSubjects)}: {tooManyPhotoSubjects}" +
$"{nameof(duplicatePhotoSubjects)}: {duplicatePhotoSubjects}" +
$"{nameof(takenInTheFuture)}: {takenInTheFuture}"
@$"Removing photo (id: {photo.PhotoId}): " +
@$"{nameof(hashNullOrEmpty)}: {hashNullOrEmpty}, " +
@$"{nameof(noHashesExist)}: {noHashesExist}, " +
@$"{nameof(largeHashIsInvalidFile)}: {largeHashIsInvalidFile}, " +
@$"{nameof(tooManyPhotoSubjects)}: {tooManyPhotoSubjects}" +
@$"{nameof(duplicatePhotoSubjects)}: {duplicatePhotoSubjects}" +
@$"{nameof(takenInTheFuture)}: {takenInTheFuture}"
);
this.database.Photos.Remove(photo);
database.Photos.Remove(photo);
}
await this.database.SaveChangesAsync();
await database.SaveChangesAsync();
}
}

View file

@ -1,23 +1,21 @@
using System;
using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Types.Maintenance;
namespace LBPUnion.ProjectLighthouse.Administration.Maintenance.MaintenanceJobs;
public class DeleteAllTokensMaintenanceJob : IMaintenanceJob
{
private readonly DatabaseContext database = DatabaseContext.CreateNewInstance();
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.");
await using DatabaseContext database = DatabaseContext.CreateNewInstance();
await database.GameTokens.RemoveWhere(t => true);
await database.WebTokens.RemoveWhere(t => true);
Console.WriteLine(@"Deleted ALL tokens.");
}
}

View file

@ -11,5 +11,5 @@ public class CleanupRoomsTask : IRepeatingTask
public string Name => "Cleanup Rooms";
public TimeSpan RepeatInterval => TimeSpan.FromSeconds(10);
public DateTime LastRan { get; set; }
public Task Run(DatabaseContext database) => RoomHelper.CleanupRooms();
public Task Run(DatabaseContext database) => RoomHelper.CleanupRooms(database);
}

View file

@ -1,9 +1,6 @@
#nullable enable
using System;
using System.Linq;
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Types.Logging;
using LBPUnion.ProjectLighthouse.Types.Misc;
namespace LBPUnion.ProjectLighthouse.Configuration;
@ -12,21 +9,6 @@ public static class ServerStatics
{
public const int PageSize = 20;
public static bool DbConnected {
get {
try
{
using DatabaseContext db = DatabaseContext.CreateNewInstance();
return db.Database.CanConnect();
}
catch(Exception e)
{
Logger.Error(e.ToString(), LogArea.Database);
return false;
}
}
}
// FIXME: This needs to go at some point.
public static bool IsUnitTesting => AppDomain.CurrentDomain.GetAssemblies().Any(assembly => assembly.FullName!.StartsWith("xunit"));

View file

@ -11,6 +11,13 @@ public static class DatabaseExtensions
public static async Task<bool> Has<T>(this IQueryable<T> queryable, Expression<Func<T, bool>> predicate)
=> await queryable.FirstOrDefaultAsync(predicate) != null;
public static async Task RemoveWhere<T>(this DbSet<T> queryable, Expression<Func<T, bool>> predicate) where T : class
=> await queryable.Where(predicate).ExecuteDeleteAsync();
/// <summary>
/// Deletes all records matching a given predicate
/// <para>Deletes are executed immediately without calling SaveChanges()</para>
/// </summary>
/// <param name="dbSet">The database set to source from</param>
/// <param name="predicate">The predicate used to determine which records to delete</param>
/// <typeparam name="T">The record type contained within the DbSet</typeparam>
public static async Task RemoveWhere<T>(this DbSet<T> dbSet, Expression<Func<T, bool>> predicate) where T : class
=> await dbSet.Where(predicate).ExecuteDeleteAsync();
}

View file

@ -27,7 +27,7 @@ public static class RoomHelper
private static int roomIdIncrement;
internal static int RoomIdIncrement => roomIdIncrement++;
public static FindBestRoomResponse? FindBestRoom(UserEntity? user, GameVersion roomVersion, RoomSlot? slot, Platform? platform, string? location)
public static FindBestRoomResponse? FindBestRoom(DatabaseContext database, UserEntity? user, GameVersion roomVersion, RoomSlot? slot, Platform? platform, string? location)
{
if (roomVersion == GameVersion.LittleBigPlanet1 || roomVersion == GameVersion.LittleBigPlanetPSP)
{
@ -87,7 +87,7 @@ public static class RoomHelper
Locations = new List<string>(),
};
foreach (UserEntity player in room.GetPlayers(DatabaseContext.CreateNewInstance()))
foreach (UserEntity player in room.GetPlayers(database))
{
response.Players.Add
(
@ -157,8 +157,8 @@ public static class RoomHelper
RoomVersion = roomVersion,
RoomPlatform = roomPlatform,
};
CleanupRooms(room.HostId, room);
using DatabaseContext database = DatabaseContext.CreateNewInstance();
CleanupRooms(database, room.HostId, room);
lock(RoomLock) Rooms.Add(room);
Logger.Info($"Created room (id: {room.RoomId}) for host {room.HostId}", LogArea.Match);
@ -175,20 +175,11 @@ public static class RoomHelper
public static Room? FindRoomByUserId(int userId)
{
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (Room room in Rooms)
{
if (room.PlayerIds.Any(p => p == userId))
{
return room;
}
}
return null;
return Rooms.FirstOrDefault(room => room.PlayerIds.Any(p => p == userId));
}
[SuppressMessage("ReSharper", "InvertIf")]
public static Task CleanupRooms(int? hostId = null, Room? newRoom = null, DatabaseContext? database = null)
public static Task CleanupRooms(DatabaseContext database, int? hostId = null, Room? newRoom = null)
{
#if DEBUG
Stopwatch stopwatch = new();
@ -204,8 +195,6 @@ public static class RoomHelper
#endif
int roomCountBeforeCleanup = rooms.Count();
database ??= DatabaseContext.CreateNewInstance();
// Remove offline players from rooms
foreach (Room room in rooms)
{

View file

@ -9,7 +9,6 @@ namespace LBPUnion.ProjectLighthouse.Helpers;
public static class StatisticsHelper
{
public static async Task<int> RecentMatches(DatabaseContext database) => await database.LastContacts.Where(l => TimeHelper.Timestamp - l.Timestamp < 300).CountAsync();
public static async Task<int> RecentMatchesForGame(DatabaseContext database, GameVersion gameVersion)

View file

@ -23,6 +23,5 @@ public class FileLogger : ILogger
File.AppendAllText(Path.Combine(logsDirectory, "all.log"), contentAll);
}
catch(IOException) {} // windows, ya goofed
}
}

View file

@ -27,7 +27,7 @@ namespace LBPUnion.ProjectLighthouse;
public static class StartupTasks
{
public static void Run(string[] args, ServerType serverType)
public static async Task Run(ServerType serverType)
{
// Log startup time
Stopwatch stopwatch = new();
@ -42,7 +42,7 @@ public static class StartupTasks
Logger.Info($"Welcome to the Project Lighthouse {serverType.ToString()}!", LogArea.Startup);
Logger.Info("Loading configurations...", LogArea.Startup);
if (!loadConfigurations())
if (!LoadConfigurations())
{
Logger.Error("Failed to load one or more configurations", LogArea.Config);
Environment.Exit(1);
@ -50,22 +50,26 @@ public static class StartupTasks
// Version info depends on ServerConfig
Logger.Info($"You are running version {VersionHelper.FullVersion}", LogArea.Startup);
Logger.Info("Connecting to the database...", LogArea.Startup);
bool dbConnected = ServerStatics.DbConnected;
if (!dbConnected)
await using DatabaseContext database = DatabaseContext.CreateNewInstance();
try
{
Logger.Error("Database unavailable! Exiting.", LogArea.Startup);
if (!await database.Database.CanConnectAsync())
{
Logger.Error("Database unavailable! Exiting.", LogArea.Startup);
Logger.Error("Ensure that you have set the dbConnectionString field in lighthouse.yml", LogArea.Startup);
Environment.Exit(-1);
}
}
else
catch (Exception e)
{
Logger.Success("Connected to the database!", LogArea.Startup);
Logger.Error("There was an error connecting to the database:", LogArea.Startup);
Logger.Error(e.ToDetailedException(), LogArea.Startup);
Environment.Exit(-1);
}
if (!dbConnected) Environment.Exit(1);
using DatabaseContext database = DatabaseContext.CreateNewInstance();
migrateDatabase(database).Wait();
await MigrateDatabase(database);
Logger.Debug
(
@ -76,13 +80,6 @@ public static class StartupTasks
);
Logger.Debug("You can do so by running any dotnet command with the flag: \"-c Release\". ", LogArea.Startup);
if (args.Length != 0)
{
List<LogLine> logLines = MaintenanceHelper.RunCommand(args).Result;
Console.WriteLine(logLines.ToLogString());
return;
}
if (ServerConfiguration.Instance.WebsiteConfiguration.ConvertAssetsOnStartup
&& serverType == ServerType.Website)
{
@ -102,7 +99,7 @@ public static class StartupTasks
admin.PermissionLevel = PermissionLevel.Administrator;
admin.PasswordResetRequired = true;
database.SaveChanges();
await database.SaveChangesAsync();
Logger.Success("No users were found, so an admin user was created. " +
$"The username is 'admin' and the password is '{passwordClear}'.", LogArea.Startup);
@ -112,7 +109,7 @@ public static class StartupTasks
Logger.Success($"Ready! Startup took {stopwatch.ElapsedMilliseconds}ms. Passing off control to ASP.NET...", LogArea.Startup);
}
private static bool loadConfigurations()
private static bool LoadConfigurations()
{
Assembly assembly = Assembly.GetAssembly(typeof(ConfigurationBase<>));
if (assembly == null) return false;
@ -147,7 +144,7 @@ public static class StartupTasks
return didLoad;
}
private static async Task migrateDatabase(DatabaseContext database)
private static async Task MigrateDatabase(DatabaseContext database)
{
int? originalTimeout = database.Database.GetCommandTimeout();
database.Database.SetCommandTimeout(TimeSpan.FromMinutes(5));
@ -179,7 +176,7 @@ public static class StartupTasks
foreach (IMigrationTask migrationTask in migrationsToRun)
{
MaintenanceHelper.RunMigration(migrationTask, database).Wait();
MaintenanceHelper.RunMigration(database, migrationTask).Wait();
}
stopwatch.Stop();

View file

@ -1,11 +1,9 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Types.Entities.Interaction;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Misc;
@ -79,21 +77,13 @@ public class SlotEntity
public string[] LevelTags(DatabaseContext database)
{
if (this.GameVersion != GameVersion.LittleBigPlanet1) return Array.Empty<string>();
// Sort tags by most popular
SortedDictionary<string, int> occurrences = new();
foreach (RatedLevelEntity r in database.RatedLevels.Where(r => r.SlotId == this.SlotId && r.TagLBP1.Length > 0))
{
if (!occurrences.ContainsKey(r.TagLBP1))
occurrences.Add(r.TagLBP1, 1);
else
occurrences[r.TagLBP1]++;
}
return occurrences.OrderBy(r => r.Value).Select(r => r.Key).ToArray();
return database.RatedLevels.Where(r => r.SlotId == this.SlotId && r.TagLBP1.Length > 0)
.GroupBy(r => r.TagLBP1)
.OrderByDescending(kvp => kvp.Count())
.Select(kvp => kvp.Key)
.ToArray();
}
public string BackgroundHash { get; set; } = "";
@ -140,18 +130,6 @@ public class SlotEntity
public bool CommentsEnabled { get; set; } = true;
[NotMapped]
public int Hearts => DatabaseContext.CreateNewInstance().HeartedLevels.Count(s => s.SlotId == this.SlotId);
[NotMapped]
public int Comments => DatabaseContext.CreateNewInstance().Comments.Count(c => c.Type == CommentType.Level && c.TargetId == this.SlotId);
[NotMapped]
public int Photos => DatabaseContext.CreateNewInstance().Photos.Count(p => p.SlotId == this.SlotId);
[NotMapped]
public int PhotosWithAuthor => DatabaseContext.CreateNewInstance().Photos.Count(p => p.SlotId == this.SlotId && p.CreatorId == this.CreatorId);
[NotMapped]
public int Plays => this.PlaysLBP1 + this.PlaysLBP2 + this.PlaysLBP3;
@ -160,12 +138,4 @@ public class SlotEntity
[NotMapped]
public int PlaysComplete => this.PlaysLBP1Complete + this.PlaysLBP2Complete + this.PlaysLBP3Complete;
public double RatingLBP1 => DatabaseContext.CreateNewInstance().RatedLevels.Where(r => r.SlotId == this.SlotId).Average(r => (double?)r.RatingLBP1) ?? 3.0;
[NotMapped]
public int Thumbsup => DatabaseContext.CreateNewInstance().RatedLevels.Count(r => r.SlotId == this.SlotId && r.Rating == 1);
[NotMapped]
public int Thumbsdown => DatabaseContext.CreateNewInstance().RatedLevels.Count(r => r.SlotId == this.SlotId && r.Rating == -1);
}

View file

@ -38,7 +38,7 @@ public class CommentEntity
public int ThumbsUp { get; set; }
public int ThumbsDown { get; set; }
public string GetCommentMessage()
public string GetCommentMessage(DatabaseContext database)
{
if (!this.Deleted)
{
@ -50,7 +50,6 @@ public class CommentEntity
return "This comment has been deleted by the author.";
}
using DatabaseContext database = DatabaseContext.CreateNewInstance();
UserEntity deletedBy = database.Users.FirstOrDefault(u => u.Username == this.DeletedBy);
if (deletedBy != null && deletedBy.UserId == this.TargetId)

View file

@ -2,7 +2,6 @@ using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Types.Misc;
@ -49,10 +48,6 @@ public class UserEntity
}
public UserStatus GetStatus(DatabaseContext database) => new(database, this.UserId);
public int GetHeartCount(DatabaseContext database) => database.HeartedProfiles.Count(hp => hp.HeartedUserId == this.UserId);
public int GetCommentCount(DatabaseContext database) => database.Comments.Count(c => c.TargetId == this.UserId && c.Type == CommentType.Profile);
public int GetUsedSlotCount(DatabaseContext database) => database.Slots.Count(s => s.CreatorId == this.UserId);
public int GetUploadedPhotoCount(DatabaseContext database) => database.Photos.Count(p => p.CreatorId == this.UserId);
/// <summary>
/// The location of the profile card on the user's earth

View file

@ -1,3 +1,4 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using LBPUnion.ProjectLighthouse.Logging;
@ -8,7 +9,7 @@ namespace LBPUnion.ProjectLighthouse.Types.Maintenance;
public interface ICommand
{
public string FirstAlias => this.Aliases()[0];
public Task Run(string[] args, Logger logger);
public Task Run(IServiceProvider provider, string[] args, Logger logger);
public string Name();

View file

@ -32,7 +32,8 @@ public class GameDeveloperSlot : SlotBase, INeedsPreparationForSerialization
public async Task PrepareSerialization(DatabaseContext database)
{
var stats = await database.Slots.Select(_ => new
var stats = await database.Slots.Where(s => s.SlotId == this.InternalSlotId)
.Select(_ => new
{
CommentCount = database.Comments.Count(c => c.TargetId == this.SlotId && c.Type == CommentType.Level),
PhotoCount = database.Photos.Count(p => p.SlotId == this.SlotId),

View file

@ -235,7 +235,7 @@ public class GameUserSlot : SlotBase, INeedsPreparationForSerialization
public async Task PrepareSerialization(DatabaseContext database)
{
var stats = await database.Slots
var stats = await database.Slots.Where(s => s.SlotId == this.SlotId)
.Select(_ => new
{
ThumbsUp = database.RatedLevels.Count(r => r.SlotId == this.SlotId && r.Rating == 1),

View file

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Types.Filter;