The Great Formatting of 2022

This commit is contained in:
jvyden 2022-01-18 23:09:02 -05:00
parent 59cc7f02fb
commit 35f50f5f8c
No known key found for this signature in database
GPG key ID: 18BCF2BE0262B278
162 changed files with 6609 additions and 6809 deletions

View file

@ -6,62 +6,61 @@ using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Settings; using LBPUnion.ProjectLighthouse.Types.Settings;
using Xunit; using Xunit;
namespace ProjectLighthouse.Tests.GameApiTests namespace ProjectLighthouse.Tests.GameApiTests;
public class AuthenticationTests : LighthouseServerTest
{ {
public class AuthenticationTests : LighthouseServerTest [Fact]
public async Task ShouldReturnErrorOnNoPostData()
{ {
[Fact] HttpResponseMessage response = await this.Client.PostAsync("/LITTLEBIGPLANETPS3_XML/login", null!);
public async Task ShouldReturnErrorOnNoPostData() Assert.False(response.IsSuccessStatusCode);
{ #if NET6_0_OR_GREATER
HttpResponseMessage response = await this.Client.PostAsync("/LITTLEBIGPLANETPS3_XML/login", null!); Assert.True(response.StatusCode == HttpStatusCode.BadRequest);
Assert.False(response.IsSuccessStatusCode); #else
#if NET6_0_OR_GREATER
Assert.True(response.StatusCode == HttpStatusCode.BadRequest);
#else
Assert.True(response.StatusCode == HttpStatusCode.NotAcceptable); Assert.True(response.StatusCode == HttpStatusCode.NotAcceptable);
#endif #endif
} }
[DatabaseFact] [DatabaseFact]
public async Task ShouldReturnWithValidData() public async Task ShouldReturnWithValidData()
{ {
HttpResponseMessage response = await this.AuthenticateResponse(); HttpResponseMessage response = await this.AuthenticateResponse();
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(ServerStatics.ServerName, responseContent);
} }
[DatabaseFact] [DatabaseFact]
public async Task CanSerializeBack() public async Task CanSerializeBack()
{ {
LoginResult loginResult = await this.Authenticate(); LoginResult loginResult = await this.Authenticate();
Assert.NotNull(loginResult); Assert.NotNull(loginResult);
Assert.NotNull(loginResult.AuthTicket); Assert.NotNull(loginResult.AuthTicket);
Assert.NotNull(loginResult.LbpEnvVer); Assert.NotNull(loginResult.LbpEnvVer);
Assert.Contains("MM_AUTH=", loginResult.AuthTicket); Assert.Contains("MM_AUTH=", loginResult.AuthTicket);
Assert.Equal(ServerStatics.ServerName, loginResult.LbpEnvVer); Assert.Equal(ServerStatics.ServerName, loginResult.LbpEnvVer);
} }
[DatabaseFact] [DatabaseFact]
public async Task CanUseToken() public async Task CanUseToken()
{ {
LoginResult loginResult = await this.Authenticate(); LoginResult loginResult = await this.Authenticate();
HttpResponseMessage response = await this.AuthenticatedRequest("/LITTLEBIGPLANETPS3_XML/enterLevel/1", loginResult.AuthTicket); HttpResponseMessage response = await this.AuthenticatedRequest("/LITTLEBIGPLANETPS3_XML/enterLevel/1", loginResult.AuthTicket);
string responseContent = await response.Content.ReadAsStringAsync(); string responseContent = await response.Content.ReadAsStringAsync();
Assert.False(response.StatusCode == HttpStatusCode.Forbidden); Assert.False(response.StatusCode == HttpStatusCode.Forbidden);
} }
[DatabaseFact] [DatabaseFact]
public async Task ShouldReturnForbiddenWhenNotAuthenticated() public async Task ShouldReturnForbiddenWhenNotAuthenticated()
{ {
HttpResponseMessage response = await this.Client.GetAsync("/LITTLEBIGPLANETPS3_XML/announce"); HttpResponseMessage response = await this.Client.GetAsync("/LITTLEBIGPLANETPS3_XML/announce");
Assert.False(response.IsSuccessStatusCode); Assert.False(response.IsSuccessStatusCode);
Assert.True(response.StatusCode == HttpStatusCode.Forbidden); Assert.True(response.StatusCode == HttpStatusCode.Forbidden);
}
} }
} }

View file

@ -6,23 +6,22 @@ using LBPUnion.ProjectLighthouse.Tests;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Xunit; using Xunit;
namespace ProjectLighthouse.Tests.GameApiTests namespace ProjectLighthouse.Tests.GameApiTests;
public class DatabaseTests : LighthouseServerTest
{ {
public class DatabaseTests : LighthouseServerTest [DatabaseFact]
public async Task CanCreateUserTwice()
{ {
[DatabaseFact] await using Database database = new();
public async Task CanCreateUserTwice() int rand = new Random().Next();
{
await using Database database = new();
int rand = new Random().Next();
User userA = await database.CreateUser("createUserTwiceTest" + rand, HashHelper.GenerateAuthToken()); User userA = await database.CreateUser("createUserTwiceTest" + rand, HashHelper.GenerateAuthToken());
User userB = await database.CreateUser("createUserTwiceTest" + rand, HashHelper.GenerateAuthToken()); User userB = await database.CreateUser("createUserTwiceTest" + rand, HashHelper.GenerateAuthToken());
Assert.NotNull(userA); Assert.NotNull(userA);
Assert.NotNull(userB); Assert.NotNull(userB);
await database.RemoveUser(userA); // Only remove userA since userA and userB are the same user await database.RemoveUser(userA); // Only remove userA since userA and userB are the same user
}
} }
} }

View file

@ -8,55 +8,54 @@ using LBPUnion.ProjectLighthouse.Tests;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Xunit; using Xunit;
namespace ProjectLighthouse.Tests.GameApiTests namespace ProjectLighthouse.Tests.GameApiTests;
public class MatchTests : LighthouseServerTest
{ {
public class MatchTests : LighthouseServerTest private static readonly SemaphoreSlim semaphore = new(1, 1);
[DatabaseFact]
public async Task ShouldRejectEmptyData()
{ {
private static readonly SemaphoreSlim semaphore = new(1, 1); LoginResult loginResult = await this.Authenticate();
await semaphore.WaitAsync();
[DatabaseFact] HttpResponseMessage result = await this.AuthenticatedUploadDataRequest("LITTLEBIGPLANETPS3_XML/match", Array.Empty<byte>(), loginResult.AuthTicket);
public async Task ShouldRejectEmptyData()
{
LoginResult loginResult = await this.Authenticate();
await semaphore.WaitAsync();
HttpResponseMessage result = await this.AuthenticatedUploadDataRequest("LITTLEBIGPLANETPS3_XML/match", Array.Empty<byte>(), loginResult.AuthTicket); semaphore.Release();
Assert.False(result.IsSuccessStatusCode);
}
semaphore.Release(); [DatabaseFact]
Assert.False(result.IsSuccessStatusCode); public async Task ShouldReturnOk()
} {
LoginResult loginResult = await this.Authenticate();
await semaphore.WaitAsync();
[DatabaseFact] HttpResponseMessage result = await this.AuthenticatedUploadDataRequest
public async Task ShouldReturnOk() ("LITTLEBIGPLANETPS3_XML/match", Encoding.ASCII.GetBytes("[UpdateMyPlayerData,[\"Player\":\"1984\"]]"), loginResult.AuthTicket);
{
LoginResult loginResult = await this.Authenticate();
await semaphore.WaitAsync();
HttpResponseMessage result = await this.AuthenticatedUploadDataRequest semaphore.Release();
("LITTLEBIGPLANETPS3_XML/match", Encoding.ASCII.GetBytes("[UpdateMyPlayerData,[\"Player\":\"1984\"]]"), loginResult.AuthTicket); Assert.True(result.IsSuccessStatusCode);
}
semaphore.Release(); [DatabaseFact]
Assert.True(result.IsSuccessStatusCode); public async Task ShouldIncrementPlayerCount()
} {
LoginResult loginResult = await this.Authenticate(new Random().Next());
[DatabaseFact] await semaphore.WaitAsync();
public async Task ShouldIncrementPlayerCount()
{
LoginResult loginResult = await this.Authenticate(new Random().Next());
await semaphore.WaitAsync(); int oldPlayerCount = await StatisticsHelper.RecentMatches();
int oldPlayerCount = await StatisticsHelper.RecentMatches(); HttpResponseMessage result = await this.AuthenticatedUploadDataRequest
("LITTLEBIGPLANETPS3_XML/match", Encoding.ASCII.GetBytes("[UpdateMyPlayerData,[\"Player\":\"1984\"]]"), loginResult.AuthTicket);
HttpResponseMessage result = await this.AuthenticatedUploadDataRequest Assert.True(result.IsSuccessStatusCode);
("LITTLEBIGPLANETPS3_XML/match", Encoding.ASCII.GetBytes("[UpdateMyPlayerData,[\"Player\":\"1984\"]]"), loginResult.AuthTicket);
Assert.True(result.IsSuccessStatusCode); int playerCount = await StatisticsHelper.RecentMatches();
int playerCount = await StatisticsHelper.RecentMatches(); semaphore.Release();
Assert.Equal(oldPlayerCount + 1, playerCount);
semaphore.Release();
Assert.Equal(oldPlayerCount + 1, playerCount);
}
} }
} }

View file

@ -9,85 +9,84 @@ using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Profiles; using LBPUnion.ProjectLighthouse.Types.Profiles;
using Xunit; using Xunit;
namespace ProjectLighthouse.Tests.GameApiTests namespace ProjectLighthouse.Tests.GameApiTests;
public class SlotTests : LighthouseServerTest
{ {
public class SlotTests : LighthouseServerTest [DatabaseFact]
public async Task ShouldOnlyShowUsersLevels()
{ {
[DatabaseFact] await using Database database = new();
public async Task ShouldOnlyShowUsersLevels()
Random r = new();
User userA = await database.CreateUser($"unitTestUser{r.Next()}", HashHelper.GenerateAuthToken());
User userB = await database.CreateUser($"unitTestUser{r.Next()}", HashHelper.GenerateAuthToken());
Location l = new()
{ {
await using Database database = new(); X = 0,
Y = 0,
};
database.Locations.Add(l);
await database.SaveChangesAsync();
Random r = new(); Slot slotA = new()
{
Creator = userA,
CreatorId = userA.UserId,
Name = "slotA",
Location = l,
LocationId = l.Id,
ResourceCollection = "",
};
User userA = await database.CreateUser($"unitTestUser{r.Next()}", HashHelper.GenerateAuthToken()); Slot slotB = new()
User userB = await database.CreateUser($"unitTestUser{r.Next()}", HashHelper.GenerateAuthToken()); {
Creator = userB,
CreatorId = userB.UserId,
Name = "slotB",
Location = l,
LocationId = l.Id,
ResourceCollection = "",
};
Location l = new() database.Slots.Add(slotA);
{ database.Slots.Add(slotB);
X = 0,
Y = 0,
};
database.Locations.Add(l);
await database.SaveChangesAsync();
Slot slotA = new() await database.SaveChangesAsync();
{
Creator = userA,
CreatorId = userA.UserId,
Name = "slotA",
Location = l,
LocationId = l.Id,
ResourceCollection = "",
};
Slot slotB = new()
{
Creator = userB,
CreatorId = userB.UserId,
Name = "slotB",
Location = l,
LocationId = l.Id,
ResourceCollection = "",
};
database.Slots.Add(slotA);
database.Slots.Add(slotB);
await database.SaveChangesAsync();
// XmlSerializer serializer = new(typeof(Slot)); // XmlSerializer serializer = new(typeof(Slot));
// Slot slot = (Slot)serializer.Deserialize(new StringReader(bodyString)); // Slot slot = (Slot)serializer.Deserialize(new StringReader(bodyString));
LoginResult loginResult = await this.Authenticate(); LoginResult loginResult = await this.Authenticate();
HttpResponseMessage respMessageA = await this.AuthenticatedRequest HttpResponseMessage respMessageA = await this.AuthenticatedRequest
($"LITTLEBIGPLANETPS3_XML/slots/by?u={userA.Username}&pageStart=1&pageSize=1", loginResult.AuthTicket); ($"LITTLEBIGPLANETPS3_XML/slots/by?u={userA.Username}&pageStart=1&pageSize=1", loginResult.AuthTicket);
HttpResponseMessage respMessageB = await this.AuthenticatedRequest HttpResponseMessage respMessageB = await this.AuthenticatedRequest
($"LITTLEBIGPLANETPS3_XML/slots/by?u={userB.Username}&pageStart=1&pageSize=1", loginResult.AuthTicket); ($"LITTLEBIGPLANETPS3_XML/slots/by?u={userB.Username}&pageStart=1&pageSize=1", loginResult.AuthTicket);
Assert.True(respMessageA.IsSuccessStatusCode); Assert.True(respMessageA.IsSuccessStatusCode);
Assert.True(respMessageB.IsSuccessStatusCode); Assert.True(respMessageB.IsSuccessStatusCode);
string respA = await respMessageA.Content.ReadAsStringAsync(); string respA = await respMessageA.Content.ReadAsStringAsync();
string respB = await respMessageB.Content.ReadAsStringAsync(); string respB = await respMessageB.Content.ReadAsStringAsync();
Assert.False(string.IsNullOrEmpty(respA)); Assert.False(string.IsNullOrEmpty(respA));
Assert.False(string.IsNullOrEmpty(respB)); Assert.False(string.IsNullOrEmpty(respB));
Assert.NotEqual(respA, respB); Assert.NotEqual(respA, respB);
Assert.DoesNotContain(respA, "slotB"); Assert.DoesNotContain(respA, "slotB");
Assert.DoesNotContain(respB, "slotA"); Assert.DoesNotContain(respB, "slotA");
// Cleanup // Cleanup
database.Slots.Remove(slotA); database.Slots.Remove(slotA);
database.Slots.Remove(slotB); database.Slots.Remove(slotB);
await database.RemoveUser(userA); await database.RemoveUser(userA);
await database.RemoveUser(userB); await database.RemoveUser(userB);
await database.SaveChangesAsync(); await database.SaveChangesAsync();
}
} }
} }

View file

@ -6,54 +6,53 @@ using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Tests; using LBPUnion.ProjectLighthouse.Tests;
using Xunit; using Xunit;
namespace ProjectLighthouse.Tests.GameApiTests namespace ProjectLighthouse.Tests.GameApiTests;
public class UploadTests : LighthouseServerTest
{ {
public class UploadTests : LighthouseServerTest public UploadTests()
{ {
public UploadTests() string assetsDirectory = Path.Combine(Environment.CurrentDirectory, "r");
{ if (Directory.Exists(assetsDirectory)) Directory.Delete(assetsDirectory, true);
string assetsDirectory = Path.Combine(Environment.CurrentDirectory, "r"); }
if (Directory.Exists(assetsDirectory)) Directory.Delete(assetsDirectory, true);
}
[Fact] [Fact]
public async Task ShouldNotAcceptScript() public async Task ShouldNotAcceptScript()
{ {
HttpResponseMessage response = await this.UploadFileEndpointRequest("ExampleFiles/TestScript.ff"); HttpResponseMessage response = await this.UploadFileEndpointRequest("ExampleFiles/TestScript.ff");
Assert.False(response.StatusCode == HttpStatusCode.Forbidden); Assert.False(response.StatusCode == HttpStatusCode.Forbidden);
Assert.False(response.IsSuccessStatusCode); Assert.False(response.IsSuccessStatusCode);
} }
[Fact] [Fact]
public async Task ShouldNotAcceptFarc() public async Task ShouldNotAcceptFarc()
{ {
HttpResponseMessage response = await this.UploadFileEndpointRequest("ExampleFiles/TestFarc.farc"); HttpResponseMessage response = await this.UploadFileEndpointRequest("ExampleFiles/TestFarc.farc");
Assert.False(response.StatusCode == HttpStatusCode.Forbidden); Assert.False(response.StatusCode == HttpStatusCode.Forbidden);
Assert.False(response.IsSuccessStatusCode); Assert.False(response.IsSuccessStatusCode);
} }
[Fact] [Fact]
public async Task ShouldNotAcceptGarbage() public async Task ShouldNotAcceptGarbage()
{ {
HttpResponseMessage response = await this.UploadFileEndpointRequest("ExampleFiles/TestGarbage.bin"); HttpResponseMessage response = await this.UploadFileEndpointRequest("ExampleFiles/TestGarbage.bin");
Assert.False(response.StatusCode == HttpStatusCode.Forbidden); Assert.False(response.StatusCode == HttpStatusCode.Forbidden);
Assert.False(response.IsSuccessStatusCode); Assert.False(response.IsSuccessStatusCode);
} }
[Fact] [Fact]
public async Task ShouldAcceptTexture() public async Task ShouldAcceptTexture()
{ {
HttpResponseMessage response = await this.UploadFileEndpointRequest("ExampleFiles/TestTexture.tex"); HttpResponseMessage response = await this.UploadFileEndpointRequest("ExampleFiles/TestTexture.tex");
Assert.False(response.StatusCode == HttpStatusCode.Forbidden); Assert.False(response.StatusCode == HttpStatusCode.Forbidden);
Assert.True(response.IsSuccessStatusCode); Assert.True(response.IsSuccessStatusCode);
} }
[Fact] [Fact]
public async Task ShouldAcceptLevel() public async Task ShouldAcceptLevel()
{ {
HttpResponseMessage response = await this.UploadFileEndpointRequest("ExampleFiles/TestLevel.lvl"); HttpResponseMessage response = await this.UploadFileEndpointRequest("ExampleFiles/TestLevel.lvl");
Assert.False(response.StatusCode == HttpStatusCode.Forbidden); Assert.False(response.StatusCode == HttpStatusCode.Forbidden);
Assert.True(response.IsSuccessStatusCode); Assert.True(response.IsSuccessStatusCode);
}
} }
} }

View file

@ -7,58 +7,57 @@ using LBPUnion.ProjectLighthouse.Types;
using OpenQA.Selenium; using OpenQA.Selenium;
using Xunit; using Xunit;
namespace ProjectLighthouse.Tests.WebsiteTests namespace ProjectLighthouse.Tests.WebsiteTests;
public class AdminTests : LighthouseWebTest
{ {
public class AdminTests : LighthouseWebTest public const string AdminPanelButtonXPath = "/html/body/div/header/div/div/div/a[2]";
[DatabaseFact]
public async Task ShouldShowAdminPanelButtonWhenAdmin()
{ {
public const string AdminPanelButtonXPath = "/html/body/div/header/div/div/div/a[2]"; await using Database database = new();
Random random = new();
User user = await database.CreateUser($"unitTestUser{random.Next()}", HashHelper.BCryptHash("i'm an engineering failure"));
[DatabaseFact] WebToken webToken = new()
public async Task ShouldShowAdminPanelButtonWhenAdmin()
{ {
await using Database database = new(); UserId = user.UserId,
Random random = new(); UserToken = HashHelper.GenerateAuthToken(),
User user = await database.CreateUser($"unitTestUser{random.Next()}", HashHelper.BCryptHash("i'm an engineering failure")); };
WebToken webToken = new() database.WebTokens.Add(webToken);
{ user.IsAdmin = true;
UserId = user.UserId, await database.SaveChangesAsync();
UserToken = HashHelper.GenerateAuthToken(),
};
database.WebTokens.Add(webToken); this.Driver.Navigate().GoToUrl(this.BaseAddress + "/");
user.IsAdmin = true; this.Driver.Manage().Cookies.AddCookie(new Cookie("LighthouseToken", webToken.UserToken));
await database.SaveChangesAsync(); this.Driver.Navigate().Refresh();
this.Driver.Navigate().GoToUrl(this.BaseAddress + "/"); Assert.Contains("Admin Panel", this.Driver.FindElement(By.XPath(AdminPanelButtonXPath)).Text);
this.Driver.Manage().Cookies.AddCookie(new Cookie("LighthouseToken", webToken.UserToken)); }
this.Driver.Navigate().Refresh();
Assert.Contains("Admin Panel", this.Driver.FindElement(By.XPath(AdminPanelButtonXPath)).Text); [DatabaseFact]
} public async Task ShouldNotShowAdminPanelButtonWhenNotAdmin()
{
await using Database database = new();
Random random = new();
User user = await database.CreateUser($"unitTestUser{random.Next()}", HashHelper.BCryptHash("i'm an engineering failure"));
[DatabaseFact] WebToken webToken = new()
public async Task ShouldNotShowAdminPanelButtonWhenNotAdmin()
{ {
await using Database database = new(); UserId = user.UserId,
Random random = new(); UserToken = HashHelper.GenerateAuthToken(),
User user = await database.CreateUser($"unitTestUser{random.Next()}", HashHelper.BCryptHash("i'm an engineering failure")); };
WebToken webToken = new() database.WebTokens.Add(webToken);
{ user.IsAdmin = false;
UserId = user.UserId, await database.SaveChangesAsync();
UserToken = HashHelper.GenerateAuthToken(),
};
database.WebTokens.Add(webToken); this.Driver.Navigate().GoToUrl(this.BaseAddress + "/");
user.IsAdmin = false; this.Driver.Manage().Cookies.AddCookie(new Cookie("LighthouseToken", webToken.UserToken));
await database.SaveChangesAsync(); this.Driver.Navigate().Refresh();
this.Driver.Navigate().GoToUrl(this.BaseAddress + "/"); Assert.DoesNotContain("Admin Panel", this.Driver.FindElement(By.XPath(AdminPanelButtonXPath)).Text);
this.Driver.Manage().Cookies.AddCookie(new Cookie("LighthouseToken", webToken.UserToken));
this.Driver.Navigate().Refresh();
Assert.DoesNotContain("Admin Panel", this.Driver.FindElement(By.XPath(AdminPanelButtonXPath)).Text);
}
} }
} }

View file

@ -9,98 +9,97 @@ using Microsoft.EntityFrameworkCore;
using OpenQA.Selenium; using OpenQA.Selenium;
using Xunit; using Xunit;
namespace ProjectLighthouse.Tests.WebsiteTests namespace ProjectLighthouse.Tests.WebsiteTests;
public class AuthenticationTests : LighthouseWebTest
{ {
public class AuthenticationTests : LighthouseWebTest [DatabaseFact]
public async Task ShouldLoginWithPassword()
{ {
[DatabaseFact] await using Database database = new();
public async Task ShouldLoginWithPassword() Random random = new();
string password = HashHelper.Sha256Hash(HashHelper.GenerateRandomBytes(64).ToArray());
User user = await database.CreateUser($"unitTestUser{random.Next()}", HashHelper.BCryptHash(HashHelper.Sha256Hash(password)));
this.Driver.Navigate().GoToUrl(this.BaseAddress + "/login");
this.Driver.FindElement(By.Id("text")).SendKeys(user.Username);
this.Driver.FindElement(By.Id("password")).SendKeys(password);
this.Driver.FindElement(By.Id("submit")).Click();
WebToken? webToken = await database.WebTokens.FirstOrDefaultAsync(t => t.UserId == user.UserId);
Assert.NotNull(webToken);
await database.RemoveUser(user);
}
[DatabaseFact]
public async Task ShouldNotLoginWithNoPassword()
{
await using Database database = new();
Random random = new();
User user = await database.CreateUser($"unitTestUser{random.Next()}", HashHelper.BCryptHash("just like the hindenberg,"));
this.Driver.Navigate().GoToUrl(this.BaseAddress + "/login");
this.Driver.FindElement(By.Id("text")).SendKeys(user.Username);
this.Driver.FindElement(By.Id("submit")).Click();
WebToken? webToken = await database.WebTokens.FirstOrDefaultAsync(t => t.UserId == user.UserId);
Assert.Null(webToken);
await database.RemoveUser(user);
}
[DatabaseFact]
public async Task ShouldNotLoginWithWrongPassword()
{
await using Database database = new();
Random random = new();
User user = await database.CreateUser($"unitTestUser{random.Next()}", HashHelper.BCryptHash("i'm an engineering failure"));
this.Driver.Navigate().GoToUrl(this.BaseAddress + "/login");
this.Driver.FindElement(By.Id("text")).SendKeys(user.Username);
this.Driver.FindElement(By.Id("password")).SendKeys("nah man");
this.Driver.FindElement(By.Id("submit")).Click();
WebToken? webToken = await database.WebTokens.FirstOrDefaultAsync(t => t.UserId == user.UserId);
Assert.Null(webToken);
await database.RemoveUser(user);
}
[DatabaseFact]
public async Task ShouldLoginWithInjectedCookie()
{
const string loggedInAsUsernameTextXPath = "/html/body/div/div/div/p[1]/b";
await using Database database = new();
Random random = new();
User user = await database.CreateUser($"unitTestUser{random.Next()}", HashHelper.BCryptHash("i'm an engineering failure"));
WebToken webToken = new()
{ {
await using Database database = new(); UserId = user.UserId,
Random random = new(); UserToken = HashHelper.GenerateAuthToken(),
};
string password = HashHelper.Sha256Hash(HashHelper.GenerateRandomBytes(64).ToArray()); database.WebTokens.Add(webToken);
User user = await database.CreateUser($"unitTestUser{random.Next()}", HashHelper.BCryptHash(HashHelper.Sha256Hash(password))); await database.SaveChangesAsync();
this.Driver.Navigate().GoToUrl(this.BaseAddress + "/login"); INavigation navigation = this.Driver.Navigate();
this.Driver.FindElement(By.Id("text")).SendKeys(user.Username); navigation.GoToUrl(this.BaseAddress + "/");
this.Driver.FindElement(By.Id("password")).SendKeys(password); this.Driver.Manage().Cookies.AddCookie(new Cookie("LighthouseToken", webToken.UserToken));
Assert.Throws<NoSuchElementException>(() => this.Driver.FindElement(By.XPath(loggedInAsUsernameTextXPath)));
navigation.Refresh();
Assert.True(this.Driver.FindElement(By.XPath(loggedInAsUsernameTextXPath)).Text == user.Username);
this.Driver.FindElement(By.Id("submit")).Click(); await database.RemoveUser(user);
WebToken? webToken = await database.WebTokens.FirstOrDefaultAsync(t => t.UserId == user.UserId);
Assert.NotNull(webToken);
await database.RemoveUser(user);
}
[DatabaseFact]
public async Task ShouldNotLoginWithNoPassword()
{
await using Database database = new();
Random random = new();
User user = await database.CreateUser($"unitTestUser{random.Next()}", HashHelper.BCryptHash("just like the hindenberg,"));
this.Driver.Navigate().GoToUrl(this.BaseAddress + "/login");
this.Driver.FindElement(By.Id("text")).SendKeys(user.Username);
this.Driver.FindElement(By.Id("submit")).Click();
WebToken? webToken = await database.WebTokens.FirstOrDefaultAsync(t => t.UserId == user.UserId);
Assert.Null(webToken);
await database.RemoveUser(user);
}
[DatabaseFact]
public async Task ShouldNotLoginWithWrongPassword()
{
await using Database database = new();
Random random = new();
User user = await database.CreateUser($"unitTestUser{random.Next()}", HashHelper.BCryptHash("i'm an engineering failure"));
this.Driver.Navigate().GoToUrl(this.BaseAddress + "/login");
this.Driver.FindElement(By.Id("text")).SendKeys(user.Username);
this.Driver.FindElement(By.Id("password")).SendKeys("nah man");
this.Driver.FindElement(By.Id("submit")).Click();
WebToken? webToken = await database.WebTokens.FirstOrDefaultAsync(t => t.UserId == user.UserId);
Assert.Null(webToken);
await database.RemoveUser(user);
}
[DatabaseFact]
public async Task ShouldLoginWithInjectedCookie()
{
const string loggedInAsUsernameTextXPath = "/html/body/div/div/div/p[1]/b";
await using Database database = new();
Random random = new();
User user = await database.CreateUser($"unitTestUser{random.Next()}", HashHelper.BCryptHash("i'm an engineering failure"));
WebToken webToken = new()
{
UserId = user.UserId,
UserToken = HashHelper.GenerateAuthToken(),
};
database.WebTokens.Add(webToken);
await database.SaveChangesAsync();
INavigation navigation = this.Driver.Navigate();
navigation.GoToUrl(this.BaseAddress + "/");
this.Driver.Manage().Cookies.AddCookie(new Cookie("LighthouseToken", webToken.UserToken));
Assert.Throws<NoSuchElementException>(() => this.Driver.FindElement(By.XPath(loggedInAsUsernameTextXPath)));
navigation.Refresh();
Assert.True(this.Driver.FindElement(By.XPath(loggedInAsUsernameTextXPath)).Text == user.Username);
await database.RemoveUser(user);
}
} }
} }

View file

@ -7,44 +7,43 @@ using OpenQA.Selenium;
using OpenQA.Selenium.Chrome; using OpenQA.Selenium.Chrome;
using Xunit; using Xunit;
namespace ProjectLighthouse.Tests.WebsiteTests namespace ProjectLighthouse.Tests.WebsiteTests;
[Collection(nameof(LighthouseWebTest))]
public class LighthouseWebTest : IDisposable
{ {
[Collection(nameof(LighthouseWebTest))] public readonly string BaseAddress;
public class LighthouseWebTest : IDisposable
public readonly IWebDriver Driver;
public readonly IWebHost WebHost = new WebHostBuilder().UseKestrel().UseStartup<TestStartup>().UseWebRoot("StaticFiles").Build();
public LighthouseWebTest()
{ {
public readonly IWebHost WebHost = new WebHostBuilder().UseKestrel().UseStartup<TestStartup>().UseWebRoot("StaticFiles").Build(); this.WebHost.Start();
public readonly string BaseAddress;
public readonly IWebDriver Driver; IServerAddressesFeature? serverAddressesFeature = this.WebHost.ServerFeatures.Get<IServerAddressesFeature>();
if (serverAddressesFeature == null) throw new ArgumentNullException();
public LighthouseWebTest() this.BaseAddress = serverAddressesFeature.Addresses.First();
ChromeOptions chromeOptions = new();
if (Convert.ToBoolean(Environment.GetEnvironmentVariable("CI") ?? "false"))
{ {
this.WebHost.Start(); chromeOptions.AddArgument("headless");
chromeOptions.AddArgument("no-sandbox");
IServerAddressesFeature? serverAddressesFeature = WebHost.ServerFeatures.Get<IServerAddressesFeature>(); chromeOptions.AddArgument("disable-dev-shm-usage");
if (serverAddressesFeature == null) throw new ArgumentNullException(); Console.WriteLine("We are in a CI environment, so chrome headless mode has been enabled.");
this.BaseAddress = serverAddressesFeature.Addresses.First();
ChromeOptions chromeOptions = new();
if (Convert.ToBoolean(Environment.GetEnvironmentVariable("CI") ?? "false"))
{
chromeOptions.AddArgument("headless");
chromeOptions.AddArgument("no-sandbox");
chromeOptions.AddArgument("disable-dev-shm-usage");
Console.WriteLine("We are in a CI environment, so chrome headless mode has been enabled.");
}
this.Driver = new ChromeDriver(chromeOptions);
} }
public void Dispose() this.Driver = new ChromeDriver(chromeOptions);
{ }
this.Driver.Close();
this.Driver.Dispose();
this.WebHost.Dispose();
GC.SuppressFinalize(this); public void Dispose()
} {
this.Driver.Close();
this.Driver.Dispose();
this.WebHost.Dispose();
GC.SuppressFinalize(this);
} }
} }

View file

@ -9,76 +9,75 @@ using Microsoft.EntityFrameworkCore;
using OpenQA.Selenium; using OpenQA.Selenium;
using Xunit; using Xunit;
namespace ProjectLighthouse.Tests.WebsiteTests namespace ProjectLighthouse.Tests.WebsiteTests;
public class RegisterTests : LighthouseWebTest
{ {
public class RegisterTests : LighthouseWebTest [DatabaseFact]
public async Task ShouldRegister()
{ {
[DatabaseFact] await using Database database = new();
public async Task ShouldRegister()
{
await using Database database = new();
string username = "unitTestUser" + new Random().Next(); string username = "unitTestUser" + new Random().Next();
string password = HashHelper.Sha256Hash(HashHelper.GenerateRandomBytes(64).ToArray()); string password = HashHelper.Sha256Hash(HashHelper.GenerateRandomBytes(64).ToArray());
this.Driver.Navigate().GoToUrl(this.BaseAddress + "/register"); this.Driver.Navigate().GoToUrl(this.BaseAddress + "/register");
this.Driver.FindElement(By.Id("text")).SendKeys(username); this.Driver.FindElement(By.Id("text")).SendKeys(username);
this.Driver.FindElement(By.Id("password")).SendKeys(password); this.Driver.FindElement(By.Id("password")).SendKeys(password);
this.Driver.FindElement(By.Id("confirmPassword")).SendKeys(password); this.Driver.FindElement(By.Id("confirmPassword")).SendKeys(password);
this.Driver.FindElement(By.Id("submit")).Click(); this.Driver.FindElement(By.Id("submit")).Click();
User? user = await database.Users.FirstOrDefaultAsync(u => u.Username == username); User? user = await database.Users.FirstOrDefaultAsync(u => u.Username == username);
Assert.NotNull(user); Assert.NotNull(user);
await database.RemoveUser(user); await database.RemoveUser(user);
} }
[DatabaseFact] [DatabaseFact]
public async Task ShouldNotRegisterWithMismatchingPasswords() public async Task ShouldNotRegisterWithMismatchingPasswords()
{ {
await using Database database = new(); await using Database database = new();
string username = "unitTestUser" + new Random().Next(); string username = "unitTestUser" + new Random().Next();
string password = HashHelper.Sha256Hash(HashHelper.GenerateRandomBytes(64).ToArray()); string password = HashHelper.Sha256Hash(HashHelper.GenerateRandomBytes(64).ToArray());
this.Driver.Navigate().GoToUrl(this.BaseAddress + "/register"); this.Driver.Navigate().GoToUrl(this.BaseAddress + "/register");
this.Driver.FindElement(By.Id("text")).SendKeys(username); this.Driver.FindElement(By.Id("text")).SendKeys(username);
this.Driver.FindElement(By.Id("password")).SendKeys(password); this.Driver.FindElement(By.Id("password")).SendKeys(password);
this.Driver.FindElement(By.Id("confirmPassword")).SendKeys(password + "a"); this.Driver.FindElement(By.Id("confirmPassword")).SendKeys(password + "a");
this.Driver.FindElement(By.Id("submit")).Click(); this.Driver.FindElement(By.Id("submit")).Click();
User? user = await database.Users.FirstOrDefaultAsync(u => u.Username == username); User? user = await database.Users.FirstOrDefaultAsync(u => u.Username == username);
Assert.Null(user); Assert.Null(user);
} }
[DatabaseFact] [DatabaseFact]
public async Task ShouldNotRegisterWithTakenUsername() public async Task ShouldNotRegisterWithTakenUsername()
{ {
await using Database database = new(); await using Database database = new();
string username = "unitTestUser" + new Random().Next(); string username = "unitTestUser" + new Random().Next();
string password = HashHelper.Sha256Hash(HashHelper.GenerateRandomBytes(64).ToArray()); string password = HashHelper.Sha256Hash(HashHelper.GenerateRandomBytes(64).ToArray());
await database.CreateUser(username, HashHelper.BCryptHash(password)); await database.CreateUser(username, HashHelper.BCryptHash(password));
User? user = await database.Users.FirstOrDefaultAsync(u => u.Username == username); User? user = await database.Users.FirstOrDefaultAsync(u => u.Username == username);
Assert.NotNull(user); Assert.NotNull(user);
this.Driver.Navigate().GoToUrl(this.BaseAddress + "/register"); this.Driver.Navigate().GoToUrl(this.BaseAddress + "/register");
this.Driver.FindElement(By.Id("text")).SendKeys(username); this.Driver.FindElement(By.Id("text")).SendKeys(username);
this.Driver.FindElement(By.Id("password")).SendKeys(password); this.Driver.FindElement(By.Id("password")).SendKeys(password);
this.Driver.FindElement(By.Id("confirmPassword")).SendKeys(password); this.Driver.FindElement(By.Id("confirmPassword")).SendKeys(password);
this.Driver.FindElement(By.Id("submit")).Click(); this.Driver.FindElement(By.Id("submit")).Click();
Assert.Contains("The username you've chosen is already taken.", this.Driver.PageSource); Assert.Contains("The username you've chosen is already taken.", this.Driver.PageSource);
}
} }
} }

View file

@ -2,28 +2,22 @@ using LBPUnion.ProjectLighthouse.Types.Settings;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Xunit; using Xunit;
namespace LBPUnion.ProjectLighthouse.Tests namespace LBPUnion.ProjectLighthouse.Tests;
{
public sealed class DatabaseFactAttribute : FactAttribute
{
private static readonly object migrateLock = new();
public DatabaseFactAttribute() public sealed class DatabaseFactAttribute : FactAttribute
{ {
ServerSettings.Instance = new ServerSettings(); private static readonly object migrateLock = new();
ServerSettings.Instance.DbConnectionString = "server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse";
if (!ServerStatics.DbConnected) public DatabaseFactAttribute()
{
ServerSettings.Instance = new ServerSettings();
ServerSettings.Instance.DbConnectionString = "server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse";
if (!ServerStatics.DbConnected) this.Skip = "Database not available";
else
lock(migrateLock)
{ {
this.Skip = "Database not available"; using Database database = new();
database.Database.Migrate();
} }
else
{
lock(migrateLock)
{
using Database database = new();
database.Database.Migrate();
}
}
}
} }
} }

View file

@ -4,58 +4,57 @@ using System.Text;
using LBPUnion.ProjectLighthouse.Types.Files; using LBPUnion.ProjectLighthouse.Types.Files;
using Xunit; using Xunit;
namespace LBPUnion.ProjectLighthouse.Tests namespace LBPUnion.ProjectLighthouse.Tests;
public class FileTypeTests
{ {
public class FileTypeTests [Fact]
public void ShouldRecognizeLevel()
{ {
[Fact] LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestLevel.lvl"));
public void ShouldRecognizeLevel() Assert.True(file.FileType == LbpFileType.Level);
{ }
LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestLevel.lvl"));
Assert.True(file.FileType == LbpFileType.Level);
}
[Fact] [Fact]
public void ShouldRecognizeScript() public void ShouldRecognizeScript()
{ {
LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestScript.ff")); LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestScript.ff"));
Assert.True(file.FileType == LbpFileType.Script); Assert.True(file.FileType == LbpFileType.Script);
} }
[Fact] [Fact]
public void ShouldRecognizeTexture() public void ShouldRecognizeTexture()
{ {
LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestTexture.tex")); LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestTexture.tex"));
Assert.True(file.FileType == LbpFileType.Texture); Assert.True(file.FileType == LbpFileType.Texture);
} }
[Fact] [Fact]
public void ShouldRecognizeFileArchive() public void ShouldRecognizeFileArchive()
{ {
LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestFarc.farc")); LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestFarc.farc"));
Assert.True(file.FileType == LbpFileType.FileArchive); Assert.True(file.FileType == LbpFileType.FileArchive);
} }
[Fact] [Fact]
public void ShouldNotRecognizeFileArchiveAsScript() public void ShouldNotRecognizeFileArchiveAsScript()
{ {
LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestFarc.farc")); LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestFarc.farc"));
Assert.False(file.FileType == LbpFileType.Script); Assert.False(file.FileType == LbpFileType.Script);
Assert.True(file.FileType == LbpFileType.FileArchive); Assert.True(file.FileType == LbpFileType.FileArchive);
} }
[Fact] [Fact]
public void ShouldRecognizeNothingAsUnknown() public void ShouldRecognizeNothingAsUnknown()
{ {
LbpFile file = new(Array.Empty<byte>()); LbpFile file = new(Array.Empty<byte>());
Assert.True(file.FileType == LbpFileType.Unknown); Assert.True(file.FileType == LbpFileType.Unknown);
} }
[Fact] [Fact]
public void ShouldRecognizeGarbageAsUnknown() public void ShouldRecognizeGarbageAsUnknown()
{ {
LbpFile file = new(Encoding.ASCII.GetBytes("free pc only $900")); LbpFile file = new(Encoding.ASCII.GetBytes("free pc only $900"));
Assert.True(file.FileType == LbpFileType.Unknown); Assert.True(file.FileType == LbpFileType.Unknown);
}
} }
} }

View file

@ -11,89 +11,84 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost; using Microsoft.AspNetCore.TestHost;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Tests namespace LBPUnion.ProjectLighthouse.Tests;
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public class LighthouseServerTest
{ {
[SuppressMessage("ReSharper", "UnusedMember.Global")] public readonly HttpClient Client;
public class LighthouseServerTest public readonly TestServer Server;
public LighthouseServerTest()
{ {
public readonly HttpClient Client; this.Server = new TestServer(new WebHostBuilder().UseStartup<TestStartup>());
public readonly TestServer Server; this.Client = this.Server.CreateClient();
}
public async Task<HttpResponseMessage> AuthenticateResponse(int number = -1, bool createUser = true)
{
if (number == -1) number = new Random().Next();
public LighthouseServerTest() const string username = "unitTestUser";
if (createUser)
{ {
this.Server = new TestServer(new WebHostBuilder().UseStartup<TestStartup>()); await using Database database = new();
this.Client = this.Server.CreateClient(); if (await database.Users.FirstOrDefaultAsync(u => u.Username == $"{username}{number}") == null)
} await database.CreateUser($"{username}{number}", HashHelper.BCryptHash($"unitTestPassword{number}"));
public async Task<HttpResponseMessage> AuthenticateResponse(int number = -1, bool createUser = true)
{
if (number == -1)
{
number = new Random().Next();
}
const string username = "unitTestUser";
if (createUser)
{
await using Database database = new();
if (await database.Users.FirstOrDefaultAsync(u => u.Username == $"{username}{number}") == null)
await database.CreateUser($"{username}{number}", HashHelper.BCryptHash($"unitTestPassword{number}"));
}
string stringContent = $"{LoginData.UsernamePrefix}{username}{number}{(char)0x00}";
HttpResponseMessage response = await this.Client.PostAsync
($"/LITTLEBIGPLANETPS3_XML/login?titleID={GameVersionHelper.LittleBigPlanet2TitleIds[0]}", new StringContent(stringContent));
return response;
} }
public async Task<LoginResult> Authenticate(int number = 0) string stringContent = $"{LoginData.UsernamePrefix}{username}{number}{(char)0x00}";
{
HttpResponseMessage response = await this.AuthenticateResponse(number);
string responseContent = LbpSerializer.StringElement("loginResult", await response.Content.ReadAsStringAsync()); HttpResponseMessage response = await this.Client.PostAsync
($"/LITTLEBIGPLANETPS3_XML/login?titleID={GameVersionHelper.LittleBigPlanet2TitleIds[0]}", new StringContent(stringContent));
return response;
}
XmlSerializer serializer = new(typeof(LoginResult)); public async Task<LoginResult> Authenticate(int number = 0)
return (LoginResult)serializer.Deserialize(new StringReader(responseContent))!; {
} HttpResponseMessage response = await this.AuthenticateResponse(number);
public Task<HttpResponseMessage> AuthenticatedRequest(string endpoint, string mmAuth) => this.AuthenticatedRequest(endpoint, mmAuth, HttpMethod.Get); string responseContent = LbpSerializer.StringElement("loginResult", await response.Content.ReadAsStringAsync());
public Task<HttpResponseMessage> AuthenticatedRequest(string endpoint, string mmAuth, HttpMethod method) XmlSerializer serializer = new(typeof(LoginResult));
{ return (LoginResult)serializer.Deserialize(new StringReader(responseContent))!;
using HttpRequestMessage requestMessage = new(method, endpoint); }
requestMessage.Headers.Add("Cookie", mmAuth);
return this.Client.SendAsync(requestMessage); public Task<HttpResponseMessage> AuthenticatedRequest(string endpoint, string mmAuth) => this.AuthenticatedRequest(endpoint, mmAuth, HttpMethod.Get);
}
public async Task<HttpResponseMessage> UploadFileEndpointRequest(string filePath) public Task<HttpResponseMessage> AuthenticatedRequest(string endpoint, string mmAuth, HttpMethod method)
{ {
byte[] bytes = await File.ReadAllBytesAsync(filePath); using HttpRequestMessage requestMessage = new(method, endpoint);
string hash = HashHelper.Sha1Hash(bytes).ToLower(); requestMessage.Headers.Add("Cookie", mmAuth);
return await this.Client.PostAsync($"/LITTLEBIGPLANETPS3_XML/upload/{hash}", new ByteArrayContent(bytes)); return this.Client.SendAsync(requestMessage);
} }
public async Task<HttpResponseMessage> UploadFileRequest(string endpoint, string filePath) public async Task<HttpResponseMessage> UploadFileEndpointRequest(string filePath)
=> await this.Client.PostAsync(endpoint, new StringContent(await File.ReadAllTextAsync(filePath))); {
byte[] bytes = await File.ReadAllBytesAsync(filePath);
string hash = HashHelper.Sha1Hash(bytes).ToLower();
public async Task<HttpResponseMessage> UploadDataRequest(string endpoint, byte[] data) return await this.Client.PostAsync($"/LITTLEBIGPLANETPS3_XML/upload/{hash}", new ByteArrayContent(bytes));
=> await this.Client.PostAsync(endpoint, new ByteArrayContent(data)); }
public async Task<HttpResponseMessage> AuthenticatedUploadFileRequest(string endpoint, string filePath, string mmAuth) public async Task<HttpResponseMessage> UploadFileRequest(string endpoint, string filePath)
{ => await this.Client.PostAsync(endpoint, new StringContent(await File.ReadAllTextAsync(filePath)));
using HttpRequestMessage requestMessage = new(HttpMethod.Post, endpoint);
requestMessage.Headers.Add("Cookie", mmAuth);
requestMessage.Content = new StringContent(await File.ReadAllTextAsync(filePath));
return await this.Client.SendAsync(requestMessage);
}
public async Task<HttpResponseMessage> AuthenticatedUploadDataRequest(string endpoint, byte[] data, string mmAuth) public async Task<HttpResponseMessage> UploadDataRequest(string endpoint, byte[] data) => await this.Client.PostAsync(endpoint, new ByteArrayContent(data));
{
using HttpRequestMessage requestMessage = new(HttpMethod.Post, endpoint); public async Task<HttpResponseMessage> AuthenticatedUploadFileRequest(string endpoint, string filePath, string mmAuth)
requestMessage.Headers.Add("Cookie", mmAuth); {
requestMessage.Content = new ByteArrayContent(data); using HttpRequestMessage requestMessage = new(HttpMethod.Post, endpoint);
return await this.Client.SendAsync(requestMessage); requestMessage.Headers.Add("Cookie", mmAuth);
} requestMessage.Content = new StringContent(await File.ReadAllTextAsync(filePath));
return await this.Client.SendAsync(requestMessage);
}
public async Task<HttpResponseMessage> AuthenticatedUploadDataRequest(string endpoint, byte[] data, string mmAuth)
{
using HttpRequestMessage requestMessage = new(HttpMethod.Post, endpoint);
requestMessage.Headers.Add("Cookie", mmAuth);
requestMessage.Content = new ByteArrayContent(data);
return await this.Client.SendAsync(requestMessage);
} }
} }

View file

@ -2,42 +2,41 @@ using System.Collections.Generic;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using Xunit; using Xunit;
namespace LBPUnion.ProjectLighthouse.Tests namespace LBPUnion.ProjectLighthouse.Tests;
public class SerializerTests
{ {
public class SerializerTests [Fact]
public void BlankElementWorks()
{ {
[Fact] Assert.Equal("<test></test>", LbpSerializer.BlankElement("test"));
public void BlankElementWorks() }
{
Assert.Equal("<test></test>", LbpSerializer.BlankElement("test"));
}
[Fact] [Fact]
public void StringElementWorks() public void StringElementWorks()
{ {
Assert.Equal("<test>asd</test>", LbpSerializer.StringElement("test", "asd")); Assert.Equal("<test>asd</test>", LbpSerializer.StringElement("test", "asd"));
Assert.Equal("<test>asd</test>", LbpSerializer.StringElement(new KeyValuePair<string, object>("test", "asd"))); Assert.Equal("<test>asd</test>", LbpSerializer.StringElement(new KeyValuePair<string, object>("test", "asd")));
} }
[Fact] [Fact]
public void TaggedStringElementWorks() public void TaggedStringElementWorks()
{ {
Assert.Equal("<test foo=\"bar\">asd</test>", LbpSerializer.TaggedStringElement("test", "asd", "foo", "bar")); Assert.Equal("<test foo=\"bar\">asd</test>", LbpSerializer.TaggedStringElement("test", "asd", "foo", "bar"));
Assert.Equal Assert.Equal
( (
"<test foo=\"bar\">asd</test>", "<test foo=\"bar\">asd</test>",
LbpSerializer.TaggedStringElement(new KeyValuePair<string, object>("test", "asd"), new KeyValuePair<string, object>("foo", "bar")) LbpSerializer.TaggedStringElement(new KeyValuePair<string, object>("test", "asd"), new KeyValuePair<string, object>("foo", "bar"))
); );
} }
[Fact] [Fact]
public void ElementsWorks() public void ElementsWorks()
{ {
Assert.Equal Assert.Equal
( (
"<test>asd</test><foo>bar</foo>", "<test>asd</test><foo>bar</foo>",
LbpSerializer.Elements(new KeyValuePair<string, object>("test", "asd"), new KeyValuePair<string, object>("foo", "bar")) LbpSerializer.Elements(new KeyValuePair<string, object>("test", "asd"), new KeyValuePair<string, object>("foo", "bar"))
); );
}
} }
} }

View file

@ -3,46 +3,45 @@ using LBPUnion.ProjectLighthouse.Types.Settings;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/plain")]
public class ClientConfigurationController : ControllerBase
{ {
[ApiController] [HttpGet("network_settings.nws")]
[Route("LITTLEBIGPLANETPS3_XML/")] [SuppressMessage("ReSharper", "StringLiteralTypo")]
[Produces("text/plain")] public IActionResult NetworkSettings()
public class ClientConfigurationController : ControllerBase
{ {
[HttpGet("network_settings.nws")] HostString hostname = this.Request.Host;
[SuppressMessage("ReSharper", "StringLiteralTypo")] return this.Ok
public IActionResult NetworkSettings() (
"ProbabilityOfPacketDelay 0.0\nMinPacketDelayFrames 0\nMaxPacketDelayFrames 3\nProbabilityOfPacketDrop 0.0\nEnableFakeConditionsForLoopback true\nNumberOfFramesPredictionAllowedForNonLocalPlayer 1000\nEnablePrediction true\nMinPredictedFrames 0\nMaxPredictedFrames 10\nAllowGameRendCameraSplit true\nFramesBeforeAgressiveCatchup 30\nPredictionPadSides 200\nPredictionPadTop 200\nPredictionPadBottom 200\nShowErrorNumbers true\nAllowModeratedLevels false\nAllowModeratedPoppetItems false\nTIMEOUT_WAIT_FOR_JOIN_RESPONSE_FROM_PREV_PARTY_HOST 50.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_HOST 30.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_MEMBER 45.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN_FRIEND 15.0\nTIMEOUT_WAIT_FOR_CONNECTION_FROM_HOST 30.0\nTIMEOUT_WAIT_FOR_ROOM_ID_TO_JOIN 60.0\nTIMEOUT_WAIT_FOR_GET_NUM_PLAYERS_ONLINE 60.0\nTIMEOUT_WAIT_FOR_SIGNALLING_CONNECTIONS 120.0\nTIMEOUT_WAIT_FOR_PARTY_DATA 60.0\nTIME_TO_WAIT_FOR_LEAVE_MESSAGE_TO_COME_BACK 20.0\nTIME_TO_WAIT_FOR_FOLLOWING_REQUESTS_TO_ARRIVE 30.0\nTIMEOUT_WAIT_FOR_FINISHED_MIGRATING_HOST 30.0\nTIMEOUT_WAIT_FOR_PARTY_LEADER_FINISH_JOINING 45.0\nTIMEOUT_WAIT_FOR_QUICKPLAY_LEVEL 60.0\nTIMEOUT_WAIT_FOR_PLAYERS_TO_JOIN 30.0\nTIMEOUT_WAIT_FOR_DIVE_IN_PLAYERS 240.0\nTIMEOUT_WAIT_FOR_FIND_BEST_ROOM 60.0\nTIMEOUT_DIVE_IN_TOTAL 300.0\nTIMEOUT_WAIT_FOR_SOCKET_CONNECTION 120.0\nTIMEOUT_WAIT_FOR_REQUEST_RESOURCE_MESSAGE 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_GET_RESOURCE_LIST 120.0\nTIMEOUT_WAIT_FOR_CLIENT_TO_LOAD_RESOURCES 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_SAVE_GAME_STATE 30.0\nTIMEOUT_WAIT_FOR_ADD_PLAYERS_TO_TAKE 30.0\nTIMEOUT_WAIT_FOR_UPDATE_FROM_CLIENT 90.0\nTIMEOUT_WAIT_FOR_HOST_TO_GET_RESOURCE_LIST 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_SAVE_GAME_STATE 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_ADD_US 30.0\nTIMEOUT_WAIT_FOR_UPDATE 60.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN 50.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_PRESENCE 60.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_CONNECTION 120.0\nSECONDS_BETWEEN_PINS_AWARDED_UPLOADS 300.0\nEnableKeepAlive true\nAllowVoIPRecordingPlayback true\nOverheatingThresholdDisallowMidgameJoin 0.95\nMaxCatchupFrames 3\nMaxLagBeforeShowLoading 23\nMinLagBeforeHideLoading 30\nLagImprovementInflectionPoint -1.0\nFlickerThreshold 2.0\nClosedDemo2014Version 1\nClosedDemo2014Expired false\nEnablePlayedFilter true\nEnableCommunityDecorations true\nGameStateUpdateRate 10.0\nGameStateUpdateRateWithConsumers 1.0\nDisableDLCPublishCheck false\nEnableDiveIn true\nEnableHackChecks false\n" +
$"TelemetryServer {hostname}\n" +
$"CDNHostName {hostname}\n" +
$"ShowLevelBoos {ServerSettings.Instance.BooingEnabled.ToString().ToLower()}\n" +
$"AllowOnlineCreate {ServerSettings.Instance.VitaCreateMode.ToString().ToLower()}\n"
);
}
[HttpGet("t_conf")]
[Produces("text/json")]
public IActionResult Conf() => this.Ok("[{\"StatusCode\":200}]");
[HttpGet("farc_hashes")]
public IActionResult FarcHashes() => this.Ok();
[HttpGet("privacySettings")]
[Produces("text/xml")]
public IActionResult PrivacySettings()
{
PrivacySettings ps = new()
{ {
HostString hostname = this.Request.Host; LevelVisibility = "all",
return this.Ok ProfileVisibility = "all",
( };
"ProbabilityOfPacketDelay 0.0\nMinPacketDelayFrames 0\nMaxPacketDelayFrames 3\nProbabilityOfPacketDrop 0.0\nEnableFakeConditionsForLoopback true\nNumberOfFramesPredictionAllowedForNonLocalPlayer 1000\nEnablePrediction true\nMinPredictedFrames 0\nMaxPredictedFrames 10\nAllowGameRendCameraSplit true\nFramesBeforeAgressiveCatchup 30\nPredictionPadSides 200\nPredictionPadTop 200\nPredictionPadBottom 200\nShowErrorNumbers true\nAllowModeratedLevels false\nAllowModeratedPoppetItems false\nTIMEOUT_WAIT_FOR_JOIN_RESPONSE_FROM_PREV_PARTY_HOST 50.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_HOST 30.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_MEMBER 45.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN_FRIEND 15.0\nTIMEOUT_WAIT_FOR_CONNECTION_FROM_HOST 30.0\nTIMEOUT_WAIT_FOR_ROOM_ID_TO_JOIN 60.0\nTIMEOUT_WAIT_FOR_GET_NUM_PLAYERS_ONLINE 60.0\nTIMEOUT_WAIT_FOR_SIGNALLING_CONNECTIONS 120.0\nTIMEOUT_WAIT_FOR_PARTY_DATA 60.0\nTIME_TO_WAIT_FOR_LEAVE_MESSAGE_TO_COME_BACK 20.0\nTIME_TO_WAIT_FOR_FOLLOWING_REQUESTS_TO_ARRIVE 30.0\nTIMEOUT_WAIT_FOR_FINISHED_MIGRATING_HOST 30.0\nTIMEOUT_WAIT_FOR_PARTY_LEADER_FINISH_JOINING 45.0\nTIMEOUT_WAIT_FOR_QUICKPLAY_LEVEL 60.0\nTIMEOUT_WAIT_FOR_PLAYERS_TO_JOIN 30.0\nTIMEOUT_WAIT_FOR_DIVE_IN_PLAYERS 240.0\nTIMEOUT_WAIT_FOR_FIND_BEST_ROOM 60.0\nTIMEOUT_DIVE_IN_TOTAL 300.0\nTIMEOUT_WAIT_FOR_SOCKET_CONNECTION 120.0\nTIMEOUT_WAIT_FOR_REQUEST_RESOURCE_MESSAGE 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_GET_RESOURCE_LIST 120.0\nTIMEOUT_WAIT_FOR_CLIENT_TO_LOAD_RESOURCES 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_SAVE_GAME_STATE 30.0\nTIMEOUT_WAIT_FOR_ADD_PLAYERS_TO_TAKE 30.0\nTIMEOUT_WAIT_FOR_UPDATE_FROM_CLIENT 90.0\nTIMEOUT_WAIT_FOR_HOST_TO_GET_RESOURCE_LIST 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_SAVE_GAME_STATE 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_ADD_US 30.0\nTIMEOUT_WAIT_FOR_UPDATE 60.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN 50.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_PRESENCE 60.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_CONNECTION 120.0\nSECONDS_BETWEEN_PINS_AWARDED_UPLOADS 300.0\nEnableKeepAlive true\nAllowVoIPRecordingPlayback true\nOverheatingThresholdDisallowMidgameJoin 0.95\nMaxCatchupFrames 3\nMaxLagBeforeShowLoading 23\nMinLagBeforeHideLoading 30\nLagImprovementInflectionPoint -1.0\nFlickerThreshold 2.0\nClosedDemo2014Version 1\nClosedDemo2014Expired false\nEnablePlayedFilter true\nEnableCommunityDecorations true\nGameStateUpdateRate 10.0\nGameStateUpdateRateWithConsumers 1.0\nDisableDLCPublishCheck false\nEnableDiveIn true\nEnableHackChecks false\n" +
$"TelemetryServer {hostname}\n" +
$"CDNHostName {hostname}\n" +
$"ShowLevelBoos {ServerSettings.Instance.BooingEnabled.ToString().ToLower()}\n" +
$"AllowOnlineCreate {ServerSettings.Instance.VitaCreateMode.ToString().ToLower()}\n"
);
}
[HttpGet("t_conf")] return this.Ok(ps.Serialize());
[Produces("text/json")]
public IActionResult Conf() => this.Ok("[{\"StatusCode\":200}]");
[HttpGet("farc_hashes")]
public IActionResult FarcHashes() => this.Ok();
[HttpGet("privacySettings")]
[Produces("text/xml")]
public IActionResult PrivacySettings()
{
PrivacySettings ps = new()
{
LevelVisibility = "all",
ProfileVisibility = "all",
};
return this.Ok(ps.Serialize());
}
} }
} }

View file

@ -11,110 +11,109 @@ using LBPUnion.ProjectLighthouse.Types.Categories;
using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Levels;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
public class CollectionController : ControllerBase
{ {
[ApiController] private readonly Database database;
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] public CollectionController(Database database)
public class CollectionController : ControllerBase
{ {
private readonly Database database; this.database = database;
}
public CollectionController(Database database) [HttpGet("user/{username}/playlists")]
{ public IActionResult GetUserPlaylists(string username) => this.Ok();
this.database = database;
}
[HttpGet("user/{username}/playlists")] [HttpGet("searches")]
public IActionResult GetUserPlaylists(string username) => this.Ok(); [HttpGet("genres")]
public async Task<IActionResult> GenresAndSearches()
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
[HttpGet("searches")] string categoriesSerialized = CollectionHelper.Categories.Aggregate
[HttpGet("genres")] (
public async Task<IActionResult> GenresAndSearches() string.Empty,
{ (current, category) =>
User? user = await this.database.UserFromGameRequest(this.Request); {
if (user == null) return this.StatusCode(403, ""); string serialized;
string categoriesSerialized = CollectionHelper.Categories.Aggregate if (category is CategoryWithUser categoryWithUser) serialized = categoryWithUser.Serialize(this.database, user);
else serialized = category.Serialize(this.database);
return current + serialized;
}
);
return this.Ok
(
LbpSerializer.TaggedStringElement
( (
string.Empty, "categories",
(current, category) => categoriesSerialized,
new Dictionary<string, object>
{ {
string serialized; {
"hint", ""
if (category is CategoryWithUser categoryWithUser) serialized = categoryWithUser.Serialize(this.database, user); },
else serialized = category.Serialize(this.database); {
"hint_start", 1
return current + serialized; },
{
"total", CollectionHelper.Categories.Count
},
} }
); )
);
}
return this.Ok [HttpGet("searches/{endpointName}")]
( public async Task<IActionResult> GetCategorySlots(string endpointName, [FromQuery] int pageStart, [FromQuery] int pageSize)
LbpSerializer.TaggedStringElement {
( User? user = await this.database.UserFromGameRequest(this.Request);
"categories", if (user == null) return this.StatusCode(403, "");
categoriesSerialized,
new Dictionary<string, object>
{
{
"hint", ""
},
{
"hint_start", 1
},
{
"total", CollectionHelper.Categories.Count
},
}
)
);
}
[HttpGet("searches/{endpointName}")] Category? category = CollectionHelper.Categories.FirstOrDefault(c => c.Endpoint == endpointName);
public async Task<IActionResult> GetCategorySlots(string endpointName, [FromQuery] int pageStart, [FromQuery] int pageSize) if (category == null) return this.NotFound();
Logger.Log("Found category " + category, LoggerLevelCategory.Instance);
List<Slot> slots;
int totalSlots;
if (category is CategoryWithUser categoryWithUser)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); slots = categoryWithUser.GetSlots(this.database, user, pageStart, pageSize).ToList();
if (user == null) return this.StatusCode(403, ""); totalSlots = categoryWithUser.GetTotalSlots(this.database, user);
Category? category = CollectionHelper.Categories.FirstOrDefault(c => c.Endpoint == endpointName);
if (category == null) return this.NotFound();
Logger.Log("Found category " + category, LoggerLevelCategory.Instance);
List<Slot> slots;
int totalSlots;
if (category is CategoryWithUser categoryWithUser)
{
slots = categoryWithUser.GetSlots(this.database, user, pageStart, pageSize).ToList();
totalSlots = categoryWithUser.GetTotalSlots(this.database, user);
}
else
{
slots = category.GetSlots(this.database, pageStart, pageSize).ToList();
totalSlots = category.GetTotalSlots(this.database);
}
string slotsSerialized = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize());
return this.Ok
(
LbpSerializer.TaggedStringElement
(
"results",
slotsSerialized,
new Dictionary<string, object>
{
{
"total", totalSlots
},
{
"hint_start", pageStart + pageSize
},
}
)
);
} }
else
{
slots = category.GetSlots(this.database, pageStart, pageSize).ToList();
totalSlots = category.GetTotalSlots(this.database);
}
string slotsSerialized = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize());
return this.Ok
(
LbpSerializer.TaggedStringElement
(
"results",
slotsSerialized,
new Dictionary<string, object>
{
{
"total", totalSlots
},
{
"hint_start", pageStart + pageSize
},
}
)
);
} }
} }

View file

@ -11,73 +11,72 @@ using LBPUnion.ProjectLighthouse.Types.Profiles;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
public class CommentController : ControllerBase
{ {
[ApiController] private readonly Database database;
[Route("LITTLEBIGPLANETPS3_XML/")] public CommentController(Database database)
[Produces("text/xml")]
public class CommentController : ControllerBase
{ {
private readonly Database database; this.database = database;
public CommentController(Database database) }
{
this.database = database;
}
[HttpGet("userComments/{username}")] [HttpGet("userComments/{username}")]
public async Task<IActionResult> GetComments(string username) public async Task<IActionResult> GetComments(string username)
{ {
List<Comment> comments = await this.database.Comments.Include List<Comment> comments = await this.database.Comments.Include
(c => c.Target) (c => c.Target)
.Include(c => c.Poster) .Include(c => c.Poster)
.Where(c => c.Target.Username == username) .Where(c => c.Target.Username == username)
.OrderByDescending(c => c.Timestamp) .OrderByDescending(c => c.Timestamp)
.ToListAsync(); .ToListAsync();
string outputXml = comments.Aggregate(string.Empty, (current, comment) => current + comment.Serialize()); string outputXml = comments.Aggregate(string.Empty, (current, comment) => current + comment.Serialize());
return this.Ok(LbpSerializer.StringElement("comments", outputXml)); return this.Ok(LbpSerializer.StringElement("comments", outputXml));
} }
[HttpPost("postUserComment/{username}")] [HttpPost("postUserComment/{username}")]
public async Task<IActionResult> PostComment(string username) public async Task<IActionResult> PostComment(string username)
{ {
this.Request.Body.Position = 0; this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(Comment)); XmlSerializer serializer = new(typeof(Comment));
Comment? comment = (Comment?)serializer.Deserialize(new StringReader(bodyString)); Comment? comment = (Comment?)serializer.Deserialize(new StringReader(bodyString));
User? poster = await this.database.UserFromGameRequest(this.Request); User? poster = await this.database.UserFromGameRequest(this.Request);
if (poster == null) return this.StatusCode(403, ""); if (poster == null) return this.StatusCode(403, "");
User? target = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); User? target = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (comment == null || target == null) return this.BadRequest(); if (comment == null || target == null) return this.BadRequest();
comment.PosterUserId = poster.UserId; comment.PosterUserId = poster.UserId;
comment.TargetUserId = target.UserId; comment.TargetUserId = target.UserId;
comment.Timestamp = TimeHelper.UnixTimeMilliseconds(); comment.Timestamp = TimeHelper.UnixTimeMilliseconds();
this.database.Comments.Add(comment); this.database.Comments.Add(comment);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
return this.Ok(); return this.Ok();
} }
[HttpPost("deleteUserComment/{username}")] [HttpPost("deleteUserComment/{username}")]
public async Task<IActionResult> DeleteComment([FromQuery] int commentId, string username) public async Task<IActionResult> DeleteComment([FromQuery] int commentId, string username)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (user == null) return this.StatusCode(403, "");
Comment? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId); Comment? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId);
if (comment == null) return this.NotFound(); if (comment == null) return this.NotFound();
if (comment.TargetUserId != user.UserId && comment.PosterUserId != user.UserId) return this.StatusCode(403, ""); if (comment.TargetUserId != user.UserId && comment.PosterUserId != user.UserId) return this.StatusCode(403, "");
this.database.Comments.Remove(comment); this.database.Comments.Remove(comment);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
return this.Ok(); return this.Ok();
}
} }
} }

View file

@ -1,12 +1,11 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")]
public class DeveloperController : Controller
{ {
[ApiController] [HttpGet("/developer_videos")]
[Route("LITTLEBIGPLANETPS3_XML/")] public IActionResult DeveloperVideos() => this.Ok();
public class DeveloperController : Controller
{
[HttpGet("/developer_videos")]
public IActionResult DeveloperVideos() => this.Ok();
}
} }

View file

@ -7,123 +7,122 @@ using LBPUnion.ProjectLighthouse.Types.Levels;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
{
[ApiController] [ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
// [Produces("text/plain")] // [Produces("text/plain")]
public class EnterLevelController : ControllerBase public class EnterLevelController : ControllerBase
{
private readonly Database database;
public EnterLevelController(Database database)
{ {
private readonly Database database; this.database = database;
}
public EnterLevelController(Database database) [HttpPost("play/user/{slotId}")]
public async Task<IActionResult> PlayLevel(int slotId)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.StatusCode(403, "");
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameVersion gameVersion = token.GameVersion;
IQueryable<VisitedLevel> visited = this.database.VisitedLevels.Where(s => s.SlotId == slotId && s.UserId == user.UserId);
VisitedLevel? v;
if (!visited.Any())
{ {
this.database = database;
}
[HttpPost("play/user/{slotId}")]
public async Task<IActionResult> PlayLevel(int slotId)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.StatusCode(403, "");
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameVersion gameVersion = token.GameVersion;
IQueryable<VisitedLevel> visited = this.database.VisitedLevels.Where(s => s.SlotId == slotId && s.UserId == user.UserId);
VisitedLevel? v;
if (!visited.Any())
{
switch (gameVersion)
{
case GameVersion.LittleBigPlanet2:
slot.PlaysLBP2Unique++;
break;
case GameVersion.LittleBigPlanet3:
slot.PlaysLBP3Unique++;
break;
case GameVersion.LittleBigPlanetVita:
slot.PlaysLBPVitaUnique++;
break;
default: return this.BadRequest();
}
v = new VisitedLevel();
v.SlotId = slotId;
v.UserId = user.UserId;
this.database.VisitedLevels.Add(v);
}
else
{
v = await visited.FirstOrDefaultAsync();
}
if (v == null) return this.NotFound();
switch (gameVersion) switch (gameVersion)
{ {
case GameVersion.LittleBigPlanet2: case GameVersion.LittleBigPlanet2:
slot.PlaysLBP2++; slot.PlaysLBP2Unique++;
v.PlaysLBP2++;
break; break;
case GameVersion.LittleBigPlanet3: case GameVersion.LittleBigPlanet3:
slot.PlaysLBP3++; slot.PlaysLBP3Unique++;
v.PlaysLBP3++;
break; break;
case GameVersion.LittleBigPlanetVita: case GameVersion.LittleBigPlanetVita:
slot.PlaysLBPVita++; slot.PlaysLBPVitaUnique++;
v.PlaysLBPVita++;
break; break;
case GameVersion.LittleBigPlanetPSP: throw new NotImplementedException(); default: return this.BadRequest();
case GameVersion.Unknown:
default:
return this.BadRequest();
} }
await this.database.SaveChangesAsync(); v = new VisitedLevel();
v.SlotId = slotId;
return this.Ok(); v.UserId = user.UserId;
this.database.VisitedLevels.Add(v);
} }
else
// Only used in LBP1
[HttpGet("enterLevel/{id:int}")]
public async Task<IActionResult> EnterLevel(int id)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); v = await visited.FirstOrDefaultAsync();
if (user == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound();
IQueryable<VisitedLevel> visited = this.database.VisitedLevels.Where(s => s.SlotId == id && s.UserId == user.UserId);
VisitedLevel? v;
if (!visited.Any())
{
slot.PlaysLBP1Unique++;
v = new VisitedLevel();
v.SlotId = id;
v.UserId = user.UserId;
this.database.VisitedLevels.Add(v);
}
else
{
v = await visited.FirstOrDefaultAsync();
}
if (v == null) return this.NotFound();
slot.PlaysLBP1++;
v.PlaysLBP1++;
await this.database.SaveChangesAsync();
return this.Ok();
} }
if (v == null) return this.NotFound();
switch (gameVersion)
{
case GameVersion.LittleBigPlanet2:
slot.PlaysLBP2++;
v.PlaysLBP2++;
break;
case GameVersion.LittleBigPlanet3:
slot.PlaysLBP3++;
v.PlaysLBP3++;
break;
case GameVersion.LittleBigPlanetVita:
slot.PlaysLBPVita++;
v.PlaysLBPVita++;
break;
case GameVersion.LittleBigPlanetPSP: throw new NotImplementedException();
case GameVersion.Unknown:
default:
return this.BadRequest();
}
await this.database.SaveChangesAsync();
return this.Ok();
}
// Only used in LBP1
[HttpGet("enterLevel/{id:int}")]
public async Task<IActionResult> EnterLevel(int id)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound();
IQueryable<VisitedLevel> visited = this.database.VisitedLevels.Where(s => s.SlotId == id && s.UserId == user.UserId);
VisitedLevel? v;
if (!visited.Any())
{
slot.PlaysLBP1Unique++;
v = new VisitedLevel();
v.SlotId = id;
v.UserId = user.UserId;
this.database.VisitedLevels.Add(v);
}
else
{
v = await visited.FirstOrDefaultAsync();
}
if (v == null) return this.NotFound();
slot.PlaysLBP1++;
v.PlaysLBP1++;
await this.database.SaveChangesAsync();
return this.Ok();
} }
} }

View file

@ -11,88 +11,87 @@ using LBPUnion.ProjectLighthouse.Types.Profiles;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")]
public class FriendsController : ControllerBase
{ {
[ApiController] private readonly Database database;
[Route("LITTLEBIGPLANETPS3_XML/")]
public class FriendsController : ControllerBase public FriendsController(Database database)
{ {
private readonly Database database; this.database = database;
}
public FriendsController(Database database) [HttpPost("npdata")]
public async Task<IActionResult> NPData()
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(NPData));
NPData? npData = (NPData?)serializer.Deserialize(new StringReader(bodyString));
if (npData == null) return this.BadRequest();
List<User> friends = new();
foreach (string friendName in npData.Friends)
{ {
this.database = database; User? friend = await this.database.Users.FirstOrDefaultAsync(u => u.Username == friendName);
if (friend == null) continue;
friends.Add(friend);
} }
[HttpPost("npdata")] List<int> blockedUsers = new();
public async Task<IActionResult> NPData() foreach (string blockedUserName in npData.BlockedUsers)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); User? blockedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == blockedUserName);
if (user == null) return this.StatusCode(403, ""); if (blockedUser == null) continue;
this.Request.Body.Position = 0; blockedUsers.Add(blockedUser.UserId);
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(NPData));
NPData? npData = (NPData?)serializer.Deserialize(new StringReader(bodyString));
if (npData == null) return this.BadRequest();
List<User> friends = new();
foreach (string friendName in npData.Friends)
{
User? friend = await this.database.Users.FirstOrDefaultAsync(u => u.Username == friendName);
if (friend == null) continue;
friends.Add(friend);
}
List<int> blockedUsers = new();
foreach (string blockedUserName in npData.BlockedUsers)
{
User? blockedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == blockedUserName);
if (blockedUser == null) continue;
blockedUsers.Add(blockedUser.UserId);
}
if (FriendHelper.FriendIdsByUserId.ContainsKey(user.UserId))
{
FriendHelper.FriendIdsByUserId.Remove(user.UserId);
FriendHelper.BlockedIdsByUserId.Remove(user.UserId);
}
FriendHelper.FriendIdsByUserId.Add(user.UserId, friends.Select(u => u.UserId).ToArray());
FriendHelper.BlockedIdsByUserId.Add(user.UserId, blockedUsers.ToArray());
string friendsSerialized = friends.Aggregate(string.Empty, (current, user1) => current + LbpSerializer.StringElement("npHandle", user1.Username));
return this.Ok(LbpSerializer.StringElement("npdata", LbpSerializer.StringElement("friends", friendsSerialized)));
} }
[HttpGet("myFriends")] if (FriendHelper.FriendIdsByUserId.ContainsKey(user.UserId))
public async Task<IActionResult> MyFriends()
{ {
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); FriendHelper.FriendIdsByUserId.Remove(user.UserId);
FriendHelper.BlockedIdsByUserId.Remove(user.UserId);
if (userAndToken == null) return this.StatusCode(403, "");
// ReSharper disable once PossibleInvalidOperationException
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
if (!FriendHelper.FriendIdsByUserId.TryGetValue(user.UserId, out int[]? friendIds) || friendIds == null)
return this.Ok(LbpSerializer.BlankElement("myFriends"));
string friends = "";
foreach (int friendId in friendIds)
{
User? friend = await this.database.Users.Include(u => u.Location).FirstOrDefaultAsync(u => u.UserId == friendId);
if (friend == null) continue;
friends += friend.Serialize(gameToken.GameVersion);
}
return this.Ok(LbpSerializer.StringElement("myFriends", friends));
} }
FriendHelper.FriendIdsByUserId.Add(user.UserId, friends.Select(u => u.UserId).ToArray());
FriendHelper.BlockedIdsByUserId.Add(user.UserId, blockedUsers.ToArray());
string friendsSerialized = friends.Aggregate(string.Empty, (current, user1) => current + LbpSerializer.StringElement("npHandle", user1.Username));
return this.Ok(LbpSerializer.StringElement("npdata", LbpSerializer.StringElement("friends", friendsSerialized)));
}
[HttpGet("myFriends")]
public async Task<IActionResult> MyFriends()
{
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
if (userAndToken == null) return this.StatusCode(403, "");
// ReSharper disable once PossibleInvalidOperationException
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
if (!FriendHelper.FriendIdsByUserId.TryGetValue(user.UserId, out int[]? friendIds) || friendIds == null)
return this.Ok(LbpSerializer.BlankElement("myFriends"));
string friends = "";
foreach (int friendId in friendIds)
{
User? friend = await this.database.Users.Include(u => u.Location).FirstOrDefaultAsync(u => u.UserId == friendId);
if (friend == null) continue;
friends += friend.Serialize(gameToken.GameVersion);
}
return this.Ok(LbpSerializer.StringElement("myFriends", friends));
} }
} }

View file

@ -2,26 +2,25 @@ using System;
using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Levels;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/tags")]
[Produces("text/plain")]
public class LevelTagsController : ControllerBase
{ {
[ApiController] [HttpGet]
[Route("LITTLEBIGPLANETPS3_XML/tags")] public IActionResult Get()
[Produces("text/plain")]
public class LevelTagsController : ControllerBase
{ {
[HttpGet] string[] tags = Enum.GetNames(typeof(LevelTags));
public IActionResult Get()
int i = 0;
foreach (string tag in tags)
{ {
string[] tags = Enum.GetNames(typeof(LevelTags)); tags[i] = $"TAG_{tag.Replace("_", "-")}";
i++;
int i = 0;
foreach (string tag in tags)
{
tags[i] = $"TAG_{tag.Replace("_", "-")}";
i++;
}
return this.Ok(string.Join(",", tags));
} }
return this.Ok(string.Join(",", tags));
} }
} }

View file

@ -9,209 +9,208 @@ using LBPUnion.ProjectLighthouse.Types.Levels;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
public class ListController : ControllerBase
{ {
[ApiController] private readonly Database database;
[Route("LITTLEBIGPLANETPS3_XML/")] public ListController(Database database)
[Produces("text/xml")]
public class ListController : ControllerBase
{ {
private readonly Database database; this.database = database;
public ListController(Database database)
{
this.database = database;
}
#region Levels
#region Level Queue (lolcatftw)
[HttpGet("slots/lolcatftw/{username}")]
public async Task<IActionResult> GetLevelQueue(string username, [FromQuery] int pageSize, [FromQuery] int pageStart)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameVersion gameVersion = token.GameVersion;
IEnumerable<QueuedLevel> queuedLevels = this.database.QueuedLevels.Include(q => q.User)
.Include(q => q.Slot)
.Include(q => q.Slot.Location)
.Include(q => q.Slot.Creator)
.Where(q => q.Slot.GameVersion <= gameVersion)
.Where(q => q.User.Username == username)
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30))
.AsEnumerable();
string response = queuedLevels.Aggregate(string.Empty, (current, q) => current + q.Slot.Serialize());
return this.Ok
(
LbpSerializer.TaggedStringElement
("slots", response, "total", this.database.QueuedLevels.Include(q => q.User).Count(q => q.User.Username == username))
);
}
[HttpPost("lolcatftw/add/user/{id:int}")]
public async Task<IActionResult> AddQueuedLevel(int id)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound();
await this.database.QueueLevel(user, slot);
return this.Ok();
}
[HttpPost("lolcatftw/remove/user/{id:int}")]
public async Task<IActionResult> RemoveQueuedLevel(int id)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound();
await this.database.UnqueueLevel(user, slot);
return this.Ok();
}
[HttpPost("lolcatftw/clear")]
public async Task<IActionResult> ClearQueuedLevels()
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
this.database.QueuedLevels.RemoveRange(this.database.QueuedLevels.Where(q => q.UserId == user.UserId));
await this.database.SaveChangesAsync();
return this.Ok();
}
#endregion
#region Hearted Levels
[HttpGet("favouriteSlots/{username}")]
public async Task<IActionResult> GetFavouriteSlots(string username, [FromQuery] int pageSize, [FromQuery] int pageStart)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameVersion gameVersion = token.GameVersion;
IEnumerable<HeartedLevel> heartedLevels = this.database.HeartedLevels.Include(q => q.User)
.Include(q => q.Slot)
.Include(q => q.Slot.Location)
.Include(q => q.Slot.Creator)
.Where(q => q.Slot.GameVersion <= gameVersion)
.Where(q => q.User.Username == username)
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30))
.AsEnumerable();
string response = heartedLevels.Aggregate(string.Empty, (current, q) => current + q.Slot.Serialize());
return this.Ok
(
LbpSerializer.TaggedStringElement
("favouriteSlots", response, "total", this.database.HeartedLevels.Include(q => q.User).Count(q => q.User.Username == username))
);
}
[HttpPost("favourite/slot/user/{id:int}")]
public async Task<IActionResult> AddFavouriteSlot(int id)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound();
await this.database.HeartLevel(user, slot);
return this.Ok();
}
[HttpPost("unfavourite/slot/user/{id:int}")]
public async Task<IActionResult> RemoveFavouriteSlot(int id)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound();
await this.database.UnheartLevel(user, slot);
return this.Ok();
}
#endregion
#endregion Levels
#region Users
[HttpGet("favouriteUsers/{username}")]
public async Task<IActionResult> GetFavouriteUsers(string username, [FromQuery] int pageSize, [FromQuery] int pageStart)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
IEnumerable<HeartedProfile> heartedProfiles = this.database.HeartedProfiles.Include
(q => q.User)
.Include(q => q.HeartedUser)
.Include(q => q.HeartedUser.Location)
.Where(q => q.User.Username == username)
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30))
.AsEnumerable();
string response = heartedProfiles.Aggregate(string.Empty, (current, q) => current + q.HeartedUser.Serialize(token.GameVersion));
return this.Ok
(
LbpSerializer.TaggedStringElement
("favouriteUsers", response, "total", this.database.HeartedProfiles.Include(q => q.User).Count(q => q.User.Username == username))
);
}
[HttpPost("favourite/user/{username}")]
public async Task<IActionResult> AddFavouriteUser(string username)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (heartedUser == null) return this.NotFound();
await this.database.HeartUser(user, heartedUser);
return this.Ok();
}
[HttpPost("unfavourite/user/{username}")]
public async Task<IActionResult> RemoveFavouriteUser(string username)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (heartedUser == null) return this.NotFound();
await this.database.UnheartUser(user, heartedUser);
return this.Ok();
}
#endregion
} }
#region Levels
#region Level Queue (lolcatftw)
[HttpGet("slots/lolcatftw/{username}")]
public async Task<IActionResult> GetLevelQueue(string username, [FromQuery] int pageSize, [FromQuery] int pageStart)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameVersion gameVersion = token.GameVersion;
IEnumerable<QueuedLevel> queuedLevels = this.database.QueuedLevels.Include(q => q.User)
.Include(q => q.Slot)
.Include(q => q.Slot.Location)
.Include(q => q.Slot.Creator)
.Where(q => q.Slot.GameVersion <= gameVersion)
.Where(q => q.User.Username == username)
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30))
.AsEnumerable();
string response = queuedLevels.Aggregate(string.Empty, (current, q) => current + q.Slot.Serialize());
return this.Ok
(
LbpSerializer.TaggedStringElement
("slots", response, "total", this.database.QueuedLevels.Include(q => q.User).Count(q => q.User.Username == username))
);
}
[HttpPost("lolcatftw/add/user/{id:int}")]
public async Task<IActionResult> AddQueuedLevel(int id)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound();
await this.database.QueueLevel(user, slot);
return this.Ok();
}
[HttpPost("lolcatftw/remove/user/{id:int}")]
public async Task<IActionResult> RemoveQueuedLevel(int id)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound();
await this.database.UnqueueLevel(user, slot);
return this.Ok();
}
[HttpPost("lolcatftw/clear")]
public async Task<IActionResult> ClearQueuedLevels()
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
this.database.QueuedLevels.RemoveRange(this.database.QueuedLevels.Where(q => q.UserId == user.UserId));
await this.database.SaveChangesAsync();
return this.Ok();
}
#endregion
#region Hearted Levels
[HttpGet("favouriteSlots/{username}")]
public async Task<IActionResult> GetFavouriteSlots(string username, [FromQuery] int pageSize, [FromQuery] int pageStart)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameVersion gameVersion = token.GameVersion;
IEnumerable<HeartedLevel> heartedLevels = this.database.HeartedLevels.Include(q => q.User)
.Include(q => q.Slot)
.Include(q => q.Slot.Location)
.Include(q => q.Slot.Creator)
.Where(q => q.Slot.GameVersion <= gameVersion)
.Where(q => q.User.Username == username)
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30))
.AsEnumerable();
string response = heartedLevels.Aggregate(string.Empty, (current, q) => current + q.Slot.Serialize());
return this.Ok
(
LbpSerializer.TaggedStringElement
("favouriteSlots", response, "total", this.database.HeartedLevels.Include(q => q.User).Count(q => q.User.Username == username))
);
}
[HttpPost("favourite/slot/user/{id:int}")]
public async Task<IActionResult> AddFavouriteSlot(int id)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound();
await this.database.HeartLevel(user, slot);
return this.Ok();
}
[HttpPost("unfavourite/slot/user/{id:int}")]
public async Task<IActionResult> RemoveFavouriteSlot(int id)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound();
await this.database.UnheartLevel(user, slot);
return this.Ok();
}
#endregion
#endregion Levels
#region Users
[HttpGet("favouriteUsers/{username}")]
public async Task<IActionResult> GetFavouriteUsers(string username, [FromQuery] int pageSize, [FromQuery] int pageStart)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
IEnumerable<HeartedProfile> heartedProfiles = this.database.HeartedProfiles.Include
(q => q.User)
.Include(q => q.HeartedUser)
.Include(q => q.HeartedUser.Location)
.Where(q => q.User.Username == username)
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30))
.AsEnumerable();
string response = heartedProfiles.Aggregate(string.Empty, (current, q) => current + q.HeartedUser.Serialize(token.GameVersion));
return this.Ok
(
LbpSerializer.TaggedStringElement
("favouriteUsers", response, "total", this.database.HeartedProfiles.Include(q => q.User).Count(q => q.User.Username == username))
);
}
[HttpPost("favourite/user/{username}")]
public async Task<IActionResult> AddFavouriteUser(string username)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (heartedUser == null) return this.NotFound();
await this.database.HeartUser(user, heartedUser);
return this.Ok();
}
[HttpPost("unfavourite/user/{username}")]
public async Task<IActionResult> RemoveFavouriteUser(string username)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (heartedUser == null) return this.NotFound();
await this.database.UnheartUser(user, heartedUser);
return this.Ok();
}
#endregion
} }

View file

@ -11,144 +11,143 @@ using LBPUnion.ProjectLighthouse.Types.Settings;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
{
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/login")]
[Produces("text/xml")]
public class LoginController : ControllerBase
{
private readonly Database database;
public LoginController(Database database) [ApiController]
[Route("LITTLEBIGPLANETPS3_XML/login")]
[Produces("text/xml")]
public class LoginController : ControllerBase
{
private readonly Database database;
public LoginController(Database database)
{
this.database = database;
}
[HttpPost]
public async Task<IActionResult> Login([FromQuery] string? titleId)
{
titleId ??= "";
string body = await new StreamReader(this.Request.Body).ReadToEndAsync();
LoginData? loginData;
try
{ {
this.database = database; loginData = LoginData.CreateFromString(body);
}
catch
{
loginData = null;
} }
[HttpPost] if (loginData == null)
public async Task<IActionResult> Login([FromQuery] string? titleId)
{ {
titleId ??= ""; Logger.Log("loginData was null, rejecting login", LoggerLevelLogin.Instance);
return this.BadRequest();
}
string body = await new StreamReader(this.Request.Body).ReadToEndAsync(); IPAddress? remoteIpAddress = this.HttpContext.Connection.RemoteIpAddress;
if (remoteIpAddress == null)
{
Logger.Log("unable to determine ip, rejecting login", LoggerLevelLogin.Instance);
return this.StatusCode(403, ""); // 403 probably isnt the best status code for this, but whatever
}
LoginData? loginData; string ipAddress = remoteIpAddress.ToString();
try
// Get an existing token from the IP & username
GameToken? token = await this.database.GameTokens.Include
(t => t.User)
.FirstOrDefaultAsync(t => t.UserLocation == ipAddress && t.User.Username == loginData.Username && !t.Used);
if (token == null) // If we cant find an existing token, try to generate a new one
{
token = await this.database.AuthenticateUser(loginData, ipAddress, titleId);
if (token == null)
{ {
loginData = LoginData.CreateFromString(body); Logger.Log("unable to find/generate a token, rejecting login", LoggerLevelLogin.Instance);
return this.StatusCode(403, ""); // If not, then 403.
} }
catch }
User? user = await this.database.UserFromGameToken(token, true);
if (user == null || user.Banned)
{
Logger.Log("unable to find a user from a token, rejecting login", LoggerLevelLogin.Instance);
return this.StatusCode(403, "");
}
if (ServerSettings.Instance.UseExternalAuth)
{
if (ServerSettings.Instance.BlockDeniedUsers)
{ {
loginData = null; string ipAddressAndName = $"{token.UserLocation}|{user.Username}";
} if (DeniedAuthenticationHelper.RecentlyDenied(ipAddressAndName) || DeniedAuthenticationHelper.GetAttempts(ipAddressAndName) > 3)
if (loginData == null)
{
Logger.Log("loginData was null, rejecting login", LoggerLevelLogin.Instance);
return this.BadRequest();
}
IPAddress? remoteIpAddress = this.HttpContext.Connection.RemoteIpAddress;
if (remoteIpAddress == null)
{
Logger.Log("unable to determine ip, rejecting login", LoggerLevelLogin.Instance);
return this.StatusCode(403, ""); // 403 probably isnt the best status code for this, but whatever
}
string ipAddress = remoteIpAddress.ToString();
// Get an existing token from the IP & username
GameToken? token = await this.database.GameTokens.Include
(t => t.User)
.FirstOrDefaultAsync(t => t.UserLocation == ipAddress && t.User.Username == loginData.Username && !t.Used);
if (token == null) // If we cant find an existing token, try to generate a new one
{
token = await this.database.AuthenticateUser(loginData, ipAddress, titleId);
if (token == null)
{ {
Logger.Log("unable to find/generate a token, rejecting login", LoggerLevelLogin.Instance); this.database.AuthenticationAttempts.RemoveRange
return this.StatusCode(403, ""); // If not, then 403. (this.database.AuthenticationAttempts.Include(a => a.GameToken).Where(a => a.GameToken.UserId == user.UserId));
DeniedAuthenticationHelper.AddAttempt(ipAddressAndName);
await this.database.SaveChangesAsync();
Logger.Log("too many denied logins, rejecting login", LoggerLevelLogin.Instance);
return this.StatusCode(403, "");
} }
} }
User? user = await this.database.UserFromGameToken(token, true); if (this.database.UserApprovedIpAddresses.Where(a => a.UserId == user.UserId).Select(a => a.IpAddress).Contains(ipAddress))
if (user == null || user.Banned)
{
Logger.Log("unable to find a user from a token, rejecting login", LoggerLevelLogin.Instance);
return this.StatusCode(403, "");
}
if (ServerSettings.Instance.UseExternalAuth)
{
if (ServerSettings.Instance.BlockDeniedUsers)
{
string ipAddressAndName = $"{token.UserLocation}|{user.Username}";
if (DeniedAuthenticationHelper.RecentlyDenied(ipAddressAndName) || DeniedAuthenticationHelper.GetAttempts(ipAddressAndName) > 3)
{
this.database.AuthenticationAttempts.RemoveRange
(this.database.AuthenticationAttempts.Include(a => a.GameToken).Where(a => a.GameToken.UserId == user.UserId));
DeniedAuthenticationHelper.AddAttempt(ipAddressAndName);
await this.database.SaveChangesAsync();
Logger.Log("too many denied logins, rejecting login", LoggerLevelLogin.Instance);
return this.StatusCode(403, "");
}
}
if (this.database.UserApprovedIpAddresses.Where(a => a.UserId == user.UserId).Select(a => a.IpAddress).Contains(ipAddress))
{
token.Approved = true;
}
else
{
AuthenticationAttempt authAttempt = new()
{
GameToken = token,
GameTokenId = token.TokenId,
Timestamp = TimestampHelper.Timestamp,
IPAddress = ipAddress,
Platform = token.GameVersion == GameVersion.LittleBigPlanetVita ? Platform.Vita : Platform.PS3, // TODO: properly identify RPCS3
};
this.database.AuthenticationAttempts.Add(authAttempt);
}
}
else
{ {
token.Approved = true; token.Approved = true;
} }
else
await this.database.SaveChangesAsync();
if (!token.Approved)
{ {
Logger.Log("token unapproved, rejecting login", LoggerLevelLogin.Instance); AuthenticationAttempt authAttempt = new()
return this.StatusCode(403, "");
}
Logger.Log($"Successfully logged in user {user.Username} as {token.GameVersion} client ({titleId})", LoggerLevelLogin.Instance);
// After this point we are now considering this session as logged in.
// We just logged in with the token. Mark it as used so someone else doesnt try to use it,
// and so we don't pick the same token up when logging in later.
token.Used = true;
await this.database.SaveChangesAsync();
// Create a new room on LBP2/3/Vita
if (token.GameVersion != GameVersion.LittleBigPlanet1) RoomHelper.CreateRoom(user, token.GameVersion);
return this.Ok
(
new LoginResult
{ {
AuthTicket = "MM_AUTH=" + token.UserToken, GameToken = token,
LbpEnvVer = ServerStatics.ServerName, GameTokenId = token.TokenId,
}.Serialize() Timestamp = TimestampHelper.Timestamp,
); IPAddress = ipAddress,
Platform = token.GameVersion == GameVersion.LittleBigPlanetVita ? Platform.Vita : Platform.PS3, // TODO: properly identify RPCS3
};
this.database.AuthenticationAttempts.Add(authAttempt);
}
} }
else
{
token.Approved = true;
}
await this.database.SaveChangesAsync();
if (!token.Approved)
{
Logger.Log("token unapproved, rejecting login", LoggerLevelLogin.Instance);
return this.StatusCode(403, "");
}
Logger.Log($"Successfully logged in user {user.Username} as {token.GameVersion} client ({titleId})", LoggerLevelLogin.Instance);
// After this point we are now considering this session as logged in.
// We just logged in with the token. Mark it as used so someone else doesnt try to use it,
// and so we don't pick the same token up when logging in later.
token.Used = true;
await this.database.SaveChangesAsync();
// Create a new room on LBP2/3/Vita
if (token.GameVersion != GameVersion.LittleBigPlanet1) RoomHelper.CreateRoom(user, token.GameVersion);
return this.Ok
(
new LoginResult
{
AuthTicket = "MM_AUTH=" + token.UserToken,
LbpEnvVer = ServerStatics.ServerName,
}.Serialize()
);
} }
} }

View file

@ -14,96 +14,115 @@ using LBPUnion.ProjectLighthouse.Types.Match;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
{
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
public class MatchController : ControllerBase
{
private readonly Database database;
public MatchController(Database database) [ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
public class MatchController : ControllerBase
{
private readonly Database database;
public MatchController(Database database)
{
this.database = database;
}
[HttpPost("match")]
[Produces("text/plain")]
public async Task<IActionResult> Match()
{
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
if (userAndToken == null) return this.StatusCode(403, "");
// ReSharper disable once PossibleInvalidOperationException
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
#region Parse match data
// Example POST /match: [UpdateMyPlayerData,["Player":"FireGamer9872"]]
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
if (bodyString.Length == 0 || bodyString[0] != '[') return this.BadRequest();
Logger.Log("Received match data: " + bodyString, LoggerLevelMatch.Instance);
IMatchData? matchData;
try
{ {
this.database = database; matchData = MatchHelper.Deserialize(bodyString);
}
catch(Exception e)
{
Logger.Log("Exception while parsing matchData: ", LoggerLevelMatch.Instance);
string[] lines = e.ToDetailedException().Split("\n");
foreach (string line in lines) Logger.Log(line, LoggerLevelMatch.Instance);
return this.BadRequest();
} }
[HttpPost("match")] if (matchData == null)
[Produces("text/plain")]
public async Task<IActionResult> Match()
{ {
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); Logger.Log("Could not parse match data: matchData is null", LoggerLevelMatch.Instance);
return this.BadRequest();
}
if (userAndToken == null) return this.StatusCode(403, ""); Logger.Log($"Parsed match from {user.Username} (type: {matchData.GetType()})", LoggerLevelMatch.Instance);
// ReSharper disable once PossibleInvalidOperationException #endregion
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
#region Parse match data await LastContactHelper.SetLastContact(user, gameToken.GameVersion);
// Example POST /match: [UpdateMyPlayerData,["Player":"FireGamer9872"]] #region Process match data
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); if (matchData is UpdateMyPlayerData playerData)
{
MatchHelper.SetUserLocation(user.UserId, gameToken.UserLocation);
Room? room = RoomHelper.FindRoomByUser(user, gameToken.GameVersion, true);
if (bodyString.Length == 0 || bodyString[0] != '[') return this.BadRequest(); if (playerData.RoomState != null)
if (room != null && Equals(room.Host, user))
room.State = (RoomState)playerData.RoomState;
}
Logger.Log("Received match data: " + bodyString, LoggerLevelMatch.Instance); if (matchData is FindBestRoom && MatchHelper.UserLocations.Count > 1)
{
FindBestRoomResponse? response = RoomHelper.FindBestRoom(user, gameToken.GameVersion, gameToken.UserLocation);
IMatchData? matchData; if (response == null) return this.NotFound();
try
string serialized = JsonSerializer.Serialize(response, typeof(FindBestRoomResponse));
foreach (Player player in response.Players) MatchHelper.AddUserRecentlyDivedIn(user.UserId, player.User.UserId);
return this.Ok($"[{{\"StatusCode\":200}},{serialized}]");
}
if (matchData is CreateRoom createRoom && MatchHelper.UserLocations.Count >= 1)
{
List<User> users = new();
foreach (string playerUsername in createRoom.Players)
{ {
matchData = MatchHelper.Deserialize(bodyString); User? player = await this.database.Users.FirstOrDefaultAsync(u => u.Username == playerUsername);
} // ReSharper disable once ConditionIsAlwaysTrueOrFalse
catch(Exception e) if (player != null) users.Add(player);
{ else return this.BadRequest();
Logger.Log("Exception while parsing matchData: ", LoggerLevelMatch.Instance);
string[] lines = e.ToDetailedException().Split("\n");
foreach (string line in lines) Logger.Log(line, LoggerLevelMatch.Instance);
return this.BadRequest();
} }
if (matchData == null) // Create a new one as requested
{ RoomHelper.CreateRoom(users, gameToken.GameVersion, createRoom.RoomSlot);
Logger.Log("Could not parse match data: matchData is null", LoggerLevelMatch.Instance); }
return this.BadRequest();
}
Logger.Log($"Parsed match from {user.Username} (type: {matchData.GetType()})", LoggerLevelMatch.Instance); if (matchData is UpdatePlayersInRoom updatePlayersInRoom)
{
Room? room = RoomHelper.Rooms.FirstOrDefault(r => r.Host == user);
#endregion if (room != null)
await LastContactHelper.SetLastContact(user, gameToken.GameVersion);
#region Process match data
if (matchData is UpdateMyPlayerData playerData)
{
MatchHelper.SetUserLocation(user.UserId, gameToken.UserLocation);
Room? room = RoomHelper.FindRoomByUser(user, gameToken.GameVersion, true);
if (playerData.RoomState != null)
if (room != null && Equals(room.Host, user))
room.State = (RoomState)playerData.RoomState;
}
if (matchData is FindBestRoom && MatchHelper.UserLocations.Count > 1)
{
FindBestRoomResponse? response = RoomHelper.FindBestRoom(user, gameToken.GameVersion, gameToken.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);
return this.Ok($"[{{\"StatusCode\":200}},{serialized}]");
}
if (matchData is CreateRoom createRoom && MatchHelper.UserLocations.Count >= 1)
{ {
List<User> users = new(); List<User> users = new();
foreach (string playerUsername in createRoom.Players) foreach (string playerUsername in updatePlayersInRoom.Players)
{ {
User? player = await this.database.Users.FirstOrDefaultAsync(u => u.Username == playerUsername); User? player = await this.database.Users.FirstOrDefaultAsync(u => u.Username == playerUsername);
// ReSharper disable once ConditionIsAlwaysTrueOrFalse // ReSharper disable once ConditionIsAlwaysTrueOrFalse
@ -111,33 +130,13 @@ namespace LBPUnion.ProjectLighthouse.Controllers
else return this.BadRequest(); else return this.BadRequest();
} }
// Create a new one as requested room.Players = users;
RoomHelper.CreateRoom(users, gameToken.GameVersion, createRoom.RoomSlot); RoomHelper.CleanupRooms(null, room);
} }
if (matchData is UpdatePlayersInRoom updatePlayersInRoom)
{
Room? room = RoomHelper.Rooms.FirstOrDefault(r => r.Host == user);
if (room != null)
{
List<User> users = new();
foreach (string playerUsername in updatePlayersInRoom.Players)
{
User? player = await this.database.Users.FirstOrDefaultAsync(u => u.Username == playerUsername);
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (player != null) users.Add(player);
else return this.BadRequest();
}
room.Players = users;
RoomHelper.CleanupRooms(null, room);
}
}
#endregion
return this.Ok("[{\"StatusCode\":200}]");
} }
#endregion
return this.Ok("[{\"StatusCode\":200}]");
} }
} }

View file

@ -8,36 +8,36 @@ using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Settings; using LBPUnion.ProjectLighthouse.Types.Settings;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/plain")]
public class MessageController : ControllerBase
{ {
[ApiController] private readonly Database database;
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/plain")] public MessageController(Database database)
public class MessageController : ControllerBase
{ {
private readonly Database database; this.database = database;
}
public MessageController(Database database) [HttpGet("eula")]
{ public async Task<IActionResult> Eula()
this.database = database; {
} User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
[HttpGet("eula")] return this.Ok($"{EulaHelper.License}\n{ServerSettings.Instance.EulaText}");
public async Task<IActionResult> Eula() }
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
return this.Ok($"{EulaHelper.License}\n{ServerSettings.Instance.EulaText}"); [HttpGet("announce")]
} public async Task<IActionResult> Announce()
{
[HttpGet("announce")] #if !DEBUG
public async Task<IActionResult> Announce() User? user = await this.database.UserFromGameRequest(this.Request);
{ if (user == null) return this.StatusCode(403, "");
#if !DEBUG #else
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
#else
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); (User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
if (userAndToken == null) return this.StatusCode(403, ""); if (userAndToken == null) return this.StatusCode(403, "");
@ -45,17 +45,17 @@ namespace LBPUnion.ProjectLighthouse.Controllers
// ReSharper disable once PossibleInvalidOperationException // ReSharper disable once PossibleInvalidOperationException
User user = userAndToken.Value.Item1; User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2; GameToken gameToken = userAndToken.Value.Item2;
#endif #endif
string announceText = ServerSettings.Instance.AnnounceText; string announceText = ServerSettings.Instance.AnnounceText;
announceText = announceText.Replace("%user", user.Username); announceText = announceText.Replace("%user", user.Username);
announceText = announceText.Replace("%id", user.UserId.ToString()); announceText = announceText.Replace("%id", user.UserId.ToString());
return this.Ok return this.Ok
( (
announceText + announceText +
#if DEBUG #if DEBUG
"\n\n---DEBUG INFO---\n" + "\n\n---DEBUG INFO---\n" +
$"user.UserId: {user.UserId}\n" + $"user.UserId: {user.UserId}\n" +
$"token.Approved: {gameToken.Approved}\n" + $"token.Approved: {gameToken.Approved}\n" +
@ -63,27 +63,26 @@ namespace LBPUnion.ProjectLighthouse.Controllers
$"token.UserLocation: {gameToken.UserLocation}\n" + $"token.UserLocation: {gameToken.UserLocation}\n" +
$"token.GameVersion: {gameToken.GameVersion}\n" + $"token.GameVersion: {gameToken.GameVersion}\n" +
"---DEBUG INFO---" + "---DEBUG INFO---" +
#endif #endif
"\n" "\n"
); );
} }
[HttpGet("notification")] [HttpGet("notification")]
public IActionResult Notification() => this.Ok(); public IActionResult Notification() => this.Ok();
/// <summary> /// <summary>
/// Filters chat messages sent by a user. /// Filters chat messages sent by a user.
/// The reponse sent is the text that will appear in-game. /// The reponse sent is the text that will appear in-game.
/// </summary> /// </summary>
[HttpPost("filter")] [HttpPost("filter")]
public async Task<IActionResult> Filter() public async Task<IActionResult> Filter()
{ {
User? user = await this.database.UserFromGameRequest(this.Request); User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (user == null) return this.StatusCode(403, "");
string loggedText = await new StreamReader(this.Request.Body).ReadToEndAsync(); string loggedText = await new StreamReader(this.Request.Body).ReadToEndAsync();
Logger.Log($"{user.Username}: {loggedText}", LoggerLevelFilter.Instance); Logger.Log($"{user.Username}: {loggedText}", LoggerLevelFilter.Instance);
return this.Ok(loggedText); return this.Ok(loggedText);
}
} }
} }

View file

@ -2,36 +2,35 @@ using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types.News; using LBPUnion.ProjectLighthouse.Types.News;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
{
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/news")]
[Produces("text/xml")]
public class NewsController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
string newsEntry = LbpSerializer.StringElement
(
"item",
new NewsEntry
{
Category = "no_category",
Summary = "test summary",
Image = new NewsImage
{
Hash = "4947269c5f7061b27225611ee58a9a91a8031bbe",
Alignment = "right",
},
Id = 1,
Title = "Test Title",
Text = "Test Text",
Date = 1348755214000,
}.Serialize()
);
return this.Ok(LbpSerializer.StringElement("news", newsEntry)); [ApiController]
} [Route("LITTLEBIGPLANETPS3_XML/news")]
[Produces("text/xml")]
public class NewsController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
string newsEntry = LbpSerializer.StringElement
(
"item",
new NewsEntry
{
Category = "no_category",
Summary = "test summary",
Image = new NewsImage
{
Hash = "4947269c5f7061b27225611ee58a9a91a8031bbe",
Alignment = "right",
},
Id = 1,
Title = "Test Title",
Text = "Test Text",
Date = 1348755214000,
}.Serialize()
);
return this.Ok(LbpSerializer.StringElement("news", newsEntry));
} }
} }

View file

@ -13,148 +13,142 @@ using LBPUnion.ProjectLighthouse.Types.Settings;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
public class PhotosController : ControllerBase
{ {
[ApiController] private readonly Database database;
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] public PhotosController(Database database)
public class PhotosController : ControllerBase
{ {
private readonly Database database; this.database = database;
}
public PhotosController(Database database) [HttpPost("uploadPhoto")]
public async Task<IActionResult> UploadPhoto()
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
if (user.PhotosByMe >= ServerSettings.Instance.PhotosQuota) return this.BadRequest();
this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(Photo));
Photo? photo = (Photo?)serializer.Deserialize(new StringReader(bodyString));
if (photo == null) return this.BadRequest();
foreach (Photo p in this.database.Photos.Where(p => p.CreatorId == user.UserId))
{ {
this.database = database; if (p.LargeHash == photo.LargeHash) return this.Ok(); // photo already uplaoded
if (p.MediumHash == photo.MediumHash) return this.Ok();
if (p.SmallHash == photo.SmallHash) return this.Ok();
if (p.PlanHash == photo.PlanHash) return this.Ok();
} }
[HttpPost("uploadPhoto")] photo.CreatorId = user.UserId;
public async Task<IActionResult> UploadPhoto() photo.Creator = user;
if (photo.Subjects.Count > 4) return this.BadRequest();
foreach (PhotoSubject subject in photo.Subjects)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); subject.User = await this.database.Users.FirstOrDefaultAsync(u => u.Username == subject.Username);
if (user == null) return this.StatusCode(403, "");
if (user.PhotosByMe >= ServerSettings.Instance.PhotosQuota) return this.BadRequest(); if (subject.User == null) continue;
this.Request.Body.Position = 0; subject.UserId = subject.User.UserId;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); Logger.Log($"Adding PhotoSubject (userid {subject.UserId}) to db", LoggerLevelPhotos.Instance);
XmlSerializer serializer = new(typeof(Photo)); this.database.PhotoSubjects.Add(subject);
Photo? photo = (Photo?)serializer.Deserialize(new StringReader(bodyString));
if (photo == null) return this.BadRequest();
foreach (Photo p in this.database.Photos.Where(p => p.CreatorId == user.UserId))
{
if (p.LargeHash == photo.LargeHash) return this.Ok(); // photo already uplaoded
if (p.MediumHash == photo.MediumHash) return this.Ok();
if (p.SmallHash == photo.SmallHash) return this.Ok();
if (p.PlanHash == photo.PlanHash) return this.Ok();
}
photo.CreatorId = user.UserId;
photo.Creator = user;
if (photo.Subjects.Count > 4)
{
return this.BadRequest();
}
foreach (PhotoSubject subject in photo.Subjects)
{
subject.User = await this.database.Users.FirstOrDefaultAsync(u => u.Username == subject.Username);
if (subject.User == null) continue;
subject.UserId = subject.User.UserId;
Logger.Log($"Adding PhotoSubject (userid {subject.UserId}) to db", LoggerLevelPhotos.Instance);
this.database.PhotoSubjects.Add(subject);
}
await this.database.SaveChangesAsync();
// Check for duplicate photo subjects
List<int> subjectUserIds = new(4);
foreach (PhotoSubject subject in photo.Subjects)
{
if (subjectUserIds.Contains(subject.UserId))
{
return this.BadRequest();
}
subjectUserIds.Add(subject.UserId);
}
photo.PhotoSubjectIds = photo.Subjects.Select(subject => subject.PhotoSubjectId.ToString()).ToArray();
// photo.Slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == photo.SlotId);
Logger.Log($"Adding PhotoSubjectCollection ({photo.PhotoSubjectCollection}) to photo", LoggerLevelPhotos.Instance);
this.database.Photos.Add(photo);
await this.database.SaveChangesAsync();
return this.Ok();
} }
[HttpGet("photos/user/{id:int}")] await this.database.SaveChangesAsync();
public async Task<IActionResult> SlotPhotos(int id)
// Check for duplicate photo subjects
List<int> subjectUserIds = new(4);
foreach (PhotoSubject subject in photo.Subjects)
{ {
List<Photo> photos = await this.database.Photos.Include(p => p.Creator).Take(10).ToListAsync(); if (subjectUserIds.Contains(subject.UserId)) return this.BadRequest();
string response = photos.Aggregate(string.Empty, (s, photo) => s + photo.Serialize(id));
return this.Ok(LbpSerializer.StringElement("photos", response)); subjectUserIds.Add(subject.UserId);
} }
[HttpGet("photos/by")] photo.PhotoSubjectIds = photo.Subjects.Select(subject => subject.PhotoSubjectId.ToString()).ToArray();
public async Task<IActionResult> UserPhotosBy([FromQuery] string user, [FromQuery] int pageStart, [FromQuery] int pageSize)
{
User? userFromQuery = await this.database.Users.FirstOrDefaultAsync(u => u.Username == user);
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (userFromQuery == null) return this.NotFound();
List<Photo> photos = await this.database.Photos.Include // photo.Slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == photo.SlotId);
(p => p.Creator)
.Where(p => p.CreatorId == userFromQuery.UserId)
.OrderByDescending(s => s.Timestamp)
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30))
.ToListAsync();
string response = photos.Aggregate(string.Empty, (s, photo) => s + photo.Serialize(0));
return this.Ok(LbpSerializer.StringElement("photos", response));
}
[HttpGet("photos/with")] Logger.Log($"Adding PhotoSubjectCollection ({photo.PhotoSubjectCollection}) to photo", LoggerLevelPhotos.Instance);
public async Task<IActionResult> UserPhotosWith([FromQuery] string user, [FromQuery] int pageStart, [FromQuery] int pageSize)
{
User? userFromQuery = await this.database.Users.FirstOrDefaultAsync(u => u.Username == user);
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (userFromQuery == null) return this.NotFound();
List<Photo> photos = new(); this.database.Photos.Add(photo);
foreach (Photo photo in this.database.Photos.Include
(p => p.Creator)) photos.AddRange(photo.Subjects.Where(subject => subject.User.UserId == userFromQuery.UserId).Select(_ => photo));
string response = photos.OrderByDescending await this.database.SaveChangesAsync();
(s => s.Timestamp)
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30))
.Aggregate(string.Empty, (s, photo) => s + photo.Serialize(0));
return this.Ok(LbpSerializer.StringElement("photos", response)); return this.Ok();
} }
[HttpPost("deletePhoto/{id:int}")] [HttpGet("photos/user/{id:int}")]
public async Task<IActionResult> DeletePhoto(int id) public async Task<IActionResult> SlotPhotos(int id)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); List<Photo> photos = await this.database.Photos.Include(p => p.Creator).Take(10).ToListAsync();
if (user == null) return this.StatusCode(403, ""); string response = photos.Aggregate(string.Empty, (s, photo) => s + photo.Serialize(id));
return this.Ok(LbpSerializer.StringElement("photos", response));
}
Photo? photo = await this.database.Photos.FirstOrDefaultAsync(p => p.PhotoId == id); [HttpGet("photos/by")]
if (photo == null) return this.NotFound(); public async Task<IActionResult> UserPhotosBy([FromQuery] string user, [FromQuery] int pageStart, [FromQuery] int pageSize)
if (photo.CreatorId != user.UserId) return this.StatusCode(401, ""); {
User? userFromQuery = await this.database.Users.FirstOrDefaultAsync(u => u.Username == user);
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (userFromQuery == null) return this.NotFound();
this.database.Photos.Remove(photo); List<Photo> photos = await this.database.Photos.Include
await this.database.SaveChangesAsync(); (p => p.Creator)
return this.Ok(); .Where(p => p.CreatorId == userFromQuery.UserId)
} .OrderByDescending(s => s.Timestamp)
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30))
.ToListAsync();
string response = photos.Aggregate(string.Empty, (s, photo) => s + photo.Serialize(0));
return this.Ok(LbpSerializer.StringElement("photos", response));
}
[HttpGet("photos/with")]
public async Task<IActionResult> UserPhotosWith([FromQuery] string user, [FromQuery] int pageStart, [FromQuery] int pageSize)
{
User? userFromQuery = await this.database.Users.FirstOrDefaultAsync(u => u.Username == user);
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (userFromQuery == null) return this.NotFound();
List<Photo> photos = new();
foreach (Photo photo in this.database.Photos.Include
(p => p.Creator)) photos.AddRange(photo.Subjects.Where(subject => subject.User.UserId == userFromQuery.UserId).Select(_ => photo));
string response = photos.OrderByDescending
(s => s.Timestamp)
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30))
.Aggregate(string.Empty, (s, photo) => s + photo.Serialize(0));
return this.Ok(LbpSerializer.StringElement("photos", response));
}
[HttpPost("deletePhoto/{id:int}")]
public async Task<IActionResult> DeletePhoto(int id)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
Photo? photo = await this.database.Photos.FirstOrDefaultAsync(p => p.PhotoId == id);
if (photo == null) return this.NotFound();
if (photo.CreatorId != user.UserId) return this.StatusCode(401, "");
this.database.Photos.Remove(photo);
await this.database.SaveChangesAsync();
return this.Ok();
} }
} }

View file

@ -13,138 +13,113 @@ using LBPUnion.ProjectLighthouse.Types.Settings;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
public class PublishController : ControllerBase
{ {
[ApiController] private readonly Database database;
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] public PublishController(Database database)
public class PublishController : ControllerBase
{ {
private readonly Database database; this.database = database;
}
public PublishController(Database database) /// <summary>
/// Endpoint the game uses to check what resources need to be uploaded and if the level can be uploaded
/// </summary>
[HttpPost("startPublish")]
public async Task<IActionResult> StartPublish()
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
if (user.UsedSlots >= ServerSettings.Instance.EntitledSlots) return this.BadRequest();
Slot? slot = await this.GetSlotFromBody();
if (slot == null) return this.BadRequest(); // if the level cant be parsed then it obviously cant be uploaded
if (string.IsNullOrEmpty(slot.RootLevel)) return this.BadRequest();
if (string.IsNullOrEmpty(slot.ResourceCollection)) slot.ResourceCollection = slot.RootLevel;
// Republish logic
if (slot.SlotId != 0)
{ {
this.database = database; Slot? oldSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slot.SlotId);
if (oldSlot == null) return this.NotFound();
if (oldSlot.CreatorId != user.UserId) return this.BadRequest();
} }
/// <summary> slot.ResourceCollection += "," + slot.IconHash; // tells LBP to upload icon after we process resources here
/// Endpoint the game uses to check what resources need to be uploaded and if the level can be uploaded
/// </summary>
[HttpPost("startPublish")]
public async Task<IActionResult> StartPublish()
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
if (user.UsedSlots >= ServerSettings.Instance.EntitledSlots) return this.BadRequest(); string resources = slot.Resources.Where
(hash => !FileHelper.ResourceExists(hash))
.Aggregate("", (current, hash) => current + LbpSerializer.StringElement("resource", hash));
Slot? slot = await this.GetSlotFromBody(); return this.Ok(LbpSerializer.TaggedStringElement("slot", resources, "type", "user"));
if (slot == null) return this.BadRequest(); // if the level cant be parsed then it obviously cant be uploaded }
if (string.IsNullOrEmpty(slot.RootLevel)) return this.BadRequest(); /// <summary>
/// Endpoint actually used to publish a level
if (string.IsNullOrEmpty(slot.ResourceCollection)) slot.ResourceCollection = slot.RootLevel; /// </summary>
[HttpPost("publish")]
// Republish logic public async Task<IActionResult> Publish()
if (slot.SlotId != 0) {
{
Slot? oldSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slot.SlotId);
if (oldSlot == null) return this.NotFound();
if (oldSlot.CreatorId != user.UserId) return this.BadRequest();
}
slot.ResourceCollection += "," + slot.IconHash; // tells LBP to upload icon after we process resources here
string resources = slot.Resources.Where
(hash => !FileHelper.ResourceExists(hash))
.Aggregate("", (current, hash) => current + LbpSerializer.StringElement("resource", hash));
return this.Ok(LbpSerializer.TaggedStringElement("slot", resources, "type", "user"));
}
/// <summary>
/// Endpoint actually used to publish a level
/// </summary>
[HttpPost("publish")]
public async Task<IActionResult> Publish()
{
// User user = await this.database.UserFromGameRequest(this.Request); // User user = await this.database.UserFromGameRequest(this.Request);
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); (User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
if (userAndToken == null) return this.StatusCode(403, ""); if (userAndToken == null) return this.StatusCode(403, "");
// ReSharper disable once PossibleInvalidOperationException // ReSharper disable once PossibleInvalidOperationException
User user = userAndToken.Value.Item1; User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2; GameToken gameToken = userAndToken.Value.Item2;
if (user.UsedSlots >= ServerSettings.Instance.EntitledSlots) return this.BadRequest(); if (user.UsedSlots >= ServerSettings.Instance.EntitledSlots) return this.BadRequest();
Slot? slot = await this.GetSlotFromBody(); Slot? slot = await this.GetSlotFromBody();
if (slot?.Location == null) return this.BadRequest(); if (slot?.Location == null) return this.BadRequest();
// Republish logic // Republish logic
if (slot.SlotId != 0) if (slot.SlotId != 0)
{ {
Slot? oldSlot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slot.SlotId); Slot? oldSlot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slot.SlotId);
if (oldSlot == null) return this.NotFound(); if (oldSlot == null) return this.NotFound();
if (oldSlot.Location == null) throw new ArgumentNullException(); if (oldSlot.Location == null) throw new ArgumentNullException();
if (oldSlot.CreatorId != user.UserId) return this.BadRequest(); if (oldSlot.CreatorId != user.UserId) return this.BadRequest();
oldSlot.Location.X = slot.Location.X; oldSlot.Location.X = slot.Location.X;
oldSlot.Location.Y = slot.Location.Y; oldSlot.Location.Y = slot.Location.Y;
slot.CreatorId = oldSlot.CreatorId; slot.CreatorId = oldSlot.CreatorId;
slot.LocationId = oldSlot.LocationId; slot.LocationId = oldSlot.LocationId;
slot.SlotId = oldSlot.SlotId; slot.SlotId = oldSlot.SlotId;
slot.PlaysLBP1 = oldSlot.PlaysLBP1; slot.PlaysLBP1 = oldSlot.PlaysLBP1;
slot.PlaysLBP1Complete = oldSlot.PlaysLBP1Complete; slot.PlaysLBP1Complete = oldSlot.PlaysLBP1Complete;
slot.PlaysLBP1Unique = oldSlot.PlaysLBP1Unique; slot.PlaysLBP1Unique = oldSlot.PlaysLBP1Unique;
slot.PlaysLBP2 = oldSlot.PlaysLBP2; slot.PlaysLBP2 = oldSlot.PlaysLBP2;
slot.PlaysLBP2Complete = oldSlot.PlaysLBP2Complete; slot.PlaysLBP2Complete = oldSlot.PlaysLBP2Complete;
slot.PlaysLBP2Unique = oldSlot.PlaysLBP2Unique; slot.PlaysLBP2Unique = oldSlot.PlaysLBP2Unique;
slot.PlaysLBP3 = oldSlot.PlaysLBP3; slot.PlaysLBP3 = oldSlot.PlaysLBP3;
slot.PlaysLBP3Complete = oldSlot.PlaysLBP3Complete; slot.PlaysLBP3Complete = oldSlot.PlaysLBP3Complete;
slot.PlaysLBP3Unique = oldSlot.PlaysLBP3Unique; slot.PlaysLBP3Unique = oldSlot.PlaysLBP3Unique;
slot.PlaysLBPVita = oldSlot.PlaysLBPVita; slot.PlaysLBPVita = oldSlot.PlaysLBPVita;
slot.PlaysLBPVitaComplete = oldSlot.PlaysLBPVitaComplete; slot.PlaysLBPVitaComplete = oldSlot.PlaysLBPVitaComplete;
slot.PlaysLBPVitaUnique = oldSlot.PlaysLBPVitaUnique; slot.PlaysLBPVitaUnique = oldSlot.PlaysLBPVitaUnique;
slot.FirstUploaded = oldSlot.FirstUploaded; slot.FirstUploaded = oldSlot.FirstUploaded;
slot.LastUpdated = TimeHelper.UnixTimeMilliseconds();
slot.TeamPick = oldSlot.TeamPick;
slot.GameVersion = gameToken.GameVersion;
if (slot.MinimumPlayers == 0 || slot.MaximumPlayers == 0)
{
slot.MinimumPlayers = 1;
slot.MaximumPlayers = 4;
}
this.database.Entry(oldSlot).CurrentValues.SetValues(slot);
await this.database.SaveChangesAsync();
return this.Ok(oldSlot.Serialize());
}
//TODO: parse location in body
Location l = new()
{
X = slot.Location.X,
Y = slot.Location.Y,
};
this.database.Locations.Add(l);
await this.database.SaveChangesAsync();
slot.LocationId = l.Id;
slot.CreatorId = user.UserId;
slot.FirstUploaded = TimeHelper.UnixTimeMilliseconds();
slot.LastUpdated = TimeHelper.UnixTimeMilliseconds(); slot.LastUpdated = TimeHelper.UnixTimeMilliseconds();
slot.TeamPick = oldSlot.TeamPick;
slot.GameVersion = gameToken.GameVersion; slot.GameVersion = gameToken.GameVersion;
if (slot.MinimumPlayers == 0 || slot.MaximumPlayers == 0) if (slot.MinimumPlayers == 0 || slot.MaximumPlayers == 0)
@ -153,42 +128,66 @@ namespace LBPUnion.ProjectLighthouse.Controllers
slot.MaximumPlayers = 4; slot.MaximumPlayers = 4;
} }
this.database.Slots.Add(slot); this.database.Entry(oldSlot).CurrentValues.SetValues(slot);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
return this.Ok(oldSlot.Serialize());
return this.Ok(slot.Serialize());
} }
[HttpPost("unpublish/{id:int}")] //TODO: parse location in body
public async Task<IActionResult> Unpublish(int id) Location l = new()
{ {
User? user = await this.database.UserFromGameRequest(this.Request); X = slot.Location.X,
if (user == null) return this.StatusCode(403, ""); Y = slot.Location.Y,
};
this.database.Locations.Add(l);
await this.database.SaveChangesAsync();
slot.LocationId = l.Id;
slot.CreatorId = user.UserId;
slot.FirstUploaded = TimeHelper.UnixTimeMilliseconds();
slot.LastUpdated = TimeHelper.UnixTimeMilliseconds();
slot.GameVersion = gameToken.GameVersion;
Slot? slot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == id); if (slot.MinimumPlayers == 0 || slot.MaximumPlayers == 0)
if (slot == null) return this.NotFound();
if (slot.Location == null) throw new ArgumentNullException();
if (slot.CreatorId != user.UserId) return this.StatusCode(403, "");
this.database.Locations.Remove(slot.Location);
this.database.Slots.Remove(slot);
await this.database.SaveChangesAsync();
return this.Ok();
}
public async Task<Slot?> GetSlotFromBody()
{ {
this.Request.Body.Position = 0; slot.MinimumPlayers = 1;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); slot.MaximumPlayers = 4;
XmlSerializer serializer = new(typeof(Slot));
Slot? slot = (Slot?)serializer.Deserialize(new StringReader(bodyString));
return slot;
} }
this.database.Slots.Add(slot);
await this.database.SaveChangesAsync();
return this.Ok(slot.Serialize());
}
[HttpPost("unpublish/{id:int}")]
public async Task<IActionResult> Unpublish(int id)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound();
if (slot.Location == null) throw new ArgumentNullException();
if (slot.CreatorId != user.UserId) return this.StatusCode(403, "");
this.database.Locations.Remove(slot.Location);
this.database.Slots.Remove(slot);
await this.database.SaveChangesAsync();
return this.Ok();
}
public async Task<Slot?> GetSlotFromBody()
{
this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(Slot));
Slot? slot = (Slot?)serializer.Deserialize(new StringReader(bodyString));
return slot;
} }
} }

View file

@ -11,79 +11,78 @@ using LBPUnion.ProjectLighthouse.Types.Files;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using IOFile = System.IO.File; using IOFile = System.IO.File;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
[ApiController]
[Produces("text/xml")]
[Route("LITTLEBIGPLANETPS3_XML")]
public class ResourcesController : ControllerBase
{ {
[ApiController] [HttpPost("showModerated")]
[Produces("text/xml")] public IActionResult ShowModerated() => this.Ok(LbpSerializer.BlankElement("resources"));
[Route("LITTLEBIGPLANETPS3_XML")]
public class ResourcesController : ControllerBase [HttpPost("filterResources")]
[HttpPost("showNotUploaded")]
public async Task<IActionResult> FilterResources()
{ {
[HttpPost("showModerated")] string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
public IActionResult ShowModerated() => this.Ok(LbpSerializer.BlankElement("resources"));
[HttpPost("filterResources")] XmlSerializer serializer = new(typeof(ResourceList));
[HttpPost("showNotUploaded")] ResourceList resourceList = (ResourceList)serializer.Deserialize(new StringReader(bodyString));
public async Task<IActionResult> FilterResources()
if (resourceList == null) return this.BadRequest();
string resources = resourceList.Resources.Where
(s => !FileHelper.ResourceExists(s))
.Aggregate("", (current, hash) => current + LbpSerializer.StringElement("resource", hash));
return this.Ok(LbpSerializer.StringElement("resources", resources));
}
[ResponseCache(Duration = 86400)]
[HttpGet("/gameAssets/{hash}")]
[HttpGet("r/{hash}")]
public IActionResult GetResource(string hash)
{
string path = FileHelper.GetResourcePath(hash);
if (FileHelper.ResourceExists(hash)) return this.File(IOFile.OpenRead(path), "application/octet-stream");
return this.NotFound();
}
// TODO: check if this is a valid hash
[HttpPost("upload/{hash}")]
public async Task<IActionResult> UploadResource(string hash)
{
string assetsDirectory = FileHelper.ResourcePath;
string path = FileHelper.GetResourcePath(hash);
FileHelper.EnsureDirectoryCreated(assetsDirectory);
if (FileHelper.ResourceExists(hash)) this.Ok(); // no reason to fail if it's already uploaded
Logger.Log($"Processing resource upload (hash: {hash})", LoggerLevelResources.Instance);
LbpFile file = new(await BinaryHelper.ReadFromPipeReader(this.Request.BodyReader));
if (!FileHelper.IsFileSafe(file))
{ {
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); Logger.Log($"File is unsafe (hash: {hash}, type: {file.FileType})", LoggerLevelResources.Instance);
return this.UnprocessableEntity();
XmlSerializer serializer = new(typeof(ResourceList));
ResourceList resourceList = (ResourceList)serializer.Deserialize(new StringReader(bodyString));
if (resourceList == null) return this.BadRequest();
string resources = resourceList.Resources.Where
(s => !FileHelper.ResourceExists(s))
.Aggregate("", (current, hash) => current + LbpSerializer.StringElement("resource", hash));
return this.Ok(LbpSerializer.StringElement("resources", resources));
} }
[ResponseCache(Duration = 86400)] string calculatedHash = HashHelper.Sha1Hash(file.Data).ToLower();
[HttpGet("/gameAssets/{hash}")] if (calculatedHash != hash)
[HttpGet("r/{hash}")]
public IActionResult GetResource(string hash)
{ {
string path = FileHelper.GetResourcePath(hash); Logger.Log
(
if (FileHelper.ResourceExists(hash)) return this.File(IOFile.OpenRead(path), "application/octet-stream"); $"File hash does not match the uploaded file! (hash: {hash}, calculatedHash: {calculatedHash}, type: {file.FileType})",
LoggerLevelResources.Instance
return this.NotFound(); );
return this.Conflict();
} }
// TODO: check if this is a valid hash Logger.Log($"File is OK! (hash: {hash}, type: {file.FileType})", LoggerLevelResources.Instance);
[HttpPost("upload/{hash}")] await IOFile.WriteAllBytesAsync(path, file.Data);
public async Task<IActionResult> UploadResource(string hash) return this.Ok();
{
string assetsDirectory = FileHelper.ResourcePath;
string path = FileHelper.GetResourcePath(hash);
FileHelper.EnsureDirectoryCreated(assetsDirectory);
if (FileHelper.ResourceExists(hash)) this.Ok(); // no reason to fail if it's already uploaded
Logger.Log($"Processing resource upload (hash: {hash})", LoggerLevelResources.Instance);
LbpFile file = new(await BinaryHelper.ReadFromPipeReader(this.Request.BodyReader));
if (!FileHelper.IsFileSafe(file))
{
Logger.Log($"File is unsafe (hash: {hash}, type: {file.FileType})", LoggerLevelResources.Instance);
return this.UnprocessableEntity();
}
string calculatedHash = HashHelper.Sha1Hash(file.Data).ToLower();
if (calculatedHash != hash)
{
Logger.Log
(
$"File hash does not match the uploaded file! (hash: {hash}, calculatedHash: {calculatedHash}, type: {file.FileType})",
LoggerLevelResources.Instance
);
return this.Conflict();
}
Logger.Log($"File is OK! (hash: {hash}, type: {file.FileType})", LoggerLevelResources.Instance);
await IOFile.WriteAllBytesAsync(path, file.Data);
return this.Ok();
}
} }
} }

View file

@ -13,319 +13,318 @@ using LBPUnion.ProjectLighthouse.Types.Reviews;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/plain")]
public class ReviewController : ControllerBase
{ {
[ApiController] private readonly Database database;
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/plain")] public ReviewController(Database database)
public class ReviewController : ControllerBase
{ {
private readonly Database database; this.database = database;
}
public ReviewController(Database database) // LBP1 rating
[HttpPost("rate/user/{slotId}")]
public async Task<IActionResult> Rate(int slotId, [FromQuery] int rating)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.Include(s => s.Creator).Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.StatusCode(403, "");
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == user.UserId);
if (ratedLevel == null)
{ {
this.database = database; ratedLevel = new RatedLevel();
ratedLevel.SlotId = slotId;
ratedLevel.UserId = user.UserId;
ratedLevel.Rating = 0;
this.database.RatedLevels.Add(ratedLevel);
} }
// LBP1 rating ratedLevel.RatingLBP1 = Math.Max(Math.Min(5, rating), 0);
[HttpPost("rate/user/{slotId}")]
public async Task<IActionResult> Rate(int slotId, [FromQuery] int rating) await this.database.SaveChangesAsync();
return this.Ok();
}
// LBP2 and beyond rating
[HttpPost("dpadrate/user/{slotId:int}")]
public async Task<IActionResult> DPadRate(int slotId, [FromQuery] int rating)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.Include(s => s.Creator).Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.StatusCode(403, "");
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == user.UserId);
if (ratedLevel == null)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); ratedLevel = new RatedLevel();
if (user == null) return this.StatusCode(403, ""); ratedLevel.SlotId = slotId;
ratedLevel.UserId = user.UserId;
Slot? slot = await this.database.Slots.Include(s => s.Creator).Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slotId); ratedLevel.RatingLBP1 = 0;
if (slot == null) return this.StatusCode(403, ""); this.database.RatedLevels.Add(ratedLevel);
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == user.UserId);
if (ratedLevel == null)
{
ratedLevel = new RatedLevel();
ratedLevel.SlotId = slotId;
ratedLevel.UserId = user.UserId;
ratedLevel.Rating = 0;
this.database.RatedLevels.Add(ratedLevel);
}
ratedLevel.RatingLBP1 = Math.Max(Math.Min(5, rating), 0);
await this.database.SaveChangesAsync();
return this.Ok();
} }
// LBP2 and beyond rating ratedLevel.Rating = Math.Max(Math.Min(1, rating), -1);
[HttpPost("dpadrate/user/{slotId:int}")]
public async Task<IActionResult> DPadRate(int slotId, [FromQuery] int rating) Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == user.UserId);
if (review != null) review.Thumb = ratedLevel.Rating;
await this.database.SaveChangesAsync();
return this.Ok();
}
[HttpPost("postReview/user/{slotId:int}")]
public async Task<IActionResult> PostReview(int slotId)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == user.UserId);
Review? newReview = await this.GetReviewFromBody();
if (newReview == null) return this.BadRequest();
if (review == null)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); review = new Review();
if (user == null) return this.StatusCode(403, ""); review.SlotId = slotId;
review.ReviewerId = user.UserId;
review.DeletedBy = DeletedBy.None;
review.ThumbsUp = 0;
review.ThumbsDown = 0;
this.database.Reviews.Add(review);
}
review.Thumb = newReview.Thumb;
review.LabelCollection = newReview.LabelCollection;
review.Text = newReview.Text;
review.Deleted = false;
review.Timestamp = TimeHelper.UnixTimeMilliseconds();
Slot? slot = await this.database.Slots.Include(s => s.Creator).Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slotId); // sometimes the game posts/updates a review rating without also calling dpadrate/user/etc (why??)
if (slot == null) return this.StatusCode(403, ""); RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == user.UserId);
if (ratedLevel == null)
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == user.UserId); {
if (ratedLevel == null) ratedLevel = new RatedLevel();
{ ratedLevel.SlotId = slotId;
ratedLevel = new RatedLevel(); ratedLevel.UserId = user.UserId;
ratedLevel.SlotId = slotId; ratedLevel.RatingLBP1 = 0;
ratedLevel.UserId = user.UserId; this.database.RatedLevels.Add(ratedLevel);
ratedLevel.RatingLBP1 = 0;
this.database.RatedLevels.Add(ratedLevel);
}
ratedLevel.Rating = Math.Max(Math.Min(1, rating), -1);
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == user.UserId);
if (review != null) review.Thumb = ratedLevel.Rating;
await this.database.SaveChangesAsync();
return this.Ok();
} }
[HttpPost("postReview/user/{slotId:int}")] ratedLevel.Rating = newReview.Thumb;
public async Task<IActionResult> PostReview(int slotId)
await this.database.SaveChangesAsync();
return this.Ok();
}
[HttpGet("reviewsFor/user/{slotId:int}")]
public async Task<IActionResult> ReviewsFor(int slotId, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10)
{
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
if (userAndToken == null) return this.StatusCode(403, "");
// ReSharper disable once PossibleInvalidOperationException
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
GameVersion gameVersion = gameToken.GameVersion;
Random rand = new();
Review? yourReview = await this.database.Reviews.FirstOrDefaultAsync
(r => r.ReviewerId == user.UserId && r.SlotId == slotId && r.Slot.GameVersion <= gameVersion);
VisitedLevel? visitedLevel = await this.database.VisitedLevels.FirstOrDefaultAsync
(v => v.UserId == user.UserId && v.SlotId == slotId && v.Slot.GameVersion <= gameVersion);
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.BadRequest();
bool canNowReviewLevel = slot.CreatorId != user.UserId && visitedLevel != null && yourReview == null;
if (canNowReviewLevel)
{ {
User? user = await this.database.UserFromGameRequest(this.Request); RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync
if (user == null) return this.StatusCode(403, ""); (r => r.UserId == user.UserId && r.SlotId == slotId && r.Slot.GameVersion <= gameVersion);
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == user.UserId); yourReview = new Review();
Review? newReview = await this.GetReviewFromBody(); yourReview.ReviewerId = user.UserId;
if (newReview == null) return this.BadRequest(); yourReview.Reviewer = user;
yourReview.Thumb = ratedLevel?.Rating == null ? 0 : ratedLevel.Rating;
if (review == null) yourReview.Slot = slot;
{ yourReview.SlotId = slotId;
review = new Review(); yourReview.Deleted = false;
review.SlotId = slotId; yourReview.DeletedBy = DeletedBy.None;
review.ReviewerId = user.UserId; yourReview.Text = "You haven't reviewed this level yet. Edit this blank review to upload one!";
review.DeletedBy = DeletedBy.None; yourReview.LabelCollection = "";
review.ThumbsUp = 0; yourReview.Timestamp = TimeHelper.UnixTimeMilliseconds();
review.ThumbsDown = 0;
this.database.Reviews.Add(review);
}
review.Thumb = newReview.Thumb;
review.LabelCollection = newReview.LabelCollection;
review.Text = newReview.Text;
review.Deleted = false;
review.Timestamp = TimeHelper.UnixTimeMilliseconds();
// sometimes the game posts/updates a review rating without also calling dpadrate/user/etc (why??)
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == user.UserId);
if (ratedLevel == null)
{
ratedLevel = new RatedLevel();
ratedLevel.SlotId = slotId;
ratedLevel.UserId = user.UserId;
ratedLevel.RatingLBP1 = 0;
this.database.RatedLevels.Add(ratedLevel);
}
ratedLevel.Rating = newReview.Thumb;
await this.database.SaveChangesAsync();
return this.Ok();
} }
[HttpGet("reviewsFor/user/{slotId:int}")] IQueryable<Review?> reviews = this.database.Reviews.Where(r => r.SlotId == slotId && r.Slot.GameVersion <= gameVersion)
public async Task<IActionResult> ReviewsFor(int slotId, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10) .Include(r => r.Reviewer)
{ .Include(r => r.Slot)
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); .OrderByDescending(r => r.ThumbsUp)
.ThenByDescending(_ => EF.Functions.Random())
.Skip(pageStart - 1)
.Take(pageSize);
if (userAndToken == null) return this.StatusCode(403, ""); IEnumerable<Review?> prependedReviews;
if (canNowReviewLevel) // this can only be true if you have not posted a review but have visited the level
// prepend the fake review to the top of the list to be easily edited
prependedReviews = reviews.ToList().Prepend(yourReview);
else prependedReviews = reviews.ToList();
// ReSharper disable once PossibleInvalidOperationException string inner = prependedReviews.Aggregate
User user = userAndToken.Value.Item1; (
GameToken gameToken = userAndToken.Value.Item2; string.Empty,
(current, review) =>
GameVersion gameVersion = gameToken.GameVersion;
Random rand = new();
Review? yourReview = await this.database.Reviews.FirstOrDefaultAsync
(r => r.ReviewerId == user.UserId && r.SlotId == slotId && r.Slot.GameVersion <= gameVersion);
VisitedLevel? visitedLevel = await this.database.VisitedLevels.FirstOrDefaultAsync
(v => v.UserId == user.UserId && v.SlotId == slotId && v.Slot.GameVersion <= gameVersion);
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.BadRequest();
bool canNowReviewLevel = slot.CreatorId != user.UserId && visitedLevel != null && yourReview == null;
if (canNowReviewLevel)
{ {
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync if (review == null) return current;
(r => r.UserId == user.UserId && r.SlotId == slotId && r.Slot.GameVersion <= gameVersion);
yourReview = new Review(); return current + review.Serialize();
yourReview.ReviewerId = user.UserId;
yourReview.Reviewer = user;
yourReview.Thumb = ratedLevel?.Rating == null ? 0 : ratedLevel.Rating;
yourReview.Slot = slot;
yourReview.SlotId = slotId;
yourReview.Deleted = false;
yourReview.DeletedBy = DeletedBy.None;
yourReview.Text = "You haven't reviewed this level yet. Edit this blank review to upload one!";
yourReview.LabelCollection = "";
yourReview.Timestamp = TimeHelper.UnixTimeMilliseconds();
} }
);
IQueryable<Review?> reviews = this.database.Reviews.Where(r => r.SlotId == slotId && r.Slot.GameVersion <= gameVersion) string response = LbpSerializer.TaggedStringElement
.Include(r => r.Reviewer) (
.Include(r => r.Slot) "reviews",
.OrderByDescending(r => r.ThumbsUp) inner,
.ThenByDescending(_ => EF.Functions.Random()) new Dictionary<string, object>
.Skip(pageStart - 1) {
.Take(pageSize);
IEnumerable<Review?> prependedReviews;
if (canNowReviewLevel) // this can only be true if you have not posted a review but have visited the level
// prepend the fake review to the top of the list to be easily edited
prependedReviews = reviews.ToList().Prepend(yourReview);
else prependedReviews = reviews.ToList();
string inner = prependedReviews.Aggregate
(
string.Empty,
(current, review) =>
{ {
if (review == null) return current; "hint_start", pageStart + pageSize
},
return current + review.Serialize();
}
);
string response = LbpSerializer.TaggedStringElement
(
"reviews",
inner,
new Dictionary<string, object>
{ {
{ "hint", pageStart // not sure
"hint_start", pageStart + pageSize },
},
{
"hint", pageStart // not sure
},
}
);
return this.Ok(response);
}
[HttpGet("reviewsBy/{username}")]
public async Task<IActionResult> ReviewsBy(string username, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10)
{
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
if (userAndToken == null) return this.StatusCode(403, "");
// ReSharper disable once PossibleInvalidOperationException
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
GameVersion gameVersion = gameToken.GameVersion;
IEnumerable<Review> reviews = this.database.Reviews.Where(r => r.Reviewer.Username == username && r.Slot.GameVersion <= gameVersion)
.Include(r => r.Reviewer)
.Include(r => r.Slot)
.OrderByDescending(r => r.Timestamp)
.Skip(pageStart - 1)
.Take(pageSize);
string inner = reviews.Aggregate
(
string.Empty,
(current, review) =>
{
//RatedLevel? ratedLevel = this.database.RatedLevels.FirstOrDefault(r => r.SlotId == review.SlotId && r.UserId == user.UserId);
//RatedReview? ratedReview = this.database.RatedReviews.FirstOrDefault(r => r.ReviewId == review.ReviewId && r.UserId == user.UserId);
return current + review.Serialize( /*, ratedReview*/);
}
);
string response = LbpSerializer.TaggedStringElement
(
"reviews",
inner,
new Dictionary<string, object>
{
{
"hint_start", pageStart
},
{
"hint", reviews.Last().Timestamp // Seems to be the timestamp of oldest
},
}
);
return this.Ok(response);
}
[HttpPost("rateReview/user/{slotId:int}/{username}")]
public async Task<IActionResult> RateReview(int slotId, string username, [FromQuery] int rating = 0)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
User? reviewer = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (reviewer == null) return this.StatusCode(400, "");
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewer.UserId);
if (review == null) return this.StatusCode(400, "");
RatedReview? ratedReview = await this.database.RatedReviews.FirstOrDefaultAsync(r => r.ReviewId == review.ReviewId && r.UserId == user.UserId);
if (ratedReview == null)
{
ratedReview = new RatedReview();
ratedReview.ReviewId = review.ReviewId;
ratedReview.UserId = user.UserId;
ratedReview.Thumb = 0;
this.database.RatedReviews.Add(ratedReview);
} }
);
return this.Ok(response);
}
int oldThumb = ratedReview.Thumb; [HttpGet("reviewsBy/{username}")]
ratedReview.Thumb = Math.Max(Math.Min(1, rating), -1); public async Task<IActionResult> ReviewsBy(string username, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10)
{
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
if (oldThumb != ratedReview.Thumb) if (userAndToken == null) return this.StatusCode(403, "");
// ReSharper disable once PossibleInvalidOperationException
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
GameVersion gameVersion = gameToken.GameVersion;
IEnumerable<Review> reviews = this.database.Reviews.Where(r => r.Reviewer.Username == username && r.Slot.GameVersion <= gameVersion)
.Include(r => r.Reviewer)
.Include(r => r.Slot)
.OrderByDescending(r => r.Timestamp)
.Skip(pageStart - 1)
.Take(pageSize);
string inner = reviews.Aggregate
(
string.Empty,
(current, review) =>
{ {
if (oldThumb == -1) review.ThumbsDown--; //RatedLevel? ratedLevel = this.database.RatedLevels.FirstOrDefault(r => r.SlotId == review.SlotId && r.UserId == user.UserId);
else if (oldThumb == 1) review.ThumbsUp--; //RatedReview? ratedReview = this.database.RatedReviews.FirstOrDefault(r => r.ReviewId == review.ReviewId && r.UserId == user.UserId);
return current + review.Serialize( /*, ratedReview*/);
if (ratedReview.Thumb == -1) review.ThumbsDown++;
else if (ratedReview.Thumb == 1) review.ThumbsUp++;
} }
);
await this.database.SaveChangesAsync(); string response = LbpSerializer.TaggedStringElement
(
"reviews",
inner,
new Dictionary<string, object>
{
{
"hint_start", pageStart
},
{
"hint", reviews.Last().Timestamp // Seems to be the timestamp of oldest
},
}
);
return this.Ok(); return this.Ok(response);
} }
[HttpPost("deleteReview/user/{slotId:int}/{username}")] [HttpPost("rateReview/user/{slotId:int}/{username}")]
public async Task<IActionResult> DeleteReview(int slotId, string username) public async Task<IActionResult> RateReview(int slotId, string username, [FromQuery] int rating = 0)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
User? reviewer = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (reviewer == null) return this.StatusCode(400, "");
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewer.UserId);
if (review == null) return this.StatusCode(400, "");
RatedReview? ratedReview = await this.database.RatedReviews.FirstOrDefaultAsync(r => r.ReviewId == review.ReviewId && r.UserId == user.UserId);
if (ratedReview == null)
{ {
User? reviewer = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); ratedReview = new RatedReview();
if (reviewer == null) return this.StatusCode(403, ""); ratedReview.ReviewId = review.ReviewId;
ratedReview.UserId = user.UserId;
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewer.UserId); ratedReview.Thumb = 0;
if (review == null) return this.StatusCode(403, ""); this.database.RatedReviews.Add(ratedReview);
review.Deleted = true;
review.DeletedBy = DeletedBy.LevelAuthor;
await this.database.SaveChangesAsync();
return this.Ok();
} }
public async Task<Review?> GetReviewFromBody() int oldThumb = ratedReview.Thumb;
ratedReview.Thumb = Math.Max(Math.Min(1, rating), -1);
if (oldThumb != ratedReview.Thumb)
{ {
this.Request.Body.Position = 0; if (oldThumb == -1) review.ThumbsDown--;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); else if (oldThumb == 1) review.ThumbsUp--;
XmlSerializer serializer = new(typeof(Review)); if (ratedReview.Thumb == -1) review.ThumbsDown++;
Review? review = (Review?)serializer.Deserialize(new StringReader(bodyString)); else if (ratedReview.Thumb == 1) review.ThumbsUp++;
return review;
} }
await this.database.SaveChangesAsync();
return this.Ok();
}
[HttpPost("deleteReview/user/{slotId:int}/{username}")]
public async Task<IActionResult> DeleteReview(int slotId, string username)
{
User? reviewer = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (reviewer == null) return this.StatusCode(403, "");
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewer.UserId);
if (review == null) return this.StatusCode(403, "");
review.Deleted = true;
review.DeletedBy = DeletedBy.LevelAuthor;
await this.database.SaveChangesAsync();
return this.Ok();
}
public async Task<Review?> GetReviewFromBody()
{
this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(Review));
Review? review = (Review?)serializer.Deserialize(new StringReader(bodyString));
return review;
} }
} }

View file

@ -11,155 +11,151 @@ using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Levels;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
public class ScoreController : ControllerBase
{ {
[ApiController] private readonly Database database;
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] public ScoreController(Database database)
public class ScoreController : ControllerBase
{ {
private readonly Database database; this.database = database;
}
public ScoreController(Database database) [HttpPost("scoreboard/user/{id:int}")]
public async Task<IActionResult> SubmitScore(int id, [FromQuery] bool lbp1 = false, [FromQuery] bool lbp2 = false, [FromQuery] bool lbp3 = false)
{
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
if (userAndToken == null) return this.StatusCode(403, "");
// ReSharper disable once PossibleInvalidOperationException
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(Score));
Score? score = (Score?)serializer.Deserialize(new StringReader(bodyString));
if (score == null) return this.BadRequest();
score.SlotId = id;
Slot? slot = this.database.Slots.FirstOrDefault(s => s.SlotId == score.SlotId);
if (slot == null) return this.BadRequest();
switch (gameToken.GameVersion)
{ {
this.database = database; case GameVersion.LittleBigPlanet1:
slot.PlaysLBP1Complete++;
break;
case GameVersion.LittleBigPlanet2:
slot.PlaysLBP2Complete++;
break;
case GameVersion.LittleBigPlanet3:
slot.PlaysLBP3Complete++;
break;
case GameVersion.LittleBigPlanetVita:
slot.PlaysLBPVitaComplete++;
break;
} }
[HttpPost("scoreboard/user/{id:int}")] IQueryable<Score> existingScore = this.database.Scores.Where(s => s.SlotId == score.SlotId && s.PlayerIdCollection == score.PlayerIdCollection);
public async Task<IActionResult> SubmitScore(int id, [FromQuery] bool lbp1 = false, [FromQuery] bool lbp2 = false, [FromQuery] bool lbp3 = false)
if (existingScore.Any())
{ {
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); Score first = existingScore.First(s => s.SlotId == score.SlotId);
score.ScoreId = first.ScoreId;
if (userAndToken == null) return this.StatusCode(403, ""); score.Points = Math.Max(first.Points, score.Points);
this.database.Entry(first).CurrentValues.SetValues(score);
// ReSharper disable once PossibleInvalidOperationException }
User user = userAndToken.Value.Item1; else
GameToken gameToken = userAndToken.Value.Item2; {
this.database.Scores.Add(score);
this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(Score));
Score? score = (Score?)serializer.Deserialize(new StringReader(bodyString));
if (score == null) return this.BadRequest();
score.SlotId = id;
Slot? slot = this.database.Slots.FirstOrDefault(s => s.SlotId == score.SlotId);
if (slot == null) return this.BadRequest();
switch (gameToken.GameVersion)
{
case GameVersion.LittleBigPlanet1:
slot.PlaysLBP1Complete++;
break;
case GameVersion.LittleBigPlanet2:
slot.PlaysLBP2Complete++;
break;
case GameVersion.LittleBigPlanet3:
slot.PlaysLBP3Complete++;
break;
case GameVersion.LittleBigPlanetVita:
slot.PlaysLBPVitaComplete++;
break;
}
IQueryable<Score> existingScore = this.database.Scores.Where(s => s.SlotId == score.SlotId && s.PlayerIdCollection == score.PlayerIdCollection);
if (existingScore.Any())
{
Score first = existingScore.First(s => s.SlotId == score.SlotId);
score.ScoreId = first.ScoreId;
score.Points = Math.Max(first.Points, score.Points);
this.database.Entry(first).CurrentValues.SetValues(score);
}
else
{
this.database.Scores.Add(score);
}
await this.database.SaveChangesAsync();
string myRanking = this.GetScores(score.SlotId, score.Type, user);
return this.Ok(myRanking);
} }
[HttpGet("friendscores/user/{slotId:int}/{type:int}")] await this.database.SaveChangesAsync();
public IActionResult FriendScores(int slotId, int type)
//=> await TopScores(slotId, type);
=> this.Ok(LbpSerializer.BlankElement("scores"));
[HttpGet("topscores/user/{slotId:int}/{type:int}")] string myRanking = this.GetScores(score.SlotId, score.Type, user);
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
public async Task<IActionResult> TopScores(int slotId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5)
{
// Get username
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); return this.Ok(myRanking);
}
return this.Ok(this.GetScores(slotId, type, user, pageStart, pageSize)); [HttpGet("friendscores/user/{slotId:int}/{type:int}")]
} public IActionResult FriendScores(int slotId, int type)
//=> await TopScores(slotId, type);
=> this.Ok(LbpSerializer.BlankElement("scores"));
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] [HttpGet("topscores/user/{slotId:int}/{type:int}")]
public string GetScores(int slotId, int type, User user, int pageStart = -1, int pageSize = 5) [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
{ public async Task<IActionResult> TopScores(int slotId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5)
// This is hella ugly but it technically assigns the proper rank to a score {
// var needed for Anonymous type returned from SELECT // Get username
var rankedScores = this.database.Scores.Where(s => s.SlotId == slotId && s.Type == type) User? user = await this.database.UserFromGameRequest(this.Request);
.OrderByDescending(s => s.Points)
.ToList()
.Select
(
(s, rank) => new
{
Score = s,
Rank = rank + 1,
}
);
// Find your score, since even if you aren't in the top list your score is pinned if (user == null) return this.StatusCode(403, "");
var myScore = rankedScores.Where
(rs => rs.Score.PlayerIdCollection.Contains(user.Username))
.OrderByDescending(rs => rs.Score.Points)
.FirstOrDefault();
// Paginated viewing: if not requesting pageStart, get results around user return this.Ok(this.GetScores(slotId, type, user, pageStart, pageSize));
var pagedScores = rankedScores.Skip(pageStart != -1 || myScore == null ? pageStart - 1 : myScore.Rank - 3).Take(Math.Min(pageSize, 30)); }
string serializedScores = pagedScores.Aggregate [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
public string GetScores(int slotId, int type, User user, int pageStart = -1, int pageSize = 5)
{
// This is hella ugly but it technically assigns the proper rank to a score
// var needed for Anonymous type returned from SELECT
var rankedScores = this.database.Scores.Where(s => s.SlotId == slotId && s.Type == type)
.OrderByDescending(s => s.Points)
.ToList()
.Select
( (
string.Empty, (s, rank) => new
(current, rs) =>
{ {
rs.Score.Rank = rs.Rank; Score = s,
return current + rs.Score.Serialize(); Rank = rank + 1,
} }
); );
string res; // Find your score, since even if you aren't in the top list your score is pinned
if (myScore == null) res = LbpSerializer.StringElement("scores", serializedScores); var myScore = rankedScores.Where(rs => rs.Score.PlayerIdCollection.Contains(user.Username)).OrderByDescending(rs => rs.Score.Points).FirstOrDefault();
else
res = LbpSerializer.TaggedStringElement
(
"scores",
serializedScores,
new Dictionary<string, object>
{
{
"yourScore", myScore.Score.Points
},
{
"yourRank", myScore.Rank
}, //This is the numerator of your position globally in the side menu.
{
"totalNumScores", rankedScores.Count()
}, // This is the denominator of your position globally in the side menu.
}
);
return res; // Paginated viewing: if not requesting pageStart, get results around user
} var pagedScores = rankedScores.Skip(pageStart != -1 || myScore == null ? pageStart - 1 : myScore.Rank - 3).Take(Math.Min(pageSize, 30));
string serializedScores = pagedScores.Aggregate
(
string.Empty,
(current, rs) =>
{
rs.Score.Rank = rs.Rank;
return current + rs.Score.Serialize();
}
);
string res;
if (myScore == null) res = LbpSerializer.StringElement("scores", serializedScores);
else
res = LbpSerializer.TaggedStringElement
(
"scores",
serializedScores,
new Dictionary<string, object>
{
{
"yourScore", myScore.Score.Points
},
{
"yourRank", myScore.Rank
}, //This is the numerator of your position globally in the side menu.
{
"totalNumScores", rankedScores.Count()
}, // This is the denominator of your position globally in the side menu.
}
);
return res;
} }
} }

View file

@ -7,48 +7,47 @@ using LBPUnion.ProjectLighthouse.Types.Levels;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
public class SearchController : ControllerBase
{ {
[ApiController] private readonly Database database;
[Route("LITTLEBIGPLANETPS3_XML/")] public SearchController(Database database)
[Produces("text/xml")]
public class SearchController : ControllerBase
{ {
private readonly Database database; this.database = database;
public SearchController(Database database) }
{
this.database = database;
}
[HttpGet("slots/search")] [HttpGet("slots/search")]
public async Task<IActionResult> SearchSlots([FromQuery] string query, [FromQuery] int pageSize, [FromQuery] int pageStart) public async Task<IActionResult> SearchSlots([FromQuery] string query, [FromQuery] int pageSize, [FromQuery] int pageStart)
{ {
if (query == null) return this.BadRequest(); if (query == null) return this.BadRequest();
query = query.ToLower(); query = query.ToLower();
string[] keywords = query.Split(" "); string[] keywords = query.Split(" ");
IQueryable<Slot> dbQuery = this.database.Slots IQueryable<Slot> dbQuery = this.database.Slots
.Include(s => s.Creator) .Include(s => s.Creator)
.Include(s => s.Location) .Include(s => s.Location)
.Where(s => s.SlotId >= 0); // dumb query to conv into IQueryable .Where(s => s.SlotId >= 0); // dumb query to conv into IQueryable
// ReSharper disable once LoopCanBeConvertedToQuery // ReSharper disable once LoopCanBeConvertedToQuery
foreach (string keyword in keywords) foreach (string keyword in keywords)
dbQuery = dbQuery.Where dbQuery = dbQuery.Where
( (
s => s.Name.ToLower().Contains(keyword) || s => s.Name.ToLower().Contains(keyword) ||
s.Description.ToLower().Contains(keyword) || s.Description.ToLower().Contains(keyword) ||
s.Creator.Username.ToLower().Contains(keyword) || s.Creator.Username.ToLower().Contains(keyword) ||
s.SlotId.ToString().Equals(keyword) s.SlotId.ToString().Equals(keyword)
); );
List<Slot> slots = await dbQuery.Skip(pageStart - 1).Take(Math.Min(pageSize, 30)).ToListAsync(); List<Slot> slots = await dbQuery.Skip(pageStart - 1).Take(Math.Min(pageSize, 30)).ToListAsync();
string response = slots.Aggregate("", (current, slot) => current + slot.Serialize()); string response = slots.Aggregate("", (current, slot) => current + slot.Serialize());
return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "total", dbQuery.Count())); return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "total", dbQuery.Count()));
}
} }
} }

View file

@ -11,345 +11,340 @@ using LBPUnion.ProjectLighthouse.Types.Settings;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
public class SlotsController : ControllerBase
{ {
[ApiController] private readonly Database database;
[Route("LITTLEBIGPLANETPS3_XML/")] public SlotsController(Database database)
[Produces("text/xml")]
public class SlotsController : ControllerBase
{ {
private readonly Database database; this.database = database;
public SlotsController(Database database) }
{
this.database = database;
}
[HttpGet("slots/by")] [HttpGet("slots/by")]
public async Task<IActionResult> SlotsBy([FromQuery] string u, [FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> SlotsBy([FromQuery] string u, [FromQuery] int pageStart, [FromQuery] int pageSize)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
GameVersion gameVersion = token.GameVersion; GameVersion gameVersion = token.GameVersion;
User? user = await this.database.Users.FirstOrDefaultAsync(dbUser => dbUser.Username == u); User? user = await this.database.Users.FirstOrDefaultAsync(dbUser => dbUser.Username == u);
if (user == null) return this.NotFound(); if (user == null) return this.NotFound();
string response = Enumerable.Aggregate string response = Enumerable.Aggregate
( (
this.database.Slots.Where(s => s.GameVersion <= gameVersion) this.database.Slots.Where(s => s.GameVersion <= gameVersion)
.Include(s => s.Creator)
.Include(s => s.Location)
.Where(s => s.Creator!.Username == user.Username)
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, ServerSettings.Instance.EntitledSlots)),
string.Empty,
(current, slot) => current + slot.Serialize()
);
return this.Ok
(
LbpSerializer.TaggedStringElement
(
"slots",
response,
new Dictionary<string, object>
{
{
"hint_start", pageStart + Math.Min(pageSize, ServerSettings.Instance.EntitledSlots)
},
{
"total", user.UsedSlots
},
}
)
);
}
[HttpGet("s/user/{id:int}")]
public async Task<IActionResult> SUser(int id)
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameVersion gameVersion = token.GameVersion;
Slot? slot = await this.database.Slots.Where(s => s.GameVersion <= gameVersion)
.Include(s => s.Creator) .Include(s => s.Creator)
.Include(s => s.Location) .Include(s => s.Location)
.FirstOrDefaultAsync(s => s.SlotId == id); .Where(s => s.Creator!.Username == user.Username)
if (slot == null) return this.NotFound();
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == id && r.UserId == user.UserId);
VisitedLevel? visitedLevel = await this.database.VisitedLevels.FirstOrDefaultAsync(r => r.SlotId == id && r.UserId == user.UserId);
return this.Ok(slot.Serialize(ratedLevel, visitedLevel));
}
[HttpGet("slots/cool")]
public async Task<IActionResult> Lbp1CoolSlots([FromQuery] int page)
{
const int pageSize = 30;
return await CoolSlots((page - 1) * pageSize, pageSize);
}
[HttpGet("slots/lbp2cool")]
public async Task<IActionResult> CoolSlots
(
[FromQuery] int pageStart,
[FromQuery] int pageSize,
[FromQuery] string? gameFilterType = null,
[FromQuery] int? players = null,
[FromQuery] bool? move = null,
[FromQuery] int? page = null
)
{
int _pageStart = pageStart;
if (page != null) _pageStart = (int)page * 30;
// bit of a better placeholder until we can track average user interaction with /stream endpoint
return await ThumbsSlots(_pageStart, Math.Min(pageSize, 30), gameFilterType, players, move, "thisWeek");
}
[HttpGet("slots")]
public async Task<IActionResult> NewestSlots([FromQuery] int pageStart, [FromQuery] int pageSize)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameVersion gameVersion = token.GameVersion;
IQueryable<Slot> slots = this.database.Slots.Where(s => s.GameVersion <= gameVersion)
.Include(s => s.Creator)
.Include(s => s.Location)
.OrderByDescending(s => s.FirstUploaded)
.Skip(pageStart - 1) .Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30)); .Take(Math.Min(pageSize, ServerSettings.Instance.EntitledSlots)),
string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize()); string.Empty,
(current, slot) => current + slot.Serialize()
);
return this.Ok return this.Ok
(
LbpSerializer.TaggedStringElement
( (
LbpSerializer.TaggedStringElement "slots",
( response,
"slots", new Dictionary<string, object>
response, {
new Dictionary<string, object>
{ {
{ "hint_start", pageStart + Math.Min(pageSize, ServerSettings.Instance.EntitledSlots)
"hint_start", pageStart + Math.Min(pageSize, ServerSettings.Instance.EntitledSlots) },
}, {
{ "total", user.UsedSlots
"total", await StatisticsHelper.SlotCount() },
}, }
} )
) );
); }
}
[HttpGet("slots/mmpicks")] [HttpGet("s/user/{id:int}")]
public async Task<IActionResult> TeamPickedSlots([FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> SUser(int id)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); User? user = await this.database.UserFromGameRequest(this.Request);
if (token == null) return this.StatusCode(403, ""); if (user == null) return this.StatusCode(403, "");
GameVersion gameVersion = token.GameVersion; GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
IQueryable<Slot> slots = this.database.Slots.Where(s => s.GameVersion <= gameVersion) GameVersion gameVersion = token.GameVersion;
.Where(s => s.TeamPick)
.Include(s => s.Creator)
.Include(s => s.Location)
.OrderByDescending(s => s.LastUpdated)
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30));
string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize());
return this.Ok Slot? slot = await this.database.Slots.Where(s => s.GameVersion <= gameVersion)
.Include(s => s.Creator)
.Include(s => s.Location)
.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound();
RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == id && r.UserId == user.UserId);
VisitedLevel? visitedLevel = await this.database.VisitedLevels.FirstOrDefaultAsync(r => r.SlotId == id && r.UserId == user.UserId);
return this.Ok(slot.Serialize(ratedLevel, visitedLevel));
}
[HttpGet("slots/cool")]
public async Task<IActionResult> Lbp1CoolSlots([FromQuery] int page)
{
const int pageSize = 30;
return await this.CoolSlots((page - 1) * pageSize, pageSize);
}
[HttpGet("slots/lbp2cool")]
public async Task<IActionResult> CoolSlots
(
[FromQuery] int pageStart,
[FromQuery] int pageSize,
[FromQuery] string? gameFilterType = null,
[FromQuery] int? players = null,
[FromQuery] bool? move = null,
[FromQuery] int? page = null
)
{
int _pageStart = pageStart;
if (page != null) _pageStart = (int)page * 30;
// bit of a better placeholder until we can track average user interaction with /stream endpoint
return await this.ThumbsSlots(_pageStart, Math.Min(pageSize, 30), gameFilterType, players, move, "thisWeek");
}
[HttpGet("slots")]
public async Task<IActionResult> NewestSlots([FromQuery] int pageStart, [FromQuery] int pageSize)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameVersion gameVersion = token.GameVersion;
IQueryable<Slot> slots = this.database.Slots.Where(s => s.GameVersion <= gameVersion)
.Include(s => s.Creator)
.Include(s => s.Location)
.OrderByDescending(s => s.FirstUploaded)
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30));
string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize());
return this.Ok
(
LbpSerializer.TaggedStringElement
( (
LbpSerializer.TaggedStringElement "slots",
( response,
"slots", new Dictionary<string, object>
response, {
new Dictionary<string, object>
{ {
{ "hint_start", pageStart + Math.Min(pageSize, ServerSettings.Instance.EntitledSlots)
"hint_start", pageStart + Math.Min(pageSize, ServerSettings.Instance.EntitledSlots) },
}, {
{ "total", await StatisticsHelper.SlotCount()
"total", await StatisticsHelper.MMPicksCount() },
}, }
} )
) );
); }
}
[HttpGet("slots/lbp2luckydip")] [HttpGet("slots/mmpicks")]
public async Task<IActionResult> LuckyDipSlots([FromQuery] int pageStart, [FromQuery] int pageSize, [FromQuery] int seed) public async Task<IActionResult> TeamPickedSlots([FromQuery] int pageStart, [FromQuery] int pageSize)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
GameVersion gameVersion = token.GameVersion; GameVersion gameVersion = token.GameVersion;
IEnumerable<Slot> slots = this.database.Slots.Where(s => s.GameVersion <= gameVersion) IQueryable<Slot> slots = this.database.Slots.Where(s => s.GameVersion <= gameVersion)
.Include(s => s.Creator) .Where(s => s.TeamPick)
.Include(s => s.Location) .Include(s => s.Creator)
.OrderBy(_ => EF.Functions.Random()) .Include(s => s.Location)
.Take(Math.Min(pageSize, 30)); .OrderByDescending(s => s.LastUpdated)
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30));
string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize());
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize()); return this.Ok
(
return this.Ok LbpSerializer.TaggedStringElement
( (
LbpSerializer.TaggedStringElement "slots",
( response,
"slots", new Dictionary<string, object>
response, {
new Dictionary<string, object>
{ {
{ "hint_start", pageStart + Math.Min(pageSize, ServerSettings.Instance.EntitledSlots)
"hint_start", pageStart + Math.Min(pageSize, ServerSettings.Instance.EntitledSlots) },
},
{
"total", await StatisticsHelper.SlotCount()
},
}
)
);
}
[HttpGet("slots/thumbs")]
public async Task<IActionResult> ThumbsSlots
(
[FromQuery] int pageStart,
[FromQuery] int pageSize,
[FromQuery] string? gameFilterType = null,
[FromQuery] int? players = null,
[FromQuery] bool? move = null,
[FromQuery] string? dateFilterType = null
)
{
Random rand = new();
IEnumerable<Slot> slots = FilterByRequest(gameFilterType, dateFilterType)
.AsEnumerable()
.OrderByDescending(s => s.Thumbsup)
.ThenBy(_ => rand.Next())
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30));
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize());
return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "hint_start", pageStart + Math.Min(pageSize, 30)));
}
[HttpGet("slots/mostUniquePlays")]
public async Task<IActionResult> MostUniquePlaysSlots
(
[FromQuery] int pageStart,
[FromQuery] int pageSize,
[FromQuery] string? gameFilterType = null,
[FromQuery] int? players = null,
[FromQuery] bool? move = null,
[FromQuery] string? dateFilterType = null
)
{
Random rand = new();
IEnumerable<Slot> slots = FilterByRequest(gameFilterType, dateFilterType)
.AsEnumerable()
.OrderByDescending
(
s =>
{ {
// probably not the best way to do this? "total", await StatisticsHelper.MMPicksCount()
return GetGameFilter(gameFilterType) switch },
{ }
GameVersion.LittleBigPlanet1 => s.PlaysLBP1Unique, )
GameVersion.LittleBigPlanet2 => s.PlaysLBP2Unique, );
GameVersion.LittleBigPlanet3 => s.PlaysLBP3Unique, }
GameVersion.LittleBigPlanetVita => s.PlaysLBPVitaUnique,
_ => s.PlaysUnique,
};
}
)
.ThenBy(_ => rand.Next())
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30));
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize()); [HttpGet("slots/lbp2luckydip")]
public async Task<IActionResult> LuckyDipSlots([FromQuery] int pageStart, [FromQuery] int pageSize, [FromQuery] int seed)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "hint_start", pageStart + Math.Min(pageSize, 30))); GameVersion gameVersion = token.GameVersion;
}
[HttpGet("slots/mostHearted")] IEnumerable<Slot> slots = this.database.Slots.Where(s => s.GameVersion <= gameVersion)
public async Task<IActionResult> MostHeartedSlots .Include(s => s.Creator)
.Include(s => s.Location)
.OrderBy(_ => EF.Functions.Random())
.Take(Math.Min(pageSize, 30));
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize());
return this.Ok
( (
[FromQuery] int pageStart, LbpSerializer.TaggedStringElement
[FromQuery] int pageSize, (
[FromQuery] string? gameFilterType = null, "slots",
[FromQuery] int? players = null, response,
[FromQuery] bool? move = null, new Dictionary<string, object>
[FromQuery] string? dateFilterType = null {
) {
"hint_start", pageStart + Math.Min(pageSize, ServerSettings.Instance.EntitledSlots)
},
{
"total", await StatisticsHelper.SlotCount()
},
}
)
);
}
[HttpGet("slots/thumbs")]
public async Task<IActionResult> ThumbsSlots
(
[FromQuery] int pageStart,
[FromQuery] int pageSize,
[FromQuery] string? gameFilterType = null,
[FromQuery] int? players = null,
[FromQuery] bool? move = null,
[FromQuery] string? dateFilterType = null
)
{
Random rand = new();
IEnumerable<Slot> slots = this.FilterByRequest(gameFilterType, dateFilterType)
.AsEnumerable()
.OrderByDescending(s => s.Thumbsup)
.ThenBy(_ => rand.Next())
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30));
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize());
return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "hint_start", pageStart + Math.Min(pageSize, 30)));
}
[HttpGet("slots/mostUniquePlays")]
public async Task<IActionResult> MostUniquePlaysSlots
(
[FromQuery] int pageStart,
[FromQuery] int pageSize,
[FromQuery] string? gameFilterType = null,
[FromQuery] int? players = null,
[FromQuery] bool? move = null,
[FromQuery] string? dateFilterType = null
)
{
Random rand = new();
IEnumerable<Slot> slots = this.FilterByRequest(gameFilterType, dateFilterType)
.AsEnumerable()
.OrderByDescending
(
s =>
{
// probably not the best way to do this?
return this.GetGameFilter(gameFilterType) switch
{
GameVersion.LittleBigPlanet1 => s.PlaysLBP1Unique,
GameVersion.LittleBigPlanet2 => s.PlaysLBP2Unique,
GameVersion.LittleBigPlanet3 => s.PlaysLBP3Unique,
GameVersion.LittleBigPlanetVita => s.PlaysLBPVitaUnique,
_ => s.PlaysUnique,
};
}
)
.ThenBy(_ => rand.Next())
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30));
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize());
return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "hint_start", pageStart + Math.Min(pageSize, 30)));
}
[HttpGet("slots/mostHearted")]
public async Task<IActionResult> MostHeartedSlots
(
[FromQuery] int pageStart,
[FromQuery] int pageSize,
[FromQuery] string? gameFilterType = null,
[FromQuery] int? players = null,
[FromQuery] bool? move = null,
[FromQuery] string? dateFilterType = null
)
{
Random rand = new();
IEnumerable<Slot> slots = this.FilterByRequest(gameFilterType, dateFilterType)
.AsEnumerable()
.OrderByDescending(s => s.Hearts)
.ThenBy(_ => rand.Next())
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30));
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize());
return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "hint_start", pageStart + Math.Min(pageSize, 30)));
}
public GameVersion GetGameFilter(string? gameFilterType)
{
return gameFilterType switch
{ {
Random rand = new(); "lbp1" => GameVersion.LittleBigPlanet1,
"lbp2" => GameVersion.LittleBigPlanet2,
"lbp3" => GameVersion.LittleBigPlanet3,
"both" => GameVersion.LittleBigPlanet2, // LBP2 default option
null => GameVersion.LittleBigPlanet1,
_ => GameVersion.Unknown,
};
}
IEnumerable<Slot> slots = FilterByRequest(gameFilterType, dateFilterType) public IQueryable<Slot> FilterByRequest(string? gameFilterType, string? dateFilterType)
.AsEnumerable() {
.OrderByDescending(s => s.Hearts) string _dateFilterType = dateFilterType ?? "";
.ThenBy(_ => rand.Next())
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30));
string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize()); long oldestTime = _dateFilterType switch
return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "hint_start", pageStart + Math.Min(pageSize, 30)));
}
public GameVersion GetGameFilter(string? gameFilterType)
{ {
return gameFilterType switch "thisWeek" => DateTimeOffset.Now.AddDays(-7).ToUnixTimeMilliseconds(),
{ "thisMonth" => DateTimeOffset.Now.AddDays(-31).ToUnixTimeMilliseconds(),
"lbp1" => GameVersion.LittleBigPlanet1, _ => 0,
"lbp2" => GameVersion.LittleBigPlanet2, };
"lbp3" => GameVersion.LittleBigPlanet3,
"both" => GameVersion.LittleBigPlanet2, // LBP2 default option
null => GameVersion.LittleBigPlanet1,
_ => GameVersion.Unknown,
};
}
public IQueryable<Slot> FilterByRequest(string? gameFilterType, string? dateFilterType) GameVersion gameVersion = this.GetGameFilter(gameFilterType);
{
string _dateFilterType = dateFilterType ?? "";
long oldestTime = _dateFilterType switch IQueryable<Slot> whereSlots;
{
"thisWeek" => DateTimeOffset.Now.AddDays(-7).ToUnixTimeMilliseconds(),
"thisMonth" => DateTimeOffset.Now.AddDays(-31).ToUnixTimeMilliseconds(),
_ => 0,
};
GameVersion gameVersion = GetGameFilter(gameFilterType); // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
if (gameFilterType == "both")
// Get game versions less than the current version
// Needs support for LBP3 ("both" = LBP1+2)
whereSlots = this.database.Slots.Where(s => s.GameVersion <= gameVersion && s.FirstUploaded >= oldestTime);
else
// Get game versions exactly equal to gamefiltertype
whereSlots = this.database.Slots.Where(s => s.GameVersion == gameVersion && s.FirstUploaded >= oldestTime);
IQueryable<Slot> whereSlots; return whereSlots.Include(s => s.Creator).Include(s => s.Location);
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
if (gameFilterType == "both")
{
// Get game versions less than the current version
// Needs support for LBP3 ("both" = LBP1+2)
whereSlots = this.database.Slots.Where(s => s.GameVersion <= gameVersion && s.FirstUploaded >= oldestTime);
}
else
{
// Get game versions exactly equal to gamefiltertype
whereSlots = this.database.Slots.Where(s => s.GameVersion == gameVersion && s.FirstUploaded >= oldestTime);
}
return whereSlots.Include(s => s.Creator).Include(s => s.Location);
}
} }
} }

View file

@ -3,37 +3,36 @@ using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/plain")]
public class StatisticsController : ControllerBase
{ {
[ApiController] private readonly Database database;
[Route("LITTLEBIGPLANETPS3_XML/")] public StatisticsController(Database database)
[Produces("text/plain")]
public class StatisticsController : ControllerBase
{ {
private readonly Database database; this.database = database;
public StatisticsController(Database database)
{
this.database = database;
}
[HttpGet("playersInPodCount")]
[HttpGet("totalPlayerCount")]
public async Task<IActionResult> TotalPlayerCount() => this.Ok((await StatisticsHelper.RecentMatches()).ToString()!);
[HttpGet("planetStats")]
public async Task<IActionResult> PlanetStats()
{
int totalSlotCount = await StatisticsHelper.SlotCount();
int mmPicksCount = await StatisticsHelper.MMPicksCount();
return this.Ok
(
LbpSerializer.StringElement
("planetStats", LbpSerializer.StringElement("totalSlotCount", totalSlotCount) + LbpSerializer.StringElement("mmPicksCount", mmPicksCount))
);
}
[HttpGet("planetStats/totalLevelCount")]
public async Task<IActionResult> TotalLevelCount() => this.Ok((await StatisticsHelper.SlotCount()).ToString());
} }
[HttpGet("playersInPodCount")]
[HttpGet("totalPlayerCount")]
public async Task<IActionResult> TotalPlayerCount() => this.Ok((await StatisticsHelper.RecentMatches()).ToString()!);
[HttpGet("planetStats")]
public async Task<IActionResult> PlanetStats()
{
int totalSlotCount = await StatisticsHelper.SlotCount();
int mmPicksCount = await StatisticsHelper.MMPicksCount();
return this.Ok
(
LbpSerializer.StringElement
("planetStats", LbpSerializer.StringElement("totalSlotCount", totalSlotCount) + LbpSerializer.StringElement("mmPicksCount", mmPicksCount))
);
}
[HttpGet("planetStats/totalLevelCount")]
public async Task<IActionResult> TotalLevelCount() => this.Ok((await StatisticsHelper.SlotCount()).ToString());
} }

View file

@ -1,13 +1,12 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
public class StoreController : Controller
{ {
[ApiController] [HttpGet("promotions")]
[Route("LITTLEBIGPLANETPS3_XML/")] public IActionResult Promotions() => this.Ok();
[Produces("text/xml")]
public class StoreController : Controller
{
[HttpGet("promotions")]
public IActionResult Promotions() => this.Ok();
}
} }

View file

@ -12,181 +12,180 @@ using LBPUnion.ProjectLighthouse.Types.Profiles;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers;
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
public class UserController : ControllerBase
{ {
[ApiController] private readonly Database database;
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] public UserController(Database database)
public class UserController : ControllerBase
{ {
private readonly Database database; this.database = database;
}
public UserController(Database database) public async Task<string?> GetSerializedUser(string username, GameVersion gameVersion = GameVersion.LittleBigPlanet1)
{
User? user = await this.database.Users.Include(u => u.Location).FirstOrDefaultAsync(u => u.Username == username);
return user?.Serialize(gameVersion);
}
[HttpGet("user/{username}")]
public async Task<IActionResult> GetUser(string username)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
string? user = await this.GetSerializedUser(username, token.GameVersion);
if (user == null) return this.NotFound();
return this.Ok(user);
}
[HttpGet("users")]
public async Task<IActionResult> GetUserAlt([FromQuery] string[] u)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
List<string?> serializedUsers = new();
foreach (string userId in u) serializedUsers.Add(await this.GetSerializedUser(userId, token.GameVersion));
string serialized = serializedUsers.Aggregate(string.Empty, (current, user) => user == null ? current : current + user);
return this.Ok(LbpSerializer.StringElement("users", serialized));
}
[HttpPost("updateUser")]
public async Task<IActionResult> UpdateUser()
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
XmlReaderSettings settings = new()
{ {
this.database = database; Async = true, // this is apparently not default
} };
public async Task<string?> GetSerializedUser(string username, GameVersion gameVersion = GameVersion.LittleBigPlanet1) bool locationChanged = false;
// this is an absolute mess, but necessary because LBP only sends what changed
//
// example for changing profile card location:
// <updateUser>
// <location>
// <x>1234</x>
// <y>1234</y>
// </location>
// </updateUser>
//
// example for changing biography:
// <updateUser>
// <biography>biography stuff</biography>
// </updateUser>
//
// if you find a way to make it not stupid feel free to replace this
using (XmlReader reader = XmlReader.Create(this.Request.Body, settings))
{ {
User? user = await this.database.Users.Include(u => u.Location).FirstOrDefaultAsync(u => u.Username == username); List<string> path = new(); // you can think of this as a file path in the XML, like <updateUser> -> <location> -> <x>
return user?.Serialize(gameVersion); while (await reader.ReadAsync()) // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
} switch (reader.NodeType)
{
[HttpGet("user/{username}")] case XmlNodeType.Element:
public async Task<IActionResult> GetUser(string username) path.Add(reader.Name);
{ break;
GameToken? token = await this.database.GameTokenFromRequest(this.Request); case XmlNodeType.Text:
if (token == null) return this.StatusCode(403, ""); switch (path[1])
{
string? user = await this.GetSerializedUser(username, token.GameVersion); case "biography":
if (user == null) return this.NotFound();
return this.Ok(user);
}
[HttpGet("users")]
public async Task<IActionResult> GetUserAlt([FromQuery] string[] u)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
List<string?> serializedUsers = new();
foreach (string userId in u) serializedUsers.Add(await this.GetSerializedUser(userId, token.GameVersion));
string serialized = serializedUsers.Aggregate(string.Empty, (current, user) => user == null ? current : current + user);
return this.Ok(LbpSerializer.StringElement("users", serialized));
}
[HttpPost("updateUser")]
public async Task<IActionResult> UpdateUser()
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
XmlReaderSettings settings = new()
{
Async = true, // this is apparently not default
};
bool locationChanged = false;
// this is an absolute mess, but necessary because LBP only sends what changed
//
// example for changing profile card location:
// <updateUser>
// <location>
// <x>1234</x>
// <y>1234</y>
// </location>
// </updateUser>
//
// example for changing biography:
// <updateUser>
// <biography>biography stuff</biography>
// </updateUser>
//
// if you find a way to make it not stupid feel free to replace this
using (XmlReader reader = XmlReader.Create(this.Request.Body, settings))
{
List<string> path = new(); // you can think of this as a file path in the XML, like <updateUser> -> <location> -> <x>
while (await reader.ReadAsync()) // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
switch (reader.NodeType)
{
case XmlNodeType.Element:
path.Add(reader.Name);
break;
case XmlNodeType.Text:
switch (path[1])
{ {
case "biography": user.Biography = await reader.GetValueAsync();
{ break;
user.Biography = await reader.GetValueAsync();
break;
}
case "location":
{
locationChanged = true; // if we're here then we're probably about to change the location.
// ReSharper disable once ConvertIfStatementToSwitchStatement
if (path[2] == "x")
user.Location.X = Convert.ToInt32
(await reader.GetValueAsync()); // GetValue only returns a string, i guess we just hope its a number lol
else if (path[2] == "y") user.Location.Y = Convert.ToInt32(await reader.GetValueAsync());
break;
}
case "icon":
{
user.IconHash = await reader.GetValueAsync();
break;
}
case "planets":
{
user.PlanetHash = await reader.GetValueAsync();
break;
}
case "yay2":
{
user.YayHash = await reader.GetValueAsync();
break;
}
case "boo2":
{
user.BooHash = await reader.GetValueAsync();
break;
}
case "meh2":
{
user.MehHash = await reader.GetValueAsync();
break;
}
} }
case "location":
{
locationChanged = true; // if we're here then we're probably about to change the location.
// ReSharper disable once ConvertIfStatementToSwitchStatement
if (path[2] == "x")
user.Location.X = Convert.ToInt32
(await reader.GetValueAsync()); // GetValue only returns a string, i guess we just hope its a number lol
else if (path[2] == "y") user.Location.Y = Convert.ToInt32(await reader.GetValueAsync());
break;
}
case "icon":
{
user.IconHash = await reader.GetValueAsync();
break;
}
case "planets":
{
user.PlanetHash = await reader.GetValueAsync();
break;
}
case "yay2":
{
user.YayHash = await reader.GetValueAsync();
break;
}
case "boo2":
{
user.BooHash = await reader.GetValueAsync();
break;
}
case "meh2":
{
user.MehHash = await reader.GetValueAsync();
break;
}
}
break; break;
case XmlNodeType.EndElement: case XmlNodeType.EndElement:
path.RemoveAt(path.Count - 1); path.RemoveAt(path.Count - 1);
break; break;
} }
}
// the way location on a user card works is stupid and will not save with the way below as-is, so we do the following:
if (locationChanged) // only modify the database if we modify here
{
Location? l = await this.database.Locations.FirstOrDefaultAsync(l => l.Id == user.LocationId); // find the location in the database again
if (l == null) throw new Exception("this shouldn't happen ever but we handle this");
// set the location in the database to the one we modified above
l.X = user.Location.X;
l.Y = user.Location.Y;
// now both are in sync, and will update in the database.
}
if (this.database.ChangeTracker.HasChanges()) await this.database.SaveChangesAsync(); // save the user to the database if we changed anything
return this.Ok();
} }
[HttpPost("update_my_pins")] // the way location on a user card works is stupid and will not save with the way below as-is, so we do the following:
public async Task<IActionResult> UpdateMyPins() if (locationChanged) // only modify the database if we modify here
{ {
User? user = await this.database.UserFromGameRequest(this.Request); Location? l = await this.database.Locations.FirstOrDefaultAsync(l => l.Id == user.LocationId); // find the location in the database again
if (user == null) return this.StatusCode(403, "");
string pinsString = await new StreamReader(this.Request.Body).ReadToEndAsync(); if (l == null) throw new Exception("this shouldn't happen ever but we handle this");
Pins? pinJson = JsonSerializer.Deserialize<Pins>(pinsString);
if (pinJson == null) return this.BadRequest();
// Sometimes the update gets called periodically as pin progress updates via playing, // set the location in the database to the one we modified above
// may not affect equipped profile pins however, so check before setting it. l.X = user.Location.X;
string currentPins = user.Pins; l.Y = user.Location.Y;
string newPins = string.Join(",", pinJson.ProfilePins);
if (string.Equals(currentPins, newPins)) return this.Ok("[{\"StatusCode\":200}]"); // now both are in sync, and will update in the database.
user.Pins = newPins;
await this.database.SaveChangesAsync();
return this.Ok("[{\"StatusCode\":200}]");
} }
if (this.database.ChangeTracker.HasChanges()) await this.database.SaveChangesAsync(); // save the user to the database if we changed anything
return this.Ok();
}
[HttpPost("update_my_pins")]
public async Task<IActionResult> UpdateMyPins()
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
string pinsString = await new StreamReader(this.Request.Body).ReadToEndAsync();
Pins? pinJson = JsonSerializer.Deserialize<Pins>(pinsString);
if (pinJson == null) return this.BadRequest();
// Sometimes the update gets called periodically as pin progress updates via playing,
// may not affect equipped profile pins however, so check before setting it.
string currentPins = user.Pins;
string newPins = string.Join(",", pinJson.ProfilePins);
if (string.Equals(currentPins, newPins)) return this.Ok("[{\"StatusCode\":200}]");
user.Pins = newPins;
await this.database.SaveChangesAsync();
return this.Ok("[{\"StatusCode\":200}]");
} }
} }

View file

@ -6,68 +6,67 @@ using LBPUnion.ProjectLighthouse.Types.Levels;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers.Website.Admin namespace LBPUnion.ProjectLighthouse.Controllers.Website.Admin;
[ApiController]
[Route("admin/slot/{id:int}")]
public class AdminSlotController : ControllerBase
{ {
[ApiController] private readonly Database database;
[Route("admin/slot/{id:int}")]
public class AdminSlotController : ControllerBase public AdminSlotController(Database database)
{ {
private readonly Database database; this.database = database;
}
public AdminSlotController(Database database) [HttpGet("teamPick")]
{ public async Task<IActionResult> TeamPick([FromRoute] int id)
this.database = database; {
} User? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsAdmin) return this.StatusCode(403, "");
[HttpGet("teamPick")] Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
public async Task<IActionResult> TeamPick([FromRoute] int id) if (slot == null) return this.NotFound();
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsAdmin) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); slot.TeamPick = true;
if (slot == null) return this.NotFound();
slot.TeamPick = true; await this.database.SaveChangesAsync();
await this.database.SaveChangesAsync(); return this.Ok();
}
return this.Ok(); [HttpGet("removeTeamPick")]
} public async Task<IActionResult> RemoveTeamPick([FromRoute] int id)
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsAdmin) return this.StatusCode(403, "");
[HttpGet("removeTeamPick")] Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
public async Task<IActionResult> RemoveTeamPick([FromRoute] int id) if (slot == null) return this.NotFound();
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsAdmin) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); slot.TeamPick = false;
if (slot == null) return this.NotFound();
slot.TeamPick = false; await this.database.SaveChangesAsync();
await this.database.SaveChangesAsync(); return this.Ok();
}
return this.Ok(); [HttpGet("delete")]
} public async Task<IActionResult> DeleteLevel([FromRoute] int id)
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsAdmin) return this.StatusCode(403, "");
[HttpGet("delete")] Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
public async Task<IActionResult> DeleteLevel([FromRoute] int id) if (slot == null) return this.Ok();
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsAdmin) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); if (slot.Location == null) throw new ArgumentNullException();
if (slot == null) return this.Ok();
if (slot.Location == null) throw new ArgumentNullException(); this.database.Locations.Remove(slot.Location);
this.database.Slots.Remove(slot);
this.database.Locations.Remove(slot.Location); await this.database.SaveChangesAsync();
this.database.Slots.Remove(slot);
await this.database.SaveChangesAsync(); return this.Ok();
return this.Ok();
}
} }
} }

View file

@ -4,34 +4,33 @@ using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers.Website.Admin namespace LBPUnion.ProjectLighthouse.Controllers.Website.Admin;
[ApiController]
[Route("admin/user/{id:int}")]
public class AdminUserController : ControllerBase
{ {
[ApiController] private readonly Database database;
[Route("admin/user/{id:int}")]
public class AdminUserController : ControllerBase public AdminUserController(Database database)
{ {
private readonly Database database; this.database = database;
}
public AdminUserController(Database database) [HttpGet("unban")]
{ public async Task<IActionResult> UnbanUser([FromRoute] int id)
this.database = database; {
} User? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsAdmin) return this.NotFound();
[HttpGet("unban")] User? targetedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id);
public async Task<IActionResult> UnbanUser([FromRoute] int id) ;
{ if (targetedUser == null) return this.NotFound();
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null || !user.IsAdmin) return this.NotFound();
User? targetedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); targetedUser.Banned = false;
; targetedUser.BannedReason = null;
if (targetedUser == null) return this.NotFound();
targetedUser.Banned = false; await this.database.SaveChangesAsync();
targetedUser.BannedReason = null; return this.Redirect($"/user/{targetedUser.UserId}");
await this.database.SaveChangesAsync();
return this.Redirect($"/user/{targetedUser.UserId}");
}
} }
} }

View file

@ -1,10 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers.Website.Debug; namespace LBPUnion.ProjectLighthouse.Controllers.Website.Debug;

View file

@ -7,85 +7,84 @@ using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers.Website.ExternalAuth namespace LBPUnion.ProjectLighthouse.Controllers.Website.ExternalAuth;
[ApiController]
[Route("/authentication")]
public class AuthenticationController : ControllerBase
{ {
[ApiController] private readonly Database database;
[Route("/authentication")]
public class AuthenticationController : ControllerBase public AuthenticationController(Database database)
{ {
private readonly Database database; this.database = database;
}
public AuthenticationController(Database database) [HttpGet("approve/{id:int}")]
public async Task<IActionResult> Approve(int id)
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("/login");
AuthenticationAttempt? authAttempt = await this.database.AuthenticationAttempts.Include
(a => a.GameToken)
.FirstOrDefaultAsync(a => a.AuthenticationAttemptId == id);
if (authAttempt == null) return this.NotFound();
if (authAttempt.GameToken.UserId != user.UserId) return this.StatusCode(403, "");
authAttempt.GameToken.Approved = true;
this.database.AuthenticationAttempts.Remove(authAttempt);
await this.database.SaveChangesAsync();
return this.Redirect("~/authentication");
}
[HttpGet("deny/{id:int}")]
public async Task<IActionResult> Deny(int id)
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("/login");
AuthenticationAttempt? authAttempt = await this.database.AuthenticationAttempts.Include
(a => a.GameToken)
.FirstOrDefaultAsync(a => a.AuthenticationAttemptId == id);
if (authAttempt == null) return this.NotFound();
if (authAttempt.GameToken.UserId != user.UserId) return this.StatusCode(403, "");
this.database.GameTokens.Remove(authAttempt.GameToken);
this.database.AuthenticationAttempts.Remove(authAttempt);
DeniedAuthenticationHelper.SetDeniedAt($"{authAttempt.IPAddress}|{user.Username}");
await this.database.SaveChangesAsync();
return this.Redirect("~/authentication");
}
[HttpGet("denyAll")]
public async Task<IActionResult> DenyAll()
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("/login");
List<AuthenticationAttempt> authAttempts = await this.database.AuthenticationAttempts.Include
(a => a.GameToken)
.Where(a => a.GameToken.UserId == user.UserId)
.ToListAsync();
foreach (AuthenticationAttempt authAttempt in authAttempts)
{ {
this.database = database;
}
[HttpGet("approve/{id:int}")]
public async Task<IActionResult> Approve(int id)
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("/login");
AuthenticationAttempt? authAttempt = await this.database.AuthenticationAttempts.Include
(a => a.GameToken)
.FirstOrDefaultAsync(a => a.AuthenticationAttemptId == id);
if (authAttempt == null) return this.NotFound();
if (authAttempt.GameToken.UserId != user.UserId) return this.StatusCode(403, "");
authAttempt.GameToken.Approved = true;
this.database.AuthenticationAttempts.Remove(authAttempt);
await this.database.SaveChangesAsync();
return this.Redirect("~/authentication");
}
[HttpGet("deny/{id:int}")]
public async Task<IActionResult> Deny(int id)
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("/login");
AuthenticationAttempt? authAttempt = await this.database.AuthenticationAttempts.Include
(a => a.GameToken)
.FirstOrDefaultAsync(a => a.AuthenticationAttemptId == id);
if (authAttempt == null) return this.NotFound();
if (authAttempt.GameToken.UserId != user.UserId) return this.StatusCode(403, "");
this.database.GameTokens.Remove(authAttempt.GameToken); this.database.GameTokens.Remove(authAttempt.GameToken);
this.database.AuthenticationAttempts.Remove(authAttempt); this.database.AuthenticationAttempts.Remove(authAttempt);
DeniedAuthenticationHelper.SetDeniedAt($"{authAttempt.IPAddress}|{user.Username}"); DeniedAuthenticationHelper.SetDeniedAt($"{authAttempt.IPAddress}|{user.Username}");
await this.database.SaveChangesAsync();
return this.Redirect("~/authentication");
} }
[HttpGet("denyAll")] await this.database.SaveChangesAsync();
public async Task<IActionResult> DenyAll()
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("/login");
List<AuthenticationAttempt> authAttempts = await this.database.AuthenticationAttempts.Include return this.Redirect("~/authentication");
(a => a.GameToken)
.Where(a => a.GameToken.UserId == user.UserId)
.ToListAsync();
foreach (AuthenticationAttempt authAttempt in authAttempts)
{
this.database.GameTokens.Remove(authAttempt.GameToken);
this.database.AuthenticationAttempts.Remove(authAttempt);
DeniedAuthenticationHelper.SetDeniedAt($"{authAttempt.IPAddress}|{user.Username}");
}
await this.database.SaveChangesAsync();
return this.Redirect("~/authentication");
}
} }
} }

View file

@ -4,64 +4,63 @@ using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers.Website.ExternalAuth namespace LBPUnion.ProjectLighthouse.Controllers.Website.ExternalAuth;
[ApiController]
[Route("/authentication")]
public class AutoApprovalController : ControllerBase
{ {
[ApiController] private readonly Database database;
[Route("/authentication")]
public class AutoApprovalController : ControllerBase public AutoApprovalController(Database database)
{ {
private readonly Database database; this.database = database;
}
public AutoApprovalController(Database database) [HttpGet("autoApprove/{id:int}")]
public async Task<IActionResult> AutoApprove([FromRoute] int id)
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("/login");
AuthenticationAttempt? authAttempt = await this.database.AuthenticationAttempts.Include
(a => a.GameToken)
.FirstOrDefaultAsync(a => a.AuthenticationAttemptId == id);
if (authAttempt == null) return this.BadRequest();
if (authAttempt.GameToken.UserId != user.UserId) return this.Redirect("/login");
authAttempt.GameToken.Approved = true;
UserApprovedIpAddress approvedIpAddress = new()
{ {
this.database = database; UserId = user.UserId,
} User = user,
IpAddress = authAttempt.IPAddress,
};
[HttpGet("autoApprove/{id:int}")] this.database.UserApprovedIpAddresses.Add(approvedIpAddress);
public async Task<IActionResult> AutoApprove([FromRoute] int id) this.database.AuthenticationAttempts.Remove(authAttempt);
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("/login");
AuthenticationAttempt? authAttempt = await this.database.AuthenticationAttempts.Include await this.database.SaveChangesAsync();
(a => a.GameToken)
.FirstOrDefaultAsync(a => a.AuthenticationAttemptId == id);
if (authAttempt == null) return this.BadRequest(); return this.Redirect("/authentication");
if (authAttempt.GameToken.UserId != user.UserId) return this.Redirect("/login"); }
authAttempt.GameToken.Approved = true; [HttpGet("revokeAutoApproval/{id:int}")]
public async Task<IActionResult> RevokeAutoApproval([FromRoute] int id)
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("/login");
UserApprovedIpAddress approvedIpAddress = new() UserApprovedIpAddress? approvedIpAddress = await this.database.UserApprovedIpAddresses.FirstOrDefaultAsync(a => a.UserApprovedIpAddressId == id);
{ if (approvedIpAddress == null) return this.BadRequest();
UserId = user.UserId, if (approvedIpAddress.UserId != user.UserId) return this.Redirect("/login");
User = user,
IpAddress = authAttempt.IPAddress,
};
this.database.UserApprovedIpAddresses.Add(approvedIpAddress); this.database.UserApprovedIpAddresses.Remove(approvedIpAddress);
this.database.AuthenticationAttempts.Remove(authAttempt);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
return this.Redirect("/authentication"); return this.Redirect("/authentication/autoApprovals");
}
[HttpGet("revokeAutoApproval/{id:int}")]
public async Task<IActionResult> RevokeAutoApproval([FromRoute] int id)
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("/login");
UserApprovedIpAddress? approvedIpAddress = await this.database.UserApprovedIpAddresses.FirstOrDefaultAsync(a => a.UserApprovedIpAddressId == id);
if (approvedIpAddress == null) return this.BadRequest();
if (approvedIpAddress.UserId != user.UserId) return this.Redirect("/login");
this.database.UserApprovedIpAddresses.Remove(approvedIpAddress);
await this.database.SaveChangesAsync();
return this.Redirect("/authentication/autoApprovals");
}
} }
} }

View file

@ -10,81 +10,80 @@ using Microsoft.EntityFrameworkCore;
// TODO: Clean up this file // TODO: Clean up this file
// - jvyden // - jvyden
namespace LBPUnion.ProjectLighthouse.Controllers.Website namespace LBPUnion.ProjectLighthouse.Controllers.Website;
[ApiController]
[Route("slot/{id:int}")]
public class SlotPageController : ControllerBase
{ {
[ApiController] private readonly Database database;
[Route("slot/{id:int}")]
public class SlotPageController : ControllerBase public SlotPageController(Database database)
{ {
private readonly Database database; this.database = database;
}
public SlotPageController(Database database) [HttpGet("heart")]
{ public async Task<IActionResult> HeartLevel([FromRoute] int id, [FromQuery] string? callbackUrl)
this.database = database; {
} if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id;
[HttpGet("heart")] User? user = this.database.UserFromWebRequest(this.Request);
public async Task<IActionResult> HeartLevel([FromRoute] int id, [FromQuery] string? callbackUrl) if (user == null) return this.Redirect("~/login");
{
if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id;
User? user = this.database.UserFromWebRequest(this.Request); Slot? heartedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (user == null) return this.Redirect("~/login"); if (heartedSlot == null) return this.NotFound();
Slot? heartedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); await this.database.HeartLevel(user, heartedSlot);
if (heartedSlot == null) return this.NotFound();
await this.database.HeartLevel(user, heartedSlot); return this.Redirect(callbackUrl);
}
return this.Redirect(callbackUrl); [HttpGet("unheart")]
} public async Task<IActionResult> UnheartLevel([FromRoute] int id, [FromQuery] string? callbackUrl)
{
if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id;
[HttpGet("unheart")] User? user = this.database.UserFromWebRequest(this.Request);
public async Task<IActionResult> UnheartLevel([FromRoute] int id, [FromQuery] string? callbackUrl) if (user == null) return this.Redirect("~/login");
{
if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id;
User? user = this.database.UserFromWebRequest(this.Request); Slot? heartedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (user == null) return this.Redirect("~/login"); if (heartedSlot == null) return this.NotFound();
Slot? heartedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); await this.database.UnheartLevel(user, heartedSlot);
if (heartedSlot == null) return this.NotFound();
await this.database.UnheartLevel(user, heartedSlot); return this.Redirect(callbackUrl);
}
return this.Redirect(callbackUrl); [HttpGet("queue")]
} public async Task<IActionResult> QueueLevel([FromRoute] int id, [FromQuery] string? callbackUrl)
{
if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id;
[HttpGet("queue")] User? user = this.database.UserFromWebRequest(this.Request);
public async Task<IActionResult> QueueLevel([FromRoute] int id, [FromQuery] string? callbackUrl) if (user == null) return this.Redirect("~/login");
{
if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id;
User? user = this.database.UserFromWebRequest(this.Request); Slot? queuedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (user == null) return this.Redirect("~/login"); if (queuedSlot == null) return this.NotFound();
Slot? queuedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); await this.database.QueueLevel(user, queuedSlot);
if (queuedSlot == null) return this.NotFound();
await this.database.QueueLevel(user, queuedSlot); return this.Redirect(callbackUrl);
}
return this.Redirect(callbackUrl); [HttpGet("unqueue")]
} public async Task<IActionResult> UnqueueLevel([FromRoute] int id, [FromQuery] string? callbackUrl)
{
if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id;
[HttpGet("unqueue")] User? user = this.database.UserFromWebRequest(this.Request);
public async Task<IActionResult> UnqueueLevel([FromRoute] int id, [FromQuery] string? callbackUrl) if (user == null) return this.Redirect("~/login");
{
if (string.IsNullOrEmpty(callbackUrl)) callbackUrl = "~/slot/" + id;
User? user = this.database.UserFromWebRequest(this.Request); Slot? queuedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (user == null) return this.Redirect("~/login"); if (queuedSlot == null) return this.NotFound();
Slot? queuedSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); await this.database.UnqueueLevel(user, queuedSlot);
if (queuedSlot == null) return this.NotFound();
await this.database.UnqueueLevel(user, queuedSlot); return this.Redirect(callbackUrl);
return this.Redirect(callbackUrl);
}
} }
} }

View file

@ -4,45 +4,44 @@ using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers.Website namespace LBPUnion.ProjectLighthouse.Controllers.Website;
[ApiController]
[Route("user/{id:int}")]
public class UserPageController : ControllerBase
{ {
[ApiController] private readonly Database database;
[Route("user/{id:int}")]
public class UserPageController : ControllerBase public UserPageController(Database database)
{ {
private readonly Database database; this.database = database;
}
public UserPageController(Database database) [HttpGet("heart")]
{ public async Task<IActionResult> HeartUser([FromRoute] int id)
this.database = database; {
} User? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login");
[HttpGet("heart")] User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id);
public async Task<IActionResult> HeartUser([FromRoute] int id) if (heartedUser == null) return this.NotFound();
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login");
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); await this.database.HeartUser(user, heartedUser);
if (heartedUser == null) return this.NotFound();
await this.database.HeartUser(user, heartedUser); return this.Redirect("~/user/" + id);
}
return this.Redirect("~/user/" + id); [HttpGet("unheart")]
} public async Task<IActionResult> UnheartUser([FromRoute] int id)
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login");
[HttpGet("unheart")] User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id);
public async Task<IActionResult> UnheartUser([FromRoute] int id) if (heartedUser == null) return this.NotFound();
{
User? user = this.database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login");
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); await this.database.UnheartUser(user, heartedUser);
if (heartedUser == null) return this.NotFound();
await this.database.UnheartUser(user, heartedUser); return this.Redirect("~/user/" + id);
return this.Redirect("~/user/" + id);
}
} }
} }

View file

@ -13,287 +13,284 @@ using LBPUnion.ProjectLighthouse.Types.Settings;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse namespace LBPUnion.ProjectLighthouse;
public class Database : DbContext
{ {
public class Database : DbContext public DbSet<User> Users { get; set; }
public DbSet<Location> Locations { get; set; }
public DbSet<Slot> Slots { get; set; }
public DbSet<QueuedLevel> QueuedLevels { get; set; }
public DbSet<HeartedLevel> HeartedLevels { get; set; }
public DbSet<HeartedProfile> HeartedProfiles { get; set; }
public DbSet<Comment> Comments { get; set; }
public DbSet<GameToken> GameTokens { get; set; }
public DbSet<WebToken> WebTokens { get; set; }
public DbSet<Score> Scores { get; set; }
public DbSet<PhotoSubject> PhotoSubjects { get; set; }
public DbSet<Photo> Photos { get; set; }
public DbSet<LastContact> LastContacts { get; set; }
public DbSet<VisitedLevel> VisitedLevels { get; set; }
public DbSet<RatedLevel> RatedLevels { get; set; }
public DbSet<AuthenticationAttempt> AuthenticationAttempts { get; set; }
public DbSet<Review> Reviews { get; set; }
public DbSet<RatedReview> RatedReviews { get; set; }
public DbSet<UserApprovedIpAddress> UserApprovedIpAddresses { get; set; }
public DbSet<DatabaseCategory> CustomCategories { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseMySql(ServerSettings.Instance.DbConnectionString, MySqlServerVersion.LatestSupportedServerVersion);
public async Task<User> CreateUser(string username, string password)
{ {
public DbSet<User> Users { get; set; } if (!password.StartsWith("$")) throw new ArgumentException(nameof(password) + " is not a BCrypt hash");
public DbSet<Location> Locations { get; set; }
public DbSet<Slot> Slots { get; set; }
public DbSet<QueuedLevel> QueuedLevels { get; set; }
public DbSet<HeartedLevel> HeartedLevels { get; set; }
public DbSet<HeartedProfile> HeartedProfiles { get; set; }
public DbSet<Comment> Comments { get; set; }
public DbSet<GameToken> GameTokens { get; set; }
public DbSet<WebToken> WebTokens { get; set; }
public DbSet<Score> Scores { get; set; }
public DbSet<PhotoSubject> PhotoSubjects { get; set; }
public DbSet<Photo> Photos { get; set; }
public DbSet<LastContact> LastContacts { get; set; }
public DbSet<VisitedLevel> VisitedLevels { get; set; }
public DbSet<RatedLevel> RatedLevels { get; set; }
public DbSet<AuthenticationAttempt> AuthenticationAttempts { get; set; }
public DbSet<Review> Reviews { get; set; }
public DbSet<RatedReview> RatedReviews { get; set; }
public DbSet<UserApprovedIpAddress> UserApprovedIpAddresses { get; set; }
public DbSet<DatabaseCategory> CustomCategories { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options) User user;
=> options.UseMySql(ServerSettings.Instance.DbConnectionString, MySqlServerVersion.LatestSupportedServerVersion); if ((user = await this.Users.Where(u => u.Username == username).FirstOrDefaultAsync()) != null) return user;
public async Task<User> CreateUser(string username, string password) Location l = new(); // store to get id after submitting
this.Locations.Add(l); // add to table
await this.SaveChangesAsync(); // saving to the database returns the id and sets it on this entity
user = new User
{ {
if (!password.StartsWith("$")) throw new ArgumentException(nameof(password) + " is not a BCrypt hash"); Username = username,
Password = password,
LocationId = l.Id,
Biography = username + " hasn't introduced themselves yet.",
};
this.Users.Add(user);
User user; await this.SaveChangesAsync();
if ((user = await this.Users.Where(u => u.Username == username).FirstOrDefaultAsync()) != null) return user;
Location l = new(); // store to get id after submitting return user;
this.Locations.Add(l); // add to table
await this.SaveChangesAsync(); // saving to the database returns the id and sets it on this entity
user = new User
{
Username = username,
Password = password,
LocationId = l.Id,
Biography = username + " hasn't introduced themselves yet.",
};
this.Users.Add(user);
await this.SaveChangesAsync();
return user;
}
#nullable enable
public async Task<GameToken?> AuthenticateUser(LoginData loginData, string userLocation, string titleId = "")
{
User? user = await this.Users.FirstOrDefaultAsync(u => u.Username == loginData.Username);
if (user == null) return null;
GameToken gameToken = new()
{
UserToken = HashHelper.GenerateAuthToken(),
User = user,
UserId = user.UserId,
UserLocation = userLocation,
GameVersion = GameVersionHelper.FromTitleId(titleId),
};
if (gameToken.GameVersion == GameVersion.Unknown)
{
Logger.Log($"Unknown GameVersion for TitleId {titleId}", LoggerLevelLogin.Instance);
return null;
}
this.GameTokens.Add(gameToken);
await this.SaveChangesAsync();
return gameToken;
}
#region Hearts & Queues
public async Task HeartUser(User user, User heartedUser)
{
HeartedProfile? heartedProfile = await this.HeartedProfiles.FirstOrDefaultAsync
(q => q.UserId == user.UserId && q.HeartedUserId == heartedUser.UserId);
if (heartedProfile != null) return;
this.HeartedProfiles.Add
(
new HeartedProfile
{
HeartedUserId = heartedUser.UserId,
UserId = user.UserId,
}
);
await this.SaveChangesAsync();
}
public async Task UnheartUser(User user, User heartedUser)
{
HeartedProfile? heartedProfile = await this.HeartedProfiles.FirstOrDefaultAsync
(q => q.UserId == user.UserId && q.HeartedUserId == heartedUser.UserId);
if (heartedProfile != null) this.HeartedProfiles.Remove(heartedProfile);
await this.SaveChangesAsync();
}
public async Task HeartLevel(User user, Slot heartedSlot)
{
HeartedLevel? heartedLevel = await this.HeartedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == heartedSlot.SlotId);
if (heartedLevel != null) return;
this.HeartedLevels.Add
(
new HeartedLevel
{
SlotId = heartedSlot.SlotId,
UserId = user.UserId,
}
);
await this.SaveChangesAsync();
}
public async Task UnheartLevel(User user, Slot heartedSlot)
{
HeartedLevel? heartedLevel = await this.HeartedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == heartedSlot.SlotId);
if (heartedLevel != null) this.HeartedLevels.Remove(heartedLevel);
await this.SaveChangesAsync();
}
public async Task QueueLevel(User user, Slot queuedSlot)
{
QueuedLevel? queuedLevel = await this.QueuedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == queuedSlot.SlotId);
if (queuedLevel != null) return;
this.QueuedLevels.Add
(
new QueuedLevel
{
SlotId = queuedSlot.SlotId,
UserId = user.UserId,
}
);
await this.SaveChangesAsync();
}
public async Task UnqueueLevel(User user, Slot queuedSlot)
{
QueuedLevel? queuedLevel = await this.QueuedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == queuedSlot.SlotId);
if (queuedLevel != null) this.QueuedLevels.Remove(queuedLevel);
await this.SaveChangesAsync();
}
#endregion
#region Game Token Shenanigans
public async Task<User?> UserFromMMAuth(string authToken, bool allowUnapproved = false)
{
if (ServerStatics.IsUnitTesting) allowUnapproved = true;
GameToken? token = await this.GameTokens.FirstOrDefaultAsync(t => t.UserToken == authToken);
if (token == null) return null;
if (!allowUnapproved && !token.Approved) return null;
return await this.Users.Include(u => u.Location).FirstOrDefaultAsync(u => u.UserId == token.UserId);
}
public async Task<User?> UserFromGameToken
(GameToken gameToken, bool allowUnapproved = false)
=> await this.UserFromMMAuth(gameToken.UserToken, allowUnapproved);
public async Task<User?> UserFromGameRequest(HttpRequest request, bool allowUnapproved = false)
{
if (ServerStatics.IsUnitTesting) allowUnapproved = true;
if (!request.Cookies.TryGetValue("MM_AUTH", out string? mmAuth) || mmAuth == null) return null;
return await this.UserFromMMAuth(mmAuth, allowUnapproved);
}
public async Task<GameToken?> GameTokenFromRequest(HttpRequest request, bool allowUnapproved = false)
{
if (ServerStatics.IsUnitTesting) allowUnapproved = true;
if (!request.Cookies.TryGetValue("MM_AUTH", out string? mmAuth) || mmAuth == null) return null;
GameToken? token = await this.GameTokens.FirstOrDefaultAsync(t => t.UserToken == mmAuth);
if (token == null) return null;
if (!allowUnapproved && !token.Approved) return null;
return token;
}
public async Task<(User, GameToken)?> UserAndGameTokenFromRequest(HttpRequest request, bool allowUnapproved = false)
{
if (ServerStatics.IsUnitTesting) allowUnapproved = true;
if (!request.Cookies.TryGetValue("MM_AUTH", out string? mmAuth) || mmAuth == null) return null;
GameToken? token = await this.GameTokens.FirstOrDefaultAsync(t => t.UserToken == mmAuth);
if (token == null) return null;
if (!allowUnapproved && !token.Approved) return null;
User? user = await this.UserFromGameToken(token);
if (user == null) return null;
return (user, token);
}
#endregion
#region Web Token Shenanigans
public User? UserFromLighthouseToken(string lighthouseToken)
{
WebToken? token = this.WebTokens.FirstOrDefault(t => t.UserToken == lighthouseToken);
if (token == null) return null;
return this.Users.Include(u => u.Location).FirstOrDefault(u => u.UserId == token.UserId);
}
public User? UserFromWebRequest(HttpRequest request)
{
if (!request.Cookies.TryGetValue("LighthouseToken", out string? lighthouseToken) || lighthouseToken == null) return null;
return this.UserFromLighthouseToken(lighthouseToken);
}
public WebToken? WebTokenFromRequest(HttpRequest request)
{
if (!request.Cookies.TryGetValue("LighthouseToken", out string? lighthouseToken) || lighthouseToken == null) return null;
return this.WebTokens.FirstOrDefault(t => t.UserToken == lighthouseToken);
}
#endregion
public async Task<Photo?> PhotoFromSubject(PhotoSubject subject)
=> await this.Photos.FirstOrDefaultAsync(p => p.PhotoSubjectIds.Contains(subject.PhotoSubjectId.ToString()));
public async Task RemoveUser(User? user)
{
if (user == null) return;
if (user.Location != null) this.Locations.Remove(user.Location);
LastContact? lastContact = await this.LastContacts.FirstOrDefaultAsync(l => l.UserId == user.UserId);
if (lastContact != null) this.LastContacts.Remove(lastContact);
foreach (Slot slot in this.Slots.Where(s => s.CreatorId == user.UserId)) await this.RemoveSlot(slot, false);
this.AuthenticationAttempts.RemoveRange(this.AuthenticationAttempts.Include(a => a.GameToken).Where(a => a.GameToken.UserId == user.UserId));
this.HeartedProfiles.RemoveRange(this.HeartedProfiles.Where(h => h.UserId == user.UserId));
this.PhotoSubjects.RemoveRange(this.PhotoSubjects.Where(s => s.UserId == user.UserId));
this.HeartedLevels.RemoveRange(this.HeartedLevels.Where(h => h.UserId == user.UserId));
this.VisitedLevels.RemoveRange(this.VisitedLevels.Where(v => v.UserId == user.UserId));
this.RatedReviews.RemoveRange(this.RatedReviews.Where(r => r.UserId == user.UserId));
this.QueuedLevels.RemoveRange(this.QueuedLevels.Where(q => q.UserId == user.UserId));
this.RatedLevels.RemoveRange(this.RatedLevels.Where(r => r.UserId == user.UserId));
this.GameTokens.RemoveRange(this.GameTokens.Where(t => t.UserId == user.UserId));
this.WebTokens.RemoveRange(this.WebTokens.Where(t => t.UserId == user.UserId));
this.Comments.RemoveRange(this.Comments.Where(c => c.PosterUserId == user.UserId));
this.Reviews.RemoveRange(this.Reviews.Where(r => r.ReviewerId == user.UserId));
this.Photos.RemoveRange(this.Photos.Where(p => p.CreatorId == user.UserId));
this.Users.Remove(user);
await this.SaveChangesAsync();
}
public async Task RemoveSlot(Slot slot, bool saveChanges = true)
{
if (slot.Location != null) this.Locations.Remove(slot.Location);
this.Slots.Remove(slot);
if (saveChanges) await this.SaveChangesAsync();
}
#nullable disable
} }
#nullable enable
public async Task<GameToken?> AuthenticateUser(LoginData loginData, string userLocation, string titleId = "")
{
User? user = await this.Users.FirstOrDefaultAsync(u => u.Username == loginData.Username);
if (user == null) return null;
GameToken gameToken = new()
{
UserToken = HashHelper.GenerateAuthToken(),
User = user,
UserId = user.UserId,
UserLocation = userLocation,
GameVersion = GameVersionHelper.FromTitleId(titleId),
};
if (gameToken.GameVersion == GameVersion.Unknown)
{
Logger.Log($"Unknown GameVersion for TitleId {titleId}", LoggerLevelLogin.Instance);
return null;
}
this.GameTokens.Add(gameToken);
await this.SaveChangesAsync();
return gameToken;
}
#region Hearts & Queues
public async Task HeartUser(User user, User heartedUser)
{
HeartedProfile? heartedProfile = await this.HeartedProfiles.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.HeartedUserId == heartedUser.UserId);
if (heartedProfile != null) return;
this.HeartedProfiles.Add
(
new HeartedProfile
{
HeartedUserId = heartedUser.UserId,
UserId = user.UserId,
}
);
await this.SaveChangesAsync();
}
public async Task UnheartUser(User user, User heartedUser)
{
HeartedProfile? heartedProfile = await this.HeartedProfiles.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.HeartedUserId == heartedUser.UserId);
if (heartedProfile != null) this.HeartedProfiles.Remove(heartedProfile);
await this.SaveChangesAsync();
}
public async Task HeartLevel(User user, Slot heartedSlot)
{
HeartedLevel? heartedLevel = await this.HeartedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == heartedSlot.SlotId);
if (heartedLevel != null) return;
this.HeartedLevels.Add
(
new HeartedLevel
{
SlotId = heartedSlot.SlotId,
UserId = user.UserId,
}
);
await this.SaveChangesAsync();
}
public async Task UnheartLevel(User user, Slot heartedSlot)
{
HeartedLevel? heartedLevel = await this.HeartedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == heartedSlot.SlotId);
if (heartedLevel != null) this.HeartedLevels.Remove(heartedLevel);
await this.SaveChangesAsync();
}
public async Task QueueLevel(User user, Slot queuedSlot)
{
QueuedLevel? queuedLevel = await this.QueuedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == queuedSlot.SlotId);
if (queuedLevel != null) return;
this.QueuedLevels.Add
(
new QueuedLevel
{
SlotId = queuedSlot.SlotId,
UserId = user.UserId,
}
);
await this.SaveChangesAsync();
}
public async Task UnqueueLevel(User user, Slot queuedSlot)
{
QueuedLevel? queuedLevel = await this.QueuedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == queuedSlot.SlotId);
if (queuedLevel != null) this.QueuedLevels.Remove(queuedLevel);
await this.SaveChangesAsync();
}
#endregion
#region Game Token Shenanigans
public async Task<User?> UserFromMMAuth(string authToken, bool allowUnapproved = false)
{
if (ServerStatics.IsUnitTesting) allowUnapproved = true;
GameToken? token = await this.GameTokens.FirstOrDefaultAsync(t => t.UserToken == authToken);
if (token == null) return null;
if (!allowUnapproved && !token.Approved) return null;
return await this.Users.Include(u => u.Location).FirstOrDefaultAsync(u => u.UserId == token.UserId);
}
public async Task<User?> UserFromGameToken
(GameToken gameToken, bool allowUnapproved = false)
=> await this.UserFromMMAuth(gameToken.UserToken, allowUnapproved);
public async Task<User?> UserFromGameRequest(HttpRequest request, bool allowUnapproved = false)
{
if (ServerStatics.IsUnitTesting) allowUnapproved = true;
if (!request.Cookies.TryGetValue("MM_AUTH", out string? mmAuth) || mmAuth == null) return null;
return await this.UserFromMMAuth(mmAuth, allowUnapproved);
}
public async Task<GameToken?> GameTokenFromRequest(HttpRequest request, bool allowUnapproved = false)
{
if (ServerStatics.IsUnitTesting) allowUnapproved = true;
if (!request.Cookies.TryGetValue("MM_AUTH", out string? mmAuth) || mmAuth == null) return null;
GameToken? token = await this.GameTokens.FirstOrDefaultAsync(t => t.UserToken == mmAuth);
if (token == null) return null;
if (!allowUnapproved && !token.Approved) return null;
return token;
}
public async Task<(User, GameToken)?> UserAndGameTokenFromRequest(HttpRequest request, bool allowUnapproved = false)
{
if (ServerStatics.IsUnitTesting) allowUnapproved = true;
if (!request.Cookies.TryGetValue("MM_AUTH", out string? mmAuth) || mmAuth == null) return null;
GameToken? token = await this.GameTokens.FirstOrDefaultAsync(t => t.UserToken == mmAuth);
if (token == null) return null;
if (!allowUnapproved && !token.Approved) return null;
User? user = await this.UserFromGameToken(token);
if (user == null) return null;
return (user, token);
}
#endregion
#region Web Token Shenanigans
public User? UserFromLighthouseToken(string lighthouseToken)
{
WebToken? token = this.WebTokens.FirstOrDefault(t => t.UserToken == lighthouseToken);
if (token == null) return null;
return this.Users.Include(u => u.Location).FirstOrDefault(u => u.UserId == token.UserId);
}
public User? UserFromWebRequest(HttpRequest request)
{
if (!request.Cookies.TryGetValue("LighthouseToken", out string? lighthouseToken) || lighthouseToken == null) return null;
return this.UserFromLighthouseToken(lighthouseToken);
}
public WebToken? WebTokenFromRequest(HttpRequest request)
{
if (!request.Cookies.TryGetValue("LighthouseToken", out string? lighthouseToken) || lighthouseToken == null) return null;
return this.WebTokens.FirstOrDefault(t => t.UserToken == lighthouseToken);
}
#endregion
public async Task<Photo?> PhotoFromSubject(PhotoSubject subject)
=> await this.Photos.FirstOrDefaultAsync(p => p.PhotoSubjectIds.Contains(subject.PhotoSubjectId.ToString()));
public async Task RemoveUser(User? user)
{
if (user == null) return;
if (user.Location != null) this.Locations.Remove(user.Location);
LastContact? lastContact = await this.LastContacts.FirstOrDefaultAsync(l => l.UserId == user.UserId);
if (lastContact != null) this.LastContacts.Remove(lastContact);
foreach (Slot slot in this.Slots.Where(s => s.CreatorId == user.UserId)) await this.RemoveSlot(slot, false);
this.AuthenticationAttempts.RemoveRange(this.AuthenticationAttempts.Include(a => a.GameToken).Where(a => a.GameToken.UserId == user.UserId));
this.HeartedProfiles.RemoveRange(this.HeartedProfiles.Where(h => h.UserId == user.UserId));
this.PhotoSubjects.RemoveRange(this.PhotoSubjects.Where(s => s.UserId == user.UserId));
this.HeartedLevels.RemoveRange(this.HeartedLevels.Where(h => h.UserId == user.UserId));
this.VisitedLevels.RemoveRange(this.VisitedLevels.Where(v => v.UserId == user.UserId));
this.RatedReviews.RemoveRange(this.RatedReviews.Where(r => r.UserId == user.UserId));
this.QueuedLevels.RemoveRange(this.QueuedLevels.Where(q => q.UserId == user.UserId));
this.RatedLevels.RemoveRange(this.RatedLevels.Where(r => r.UserId == user.UserId));
this.GameTokens.RemoveRange(this.GameTokens.Where(t => t.UserId == user.UserId));
this.WebTokens.RemoveRange(this.WebTokens.Where(t => t.UserId == user.UserId));
this.Comments.RemoveRange(this.Comments.Where(c => c.PosterUserId == user.UserId));
this.Reviews.RemoveRange(this.Reviews.Where(r => r.ReviewerId == user.UserId));
this.Photos.RemoveRange(this.Photos.Where(p => p.CreatorId == user.UserId));
this.Users.Remove(user);
await this.SaveChangesAsync();
}
public async Task RemoveSlot(Slot slot, bool saveChanges = true)
{
if (slot.Location != null) this.Locations.Remove(slot.Location);
this.Slots.Remove(slot);
if (saveChanges) await this.SaveChangesAsync();
}
#nullable disable
} }

View file

@ -2,23 +2,22 @@ using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
namespace LBPUnion.ProjectLighthouse namespace LBPUnion.ProjectLighthouse;
public class FakeRemoteIPAddressMiddleware
{ {
public class FakeRemoteIPAddressMiddleware private readonly IPAddress fakeIpAddress = IPAddress.Parse("127.0.0.1");
private readonly RequestDelegate next;
public FakeRemoteIPAddressMiddleware(RequestDelegate next)
{ {
private readonly IPAddress fakeIpAddress = IPAddress.Parse("127.0.0.1"); this.next = next;
private readonly RequestDelegate next; }
public FakeRemoteIPAddressMiddleware(RequestDelegate next) public async Task Invoke(HttpContext httpContext)
{ {
this.next = next; httpContext.Connection.RemoteIpAddress = this.fakeIpAddress;
}
public async Task Invoke(HttpContext httpContext) await this.next(httpContext);
{
httpContext.Connection.RemoteIpAddress = this.fakeIpAddress;
await this.next(httpContext);
}
} }
} }

View file

@ -6,60 +6,59 @@ using System.IO.Pipelines;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace LBPUnion.ProjectLighthouse.Helpers namespace LBPUnion.ProjectLighthouse.Helpers;
public static class BinaryHelper
{ {
public static class BinaryHelper public static string ReadString(BinaryReader reader)
{ {
public static string ReadString(BinaryReader reader) List<byte> readBytes = new();
byte readByte;
do readBytes.Add(readByte = reader.ReadByte());
while (readByte != 0x00);
return Encoding.UTF8.GetString(readBytes.ToArray());
}
public static void ReadUntilByte(BinaryReader reader, byte byteToReadTo)
{
byte readByte;
do readByte = reader.ReadByte();
while (readByte != byteToReadTo);
}
public static byte[] ReadLastBytes(BinaryReader reader, int count, bool restoreOldPosition = true)
{
long oldPosition = reader.BaseStream.Position;
if (reader.BaseStream.Length < count) return Array.Empty<byte>();
reader.BaseStream.Position = reader.BaseStream.Length - count;
byte[] data = reader.ReadBytes(count);
if (restoreOldPosition) reader.BaseStream.Position = oldPosition;
return data;
}
// Written with reference from
// https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/request-response?view=aspnetcore-5.0
// Surprisingly doesn't take seconds. (67ms for a 100kb file)
public static async Task<byte[]> ReadFromPipeReader(PipeReader reader)
{
List<byte> data = new();
while (true)
{ {
List<byte> readBytes = new(); ReadResult readResult = await reader.ReadAsync();
ReadOnlySequence<byte> buffer = readResult.Buffer;
byte readByte; if (readResult.IsCompleted && buffer.Length > 0) data.AddRange(buffer.ToArray());
do readBytes.Add(readByte = reader.ReadByte());
while (readByte != 0x00);
return Encoding.UTF8.GetString(readBytes.ToArray()); reader.AdvanceTo(buffer.Start, buffer.End);
if (readResult.IsCompleted) break;
} }
public static void ReadUntilByte(BinaryReader reader, byte byteToReadTo) return data.ToArray();
{
byte readByte;
do readByte = reader.ReadByte();
while (readByte != byteToReadTo);
}
public static byte[] ReadLastBytes(BinaryReader reader, int count, bool restoreOldPosition = true)
{
long oldPosition = reader.BaseStream.Position;
if (reader.BaseStream.Length < count) return Array.Empty<byte>();
reader.BaseStream.Position = reader.BaseStream.Length - count;
byte[] data = reader.ReadBytes(count);
if (restoreOldPosition) reader.BaseStream.Position = oldPosition;
return data;
}
// Written with reference from
// https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/request-response?view=aspnetcore-5.0
// Surprisingly doesn't take seconds. (67ms for a 100kb file)
public static async Task<byte[]> ReadFromPipeReader(PipeReader reader)
{
List<byte> data = new();
while (true)
{
ReadResult readResult = await reader.ReadAsync();
ReadOnlySequence<byte> buffer = readResult.Buffer;
if (readResult.IsCompleted && buffer.Length > 0) data.AddRange(buffer.ToArray());
reader.AdvanceTo(buffer.Start, buffer.End);
if (readResult.IsCompleted) break;
}
return data.ToArray();
}
} }
} }

View file

@ -1,21 +1,20 @@
using System.Collections.Generic; using System.Collections.Generic;
using LBPUnion.ProjectLighthouse.Types.Categories; using LBPUnion.ProjectLighthouse.Types.Categories;
namespace LBPUnion.ProjectLighthouse.Helpers namespace LBPUnion.ProjectLighthouse.Helpers;
public static class CollectionHelper
{ {
public static class CollectionHelper public static readonly List<Category> Categories = new();
static CollectionHelper()
{ {
public static readonly List<Category> Categories = new(); Categories.Add(new TeamPicksCategory());
Categories.Add(new NewestLevelsCategory());
Categories.Add(new QueueCategory());
Categories.Add(new HeartedCategory());
static CollectionHelper() using Database database = new();
{ foreach (DatabaseCategory category in database.CustomCategories) Categories.Add(new CustomCategory(category));
Categories.Add(new TeamPicksCategory());
Categories.Add(new NewestLevelsCategory());
Categories.Add(new QueueCategory());
Categories.Add(new HeartedCategory());
using Database database = new();
foreach (DatabaseCategory category in database.CustomCategories) Categories.Add(new CustomCategory(category));
}
} }
} }

View file

@ -1,38 +1,37 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace LBPUnion.ProjectLighthouse.Helpers namespace LBPUnion.ProjectLighthouse.Helpers;
public static class DeniedAuthenticationHelper
{ {
public static class DeniedAuthenticationHelper public static readonly Dictionary<string, long> IPAddressAndNameDeniedAt = new();
public static readonly Dictionary<string, int> AttemptsByIPAddressAndName = new();
public static void SetDeniedAt(string ipAddressAndName, long timestamp = 0)
{ {
public static readonly Dictionary<string, long> IPAddressAndNameDeniedAt = new(); if (timestamp == 0) timestamp = TimestampHelper.Timestamp;
public static readonly Dictionary<string, int> AttemptsByIPAddressAndName = new();
public static void SetDeniedAt(string ipAddressAndName, long timestamp = 0) if (IPAddressAndNameDeniedAt.TryGetValue(ipAddressAndName, out long _)) IPAddressAndNameDeniedAt.Remove(ipAddressAndName);
{ IPAddressAndNameDeniedAt.Add(ipAddressAndName, timestamp);
if (timestamp == 0) timestamp = TimestampHelper.Timestamp; }
if (IPAddressAndNameDeniedAt.TryGetValue(ipAddressAndName, out long _)) IPAddressAndNameDeniedAt.Remove(ipAddressAndName); public static bool RecentlyDenied(string ipAddressAndName)
IPAddressAndNameDeniedAt.Add(ipAddressAndName, timestamp); {
} if (!IPAddressAndNameDeniedAt.TryGetValue(ipAddressAndName, out long timestamp)) return false;
public static bool RecentlyDenied(string ipAddressAndName) return TimestampHelper.Timestamp < timestamp + 300;
{ }
if (!IPAddressAndNameDeniedAt.TryGetValue(ipAddressAndName, out long timestamp)) return false;
return TimestampHelper.Timestamp < timestamp + 300; public static void AddAttempt(string ipAddressAndName)
} {
if (AttemptsByIPAddressAndName.TryGetValue(ipAddressAndName, out int attempts)) AttemptsByIPAddressAndName.Remove(ipAddressAndName);
AttemptsByIPAddressAndName.Add(ipAddressAndName, attempts + 1);
}
public static void AddAttempt(string ipAddressAndName) public static int GetAttempts(string ipAddressAndName)
{ {
if (AttemptsByIPAddressAndName.TryGetValue(ipAddressAndName, out int attempts)) AttemptsByIPAddressAndName.Remove(ipAddressAndName); if (!AttemptsByIPAddressAndName.TryGetValue(ipAddressAndName, out int attempts)) return 0;
AttemptsByIPAddressAndName.Add(ipAddressAndName, attempts + 1);
}
public static int GetAttempts(string ipAddressAndName) return attempts;
{
if (!AttemptsByIPAddressAndName.TryGetValue(ipAddressAndName, out int attempts)) return 0;
return attempts;
}
} }
} }

View file

@ -1,8 +1,8 @@
namespace LBPUnion.ProjectLighthouse.Helpers namespace LBPUnion.ProjectLighthouse.Helpers;
public static class EulaHelper
{ {
public static class EulaHelper public const string License = @"
{
public const string License = @"
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the published by the Free Software Foundation, either version 3 of the
@ -15,5 +15,4 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>."; along with this program. If not, see <https://www.gnu.org/licenses/>.";
}
} }

View file

@ -3,26 +3,25 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
namespace LBPUnion.ProjectLighthouse.Helpers.Extensions namespace LBPUnion.ProjectLighthouse.Helpers.Extensions;
// https://stackoverflow.com/a/8039737
public static class ExceptionExtensions
{ {
// https://stackoverflow.com/a/8039737 public static string ToDetailedException(this Exception exception)
public static class ExceptionExtensions
{ {
public static string ToDetailedException(this Exception exception) PropertyInfo[] properties = exception.GetType().GetProperties();
{
PropertyInfo[] properties = exception.GetType().GetProperties();
IEnumerable<string> fields = properties.Select IEnumerable<string> fields = properties.Select
( (
property => new property => new
{ {
property.Name, property.Name,
Value = property.GetValue(exception, null), Value = property.GetValue(exception, null),
} }
) )
.Select(x => $"{x.Name} = {(x.Value != null ? x.Value.ToString() : string.Empty)}"); .Select(x => $"{x.Name} = {(x.Value != null ? x.Value.ToString() : string.Empty)}");
return string.Join("\n", fields); return string.Join("\n", fields);
}
} }
} }

View file

@ -2,14 +2,13 @@ using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
namespace LBPUnion.ProjectLighthouse.Helpers.Extensions namespace LBPUnion.ProjectLighthouse.Helpers.Extensions;
{
// yoinked and adapted from https://stackoverflow.com/a/68641796
public static class RequestExtensions
{
private static readonly Regex mobileCheck = new
("Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled);
public static bool IsMobile(this HttpRequest request) => mobileCheck.IsMatch(request.Headers[HeaderNames.UserAgent].ToString()); // yoinked and adapted from https://stackoverflow.com/a/68641796
} public static class RequestExtensions
{
private static readonly Regex mobileCheck = new
("Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled);
public static bool IsMobile(this HttpRequest request) => mobileCheck.IsMatch(request.Headers[HeaderNames.UserAgent].ToString());
} }

View file

@ -1,10 +1,9 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
namespace LBPUnion.ProjectLighthouse.Helpers.Extensions namespace LBPUnion.ProjectLighthouse.Helpers.Extensions;
public static class StringExtensions
{ {
public static class StringExtensions public static string ToFileName(this string text) => Path.GetInvalidFileNameChars().Aggregate(text, (current, c) => current.Replace(c.ToString(), ""));
{
public static string ToFileName(this string text) => Path.GetInvalidFileNameChars().Aggregate(text, (current, c) => current.Replace(c.ToString(), ""));
}
} }

View file

@ -5,99 +5,98 @@ using System.Text;
using LBPUnion.ProjectLighthouse.Types.Files; using LBPUnion.ProjectLighthouse.Types.Files;
using LBPUnion.ProjectLighthouse.Types.Settings; using LBPUnion.ProjectLighthouse.Types.Settings;
namespace LBPUnion.ProjectLighthouse.Helpers namespace LBPUnion.ProjectLighthouse.Helpers;
public static class FileHelper
{ {
public static class FileHelper public static readonly string ResourcePath = Path.Combine(Environment.CurrentDirectory, "r");
public static string GetResourcePath(string hash) => Path.Combine(ResourcePath, hash);
public static bool IsFileSafe(LbpFile file)
{ {
public static readonly string ResourcePath = Path.Combine(Environment.CurrentDirectory, "r"); if (!ServerSettings.Instance.CheckForUnsafeFiles) return true;
public static string GetResourcePath(string hash) => Path.Combine(ResourcePath, hash); if (file.FileType == LbpFileType.Unknown) file.FileType = DetermineFileType(file.Data);
public static bool IsFileSafe(LbpFile file) return file.FileType switch
{ {
if (!ServerSettings.Instance.CheckForUnsafeFiles) return true; LbpFileType.FileArchive => false,
LbpFileType.Painting => true,
if (file.FileType == LbpFileType.Unknown) file.FileType = DetermineFileType(file.Data); LbpFileType.Unknown => false,
LbpFileType.Texture => true,
return file.FileType switch LbpFileType.Script => false,
{ LbpFileType.Level => true,
LbpFileType.FileArchive => false, LbpFileType.Voice => true,
LbpFileType.Painting => true, LbpFileType.Plan => true,
LbpFileType.Unknown => false, LbpFileType.Jpeg => true,
LbpFileType.Texture => true, LbpFileType.Png => true,
LbpFileType.Script => false, #if DEBUG
LbpFileType.Level => true,
LbpFileType.Voice => true,
LbpFileType.Plan => true,
LbpFileType.Jpeg => true,
LbpFileType.Png => true,
#if DEBUG
_ => throw new ArgumentOutOfRangeException(nameof(file), $"Unhandled file type ({file.FileType}) in FileHelper.IsFileSafe()"), _ => throw new ArgumentOutOfRangeException(nameof(file), $"Unhandled file type ({file.FileType}) in FileHelper.IsFileSafe()"),
#else #else
_ => false, _ => false,
#endif #endif
}; };
}
public static LbpFileType DetermineFileType(byte[] data)
{
if (data.Length == 0) return LbpFileType.Unknown; // Can't be anything if theres no data.
using MemoryStream ms = new(data);
using BinaryReader reader = new(ms);
// Determine if file is a FARC (File Archive).
// Needs to be done before anything else that determines the type by the header
// because this determines the type by the footer.
string footer = Encoding.ASCII.GetString(BinaryHelper.ReadLastBytes(reader, 4));
if (footer == "FARC") return LbpFileType.FileArchive;
byte[] header = reader.ReadBytes(3);
return Encoding.ASCII.GetString(header) switch
{
"PTG" => LbpFileType.Painting,
"TEX" => LbpFileType.Texture,
"FSH" => LbpFileType.Script,
"VOP" => LbpFileType.Voice,
"LVL" => LbpFileType.Level,
"PLN" => LbpFileType.Plan,
_ => determineFileTypePartTwoWeirdName(reader),
};
}
private static LbpFileType determineFileTypePartTwoWeirdName(BinaryReader reader)
{
reader.BaseStream.Position = 0;
// Determine if file is JPEG/PNG
byte[] header = reader.ReadBytes(9);
if (header[0] == 0xFF && header[1] == 0xD8 && header[2] == 0xFF && header[3] == 0xE0) return LbpFileType.Jpeg;
if (header[0] == 0x89 && header[1] == 0x50 && header[2] == 0x4E && header[3] == 0x47) return LbpFileType.Png;
return LbpFileType.Unknown; // Still unknown.
}
public static bool ResourceExists(string hash) => File.Exists(GetResourcePath(hash));
public static int ResourceSize(string hash)
{
try
{
return (int)new FileInfo(GetResourcePath(hash)).Length;
}
catch
{
return 0;
}
}
public static void EnsureDirectoryCreated(string path)
{
if (!Directory.Exists(path)) Directory.CreateDirectory(path ?? throw new ArgumentNullException(nameof(path)));
}
public static string[] ResourcesNotUploaded(params string[] hashes) => hashes.Where(hash => !ResourceExists(hash)).ToArray();
} }
public static LbpFileType DetermineFileType(byte[] data)
{
if (data.Length == 0) return LbpFileType.Unknown; // Can't be anything if theres no data.
using MemoryStream ms = new(data);
using BinaryReader reader = new(ms);
// Determine if file is a FARC (File Archive).
// Needs to be done before anything else that determines the type by the header
// because this determines the type by the footer.
string footer = Encoding.ASCII.GetString(BinaryHelper.ReadLastBytes(reader, 4));
if (footer == "FARC") return LbpFileType.FileArchive;
byte[] header = reader.ReadBytes(3);
return Encoding.ASCII.GetString(header) switch
{
"PTG" => LbpFileType.Painting,
"TEX" => LbpFileType.Texture,
"FSH" => LbpFileType.Script,
"VOP" => LbpFileType.Voice,
"LVL" => LbpFileType.Level,
"PLN" => LbpFileType.Plan,
_ => determineFileTypePartTwoWeirdName(reader),
};
}
private static LbpFileType determineFileTypePartTwoWeirdName(BinaryReader reader)
{
reader.BaseStream.Position = 0;
// Determine if file is JPEG/PNG
byte[] header = reader.ReadBytes(9);
if (header[0] == 0xFF && header[1] == 0xD8 && header[2] == 0xFF && header[3] == 0xE0) return LbpFileType.Jpeg;
if (header[0] == 0x89 && header[1] == 0x50 && header[2] == 0x4E && header[3] == 0x47) return LbpFileType.Png;
return LbpFileType.Unknown; // Still unknown.
}
public static bool ResourceExists(string hash) => File.Exists(GetResourcePath(hash));
public static int ResourceSize(string hash)
{
try
{
return (int)new FileInfo(GetResourcePath(hash)).Length;
}
catch
{
return 0;
}
}
public static void EnsureDirectoryCreated(string path)
{
if (!Directory.Exists(path)) Directory.CreateDirectory(path ?? throw new ArgumentNullException(nameof(path)));
}
public static string[] ResourcesNotUploaded(params string[] hashes) => hashes.Where(hash => !ResourceExists(hash)).ToArray();
} }

View file

@ -2,13 +2,12 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
namespace LBPUnion.ProjectLighthouse.Helpers namespace LBPUnion.ProjectLighthouse.Helpers;
[NotMapped]
[SuppressMessage("ReSharper", "CollectionNeverQueried.Global")]
public static class FriendHelper
{ {
[NotMapped] public static readonly Dictionary<int, int[]> FriendIdsByUserId = new();
[SuppressMessage("ReSharper", "CollectionNeverQueried.Global")] public static readonly Dictionary<int, int[]> BlockedIdsByUserId = new();
public static class FriendHelper
{
public static readonly Dictionary<int, int[]> FriendIdsByUserId = new();
public static readonly Dictionary<int, int[]> BlockedIdsByUserId = new();
}
} }

View file

@ -1,102 +1,101 @@
using System.Linq; using System.Linq;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
namespace LBPUnion.ProjectLighthouse.Helpers namespace LBPUnion.ProjectLighthouse.Helpers;
public class GameVersionHelper
{ {
public class GameVersionHelper // https://www.serialstation.com/games/b89b4eb4-4e4c-4e54-b72b-f7f9dbfac125
public static readonly string[] LittleBigPlanet1TitleIds =
{ {
// https://www.serialstation.com/games/b89b4eb4-4e4c-4e54-b72b-f7f9dbfac125 "BCES00141",
public static readonly string[] LittleBigPlanet1TitleIds = "BCAS20091",
{ "BCUS98208",
"BCES00141", "BCAS20078",
"BCAS20091", "BCJS70009",
"BCUS98208", "BCES00611",
"BCAS20078", "BCUS98148",
"BCJS70009", "BCAS20058",
"BCES00611", "BCJS30018",
"BCUS98148", "UCAS40262",
"BCAS20058", "BCET70011",
"BCJS30018", "BCUS98199",
"UCAS40262", "BCJB95003",
"BCET70011", "NPUA70045",
"BCUS98199", "NPEA00241",
"BCJB95003", "NPEA00147",
"NPUA70045", "NPHG00033",
"NPEA00241", "NPHG00035",
"NPEA00147", };
"NPHG00033",
"NPHG00035",
};
// https://serialstation.com/games/35e69aba-1872-4fd7-9d39-11ce75924040 // https://serialstation.com/games/35e69aba-1872-4fd7-9d39-11ce75924040
public static readonly string[] LittleBigPlanet2TitleIds = public static readonly string[] LittleBigPlanet2TitleIds =
{ {
"BCUS98249", "BCUS98249",
"BCES01086", "BCES01086",
"BCAS20113", "BCAS20113",
"BCJS70024", "BCJS70024",
"BCAS20201", "BCAS20201",
"BCUS98245", "BCUS98245",
"BCES01345", "BCES01345",
"BCJS30058", "BCJS30058",
"BCUS98372", "BCUS98372",
"BCES00850", "BCES00850",
"BCES01346", "BCES01346",
"BCUS90260", "BCUS90260",
"BCET70023", "BCET70023",
"BCES01694", "BCES01694",
"NPUA80662", "NPUA80662",
}; };
// https://www.serialstation.com/games/b62d53d9-fdff-4463-8134-64b81e1cbd50 // https://www.serialstation.com/games/b62d53d9-fdff-4463-8134-64b81e1cbd50
// includes PS4 games // includes PS4 games
public static readonly string[] LittleBigPlanet3TitleIds = public static readonly string[] LittleBigPlanet3TitleIds =
{ {
"CUSA00063", "CUSA00063",
"CUSA00693", "CUSA00693",
"CUSA00473", "CUSA00473",
"CUSA00810", "CUSA00810",
"CUSA00473", "CUSA00473",
"CUSA01072", "CUSA01072",
"CUSA00738", "CUSA00738",
"PCJS50003", "PCJS50003",
"BCES02068", "BCES02068",
"BCAS20322", "BCAS20322",
"BCJS30095", "BCJS30095",
"BCES01663", "BCES01663",
"CUSA00063", "CUSA00063",
"BCUS98362", "BCUS98362",
"PCKS90007", "PCKS90007",
"PCAS00012", "PCAS00012",
"CUSA00601", "CUSA00601",
"CUSA00810", "CUSA00810",
"CUSA00762", "CUSA00762",
"PCAS20007", "PCAS20007",
"CUSA00473", "CUSA00473",
"CUSA01077", "CUSA01077",
"CUSA01304", "CUSA01304",
"NPUA81116", "NPUA81116",
}; };
public static readonly string[] LittleBigPlanetVitaTitleIds = public static readonly string[] LittleBigPlanetVitaTitleIds =
{ {
"PCSF00021", "PCSA00017", "PCSC00013", "PCSD00006", "PCSA00549", "PCSF00516", "PCSF00021", "PCSA00017", "PCSC00013", "PCSD00006", "PCSA00549", "PCSF00516",
}; };
public static readonly string[] LittleBigPlanetPSPTitleIds = public static readonly string[] LittleBigPlanetPSPTitleIds =
{ {
"NPWR00500", "UCAS40262", "UCES01264", "UCUS98744", "UCJS10107", "NPWR00500", "UCAS40262", "UCES01264", "UCUS98744", "UCJS10107",
}; };
public static GameVersion FromTitleId(string titleId) public static GameVersion FromTitleId(string titleId)
{ {
if (LittleBigPlanet1TitleIds.Contains(titleId)) return GameVersion.LittleBigPlanet1; if (LittleBigPlanet1TitleIds.Contains(titleId)) return GameVersion.LittleBigPlanet1;
if (LittleBigPlanet2TitleIds.Contains(titleId)) return GameVersion.LittleBigPlanet2; if (LittleBigPlanet2TitleIds.Contains(titleId)) return GameVersion.LittleBigPlanet2;
if (LittleBigPlanet3TitleIds.Contains(titleId)) return GameVersion.LittleBigPlanet3; if (LittleBigPlanet3TitleIds.Contains(titleId)) return GameVersion.LittleBigPlanet3;
if (LittleBigPlanetVitaTitleIds.Contains(titleId)) return GameVersion.LittleBigPlanetVita; if (LittleBigPlanetVitaTitleIds.Contains(titleId)) return GameVersion.LittleBigPlanetVita;
if (LittleBigPlanetPSPTitleIds.Contains(titleId)) return GameVersion.LittleBigPlanetPSP; if (LittleBigPlanetPSPTitleIds.Contains(titleId)) return GameVersion.LittleBigPlanetPSP;
return GameVersion.LittleBigPlanet1; return GameVersion.LittleBigPlanet1;
}
} }
} }

View file

@ -6,78 +6,77 @@ using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace LBPUnion.ProjectLighthouse.Helpers namespace LBPUnion.ProjectLighthouse.Helpers;
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public static class HashHelper
{ {
[SuppressMessage("ReSharper", "UnusedMember.Global")] private static readonly SHA1 sha1 = SHA1.Create();
public static class HashHelper private static readonly SHA256 sha256 = SHA256.Create();
private static readonly Random random = new();
/// <summary>
/// Generates a specified amount of random bytes in an array.
/// </summary>
/// <param name="count">The amount of bytes to generate.</param>
/// <returns>The bytes generated</returns>
public static IEnumerable<byte> GenerateRandomBytes(int count)
{ {
private static readonly SHA1 sha1 = SHA1.Create(); byte[] b = new byte[count];
private static readonly SHA256 sha256 = SHA256.Create(); random.NextBytes(b);
private static readonly Random random = new();
/// <summary>
/// Generates a specified amount of random bytes in an array.
/// </summary>
/// <param name="count">The amount of bytes to generate.</param>
/// <returns>The bytes generated</returns>
public static IEnumerable<byte> GenerateRandomBytes(int count)
{
byte[] b = new byte[count];
random.NextBytes(b);
return b;
}
/// <summary>
/// Generates a random SHA256 & BCrypted token
/// </summary>
/// <returns>The token as a string.</returns>
public static string GenerateAuthToken()
{
byte[] bytes = (byte[])GenerateRandomBytes(256);
return BCryptHash(Sha256Hash(bytes));
}
public static async Task<string> ComputeDigest(string path, string authCookie, Stream body, string digestKey)
{
MemoryStream memoryStream = new();
byte[] pathBytes = Encoding.UTF8.GetBytes(path);
byte[] cookieBytes = string.IsNullOrEmpty(authCookie) ? Array.Empty<byte>() : Encoding.UTF8.GetBytes(authCookie);
byte[] keyBytes = Encoding.UTF8.GetBytes(digestKey);
await body.CopyToAsync(memoryStream);
byte[] bodyBytes = memoryStream.ToArray();
using IncrementalHash sha1 = IncrementalHash.CreateHash(HashAlgorithmName.SHA1);
sha1.AppendData(bodyBytes);
if (cookieBytes.Length > 0) sha1.AppendData(cookieBytes);
sha1.AppendData(pathBytes);
sha1.AppendData(keyBytes);
byte[] digestBytes = sha1.GetHashAndReset();
string digestString = Convert.ToHexString(digestBytes).ToLower();
return digestString;
}
#region Hash Functions
public static string Sha256Hash(string str) => Sha256Hash(Encoding.UTF8.GetBytes(str));
public static string Sha256Hash(byte[] bytes) => BitConverter.ToString(sha256.ComputeHash(bytes)).Replace("-", "").ToLower();
public static string Sha1Hash(string str) => Sha1Hash(Encoding.UTF8.GetBytes(str));
public static string Sha1Hash(byte[] bytes) => BitConverter.ToString(sha1.ComputeHash(bytes)).Replace("-", "");
public static string BCryptHash(string str) => BCrypt.Net.BCrypt.HashPassword(str);
public static string BCryptHash(byte[] bytes) => BCrypt.Net.BCrypt.HashPassword(Encoding.UTF8.GetString(bytes));
#endregion
return b;
} }
/// <summary>
/// Generates a random SHA256 & BCrypted token
/// </summary>
/// <returns>The token as a string.</returns>
public static string GenerateAuthToken()
{
byte[] bytes = (byte[])GenerateRandomBytes(256);
return BCryptHash(Sha256Hash(bytes));
}
public static async Task<string> ComputeDigest(string path, string authCookie, Stream body, string digestKey)
{
MemoryStream memoryStream = new();
byte[] pathBytes = Encoding.UTF8.GetBytes(path);
byte[] cookieBytes = string.IsNullOrEmpty(authCookie) ? Array.Empty<byte>() : Encoding.UTF8.GetBytes(authCookie);
byte[] keyBytes = Encoding.UTF8.GetBytes(digestKey);
await body.CopyToAsync(memoryStream);
byte[] bodyBytes = memoryStream.ToArray();
using IncrementalHash sha1 = IncrementalHash.CreateHash(HashAlgorithmName.SHA1);
sha1.AppendData(bodyBytes);
if (cookieBytes.Length > 0) sha1.AppendData(cookieBytes);
sha1.AppendData(pathBytes);
sha1.AppendData(keyBytes);
byte[] digestBytes = sha1.GetHashAndReset();
string digestString = Convert.ToHexString(digestBytes).ToLower();
return digestString;
}
#region Hash Functions
public static string Sha256Hash(string str) => Sha256Hash(Encoding.UTF8.GetBytes(str));
public static string Sha256Hash(byte[] bytes) => BitConverter.ToString(sha256.ComputeHash(bytes)).Replace("-", "").ToLower();
public static string Sha1Hash(string str) => Sha1Hash(Encoding.UTF8.GetBytes(str));
public static string Sha1Hash(byte[] bytes) => BitConverter.ToString(sha1.ComputeHash(bytes)).Replace("-", "");
public static string BCryptHash(string str) => BCrypt.Net.BCrypt.HashPassword(str);
public static string BCryptHash(byte[] bytes) => BCrypt.Net.BCrypt.HashPassword(Encoding.UTF8.GetString(bytes));
#endregion
} }

View file

@ -6,45 +6,44 @@ using Kettu;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Types.Settings; using LBPUnion.ProjectLighthouse.Types.Settings;
namespace LBPUnion.ProjectLighthouse.Helpers namespace LBPUnion.ProjectLighthouse.Helpers;
public static class InfluxHelper
{ {
public static class InfluxHelper public static readonly InfluxDBClient Client = InfluxDBClientFactory.Create(ServerSettings.Instance.InfluxUrl, ServerSettings.Instance.InfluxToken);
public static async void Log()
{ {
public static readonly InfluxDBClient Client = InfluxDBClientFactory.Create(ServerSettings.Instance.InfluxUrl, ServerSettings.Instance.InfluxToken); using WriteApi writeApi = Client.GetWriteApi();
PointData point = PointData.Measurement("lighthouse")
.Field("playerCount", await StatisticsHelper.RecentMatches())
.Field("slotCount", await StatisticsHelper.SlotCount());
public static async void Log() writeApi.WritePoint(ServerSettings.Instance.InfluxBucket, ServerSettings.Instance.InfluxOrg, point);
{
using WriteApi writeApi = Client.GetWriteApi();
PointData point = PointData.Measurement("lighthouse")
.Field("playerCount", await StatisticsHelper.RecentMatches())
.Field("slotCount", await StatisticsHelper.SlotCount());
writeApi.WritePoint(ServerSettings.Instance.InfluxBucket, ServerSettings.Instance.InfluxOrg, point); writeApi.Flush();
}
writeApi.Flush(); public static async Task StartLogging()
} {
await Client.ReadyAsync();
public static async Task StartLogging() Logger.Log("InfluxDB is now ready.", LoggerLevelInflux.Instance);
{ Thread t = new
await Client.ReadyAsync(); (
Logger.Log("InfluxDB is now ready.", LoggerLevelInflux.Instance); delegate()
Thread t = new {
( while (true)
delegate()
{ {
while (true) #pragma warning disable CS4014
{ Log();
#pragma warning disable CS4014 #pragma warning restore CS4014
Log();
#pragma warning restore CS4014
// Logger.Log("Logged.", LoggerLevelInflux.Instance); // Logger.Log("Logged.", LoggerLevelInflux.Instance);
Thread.Sleep(60000); Thread.Sleep(60000);
}
} }
); }
t.IsBackground = true; );
t.Name = "InfluxDB Logger"; t.IsBackground = true;
t.Start(); t.Name = "InfluxDB Logger";
} t.Start();
} }
} }

View file

@ -5,31 +5,30 @@ using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Profiles; using LBPUnion.ProjectLighthouse.Types.Profiles;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Helpers namespace LBPUnion.ProjectLighthouse.Helpers;
public static class LastContactHelper
{ {
public static class LastContactHelper private static readonly Database database = new();
public static async Task SetLastContact(User user, GameVersion gameVersion)
{ {
private static readonly Database database = new(); LastContact? lastContact = await database.LastContacts.Where(l => l.UserId == user.UserId).FirstOrDefaultAsync();
public static async Task SetLastContact(User user, GameVersion gameVersion) // below makes it not look like trash
// ReSharper disable once ConvertIfStatementToNullCoalescingExpression
if (lastContact == null)
{ {
LastContact? lastContact = await database.LastContacts.Where(l => l.UserId == user.UserId).FirstOrDefaultAsync(); lastContact = new LastContact
// below makes it not look like trash
// ReSharper disable once ConvertIfStatementToNullCoalescingExpression
if (lastContact == null)
{ {
lastContact = new LastContact UserId = user.UserId,
{ };
UserId = user.UserId, database.LastContacts.Add(lastContact);
};
database.LastContacts.Add(lastContact);
}
lastContact.Timestamp = TimestampHelper.Timestamp;
lastContact.GameVersion = gameVersion;
await database.SaveChangesAsync();
} }
lastContact.Timestamp = TimestampHelper.Timestamp;
lastContact.GameVersion = gameVersion;
await database.SaveChangesAsync();
} }
} }

View file

@ -6,65 +6,61 @@ using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Maintenance; using LBPUnion.ProjectLighthouse.Maintenance;
namespace LBPUnion.ProjectLighthouse.Helpers namespace LBPUnion.ProjectLighthouse.Helpers;
public static class MaintenanceHelper
{ {
public static class MaintenanceHelper
static MaintenanceHelper()
{ {
Commands = getListOfInterfaceObjects<ICommand>();
MaintenanceJobs = getListOfInterfaceObjects<IMaintenanceJob>();
}
public static List<ICommand> Commands { get; }
static MaintenanceHelper() public static List<IMaintenanceJob> MaintenanceJobs { get; }
private static List<T> getListOfInterfaceObjects<T>() where T : class
{
return Assembly.GetExecutingAssembly()
.GetTypes()
.Where(t => t.GetInterfaces().Contains(typeof(T)) && t.GetConstructor(Type.EmptyTypes) != null)
.Select(t => Activator.CreateInstance(t) as T)
.ToList()!;
}
public static async Task RunCommand(string[] args)
{
if (args.Length < 1)
throw new Exception
("This should never happen. " + "If it did, its because you tried to run a command before validating that the user actually wants to run one.");
string baseCmd = args[0];
args = args.Skip(1).ToArray();
IEnumerable<ICommand> suitableCommands = Commands.Where
(command => command.Aliases().Any(a => a.ToLower() == baseCmd.ToLower()))
.Where(command => args.Length >= command.RequiredArgs());
foreach (ICommand command in suitableCommands)
{ {
Commands = getListOfInterfaceObjects<ICommand>(); Console.WriteLine("Running command " + command.Name());
MaintenanceJobs = getListOfInterfaceObjects<IMaintenanceJob>(); await command.Run(args);
} return;
public static List<ICommand> Commands { get; }
public static List<IMaintenanceJob> MaintenanceJobs { get; }
private static List<T> getListOfInterfaceObjects<T>() where T : class
{
return Assembly.GetExecutingAssembly()
.GetTypes()
.Where(t => t.GetInterfaces().Contains(typeof(T)) && t.GetConstructor(Type.EmptyTypes) != null)
.Select(t => Activator.CreateInstance(t) as T)
.ToList()!;
} }
public static async Task RunCommand(string[] args) Console.WriteLine("Command not found.");
{ }
if (args.Length < 1)
throw new Exception
(
"This should never happen. " +
"If it did, its because you tried to run a command before validating that the user actually wants to run one."
);
string baseCmd = args[0]; public static async Task RunMaintenanceJob(string jobName)
args = args.Skip(1).ToArray(); {
IMaintenanceJob? job = MaintenanceJobs.FirstOrDefault(j => j.GetType().Name == jobName);
if (job == null) throw new ArgumentNullException();
IEnumerable<ICommand> suitableCommands = Commands.Where await RunMaintenanceJob(job);
(command => command.Aliases().Any(a => a.ToLower() == baseCmd.ToLower())) }
.Where(command => args.Length >= command.RequiredArgs());
foreach (ICommand command in suitableCommands)
{
Console.WriteLine("Running command " + command.Name());
await command.Run(args);
return;
}
Console.WriteLine("Command not found."); public static async Task RunMaintenanceJob(IMaintenanceJob job)
} {
await job.Run();
public static async Task RunMaintenanceJob(string jobName)
{
IMaintenanceJob? job = MaintenanceJobs.FirstOrDefault(j => j.GetType().Name == jobName);
if (job == null) throw new ArgumentNullException();
await RunMaintenanceJob(job);
}
public static async Task RunMaintenanceJob(IMaintenanceJob job)
{
await job.Run();
}
} }
} }

View file

@ -7,70 +7,69 @@ using System.Text.Json;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using LBPUnion.ProjectLighthouse.Types.Match; using LBPUnion.ProjectLighthouse.Types.Match;
namespace LBPUnion.ProjectLighthouse.Helpers namespace LBPUnion.ProjectLighthouse.Helpers;
public static class MatchHelper
{ {
public static class MatchHelper public static readonly Dictionary<int, string?> UserLocations = new();
public static readonly Dictionary<int, List<int>?> UserRecentlyDivedIn = new();
public static void SetUserLocation(int userId, string location)
{ {
public static readonly Dictionary<int, string?> UserLocations = new(); if (UserLocations.TryGetValue(userId, out string? _)) UserLocations.Remove(userId);
public static readonly Dictionary<int, List<int>?> UserRecentlyDivedIn = new(); UserLocations.Add(userId, location);
}
public static void SetUserLocation(int userId, string location) public static void AddUserRecentlyDivedIn(int userId, int otherUserId)
{
if (!UserRecentlyDivedIn.TryGetValue(userId, out List<int>? recentlyDivedIn)) UserRecentlyDivedIn.Add(userId, recentlyDivedIn = new List<int>());
Debug.Assert(recentlyDivedIn != null, nameof(recentlyDivedIn) + " is null, somehow.");
recentlyDivedIn.Add(otherUserId);
}
public static bool DidUserRecentlyDiveInWith(int userId, int otherUserId)
{
if (!UserRecentlyDivedIn.TryGetValue(userId, out List<int>? recentlyDivedIn) || recentlyDivedIn == null) return false;
return recentlyDivedIn.Contains(otherUserId);
}
// This is the function used to show people how laughably awful LBP's protocol is. Beware.
public static IMatchData? Deserialize(string data)
{
string matchType = "";
int i = 1;
while (true)
{ {
if (UserLocations.TryGetValue(userId, out string? _)) UserLocations.Remove(userId); if (data[i] == ',') break;
UserLocations.Add(userId, location);
matchType += data[i];
i++;
} }
public static void AddUserRecentlyDivedIn(int userId, int otherUserId) string matchData = $"{{{string.Concat(data.Skip(matchType.Length + 3).SkipLast(2))}}}"; // unfuck formatting so we can parse it as json
// JSON does not like the hex value that location comes in (0x7f000001) so, convert it to int
matchData = Regex.Replace(matchData, @"0x[a-fA-F0-9]{8}", m => Convert.ToInt32(m.Value, 16).ToString());
// oh, but it gets better than that! LBP also likes to send hex values with an uneven amount of digits (0xa000064, 7 digits). in any case, we handle it here:
matchData = Regex.Replace(matchData, @"0x[a-fA-F0-9]{7}", m => Convert.ToInt32(m.Value, 16).ToString());
// i'm actually crying about it.
return Deserialize(matchType, matchData);
}
public static IMatchData? Deserialize(string matchType, string matchData)
{
return matchType switch
{ {
if (!UserRecentlyDivedIn.TryGetValue(userId, out List<int>? recentlyDivedIn)) UserRecentlyDivedIn.Add(userId, recentlyDivedIn = new List<int>()); "UpdateMyPlayerData" => JsonSerializer.Deserialize<UpdateMyPlayerData>(matchData),
"UpdatePlayersInRoom" => JsonSerializer.Deserialize<UpdatePlayersInRoom>(matchData),
Debug.Assert(recentlyDivedIn != null, nameof(recentlyDivedIn) + " is null, somehow."); "CreateRoom" => JsonSerializer.Deserialize<CreateRoom>(matchData),
"FindBestRoom" => JsonSerializer.Deserialize<FindBestRoom>(matchData),
recentlyDivedIn.Add(otherUserId); _ => null,
} };
public static bool DidUserRecentlyDiveInWith(int userId, int otherUserId)
{
if (!UserRecentlyDivedIn.TryGetValue(userId, out List<int>? recentlyDivedIn) || recentlyDivedIn == null) return false;
return recentlyDivedIn.Contains(otherUserId);
}
// This is the function used to show people how laughably awful LBP's protocol is. Beware.
public static IMatchData? Deserialize(string data)
{
string matchType = "";
int i = 1;
while (true)
{
if (data[i] == ',') break;
matchType += data[i];
i++;
}
string matchData = $"{{{string.Concat(data.Skip(matchType.Length + 3).SkipLast(2))}}}"; // unfuck formatting so we can parse it as json
// JSON does not like the hex value that location comes in (0x7f000001) so, convert it to int
matchData = Regex.Replace(matchData, @"0x[a-fA-F0-9]{8}", m => Convert.ToInt32(m.Value, 16).ToString());
// oh, but it gets better than that! LBP also likes to send hex values with an uneven amount of digits (0xa000064, 7 digits). in any case, we handle it here:
matchData = Regex.Replace(matchData, @"0x[a-fA-F0-9]{7}", m => Convert.ToInt32(m.Value, 16).ToString());
// i'm actually crying about it.
return Deserialize(matchType, matchData);
}
public static IMatchData? Deserialize(string matchType, string matchData)
{
return matchType switch
{
"UpdateMyPlayerData" => JsonSerializer.Deserialize<UpdateMyPlayerData>(matchData),
"UpdatePlayersInRoom" => JsonSerializer.Deserialize<UpdatePlayersInRoom>(matchData),
"CreateRoom" => JsonSerializer.Deserialize<CreateRoom>(matchData),
"FindBestRoom" => JsonSerializer.Deserialize<FindBestRoom>(matchData),
_ => null,
};
}
} }
} }

View file

@ -8,184 +8,177 @@ using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Match; using LBPUnion.ProjectLighthouse.Types.Match;
namespace LBPUnion.ProjectLighthouse.Helpers namespace LBPUnion.ProjectLighthouse.Helpers;
public class RoomHelper
{ {
public class RoomHelper public static readonly List<Room> Rooms = new();
public static readonly RoomSlot PodSlot = new()
{ {
public static readonly List<Room> Rooms = new(); SlotType = SlotType.Pod,
SlotId = 0,
};
public static readonly RoomSlot PodSlot = new() private static int roomIdIncrement;
internal static int RoomIdIncrement => roomIdIncrement++;
public static FindBestRoomResponse? FindBestRoom(User? user, GameVersion roomVersion, string? location)
{
if (roomVersion == GameVersion.LittleBigPlanet1 || roomVersion == GameVersion.LittleBigPlanetPSP)
{ {
SlotType = SlotType.Pod, Logger.Log($"Returning null for FindBestRoom, game ({roomVersion}) does not support dive in", LoggerLevelMatch.Instance);
SlotId = 0,
};
private static int roomIdIncrement;
internal static int RoomIdIncrement => roomIdIncrement++;
public static FindBestRoomResponse? FindBestRoom(User? user, GameVersion roomVersion, string? location)
{
if (roomVersion == GameVersion.LittleBigPlanet1 || roomVersion == GameVersion.LittleBigPlanetPSP)
{
Logger.Log($"Returning null for FindBestRoom, game ({roomVersion}) does not support dive in", LoggerLevelMatch.Instance);
return null;
}
bool anyRoomsLookingForPlayers;
List<Room> rooms;
lock(Rooms)
{
anyRoomsLookingForPlayers = Rooms.Any(r => r.IsLookingForPlayers);
rooms = anyRoomsLookingForPlayers ? Rooms.Where(r => anyRoomsLookingForPlayers && r.IsLookingForPlayers).ToList() : Rooms;
}
rooms = rooms.Where(r => r.RoomVersion == roomVersion).ToList();
foreach (Room room in rooms)
// Look for rooms looking for players before moving on to rooms that are idle.
{
if (user != null && MatchHelper.DidUserRecentlyDiveInWith(user.UserId, room.Host.UserId)) continue;
Dictionary<int, string> relevantUserLocations = new();
// Determine if all players in a room have UserLocations stored, also store the relevant userlocations while we're at it
bool allPlayersHaveLocations = room.Players.All
(
p =>
{
bool gotValue = MatchHelper.UserLocations.TryGetValue(p.UserId, out string? value);
if (gotValue && value != null) relevantUserLocations.Add(p.UserId, value);
return gotValue;
}
);
// If we don't have all locations then the game won't know how to communicate. Thus, it's not a valid room.
if (!allPlayersHaveLocations) continue;
// If we got here then it should be a valid room.
FindBestRoomResponse response = new();
response.RoomId = room.RoomId;
response.Players = new List<Player>();
response.Locations = new List<string>();
foreach (User player in room.Players)
{
response.Players.Add
(
new Player
{
MatchingRes = 0,
User = player,
}
);
response.Locations.Add(relevantUserLocations.GetValueOrDefault(player.UserId)); // Already validated to exist
}
if (user != null)
{
response.Players.Add
(
new Player
{
MatchingRes = 1,
User = user,
}
);
}
if (location == null)
{
response.Locations.Add(location);
}
response.Slots = new List<List<int>>
{
new()
{
(int)room.Slot.SlotType,
room.Slot.SlotId,
},
};
Logger.Log($"Found a room (id: {room.RoomId}) for user {user?.Username ?? "null"} (id: {user?.UserId ?? -1})", LoggerLevelMatch.Instance);
return response;
}
return null; return null;
} }
public static Room CreateRoom(User user, GameVersion roomVersion, RoomSlot? slot = null) bool anyRoomsLookingForPlayers;
=> CreateRoom List<Room> rooms;
(
new List<User> lock(Rooms)
{
user,
},
roomVersion,
slot
);
public static Room CreateRoom(List<User> users, GameVersion roomVersion, RoomSlot? slot = null)
{ {
Room room = new() anyRoomsLookingForPlayers = Rooms.Any(r => r.IsLookingForPlayers);
rooms = anyRoomsLookingForPlayers ? Rooms.Where(r => anyRoomsLookingForPlayers && r.IsLookingForPlayers).ToList() : Rooms;
}
rooms = rooms.Where(r => r.RoomVersion == roomVersion).ToList();
foreach (Room room in rooms)
// Look for rooms looking for players before moving on to rooms that are idle.
{
if (user != null && MatchHelper.DidUserRecentlyDiveInWith(user.UserId, room.Host.UserId)) continue;
Dictionary<int, string> relevantUserLocations = new();
// Determine if all players in a room have UserLocations stored, also store the relevant userlocations while we're at it
bool allPlayersHaveLocations = room.Players.All
(
p =>
{
bool gotValue = MatchHelper.UserLocations.TryGetValue(p.UserId, out string? value);
if (gotValue && value != null) relevantUserLocations.Add(p.UserId, value);
return gotValue;
}
);
// If we don't have all locations then the game won't know how to communicate. Thus, it's not a valid room.
if (!allPlayersHaveLocations) continue;
// If we got here then it should be a valid room.
FindBestRoomResponse response = new();
response.RoomId = room.RoomId;
response.Players = new List<Player>();
response.Locations = new List<string>();
foreach (User player in room.Players)
{ {
RoomId = RoomIdIncrement, response.Players.Add
Players = users, (
State = RoomState.Idle, new Player
Slot = slot ?? PodSlot, {
RoomVersion = roomVersion, MatchingRes = 0,
User = player,
}
);
response.Locations.Add(relevantUserLocations.GetValueOrDefault(player.UserId)); // Already validated to exist
}
if (user != null)
response.Players.Add
(
new Player
{
MatchingRes = 1,
User = user,
}
);
if (location == null) response.Locations.Add(location);
response.Slots = new List<List<int>>
{
new()
{
(int)room.Slot.SlotType,
room.Slot.SlotId,
},
}; };
CleanupRooms(room.Host, room); Logger.Log($"Found a room (id: {room.RoomId}) for user {user?.Username ?? "null"} (id: {user?.UserId ?? -1})", LoggerLevelMatch.Instance);
lock(Rooms) Rooms.Add(room);
Logger.Log($"Created room (id: {room.RoomId}) for host {room.Host.Username} (id: {room.Host.UserId})", LoggerLevelMatch.Instance);
return room; return response;
} }
public static Room? FindRoomByUser(User user, GameVersion roomVersion, bool createIfDoesNotExist = false) return null;
{ }
lock(Rooms)
public static Room CreateRoom(User user, GameVersion roomVersion, RoomSlot? slot = null)
=> CreateRoom
(
new List<User>
{ {
foreach (Room room in Rooms.Where(room => room.Players.Any(player => user == player))) return room; user,
} },
roomVersion,
return createIfDoesNotExist ? CreateRoom(user, roomVersion) : null; slot
} );
public static Room CreateRoom(List<User> users, GameVersion roomVersion, RoomSlot? slot = null)
[SuppressMessage("ReSharper", "InvertIf")] {
public static void CleanupRooms(User? host = null, Room? newRoom = null) Room room = new()
{ {
lock(Rooms) RoomId = RoomIdIncrement,
{ Players = users,
// Delete old rooms based on host State = RoomState.Idle,
if (host != null) Slot = slot ?? PodSlot,
try RoomVersion = roomVersion,
{ };
Rooms.RemoveAll(r => r.Host == host);
}
catch
{
// TODO: detect the room that failed and remove it
}
// Remove players in this new room from other rooms CleanupRooms(room.Host, room);
if (newRoom != null) lock(Rooms) Rooms.Add(room);
foreach (Room room in Rooms) Logger.Log($"Created room (id: {room.RoomId}) for host {room.Host.Username} (id: {room.Host.UserId})", LoggerLevelMatch.Instance);
{
if (room == newRoom) continue;
foreach (User newRoomPlayer in newRoom.Players) room.Players.RemoveAll(p => p == newRoomPlayer); return room;
} }
Rooms.RemoveAll(r => r.Players.Count == 0); // Remove empty rooms public static Room? FindRoomByUser(User user, GameVersion roomVersion, bool createIfDoesNotExist = false)
Rooms.RemoveAll(r => r.Players.Count > 4); // Remove obviously bogus rooms {
} lock(Rooms)
foreach (Room room in Rooms.Where(room => room.Players.Any(player => user == player)))
return room;
return createIfDoesNotExist ? CreateRoom(user, roomVersion) : null;
}
[SuppressMessage("ReSharper", "InvertIf")]
public static void CleanupRooms(User? host = null, Room? newRoom = null)
{
lock(Rooms)
{
// Delete old rooms based on host
if (host != null)
try
{
Rooms.RemoveAll(r => r.Host == host);
}
catch
{
// TODO: detect the room that failed and remove it
}
// Remove players in this new room from other rooms
if (newRoom != null)
foreach (Room room in Rooms)
{
if (room == newRoom) continue;
foreach (User newRoomPlayer in newRoom.Players) room.Players.RemoveAll(p => p == newRoomPlayer);
}
Rooms.RemoveAll(r => r.Players.Count == 0); // Remove empty rooms
Rooms.RemoveAll(r => r.Players.Count > 4); // Remove obviously bogus rooms
} }
} }
} }

View file

@ -2,18 +2,17 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Helpers namespace LBPUnion.ProjectLighthouse.Helpers;
public static class StatisticsHelper
{ {
public static class StatisticsHelper private static readonly Database database = new();
{
private static readonly Database database = new();
public static async Task<int> RecentMatches() => await database.LastContacts.Where(l => TimestampHelper.Timestamp - l.Timestamp < 300).CountAsync(); public static async Task<int> RecentMatches() => await database.LastContacts.Where(l => TimestampHelper.Timestamp - l.Timestamp < 300).CountAsync();
public static async Task<int> SlotCount() => await database.Slots.CountAsync(); public static async Task<int> SlotCount() => await database.Slots.CountAsync();
public static async Task<int> MMPicksCount() => await database.Slots.CountAsync(s => s.TeamPick); public static async Task<int> MMPicksCount() => await database.Slots.CountAsync(s => s.TeamPick);
public static async Task<int> PhotoCount() => await database.Photos.CountAsync(); public static async Task<int> PhotoCount() => await database.Photos.CountAsync();
}
} }

View file

@ -1,12 +1,11 @@
using System; using System;
namespace LBPUnion.ProjectLighthouse.Helpers namespace LBPUnion.ProjectLighthouse.Helpers;
public static class TimeHelper
{ {
public static class TimeHelper public static long UnixTimeMilliseconds() => DateTimeOffset.Now.ToUnixTimeMilliseconds();
{ public static long UnixTimeSeconds() => DateTimeOffset.Now.ToUnixTimeSeconds();
public static long UnixTimeMilliseconds() => DateTimeOffset.Now.ToUnixTimeMilliseconds();
public static long UnixTimeSeconds() => DateTimeOffset.Now.ToUnixTimeSeconds();
}
} }
// 1397109686193 // 1397109686193

View file

@ -1,11 +1,10 @@
using System; using System;
namespace LBPUnion.ProjectLighthouse.Helpers namespace LBPUnion.ProjectLighthouse.Helpers;
{
public static class TimestampHelper
{
public static long Timestamp => (long)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
public static long TimestampMillis => (long)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds; public static class TimestampHelper
} {
public static long Timestamp => (long)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
public static long TimestampMillis => (long)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds;
} }

View file

@ -5,79 +5,78 @@ using Kettu;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Types.Settings; using LBPUnion.ProjectLighthouse.Types.Settings;
namespace LBPUnion.ProjectLighthouse.Helpers namespace LBPUnion.ProjectLighthouse.Helpers;
public static class VersionHelper
{ {
public static class VersionHelper static VersionHelper()
{ {
static VersionHelper() try
{ {
try CommitHash = readManifestFile("gitVersion.txt");
{ Branch = readManifestFile("gitBranch.txt");
CommitHash = readManifestFile("gitVersion.txt");
Branch = readManifestFile("gitBranch.txt");
string remotesFile = readManifestFile("gitRemotes.txt"); string remotesFile = readManifestFile("gitRemotes.txt");
string[] lines = remotesFile.Split('\n'); string[] lines = remotesFile.Split('\n');
// line[0] line[1] line[2] // line[0] line[1] line[2]
// origin git@github.com:LBPUnion/project-lighthouse.git (fetch) // origin git@github.com:LBPUnion/project-lighthouse.git (fetch)
// linq is a serious and painful catastrophe but its useful so i'm gonna keep using it // linq is a serious and painful catastrophe but its useful so i'm gonna keep using it
Remotes = lines.Select(line => line.Split("\t")[1]).ToArray(); Remotes = lines.Select(line => line.Split("\t")[1]).ToArray();
CommitsOutOfDate = readManifestFile("gitUnpushed.txt").Split('\n').Length; CommitsOutOfDate = readManifestFile("gitUnpushed.txt").Split('\n').Length;
CanCheckForUpdates = true; CanCheckForUpdates = true;
} }
catch catch
{ {
Logger.Log Logger.Log
( (
"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.",
LoggerLevelStartup.Instance LoggerLevelStartup.Instance
); );
CommitHash = "invalid"; CommitHash = "invalid";
Branch = "invalid"; Branch = "invalid";
CanCheckForUpdates = false; CanCheckForUpdates = false;
}
if (IsDirty)
{
Logger.Log
(
"This is a modified version of Project Lighthouse. " +
"Please make sure you are properly disclosing the source code to any users who may be using this instance.",
LoggerLevelStartup.Instance
);
CanCheckForUpdates = false;
}
} }
private static string readManifestFile(string fileName) if (IsDirty)
{ {
using Stream stream = typeof(Program).Assembly.GetManifestResourceStream($"{typeof(Program).Namespace}.{fileName}"); Logger.Log
using StreamReader reader = new(stream ?? throw new Exception("The assembly or manifest resource is null.")); (
"This is a modified version of Project Lighthouse. " +
return reader.ReadToEnd().Trim(); "Please make sure you are properly disclosing the source code to any users who may be using this instance.",
LoggerLevelStartup.Instance
);
CanCheckForUpdates = false;
} }
public static string CommitHash { get; set; }
public static string Branch { get; set; }
public static string FullVersion => $"{ServerStatics.ServerName} {Branch}@{CommitHash} {Build}";
public static bool IsDirty => CommitHash.EndsWith("-dirty") || CommitsOutOfDate != 1 || CommitHash == "invalid" || Branch == "invalid";
public static int CommitsOutOfDate { get; set; }
public static bool CanCheckForUpdates { get; set; }
public static string[] Remotes { get; set; }
public const string Build =
#if DEBUG
"Debug";
#elif RELEASE
"Release";
#else
"Unknown";
#endif
} }
private static string readManifestFile(string fileName)
{
using Stream stream = typeof(Program).Assembly.GetManifestResourceStream($"{typeof(Program).Namespace}.{fileName}");
using StreamReader reader = new(stream ?? throw new Exception("The assembly or manifest resource is null."));
return reader.ReadToEnd().Trim();
}
public static string CommitHash { get; set; }
public static string Branch { get; set; }
public static string FullVersion => $"{ServerStatics.ServerName} {Branch}@{CommitHash} {Build}";
public static bool IsDirty => CommitHash.EndsWith("-dirty") || CommitsOutOfDate != 1 || CommitHash == "invalid" || Branch == "invalid";
public static int CommitsOutOfDate { get; set; }
public static bool CanCheckForUpdates { get; set; }
public static string[] Remotes { get; set; }
public const string Build =
#if DEBUG
"Debug";
#elif RELEASE
"Release";
#else
"Unknown";
#endif
} }

View file

@ -3,22 +3,21 @@ using Kettu;
using LBPUnion.ProjectLighthouse.Helpers.Extensions; using LBPUnion.ProjectLighthouse.Helpers.Extensions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace LBPUnion.ProjectLighthouse.Logging namespace LBPUnion.ProjectLighthouse.Logging;
public class AspNetToKettuLogger : ILogger
{ {
public class AspNetToKettuLogger : ILogger public IDisposable BeginScope<TState>(TState state) => NullScope.Instance;
public bool IsEnabled(LogLevel logLevel) => true;
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{ {
public IDisposable BeginScope<TState>(TState state) => NullScope.Instance; LoggerLevel loggerLevel = new LoggerLevelAspNet(logLevel);
public bool IsEnabled(LogLevel logLevel) => true;
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) Logger.Log(state.ToString(), loggerLevel);
{ if (exception == null) return;
LoggerLevel loggerLevel = new LoggerLevelAspNet(logLevel);
Logger.Log(state.ToString(), loggerLevel); string[] lines = exception.ToDetailedException().Replace("\r", "").Split("\n");
if (exception == null) return; foreach (string line in lines) Logger.Log(line, loggerLevel);
string[] lines = exception.ToDetailedException().Replace("\r", "").Split("\n");
foreach (string line in lines) Logger.Log(line, loggerLevel);
}
} }
} }

View file

@ -1,16 +1,15 @@
using System; using System;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace LBPUnion.ProjectLighthouse.Logging namespace LBPUnion.ProjectLighthouse.Logging;
{
[ProviderAlias("Kettu")]
public class AspNetToKettuLoggerProvider : ILoggerProvider, IDisposable
{
public void Dispose()
{
GC.SuppressFinalize(this);
}
public ILogger CreateLogger(string categoryName) => new AspNetToKettuLogger(); [ProviderAlias("Kettu")]
public class AspNetToKettuLoggerProvider : ILoggerProvider, IDisposable
{
public void Dispose()
{
GC.SuppressFinalize(this);
} }
public ILogger CreateLogger(string categoryName) => new AspNetToKettuLogger();
} }

View file

@ -4,24 +4,23 @@ using Kettu;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Types.Settings; using LBPUnion.ProjectLighthouse.Types.Settings;
namespace LBPUnion.ProjectLighthouse.Logging namespace LBPUnion.ProjectLighthouse.Logging;
public class InfluxLogger : LoggerBase
{ {
public class InfluxLogger : LoggerBase public override bool AllowMultiple => false;
public override void Send(LoggerLine line)
{ {
public override bool AllowMultiple => false; string channel = string.IsNullOrEmpty(line.LoggerLevel.Channel) ? "" : $"[{line.LoggerLevel.Channel}] ";
public override void Send(LoggerLine line) string level = $"{$"{line.LoggerLevel.Name} {channel}".TrimEnd()}";
{ string content = line.LineData;
string channel = string.IsNullOrEmpty(line.LoggerLevel.Channel) ? "" : $"[{line.LoggerLevel.Channel}] ";
string level = $"{$"{line.LoggerLevel.Name} {channel}".TrimEnd()}"; using WriteApi writeApi = InfluxHelper.Client.GetWriteApi();
string content = line.LineData;
using WriteApi writeApi = InfluxHelper.Client.GetWriteApi(); PointData point = PointData.Measurement("lighthouseLog").Field("level", level).Field("content", content);
PointData point = PointData.Measurement("lighthouseLog").Field("level", level).Field("content", content); writeApi.WritePoint(ServerSettings.Instance.InfluxBucket, ServerSettings.Instance.InfluxOrg, point);
writeApi.WritePoint(ServerSettings.Instance.InfluxBucket, ServerSettings.Instance.InfluxOrg, point);
}
} }
} }

View file

@ -4,29 +4,28 @@ using Kettu;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Helpers.Extensions; using LBPUnion.ProjectLighthouse.Helpers.Extensions;
namespace LBPUnion.ProjectLighthouse.Logging namespace LBPUnion.ProjectLighthouse.Logging;
public class LighthouseFileLogger : LoggerBase
{ {
public class LighthouseFileLogger : LoggerBase private static readonly string logsDirectory = Path.Combine(Environment.CurrentDirectory, "logs");
public override bool AllowMultiple => false;
public override void Send(LoggerLine line)
{ {
private static readonly string logsDirectory = Path.Combine(Environment.CurrentDirectory, "logs"); FileHelper.EnsureDirectoryCreated(logsDirectory);
public override bool AllowMultiple => false;
public override void Send(LoggerLine line) string channel = string.IsNullOrEmpty(line.LoggerLevel.Channel) ? "" : $"[{line.LoggerLevel.Channel}] ";
string contentFile = $"{channel}{line.LineData}\n";
string contentAll = $"[{$"{line.LoggerLevel.Name} {channel}".TrimEnd()}] {line.LineData}\n";
try
{ {
FileHelper.EnsureDirectoryCreated(logsDirectory); File.AppendAllText(Path.Combine(logsDirectory, line.LoggerLevel.Name.ToFileName() + ".log"), contentFile);
File.AppendAllText(Path.Combine(logsDirectory, "all.log"), contentAll);
string channel = string.IsNullOrEmpty(line.LoggerLevel.Channel) ? "" : $"[{line.LoggerLevel.Channel}] ";
string contentFile = $"{channel}{line.LineData}\n";
string contentAll = $"[{$"{line.LoggerLevel.Name} {channel}".TrimEnd()}] {line.LineData}\n";
try
{
File.AppendAllText(Path.Combine(logsDirectory, line.LoggerLevel.Name.ToFileName() + ".log"), contentFile);
File.AppendAllText(Path.Combine(logsDirectory, "all.log"), contentAll);
}
catch(IOException) {} // windows, ya goofed
} }
catch(IOException) {} // windows, ya goofed
} }
} }

View file

@ -1,81 +1,80 @@
using Kettu; using Kettu;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace LBPUnion.ProjectLighthouse.Logging namespace LBPUnion.ProjectLighthouse.Logging;
public class LoggerLevelStartup : LoggerLevel
{ {
public class LoggerLevelStartup : LoggerLevel public static readonly LoggerLevelStartup Instance = new();
{ public override string Name => "Startup";
public static readonly LoggerLevelStartup Instance = new(); }
public override string Name => "Startup";
} public class LoggerLevelDatabase : LoggerLevel
{
public class LoggerLevelDatabase : LoggerLevel public static readonly LoggerLevelDatabase Instance = new();
{ public override string Name => "Database";
public static readonly LoggerLevelDatabase Instance = new(); }
public override string Name => "Database";
} public class LoggerLevelHttp : LoggerLevel
{
public class LoggerLevelHttp : LoggerLevel public static readonly LoggerLevelHttp Instance = new();
{ public override string Name => "HTTP";
public static readonly LoggerLevelHttp Instance = new(); }
public override string Name => "HTTP";
} public class LoggerLevelFilter : LoggerLevel
{
public class LoggerLevelFilter : LoggerLevel public static readonly LoggerLevelFilter Instance = new();
{ public override string Name => "Filter";
public static readonly LoggerLevelFilter Instance = new(); }
public override string Name => "Filter";
} public class LoggerLevelLogin : LoggerLevel
{
public class LoggerLevelLogin : LoggerLevel public static readonly LoggerLevelLogin Instance = new();
{ public override string Name => "Login";
public static readonly LoggerLevelLogin Instance = new(); }
public override string Name => "Login";
} public class LoggerLevelResources : LoggerLevel
{
public class LoggerLevelResources : LoggerLevel public static readonly LoggerLevelResources Instance = new();
{ public override string Name => "Resources";
public static readonly LoggerLevelResources Instance = new(); }
public override string Name => "Resources";
} public class LoggerLevelMatch : LoggerLevel
{
public class LoggerLevelMatch : LoggerLevel public static readonly LoggerLevelMatch Instance = new();
{ public override string Name => "Match";
public static readonly LoggerLevelMatch Instance = new(); }
public override string Name => "Match";
} public class LoggerLevelPhotos : LoggerLevel
{
public class LoggerLevelPhotos : LoggerLevel public static readonly LoggerLevelPhotos Instance = new();
{ public override string Name => "Photos";
public static readonly LoggerLevelPhotos Instance = new(); }
public override string Name => "Photos";
} public class LoggerLevelConfig : LoggerLevel
{
public class LoggerLevelConfig : LoggerLevel public static readonly LoggerLevelConfig Instance = new();
{ public override string Name => "Config";
public static readonly LoggerLevelConfig Instance = new(); }
public override string Name => "Config";
} public class LoggerLevelInflux : LoggerLevel
{
public class LoggerLevelInflux : LoggerLevel public static readonly LoggerLevelInflux Instance = new();
{ public override string Name => "Influx";
public static readonly LoggerLevelInflux Instance = new(); }
public override string Name => "Influx";
} public class LoggerLevelAspNet : LoggerLevel
{
public class LoggerLevelAspNet : LoggerLevel
{ public LoggerLevelAspNet(LogLevel level)
{
public LoggerLevelAspNet(LogLevel level) this.Channel = level.ToString();
{ }
this.Channel = level.ToString(); public override string Name => "AspNet";
} }
public override string Name => "AspNet";
} public class LoggerLevelCategory : LoggerLevel
{
public class LoggerLevelCategory : LoggerLevel public static readonly LoggerLevelCategory Instance = new();
{ public override string Name => "Category";
public static readonly LoggerLevelCategory Instance = new();
public override string Name => "Category";
}
} }

View file

@ -1,17 +1,16 @@
using System; using System;
namespace LBPUnion.ProjectLighthouse.Logging namespace LBPUnion.ProjectLighthouse.Logging;
public class NullScope : IDisposable
{ {
public class NullScope : IDisposable
private NullScope()
{}
public static NullScope Instance { get; } = new();
public void Dispose()
{ {
GC.SuppressFinalize(this);
private NullScope()
{}
public static NullScope Instance { get; } = new();
public void Dispose()
{
GC.SuppressFinalize(this);
}
} }
} }

View file

@ -7,48 +7,47 @@ using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Maintenance.Commands namespace LBPUnion.ProjectLighthouse.Maintenance.Commands;
[UsedImplicitly]
public class CreateUserCommand : ICommand
{ {
[UsedImplicitly] private readonly Database _database = new();
public class CreateUserCommand : ICommand
public async Task Run(string[] args)
{ {
private readonly Database _database = new(); string onlineId = args[0];
string password = args[1];
public async Task Run(string[] args) password = HashHelper.Sha256Hash(password);
User? user = await this._database.Users.FirstOrDefaultAsync(u => u.Username == onlineId);
if (user == null)
{ {
string onlineId = args[0]; user = await this._database.CreateUser(onlineId, HashHelper.BCryptHash(password));
string password = args[1]; Logger.Log($"Created user {user.UserId} with online ID (username) {user.Username} and the specified password.", LoggerLevelLogin.Instance);
password = HashHelper.Sha256Hash(password); user.PasswordResetRequired = true;
Logger.Log("This user will need to reset their password when they log in.", LoggerLevelLogin.Instance);
User? user = await this._database.Users.FirstOrDefaultAsync(u => u.Username == onlineId); await this._database.SaveChangesAsync();
if (user == null) Logger.Log("Database changes saved.", LoggerLevelDatabase.Instance);
{ }
user = await this._database.CreateUser(onlineId, HashHelper.BCryptHash(password)); else
Logger.Log($"Created user {user.UserId} with online ID (username) {user.Username} and the specified password.", LoggerLevelLogin.Instance); {
Logger.Log("A user with this username already exists.", LoggerLevelLogin.Instance);
user.PasswordResetRequired = true;
Logger.Log("This user will need to reset their password when they log in.", LoggerLevelLogin.Instance);
await this._database.SaveChangesAsync();
Logger.Log("Database changes saved.", LoggerLevelDatabase.Instance);
}
else
{
Logger.Log("A user with this username already exists.", LoggerLevelLogin.Instance);
}
} }
public string Name() => "Create New User";
public string[] Aliases()
=> new[]
{
"useradd", "adduser", "newuser", "createUser",
};
public string Arguments() => "<OnlineID> <Password>";
public int RequiredArgs() => 2;
} }
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

@ -5,36 +5,35 @@ using JetBrains.Annotations;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Maintenance.Commands namespace LBPUnion.ProjectLighthouse.Maintenance.Commands;
{
[UsedImplicitly]
public class DeleteUserCommand : ICommand
{
private readonly Database database = new();
public string Name() => "Delete User";
public string[] Aliases()
=> new[]
{
"deleteUser", "wipeUser",
};
public string Arguments() => "<username/userId>";
public int RequiredArgs() => 1;
public async Task Run(string[] args)
{
User? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == args[0]);
if (user == null)
try
{
user = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == Convert.ToInt32(args[0]));
if (user == null) throw new Exception();
}
catch
{
Console.WriteLine($"Could not find user by parameter '{args[0]}'");
return;
}
await this.database.RemoveUser(user); [UsedImplicitly]
} public class DeleteUserCommand : ICommand
{
private readonly Database database = new();
public string Name() => "Delete User";
public string[] Aliases()
=> new[]
{
"deleteUser", "wipeUser",
};
public string Arguments() => "<username/userId>";
public int RequiredArgs() => 1;
public async Task Run(string[] args)
{
User? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == args[0]);
if (user == null)
try
{
user = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == Convert.ToInt32(args[0]));
if (user == null) throw new Exception();
}
catch
{
Console.WriteLine($"Could not find user by parameter '{args[0]}'");
return;
}
await this.database.RemoveUser(user);
} }
} }

View file

@ -5,41 +5,40 @@ using JetBrains.Annotations;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Maintenance.Commands namespace LBPUnion.ProjectLighthouse.Maintenance.Commands;
[UsedImplicitly]
public class MakeUserAdminCommand : ICommand
{ {
[UsedImplicitly] private readonly Database database = new();
public class MakeUserAdminCommand : ICommand
{
private readonly Database database = new();
public string Name() => "Make User Admin"; public string Name() => "Make User Admin";
public string[] Aliases() public string[] Aliases()
=> new[] => new[]
{
"makeAdmin",
};
public string Arguments() => "<username/userId>";
public int RequiredArgs() => 1;
public async Task Run(string[] args)
{ {
User? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == args[0]); "makeAdmin",
if (user == null) };
try public string Arguments() => "<username/userId>";
{ public int RequiredArgs() => 1;
user = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == Convert.ToInt32(args[0]));
if (user == null) throw new Exception();
}
catch
{
Console.WriteLine($"Could not find user by parameter '{args[0]}'");
return;
}
user.IsAdmin = true; public async Task Run(string[] args)
await this.database.SaveChangesAsync(); {
User? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == args[0]);
if (user == null)
try
{
user = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == Convert.ToInt32(args[0]));
if (user == null) throw new Exception();
}
catch
{
Console.WriteLine($"Could not find user by parameter '{args[0]}'");
return;
}
Console.WriteLine($"The user {user.Username} (id: {user.UserId}) is now an admin."); user.IsAdmin = true;
} await this.database.SaveChangesAsync();
Console.WriteLine($"The user {user.Username} (id: {user.UserId}) is now an admin.");
} }
} }

View file

@ -6,44 +6,43 @@ using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Maintenance.Commands namespace LBPUnion.ProjectLighthouse.Maintenance.Commands;
[UsedImplicitly]
public class ResetPasswordCommand : ICommand
{ {
[UsedImplicitly] private readonly Database database = new();
public class ResetPasswordCommand : ICommand public string Name() => "Reset Password";
{ public string[] Aliases()
private readonly Database database = new(); => new[]
public string Name() => "Reset Password";
public string[] Aliases()
=> new[]
{
"setPassword", "resetPassword", "passwd", "password",
};
public string Arguments() => "<username/userId> <sha256/plaintext>";
public int RequiredArgs() => 2;
public async Task Run(string[] args)
{ {
User? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == args[0]); "setPassword", "resetPassword", "passwd", "password",
if (user == null) };
try public string Arguments() => "<username/userId> <sha256/plaintext>";
{ public int RequiredArgs() => 2;
user = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == Convert.ToInt32(args[0]));
if (user == null) throw new Exception();
}
catch
{
Console.WriteLine($"Could not find user by parameter '{args[0]}'");
return;
}
string password = args[1];
if (password.Length != 64) password = HashHelper.Sha256Hash(password);
user.Password = HashHelper.BCryptHash(password); public async Task Run(string[] args)
user.PasswordResetRequired = true; {
User? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == args[0]);
if (user == null)
try
{
user = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == Convert.ToInt32(args[0]));
if (user == null) throw new Exception();
}
catch
{
Console.WriteLine($"Could not find user by parameter '{args[0]}'");
return;
}
string password = args[1];
if (password.Length != 64) password = HashHelper.Sha256Hash(password);
await this.database.SaveChangesAsync(); user.Password = HashHelper.BCryptHash(password);
user.PasswordResetRequired = true;
Console.WriteLine($"The password for user {user.Username} (id: {user.UserId}) has been reset."); await this.database.SaveChangesAsync();
}
Console.WriteLine($"The password for user {user.Username} (id: {user.UserId}) has been reset.");
} }
} }

View file

@ -5,41 +5,40 @@ using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Maintenance.Commands namespace LBPUnion.ProjectLighthouse.Maintenance.Commands;
public class WipeTokensForUserCommand : ICommand
{ {
public class WipeTokensForUserCommand : ICommand private readonly Database database = new();
{
private readonly Database database = new();
public string Name() => "Wipe tokens for user"; public string Name() => "Wipe tokens for user";
public string[] Aliases() public string[] Aliases()
=> new[] => new[]
{
"wipeTokens", "wipeToken", "deleteTokens", "deleteToken", "removeTokens", "removeToken",
};
public string Arguments() => "<username/userId>";
public int RequiredArgs() => 1;
public async Task Run(string[] args)
{ {
User? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == args[0]); "wipeTokens", "wipeToken", "deleteTokens", "deleteToken", "removeTokens", "removeToken",
if (user == null) };
try public string Arguments() => "<username/userId>";
{ public int RequiredArgs() => 1;
user = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == Convert.ToInt32(args[0])); public async Task Run(string[] args)
if (user == null) throw new Exception(); {
} User? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == args[0]);
catch if (user == null)
{ try
Console.WriteLine($"Could not find user by parameter '{args[0]}'"); {
return; user = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == Convert.ToInt32(args[0]));
} if (user == null) throw new Exception();
}
catch
{
Console.WriteLine($"Could not find user by parameter '{args[0]}'");
return;
}
this.database.GameTokens.RemoveRange(this.database.GameTokens.Where(t => t.UserId == user.UserId)); this.database.GameTokens.RemoveRange(this.database.GameTokens.Where(t => t.UserId == user.UserId));
this.database.WebTokens.RemoveRange(this.database.WebTokens.Where(t => t.UserId == user.UserId)); this.database.WebTokens.RemoveRange(this.database.WebTokens.Where(t => t.UserId == user.UserId));
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
Console.WriteLine($"Deleted all tokens for {user.Username} (id: {user.UserId})."); Console.WriteLine($"Deleted all tokens for {user.Username} (id: {user.UserId}).");
}
} }
} }

View file

@ -1,19 +1,18 @@
using System.Threading.Tasks; using System.Threading.Tasks;
namespace LBPUnion.ProjectLighthouse.Maintenance namespace LBPUnion.ProjectLighthouse.Maintenance;
public interface ICommand
{ {
public interface ICommand
{
public string FirstAlias => this.Aliases()[0]; public string FirstAlias => this.Aliases()[0];
public Task Run(string[] args); public Task Run(string[] args);
public string Name(); public string Name();
public string[] Aliases(); public string[] Aliases();
public string Arguments(); public string Arguments();
public int RequiredArgs(); public int RequiredArgs();
}
} }

View file

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

View file

@ -7,86 +7,85 @@ using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Files; using LBPUnion.ProjectLighthouse.Types.Files;
namespace LBPUnion.ProjectLighthouse.Maintenance.MaintenanceJobs namespace LBPUnion.ProjectLighthouse.Maintenance.MaintenanceJobs;
public class CleanupBrokenPhotosMaintenanceJob : IMaintenanceJob
{ {
public class CleanupBrokenPhotosMaintenanceJob : IMaintenanceJob private readonly Database database = new();
public string Name() => "Cleanup Broken Photos";
public string Description() => "Deletes all photos that have missing assets or invalid photo subjects.";
[SuppressMessage("ReSharper", "LoopCanBePartlyConvertedToQuery")]
public async Task Run()
{ {
private readonly Database database = new(); foreach (Photo photo in this.database.Photos)
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 (Photo photo in this.database.Photos) bool hashNullOrEmpty = false;
bool noHashesExist = false;
bool largeHashIsInvalidFile = false;
bool tooManyPhotoSubjects = false;
bool duplicatePhotoSubjects = false;
hashNullOrEmpty = string.IsNullOrEmpty
(photo.LargeHash) ||
string.IsNullOrEmpty(photo.MediumHash) ||
string.IsNullOrEmpty(photo.SmallHash) ||
string.IsNullOrEmpty(photo.PlanHash);
if (hashNullOrEmpty) goto removePhoto;
List<string> hashes = new()
{ {
bool hashNullOrEmpty = false; photo.LargeHash,
bool noHashesExist = false; photo.MediumHash,
bool largeHashIsInvalidFile = false; photo.SmallHash,
bool tooManyPhotoSubjects = false; photo.PlanHash,
bool duplicatePhotoSubjects = false; };
hashNullOrEmpty = string.IsNullOrEmpty noHashesExist = FileHelper.ResourcesNotUploaded(hashes.ToArray()).Length != 0;
(photo.LargeHash) || if (noHashesExist) goto removePhoto;
string.IsNullOrEmpty(photo.MediumHash) ||
string.IsNullOrEmpty(photo.SmallHash) ||
string.IsNullOrEmpty(photo.PlanHash);
if (hashNullOrEmpty) goto removePhoto;
List<string> hashes = new() LbpFile? file = LbpFile.FromHash(photo.LargeHash);
{
photo.LargeHash,
photo.MediumHash,
photo.SmallHash,
photo.PlanHash,
};
noHashesExist = FileHelper.ResourcesNotUploaded(hashes.ToArray()).Length != 0;
if (noHashesExist) goto removePhoto;
LbpFile? file = LbpFile.FromHash(photo.LargeHash);
// Console.WriteLine(file.FileType, ); // Console.WriteLine(file.FileType, );
if (file == null || file.FileType != LbpFileType.Jpeg && file.FileType != LbpFileType.Png) if (file == null || file.FileType != LbpFileType.Jpeg && file.FileType != LbpFileType.Png)
{ {
largeHashIsInvalidFile = true; largeHashIsInvalidFile = true;
goto removePhoto; goto removePhoto;
}
if (photo.Subjects.Count > 4)
{
tooManyPhotoSubjects = true;
goto removePhoto;
}
List<int> subjectUserIds = new(4);
foreach (PhotoSubject subject in photo.Subjects)
{
if (subjectUserIds.Contains(subject.UserId))
{
duplicatePhotoSubjects = true;
goto removePhoto;
}
subjectUserIds.Add(subject.UserId);
}
continue;
removePhoto:
Console.WriteLine
(
$"Removing photo (id: {photo.PhotoId}): " +
$"{nameof(hashNullOrEmpty)}: {hashNullOrEmpty}, " +
$"{nameof(noHashesExist)}: {noHashesExist}, " +
$"{nameof(largeHashIsInvalidFile)}: {largeHashIsInvalidFile}, " +
$"{nameof(tooManyPhotoSubjects)}: {tooManyPhotoSubjects}" +
$"{nameof(duplicatePhotoSubjects)}: {duplicatePhotoSubjects}"
);
this.database.Photos.Remove(photo);
} }
await this.database.SaveChangesAsync(); if (photo.Subjects.Count > 4)
{
tooManyPhotoSubjects = true;
goto removePhoto;
}
List<int> subjectUserIds = new(4);
foreach (PhotoSubject subject in photo.Subjects)
{
if (subjectUserIds.Contains(subject.UserId))
{
duplicatePhotoSubjects = true;
goto removePhoto;
}
subjectUserIds.Add(subject.UserId);
}
continue;
removePhoto:
Console.WriteLine
(
$"Removing photo (id: {photo.PhotoId}): " +
$"{nameof(hashNullOrEmpty)}: {hashNullOrEmpty}, " +
$"{nameof(noHashesExist)}: {noHashesExist}, " +
$"{nameof(largeHashIsInvalidFile)}: {largeHashIsInvalidFile}, " +
$"{nameof(tooManyPhotoSubjects)}: {tooManyPhotoSubjects}" +
$"{nameof(duplicatePhotoSubjects)}: {duplicatePhotoSubjects}"
);
this.database.Photos.Remove(photo);
} }
await this.database.SaveChangesAsync();
} }
} }

View file

@ -4,30 +4,29 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Types.Profiles; using LBPUnion.ProjectLighthouse.Types.Profiles;
namespace LBPUnion.ProjectLighthouse.Maintenance.MaintenanceJobs namespace LBPUnion.ProjectLighthouse.Maintenance.MaintenanceJobs;
public class CleanupUnusedLocationsMaintenanceJob : IMaintenanceJob
{ {
public class CleanupUnusedLocationsMaintenanceJob : IMaintenanceJob private readonly Database database = new();
public string Name() => "Cleanup Unused Locations";
public string Description() => "Cleanup unused locations in the database.";
public async Task Run()
{ {
private readonly Database database = new(); List<int> usedLocationIds = new();
public string Name() => "Cleanup Unused Locations";
public string Description() => "Cleanup unused locations in the database.";
public async Task Run() usedLocationIds.AddRange(this.database.Slots.Select(slot => slot.LocationId));
usedLocationIds.AddRange(this.database.Users.Select(user => user.LocationId));
IQueryable<Location> locationsToRemove = this.database.Locations.Where(l => !usedLocationIds.Contains(l.Id));
foreach (Location location in locationsToRemove)
{ {
List<int> usedLocationIds = new(); Console.WriteLine("Removing location " + location.Id);
this.database.Locations.Remove(location);
usedLocationIds.AddRange(this.database.Slots.Select(slot => slot.LocationId));
usedLocationIds.AddRange(this.database.Users.Select(user => user.LocationId));
IQueryable<Location> locationsToRemove = this.database.Locations.Where(l => !usedLocationIds.Contains(l.Id));
foreach (Location location in locationsToRemove)
{
Console.WriteLine("Removing location " + location.Id);
this.database.Locations.Remove(location);
}
await this.database.SaveChangesAsync();
} }
await this.database.SaveChangesAsync();
} }
} }

View file

@ -1,22 +1,21 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace LBPUnion.ProjectLighthouse.Maintenance.MaintenanceJobs namespace LBPUnion.ProjectLighthouse.Maintenance.MaintenanceJobs;
public class DeleteAllTokensMaintenanceJob : IMaintenanceJob
{ {
public class DeleteAllTokensMaintenanceJob : IMaintenanceJob private readonly Database database = new();
public string Name() => "Delete ALL Tokens";
public string Description() => "Deletes ALL game tokens and web tokens.";
public async Task Run()
{ {
private readonly Database database = new(); this.database.GameTokens.RemoveRange(this.database.GameTokens);
this.database.WebTokens.RemoveRange(this.database.WebTokens);
public string Name() => "Delete ALL Tokens"; await this.database.SaveChangesAsync();
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.");
Console.WriteLine("Deleted ALL tokens.");
}
} }
} }

View file

@ -2,30 +2,29 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Levels;
namespace LBPUnion.ProjectLighthouse.Maintenance.MaintenanceJobs namespace LBPUnion.ProjectLighthouse.Maintenance.MaintenanceJobs;
public class FixAllBrokenPlayerRequirementsMaintenanceJob : IMaintenanceJob
{ {
public class FixAllBrokenPlayerRequirementsMaintenanceJob : IMaintenanceJob private readonly Database database = new();
public string Name() => "Fix All Broken Player Requirements";
public string Description() => "Some LBP1 levels may report that they are designed for 0 players. This job will fix that.";
public async Task Run()
{ {
private readonly Database database = new(); int count = 0;
await foreach (Slot slot in this.database.Slots)
if (slot.MinimumPlayers == 0 || slot.MaximumPlayers == 0)
{
slot.MinimumPlayers = 1;
slot.MaximumPlayers = 4;
public string Name() => "Fix All Broken Player Requirements"; Console.WriteLine($"Fixed slotId {slot.SlotId}");
public string Description() => "Some LBP1 levels may report that they are designed for 0 players. This job will fix that."; count++;
public async Task Run() }
{
int count = 0;
await foreach (Slot slot in this.database.Slots)
if (slot.MinimumPlayers == 0 || slot.MaximumPlayers == 0)
{
slot.MinimumPlayers = 1;
slot.MaximumPlayers = 4;
Console.WriteLine($"Fixed slotId {slot.SlotId}"); await this.database.SaveChangesAsync();
count++;
}
await this.database.SaveChangesAsync(); Console.WriteLine($"Fixed {count} broken player requirements.");
Console.WriteLine($"Fixed {count} broken player requirements.");
}
} }
} }

View file

@ -10,10 +10,10 @@ namespace LBPUnion.ProjectLighthouse.Pages.Admin;
public class AdminBanUserPage : BaseLayout public class AdminBanUserPage : BaseLayout
{ {
public AdminBanUserPage(Database database) : base(database)
{}
public User? TargetedUser; public User? TargetedUser;
public AdminBanUserPage(Database database) : base(database)
{}
public async Task<IActionResult> OnGet([FromRoute] int id) public async Task<IActionResult> OnGet([FromRoute] int id)
{ {

View file

@ -11,12 +11,12 @@ namespace LBPUnion.ProjectLighthouse.Pages.Admin;
public class AdminPanelUsersPage : BaseLayout public class AdminPanelUsersPage : BaseLayout
{ {
public AdminPanelUsersPage(Database database) : base(database)
{}
public int UserCount; public int UserCount;
public List<User> Users; public List<User> Users;
public AdminPanelUsersPage(Database database) : base(database)
{}
public async Task<IActionResult> OnGet() public async Task<IActionResult> OnGet()
{ {

View file

@ -9,31 +9,30 @@ using LBPUnion.ProjectLighthouse.Types.Settings;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Pages.ExternalAuth namespace LBPUnion.ProjectLighthouse.Pages.ExternalAuth;
public class AuthenticationPage : BaseLayout
{ {
public class AuthenticationPage : BaseLayout
public List<AuthenticationAttempt> AuthenticationAttempts;
public IPAddress? IpAddress;
public AuthenticationPage(Database database) : base(database)
{}
public async Task<IActionResult> OnGet()
{ {
if (!ServerSettings.Instance.UseExternalAuth) return this.NotFound();
if (this.User == null) return this.StatusCode(403, "");
public List<AuthenticationAttempt> AuthenticationAttempts; this.IpAddress = this.HttpContext.Connection.RemoteIpAddress;
public IPAddress? IpAddress; this.AuthenticationAttempts = this.Database.AuthenticationAttempts.Include
public AuthenticationPage(Database database) : base(database) (a => a.GameToken)
{} .Where(a => a.GameToken.UserId == this.User.UserId)
.OrderByDescending(a => a.Timestamp)
.ToList();
public async Task<IActionResult> OnGet() return this.Page();
{
if (!ServerSettings.Instance.UseExternalAuth) return this.NotFound();
if (this.User == null) return this.StatusCode(403, "");
this.IpAddress = this.HttpContext.Connection.RemoteIpAddress;
this.AuthenticationAttempts = this.Database.AuthenticationAttempts.Include
(a => a.GameToken)
.Where(a => a.GameToken.UserId == this.User.UserId)
.OrderByDescending(a => a.Timestamp)
.ToList();
return this.Page();
}
} }
} }

View file

@ -7,23 +7,22 @@ using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Pages.ExternalAuth namespace LBPUnion.ProjectLighthouse.Pages.ExternalAuth;
public class ManageUserApprovedIpAddressesPage : BaseLayout
{ {
public class ManageUserApprovedIpAddressesPage : BaseLayout
public List<UserApprovedIpAddress> ApprovedIpAddresses;
public ManageUserApprovedIpAddressesPage(Database database) : base(database)
{}
public async Task<IActionResult> OnGet()
{ {
public ManageUserApprovedIpAddressesPage(Database database) : base(database) User? user = this.Database.UserFromWebRequest(this.Request);
{} if (user == null) return this.Redirect("/login");
public List<UserApprovedIpAddress> ApprovedIpAddresses; this.ApprovedIpAddresses = await this.Database.UserApprovedIpAddresses.Where(a => a.UserId == user.UserId).ToListAsync();
public async Task<IActionResult> OnGet() return this.Page();
{
User? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("/login");
this.ApprovedIpAddresses = await this.Database.UserApprovedIpAddresses.Where(a => a.UserId == user.UserId).ToListAsync();
return this.Page();
}
} }
} }

View file

@ -9,37 +9,34 @@ using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Pages namespace LBPUnion.ProjectLighthouse.Pages;
public class LandingPage : BaseLayout
{ {
public class LandingPage : BaseLayout
public int AuthenticationAttemptsCount;
public List<User> PlayersOnline;
public int PlayersOnlineCount;
public LandingPage(Database database) : base(database)
{}
[UsedImplicitly]
public async Task<IActionResult> OnGet()
{ {
public List<User> PlayersOnline; User? user = this.Database.UserFromWebRequest(this.Request);
if (user != null && user.PasswordResetRequired) return this.Redirect("~/passwordResetRequired");
public int PlayersOnlineCount; this.PlayersOnlineCount = await StatisticsHelper.RecentMatches();
public int AuthenticationAttemptsCount; if (user != null)
public LandingPage(Database database) : base(database) this.AuthenticationAttemptsCount = await this.Database.AuthenticationAttempts.Include
{} (a => a.GameToken)
.CountAsync(a => a.GameToken.UserId == user.UserId);
[UsedImplicitly] List<int> userIds = await this.Database.LastContacts.Where(l => TimestampHelper.Timestamp - l.Timestamp < 300).Select(l => l.UserId).ToListAsync();
public async Task<IActionResult> OnGet()
{
User? user = this.Database.UserFromWebRequest(this.Request);
if (user != null && user.PasswordResetRequired) return this.Redirect("~/passwordResetRequired");
this.PlayersOnlineCount = await StatisticsHelper.RecentMatches(); this.PlayersOnline = await this.Database.Users.Where(u => userIds.Contains(u.UserId)).ToListAsync();
return this.Page();
if (user != null)
{
this.AuthenticationAttemptsCount = await this.Database.AuthenticationAttempts.Include
(a => a.GameToken)
.CountAsync(a => a.GameToken.UserId == user.UserId);
}
List<int> userIds = await this.Database.LastContacts.Where(l => TimestampHelper.Timestamp - l.Timestamp < 300).Select(l => l.UserId).ToListAsync();
this.PlayersOnline = await this.Database.Users.Where(u => userIds.Contains(u.UserId)).ToListAsync();
return this.Page();
}
} }
} }

View file

@ -3,42 +3,41 @@ using System.Collections.Generic;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
namespace LBPUnion.ProjectLighthouse.Pages.Layouts namespace LBPUnion.ProjectLighthouse.Pages.Layouts;
public class BaseLayout : PageModel
{ {
public class BaseLayout : PageModel
public readonly Database Database;
public readonly List<PageNavigationItem> NavigationItems = new()
{ {
public BaseLayout(Database database) new PageNavigationItem("Home", "/", "home"),
{ new PageNavigationItem("Photos", "/photos/0", "camera"),
this.Database = database; new PageNavigationItem("Levels", "/slots/0", "certificate"),
} };
public bool IsMobile; public readonly List<PageNavigationItem> NavigationItemsRight = new();
public string Description = string.Empty;
public readonly Database Database;
public bool IsMobile;
public readonly List<PageNavigationItem> NavigationItems = new()
{ public bool ShowTitleInPage = true;
new PageNavigationItem("Home", "/", "home"),
new PageNavigationItem("Photos", "/photos/0", "camera"), public string Title = string.Empty;
new PageNavigationItem("Levels", "/slots/0", "certificate"),
}; private User? user;
public BaseLayout(Database database)
public readonly List<PageNavigationItem> NavigationItemsRight = new(); {
this.Database = database;
public bool ShowTitleInPage = true; }
public string Title = string.Empty; public new User? User {
public string Description = string.Empty; get {
if (this.user != null) return this.user;
private User? user;
return this.user = this.Database.UserFromWebRequest(this.Request);
public new User? User {
get {
if (this.user != null) return this.user;
return this.user = this.Database.UserFromWebRequest(this.Request);
}
set => this.user = value;
} }
set => this.user = value;
} }
} }

View file

@ -9,75 +9,74 @@ using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Pages namespace LBPUnion.ProjectLighthouse.Pages;
public class LoginForm : BaseLayout
{ {
public class LoginForm : BaseLayout public LoginForm(Database database) : base(database)
{}
public string Error { get; private set; }
public bool WasLoginRequest { get; private set; }
[UsedImplicitly]
public async Task<IActionResult> OnPost(string username, string password)
{ {
public LoginForm(Database database) : base(database) if (string.IsNullOrWhiteSpace(username))
{}
public string Error { get; private set; }
public bool WasLoginRequest { get; private set; }
[UsedImplicitly]
public async Task<IActionResult> OnPost(string username, string password)
{ {
if (string.IsNullOrWhiteSpace(username)) this.Error = "The username field is required.";
{
this.Error = "The username field is required.";
return this.Page();
}
if (string.IsNullOrWhiteSpace(password))
{
this.Error = "The password field is required.";
return this.Page();
}
User? user = await this.Database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (user == null)
{
Logger.Log($"User {username} failed to login on web due to invalid username", LoggerLevelLogin.Instance);
this.Error = "The username or password you entered is invalid.";
return this.Page();
}
if (!BCrypt.Net.BCrypt.Verify(password, user.Password))
{
Logger.Log($"User {user.Username} (id: {user.UserId}) failed to login on web due to invalid password", LoggerLevelLogin.Instance);
this.Error = "The username or password you entered is invalid.";
return this.Page();
}
if (user.Banned)
{
Logger.Log($"User {user.Username} (id: {user.UserId}) failed to login on web due to being banned", LoggerLevelLogin.Instance);
this.Error = "You have been banned. Please contact an administrator for more information.\nReason: " + user.BannedReason;
return this.Page();
}
WebToken webToken = new()
{
UserId = user.UserId,
UserToken = HashHelper.GenerateAuthToken(),
};
this.Database.WebTokens.Add(webToken);
await this.Database.SaveChangesAsync();
this.Response.Cookies.Append("LighthouseToken", webToken.UserToken);
if (user.PasswordResetRequired) return this.Redirect("~/passwordResetRequired");
return this.RedirectToPage(nameof(LandingPage));
}
[UsedImplicitly]
public async Task<IActionResult> OnGet()
{
this.Error = string.Empty;
return this.Page(); return this.Page();
} }
if (string.IsNullOrWhiteSpace(password))
{
this.Error = "The password field is required.";
return this.Page();
}
User? user = await this.Database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (user == null)
{
Logger.Log($"User {username} failed to login on web due to invalid username", LoggerLevelLogin.Instance);
this.Error = "The username or password you entered is invalid.";
return this.Page();
}
if (!BCrypt.Net.BCrypt.Verify(password, user.Password))
{
Logger.Log($"User {user.Username} (id: {user.UserId}) failed to login on web due to invalid password", LoggerLevelLogin.Instance);
this.Error = "The username or password you entered is invalid.";
return this.Page();
}
if (user.Banned)
{
Logger.Log($"User {user.Username} (id: {user.UserId}) failed to login on web due to being banned", LoggerLevelLogin.Instance);
this.Error = "You have been banned. Please contact an administrator for more information.\nReason: " + user.BannedReason;
return this.Page();
}
WebToken webToken = new()
{
UserId = user.UserId,
UserToken = HashHelper.GenerateAuthToken(),
};
this.Database.WebTokens.Add(webToken);
await this.Database.SaveChangesAsync();
this.Response.Cookies.Append("LighthouseToken", webToken.UserToken);
if (user.PasswordResetRequired) return this.Redirect("~/passwordResetRequired");
return this.RedirectToPage(nameof(LandingPage));
}
[UsedImplicitly]
public async Task<IActionResult> OnGet()
{
this.Error = string.Empty;
return this.Page();
} }
} }

View file

@ -4,23 +4,22 @@ using LBPUnion.ProjectLighthouse.Pages.Layouts;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Pages namespace LBPUnion.ProjectLighthouse.Pages;
public class LogoutPage : BaseLayout
{ {
public class LogoutPage : BaseLayout public LogoutPage(Database database) : base(database)
{}
public async Task<IActionResult> OnGet()
{ {
public LogoutPage(Database database) : base(database) WebToken? token = this.Database.WebTokenFromRequest(this.Request);
{} if (token == null) return this.BadRequest();
public async Task<IActionResult> OnGet()
{
WebToken? token = this.Database.WebTokenFromRequest(this.Request);
if (token == null) return this.BadRequest();
this.Database.WebTokens.Remove(token); this.Database.WebTokens.Remove(token);
await this.Database.SaveChangesAsync(); await this.Database.SaveChangesAsync();
this.Response.Cookies.Delete("LighthouseToken"); this.Response.Cookies.Delete("LighthouseToken");
return this.Page(); return this.Page();
}
} }
} }

View file

@ -6,48 +6,47 @@ using LBPUnion.ProjectLighthouse.Pages.Layouts;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Pages namespace LBPUnion.ProjectLighthouse.Pages;
public class PasswordResetPage : BaseLayout
{ {
public class PasswordResetPage : BaseLayout public PasswordResetPage(Database database) : base(database)
{}
public string Error { get; private set; }
[UsedImplicitly]
public async Task<IActionResult> OnPost(string password, string confirmPassword)
{ {
public PasswordResetPage(Database database) : base(database) User? user = this.Database.UserFromWebRequest(this.Request);
{} if (user == null) return this.Redirect("~/login");
public string Error { get; private set; } if (string.IsNullOrWhiteSpace(password))
[UsedImplicitly]
public async Task<IActionResult> OnPost(string password, string confirmPassword)
{ {
User? user = this.Database.UserFromWebRequest(this.Request); this.Error = "The password field is required.";
if (user == null) return this.Redirect("~/login");
if (string.IsNullOrWhiteSpace(password))
{
this.Error = "The password field is required.";
return this.Page();
}
if (password != confirmPassword)
{
this.Error = "Passwords do not match!";
return this.Page();
}
user.Password = HashHelper.BCryptHash(password);
user.PasswordResetRequired = false;
await this.Database.SaveChangesAsync();
return this.Redirect("~/");
}
[UsedImplicitly]
public IActionResult OnGet()
{
User? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login");
return this.Page(); return this.Page();
} }
if (password != confirmPassword)
{
this.Error = "Passwords do not match!";
return this.Page();
}
user.Password = HashHelper.BCryptHash(password);
user.PasswordResetRequired = false;
await this.Database.SaveChangesAsync();
return this.Redirect("~/");
}
[UsedImplicitly]
public IActionResult OnGet()
{
User? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login");
return this.Page();
} }
} }

View file

@ -5,22 +5,21 @@ using LBPUnion.ProjectLighthouse.Pages.Layouts;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Pages namespace LBPUnion.ProjectLighthouse.Pages;
public class PasswordResetRequiredPage : BaseLayout
{ {
public class PasswordResetRequiredPage : BaseLayout public PasswordResetRequiredPage([NotNull] Database database) : base(database)
{}
public bool WasResetRequest { get; private set; }
public async Task<IActionResult> OnGet()
{ {
public PasswordResetRequiredPage([NotNull] Database database) : base(database) User? user = this.Database.UserFromWebRequest(this.Request);
{} if (user == null) return this.Redirect("~/login");
if (!user.PasswordResetRequired) return this.Redirect("~/passwordReset");
public bool WasResetRequest { get; private set; } return this.Page();
public async Task<IActionResult> OnGet()
{
User? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login");
if (!user.PasswordResetRequired) return this.Redirect("~/passwordReset");
return this.Page();
}
} }
} }

View file

@ -10,41 +10,37 @@ using LBPUnion.ProjectLighthouse.Types.Settings;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Pages namespace LBPUnion.ProjectLighthouse.Pages;
public class PhotosPage : BaseLayout
{ {
public class PhotosPage : BaseLayout
public int PageAmount;
public int PageNumber;
public int PhotoCount;
public List<Photo> Photos;
public PhotosPage([NotNull] Database database) : base(database)
{}
public async Task<IActionResult> OnGet([FromRoute] int pageNumber)
{ {
this.PhotoCount = await StatisticsHelper.PhotoCount();
public int PageNumber; this.PageNumber = pageNumber;
this.PageAmount = (int)Math.Ceiling((double)this.PhotoCount / ServerStatics.PageSize);
public int PhotoCount; if (this.PageNumber < 0 || this.PageNumber >= this.PageAmount) return this.Redirect($"/photos/{Math.Clamp(this.PageNumber, 0, this.PageAmount - 1)}");
public int PageAmount; this.Photos = await this.Database.Photos.Include
(p => p.Creator)
.OrderByDescending(p => p.Timestamp)
.Skip(pageNumber * ServerStatics.PageSize)
.Take(ServerStatics.PageSize)
.ToListAsync();
public List<Photo> Photos; return this.Page();
public PhotosPage([NotNull] Database database) : base(database)
{}
public async Task<IActionResult> OnGet([FromRoute] int pageNumber)
{
this.PhotoCount = await StatisticsHelper.PhotoCount();
this.PageNumber = pageNumber;
this.PageAmount = (int)Math.Ceiling((double)this.PhotoCount / ServerStatics.PageSize);
if (this.PageNumber < 0 || this.PageNumber >= this.PageAmount)
{
return this.Redirect($"/photos/{Math.Clamp(this.PageNumber, 0, this.PageAmount - 1)}");
}
this.Photos = await this.Database.Photos.Include
(p => p.Creator)
.OrderByDescending(p => p.Timestamp)
.Skip(pageNumber * ServerStatics.PageSize)
.Take(ServerStatics.PageSize)
.ToListAsync();
return this.Page();
}
} }
} }

View file

@ -8,71 +8,70 @@ using LBPUnion.ProjectLighthouse.Types.Settings;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Pages namespace LBPUnion.ProjectLighthouse.Pages;
public class RegisterForm : BaseLayout
{ {
public class RegisterForm : BaseLayout public RegisterForm(Database database) : base(database)
{}
public string Error { get; private set; }
public bool WasRegisterRequest { get; private set; }
[UsedImplicitly]
[SuppressMessage("ReSharper", "SpecifyStringComparison")]
public async Task<IActionResult> OnPost(string username, string password, string confirmPassword)
{ {
public RegisterForm(Database database) : base(database) if (!ServerSettings.Instance.RegistrationEnabled) return this.NotFound();
{}
public string Error { get; private set; } if (string.IsNullOrWhiteSpace(username))
public bool WasRegisterRequest { get; private set; }
[UsedImplicitly]
[SuppressMessage("ReSharper", "SpecifyStringComparison")]
public async Task<IActionResult> OnPost(string username, string password, string confirmPassword)
{ {
if (!ServerSettings.Instance.RegistrationEnabled) return this.NotFound(); this.Error = "The username field is blank.";
if (string.IsNullOrWhiteSpace(username))
{
this.Error = "The username field is blank.";
return this.Page();
}
if (string.IsNullOrWhiteSpace(password))
{
this.Error = "Password field is required.";
return this.Page();
}
if (password != confirmPassword)
{
this.Error = "Passwords do not match!";
return this.Page();
}
bool userExists = await this.Database.Users.FirstOrDefaultAsync(u => u.Username.ToLower() == username.ToLower()) != null;
if (userExists)
{
this.Error = "The username you've chosen is already taken.";
return this.Page();
}
User user = await this.Database.CreateUser(username, HashHelper.BCryptHash(password));
WebToken webToken = new()
{
UserId = user.UserId,
UserToken = HashHelper.GenerateAuthToken(),
};
this.Database.WebTokens.Add(webToken);
await this.Database.SaveChangesAsync();
this.Response.Cookies.Append("LighthouseToken", webToken.UserToken);
return this.RedirectToPage(nameof(LandingPage));
}
[UsedImplicitly]
[SuppressMessage("ReSharper", "SpecifyStringComparison")]
public IActionResult OnGet()
{
this.Error = string.Empty;
if (!ServerSettings.Instance.RegistrationEnabled) return this.NotFound();
return this.Page(); return this.Page();
} }
if (string.IsNullOrWhiteSpace(password))
{
this.Error = "Password field is required.";
return this.Page();
}
if (password != confirmPassword)
{
this.Error = "Passwords do not match!";
return this.Page();
}
bool userExists = await this.Database.Users.FirstOrDefaultAsync(u => u.Username.ToLower() == username.ToLower()) != null;
if (userExists)
{
this.Error = "The username you've chosen is already taken.";
return this.Page();
}
User user = await this.Database.CreateUser(username, HashHelper.BCryptHash(password));
WebToken webToken = new()
{
UserId = user.UserId,
UserToken = HashHelper.GenerateAuthToken(),
};
this.Database.WebTokens.Add(webToken);
await this.Database.SaveChangesAsync();
this.Response.Cookies.Append("LighthouseToken", webToken.UserToken);
return this.RedirectToPage(nameof(LandingPage));
}
[UsedImplicitly]
[SuppressMessage("ReSharper", "SpecifyStringComparison")]
public IActionResult OnGet()
{
this.Error = string.Empty;
if (!ServerSettings.Instance.RegistrationEnabled) return this.NotFound();
return this.Page();
} }
} }

View file

@ -6,23 +6,22 @@ using LBPUnion.ProjectLighthouse.Types.Levels;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Pages namespace LBPUnion.ProjectLighthouse.Pages;
public class SlotPage : BaseLayout
{ {
public class SlotPage : BaseLayout
public Slot Slot;
public SlotPage([NotNull] Database database) : base(database)
{}
public async Task<IActionResult> OnGet([FromRoute] int id)
{ {
Slot? slot = await this.Database.Slots.Include(s => s.Creator).FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound();
public Slot Slot; this.Slot = slot;
public SlotPage([NotNull] Database database) : base(database)
{}
public async Task<IActionResult> OnGet([FromRoute] int id) return this.Page();
{
Slot? slot = await this.Database.Slots.Include(s => s.Creator).FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound();
this.Slot = slot;
return this.Page();
}
} }
} }

View file

@ -10,41 +10,37 @@ using LBPUnion.ProjectLighthouse.Types.Settings;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Pages namespace LBPUnion.ProjectLighthouse.Pages;
public class SlotsPage : BaseLayout
{ {
public class SlotsPage : BaseLayout
public int PageAmount;
public int PageNumber;
public int SlotCount;
public List<Slot> Slots;
public SlotsPage([NotNull] Database database) : base(database)
{}
public async Task<IActionResult> OnGet([FromRoute] int pageNumber)
{ {
this.SlotCount = await StatisticsHelper.SlotCount();
public int PageNumber; this.PageNumber = pageNumber;
this.PageAmount = (int)Math.Ceiling((double)this.SlotCount / ServerStatics.PageSize);
public int SlotCount; if (this.PageNumber < 0 || this.PageNumber >= this.PageAmount) return this.Redirect($"/slots/{Math.Clamp(this.PageNumber, 0, this.PageAmount - 1)}");
public int PageAmount; this.Slots = await this.Database.Slots.Include
(p => p.Creator)
.OrderByDescending(p => p.FirstUploaded)
.Skip(pageNumber * ServerStatics.PageSize)
.Take(ServerStatics.PageSize)
.ToListAsync();
public List<Slot> Slots; return this.Page();
public SlotsPage([NotNull] Database database) : base(database)
{}
public async Task<IActionResult> OnGet([FromRoute] int pageNumber)
{
this.SlotCount = await StatisticsHelper.SlotCount();
this.PageNumber = pageNumber;
this.PageAmount = (int)Math.Ceiling((double)this.SlotCount / ServerStatics.PageSize);
if (this.PageNumber < 0 || this.PageNumber >= this.PageAmount)
{
return this.Redirect($"/slots/{Math.Clamp(this.PageNumber, 0, this.PageAmount - 1)}");
}
this.Slots = await this.Database.Slots.Include
(p => p.Creator)
.OrderByDescending(p => p.FirstUploaded)
.Skip(pageNumber * ServerStatics.PageSize)
.Take(ServerStatics.PageSize)
.ToListAsync();
return this.Page();
}
} }
} }

View file

@ -125,9 +125,9 @@
@foreach (Comment comment in Model.Comments!) @foreach (Comment comment in Model.Comments!)
{ {
DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(comment.Timestamp / 1000); DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(comment.Timestamp / 1000);
StringWriter messageWriter = new StringWriter(); StringWriter messageWriter = new();
HttpUtility.HtmlDecode(comment.Message, messageWriter); HttpUtility.HtmlDecode(comment.Message, messageWriter);
String decodedMessage = messageWriter.ToString(); string decodedMessage = messageWriter.ToString();
<div> <div>
<b><a href="/user/@comment.PosterUserId">@comment.Poster.Username</a>: </b> <b><a href="/user/@comment.PosterUserId">@comment.Poster.Username</a>: </b>
<span>@decodedMessage</span> <span>@decodedMessage</span>

View file

@ -8,40 +8,39 @@ using LBPUnion.ProjectLighthouse.Types.Profiles;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Pages namespace LBPUnion.ProjectLighthouse.Pages;
public class UserPage : BaseLayout
{ {
public class UserPage : BaseLayout public List<Comment>? Comments;
public bool IsProfileUserHearted;
public List<Photo>? Photos;
public User? ProfileUser;
public UserPage(Database database) : base(database)
{}
public async Task<IActionResult> OnGet([FromRoute] int userId)
{ {
public List<Comment>? Comments; this.ProfileUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == userId);
if (this.ProfileUser == null) return this.NotFound();
public bool IsProfileUserHearted; this.Photos = await this.Database.Photos.OrderByDescending(p => p.Timestamp).Where(p => p.CreatorId == userId).Take(5).ToListAsync();
this.Comments = await this.Database.Comments.Include
(p => p.Poster)
.Include(p => p.Target)
.OrderByDescending(p => p.Timestamp)
.Where(p => p.TargetUserId == userId)
.Take(50)
.ToListAsync();
public List<Photo>? Photos; if (this.User != null)
this.IsProfileUserHearted = await this.Database.HeartedProfiles.FirstOrDefaultAsync
(u => u.UserId == this.User.UserId && u.HeartedUserId == this.ProfileUser.UserId) !=
null;
public User? ProfileUser; return this.Page();
public UserPage(Database database) : base(database)
{}
public async Task<IActionResult> OnGet([FromRoute] int userId)
{
this.ProfileUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == userId);
if (this.ProfileUser == null) return this.NotFound();
this.Photos = await this.Database.Photos.OrderByDescending(p => p.Timestamp).Where(p => p.CreatorId == userId).Take(5).ToListAsync();
this.Comments = await this.Database.Comments.Include
(p => p.Poster)
.Include(p => p.Target)
.OrderByDescending(p => p.Timestamp)
.Where(p => p.TargetUserId == userId)
.Take(50)
.ToListAsync();
if (this.User != null)
this.IsProfileUserHearted = await this.Database.HeartedProfiles.FirstOrDefaultAsync
(u => u.UserId == this.User.UserId && u.HeartedUserId == this.ProfileUser.UserId) !=
null;
return this.Page();
}
} }
} }

View file

@ -12,55 +12,55 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace LBPUnion.ProjectLighthouse namespace LBPUnion.ProjectLighthouse;
public static class Program
{ {
public static class Program public static void Main(string[] args)
{ {
public static void Main(string[] args) if (args.Length != 0 && args[0] == "--wait-for-debugger")
{ {
if (args.Length != 0 && args[0] == "--wait-for-debugger") Console.WriteLine("Waiting for a debugger to be attached...");
{ while (!Debugger.IsAttached) Thread.Sleep(100);
Console.WriteLine("Waiting for a debugger to be attached..."); Console.WriteLine("Debugger attached.");
while (!Debugger.IsAttached) Thread.Sleep(100); }
Console.WriteLine("Debugger attached.");
}
// Log startup time // Log startup time
Stopwatch stopwatch = new(); Stopwatch stopwatch = new();
stopwatch.Start(); stopwatch.Start();
// Setup logging // Setup logging
Logger.StartLogging(); Logger.StartLogging();
Logger.UpdateRate /= 2; Logger.UpdateRate /= 2;
LoggerLine.LogFormat = "[{0}] {1}"; LoggerLine.LogFormat = "[{0}] {1}";
Logger.AddLogger(new ConsoleLogger()); Logger.AddLogger(new ConsoleLogger());
Logger.AddLogger(new LighthouseFileLogger()); Logger.AddLogger(new LighthouseFileLogger());
Logger.Log("Welcome to Project Lighthouse!", LoggerLevelStartup.Instance); Logger.Log("Welcome to Project Lighthouse!", LoggerLevelStartup.Instance);
Logger.Log($"Running {VersionHelper.FullVersion}", LoggerLevelStartup.Instance); Logger.Log($"Running {VersionHelper.FullVersion}", LoggerLevelStartup.Instance);
// This loads the config, see ServerSettings.cs for more information // This loads the config, see ServerSettings.cs for more information
Logger.Log("Loaded config file version " + ServerSettings.Instance.ConfigVersion, LoggerLevelStartup.Instance); Logger.Log("Loaded config file version " + ServerSettings.Instance.ConfigVersion, LoggerLevelStartup.Instance);
Logger.Log("Determining if the database is available...", LoggerLevelStartup.Instance); Logger.Log("Determining if the database is available...", LoggerLevelStartup.Instance);
bool dbConnected = ServerStatics.DbConnected; bool dbConnected = ServerStatics.DbConnected;
Logger.Log(dbConnected ? "Connected to the database." : "Database unavailable! Exiting.", LoggerLevelStartup.Instance); Logger.Log(dbConnected ? "Connected to the database." : "Database unavailable! Exiting.", LoggerLevelStartup.Instance);
if (!dbConnected) Environment.Exit(1); if (!dbConnected) Environment.Exit(1);
using Database database = new(); using Database database = new();
Logger.Log("Migrating database...", LoggerLevelDatabase.Instance); Logger.Log("Migrating database...", LoggerLevelDatabase.Instance);
MigrateDatabase(database); MigrateDatabase(database);
if (ServerSettings.Instance.InfluxEnabled) if (ServerSettings.Instance.InfluxEnabled)
{ {
Logger.Log("Influx logging is enabled. Starting influx logging...", LoggerLevelStartup.Instance); Logger.Log("Influx logging is enabled. Starting influx logging...", LoggerLevelStartup.Instance);
InfluxHelper.StartLogging().Wait(); InfluxHelper.StartLogging().Wait();
if (ServerSettings.Instance.InfluxLoggingEnabled) Logger.AddLogger(new InfluxLogger()); if (ServerSettings.Instance.InfluxLoggingEnabled) Logger.AddLogger(new InfluxLogger());
} }
#if DEBUG #if DEBUG
Logger.Log Logger.Log
( (
"This is a debug build, so performance may suffer! " + "This is a debug build, so performance may suffer! " +
@ -69,48 +69,47 @@ namespace LBPUnion.ProjectLighthouse
LoggerLevelStartup.Instance LoggerLevelStartup.Instance
); );
Logger.Log("You can do so by running any dotnet command with the flag: \"-c Release\". ", LoggerLevelStartup.Instance); Logger.Log("You can do so by running any dotnet command with the flag: \"-c Release\". ", LoggerLevelStartup.Instance);
#endif #endif
if (args.Length != 0) if (args.Length != 0)
{
MaintenanceHelper.RunCommand(args).Wait();
return;
}
stopwatch.Stop();
Logger.Log($"Ready! Startup took {stopwatch.ElapsedMilliseconds}ms. Passing off control to ASP.NET...", LoggerLevelStartup.Instance);
CreateHostBuilder(args).Build().Run();
}
public static void MigrateDatabase(Database database)
{ {
Stopwatch stopwatch = new(); MaintenanceHelper.RunCommand(args).Wait();
stopwatch.Start(); return;
database.Database.MigrateAsync().Wait();
stopwatch.Stop();
Logger.Log($"Migration took {stopwatch.ElapsedMilliseconds}ms.", LoggerLevelDatabase.Instance);
} }
public static IHostBuilder CreateHostBuilder(string[] args) stopwatch.Stop();
=> Host.CreateDefaultBuilder(args) Logger.Log($"Ready! Startup took {stopwatch.ElapsedMilliseconds}ms. Passing off control to ASP.NET...", LoggerLevelStartup.Instance);
.ConfigureWebHostDefaults
( CreateHostBuilder(args).Build().Run();
webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.UseWebRoot("StaticFiles");
}
)
.ConfigureLogging
(
logging =>
{
logging.ClearProviders();
logging.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, AspNetToKettuLoggerProvider>());
}
);
} }
public static void MigrateDatabase(Database database)
{
Stopwatch stopwatch = new();
stopwatch.Start();
database.Database.MigrateAsync().Wait();
stopwatch.Stop();
Logger.Log($"Migration took {stopwatch.ElapsedMilliseconds}ms.", LoggerLevelDatabase.Instance);
}
public static IHostBuilder CreateHostBuilder(string[] args)
=> Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults
(
webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.UseWebRoot("StaticFiles");
}
)
.ConfigureLogging
(
logging =>
{
logging.ClearProviders();
logging.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, AspNetToKettuLoggerProvider>());
}
);
} }

View file

@ -1,13 +1,12 @@
using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Formatters;
namespace LBPUnion.ProjectLighthouse.Serialization namespace LBPUnion.ProjectLighthouse.Serialization;
public class JsonOutputFormatter : StringOutputFormatter
{ {
public class JsonOutputFormatter : StringOutputFormatter public JsonOutputFormatter()
{ {
public JsonOutputFormatter() this.SupportedMediaTypes.Add("text/json");
{ this.SupportedMediaTypes.Add("application/json");
this.SupportedMediaTypes.Add("text/json");
this.SupportedMediaTypes.Add("application/json");
}
} }
} }

View file

@ -2,35 +2,34 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
namespace LBPUnion.ProjectLighthouse.Serialization namespace LBPUnion.ProjectLighthouse.Serialization;
/// <summary>
/// LBP doesn't like the XML serializer by C# that much, and it cant be controlled that much (cant have two root
/// elements),
/// so I wrote my own crappy one.
/// </summary>
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public static class LbpSerializer
{ {
/// <summary> public static string BlankElement(string key) => $"<{key}></{key}>";
/// LBP doesn't like the XML serializer by C# that much, and it cant be controlled that much (cant have two root
/// elements),
/// so I wrote my own crappy one.
/// </summary>
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public static class LbpSerializer
{
public static string BlankElement(string key) => $"<{key}></{key}>";
public static string StringElement(KeyValuePair<string, object> pair) => $"<{pair.Key}>{pair.Value}</{pair.Key}>"; public static string StringElement(KeyValuePair<string, object> pair) => $"<{pair.Key}>{pair.Value}</{pair.Key}>";
public static string StringElement(string key, bool value) => $"<{key}>{value.ToString().ToLower()}</{key}>"; public static string StringElement(string key, bool value) => $"<{key}>{value.ToString().ToLower()}</{key}>";
public static string StringElement(string key, object value) => $"<{key}>{value}</{key}>"; public static string StringElement(string key, object value) => $"<{key}>{value}</{key}>";
public static string TaggedStringElement public static string TaggedStringElement
(KeyValuePair<string, object> pair, KeyValuePair<string, object> tagPair) (KeyValuePair<string, object> pair, KeyValuePair<string, object> tagPair)
=> $"<{pair.Key} {tagPair.Key}=\"{tagPair.Value}\">{pair.Value}</{pair.Key}>"; => $"<{pair.Key} {tagPair.Key}=\"{tagPair.Value}\">{pair.Value}</{pair.Key}>";
public static string TaggedStringElement(string key, object value, string tagKey, object tagValue) => $"<{key} {tagKey}=\"{tagValue}\">{value}</{key}>"; public static string TaggedStringElement(string key, object value, string tagKey, object tagValue) => $"<{key} {tagKey}=\"{tagValue}\">{value}</{key}>";
public static string TaggedStringElement(string key, object value, Dictionary<string, object> attrKeyValuePairs) public static string TaggedStringElement(string key, object value, Dictionary<string, object> attrKeyValuePairs)
=> $"<{key} " + attrKeyValuePairs.Aggregate(string.Empty, (current, kvp) => current + $"{kvp.Key}=\"{kvp.Value}\" ") + $">{value}</{key}>"; => $"<{key} " + attrKeyValuePairs.Aggregate(string.Empty, (current, kvp) => current + $"{kvp.Key}=\"{kvp.Value}\" ") + $">{value}</{key}>";
public static string Elements public static string Elements
(params KeyValuePair<string, object>[] pairs) (params KeyValuePair<string, object>[] pairs)
=> pairs.Aggregate(string.Empty, (current, pair) => current + StringElement(pair)); => pairs.Aggregate(string.Empty, (current, pair) => current + StringElement(pair));
}
} }

Some files were not shown because too many files have changed in this diff Show more